@raingor/pi-web-switch 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/pi-reader.ts +33 -6
- package/src/components/providers/ProvidersModelsPage.tsx +266 -108
- package/src/components/settings/SettingsPage.tsx +23 -33
- package/src/components/speedtest/ModelSpeedTestPage.tsx +138 -4
- package/src/components/subagents/SubagentsPage.tsx +30 -21
- package/src/data/changelog.ts +10 -0
- package/src/index.css +1 -2
- package/src/lib/translations/en.ts +25 -8
- package/src/lib/translations/ja.ts +25 -8
- package/src/lib/translations/zh-CN.ts +25 -8
- package/src/lib/translations/zh-TW.ts +25 -8
- package/src/store/config-store.ts +3 -1
- package/src/types/index.ts +14 -0
|
@@ -91,7 +91,6 @@ export function SettingsPage() {
|
|
|
91
91
|
auth,
|
|
92
92
|
modelsJson,
|
|
93
93
|
allProviders,
|
|
94
|
-
allModels,
|
|
95
94
|
updateSettings,
|
|
96
95
|
setTheme,
|
|
97
96
|
addPackage,
|
|
@@ -135,10 +134,16 @@ export function SettingsPage() {
|
|
|
135
134
|
}
|
|
136
135
|
};
|
|
137
136
|
|
|
138
|
-
//
|
|
139
|
-
//
|
|
137
|
+
// Names of everything that can be updated: pi core (routed to `pi update`
|
|
138
|
+
// server-side) plus every outdated extension.
|
|
139
|
+
const updatableNames = [
|
|
140
|
+
...(updateResult?.pi?.hasUpdate ? [updateResult.pi.name] : []),
|
|
141
|
+
...(updateResult?.extensions ?? []).filter((e) => e.hasUpdate).map((e) => e.name),
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
// One-click update, then re-check so the list reflects the new installed versions.
|
|
140
145
|
const handleApplyUpdates = async () => {
|
|
141
|
-
const names =
|
|
146
|
+
const names = updatableNames;
|
|
142
147
|
if (names.length === 0) return;
|
|
143
148
|
setApplying(true);
|
|
144
149
|
setApplyMessage(null);
|
|
@@ -224,7 +229,18 @@ export function SettingsPage() {
|
|
|
224
229
|
// ── Default model select: composite `providerId/modelId` values, but the
|
|
225
230
|
// settings file stores the bare model id (that's what pi expects on disk).
|
|
226
231
|
const modelValue = (providerId: string, modelId: string) => `${providerId}/${modelId}`;
|
|
227
|
-
|
|
232
|
+
// Only providers that are actually usable belong in the defaults lists: a saved
|
|
233
|
+
// API key (auth.json / models.json override) or a custom provider. A stale
|
|
234
|
+
// saved value is kept so the selects don't render blank.
|
|
235
|
+
const providerOptions = allProviders.filter(
|
|
236
|
+
(p) =>
|
|
237
|
+
p.type === "custom" ||
|
|
238
|
+
p.hasAuth ||
|
|
239
|
+
!!p.apiKey ||
|
|
240
|
+
!!auth?.[p.id]?.key ||
|
|
241
|
+
p.id === settings?.defaultProvider
|
|
242
|
+
);
|
|
243
|
+
const modelProviders = providerOptions.filter((p) => p.models.length > 0);
|
|
228
244
|
const scopedId = settings?.defaultProvider;
|
|
229
245
|
const groupedModelOptions = scopedId
|
|
230
246
|
? [...modelProviders.filter((p) => p.id === scopedId), ...modelProviders.filter((p) => p.id !== scopedId)]
|
|
@@ -265,13 +281,6 @@ export function SettingsPage() {
|
|
|
265
281
|
updateSettings(clearModel ? { defaultProvider: v, defaultModel: undefined } : { defaultProvider: v || undefined });
|
|
266
282
|
};
|
|
267
283
|
|
|
268
|
-
const enabledModels = settings?.enabledModels ?? [];
|
|
269
|
-
const themeLabelKey: Record<string, string> = {
|
|
270
|
-
light: "settings.light",
|
|
271
|
-
dark: "settings.dark",
|
|
272
|
-
"light/dark": "settings.system",
|
|
273
|
-
};
|
|
274
|
-
|
|
275
284
|
const tabs: { key: SettingsTab; icon: typeof Palette; label: string }[] = [
|
|
276
285
|
{ key: "appearance", icon: Palette, label: t("settings.appearance") },
|
|
277
286
|
{ key: "models", icon: LayoutGrid, label: t("settings.tab_models") },
|
|
@@ -290,25 +299,6 @@ export function SettingsPage() {
|
|
|
290
299
|
</div>
|
|
291
300
|
<h1 className="mt-1 text-2xl font-bold text-white">{t("settings.title")}</h1>
|
|
292
301
|
<p className="mt-1 text-sm text-gray-400">{t("settings.subtitle")}</p>
|
|
293
|
-
<div className="mt-4 grid max-w-lg grid-cols-3 gap-3">
|
|
294
|
-
<div className="rounded-xl border border-gray-800 bg-gray-900/50 px-4 py-3">
|
|
295
|
-
<div className="text-xl font-bold text-white">{allProviders.length}</div>
|
|
296
|
-
<div className="text-xs text-gray-500">{t("settings.stat_providers")}</div>
|
|
297
|
-
</div>
|
|
298
|
-
<div className="rounded-xl border border-gray-800 bg-gray-900/50 px-4 py-3">
|
|
299
|
-
<div className="text-xl font-bold text-white">
|
|
300
|
-
{enabledModels.length}
|
|
301
|
-
<small className="ml-0.5 text-xs font-normal text-gray-500">/ {allModels.length}</small>
|
|
302
|
-
</div>
|
|
303
|
-
<div className="text-xs text-gray-500">{t("settings.stat_enabled")}</div>
|
|
304
|
-
</div>
|
|
305
|
-
<div className="rounded-xl border border-gray-800 bg-gray-900/50 px-4 py-3">
|
|
306
|
-
<div className="pt-1 text-sm font-bold text-white">
|
|
307
|
-
{t(themeLabelKey[settings?.theme ?? "light/dark"] ?? "settings.system")}
|
|
308
|
-
</div>
|
|
309
|
-
<div className="mt-0.5 text-xs text-gray-500">{t("settings.stat_theme")}</div>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
302
|
</header>
|
|
313
303
|
|
|
314
304
|
{/* ── Tab nav ──────────────────────────────────────── */}
|
|
@@ -431,7 +421,7 @@ export function SettingsPage() {
|
|
|
431
421
|
className={cn(selectCls, "w-56")}
|
|
432
422
|
>
|
|
433
423
|
<option value="">{t("settings.none")}</option>
|
|
434
|
-
{
|
|
424
|
+
{providerOptions.map((p) => (
|
|
435
425
|
<option key={p.id} value={p.id}>{p.name}</option>
|
|
436
426
|
))}
|
|
437
427
|
</select>
|
|
@@ -493,7 +483,7 @@ export function SettingsPage() {
|
|
|
493
483
|
<RefreshCw className={cn("h-4 w-4", checkingUpdates && "animate-spin")} />
|
|
494
484
|
{t("settings.check_updates")}
|
|
495
485
|
</button>
|
|
496
|
-
{
|
|
486
|
+
{updatableNames.length > 0 && (
|
|
497
487
|
<button
|
|
498
488
|
onClick={handleApplyUpdates}
|
|
499
489
|
disabled={applying || checkingUpdates}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useMemo, useState, useEffect } from "react";
|
|
2
|
-
import { Gauge, Loader2, Zap, Check, X, RotateCcw, Download } from "lucide-react";
|
|
2
|
+
import { Gauge, Loader2, Zap, Check, X, RotateCcw, Download, Plus, ListPlus } from "lucide-react";
|
|
3
3
|
import { useTranslation } from "@/lib/i18n";
|
|
4
4
|
import { useConfigStore } from "@/store/config-store";
|
|
5
5
|
import { cn } from "@/lib/utils";
|
|
6
|
-
import type { Provider } from "@/types";
|
|
6
|
+
import type { Provider, Model } from "@/types";
|
|
7
7
|
|
|
8
8
|
// Model returned by /api/pi/provider-models. Kept local to this page — these
|
|
9
9
|
// are stored separately from the provider's configured/enabled models.
|
|
@@ -63,6 +63,41 @@ function saveCatalog(catalog: Record<string, FetchedModel[]>) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// LocalStorage key: per-provider speed-test results, so they survive
|
|
67
|
+
// navigating away and back (the route unmounts this page).
|
|
68
|
+
const RESULTS_KEY = "speedtest:model-results";
|
|
69
|
+
// LocalStorage key: last selected provider on this page.
|
|
70
|
+
const LAST_PROVIDER_KEY = "speedtest:last-provider";
|
|
71
|
+
|
|
72
|
+
type AllResults = Record<string, Record<string, ModelResult>>;
|
|
73
|
+
|
|
74
|
+
function loadResults(): AllResults {
|
|
75
|
+
try {
|
|
76
|
+
const raw = localStorage.getItem(RESULTS_KEY);
|
|
77
|
+
if (!raw) return {};
|
|
78
|
+
const all = JSON.parse(raw) as AllResults;
|
|
79
|
+
// Drop entries stuck in "testing" (page left mid-run) — they never finished.
|
|
80
|
+
for (const pid of Object.keys(all)) {
|
|
81
|
+
const kept = Object.fromEntries(
|
|
82
|
+
Object.entries(all[pid] ?? {}).filter(([, r]) => r.status === "done")
|
|
83
|
+
);
|
|
84
|
+
if (Object.keys(kept).length === 0) delete all[pid];
|
|
85
|
+
else all[pid] = kept;
|
|
86
|
+
}
|
|
87
|
+
return all;
|
|
88
|
+
} catch {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function saveResults(all: AllResults) {
|
|
94
|
+
try {
|
|
95
|
+
localStorage.setItem(RESULTS_KEY, JSON.stringify(all));
|
|
96
|
+
} catch {
|
|
97
|
+
/* ignore quota errors */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
66
101
|
export function ModelSpeedTestPage() {
|
|
67
102
|
const { t } = useTranslation();
|
|
68
103
|
const { allProviders, auth } = useConfigStore();
|
|
@@ -79,22 +114,93 @@ export function ModelSpeedTestPage() {
|
|
|
79
114
|
);
|
|
80
115
|
|
|
81
116
|
const [selectedId, setSelectedId] = useState<string | null>(
|
|
82
|
-
testableProviders[0]?.id ?? null
|
|
117
|
+
() => localStorage.getItem(LAST_PROVIDER_KEY) ?? testableProviders[0]?.id ?? null
|
|
83
118
|
);
|
|
84
119
|
const selected = testableProviders.find((p) => p.id === selectedId) ?? testableProviders[0] ?? null;
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (selected?.id) localStorage.setItem(LAST_PROVIDER_KEY, selected.id);
|
|
122
|
+
}, [selected?.id]);
|
|
85
123
|
|
|
86
124
|
// Speed-test model catalog, persisted in localStorage.
|
|
87
125
|
const [catalog, setCatalog] = useState<Record<string, FetchedModel[]>>({});
|
|
88
126
|
useEffect(() => { setCatalog(loadCatalog()); }, []);
|
|
89
127
|
const models = selected ? (catalog[selected.id] ?? []) : [];
|
|
90
128
|
|
|
91
|
-
|
|
129
|
+
// Results persisted per provider so they survive route changes; the map
|
|
130
|
+
// below is the current provider's slice.
|
|
131
|
+
const [allResults, setAllResults] = useState<AllResults>(() => loadResults());
|
|
132
|
+
useEffect(() => { saveResults(allResults); }, [allResults]);
|
|
133
|
+
const results = useMemo(
|
|
134
|
+
() => new Map(Object.entries(allResults[selected?.id ?? ""] ?? {})),
|
|
135
|
+
[allResults, selected]
|
|
136
|
+
);
|
|
137
|
+
// setResults scoped to the selected provider (drop-in for the old state setter).
|
|
138
|
+
const setResults = (
|
|
139
|
+
next: Map<string, ModelResult> | ((prev: Map<string, ModelResult>) => Map<string, ModelResult>)
|
|
140
|
+
) => {
|
|
141
|
+
const pid = selected?.id ?? "";
|
|
142
|
+
setAllResults((prevAll) => {
|
|
143
|
+
const prevMap = new Map(Object.entries(prevAll[pid] ?? {}));
|
|
144
|
+
const nextMap = typeof next === "function" ? next(prevMap) : next;
|
|
145
|
+
return { ...prevAll, [pid]: Object.fromEntries(nextMap) };
|
|
146
|
+
});
|
|
147
|
+
};
|
|
92
148
|
const [running, setRunning] = useState(false);
|
|
93
149
|
const [speedMode, setSpeedMode] = useState<SpeedMode>("normal");
|
|
94
150
|
const [fetching, setFetching] = useState(false);
|
|
95
151
|
const [fetchError, setFetchError] = useState<string | null>(null);
|
|
96
152
|
const [fetchInfo, setFetchInfo] = useState<string | null>(null);
|
|
97
153
|
|
|
154
|
+
// Add-to-provider support: models already configured under this provider.
|
|
155
|
+
const { addModel } = useConfigStore();
|
|
156
|
+
const configuredIds = useMemo(
|
|
157
|
+
() => new Set((selected?.models ?? []).map((m) => m.id)),
|
|
158
|
+
[selected]
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const toModelDef = (m: FetchedModel): Model => {
|
|
162
|
+
const input: Model["input"] = ["text"];
|
|
163
|
+
if (m.vision) input.push("image");
|
|
164
|
+
if (m.audio) input.push("audio");
|
|
165
|
+
return {
|
|
166
|
+
id: m.id,
|
|
167
|
+
name: m.name,
|
|
168
|
+
reasoning: m.reasoning ?? false,
|
|
169
|
+
input,
|
|
170
|
+
contextWindow: m.contextWindow ?? 262144,
|
|
171
|
+
maxTokens: m.maxTokens ?? 32768,
|
|
172
|
+
cost: m.cost
|
|
173
|
+
? {
|
|
174
|
+
input: m.cost.input ?? 0,
|
|
175
|
+
output: m.cost.output ?? 0,
|
|
176
|
+
cacheRead: m.cost.cacheRead ?? 0,
|
|
177
|
+
cacheWrite: m.cost.cacheWrite ?? 0,
|
|
178
|
+
}
|
|
179
|
+
: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Models that passed 100% and are not configured yet — the batch-add set.
|
|
184
|
+
const passedNew = useMemo(
|
|
185
|
+
() =>
|
|
186
|
+
models.filter((m) => {
|
|
187
|
+
const r = results.get(m.id);
|
|
188
|
+
return !!r && r.runs > 0 && r.success === r.runs && !configuredIds.has(m.id);
|
|
189
|
+
}),
|
|
190
|
+
[models, results, configuredIds]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const addToProvider = async (m: FetchedModel) => {
|
|
194
|
+
if (!selected || configuredIds.has(m.id)) return;
|
|
195
|
+
// Added disabled by default — the user enables it on the providers page.
|
|
196
|
+
addModel(selected.id, toModelDef(m));
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const addAllPassed = async () => {
|
|
200
|
+
if (!selected || running || passedNew.length === 0) return;
|
|
201
|
+
passedNew.forEach((m) => addModel(selected.id, toModelDef(m)));
|
|
202
|
+
};
|
|
203
|
+
|
|
98
204
|
const setResult = (modelId: string, patch: Partial<ModelResult>) => {
|
|
99
205
|
setResults((prev) => {
|
|
100
206
|
const next = new Map(prev);
|
|
@@ -313,6 +419,15 @@ export function ModelSpeedTestPage() {
|
|
|
313
419
|
<RotateCcw className="h-4 w-4" />
|
|
314
420
|
{t("speed_test.reset")}
|
|
315
421
|
</button>
|
|
422
|
+
<button
|
|
423
|
+
onClick={addAllPassed}
|
|
424
|
+
disabled={running || fetching || passedNew.length === 0}
|
|
425
|
+
className="flex items-center gap-1.5 rounded-lg border border-emerald-600/40 bg-emerald-600/10 px-3 py-2 text-sm text-emerald-300 transition-colors hover:bg-emerald-600/20 disabled:cursor-not-allowed disabled:opacity-50"
|
|
426
|
+
title={t("speed_test.add_all_passed_desc")}
|
|
427
|
+
>
|
|
428
|
+
<ListPlus className="h-4 w-4" />
|
|
429
|
+
{t("speed_test.add_all_passed")} ({passedNew.length})
|
|
430
|
+
</button>
|
|
316
431
|
<button
|
|
317
432
|
onClick={runAll}
|
|
318
433
|
disabled={running || fetching || models.length === 0}
|
|
@@ -355,6 +470,7 @@ export function ModelSpeedTestPage() {
|
|
|
355
470
|
<th className="px-3 py-2 font-medium text-right">{t("speed_test.col_avg_latency")}</th>
|
|
356
471
|
<th className="px-3 py-2 font-medium text-right">{t("speed_test.col_range")}</th>
|
|
357
472
|
<th className="px-3 py-2 font-medium">{t("speed_test.col_status")}</th>
|
|
473
|
+
<th className="px-3 py-2 font-medium text-right">{t("speed_test.col_action")}</th>
|
|
358
474
|
</tr>
|
|
359
475
|
</thead>
|
|
360
476
|
<tbody>
|
|
@@ -412,6 +528,24 @@ export function ModelSpeedTestPage() {
|
|
|
412
528
|
</span>
|
|
413
529
|
)}
|
|
414
530
|
</td>
|
|
531
|
+
<td className="px-3 py-2 text-right">
|
|
532
|
+
{rate === 100 &&
|
|
533
|
+
(configuredIds.has(m.id) ? (
|
|
534
|
+
<span className="inline-flex items-center gap-1 text-xs text-gray-500">
|
|
535
|
+
<Check className="h-3.5 w-3.5" />
|
|
536
|
+
{t("speed_test.exists")}
|
|
537
|
+
</span>
|
|
538
|
+
) : (
|
|
539
|
+
<button
|
|
540
|
+
onClick={() => addToProvider(m)}
|
|
541
|
+
disabled={running || fetching}
|
|
542
|
+
className="inline-flex items-center gap-1 rounded-md border border-blue-600/50 bg-blue-600/10 px-2 py-1 text-xs font-medium text-blue-400 transition-colors hover:bg-blue-600/20 disabled:cursor-not-allowed disabled:opacity-50"
|
|
543
|
+
>
|
|
544
|
+
<Plus className="h-3.5 w-3.5" />
|
|
545
|
+
{t("speed_test.add_to_provider")}
|
|
546
|
+
</button>
|
|
547
|
+
))}
|
|
548
|
+
</td>
|
|
415
549
|
</tr>
|
|
416
550
|
);
|
|
417
551
|
})}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
1
|
+
import { useEffect, useState, useMemo } from "react";
|
|
2
2
|
import { useTranslation } from "@/lib/i18n";
|
|
3
3
|
import { useConfigStore } from "@/store/config-store";
|
|
4
4
|
import { Badge } from "@/components/ui/Badge";
|
|
@@ -189,7 +189,10 @@ function AgentList({
|
|
|
189
189
|
searchActive: boolean;
|
|
190
190
|
}) {
|
|
191
191
|
const { t } = useTranslation();
|
|
192
|
-
const [
|
|
192
|
+
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
|
193
|
+
// Resolve the selected agent from the live list so it reflects saved edits
|
|
194
|
+
// after a refresh (matching by fileName), instead of a stale captured object.
|
|
195
|
+
const selected = agents.find((a) => a.fileName === selectedFile) ?? null;
|
|
193
196
|
|
|
194
197
|
if (agents.length === 0) {
|
|
195
198
|
return (
|
|
@@ -208,7 +211,7 @@ function AgentList({
|
|
|
208
211
|
{agents.map((agent) => (
|
|
209
212
|
<button
|
|
210
213
|
key={agent.fileName}
|
|
211
|
-
onClick={() =>
|
|
214
|
+
onClick={() => setSelectedFile(agent.fileName)}
|
|
212
215
|
className={`w-full rounded-lg border px-3 py-3 text-left transition-colors ${
|
|
213
216
|
selected?.fileName === agent.fileName
|
|
214
217
|
? "border-blue-500/30 bg-gray-800 text-white"
|
|
@@ -243,7 +246,14 @@ function AgentList({
|
|
|
243
246
|
|
|
244
247
|
function AgentDetail({ agent, onSaved }: { agent: AgentDef; onSaved: () => void }) {
|
|
245
248
|
const { t } = useTranslation();
|
|
246
|
-
const { allModels } = useConfigStore();
|
|
249
|
+
const { allModels, allProviders } = useConfigStore();
|
|
250
|
+
// Eligible = custom providers + built-in providers with an API key saved.
|
|
251
|
+
const eligibleModels = useMemo(() => {
|
|
252
|
+
const usable = new Set(
|
|
253
|
+
allProviders.filter((p) => p.type === "custom" || p.hasAuth).map((p) => p.id)
|
|
254
|
+
);
|
|
255
|
+
return allModels.filter((m) => usable.has(m.providerId));
|
|
256
|
+
}, [allModels, allProviders]);
|
|
247
257
|
const [editing, setEditing] = useState(false);
|
|
248
258
|
const [model, setModel] = useState(agent.model ?? "");
|
|
249
259
|
const [thinking, setThinking] = useState(agent.thinking ?? "");
|
|
@@ -307,23 +317,22 @@ function AgentDetail({ agent, onSaved }: { agent: AgentDef; onSaved: () => void
|
|
|
307
317
|
<div className={editing ? "col-span-2" : ""}>
|
|
308
318
|
<span className="text-xs text-gray-500">{t("subagents.model")}</span>
|
|
309
319
|
{editing ? (
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
</>
|
|
320
|
+
<select
|
|
321
|
+
value={model}
|
|
322
|
+
onChange={(e) => setModel(e.target.value)}
|
|
323
|
+
className="mt-1 w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 font-mono text-sm text-gray-100 outline-none focus:ring-1 focus:ring-blue-500"
|
|
324
|
+
>
|
|
325
|
+
<option value="">{t("subagents.model_default")}</option>
|
|
326
|
+
{/* Keep a saved-but-unavailable model selectable so it isn't lost. */}
|
|
327
|
+
{model && !eligibleModels.some((m) => `${m.providerId}/${m.id}` === model) && (
|
|
328
|
+
<option value={model}>{model}</option>
|
|
329
|
+
)}
|
|
330
|
+
{eligibleModels.map((m) => (
|
|
331
|
+
<option key={`${m.providerId}/${m.id}`} value={`${m.providerId}/${m.id}`}>
|
|
332
|
+
{m.providerName} · {m.name ?? m.id}
|
|
333
|
+
</option>
|
|
334
|
+
))}
|
|
335
|
+
</select>
|
|
327
336
|
) : (
|
|
328
337
|
<p className="mt-0.5 text-sm text-gray-200 font-mono">{agent.model || t("subagents.model_default")}</p>
|
|
329
338
|
)}
|
package/src/data/changelog.ts
CHANGED
|
@@ -9,6 +9,16 @@ export interface ChangelogEntry {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export const CHANGELOG: ChangelogEntry[] = [
|
|
12
|
+
{
|
|
13
|
+
version: "0.8.2",
|
|
14
|
+
date: "2026-08-28",
|
|
15
|
+
itemKeys: ["changelog.0_8_2_1", "changelog.0_8_2_2", "changelog.0_8_2_3", "changelog.0_8_2_4"],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
version: "0.8.1",
|
|
19
|
+
date: "2026-08-27",
|
|
20
|
+
itemKeys: ["changelog.0_8_1_1", "changelog.0_8_1_2", "changelog.0_8_1_3"],
|
|
21
|
+
},
|
|
12
22
|
{
|
|
13
23
|
version: "0.8.0",
|
|
14
24
|
date: "2026-08-27",
|
package/src/index.css
CHANGED
|
@@ -692,8 +692,7 @@ table { border-collapse: collapse; }
|
|
|
692
692
|
align-items: center;
|
|
693
693
|
justify-content: center;
|
|
694
694
|
gap: 8px;
|
|
695
|
-
|
|
696
|
-
margin-bottom: 10px;
|
|
695
|
+
margin: 14px 14px 0;
|
|
697
696
|
padding: 9px 12px;
|
|
698
697
|
border-radius: 9px;
|
|
699
698
|
border: 1px solid color-mix(in srgb, #229ED9 45%, var(--card-border));
|
|
@@ -33,15 +33,20 @@ const en: Record<string, string> = {
|
|
|
33
33
|
"providers_models.invalid_url": "Enter a valid http(s) URL",
|
|
34
34
|
"providers_models.baseurl_override": "This URL overrides the built-in default (saved to models.json)",
|
|
35
35
|
"providers_models.enable_all": "Enable All",
|
|
36
|
-
"providers_models.sort_by": "Sort",
|
|
37
|
-
"providers_models.sort_default": "Default",
|
|
38
|
-
"providers_models.sort_family": "By family",
|
|
39
|
-
"providers_models.sort_price_asc": "Price ↑",
|
|
40
|
-
"providers_models.sort_price_desc": "Price ↓",
|
|
41
36
|
"providers_models.disable_all": "Disable All",
|
|
42
37
|
"providers_models.enabled_models_title": "Enabled Models",
|
|
43
38
|
"providers_models.no_enabled_models": "No enabled models yet. Enable models in a provider's model list and they'll appear here.",
|
|
44
39
|
"providers_models.api_key_env": "Uses the value of environment variable {0} as the key (not stored in plaintext)",
|
|
40
|
+
"providers_models.api_key_add": "Add Key",
|
|
41
|
+
"providers_models.api_key_count": "{0} keys",
|
|
42
|
+
"providers_models.api_key_active": "In use",
|
|
43
|
+
"providers_models.api_key_use": "Use this key",
|
|
44
|
+
"providers_models.api_key_show": "Show key",
|
|
45
|
+
"providers_models.api_key_hide": "Hide key",
|
|
46
|
+
"providers_models.api_key_delete": "Delete key",
|
|
47
|
+
"providers_models.api_key_empty": "No key yet — add one below.",
|
|
48
|
+
"providers_models.api_key_duplicate": "This key is already in the list",
|
|
49
|
+
"providers_models.api_key_switch_hint": "Pick a key to switch instantly — pi uses the one marked “In use”.",
|
|
45
50
|
"providers_models.test_all": "Test All",
|
|
46
51
|
"providers_models.id_exists": "ID “{0}” already exists, pick another name",
|
|
47
52
|
"providers_models.id_preview": "Will be saved as ID “{0}”",
|
|
@@ -89,6 +94,11 @@ const en: Record<string, string> = {
|
|
|
89
94
|
"speed_test.empty_catalog": "No models fetched yet",
|
|
90
95
|
"speed_test.empty_catalog_desc": "Click \"Fetch Models\" to pull all models from the provider endpoint. Used only for speed testing; does not affect configured models.",
|
|
91
96
|
"speed_test.testing": "Testing",
|
|
97
|
+
"speed_test.col_action": "Action",
|
|
98
|
+
"speed_test.add_to_provider": "Add",
|
|
99
|
+
"speed_test.exists": "Exists",
|
|
100
|
+
"speed_test.add_all_passed": "Add all passed",
|
|
101
|
+
"speed_test.add_all_passed_desc": "Add every model that passed 100% and is not yet configured to this provider",
|
|
92
102
|
"speed_test.reset": "Reset",
|
|
93
103
|
"speed_test.pending": "Pending",
|
|
94
104
|
"speed_test.ok": "All OK",
|
|
@@ -129,6 +139,13 @@ const en: Record<string, string> = {
|
|
|
129
139
|
"app.version": "pi-switch v0.7.0",
|
|
130
140
|
"changelog.button": "Changelog",
|
|
131
141
|
"changelog.title": "What's New",
|
|
142
|
+
"changelog.0_8_2_1": "Speed-test results now persist across page switches; the last selected provider is remembered.",
|
|
143
|
+
"changelog.0_8_2_2": "Models that pass 100% can be added to their provider in one click (single or batch); already-configured ones are marked.",
|
|
144
|
+
"changelog.0_8_2_3": "Newly added models default to disabled until manually enabled.",
|
|
145
|
+
"changelog.0_8_2_4": "Removed the search boxes from the provider model list and the remote-model fetch dialog.",
|
|
146
|
+
"changelog.0_8_1_1": "Settings: removed the top stat cards (providers / enabled models / theme).",
|
|
147
|
+
"changelog.0_8_1_2": "Package browser gained a \"Recommended\" tab; the search tab is filterable by All/Available/Installed.",
|
|
148
|
+
"changelog.0_8_1_3": "Fix: memory & subagent model pickers are now real dropdowns; the subagent panel updates immediately after saving.",
|
|
132
149
|
"changelog.0_8_0_1": "Settings: added a package browser dialog with \"Recommended\" and \"Search\" tabs.",
|
|
133
150
|
"changelog.0_8_0_2": "Search tab fuzzy-searches npm for pi packages, filterable by All/Available/Installed.",
|
|
134
151
|
"changelog.0_8_0_3": "One-click install with an installed-state indicator.",
|
|
@@ -457,9 +474,9 @@ const en: Record<string, string> = {
|
|
|
457
474
|
"settings.updates_failed": "Update check failed",
|
|
458
475
|
"settings.update_all": "Update All",
|
|
459
476
|
"settings.updating": "Updating…",
|
|
460
|
-
"settings.update_success": "Successfully updated {0}
|
|
461
|
-
"settings.update_failed_names": "{0}
|
|
462
|
-
"settings.updates_hint": "Extensions
|
|
477
|
+
"settings.update_success": "Successfully updated {0} item(s)",
|
|
478
|
+
"settings.update_failed_names": "{0} item(s) failed to update: {1}",
|
|
479
|
+
"settings.updates_hint": "Extensions are updated with npm install in ~/.pi/agent/npm; pi core is updated by running `pi update`. Restart pi after updating.",
|
|
463
480
|
"settings.defaults": "Defaults",
|
|
464
481
|
"settings.default_provider": "Default Provider",
|
|
465
482
|
"settings.default_model": "Default Model",
|
|
@@ -31,15 +31,20 @@ const ja: Record<string, string> = {
|
|
|
31
31
|
"providers_models.invalid_url": "有効な http(s) URL を入力してください",
|
|
32
32
|
"providers_models.baseurl_override": "この URL は内蔵デフォルトを上書きします(models.json に保存)",
|
|
33
33
|
"providers_models.enable_all": "すべて有効化",
|
|
34
|
-
"providers_models.sort_by": "並び順",
|
|
35
|
-
"providers_models.sort_default": "デフォルト",
|
|
36
|
-
"providers_models.sort_family": "ファミリー順",
|
|
37
|
-
"providers_models.sort_price_asc": "価格昇順",
|
|
38
|
-
"providers_models.sort_price_desc": "価格降順",
|
|
39
34
|
"providers_models.disable_all": "すべて無効化",
|
|
40
35
|
"providers_models.enabled_models_title": "有効なモデル",
|
|
41
36
|
"providers_models.no_enabled_models": "有効なモデルはまだありません。プロバイダーのモデルリストでモデルを有効にすると、ここに表示されます。",
|
|
42
37
|
"providers_models.api_key_env": "環境変数 {0} の値をキーとして使用します(平文保存されません)",
|
|
38
|
+
"providers_models.api_key_add": "キーを追加",
|
|
39
|
+
"providers_models.api_key_count": "キー {0} 件",
|
|
40
|
+
"providers_models.api_key_active": "使用中",
|
|
41
|
+
"providers_models.api_key_use": "このキーを使用",
|
|
42
|
+
"providers_models.api_key_show": "キーを表示",
|
|
43
|
+
"providers_models.api_key_hide": "キーを非表示",
|
|
44
|
+
"providers_models.api_key_delete": "キーを削除",
|
|
45
|
+
"providers_models.api_key_empty": "キーがまだありません。下で追加してください。",
|
|
46
|
+
"providers_models.api_key_duplicate": "このキーは既にリストにあります",
|
|
47
|
+
"providers_models.api_key_switch_hint": "選択するとすぐに切り替わります。pi は「使用中」のキーを使います。",
|
|
43
48
|
"providers_models.test_all": "すべてテスト",
|
|
44
49
|
"providers_models.id_exists": "ID “{0}” は既に存在します。別の名前を使用してください",
|
|
45
50
|
"providers_models.id_preview": "ID “{0}” として保存されます",
|
|
@@ -87,6 +92,11 @@ const ja: Record<string, string> = {
|
|
|
87
92
|
"speed_test.empty_catalog": "モデル未取得",
|
|
88
93
|
"speed_test.empty_catalog_desc": "「モデルを取得」をクリックしてプロバイダーから全モデルを取得します。速度テスト専用で、設定済みモデルには影響しません。",
|
|
89
94
|
"speed_test.testing": "テスト中",
|
|
95
|
+
"speed_test.col_action": "操作",
|
|
96
|
+
"speed_test.add_to_provider": "追加",
|
|
97
|
+
"speed_test.exists": "存在",
|
|
98
|
+
"speed_test.add_all_passed": "合格を一括追加",
|
|
99
|
+
"speed_test.add_all_passed_desc": "100%合格かつ未設定のモデルをすべてこのプロバイダーに追加",
|
|
90
100
|
"speed_test.reset": "リセット",
|
|
91
101
|
"speed_test.pending": "待機中",
|
|
92
102
|
"speed_test.ok": "すべて成功",
|
|
@@ -127,6 +137,13 @@ const ja: Record<string, string> = {
|
|
|
127
137
|
"app.version": "pi-switch v0.7.0",
|
|
128
138
|
"changelog.button": "変更履歴",
|
|
129
139
|
"changelog.title": "更新情報",
|
|
140
|
+
"changelog.0_8_2_1": "速度テスト結果をページ切替後も保持し、前回選択したプロバイダーも記憶。",
|
|
141
|
+
"changelog.0_8_2_2": "100%合格モデルをワンクリックでプロバイダーに追加(単発/一括)、設定済みは自動マーク。",
|
|
142
|
+
"changelog.0_8_2_3": "追加したモデルはデフォルト無効、手動で有効化。",
|
|
143
|
+
"changelog.0_8_2_4": "プロバイダーのモデル一覧とリモート取得ダイアログの検索ボックスを削除。",
|
|
144
|
+
"changelog.0_8_1_1": "設定:上部の統計カード(プロバイダー / 有効モデル / テーマ)を削除。",
|
|
145
|
+
"changelog.0_8_1_2": "パッケージ検索に「おすすめ」タブを追加。検索タブはすべて/未インストール/インストール済みで絞り込み可能。",
|
|
146
|
+
"changelog.0_8_1_3": "修正:メモリとサブエージェントのモデル選択を実際のドロップダウンに。サブエージェントは保存後すぐ反映。",
|
|
130
147
|
"changelog.0_8_0_1": "設定:「おすすめ」と「検索」タブを備えたパッケージ検索ダイアログを追加。",
|
|
131
148
|
"changelog.0_8_0_2": "検索タブは npm の pi パッケージをあいまい検索し、すべて/未インストール/インストール済みで絞り込み可能。",
|
|
132
149
|
"changelog.0_8_0_3": "ワンクリックインストールとインストール済み表示。",
|
|
@@ -449,9 +466,9 @@ const ja: Record<string, string> = {
|
|
|
449
466
|
"settings.updates_failed": "アップデート確認に失敗しました",
|
|
450
467
|
"settings.update_all": "一括更新",
|
|
451
468
|
"settings.updating": "更新中…",
|
|
452
|
-
"settings.update_success": "{0}
|
|
453
|
-
"settings.update_failed_names": "{0}
|
|
454
|
-
"settings.updates_hint": "拡張は
|
|
469
|
+
"settings.update_success": "{0} 件を更新しました",
|
|
470
|
+
"settings.update_failed_names": "{0} 件の更新に失敗:{1}",
|
|
471
|
+
"settings.updates_hint": "拡張は ~/.pi/agent/npm での npm install で更新します。pi 本体は `pi update` で更新します。更新後は pi を再起動してください。",
|
|
455
472
|
"settings.defaults": "デフォルト",
|
|
456
473
|
"settings.default_provider": "デフォルトプロバイダー",
|
|
457
474
|
"settings.default_model": "デフォルトモデル",
|
|
@@ -31,15 +31,20 @@ const zhCN: Record<string, string> = {
|
|
|
31
31
|
"providers_models.invalid_url": "请输入合法的 http(s) 地址",
|
|
32
32
|
"providers_models.baseurl_override": "此地址将覆盖内置默认地址(写入 models.json)",
|
|
33
33
|
"providers_models.enable_all": "全部启用",
|
|
34
|
-
"providers_models.sort_by": "排序",
|
|
35
|
-
"providers_models.sort_default": "默认",
|
|
36
|
-
"providers_models.sort_family": "按厂商",
|
|
37
|
-
"providers_models.sort_price_asc": "价格升序",
|
|
38
|
-
"providers_models.sort_price_desc": "价格降序",
|
|
39
34
|
"providers_models.disable_all": "全部禁用",
|
|
40
35
|
"providers_models.enabled_models_title": "已启用模型",
|
|
41
36
|
"providers_models.no_enabled_models": "暂无启用的模型。在供应商的模型列表中开启模型后,会显示在这里。",
|
|
42
37
|
"providers_models.api_key_env": "将以环境变量 {0} 的值作为密钥(不会明文写入)",
|
|
38
|
+
"providers_models.api_key_add": "添加密钥",
|
|
39
|
+
"providers_models.api_key_count": "共 {0} 个密钥",
|
|
40
|
+
"providers_models.api_key_active": "使用中",
|
|
41
|
+
"providers_models.api_key_use": "使用此密钥",
|
|
42
|
+
"providers_models.api_key_show": "显示密钥",
|
|
43
|
+
"providers_models.api_key_hide": "隐藏密钥",
|
|
44
|
+
"providers_models.api_key_delete": "删除密钥",
|
|
45
|
+
"providers_models.api_key_empty": "还没有密钥,请在下方添加。",
|
|
46
|
+
"providers_models.api_key_duplicate": "该密钥已在列表中",
|
|
47
|
+
"providers_models.api_key_switch_hint": "选中即可立即切换,pi 会使用标记为「使用中」的那一个。",
|
|
43
48
|
"providers_models.test_all": "测试全部",
|
|
44
49
|
"providers_models.id_exists": "ID “{0}” 已存在,请换一个名称",
|
|
45
50
|
"providers_models.id_preview": "将以 ID “{0}” 保存",
|
|
@@ -87,6 +92,11 @@ const zhCN: Record<string, string> = {
|
|
|
87
92
|
"speed_test.empty_catalog": "尚未获取模型列表",
|
|
88
93
|
"speed_test.empty_catalog_desc": "点击“一键获取模型”从供应商接口拉取全部模型,仅用于测速,不影响已配置的模型。",
|
|
89
94
|
"speed_test.testing": "测速中",
|
|
95
|
+
"speed_test.col_action": "操作",
|
|
96
|
+
"speed_test.add_to_provider": "加入",
|
|
97
|
+
"speed_test.exists": "已存在",
|
|
98
|
+
"speed_test.add_all_passed": "一键加入全部通过",
|
|
99
|
+
"speed_test.add_all_passed_desc": "把测速 100% 通过且尚未配置的模型全部加入该供应商",
|
|
90
100
|
"speed_test.reset": "重置",
|
|
91
101
|
"speed_test.pending": "待测",
|
|
92
102
|
"speed_test.ok": "全部成功",
|
|
@@ -127,6 +137,13 @@ const zhCN: Record<string, string> = {
|
|
|
127
137
|
"app.version": "pi-switch v0.7.0",
|
|
128
138
|
"changelog.button": "更新日志",
|
|
129
139
|
"changelog.title": "版本更新说明",
|
|
140
|
+
"changelog.0_8_2_1": "测速结果持久化:切换页面再回来结果不丢,并记住上次选中的供应商。",
|
|
141
|
+
"changelog.0_8_2_2": "测速 100% 通过的模型支持一键加入对应供应商(单个/批量),已存在自动标记。",
|
|
142
|
+
"changelog.0_8_2_3": "新添加的模型默认为禁用状态,需手动启用。",
|
|
143
|
+
"changelog.0_8_2_4": "移除供应商模型列表与远程模型获取弹窗中的搜索框。",
|
|
144
|
+
"changelog.0_8_1_1": "设置页移除顶部统计卡片(供应商 / 已启用模型 / 当前主题)。",
|
|
145
|
+
"changelog.0_8_1_2": "包浏览弹窗新增「推荐安装」标签页,搜索标签支持按全部/未安装/已安装筛选。",
|
|
146
|
+
"changelog.0_8_1_3": "修复:记忆与子代理的模型选择改为真正的下拉;子代理保存后面板即时更新。",
|
|
130
147
|
"changelog.0_8_0_1": "设置:新增扩展包浏览弹窗,含「推荐安装」与「搜索」两个标签页。",
|
|
131
148
|
"changelog.0_8_0_2": "搜索标签支持模糊搜索 npm 上的 pi 扩展包,并可按全部/未安装/已安装筛选。",
|
|
132
149
|
"changelog.0_8_0_3": "扩展包一键安装,已安装状态自动标识。",
|
|
@@ -449,9 +466,9 @@ const zhCN: Record<string, string> = {
|
|
|
449
466
|
"settings.updates_failed": "检查更新失败",
|
|
450
467
|
"settings.update_all": "一键更新",
|
|
451
468
|
"settings.updating": "更新中…",
|
|
452
|
-
"settings.update_success": "已成功更新 {0}
|
|
453
|
-
"settings.update_failed_names": "{0}
|
|
454
|
-
"settings.updates_hint": "
|
|
469
|
+
"settings.update_success": "已成功更新 {0} 项",
|
|
470
|
+
"settings.update_failed_names": "{0} 项更新失败:{1}",
|
|
471
|
+
"settings.updates_hint": "扩展通过 ~/.pi/agent/npm 里的 npm install 更新;pi 本体通过执行 `pi update` 更新。更新后请重启 pi。",
|
|
455
472
|
"settings.defaults": "默认值",
|
|
456
473
|
"settings.default_provider": "默认提供商",
|
|
457
474
|
"settings.default_model": "默认模型",
|