@raingor/pi-web-switch 0.3.2 → 0.4.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.
@@ -5,6 +5,7 @@ const zhTW: Record<string, string> = {
5
5
  "nav.models": "模型",
6
6
  "nav.providers": "提供商",
7
7
  "nav.providers_models": "提供商與模型",
8
+ "nav.subagents": "子代理",
8
9
  "providers_models.subtitle": "管理模型供應商與模型,配置後寫入 ~/.pi/agent/。",
9
10
  "providers_models.custom_providers": "自定義供應商",
10
11
  "providers_models.model_list": "模型列表",
@@ -21,8 +22,18 @@ const zhTW: Record<string, string> = {
21
22
  "providers_models.test_ok": "連線正常 ({0} ms)",
22
23
  "providers_models.test_fail": "連線失敗:{0}",
23
24
  "providers_models.invalid_url": "請輸入合法的 http(s) 位址",
25
+ "providers_models.baseurl_override": "此位址將覆蓋內建預設位址(寫入 models.json)",
24
26
  "providers_models.enable_all": "全部啟用",
27
+ "providers_models.sort_by": "排序",
28
+ "providers_models.sort_default": "預設",
29
+ "providers_models.sort_family": "依廠商",
30
+ "providers_models.sort_price_asc": "價格升序",
31
+ "providers_models.sort_price_desc": "價格降序",
25
32
  "providers_models.disable_all": "全部停用",
33
+ "providers_models.enabled_models_title": "已啟用模型",
34
+ "providers_models.no_enabled_models": "尚無啟用的模型。在供應商的模型清單中開啟模型後,會顯示在這裡。",
35
+ "providers_models.api_key_env": "將以環境變數 {0} 的值作為密鑰(不會明碼寫入)",
36
+ "providers_models.test_all": "測試全部",
26
37
  "providers_models.id_exists": "ID “{0}” 已存在,請換一個名稱",
27
38
  "providers_models.id_preview": "將以 ID “{0}” 儲存",
28
39
  "providers_models.import": "匯入",
@@ -31,13 +42,61 @@ const zhTW: Record<string, string> = {
31
42
  "providers_models.import_placeholder": "tokenrouter baseurl:https://api.example.com/v1 key:sk-xxxx\nmodelid:vendor/model-name",
32
43
  "providers_models.import_empty": "未能從文字中識別出有效內容",
33
44
  "providers_models.import_merge": "供應商 “{0}” 已存在,將合併模型至該供應商",
34
- "providers_models.import_builtin_conflict": "與內建供應商 “{0}” 衝突,請修改名稱",
45
+ "providers_models.import_builtin_conflict": "與內建供應商 “{0}” 衝突,請修改名稱",
46
+ "providers_models.fetch_models": "取得模型列表",
47
+ "providers_models.fetch_no_endpoint": "該供應商未提供可用的 /models 端點",
48
+ "providers_models.fetching": "正在取得...",
49
+ "providers_models.fetch_error": "取得失敗:{0}",
50
+ "providers_models.fetch_empty": "該供應商未回傳可用模型",
51
+ "providers_models.modality_vision": "圖文",
52
+ "providers_models.modality_text": "純文字",
53
+ "providers_models.id_invalid": "無法從名稱或地址產生有效 ID,請先填寫基礎地址或使用英文名稱",
54
+ "providers_models.fetch_title": "匯入模型",
55
+ "providers_models.fetch_desc": "從 {0} 的 /models 端點取得可用模型,選擇要匯入的模型。",
56
+ "providers_models.select_all": "全選",
57
+ "providers_models.deselect_all": "取消全選",
58
+ "providers_models.selected_count": "已選擇 {0}/{1} 個模型",
59
+ "providers_models.confirm_import": "匯入選中模型",
60
+
61
+ "providers_models.import_done": "已匯入 {0} 個模型",
62
+
35
63
  "nav.sessions": "會話",
36
64
  "nav.memory": "記憶",
37
65
  "nav.settings": "設定",
38
66
  "app.subtitle": "設定管理器",
39
67
  "app.version": "pi-switch v0.1.0",
40
68
 
69
+ // Subagents
70
+ "subagents.summary": "{0} 個代理 · {1} 個流水線",
71
+ "subagents.tab_agents": "代理",
72
+ "subagents.tab_chains": "流水線",
73
+ "subagents.tab_history": "執行記錄",
74
+ "subagents.no_agents": "暫無代理",
75
+ "subagents.no_agents_desc": "在 ~/.pi/agent/agents/ 目錄下建立 .md 檔案即可新增子代理。",
76
+ "subagents.no_chains": "暫無流水線",
77
+ "subagents.no_chains_desc": "在 ~/.pi/agent/chains/ 目錄下建立 .chain.md 檔案即可新增流水線。",
78
+ "subagents.no_history": "暫無執行記錄",
79
+ "subagents.no_history_desc": "執行子代理後,執行記錄將顯示在這裡。",
80
+ "subagents.model": "模型",
81
+ "subagents.thinking": "思考級別",
82
+ "subagents.tools": "工具",
83
+ "subagents.system_prompt_mode": "系統提示模式",
84
+ "subagents.input": "輸入類型",
85
+ "subagents.system_prompt": "系統提示詞",
86
+ "subagents.empty_prompt": "(空)",
87
+ "subagents.steps_count": "步驟",
88
+ "subagents.pipeline": "執行流程",
89
+ "subagents.phase": "階段",
90
+ "subagents.label": "標籤",
91
+ "subagents.output": "輸出",
92
+ "subagents.agent": "代理",
93
+ "subagents.time": "時間",
94
+ "subagents.status": "狀態",
95
+ "subagents.duration": "耗時",
96
+ "subagents.status_ok": "成功",
97
+ "subagents.status_error": "失敗",
98
+ "subagents.showing_recent": "顯示最近 100 條記錄",
99
+
41
100
  "dashboard.title": "使用統計",
42
101
  "dashboard.requests_count": "{0} 次請求 · 總成本 {1}",
43
102
  "dashboard.range.today": "當天",
@@ -86,6 +145,8 @@ const zhTW: Record<string, string> = {
86
145
 
87
146
  "models.search_placeholder": "搜尋模型...",
88
147
  "models.add_model": "新增模型",
148
+
149
+ "models.test_model": "測試模型",
89
150
  "models.edit_model": "編輯模型",
90
151
  "models.delete_model": "刪除模型",
91
152
  "models.no_models": "未找到模型",
@@ -105,9 +166,35 @@ const zhTW: Record<string, string> = {
105
166
  "models.save": "儲存",
106
167
  "models.cancel": "取消",
107
168
  "models.delete_confirm": "確定要刪除此模型嗎?",
169
+ "models.pick_preset": "從目錄選擇",
170
+ "models.pick_preset_placeholder": "搜尋常用模型(Claude、GPT、Qwen、GLM、DeepSeek…)",
171
+ "models.detected_catalog": "已匹配:{0}",
172
+ "models.detected_heuristic": "已根據 ID 推斷參數",
173
+ "models.manual_fill": "手動填寫",
174
+ "models.preset_claude": "Claude 風格",
175
+ "models.preset_gpt": "GPT 風格",
176
+ "models.preset_reasoning": "推理模型",
177
+ "models.preset_small_local": "本地小模型",
178
+ "models.audio_input": "音訊輸入",
179
+ "models.quick_add": "快速新增",
180
+ "models.quick_add_placeholder": "輸入模型 ID 按 Enter 即加(自動識別參數)",
181
+ "models.copy_params": "複製參數",
182
+ "models.params_copied": "參數已複製",
183
+ "models.catalog_hint": "找不到你的模型?直接在下方輸入 ID,會自動推斷常見參數。",
184
+
185
+ // Compat
186
+ "compat.title": "API 相容性",
187
+ "compat.desc": "當 OpenAI 相容 API 不支援某些功能時,覆蓋預設行為。",
188
+ "compat.supports_developer_role": "支援 developer 角色",
189
+ "compat.supports_developer_role_desc": "使用 'developer' 角色發送系統提示(推理模型)。如果不支援,請關閉以改用 'system' 角色。",
190
+ "compat.supports_reasoning_effort": "支援 reasoning_effort",
191
+ "compat.supports_reasoning_effort_desc": "發送 reasoning_effort 參數。如果 API 不支援,請關閉。",
108
192
 
109
193
  "providers.add_provider": "新增提供商",
110
194
  "providers.delete_provider": "刪除提供商",
195
+ "providers_models.expand": "展開(還有 {0} 個)",
196
+ "providers_models.collapse": "收起",
197
+ "providers.duplicate_provider": "複製提供商",
111
198
  "providers.custom": "自訂",
112
199
  "providers.builtin": "內建",
113
200
  "providers.configured": "已設定",
@@ -191,6 +278,9 @@ const zhTW: Record<string, string> = {
191
278
  "settings.theme_desc": "選擇介面配色方案",
192
279
  "settings.font_size": "字體大小",
193
280
  "settings.font_size_desc": "調整介面整體字體大小",
281
+ "settings.ui_zoom": "介面縮放",
282
+ "settings.ui_zoom_desc": "依百分比縮放整個介面(50%–200%)",
283
+ "settings.ui_zoom_reset": "重設",
194
284
  "settings.enabled_models_desc": "點擊標籤以啟用或停用模型",
195
285
  "settings.updates_title": "更新檢測",
196
286
  "settings.updates_desc": "檢查 pi 本體與擴充在 npm 上是否有新版本。",
package/src/main.tsx CHANGED
@@ -108,6 +108,15 @@ function applySavedFontSize() {
108
108
  }
109
109
  }
110
110
 
111
+ // ─── UI zoom: apply saved percentage before first paint ─
112
+ applySavedZoom();
113
+ function applySavedZoom() {
114
+ const saved = Number(localStorage.getItem("pi-ui-zoom"));
115
+ if (saved >= 50 && saved <= 200) {
116
+ document.documentElement.style.zoom = `${saved}%`;
117
+ }
118
+ }
119
+
111
120
  createRoot(document.getElementById("root")!).render(
112
121
  <StrictMode>
113
122
  <Root />
@@ -37,7 +37,7 @@ function getCustomProviders(modelsJson: PiModelsJson | null): Provider[] {
37
37
  if (!modelsJson) return [];
38
38
  return Object.entries(modelsJson.providers).map(([id, cfg]) => ({
39
39
  id,
40
- name: id.charAt(0).toUpperCase() + id.slice(1),
40
+ name: cfg.name ?? (id.charAt(0).toUpperCase() + id.slice(1)),
41
41
  type: "custom" as const,
42
42
  baseUrl: cfg.baseUrl,
43
43
  api: cfg.api,
@@ -51,14 +51,53 @@ function getCustomProviders(modelsJson: PiModelsJson | null): Provider[] {
51
51
  }));
52
52
  }
53
53
 
54
- function mergeProviders(auth: PiAuth, customModels: PiModelsJson | null): Provider[] {
55
- const builtins = BUILTIN_PROVIDERS.map((p) => ({
56
- ...p,
57
- hasAuth: p.hasAuth || !!auth[p.id],
58
- authMethod: auth[p.id] ? "file" : p.authMethod,
59
- }));
54
+ function mergeProviders(
55
+ builtinProviders: Provider[],
56
+ auth: PiAuth,
57
+ customModels: PiModelsJson | null
58
+ ): Provider[] {
60
59
  const customs = getCustomProviders(customModels);
61
- return [...builtins, ...customs];
60
+ // models.json entries whose id matches a builtin provider are overrides
61
+ // (e.g. models imported onto a builtin) — merge them into the builtin
62
+ // instead of listing a duplicate custom provider.
63
+ const builtinIds = new Set(builtinProviders.map((p) => p.id));
64
+ // Customs with an apiKey are standalone custom providers (e.g. user-imported
65
+ // ones that happen to share an id with a builtin). They should not be merged
66
+ // into the builtin but instead shown as a custom provider.
67
+ const standaloneIds = new Set(
68
+ customs.filter((c) => builtinIds.has(c.id) && c.apiKey).map((c) => c.id)
69
+ );
70
+ const overrides = new Map(
71
+ customs.filter((c) => builtinIds.has(c.id) && !c.apiKey).map((c) => [c.id, c])
72
+ );
73
+ const builtins = builtinProviders
74
+ .map((p) => {
75
+ if (standaloneIds.has(p.id)) return null; // replaced by standalone custom
76
+ const override = overrides.get(p.id);
77
+ let models = p.models;
78
+ if (override) {
79
+ const byId = new Map(p.models.map((m) => [m.id, m]));
80
+ for (const m of override.models) byId.set(m.id, m);
81
+ models = [...byId.values()];
82
+ }
83
+ return {
84
+ ...p,
85
+ // A user-provided display name in models.json wins over the catalog name.
86
+ name: customModels?.providers?.[p.id]?.name ?? p.name,
87
+ // Apply baseUrl / api overrides so builtins can target a proxy/gateway
88
+ // while keeping their builtin model catalog.
89
+ baseUrl: override?.baseUrl ?? p.baseUrl,
90
+ api: override?.api ?? p.api,
91
+ headers: override?.headers ?? p.headers,
92
+ // hasAuth = a key is actually saved (auth.json or models.json override) —
93
+ // the static builtin flag only means the provider supports auth.
94
+ hasAuth: !!auth[p.id] || !!override?.apiKey,
95
+ authMethod: auth[p.id] ? "file" : p.authMethod,
96
+ models,
97
+ };
98
+ })
99
+ .filter(<T>(p: T): p is NonNullable<T> => p != null);
100
+ return [...builtins, ...customs.filter((c) => !builtinIds.has(c.id) || standaloneIds.has(c.id))];
62
101
  }
63
102
 
64
103
  // ─── State Types ─────────────────────────────────────────
@@ -99,6 +138,9 @@ interface ConfigState {
99
138
  auth: PiAuth | null;
100
139
  modelsJson: PiModelsJson | null;
101
140
 
141
+ // Builtin provider catalog (live from the pi install, static fallback)
142
+ builtinProviders: Provider[];
143
+
102
144
  // Derived
103
145
  allProviders: Provider[];
104
146
  allModels: (Model & { providerId: string; providerName: string })[];
@@ -149,6 +191,7 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
149
191
  settings: null,
150
192
  auth: null,
151
193
  modelsJson: null,
194
+ builtinProviders: BUILTIN_PROVIDERS,
152
195
  allProviders: [],
153
196
  allModels: [],
154
197
  usage: null,
@@ -161,14 +204,17 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
161
204
  init: async () => {
162
205
  set({ loading: true, error: null });
163
206
  try {
164
- const [settings, auth, modelsJson, usage] = await Promise.all([
207
+ const [settings, auth, modelsJson, usage, builtinsRes] = await Promise.all([
165
208
  apiGet<PiSettings>("/settings"),
166
209
  apiGet<PiAuth>("/auth"),
167
210
  apiGet<PiModelsJson>("/models"),
168
211
  apiGet<UsageData>("/usage"),
212
+ apiGet<Provider[]>("/builtin-providers").catch(() => null),
169
213
  ]);
170
214
 
171
- const allProviders = mergeProviders(auth ?? {}, modelsJson);
215
+ const builtinProviders =
216
+ builtinsRes && builtinsRes.length > 0 ? builtinsRes : BUILTIN_PROVIDERS;
217
+ const allProviders = mergeProviders(builtinProviders, auth ?? {}, modelsJson);
172
218
  const allModels = allProviders.flatMap((p) =>
173
219
  p.models.map((m) => ({
174
220
  ...m,
@@ -181,6 +227,7 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
181
227
  settings,
182
228
  auth,
183
229
  modelsJson,
230
+ builtinProviders,
184
231
  allProviders,
185
232
  allModels,
186
233
  usage,
@@ -271,8 +318,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
271
318
  if (ok) {
272
319
  set({ auth: updated });
273
320
  // Recompute providers with updated auth state
274
- const { modelsJson } = get();
275
- set({ allProviders: mergeProviders(updated, modelsJson) });
321
+ const { modelsJson, builtinProviders } = get();
322
+ set({ allProviders: mergeProviders(builtinProviders, updated, modelsJson) });
276
323
  }
277
324
  return ok;
278
325
  },
@@ -284,8 +331,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
284
331
  const ok = await apiPost("/auth", rest);
285
332
  if (ok) {
286
333
  set({ auth: rest });
287
- const { modelsJson } = get();
288
- set({ allProviders: mergeProviders(rest, modelsJson) });
334
+ const { modelsJson, builtinProviders } = get();
335
+ set({ allProviders: mergeProviders(builtinProviders, rest, modelsJson) });
289
336
  }
290
337
  return ok;
291
338
  },
@@ -309,8 +356,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
309
356
  // Persist
310
357
  apiPost("/models", updated);
311
358
  // Recompute providers
312
- const { auth } = get();
313
- set({ allProviders: mergeProviders(auth ?? {}, updated) });
359
+ const { auth, builtinProviders } = get();
360
+ set({ allProviders: mergeProviders(builtinProviders, auth ?? {}, updated) });
314
361
  },
315
362
 
316
363
  updateModel: (providerId, modelId, updates) => {
@@ -339,8 +386,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
339
386
  const updated = { providers: newProviders };
340
387
  set({ modelsJson: updated });
341
388
  apiPost("/models", updated);
342
- const { auth } = get();
343
- set({ allProviders: mergeProviders(auth ?? {}, updated) });
389
+ const { auth, builtinProviders } = get();
390
+ set({ allProviders: mergeProviders(builtinProviders, auth ?? {}, updated) });
344
391
  },
345
392
 
346
393
  addModel: (providerId, model) => {
@@ -357,8 +404,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
357
404
  const updated = { providers: newProviders };
358
405
  set({ modelsJson: updated });
359
406
  apiPost("/models", updated);
360
- const { auth } = get();
361
- const newAllProviders = mergeProviders(auth ?? {}, updated);
407
+ const { auth, builtinProviders } = get();
408
+ const newAllProviders = mergeProviders(builtinProviders, auth ?? {}, updated);
362
409
  const newAllModels = newAllProviders.flatMap((p) =>
363
410
  p.models.map((m) => ({ ...m, providerId: p.id, providerName: p.name }))
364
411
  );
@@ -378,8 +425,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
378
425
  const updated = { providers: newProviders };
379
426
  set({ modelsJson: updated });
380
427
  apiPost("/models", updated);
381
- const { auth } = get();
382
- set({ allProviders: mergeProviders(auth ?? {}, updated) });
428
+ const { auth, builtinProviders } = get();
429
+ set({ allProviders: mergeProviders(builtinProviders, auth ?? {}, updated) });
383
430
  },
384
431
 
385
432
  // ─── Custom Provider CRUD ──────────────────────────────
@@ -392,8 +439,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
392
439
  const ok = await apiPost("/models", updated);
393
440
  if (ok) {
394
441
  set({ modelsJson: updated });
395
- const { auth } = get();
396
- set({ allProviders: mergeProviders(auth ?? {}, updated) });
442
+ const { auth, builtinProviders } = get();
443
+ set({ allProviders: mergeProviders(builtinProviders, auth ?? {}, updated) });
397
444
  }
398
445
  return ok;
399
446
  },
@@ -411,8 +458,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
411
458
  const ok = await apiPost("/models", updated);
412
459
  if (ok) {
413
460
  set({ modelsJson: updated });
414
- const { auth } = get();
415
- set({ allProviders: mergeProviders(auth ?? {}, updated) });
461
+ const { auth, builtinProviders } = get();
462
+ set({ allProviders: mergeProviders(builtinProviders, auth ?? {}, updated) });
416
463
  }
417
464
  return ok;
418
465
  },
@@ -425,8 +472,8 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
425
472
  const ok = await apiPost("/models", updated);
426
473
  if (ok) {
427
474
  set({ modelsJson: updated });
428
- const { auth } = get();
429
- const newAllProviders = mergeProviders(auth ?? {}, updated);
475
+ const { auth, builtinProviders } = get();
476
+ const newAllProviders = mergeProviders(builtinProviders, auth ?? {}, updated);
430
477
  const newAllModels = newAllProviders.flatMap((p) =>
431
478
  p.models.map((m) => ({ ...m, providerId: p.id, providerName: p.name }))
432
479
  );
@@ -116,6 +116,7 @@ export interface PiAuth {
116
116
  }
117
117
 
118
118
  export interface CustomProviderConfig {
119
+ name?: string;
119
120
  baseUrl?: string;
120
121
  api?: ApiType;
121
122
  apiKey?: string;
@@ -192,6 +193,57 @@ export interface ProviderUsageSummary {
192
193
  modelCount: number;
193
194
  }
194
195
 
196
+ // ─── Subagents ───────────────────────────────────────────
197
+
198
+ export interface AgentDef {
199
+ name: string;
200
+ fileName: string;
201
+ filePath: string;
202
+ package: string;
203
+ description: string;
204
+ model?: string;
205
+ tools?: string[];
206
+ thinking?: string;
207
+ systemPromptMode?: string;
208
+ inheritProjectContext?: boolean;
209
+ inheritSkills?: boolean;
210
+ input?: string[];
211
+ body: string;
212
+ }
213
+
214
+ export interface ChainStep {
215
+ agent: string;
216
+ phase?: string;
217
+ label?: string;
218
+ output?: string;
219
+ as?: string;
220
+ task?: string;
221
+ }
222
+
223
+ export interface ChainDef {
224
+ name: string;
225
+ fileName: string;
226
+ filePath: string;
227
+ description: string;
228
+ steps: ChainStep[];
229
+ body: string;
230
+ }
231
+
232
+ export interface RunRecord {
233
+ agent: string;
234
+ ts: number;
235
+ status: string;
236
+ duration?: number;
237
+ exit?: number;
238
+ taskHash?: string;
239
+ }
240
+
241
+ export interface SubagentsData {
242
+ agents: AgentDef[];
243
+ chains: ChainDef[];
244
+ runHistory: RunRecord[];
245
+ }
246
+
195
247
  // ─── Config Import/Export ─────────────────────────────────
196
248
 
197
249
  export interface ExportPayload {
package/tsconfig.json CHANGED
@@ -20,5 +20,5 @@
20
20
  "@/*": ["src/*"]
21
21
  }
22
22
  },
23
- "include": ["src"]
23
+ "include": ["src", "electron"]
24
24
  }
package/vite.config.ts CHANGED
@@ -63,7 +63,10 @@ function piApiPlugin(): Plugin {
63
63
  },
64
64
  "GET /api/pi/builtin-providers"(_, res) {
65
65
  res.setHeader("Content-Type", "application/json");
66
- res.end(JSON.stringify(builtins.getBuiltinProviders()));
66
+ // Prefer the live catalog from the local pi install; fall back to
67
+ // the hand-written static list when pi isn't found on this machine.
68
+ const catalog = pi.readBuiltinCatalog();
69
+ res.end(JSON.stringify(catalog ?? builtins.getBuiltinProviders()));
67
70
  },
68
71
  "GET /api/pi/usage"(_, res) {
69
72
  const records = pi.readAllUsage();
@@ -87,6 +90,11 @@ function piApiPlugin(): Plugin {
87
90
  res.setHeader("Content-Type", "application/json");
88
91
  res.end(JSON.stringify(memory));
89
92
  },
93
+ "GET /api/pi/subagents"(_, res) {
94
+ const data = pi.readSubagents();
95
+ res.setHeader("Content-Type", "application/json");
96
+ res.end(JSON.stringify(data));
97
+ },
90
98
  "POST /api/pi/memory/delete-entry"(req, res) {
91
99
  let body = "";
92
100
  req.on("data", (chunk: string) => (body += chunk));
@@ -179,6 +187,46 @@ function piApiPlugin(): Plugin {
179
187
  }
180
188
  });
181
189
  },
190
+ "POST /api/pi/provider-models"(req, res) {
191
+ let body = "";
192
+ req.on("data", (chunk: string) => (body += chunk));
193
+ req.on("end", () => {
194
+ try {
195
+ const { baseUrl, apiKey, providerId } = JSON.parse(body) as {
196
+ baseUrl: string;
197
+ apiKey?: string;
198
+ providerId?: string;
199
+ };
200
+ if (!baseUrl) throw new Error("missing baseUrl");
201
+ pi.fetchProviderModels(baseUrl, apiKey, providerId).then((result) => {
202
+ res.setHeader("Content-Type", "application/json");
203
+ res.end(JSON.stringify(result));
204
+ });
205
+ } catch {
206
+ res.statusCode = 400;
207
+ res.setHeader("Content-Type", "application/json");
208
+ res.end(JSON.stringify({ models: [], error: "Invalid request body" }));
209
+ }
210
+ });
211
+ },
212
+ "POST /api/pi/model-test"(req, res) {
213
+ let body = "";
214
+ req.on("data", (chunk: string) => (body += chunk));
215
+ req.on("end", () => {
216
+ try {
217
+ const { baseUrl, modelId, apiKey, apiType } = JSON.parse(body) as { baseUrl: string; modelId: string; apiKey?: string; apiType?: string };
218
+ if (!baseUrl || !modelId) throw new Error("missing baseUrl or modelId");
219
+ pi.testModel(baseUrl, modelId, apiKey, apiType ?? "openai-completions").then((result) => {
220
+ res.setHeader("Content-Type", "application/json");
221
+ res.end(JSON.stringify(result));
222
+ });
223
+ } catch {
224
+ res.statusCode = 400;
225
+ res.setHeader("Content-Type", "application/json");
226
+ res.end(JSON.stringify({ success: false, message: "Invalid request body" }));
227
+ }
228
+ });
229
+ },
182
230
  "POST /api/pi/provider-test"(req, res) {
183
231
  let body = "";
184
232
  req.on("data", (chunk: string) => (body += chunk));
@@ -275,10 +323,18 @@ function piApiPlugin(): Plugin {
275
323
  // ─── Vite Config ────────────────────────────────────────
276
324
 
277
325
  export default defineConfig({
278
- plugins: [react(), tailwindcss(), piApiPlugin()],
326
+ plugins: [
327
+ react(),
328
+ tailwindcss(),
329
+ piApiPlugin(),
330
+ ],
279
331
  resolve: {
280
332
  alias: {
281
333
  "@": path.resolve(__dirname, "src"),
282
334
  },
283
335
  },
336
+ server: {
337
+ port: 5176,
338
+ strictPort: true,
339
+ },
284
340
  });
@@ -1 +0,0 @@
1
- {"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/dashboard/dashboardpage.tsx","./src/components/layout/appshell.tsx","./src/components/layout/sidebar.tsx","./src/components/providers/providersmodelspage.tsx","./src/components/sessions/memorypage.tsx","./src/components/sessions/sessionspage.tsx","./src/components/settings/settingspage.tsx","./src/components/ui/badge.tsx","./src/components/ui/emptystate.tsx","./src/components/ui/modal.tsx","./src/components/ui/statcard.tsx","./src/data/builtin-providers.ts","./src/data/mock-config.ts","./src/data/mock-usage.ts","./src/lib/config.ts","./src/lib/currency.ts","./src/lib/i18n.tsx","./src/lib/utils.ts","./src/lib/translations/en.ts","./src/lib/translations/index.ts","./src/lib/translations/ja.ts","./src/lib/translations/zh-cn.ts","./src/lib/translations/zh-tw.ts","./src/store/config-store.ts","./src/types/index.ts"],"version":"5.8.3"}