Skip to content

Commit 2e66999

Browse files
author
www.xueximeng.com
committed
完善效果对比页的对话框功能
1 parent ff4d164 commit 2e66999

4 files changed

Lines changed: 871 additions & 105 deletions

File tree

data/logs/backend/info.log

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,3 +1199,65 @@
11991199
2025-12-18 18:38:53,280 | INFO | ✅ 本地用户登录成功: username=admin, id=40
12001200
2025-12-18 18:39:26,637 | INFO | Received signal SIGINT. Shutting down.
12011201
2025-12-18 18:39:36,985 | INFO | Server Stopped
1202+
2025-12-20 07:56:15,198 | INFO | Shutdown interrupted. Killing.
1203+
2025-12-20 07:56:15,306 | INFO | Killing Sanic-Server-0-0 [75649]
1204+
2025-12-20 07:56:15,313 | INFO | Killing Sanic-Reloader-0 [75650]
1205+
2025-12-20 07:56:15,328 | INFO | Received signal SIGINT. Shutting down.
1206+
2025-12-20 07:56:30,653 | INFO |
1207+
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
1208+
│ Sanic v23.12.1 │
1209+
│ Goin' Fast @ http://0.0.0.0:8888 │
1210+
├───────────────────────┬────────────────────────────────────────────────────────────────────────────────┤
1211+
│   │ app: apps │
1212+
│  ▄███ █████ ██  │ mode: production, single worker │
1213+
│  ██  │ server: sanic, HTTP/1.1 │
1214+
│  ▀███████ ███▄  │ python: 3.10.10 │
1215+
│  ██  │ platform: macOS-14.4-x86_64-i386-64bit │
1216+
│  ████ ████████▀  │ auto-reload: enabled, /Users/macbookpro/Desktop/提词管家/YPrompt/backend/templates │
1217+
│   │ packages: sanic-routing==23.12.0, sanic-ext==23.12.0 │
1218+
│ Build Fast. Run Fast. │ │
1219+
└───────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
1220+
1221+
2025-12-20 07:56:32,916 | INFO | Sanic Extensions:
1222+
2025-12-20 07:56:32,916 | INFO | > injection [0 dependencies; 0 constants]
1223+
2025-12-20 07:56:32,916 | INFO | > openapi [http://0.0.0.0:8888/docs]
1224+
2025-12-20 07:56:32,917 | INFO | > http
1225+
2025-12-20 07:56:32,917 | INFO | > templating [jinja2==3.1.2]
1226+
2025-12-20 07:56:32,970 | INFO | 📦 初始化数据库: sqlite
1227+
2025-12-20 07:56:32,971 | INFO | 📁 SQLite数据库路径: ../data/yprompt.db
1228+
2025-12-20 07:56:32,987 | INFO | ✅ SQLite连接成功: ../data/yprompt.db
1229+
2025-12-20 07:56:32,991 | INFO | 📦 SQLite数据库为空,开始初始化...
1230+
2025-12-20 07:56:33,049 | INFO | ✅ SQLite表结构初始化完成
1231+
2025-12-20 07:56:33,302 | INFO | ✅ 默认管理员账号创建成功: admin / admin123
1232+
2025-12-20 07:56:33,302 | INFO | ✅ 数据库初始化成功: sqlite
1233+
2025-12-20 07:56:38,167 | INFO | ✅ 本地用户登录成功: username=admin, id=1
1234+
2025-12-20 07:56:38,170 | INFO | ✅ 本地用户登录成功: username=admin, id=1
1235+
2025-12-20 07:58:15,858 | INFO | Created user AI config successfully: user_id=1
1236+
2025-12-20 07:58:17,643 | INFO | Updated user AI config successfully: user_id=1
1237+
2025-12-20 07:58:17,946 | INFO | Updated user AI config successfully: user_id=1
1238+
2025-12-20 07:58:18,648 | INFO | Updated user AI config successfully: user_id=1
1239+
2025-12-20 07:58:18,995 | INFO | Updated user AI config successfully: user_id=1
1240+
2025-12-20 07:58:19,173 | INFO | Updated user AI config successfully: user_id=1
1241+
2025-12-20 07:58:19,523 | INFO | Updated user AI config successfully: user_id=1
1242+
2025-12-20 07:58:19,790 | INFO | Updated user AI config successfully: user_id=1
1243+
2025-12-20 07:58:19,973 | INFO | Updated user AI config successfully: user_id=1
1244+
2025-12-20 07:58:20,103 | INFO | Updated user AI config successfully: user_id=1
1245+
2025-12-20 07:58:20,231 | INFO | Updated user AI config successfully: user_id=1
1246+
2025-12-20 07:58:33,874 | INFO | Updated user AI config successfully: user_id=1
1247+
2025-12-20 07:58:49,280 | INFO | Updated user AI config successfully: user_id=1
1248+
2025-12-20 07:58:50,373 | INFO | Updated user AI config successfully: user_id=1
1249+
2025-12-20 07:58:52,790 | INFO | Updated user AI config successfully: user_id=1
1250+
2025-12-20 07:58:54,594 | INFO | Updated user AI config successfully: user_id=1
1251+
2025-12-20 08:02:21,561 | INFO | Updated user AI config successfully: user_id=1
1252+
2025-12-20 08:02:23,624 | INFO | Updated user AI config successfully: user_id=1
1253+
2025-12-20 08:03:11,936 | INFO | Updated user AI config successfully: user_id=1
1254+
2025-12-20 08:03:40,419 | INFO | Updated user AI config successfully: user_id=1
1255+
2025-12-20 08:03:43,019 | INFO | Updated user AI config successfully: user_id=1
1256+
2025-12-20 08:04:10,607 | INFO | Updated user AI config successfully: user_id=1
1257+
2025-12-20 08:04:12,399 | INFO | Updated user AI config successfully: user_id=1
1258+
2025-12-20 08:04:32,653 | INFO | Updated user AI config successfully: user_id=1
1259+
2025-12-20 08:04:59,099 | INFO | Updated user AI config successfully: user_id=1
1260+
2025-12-20 08:05:01,350 | INFO | Updated user AI config successfully: user_id=1
1261+
2025-12-20 08:07:53,710 | INFO | 📤 用户登出: user_id=1
1262+
2025-12-20 08:07:59,925 | INFO | ✅ 本地用户登录成功: username=admin, id=1
1263+
2025-12-20 08:07:59,926 | INFO | ✅ 本地用户登录成功: username=admin, id=1
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
<template>
2+
<div
3+
class="flex group"
4+
:class="message.role === 'user' ? 'justify-end' : 'justify-start'"
5+
>
6+
<div
7+
class="flex flex-col w-full"
8+
:class="message.isEditing ? 'max-w-full sm:max-w-2xl' : 'max-w-xs lg:max-w-md'"
9+
>
10+
<div
11+
:class="[
12+
message.isEditing
13+
? 'bg-transparent border-0 shadow-none p-0'
14+
: message.role === 'user'
15+
? 'bg-blue-500 text-white px-4 py-3 rounded-lg'
16+
: 'bg-gray-100 text-gray-800 px-4 py-3 rounded-lg',
17+
!message.isEditing && (message.role === 'user' ? 'ml-auto' : 'mr-auto'),
18+
'transition-all duration-300 relative'
19+
]"
20+
>
21+
<div v-if="message.isEditing" class="relative">
22+
<div class="relative border border-blue-300 rounded-2xl overflow-hidden bg-white">
23+
<textarea
24+
:value="localEditingContent"
25+
@input="updateEditingContent($event)"
26+
class="w-full p-4 border-0 resize-none focus:outline-none text-gray-800 bg-white min-h-[80px] max-h-[200px] overflow-y-auto text-base"
27+
placeholder="编辑消息内容..."
28+
></textarea>
29+
</div>
30+
</div>
31+
32+
<template v-else>
33+
<div v-if="message.role === 'assistant'">
34+
<div
35+
v-if="message.isStreaming && !message.content"
36+
class="flex space-x-1 text-gray-500"
37+
>
38+
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
39+
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
40+
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
41+
</div>
42+
<div
43+
v-else
44+
v-html="renderMarkdown(message.content)"
45+
class="prose prose-sm max-w-none prose-headings:text-gray-800 prose-p:text-gray-800 prose-li:text-gray-800 prose-strong:text-gray-800"
46+
></div>
47+
</div>
48+
<div
49+
v-else
50+
v-html="renderUserMessage(message.content)"
51+
class="text-white [&_p]:text-white [&_p]:mb-2 [&_strong]:font-bold [&_strong]:text-white [&_ul]:list-disc [&_ul]:list-inside [&_ul]:text-white [&_ol]:list-decimal [&_ol]:list-inside [&_ol]:text-white [&_ol]:mb-2 [&_li]:text-white [&_li]:mb-1 [&_code]:bg-blue-600 [&_code]:text-blue-100 [&_code]:px-1 [&_code]:rounded [&_code]:font-mono [&_pre]:bg-blue-600 [&_pre]:text-blue-100 [&_pre]:p-2 [&_pre]:rounded [&_pre]:overflow-x-auto [&_a]:text-blue-200 [&_a]:underline [&_blockquote]:border-l-2 [&_blockquote]:border-blue-300 [&_blockquote]:pl-2 [&_blockquote]:text-blue-100"
52+
></div>
53+
</template>
54+
</div>
55+
56+
<div
57+
v-if="message.attachments && message.attachments.length > 0 && !message.isEditing"
58+
class="mt-2"
59+
:class="message.role === 'user' ? 'ml-auto max-w-xs lg:max-w-md' : 'mr-auto max-w-xs lg:max-w-md'"
60+
>
61+
<div class="text-xs text-gray-500 mb-1">附件 ({{ message.attachments.length }})</div>
62+
<div class="flex gap-2 overflow-x-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 pb-1">
63+
<div
64+
v-for="attachment in message.attachments"
65+
:key="attachment.id"
66+
class="flex-shrink-0 flex items-center gap-2 px-2 py-1.5 rounded-md text-xs border min-w-0"
67+
:class="message.role === 'user' ? 'border-blue-200 bg-blue-50' : 'border-gray-200 bg-gray-100'"
68+
>
69+
<div class="flex items-center gap-2 min-w-0">
70+
<div class="flex-shrink-0">
71+
<svg v-if="attachment.type === 'image'" class="w-3 h-3 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
72+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
73+
</svg>
74+
<svg v-else-if="attachment.type === 'document'" class="w-3 h-3 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
75+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
76+
</svg>
77+
<svg v-else-if="attachment.type === 'audio'" class="w-3 h-3 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
78+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" />
79+
</svg>
80+
<svg v-else-if="attachment.type === 'video'" class="w-3 h-3 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
81+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
82+
</svg>
83+
<svg v-else class="w-3 h-3 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
84+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
85+
</svg>
86+
</div>
87+
<div class="min-w-0 flex-1">
88+
<div
89+
class="truncate max-w-20 font-medium text-xs"
90+
:class="message.role === 'user' ? 'text-blue-700' : 'text-gray-700'"
91+
:title="attachment.name"
92+
>
93+
{{ attachment.name }}
94+
</div>
95+
<div
96+
class="text-xs"
97+
:class="message.role === 'user' ? 'text-blue-500' : 'text-gray-500'"
98+
>
99+
{{ (attachment.size / 1024).toFixed(1) }}KB
100+
</div>
101+
</div>
102+
</div>
103+
</div>
104+
</div>
105+
</div>
106+
107+
<div
108+
v-if="!message.isStreaming"
109+
class="flex space-x-1 mt-2 transition-opacity duration-200"
110+
:class="[
111+
message.isEditing ? 'opacity-100 justify-end' : 'opacity-0 group-hover:opacity-100 ' + (message.role === 'user' ? 'justify-end' : 'justify-start')
112+
]"
113+
>
114+
<template v-if="message.isEditing">
115+
<button
116+
@click="message.role === 'user' ? $emit('resend', message.id) : $emit('save-edit', message.id)"
117+
class="p-1.5 text-gray-500 hover:text-blue-600 transition-colors rounded-lg hover:bg-gray-100"
118+
:title="message.role === 'user' ? '保存并重新发送' : '保存编辑'"
119+
:disabled="isActionsDisabled"
120+
>
121+
<Send class="w-3.5 h-3.5" />
122+
</button>
123+
124+
<button
125+
@click="$emit('cancel-edit', message.id)"
126+
class="p-1.5 text-gray-500 hover:text-red-600 transition-colors rounded-lg hover:bg-gray-100"
127+
title="取消编辑"
128+
>
129+
<X class="w-3.5 h-3.5" />
130+
</button>
131+
</template>
132+
133+
<template v-else>
134+
<button
135+
v-if="message.role === 'assistant'"
136+
@click="$emit('regenerate', message.id)"
137+
class="p-1.5 text-gray-500 hover:text-blue-600 transition-colors rounded-lg hover:bg-gray-100"
138+
title="重新生成回复"
139+
:disabled="isActionsDisabled"
140+
>
141+
<RefreshCw class="w-3.5 h-3.5" />
142+
</button>
143+
<button
144+
v-if="message.role === 'user'"
145+
@click="$emit('resend', message.id)"
146+
class="p-1.5 text-gray-500 hover:text-blue-600 transition-colors rounded-lg hover:bg-gray-100"
147+
title="重新发送消息"
148+
:disabled="isActionsDisabled"
149+
>
150+
<Send class="w-3.5 h-3.5" />
151+
</button>
152+
153+
<button
154+
@click="$emit('start-edit', message.id)"
155+
class="p-1.5 text-gray-500 hover:text-green-600 transition-colors rounded-lg hover:bg-gray-100"
156+
title="编辑消息"
157+
>
158+
<Edit2 class="w-3.5 h-3.5" />
159+
</button>
160+
161+
<button
162+
@click="$emit('delete', message.id)"
163+
class="p-1.5 text-gray-500 hover:text-red-600 transition-colors rounded-lg hover:bg-gray-100"
164+
title="删除消息"
165+
>
166+
<Trash2 class="w-3.5 h-3.5" />
167+
</button>
168+
169+
<button
170+
@click="$emit('copy', message.content)"
171+
class="p-1.5 text-gray-500 hover:text-blue-600 transition-colors rounded-lg hover:bg-gray-100"
172+
title="复制消息内容"
173+
>
174+
<Copy class="w-3.5 h-3.5" />
175+
</button>
176+
</template>
177+
</div>
178+
</div>
179+
</div>
180+
</template>
181+
182+
<script setup lang="ts">
183+
import { computed } from 'vue'
184+
import { Edit2, Trash2, Copy, X, Send, RefreshCw } from 'lucide-vue-next'
185+
import { marked } from 'marked'
186+
import type { ChatMessage } from '../composables/useComparison'
187+
188+
const props = defineProps<{
189+
message: ChatMessage
190+
editingContent: string
191+
isDisabled: boolean
192+
}>()
193+
194+
const emit = defineEmits<{
195+
'start-edit': [messageId: string]
196+
'save-edit': [messageId: string]
197+
'cancel-edit': [messageId: string]
198+
'delete': [messageId: string]
199+
'copy': [content: string]
200+
'resend': [messageId: string]
201+
'regenerate': [messageId: string]
202+
'update:editing-content': [value: string]
203+
}>()
204+
205+
const localEditingContent = computed({
206+
get: () => props.editingContent,
207+
set: (value: string) => emit('update:editing-content', value)
208+
})
209+
210+
const isActionsDisabled = computed(() => props.isDisabled || !!props.message.isStreaming)
211+
212+
const renderMarkdown = (content: string): string => {
213+
try {
214+
const result = marked(content, { breaks: true, gfm: true })
215+
return typeof result === 'string' ? result : String(result)
216+
} catch (error) {
217+
console.error('Markdown 渲染失败:', error)
218+
return content
219+
}
220+
}
221+
222+
const renderUserMessage = (content: string): string => {
223+
try {
224+
const hasMarkdown = /^#|^\*\*|^##|^\*|^-|\*\*.*\*\*|^1\.|```/.test(content) ||
225+
content.includes('**') || content.includes('##') || content.includes('# ')
226+
227+
if (hasMarkdown || content.length > 50) {
228+
const result = marked(content, { breaks: true, gfm: true })
229+
return typeof result === 'string' ? result : String(result)
230+
}
231+
return content.replace(/\n/g, '<br>')
232+
} catch (error) {
233+
console.error('用户消息渲染失败:', error)
234+
try {
235+
const result = marked(content, { breaks: true, gfm: true })
236+
return typeof result === 'string' ? result : String(result)
237+
} catch {
238+
return content.replace(/\n/g, '<br>')
239+
}
240+
}
241+
}
242+
243+
const updateEditingContent = (event: Event) => {
244+
const target = event.target as HTMLTextAreaElement
245+
emit('update:editing-content', target.value)
246+
}
247+
</script>

0 commit comments

Comments
 (0)