@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.
Files changed (50) 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 +5 -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 +1405 -1089
  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/ai_provider.d.ts +13 -6
  10. package/dist/package/declarations/src/rpc/system/admin/v1/ai_provider.d.ts +85 -4
  11. package/dist/package/declarations/src/rpc/system/admin/v1/ai_query.d.ts +73 -0
  12. package/dist/package/declarations/src/rpc/system/admin/v1/ai_session.d.ts +114 -0
  13. package/dist/package/declarations/src/views/ai/chat/components/ChatPanel.vue.d.ts +9 -2
  14. package/dist/package/declarations/src/views/ai/chat/components/XSender.vue.d.ts +7 -0
  15. package/dist/package/declarations/src/views/ai/chat/modelDisplay.d.ts +5 -0
  16. package/dist/package/declarations/src/views/ai/chat/modelSelection.d.ts +9 -0
  17. package/dist/package/declarations/src/views/ai/query/index.vue.d.ts +3 -0
  18. package/dist/package/declarations/src/views/ai/session/index.vue.d.ts +3 -0
  19. package/dist/package/src/api/base/v1/ai_knowledge.ts +22 -0
  20. package/dist/package/src/api/system/admin/v1/ai_provider.ts +14 -0
  21. package/dist/package/src/api/system/admin/v1/ai_query.ts +25 -0
  22. package/dist/package/src/api/system/admin/v1/ai_session.ts +25 -0
  23. package/dist/package/src/components/account/ChangePasswordForm.vue +1 -1
  24. package/dist/package/src/components/tenant-project/TenantProjectSelect.vue +21 -4
  25. package/dist/package/src/locales/en-US.json +351 -272
  26. package/dist/package/src/locales/ja-JP.json +351 -272
  27. package/dist/package/src/locales/zh-CN.json +361 -282
  28. package/dist/package/src/locales/zh-TW.json +351 -272
  29. package/dist/package/src/rpc/base/v1/ai_knowledge.ts +33 -0
  30. package/dist/package/src/rpc/base/v1/ai_message.ts +2 -0
  31. package/dist/package/src/rpc/base/v1/ai_provider.ts +14 -6
  32. package/dist/package/src/rpc/system/admin/v1/ai_provider.ts +93 -4
  33. package/dist/package/src/rpc/system/admin/v1/ai_query.ts +89 -0
  34. package/dist/package/src/rpc/system/admin/v1/ai_session.ts +131 -0
  35. package/dist/package/src/views/ai/chat/components/ChatPanel.vue +22 -1
  36. package/dist/package/src/views/ai/chat/components/XSender.vue +367 -71
  37. package/dist/package/src/views/ai/chat/index.vue +46 -8
  38. package/dist/package/src/views/ai/chat/modelDisplay.ts +67 -0
  39. package/dist/package/src/views/ai/chat/modelSelection.ts +31 -0
  40. package/dist/package/src/views/ai/query/index.vue +222 -0
  41. package/dist/package/src/views/ai/session/index.vue +422 -0
  42. package/dist/package/src/views/base/ai-provider/index.vue +301 -154
  43. package/dist/package/src/views/base/api-rate-limit-policy/index.vue +4 -3
  44. package/dist/package/src/views/base/message-provider/index.vue +9 -5
  45. package/dist/package/src/views/base/message-template/index.vue +2 -2
  46. package/dist/package/src/views/base/oauth-provider/index.vue +2 -2
  47. package/dist/package/src/views/base/rate-limit-rule/index.vue +8 -3
  48. package/dist/package/src/views/base/tenant-project-grant/index.vue +9 -4
  49. package/dist/package/src/views/tool/code-gen/config.ts +1 -0
  50. package/package.json +2 -2
