@liujitcn/kratos-admin-system 0.0.65 → 0.0.67

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.
Files changed (65) hide show
  1. package/README.md +5 -3
  2. package/dist/package/declarations/src/api/base/v1/ai_knowledge.d.ts +7 -0
  3. package/dist/package/declarations/src/api/system/admin/v1/ai_provider.d.ts +3 -1
  4. package/dist/package/declarations/src/api/system/admin/v1/ai_query.d.ts +10 -0
  5. package/dist/package/declarations/src/api/system/admin/v1/ai_session.d.ts +9 -0
  6. package/dist/package/declarations/src/locales/generated.d.ts +1805 -1553
  7. package/dist/package/declarations/src/rpc/base/v1/ai_knowledge.d.ts +22 -0
  8. package/dist/package/declarations/src/rpc/base/v1/ai_message.d.ts +2 -0
  9. package/dist/package/declarations/src/rpc/base/v1/login.d.ts +2 -39
  10. package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +2 -2
  11. package/dist/package/declarations/src/rpc/common/v1/types.d.ts +4 -4
  12. package/dist/package/declarations/src/rpc/system/admin/v1/ai_provider.d.ts +66 -7
  13. package/dist/package/declarations/src/rpc/system/admin/v1/ai_query.d.ts +73 -0
  14. package/dist/package/declarations/src/rpc/system/admin/v1/ai_session.d.ts +114 -0
  15. package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +3 -3
  16. package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +4 -3
  17. package/dist/package/declarations/src/rpc/system/admin/v1/base_login_policy.d.ts +2 -2
  18. package/dist/package/declarations/src/rpc/system/admin/v1/base_message_provider.d.ts +3 -2
  19. package/dist/package/declarations/src/rpc/system/admin/v1/base_oauth_provider.d.ts +3 -2
  20. package/dist/package/declarations/src/rpc/system/admin/v1/base_user.d.ts +3 -3
  21. package/dist/package/declarations/src/views/ai/chat/components/ChatPanel.vue.d.ts +9 -2
  22. package/dist/package/declarations/src/views/ai/chat/components/XSender.vue.d.ts +7 -0
  23. package/dist/package/declarations/src/views/ai/query/index.vue.d.ts +3 -0
  24. package/dist/package/declarations/src/views/ai/session/index.vue.d.ts +3 -0
  25. package/dist/package/src/api/base/v1/ai_knowledge.ts +22 -0
  26. package/dist/package/src/api/system/admin/v1/ai_provider.ts +7 -0
  27. package/dist/package/src/api/system/admin/v1/ai_query.ts +25 -0
  28. package/dist/package/src/api/system/admin/v1/ai_session.ts +25 -0
  29. package/dist/package/src/components/account/ChangePasswordForm.vue +7 -5
  30. package/dist/package/src/components/tenant-project/TenantProjectSelect.vue +21 -4
  31. package/dist/package/src/locales/README.md +1 -1
  32. package/dist/package/src/locales/en-US.json +355 -292
  33. package/dist/package/src/locales/ja-JP.json +352 -289
  34. package/dist/package/src/locales/zh-CN.json +352 -289
  35. package/dist/package/src/locales/zh-TW.json +352 -289
  36. package/dist/package/src/module.ts +8 -0
  37. package/dist/package/src/rpc/base/v1/ai_knowledge.ts +33 -0
  38. package/dist/package/src/rpc/base/v1/ai_message.ts +2 -0
  39. package/dist/package/src/rpc/base/v1/login.ts +2 -42
  40. package/dist/package/src/rpc/base/v1/oauth.ts +2 -2
  41. package/dist/package/src/rpc/common/v1/types.ts +4 -4
  42. package/dist/package/src/rpc/system/admin/v1/ai_provider.ts +73 -7
  43. package/dist/package/src/rpc/system/admin/v1/ai_query.ts +89 -0
  44. package/dist/package/src/rpc/system/admin/v1/ai_session.ts +131 -0
  45. package/dist/package/src/rpc/system/admin/v1/auth.ts +3 -3
  46. package/dist/package/src/rpc/system/admin/v1/base_config.ts +6 -3
  47. package/dist/package/src/rpc/system/admin/v1/base_login_policy.ts +2 -2
  48. package/dist/package/src/rpc/system/admin/v1/base_message_provider.ts +5 -2
  49. package/dist/package/src/rpc/system/admin/v1/base_oauth_provider.ts +5 -2
  50. package/dist/package/src/rpc/system/admin/v1/base_user.ts +3 -3
  51. package/dist/package/src/views/ai/chat/components/ChatPanel.vue +18 -0
  52. package/dist/package/src/views/ai/chat/components/XSender.vue +102 -0
  53. package/dist/package/src/views/ai/chat/index.vue +22 -2
  54. package/dist/package/src/views/ai/query/index.vue +222 -0
  55. package/dist/package/src/views/ai/session/index.vue +422 -0
  56. package/dist/package/src/views/base/ai-provider/index.vue +216 -169
  57. package/dist/package/src/views/base/config/index.vue +9 -9
  58. package/dist/package/src/views/base/login-policy/index.vue +2 -2
  59. package/dist/package/src/views/base/message-provider/index.vue +7 -5
  60. package/dist/package/src/views/base/oauth-provider/index.vue +10 -7
  61. package/dist/package/src/views/base/tenant-project-grant/index.vue +9 -4
  62. package/dist/package/src/views/base/user/index.vue +3 -3
  63. package/dist/package/src/views/profile/components/security.vue +4 -5
  64. package/dist/package/src/views/tool/code-gen/config.ts +2 -0
  65. package/package.json +2 -2
