dsh-plugin-litellm-bridge 2.0.6 → 2.0.8
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.
|
@@ -582,6 +582,29 @@ window.__ModuleLoader__.load({
|
|
|
582
582
|
}
|
|
583
583
|
}, [draft, pid]);
|
|
584
584
|
|
|
585
|
+
// 模型池按供应商分组,供下拉与"添加"列表使用
|
|
586
|
+
//
|
|
587
|
+
// 【React #310 血案】这个 useMemo 原来写在下面两个提前 return **之后**:
|
|
588
|
+
// if (!draft) return ...; ← 首次渲染 draft=null,从这里返回
|
|
589
|
+
// if (!prof) return ...;
|
|
590
|
+
// const byProvider = useMemo(...) ← 首次渲染根本执行不到
|
|
591
|
+
// 第二次渲染(routes/get 返回后 draft 有值)才执行到这一行,
|
|
592
|
+
// hooks 数量从 N 变成 N+1 → React 抛
|
|
593
|
+
// "Rendered more hooks than during the previous render."(#310)
|
|
594
|
+
// 表现:点「路由配置」→ 整页空白。
|
|
595
|
+
//
|
|
596
|
+
// 规则:**所有 hooks 必须写在任何条件 return 之前**,保证每次渲染
|
|
597
|
+
// 执行完全一样多的 hooks。所以把它移到这里。
|
|
598
|
+
const byProvider = useMemo(() => {
|
|
599
|
+
const m = new Map();
|
|
600
|
+
for (const x of pool.models || []) {
|
|
601
|
+
const key = x.provider || '未知供应商';
|
|
602
|
+
if (!m.has(key)) m.set(key, []);
|
|
603
|
+
m.get(key).push(x);
|
|
604
|
+
}
|
|
605
|
+
return [...m.entries()];
|
|
606
|
+
}, [pool.models]);
|
|
607
|
+
|
|
585
608
|
// 加载失败时给出可读原因 + 重试,而不是无限「加载中…」
|
|
586
609
|
if (!draft) {
|
|
587
610
|
return h('div', null,
|
|
@@ -596,8 +619,16 @@ window.__ModuleLoader__.load({
|
|
|
596
619
|
: h('div', { style: { opacity: 0.6 } }, '加载中…'),
|
|
597
620
|
);
|
|
598
621
|
}
|
|
599
|
-
|
|
600
|
-
|
|
622
|
+
// 【防御】prof 可能是空对象或残缺结构。
|
|
623
|
+
// 先判断存在性再往下渲染,否则后面读 prof.agents[at] 会抛 TypeError。
|
|
624
|
+
const prof = draft?.[pid];
|
|
625
|
+
if (!prof || typeof prof !== 'object') {
|
|
626
|
+
return h('div', { style: S.notice('warn') },
|
|
627
|
+
`profile「${pid}」的数据不完整(缺少 agents 配置)。`,
|
|
628
|
+
h('div', { style: { ...S.hint } },
|
|
629
|
+
'可能来自旧版本的状态文件。可以在上方点击「+ 新建」创建新 profile,' +
|
|
630
|
+
'或删除当前 profile 后重建。'));
|
|
631
|
+
}
|
|
601
632
|
|
|
602
633
|
const setAgent = (at, patch) =>
|
|
603
634
|
setDraft((d) => {
|
|
@@ -692,17 +723,6 @@ window.__ModuleLoader__.load({
|
|
|
692
723
|
setBusy(false);
|
|
693
724
|
};
|
|
694
725
|
|
|
695
|
-
// 模型池按供应商分组,供下拉与"添加"列表使用
|
|
696
|
-
const byProvider = useMemo(() => {
|
|
697
|
-
const m = new Map();
|
|
698
|
-
for (const x of pool.models || []) {
|
|
699
|
-
const key = x.provider || '未知供应商';
|
|
700
|
-
if (!m.has(key)) m.set(key, []);
|
|
701
|
-
m.get(key).push(x);
|
|
702
|
-
}
|
|
703
|
-
return [...m.entries()];
|
|
704
|
-
}, [pool.models]);
|
|
705
|
-
|
|
706
726
|
const allIds = (pool.models || []).map((x) => x.id);
|
|
707
727
|
|
|
708
728
|
return h('div', null,
|
|
@@ -750,7 +770,10 @@ window.__ModuleLoader__.load({
|
|
|
750
770
|
|
|
751
771
|
// ---- 三条链:root / coder / research ----
|
|
752
772
|
AGENT_TYPES.map((at) => {
|
|
753
|
-
|
|
773
|
+
// 【防御】cfg 可能缺失(旧版 state.json 结构不全 / 宿主返回残缺)。
|
|
774
|
+
// 直接读 cfg.primary 会抛 TypeError,进而让整页崩掉。
|
|
775
|
+
const cfg = prof?.agents?.[at]
|
|
776
|
+
|| { label: at, primary: '', timeoutMs: 600000, fallbacks: [] };
|
|
754
777
|
const isSub = at !== 'root';
|
|
755
778
|
const groupLabel = at === 'root' ? 'root · 主代理降级链'
|
|
756
779
|
: at === 'coder' ? 'coder · 子代理降级链(Coder)'
|
|
@@ -1275,12 +1298,47 @@ window.__ModuleLoader__.load({
|
|
|
1275
1298
|
);
|
|
1276
1299
|
}
|
|
1277
1300
|
|
|
1278
|
-
/**
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1301
|
+
/**
|
|
1302
|
+
* 错误边界 —— 必须是**真正的 React class 错误边界**。
|
|
1303
|
+
*
|
|
1304
|
+
* 【为什么之前那版是无效的】
|
|
1305
|
+
* 原来写的是:
|
|
1306
|
+
* function Guard({ title, children }) {
|
|
1307
|
+
* try { return children(); } catch (err) { ... }
|
|
1308
|
+
* }
|
|
1309
|
+
* 用法:h(Guard, {}, () => h(RouteTab, {...}))
|
|
1310
|
+
*
|
|
1311
|
+
* 问题:children() 只是**创建元素**(h() 是纯函数,返回一个普通对象),
|
|
1312
|
+
* 此时并不会渲染 RouteTab。真正的渲染发生在 Guard 返回之后、React 内部。
|
|
1313
|
+
* 所以 RouteTab 抛的异常**根本不在这个 try 里**,catch 永远抓不到,
|
|
1314
|
+
* React 直接向上冒泡 → 整个设置页空白(连错误框都没有)。
|
|
1315
|
+
* 这正是「点路由配置后整页全空、什么都不显示」的原因。
|
|
1316
|
+
*
|
|
1317
|
+
* 正确做法:class 组件的 getDerivedStateFromError / componentDidCatch,
|
|
1318
|
+
* 这是 React 唯一能捕获子组件渲染异常的机制。
|
|
1319
|
+
*/
|
|
1320
|
+
class Guard extends React.Component {
|
|
1321
|
+
constructor(props) {
|
|
1322
|
+
super(props);
|
|
1323
|
+
this.state = { error: null };
|
|
1324
|
+
}
|
|
1325
|
+
static getDerivedStateFromError(error) {
|
|
1326
|
+
return { error };
|
|
1327
|
+
}
|
|
1328
|
+
componentDidCatch(error, info) {
|
|
1329
|
+
// 打到控制台便于排查(浏览器 F12)
|
|
1330
|
+
try {
|
|
1331
|
+
console.error('[litellm-bridge] 渲染异常:', error, info?.componentStack);
|
|
1332
|
+
} catch { /* ignore */ }
|
|
1333
|
+
}
|
|
1334
|
+
render() {
|
|
1335
|
+
if (this.state.error) {
|
|
1336
|
+
return h(ErrorBox, { title: this.props.title, error: this.state.error });
|
|
1337
|
+
}
|
|
1338
|
+
// children 既可能是元素也可能是函数,两种都支持
|
|
1339
|
+
return typeof this.props.children === 'function'
|
|
1340
|
+
? this.props.children()
|
|
1341
|
+
: this.props.children;
|
|
1284
1342
|
}
|
|
1285
1343
|
}
|
|
1286
1344
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-litellm-bridge",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "LiteLLM gateway bridge for DSH: assemble root / coder / research fallback chains from the DSH model pool and hand the order to a LiteLLM gateway that executes retries and fallbacks. The plugin performs no routing itself.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|