feat: 图片引用

This commit is contained in:
2026-03-24 16:57:40 +08:00
parent b1bf290c06
commit 0428783f5c
9 changed files with 79 additions and 28 deletions

View File

@@ -155,6 +155,9 @@ export default {
deleteSuccess: 'Successfully deleted', deleteSuccess: 'Successfully deleted',
thinking: 'Thinking...', thinking: 'Thinking...',
thinkComplete: 'Thinking complete.', thinkComplete: 'Thinking complete.',
quote: 'Quote',
delete: 'Delete',
edit: 'Edit',
}, },
// Version Tree // Version Tree

View File

@@ -149,7 +149,10 @@ export default {
Download: '下载', Download: '下载',
deleteSuccess: '删除成功', deleteSuccess: '删除成功',
thinking:'已思考', thinking:'已思考',
thinkComplete: '思考完成。' thinkComplete: '思考完成。',
quote: '引用',
delete: '删除',
edit: '编辑',
}, },
// Version Tree // Version Tree

View File

@@ -15,6 +15,8 @@ type InitialProjectData = {
style: string style: string
useReport:boolean useReport:boolean
needSuggestion:boolean needSuggestion:boolean
quoteList: Array<string>
tempImages: any[]
} }
export const useAgentStore = defineStore('agent', () => { export const useAgentStore = defineStore('agent', () => {
const initialProjectData = ref<InitialProjectData | null>(null) const initialProjectData = ref<InitialProjectData | null>(null)

View File

@@ -64,7 +64,8 @@
region: '', region: '',
style: '' style: ''
}, },
imageUrlList: [] imageUrlList: [],
quotaUrl: []
}) })
const sketchList = ref([]) const sketchList = ref([])
@@ -110,7 +111,8 @@
text: initialData.text, text: initialData.text,
images: initialData.images, images: initialData.images,
useReport: initialData.useReport, useReport: initialData.useReport,
tempImages: initialData.tempImages tempImages: initialData.tempImages,
quoteList: initialData.quoteList
}) })
// 更新 configParams // 更新 configParams
@@ -125,6 +127,7 @@
images: Array<{ url: string; name: string }> images: Array<{ url: string; name: string }>
tempImages: any[] tempImages: any[]
useReport: boolean useReport: boolean
quoteList: Array<string>
}, },
skipUserMessage = false skipUserMessage = false
) => { ) => {
@@ -136,14 +139,14 @@
} }
params.imageUrlList = message.images || [] params.imageUrlList = message.images || []
params.quotaUrl = message.quoteList || []
// 如果不是重新生成模式,则添加用户消息到列表 // 如果不是重新生成模式,则添加用户消息到列表
if (!skipUserMessage) { if (!skipUserMessage) {
messageList.value.push({ messageList.value.push({
id: messageList.value.length + 1, id: messageList.value.length + 1,
text: message.text, text: message.text,
isUser: true, isUser: true,
imageUrls: message.tempImages imageUrls: message.tempImages.concat(message.quoteList)
}) })
} }
@@ -223,6 +226,8 @@
let hasReportStarted = false // 标记 report 是否已经开始 let hasReportStarted = false // 标记 report 是否已经开始
let hasSketchEvent = false let hasSketchEvent = false
let hasReportEvent = false
try { try {
let flag = true let flag = true
while (flag) { while (flag) {
@@ -231,6 +236,9 @@
if (hasSketchEvent) { if (hasSketchEvent) {
aiMessage.text += `<slot slot-name="sketch"></slot>` aiMessage.text += `<slot slot-name="sketch"></slot>`
} }
if (hasReportEvent) {
aiMessage.text += `<slot slot-name="card" title="Report" content="Report"></slot>`
}
aiMessage.streaming = false aiMessage.streaming = false
aiMessage.loading = false aiMessage.loading = false
@@ -256,7 +264,8 @@
if (!hasReportStarted && eventName === 'report') { if (!hasReportStarted && eventName === 'report') {
isGeneratingReport.value = true isGeneratingReport.value = true
contentBody += `<slot slot-name="card" title="Report" content="Report"></slot>` hasReportEvent = true
// contentBody += `<slot slot-name="card" title="Report" content="Report"></slot>`
hasReportStarted = true hasReportStarted = true
} }
@@ -588,7 +597,7 @@
nextTick(() => { nextTick(() => {
ancestorsList.forEach((item) => { ancestorsList.forEach((item) => {
if (item.image_url) { if (item.image_url && item.role !== 'user') {
item.text += `<slot slot-name="sketch"></slot>` item.text += `<slot slot-name="sketch"></slot>`
} }
}) })

View File

@@ -129,7 +129,7 @@
}>() }>()
// watch( // watch(
// () => props.content, // () => props,
// (newVal) => { // (newVal) => {
// console.log('newVal-----', newVal) // console.log('newVal-----', newVal)
// }, // },
@@ -139,8 +139,12 @@
const emit = defineEmits(['regenerate']) const emit = defineEmits(['regenerate'])
const imageList = computed(() => { const imageList = computed(() => {
const { imageUrls } = props.content const { imageUrls, role } = props.content
const list = [] const list = []
if (role === 'user') {
const quotaList = props.content.image_url ?? []
list.push(...quotaList)
}
if (!imageUrls || imageUrls.length === 0) return list if (!imageUrls || imageUrls.length === 0) return list
imageUrls.forEach((item) => { imageUrls.forEach((item) => {
if (typeof item === 'string') { if (typeof item === 'string') {

View File

@@ -13,12 +13,15 @@
<Menu /> <Menu />
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item class="sketch-item flex align-center"> <el-dropdown-item
class="sketch-item flex align-center"
@click="handleClickQuote(item)"
>
<img <img
src="@/assets/images/restore-sketch.png" src="@/assets/images/restore-sketch.png"
class="dropdown-icon restore" class="dropdown-icon restore"
/> />
<span class="dropdown-txt">Quote</span> <span class="dropdown-txt">{{ $t('agent.quote') }}</span>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
class="sketch-item flex align-center" class="sketch-item flex align-center"
@@ -28,7 +31,7 @@
src="@/assets/images/delete.png" src="@/assets/images/delete.png"
class="dropdown-icon delete" class="dropdown-icon delete"
/> />
<span class="dropdown-txt del">Delete</span> <span class="dropdown-txt del">{{ $t('agent.delete') }}</span>
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@@ -37,7 +40,7 @@
class="edit-btn flex align-center space-between" class="edit-btn flex align-center space-between"
@click="handleClickEdit(item)" @click="handleClickEdit(item)"
> >
<div>Edit</div> <div>{{ $t('agent.edit') }}</div>
<img src="@/assets/images/arrow-top-right.png" /> <img src="@/assets/images/arrow-top-right.png" />
</div> </div>
<!-- 已加载完成的 sketch 显示实际图片 --> <!-- 已加载完成的 sketch 显示实际图片 -->
@@ -218,6 +221,12 @@
myEvent.emit('openFlowCanvas', { url, imgId, nodeId }) myEvent.emit('openFlowCanvas', { url, imgId, nodeId })
} }
const handleClickQuote = (item) => {
console.log(item)
const url = Object.values(item)[0]
MyEvent.emit('quote', url)
}
const handleClickDelete = (item: string | Object) => { const handleClickDelete = (item: string | Object) => {
deleteSketchFlowCanvas({ deleteSketchFlowCanvas({
id: Object.keys(item)[0], id: Object.keys(item)[0],

View File

@@ -48,8 +48,12 @@
} }
&.is-sketch { &.is-sketch {
background: url('@/assets/images/sketch-card.png') no-repeat; background: url('@/assets/images/sketch-card.png') no-repeat;
background-size: contain;
padding: 2rem 3rem; padding: 2rem 3rem;
max-width: 52.5rem;
height: 8rem;
box-sizing: border-box;
background-size: 100% 100%;
min-height: initial;
.report-card-header { .report-card-header {
margin-bottom: 0; margin-bottom: 0;
} }

View File

@@ -7,9 +7,5 @@
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
:deep(.report-card) {
width: 52.5rem;
height: 8rem;
box-sizing: border-box;
}
</style> </style>

View File

@@ -2,15 +2,17 @@
<div class="assist-input-wrapper flex flex-col" :class="{ agent: isAgentMode }"> <div class="assist-input-wrapper flex flex-col" :class="{ agent: isAgentMode }">
<div class="animate-container flex-1 flex flex-col"> <div class="animate-container flex-1 flex flex-col">
<div class="scroll-content flex-col"> <div class="scroll-content flex-col">
<div v-if="uploadedImages.length > 0" class="image-preview-list flex wrap"> <div
v-if="uploadedImages.length > 0 || quoteList.length > 0"
class="image-preview-list flex wrap"
>
<div <div
v-for="(image, index) in uploadedImages" v-for="(image, index) in [...uploadedImages, ...quoteList]"
:key="index" :key="index"
class="image-preview-item" class="image-preview-item"
> >
<img <img
:src="image.url" :src="image.url || image"
:alt="image.name"
class="preview-image" class="preview-image"
@click="previewImage(image.url)" @click="previewImage(image.url)"
/> />
@@ -211,7 +213,8 @@
<div <div
v-if="!isAgentMode" v-if="!isAgentMode"
class="report-btn flex space-between align-center" class="report-btn flex space-between align-center outer"
:class="{ 'is-cn': isCn }"
@click="toogltReportTag" @click="toogltReportTag"
> >
<SvgIcon class="light-icon" color="#FFDB56" name="light" size="16" /> <SvgIcon class="light-icon" color="#FFDB56" name="light" size="16" />
@@ -222,7 +225,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch, nextTick, onMounted } from 'vue' import { computed, ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { areaList } from '@/utils/area' import { areaList } from '@/utils/area'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -254,11 +257,16 @@
const emits = defineEmits(['send', 'pause']) const emits = defineEmits(['send', 'pause'])
const { t } = useI18n() const { t, locale } = useI18n()
const isCn = computed(() => {
return locale.value === 'CHINESE_SIMPLIFIED'
})
// 图片上传相关 // 图片上传相关
const fileInputRef = ref<HTMLInputElement | null>(null) const fileInputRef = ref<HTMLInputElement | null>(null)
const uploadedImages = ref<Array<{ url: string; name: string }>>([]) const uploadedImages = ref<Array<{ url: string; name: string }>>([])
const quoteList = ref<Array<string>>([])
// 触发文件上传 // 触发文件上传
const triggerFileUpload = () => { const triggerFileUpload = () => {
@@ -684,7 +692,8 @@
const payload = { const payload = {
text: inputValue.value.trim(), text: inputValue.value.trim(),
images: imageUrlList, images: imageUrlList,
tempImages: uploadedImages.value tempImages: uploadedImages.value,
quoteList: quoteList.value
} }
if (reportTags.value.length > 0) { if (reportTags.value.length > 0) {
payload.useReport = true payload.useReport = true
@@ -692,6 +701,7 @@
emits('send', payload) emits('send', payload)
// 发送后清空图片列表 // 发送后清空图片列表
uploadedImages.value = [] uploadedImages.value = []
quoteList.value = []
// 发送后清空输入框 // 发送后清空输入框
if (editorRef.value) { if (editorRef.value) {
editorRef.value.innerHTML = '' editorRef.value.innerHTML = ''
@@ -707,6 +717,7 @@
// 初始化编辑器高度 // 初始化编辑器高度
onMounted(() => { onMounted(() => {
MyEvent.add('quote', handleQuote)
nextTick(() => { nextTick(() => {
autoResizeEditor() autoResizeEditor()
}) })
@@ -803,6 +814,12 @@
previewUrl.value = url previewUrl.value = url
} }
const handleQuote = (url: string) => {
quoteList.value.push(url)
}
onUnmounted(() => {
MyEvent.remove('quote', handleQuote)
})
// 暴露方法给父组件 // 暴露方法给父组件
defineExpose({ defineExpose({
addReportTag addReportTag
@@ -821,6 +838,10 @@
background-color: #fff; background-color: #fff;
border: 1.1px solid #f6f4ef1a; border: 1.1px solid #f6f4ef1a;
cursor: pointer; cursor: pointer;
&.outer.is-cn {
justify-content: center;
column-gap: 3rem;
}
.c-svg { .c-svg {
width: 1.5rem; width: 1.5rem;
@@ -1378,7 +1399,7 @@
.agent-modal { .agent-modal {
// width: 14.6rem; // width: 14.6rem;
// height: 8.5rem; // height: 8.5rem;
row-gap: 1.2rem; row-gap: 1.2rem;
font-family: 'Medium'; font-family: 'Medium';
font-weight: 500; font-weight: 500;