@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.
- package/README.ja.md +17 -1
- package/README.md +17 -1
- package/README.zh-CN.md +17 -1
- package/package.json +46 -3
- package/public/apple-touch-icon.png +0 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/pi.svg +47 -4
- package/server/pi-reader.ts +750 -4
- package/src/App.tsx +2 -0
- package/src/components/layout/Sidebar.tsx +3 -5
- package/src/components/providers/ProvidersModelsPage.tsx +1171 -124
- package/src/components/settings/SettingsPage.tsx +74 -55
- package/src/components/subagents/SubagentsPage.tsx +502 -0
- package/src/data/builtin-providers.ts +15 -7
- package/src/data/model-catalog.ts +967 -0
- package/src/index.css +5 -1
- package/src/lib/config.ts +61 -0
- package/src/lib/translations/en.ts +91 -1
- package/src/lib/translations/ja.ts +91 -1
- package/src/lib/translations/zh-CN.ts +91 -1
- package/src/lib/translations/zh-TW.ts +91 -1
- package/src/main.tsx +9 -0
- package/src/store/config-store.ts +75 -28
- package/src/types/index.ts +52 -0
- package/tsconfig.json +1 -1
- package/vite.config.ts +58 -2
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -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
|
-
|
|
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(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}));
|
|
54
|
+
function mergeProviders(
|
|
55
|
+
builtinProviders: Provider[],
|
|
56
|
+
auth: PiAuth,
|
|
57
|
+
customModels: PiModelsJson | null
|
|
58
|
+
): Provider[] {
|
|
60
59
|
const customs = getCustomProviders(customModels);
|
|
61
|
-
|
|
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
|
|
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
|
);
|
package/src/types/index.ts
CHANGED
|
@@ -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
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
|
-
|
|
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: [
|
|
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
|
});
|
package/tsconfig.tsbuildinfo
DELETED
|
@@ -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"}
|