@liujitcn/kratos-admin-system 0.0.61 → 0.0.64
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 +16 -1
- package/dist/package/declarations/src/api/base/v1/ai_provider.d.ts +7 -0
- package/dist/package/declarations/src/api/system/admin/v1/ai_provider.d.ts +18 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_api_rate_limit_policy.d.ts +19 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_message_provider.d.ts +21 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_message_template.d.ts +16 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_oauth_provider.d.ts +18 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_rate_limit_rule.d.ts +21 -0
- package/dist/package/declarations/src/components/api/ApiTransfer.vue.d.ts +35 -0
- package/dist/package/declarations/src/components/api/apiTransfer.d.ts +13 -0
- package/dist/package/declarations/src/components/i18n/DynamicI18nCell.vue.d.ts +2 -2
- package/dist/package/declarations/src/components/i18n/dynamicI18n.d.ts +3 -4
- package/dist/package/declarations/src/locales/generated.d.ts +900 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_message.d.ts +4 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_provider.d.ts +22 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +20 -0
- package/dist/package/declarations/src/rpc/google/protobuf/struct.d.ts +81 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/ai_provider.d.ts +110 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_api_rate_limit_policy.d.ts +137 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +4 -23
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message.d.ts +4 -4
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message_category.d.ts +19 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message_provider.d.ts +126 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message_template.d.ts +99 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_oauth_provider.d.ts +133 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_rate_limit_rule.d.ts +144 -0
- package/dist/package/declarations/src/utils/persisted-message.d.ts +4 -0
- package/dist/package/declarations/src/utils/rateLimit.d.ts +24 -0
- package/dist/package/declarations/src/views/ai/chat/components/ChatPanel.vue.d.ts +11 -2
- package/dist/package/declarations/src/views/ai/chat/components/XSender.vue.d.ts +9 -0
- package/dist/package/declarations/src/views/base/ai-provider/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/api-rate-limit-policy/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/message-provider/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/message-template/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/oauth-provider/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/rate-limit-rule/index.vue.d.ts +3 -0
- package/dist/package/src/api/base/v1/ai_provider.ts +22 -0
- package/dist/package/src/api/system/admin/v1/ai_provider.ts +50 -0
- package/dist/package/src/api/system/admin/v1/base_api_rate_limit_policy.ts +51 -0
- package/dist/package/src/api/system/admin/v1/base_message_provider.ts +57 -0
- package/dist/package/src/api/system/admin/v1/base_message_template.ts +44 -0
- package/dist/package/src/api/system/admin/v1/base_oauth_provider.ts +50 -0
- package/dist/package/src/api/system/admin/v1/base_rate_limit_rule.ts +58 -0
- package/dist/package/src/components/ai/Ai.vue +1 -1
- package/dist/package/src/components/api/ApiTransfer.vue +117 -0
- package/dist/package/src/components/api/apiTransfer.ts +27 -0
- package/dist/package/src/components/i18n/DynamicI18nCell.vue +8 -8
- package/dist/package/src/components/i18n/DynamicI18nEditor.vue +6 -3
- package/dist/package/src/components/i18n/dynamicI18n.ts +4 -5
- package/dist/package/src/components/tenant-project/TenantProjectManager.vue +2 -1
- package/dist/package/src/locales/en-US.json +229 -4
- package/dist/package/src/locales/ja-JP.json +229 -4
- package/dist/package/src/locales/zh-CN.json +229 -4
- package/dist/package/src/locales/zh-TW.json +229 -4
- package/dist/package/src/rpc/base/v1/ai_message.ts +7 -1
- package/dist/package/src/rpc/base/v1/ai_provider.ts +33 -0
- package/dist/package/src/rpc/base/v1/oauth.ts +16 -0
- package/dist/package/src/rpc/google/protobuf/struct.ts +80 -0
- package/dist/package/src/rpc/system/admin/v1/ai_provider.ts +129 -0
- package/dist/package/src/rpc/system/admin/v1/base_api_rate_limit_policy.ts +160 -0
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +4 -24
- package/dist/package/src/rpc/system/admin/v1/base_message.ts +4 -4
- package/dist/package/src/rpc/system/admin/v1/base_message_category.ts +20 -0
- package/dist/package/src/rpc/system/admin/v1/base_message_provider.ts +150 -0
- package/dist/package/src/rpc/system/admin/v1/base_message_template.ts +117 -0
- package/dist/package/src/rpc/system/admin/v1/base_oauth_provider.ts +156 -0
- package/dist/package/src/rpc/system/admin/v1/base_rate_limit_rule.ts +167 -0
- package/dist/package/src/utils/persisted-message.ts +21 -0
- package/dist/package/src/utils/rateLimit.ts +140 -0
- package/dist/package/src/views/ai/chat/components/ChatPanel.vue +36 -4
- package/dist/package/src/views/ai/chat/components/XSender.vue +112 -1
- package/dist/package/src/views/ai/chat/index.vue +30 -1
- package/dist/package/src/views/base/ai-provider/index.vue +512 -0
- package/dist/package/src/views/base/api-rate-limit-policy/index.vue +428 -0
- package/dist/package/src/views/base/backup-management/archive-record/index.vue +2 -0
- package/dist/package/src/views/base/backup-management/archive-restore/index.vue +2 -1
- package/dist/package/src/views/base/backup-management/backup-record/index.vue +2 -0
- package/dist/package/src/views/base/backup-management/backup-restore/index.vue +2 -1
- package/dist/package/src/views/base/config/index.vue +12 -13
- package/dist/package/src/views/base/dict/index.vue +2 -3
- package/dist/package/src/views/base/dict/item.vue +2 -3
- package/dist/package/src/views/base/job/index.vue +2 -3
- package/dist/package/src/views/base/job/log.vue +3 -2
- package/dist/package/src/views/base/menu/index.vue +27 -83
- package/dist/package/src/views/base/message/index.vue +9 -3
- package/dist/package/src/views/base/message-category/index.vue +34 -3
- package/dist/package/src/views/base/message-provider/index.vue +164 -0
- package/dist/package/src/views/base/message-template/index.vue +105 -0
- package/dist/package/src/views/base/migration/index.vue +1 -1
- package/dist/package/src/views/base/oauth-client/index.vue +6 -6
- package/dist/package/src/views/base/oauth-provider/index.vue +274 -0
- package/dist/package/src/views/base/rate-limit-rule/index.vue +322 -0
- package/dist/package/src/views/base/redact-output-policy/index.vue +1 -1
- package/dist/package/src/views/base/redact-rule/index.vue +12 -2
- package/dist/package/src/views/base/redact-storage-policy/index.vue +4 -0
- package/dist/package/src/views/base/tenant/index.vue +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { BaseRateLimitAlgorithm } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_rate_limit_rule";
|
|
2
|
+
|
|
3
|
+
/** 限流算法的可编辑参数集合。 */
|
|
4
|
+
export interface RateLimitParams {
|
|
5
|
+
tokens_per_second?: number;
|
|
6
|
+
burst?: number;
|
|
7
|
+
limit?: number;
|
|
8
|
+
window_seconds?: number;
|
|
9
|
+
leak_rate_per_second?: number;
|
|
10
|
+
capacity?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 返回所选算法的空参数表单。 */
|
|
14
|
+
export function defaultRateLimitParams(algorithm: BaseRateLimitAlgorithm): RateLimitParams {
|
|
15
|
+
switch (algorithm) {
|
|
16
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_TOKEN_BUCKET:
|
|
17
|
+
return { tokens_per_second: undefined, burst: undefined };
|
|
18
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_FIXED_WINDOW:
|
|
19
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_COUNTER:
|
|
20
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_LOG:
|
|
21
|
+
return { limit: undefined, window_seconds: undefined };
|
|
22
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_LEAKY_BUCKET:
|
|
23
|
+
return { leak_rate_per_second: undefined, capacity: undefined };
|
|
24
|
+
default:
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 按算法解析 JSON,只保留该算法支持的参数。 */
|
|
30
|
+
export function parseRateLimitParams(algorithm: BaseRateLimitAlgorithm, raw: string): RateLimitParams {
|
|
31
|
+
let value: Record<string, unknown> = {};
|
|
32
|
+
try {
|
|
33
|
+
const parsed: unknown = JSON.parse(raw);
|
|
34
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) value = parsed as Record<string, unknown>;
|
|
35
|
+
} catch {
|
|
36
|
+
return defaultRateLimitParams(algorithm);
|
|
37
|
+
}
|
|
38
|
+
const number = (key: string) => (typeof value[key] === "number" && Number.isFinite(value[key]) ? (value[key] as number) : undefined);
|
|
39
|
+
switch (algorithm) {
|
|
40
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_TOKEN_BUCKET:
|
|
41
|
+
return { tokens_per_second: number("tokens_per_second"), burst: number("burst") };
|
|
42
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_FIXED_WINDOW:
|
|
43
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_COUNTER:
|
|
44
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_LOG:
|
|
45
|
+
return { limit: number("limit"), window_seconds: number("window_seconds") };
|
|
46
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_LEAKY_BUCKET:
|
|
47
|
+
return { leak_rate_per_second: number("leak_rate_per_second"), capacity: number("capacity") };
|
|
48
|
+
default:
|
|
49
|
+
return defaultRateLimitParams(algorithm);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 校验参数并生成仅包含当前算法字段的 JSON 对象。 */
|
|
54
|
+
export function serializeRateLimitParams(algorithm: BaseRateLimitAlgorithm, params: RateLimitParams): Record<string, number> | undefined {
|
|
55
|
+
switch (algorithm) {
|
|
56
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_TOKEN_BUCKET:
|
|
57
|
+
if (!isRate(params.tokens_per_second) || !isIntegerInRange(params.burst, 1, 1_000_000)) return undefined;
|
|
58
|
+
return { tokens_per_second: params.tokens_per_second, burst: params.burst };
|
|
59
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_FIXED_WINDOW:
|
|
60
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_COUNTER:
|
|
61
|
+
if (!isIntegerInRange(params.limit, 1, 1_000_000) || !isIntegerInRange(params.window_seconds, 1, 86_400)) return undefined;
|
|
62
|
+
return { limit: params.limit, window_seconds: params.window_seconds };
|
|
63
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_LOG:
|
|
64
|
+
if (!isIntegerInRange(params.limit, 1, 10_000) || !isIntegerInRange(params.window_seconds, 1, 86_400)) return undefined;
|
|
65
|
+
return { limit: params.limit, window_seconds: params.window_seconds };
|
|
66
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_LEAKY_BUCKET:
|
|
67
|
+
if (!isRate(params.leak_rate_per_second) || !isIntegerInRange(params.capacity, 1, 1_000_000)) return undefined;
|
|
68
|
+
return { leak_rate_per_second: params.leak_rate_per_second, capacity: params.capacity };
|
|
69
|
+
default:
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 返回可直接交给国际化模板的完整参数。 */
|
|
75
|
+
export function rateLimitSummaryParams(algorithm: BaseRateLimitAlgorithm, raw: string): Record<string, string | number> {
|
|
76
|
+
const params = parseRateLimitParams(algorithm, raw);
|
|
77
|
+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, value ?? 0])) as Record<string, string | number>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 返回限流算法名称对应的多语言资源键。 */
|
|
81
|
+
export function rateLimitAlgorithmLabelKey(algorithm: BaseRateLimitAlgorithm): string {
|
|
82
|
+
switch (algorithm) {
|
|
83
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_TOKEN_BUCKET:
|
|
84
|
+
return "system.base.rate_limit_rule.algorithm.token_bucket";
|
|
85
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_FIXED_WINDOW:
|
|
86
|
+
return "system.base.rate_limit_rule.algorithm.fixed_window";
|
|
87
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_COUNTER:
|
|
88
|
+
return "system.base.rate_limit_rule.algorithm.sliding_window_counter";
|
|
89
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_LOG:
|
|
90
|
+
return "system.base.rate_limit_rule.algorithm.sliding_window_log";
|
|
91
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_LEAKY_BUCKET:
|
|
92
|
+
return "system.base.rate_limit_rule.algorithm.leaky_bucket";
|
|
93
|
+
default:
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 返回所选限流算法说明对应的多语言资源键。 */
|
|
99
|
+
export function rateLimitAlgorithmTooltipKey(algorithm: BaseRateLimitAlgorithm): string {
|
|
100
|
+
switch (algorithm) {
|
|
101
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_TOKEN_BUCKET:
|
|
102
|
+
return "system.base.rate_limit_rule.algorithm_help.token_bucket";
|
|
103
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_FIXED_WINDOW:
|
|
104
|
+
return "system.base.rate_limit_rule.algorithm_help.fixed_window";
|
|
105
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_COUNTER:
|
|
106
|
+
return "system.base.rate_limit_rule.algorithm_help.sliding_window_counter";
|
|
107
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_LOG:
|
|
108
|
+
return "system.base.rate_limit_rule.algorithm_help.sliding_window_log";
|
|
109
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_LEAKY_BUCKET:
|
|
110
|
+
return "system.base.rate_limit_rule.algorithm_help.leaky_bucket";
|
|
111
|
+
default:
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 返回参数摘要对应的多语言资源键。 */
|
|
117
|
+
export function rateLimitSummaryKey(algorithm: BaseRateLimitAlgorithm): string {
|
|
118
|
+
switch (algorithm) {
|
|
119
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_TOKEN_BUCKET:
|
|
120
|
+
return "system.base.rate_limit_rule.summary.token_bucket";
|
|
121
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_FIXED_WINDOW:
|
|
122
|
+
return "system.base.rate_limit_rule.summary.fixed_window";
|
|
123
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_COUNTER:
|
|
124
|
+
return "system.base.rate_limit_rule.summary.sliding_window_counter";
|
|
125
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_SLIDING_WINDOW_LOG:
|
|
126
|
+
return "system.base.rate_limit_rule.summary.sliding_window_log";
|
|
127
|
+
case BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_LEAKY_BUCKET:
|
|
128
|
+
return "system.base.rate_limit_rule.summary.leaky_bucket";
|
|
129
|
+
default:
|
|
130
|
+
return "";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isRate(value: number | undefined): value is number {
|
|
135
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0.001 && value <= 1_000_000;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isIntegerInRange(value: number | undefined, min: number, max: number): value is number {
|
|
139
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
140
|
+
}
|
|
@@ -47,7 +47,15 @@
|
|
|
47
47
|
</div>
|
|
48
48
|
</section>
|
|
49
49
|
<div class="agent-chat-empty__sender">
|
|
50
|
-
<XSender
|
|
50
|
+
<XSender
|
|
51
|
+
:key="senderKey"
|
|
52
|
+
:sending="sending"
|
|
53
|
+
:model-providers="modelProviders"
|
|
54
|
+
:provider-id="providerId"
|
|
55
|
+
:model-name="modelName"
|
|
56
|
+
@submit="handleSubmit"
|
|
57
|
+
@model-change="handleModelChange"
|
|
58
|
+
/>
|
|
51
59
|
</div>
|
|
52
60
|
</div>
|
|
53
61
|
</template>
|
|
@@ -138,7 +146,7 @@
|
|
|
138
146
|
<div class="agent-message-error__content">{{ resolveAIErrorMessage(item) }}</div>
|
|
139
147
|
<el-collapse v-if="item.fallback_reason" class="agent-message-error__detail" accordion>
|
|
140
148
|
<el-collapse-item :title="t('system.ai.chat.title.error_detail')" :name="String(item.id)">
|
|
141
|
-
<pre>{{ item.fallback_reason }}</pre>
|
|
149
|
+
<pre>{{ resolvePersistedMessage(item.fallback_reason) }}</pre>
|
|
142
150
|
</el-collapse-item>
|
|
143
151
|
</el-collapse>
|
|
144
152
|
</div>
|
|
@@ -147,7 +155,7 @@
|
|
|
147
155
|
<slot name="flow-blocks" :message="item" :active-flow-message-id="activeFlowMessageID" />
|
|
148
156
|
<el-collapse v-if="item.fallback_reason" class="agent-message-error__detail" accordion>
|
|
149
157
|
<el-collapse-item :title="t('system.ai.chat.title.error_detail')" :name="String(item.id)">
|
|
150
|
-
<pre>{{ item.fallback_reason }}</pre>
|
|
158
|
+
<pre>{{ resolvePersistedMessage(item.fallback_reason) }}</pre>
|
|
151
159
|
</el-collapse-item>
|
|
152
160
|
</el-collapse>
|
|
153
161
|
</template>
|
|
@@ -320,7 +328,15 @@
|
|
|
320
328
|
</button>
|
|
321
329
|
</div>
|
|
322
330
|
</section>
|
|
323
|
-
<XSender
|
|
331
|
+
<XSender
|
|
332
|
+
:key="senderKey"
|
|
333
|
+
:sending="sending"
|
|
334
|
+
:model-providers="modelProviders"
|
|
335
|
+
:provider-id="providerId"
|
|
336
|
+
:model-name="modelName"
|
|
337
|
+
@submit="handleSubmit"
|
|
338
|
+
@model-change="handleModelChange"
|
|
339
|
+
/>
|
|
324
340
|
</div>
|
|
325
341
|
</template>
|
|
326
342
|
</main>
|
|
@@ -337,8 +353,10 @@ import { getCurrentLocale, t } from "@liujitcn/kratos-admin-core";
|
|
|
337
353
|
import { formatDateTime } from "@liujitcn/kratos-admin-core/format";
|
|
338
354
|
import type { AiAction } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_message";
|
|
339
355
|
import { type AiAttachment, type AiSession } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
356
|
+
import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
|
|
340
357
|
import type { AiShortcut, AiToolCall } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_tool";
|
|
341
358
|
import { AiMessageStatus } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
359
|
+
import { resolvePersistedMessage } from "../../../../utils/persisted-message";
|
|
342
360
|
import XSender from "./XSender.vue";
|
|
343
361
|
|
|
344
362
|
// AI Markdown 渲染器依赖较重,仅在真正出现助手消息时再加载。
|
|
@@ -474,11 +492,19 @@ const props = defineProps<{
|
|
|
474
492
|
loadingShortcuts?: boolean;
|
|
475
493
|
/** 当前终端可用快捷入口。 */
|
|
476
494
|
shortcuts?: AiShortcut[];
|
|
495
|
+
/** 当前启用的供应商和模型。 */
|
|
496
|
+
modelProviders: AiProviderModelOption[];
|
|
497
|
+
/** 当前选择的供应商编号。 */
|
|
498
|
+
providerId: number;
|
|
499
|
+
/** 当前选择的模型名称。 */
|
|
500
|
+
modelName: string;
|
|
477
501
|
}>();
|
|
478
502
|
|
|
479
503
|
const emit = defineEmits<{
|
|
480
504
|
/** 提交输入框内容。 */
|
|
481
505
|
submit: [payload: SubmitPayload];
|
|
506
|
+
/** 更新当前选择的供应商和模型。 */
|
|
507
|
+
modelChange: [providerId: number, modelName: string];
|
|
482
508
|
/** 触发消息级操作。 */
|
|
483
509
|
messageAction: [payload: { action: ChatMessageAction; item: ChatMessageItem }];
|
|
484
510
|
/** 提交当前用户消息的文本编辑。 */
|
|
@@ -614,6 +640,11 @@ function handleSubmit(payload: SubmitPayload) {
|
|
|
614
640
|
emit("submit", payload);
|
|
615
641
|
}
|
|
616
642
|
|
|
643
|
+
/** 转发输入器发出的供应商和模型变更。 */
|
|
644
|
+
function handleModelChange(providerId: number, modelName: string) {
|
|
645
|
+
emit("modelChange", providerId, modelName);
|
|
646
|
+
}
|
|
647
|
+
|
|
617
648
|
/** 点击快捷入口时提交对应入口动作。 */
|
|
618
649
|
function handleShortcutClick(shortcut: AiShortcut) {
|
|
619
650
|
emit("submit", {
|
|
@@ -792,6 +823,7 @@ function resolveVisibleTools(item: ChatMessageItem) {
|
|
|
792
823
|
|
|
793
824
|
/** 生成工具展示名称。 */
|
|
794
825
|
function resolveToolTitle(tool: AiToolCall) {
|
|
826
|
+
if (tool.name === "web_search") return t("system.ai.chat.value.web_search");
|
|
795
827
|
return tool.title || tool.name || t("system.ai.chat.value.tool");
|
|
796
828
|
}
|
|
797
829
|
|
|
@@ -47,13 +47,28 @@
|
|
|
47
47
|
</button>
|
|
48
48
|
</div>
|
|
49
49
|
</el-popover>
|
|
50
|
-
|
|
51
50
|
<span class="agent-action-text">{{ actionHintText }}</span>
|
|
52
51
|
</div>
|
|
53
52
|
</template>
|
|
54
53
|
|
|
55
54
|
<template #action-list>
|
|
56
55
|
<div class="agent-sender-actions">
|
|
56
|
+
<div class="agent-model-selector" :class="{ 'is-disabled': sending || !modelOptions.length }">
|
|
57
|
+
<el-cascader
|
|
58
|
+
:model-value="selectedModelPath"
|
|
59
|
+
:options="modelOptions"
|
|
60
|
+
:props="{ expandTrigger: 'click' }"
|
|
61
|
+
:placeholder="t('system.ai.model.model_placeholder')"
|
|
62
|
+
:disabled="sending || !modelOptions.length"
|
|
63
|
+
:show-all-levels="true"
|
|
64
|
+
separator="/"
|
|
65
|
+
:clearable="true"
|
|
66
|
+
:teleported="true"
|
|
67
|
+
popper-class="agent-model-cascader-popper"
|
|
68
|
+
size="small"
|
|
69
|
+
@change="handleModelPathChange"
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
57
72
|
<el-tooltip
|
|
58
73
|
:content="recording ? t('system.ai.chat.action.stop_voice_input') : t('system.ai.chat.action.voice_input')"
|
|
59
74
|
placement="top"
|
|
@@ -106,9 +121,11 @@ import { Attachments, XSender as BaseXSender } from "vue-element-plus-x";
|
|
|
106
121
|
import type { FilesCardProps } from "vue-element-plus-x/types/FilesCard";
|
|
107
122
|
import { Loading, Microphone, Paperclip, Promotion } from "@element-plus/icons-vue";
|
|
108
123
|
import { ElMessage } from "element-plus";
|
|
124
|
+
import type { CascaderValue } from "element-plus";
|
|
109
125
|
import { t } from "@liujitcn/kratos-admin-core";
|
|
110
126
|
import { defFileService } from "@liujitcn/kratos-admin-core/api/base/v1/file";
|
|
111
127
|
import type { AiAttachment } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
128
|
+
import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
|
|
112
129
|
import type { SubmitPayload } from "../types";
|
|
113
130
|
import { buildAIAttachmentFileCard } from "../attachment";
|
|
114
131
|
import { useSpeechRecognition } from "./speech-recognition";
|
|
@@ -117,17 +134,34 @@ import type { SpeechRecognitionError } from "./speech-recognition";
|
|
|
117
134
|
const props = defineProps<{
|
|
118
135
|
/** 消息发送加载状态。 */
|
|
119
136
|
sending: boolean;
|
|
137
|
+
/** 当前可供选择的供应商与模型。 */
|
|
138
|
+
modelProviders: AiProviderModelOption[];
|
|
139
|
+
/** 当前选择的供应商编号。 */
|
|
140
|
+
providerId: number;
|
|
141
|
+
/** 当前选择的模型名称。 */
|
|
142
|
+
modelName: string;
|
|
120
143
|
}>();
|
|
121
144
|
|
|
122
145
|
const emit = defineEmits<{
|
|
123
146
|
/** 提交输入器内容。 */
|
|
124
147
|
submit: [payload: SubmitPayload];
|
|
148
|
+
/** 更新当前选择的供应商和模型。 */
|
|
149
|
+
"model-change": [providerId: number, modelName: string];
|
|
125
150
|
}>();
|
|
126
151
|
|
|
127
152
|
const senderRef = ref<InstanceType<typeof BaseXSender>>();
|
|
128
153
|
const fileInputRef = ref<HTMLInputElement>();
|
|
129
154
|
const inputText = ref("");
|
|
130
155
|
const selectedAttachments = ref<AiAttachment[]>([]);
|
|
156
|
+
const modelOptions = computed(() =>
|
|
157
|
+
props.modelProviders.map(provider => ({
|
|
158
|
+
value: provider.provider_id,
|
|
159
|
+
label: provider.name,
|
|
160
|
+
disabled: provider.models.length === 0,
|
|
161
|
+
children: provider.models.map(model => ({ value: model, label: model }))
|
|
162
|
+
}))
|
|
163
|
+
);
|
|
164
|
+
const selectedModelPath = computed(() => (props.providerId && props.modelName ? [props.providerId, props.modelName] : []));
|
|
131
165
|
const uploading = ref(false);
|
|
132
166
|
const maxAttachmentCount = 6;
|
|
133
167
|
const maxAttachmentSizeMB = 20;
|
|
@@ -184,6 +218,12 @@ const isSubmitDisabled = computed(() => {
|
|
|
184
218
|
return uploading.value || (!inputText.value.trim() && selectedAttachments.value.length === 0);
|
|
185
219
|
});
|
|
186
220
|
|
|
221
|
+
/** 更新当前选中的供应商和模型路径。 */
|
|
222
|
+
function handleModelPathChange(value: CascaderValue | null | undefined) {
|
|
223
|
+
const path = Array.isArray(value) ? value : [];
|
|
224
|
+
emit("model-change", Number(path[0]) || 0, typeof path[1] === "string" ? path[1] : "");
|
|
225
|
+
}
|
|
226
|
+
|
|
187
227
|
/** 读取输入内容并发送给父组件。 */
|
|
188
228
|
function handleSubmit() {
|
|
189
229
|
if (uploading.value) return;
|
|
@@ -392,6 +432,13 @@ function resetFileInput() {
|
|
|
392
432
|
align-items: center;
|
|
393
433
|
justify-content: space-between;
|
|
394
434
|
}
|
|
435
|
+
:deep(.elx-x-sender__updown-action-list .elx-x-sender__prefix) {
|
|
436
|
+
flex: 1 1 auto;
|
|
437
|
+
width: 100%;
|
|
438
|
+
min-width: 0;
|
|
439
|
+
height: auto;
|
|
440
|
+
padding-left: 0;
|
|
441
|
+
}
|
|
395
442
|
:deep(.elx-x-sender__action-list) {
|
|
396
443
|
height: auto;
|
|
397
444
|
}
|
|
@@ -410,10 +457,71 @@ function resetFileInput() {
|
|
|
410
457
|
}
|
|
411
458
|
.agent-prefix-actions {
|
|
412
459
|
display: flex;
|
|
460
|
+
flex-wrap: wrap;
|
|
413
461
|
gap: 10px;
|
|
414
462
|
align-items: center;
|
|
463
|
+
width: 100%;
|
|
415
464
|
min-height: 36px;
|
|
416
465
|
}
|
|
466
|
+
.agent-model-selector {
|
|
467
|
+
display: flex;
|
|
468
|
+
flex: 0 1 auto;
|
|
469
|
+
width: clamp(180px, 18vw, 200px);
|
|
470
|
+
min-width: 0;
|
|
471
|
+
height: 34px;
|
|
472
|
+
box-sizing: border-box;
|
|
473
|
+
color: var(--admin-page-text-secondary);
|
|
474
|
+
cursor: pointer;
|
|
475
|
+
background: var(--admin-page-card-bg);
|
|
476
|
+
border: 1px solid var(--el-border-color);
|
|
477
|
+
border-radius: var(--admin-page-radius);
|
|
478
|
+
transition:
|
|
479
|
+
color 0.2s ease,
|
|
480
|
+
border-color 0.2s ease,
|
|
481
|
+
background-color 0.2s ease;
|
|
482
|
+
}
|
|
483
|
+
.agent-model-selector:hover {
|
|
484
|
+
color: var(--el-color-primary);
|
|
485
|
+
background: var(--el-color-primary-light-9);
|
|
486
|
+
border-color: var(--el-color-primary-light-5);
|
|
487
|
+
}
|
|
488
|
+
.agent-model-selector:focus-within {
|
|
489
|
+
border-color: var(--el-color-primary-light-5);
|
|
490
|
+
}
|
|
491
|
+
.agent-model-selector.is-disabled {
|
|
492
|
+
color: var(--el-disabled-text-color);
|
|
493
|
+
cursor: not-allowed;
|
|
494
|
+
background: var(--el-disabled-bg-color);
|
|
495
|
+
border-color: var(--el-disabled-border-color);
|
|
496
|
+
}
|
|
497
|
+
.agent-model-selector :deep(.el-cascader) {
|
|
498
|
+
width: 100%;
|
|
499
|
+
height: 100%;
|
|
500
|
+
min-width: 0;
|
|
501
|
+
}
|
|
502
|
+
.agent-model-selector :deep(.el-input) {
|
|
503
|
+
height: 100%;
|
|
504
|
+
}
|
|
505
|
+
.agent-model-selector :deep(.el-input__wrapper) {
|
|
506
|
+
height: 100%;
|
|
507
|
+
padding: 0 9px;
|
|
508
|
+
background: transparent;
|
|
509
|
+
box-shadow: none;
|
|
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;
|
|
524
|
+
}
|
|
417
525
|
.agent-sender-actions {
|
|
418
526
|
display: inline-flex;
|
|
419
527
|
gap: 8px;
|
|
@@ -509,6 +617,9 @@ function resetFileInput() {
|
|
|
509
617
|
.agent-prefix-actions {
|
|
510
618
|
gap: 8px;
|
|
511
619
|
}
|
|
620
|
+
.agent-model-selector {
|
|
621
|
+
width: clamp(128px, calc(100vw - 220px), 180px);
|
|
622
|
+
}
|
|
512
623
|
.agent-action-text {
|
|
513
624
|
display: none;
|
|
514
625
|
}
|
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
:sending="currentSessionSending"
|
|
28
28
|
:shortcuts="starterShortcuts"
|
|
29
29
|
:loading-shortcuts="loadingShortcuts"
|
|
30
|
+
:model-providers="modelProviders"
|
|
31
|
+
:provider-id="selectedProviderId"
|
|
32
|
+
:model-name="selectedModelName"
|
|
30
33
|
@submit="handleSubmit"
|
|
34
|
+
@model-change="handleModelChange"
|
|
31
35
|
@message-action="handleMessageAction"
|
|
32
36
|
@message-edit="handleEditMessage"
|
|
33
37
|
>
|
|
@@ -54,8 +58,10 @@ import { getAdminAiExtension } from "@liujitcn/kratos-admin-system";
|
|
|
54
58
|
import { defAiMessageService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_message";
|
|
55
59
|
import { defAiSessionService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_session";
|
|
56
60
|
import { defAiToolService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_tool";
|
|
61
|
+
import { defAiModelService } from "@liujitcn/kratos-admin-system/api/base/v1/ai_provider";
|
|
57
62
|
import type { AiAction } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_message";
|
|
58
63
|
import type { AiSession } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
64
|
+
import type { AiProviderModelOption } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_provider";
|
|
59
65
|
import type { AiShortcut } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_tool";
|
|
60
66
|
import { AiMessageStatus } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
|
|
61
67
|
import { Terminal } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_tool";
|
|
@@ -102,6 +108,9 @@ const loadingSessionID = ref("");
|
|
|
102
108
|
const loadingShortcuts = ref(false);
|
|
103
109
|
const sessions = ref<AiSession[]>([]);
|
|
104
110
|
const starterShortcuts = ref<AiShortcut[]>([]);
|
|
111
|
+
const modelProviders = ref<AiProviderModelOption[]>([]);
|
|
112
|
+
const selectedProviderId = ref(0);
|
|
113
|
+
const selectedModelName = ref("");
|
|
105
114
|
const messages = ref<Record<string, ChatMessageItem[]>>({});
|
|
106
115
|
const pendingDeltaMap = new Map<string, AiStreamPayload>();
|
|
107
116
|
const runningStreamTaskMap = new Map<string, AiStreamTask>();
|
|
@@ -217,6 +226,8 @@ async function runAiStreamTask(sessionID: string, payload: SubmitPayload) {
|
|
|
217
226
|
{
|
|
218
227
|
session_id: sessionID,
|
|
219
228
|
content: payload.text,
|
|
229
|
+
provider_id: selectedProviderId.value,
|
|
230
|
+
model_name: selectedModelName.value,
|
|
220
231
|
attachments: payload.attachments.map(item => ({
|
|
221
232
|
id: item.id,
|
|
222
233
|
name: item.name,
|
|
@@ -645,15 +656,33 @@ async function loadAiShortcuts() {
|
|
|
645
656
|
if (loadingShortcuts.value) return;
|
|
646
657
|
loadingShortcuts.value = true;
|
|
647
658
|
try {
|
|
648
|
-
const response = await
|
|
659
|
+
const [response, providerResponse] = await Promise.all([
|
|
660
|
+
defAiToolService.ListAiShortcut({ terminal: Terminal.TERMINAL_ADMIN }),
|
|
661
|
+
defAiModelService.ListAiProviderModelOptions({})
|
|
662
|
+
]);
|
|
649
663
|
starterShortcuts.value = normalizeStarterShortcuts(response.shortcuts);
|
|
664
|
+
modelProviders.value = providerResponse.providers ?? [];
|
|
665
|
+
if (!modelProviders.value.some(item => item.provider_id === selectedProviderId.value && item.models.includes(selectedModelName.value))) {
|
|
666
|
+
const firstProvider = modelProviders.value[0];
|
|
667
|
+
selectedProviderId.value = firstProvider?.provider_id ?? 0;
|
|
668
|
+
selectedModelName.value = firstProvider?.models[0] ?? "";
|
|
669
|
+
}
|
|
650
670
|
} catch {
|
|
651
671
|
starterShortcuts.value = [];
|
|
672
|
+
modelProviders.value = [];
|
|
673
|
+
selectedProviderId.value = 0;
|
|
674
|
+
selectedModelName.value = "";
|
|
652
675
|
} finally {
|
|
653
676
|
loadingShortcuts.value = false;
|
|
654
677
|
}
|
|
655
678
|
}
|
|
656
679
|
|
|
680
|
+
/** 更新当前聊天使用的供应商与模型。 */
|
|
681
|
+
function handleModelChange(providerId: number, modelName: string) {
|
|
682
|
+
selectedProviderId.value = providerId;
|
|
683
|
+
selectedModelName.value = modelName;
|
|
684
|
+
}
|
|
685
|
+
|
|
657
686
|
/** 使指定会话仍在途的消息列表请求失效。 */
|
|
658
687
|
function invalidateLoadMessages(sessionID: string) {
|
|
659
688
|
loadMessagesSeq.set(sessionID, (loadMessagesSeq.get(sessionID) ?? 0) + 1);
|