feat: 优化sketch显示&添加sketch卡片

This commit is contained in:
2026-03-20 17:07:02 +08:00
parent 7f81997db1
commit 635b1e9c3a
12 changed files with 175 additions and 48 deletions

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -32,7 +32,8 @@
watch(
() => props.messageList,
() => {
(val) => {
console.log('messageList',val)
scrollToBottom()
},
{ deep: true }

View File

@@ -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;

View File

@@ -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;
// }

View 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>

View File

@@ -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;