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

@@ -0,0 +1,3 @@
<svg width="8" height="5" viewBox="0 0 8 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.167367 0.183059C0.390523 -0.0610191 0.752332 -0.0610197 0.975489 0.183058L4.00001 3.49112L7.02451 0.183059C7.24767 -0.0610191 7.60948 -0.0610197 7.83263 0.183058C8.05579 0.427135 8.05579 0.822863 7.83263 1.06694L4.40407 4.81694C4.29691 4.93415 4.15156 5 4.00001 5C3.84846 5 3.70311 4.93415 3.59595 4.81694L0.167368 1.06694C-0.0557889 0.822865 -0.0557895 0.427137 0.167367 0.183059Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 516 B

View File

@@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 6C0 2.68629 2.68629 0 6 0C9.31371 0 12 2.68629 12 6C12 9.31371 9.31371 12 6 12C2.68629 12 0 9.31371 0 6ZM8.4453 3.55932C8.1743 3.36861 7.80002 3.43371 7.60932 3.7047L5.19026 7.14232L4.19396 6.0464C3.97106 5.8012 3.59159 5.78313 3.3464 6.00604C3.1012 6.22894 3.08313 6.60841 3.30604 6.8536L4.80604 8.5036C4.92853 8.63835 5.1056 8.71017 5.28736 8.69884C5.46911 8.6875 5.63588 8.59423 5.74068 8.4453L8.59068 4.3953C8.78139 4.1243 8.71629 3.75002 8.4453 3.55932Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -154,6 +154,7 @@ export default {
Download: 'Download',
deleteSuccess: 'Successfully deleted',
thinking: 'Thinking...',
thinkComplete: 'Thinking complete.',
},
// Version Tree

View File

@@ -148,7 +148,8 @@ export default {
'复制失败。您的浏览器可能限制了剪贴板访问,请允许浏览器访问剪贴板或尝试手动复制。',
Download: '下载',
deleteSuccess: '删除成功',
thinking:'已思考'
thinking:'已思考',
thinkComplete: '思考完成。'
},
// Version Tree

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;