@liujitcn/kratos-admin-system 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/dist/package/declarations/index.d.ts +1 -0
- package/dist/package/declarations/src/ai.d.ts +10 -0
- package/dist/package/declarations/src/api/base/ai_message.d.ts +27 -0
- package/dist/package/declarations/src/api/base/ai_session.d.ts +17 -0
- package/dist/package/declarations/src/api/base/ai_tool.d.ts +7 -0
- package/dist/package/declarations/src/api/base/oauth.d.ts +13 -0
- package/dist/package/declarations/src/api/base/sse.d.ts +31 -0
- package/dist/package/declarations/src/api/system/auth.d.ts +17 -0
- package/dist/package/declarations/src/api/system/base_api.d.ts +22 -0
- package/dist/package/declarations/src/api/system/base_area.d.ts +19 -0
- package/dist/package/declarations/src/api/system/base_config.d.ts +20 -0
- package/dist/package/declarations/src/api/system/base_dept.d.ts +21 -0
- package/dist/package/declarations/src/api/system/base_dict.d.ts +32 -0
- package/dist/package/declarations/src/api/system/base_job.d.ts +28 -0
- package/dist/package/declarations/src/api/system/base_log.d.ts +9 -0
- package/dist/package/declarations/src/api/system/base_menu.d.ts +21 -0
- package/dist/package/declarations/src/api/system/base_migration.d.ts +10 -0
- package/dist/package/declarations/src/api/system/base_post.d.ts +21 -0
- package/dist/package/declarations/src/api/system/base_role.d.ts +23 -0
- package/dist/package/declarations/src/api/system/base_tenant.d.ts +21 -0
- package/dist/package/declarations/src/api/system/base_user.d.ts +23 -0
- package/dist/package/declarations/src/api/system/code_gen.d.ts +14 -0
- package/dist/package/declarations/src/api/system/code_gen_column.d.ts +12 -0
- package/dist/package/declarations/src/api/system/code_gen_proto.d.ts +10 -0
- package/dist/package/declarations/src/api/system/code_gen_sse.d.ts +8 -0
- package/dist/package/declarations/src/api/system/code_gen_table.d.ts +18 -0
- package/dist/package/declarations/src/api/system/project_document.d.ts +9 -0
- package/dist/package/declarations/src/components/Ai.vue.d.ts +3 -0
- package/dist/package/declarations/src/index.d.ts +3 -0
- package/dist/package/declarations/src/module.d.ts +2 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_message.d.ts +82 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_session.d.ts +176 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_tool.d.ts +59 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +167 -0
- package/dist/package/declarations/src/rpc/base/v1/sse.d.ts +13 -0
- package/dist/package/declarations/src/rpc/common/v1/common.d.ts +57 -0
- package/dist/package/declarations/src/rpc/common/v1/enum.d.ts +58 -0
- package/dist/package/declarations/src/rpc/common/v1/types.d.ts +60 -0
- package/dist/package/declarations/src/rpc/google/protobuf/empty.d.ts +11 -0
- package/dist/package/declarations/src/rpc/google/protobuf/timestamp.d.ts +107 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +156 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_api.d.ts +193 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_area.d.ts +82 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +112 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dept.d.ts +112 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +215 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_job.d.ts +177 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_log.d.ts +87 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +151 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_migration.d.ts +64 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_post.d.ts +112 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_role.d.ts +128 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant.d.ts +112 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_user.d.ts +161 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +155 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_column.d.ts +143 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_proto.d.ts +86 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_table.d.ts +149 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/project_document.d.ts +64 -0
- package/dist/package/declarations/src/rpc/system/common/v1/enum.d.ts +67 -0
- package/dist/package/declarations/src/views/ai/chat/attachment.d.ts +14 -0
- package/dist/package/declarations/src/views/ai/chat/components/AiMarkdown.vue.d.ts +13 -0
- package/dist/package/declarations/src/views/ai/chat/components/ChatPanel.vue.d.ts +45 -0
- package/dist/package/declarations/src/views/ai/chat/components/SessionPanel.vue.d.ts +34 -0
- package/dist/package/declarations/src/views/ai/chat/components/XSender.vue.d.ts +12 -0
- package/dist/package/declarations/src/views/ai/chat/index.vue.d.ts +4 -0
- package/dist/package/declarations/src/views/ai/chat/message.d.ts +59 -0
- package/dist/package/declarations/src/views/ai/chat/stream.d.ts +10 -0
- package/dist/package/declarations/src/views/ai/chat/types.d.ts +115 -0
- package/dist/package/declarations/src/views/base/api/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/area/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/config/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/dept/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/dict/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/dict/item.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/job/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/job/log.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/log/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/menu/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/migration/index.vue.d.ts +4 -0
- package/dist/package/declarations/src/views/base/post/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/role/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/tenant/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/user/components/dept-tree.vue.d.ts +19 -0
- package/dist/package/declarations/src/views/base/user/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/profile/components/base.vue.d.ts +13 -0
- package/dist/package/declarations/src/views/profile/components/password.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/profile/components/security.vue.d.ts +15 -0
- package/dist/package/declarations/src/views/profile/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/tool/api-doc/index.vue.d.ts +4 -0
- package/dist/package/declarations/src/views/tool/code-gen/code-preview/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/tool/code-gen/columns/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/tool/code-gen/columns/option-copy.d.ts +25 -0
- package/dist/package/declarations/src/views/tool/code-gen/components/CodeGenProgressDialog.vue.d.ts +16 -0
- package/dist/package/declarations/src/views/tool/code-gen/components/CodePreviewPane.vue.d.ts +9 -0
- package/dist/package/declarations/src/views/tool/code-gen/config.d.ts +32 -0
- package/dist/package/declarations/src/views/tool/code-gen/preview/capabilities.d.ts +20 -0
- package/dist/package/declarations/src/views/tool/code-gen/preview/data.d.ts +42 -0
- package/dist/package/declarations/src/views/tool/code-gen/preview/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/tool/code-gen/proto/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/tool/code-gen/table/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/tool/project-doc/index.vue.d.ts +4 -0
- package/dist/package/src/ai.ts +16 -0
- package/dist/package/src/api/base/ai_message.ts +139 -0
- package/dist/package/src/api/base/ai_session.ts +77 -0
- package/dist/package/src/api/base/ai_tool.ts +22 -0
- package/dist/package/src/api/base/oauth.ts +47 -0
- package/dist/package/src/api/base/sse.ts +192 -0
- package/dist/package/src/api/system/auth.ts +65 -0
- package/dist/package/src/api/system/base_api.ts +95 -0
- package/dist/package/src/api/system/base_area.ts +73 -0
- package/dist/package/src/api/system/base_config.ts +82 -0
- package/dist/package/src/api/system/base_dept.ts +83 -0
- package/dist/package/src/api/system/base_dict.ts +144 -0
- package/dist/package/src/api/system/base_job.ts +125 -0
- package/dist/package/src/api/system/base_log.ts +32 -0
- package/dist/package/src/api/system/base_menu.ts +83 -0
- package/dist/package/src/api/system/base_migration.ts +35 -0
- package/dist/package/src/api/system/base_post.ts +83 -0
- package/dist/package/src/api/system/base_role.ts +93 -0
- package/dist/package/src/api/system/base_tenant.ts +83 -0
- package/dist/package/src/api/system/base_user.ts +93 -0
- package/dist/package/src/api/system/code_gen.ts +56 -0
- package/dist/package/src/api/system/code_gen_column.ts +43 -0
- package/dist/package/src/api/system/code_gen_proto.ts +32 -0
- package/dist/package/src/api/system/code_gen_sse.ts +33 -0
- package/dist/package/src/api/system/code_gen_table.ts +74 -0
- package/dist/package/src/api/system/project_document.ts +33 -0
- package/dist/package/src/components/Ai.vue +57 -0
- package/dist/package/src/index.ts +3 -0
- package/dist/package/src/module.ts +17 -0
- package/dist/package/src/rpc/base/v1/ai_message.ts +98 -0
- package/dist/package/src/rpc/base/v1/ai_session.ts +214 -0
- package/dist/package/src/rpc/base/v1/ai_tool.ts +74 -0
- package/dist/package/src/rpc/base/v1/oauth.ts +199 -0
- package/dist/package/src/rpc/base/v1/sse.ts +22 -0
- package/dist/package/src/rpc/common/v1/common.ts +71 -0
- package/dist/package/src/rpc/common/v1/enum.ts +71 -0
- package/dist/package/src/rpc/common/v1/types.ts +77 -0
- package/dist/package/src/rpc/google/protobuf/empty.ts +19 -0
- package/dist/package/src/rpc/google/protobuf/timestamp.ts +115 -0
- package/dist/package/src/rpc/system/admin/v1/auth.ts +208 -0
- package/dist/package/src/rpc/system/admin/v1/base_api.ts +241 -0
- package/dist/package/src/rpc/system/admin/v1/base_area.ts +105 -0
- package/dist/package/src/rpc/system/admin/v1/base_config.ts +140 -0
- package/dist/package/src/rpc/system/admin/v1/base_dept.ts +140 -0
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +249 -0
- package/dist/package/src/rpc/system/admin/v1/base_job.ts +206 -0
- package/dist/package/src/rpc/system/admin/v1/base_log.ts +101 -0
- package/dist/package/src/rpc/system/admin/v1/base_menu.ts +195 -0
- package/dist/package/src/rpc/system/admin/v1/base_migration.ts +81 -0
- package/dist/package/src/rpc/system/admin/v1/base_post.ts +134 -0
- package/dist/package/src/rpc/system/admin/v1/base_role.ts +151 -0
- package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +134 -0
- package/dist/package/src/rpc/system/admin/v1/base_user.ts +190 -0
- package/dist/package/src/rpc/system/admin/v1/code_gen.ts +176 -0
- package/dist/package/src/rpc/system/admin/v1/code_gen_column.ts +170 -0
- package/dist/package/src/rpc/system/admin/v1/code_gen_proto.ts +104 -0
- package/dist/package/src/rpc/system/admin/v1/code_gen_table.ts +179 -0
- package/dist/package/src/rpc/system/admin/v1/project_document.ts +79 -0
- package/dist/package/src/rpc/system/common/v1/enum.ts +80 -0
- package/dist/package/src/typings/swagger-ui-dist.d.ts +28 -0
- package/dist/package/src/typings/x-markdown-vue.d.ts +1 -0
- package/dist/package/src/views/ai/chat/attachment.ts +65 -0
- package/dist/package/src/views/ai/chat/components/AiMarkdown.vue +162 -0
- package/dist/package/src/views/ai/chat/components/ChatPanel.vue +1861 -0
- package/dist/package/src/views/ai/chat/components/SessionPanel.vue +316 -0
- package/dist/package/src/views/ai/chat/components/XSender.vue +554 -0
- package/dist/package/src/views/ai/chat/index.vue +848 -0
- package/dist/package/src/views/ai/chat/message.ts +416 -0
- package/dist/package/src/views/ai/chat/stream.ts +115 -0
- package/dist/package/src/views/ai/chat/types.ts +126 -0
- package/dist/package/src/views/base/api/index.vue +455 -0
- package/dist/package/src/views/base/area/index.vue +269 -0
- package/dist/package/src/views/base/config/index.vue +530 -0
- package/dist/package/src/views/base/dept/index.vue +461 -0
- package/dist/package/src/views/base/dict/index.vue +304 -0
- package/dist/package/src/views/base/dict/item.vue +373 -0
- package/dist/package/src/views/base/job/index.vue +535 -0
- package/dist/package/src/views/base/job/log.vue +202 -0
- package/dist/package/src/views/base/log/index.vue +317 -0
- package/dist/package/src/views/base/menu/index.vue +944 -0
- package/dist/package/src/views/base/migration/index.vue +791 -0
- package/dist/package/src/views/base/post/index.vue +324 -0
- package/dist/package/src/views/base/role/index.vue +684 -0
- package/dist/package/src/views/base/tenant/index.vue +328 -0
- package/dist/package/src/views/base/user/components/dept-tree.vue +75 -0
- package/dist/package/src/views/base/user/index.vue +783 -0
- package/dist/package/src/views/profile/components/base.vue +320 -0
- package/dist/package/src/views/profile/components/password.vue +269 -0
- package/dist/package/src/views/profile/components/security.vue +443 -0
- package/dist/package/src/views/profile/index.vue +199 -0
- package/dist/package/src/views/tool/api-doc/index.vue +275 -0
- package/dist/package/src/views/tool/code-gen/code-preview/index.vue +233 -0
- package/dist/package/src/views/tool/code-gen/columns/index.vue +1456 -0
- package/dist/package/src/views/tool/code-gen/columns/option-copy.ts +125 -0
- package/dist/package/src/views/tool/code-gen/components/CodeGenProgressDialog.vue +328 -0
- package/dist/package/src/views/tool/code-gen/components/CodePreviewPane.vue +42 -0
- package/dist/package/src/views/tool/code-gen/config.ts +175 -0
- package/dist/package/src/views/tool/code-gen/preview/capabilities.ts +34 -0
- package/dist/package/src/views/tool/code-gen/preview/data.ts +331 -0
- package/dist/package/src/views/tool/code-gen/preview/index.vue +831 -0
- package/dist/package/src/views/tool/code-gen/proto/index.vue +703 -0
- package/dist/package/src/views/tool/code-gen/table/index.vue +864 -0
- package/dist/package/src/views/tool/project-doc/index.vue +561 -0
- package/package.json +60 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="agent-sender-wrap">
|
|
3
|
+
<BaseXSender
|
|
4
|
+
ref="senderRef"
|
|
5
|
+
class="agent-sender"
|
|
6
|
+
variant="updown"
|
|
7
|
+
submit-type="enter"
|
|
8
|
+
placeholder="输入任何问题,或上传附件后继续提问"
|
|
9
|
+
:loading="sending"
|
|
10
|
+
:clearable="true"
|
|
11
|
+
:tip-config="false"
|
|
12
|
+
@change="handleInputChange"
|
|
13
|
+
@submit="handleSubmit"
|
|
14
|
+
@paste-file="handlePasteFile"
|
|
15
|
+
>
|
|
16
|
+
<template v-if="selectedAttachments.length" #header>
|
|
17
|
+
<Attachments
|
|
18
|
+
class="agent-attachments"
|
|
19
|
+
:items="attachmentItems"
|
|
20
|
+
overflow="wrap"
|
|
21
|
+
:hide-upload="true"
|
|
22
|
+
@delete-card="handleDeleteCard"
|
|
23
|
+
/>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<template #prefix>
|
|
27
|
+
<div class="agent-prefix-actions">
|
|
28
|
+
<el-popover placement="top-start" :width="236" trigger="click" popper-class="agent-sender-popover">
|
|
29
|
+
<template #reference>
|
|
30
|
+
<button class="agent-icon-button" type="button" :disabled="sending || uploading" aria-label="上传附件">
|
|
31
|
+
<el-icon :class="{ 'is-loading': uploading }">
|
|
32
|
+
<Loading v-if="uploading" />
|
|
33
|
+
<Paperclip v-else />
|
|
34
|
+
</el-icon>
|
|
35
|
+
</button>
|
|
36
|
+
</template>
|
|
37
|
+
<div class="agent-popover-card">
|
|
38
|
+
<div class="agent-popover-title">上传附件</div>
|
|
39
|
+
<div class="agent-popover-desc">可补充图片或文本类附件,发送后用于当前问题分析。</div>
|
|
40
|
+
<button class="agent-popover-action" type="button" :disabled="sending || uploading" @click="handleSelectAttachment">
|
|
41
|
+
{{ uploading ? "上传中..." : "选择本地文件" }}
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
</el-popover>
|
|
45
|
+
|
|
46
|
+
<span class="agent-action-text">{{ actionHintText }}</span>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<template #action-list>
|
|
51
|
+
<div class="agent-sender-actions">
|
|
52
|
+
<el-tooltip :content="recording ? '停止语音输入' : '语音输入'" placement="top">
|
|
53
|
+
<button
|
|
54
|
+
class="agent-icon-button"
|
|
55
|
+
:class="{ 'is-active': recording }"
|
|
56
|
+
type="button"
|
|
57
|
+
:disabled="sending"
|
|
58
|
+
:aria-pressed="recording"
|
|
59
|
+
:aria-label="recording ? '停止语音输入' : '语音输入'"
|
|
60
|
+
@click="handleToggleRecord"
|
|
61
|
+
>
|
|
62
|
+
<el-icon :class="{ 'is-loading': recording }">
|
|
63
|
+
<Loading v-if="recording" />
|
|
64
|
+
<Microphone v-else />
|
|
65
|
+
</el-icon>
|
|
66
|
+
</button>
|
|
67
|
+
</el-tooltip>
|
|
68
|
+
<el-tooltip content="发送" placement="top">
|
|
69
|
+
<button
|
|
70
|
+
class="agent-send-button"
|
|
71
|
+
type="button"
|
|
72
|
+
:disabled="sending || uploading || recording || isSubmitDisabled"
|
|
73
|
+
aria-label="发送"
|
|
74
|
+
@click="handleSubmit()"
|
|
75
|
+
>
|
|
76
|
+
<el-icon v-if="!sending"><Promotion /></el-icon>
|
|
77
|
+
<el-icon v-else class="is-loading"><Loading /></el-icon>
|
|
78
|
+
</button>
|
|
79
|
+
</el-tooltip>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
82
|
+
</BaseXSender>
|
|
83
|
+
|
|
84
|
+
<input
|
|
85
|
+
ref="fileInputRef"
|
|
86
|
+
class="agent-file-input"
|
|
87
|
+
type="file"
|
|
88
|
+
multiple
|
|
89
|
+
:accept="acceptedAttachmentTypes"
|
|
90
|
+
@change="handleFileChange"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<script setup lang="ts" name="XSender">
|
|
96
|
+
import { computed, ref, watch } from "vue";
|
|
97
|
+
import { Attachments, useRecord, XSender as BaseXSender } from "vue-element-plus-x";
|
|
98
|
+
import type { FilesCardProps } from "vue-element-plus-x/types/FilesCard";
|
|
99
|
+
import { Loading, Microphone, Paperclip, Promotion } from "@element-plus/icons-vue";
|
|
100
|
+
import { ElMessage } from "element-plus";
|
|
101
|
+
import { defFileService } from "@liujitcn/kratos-admin-core/api/base/file";
|
|
102
|
+
import type { AiAttachment } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
103
|
+
import type { SubmitPayload } from "../types";
|
|
104
|
+
import { buildAIAttachmentFileCard } from "../attachment";
|
|
105
|
+
|
|
106
|
+
/** 语音识别错误的最小字段,兼容浏览器事件和不支持错误。 */
|
|
107
|
+
type RecordError = {
|
|
108
|
+
code?: number;
|
|
109
|
+
error?: string;
|
|
110
|
+
message?: string;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const props = defineProps<{
|
|
114
|
+
/** 消息发送加载状态。 */
|
|
115
|
+
sending: boolean;
|
|
116
|
+
}>();
|
|
117
|
+
|
|
118
|
+
const emit = defineEmits<{
|
|
119
|
+
/** 提交输入器内容。 */
|
|
120
|
+
submit: [payload: SubmitPayload];
|
|
121
|
+
}>();
|
|
122
|
+
|
|
123
|
+
const senderRef = ref<InstanceType<typeof BaseXSender>>();
|
|
124
|
+
const fileInputRef = ref<HTMLInputElement>();
|
|
125
|
+
const inputText = ref("");
|
|
126
|
+
const selectedAttachments = ref<AiAttachment[]>([]);
|
|
127
|
+
const uploading = ref(false);
|
|
128
|
+
const maxAttachmentCount = 6;
|
|
129
|
+
const maxAttachmentSizeMB = 20;
|
|
130
|
+
const maxAttachmentSize = maxAttachmentSizeMB * 1024 * 1024;
|
|
131
|
+
const acceptedAttachmentExtensions = [
|
|
132
|
+
".txt",
|
|
133
|
+
".md",
|
|
134
|
+
".markdown",
|
|
135
|
+
".log",
|
|
136
|
+
".json",
|
|
137
|
+
".xml",
|
|
138
|
+
".csv",
|
|
139
|
+
".png",
|
|
140
|
+
".jpg",
|
|
141
|
+
".jpeg",
|
|
142
|
+
".gif",
|
|
143
|
+
".webp"
|
|
144
|
+
];
|
|
145
|
+
const acceptedAttachmentTypes = acceptedAttachmentExtensions.join(",");
|
|
146
|
+
|
|
147
|
+
const {
|
|
148
|
+
loading: recording,
|
|
149
|
+
start: startRecord,
|
|
150
|
+
stop: stopRecord,
|
|
151
|
+
value: recordText
|
|
152
|
+
} = useRecord({
|
|
153
|
+
onEnd: handleRecordEnd,
|
|
154
|
+
onError: handleRecordError
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const actionHintText = computed(() => {
|
|
158
|
+
if (recording.value) return "正在识别语音...";
|
|
159
|
+
if (uploading.value) return "附件上传中...";
|
|
160
|
+
if (selectedAttachments.value.length) return `已选 ${selectedAttachments.value.length} 个附件`;
|
|
161
|
+
return "可上传附件";
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const attachmentItems = computed<FilesCardProps[]>(() =>
|
|
165
|
+
selectedAttachments.value.map(item =>
|
|
166
|
+
buildAIAttachmentFileCard(item, {
|
|
167
|
+
showDelIcon: true,
|
|
168
|
+
maxWidth: "220px"
|
|
169
|
+
})
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const isSubmitDisabled = computed(() => {
|
|
174
|
+
return uploading.value || (!inputText.value.trim() && selectedAttachments.value.length === 0);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
/** 读取输入内容并发送给父组件。 */
|
|
178
|
+
function handleSubmit() {
|
|
179
|
+
if (uploading.value) return;
|
|
180
|
+
if (recording.value) {
|
|
181
|
+
stopRecord();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const trimmedText = inputText.value.trim();
|
|
185
|
+
if (!trimmedText && selectedAttachments.value.length === 0) return;
|
|
186
|
+
|
|
187
|
+
emit("submit", {
|
|
188
|
+
text: trimmedText || "请结合附件内容继续分析",
|
|
189
|
+
attachments: [...selectedAttachments.value]
|
|
190
|
+
});
|
|
191
|
+
inputText.value = "";
|
|
192
|
+
selectedAttachments.value = [];
|
|
193
|
+
senderRef.value?.clear();
|
|
194
|
+
resetFileInput();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** 切换浏览器语音识别状态。 */
|
|
198
|
+
function handleToggleRecord() {
|
|
199
|
+
if (recording.value) {
|
|
200
|
+
stopRecord();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
startRecord();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** 语音识别结束后保留当前识别文本。 */
|
|
207
|
+
function handleRecordEnd(result: string) {
|
|
208
|
+
setRecordText(result);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** 处理语音识别失败,提示浏览器不支持或麦克风授权异常。 */
|
|
212
|
+
function handleRecordError(error: RecordError) {
|
|
213
|
+
const message = resolveRecordErrorMessage(error);
|
|
214
|
+
ElMessage.warning(message);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** 将识别文本与识别前内容合并后写回输入器。 */
|
|
218
|
+
function setRecordText(recordText: string) {
|
|
219
|
+
const normalizedRecordText = normalizeRecordText(recordText);
|
|
220
|
+
if (!normalizedRecordText) return;
|
|
221
|
+
senderRef.value?.setText(normalizedRecordText);
|
|
222
|
+
inputText.value = normalizedRecordText;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** 标准化语音识别文本,压掉多余空白。 */
|
|
226
|
+
function normalizeRecordText(recordText: string) {
|
|
227
|
+
return collapseCumulativeRecordText(recordText.replace(/\s+/g, " ").trim());
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** 压缩 useRecord 在中文连续识别时返回的前缀累计文本。 */
|
|
231
|
+
function collapseCumulativeRecordText(text: string) {
|
|
232
|
+
if (text.length < 4) return text;
|
|
233
|
+
for (let candidateLength = 2; candidateLength <= Math.floor(text.length / 2); candidateLength++) {
|
|
234
|
+
const candidate = text.slice(-candidateLength);
|
|
235
|
+
const matchResult = matchCumulativeRecordCandidate(text, candidate);
|
|
236
|
+
if (matchResult.matched && (matchResult.hasPartial || matchResult.segmentCount >= 3)) {
|
|
237
|
+
return candidate;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return text;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** 判断文本是否由同一识别结果的前缀片段累计组成。 */
|
|
244
|
+
function matchCumulativeRecordCandidate(text: string, candidate: string) {
|
|
245
|
+
let index = 0;
|
|
246
|
+
let segmentCount = 0;
|
|
247
|
+
let hasPartial = false;
|
|
248
|
+
while (index < text.length) {
|
|
249
|
+
let matchedLength = 0;
|
|
250
|
+
const maxLength = Math.min(candidate.length, text.length - index);
|
|
251
|
+
for (let length = maxLength; length >= 1; length--) {
|
|
252
|
+
if (candidate.startsWith(text.slice(index, index + length))) {
|
|
253
|
+
matchedLength = length;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (!matchedLength) return { matched: false, segmentCount, hasPartial };
|
|
258
|
+
if (matchedLength < candidate.length) hasPartial = true;
|
|
259
|
+
segmentCount++;
|
|
260
|
+
index += matchedLength;
|
|
261
|
+
}
|
|
262
|
+
return { matched: true, segmentCount, hasPartial };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** 根据浏览器语音识别错误类型生成用户可理解的提示。 */
|
|
266
|
+
function resolveRecordErrorMessage(error: RecordError) {
|
|
267
|
+
if (error.code === -1) return "当前浏览器不支持语音识别";
|
|
268
|
+
const errorName = error.error ?? "";
|
|
269
|
+
if (["not-allowed", "service-not-allowed", "permission-denied"].includes(errorName)) return "麦克风授权失败,请检查浏览器权限";
|
|
270
|
+
return "语音识别失败,请稍后重试";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** 同步输入器内部文本,保证发送按钮禁用态能实时响应。 */
|
|
274
|
+
function handleInputChange() {
|
|
275
|
+
inputText.value = senderRef.value?.getModelValue().text ?? "";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** 打开本地文件选择框。 */
|
|
279
|
+
function handleSelectAttachment() {
|
|
280
|
+
if (uploading.value || props.sending) return;
|
|
281
|
+
fileInputRef.value?.click();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** 将本地文件列表上传后同步到输入器附件区。 */
|
|
285
|
+
async function handleFileChange(event: Event) {
|
|
286
|
+
const target = event.target as HTMLInputElement;
|
|
287
|
+
await uploadAttachments(Array.from(target.files ?? []));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 处理粘贴文件,和点击上传保持同一份附件状态。 */
|
|
291
|
+
async function handlePasteFile(firstFile: File, fileList: FileList) {
|
|
292
|
+
await uploadAttachments([firstFile, ...Array.from(fileList).slice(1)]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** 上传附件并按文件地址去重。 */
|
|
296
|
+
async function uploadAttachments(files: File[]) {
|
|
297
|
+
if (uploading.value || props.sending) return;
|
|
298
|
+
const uploadFiles = filterUploadFiles(files);
|
|
299
|
+
if (!uploadFiles.length) return;
|
|
300
|
+
uploading.value = true;
|
|
301
|
+
try {
|
|
302
|
+
const response = await defFileService.MultiUploadFile(uploadFiles, "ai");
|
|
303
|
+
const fileMap = new Map<string, File[]>();
|
|
304
|
+
uploadFiles.forEach(file => {
|
|
305
|
+
const group = fileMap.get(file.name) ?? [];
|
|
306
|
+
group.push(file);
|
|
307
|
+
fileMap.set(file.name, group);
|
|
308
|
+
});
|
|
309
|
+
const attachmentMap = new Map(selectedAttachments.value.map(item => [item.url || item.id, item]));
|
|
310
|
+
response?.files?.forEach(item => {
|
|
311
|
+
const matchedFile = fileMap.get(item.name || "")?.shift();
|
|
312
|
+
const attachment = buildAttachmentItem(item.url || "", item.name || "", matchedFile);
|
|
313
|
+
attachmentMap.set(attachment.url || attachment.id, attachment);
|
|
314
|
+
});
|
|
315
|
+
selectedAttachments.value = Array.from(attachmentMap.values());
|
|
316
|
+
if (response?.files?.length) {
|
|
317
|
+
ElMessage.success(`已上传 ${response.files.length} 个附件`);
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
ElMessage.error("附件上传失败");
|
|
321
|
+
} finally {
|
|
322
|
+
uploading.value = false;
|
|
323
|
+
resetFileInput();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** 过滤不符合数量和大小约束的附件。 */
|
|
328
|
+
function filterUploadFiles(files: File[]) {
|
|
329
|
+
const remainingCount = maxAttachmentCount - selectedAttachments.value.length;
|
|
330
|
+
if (remainingCount <= 0) {
|
|
331
|
+
ElMessage.warning(`最多上传 ${maxAttachmentCount} 个附件`);
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
const validFiles = files.filter(file => {
|
|
335
|
+
if (!isAcceptedAttachmentFile(file)) {
|
|
336
|
+
ElMessage.warning(`附件「${file.name}」暂不支持,已跳过`);
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
if (file.size > maxAttachmentSize) {
|
|
340
|
+
ElMessage.warning(`附件「${file.name}」超过 ${maxAttachmentSizeMB}MB,已跳过`);
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
});
|
|
345
|
+
if (validFiles.length > remainingCount) {
|
|
346
|
+
ElMessage.warning(`最多还能选择 ${remainingCount} 个附件,已自动截取`);
|
|
347
|
+
}
|
|
348
|
+
return validFiles.slice(0, remainingCount);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** 判断附件类型是否在当前 AI 助手可解析范围内。 */
|
|
352
|
+
function isAcceptedAttachmentFile(file: File) {
|
|
353
|
+
const fileName = file.name.toLowerCase();
|
|
354
|
+
return acceptedAttachmentExtensions.some(extension => fileName.endsWith(extension));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** 根据上传结果构建附件展示项。 */
|
|
358
|
+
function buildAttachmentItem(url: string, name: string, file?: File): AiAttachment {
|
|
359
|
+
return {
|
|
360
|
+
id: url || `${name}-${file?.size ?? 0}-${file?.lastModified ?? Date.now()}`,
|
|
361
|
+
name,
|
|
362
|
+
size: file?.size ?? 0,
|
|
363
|
+
url,
|
|
364
|
+
mime_type: file?.type ?? ""
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** 复用 Attachments 删除事件,保持附件状态与组件展示同步。 */
|
|
369
|
+
function handleDeleteCard(item: { uid?: string | number }) {
|
|
370
|
+
if (!item.uid) return;
|
|
371
|
+
selectedAttachments.value = selectedAttachments.value.filter(attachment => attachment.id !== String(item.uid));
|
|
372
|
+
if (!selectedAttachments.value.length) resetFileInput();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** 清空文件输入框,保证重复选择同名文件时仍能触发 change。 */
|
|
376
|
+
function resetFileInput() {
|
|
377
|
+
if (fileInputRef.value) fileInputRef.value.value = "";
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** 按 useRecord 文档监听识别文本,并同步到 XSender。 */
|
|
381
|
+
watch(
|
|
382
|
+
recordText,
|
|
383
|
+
value => {
|
|
384
|
+
setRecordText(value);
|
|
385
|
+
},
|
|
386
|
+
{ deep: true }
|
|
387
|
+
);
|
|
388
|
+
</script>
|
|
389
|
+
|
|
390
|
+
<style scoped lang="scss">
|
|
391
|
+
.agent-sender-wrap {
|
|
392
|
+
width: 100%;
|
|
393
|
+
}
|
|
394
|
+
.agent-sender {
|
|
395
|
+
:deep(.elx-x-sender) {
|
|
396
|
+
border-radius: var(--admin-page-radius);
|
|
397
|
+
box-shadow: none;
|
|
398
|
+
}
|
|
399
|
+
:deep(.elx-x-sender__content) {
|
|
400
|
+
padding: 8px 10px 10px;
|
|
401
|
+
background: var(--admin-page-card-bg);
|
|
402
|
+
border: 1px solid var(--admin-page-card-border);
|
|
403
|
+
border-radius: var(--admin-page-radius);
|
|
404
|
+
}
|
|
405
|
+
:deep(.elx-x-sender__content--variant-updown) {
|
|
406
|
+
gap: 10px;
|
|
407
|
+
}
|
|
408
|
+
:deep(.chat-rich-text) {
|
|
409
|
+
min-height: 64px;
|
|
410
|
+
max-height: 120px;
|
|
411
|
+
padding: 8px 10px;
|
|
412
|
+
font-size: 14px;
|
|
413
|
+
line-height: 22px;
|
|
414
|
+
}
|
|
415
|
+
:deep(.chat-placeholder-wrap) {
|
|
416
|
+
padding: 8px 10px;
|
|
417
|
+
font-size: 14px;
|
|
418
|
+
font-weight: 400;
|
|
419
|
+
}
|
|
420
|
+
:deep(.elx-x-sender__updown-action-list) {
|
|
421
|
+
align-items: center;
|
|
422
|
+
justify-content: space-between;
|
|
423
|
+
}
|
|
424
|
+
:deep(.elx-x-sender__action-list) {
|
|
425
|
+
height: auto;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
.agent-attachments {
|
|
429
|
+
padding: 12px 12px 0;
|
|
430
|
+
:deep(.elx-files-card) {
|
|
431
|
+
max-width: 220px;
|
|
432
|
+
border-radius: var(--admin-page-radius);
|
|
433
|
+
}
|
|
434
|
+
:deep(.elx-files-card-img),
|
|
435
|
+
:deep(.elx-files-card__image-preview),
|
|
436
|
+
:deep(.elx-files-card-delete-icon) {
|
|
437
|
+
border-radius: var(--admin-page-radius);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
.agent-prefix-actions {
|
|
441
|
+
display: flex;
|
|
442
|
+
gap: 10px;
|
|
443
|
+
align-items: center;
|
|
444
|
+
min-height: 36px;
|
|
445
|
+
}
|
|
446
|
+
.agent-sender-actions {
|
|
447
|
+
display: inline-flex;
|
|
448
|
+
gap: 8px;
|
|
449
|
+
align-items: center;
|
|
450
|
+
}
|
|
451
|
+
.agent-icon-button,
|
|
452
|
+
.agent-send-button {
|
|
453
|
+
display: inline-flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
justify-content: center;
|
|
456
|
+
width: 34px;
|
|
457
|
+
height: 34px;
|
|
458
|
+
color: var(--admin-page-text-secondary);
|
|
459
|
+
cursor: pointer;
|
|
460
|
+
background: var(--admin-page-card-bg);
|
|
461
|
+
border: 1px solid var(--el-border-color);
|
|
462
|
+
border-radius: var(--admin-page-radius);
|
|
463
|
+
transition:
|
|
464
|
+
color 0.2s ease,
|
|
465
|
+
border-color 0.2s ease,
|
|
466
|
+
background-color 0.2s ease,
|
|
467
|
+
opacity 0.2s ease;
|
|
468
|
+
&:hover {
|
|
469
|
+
color: var(--el-color-primary);
|
|
470
|
+
background: var(--el-color-primary-light-9);
|
|
471
|
+
border-color: var(--el-color-primary-light-5);
|
|
472
|
+
}
|
|
473
|
+
&:disabled {
|
|
474
|
+
cursor: not-allowed;
|
|
475
|
+
opacity: 0.55;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
.agent-send-button {
|
|
479
|
+
color: var(--el-color-primary);
|
|
480
|
+
background: var(--el-color-primary-light-9);
|
|
481
|
+
border-color: var(--el-color-primary-light-5);
|
|
482
|
+
}
|
|
483
|
+
.agent-icon-button.is-active {
|
|
484
|
+
color: var(--el-color-primary);
|
|
485
|
+
background: var(--el-color-primary-light-9);
|
|
486
|
+
border-color: var(--el-color-primary-light-5);
|
|
487
|
+
}
|
|
488
|
+
.agent-action-text {
|
|
489
|
+
font-size: 12px;
|
|
490
|
+
line-height: 18px;
|
|
491
|
+
color: var(--admin-page-text-secondary);
|
|
492
|
+
}
|
|
493
|
+
.is-loading {
|
|
494
|
+
animation: rotating 1s linear infinite;
|
|
495
|
+
}
|
|
496
|
+
.agent-file-input {
|
|
497
|
+
display: none;
|
|
498
|
+
}
|
|
499
|
+
:global(.agent-sender-popover) {
|
|
500
|
+
padding: 0 !important;
|
|
501
|
+
border-radius: var(--admin-page-radius) !important;
|
|
502
|
+
}
|
|
503
|
+
.agent-popover-card {
|
|
504
|
+
padding: 14px;
|
|
505
|
+
}
|
|
506
|
+
.agent-popover-title {
|
|
507
|
+
font-size: 14px;
|
|
508
|
+
font-weight: 700;
|
|
509
|
+
color: var(--admin-page-text-primary);
|
|
510
|
+
}
|
|
511
|
+
.agent-popover-desc {
|
|
512
|
+
margin-top: 6px;
|
|
513
|
+
font-size: 12px;
|
|
514
|
+
line-height: 20px;
|
|
515
|
+
color: var(--admin-page-text-secondary);
|
|
516
|
+
}
|
|
517
|
+
.agent-popover-action {
|
|
518
|
+
width: 100%;
|
|
519
|
+
height: 36px;
|
|
520
|
+
margin-top: 12px;
|
|
521
|
+
font-size: 13px;
|
|
522
|
+
font-weight: 600;
|
|
523
|
+
color: var(--el-color-primary);
|
|
524
|
+
cursor: pointer;
|
|
525
|
+
background: var(--el-color-primary-light-9);
|
|
526
|
+
border: 0;
|
|
527
|
+
border-radius: var(--admin-page-radius);
|
|
528
|
+
&:disabled {
|
|
529
|
+
cursor: not-allowed;
|
|
530
|
+
opacity: 0.55;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
:global(.agent-sender-popover.el-popover) {
|
|
534
|
+
box-shadow: 0 14px 36px rgb(15 23 42 / 10%);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
@media screen and (width <= 768px) {
|
|
538
|
+
.agent-prefix-actions {
|
|
539
|
+
gap: 8px;
|
|
540
|
+
}
|
|
541
|
+
.agent-action-text {
|
|
542
|
+
display: none;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
@keyframes rotating {
|
|
547
|
+
from {
|
|
548
|
+
transform: rotate(0deg);
|
|
549
|
+
}
|
|
550
|
+
to {
|
|
551
|
+
transform: rotate(360deg);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
</style>
|