@liujitcn/kratos-uni-app-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.
package/dist/index.mjs CHANGED
@@ -21,6 +21,10 @@ var NotificationServiceImpl = class {
21
21
  const data = Object.fromEntries(
22
22
  Object.entries(request).filter(([, value]) => value !== void 0)
23
23
  );
24
+ if (data.page_num === void 0)
25
+ data.page_num = 1;
26
+ if (data.page_size === void 0)
27
+ data.page_size = 20;
24
28
  const response = await http({
25
29
  url: NOTIFICATION_URL,
26
30
  method: "GET",
@@ -372,7 +376,12 @@ var zh_CN_default = {
372
376
  "system.ai.model.load_failed": "\u52A0\u8F7D\u4F9B\u5E94\u5546\u548C\u6A21\u578B\u5931\u8D25",
373
377
  "system.ai.model.title": "\u9009\u62E9\u6A21\u578B",
374
378
  "system.ai.model.provider_model_count": "{providers} \u4E2A\u4F9B\u5E94\u5546 \xB7 {models} \u4E2A\u6A21\u578B",
375
- "system.ai.model.empty": "\u6682\u65E0\u53EF\u9009\u6A21\u578B"
379
+ "system.ai.model.empty": "\u6682\u65E0\u53EF\u9009\u6A21\u578B",
380
+ "system.ai.knowledge.title": "\u77E5\u8BC6\u5E93",
381
+ "system.ai.knowledge.selected_count": "\u5DF2\u9009 {count} \u4E2A\u77E5\u8BC6\u5E93",
382
+ "system.ai.knowledge.doc_count": "{count} \u7BC7\u6587\u6863",
383
+ "system.ai.knowledge.select_hint": "\u52FE\u9009\u8981\u6CE8\u5165\u4E0A\u4E0B\u6587\u7684\u77E5\u8BC6\u5E93",
384
+ "system.ai.knowledge.empty": "\u6682\u65E0\u77E5\u8BC6\u5E93"
376
385
  };
377
386
 
378
387
  // src/locales/en-US.json
@@ -494,7 +503,12 @@ var en_US_default = {
494
503
  "system.ai.model.load_failed": "Failed to load providers and models",
495
504
  "system.ai.model.title": "Select Model",
496
505
  "system.ai.model.provider_model_count": "{providers} providers \xB7 {models} models",
497
- "system.ai.model.empty": "No models available"
506
+ "system.ai.model.empty": "No models available",
507
+ "system.ai.knowledge.title": "Knowledge Base",
508
+ "system.ai.knowledge.selected_count": "{count} knowledge base(s) selected",
509
+ "system.ai.knowledge.doc_count": "{count} doc(s)",
510
+ "system.ai.knowledge.select_hint": "Select knowledge bases to inject into context",
511
+ "system.ai.knowledge.empty": "No knowledge base available"
498
512
  };
499
513
 
500
514
  // src/locales/ja-JP.json
@@ -616,7 +630,12 @@ var ja_JP_default = {
616
630
  "system.ai.model.load_failed": "\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u3068\u30E2\u30C7\u30EB\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F",
617
631
  "system.ai.model.title": "\u30E2\u30C7\u30EB\u3092\u9078\u629E",
618
632
  "system.ai.model.provider_model_count": "{providers} \u30D7\u30ED\u30D0\u30A4\u30C0\u30FC \xB7 {models} \u30E2\u30C7\u30EB",
619
- "system.ai.model.empty": "\u9078\u629E\u53EF\u80FD\u306A\u30E2\u30C7\u30EB\u304C\u3042\u308A\u307E\u305B\u3093"
633
+ "system.ai.model.empty": "\u9078\u629E\u53EF\u80FD\u306A\u30E2\u30C7\u30EB\u304C\u3042\u308A\u307E\u305B\u3093",
634
+ "system.ai.knowledge.title": "\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9",
635
+ "system.ai.knowledge.selected_count": "{count} \u4EF6\u306E\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9\u3092\u9078\u629E\u4E2D",
636
+ "system.ai.knowledge.doc_count": "{count} \u4EF6\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8",
637
+ "system.ai.knowledge.select_hint": "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306B\u6CE8\u5165\u3059\u308B\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9\u3092\u9078\u629E",
638
+ "system.ai.knowledge.empty": "\u5229\u7528\u53EF\u80FD\u306A\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9\u304C\u3042\u308A\u307E\u305B\u3093"
620
639
  };
621
640
 
622
641
  // src/locales/zh-TW.json
@@ -738,7 +757,12 @@ var zh_TW_default = {
738
757
  "system.ai.model.load_failed": "\u8F09\u5165\u4F9B\u61C9\u5546\u8207\u6A21\u578B\u5931\u6557",
739
758
  "system.ai.model.title": "\u9078\u64C7\u6A21\u578B",
740
759
  "system.ai.model.provider_model_count": "{providers} \u500B\u4F9B\u61C9\u5546 \xB7 {models} \u500B\u6A21\u578B",
741
- "system.ai.model.empty": "\u66AB\u7121\u53EF\u9078\u6A21\u578B"
760
+ "system.ai.model.empty": "\u66AB\u7121\u53EF\u9078\u6A21\u578B",
761
+ "system.ai.knowledge.title": "\u77E5\u8B58\u5EAB",
762
+ "system.ai.knowledge.selected_count": "\u5DF2\u9078 {count} \u500B\u77E5\u8B58\u5EAB",
763
+ "system.ai.knowledge.doc_count": "{count} \u7BC7\u6587\u4EF6",
764
+ "system.ai.knowledge.select_hint": "\u52FE\u9078\u8981\u6CE8\u5165\u4E0A\u4E0B\u6587\u7684\u77E5\u8B58\u5EAB",
765
+ "system.ai.knowledge.empty": "\u66AB\u7121\u77E5\u8B58\u5EAB"
742
766
  };
743
767
 
744
768
  // src/locales/generated.ts
package/dist/module.mjs CHANGED
@@ -152,7 +152,12 @@ var zh_CN_default = {
152
152
  "system.ai.model.load_failed": "\u52A0\u8F7D\u4F9B\u5E94\u5546\u548C\u6A21\u578B\u5931\u8D25",
153
153
  "system.ai.model.title": "\u9009\u62E9\u6A21\u578B",
154
154
  "system.ai.model.provider_model_count": "{providers} \u4E2A\u4F9B\u5E94\u5546 \xB7 {models} \u4E2A\u6A21\u578B",
155
- "system.ai.model.empty": "\u6682\u65E0\u53EF\u9009\u6A21\u578B"
155
+ "system.ai.model.empty": "\u6682\u65E0\u53EF\u9009\u6A21\u578B",
156
+ "system.ai.knowledge.title": "\u77E5\u8BC6\u5E93",
157
+ "system.ai.knowledge.selected_count": "\u5DF2\u9009 {count} \u4E2A\u77E5\u8BC6\u5E93",
158
+ "system.ai.knowledge.doc_count": "{count} \u7BC7\u6587\u6863",
159
+ "system.ai.knowledge.select_hint": "\u52FE\u9009\u8981\u6CE8\u5165\u4E0A\u4E0B\u6587\u7684\u77E5\u8BC6\u5E93",
160
+ "system.ai.knowledge.empty": "\u6682\u65E0\u77E5\u8BC6\u5E93"
156
161
  };
157
162
 
158
163
  // src/locales/en-US.json
@@ -274,7 +279,12 @@ var en_US_default = {
274
279
  "system.ai.model.load_failed": "Failed to load providers and models",
275
280
  "system.ai.model.title": "Select Model",
276
281
  "system.ai.model.provider_model_count": "{providers} providers \xB7 {models} models",
277
- "system.ai.model.empty": "No models available"
282
+ "system.ai.model.empty": "No models available",
283
+ "system.ai.knowledge.title": "Knowledge Base",
284
+ "system.ai.knowledge.selected_count": "{count} knowledge base(s) selected",
285
+ "system.ai.knowledge.doc_count": "{count} doc(s)",
286
+ "system.ai.knowledge.select_hint": "Select knowledge bases to inject into context",
287
+ "system.ai.knowledge.empty": "No knowledge base available"
278
288
  };
279
289
 
280
290
  // src/locales/ja-JP.json
@@ -396,7 +406,12 @@ var ja_JP_default = {
396
406
  "system.ai.model.load_failed": "\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u3068\u30E2\u30C7\u30EB\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F",
397
407
  "system.ai.model.title": "\u30E2\u30C7\u30EB\u3092\u9078\u629E",
398
408
  "system.ai.model.provider_model_count": "{providers} \u30D7\u30ED\u30D0\u30A4\u30C0\u30FC \xB7 {models} \u30E2\u30C7\u30EB",
399
- "system.ai.model.empty": "\u9078\u629E\u53EF\u80FD\u306A\u30E2\u30C7\u30EB\u304C\u3042\u308A\u307E\u305B\u3093"
409
+ "system.ai.model.empty": "\u9078\u629E\u53EF\u80FD\u306A\u30E2\u30C7\u30EB\u304C\u3042\u308A\u307E\u305B\u3093",
410
+ "system.ai.knowledge.title": "\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9",
411
+ "system.ai.knowledge.selected_count": "{count} \u4EF6\u306E\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9\u3092\u9078\u629E\u4E2D",
412
+ "system.ai.knowledge.doc_count": "{count} \u4EF6\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8",
413
+ "system.ai.knowledge.select_hint": "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306B\u6CE8\u5165\u3059\u308B\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9\u3092\u9078\u629E",
414
+ "system.ai.knowledge.empty": "\u5229\u7528\u53EF\u80FD\u306A\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9\u304C\u3042\u308A\u307E\u305B\u3093"
400
415
  };
401
416
 
402
417
  // src/locales/zh-TW.json
@@ -518,7 +533,12 @@ var zh_TW_default = {
518
533
  "system.ai.model.load_failed": "\u8F09\u5165\u4F9B\u61C9\u5546\u8207\u6A21\u578B\u5931\u6557",
519
534
  "system.ai.model.title": "\u9078\u64C7\u6A21\u578B",
520
535
  "system.ai.model.provider_model_count": "{providers} \u500B\u4F9B\u61C9\u5546 \xB7 {models} \u500B\u6A21\u578B",
521
- "system.ai.model.empty": "\u66AB\u7121\u53EF\u9078\u6A21\u578B"
536
+ "system.ai.model.empty": "\u66AB\u7121\u53EF\u9078\u6A21\u578B",
537
+ "system.ai.knowledge.title": "\u77E5\u8B58\u5EAB",
538
+ "system.ai.knowledge.selected_count": "\u5DF2\u9078 {count} \u500B\u77E5\u8B58\u5EAB",
539
+ "system.ai.knowledge.doc_count": "{count} \u7BC7\u6587\u4EF6",
540
+ "system.ai.knowledge.select_hint": "\u52FE\u9078\u8981\u6CE8\u5165\u4E0A\u4E0B\u6587\u7684\u77E5\u8B58\u5EAB",
541
+ "system.ai.knowledge.empty": "\u66AB\u7121\u77E5\u8B58\u5EAB"
522
542
  };
523
543
 
524
544
  // src/locales/generated.ts
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "type": "git",
8
8
  "url": "git@github.com:liujitcn/kratos-admin.git"
9
9
  },
10
- "version": "0.0.65",
10
+ "version": "0.0.67",
11
11
  "type": "module",
12
12
  "files": [
13
13
  "dist",
@@ -35,7 +35,7 @@
35
35
  "@dcloudio/uni-app": "3.0.0-5010520260709002",
36
36
  "@dcloudio/uni-ui": "^1.4.28",
37
37
  "vue": "^3.4.21",
38
- "@liujitcn/kratos-uni-app-core": "^0.0.65"
38
+ "@liujitcn/kratos-uni-app-core": "^0.0.67"
39
39
  },
40
40
  "devDependencies": {
41
41
  "esbuild": "^0.20.2",
@@ -0,0 +1,25 @@
1
+ import { http } from '@liujitcn/kratos-uni-app-core/utils/http'
2
+ import type {
3
+ AiKnowledgeService,
4
+ ListAiKnowledgeOptionsRequest,
5
+ ListAiKnowledgeOptionsResponse,
6
+ } from '../../../rpc/base/v1/ai_knowledge'
7
+
8
+ const AI_KNOWLEDGE_OPTIONS_URL = '/v1/base/ai/knowledge/options'
9
+
10
+ /** AI知识库选项服务。 */
11
+ export class AiKnowledgeServiceImpl implements AiKnowledgeService {
12
+ /** 查询可用的AI知识库选项。 */
13
+ ListAiKnowledgeOptions(
14
+ request: ListAiKnowledgeOptionsRequest,
15
+ ): Promise<ListAiKnowledgeOptionsResponse> {
16
+ return http<ListAiKnowledgeOptionsResponse>({
17
+ url: AI_KNOWLEDGE_OPTIONS_URL,
18
+ method: 'GET',
19
+ authMode: 'required',
20
+ data: request,
21
+ })
22
+ }
23
+ }
24
+
25
+ export const defAiKnowledgeService = new AiKnowledgeServiceImpl()
@@ -27,6 +27,9 @@ export class NotificationServiceImpl implements NotificationService {
27
27
  const data = Object.fromEntries(
28
28
  Object.entries(request).filter(([, value]) => value !== undefined),
29
29
  )
30
+ // 后端 buf.validate 要求 page_num>=1;游标模式下该值不参与查询,补默认值保证校验通过。
31
+ if (data.page_num === undefined) data.page_num = 1
32
+ if (data.page_size === undefined) data.page_size = 20
30
33
  const response = await http<Partial<PageNotificationResponse>>({
31
34
  url: NOTIFICATION_URL,
32
35
  method: 'GET',
@@ -11,4 +11,4 @@
11
11
 
12
12
  语言键必须使用 `system.` 命名空间;语言包保持标准 JSON,不在文件内写注释。新增语言后执行仓库根目录的 `make i18n`。
13
13
 
14
- 新增语言的完整文件清单和迁移流程见[国际化语言扩展指南](../../../../../../../docs/国际化语言扩展指南.md)。
14
+ 新增语言的完整文件清单和迁移流程见[国际化设计与语言扩展](../../../../../../../docs/国际化最终方案.md)。
@@ -116,5 +116,10 @@
116
116
  "system.ai.model.load_failed": "Failed to load providers and models",
117
117
  "system.ai.model.title": "Select Model",
118
118
  "system.ai.model.provider_model_count": "{providers} providers · {models} models",
119
- "system.ai.model.empty": "No models available"
119
+ "system.ai.model.empty": "No models available",
120
+ "system.ai.knowledge.title": "Knowledge Base",
121
+ "system.ai.knowledge.selected_count": "{count} knowledge base(s) selected",
122
+ "system.ai.knowledge.doc_count": "{count} doc(s)",
123
+ "system.ai.knowledge.select_hint": "Select knowledge bases to inject into context",
124
+ "system.ai.knowledge.empty": "No knowledge base available"
120
125
  }
@@ -116,5 +116,10 @@
116
116
  "system.ai.model.load_failed": "プロバイダーとモデルを読み込めませんでした",
117
117
  "system.ai.model.title": "モデルを選択",
118
118
  "system.ai.model.provider_model_count": "{providers} プロバイダー · {models} モデル",
119
- "system.ai.model.empty": "選択可能なモデルがありません"
119
+ "system.ai.model.empty": "選択可能なモデルがありません",
120
+ "system.ai.knowledge.title": "ナレッジベース",
121
+ "system.ai.knowledge.selected_count": "{count} 件のナレッジベースを選択中",
122
+ "system.ai.knowledge.doc_count": "{count} 件のドキュメント",
123
+ "system.ai.knowledge.select_hint": "コンテキストに注入するナレッジベースを選択",
124
+ "system.ai.knowledge.empty": "利用可能なナレッジベースがありません"
120
125
  }
@@ -116,5 +116,10 @@
116
116
  "system.ai.model.load_failed": "加载供应商和模型失败",
117
117
  "system.ai.model.title": "选择模型",
118
118
  "system.ai.model.provider_model_count": "{providers} 个供应商 · {models} 个模型",
119
- "system.ai.model.empty": "暂无可选模型"
119
+ "system.ai.model.empty": "暂无可选模型",
120
+ "system.ai.knowledge.title": "知识库",
121
+ "system.ai.knowledge.selected_count": "已选 {count} 个知识库",
122
+ "system.ai.knowledge.doc_count": "{count} 篇文档",
123
+ "system.ai.knowledge.select_hint": "勾选要注入上下文的知识库",
124
+ "system.ai.knowledge.empty": "暂无知识库"
120
125
  }
@@ -116,5 +116,10 @@
116
116
  "system.ai.model.load_failed": "載入供應商與模型失敗",
117
117
  "system.ai.model.title": "選擇模型",
118
118
  "system.ai.model.provider_model_count": "{providers} 個供應商 · {models} 個模型",
119
- "system.ai.model.empty": "暫無可選模型"
119
+ "system.ai.model.empty": "暫無可選模型",
120
+ "system.ai.knowledge.title": "知識庫",
121
+ "system.ai.knowledge.selected_count": "已選 {count} 個知識庫",
122
+ "system.ai.knowledge.doc_count": "{count} 篇文件",
123
+ "system.ai.knowledge.select_hint": "勾選要注入上下文的知識庫",
124
+ "system.ai.knowledge.empty": "暫無知識庫"
120
125
  }
@@ -0,0 +1,33 @@
1
+ // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
+ // versions:
3
+ // protoc-gen-ts_proto v2.11.8
4
+ // protoc unknown
5
+ // source: base/v1/ai_knowledge.proto
6
+
7
+ /* eslint-disable */
8
+
9
+ /** AI知识库选项查询请求。 */
10
+ export interface ListAiKnowledgeOptionsRequest {
11
+ }
12
+
13
+ /** AI知识库选项查询响应。 */
14
+ export interface ListAiKnowledgeOptionsResponse {
15
+ /** 知识库选项列表 */
16
+ options: AiKnowledgeOption[];
17
+ }
18
+
19
+ /** AI知识库选项。 */
20
+ export interface AiKnowledgeOption {
21
+ /** 知识库ID */
22
+ id: number;
23
+ /** 知识库名称 */
24
+ name: string;
25
+ /** 已就绪文档数,不含处理失败 */
26
+ doc_count: number;
27
+ }
28
+
29
+ /** AI 知识库选项查询服务。 */
30
+ export interface AiKnowledgeService {
31
+ /** 查询可用的AI知识库选项。 */
32
+ ListAiKnowledgeOptions(request: ListAiKnowledgeOptionsRequest): Promise<ListAiKnowledgeOptionsResponse>;
33
+ }
@@ -53,6 +53,8 @@ export interface SendAiMessageRequest {
53
53
  provider_id: number;
54
54
  /** 所选模型名称 */
55
55
  model_name: string;
56
+ /** 所选知识库ID列表 */
57
+ knowledge_base_ids: number[];
56
58
  }
57
59
 
58
60
  /** AI 助手失败消息重试请求 */
@@ -1,9 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref } from 'vue'
3
+ import { useI18n } from '@liujitcn/kratos-uni-app-core'
3
4
  import type { AiAttachment } from '../../../../rpc/base/v1/ai_session'
4
5
  import type { AiProviderModelOption } from '../../../../rpc/base/v1/ai_provider'
6
+ import type { AiKnowledgeOption } from '../../../../rpc/base/v1/ai_knowledge'
5
7
  import { fallbackModelDisplayName } from '../modelDisplay'
6
8
  import ModelPicker from './ModelPicker.vue'
9
+ import KnowledgePicker from './KnowledgePicker.vue'
10
+
11
+ const { t } = useI18n()
7
12
 
8
13
  type InputEventValue = {
9
14
  detail: {
@@ -19,6 +24,10 @@ const props = defineProps<{
19
24
  /** 当前选中的供应商编号和模型名称。 */
20
25
  providerId: number
21
26
  modelName: string
27
+ /** 当前可供选择的知识库。 */
28
+ knowledgeOptions: AiKnowledgeOption[]
29
+ /** 当前选择的知识库编号列表。 */
30
+ knowledgeIds: number[]
22
31
  modelPlaceholder: string
23
32
  placeholder: string
24
33
  bottom: string
@@ -34,9 +43,11 @@ const emit = defineEmits<{
34
43
  send: []
35
44
  'remove-attachment': [attachment: AiAttachment]
36
45
  'model-change': [providerId: number, modelName: string]
46
+ 'knowledge-change': [knowledgeIds: number[]]
37
47
  }>()
38
48
 
39
49
  const showModelPicker = ref(false)
50
+ const showKnowledgePicker = ref(false)
40
51
 
41
52
  const selectedModelDisplayName = computed(() => {
42
53
  const provider = props.modelProviders.find((item) => item.provider_id === props.providerId)
@@ -59,6 +70,23 @@ function handleModelSelect(providerId: number, modelName: string) {
59
70
  showModelPicker.value = false
60
71
  emit('model-change', providerId, modelName)
61
72
  }
73
+
74
+ function openKnowledgePicker() {
75
+ if (props.sending || !props.knowledgeOptions.length) {
76
+ return
77
+ }
78
+ showKnowledgePicker.value = true
79
+ }
80
+
81
+ function handleKnowledgeToggle(knowledgeId: number) {
82
+ const current = new Set(props.knowledgeIds)
83
+ if (current.has(knowledgeId)) {
84
+ current.delete(knowledgeId)
85
+ } else {
86
+ current.add(knowledgeId)
87
+ }
88
+ emit('knowledge-change', [...current])
89
+ }
62
90
  </script>
63
91
 
64
92
  <template>
@@ -69,7 +97,25 @@ function handleModelSelect(providerId: number, modelName: string) {
69
97
  @tap="openModelPicker"
70
98
  >
71
99
  <text class="composer-model-chip__spark">✦</text>
72
- <text class="composer-model-chip__label">{{ selectedModelDisplayName || modelPlaceholder }}</text>
100
+ <text class="composer-model-chip__label">{{
101
+ selectedModelDisplayName || modelPlaceholder
102
+ }}</text>
103
+ <uni-icons type="down" size="12" color="#8a8f99" />
104
+ </view>
105
+ <view
106
+ class="composer-model-chip"
107
+ :class="{
108
+ 'is-disabled': sending || !knowledgeOptions.length,
109
+ 'is-selected': knowledgeIds.length > 0,
110
+ }"
111
+ @tap="openKnowledgePicker"
112
+ >
113
+ <text class="composer-model-chip__spark">📚</text>
114
+ <text class="composer-model-chip__label">{{
115
+ knowledgeIds.length > 0
116
+ ? t('system.ai.knowledge.selected_count', { count: knowledgeIds.length })
117
+ : t('system.ai.knowledge.title')
118
+ }}</text>
73
119
  <uni-icons type="down" size="12" color="#8a8f99" />
74
120
  </view>
75
121
  <view class="composer-main">
@@ -124,6 +170,14 @@ function handleModelSelect(providerId: number, modelName: string) {
124
170
  @select="handleModelSelect"
125
171
  @close="showModelPicker = false"
126
172
  />
173
+
174
+ <KnowledgePicker
175
+ v-if="showKnowledgePicker"
176
+ :options="knowledgeOptions"
177
+ :selected-ids="knowledgeIds"
178
+ @toggle="handleKnowledgeToggle"
179
+ @close="showKnowledgePicker = false"
180
+ />
127
181
  </view>
128
182
  </template>
129
183
 
@@ -0,0 +1,181 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { useI18n } from '@liujitcn/kratos-uni-app-core'
4
+ import type { AiKnowledgeOption } from '../../../../rpc/base/v1/ai_knowledge'
5
+
6
+ const props = defineProps<{
7
+ options: AiKnowledgeOption[]
8
+ selectedIds: number[]
9
+ }>()
10
+
11
+ const emit = defineEmits<{
12
+ toggle: [knowledgeId: number]
13
+ close: []
14
+ }>()
15
+
16
+ const { t } = useI18n()
17
+
18
+ const selectedCount = computed(() => props.selectedIds.length)
19
+
20
+ function isSelected(knowledgeId: number) {
21
+ return props.selectedIds.includes(knowledgeId)
22
+ }
23
+
24
+ function handleToggle(knowledgeId: number) {
25
+ emit('toggle', knowledgeId)
26
+ }
27
+ </script>
28
+
29
+ <template>
30
+ <view class="knowledge-picker">
31
+ <view class="knowledge-picker__mask" @tap="emit('close')" />
32
+ <view class="knowledge-picker__sheet">
33
+ <view class="knowledge-picker__handle" />
34
+ <view class="knowledge-picker__head">
35
+ <text class="knowledge-picker__title">{{ t('system.ai.knowledge.title') }}</text>
36
+ <text class="knowledge-picker__count">{{
37
+ t('system.ai.knowledge.selected_count', { count: selectedCount })
38
+ }}</text>
39
+ <button class="knowledge-picker__close" hover-class="none" @tap="emit('close')">✕</button>
40
+ </view>
41
+ <text class="knowledge-picker__hint">{{ t('system.ai.knowledge.select_hint') }}</text>
42
+ <scroll-view class="knowledge-picker__body" scroll-y :show-scrollbar="true">
43
+ <view
44
+ v-for="option in options"
45
+ :key="option.id"
46
+ class="knowledge-picker__row"
47
+ :class="{ 'is-selected': isSelected(option.id) }"
48
+ @tap="handleToggle(option.id)"
49
+ >
50
+ <view class="knowledge-picker__row-main">
51
+ <text class="knowledge-picker__row-name">{{ option.name }}</text>
52
+ <text v-if="option.doc_count" class="knowledge-picker__row-meta">{{
53
+ t('system.ai.knowledge.doc_count', { count: option.doc_count })
54
+ }}</text>
55
+ </view>
56
+ <view class="knowledge-picker__check" :class="{ 'is-checked': isSelected(option.id) }">
57
+ <text v-if="isSelected(option.id)">✓</text>
58
+ </view>
59
+ </view>
60
+ </scroll-view>
61
+ </view>
62
+ </view>
63
+ </template>
64
+
65
+ <style lang="scss" scoped>
66
+ .knowledge-picker {
67
+ position: fixed;
68
+ inset: 0;
69
+ z-index: 1000;
70
+ display: flex;
71
+ align-items: flex-end;
72
+ }
73
+
74
+ .knowledge-picker__mask {
75
+ position: absolute;
76
+ inset: 0;
77
+ background: rgba(0, 0, 0, 0.45);
78
+ }
79
+
80
+ .knowledge-picker__sheet {
81
+ position: relative;
82
+ display: flex;
83
+ flex-direction: column;
84
+ width: 100%;
85
+ max-height: 62vh;
86
+ padding: 10px 20px calc(20px + env(safe-area-inset-bottom));
87
+ background: #fff;
88
+ border-radius: 20px 20px 0 0;
89
+ }
90
+
91
+ .knowledge-picker__handle {
92
+ width: 36px;
93
+ height: 4px;
94
+ margin: 0 auto 10px;
95
+ background: #e5e6eb;
96
+ border-radius: 2px;
97
+ }
98
+
99
+ .knowledge-picker__head {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 10px;
103
+ }
104
+
105
+ .knowledge-picker__title {
106
+ font-size: 17px;
107
+ font-weight: 600;
108
+ color: #111;
109
+ }
110
+
111
+ .knowledge-picker__count {
112
+ flex: 1;
113
+ font-size: 12px;
114
+ color: #8a8f99;
115
+ }
116
+
117
+ .knowledge-picker__close {
118
+ padding: 0;
119
+ font-size: 16px;
120
+ line-height: 1;
121
+ color: #8a8f99;
122
+ background: transparent;
123
+ }
124
+
125
+ .knowledge-picker__close::after {
126
+ border: none;
127
+ }
128
+
129
+ .knowledge-picker__hint {
130
+ margin-top: 6px;
131
+ font-size: 12px;
132
+ color: #8a8f99;
133
+ }
134
+
135
+ .knowledge-picker__body {
136
+ margin-top: 10px;
137
+ }
138
+
139
+ .knowledge-picker__row {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: space-between;
143
+ gap: 12px;
144
+ padding: 12px 4px;
145
+ border-bottom: 1px solid #f2f3f5;
146
+ }
147
+
148
+ .knowledge-picker__row-main {
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: 2px;
152
+ min-width: 0;
153
+ }
154
+
155
+ .knowledge-picker__row-name {
156
+ font-size: 15px;
157
+ color: #111;
158
+ }
159
+
160
+ .knowledge-picker__row-meta {
161
+ font-size: 12px;
162
+ color: #8a8f99;
163
+ }
164
+
165
+ .knowledge-picker__check {
166
+ display: flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ width: 22px;
170
+ height: 22px;
171
+ border: 1px solid #d0d2d6;
172
+ border-radius: 50%;
173
+ font-size: 13px;
174
+ color: #fff;
175
+ }
176
+
177
+ .knowledge-picker__check.is-checked {
178
+ background: #00a96b;
179
+ border-color: #00a96b;
180
+ }
181
+ </style>
@@ -8,9 +8,11 @@ import {
8
8
  import { defAiSessionService } from '../../../api/base/v1/ai_session'
9
9
  import { defAiToolService } from '../../../api/base/v1/ai_tool'
10
10
  import { defAiModelService } from '../../../api/base/v1/ai_provider'
11
+ import { defAiKnowledgeService } from '../../../api/base/v1/ai_knowledge'
11
12
  import type { AiMessage } from '../../../rpc/base/v1/ai_session'
12
13
  import type { AiAttachment, AiSession } from '../../../rpc/base/v1/ai_session'
13
14
  import type { AiProviderModelOption } from '../../../rpc/base/v1/ai_provider'
15
+ import type { AiKnowledgeOption } from '../../../rpc/base/v1/ai_knowledge'
14
16
  import type { AiShortcut, AiToolCall } from '../../../rpc/base/v1/ai_tool'
15
17
  import { AiMessageStatus } from '../../../rpc/base/v1/ai_session'
16
18
  import { Terminal } from '../../../rpc/base/v1/ai_tool'
@@ -100,6 +102,8 @@ const selectedAttachments = ref<AiAttachment[]>([])
100
102
  const modelProviders = ref<AiProviderModelOption[]>([])
101
103
  const selectedProviderId = ref(0)
102
104
  const selectedModelName = ref('')
105
+ const knowledgeOptions = ref<AiKnowledgeOption[]>([])
106
+ const selectedKnowledgeIds = ref<number[]>([])
103
107
  const runningStreamTaskMap = new Map<string, StreamTask>()
104
108
  const pendingDeltaMap = new Map<string, AiStreamPayload>()
105
109
  let pendingDeltaTimer = 0
@@ -472,11 +476,16 @@ async function loadAiShortcuts() {
472
476
  }
473
477
  loadingShortcuts.value = true
474
478
  try {
475
- const [response, providerResponse] = await Promise.all([
479
+ const [response, providerResponse, knowledgeResponse] = await Promise.all([
476
480
  defAiToolService.ListAiShortcut({ terminal: AI_TERMINAL }),
477
481
  defAiModelService.ListAiProviderModelOptions({}),
482
+ defAiKnowledgeService.ListAiKnowledgeOptions({}),
478
483
  ])
479
484
  modelProviders.value = providerResponse.providers ?? []
485
+ knowledgeOptions.value = knowledgeResponse.options ?? []
486
+ selectedKnowledgeIds.value = selectedKnowledgeIds.value.filter((id) =>
487
+ knowledgeOptions.value.some((option) => option.id === id),
488
+ )
480
489
  applyModelSelection()
481
490
  const shortcuts = normalizeStarterShortcuts(response.shortcuts).filter((item) => !item.action)
482
491
  if (shortcuts.length) {
@@ -487,6 +496,7 @@ async function loadAiShortcuts() {
487
496
  modelProviders.value = []
488
497
  selectedProviderId.value = 0
489
498
  selectedModelName.value = ''
499
+ knowledgeOptions.value = []
490
500
  showError(error, t('system.ai.load_shortcuts_failed'))
491
501
  } finally {
492
502
  loadingShortcuts.value = false
@@ -520,6 +530,11 @@ function handleModelChange(providerId: number, modelName: string) {
520
530
  saveModelSelection({ provider_id: providerId, model_name: modelName })
521
531
  }
522
532
 
533
+ /** 更新当前聊天检索注入的知识库选择。 */
534
+ function handleKnowledgeChange(knowledgeIds: number[]) {
535
+ selectedKnowledgeIds.value = knowledgeIds
536
+ }
537
+
523
538
  async function sendAiPayload(payload: { text: string; attachments: AiAttachment[] }) {
524
539
  const sessionID = await ensureActiveSession()
525
540
  if (!sessionID || isSessionSending(sessionID)) {
@@ -550,6 +565,7 @@ async function runAiTask(
550
565
  content: payload.text,
551
566
  provider_id: selectedProviderId.value,
552
567
  model_name: selectedModelName.value,
568
+ knowledge_base_ids: selectedKnowledgeIds.value,
553
569
  attachments: payload.attachments,
554
570
  action: undefined,
555
571
  }
@@ -1293,7 +1309,9 @@ function showError(error: unknown, fallback: string) {
1293
1309
  >
1294
1310
  <view v-if="item.role === 'ai' && item.model" class="reply-meta">
1295
1311
  <text class="reply-tag">{{ t('system.ai.model_reply') }}</text>
1296
- <text class="reply-model">{{ resolveModelDisplayName(modelProviders, item.model) }}</text>
1312
+ <text class="reply-model">{{
1313
+ resolveModelDisplayName(modelProviders, item.model)
1314
+ }}</text>
1297
1315
  </view>
1298
1316
  <view class="bubble-content">{{ item.content }}</view>
1299
1317
  <view v-if="item.attachments.length" class="attachment-list">
@@ -1332,6 +1350,8 @@ function showError(error: unknown, fallback: string) {
1332
1350
  :model-providers="modelProviders"
1333
1351
  :provider-id="selectedProviderId"
1334
1352
  :model-name="selectedModelName"
1353
+ :knowledge-options="knowledgeOptions"
1354
+ :knowledge-ids="selectedKnowledgeIds"
1335
1355
  :model-placeholder="t('system.ai.model.model_placeholder')"
1336
1356
  :placeholder="composerPlaceholder"
1337
1357
  :bottom="composerBottom"
@@ -1342,6 +1362,7 @@ function showError(error: unknown, fallback: string) {
1342
1362
  @record="handleToggleRecord"
1343
1363
  @send="handleSend"
1344
1364
  @model-change="handleModelChange"
1365
+ @knowledge-change="handleKnowledgeChange"
1345
1366
  @remove-attachment="removeSelectedAttachment"
1346
1367
  />
1347
1368