@@ -0,0 +1,67 @@
1
+ import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
2
+
3
+ /** 常见模型的展示名映射;未命中时按通用规则美化原始名。 */
4
+ const MODEL_DISPLAY_NAMES: Record<string, string> = {
5
+ "deepseek-chat": "DeepSeek Chat",
6
+ "deepseek-reasoner": "DeepSeek Reasoner",
7
+ "deepseek-v3": "DeepSeek V3",
8
+ "deepseek-r1": "DeepSeek R1",
9
+ "gpt-4o": "GPT-4o",
10
+ "gpt-4o-mini": "GPT-4o mini",
11
+ "gpt-4.1": "GPT-4.1",
12
+ "gpt-4.1-mini": "GPT-4.1 mini",
13
+ "gpt-4-turbo": "GPT-4 Turbo",
14
+ "o3-mini": "O3-mini",
15
+ "claude-3-5-sonnet": "Claude 3.5 Sonnet",
16
+ "claude-3-7-sonnet": "Claude 3.7 Sonnet",
17
+ "qwen-max": "Qwen Max",
18
+ "qwen-plus": "Qwen Plus"
19
+ };
20
+
21
+ /** 需要整体大写的模型名片段。 */
22
+ const UPPERCASE_TOKENS = new Set([
23
+ "gpt",
24
+ "llama",
25
+ "qwen",
26
+ "glm",
27
+ "kimi",
28
+ "gemini",
29
+ "claude",
30
+ "doubao",
31
+ "deepseek",
32
+ "moonshot",
33
+ "hunyuan"
34
+ ]);
35
+
36
+ /** 对未配置展示名的模型名做通用美化。 */
37
+ export function fallbackModelDisplayName(modelName: string): string {
38
+ if (!modelName) return "";
39
+ const mapped = MODEL_DISPLAY_NAMES[modelName.toLowerCase()];
40
+ if (mapped) return mapped;
41
+ return modelName
42
+ .split(/[-_]/)
43
+ .filter(Boolean)
44
+ .map(token => {
45
+ const lower = token.toLowerCase();
46
+ if (UPPERCASE_TOKENS.has(lower)) return lower.toUpperCase();
47
+ return lower.charAt(0).toUpperCase() + lower.slice(1);
48
+ })
49
+ .join(" ");
50
+ }
51
+
52
+ /** 从供应商模型选项中解析模型展示名称,兼容历史消息中「供应商 / 模型」格式的原始值。 */
53
+ export function resolveModelDisplayName(
54
+ providers: AiProviderModelOption[] | undefined,
55
+ modelName: string
56
+ ): string {
57
+ if (!modelName) return "";
58
+ const separator = " / ";
59
+ const modelPart = modelName.includes(separator)
60
+ ? modelName.slice(modelName.lastIndexOf(separator) + separator.length)
61
+ : modelName;
62
+ for (const provider of providers ?? []) {
63
+ const matched = provider.models.find(item => item.model_name === modelPart);
64
+ if (matched?.display_name) return matched.display_name;
65
+ }
66
+ return fallbackModelDisplayName(modelPart);
67
+ }
@@ -0,0 +1,31 @@
1
+ /** 用户最近一次选择的供应商与模型,仅保存在浏览器本地。 */
2
+ export interface AiModelSelection {
3
+ provider_id: number;
4
+ model_name: string;
5
+ }
6
+
7
+ const MODEL_SELECTION_STORAGE_KEY = "kratos-admin:ai:model-selection";
8
+
9
+ /** 读取本地缓存的模型选择,缓存缺失或损坏时返回 undefined。 */
10
+ export function loadStoredModelSelection(): AiModelSelection | undefined {
11
+ try {
12
+ const raw = localStorage.getItem(MODEL_SELECTION_STORAGE_KEY);
13
+ if (!raw) return undefined;
14
+ const parsed = JSON.parse(raw) as Partial<AiModelSelection>;
15
+ if (typeof parsed.provider_id === "number" && typeof parsed.model_name === "string" && parsed.model_name) {
16
+ return { provider_id: parsed.provider_id, model_name: parsed.model_name };
17
+ }
18
+ } catch {
19
+ // 缓存损坏或存储不可用时忽略,回退到默认选择。
20
+ }
21
+ return undefined;
22
+ }
23
+
24
+ /** 持久化模型选择,存储不可用时静默失败,选择仍对当前会话生效。 */
25
+ export function saveModelSelection(selection: AiModelSelection): void {
26
+ try {
27
+ localStorage.setItem(MODEL_SELECTION_STORAGE_KEY, JSON.stringify(selection));
28
+ } catch {
29
+ // 忽略存储失败,不影响当前会话内的选择。
30
+ }
31
+ }
@@ -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>