dsh-openrouter-provider-advisor 0.1.0
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/LICENSE +21 -0
- package/README.md +163 -0
- package/README_EN.md +116 -0
- package/cordis.patch.yml +4 -0
- package/docs/images/provider-panel.png +0 -0
- package/docs/images/settings-weights.png +0 -0
- package/dsh.plugin.json +16 -0
- package/lib/client.js +1369 -0
- package/lib/index.js +954 -0
- package/lib/types/apply-provider.d.ts +52 -0
- package/lib/types/client/i18n.d.ts +24 -0
- package/lib/types/client/index.d.ts +4 -0
- package/lib/types/client/linked-weights.d.ts +3 -0
- package/lib/types/client/provider-panel-store.d.ts +42 -0
- package/lib/types/client/provider-panel.d.ts +47 -0
- package/lib/types/client/settings-card.d.ts +24 -0
- package/lib/types/client/weight-group.d.ts +22 -0
- package/lib/types/config.d.ts +24 -0
- package/lib/types/controller.d.ts +107 -0
- package/lib/types/directory.d.ts +39 -0
- package/lib/types/host-adapter.d.ts +64 -0
- package/lib/types/host-routes.d.ts +21 -0
- package/lib/types/index.d.ts +10 -0
- package/lib/types/openrouter.d.ts +34 -0
- package/lib/types/resolve-model.d.ts +23 -0
- package/lib/types/score.d.ts +5 -0
- package/lib/types/tools.d.ts +17 -0
- package/lib/types/types.d.ts +53 -0
- package/package.json +77 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1369 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-openrouter-provider-advisor", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
//#region rolldown:runtime
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
+
value: mod,
|
|
22
|
+
enumerable: true
|
|
23
|
+
}) : target, mod));
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
let react = require("react");
|
|
27
|
+
react = __toESM(react);
|
|
28
|
+
|
|
29
|
+
//#region src/client/provider-panel-store.ts
|
|
30
|
+
async function responseJson(response) {
|
|
31
|
+
const body = await response.json();
|
|
32
|
+
if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
|
|
33
|
+
return body;
|
|
34
|
+
}
|
|
35
|
+
function createProviderPanelStore(fetchImpl = fetch) {
|
|
36
|
+
let snapshot = {
|
|
37
|
+
open: false,
|
|
38
|
+
status: "idle",
|
|
39
|
+
sessionId: null,
|
|
40
|
+
data: null,
|
|
41
|
+
models: [],
|
|
42
|
+
selected: null,
|
|
43
|
+
strategy: "balanced",
|
|
44
|
+
error: null,
|
|
45
|
+
credential: null
|
|
46
|
+
};
|
|
47
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
48
|
+
let generation = 0;
|
|
49
|
+
let controller = null;
|
|
50
|
+
const publish = (patch) => {
|
|
51
|
+
snapshot = {
|
|
52
|
+
...snapshot,
|
|
53
|
+
...patch
|
|
54
|
+
};
|
|
55
|
+
for (const listener of listeners) listener();
|
|
56
|
+
};
|
|
57
|
+
const begin = () => {
|
|
58
|
+
controller?.abort();
|
|
59
|
+
controller = new AbortController();
|
|
60
|
+
return {
|
|
61
|
+
id: ++generation,
|
|
62
|
+
signal: controller.signal
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const load = async (sessionId, refresh, selected = snapshot.selected, strategy = snapshot.strategy) => {
|
|
66
|
+
const operation = begin();
|
|
67
|
+
publish({
|
|
68
|
+
open: true,
|
|
69
|
+
status: "loading",
|
|
70
|
+
sessionId,
|
|
71
|
+
error: null
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
const selection = selected ?? void 0;
|
|
75
|
+
const data = await responseJson(refresh ? await fetchImpl("/api/openrouter-providers/refresh", {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "content-type": "application/json" },
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
sessionId,
|
|
80
|
+
...selection,
|
|
81
|
+
strategy
|
|
82
|
+
}),
|
|
83
|
+
signal: operation.signal
|
|
84
|
+
}) : await fetchImpl(`/api/openrouter-providers/recommend?${new URLSearchParams({
|
|
85
|
+
sessionId,
|
|
86
|
+
...selection ?? {},
|
|
87
|
+
strategy
|
|
88
|
+
}).toString()}`, { signal: operation.signal }));
|
|
89
|
+
if (operation.id !== generation) return;
|
|
90
|
+
publish({
|
|
91
|
+
status: "ready",
|
|
92
|
+
data,
|
|
93
|
+
error: null
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (operation.id !== generation || operation.signal.aborted) return;
|
|
97
|
+
publish({
|
|
98
|
+
status: "error",
|
|
99
|
+
data: null,
|
|
100
|
+
error: error instanceof Error ? error.message : String(error)
|
|
101
|
+
});
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const store = {
|
|
106
|
+
getSnapshot: () => snapshot,
|
|
107
|
+
subscribe(listener) {
|
|
108
|
+
listeners.add(listener);
|
|
109
|
+
return () => listeners.delete(listener);
|
|
110
|
+
},
|
|
111
|
+
async toggle(sessionId) {
|
|
112
|
+
if (snapshot.open) store.close();
|
|
113
|
+
else await store.open(sessionId);
|
|
114
|
+
},
|
|
115
|
+
async open(sessionId) {
|
|
116
|
+
const operation = begin();
|
|
117
|
+
publish({
|
|
118
|
+
open: true,
|
|
119
|
+
status: "loading",
|
|
120
|
+
sessionId,
|
|
121
|
+
strategy: "balanced",
|
|
122
|
+
data: null,
|
|
123
|
+
models: [],
|
|
124
|
+
selected: null,
|
|
125
|
+
credential: null,
|
|
126
|
+
error: null
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
const catalog = await responseJson(await fetchImpl(`/api/openrouter-providers/models?sessionId=${encodeURIComponent(sessionId)}`, { signal: operation.signal }));
|
|
130
|
+
if (operation.id !== generation) return;
|
|
131
|
+
const currentModel = catalog.models.find((model) => model.current);
|
|
132
|
+
const selected = currentModel ? {
|
|
133
|
+
provider: currentModel.provider,
|
|
134
|
+
model: currentModel.model,
|
|
135
|
+
name: currentModel.matchName
|
|
136
|
+
} : catalog.current;
|
|
137
|
+
publish({
|
|
138
|
+
models: catalog.models,
|
|
139
|
+
selected,
|
|
140
|
+
credential: catalog.credential
|
|
141
|
+
});
|
|
142
|
+
if (!catalog.credential.configured) {
|
|
143
|
+
publish({
|
|
144
|
+
status: "ready",
|
|
145
|
+
data: null,
|
|
146
|
+
error: null
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
await load(sessionId, false, selected, "balanced");
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (operation.id !== generation || operation.signal.aborted) return;
|
|
153
|
+
publish({
|
|
154
|
+
status: "error",
|
|
155
|
+
data: null,
|
|
156
|
+
error: error instanceof Error ? error.message : String(error)
|
|
157
|
+
});
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
close() {
|
|
162
|
+
controller?.abort();
|
|
163
|
+
controller = null;
|
|
164
|
+
generation += 1;
|
|
165
|
+
publish({
|
|
166
|
+
open: false,
|
|
167
|
+
status: snapshot.data ? "ready" : "idle",
|
|
168
|
+
strategy: "balanced",
|
|
169
|
+
error: null
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
refresh: (sessionId) => load(sessionId, true),
|
|
173
|
+
async selectModel(sessionId, provider, model, name) {
|
|
174
|
+
const selected = {
|
|
175
|
+
provider,
|
|
176
|
+
model,
|
|
177
|
+
name
|
|
178
|
+
};
|
|
179
|
+
publish({
|
|
180
|
+
selected,
|
|
181
|
+
data: null
|
|
182
|
+
});
|
|
183
|
+
await load(sessionId, false, selected);
|
|
184
|
+
},
|
|
185
|
+
async setStrategy(sessionId, strategy) {
|
|
186
|
+
publish({ strategy });
|
|
187
|
+
await load(sessionId, false, snapshot.selected, strategy);
|
|
188
|
+
},
|
|
189
|
+
async apply(sessionId, tag) {
|
|
190
|
+
const operation = begin();
|
|
191
|
+
publish({
|
|
192
|
+
open: true,
|
|
193
|
+
status: "applying",
|
|
194
|
+
sessionId,
|
|
195
|
+
error: null
|
|
196
|
+
});
|
|
197
|
+
try {
|
|
198
|
+
await responseJson(await fetchImpl("/api/openrouter-providers/apply", {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: { "content-type": "application/json" },
|
|
201
|
+
body: JSON.stringify({
|
|
202
|
+
sessionId,
|
|
203
|
+
tag,
|
|
204
|
+
...snapshot.selected ?? {},
|
|
205
|
+
strategy: snapshot.strategy
|
|
206
|
+
}),
|
|
207
|
+
signal: operation.signal
|
|
208
|
+
}));
|
|
209
|
+
if (operation.id !== generation) return;
|
|
210
|
+
store.close();
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (operation.id !== generation || operation.signal.aborted) return;
|
|
213
|
+
publish({
|
|
214
|
+
open: true,
|
|
215
|
+
status: "error",
|
|
216
|
+
error: error instanceof Error ? error.message : String(error)
|
|
217
|
+
});
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
dispose() {
|
|
222
|
+
controller?.abort();
|
|
223
|
+
controller = null;
|
|
224
|
+
generation += 1;
|
|
225
|
+
listeners.clear();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
return store;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/client/i18n.ts
|
|
233
|
+
const messages = {
|
|
234
|
+
"zh-CN": {
|
|
235
|
+
"panel.title": "OpenRouter 供应商",
|
|
236
|
+
"panel.currentModelLoading": "正在读取当前模型…",
|
|
237
|
+
"panel.modelSelect": "选择 DSH 模型",
|
|
238
|
+
"panel.strategyGroup": "推荐策略",
|
|
239
|
+
"panel.refresh": "刷新",
|
|
240
|
+
"panel.close": "关闭",
|
|
241
|
+
"panel.loading": "正在加载供应商…",
|
|
242
|
+
"panel.titleTrigger": "OpenRouter 供应商",
|
|
243
|
+
"panel.unavailable": "当前会话不可切换供应商",
|
|
244
|
+
"panel.group.recommended": "推荐 Top 5",
|
|
245
|
+
"panel.group.rest": "其他合格供应商",
|
|
246
|
+
"panel.column.provider": "供应商",
|
|
247
|
+
"panel.column.details": "规格详情",
|
|
248
|
+
"panel.column.score": "分数",
|
|
249
|
+
"panel.current": "当前",
|
|
250
|
+
"panel.credentialMissing": "未检测到 OpenRouter 凭据“{ref}”。请前往 DSH 设置 → 模型完成配置;推荐、刷新和供应商切换暂不可用。",
|
|
251
|
+
"panel.switchHint": "点击供应商即可实时切换;当前会话与默认模型会立即更新。",
|
|
252
|
+
"panel.uptime": "可用率",
|
|
253
|
+
"panel.uptimeDelayHint": "可用率来自 OpenRouter 近 30 分钟统计,故障与恢复可能存在检测延迟。",
|
|
254
|
+
"panel.strategy.balanced": "综合最佳",
|
|
255
|
+
"panel.strategy.price": "价格优先",
|
|
256
|
+
"panel.strategy.speed": "速度优先",
|
|
257
|
+
"panel.strategy.context": "上下文优先",
|
|
258
|
+
"settings.title": "OpenRouter 供应商",
|
|
259
|
+
"settings.summary": "推荐数量、综合最佳权重与全局价格模型。",
|
|
260
|
+
"settings.action.weights": "应用综合权重",
|
|
261
|
+
"settings.action.priceBlend": "应用价格占比",
|
|
262
|
+
"settings.action.reset": "恢复默认",
|
|
263
|
+
"settings.action.lock": "锁定 {name}",
|
|
264
|
+
"settings.action.unlock": "解锁 {name}",
|
|
265
|
+
"settings.expand": "展开",
|
|
266
|
+
"settings.collapse": "收起",
|
|
267
|
+
"settings.row.minContextTokens": "最小上下文 tokens",
|
|
268
|
+
"settings.row.recommendedCount": "推荐数量(1–10)",
|
|
269
|
+
"settings.row.cacheTtlMs": "缓存 TTL(毫秒)",
|
|
270
|
+
"settings.row.weights.quantization": "量化权重",
|
|
271
|
+
"settings.row.weights.speed": "速度权重",
|
|
272
|
+
"settings.row.weights.price": "价格权重",
|
|
273
|
+
"settings.row.weights.context": "上下文权重",
|
|
274
|
+
"settings.row.priceBlend.input": "输入价格占比",
|
|
275
|
+
"settings.row.priceBlend.output": "输出价格占比",
|
|
276
|
+
"settings.row.priceBlend.cache": "缓存价格占比",
|
|
277
|
+
"settings.row.uptimePenaltyThreshold": "Uptime 降权阈值(%)",
|
|
278
|
+
"settings.row.uptimePenaltyFactor": "Uptime 降权系数",
|
|
279
|
+
"settings.group.weights.title": "综合最佳评分权重",
|
|
280
|
+
"settings.group.weights.scope": "仅影响“综合最佳”推荐策略;调整一项时,其他未锁定项会按比例联动。",
|
|
281
|
+
"settings.group.priceBlend.title": "价格计算占比",
|
|
282
|
+
"settings.group.priceBlend.scope": "所有推荐策略共用。Code Agent 默认按 95% 以上缓存流量优化。",
|
|
283
|
+
"settings.group.advanced.title": "筛选与可靠性",
|
|
284
|
+
"settings.group.strategies.title": "查看其他策略权重",
|
|
285
|
+
"settings.group.strategies.scope": "以下策略使用内置权重;价格维度仍使用上方全局价格占比。",
|
|
286
|
+
"settings.dimension.quantization": "量化",
|
|
287
|
+
"settings.dimension.speed": "速度",
|
|
288
|
+
"settings.dimension.price": "价格",
|
|
289
|
+
"settings.dimension.context": "上下文",
|
|
290
|
+
"settings.dimension.input": "输入",
|
|
291
|
+
"settings.dimension.output": "输出",
|
|
292
|
+
"settings.dimension.cache": "缓存",
|
|
293
|
+
"settings.connection.title": "OpenRouter 连接",
|
|
294
|
+
"settings.connection.loading": "正在检查 DSH OpenRouter 配置…",
|
|
295
|
+
"settings.connection.configured": "已复用 DSH 中配置的 OpenRouter 凭据",
|
|
296
|
+
"settings.connection.missing": "尚未检测到可用的 OpenRouter 凭据",
|
|
297
|
+
"settings.connection.error": "暂时无法读取连接状态",
|
|
298
|
+
"settings.connection.guide": "请前往 DSH 设置 → 模型,为 OpenRouter provider 配置 API Key。插件不会读取或显示密钥内容。"
|
|
299
|
+
},
|
|
300
|
+
"en-US": {
|
|
301
|
+
"panel.title": "OpenRouter Providers",
|
|
302
|
+
"panel.currentModelLoading": "Loading current model…",
|
|
303
|
+
"panel.modelSelect": "Select DSH model",
|
|
304
|
+
"panel.strategyGroup": "Ranking strategy",
|
|
305
|
+
"panel.refresh": "Refresh",
|
|
306
|
+
"panel.close": "Close",
|
|
307
|
+
"panel.loading": "Loading providers…",
|
|
308
|
+
"panel.titleTrigger": "OpenRouter Providers",
|
|
309
|
+
"panel.unavailable": "Provider switching is unavailable for this session",
|
|
310
|
+
"panel.group.recommended": "Top 5 Recommendations",
|
|
311
|
+
"panel.group.rest": "Other Eligible Providers",
|
|
312
|
+
"panel.column.provider": "Provider",
|
|
313
|
+
"panel.column.details": "Specs & Performance",
|
|
314
|
+
"panel.column.score": "Score",
|
|
315
|
+
"panel.current": "Current",
|
|
316
|
+
"panel.credentialMissing": "OpenRouter credential “{ref}” is not configured. Configure it in DSH Settings → Models; recommendations, refresh, and provider switching are disabled for now.",
|
|
317
|
+
"panel.switchHint": "Click a provider to switch immediately; the current session and default model update together.",
|
|
318
|
+
"panel.uptime": "uptime",
|
|
319
|
+
"panel.uptimeDelayHint": "Uptime comes from OpenRouter's trailing 30-minute statistics, so failures and recoveries may be detected with a delay.",
|
|
320
|
+
"panel.strategy.balanced": "Best overall",
|
|
321
|
+
"panel.strategy.price": "Price first",
|
|
322
|
+
"panel.strategy.speed": "Speed first",
|
|
323
|
+
"panel.strategy.context": "Context first",
|
|
324
|
+
"settings.title": "OpenRouter Providers",
|
|
325
|
+
"settings.summary": "Recommended count, ranking weights, and price blend.",
|
|
326
|
+
"settings.action.weights": "Apply ranking weights",
|
|
327
|
+
"settings.action.priceBlend": "Apply price blend",
|
|
328
|
+
"settings.action.reset": "Reset defaults",
|
|
329
|
+
"settings.action.lock": "Lock {name}",
|
|
330
|
+
"settings.action.unlock": "Unlock {name}",
|
|
331
|
+
"settings.expand": "Expand",
|
|
332
|
+
"settings.collapse": "Collapse",
|
|
333
|
+
"settings.row.minContextTokens": "Minimum context tokens",
|
|
334
|
+
"settings.row.recommendedCount": "Recommended count (1-10)",
|
|
335
|
+
"settings.row.cacheTtlMs": "Cache TTL (ms)",
|
|
336
|
+
"settings.row.weights.quantization": "Quantization weight",
|
|
337
|
+
"settings.row.weights.speed": "Speed weight",
|
|
338
|
+
"settings.row.weights.price": "Price weight",
|
|
339
|
+
"settings.row.weights.context": "Context weight",
|
|
340
|
+
"settings.row.priceBlend.input": "Input price share",
|
|
341
|
+
"settings.row.priceBlend.output": "Output price share",
|
|
342
|
+
"settings.row.priceBlend.cache": "Cache price share",
|
|
343
|
+
"settings.row.uptimePenaltyThreshold": "Uptime penalty threshold (%)",
|
|
344
|
+
"settings.row.uptimePenaltyFactor": "Uptime penalty factor",
|
|
345
|
+
"settings.group.weights.title": "Best-overall scoring weights",
|
|
346
|
+
"settings.group.weights.scope": "Affects only Best overall. Changing one item proportionally redistributes the other unlocked items.",
|
|
347
|
+
"settings.group.priceBlend.title": "Price calculation blend",
|
|
348
|
+
"settings.group.priceBlend.scope": "Shared by all ranking strategies. The Code Agent default is optimized for 95%+ cached traffic.",
|
|
349
|
+
"settings.group.advanced.title": "Filtering & reliability",
|
|
350
|
+
"settings.group.strategies.title": "View other strategy weights",
|
|
351
|
+
"settings.group.strategies.scope": "These strategies use built-in weights; their price dimension still uses the global price blend above.",
|
|
352
|
+
"settings.dimension.quantization": "Quality",
|
|
353
|
+
"settings.dimension.speed": "Speed",
|
|
354
|
+
"settings.dimension.price": "Price",
|
|
355
|
+
"settings.dimension.context": "Context",
|
|
356
|
+
"settings.dimension.input": "Input",
|
|
357
|
+
"settings.dimension.output": "Output",
|
|
358
|
+
"settings.dimension.cache": "Cache",
|
|
359
|
+
"settings.connection.title": "OpenRouter connection",
|
|
360
|
+
"settings.connection.loading": "Checking the DSH OpenRouter configuration…",
|
|
361
|
+
"settings.connection.configured": "Reusing the OpenRouter credential configured in DSH",
|
|
362
|
+
"settings.connection.missing": "No usable OpenRouter credential was found",
|
|
363
|
+
"settings.connection.error": "Connection status is temporarily unavailable",
|
|
364
|
+
"settings.connection.guide": "Open DSH Settings → Models and configure an API key for the OpenRouter provider. The plugin never reads or displays the key in the client."
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const zh = messages["zh-CN"];
|
|
368
|
+
const en = messages["en-US"];
|
|
369
|
+
const LOCALE_NS = "openrouterProviders";
|
|
370
|
+
let localeService;
|
|
371
|
+
function attachLocale(service) {
|
|
372
|
+
localeService = service;
|
|
373
|
+
}
|
|
374
|
+
function normalizeLocale(input) {
|
|
375
|
+
return input?.toLowerCase().startsWith("zh") ? "zh-CN" : "en-US";
|
|
376
|
+
}
|
|
377
|
+
function getLocaleSnapshot() {
|
|
378
|
+
const active = localeService?.getSnapshot().active;
|
|
379
|
+
const browserLang = typeof navigator === "undefined" ? void 0 : navigator.language;
|
|
380
|
+
return normalizeLocale(active ?? browserLang);
|
|
381
|
+
}
|
|
382
|
+
function createI18n(locale) {
|
|
383
|
+
const normalized = normalizeLocale(locale);
|
|
384
|
+
return {
|
|
385
|
+
locale: normalized,
|
|
386
|
+
t(key, params) {
|
|
387
|
+
let text = messages[normalized][key];
|
|
388
|
+
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
389
|
+
return text;
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function subscribeLocale(listener) {
|
|
394
|
+
return localeService?.subscribe(listener) ?? (() => {});
|
|
395
|
+
}
|
|
396
|
+
function useUiI18n() {
|
|
397
|
+
return createI18n((0, react.useSyncExternalStore)(subscribeLocale, getLocaleSnapshot, () => "en-US"));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/client/provider-panel.ts
|
|
402
|
+
const STRATEGY_OPTIONS = [
|
|
403
|
+
{
|
|
404
|
+
id: "balanced",
|
|
405
|
+
labelKey: "panel.strategy.balanced"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
id: "price",
|
|
409
|
+
labelKey: "panel.strategy.price"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: "speed",
|
|
413
|
+
labelKey: "panel.strategy.speed"
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: "context",
|
|
417
|
+
labelKey: "panel.strategy.context"
|
|
418
|
+
}
|
|
419
|
+
];
|
|
420
|
+
function providerRows(data, i18n = createI18n("zh-CN")) {
|
|
421
|
+
const map = (group) => (row) => ({
|
|
422
|
+
group,
|
|
423
|
+
tag: row.tag,
|
|
424
|
+
providerName: row.providerName,
|
|
425
|
+
current: row.current,
|
|
426
|
+
score: row.score,
|
|
427
|
+
detail: `${row.quantization} · ${row.tps} t/s · $${row.price.input}/$${row.price.output} · ${row.contextLength.toLocaleString()} ctx · ${row.uptime === null ? "N/A" : `${row.uptime.toFixed(2)}%`} ${i18n.t("panel.uptime")}`,
|
|
428
|
+
scoreLabel: row.current ? `${i18n.t("panel.current")} · ${row.score.toFixed(1)}` : row.score.toFixed(1)
|
|
429
|
+
});
|
|
430
|
+
return [...data.recommended.map(map(i18n.t("panel.group.recommended"))), ...data.rest.map(map(i18n.t("panel.group.rest")))];
|
|
431
|
+
}
|
|
432
|
+
function providerTableColumns(i18n = createI18n("zh-CN")) {
|
|
433
|
+
return [
|
|
434
|
+
i18n.t("panel.column.provider"),
|
|
435
|
+
i18n.t("panel.column.details"),
|
|
436
|
+
i18n.t("panel.column.score")
|
|
437
|
+
];
|
|
438
|
+
}
|
|
439
|
+
function providerTriggerState(state) {
|
|
440
|
+
if (state.current === void 0 || state.currentAddress !== void 0) return {
|
|
441
|
+
sessionId: null,
|
|
442
|
+
disabled: true
|
|
443
|
+
};
|
|
444
|
+
return {
|
|
445
|
+
sessionId: state.current,
|
|
446
|
+
disabled: false
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const triggerStyle = {
|
|
450
|
+
display: "flex",
|
|
451
|
+
alignItems: "center",
|
|
452
|
+
gap: 8,
|
|
453
|
+
border: 0,
|
|
454
|
+
background: "transparent",
|
|
455
|
+
color: "inherit",
|
|
456
|
+
cursor: "pointer",
|
|
457
|
+
minHeight: 36,
|
|
458
|
+
padding: "0 10px",
|
|
459
|
+
borderRadius: 8,
|
|
460
|
+
fontSize: 14
|
|
461
|
+
};
|
|
462
|
+
function providerTriggerLayout(wide) {
|
|
463
|
+
return wide ? {
|
|
464
|
+
flex: "1 0 100%",
|
|
465
|
+
width: "100%",
|
|
466
|
+
whiteSpace: "nowrap",
|
|
467
|
+
justifyContent: "flex-start"
|
|
468
|
+
} : {
|
|
469
|
+
flex: "0 0 36px",
|
|
470
|
+
width: 36,
|
|
471
|
+
whiteSpace: "nowrap",
|
|
472
|
+
justifyContent: "center"
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function consume(operation) {
|
|
476
|
+
operation.catch(() => {});
|
|
477
|
+
}
|
|
478
|
+
function ProviderSidebarTrigger(props) {
|
|
479
|
+
const i18n = useUiI18n();
|
|
480
|
+
const current = props.useSessions((state) => state.current);
|
|
481
|
+
const currentAddress = props.useSessions((state) => state.currentAddress);
|
|
482
|
+
const panel = (0, react.useSyncExternalStore)(props.store.subscribe, props.store.getSnapshot, props.store.getSnapshot);
|
|
483
|
+
const target = providerTriggerState({
|
|
484
|
+
current,
|
|
485
|
+
currentAddress
|
|
486
|
+
});
|
|
487
|
+
return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("style", null, "div:has(> div[data-slot=\"sidebar.footer.action\"] > button[data-openrouter-provider-trigger]){flex-wrap:wrap;row-gap:4px}"), (0, react.createElement)("button", {
|
|
488
|
+
type: "button",
|
|
489
|
+
disabled: target.disabled,
|
|
490
|
+
"aria-label": i18n.t("panel.titleTrigger"),
|
|
491
|
+
"aria-pressed": panel.open,
|
|
492
|
+
"data-openrouter-provider-trigger": "",
|
|
493
|
+
title: target.disabled ? i18n.t("panel.unavailable") : i18n.t("panel.titleTrigger"),
|
|
494
|
+
style: {
|
|
495
|
+
...triggerStyle,
|
|
496
|
+
...providerTriggerLayout(props.wide),
|
|
497
|
+
opacity: target.disabled ? .45 : 1
|
|
498
|
+
},
|
|
499
|
+
onClick: () => {
|
|
500
|
+
if (target.sessionId) consume(props.store.toggle(target.sessionId));
|
|
501
|
+
}
|
|
502
|
+
}, (0, react.createElement)("span", { "aria-hidden": true }, "⇄"), props.wide ? (0, react.createElement)("span", null, i18n.t("panel.titleTrigger")) : null));
|
|
503
|
+
}
|
|
504
|
+
const backdrop = {
|
|
505
|
+
position: "fixed",
|
|
506
|
+
inset: 0,
|
|
507
|
+
zIndex: 90,
|
|
508
|
+
background: "rgba(0,0,0,.52)",
|
|
509
|
+
display: "flex",
|
|
510
|
+
alignItems: "center",
|
|
511
|
+
justifyContent: "center",
|
|
512
|
+
padding: 24
|
|
513
|
+
};
|
|
514
|
+
const panelStyle = {
|
|
515
|
+
width: "min(720px, 100%)",
|
|
516
|
+
maxHeight: "min(760px, calc(100vh - 48px))",
|
|
517
|
+
overflow: "hidden",
|
|
518
|
+
display: "flex",
|
|
519
|
+
flexDirection: "column",
|
|
520
|
+
border: "1px solid rgba(255,255,255,.12)",
|
|
521
|
+
borderRadius: 16,
|
|
522
|
+
background: "#18181b",
|
|
523
|
+
color: "#f4f4f5",
|
|
524
|
+
boxShadow: "0 24px 80px rgba(0,0,0,.5)"
|
|
525
|
+
};
|
|
526
|
+
const providerGridColumns = "minmax(110px,1fr) minmax(0,2fr) minmax(54px,auto)";
|
|
527
|
+
function ProviderOverlay(props) {
|
|
528
|
+
const i18n = useUiI18n();
|
|
529
|
+
const current = props.useSessions((state$1) => state$1.current);
|
|
530
|
+
const currentAddress = props.useSessions((state$1) => state$1.currentAddress);
|
|
531
|
+
const state = (0, react.useSyncExternalStore)(props.store.subscribe, props.store.getSnapshot, props.store.getSnapshot);
|
|
532
|
+
const target = providerTriggerState({
|
|
533
|
+
current,
|
|
534
|
+
currentAddress
|
|
535
|
+
});
|
|
536
|
+
(0, react.useEffect)(() => {
|
|
537
|
+
if (!state.open) return;
|
|
538
|
+
if (!target.sessionId) props.store.close();
|
|
539
|
+
else if (state.sessionId !== target.sessionId) consume(props.store.open(target.sessionId));
|
|
540
|
+
}, [
|
|
541
|
+
state.open,
|
|
542
|
+
state.sessionId,
|
|
543
|
+
target.sessionId,
|
|
544
|
+
props.store
|
|
545
|
+
]);
|
|
546
|
+
if (!state.open) return null;
|
|
547
|
+
const rows = state.data ? providerRows(state.data, i18n) : [];
|
|
548
|
+
const credentialReady = state.credential?.configured === true;
|
|
549
|
+
const columns = providerTableColumns(i18n);
|
|
550
|
+
const selectedValue = state.selected ? JSON.stringify([state.selected.provider, state.selected.model]) : "";
|
|
551
|
+
let lastGroup = null;
|
|
552
|
+
return (0, react.createElement)("div", {
|
|
553
|
+
style: backdrop,
|
|
554
|
+
role: "presentation",
|
|
555
|
+
onMouseDown: (event) => {
|
|
556
|
+
if (event.target === event.currentTarget) props.store.close();
|
|
557
|
+
}
|
|
558
|
+
}, (0, react.createElement)("section", {
|
|
559
|
+
role: "dialog",
|
|
560
|
+
"aria-modal": true,
|
|
561
|
+
"aria-label": i18n.t("panel.title"),
|
|
562
|
+
style: panelStyle
|
|
563
|
+
}, (0, react.createElement)("header", { style: {
|
|
564
|
+
display: "flex",
|
|
565
|
+
alignItems: "start",
|
|
566
|
+
justifyContent: "space-between",
|
|
567
|
+
padding: "20px 22px 16px",
|
|
568
|
+
borderBottom: "1px solid rgba(255,255,255,.1)"
|
|
569
|
+
} }, (0, react.createElement)("div", null, (0, react.createElement)("h2", { style: {
|
|
570
|
+
margin: 0,
|
|
571
|
+
fontSize: 20
|
|
572
|
+
} }, i18n.t("panel.title")), (0, react.createElement)("p", { style: {
|
|
573
|
+
margin: "6px 0 10px",
|
|
574
|
+
color: "#a1a1aa",
|
|
575
|
+
fontSize: 13
|
|
576
|
+
} }, state.data ? state.data.openrouterModel : i18n.t("panel.currentModelLoading")), (0, react.createElement)("select", {
|
|
577
|
+
"aria-label": i18n.t("panel.modelSelect"),
|
|
578
|
+
value: selectedValue,
|
|
579
|
+
disabled: !credentialReady || state.models.length === 0 || state.status === "applying",
|
|
580
|
+
style: {
|
|
581
|
+
minWidth: 300,
|
|
582
|
+
maxWidth: "min(460px, 60vw)",
|
|
583
|
+
padding: "7px 10px",
|
|
584
|
+
borderRadius: 8,
|
|
585
|
+
border: "1px solid rgba(255,255,255,.16)",
|
|
586
|
+
background: "#27272a",
|
|
587
|
+
color: "#f4f4f5"
|
|
588
|
+
},
|
|
589
|
+
onChange: (event) => {
|
|
590
|
+
if (!target.sessionId) return;
|
|
591
|
+
const [provider, model] = JSON.parse(event.currentTarget.value);
|
|
592
|
+
const selected = state.models.find((item) => item.provider === provider && item.model === model);
|
|
593
|
+
if (selected) consume(props.store.selectModel(target.sessionId, provider, model, selected.matchName));
|
|
594
|
+
}
|
|
595
|
+
}, ...state.models.map((model) => (0, react.createElement)("option", {
|
|
596
|
+
key: `${model.provider}\0${model.model}`,
|
|
597
|
+
value: JSON.stringify([model.provider, model.model])
|
|
598
|
+
}, `${model.name}${model.current ? ` (${i18n.t("panel.current")})` : ""}`))), (0, react.createElement)("div", {
|
|
599
|
+
role: "group",
|
|
600
|
+
"aria-label": i18n.t("panel.strategyGroup"),
|
|
601
|
+
style: {
|
|
602
|
+
display: "grid",
|
|
603
|
+
gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
|
|
604
|
+
gap: 6,
|
|
605
|
+
width: "min(560px, 70vw)",
|
|
606
|
+
marginTop: 12,
|
|
607
|
+
padding: 4,
|
|
608
|
+
borderRadius: 10,
|
|
609
|
+
background: "rgba(255,255,255,.05)"
|
|
610
|
+
}
|
|
611
|
+
}, ...STRATEGY_OPTIONS.map((option) => {
|
|
612
|
+
const active = state.strategy === option.id;
|
|
613
|
+
return (0, react.createElement)("button", {
|
|
614
|
+
key: option.id,
|
|
615
|
+
type: "button",
|
|
616
|
+
"aria-pressed": active,
|
|
617
|
+
disabled: !credentialReady || state.status === "loading" || state.status === "applying",
|
|
618
|
+
onClick: () => {
|
|
619
|
+
if (target.sessionId && !active) consume(props.store.setStrategy(target.sessionId, option.id));
|
|
620
|
+
},
|
|
621
|
+
style: {
|
|
622
|
+
border: active ? "1px solid #3b82f6" : "1px solid transparent",
|
|
623
|
+
borderRadius: 7,
|
|
624
|
+
padding: "7px 10px",
|
|
625
|
+
background: active ? "rgba(37,99,235,.2)" : "transparent",
|
|
626
|
+
color: active ? "#93c5fd" : "#d4d4d8",
|
|
627
|
+
cursor: active ? "default" : "pointer",
|
|
628
|
+
whiteSpace: "nowrap"
|
|
629
|
+
}
|
|
630
|
+
}, i18n.t(option.labelKey));
|
|
631
|
+
}))), (0, react.createElement)("div", { style: {
|
|
632
|
+
display: "flex",
|
|
633
|
+
gap: 8
|
|
634
|
+
} }, (0, react.createElement)("button", {
|
|
635
|
+
type: "button",
|
|
636
|
+
disabled: !credentialReady || !target.sessionId || state.status === "loading" || state.status === "applying",
|
|
637
|
+
onClick: () => {
|
|
638
|
+
if (target.sessionId) consume(props.store.refresh(target.sessionId));
|
|
639
|
+
}
|
|
640
|
+
}, i18n.t("panel.refresh")), (0, react.createElement)("button", {
|
|
641
|
+
type: "button",
|
|
642
|
+
"aria-label": i18n.t("panel.close"),
|
|
643
|
+
onClick: () => props.store.close()
|
|
644
|
+
}, "×"))), state.credential !== null && !state.credential.configured ? (0, react.createElement)("p", {
|
|
645
|
+
role: "status",
|
|
646
|
+
style: {
|
|
647
|
+
margin: "14px 22px 0",
|
|
648
|
+
padding: 12,
|
|
649
|
+
borderRadius: 8,
|
|
650
|
+
color: "#fdba74",
|
|
651
|
+
background: "rgba(124,45,18,.28)",
|
|
652
|
+
border: "1px solid rgba(249,115,22,.25)",
|
|
653
|
+
fontSize: 13,
|
|
654
|
+
lineHeight: 1.5
|
|
655
|
+
}
|
|
656
|
+
}, i18n.t("panel.credentialMissing", { ref: state.credential.ref })) : null, state.error ? (0, react.createElement)("p", {
|
|
657
|
+
role: "alert",
|
|
658
|
+
style: {
|
|
659
|
+
margin: "14px 22px 0",
|
|
660
|
+
padding: 12,
|
|
661
|
+
borderRadius: 8,
|
|
662
|
+
color: "#fca5a5",
|
|
663
|
+
background: "rgba(127,29,29,.35)"
|
|
664
|
+
}
|
|
665
|
+
}, state.error) : null, state.status === "loading" && rows.length === 0 ? (0, react.createElement)("p", { style: {
|
|
666
|
+
padding: 22,
|
|
667
|
+
color: "#a1a1aa"
|
|
668
|
+
} }, i18n.t("panel.loading")) : null, credentialReady && rows.length > 0 ? (0, react.createElement)("div", { style: {
|
|
669
|
+
display: "grid",
|
|
670
|
+
gap: 2,
|
|
671
|
+
margin: "12px 22px 0",
|
|
672
|
+
color: "#71717a",
|
|
673
|
+
fontSize: 11,
|
|
674
|
+
lineHeight: 1.5
|
|
675
|
+
} }, (0, react.createElement)("span", null, i18n.t("panel.switchHint")), (0, react.createElement)("span", { style: {
|
|
676
|
+
color: "#52525b",
|
|
677
|
+
fontSize: 10
|
|
678
|
+
} }, i18n.t("panel.uptimeDelayHint"))) : null, (0, react.createElement)("div", { style: {
|
|
679
|
+
overflowY: "auto",
|
|
680
|
+
padding: "12px 14px 20px"
|
|
681
|
+
} }, ...rows.flatMap((row) => {
|
|
682
|
+
const nodes = [];
|
|
683
|
+
if (lastGroup !== row.group) {
|
|
684
|
+
lastGroup = row.group;
|
|
685
|
+
nodes.push((0, react.createElement)("h3", {
|
|
686
|
+
key: `group-${row.group}`,
|
|
687
|
+
style: {
|
|
688
|
+
margin: "14px 8px 8px",
|
|
689
|
+
color: "#a1a1aa",
|
|
690
|
+
fontSize: 12,
|
|
691
|
+
textTransform: "uppercase",
|
|
692
|
+
letterSpacing: ".06em"
|
|
693
|
+
}
|
|
694
|
+
}, row.group));
|
|
695
|
+
nodes.push((0, react.createElement)("div", {
|
|
696
|
+
key: `columns-${row.group}`,
|
|
697
|
+
style: {
|
|
698
|
+
display: "grid",
|
|
699
|
+
gridTemplateColumns: providerGridColumns,
|
|
700
|
+
gap: 14,
|
|
701
|
+
padding: "0 14px 8px",
|
|
702
|
+
color: "#71717a",
|
|
703
|
+
fontSize: 11,
|
|
704
|
+
letterSpacing: ".04em",
|
|
705
|
+
textTransform: "uppercase"
|
|
706
|
+
}
|
|
707
|
+
}, (0, react.createElement)("span", null, columns[0]), (0, react.createElement)("span", null, columns[1]), (0, react.createElement)("span", { style: { textAlign: "right" } }, columns[2])));
|
|
708
|
+
}
|
|
709
|
+
nodes.push((0, react.createElement)("button", {
|
|
710
|
+
key: row.tag,
|
|
711
|
+
type: "button",
|
|
712
|
+
disabled: row.current || state.status === "applying",
|
|
713
|
+
onClick: () => {
|
|
714
|
+
if (target.sessionId) consume(props.store.apply(target.sessionId, row.tag));
|
|
715
|
+
},
|
|
716
|
+
style: {
|
|
717
|
+
width: "100%",
|
|
718
|
+
display: "grid",
|
|
719
|
+
gridTemplateColumns: providerGridColumns,
|
|
720
|
+
gap: 14,
|
|
721
|
+
alignItems: "center",
|
|
722
|
+
padding: "12px 14px",
|
|
723
|
+
marginBottom: 6,
|
|
724
|
+
borderRadius: 10,
|
|
725
|
+
border: row.current ? "1px solid #3b82f6" : "1px solid rgba(255,255,255,.08)",
|
|
726
|
+
background: row.current ? "rgba(37,99,235,.14)" : "rgba(255,255,255,.035)",
|
|
727
|
+
color: "inherit",
|
|
728
|
+
textAlign: "left",
|
|
729
|
+
cursor: row.current ? "default" : "pointer"
|
|
730
|
+
}
|
|
731
|
+
}, (0, react.createElement)("strong", null, row.providerName), (0, react.createElement)("span", {
|
|
732
|
+
title: row.detail,
|
|
733
|
+
style: {
|
|
734
|
+
color: "#a1a1aa",
|
|
735
|
+
fontSize: 13,
|
|
736
|
+
overflow: "hidden",
|
|
737
|
+
textOverflow: "ellipsis",
|
|
738
|
+
whiteSpace: "nowrap"
|
|
739
|
+
}
|
|
740
|
+
}, row.detail), (0, react.createElement)("span", { style: {
|
|
741
|
+
color: row.current ? "#60a5fa" : "#d4d4d8",
|
|
742
|
+
fontSize: 12,
|
|
743
|
+
textAlign: "right"
|
|
744
|
+
} }, row.scoreLabel)));
|
|
745
|
+
return nodes;
|
|
746
|
+
}))));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
//#endregion
|
|
750
|
+
//#region src/config.ts
|
|
751
|
+
const STRATEGY_WEIGHTS = {
|
|
752
|
+
price: {
|
|
753
|
+
quantization: .1,
|
|
754
|
+
speed: .1,
|
|
755
|
+
price: .7,
|
|
756
|
+
context: .1
|
|
757
|
+
},
|
|
758
|
+
speed: {
|
|
759
|
+
quantization: .15,
|
|
760
|
+
speed: .65,
|
|
761
|
+
price: .1,
|
|
762
|
+
context: .1
|
|
763
|
+
},
|
|
764
|
+
context: {
|
|
765
|
+
quantization: .15,
|
|
766
|
+
speed: .1,
|
|
767
|
+
price: .1,
|
|
768
|
+
context: .65
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
const DEFAULT_CONFIG = Object.freeze({
|
|
772
|
+
minContextTokens: 1e5,
|
|
773
|
+
cacheTtlMs: 3e5,
|
|
774
|
+
recommendedCount: 5,
|
|
775
|
+
weights: Object.freeze({
|
|
776
|
+
quantization: .3,
|
|
777
|
+
speed: .3,
|
|
778
|
+
price: .3,
|
|
779
|
+
context: .1
|
|
780
|
+
}),
|
|
781
|
+
priceBlend: Object.freeze({
|
|
782
|
+
input: .02,
|
|
783
|
+
output: .08,
|
|
784
|
+
cache: .9
|
|
785
|
+
}),
|
|
786
|
+
uptimePenaltyThreshold: 99,
|
|
787
|
+
uptimePenaltyFactor: .85
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
//#endregion
|
|
791
|
+
//#region src/client/linked-weights.ts
|
|
792
|
+
const PRECISION = 1e6;
|
|
793
|
+
function round(value) {
|
|
794
|
+
return Math.round((value + Number.EPSILON) * PRECISION) / PRECISION;
|
|
795
|
+
}
|
|
796
|
+
function rebalanceWeights(values, changedKey, requestedValue, lockedKeys) {
|
|
797
|
+
const keys = Object.keys(values);
|
|
798
|
+
const lockedPeers = keys.filter((key) => key !== changedKey && lockedKeys.has(key));
|
|
799
|
+
const adjustablePeers = keys.filter((key) => key !== changedKey && !lockedKeys.has(key));
|
|
800
|
+
const lockedTotal = lockedPeers.reduce((sum, key) => sum + values[key], 0);
|
|
801
|
+
const capacity = Math.max(0, 1 - lockedTotal);
|
|
802
|
+
const changed = adjustablePeers.length === 0 ? capacity : Math.min(capacity, Math.max(0, Number.isFinite(requestedValue) ? requestedValue : values[changedKey]));
|
|
803
|
+
const remainder = Math.max(0, capacity - changed);
|
|
804
|
+
const peerTotal = adjustablePeers.reduce((sum, key) => sum + Math.max(0, values[key]), 0);
|
|
805
|
+
const result = {
|
|
806
|
+
...values,
|
|
807
|
+
[changedKey]: round(changed)
|
|
808
|
+
};
|
|
809
|
+
adjustablePeers.forEach((key, index) => {
|
|
810
|
+
if (index === adjustablePeers.length - 1) {
|
|
811
|
+
const assigned = keys.reduce((sum, item) => item === key ? sum : sum + result[item], 0);
|
|
812
|
+
result[key] = round(Math.max(0, 1 - assigned));
|
|
813
|
+
} else result[key] = round(remainder * (peerTotal === 0 ? 1 / adjustablePeers.length : Math.max(0, values[key]) / peerTotal));
|
|
814
|
+
});
|
|
815
|
+
return result;
|
|
816
|
+
}
|
|
817
|
+
function percent(value) {
|
|
818
|
+
const amount = Math.round(value * 1e3) / 10;
|
|
819
|
+
return `${Number.isInteger(amount) ? amount.toFixed(0) : amount.toFixed(1)}%`;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
//#endregion
|
|
823
|
+
//#region src/client/weight-group.ts
|
|
824
|
+
const cardStyle = {
|
|
825
|
+
display: "grid",
|
|
826
|
+
gap: 14,
|
|
827
|
+
padding: 16,
|
|
828
|
+
borderRadius: 12,
|
|
829
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))",
|
|
830
|
+
background: "var(--dsw-alias-bg-layer-3, rgba(255,255,255,.025))"
|
|
831
|
+
};
|
|
832
|
+
function WeightGroup(props) {
|
|
833
|
+
const [locked, setLocked] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
834
|
+
const unlockedCount = props.items.reduce((count, item) => count + (locked.has(item.key) ? 0 : 1), 0);
|
|
835
|
+
const dirty = props.items.some((item) => Math.abs(props.values[item.key] - props.defaults[item.key]) > 1e-6);
|
|
836
|
+
const total = (0, react.useMemo)(() => props.items.reduce((sum, item) => sum + props.values[item.key], 0), [props.items, props.values]);
|
|
837
|
+
const update = (key, percentage) => {
|
|
838
|
+
props.onChange(rebalanceWeights(props.values, key, percentage / 100, locked));
|
|
839
|
+
};
|
|
840
|
+
const toggleLock = (key) => {
|
|
841
|
+
setLocked((current) => {
|
|
842
|
+
const next = new Set(current);
|
|
843
|
+
if (next.has(key)) next.delete(key);
|
|
844
|
+
else next.add(key);
|
|
845
|
+
return next;
|
|
846
|
+
});
|
|
847
|
+
};
|
|
848
|
+
return (0, react.createElement)("section", { style: cardStyle }, (0, react.createElement)("div", { style: {
|
|
849
|
+
display: "flex",
|
|
850
|
+
alignItems: "start",
|
|
851
|
+
justifyContent: "space-between",
|
|
852
|
+
gap: 16
|
|
853
|
+
} }, (0, react.createElement)("div", null, (0, react.createElement)("h4", { style: {
|
|
854
|
+
margin: 0,
|
|
855
|
+
fontSize: 15,
|
|
856
|
+
fontWeight: 650
|
|
857
|
+
} }, props.title), (0, react.createElement)("p", { style: {
|
|
858
|
+
margin: "5px 0 0",
|
|
859
|
+
color: "var(--dsw-alias-label-tertiary, #a1a1aa)",
|
|
860
|
+
fontSize: 12,
|
|
861
|
+
lineHeight: 1.55
|
|
862
|
+
} }, props.scope)), (0, react.createElement)("span", { style: {
|
|
863
|
+
flex: "0 0 auto",
|
|
864
|
+
padding: "3px 8px",
|
|
865
|
+
borderRadius: 999,
|
|
866
|
+
fontSize: 11,
|
|
867
|
+
color: "#86efac",
|
|
868
|
+
background: "rgba(34,197,94,.12)"
|
|
869
|
+
} }, `${percent(total)} ✓`)), (0, react.createElement)("div", {
|
|
870
|
+
"aria-hidden": true,
|
|
871
|
+
style: {
|
|
872
|
+
display: "flex",
|
|
873
|
+
height: 8,
|
|
874
|
+
overflow: "hidden",
|
|
875
|
+
borderRadius: 999,
|
|
876
|
+
background: "rgba(255,255,255,.06)"
|
|
877
|
+
}
|
|
878
|
+
}, ...props.items.map((item) => (0, react.createElement)("span", {
|
|
879
|
+
key: item.key,
|
|
880
|
+
style: {
|
|
881
|
+
width: `${props.values[item.key] * 100}%`,
|
|
882
|
+
background: item.color,
|
|
883
|
+
transition: "width .12s ease"
|
|
884
|
+
}
|
|
885
|
+
}))), (0, react.createElement)("div", { style: {
|
|
886
|
+
display: "grid",
|
|
887
|
+
gap: 12
|
|
888
|
+
} }, ...props.items.map((item) => {
|
|
889
|
+
const isLocked = locked.has(item.key);
|
|
890
|
+
return (0, react.createElement)("div", {
|
|
891
|
+
key: item.key,
|
|
892
|
+
style: {
|
|
893
|
+
display: "grid",
|
|
894
|
+
gridTemplateColumns: "minmax(84px, .7fr) minmax(150px, 2fr) 70px 34px",
|
|
895
|
+
gap: 10,
|
|
896
|
+
alignItems: "center"
|
|
897
|
+
}
|
|
898
|
+
}, (0, react.createElement)("span", { style: {
|
|
899
|
+
display: "flex",
|
|
900
|
+
alignItems: "center",
|
|
901
|
+
gap: 8,
|
|
902
|
+
fontSize: 13
|
|
903
|
+
} }, (0, react.createElement)("i", {
|
|
904
|
+
"aria-hidden": true,
|
|
905
|
+
style: {
|
|
906
|
+
width: 8,
|
|
907
|
+
height: 8,
|
|
908
|
+
borderRadius: 2,
|
|
909
|
+
background: item.color
|
|
910
|
+
}
|
|
911
|
+
}), item.label), (0, react.createElement)("input", {
|
|
912
|
+
type: "range",
|
|
913
|
+
min: 0,
|
|
914
|
+
max: 100,
|
|
915
|
+
step: 1,
|
|
916
|
+
value: Math.round(props.values[item.key] * 100),
|
|
917
|
+
disabled: !props.writable || isLocked,
|
|
918
|
+
"aria-label": item.label,
|
|
919
|
+
style: {
|
|
920
|
+
width: "100%",
|
|
921
|
+
accentColor: item.color
|
|
922
|
+
},
|
|
923
|
+
onChange: (event) => update(item.key, event.currentTarget.valueAsNumber)
|
|
924
|
+
}), (0, react.createElement)("div", { style: {
|
|
925
|
+
display: "flex",
|
|
926
|
+
alignItems: "center",
|
|
927
|
+
gap: 3
|
|
928
|
+
} }, (0, react.createElement)("input", {
|
|
929
|
+
type: "number",
|
|
930
|
+
min: 0,
|
|
931
|
+
max: 100,
|
|
932
|
+
step: 1,
|
|
933
|
+
value: Math.round(props.values[item.key] * 1e3) / 10,
|
|
934
|
+
disabled: !props.writable || isLocked,
|
|
935
|
+
"aria-label": `${item.label} %`,
|
|
936
|
+
style: {
|
|
937
|
+
width: 50,
|
|
938
|
+
padding: "5px 6px",
|
|
939
|
+
borderRadius: 6,
|
|
940
|
+
textAlign: "right"
|
|
941
|
+
},
|
|
942
|
+
onChange: (event) => update(item.key, event.currentTarget.valueAsNumber)
|
|
943
|
+
}), (0, react.createElement)("span", { style: {
|
|
944
|
+
color: "#a1a1aa",
|
|
945
|
+
fontSize: 12
|
|
946
|
+
} }, "%")), (0, react.createElement)("button", {
|
|
947
|
+
type: "button",
|
|
948
|
+
disabled: !props.writable || !isLocked && unlockedCount <= 1,
|
|
949
|
+
"aria-label": props.t(isLocked ? "settings.action.unlock" : "settings.action.lock", { name: item.label }),
|
|
950
|
+
title: props.t(isLocked ? "settings.action.unlock" : "settings.action.lock", { name: item.label }),
|
|
951
|
+
onClick: () => toggleLock(item.key),
|
|
952
|
+
style: {
|
|
953
|
+
width: 30,
|
|
954
|
+
height: 30,
|
|
955
|
+
padding: 0,
|
|
956
|
+
borderRadius: 7,
|
|
957
|
+
border: "1px solid rgba(255,255,255,.1)",
|
|
958
|
+
background: isLocked ? "rgba(59,130,246,.16)" : "transparent",
|
|
959
|
+
color: isLocked ? "#93c5fd" : "#a1a1aa",
|
|
960
|
+
cursor: "pointer"
|
|
961
|
+
}
|
|
962
|
+
}, isLocked ? "🔒" : "🔓"));
|
|
963
|
+
})), (0, react.createElement)("div", { style: {
|
|
964
|
+
display: "flex",
|
|
965
|
+
justifyContent: "flex-end",
|
|
966
|
+
gap: 8
|
|
967
|
+
} }, (0, react.createElement)("button", {
|
|
968
|
+
type: "button",
|
|
969
|
+
disabled: !props.writable || !dirty,
|
|
970
|
+
onClick: () => props.onChange({ ...props.defaults }),
|
|
971
|
+
style: { padding: "6px 10px" }
|
|
972
|
+
}, props.resetLabel), (0, react.createElement)("button", {
|
|
973
|
+
type: "button",
|
|
974
|
+
disabled: !props.writable,
|
|
975
|
+
onClick: props.onApply,
|
|
976
|
+
style: { padding: "6px 12px" }
|
|
977
|
+
}, props.applyLabel)));
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
//#endregion
|
|
981
|
+
//#region src/client/settings-card.ts
|
|
982
|
+
function ConnectionStatus({ t }) {
|
|
983
|
+
const [state, setState] = (0, react.useState)({ status: "loading" });
|
|
984
|
+
(0, react.useEffect)(() => {
|
|
985
|
+
const controller = new AbortController();
|
|
986
|
+
fetch("/api/openrouter-providers/connection", { signal: controller.signal }).then(async (response) => {
|
|
987
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
988
|
+
setState({
|
|
989
|
+
status: "ready",
|
|
990
|
+
value: await response.json()
|
|
991
|
+
});
|
|
992
|
+
}).catch((error) => {
|
|
993
|
+
if (!controller.signal.aborted) setState({ status: "error" });
|
|
994
|
+
});
|
|
995
|
+
return () => controller.abort();
|
|
996
|
+
}, []);
|
|
997
|
+
const configured = state.value?.configured === true;
|
|
998
|
+
const label = state.status === "loading" ? t("settings.connection.loading") : state.status === "error" ? t("settings.connection.error") : t(configured ? "settings.connection.configured" : "settings.connection.missing");
|
|
999
|
+
const color = configured ? "#22c55e" : state.status === "error" ? "#f59e0b" : "#f97316";
|
|
1000
|
+
return (0, react.createElement)("section", { style: {
|
|
1001
|
+
display: "grid",
|
|
1002
|
+
gap: 8,
|
|
1003
|
+
padding: 14,
|
|
1004
|
+
borderRadius: 10,
|
|
1005
|
+
border: `1px solid color-mix(in srgb, ${color} 35%, transparent)`,
|
|
1006
|
+
background: `color-mix(in srgb, ${color} 8%, transparent)`
|
|
1007
|
+
} }, (0, react.createElement)("div", { style: {
|
|
1008
|
+
display: "flex",
|
|
1009
|
+
alignItems: "center",
|
|
1010
|
+
gap: 9
|
|
1011
|
+
} }, (0, react.createElement)("span", {
|
|
1012
|
+
"aria-hidden": true,
|
|
1013
|
+
style: {
|
|
1014
|
+
width: 8,
|
|
1015
|
+
height: 8,
|
|
1016
|
+
borderRadius: 99,
|
|
1017
|
+
background: color,
|
|
1018
|
+
boxShadow: `0 0 0 4px color-mix(in srgb, ${color} 15%, transparent)`
|
|
1019
|
+
}
|
|
1020
|
+
}), (0, react.createElement)("strong", { style: { fontSize: 13 } }, t("settings.connection.title")), (0, react.createElement)("span", { style: {
|
|
1021
|
+
color: "#a1a1aa",
|
|
1022
|
+
fontSize: 12
|
|
1023
|
+
} }, label)), state.status === "ready" && !configured ? (0, react.createElement)("p", { style: {
|
|
1024
|
+
margin: 0,
|
|
1025
|
+
color: "#a1a1aa",
|
|
1026
|
+
fontSize: 12,
|
|
1027
|
+
lineHeight: 1.55
|
|
1028
|
+
} }, t("settings.connection.guide")) : null, state.value?.routes.length ? (0, react.createElement)("div", { style: {
|
|
1029
|
+
display: "flex",
|
|
1030
|
+
flexWrap: "wrap",
|
|
1031
|
+
gap: 6
|
|
1032
|
+
} }, ...state.value.routes.map((route) => (0, react.createElement)("code", {
|
|
1033
|
+
key: route.provider,
|
|
1034
|
+
style: {
|
|
1035
|
+
padding: "3px 7px",
|
|
1036
|
+
borderRadius: 6,
|
|
1037
|
+
fontSize: 11,
|
|
1038
|
+
background: "rgba(127,127,127,.1)",
|
|
1039
|
+
color: route.configured ? "#86efac" : "#fdba74"
|
|
1040
|
+
}
|
|
1041
|
+
}, `${route.provider} · ${route.credentialRef}`))) : null);
|
|
1042
|
+
}
|
|
1043
|
+
async function setConfigField(scope, config, field, value) {
|
|
1044
|
+
const [group, nested] = field.split(".");
|
|
1045
|
+
if (nested === void 0) return scope.set(group, value);
|
|
1046
|
+
if (group === "weights") return scope.set(group, {
|
|
1047
|
+
...config.weights,
|
|
1048
|
+
[nested]: value
|
|
1049
|
+
});
|
|
1050
|
+
if (group === "priceBlend") return scope.set(group, {
|
|
1051
|
+
...config.priceBlend,
|
|
1052
|
+
[nested]: value
|
|
1053
|
+
});
|
|
1054
|
+
throw new Error(`unknown config field: ${field}`);
|
|
1055
|
+
}
|
|
1056
|
+
async function setConfigGroup(scope, group, value) {
|
|
1057
|
+
const sum = Object.values(value).reduce((total, item) => total + item, 0);
|
|
1058
|
+
if (Object.values(value).some((item) => !Number.isFinite(item) || item < 0) || Math.abs(sum - 1) > .001) throw new Error(`${group} must sum to 1`);
|
|
1059
|
+
await scope.set(group, value);
|
|
1060
|
+
}
|
|
1061
|
+
function settingsDisclosureLabel(open, locale = "zh-CN") {
|
|
1062
|
+
const i18n = createI18n(locale);
|
|
1063
|
+
return `${open ? i18n.t("settings.collapse") : i18n.t("settings.expand")}: ${i18n.t("settings.title")}`;
|
|
1064
|
+
}
|
|
1065
|
+
const scalarRows = [
|
|
1066
|
+
{
|
|
1067
|
+
field: "minContextTokens",
|
|
1068
|
+
labelKey: "settings.row.minContextTokens",
|
|
1069
|
+
min: 1,
|
|
1070
|
+
step: 1e3
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
field: "recommendedCount",
|
|
1074
|
+
labelKey: "settings.row.recommendedCount",
|
|
1075
|
+
min: 1,
|
|
1076
|
+
max: 10,
|
|
1077
|
+
step: 1
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
field: "cacheTtlMs",
|
|
1081
|
+
labelKey: "settings.row.cacheTtlMs",
|
|
1082
|
+
min: 0,
|
|
1083
|
+
step: 1e3
|
|
1084
|
+
},
|
|
1085
|
+
{
|
|
1086
|
+
field: "uptimePenaltyThreshold",
|
|
1087
|
+
labelKey: "settings.row.uptimePenaltyThreshold",
|
|
1088
|
+
min: 0,
|
|
1089
|
+
max: 100,
|
|
1090
|
+
step: .1
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
field: "uptimePenaltyFactor",
|
|
1094
|
+
labelKey: "settings.row.uptimePenaltyFactor",
|
|
1095
|
+
min: 0,
|
|
1096
|
+
max: 1,
|
|
1097
|
+
step: .05
|
|
1098
|
+
}
|
|
1099
|
+
];
|
|
1100
|
+
function strategyWeightRows(locale = "zh-CN") {
|
|
1101
|
+
const i18n = createI18n(locale);
|
|
1102
|
+
const dimensions = [
|
|
1103
|
+
["quantization", i18n.t("settings.dimension.quantization")],
|
|
1104
|
+
["speed", i18n.t("settings.dimension.speed")],
|
|
1105
|
+
["price", i18n.t("settings.dimension.price")],
|
|
1106
|
+
["context", i18n.t("settings.dimension.context")]
|
|
1107
|
+
];
|
|
1108
|
+
return [
|
|
1109
|
+
"price",
|
|
1110
|
+
"speed",
|
|
1111
|
+
"context"
|
|
1112
|
+
].map((strategy) => ({
|
|
1113
|
+
name: i18n.t(`panel.strategy.${strategy}`),
|
|
1114
|
+
values: dimensions.map(([key, label]) => `${label} ${STRATEGY_WEIGHTS[strategy][key] * 100}%`).join(" · ")
|
|
1115
|
+
}));
|
|
1116
|
+
}
|
|
1117
|
+
function valueAt(config, field) {
|
|
1118
|
+
const [group, nested] = field.split(".");
|
|
1119
|
+
if (nested === void 0) return config[group];
|
|
1120
|
+
return config[group][nested];
|
|
1121
|
+
}
|
|
1122
|
+
function OpenRouterProvidersSettingsCard({ scope }) {
|
|
1123
|
+
const i18n = useUiI18n();
|
|
1124
|
+
const snapshot = (0, react.useSyncExternalStore)(scope.subscribe.bind(scope), scope.getSnapshot.bind(scope), scope.getSnapshot.bind(scope));
|
|
1125
|
+
const config = snapshot.value ?? DEFAULT_CONFIG;
|
|
1126
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1127
|
+
const [draftWeights, setDraftWeights] = (0, react.useState)({ ...config.weights });
|
|
1128
|
+
const [draftPriceBlend, setDraftPriceBlend] = (0, react.useState)({ ...config.priceBlend });
|
|
1129
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1130
|
+
(0, react.useEffect)(() => {
|
|
1131
|
+
setDraftWeights({ ...config.weights });
|
|
1132
|
+
setDraftPriceBlend({ ...config.priceBlend });
|
|
1133
|
+
}, [config]);
|
|
1134
|
+
const displayed = {
|
|
1135
|
+
...config,
|
|
1136
|
+
weights: draftWeights,
|
|
1137
|
+
priceBlend: draftPriceBlend
|
|
1138
|
+
};
|
|
1139
|
+
const saveGroup = (group, value) => {
|
|
1140
|
+
setConfigGroup(scope, group, value).then(() => setError(null), (failure) => setError(failure instanceof Error ? failure.message : String(failure)));
|
|
1141
|
+
};
|
|
1142
|
+
const weightItems = [
|
|
1143
|
+
{
|
|
1144
|
+
key: "quantization",
|
|
1145
|
+
label: i18n.t("settings.dimension.quantization"),
|
|
1146
|
+
color: "#60a5fa"
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
key: "speed",
|
|
1150
|
+
label: i18n.t("settings.dimension.speed"),
|
|
1151
|
+
color: "#34d399"
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
key: "price",
|
|
1155
|
+
label: i18n.t("settings.dimension.price"),
|
|
1156
|
+
color: "#fbbf24"
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
key: "context",
|
|
1160
|
+
label: i18n.t("settings.dimension.context"),
|
|
1161
|
+
color: "#a78bfa"
|
|
1162
|
+
}
|
|
1163
|
+
];
|
|
1164
|
+
const priceItems = [
|
|
1165
|
+
{
|
|
1166
|
+
key: "input",
|
|
1167
|
+
label: i18n.t("settings.dimension.input"),
|
|
1168
|
+
color: "#60a5fa"
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
key: "output",
|
|
1172
|
+
label: i18n.t("settings.dimension.output"),
|
|
1173
|
+
color: "#f59e0b"
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
key: "cache",
|
|
1177
|
+
label: i18n.t("settings.dimension.cache"),
|
|
1178
|
+
color: "#34d399"
|
|
1179
|
+
}
|
|
1180
|
+
];
|
|
1181
|
+
const strategyRows = strategyWeightRows(i18n.locale);
|
|
1182
|
+
const body = (0, react.createElement)("div", { style: {
|
|
1183
|
+
display: "grid",
|
|
1184
|
+
gap: 14,
|
|
1185
|
+
margin: "0 16px",
|
|
1186
|
+
padding: "16px 0 12px",
|
|
1187
|
+
borderTop: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))"
|
|
1188
|
+
} }, (0, react.createElement)(ConnectionStatus, { t: i18n.t }), (0, react.createElement)(WeightGroup, {
|
|
1189
|
+
title: i18n.t("settings.group.weights.title"),
|
|
1190
|
+
scope: i18n.t("settings.group.weights.scope"),
|
|
1191
|
+
values: draftWeights,
|
|
1192
|
+
defaults: { ...DEFAULT_CONFIG.weights },
|
|
1193
|
+
items: weightItems,
|
|
1194
|
+
writable: snapshot.writable,
|
|
1195
|
+
applyLabel: i18n.t("settings.action.weights"),
|
|
1196
|
+
resetLabel: i18n.t("settings.action.reset"),
|
|
1197
|
+
t: i18n.t,
|
|
1198
|
+
onChange: setDraftWeights,
|
|
1199
|
+
onApply: () => saveGroup("weights", draftWeights)
|
|
1200
|
+
}), (0, react.createElement)(WeightGroup, {
|
|
1201
|
+
title: i18n.t("settings.group.priceBlend.title"),
|
|
1202
|
+
scope: i18n.t("settings.group.priceBlend.scope"),
|
|
1203
|
+
values: draftPriceBlend,
|
|
1204
|
+
defaults: { ...DEFAULT_CONFIG.priceBlend },
|
|
1205
|
+
items: priceItems,
|
|
1206
|
+
writable: snapshot.writable,
|
|
1207
|
+
applyLabel: i18n.t("settings.action.priceBlend"),
|
|
1208
|
+
resetLabel: i18n.t("settings.action.reset"),
|
|
1209
|
+
t: i18n.t,
|
|
1210
|
+
onChange: setDraftPriceBlend,
|
|
1211
|
+
onApply: () => saveGroup("priceBlend", draftPriceBlend)
|
|
1212
|
+
}), (0, react.createElement)("details", { style: {
|
|
1213
|
+
padding: "11px 14px",
|
|
1214
|
+
borderRadius: 10,
|
|
1215
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))"
|
|
1216
|
+
} }, (0, react.createElement)("summary", { style: {
|
|
1217
|
+
cursor: "pointer",
|
|
1218
|
+
fontSize: 13,
|
|
1219
|
+
fontWeight: 600
|
|
1220
|
+
} }, i18n.t("settings.group.strategies.title")), (0, react.createElement)("p", { style: {
|
|
1221
|
+
margin: "10px 0",
|
|
1222
|
+
color: "#a1a1aa",
|
|
1223
|
+
fontSize: 12
|
|
1224
|
+
} }, i18n.t("settings.group.strategies.scope")), ...strategyRows.map((row) => (0, react.createElement)("div", {
|
|
1225
|
+
key: row.name,
|
|
1226
|
+
style: {
|
|
1227
|
+
display: "grid",
|
|
1228
|
+
gridTemplateColumns: "110px 1fr",
|
|
1229
|
+
gap: 12,
|
|
1230
|
+
padding: "7px 0",
|
|
1231
|
+
borderTop: "1px solid rgba(255,255,255,.06)",
|
|
1232
|
+
fontSize: 12
|
|
1233
|
+
}
|
|
1234
|
+
}, (0, react.createElement)("strong", null, row.name), (0, react.createElement)("span", { style: { color: "#a1a1aa" } }, row.values)))), (0, react.createElement)("section", { style: {
|
|
1235
|
+
display: "grid",
|
|
1236
|
+
gap: 10,
|
|
1237
|
+
padding: 14,
|
|
1238
|
+
borderRadius: 10,
|
|
1239
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))"
|
|
1240
|
+
} }, (0, react.createElement)("h4", { style: {
|
|
1241
|
+
margin: "0 0 2px",
|
|
1242
|
+
fontSize: 14
|
|
1243
|
+
} }, i18n.t("settings.group.advanced.title")), ...scalarRows.map((row) => (0, react.createElement)("label", {
|
|
1244
|
+
key: row.field,
|
|
1245
|
+
style: {
|
|
1246
|
+
display: "grid",
|
|
1247
|
+
gridTemplateColumns: "minmax(180px, 1fr) 140px",
|
|
1248
|
+
alignItems: "center",
|
|
1249
|
+
gap: 12
|
|
1250
|
+
}
|
|
1251
|
+
}, (0, react.createElement)("span", { style: { fontSize: 13 } }, i18n.t(row.labelKey)), (0, react.createElement)("input", {
|
|
1252
|
+
type: "number",
|
|
1253
|
+
value: valueAt(displayed, row.field),
|
|
1254
|
+
min: row.min,
|
|
1255
|
+
max: row.max,
|
|
1256
|
+
step: row.step,
|
|
1257
|
+
disabled: !snapshot.writable,
|
|
1258
|
+
onChange: (event) => void setConfigField(scope, config, row.field, event.currentTarget.valueAsNumber)
|
|
1259
|
+
})))), error === null ? null : (0, react.createElement)("p", {
|
|
1260
|
+
role: "alert",
|
|
1261
|
+
style: {
|
|
1262
|
+
color: "#b42318",
|
|
1263
|
+
margin: 0
|
|
1264
|
+
}
|
|
1265
|
+
}, error));
|
|
1266
|
+
return (0, react.createElement)("li", { style: {
|
|
1267
|
+
listStyle: "none",
|
|
1268
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))",
|
|
1269
|
+
borderRadius: 12,
|
|
1270
|
+
background: open ? "var(--dsw-alias-bg-layer-2, rgba(255,255,255,.04))" : "var(--dsw-alias-bg-layer-3, rgba(255,255,255,.025))"
|
|
1271
|
+
} }, (0, react.createElement)("button", {
|
|
1272
|
+
type: "button",
|
|
1273
|
+
"aria-expanded": open,
|
|
1274
|
+
"aria-label": settingsDisclosureLabel(open, i18n.locale),
|
|
1275
|
+
onClick: () => setOpen((value) => !value),
|
|
1276
|
+
style: {
|
|
1277
|
+
width: "100%",
|
|
1278
|
+
appearance: "none",
|
|
1279
|
+
border: 0,
|
|
1280
|
+
background: "none",
|
|
1281
|
+
color: "inherit",
|
|
1282
|
+
textAlign: "left",
|
|
1283
|
+
cursor: "pointer",
|
|
1284
|
+
display: "flex",
|
|
1285
|
+
alignItems: "center",
|
|
1286
|
+
gap: 12,
|
|
1287
|
+
padding: "14px 16px",
|
|
1288
|
+
borderRadius: 12,
|
|
1289
|
+
font: "inherit"
|
|
1290
|
+
}
|
|
1291
|
+
}, (0, react.createElement)("span", { style: {
|
|
1292
|
+
flex: 1,
|
|
1293
|
+
minWidth: 0,
|
|
1294
|
+
display: "flex",
|
|
1295
|
+
flexDirection: "column",
|
|
1296
|
+
gap: 4
|
|
1297
|
+
} }, (0, react.createElement)("span", { style: {
|
|
1298
|
+
fontSize: 15,
|
|
1299
|
+
fontWeight: 600,
|
|
1300
|
+
lineHeight: 1.4
|
|
1301
|
+
} }, i18n.t("settings.title")), (0, react.createElement)("span", { style: {
|
|
1302
|
+
fontSize: 13,
|
|
1303
|
+
lineHeight: 1.5,
|
|
1304
|
+
color: "var(--dsw-alias-label-tertiary, #a1a1aa)"
|
|
1305
|
+
} }, i18n.t("settings.summary"))), (0, react.createElement)("span", {
|
|
1306
|
+
"aria-hidden": true,
|
|
1307
|
+
style: {
|
|
1308
|
+
color: "var(--dsw-alias-label-tertiary, #a1a1aa)",
|
|
1309
|
+
transform: open ? "rotate(180deg)" : "none",
|
|
1310
|
+
transition: "transform .16s",
|
|
1311
|
+
fontSize: 18
|
|
1312
|
+
}
|
|
1313
|
+
}, "⌄")), open ? body : null);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
//#endregion
|
|
1317
|
+
//#region src/client/index.ts
|
|
1318
|
+
const inject = [
|
|
1319
|
+
"sessions",
|
|
1320
|
+
"slots",
|
|
1321
|
+
"settingsScope",
|
|
1322
|
+
"locale"
|
|
1323
|
+
];
|
|
1324
|
+
function apply(ctx) {
|
|
1325
|
+
const client = ctx;
|
|
1326
|
+
attachLocale(client.locale);
|
|
1327
|
+
ctx.effect(() => {
|
|
1328
|
+
const offZh = client.locale.register(LOCALE_NS, "zh", zh);
|
|
1329
|
+
const offEn = client.locale.register(LOCALE_NS, "en", en);
|
|
1330
|
+
return () => {
|
|
1331
|
+
offZh();
|
|
1332
|
+
offEn();
|
|
1333
|
+
attachLocale(void 0);
|
|
1334
|
+
};
|
|
1335
|
+
}, "openrouter-providers: locale dictionaries");
|
|
1336
|
+
const panelStore = createProviderPanelStore();
|
|
1337
|
+
const BoundTrigger = (props) => (0, react.createElement)(ProviderSidebarTrigger, {
|
|
1338
|
+
...props,
|
|
1339
|
+
store: panelStore
|
|
1340
|
+
});
|
|
1341
|
+
const BoundOverlay = (props) => (0, react.createElement)(ProviderOverlay, {
|
|
1342
|
+
...props,
|
|
1343
|
+
store: panelStore
|
|
1344
|
+
});
|
|
1345
|
+
client.slots.inject("sidebar.footer.action", () => client.slots.register({
|
|
1346
|
+
name: "sidebar.footer.action",
|
|
1347
|
+
id: "openrouter-providers-trigger",
|
|
1348
|
+
order: 20
|
|
1349
|
+
}, BoundTrigger));
|
|
1350
|
+
client.slots.inject("shell.overlay", () => client.slots.register({
|
|
1351
|
+
name: "shell.overlay",
|
|
1352
|
+
id: "openrouter-providers-overlay",
|
|
1353
|
+
order: 20
|
|
1354
|
+
}, BoundOverlay));
|
|
1355
|
+
ctx.effect(() => () => panelStore.dispose(), "openrouter-providers: provider panel store");
|
|
1356
|
+
const scope = client.settingsScope.bind({ namespace: "openrouter-providers" });
|
|
1357
|
+
client.slots.inject("settings.plugin.item", () => client.slots.register({
|
|
1358
|
+
name: "settings.plugin.item",
|
|
1359
|
+
key: "openrouter-providers",
|
|
1360
|
+
locale: "openrouter-providers",
|
|
1361
|
+
inject: () => ({ scope })
|
|
1362
|
+
}, OpenRouterProvidersSettingsCard));
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
//#endregion
|
|
1366
|
+
exports.apply = apply;
|
|
1367
|
+
exports.inject = inject;
|
|
1368
|
+
return module.exports; } });
|
|
1369
|
+
//# sourceMappingURL=client.js.map
|