fix
Some checks failed
Deploy / deploy (build, debian, 22) (push) Has been cancelled
Deploy / deploy (build:staging, school, 8822) (push) Has been cancelled

This commit is contained in:
2026-03-18 19:50:55 +08:00
parent 83cd62a110
commit 88d6ffaf53
15 changed files with 762 additions and 189 deletions

View File

@@ -6,7 +6,7 @@
<span>加载历史记录</span>
</div>
<div v-for="(msg, i) in messages" :key="i" :class="['message', msg.role]">
<div class="message-role">{{ msg.role === 'user' ? '我' : 'AI' }}</div>
<div class="message-role">{{ msg.role === "user" ? "我" : "AI" }}</div>
<div class="message-content" v-html="renderContent(msg)"></div>
</div>
<div v-if="streaming" class="message assistant">
@@ -14,7 +14,11 @@
<div v-if="!streamingContent" class="typing-indicator">
<span></span><span></span><span></span>
</div>
<div v-else class="message-content" v-html="renderMarkdown(streamingContent)"></div>
<div
v-else
class="message-content"
v-html="renderMarkdown(streamingContent)"
></div>
<div class="streaming-hint">AI 正在思考中</div>
</div>
</div>
@@ -28,7 +32,12 @@
@keydown.enter.exact.prevent="send"
/>
<n-flex justify="space-between" align="center" style="margin-top: 8px">
<n-button text size="small" @click="newConversation" :disabled="streaming">
<n-button
text
size="small"
@click="newConversation"
:disabled="streaming"
>
新对话
</n-button>
<n-button
@@ -69,13 +78,46 @@ function send() {
const renderer = new Renderer()
renderer.code = function ({ lang }: { text: string; lang?: string }) {
const label = lang ? lang.toUpperCase() : "CODE"
const colors: Record<string, { bg: string; fg: string; dot: string; border: string; shimmer: string }> = {
html: { bg: "#fff5f0", fg: "#e05020", dot: "#e05020", border: "#f0d0c0", shimmer: "#fff5f0, #ffeee5, #fff5f0" },
css: { bg: "#f0f0ff", fg: "#6060d0", dot: "#6060d0", border: "#d0d0f0", shimmer: "#f0f0ff, #e8e8fa, #f0f0ff" },
js: { bg: "#fffbf0", fg: "#c0960a", dot: "#c0960a", border: "#f0e0b0", shimmer: "#fffbf0, #fff5e0, #fffbf0" },
javascript: { bg: "#fffbf0", fg: "#c0960a", dot: "#c0960a", border: "#f0e0b0", shimmer: "#fffbf0, #fff5e0, #fffbf0" },
const colors: Record<
string,
{ bg: string; fg: string; dot: string; border: string; shimmer: string }
> = {
html: {
bg: "#fff5f0",
fg: "#e05020",
dot: "#e05020",
border: "#f0d0c0",
shimmer: "#fff5f0, #ffeee5, #fff5f0",
},
css: {
bg: "#f0f0ff",
fg: "#6060d0",
dot: "#6060d0",
border: "#d0d0f0",
shimmer: "#f0f0ff, #e8e8fa, #f0f0ff",
},
js: {
bg: "#fffbf0",
fg: "#c0960a",
dot: "#c0960a",
border: "#f0e0b0",
shimmer: "#fffbf0, #fff5e0, #fffbf0",
},
javascript: {
bg: "#fffbf0",
fg: "#c0960a",
dot: "#c0960a",
border: "#f0e0b0",
shimmer: "#fffbf0, #fff5e0, #fffbf0",
},
}
const c = colors[(lang ?? "").toLowerCase()] ?? {
bg: "#f0f7ff",
fg: "#2080f0",
dot: "#2080f0",
border: "#e0eaf5",
shimmer: "#f0f7ff, #e8f4f8, #f0f7ff",
}
const c = colors[(lang ?? "").toLowerCase()] ?? { bg: "#f0f7ff", fg: "#2080f0", dot: "#2080f0", border: "#e0eaf5", shimmer: "#f0f7ff, #e8f4f8, #f0f7ff" }
return `<div class="code-placeholder" style="background: linear-gradient(90deg, ${c.shimmer}); background-size: 200% 100%; border-color: ${c.border}"><span class="code-placeholder-dot" style="background: ${c.dot}"></span><span class="code-placeholder-label" style="color: ${c.fg}; background: ${c.fg}18">${label}</span><span class="code-placeholder-text">代码已自动应用到预览区</span></div>`
}
@@ -88,16 +130,13 @@ function renderContent(msg: { role: string; content: string }): string {
}
// Auto-scroll to bottom on new messages
watch(
[() => messages.value.length, streamingContent],
() => {
nextTick(() => {
if (messagesRef.value) {
messagesRef.value.scrollTop = messagesRef.value.scrollHeight
}
})
}
)
watch([() => messages.value.length, streamingContent], () => {
nextTick(() => {
if (messagesRef.value) {
messagesRef.value.scrollTop = messagesRef.value.scrollHeight
}
})
})
</script>
<style scoped>
@@ -180,13 +219,22 @@ watch(
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
@keyframes pulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
0%,
100% {
opacity: 0.4;
}
50% {
opacity: 1;
}
}
.typing-indicator {
@@ -212,8 +260,16 @@ watch(
}
@keyframes bounce {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-6px); opacity: 1; }
0%,
60%,
100% {
transform: translateY(0);
opacity: 0.4;
}
30% {
transform: translateY(-6px);
opacity: 1;
}
}
.streaming-hint {
@@ -237,5 +293,4 @@ watch(
font-size: 13px;
justify-content: center;
}
</style>