@@ -53,8 +53,11 @@
53
53
  :model-providers="modelProviders"
54
54
  :provider-id="providerId"
55
55
  :model-name="modelName"
56
+ :knowledge-options="knowledgeOptions"
57
+ :knowledge-ids="knowledgeIds"
56
58
  @submit="handleSubmit"
57
59
  @model-change="handleModelChange"
60
+ @knowledge-change="handleKnowledgeChange"
58
61
  />
59
62
  </div>
60
63
  </div>
@@ -336,8 +339,11 @@
336
339
  :model-providers="modelProviders"
337
340
  :provider-id="providerId"
338
341
  :model-name="modelName"
342
+ :knowledge-options="knowledgeOptions"
343
+ :knowledge-ids="knowledgeIds"
339
344
  @submit="handleSubmit"
340
345
  @model-change="handleModelChange"
346
+ @knowledge-change="handleKnowledgeChange"
341
347
  />
342
348
  </div>
343
349
  </template>
@@ -356,6 +362,7 @@ import { formatDateTime } from "@liujitcn/kratos-admin-core/format";
356
362
  import type { AiAction } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_message";
357
363
  import { type AiAttachment, type AiSession } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
358
364
  import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
365
+ import type { AiKnowledgeOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_knowledge";
359
366
  import type { AiShortcut, AiToolCall } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_tool";
360
367
  import { AiMessageStatus } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
361
368
  import { resolvePersistedMessage } from "../../../../utils/persisted-message";
@@ -501,6 +508,10 @@ const props = defineProps<{
501
508
  providerId: number;
502
509
  /** 当前选择的模型名称。 */
503
510
  modelName: string;
511
+ /** 当前可供选择的知识库。 */
512
+ knowledgeOptions: AiKnowledgeOption[];
513
+ /** 当前选择的知识库编号列表。 */
514
+ knowledgeIds: number[];
504
515
  }>();
505
516
 
506
517
  const emit = defineEmits<{
@@ -508,6 +519,8 @@ const emit = defineEmits<{
508
519
  submit: [payload: SubmitPayload];
509
520
  /** 更新当前选择的供应商和模型。 */
510
521
  modelChange: [providerId: number, modelName: string];
522
+ /** 更新当前选择的知识库编号列表。 */
523
+ knowledgeChange: [knowledgeIds: number[]];
511
524
  /** 触发消息级操作。 */
512
525
  messageAction: [payload: { action: ChatMessageAction; item: ChatMessageItem }];
513
526
  /** 提交当前用户消息的文本编辑。 */
@@ -648,6 +661,11 @@ function handleModelChange(providerId: number, modelName: string) {
648
661
  emit("modelChange", providerId, modelName);
649
662
  }
650
663
 
664
+ /** 更新当前选择的知识库编号列表。 */
665
+ function handleKnowledgeChange(knowledgeIds: number[]) {
666
+ emit("knowledgeChange", knowledgeIds);
667
+ }
668
+
651
669
  /** 点击快捷入口时提交对应入口动作。 */
652
670
  function handleShortcutClick(shortcut: AiShortcut) {
653
671
  emit("submit", {
@@ -126,6 +126,73 @@
126
126
  {{ t("system.ai.model.empty") }}
127
127
  </div>
128
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
+ <!-- proto3 JSON 会省略零值字段,计数为 0 时前端补默认值。 -->
173
+ <span class="agent-model-row__raw">{{
174
+ t("system.ai.knowledge.doc_count", { count: option.doc_count ?? 0 })
175
+ }}</span>
176
+ <el-icon v-if="isKnowledgeSelected(option.id)" class="agent-model-row__check">
177
+ <Check />
178
+ </el-icon>
179
+ </button>
180
+ </div>
181
+ <button
182
+ v-if="aiKnowledgeRoute"
183
+ class="agent-model-panel__manage"
184
+ type="button"
185
+ @click="openKnowledgeManage"
186
+ >
187
+ <el-icon><Setting /></el-icon>
188
+ <span>{{ t("system.ai.knowledge.manage") }}</span>
189
+ <el-icon class="agent-model-panel__manage-arrow"><ArrowRight /></el-icon>
190
+ </button>
191
+ </div>
192
+ <div v-else class="agent-model-panel agent-model-panel--empty">
193
+ {{ t("system.ai.knowledge.empty") }}
194
+ </div>
195
+ </el-popover>
129
196
  <el-tooltip
130
197
  :content="recording ? t('system.ai.chat.action.stop_voice_input') : t('system.ai.chat.action.voice_input')"
131
198
  placement="top"
@@ -181,6 +248,7 @@ import {
181
248
  ArrowDown,
182
249
  ArrowRight,
183
250
  Check,
251
+ Collection,
184
252
  Loading,
185
253
  MagicStick,
186
254
  Microphone,
@@ -195,6 +263,7 @@ import { useAuthStore } from "@liujitcn/kratos-admin-core/stores/runtime";
195
263
  import { defFileService } from "@liujitcn/kratos-admin-core/api/base/v1/file";
196
264
  import type { AiAttachment } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
197
265
  import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
266
+ import type { AiKnowledgeOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_knowledge";
198
267
  import type { SubmitPayload } from "../types";
199
268
  import { buildAIAttachmentFileCard } from "../attachment";
200
269
  import { fallbackModelDisplayName } from "../modelDisplay";
@@ -210,6 +279,10 @@ const props = defineProps<{
210
279
  providerId: number;
211
280
  /** 当前选择的模型名称。 */
212
281
  modelName: string;
282
+ /** 当前可供选择的知识库。 */
283
+ knowledgeOptions: AiKnowledgeOption[];
284
+ /** 当前选择的知识库编号列表。 */
285
+ knowledgeIds: number[];
213
286
  }>();
214
287
 
215
288
  const emit = defineEmits<{
@@ -217,6 +290,8 @@ const emit = defineEmits<{
217
290
  submit: [payload: SubmitPayload];
218
291
  /** 更新当前选择的供应商和模型。 */
219
292
  "model-change": [providerId: number, modelName: string];
293
+ /** 更新当前选择的知识库编号列表。 */
294
+ "knowledge-change": [knowledgeIds: number[]];
220
295
  }>();
221
296
 
222
297
  const senderRef = ref<InstanceType<typeof BaseXSender>>();
@@ -249,6 +324,9 @@ const selectedModelDisplayName = computed(() => {
249
324
  const aiProviderRoute = computed(() =>
250
325
  authStore.flatMenuListGet.find(item => item.name === "AiProvider" && item.path)
251
326
  );
327
+ const aiKnowledgeRoute = computed(() =>
328
+ authStore.flatMenuListGet.find(item => item.name === "AiKnowledge" && item.path)
329
+ );
252
330
  const uploading = ref(false);
253
331
  const maxAttachmentCount = 6;
254
332
  const maxAttachmentSizeMB = 20;
@@ -323,6 +401,30 @@ async function openModelManage() {
323
401
  await navigateTo(router, aiProviderRoute.value.path);
324
402
  }
325
403
 
404
+ const knowledgePopoverRef = ref<InstanceType<typeof ElPopover>>();
405
+
406
+ const selectedKnowledgeCount = computed(() => props.knowledgeIds.length);
407
+
408
+ /** 判断知识库是否在当前选择中。 */
409
+ function isKnowledgeSelected(knowledgeId: number) {
410
+ return props.knowledgeIds.includes(knowledgeId);
411
+ }
412
+
413
+ /** 切换知识库选择状态并同步父组件。 */
414
+ function handleKnowledgeToggle(knowledgeId: number) {
415
+ const current = new Set(props.knowledgeIds);
416
+ if (current.has(knowledgeId)) current.delete(knowledgeId);
417
+ else current.add(knowledgeId);
418
+ emit("knowledge-change", [...current]);
419
+ }
420
+
421
+ /** 跳转到 AI 知识库管理页。 */
422
+ async function openKnowledgeManage() {
423
+ if (!aiKnowledgeRoute.value?.path) return;
424
+ knowledgePopoverRef.value?.hide();
425
+ await navigateTo(router, aiKnowledgeRoute.value.path);
426
+ }
427
+
326
428
  /** 读取输入内容并发送给父组件。 */
327
429
  function handleSubmit() {
328
430
  if (uploading.value) return;
@@ -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";
@@ -112,6 +117,8 @@ const starterShortcuts = ref<AiShortcut[]>([]);
112
117
  const modelProviders = ref<AiProviderModelOption[]>([]);
113
118
  const selectedProviderId = ref(0);
114
119
  const selectedModelName = ref("");
120
+ const knowledgeOptions = ref<AiKnowledgeOption[]>([]);
121
+ const selectedKnowledgeIds = ref<number[]>([]);
115
122
  const messages = ref<Record<string, ChatMessageItem[]>>({});
116
123
  const pendingDeltaMap = new Map<string, AiStreamPayload>();
117
124
  const runningStreamTaskMap = new Map<string, AiStreamTask>();
@@ -229,6 +236,7 @@ async function runAiStreamTask(sessionID: string, payload: SubmitPayload) {
229
236
  content: payload.text,
230
237
  provider_id: selectedProviderId.value,
231
238
  model_name: selectedModelName.value,
239
+ knowledge_base_ids: selectedKnowledgeIds.value,
232
240
  attachments: payload.attachments.map(item => ({
233
241
  id: item.id,
234
242
  name: item.name,
@@ -657,16 +665,23 @@ async function loadAiShortcuts() {
657
665
  if (loadingShortcuts.value) return;
658
666
  loadingShortcuts.value = true;
659
667
  try {
660
- const [response, providerResponse] = await Promise.all([
668
+ const [response, providerResponse, knowledgeResponse] = await Promise.all([
661
669
  defAiToolService.ListAiShortcut({ terminal: Terminal.TERMINAL_ADMIN }),
662
- defAiModelService.ListAiProviderModelOptions({})
670
+ defAiModelService.ListAiProviderModelOptions({}),
671
+ defAiKnowledgeService.ListAiKnowledgeOptions({})
663
672
  ]);
664
673
  starterShortcuts.value = normalizeStarterShortcuts(response.shortcuts);
665
674
  modelProviders.value = providerResponse.providers ?? [];
675
+ knowledgeOptions.value = knowledgeResponse.options ?? [];
676
+ // 知识库可能已被删除,剔除无效选择。
677
+ selectedKnowledgeIds.value = selectedKnowledgeIds.value.filter(id =>
678
+ knowledgeOptions.value.some(option => option.id === id)
679
+ );
666
680
  applyModelSelection();
667
681
  } catch {
668
682
  starterShortcuts.value = [];
669
683
  modelProviders.value = [];
684
+ knowledgeOptions.value = [];
670
685
  selectedProviderId.value = 0;
671
686
  selectedModelName.value = "";
672
687
  } finally {
@@ -701,6 +716,11 @@ function handleModelChange(providerId: number, modelName: string) {
701
716
  saveModelSelection({ provider_id: providerId, model_name: modelName });
702
717
  }
703
718
 
719
+ /** 更新当前聊天检索注入的知识库选择。 */
720
+ function handleKnowledgeChange(knowledgeIds: number[]) {
721
+ selectedKnowledgeIds.value = knowledgeIds;
722
+ }
723
+
704
724
  /** 使指定会话仍在途的消息列表请求失效。 */
705
725
  function invalidateLoadMessages(sessionID: string) {
706
726
  loadMessagesSeq.set(sessionID, (loadMessagesSeq.get(sessionID) ?? 0) + 1);
@@ -0,0 +1,222 @@
1
+ <template>
2
+ <div class="table-box">
3
+ <ProTable ref="table" row-key="id" :columns="columns" :request-api="requestTable" />
4
+
5
+ <el-drawer
6
+ v-model="detail.visible"
7
+ :title="t('system.ai.query.detail.title')"
8
+ size="min(720px, calc(100vw - 32px))"
9
+ destroy-on-close
10
+ >
11
+ <div v-if="detail.row" v-loading="detail.loading" class="ai-query-detail">
12
+ <div class="ai-query-detail__meta">
13
+ <span>{{ t("system.ai.query.field.user") }}:{{ displayUser(detail.row) }}</span>
14
+ <el-tag :type="statusTagType(detail.row.status)" size="small" effect="plain">
15
+ {{ statusLabel(detail.row.status) }}
16
+ </el-tag>
17
+ <span>{{ t("system.ai.query.field.row_count") }}:{{ detail.row.row_count }}</span>
18
+ <span>{{ t("system.ai.query.field.elapsed_ms") }}:{{ detail.row.elapsed_ms }}</span>
19
+ <span>{{ t("common.field.created_at") }}:{{ detail.row.created_at }}</span>
20
+ </div>
21
+ <div class="ai-query-detail__question">{{ detail.row.question }}</div>
22
+ <div v-if="detail.row.error_msg" class="ai-query-detail__error">
23
+ {{ t("system.ai.query.field.error_msg") }}:{{ detail.row.error_msg }}
24
+ </div>
25
+ <div class="ai-query-detail__sql-title">{{ t("system.ai.query.field.sql") }}</div>
26
+ <CodeBlock class="ai-query-detail__sql" :code="detail.row.sql || '-'" language="sql" />
27
+ <template v-if="resultRows.length">
28
+ <div class="ai-query-detail__sql-title">{{ t("system.ai.query.detail.result") }}</div>
29
+ <el-table :data="resultRows" size="small" max-height="320" border class="ai-query-detail__result">
30
+ <el-table-column
31
+ v-for="(column, index) in detail.row.result_columns"
32
+ :key="`${column}-${index}`"
33
+ :prop="String(index)"
34
+ :label="column"
35
+ show-overflow-tooltip
36
+ min-width="120"
37
+ />
38
+ </el-table>
39
+ </template>
40
+ </div>
41
+ </el-drawer>
42
+ </div>
43
+ </template>
44
+
45
+ <script setup lang="ts">
46
+ import { computed, reactive, ref } from "vue";
47
+ import { View } from "@element-plus/icons-vue";
48
+ import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
49
+ import CodeBlock from "@liujitcn/kratos-admin-core/components/CodeBlock/index.vue";
50
+ import type { ColumnProps, EnumProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
51
+ import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
52
+ import { buildPageRequest } from "@liujitcn/kratos-admin-core/table";
53
+ import { t } from "@liujitcn/kratos-admin-core";
54
+ import { defAiQueryService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/ai_query";
55
+ import type { AiQueryRecord, PageAiQueryRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/ai_query";
56
+ import { AiQueryStatus } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/ai_query";
57
+
58
+ defineOptions({ name: "AiQuery", inheritAttrs: false });
59
+
60
+ const { BUTTONS } = useAuthButtons();
61
+ const table = ref<ProTableInstance>();
62
+ const detail = reactive({
63
+ visible: false,
64
+ row: null as AiQueryRecord | null,
65
+ loading: false
66
+ });
67
+
68
+ /** 问数状态枚举选项。 */
69
+ const statusEnum = computed<EnumProps[]>(() => [
70
+ { label: t("system.ai.query.status.success"), value: AiQueryStatus.AI_QUERY_STATUS_SUCCESS, tagType: "success" },
71
+ { label: t("system.ai.query.status.failed"), value: AiQueryStatus.AI_QUERY_STATUS_FAILED, tagType: "danger" }
72
+ ]);
73
+
74
+ const columns = computed<ColumnProps[]>(() => [
75
+ { prop: "id", label: t("system.ai.query.field.id"), width: 90, align: "center" },
76
+ {
77
+ prop: "question",
78
+ label: t("system.ai.query.field.question"),
79
+ minWidth: 220,
80
+ showOverflowTooltip: true,
81
+ search: { el: "input" }
82
+ },
83
+ {
84
+ prop: "user",
85
+ label: t("system.ai.query.field.user"),
86
+ minWidth: 120,
87
+ showOverflowTooltip: true,
88
+ render: scope => displayUser(scope.row as AiQueryRecord)
89
+ },
90
+ {
91
+ prop: "status",
92
+ label: t("system.ai.query.field.status"),
93
+ width: 110,
94
+ align: "center",
95
+ enum: statusEnum,
96
+ search: { el: "select", props: { clearable: true } }
97
+ },
98
+ { prop: "row_count", label: t("system.ai.query.field.row_count"), width: 100, align: "right" },
99
+ { prop: "elapsed_ms", label: t("system.ai.query.field.elapsed_ms"), width: 110, align: "right" },
100
+ { prop: "created_at", label: t("common.field.created_at"), minWidth: 170, align: "center" },
101
+ {
102
+ prop: "operation",
103
+ label: t("common.field.operation"),
104
+ cellType: "actions",
105
+ fixed: "right",
106
+ width: 120,
107
+ actions: [
108
+ {
109
+ label: t("system.ai.query.action.detail"),
110
+ link: true,
111
+ icon: View,
112
+ hidden: () => !BUTTONS.value["base:ai-query:detail"],
113
+ onClick: scope => openDetail(scope.row as AiQueryRecord)
114
+ }
115
+ ]
116
+ }
117
+ ]);
118
+
119
+ /** 请求智能问数记录表格数据。 */
120
+ async function requestTable(params: Record<string, unknown>) {
121
+ const request = buildPageRequest<PageAiQueryRequest>(params as unknown as PageAiQueryRequest);
122
+ if (!request.status) delete request.status;
123
+ const data = await defAiQueryService.PageAiQuery(request);
124
+ return { data: { list: data.queries ?? [], total: data.total } };
125
+ }
126
+
127
+ /** 展示记录所属用户,昵称缺失时回退账号。 */
128
+ function displayUser(row: AiQueryRecord): string {
129
+ return row.user_name || String(row.user_id ?? "");
130
+ }
131
+
132
+ /** 转换问数状态为展示文案。 */
133
+ function statusLabel(status?: AiQueryStatus): string {
134
+ return status === AiQueryStatus.AI_QUERY_STATUS_SUCCESS
135
+ ? t("system.ai.query.status.success")
136
+ : status === AiQueryStatus.AI_QUERY_STATUS_FAILED
137
+ ? t("system.ai.query.status.failed")
138
+ : "";
139
+ }
140
+
141
+ /** 转换问数状态为标签类型。 */
142
+ function statusTagType(status?: AiQueryStatus): "success" | "danger" | "info" {
143
+ switch (status) {
144
+ case AiQueryStatus.AI_QUERY_STATUS_SUCCESS:
145
+ return "success";
146
+ case AiQueryStatus.AI_QUERY_STATUS_FAILED:
147
+ return "danger";
148
+ default:
149
+ return "info";
150
+ }
151
+ }
152
+
153
+ /** 打开问数详情抽屉。 */
154
+ async function openDetail(row: AiQueryRecord) {
155
+ detail.row = row;
156
+ detail.visible = true;
157
+ detail.loading = true;
158
+ try {
159
+ detail.row = await defAiQueryService.GetAiQuery({ id: row.id });
160
+ } finally {
161
+ detail.loading = false;
162
+ }
163
+ }
164
+
165
+ /** 将结果快照行转换为表格行数据,按列下标取值。 */
166
+ const resultRows = computed<Record<string, string>[]>(() => {
167
+ const columns = detail.row?.result_columns ?? [];
168
+ return (detail.row?.result_rows ?? []).map(row => {
169
+ const item: Record<string, string> = {};
170
+ columns.forEach((_, index) => {
171
+ item[String(index)] = row.values[index] ?? "";
172
+ });
173
+ return item;
174
+ });
175
+ });
176
+ </script>
177
+
178
+ <style scoped>
179
+ .ai-query-detail {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: 16px;
183
+ }
184
+
185
+ .ai-query-detail__meta {
186
+ display: flex;
187
+ align-items: center;
188
+ flex-wrap: wrap;
189
+ gap: 12px;
190
+ color: var(--el-text-color-secondary);
191
+ font-size: 12px;
192
+ }
193
+
194
+ .ai-query-detail__question {
195
+ font-size: 14px;
196
+ font-weight: 600;
197
+ line-height: 1.6;
198
+ }
199
+
200
+ .ai-query-detail__error {
201
+ padding: 8px 12px;
202
+ color: var(--el-color-danger);
203
+ font-size: 13px;
204
+ border: 1px solid var(--el-color-danger-light-8);
205
+ border-radius: 6px;
206
+ background-color: var(--el-color-danger-light-9);
207
+ }
208
+
209
+ .ai-query-detail__sql-title {
210
+ color: var(--el-text-color-secondary);
211
+ font-size: 12px;
212
+ font-weight: 600;
213
+ }
214
+
215
+ .ai-query-detail__result {
216
+ width: 100%;
217
+ }
218
+
219
+ :deep(.ai-query-detail__result .el-table__cell) {
220
+ font-size: 12px;
221
+ }
222
+ </style>