@raingor/pi-web-switch 0.7.1 → 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 +77 -6
- package/src/components/providers/ProvidersModelsPage.tsx +266 -108
- package/src/components/settings/PackageBrowser.tsx +222 -0
- package/src/components/settings/SettingsPage.tsx +120 -60
- package/src/components/speedtest/ModelSpeedTestPage.tsx +138 -4
- package/src/components/subagents/SubagentsPage.tsx +30 -21
- package/src/data/changelog.ts +15 -0
- package/src/data/recommended-packages.ts +20 -0
- package/src/index.css +1 -2
- package/src/lib/translations/en.ts +52 -8
- package/src/lib/translations/ja.ts +52 -8
- package/src/lib/translations/zh-CN.ts +52 -8
- package/src/lib/translations/zh-TW.ts +52 -8
- package/src/store/config-store.ts +3 -1
- package/src/types/index.ts +14 -0
- package/vite.config.ts +11 -0
|
@@ -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,21 @@ 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
|
+
},
|
|
22
|
+
{
|
|
23
|
+
version: "0.8.0",
|
|
24
|
+
date: "2026-08-27",
|
|
25
|
+
itemKeys: ["changelog.0_8_0_1", "changelog.0_8_0_2", "changelog.0_8_0_3"],
|
|
26
|
+
},
|
|
12
27
|
{
|
|
13
28
|
version: "0.7.0",
|
|
14
29
|
date: "2026-08-27",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Recommended pi extension packages, surfaced in Settings for one-click install.
|
|
2
|
+
// Seeded from the maintainer's own setup. `descKey` points to an i18n string.
|
|
3
|
+
export interface RecommendedPackage {
|
|
4
|
+
id: string; // exact value written to settings.packages, e.g. "npm:pi-hermes-memory"
|
|
5
|
+
name: string; // short display name
|
|
6
|
+
descKey: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const RECOMMENDED_PACKAGES: RecommendedPackage[] = [
|
|
10
|
+
{ id: "npm:pi-hermes-memory", name: "pi-hermes-memory", descKey: "pkg.hermes_memory" },
|
|
11
|
+
{ id: "npm:context-mode", name: "context-mode", descKey: "pkg.context_mode" },
|
|
12
|
+
{ id: "npm:pi-subagents", name: "pi-subagents", descKey: "pkg.subagents" },
|
|
13
|
+
{ id: "npm:pi-web-access", name: "pi-web-access", descKey: "pkg.web_access" },
|
|
14
|
+
{ id: "npm:pi-smart-fetch", name: "pi-smart-fetch", descKey: "pkg.smart_fetch" },
|
|
15
|
+
{ id: "npm:pi-rtk-optimizer", name: "pi-rtk-optimizer", descKey: "pkg.rtk_optimizer" },
|
|
16
|
+
{ id: "npm:pi-puppeteer", name: "pi-puppeteer", descKey: "pkg.puppeteer" },
|
|
17
|
+
{ id: "npm:pi-intercom", name: "pi-intercom", descKey: "pkg.intercom" },
|
|
18
|
+
{ id: "npm:pi-prompt-template-model", name: "pi-prompt-template-model", descKey: "pkg.prompt_template" },
|
|
19
|
+
{ id: "npm:@pi-unipi/notify", name: "@pi-unipi/notify", descKey: "pkg.notify" },
|
|
20
|
+
];
|
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,16 @@ 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.",
|
|
149
|
+
"changelog.0_8_0_1": "Settings: added a package browser dialog with \"Recommended\" and \"Search\" tabs.",
|
|
150
|
+
"changelog.0_8_0_2": "Search tab fuzzy-searches npm for pi packages, filterable by All/Available/Installed.",
|
|
151
|
+
"changelog.0_8_0_3": "One-click install with an installed-state indicator.",
|
|
132
152
|
"changelog.0_7_0_1": "Added a floating \"User Guide\" button (bottom-right) with a project overview and per-module how-to.",
|
|
133
153
|
"changelog.0_7_0_2": "Added \"Join Telegram group\" entry points (sidebar + user guide dialog).",
|
|
134
154
|
"changelog.0_7_0_3": "Added this \"What’s New\" changelog dialog.",
|
|
@@ -454,9 +474,9 @@ const en: Record<string, string> = {
|
|
|
454
474
|
"settings.updates_failed": "Update check failed",
|
|
455
475
|
"settings.update_all": "Update All",
|
|
456
476
|
"settings.updating": "Updating…",
|
|
457
|
-
"settings.update_success": "Successfully updated {0}
|
|
458
|
-
"settings.update_failed_names": "{0}
|
|
459
|
-
"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.",
|
|
460
480
|
"settings.defaults": "Defaults",
|
|
461
481
|
"settings.default_provider": "Default Provider",
|
|
462
482
|
"settings.default_model": "Default Model",
|
|
@@ -473,6 +493,30 @@ const en: Record<string, string> = {
|
|
|
473
493
|
"settings.packages": "Extensions & Packages",
|
|
474
494
|
"settings.package_placeholder": "npm:package-name",
|
|
475
495
|
"settings.add": "Add",
|
|
496
|
+
"settings.install": "Install",
|
|
497
|
+
"settings.browse_packages": "Browse & add packages",
|
|
498
|
+
"settings.tab_recommended": "Recommended",
|
|
499
|
+
"settings.tab_search": "Search",
|
|
500
|
+
"settings.filter_all": "All",
|
|
501
|
+
"settings.filter_available": "Available",
|
|
502
|
+
"settings.filter_installed": "Installed",
|
|
503
|
+
"settings.search_packages_placeholder": "Search packages (fuzzy)…",
|
|
504
|
+
"settings.no_packages_found": "No matching packages found",
|
|
505
|
+
"settings.installed": "Installed",
|
|
506
|
+
"settings.view_on_npm": "View on npm",
|
|
507
|
+
"settings.recommended_packages": "Recommended packages (one-click install)",
|
|
508
|
+
"settings.install_all_recommended": "Install all recommended",
|
|
509
|
+
"settings.add_custom_package": "Add another package manually…",
|
|
510
|
+
"pkg.hermes_memory": "Long-term memory (read/write + consolidation)",
|
|
511
|
+
"pkg.context_mode": "Large-output handling & knowledge base",
|
|
512
|
+
"pkg.subagents": "Subagents & multi-step orchestration",
|
|
513
|
+
"pkg.web_access": "Web fetch & search",
|
|
514
|
+
"pkg.smart_fetch": "Browser-fingerprint web fetch",
|
|
515
|
+
"pkg.rtk_optimizer": "RTK optimization & conventions",
|
|
516
|
+
"pkg.puppeteer": "Browser automation",
|
|
517
|
+
"pkg.intercom": "Cross-session coordination",
|
|
518
|
+
"pkg.prompt_template": "Custom prompt templates & models",
|
|
519
|
+
"pkg.notify": "Cross-platform notifications",
|
|
476
520
|
"settings.no_packages": "No packages installed",
|
|
477
521
|
"settings.import_export": "Import & Export",
|
|
478
522
|
"settings.export": "Export Config",
|
|
@@ -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,16 @@ 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": "修正:メモリとサブエージェントのモデル選択を実際のドロップダウンに。サブエージェントは保存後すぐ反映。",
|
|
147
|
+
"changelog.0_8_0_1": "設定:「おすすめ」と「検索」タブを備えたパッケージ検索ダイアログを追加。",
|
|
148
|
+
"changelog.0_8_0_2": "検索タブは npm の pi パッケージをあいまい検索し、すべて/未インストール/インストール済みで絞り込み可能。",
|
|
149
|
+
"changelog.0_8_0_3": "ワンクリックインストールとインストール済み表示。",
|
|
130
150
|
"changelog.0_7_0_1": "右下にフローティング「使い方」ボタンを追加。プロジェクト概要と各モジュールの使い方を表示。",
|
|
131
151
|
"changelog.0_7_0_2": "「Telegram グループに参加」の入口を追加(サイドバー + 使い方ダイアログ)。",
|
|
132
152
|
"changelog.0_7_0_3": "この「更新情報」ダイアログを追加。",
|
|
@@ -446,9 +466,9 @@ const ja: Record<string, string> = {
|
|
|
446
466
|
"settings.updates_failed": "アップデート確認に失敗しました",
|
|
447
467
|
"settings.update_all": "一括更新",
|
|
448
468
|
"settings.updating": "更新中…",
|
|
449
|
-
"settings.update_success": "{0}
|
|
450
|
-
"settings.update_failed_names": "{0}
|
|
451
|
-
"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 を再起動してください。",
|
|
452
472
|
"settings.defaults": "デフォルト",
|
|
453
473
|
"settings.default_provider": "デフォルトプロバイダー",
|
|
454
474
|
"settings.default_model": "デフォルトモデル",
|
|
@@ -465,6 +485,30 @@ const ja: Record<string, string> = {
|
|
|
465
485
|
"settings.packages": "拡張機能とパッケージ",
|
|
466
486
|
"settings.package_placeholder": "npm:package-name",
|
|
467
487
|
"settings.add": "追加",
|
|
488
|
+
"settings.install": "インストール",
|
|
489
|
+
"settings.browse_packages": "パッケージを検索して追加",
|
|
490
|
+
"settings.tab_recommended": "おすすめ",
|
|
491
|
+
"settings.tab_search": "検索",
|
|
492
|
+
"settings.filter_all": "すべて",
|
|
493
|
+
"settings.filter_available": "未インストール",
|
|
494
|
+
"settings.filter_installed": "インストール済み",
|
|
495
|
+
"settings.search_packages_placeholder": "パッケージを検索(あいまい)…",
|
|
496
|
+
"settings.no_packages_found": "一致するパッケージがありません",
|
|
497
|
+
"settings.installed": "インストール済み",
|
|
498
|
+
"settings.view_on_npm": "npm で見る",
|
|
499
|
+
"settings.recommended_packages": "おすすめパッケージ(ワンクリック)",
|
|
500
|
+
"settings.install_all_recommended": "おすすめを一括インストール",
|
|
501
|
+
"settings.add_custom_package": "他のパッケージを手動追加…",
|
|
502
|
+
"pkg.hermes_memory": "長期メモリ(読み書き・統合)",
|
|
503
|
+
"pkg.context_mode": "大出力処理と知識ベース",
|
|
504
|
+
"pkg.subagents": "サブエージェントと多段編成",
|
|
505
|
+
"pkg.web_access": "Web取得と検索",
|
|
506
|
+
"pkg.smart_fetch": "ブラウザ指紋レベルのWeb取得",
|
|
507
|
+
"pkg.rtk_optimizer": "RTK最適化と規約",
|
|
508
|
+
"pkg.puppeteer": "ブラウザ自動化",
|
|
509
|
+
"pkg.intercom": "セッション間連携",
|
|
510
|
+
"pkg.prompt_template": "カスタムプロンプトとモデル",
|
|
511
|
+
"pkg.notify": "クロスプラットフォーム通知",
|
|
468
512
|
"settings.no_packages": "パッケージがインストールされていません",
|
|
469
513
|
"settings.import_export": "インポートとエクスポート",
|
|
470
514
|
"settings.export": "設定をエクスポート",
|
|
@@ -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,16 @@ 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": "修复:记忆与子代理的模型选择改为真正的下拉;子代理保存后面板即时更新。",
|
|
147
|
+
"changelog.0_8_0_1": "设置:新增扩展包浏览弹窗,含「推荐安装」与「搜索」两个标签页。",
|
|
148
|
+
"changelog.0_8_0_2": "搜索标签支持模糊搜索 npm 上的 pi 扩展包,并可按全部/未安装/已安装筛选。",
|
|
149
|
+
"changelog.0_8_0_3": "扩展包一键安装,已安装状态自动标识。",
|
|
130
150
|
"changelog.0_7_0_1": "新增右下角浮动「使用说明」按钮,弹窗含项目总览与各板块使用指引。",
|
|
131
151
|
"changelog.0_7_0_2": "新增「加入 Telegram 交流群」入口(侧边栏 + 使用说明弹窗)。",
|
|
132
152
|
"changelog.0_7_0_3": "新增本「版本更新说明」弹窗。",
|
|
@@ -446,9 +466,9 @@ const zhCN: Record<string, string> = {
|
|
|
446
466
|
"settings.updates_failed": "检查更新失败",
|
|
447
467
|
"settings.update_all": "一键更新",
|
|
448
468
|
"settings.updating": "更新中…",
|
|
449
|
-
"settings.update_success": "已成功更新 {0}
|
|
450
|
-
"settings.update_failed_names": "{0}
|
|
451
|
-
"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。",
|
|
452
472
|
"settings.defaults": "默认值",
|
|
453
473
|
"settings.default_provider": "默认提供商",
|
|
454
474
|
"settings.default_model": "默认模型",
|
|
@@ -465,6 +485,30 @@ const zhCN: Record<string, string> = {
|
|
|
465
485
|
"settings.packages": "扩展与包",
|
|
466
486
|
"settings.package_placeholder": "npm:package-name",
|
|
467
487
|
"settings.add": "添加",
|
|
488
|
+
"settings.install": "安装",
|
|
489
|
+
"settings.browse_packages": "浏览并添加扩展包",
|
|
490
|
+
"settings.tab_recommended": "推荐安装",
|
|
491
|
+
"settings.tab_search": "搜索",
|
|
492
|
+
"settings.filter_all": "全部",
|
|
493
|
+
"settings.filter_available": "未安装",
|
|
494
|
+
"settings.filter_installed": "已安装",
|
|
495
|
+
"settings.search_packages_placeholder": "搜索扩展包(模糊匹配)…",
|
|
496
|
+
"settings.no_packages_found": "没有找到匹配的扩展包",
|
|
497
|
+
"settings.installed": "已安装",
|
|
498
|
+
"settings.view_on_npm": "在 npm 查看",
|
|
499
|
+
"settings.recommended_packages": "推荐扩展包(点击安装)",
|
|
500
|
+
"settings.install_all_recommended": "一键安装全部推荐",
|
|
501
|
+
"settings.add_custom_package": "手动添加其他包…",
|
|
502
|
+
"pkg.hermes_memory": "长期记忆系统(记忆读写与整合)",
|
|
503
|
+
"pkg.context_mode": "大输出处理与知识库检索",
|
|
504
|
+
"pkg.subagents": "子代理与多步编排",
|
|
505
|
+
"pkg.web_access": "网页抓取与搜索",
|
|
506
|
+
"pkg.smart_fetch": "浏览器指纹级网页抓取",
|
|
507
|
+
"pkg.rtk_optimizer": "RTK 优化与规范",
|
|
508
|
+
"pkg.puppeteer": "浏览器自动化",
|
|
509
|
+
"pkg.intercom": "多会话协作通信",
|
|
510
|
+
"pkg.prompt_template": "自定义提示词模板与模型",
|
|
511
|
+
"pkg.notify": "跨平台通知",
|
|
468
512
|
"settings.no_packages": "未安装任何包",
|
|
469
513
|
"settings.import_export": "导入与导出",
|
|
470
514
|
"settings.export": "导出配置",
|