add new session button

This commit is contained in:
Timi
2026-09-22 21:27:33 +08:00
parent 45f77d3168
commit b54c07fe49
6 changed files with 100 additions and 54 deletions
+4 -2
View File
@@ -1,6 +1,6 @@
# AiChatComposer 输入框 # AiChatComposer 输入框
对话输入框,按状态在发送、停止与重试之间切换。 对话输入框,按状态在发送、停止、重试与新开会话之间切换。
## 基础用法 ## 基础用法
@@ -18,17 +18,19 @@ demo-preview=../demos/ai-chat-composer/basic.vue
| 状态 | 按钮 | | 状态 | 按钮 |
| --- | --- | | --- | --- |
| 空闲且未禁用 | 新开会话(`chat-add`) |
| 默认 | 发送(`sendText`) | | 默认 | 发送(`sendText`) |
| `isGenerating` 为真 | 停止(`stopText`,危险色) | | `isGenerating` 为真 | 停止(`stopText`,危险色) |
| 有 `lastError` 且 `canRetry` | 错误条 + 重试(`retryText`) | | 有 `lastError` 且 `canRetry` | 错误条 + 重试(`retryText`) |
生成中或 `disabled` 为真时输入框不可编辑,`send` 事件也不会抛出。 生成中或 `disabled` 为真时输入框不可编辑,新开会话和 `send` 事件也不会触发。
## 事件 ## 事件
- `send`:抛出**去除首尾空白后**的内容,抛完自动清空输入框;空内容不抛 - `send`:抛出**去除首尾空白后**的内容,抛完自动清空输入框;空内容不抛
- `stop`:点停止按钮 - `stop`:点停止按钮
- `retry`:点错误条上的重试 - `retry`:点错误条上的重试
- `new-conversation`:清空输入框并请求业务侧开始一个新会话
输入框内容用 `v-model` 绑定,`maxLength` 默认 2000。 输入框内容用 `v-model` 绑定,`maxLength` 默认 2000。
+3 -2
View File
@@ -24,7 +24,7 @@ import { AiChatPanel, useAiChat } from "timi-tdesign-mobile";
const { const {
messages, isGenerating, activeToolStatus, lastError, canRetry, messages, isGenerating, activeToolStatus, lastError, canRetry,
sendMessage, stopGeneration, retryLastMessage sendMessage, stopGeneration, retryLastMessage, resetConversation
} = useAiChat({ } = useAiChat({
request: ({ message, signal }) => fetchReply(message, signal) request: ({ message, signal }) => fetchReply(message, signal)
}); });
@@ -40,6 +40,7 @@ const {
@send="sendMessage" @send="sendMessage"
@stop="stopGeneration" @stop="stopGeneration"
@retry="retryLastMessage" @retry="retryLastMessage"
@new-conversation="resetConversation"
/> />
</template> </template>
``` ```
@@ -63,7 +64,7 @@ const {
## 自定义 ## 自定义
`welcome`、`composer`、`tool-status`、`message` 等插槽可以替换对应区域。`composer` 插槽会拿到 `draft`、`on-send`、`on-stop`、`on-retry` 等,便于接入自定义输入框。 `welcome`、`composer`、`tool-status`、`message` 等插槽可以替换对应区域。`composer` 插槽会拿到 `draft`、`on-send`、`on-stop`、`on-retry`、`on-new-conversation` 等,便于接入自定义输入框。
## API ## API
+6
View File
@@ -8,6 +8,7 @@
@send="onSend" @send="onSend"
@stop="onStop" @stop="onStop"
@retry="onRetry" @retry="onRetry"
@new-conversation="onNewConversation"
/> />
<div class="bar"> <div class="bar">
@@ -53,6 +54,11 @@ function onRetry() {
error.value = ""; error.value = "";
push("retry()"); push("retry()");
} }
function onNewConversation() {
error.value = "";
push("new-conversation()");
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
+3 -1
View File
@@ -10,6 +10,7 @@
@send="sendMessage" @send="sendMessage"
@stop="stopGeneration" @stop="stopGeneration"
@retry="retryLastMessage" @retry="retryLastMessage"
@new-conversation="resetConversation"
/> />
</div> </div>
@@ -33,7 +34,8 @@ const {
canRetry, canRetry,
sendMessage, sendMessage,
stopGeneration, stopGeneration,
retryLastMessage retryLastMessage,
resetConversation
} = useAiChat({ } = useAiChat({
request: async ({ message, signal }) => createMockReply(message, signal) request: async ({ message, signal }) => createMockReply(message, signal)
}); });
+81 -49
View File
@@ -12,46 +12,60 @@
/> />
</slot> </slot>
</div> </div>
<t-form class="input-wrap" @submit="submitMessage"> <div class="input-wrap">
<t-input <t-button
v-model="draft" type="button"
class="input" theme="primary"
borderless shape="circle"
aria-label="新开会话"
:disabled="props.isGenerating || props.disabled" :disabled="props.isGenerating || props.disabled"
:maxlength="props.maxLength" @click="startNewConversation"
:placeholder="props.placeholder"
/>
<slot
v-if="props.isGenerating"
name="stop"
:disabled="props.disabled"
:on-stop="() => emit('stop')"
> >
<t-button <template #icon>
class="send" <t-icon name="chat-add" />
type="button" </template>
size="small" </t-button>
theme="danger" <t-form class="form glass-white" @submit="submitMessage">
:content="props.stopText" <t-input
@click="emit('stop')" v-model="draft"
class="input bg-transparent"
borderless
:disabled="props.isGenerating || props.disabled"
:maxlength="props.maxLength"
:placeholder="props.placeholder"
/> />
</slot> <slot
<slot v-if="props.isGenerating"
v-else name="stop"
name="send"
:disabled="props.disabled"
:on-send="submitMessage"
>
<t-button
class="send"
type="submit"
size="small"
theme="primary"
:disabled="props.disabled" :disabled="props.disabled"
:content="props.sendText" :on-stop="() => emit('stop')"
/> >
</slot> <t-button
</t-form> class="send"
type="button"
size="small"
theme="danger"
:content="props.stopText"
@click="emit('stop')"
/>
</slot>
<slot
v-else
name="send"
:disabled="props.disabled"
:on-send="submitMessage"
>
<t-button
class="send"
type="submit"
size="small"
theme="primary"
:disabled="props.disabled"
:content="props.sendText"
/>
</slot>
</t-form>
</div>
</div> </div>
</template> </template>
@@ -88,8 +102,17 @@ const emit = defineEmits<{
send: [message: string]; send: [message: string];
stop: []; stop: [];
retry: []; retry: [];
"new-conversation": [];
}>(); }>();
function startNewConversation(): void {
if (props.disabled || props.isGenerating) {
return;
}
draft.value = "";
emit("new-conversation");
}
function submitMessage(): void { function submitMessage(): void {
if (props.disabled || props.isGenerating) { if (props.disabled || props.isGenerating) {
return; return;
@@ -130,26 +153,35 @@ function submitMessage(): void {
} }
.input-wrap { .input-wrap {
--td-input-bg-color: transparent;
--td-button-border-radius: 1rem;
gap: .5rem; gap: .75rem;
border: 1px solid var(--td-component-border);
margin: .5rem 0 0; margin: .5rem 0 0;
display: flex; display: flex;
padding: .25rem .25rem .25rem .75rem;
box-shadow: 0 .5rem 1.5rem rgba(15, 23, 42, .12);
align-items: center; align-items: center;
border-radius: 1.25rem;
.input { .form {
min-width: 0; --td-input-bg-color: transparent;
--td-button-border-radius: 1rem;
gap: .5rem;
flex: 1; flex: 1;
} border: 1px solid var(--td-component-border);
margin: 0;
display: flex;
padding: .25rem .25rem .25rem .75rem;
box-shadow: 0 .5rem 1.5rem rgba(15, 23, 42, .12);
align-items: center;
border-radius: 1.25rem;
.send { .input {
width: 4rem; flex: 1;
flex: none; min-width: 0;
}
.send {
width: 4rem;
flex: none;
}
} }
} }
} }
+3
View File
@@ -57,6 +57,7 @@
:on-send="submitMessage" :on-send="submitMessage"
:on-stop="() => emit('stop')" :on-stop="() => emit('stop')"
:on-retry="() => emit('retry')" :on-retry="() => emit('retry')"
:on-new-conversation="() => emit('new-conversation')"
> >
<ai-chat-composer <ai-chat-composer
v-model="draft" v-model="draft"
@@ -72,6 +73,7 @@
@send="submitMessage" @send="submitMessage"
@stop="emit('stop')" @stop="emit('stop')"
@retry="emit('retry')" @retry="emit('retry')"
@new-conversation="emit('new-conversation')"
/> />
</slot> </slot>
</div> </div>
@@ -141,6 +143,7 @@ const emit = defineEmits<{
send: [message: string]; send: [message: string];
stop: []; stop: [];
retry: []; retry: [];
"new-conversation": [];
}>(); }>();
const messageListRef = useTemplateRef<{ scrollToBottom: () => Promise<void> }>("messageList"); const messageListRef = useTemplateRef<{ scrollToBottom: () => Promise<void> }>("messageList");