@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.
Files changed (25) hide show
  1. package/README.md +2 -2
  2. package/dist/package/declarations/src/api/system/admin/v1/ai_provider.d.ts +3 -1
  3. package/dist/package/declarations/src/locales/generated.d.ts +68 -0
  4. package/dist/package/declarations/src/rpc/base/v1/ai_provider.d.ts +13 -6
  5. package/dist/package/declarations/src/rpc/system/admin/v1/ai_provider.d.ts +23 -0
  6. package/dist/package/declarations/src/views/ai/chat/modelDisplay.d.ts +5 -0
  7. package/dist/package/declarations/src/views/ai/chat/modelSelection.d.ts +9 -0
  8. package/dist/package/src/api/system/admin/v1/ai_provider.ts +7 -0
  9. package/dist/package/src/locales/en-US.json +18 -1
  10. package/dist/package/src/locales/ja-JP.json +18 -1
  11. package/dist/package/src/locales/zh-CN.json +18 -1
  12. package/dist/package/src/locales/zh-TW.json +18 -1
  13. package/dist/package/src/rpc/base/v1/ai_provider.ts +14 -6
  14. package/dist/package/src/rpc/system/admin/v1/ai_provider.ts +26 -0
  15. package/dist/package/src/views/ai/chat/components/ChatPanel.vue +4 -1
  16. package/dist/package/src/views/ai/chat/components/XSender.vue +266 -71
  17. package/dist/package/src/views/ai/chat/index.vue +24 -6
  18. package/dist/package/src/views/ai/chat/modelDisplay.ts +67 -0
  19. package/dist/package/src/views/ai/chat/modelSelection.ts +31 -0
  20. package/dist/package/src/views/base/ai-provider/index.vue +123 -22
  21. package/dist/package/src/views/base/api-rate-limit-policy/index.vue +4 -3
  22. package/dist/package/src/views/base/message-provider/index.vue +7 -3
  23. package/dist/package/src/views/base/message-template/index.vue +2 -2
  24. package/dist/package/src/views/base/rate-limit-rule/index.vue +8 -3
  25. 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
- const payload: AiProviderForm = {
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
- await ElMessageBox.confirm(t("system.base.ai_provider.message.confirm_delete", { name: row?.name ?? ids.join(", ") }), t("common.action.delete"), { type: "warning" });
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
- await defAiProviderService.SetAiProviderStatus({
423
- id: row.id,
424
- status: row.status === Status.STATUS_ENABLE ? Status.STATUS_DISABLE : Status.STATUS_ENABLE
425
- });
426
- ElMessage.success(t("system.base.ai_provider.message.status_success"));
427
- table.value?.getTableList();
428
- return true;
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="0" :max="1000000" :precision="0" controls-position="right" />
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
- form.rule_type = selected?.rule_type ?? BaseRateLimitAlgorithm.BASE_RATE_LIMIT_ALGORITHM_UNSPECIFIED;
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.placeholder.provider"), trigger: "blur" }],
63
- name: [{ required: true, message: t("system.base.message_provider.placeholder.name"), trigger: "blur" }]
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
- await ElMessageBox.confirm(t("common.confirm.delete"), t("common.title.warning"), { type: "warning" });
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.placeholder.category"), trigger: "change" }], provider_id: [{ required: true, message: t("system.base.message_template.placeholder.provider"), trigger: "change" }], title: [{ required: true, message: t("system.base.message_template.placeholder.title"), trigger: "blur" }] }));
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="0" :max="1000000" :precision="0" controls-position="right" />
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: t(rateLimitAlgorithmTooltipKey(form.rule_type)), component: "select", options: algorithmOptions.value, props: { disabled: Boolean(form.id), onChange: handleAlgorithmChange } },
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.64",
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.64"
19
+ "@liujitcn/kratos-admin-core": "0.0.65"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "@element-plus/icons-vue": "^2.3.1",