Files
timi-tdesign-mobile/src/components/ai/AiChatComposer.vue
T
2026-10-12 16:18:06 +08:00

232 lines
4.6 KiB
Vue

<template>
<div class="composer">
<div v-if="props.lastError" class="error-bar">
<slot name="error" :error="props.lastError" :can-retry="props.canRetry">
<span class="error-text" v-text="props.lastError" />
<t-button
v-if="props.canRetry"
variant="text"
size="small"
:content="props.retryText"
@click="emit('retry')"
/>
</slot>
</div>
<t-form class="form glass-white" @submit="submitMessage">
<t-textarea
v-model="draft"
class="input"
:autosize="{ minRows: 3, maxRows: 6 }"
:disabled="props.isGenerating || props.disabled"
:maxlength="props.maxLength"
:placeholder="props.placeholder"
/>
<div class="actions">
<div class="tools">
<t-button
class="tool"
type="button"
size="extra-small"
variant="outline"
aria-label="新会话"
content="新会话"
:disabled="props.isGenerating || props.disabled"
@click="startNewConversation"
>
<template #icon>
<t-icon name="chat-add" size="small" />
</template>
</t-button>
<t-button
class="tool"
type="button"
size="extra-small"
variant="outline"
aria-label="上传附件"
content="上传附件"
:disabled="props.isGenerating || props.disabled"
@click="addAttachment"
>
<template #icon>
<t-icon name="file-add" size="small" />
</template>
</t-button>
</div>
<slot
v-if="props.isGenerating"
name="stop"
:disabled="props.disabled"
:on-stop="() => emit('stop')"
>
<t-button
class="send"
type="button"
size="extra-small"
theme="danger"
:disabled="props.disabled"
: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="extra-small"
theme="primary"
:disabled="props.disabled"
:content="props.sendText"
/>
</slot>
</div>
</t-form>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "AiChatComposer"
});
export interface Props {
isGenerating: boolean;
lastError?: string;
canRetry?: boolean;
disabled?: boolean;
placeholder?: string;
sendText?: string;
stopText?: string;
retryText?: string;
maxLength?: number;
}
const props = withDefaults(defineProps<Props>(), {
lastError: "",
canRetry: false,
disabled: false,
placeholder: "输入消息…",
sendText: "发送",
stopText: "停止",
retryText: "重试",
maxLength: 2000
});
const draft = defineModel<string>({ required: true });
const emit = defineEmits<{
send: [message: string];
stop: [];
retry: [];
"new-conversation": [];
"add-attachment": [];
}>();
function startNewConversation(): void {
if (props.disabled || props.isGenerating) {
return;
}
draft.value = "";
emit("new-conversation");
}
function addAttachment(): void {
if (props.disabled || props.isGenerating) {
return;
}
emit("add-attachment");
}
function submitMessage(): void {
if (props.disabled || props.isGenerating) {
return;
}
const message = draft.value.trim();
if (!message) {
return;
}
draft.value = "";
emit("send", message);
}
</script>
<style scoped lang="less">
.composer {
width: 100%;
.error-bar {
gap: .375rem;
display: flex;
padding: .5rem .75rem;
border-radius: .75rem;
color: var(--ai-chat-error-color, #b42318);
font-size: .75rem;
background: var(--ai-chat-error-background, #fef3f2);
align-items: center;
justify-content: space-between;
.error-text {
min-width: 0;
word-break: break-word;
}
.t-button {
color: inherit;
flex: none;
}
}
.form {
--td-textarea-background-color: transparent;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border: 1px solid var(--td-component-border);
margin: .5rem 0 0;
display: flex;
padding: .5rem;
box-shadow: 0 .5rem 1.5rem rgba(15, 23, 42, .12);
align-items: stretch;
border-radius: 1.25rem;
flex-direction: column;
.input {
flex: 1;
width: 100%;
padding: 0 .25rem;
min-width: 0;
}
.actions {
--td-button-border-radius: .75rem;
gap: .5rem;
margin-top: .375rem;
display: flex;
align-items: center;
justify-content: space-between;
.tools {
gap: .5rem;
display: flex;
align-items: center;
}
.send {
padding: 0 1rem;
}
}
}
}
:global(html.theme-dark) {
.error-bar {
color: var(--ai-chat-error-color-dark, #fda29b);
background: var(--ai-chat-error-background-dark, rgba(180, 35, 24, .18));
}
}
</style>