@liujitcn/kratos-admin-system 0.0.64 → 0.0.65
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 +2 -2
- package/dist/package/declarations/src/api/system/admin/v1/ai_provider.d.ts +3 -1
- package/dist/package/declarations/src/locales/generated.d.ts +68 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_provider.d.ts +13 -6
- package/dist/package/declarations/src/rpc/system/admin/v1/ai_provider.d.ts +23 -0
- package/dist/package/declarations/src/views/ai/chat/modelDisplay.d.ts +5 -0
- package/dist/package/declarations/src/views/ai/chat/modelSelection.d.ts +9 -0
- package/dist/package/src/api/system/admin/v1/ai_provider.ts +7 -0
- package/dist/package/src/locales/en-US.json +18 -1
- package/dist/package/src/locales/ja-JP.json +18 -1
- package/dist/package/src/locales/zh-CN.json +18 -1
- package/dist/package/src/locales/zh-TW.json +18 -1
- package/dist/package/src/rpc/base/v1/ai_provider.ts +14 -6
- package/dist/package/src/rpc/system/admin/v1/ai_provider.ts +26 -0
- package/dist/package/src/views/ai/chat/components/ChatPanel.vue +4 -1
- package/dist/package/src/views/ai/chat/components/XSender.vue +266 -71
- package/dist/package/src/views/ai/chat/index.vue +24 -6
- package/dist/package/src/views/ai/chat/modelDisplay.ts +67 -0
- package/dist/package/src/views/ai/chat/modelSelection.ts +31 -0
- package/dist/package/src/views/base/ai-provider/index.vue +123 -22
- package/dist/package/src/views/base/api-rate-limit-policy/index.vue +4 -3
- package/dist/package/src/views/base/message-provider/index.vue +7 -3
- package/dist/package/src/views/base/message-template/index.vue +2 -2
- package/dist/package/src/views/base/rate-limit-rule/index.vue +8 -3
- package/package.json +2 -2
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
:model="form"
|
|
11
11
|
:fields="fields"
|
|
12
12
|
:rules="rules"
|
|
13
|
+
:form-props="{ disabled: testingModels }"
|
|
14
|
+
:close-on-click-modal="!testingModels"
|
|
15
|
+
:close-on-press-escape="!testingModels"
|
|
13
16
|
@confirm="submit"
|
|
14
17
|
@close="resetForm"
|
|
15
18
|
>
|
|
@@ -23,7 +26,24 @@
|
|
|
23
26
|
<div class="provider-models__content">
|
|
24
27
|
<div class="provider-models__heading">
|
|
25
28
|
<strong>{{ model.model_name }}</strong>
|
|
29
|
+
<span v-if="model.display_name" class="provider-models__alias">✦ {{ model.display_name }}</span>
|
|
26
30
|
<el-tag size="small" effect="plain">{{ apiTypeLabel(model.api_type) }}</el-tag>
|
|
31
|
+
<el-tooltip
|
|
32
|
+
v-if="modelTestResults[model.model_name]"
|
|
33
|
+
:content="modelTestResults[model.model_name]?.message"
|
|
34
|
+
:disabled="!modelTestResults[model.model_name]?.message"
|
|
35
|
+
placement="top"
|
|
36
|
+
>
|
|
37
|
+
<el-tag
|
|
38
|
+
size="small"
|
|
39
|
+
effect="plain"
|
|
40
|
+
:type="modelTestResults[model.model_name]?.success ? 'success' : 'danger'"
|
|
41
|
+
>
|
|
42
|
+
{{ t(modelTestResults[model.model_name]?.success
|
|
43
|
+
? "system.base.ai_provider.model.test_success"
|
|
44
|
+
: "system.base.ai_provider.model.test_failed") }} · {{ modelTestResults[model.model_name]?.duration_ms }} ms
|
|
45
|
+
</el-tag>
|
|
46
|
+
</el-tooltip>
|
|
27
47
|
</div>
|
|
28
48
|
<div class="provider-models__details">
|
|
29
49
|
<span>{{ t("system.base.ai_provider.field.temperature") }}: {{ model.temperature }}</span>
|
|
@@ -34,19 +54,32 @@
|
|
|
34
54
|
</div>
|
|
35
55
|
<div class="provider-models__actions">
|
|
36
56
|
<el-tooltip :content="t('common.action.edit')" placement="top">
|
|
37
|
-
<el-button text circle :icon="EditPen" :aria-label="t('common.action.edit')" @click="openModelDialog(index)" />
|
|
57
|
+
<el-button text circle :disabled="testingModels" :icon="EditPen" :aria-label="t('common.action.edit')" @click="openModelDialog(index)" />
|
|
38
58
|
</el-tooltip>
|
|
39
59
|
<el-tooltip :content="t('common.action.delete')" placement="top">
|
|
40
|
-
<el-button text circle :icon="Delete" :aria-label="t('common.action.delete')" @click="removeModel(index)" />
|
|
60
|
+
<el-button text circle :disabled="testingModels" :icon="Delete" :aria-label="t('common.action.delete')" @click="removeModel(index)" />
|
|
41
61
|
</el-tooltip>
|
|
42
62
|
</div>
|
|
43
63
|
</div>
|
|
44
64
|
</div>
|
|
45
|
-
<el-button type="primary" plain :icon="CirclePlus" @click="openModelDialog()">
|
|
65
|
+
<el-button type="primary" plain :icon="CirclePlus" :disabled="testingModels" @click="openModelDialog()">
|
|
46
66
|
{{ t("system.base.ai_provider.model.add") }}
|
|
47
67
|
</el-button>
|
|
48
68
|
</div>
|
|
49
69
|
</template>
|
|
70
|
+
<template #footer>
|
|
71
|
+
<div class="dialog-footer">
|
|
72
|
+
<el-button :disabled="testingModels" @click="dialog.visible = false">
|
|
73
|
+
{{ t("common.action.cancel") }}
|
|
74
|
+
</el-button>
|
|
75
|
+
<el-button type="primary" plain :icon="Connection" :loading="testingModels" :disabled="!form.models.length" @click="testProviderModels">
|
|
76
|
+
{{ t("system.base.ai_provider.model.test") }}
|
|
77
|
+
</el-button>
|
|
78
|
+
<el-button type="primary" :disabled="testingModels" @click="submit">
|
|
79
|
+
{{ t("common.action.confirm") }}
|
|
80
|
+
</el-button>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
50
83
|
</FormDialog>
|
|
51
84
|
|
|
52
85
|
<FormDialog
|
|
@@ -66,10 +99,10 @@
|
|
|
66
99
|
</template>
|
|
67
100
|
|
|
68
101
|
<script setup lang="ts">
|
|
69
|
-
import { computed, reactive, ref } from "vue";
|
|
102
|
+
import { computed, reactive, ref, watch } from "vue";
|
|
70
103
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
71
104
|
import type { FormRules } from "element-plus";
|
|
72
|
-
import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
|
|
105
|
+
import { CirclePlus, Connection, Delete, EditPen } from "@element-plus/icons-vue";
|
|
73
106
|
import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
|
|
74
107
|
import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
|
|
75
108
|
import type { ColumnProps, HeaderActionProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
@@ -82,6 +115,7 @@ import { defAiProviderService } from "@liujitcn/kratos-admin-system/api/system/a
|
|
|
82
115
|
import type {
|
|
83
116
|
AiProvider,
|
|
84
117
|
AiProviderForm,
|
|
118
|
+
AiProviderModelTestResult,
|
|
85
119
|
PageAiProviderRequest
|
|
86
120
|
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/ai_provider";
|
|
87
121
|
import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
|
|
@@ -101,6 +135,8 @@ type ModelApiType = "CHAT_COMPLETIONS" | "RESPONSES";
|
|
|
101
135
|
interface AiModelConfig {
|
|
102
136
|
/** 服务商侧的模型名称。 */
|
|
103
137
|
model_name: string;
|
|
138
|
+
/** 模型在聊天前端的展示名称,留空时由前端按模型名自动映射。 */
|
|
139
|
+
display_name: string;
|
|
104
140
|
/** 模型请求接口类型。 */
|
|
105
141
|
api_type: ModelApiType;
|
|
106
142
|
/** 文本生成温度。 */
|
|
@@ -128,6 +164,15 @@ const dialog = reactive({ visible: false, editing: false });
|
|
|
128
164
|
const modelDialog = reactive({ visible: false, editing: false, index: -1 });
|
|
129
165
|
const form = reactive<FormState>(defaultForm());
|
|
130
166
|
const modelForm = reactive<AiModelConfig>(defaultModelConfig());
|
|
167
|
+
const testingModels = ref(false);
|
|
168
|
+
const modelTestResults = ref<Record<string, AiProviderModelTestResult>>({});
|
|
169
|
+
|
|
170
|
+
watch(
|
|
171
|
+
() => [form.provider, form.base_url, form.api_key, JSON.stringify(form.config_items), JSON.stringify(form.models)],
|
|
172
|
+
() => {
|
|
173
|
+
modelTestResults.value = {};
|
|
174
|
+
}
|
|
175
|
+
);
|
|
131
176
|
|
|
132
177
|
const statusOptions = computed<ProFormOption[]>(() => [
|
|
133
178
|
{ label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
|
|
@@ -157,6 +202,7 @@ const fields = computed<ProFormField[]>(() => [
|
|
|
157
202
|
|
|
158
203
|
const modelFields = computed<ProFormField[]>(() => [
|
|
159
204
|
{ prop: "model_name", label: t("system.base.ai_provider.field.model_name"), component: "input", colSpan: 12, props: { maxlength: 100 } },
|
|
205
|
+
{ prop: "display_name", label: t("system.base.ai_provider.field.display_name"), component: "input", colSpan: 12, props: { maxlength: 100, placeholder: t("system.base.ai_provider.model.placeholder.display_name") } },
|
|
160
206
|
{ prop: "api_type", label: t("system.base.ai_provider.field.api_type"), component: "select", colSpan: 12, options: modelApiTypeOptions.value },
|
|
161
207
|
{ prop: "temperature", label: t("system.base.ai_provider.field.temperature"), component: "input-number", colSpan: 12, props: { min: 0, max: 2, precision: 2, step: 0.1, controlsPosition: "right" } },
|
|
162
208
|
{ prop: "max_tokens", label: t("system.base.ai_provider.field.max_tokens"), component: "input-number", colSpan: 12, props: { min: 0, max: 200000, precision: 0, controlsPosition: "right" } },
|
|
@@ -189,6 +235,7 @@ const modelRules = computed<FormRules>(() => ({
|
|
|
189
235
|
{ required: true, message: t("system.base.ai_provider.validation.model_name"), trigger: "blur" },
|
|
190
236
|
{ max: 100, message: t("system.base.ai_provider.validation.model_name_length"), trigger: "blur" }
|
|
191
237
|
],
|
|
238
|
+
display_name: [{ max: 100, message: t("system.base.ai_provider.model.validation.display_name"), trigger: "blur" }],
|
|
192
239
|
api_type: [{ required: true, message: t("system.base.ai_provider.validation.api_type"), trigger: "change" }],
|
|
193
240
|
temperature: [{ type: "number", required: true, min: 0, max: 2, message: t("system.base.ai_provider.model.validation.range"), trigger: "change" }],
|
|
194
241
|
max_tokens: [{ type: "number", required: true, min: 0, max: 200000, message: t("system.base.ai_provider.model.validation.range"), trigger: "change" }],
|
|
@@ -281,9 +328,39 @@ async function openDialog(id?: number) {
|
|
|
281
328
|
async function submit() {
|
|
282
329
|
const valid = await dialogRef.value?.validate();
|
|
283
330
|
if (!valid) return;
|
|
331
|
+
const payload = buildAiProviderPayload();
|
|
332
|
+
if (!payload) return;
|
|
333
|
+
if (dialog.editing) await defAiProviderService.UpdateAiProvider({ ai_provider: payload });
|
|
334
|
+
else await defAiProviderService.CreateAiProvider({ ai_provider: payload });
|
|
335
|
+
ElMessage.success(t(dialog.editing ? "system.base.ai_provider.message.update_success" : "system.base.ai_provider.message.create_success"));
|
|
336
|
+
dialog.visible = false;
|
|
337
|
+
resetForm();
|
|
338
|
+
table.value?.getTableList();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** 测试当前草稿中的全部模型并展示逐项结果。 */
|
|
342
|
+
async function testProviderModels() {
|
|
343
|
+
const valid = await dialogRef.value?.validate();
|
|
344
|
+
if (!valid) return;
|
|
345
|
+
const payload = buildAiProviderPayload();
|
|
346
|
+
if (!payload) return;
|
|
347
|
+
|
|
348
|
+
testingModels.value = true;
|
|
349
|
+
modelTestResults.value = {};
|
|
350
|
+
let response: Awaited<ReturnType<typeof defAiProviderService.TestAiProviderModels>>;
|
|
351
|
+
try {
|
|
352
|
+
response = await defAiProviderService.TestAiProviderModels({ ai_provider: payload });
|
|
353
|
+
} finally {
|
|
354
|
+
testingModels.value = false;
|
|
355
|
+
}
|
|
356
|
+
modelTestResults.value = Object.fromEntries(response.results.map(result => [result.model_name, result]));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** 构造AI Provider保存或测试请求参数。 */
|
|
360
|
+
function buildAiProviderPayload(): AiProviderForm | undefined {
|
|
284
361
|
const config = configItemsToMap(form.config_items);
|
|
285
|
-
if (!config) return;
|
|
286
|
-
|
|
362
|
+
if (!config) return undefined;
|
|
363
|
+
return {
|
|
287
364
|
id: form.id,
|
|
288
365
|
provider: form.provider,
|
|
289
366
|
name: form.name,
|
|
@@ -295,12 +372,6 @@ async function submit() {
|
|
|
295
372
|
sort: form.sort,
|
|
296
373
|
status: form.status
|
|
297
374
|
};
|
|
298
|
-
if (dialog.editing) await defAiProviderService.UpdateAiProvider({ ai_provider: payload });
|
|
299
|
-
else await defAiProviderService.CreateAiProvider({ ai_provider: payload });
|
|
300
|
-
ElMessage.success(t(dialog.editing ? "system.base.ai_provider.message.update_success" : "system.base.ai_provider.message.create_success"));
|
|
301
|
-
dialog.visible = false;
|
|
302
|
-
resetForm();
|
|
303
|
-
table.value?.getTableList();
|
|
304
375
|
}
|
|
305
376
|
|
|
306
377
|
/** 重置供应商表单。 */
|
|
@@ -365,6 +436,7 @@ function parseModelConfigs(raw: string): AiModelConfig[] {
|
|
|
365
436
|
const values = JSON.parse(raw) as Partial<AiModelConfig>[];
|
|
366
437
|
return values.map(value => ({
|
|
367
438
|
model_name: value.model_name ?? "",
|
|
439
|
+
display_name: value.display_name ?? "",
|
|
368
440
|
api_type: value.api_type === "RESPONSES" ? "RESPONSES" : "CHAT_COMPLETIONS",
|
|
369
441
|
temperature: value.temperature ?? 0,
|
|
370
442
|
max_tokens: value.max_tokens ?? 0,
|
|
@@ -375,7 +447,7 @@ function parseModelConfigs(raw: string): AiModelConfig[] {
|
|
|
375
447
|
|
|
376
448
|
/** 创建单个模型的默认配置。 */
|
|
377
449
|
function defaultModelConfig(): AiModelConfig {
|
|
378
|
-
return { model_name: "", api_type: "CHAT_COMPLETIONS", temperature: 0.2, max_tokens: 1024, timeout_seconds: 60, max_retries: 2 };
|
|
450
|
+
return { model_name: "", display_name: "", api_type: "CHAT_COMPLETIONS", temperature: 0.2, max_tokens: 1024, timeout_seconds: 60, max_retries: 2 };
|
|
379
451
|
}
|
|
380
452
|
|
|
381
453
|
/** 将Provider配置对象转换为键值表单项。 */
|
|
@@ -411,7 +483,11 @@ async function handleDelete(target: AiProvider | number | Array<number>) {
|
|
|
411
483
|
const row = typeof target === "object" && !Array.isArray(target) ? target : undefined;
|
|
412
484
|
const ids = (row ? [row.id] : normalizeSelectedIds(target as number | number[])).map(Number);
|
|
413
485
|
if (!ids.length) return;
|
|
414
|
-
|
|
486
|
+
try {
|
|
487
|
+
await ElMessageBox.confirm(t("system.base.ai_provider.message.confirm_delete", { name: row?.name ?? ids.join(", ") }), t("common.action.delete"), { type: "warning" });
|
|
488
|
+
} catch {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
415
491
|
for (const id of ids) await defAiProviderService.DeleteAiProvider({ id });
|
|
416
492
|
ElMessage.success(t("system.base.ai_provider.message.delete_success"));
|
|
417
493
|
table.value?.getTableList();
|
|
@@ -419,13 +495,25 @@ async function handleDelete(target: AiProvider | number | Array<number>) {
|
|
|
419
495
|
|
|
420
496
|
/** 更新AI供应商状态。 */
|
|
421
497
|
async function handleSetStatus(row: AiProvider) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
498
|
+
const status = row.status === Status.STATUS_ENABLE ? Status.STATUS_DISABLE : Status.STATUS_ENABLE;
|
|
499
|
+
try {
|
|
500
|
+
await ElMessageBox.confirm(
|
|
501
|
+
t("common.dialog.status_change", {
|
|
502
|
+
action: t(status === Status.STATUS_ENABLE ? "common.status.enabled" : "common.status.disabled"),
|
|
503
|
+
resource: t("system.base.ai_provider.resource"),
|
|
504
|
+
field: t("system.base.ai_provider.field.name"),
|
|
505
|
+
value: row.name
|
|
506
|
+
}),
|
|
507
|
+
t("common.title.notice"),
|
|
508
|
+
{ type: "warning" }
|
|
509
|
+
);
|
|
510
|
+
await defAiProviderService.SetAiProviderStatus({ id: row.id, status });
|
|
511
|
+
ElMessage.success(t("system.base.ai_provider.message.status_success"));
|
|
512
|
+
table.value?.getTableList();
|
|
513
|
+
return true;
|
|
514
|
+
} catch {
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
429
517
|
}
|
|
430
518
|
</script>
|
|
431
519
|
|
|
@@ -491,6 +579,19 @@ async function handleSetStatus(row: AiProvider) {
|
|
|
491
579
|
overflow-wrap: anywhere;
|
|
492
580
|
}
|
|
493
581
|
|
|
582
|
+
.provider-models__alias {
|
|
583
|
+
display: inline-flex;
|
|
584
|
+
align-items: center;
|
|
585
|
+
gap: 4px;
|
|
586
|
+
padding: 1px 8px;
|
|
587
|
+
font-size: 12px;
|
|
588
|
+
font-weight: 600;
|
|
589
|
+
color: var(--el-color-primary);
|
|
590
|
+
background: var(--el-color-primary-light-9);
|
|
591
|
+
border-radius: 999px;
|
|
592
|
+
white-space: nowrap;
|
|
593
|
+
}
|
|
594
|
+
|
|
494
595
|
.provider-models__details {
|
|
495
596
|
flex-wrap: wrap;
|
|
496
597
|
gap: 4px 14px;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<el-icon class="parameter-help" tabindex="0"><QuestionFilled /></el-icon>
|
|
31
31
|
</el-tooltip>
|
|
32
32
|
</span>
|
|
33
|
-
<el-input-number v-model="form.params.tokens_per_second" :min="0" :max="1000000" :precision="3" :step="1" controls-position="right" />
|
|
33
|
+
<el-input-number v-model="form.params.tokens_per_second" :min="0.001" :max="1000000" :precision="3" :step="1" controls-position="right" />
|
|
34
34
|
</div>
|
|
35
35
|
<div class="parameter-item">
|
|
36
36
|
<span class="parameter-label">
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
<el-icon class="parameter-help" tabindex="0"><QuestionFilled /></el-icon>
|
|
40
40
|
</el-tooltip>
|
|
41
41
|
</span>
|
|
42
|
-
<el-input-number v-model="form.params.burst" :min="
|
|
42
|
+
<el-input-number v-model="form.params.burst" :min="1" :max="1000000" :precision="0" controls-position="right" />
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
45
45
|
<div v-else-if="windowedAlgorithms.includes(form.rule_type)" class="parameter-grid">
|
|
@@ -262,7 +262,8 @@ function resetForm() {
|
|
|
262
262
|
/** 根据所选模板加载默认参数。 */
|
|
263
263
|
function handleRuleChange(ruleId?: number) {
|
|
264
264
|
const selected = ruleCatalog.value.find(rule => rule.id === Number(ruleId));
|
|
265
|
-
|
|
265
|
+
// 模板缺失时保留当前算法类型,避免编辑态参数区空白导致无法保存。
|
|
266
|
+
if (selected) form.rule_type = selected.rule_type;
|
|
266
267
|
form.params = selected ? parseRateLimitParams(form.rule_type, selected.default_params) : defaultRateLimitParams(form.rule_type);
|
|
267
268
|
}
|
|
268
269
|
|
|
@@ -59,8 +59,8 @@ const statusOptions = computed<ProFormOption[]>(() => [
|
|
|
59
59
|
]);
|
|
60
60
|
|
|
61
61
|
const rules = computed(() => ({
|
|
62
|
-
provider: [{ required: true, message: t("system.base.message_provider.
|
|
63
|
-
name: [{ required: true, message: t("system.base.message_provider.
|
|
62
|
+
provider: [{ required: true, message: t("system.base.message_provider.validation.provider"), trigger: "blur" }],
|
|
63
|
+
name: [{ required: true, message: t("system.base.message_provider.validation.name"), trigger: "blur" }]
|
|
64
64
|
}));
|
|
65
65
|
|
|
66
66
|
const formFields = computed<ProFormField[]>(() => [
|
|
@@ -131,7 +131,11 @@ async function handleSubmit() {
|
|
|
131
131
|
async function handleDelete(value: BaseMessageProvider | number | number[]) {
|
|
132
132
|
const ids = (Array.isArray(value) ? value : typeof value === "object" ? [value.id] : normalizeSelectedIds(value)).join(",");
|
|
133
133
|
if (!ids) return;
|
|
134
|
-
|
|
134
|
+
try {
|
|
135
|
+
await ElMessageBox.confirm(t("common.confirm.delete"), t("common.title.warning"), { type: "warning" });
|
|
136
|
+
} catch {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
135
139
|
await defBaseMessageProviderService.DeleteBaseMessageProvider({ id: ids });
|
|
136
140
|
await proTable.value?.getTableList();
|
|
137
141
|
ElMessage.success(t("common.message.operation_success"));
|
|
@@ -60,7 +60,7 @@ const contentFormatOptions = computed<ProFormOption[]>(() => [
|
|
|
60
60
|
]);
|
|
61
61
|
const categoryOptions = computed<ProFormOption[]>(() => categories.value.map(item => ({ label: item.name, value: item.id })));
|
|
62
62
|
const providerOptions = computed<ProFormOption[]>(() => providers.value.map(item => ({ label: item.name, value: item.id, disabled: item.status !== 1 })));
|
|
63
|
-
const rules = computed(() => ({ category_id: [{ required: true, message: t("system.base.message_template.
|
|
63
|
+
const rules = computed(() => ({ category_id: [{ required: true, message: t("system.base.message_template.validation.category"), trigger: "change" }], provider_id: [{ required: true, message: t("system.base.message_template.validation.provider"), trigger: "change" }], title: [{ required: true, message: t("system.base.message_template.validation.title"), trigger: "blur" }] }));
|
|
64
64
|
const formFields = computed<ProFormField[]>(() => [
|
|
65
65
|
{ prop: "category_id", label: t("system.base.message_template.field.category"), component: "select", options: categoryOptions.value, props: { disabled: dialog.editing, filterable: true } },
|
|
66
66
|
{ prop: "provider_id", label: t("system.base.message_template.field.provider"), component: "select", options: providerOptions.value, props: { disabled: dialog.editing, filterable: true } },
|
|
@@ -97,7 +97,7 @@ async function loadOptions() {
|
|
|
97
97
|
async function requestTable(params: Record<string, unknown>) { const data = await defBaseMessageTemplateService.PageBaseMessageTemplate(buildPageRequest<PageBaseMessageTemplateRequest>(params as unknown as PageBaseMessageTemplateRequest)); return { data: { list: data.base_message_templates ?? [], total: data.total } }; }
|
|
98
98
|
async function openDialog(id?: number) { await loadOptions(); await formDialogRef.value?.open({ load: () => (id ? defBaseMessageTemplateService.GetBaseMessageTemplate({ id }) : undefined), commit: data => { Object.assign(formData, defaultForm(), data ?? {}); dialog.editing = Boolean(id); formData.config_items = configToItems(formData.config); } }); }
|
|
99
99
|
async function handleSubmit() { if (!(await formDialogRef.value?.validate())) return; const config = configItemsToMap(formData.config_items); if (!config) return; const payload = JSON.parse(JSON.stringify(formData)) as BaseMessageTemplateForm & { config_items?: ConfigItem[] }; delete payload.config_items; payload.config = config; if (payload.id) await defBaseMessageTemplateService.UpdateBaseMessageTemplate({ base_message_template: payload }); else await defBaseMessageTemplateService.CreateBaseMessageTemplate({ base_message_template: payload }); ElMessage.success(t("common.message.operation_success")); dialog.visible = false; await proTable.value?.getTableList(); }
|
|
100
|
-
async function handleDelete(value: BaseMessageTemplate | number | number[]) { const ids = (Array.isArray(value) ? value : typeof value === "object" ? [value.id] : normalizeSelectedIds(value)).join(","); if (!ids) return; await ElMessageBox.confirm(t("common.confirm.delete"), t("common.title.warning"), { type: "warning" }); await defBaseMessageTemplateService.DeleteBaseMessageTemplate({ id: ids }); await proTable.value?.getTableList(); ElMessage.success(t("common.message.operation_success")); }
|
|
100
|
+
async function handleDelete(value: BaseMessageTemplate | number | number[]) { const ids = (Array.isArray(value) ? value : typeof value === "object" ? [value.id] : normalizeSelectedIds(value)).join(","); if (!ids) return; try { await ElMessageBox.confirm(t("common.confirm.delete"), t("common.title.warning"), { type: "warning" }); } catch { return; } await defBaseMessageTemplateService.DeleteBaseMessageTemplate({ id: ids }); await proTable.value?.getTableList(); ElMessage.success(t("common.message.operation_success")); }
|
|
101
101
|
function handleClose() { Object.assign(formData, defaultForm()); formDialogRef.value?.resetFields(); }
|
|
102
102
|
function contentFormatLabel(value: MessageContentFormat) { return contentFormatOptions.value.find(item => item.value === value)?.label ?? ""; }
|
|
103
103
|
function configToItems(config: Record<string, any> | undefined): ConfigItem[] { return Object.entries(config ?? {}).map(([key, value]) => ({ key, value: typeof value === "string" ? value : JSON.stringify(value) })); }
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<el-icon class="parameter-help" tabindex="0"><QuestionFilled /></el-icon>
|
|
22
22
|
</el-tooltip>
|
|
23
23
|
</span>
|
|
24
|
-
<el-input-number v-model="form.params.tokens_per_second" :min="0" :max="1000000" :precision="3" :step="1" controls-position="right" />
|
|
24
|
+
<el-input-number v-model="form.params.tokens_per_second" :min="0.001" :max="1000000" :precision="3" :step="1" controls-position="right" />
|
|
25
25
|
</div>
|
|
26
26
|
<div class="parameter-item">
|
|
27
27
|
<span class="parameter-label">
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<el-icon class="parameter-help" tabindex="0"><QuestionFilled /></el-icon>
|
|
31
31
|
</el-tooltip>
|
|
32
32
|
</span>
|
|
33
|
-
<el-input-number v-model="form.params.burst" :min="
|
|
33
|
+
<el-input-number v-model="form.params.burst" :min="1" :max="1000000" :precision="0" controls-position="right" />
|
|
34
34
|
</div>
|
|
35
35
|
</div>
|
|
36
36
|
<div v-else-if="windowedAlgorithms.includes(form.rule_type)" class="parameter-grid">
|
|
@@ -125,10 +125,15 @@ const statusOptions = computed<ProFormOption[]>(() => [
|
|
|
125
125
|
{ label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
|
|
126
126
|
{ label: t("common.status.disabled"), value: Status.STATUS_DISABLE }
|
|
127
127
|
]);
|
|
128
|
+
/** 当前算法说明提示,未选择算法时为空避免渲染空 tooltip。 */
|
|
129
|
+
const ruleTypeTooltip = computed(() => {
|
|
130
|
+
const key = rateLimitAlgorithmTooltipKey(form.rule_type);
|
|
131
|
+
return key ? t(key) : undefined;
|
|
132
|
+
});
|
|
128
133
|
const fields = computed<ProFormField[]>(() => [
|
|
129
134
|
{ prop: "code", label: t("system.base.rate_limit_rule.field.code"), component: "input", props: { disabled: Boolean(form.id) } },
|
|
130
135
|
{ prop: "name", label: t("system.base.rate_limit_rule.field.name"), component: "input" },
|
|
131
|
-
{ prop: "rule_type", label: t("system.base.rate_limit_rule.field.rule_type"), labelTooltip:
|
|
136
|
+
{ prop: "rule_type", label: t("system.base.rate_limit_rule.field.rule_type"), labelTooltip: ruleTypeTooltip.value, component: "select", options: algorithmOptions.value, props: { disabled: Boolean(form.id), onChange: handleAlgorithmChange } },
|
|
132
137
|
{ prop: "parameters", label: t("system.base.rate_limit_rule.field.parameters"), labelTooltip: t("system.base.rate_limit_rule.tooltip.parameters"), component: "slot", slotName: "parameters", colSpan: 24 },
|
|
133
138
|
{ prop: "status", label: t("common.field.status"), component: "radio-group", options: statusOptions.value },
|
|
134
139
|
{ prop: "remark", label: t("common.field.remark"), component: "textarea" }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liujitcn/kratos-admin-system",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.65",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "kratos-admin system management module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"sortablejs": "^1.15.2",
|
|
17
17
|
"swagger-ui-dist": "^5.32.15",
|
|
18
18
|
"vue-element-plus-x": "^2.0.3",
|
|
19
|
-
"@liujitcn/kratos-admin-core": "0.0.
|
|
19
|
+
"@liujitcn/kratos-admin-core": "0.0.65"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@element-plus/icons-vue": "^2.3.1",
|