232 lines
4.6 KiB
Vue
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>
|