add AIAgent think

This commit is contained in:
Timi
2026-09-16 16:15:24 +08:00
parent f8a9c0e649
commit 91938f2427
4 changed files with 307 additions and 18 deletions
+3 -3
View File
@@ -4,14 +4,14 @@
<span class="error-text" v-text="lastError" />
<t-button v-if="canRetry" variant="text" size="small" content="重试" @click="emit('retry')" />
</div>
<t-form class="input-wrap glass-white" @submit.prevent="submitMessage">
<t-form class="input-wrap glass-white" @submit="submitMessage">
<t-input
v-model="draft"
class="input"
borderless
:disabled="isGenerating"
:maxlength="256"
placeholder="问问小糕怎么个事(●'◡'●)…"
placeholder="问问小糕怎么个事…"
/>
<t-button
v-if="isGenerating"
@@ -93,9 +93,9 @@ function submitMessage(): void {
gap: .5rem;
border: 1px solid var(--td-component-border);
margin: .5rem 0 0 0;
display: flex;
padding: .25rem .25rem .25rem .75rem;
margin-top: .5rem;
box-shadow: 0 .5rem 1.5rem rgba(15, 23, 42, .12);
align-items: center;
border-radius: 1rem;
+44 -8
View File
@@ -24,12 +24,22 @@
class="message-row"
:class="message.role"
>
<div class="avatar">
<t-icon :name="message.role === 'assistant' ? 'robot-2' : 'user'" />
<div class="message-header">
<div class="avatar">
<t-icon :name="message.role === 'assistant' ? 'robot-2' : 'user'" />
</div>
<div class="role-name" v-text="message.role === 'assistant' ? 'AI 小糕' : '店长'" />
</div>
<div class="message-main">
<div class="role-name" v-text="message.role === 'assistant' ? 'AI 小糕' : '店长'" />
<div class="bubble" :class="message.role">
<template v-if="message.role === 'assistant'">
<ai-chat-thinking
v-for="thinking in message.thinkingList"
:key="thinking.id"
:content="thinking.content"
:is-generating="props.isGenerating && thinking.id === message.thinkingList[message.thinkingList.length - 1]?.id"
/>
</template>
<div v-if="message.content && message.role === 'assistant'" class="content markdown-content">
<markdown-view :content="message.content" />
</div>
@@ -57,6 +67,7 @@
<script setup lang="ts">
import { MarkdownView } from "timi-web";
import type { AiChatMessage } from "@/composables/useAiChat";
import AiChatThinking from "@/pages/ai/components/AiChatThinking.vue";
defineOptions({
name: "AiChatMessageList"
@@ -124,7 +135,7 @@ defineExpose({
gap: 1rem;
flex: 1;
display: flex;
padding: .5rem 1rem calc(12rem + env(safe-area-inset-bottom, 0px));
padding: .5rem 1rem calc(6rem + env(safe-area-inset-bottom, 0px));
min-height: 0;
overflow-y: auto;
flex-direction: column;
@@ -173,10 +184,29 @@ defineExpose({
.message-row {
gap: .5rem;
display: flex;
align-items: flex-start;
width: 100%;
align-items: stretch;
flex-direction: column;
&.user {
flex-direction: row-reverse;
.message-header {
justify-content: flex-end;
}
.role-name {
order: 1;
}
.avatar {
order: 2;
}
}
.message-header {
gap: .5rem;
width: 100%;
display: flex;
align-items: center;
}
.avatar {
@@ -196,7 +226,8 @@ defineExpose({
}
.message-main {
max-width: calc(100% - 2.5rem);
width: 100%;
max-width: none;
}
&.user .message-main {
@@ -205,18 +236,23 @@ defineExpose({
.role-name {
color: var(--td-text-color-secondary);
margin: 0 .25rem .25rem;
margin: 0;
font-size: .6875rem;
}
.bubble {
color: var(--td-text-color-primary);
width: 100%;
padding: .75rem;
border-radius: .25rem 1rem 1rem 1rem;
background: var(--ai-bubble);
text-align: left;
box-shadow: 0 .25rem 1rem rgba(15, 23, 42, .05);
.thinking + .thinking {
margin-top: .75rem;
}
&.user {
color: white;
border-radius: 1rem .25rem 1rem 1rem;
@@ -0,0 +1,84 @@
<template>
<section class="thinking" aria-label="Agent think">
<div class="thinking-header">
<span>小糕正在思考...</span>
</div>
<div
ref="thinkingContent"
class="thinking-content"
role="log"
aria-live="polite"
>
<span v-text="props.content" />
</div>
</section>
</template>
<script setup lang="ts">
interface Props {
content: string;
isGenerating: boolean;
}
const props = defineProps<Props>();
const thinkingContentRef = useTemplateRef<HTMLElement>("thinkingContent");
// ---------- 自动滚动 ----------
async function scrollToBottom(): Promise<void> {
await nextTick();
const element = thinkingContentRef.value;
if (!element) {
return;
}
element.scrollTop = element.scrollHeight;
}
watch(
[() => props.content, () => props.isGenerating],
async ([content, isGenerating]) => {
if (content && isGenerating) {
await scrollToBottom();
}
},
{ deep: true, flush: "post" }
);
</script>
<style scoped lang="less">
.thinking {
gap: .5rem;
height: clamp(2rem, 24vh, 10rem);
display: flex;
padding: .625rem .75rem;
border: 1px solid var(--td-component-border);
border-radius: .75rem;
background: var(--ai-background);
flex-direction: column;
min-height: 2rem;
max-height: 10rem;
overflow: hidden;
.thinking-header {
gap: .375rem;
display: flex;
align-items: center;
color: var(--td-text-color-secondary);
font-size: .75rem;
line-height: 1rem;
}
.thinking-content {
flex: 1;
padding-right: .25rem;
color: var(--td-text-color-secondary);
font-size: .75rem;
line-height: 1.5;
min-height: 0;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-word;
}
}
</style>