dsh-model-health 0.2.1 → 0.2.2

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 (2) hide show
  1. package/dist/client.js +76 -45
  2. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -17,7 +17,7 @@ window.__ModuleLoader__.load({
17
17
  var exports = module.exports;
18
18
 
19
19
  const React = require("react");
20
- const { useState, useEffect, useCallback } = React;
20
+ const { useState, useEffect, useCallback, useRef } = React;
21
21
 
22
22
  // ── 多语言词典(locale 服务,语言跟随 harness)─────────────────────────
23
23
  const NS = "dsh-model-health";
@@ -48,6 +48,8 @@ window.__ModuleLoader__.load({
48
48
  statusOk: "可用",
49
49
  statusFail: "不可用",
50
50
  statusSkip: "跳过",
51
+ testOne: "测试",
52
+ testOneTip: "点击测试此模型",
51
53
  };
52
54
  const en = {
53
55
  tab: "Model Health",
@@ -76,6 +78,8 @@ window.__ModuleLoader__.load({
76
78
  statusOk: "Available",
77
79
  statusFail: "Unavailable",
78
80
  statusSkip: "Skipped",
81
+ testOne: "Test",
82
+ testOneTip: "Click to test this model",
79
83
  };
80
84
 
81
85
  // 内联样式(适配 DSH 明暗主题的 CSS 变量)
@@ -139,6 +143,20 @@ window.__ModuleLoader__.load({
139
143
  opacity: 0.5,
140
144
  cursor: "not-allowed",
141
145
  },
146
+ // 状态列里的"测试"小按钮(未测试的模型显示它代替"未测试"文字)
147
+ btnTestSmall: {
148
+ boxSizing: "border-box",
149
+ height: 22,
150
+ padding: "0 12px",
151
+ fontSize: 12,
152
+ lineHeight: "16px",
153
+ cursor: "pointer",
154
+ border: "1px solid var(--dsw-alias-button-primary-fill, #4f46e5)",
155
+ borderRadius: 11,
156
+ background: "transparent",
157
+ color: "var(--dsw-alias-button-primary-fill, #4f46e5)",
158
+ whiteSpace: "nowrap",
159
+ },
142
160
  progress: {
143
161
  fontSize: 12,
144
162
  color: "var(--dsw-alias-label-tertiary, #888)",
@@ -355,6 +373,9 @@ window.__ModuleLoader__.load({
355
373
  // 初始值从 localStorage 读取,实现跨刷新持久化
356
374
  const [testResults, setTestResults] = useState(loadStoredResults);
357
375
  const [testing, setTesting] = useState(false);
376
+ // 请求序号:同一模型重复触发测试(单测 + 测试全部并发)时,
377
+ // 只有最新一次请求的结果允许写入,避免旧响应覆盖新状态
378
+ const seqRef = useRef({});
358
379
  const [progress, setProgress] = useState({ done: 0, total: 0 });
359
380
  // 可选列显示状态:{ [colKey]: boolean },默认 false
360
381
  const [optionalCols, setOptionalCols] = useState({});
@@ -386,6 +407,42 @@ window.__ModuleLoader__.load({
386
407
  loadData();
387
408
  }, [loadData]);
388
409
 
410
+ // 测试单个模型:该行置为 testing → POST /api/model-health/test → 写回结果
411
+ // ("测试全部"与状态列的"测试"按钮共用此逻辑)
412
+ const testModel = useCallback(async (key) => {
413
+ const seq = (seqRef.current[key] || 0) + 1;
414
+ seqRef.current[key] = seq;
415
+ setTestResults((s) => ({ ...s, [key]: { status: "testing" } }));
416
+
417
+ let result;
418
+ try {
419
+ const resp = await fetch("/api/model-health/test", {
420
+ method: "POST",
421
+ headers: { "Content-Type": "application/json" },
422
+ body: JSON.stringify({ key }),
423
+ cache: "no-store",
424
+ });
425
+ const json = await resp.json();
426
+ result = !json.ok
427
+ ? { status: "fail", error: json.error }
428
+ : {
429
+ status: json.status || "fail",
430
+ latency: json.latency,
431
+ error: json.error,
432
+ };
433
+ } catch (e) {
434
+ result = { status: "fail", error: e.message };
435
+ }
436
+
437
+ // 期间若同一模型又发起了更新的测试,丢弃这份过期结果
438
+ if (seqRef.current[key] !== seq) return;
439
+ setTestResults((s) => {
440
+ const next = { ...s, [key]: result };
441
+ saveStoredResults(next);
442
+ return next;
443
+ });
444
+ }, []);
445
+
389
446
  // 测试全部:并发发起所有测试请求,每个完成即更新对应行状态
390
447
  const testAll = useCallback(async () => {
391
448
  const data = state.data;
@@ -393,59 +450,20 @@ window.__ModuleLoader__.load({
393
450
  const total = data.models.length;
394
451
  setTesting(true);
395
452
  setProgress({ done: 0, total });
396
- // 初始化所有为 testing(按 provider/modelId key)
397
- const init = {};
398
- for (const m of data.models) init[m.key] = { status: "testing" };
399
- setTestResults(init);
400
- saveStoredResults(init);
401
453
 
402
454
  let done = 0;
403
455
  // 并发测试,但限制并发数避免压垮 host/网络
404
456
  const CONCURRENCY = 6;
405
457
  const queue = data.models.map((m) => m.key);
406
458
 
407
- // 更新单个结果并同步到 localStorage
408
- function updateResult(key, result) {
409
- setTestResults((s) => {
410
- const next = { ...s, [key]: result };
411
- saveStoredResults(next);
412
- return next;
413
- });
414
- }
415
-
416
- async function runOne(key) {
417
- try {
418
- const resp = await fetch("/api/model-health/test", {
419
- method: "POST",
420
- headers: { "Content-Type": "application/json" },
421
- body: JSON.stringify({ key }),
422
- cache: "no-store",
423
- });
424
- const json = await resp.json();
425
- if (!json.ok) {
426
- updateResult(key, { status: "fail", error: json.error });
427
- } else {
428
- updateResult(key, {
429
- status: json.status || "fail",
430
- latency: json.latency,
431
- error: json.error,
432
- });
433
- }
434
- } catch (e) {
435
- updateResult(key, { status: "fail", error: e.message });
436
- } finally {
437
- done++;
438
- setProgress({ done, total });
439
- if (done >= total) setTesting(false);
440
- }
441
- }
442
-
443
- // 限制并发的 worker pool
444
459
  async function worker() {
445
460
  while (queue.length > 0) {
446
461
  const key = queue.shift();
447
462
  if (key === undefined) break;
448
- await runOne(key);
463
+ await testModel(key);
464
+ done++;
465
+ setProgress({ done, total });
466
+ if (done >= total) setTesting(false);
449
467
  }
450
468
  }
451
469
  const workers = Array.from(
@@ -453,7 +471,7 @@ window.__ModuleLoader__.load({
453
471
  () => worker()
454
472
  );
455
473
  await Promise.all(workers);
456
- }, [state.data]);
474
+ }, [state.data, testModel]);
457
475
 
458
476
  const h = React.createElement;
459
477
 
@@ -611,6 +629,19 @@ window.__ModuleLoader__.load({
611
629
  }, row.provider);
612
630
  }
613
631
  if (col.key === "_status") {
632
+ // 未测试:显示可点击的"测试"按钮,点击即测单个模型
633
+ if (tr.status === "idle") {
634
+ return h("td", {
635
+ key: col.key,
636
+ style: STYLES.td,
637
+ },
638
+ h("button", {
639
+ style: STYLES.btnTestSmall,
640
+ title: t("testOneTip"),
641
+ onClick: () => testModel(row.key),
642
+ }, t("testOne"))
643
+ );
644
+ }
614
645
  const hasError = tr.error && tr.status === "fail";
615
646
  return h("td", {
616
647
  key: col.key,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-model-health",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "DSH plugin that lists all configured models with availability/latency testing, by reading $DSH_HOME/settings.yaml.",
5
5
  "license": "MIT",
6
6
  "repository": {