326 lines
6.3 KiB
Vue

<script setup lang="ts">
import {
ArrowUp,
Bot,
Box,
Building2,
Camera,
ClipboardCheck,
FileSearch,
Mic,
Search,
} from "@lucide/vue";
import { featureFlags } from "@/config/features";
defineProps<{ modelValue: string; loading: boolean; canUpload: boolean }>();
const emit = defineEmits<{
"update:modelValue": [value: string];
submit: [];
select: [value: string];
tool: [
value:
| "assessment"
| "supplier"
| "material"
| "cases"
| "solution"
| "review"
| "ocr"
| "voice",
];
}>();
const functions: Array<{
title: string;
description: string;
icon: typeof Search;
tone: string;
prompt?: string;
tool:
"assessment" | "supplier" | "material" | "cases" | "solution" | "review";
}> = [
{
title: "评估新品",
description: "含品类+频次",
icon: Search,
tone: "blue",
prompt: "帮我评估一款新品的综合风险",
tool: "assessment",
},
{
title: "查供应商",
description: "深度画像+分层",
icon: Building2,
tone: "green",
prompt: "帮我查询供应商风险画像",
tool: "supplier",
},
{
title: "查物料",
description: "品类常见问题",
icon: Box,
tone: "amber",
prompt: "帮我查询物料常见风险",
tool: "material",
},
{
title: "搜案例",
description: "语义检索",
icon: FileSearch,
tone: "red",
prompt: "搜索相似质量异常案例",
tool: "cases",
},
{
title: "AI方案",
description: "推理生成",
icon: Bot,
tone: "purple",
prompt: "帮我生成一个新品风险预防方案",
tool: "solution",
},
{
title: "审核队列",
description: "查看待审核风险项",
icon: ClipboardCheck,
tone: "red",
tool: "review",
},
];
</script>
<template>
<div class="composer-wrap">
<form class="composer" @submit.prevent="emit('submit')">
<button
v-if="featureFlags.voiceEntry"
class="composer-icon"
type="button"
title="语音录入"
aria-label="语音录入"
@click="emit('tool', 'voice')"
>
<Mic :size="16" />
</button>
<button
v-if="canUpload"
class="composer-icon"
type="button"
title="拍照识别"
aria-label="拍照识别"
@click="emit('tool', 'ocr')"
>
<Camera :size="16" />
</button>
<textarea
:value="modelValue"
rows="1"
placeholder="输入产品参数或问题…"
aria-label="输入产品参数或问题"
maxlength="2000"
@input="
emit(
'update:modelValue',
($event.target as HTMLTextAreaElement).value,
)
"
@keydown.enter.exact.prevent="emit('submit')"
></textarea>
<button
class="send-button"
type="submit"
:disabled="loading || !modelValue.trim()"
title="发送"
aria-label="发送"
>
<ArrowUp :size="17" />
</button>
</form>
<div class="function-grid">
<button
v-for="item in functions"
:key="item.title"
class="function-card"
type="button"
@click="emit('tool', item.tool)"
>
<span class="function-dot" :class="`function-dot--${item.tone}`"></span>
<component :is="item.icon" :size="13" />
<span
><strong>{{ item.title }}</strong
><small>{{ item.description }}</small></span
>
</button>
</div>
</div>
</template>
<style scoped>
.composer-wrap {
padding: 12px 24px 14px;
background: linear-gradient(
to bottom,
rgb(250 250 250 / 88%),
var(--color-bg) 18%
);
}
.composer {
display: flex;
max-width: var(--content-width);
min-height: 52px;
align-items: flex-end;
gap: 7px;
border: 1px solid var(--color-border);
border-radius: 20px;
margin: 0 auto;
padding: 8px 10px;
background: var(--color-surface);
box-shadow: var(--shadow-md);
}
.composer:focus-within {
border-color: var(--color-border-strong);
box-shadow: var(--shadow-lg);
}
.composer textarea {
min-width: 0;
min-height: 32px;
max-height: 92px;
flex: 1;
resize: none;
border: 0;
padding: 6px 3px;
background: transparent;
color: var(--color-text);
font: inherit;
line-height: 1.45;
outline: 0;
}
.composer textarea::placeholder {
color: var(--color-text-faint);
}
.composer-icon,
.send-button {
display: grid;
width: 34px;
height: 34px;
flex: 0 0 34px;
place-items: center;
border: 0;
border-radius: 50%;
}
.composer-icon {
background: var(--color-bg-warm);
color: var(--color-text-muted);
}
.composer-icon:hover {
background: var(--color-border);
color: var(--color-text);
}
.send-button {
background: var(--color-text);
color: white;
}
.send-button:disabled {
opacity: 0.35;
}
.function-grid {
display: grid;
max-width: var(--content-width);
margin: 8px auto 0;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 6px;
}
.function-card {
display: flex;
min-width: 0;
min-height: 44px;
align-items: center;
gap: 6px;
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: 9px;
padding: 7px 8px;
background: var(--color-surface);
color: var(--color-text-secondary);
text-align: left;
}
.function-card:hover {
border-color: var(--color-border-strong);
box-shadow: var(--shadow-sm);
}
.function-card > span:last-child {
display: grid;
min-width: 0;
}
.function-card strong,
.function-card small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.function-card strong {
color: var(--color-text);
font-size: 10px;
}
.function-card small {
color: var(--color-text-faint);
font-size: 8px;
}
.function-dot {
width: 6px;
height: 6px;
flex: 0 0 6px;
border-radius: 50%;
}
.function-dot--blue {
background: var(--color-accent);
}
.function-dot--green {
background: var(--color-success);
}
.function-dot--amber {
background: var(--color-warning);
}
.function-dot--red {
background: var(--color-danger);
}
.function-dot--purple {
background: var(--color-purple);
}
@media (max-width: 760px) {
.composer-wrap {
padding-inline: 10px;
}
.function-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.composer-icon:nth-child(2) {
display: none;
}
}
</style>