@liujitcn/kratos-admin-system 0.0.64 → 0.0.66
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 +5 -3
- package/dist/package/declarations/src/api/base/v1/ai_knowledge.d.ts +7 -0
- package/dist/package/declarations/src/api/system/admin/v1/ai_provider.d.ts +5 -1
- package/dist/package/declarations/src/api/system/admin/v1/ai_query.d.ts +10 -0
- package/dist/package/declarations/src/api/system/admin/v1/ai_session.d.ts +9 -0
- package/dist/package/declarations/src/locales/generated.d.ts +1405 -1089
- package/dist/package/declarations/src/rpc/base/v1/ai_knowledge.d.ts +22 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_message.d.ts +2 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_provider.d.ts +13 -6
- package/dist/package/declarations/src/rpc/system/admin/v1/ai_provider.d.ts +85 -4
- package/dist/package/declarations/src/rpc/system/admin/v1/ai_query.d.ts +73 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/ai_session.d.ts +114 -0
- package/dist/package/declarations/src/views/ai/chat/components/ChatPanel.vue.d.ts +9 -2
- package/dist/package/declarations/src/views/ai/chat/components/XSender.vue.d.ts +7 -0
- package/dist/package/declarations/src/views/ai/chat/modelDisplay.d.ts +5 -0
- package/dist/package/declarations/src/views/ai/chat/modelSelection.d.ts +9 -0
- package/dist/package/declarations/src/views/ai/query/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/ai/session/index.vue.d.ts +3 -0
- package/dist/package/src/api/base/v1/ai_knowledge.ts +22 -0
- package/dist/package/src/api/system/admin/v1/ai_provider.ts +14 -0
- package/dist/package/src/api/system/admin/v1/ai_query.ts +25 -0
- package/dist/package/src/api/system/admin/v1/ai_session.ts +25 -0
- package/dist/package/src/components/account/ChangePasswordForm.vue +1 -1
- package/dist/package/src/components/tenant-project/TenantProjectSelect.vue +21 -4
- package/dist/package/src/locales/en-US.json +351 -272
- package/dist/package/src/locales/ja-JP.json +351 -272
- package/dist/package/src/locales/zh-CN.json +361 -282
- package/dist/package/src/locales/zh-TW.json +351 -272
- package/dist/package/src/rpc/base/v1/ai_knowledge.ts +33 -0
- package/dist/package/src/rpc/base/v1/ai_message.ts +2 -0
- package/dist/package/src/rpc/base/v1/ai_provider.ts +14 -6
- package/dist/package/src/rpc/system/admin/v1/ai_provider.ts +93 -4
- package/dist/package/src/rpc/system/admin/v1/ai_query.ts +89 -0
- package/dist/package/src/rpc/system/admin/v1/ai_session.ts +131 -0
- package/dist/package/src/views/ai/chat/components/ChatPanel.vue +22 -1
- package/dist/package/src/views/ai/chat/components/XSender.vue +367 -71
- package/dist/package/src/views/ai/chat/index.vue +46 -8
- package/dist/package/src/views/ai/chat/modelDisplay.ts +67 -0
- package/dist/package/src/views/ai/chat/modelSelection.ts +31 -0
- package/dist/package/src/views/ai/query/index.vue +222 -0
- package/dist/package/src/views/ai/session/index.vue +422 -0
- package/dist/package/src/views/base/ai-provider/index.vue +301 -154
- package/dist/package/src/views/base/api-rate-limit-policy/index.vue +4 -3
- package/dist/package/src/views/base/message-provider/index.vue +9 -5
- package/dist/package/src/views/base/message-template/index.vue +2 -2
- package/dist/package/src/views/base/oauth-provider/index.vue +2 -2
- package/dist/package/src/views/base/rate-limit-rule/index.vue +8 -3
- package/dist/package/src/views/base/tenant-project-grant/index.vue +9 -4
- package/dist/package/src/views/tool/code-gen/config.ts +1 -0
- package/package.json +2 -2
|
@@ -53,22 +53,145 @@
|
|
|
53
53
|
|
|
54
54
|
<template #action-list>
|
|
55
55
|
<div class="agent-sender-actions">
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
56
|
+
<el-popover
|
|
57
|
+
ref="modelPopoverRef"
|
|
58
|
+
placement="top-start"
|
|
59
|
+
:width="324"
|
|
60
|
+
trigger="click"
|
|
61
|
+
:show-arrow="false"
|
|
62
|
+
:persistent="false"
|
|
63
|
+
popper-class="agent-model-popper"
|
|
64
|
+
>
|
|
65
|
+
<template #reference>
|
|
66
|
+
<button
|
|
67
|
+
class="agent-model-selector"
|
|
68
|
+
:class="{ 'is-disabled': sending || !modelOptions.length }"
|
|
69
|
+
type="button"
|
|
70
|
+
:disabled="sending || !modelOptions.length"
|
|
71
|
+
:aria-label="t('system.ai.model.title')"
|
|
72
|
+
>
|
|
73
|
+
<el-icon class="agent-model-selector__spark"><MagicStick /></el-icon>
|
|
74
|
+
<span class="agent-model-selector__label">{{
|
|
75
|
+
selectedModelDisplayName || t("system.ai.model.model_placeholder")
|
|
76
|
+
}}</span>
|
|
77
|
+
<el-icon class="agent-model-selector__arrow"><ArrowDown /></el-icon>
|
|
78
|
+
</button>
|
|
79
|
+
</template>
|
|
80
|
+
<div v-if="modelOptions.length" class="agent-model-panel">
|
|
81
|
+
<div class="agent-model-panel__head">
|
|
82
|
+
<span class="agent-model-panel__title">{{ t("system.ai.model.title") }}</span>
|
|
83
|
+
<span class="agent-model-panel__meta">{{
|
|
84
|
+
t("system.ai.model.provider_model_count", {
|
|
85
|
+
providers: modelOptions.length,
|
|
86
|
+
models: modelOptionCount
|
|
87
|
+
})
|
|
88
|
+
}}</span>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="agent-model-panel__list">
|
|
91
|
+
<template v-for="provider in modelOptions" :key="provider.providerId">
|
|
92
|
+
<div class="agent-model-panel__group">{{ provider.label }}</div>
|
|
93
|
+
<button
|
|
94
|
+
v-for="model in provider.models"
|
|
95
|
+
:key="model.name"
|
|
96
|
+
class="agent-model-row"
|
|
97
|
+
:class="{ 'is-selected': isModelSelected(provider.providerId, model.name) }"
|
|
98
|
+
type="button"
|
|
99
|
+
@click="handleModelSelect(provider.providerId, model.name)"
|
|
100
|
+
>
|
|
101
|
+
<span class="agent-model-row__name">{{ model.displayName }}</span>
|
|
102
|
+
<span v-if="model.displayName !== model.name" class="agent-model-row__raw">{{
|
|
103
|
+
model.name
|
|
104
|
+
}}</span>
|
|
105
|
+
<el-icon
|
|
106
|
+
v-if="isModelSelected(provider.providerId, model.name)"
|
|
107
|
+
class="agent-model-row__check"
|
|
108
|
+
>
|
|
109
|
+
<Check />
|
|
110
|
+
</el-icon>
|
|
111
|
+
</button>
|
|
112
|
+
</template>
|
|
113
|
+
</div>
|
|
114
|
+
<button
|
|
115
|
+
v-if="aiProviderRoute"
|
|
116
|
+
class="agent-model-panel__manage"
|
|
117
|
+
type="button"
|
|
118
|
+
@click="openModelManage"
|
|
119
|
+
>
|
|
120
|
+
<el-icon><Setting /></el-icon>
|
|
121
|
+
<span>{{ t("system.ai.model.manage") }}</span>
|
|
122
|
+
<el-icon class="agent-model-panel__manage-arrow"><ArrowRight /></el-icon>
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
<div v-else class="agent-model-panel agent-model-panel--empty">
|
|
126
|
+
{{ t("system.ai.model.empty") }}
|
|
127
|
+
</div>
|
|
128
|
+
</el-popover>
|
|
129
|
+
<el-popover
|
|
130
|
+
ref="knowledgePopoverRef"
|
|
131
|
+
placement="top-start"
|
|
132
|
+
:width="324"
|
|
133
|
+
trigger="click"
|
|
134
|
+
:show-arrow="false"
|
|
135
|
+
:persistent="false"
|
|
136
|
+
popper-class="agent-model-popper"
|
|
137
|
+
>
|
|
138
|
+
<template #reference>
|
|
139
|
+
<button
|
|
140
|
+
class="agent-model-selector"
|
|
141
|
+
:class="{ 'is-disabled': sending || !knowledgeOptions.length, 'is-selected': selectedKnowledgeCount > 0 }"
|
|
142
|
+
type="button"
|
|
143
|
+
:disabled="sending || !knowledgeOptions.length"
|
|
144
|
+
:aria-label="t('system.ai.knowledge.title')"
|
|
145
|
+
>
|
|
146
|
+
<el-icon class="agent-model-selector__spark"><Collection /></el-icon>
|
|
147
|
+
<span class="agent-model-selector__label">{{
|
|
148
|
+
selectedKnowledgeCount > 0
|
|
149
|
+
? t("system.ai.knowledge.selected_count", { count: selectedKnowledgeCount })
|
|
150
|
+
: t("system.ai.knowledge.title")
|
|
151
|
+
}}</span>
|
|
152
|
+
<el-icon class="agent-model-selector__arrow"><ArrowDown /></el-icon>
|
|
153
|
+
</button>
|
|
154
|
+
</template>
|
|
155
|
+
<div v-if="knowledgeOptions.length" class="agent-model-panel">
|
|
156
|
+
<div class="agent-model-panel__head">
|
|
157
|
+
<span class="agent-model-panel__title">{{ t("system.ai.knowledge.title") }}</span>
|
|
158
|
+
<span class="agent-model-panel__meta">{{
|
|
159
|
+
t("system.ai.knowledge.select_hint")
|
|
160
|
+
}}</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="agent-model-panel__list">
|
|
163
|
+
<button
|
|
164
|
+
v-for="option in knowledgeOptions"
|
|
165
|
+
:key="option.id"
|
|
166
|
+
class="agent-model-row"
|
|
167
|
+
:class="{ 'is-selected': isKnowledgeSelected(option.id) }"
|
|
168
|
+
type="button"
|
|
169
|
+
@click="handleKnowledgeToggle(option.id)"
|
|
170
|
+
>
|
|
171
|
+
<span class="agent-model-row__name">{{ option.name }}</span>
|
|
172
|
+
<span v-if="option.doc_count" class="agent-model-row__raw">{{
|
|
173
|
+
t("system.ai.knowledge.doc_count", { count: option.doc_count })
|
|
174
|
+
}}</span>
|
|
175
|
+
<el-icon v-if="isKnowledgeSelected(option.id)" class="agent-model-row__check">
|
|
176
|
+
<Check />
|
|
177
|
+
</el-icon>
|
|
178
|
+
</button>
|
|
179
|
+
</div>
|
|
180
|
+
<button
|
|
181
|
+
v-if="aiKnowledgeRoute"
|
|
182
|
+
class="agent-model-panel__manage"
|
|
183
|
+
type="button"
|
|
184
|
+
@click="openKnowledgeManage"
|
|
185
|
+
>
|
|
186
|
+
<el-icon><Setting /></el-icon>
|
|
187
|
+
<span>{{ t("system.ai.knowledge.manage") }}</span>
|
|
188
|
+
<el-icon class="agent-model-panel__manage-arrow"><ArrowRight /></el-icon>
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
<div v-else class="agent-model-panel agent-model-panel--empty">
|
|
192
|
+
{{ t("system.ai.knowledge.empty") }}
|
|
193
|
+
</div>
|
|
194
|
+
</el-popover>
|
|
72
195
|
<el-tooltip
|
|
73
196
|
:content="recording ? t('system.ai.chat.action.stop_voice_input') : t('system.ai.chat.action.voice_input')"
|
|
74
197
|
placement="top"
|
|
@@ -117,17 +240,32 @@
|
|
|
117
240
|
|
|
118
241
|
<script setup lang="ts" name="XSender">
|
|
119
242
|
import { computed, ref } from "vue";
|
|
243
|
+
import { useRouter } from "vue-router";
|
|
120
244
|
import { Attachments, XSender as BaseXSender } from "vue-element-plus-x";
|
|
121
245
|
import type { FilesCardProps } from "vue-element-plus-x/types/FilesCard";
|
|
122
|
-
import {
|
|
123
|
-
|
|
124
|
-
|
|
246
|
+
import {
|
|
247
|
+
ArrowDown,
|
|
248
|
+
ArrowRight,
|
|
249
|
+
Check,
|
|
250
|
+
Collection,
|
|
251
|
+
Loading,
|
|
252
|
+
MagicStick,
|
|
253
|
+
Microphone,
|
|
254
|
+
Paperclip,
|
|
255
|
+
Promotion,
|
|
256
|
+
Setting
|
|
257
|
+
} from "@element-plus/icons-vue";
|
|
258
|
+
import { ElMessage, ElPopover } from "element-plus";
|
|
125
259
|
import { t } from "@liujitcn/kratos-admin-core";
|
|
260
|
+
import { navigateTo } from "@liujitcn/kratos-admin-core/navigation";
|
|
261
|
+
import { useAuthStore } from "@liujitcn/kratos-admin-core/stores/runtime";
|
|
126
262
|
import { defFileService } from "@liujitcn/kratos-admin-core/api/base/v1/file";
|
|
127
263
|
import type { AiAttachment } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
128
264
|
import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
|
|
265
|
+
import type { AiKnowledgeOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_knowledge";
|
|
129
266
|
import type { SubmitPayload } from "../types";
|
|
130
267
|
import { buildAIAttachmentFileCard } from "../attachment";
|
|
268
|
+
import { fallbackModelDisplayName } from "../modelDisplay";
|
|
131
269
|
import { useSpeechRecognition } from "./speech-recognition";
|
|
132
270
|
import type { SpeechRecognitionError } from "./speech-recognition";
|
|
133
271
|
|
|
@@ -140,6 +278,10 @@ const props = defineProps<{
|
|
|
140
278
|
providerId: number;
|
|
141
279
|
/** 当前选择的模型名称。 */
|
|
142
280
|
modelName: string;
|
|
281
|
+
/** 当前可供选择的知识库。 */
|
|
282
|
+
knowledgeOptions: AiKnowledgeOption[];
|
|
283
|
+
/** 当前选择的知识库编号列表。 */
|
|
284
|
+
knowledgeIds: number[];
|
|
143
285
|
}>();
|
|
144
286
|
|
|
145
287
|
const emit = defineEmits<{
|
|
@@ -147,21 +289,43 @@ const emit = defineEmits<{
|
|
|
147
289
|
submit: [payload: SubmitPayload];
|
|
148
290
|
/** 更新当前选择的供应商和模型。 */
|
|
149
291
|
"model-change": [providerId: number, modelName: string];
|
|
292
|
+
/** 更新当前选择的知识库编号列表。 */
|
|
293
|
+
"knowledge-change": [knowledgeIds: number[]];
|
|
150
294
|
}>();
|
|
151
295
|
|
|
152
296
|
const senderRef = ref<InstanceType<typeof BaseXSender>>();
|
|
153
297
|
const fileInputRef = ref<HTMLInputElement>();
|
|
298
|
+
const modelPopoverRef = ref<InstanceType<typeof ElPopover>>();
|
|
299
|
+
const router = useRouter();
|
|
300
|
+
const authStore = useAuthStore();
|
|
154
301
|
const inputText = ref("");
|
|
155
302
|
const selectedAttachments = ref<AiAttachment[]>([]);
|
|
156
303
|
const modelOptions = computed(() =>
|
|
157
304
|
props.modelProviders.map(provider => ({
|
|
158
|
-
|
|
305
|
+
providerId: provider.provider_id,
|
|
159
306
|
label: provider.name,
|
|
160
|
-
|
|
161
|
-
|
|
307
|
+
models: provider.models.map(model => ({
|
|
308
|
+
name: model.model_name,
|
|
309
|
+
displayName: model.display_name || fallbackModelDisplayName(model.model_name)
|
|
310
|
+
}))
|
|
162
311
|
}))
|
|
163
312
|
);
|
|
164
|
-
const
|
|
313
|
+
const modelOptionCount = computed(() =>
|
|
314
|
+
modelOptions.value.reduce((total, provider) => total + provider.models.length, 0)
|
|
315
|
+
);
|
|
316
|
+
const selectedModelDisplayName = computed(() => {
|
|
317
|
+
const provider = modelOptions.value.find(item => item.providerId === props.providerId);
|
|
318
|
+
return (
|
|
319
|
+
provider?.models.find(model => model.name === props.modelName)?.displayName ??
|
|
320
|
+
fallbackModelDisplayName(props.modelName)
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
const aiProviderRoute = computed(() =>
|
|
324
|
+
authStore.flatMenuListGet.find(item => item.name === "AiProvider" && item.path)
|
|
325
|
+
);
|
|
326
|
+
const aiKnowledgeRoute = computed(() =>
|
|
327
|
+
authStore.flatMenuListGet.find(item => item.name === "AiKnowledge" && item.path)
|
|
328
|
+
);
|
|
165
329
|
const uploading = ref(false);
|
|
166
330
|
const maxAttachmentCount = 6;
|
|
167
331
|
const maxAttachmentSizeMB = 20;
|
|
@@ -218,10 +382,46 @@ const isSubmitDisabled = computed(() => {
|
|
|
218
382
|
return uploading.value || (!inputText.value.trim() && selectedAttachments.value.length === 0);
|
|
219
383
|
});
|
|
220
384
|
|
|
221
|
-
/**
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
|
|
385
|
+
/** 判断指定供应商与模型是否为当前选择。 */
|
|
386
|
+
function isModelSelected(providerId: number, modelName: string) {
|
|
387
|
+
return props.providerId === providerId && props.modelName === modelName;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** 选中模型并关闭选择面板。 */
|
|
391
|
+
function handleModelSelect(providerId: number, modelName: string) {
|
|
392
|
+
emit("model-change", providerId, modelName);
|
|
393
|
+
modelPopoverRef.value?.hide();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** 跳转到 AI 供应商配置页。 */
|
|
397
|
+
async function openModelManage() {
|
|
398
|
+
if (!aiProviderRoute.value?.path) return;
|
|
399
|
+
modelPopoverRef.value?.hide();
|
|
400
|
+
await navigateTo(router, aiProviderRoute.value.path);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const knowledgePopoverRef = ref<InstanceType<typeof ElPopover>>();
|
|
404
|
+
|
|
405
|
+
const selectedKnowledgeCount = computed(() => props.knowledgeIds.length);
|
|
406
|
+
|
|
407
|
+
/** 判断知识库是否在当前选择中。 */
|
|
408
|
+
function isKnowledgeSelected(knowledgeId: number) {
|
|
409
|
+
return props.knowledgeIds.includes(knowledgeId);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** 切换知识库选择状态并同步父组件。 */
|
|
413
|
+
function handleKnowledgeToggle(knowledgeId: number) {
|
|
414
|
+
const current = new Set(props.knowledgeIds);
|
|
415
|
+
if (current.has(knowledgeId)) current.delete(knowledgeId);
|
|
416
|
+
else current.add(knowledgeId);
|
|
417
|
+
emit("knowledge-change", [...current]);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** 跳转到 AI 知识库管理页。 */
|
|
421
|
+
async function openKnowledgeManage() {
|
|
422
|
+
if (!aiKnowledgeRoute.value?.path) return;
|
|
423
|
+
knowledgePopoverRef.value?.hide();
|
|
424
|
+
await navigateTo(router, aiKnowledgeRoute.value.path);
|
|
225
425
|
}
|
|
226
426
|
|
|
227
427
|
/** 读取输入内容并发送给父组件。 */
|
|
@@ -464,11 +664,15 @@ function resetFileInput() {
|
|
|
464
664
|
min-height: 36px;
|
|
465
665
|
}
|
|
466
666
|
.agent-model-selector {
|
|
467
|
-
display: flex;
|
|
667
|
+
display: inline-flex;
|
|
468
668
|
flex: 0 1 auto;
|
|
469
|
-
|
|
669
|
+
align-items: center;
|
|
670
|
+
gap: 6px;
|
|
671
|
+
width: clamp(180px, 18vw, 220px);
|
|
470
672
|
min-width: 0;
|
|
471
673
|
height: 34px;
|
|
674
|
+
padding: 0 10px;
|
|
675
|
+
overflow: hidden;
|
|
472
676
|
box-sizing: border-box;
|
|
473
677
|
color: var(--admin-page-text-secondary);
|
|
474
678
|
cursor: pointer;
|
|
@@ -479,48 +683,34 @@ function resetFileInput() {
|
|
|
479
683
|
color 0.2s ease,
|
|
480
684
|
border-color 0.2s ease,
|
|
481
685
|
background-color 0.2s ease;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
border-radius: inherit;
|
|
511
|
-
}
|
|
512
|
-
.agent-model-selector :deep(.el-input__wrapper.is-focus) {
|
|
513
|
-
box-shadow: none;
|
|
514
|
-
}
|
|
515
|
-
.agent-model-selector :deep(.el-input__inner) {
|
|
516
|
-
font-size: 14px;
|
|
517
|
-
font-weight: 500;
|
|
518
|
-
line-height: 20px;
|
|
519
|
-
color: currentColor;
|
|
520
|
-
}
|
|
521
|
-
.agent-model-selector :deep(.el-input__suffix .el-icon) {
|
|
522
|
-
font-size: 14px;
|
|
523
|
-
color: currentColor;
|
|
686
|
+
&:hover {
|
|
687
|
+
color: var(--el-color-primary);
|
|
688
|
+
background: var(--el-color-primary-light-9);
|
|
689
|
+
border-color: var(--el-color-primary-light-5);
|
|
690
|
+
}
|
|
691
|
+
&:disabled {
|
|
692
|
+
cursor: not-allowed;
|
|
693
|
+
opacity: 0.55;
|
|
694
|
+
}
|
|
695
|
+
&__spark {
|
|
696
|
+
flex-shrink: 0;
|
|
697
|
+
font-size: 15px;
|
|
698
|
+
}
|
|
699
|
+
&__label {
|
|
700
|
+
flex: 1;
|
|
701
|
+
min-width: 0;
|
|
702
|
+
overflow: hidden;
|
|
703
|
+
font-size: 14px;
|
|
704
|
+
font-weight: 500;
|
|
705
|
+
line-height: 20px;
|
|
706
|
+
text-align: left;
|
|
707
|
+
text-overflow: ellipsis;
|
|
708
|
+
white-space: nowrap;
|
|
709
|
+
}
|
|
710
|
+
&__arrow {
|
|
711
|
+
flex-shrink: 0;
|
|
712
|
+
font-size: 14px;
|
|
713
|
+
}
|
|
524
714
|
}
|
|
525
715
|
.agent-sender-actions {
|
|
526
716
|
display: inline-flex;
|
|
@@ -612,6 +802,112 @@ function resetFileInput() {
|
|
|
612
802
|
:global(.agent-sender-popover.el-popover) {
|
|
613
803
|
box-shadow: 0 14px 36px rgb(15 23 42 / 10%);
|
|
614
804
|
}
|
|
805
|
+
:global(.agent-model-popper.el-popover) {
|
|
806
|
+
padding: 0 !important;
|
|
807
|
+
border-radius: var(--admin-page-radius) !important;
|
|
808
|
+
box-shadow: 0 14px 36px rgb(15 23 42 / 16%) !important;
|
|
809
|
+
}
|
|
810
|
+
.agent-model-panel {
|
|
811
|
+
padding: 10px;
|
|
812
|
+
&__list {
|
|
813
|
+
max-height: min(52vh, 420px);
|
|
814
|
+
overflow-y: auto;
|
|
815
|
+
scrollbar-width: thin;
|
|
816
|
+
scrollbar-color: var(--el-border-color-darker) transparent;
|
|
817
|
+
}
|
|
818
|
+
&__head {
|
|
819
|
+
display: flex;
|
|
820
|
+
align-items: baseline;
|
|
821
|
+
justify-content: space-between;
|
|
822
|
+
padding: 4px 6px 10px;
|
|
823
|
+
}
|
|
824
|
+
&__title {
|
|
825
|
+
font-size: 14px;
|
|
826
|
+
font-weight: 700;
|
|
827
|
+
color: var(--admin-page-text-primary);
|
|
828
|
+
}
|
|
829
|
+
&__meta {
|
|
830
|
+
font-size: 11px;
|
|
831
|
+
color: var(--el-text-color-placeholder);
|
|
832
|
+
}
|
|
833
|
+
&__group {
|
|
834
|
+
padding: 8px 6px 6px;
|
|
835
|
+
font-size: 12px;
|
|
836
|
+
color: var(--admin-page-text-secondary);
|
|
837
|
+
}
|
|
838
|
+
&__manage {
|
|
839
|
+
display: inline-flex;
|
|
840
|
+
align-items: center;
|
|
841
|
+
gap: 6px;
|
|
842
|
+
width: 100%;
|
|
843
|
+
height: 36px;
|
|
844
|
+
margin-top: 6px;
|
|
845
|
+
padding: 0 6px;
|
|
846
|
+
font-size: 12.5px;
|
|
847
|
+
font-weight: 600;
|
|
848
|
+
color: var(--el-color-primary);
|
|
849
|
+
cursor: pointer;
|
|
850
|
+
background: transparent;
|
|
851
|
+
border: 0;
|
|
852
|
+
border-top: 1px solid var(--el-border-color-lighter);
|
|
853
|
+
border-radius: 0;
|
|
854
|
+
&:hover {
|
|
855
|
+
color: var(--el-color-primary-light-3);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
&__manage-arrow {
|
|
859
|
+
margin-left: auto;
|
|
860
|
+
}
|
|
861
|
+
&--empty {
|
|
862
|
+
padding: 20px 10px;
|
|
863
|
+
font-size: 13px;
|
|
864
|
+
color: var(--admin-page-text-secondary);
|
|
865
|
+
text-align: center;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
.agent-model-row {
|
|
869
|
+
display: flex;
|
|
870
|
+
align-items: center;
|
|
871
|
+
gap: 8px;
|
|
872
|
+
width: 100%;
|
|
873
|
+
height: 42px;
|
|
874
|
+
margin-bottom: 2px;
|
|
875
|
+
padding: 0 10px;
|
|
876
|
+
font-size: 13.5px;
|
|
877
|
+
color: var(--admin-page-text-primary);
|
|
878
|
+
text-align: left;
|
|
879
|
+
cursor: pointer;
|
|
880
|
+
background: transparent;
|
|
881
|
+
border: 0;
|
|
882
|
+
border-radius: var(--admin-page-radius);
|
|
883
|
+
&:hover {
|
|
884
|
+
background: var(--el-fill-color-light);
|
|
885
|
+
}
|
|
886
|
+
&.is-selected {
|
|
887
|
+
font-weight: 600;
|
|
888
|
+
color: var(--el-color-primary);
|
|
889
|
+
background: var(--el-color-primary-light-9);
|
|
890
|
+
}
|
|
891
|
+
&__name {
|
|
892
|
+
overflow: hidden;
|
|
893
|
+
text-overflow: ellipsis;
|
|
894
|
+
white-space: nowrap;
|
|
895
|
+
}
|
|
896
|
+
&__raw {
|
|
897
|
+
flex: 1;
|
|
898
|
+
min-width: 0;
|
|
899
|
+
overflow: hidden;
|
|
900
|
+
font-size: 12px;
|
|
901
|
+
font-weight: 400;
|
|
902
|
+
color: var(--el-text-color-placeholder);
|
|
903
|
+
text-overflow: ellipsis;
|
|
904
|
+
white-space: nowrap;
|
|
905
|
+
}
|
|
906
|
+
&__check {
|
|
907
|
+
flex-shrink: 0;
|
|
908
|
+
font-size: 15px;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
615
911
|
|
|
616
912
|
@media screen and (width <= 768px) {
|
|
617
913
|
.agent-prefix-actions {
|
|
@@ -30,8 +30,11 @@
|
|
|
30
30
|
:model-providers="modelProviders"
|
|
31
31
|
:provider-id="selectedProviderId"
|
|
32
32
|
:model-name="selectedModelName"
|
|
33
|
+
:knowledge-options="knowledgeOptions"
|
|
34
|
+
:knowledge-ids="selectedKnowledgeIds"
|
|
33
35
|
@submit="handleSubmit"
|
|
34
36
|
@model-change="handleModelChange"
|
|
37
|
+
@knowledge-change="handleKnowledgeChange"
|
|
35
38
|
@message-action="handleMessageAction"
|
|
36
39
|
@message-edit="handleEditMessage"
|
|
37
40
|
>
|
|
@@ -59,9 +62,11 @@ import { defAiMessageService } from "@liujitcn/kratos-admin-system/api/base/v1/a
|
|
|
59
62
|
import { defAiSessionService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_session";
|
|
60
63
|
import { defAiToolService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_tool";
|
|
61
64
|
import { defAiModelService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_provider";
|
|
65
|
+
import { defAiKnowledgeService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_knowledge";
|
|
62
66
|
import type { AiAction } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_message";
|
|
63
67
|
import type { AiSession } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
64
68
|
import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
|
|
69
|
+
import type { AiKnowledgeOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_knowledge";
|
|
65
70
|
import type { AiShortcut } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_tool";
|
|
66
71
|
import { AiMessageStatus } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
67
72
|
import { Terminal } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_tool";
|
|
@@ -85,6 +90,7 @@ import {
|
|
|
85
90
|
sortMessages
|
|
86
91
|
} from "./message";
|
|
87
92
|
import { readAiEventStream } from "./stream";
|
|
93
|
+
import { loadStoredModelSelection, saveModelSelection } from "./modelSelection";
|
|
88
94
|
import type {
|
|
89
95
|
AiStreamEvent,
|
|
90
96
|
AiStreamPayload,
|
|
@@ -111,6 +117,8 @@ const starterShortcuts = ref<AiShortcut[]>([]);
|
|
|
111
117
|
const modelProviders = ref<AiProviderModelOption[]>([]);
|
|
112
118
|
const selectedProviderId = ref(0);
|
|
113
119
|
const selectedModelName = ref("");
|
|
120
|
+
const knowledgeOptions = ref<AiKnowledgeOption[]>([]);
|
|
121
|
+
const selectedKnowledgeIds = ref<number[]>([]);
|
|
114
122
|
const messages = ref<Record<string, ChatMessageItem[]>>({});
|
|
115
123
|
const pendingDeltaMap = new Map<string, AiStreamPayload>();
|
|
116
124
|
const runningStreamTaskMap = new Map<string, AiStreamTask>();
|
|
@@ -228,6 +236,7 @@ async function runAiStreamTask(sessionID: string, payload: SubmitPayload) {
|
|
|
228
236
|
content: payload.text,
|
|
229
237
|
provider_id: selectedProviderId.value,
|
|
230
238
|
model_name: selectedModelName.value,
|
|
239
|
+
knowledge_base_ids: selectedKnowledgeIds.value,
|
|
231
240
|
attachments: payload.attachments.map(item => ({
|
|
232
241
|
id: item.id,
|
|
233
242
|
name: item.name,
|
|
@@ -656,20 +665,23 @@ async function loadAiShortcuts() {
|
|
|
656
665
|
if (loadingShortcuts.value) return;
|
|
657
666
|
loadingShortcuts.value = true;
|
|
658
667
|
try {
|
|
659
|
-
const [response, providerResponse] = await Promise.all([
|
|
668
|
+
const [response, providerResponse, knowledgeResponse] = await Promise.all([
|
|
660
669
|
defAiToolService.ListAiShortcut({ terminal: Terminal.TERMINAL_ADMIN }),
|
|
661
|
-
defAiModelService.ListAiProviderModelOptions({})
|
|
670
|
+
defAiModelService.ListAiProviderModelOptions({}),
|
|
671
|
+
defAiKnowledgeService.ListAiKnowledgeOptions({})
|
|
662
672
|
]);
|
|
663
673
|
starterShortcuts.value = normalizeStarterShortcuts(response.shortcuts);
|
|
664
674
|
modelProviders.value = providerResponse.providers ?? [];
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
675
|
+
knowledgeOptions.value = knowledgeResponse.options ?? [];
|
|
676
|
+
// 知识库可能已被删除,剔除无效选择。
|
|
677
|
+
selectedKnowledgeIds.value = selectedKnowledgeIds.value.filter(id =>
|
|
678
|
+
knowledgeOptions.value.some(option => option.id === id)
|
|
679
|
+
);
|
|
680
|
+
applyModelSelection();
|
|
670
681
|
} catch {
|
|
671
682
|
starterShortcuts.value = [];
|
|
672
683
|
modelProviders.value = [];
|
|
684
|
+
knowledgeOptions.value = [];
|
|
673
685
|
selectedProviderId.value = 0;
|
|
674
686
|
selectedModelName.value = "";
|
|
675
687
|
} finally {
|
|
@@ -677,10 +689,36 @@ async function loadAiShortcuts() {
|
|
|
677
689
|
}
|
|
678
690
|
}
|
|
679
691
|
|
|
680
|
-
/**
|
|
692
|
+
/** 优先恢复本地缓存的模型选择,缓存失效时回退到首个可用模型。 */
|
|
693
|
+
function applyModelSelection() {
|
|
694
|
+
const stored = loadStoredModelSelection();
|
|
695
|
+
const provider = stored
|
|
696
|
+
? modelProviders.value.find(
|
|
697
|
+
item =>
|
|
698
|
+
item.provider_id === stored.provider_id &&
|
|
699
|
+
item.models.some(model => model.model_name === stored.model_name)
|
|
700
|
+
)
|
|
701
|
+
: undefined;
|
|
702
|
+
if (provider && stored) {
|
|
703
|
+
selectedProviderId.value = provider.provider_id;
|
|
704
|
+
selectedModelName.value = stored.model_name;
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const firstProvider = modelProviders.value[0];
|
|
708
|
+
selectedProviderId.value = firstProvider?.provider_id ?? 0;
|
|
709
|
+
selectedModelName.value = firstProvider?.models[0]?.model_name ?? "";
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/** 更新当前聊天使用的供应商与模型并持久化到本地缓存。 */
|
|
681
713
|
function handleModelChange(providerId: number, modelName: string) {
|
|
682
714
|
selectedProviderId.value = providerId;
|
|
683
715
|
selectedModelName.value = modelName;
|
|
716
|
+
saveModelSelection({ provider_id: providerId, model_name: modelName });
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** 更新当前聊天检索注入的知识库选择。 */
|
|
720
|
+
function handleKnowledgeChange(knowledgeIds: number[]) {
|
|
721
|
+
selectedKnowledgeIds.value = knowledgeIds;
|
|
684
722
|
}
|
|
685
723
|
|
|
686
724
|
/** 使指定会话仍在途的消息列表请求失效。 */
|