feat: 优化sketch显示&添加sketch卡片
This commit is contained in:
@@ -156,7 +156,6 @@
|
||||
loading: true,
|
||||
thinking: false,
|
||||
thinkingText: '',
|
||||
thinkingCollapsed: false,
|
||||
streaming: true
|
||||
})
|
||||
messageList.value.push(aiMessage)
|
||||
@@ -309,7 +308,7 @@
|
||||
// }
|
||||
|
||||
if (eventName === 'tool') {
|
||||
MyEvent.emit('loading-sketch')
|
||||
MyEvent.emit('loading-sketch', sketchList.value.length)
|
||||
}
|
||||
|
||||
if (dataLines.length === 0) continue
|
||||
@@ -330,7 +329,10 @@
|
||||
sketchList.value.push({
|
||||
[Object.keys(jsonData)[0]]: jsonData[Object.keys(jsonData)[0]]
|
||||
})
|
||||
// 通知 Preview 有新 sketch 正在加载,传入 sketch 索引
|
||||
MyEvent.emit('loading-sketch', sketchList.value.length - 1)
|
||||
MyEvent.emit('OpenSketch')
|
||||
contentBody += `<slot slot-name="sketch"></slot>`
|
||||
}
|
||||
if (eventName === 'report') {
|
||||
reportsContent.value += jsonData.report
|
||||
@@ -575,6 +577,15 @@
|
||||
|
||||
// 延迟设置新数据,确保 UI 有时间响应清空操作
|
||||
nextTick(() => {
|
||||
// 找到每个 sessionId 对应的最后一项,插入sketch卡片
|
||||
const sessionLastIndexMap = new Map<string, number>()
|
||||
ancestorsList.forEach((item, index) => {
|
||||
sessionLastIndexMap.set(item.sessionId, index)
|
||||
})
|
||||
sessionLastIndexMap.forEach((lastIndex) => {
|
||||
ancestorsList[lastIndex].text += '<slot slot-name="sketch"></slot>'
|
||||
})
|
||||
|
||||
messageList.value = [...ancestorsList]
|
||||
params.versionID = current?.id
|
||||
sketchList.value = imgList
|
||||
|
||||
@@ -16,22 +16,40 @@
|
||||
class="img-item"
|
||||
/>
|
||||
</div>
|
||||
<div class="message-context" v-show="content.thinkingText?.length > 0">
|
||||
<!-- <div class="thinking-wrapper" v-show="content.thinkingText?.length > 0">
|
||||
<div class="thinking">
|
||||
<div
|
||||
class="thinking-header flex align-center"
|
||||
class="thinking-header flex align-center space-between"
|
||||
@click="toggleThinkingCollapsed"
|
||||
>
|
||||
<span>{{ t('agent.thinking') }}</span>
|
||||
<!-- <SvgIcon :name="content.thinkingCollapsed ? 'arrowDown' : 'arrowUp'" size="16" color="#666" /> -->
|
||||
<div class="left flex align-center">
|
||||
<span class="think-title" v-show="content.thinking">{{
|
||||
t('agent.thinking')
|
||||
}}</span>
|
||||
<SvgIcon
|
||||
v-show="!content.thinking"
|
||||
class="think-icon"
|
||||
name="checked"
|
||||
size="12"
|
||||
color="#FF7A51"
|
||||
/>
|
||||
<span class="think-title" v-show="!content.thinking">{{
|
||||
t('agent.thinkComplete')
|
||||
}}</span>
|
||||
</div>
|
||||
<SvgIcon
|
||||
:class="{ reverse: thinkingCollapsed }"
|
||||
name="arrowDown"
|
||||
size="16"
|
||||
color="#FF7A51"
|
||||
/>
|
||||
</div>
|
||||
<div class="thinking-content" v-show="!content.thinkingCollapsed">
|
||||
<div class="thinking-content" v-show="!thinkingCollapsed">
|
||||
<pre>{{ content.thinkingText }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="message-txt markdown-body flex flex-col">
|
||||
<!-- <div v-html="formatMessage"></div> -->
|
||||
<VueMarkdown
|
||||
:custom-attrs="customAttrs"
|
||||
:markdown="content.text"
|
||||
@@ -43,6 +61,9 @@
|
||||
<template v-slot:s-url="{ children: children }">
|
||||
<Url :list="content.webAddress" @click.native="handleClickUrls" />
|
||||
</template>
|
||||
<template v-slot:s-sketch="{ children: children }">
|
||||
<Sketch @click.native="handleClickSketch" />
|
||||
</template>
|
||||
</VueMarkdown>
|
||||
<div
|
||||
class="web-address flex align-center"
|
||||
@@ -94,6 +115,7 @@
|
||||
import agentThumb from '@/assets/images/agent-thumb.png'
|
||||
import Card from './ReportCard.vue'
|
||||
import Url from './UrlCard.vue'
|
||||
import Sketch from './SketchCard.vue'
|
||||
import { VueMarkdown } from '@crazydos/vue-markdown'
|
||||
import type { CustomAttrs } from '@crazydos/vue-markdown'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
@@ -228,9 +250,9 @@
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const thinkingCollapsed = ref(true)
|
||||
const toggleThinkingCollapsed = () => {
|
||||
props.content.thinkingCollapsed = !props.content.thinkingCollapsed
|
||||
thinkingCollapsed.value = !thinkingCollapsed.value
|
||||
}
|
||||
|
||||
const handleClickReport = () => {
|
||||
@@ -241,6 +263,9 @@
|
||||
MyEvent.emit('openUrls', props.content.webAddress)
|
||||
// 点击显示来源
|
||||
}
|
||||
const handleClickSketch = () => {
|
||||
MyEvent.emit('openSketch')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -327,24 +352,42 @@
|
||||
}
|
||||
|
||||
.thinking {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background-color: #f9f9f9;
|
||||
color: #ff7a51;
|
||||
font-family: 'Medium';
|
||||
margin-bottom: 1.6rem;
|
||||
border: 0.1rem solid transparent;
|
||||
border-radius: 0.4rem;
|
||||
background: linear-gradient(#fffcf4, #fffcf4),
|
||||
linear-gradient(
|
||||
119.03deg,
|
||||
rgba(233, 121, 60, 0.3) 1.61%,
|
||||
rgba(255, 207, 144, 0.3) 101.01%
|
||||
);
|
||||
background-origin: padding-box, border-box;
|
||||
background-clip: padding-box, border-box;
|
||||
|
||||
.thinking-header {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
justify-content: space-between;
|
||||
padding: 0.8rem 1.2rem;
|
||||
.think-icon {
|
||||
margin-right: 0.6rem;
|
||||
}
|
||||
.reverse {
|
||||
transform: rotate(180deg);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.thinking-content {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0 1.4rem 1.6rem;
|
||||
pre {
|
||||
color: #f6a478;
|
||||
margin: 0;
|
||||
border-left: 0.035rem solid #f6a478;
|
||||
white-space: pre-wrap;
|
||||
font-family: inherit;
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
|
||||
watch(
|
||||
() => props.messageList,
|
||||
() => {
|
||||
(val) => {
|
||||
console.log('messageList',val)
|
||||
scrollToBottom()
|
||||
},
|
||||
{ deep: true }
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
>
|
||||
<template v-if="type === 'sketch'">
|
||||
<div
|
||||
v-show="!showLoading"
|
||||
class="sketch-item"
|
||||
v-for="(item, index) in sketchList"
|
||||
:key="'sketch-item-' + index"
|
||||
@@ -41,14 +40,16 @@
|
||||
<div>Edit</div>
|
||||
<img src="@/assets/images/arrow-top-right.png" />
|
||||
</div>
|
||||
<!-- 已加载完成的 sketch 显示实际图片 -->
|
||||
<img
|
||||
:src="getImageSrc(item, index)"
|
||||
:alt="'sketch-' + index"
|
||||
v-show="!pendingSketchIndexes.includes(index)"
|
||||
v-img-loading="getImageSrc(item, index)"
|
||||
@load="handleImageLoad(index)"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="showLoading" class="sketch-item loading-gif">
|
||||
<img src="@/assets/images/sketch-loading.gif" alt="loading" />
|
||||
<!-- 正在加载的 sketch 显示 loading gif overlay -->
|
||||
<div v-if="pendingSketchIndexes.includes(index)" class="loading-wrapper">
|
||||
<img src="@/assets/images/sketch-loading.gif" alt="loading" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="type === 'url'">
|
||||
@@ -113,7 +114,10 @@
|
||||
const emits = defineEmits(['deleteSketch'])
|
||||
|
||||
// 存储每个图片的加载状态
|
||||
const loadedStatus = ref<boolean>(false)
|
||||
const loadedStatus = ref<boolean[]>([])
|
||||
|
||||
// 存储正在加载的 sketch 索引
|
||||
const pendingSketchIndexes = ref<number[]>([])
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -176,8 +180,14 @@
|
||||
|
||||
// 图片加载完成时触发
|
||||
const handleImageLoad = (index: number) => {
|
||||
loadedStatus[index] = true
|
||||
showLoading.value = false
|
||||
loadedStatus.value[index] = true
|
||||
pendingSketchIndexes.value = pendingSketchIndexes.value.filter((i) => i !== index)
|
||||
}
|
||||
|
||||
// 图片加载失败时触发
|
||||
const handleImageError = (index: number) => {
|
||||
pendingSketchIndexes.value = pendingSketchIndexes.value.filter((i) => i !== index)
|
||||
console.error(`Failed to load sketch at index ${index}`)
|
||||
}
|
||||
|
||||
// 获取当前显示的图片源
|
||||
@@ -235,23 +245,42 @@
|
||||
}
|
||||
|
||||
const showLoading = ref(false)
|
||||
const handleLoadingSketch = () => {
|
||||
const handleLoadingSketch = (sketchIndex?: number) => {
|
||||
showLoading.value = true
|
||||
// 记录正在加载的 sketch 索引
|
||||
if (sketchIndex !== undefined) {
|
||||
pendingSketchIndexes.value.push(sketchIndex)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickUrl = (item: { url: string, title: string }) => {
|
||||
const handleClickUrl = (item: { url: string; title: string }) => {
|
||||
window.open(item.url, '_blank')
|
||||
}
|
||||
|
||||
// watch(
|
||||
// () => props.sketchList,
|
||||
// (val) => {
|
||||
// if (val.length > 0) {
|
||||
// showLoading.value = false
|
||||
// }
|
||||
// },
|
||||
// { deep: true }
|
||||
// )
|
||||
const vImgLoading = {
|
||||
mounted(el, binding) {
|
||||
// 1. 设置初始状态:显示 Loading 图(或者你可以从 binding.arg 传进来)
|
||||
const loadingUrl = LoadingImg
|
||||
const finalSrc = binding.value // 真正要加载的图
|
||||
|
||||
el.src = loadingUrl
|
||||
|
||||
// 2. 预加载真实图片
|
||||
const img = new Image()
|
||||
img.src = finalSrc
|
||||
img.onload = () => {
|
||||
// 3. 加载完成后替换
|
||||
el.src = finalSrc
|
||||
el.style.opacity = 1
|
||||
}
|
||||
}
|
||||
// 关键:如果图片地址是动态变化的,需要监听更新
|
||||
// updated(el, binding) {
|
||||
// if (binding.value !== binding.oldValue) {
|
||||
// // 重复上面的加载逻辑...
|
||||
// }
|
||||
// }
|
||||
}
|
||||
onMounted(() => {
|
||||
MyEvent.add('loading-sketch', handleLoadingSketch)
|
||||
})
|
||||
@@ -284,6 +313,19 @@
|
||||
height: 100%;
|
||||
border-radius: 1.6rem;
|
||||
}
|
||||
.loading-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 1.6rem;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 1.6rem;
|
||||
}
|
||||
}
|
||||
:deep(.menu-btn) {
|
||||
position: absolute;
|
||||
top: 2.1rem;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="report-card" :class="{ 'is-url': isUrl }">
|
||||
<div class="report-card" :class="{ 'is-url': isUrl, 'is-sketch': isSketch }">
|
||||
<div class="report-card-header">
|
||||
<span v-if="!isUrl">{{ title }}</span>
|
||||
<span v-if="!isUrl && !isSketch">{{ report.title }}</span>
|
||||
<div v-else class="web-sources flex align-center">
|
||||
<span>Web Sources</span>
|
||||
<span>{{ report.title }}</span>
|
||||
<img src="@/assets/images/link.png" class="link-icon" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-card-content">
|
||||
<span v-if="!isUrl">markdown.md</span>
|
||||
<span v-else>Destination URL</span>
|
||||
<span v-if="!isUrl && !isSketch">{{ report.content || 'markdown.md' }}</span>
|
||||
<span v-else>{{ report.content }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -25,11 +25,17 @@
|
||||
title?: string
|
||||
content?: string
|
||||
isUrl?: boolean
|
||||
isSketch?: boolean
|
||||
report: {
|
||||
title: ''
|
||||
content: ''
|
||||
}
|
||||
}>(),
|
||||
{
|
||||
isUrl: false,
|
||||
title: '',
|
||||
content: ''
|
||||
content: '',
|
||||
isSketch: false
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -48,6 +54,10 @@
|
||||
background: url('@/assets/images/link-card.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
&.is-sketch {
|
||||
background: url('@/assets/images/sketch-card.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
// &:first-of-type{
|
||||
// margin-top: 0;
|
||||
// }
|
||||
|
||||
10
src/views/home/agent/components/SketchCard.vue
Normal file
10
src/views/home/agent/components/SketchCard.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<ReportCard is-sketch :report="{title: 'Sketches Results', content: 'JPG file'}"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ReportCard from './ReportCard.vue'
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
@@ -465,7 +465,7 @@
|
||||
|
||||
// 打字机效果显示placeholder文本
|
||||
const placeholderText =
|
||||
'Generate a furniture trending report for 2025, including popular styles and design directions.'
|
||||
'Generate a furniture trending report for 2026, including popular styles and design directions.'
|
||||
typeWriterEffect(placeholderSpan, placeholderText)
|
||||
|
||||
const removePlaceholderOnInput = () => {
|
||||
@@ -1378,6 +1378,7 @@
|
||||
.agent-modal {
|
||||
// width: 14.6rem;
|
||||
// height: 8.5rem;
|
||||
|
||||
row-gap: 1.2rem;
|
||||
font-family: 'Medium';
|
||||
font-weight: 500;
|
||||
@@ -1388,6 +1389,7 @@
|
||||
box-shadow: 0px 6.53px 32.63px 0px #0000000d;
|
||||
border-radius: 1rem;
|
||||
padding: 1.2rem 1.4rem;
|
||||
transform: translateX(calc(50% - 1.6rem));
|
||||
.c-svg {
|
||||
width: initial;
|
||||
width: 1rem;
|
||||
|
||||
Reference in New Issue
Block a user