@raingor/pi-web-switch 0.7.0 → 0.8.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 +12 -0
- package/README.md +12 -0
- package/README.zh-CN.md +12 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/pi-reader.ts +44 -0
- package/src/components/help/ChangelogButton.tsx +53 -0
- package/src/components/layout/Sidebar.tsx +2 -1
- package/src/components/sessions/MemoryPage.tsx +30 -11
- package/src/components/settings/PackageBrowser.tsx +222 -0
- package/src/components/settings/SettingsPage.tsx +97 -27
- package/src/data/changelog.ts +55 -0
- package/src/data/recommended-packages.ts +20 -0
- package/src/index.css +22 -0
- package/src/lib/translations/en.ts +42 -1
- package/src/lib/translations/ja.ts +42 -1
- package/src/lib/translations/zh-CN.ts +42 -1
- package/src/lib/translations/zh-TW.ts +42 -1
- package/vite.config.ts +11 -0
package/README.ja.md
CHANGED
|
@@ -167,6 +167,18 @@ pi-web-switch/
|
|
|
167
167
|
└── src/ # React フロントエンド
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
## 💬 コミュニティ
|
|
171
|
+
|
|
172
|
+
ご質問・ご提案・バグ報告は **Telegram グループ** へどうぞ:
|
|
173
|
+
|
|
174
|
+
👉 **[pi-web-switch Telegram グループに参加](https://t.me/+ODpy7_7NlOE4NzA1)**
|
|
175
|
+
|
|
176
|
+
問題を報告する際は以下を添えてください:
|
|
177
|
+
|
|
178
|
+
1. OS(macOS / Windows / Linux)
|
|
179
|
+
2. バージョン —— `npm view @raingor/pi-web-switch version`
|
|
180
|
+
3. 具体的な問題の説明とエラーのスクリーンショットまたはログ
|
|
181
|
+
|
|
170
182
|
## 🔗 リンク
|
|
171
183
|
|
|
172
184
|
- **ホームページ:** [raingor.github.io/my-blog](https://raingor.github.io/my-blog/)
|
package/README.md
CHANGED
|
@@ -330,6 +330,18 @@ pi-web-switch/
|
|
|
330
330
|
└── src/ # React frontend
|
|
331
331
|
```
|
|
332
332
|
|
|
333
|
+
## 💬 Community
|
|
334
|
+
|
|
335
|
+
Join the **Telegram group** for questions, suggestions, and bug reports:
|
|
336
|
+
|
|
337
|
+
👉 **[Join the pi-web-switch Telegram group](https://t.me/+ODpy7_7NlOE4NzA1)**
|
|
338
|
+
|
|
339
|
+
When reporting an issue, please include:
|
|
340
|
+
|
|
341
|
+
1. Your OS (macOS / Windows / Linux)
|
|
342
|
+
2. Your version — `npm view @raingor/pi-web-switch version`
|
|
343
|
+
3. A clear description plus any error screenshots or logs
|
|
344
|
+
|
|
333
345
|
## 🔗 Links
|
|
334
346
|
|
|
335
347
|
- **Homepage:** [raingor.github.io/my-blog](https://raingor.github.io/my-blog/)
|
package/README.zh-CN.md
CHANGED
|
@@ -205,6 +205,18 @@ pi-web-switch/
|
|
|
205
205
|
└── src/ # React 前端
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
## 💬 交流群
|
|
209
|
+
|
|
210
|
+
有任何疑问、建议或 bug 反馈,欢迎加入 **Telegram 交流群**:
|
|
211
|
+
|
|
212
|
+
👉 **[加入 pi-web-switch Telegram 交流群](https://t.me/+ODpy7_7NlOE4NzA1)**
|
|
213
|
+
|
|
214
|
+
提问时请附上:
|
|
215
|
+
|
|
216
|
+
1. 你的系统(macOS / Windows / Linux)
|
|
217
|
+
2. 版本号 —— `npm view @raingor/pi-web-switch version`
|
|
218
|
+
3. 清晰的问题描述,以及报错截图或日志
|
|
219
|
+
|
|
208
220
|
## 🔗 相关链接
|
|
209
221
|
|
|
210
222
|
- **个人主页:** [raingor.github.io/my-blog](https://raingor.github.io/my-blog/)
|
package/dist/index.html
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
<link rel="manifest" href="./manifest.webmanifest" />
|
|
10
10
|
<meta name="theme-color" content="#05090d" />
|
|
11
11
|
<meta name="description" content="Web UI for pi coding agent — configuration management, session browser, and usage dashboard" />
|
|
12
|
-
<script type="module" crossorigin src="./assets/main-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="./assets/main-
|
|
12
|
+
<script type="module" crossorigin src="./assets/main-DJ0fZLIg.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="./assets/main-K50Fa8uc.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="root"></div>
|
package/package.json
CHANGED
package/server/pi-reader.ts
CHANGED
|
@@ -2754,6 +2754,50 @@ export function readSubagents(): SubagentsData {
|
|
|
2754
2754
|
};
|
|
2755
2755
|
}
|
|
2756
2756
|
|
|
2757
|
+
// ─── Package Search (npm registry) ─────────────────────────
|
|
2758
|
+
// pi packages are npm packages tagged for pi. The pi.dev/packages catalog is
|
|
2759
|
+
// SSR-only (no public JSON API — /api/packages returns 501), so we query the
|
|
2760
|
+
// public npm registry search endpoint directly, which supports fuzzy text and
|
|
2761
|
+
// returns name/description/downloads.
|
|
2762
|
+
|
|
2763
|
+
export interface PackageSearchResult {
|
|
2764
|
+
name: string;
|
|
2765
|
+
description: string;
|
|
2766
|
+
version: string;
|
|
2767
|
+
downloads: number;
|
|
2768
|
+
link: string;
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
export async function searchPackages(query: string): Promise<PackageSearchResult[]> {
|
|
2772
|
+
const q = query.trim();
|
|
2773
|
+
// Bias the search toward pi extensions. When the user types nothing we still
|
|
2774
|
+
// surface the most popular pi packages.
|
|
2775
|
+
const text = q ? `${q} pi` : "pi-extension";
|
|
2776
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(text)}&size=40`;
|
|
2777
|
+
try {
|
|
2778
|
+
const res = await fetchExternal(url, { signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS) });
|
|
2779
|
+
if (!res.ok) return [];
|
|
2780
|
+
const data = (await res.json()) as {
|
|
2781
|
+
objects?: { package?: { name?: string; description?: string; version?: string; links?: { npm?: string } }; searchScore?: number }[];
|
|
2782
|
+
};
|
|
2783
|
+
const rows = (data.objects ?? [])
|
|
2784
|
+
.map((o) => o.package)
|
|
2785
|
+
.filter((p): p is NonNullable<typeof p> => !!p?.name)
|
|
2786
|
+
// Keep pi-related packages only (name or description mentions pi).
|
|
2787
|
+
.filter((p) => /(^|[@/-])pi([-/]|$)|pi coding|pi extension|pi agent/i.test(`${p.name} ${p.description ?? ""}`))
|
|
2788
|
+
.map((p) => ({
|
|
2789
|
+
name: p.name!,
|
|
2790
|
+
description: p.description ?? "",
|
|
2791
|
+
version: p.version ?? "",
|
|
2792
|
+
downloads: 0,
|
|
2793
|
+
link: p.links?.npm ?? `https://www.npmjs.com/package/${p.name}`,
|
|
2794
|
+
}));
|
|
2795
|
+
return rows;
|
|
2796
|
+
} catch {
|
|
2797
|
+
return [];
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2757
2801
|
const AGENT_NAME_RE = /^[\w.-]+\.md$/;
|
|
2758
2802
|
|
|
2759
2803
|
/**
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Sparkles } from "lucide-react";
|
|
3
|
+
import { Modal } from "@/components/ui/Modal";
|
|
4
|
+
import { useTranslation } from "@/lib/i18n";
|
|
5
|
+
import { CHANGELOG } from "@/data/changelog";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* "What's new" changelog dialog. Trigger lives in the sidebar footer (version
|
|
9
|
+
* strip). Entries come from src/data/changelog.ts with i18n item keys.
|
|
10
|
+
*/
|
|
11
|
+
export function ChangelogButton() {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const [open, setOpen] = useState(false);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<button
|
|
18
|
+
onClick={() => setOpen(true)}
|
|
19
|
+
className="changelog-trigger"
|
|
20
|
+
title={t("changelog.title")}
|
|
21
|
+
>
|
|
22
|
+
<Sparkles className="h-3.5 w-3.5" />
|
|
23
|
+
<span>{t("changelog.button")}</span>
|
|
24
|
+
</button>
|
|
25
|
+
|
|
26
|
+
<Modal open={open} onClose={() => setOpen(false)} title={t("changelog.title")} size="lg">
|
|
27
|
+
<div className="space-y-5">
|
|
28
|
+
{CHANGELOG.map((entry) => (
|
|
29
|
+
<div key={entry.version}>
|
|
30
|
+
<div className="flex items-baseline gap-2">
|
|
31
|
+
<span
|
|
32
|
+
className="rounded-md px-2 py-0.5 text-xs font-semibold"
|
|
33
|
+
style={{ backgroundColor: "color-mix(in srgb, var(--signal-cyan) 16%, transparent)", color: "var(--signal-cyan, #38bdf8)" }}
|
|
34
|
+
>
|
|
35
|
+
v{entry.version}
|
|
36
|
+
</span>
|
|
37
|
+
<span className="text-xs" style={{ color: "var(--subtle-text)" }}>{entry.date}</span>
|
|
38
|
+
</div>
|
|
39
|
+
<ul className="mt-2 space-y-1.5">
|
|
40
|
+
{entry.itemKeys.map((k) => (
|
|
41
|
+
<li key={k} className="flex gap-2 text-sm leading-relaxed" style={{ color: "var(--muted-text)" }}>
|
|
42
|
+
<span style={{ color: "var(--signal-cyan, #38bdf8)" }}>›</span>
|
|
43
|
+
<span>{t(k)}</span>
|
|
44
|
+
</li>
|
|
45
|
+
))}
|
|
46
|
+
</ul>
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
</Modal>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { cn } from "@/lib/utils";
|
|
17
17
|
import { useTranslation, LANGUAGES } from "@/lib/i18n";
|
|
18
18
|
import { useState } from "react";
|
|
19
|
+
import { ChangelogButton } from "@/components/help/ChangelogButton";
|
|
19
20
|
|
|
20
21
|
// Telegram group invite link (same as the help dialog).
|
|
21
22
|
const TELEGRAM_GROUP_URL = "https://t.me/+ODpy7_7NlOE4NzA1";
|
|
@@ -122,7 +123,7 @@ export function Sidebar({ mobileOpen = false, onClose }: SidebarProps) {
|
|
|
122
123
|
|
|
123
124
|
<div className="version-strip">
|
|
124
125
|
<span>{t("app.version")}</span>
|
|
125
|
-
<
|
|
126
|
+
<ChangelogButton />
|
|
126
127
|
</div>
|
|
127
128
|
</div>
|
|
128
129
|
</aside>
|
|
@@ -372,7 +372,7 @@ function MemoryFileSection({
|
|
|
372
372
|
|
|
373
373
|
export function MemoryPage() {
|
|
374
374
|
const { t } = useTranslation();
|
|
375
|
-
const { initialized, allModels } = useConfigStore();
|
|
375
|
+
const { initialized, allModels, allProviders } = useConfigStore();
|
|
376
376
|
const [files, setFiles] = useState<MemoryFile[]>([]);
|
|
377
377
|
const [loading, setLoading] = useState(true);
|
|
378
378
|
const [refreshing, setRefreshing] = useState(false);
|
|
@@ -396,6 +396,16 @@ export function MemoryPage() {
|
|
|
396
396
|
|
|
397
397
|
// Capacity status (per-target chars vs limit) for progress bars
|
|
398
398
|
const [status, setStatus] = useState<{ filename: string; target: string; chars: number; limit: number }[]>([]);
|
|
399
|
+
|
|
400
|
+
// Models eligible as the memory-write model: custom providers, plus built-in
|
|
401
|
+
// providers that have an API key actually saved (hasAuth). Built-ins without
|
|
402
|
+
// a key can't run, so they're excluded from the dropdown.
|
|
403
|
+
const eligibleModels = useMemo(() => {
|
|
404
|
+
const usableProviderIds = new Set(
|
|
405
|
+
allProviders.filter((p) => p.type === "custom" || p.hasAuth).map((p) => p.id)
|
|
406
|
+
);
|
|
407
|
+
return allModels.filter((m) => usableProviderIds.has(m.providerId));
|
|
408
|
+
}, [allModels, allProviders]);
|
|
399
409
|
const loadStatus = useCallback(() => {
|
|
400
410
|
fetch("/api/pi/memory/status")
|
|
401
411
|
.then((r) => r.json())
|
|
@@ -493,7 +503,11 @@ export function MemoryPage() {
|
|
|
493
503
|
});
|
|
494
504
|
const { success } = (await res.json()) as { success: boolean };
|
|
495
505
|
setCfgMsg({ ok: !!success, text: success ? t("memory.config_saved") : t("memory.config_save_failed") });
|
|
496
|
-
if (success)
|
|
506
|
+
if (success) {
|
|
507
|
+
loadStatus();
|
|
508
|
+
// Close the modal shortly after showing the success message.
|
|
509
|
+
setTimeout(() => setShowConfig(false), 600);
|
|
510
|
+
}
|
|
497
511
|
} catch {
|
|
498
512
|
setCfgMsg({ ok: false, text: t("memory.config_save_failed") });
|
|
499
513
|
} finally {
|
|
@@ -724,20 +738,25 @@ export function MemoryPage() {
|
|
|
724
738
|
<p className="text-xs" style={{ color: "var(--muted-text)" }}>{t("memory.config_desc")}</p>
|
|
725
739
|
<div>
|
|
726
740
|
<label className="mb-1 block text-xs" style={{ color: "var(--muted-text)" }}>{t("memory.config_model")}</label>
|
|
727
|
-
<
|
|
728
|
-
type="text"
|
|
729
|
-
list="memory-model-options"
|
|
741
|
+
<select
|
|
730
742
|
value={cfg.llmModelOverride}
|
|
731
743
|
onChange={(e) => setCfg({ ...cfg, llmModelOverride: e.target.value })}
|
|
732
|
-
placeholder={t("memory.config_model_default")}
|
|
733
744
|
className="w-full rounded-lg border px-3 py-1.5 text-sm outline-none focus:ring-1 focus:ring-blue-500"
|
|
734
745
|
style={{ backgroundColor: "var(--card-bg)", borderColor: "var(--card-border)", color: "var(--page-text)" }}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
{
|
|
738
|
-
|
|
746
|
+
>
|
|
747
|
+
<option value="">{t("memory.config_model_default")}</option>
|
|
748
|
+
{/* If the saved model isn't in the current model list (e.g. a
|
|
749
|
+
provider was removed), still show it so it isn't silently lost. */}
|
|
750
|
+
{cfg.llmModelOverride &&
|
|
751
|
+
!eligibleModels.some((m) => `${m.providerId}/${m.id}` === cfg.llmModelOverride) && (
|
|
752
|
+
<option value={cfg.llmModelOverride}>{cfg.llmModelOverride}</option>
|
|
753
|
+
)}
|
|
754
|
+
{eligibleModels.map((m) => (
|
|
755
|
+
<option key={`${m.providerId}/${m.id}`} value={`${m.providerId}/${m.id}`}>
|
|
756
|
+
{m.providerName} · {m.name ?? m.id}
|
|
757
|
+
</option>
|
|
739
758
|
))}
|
|
740
|
-
</
|
|
759
|
+
</select>
|
|
741
760
|
</div>
|
|
742
761
|
<div className="flex gap-3">
|
|
743
762
|
<div className="flex-1">
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
+
import { Search, Plus, Check, Loader2, ExternalLink, Sparkles } from "lucide-react";
|
|
3
|
+
import { Modal } from "@/components/ui/Modal";
|
|
4
|
+
import { useTranslation } from "@/lib/i18n";
|
|
5
|
+
import { RECOMMENDED_PACKAGES } from "@/data/recommended-packages";
|
|
6
|
+
|
|
7
|
+
interface PackageSearchResult {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
version: string;
|
|
11
|
+
downloads: number;
|
|
12
|
+
link: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type PackageFilter = "all" | "installed" | "available";
|
|
16
|
+
type Tab = "recommended" | "search";
|
|
17
|
+
|
|
18
|
+
interface PackageBrowserProps {
|
|
19
|
+
open: boolean;
|
|
20
|
+
onClose: () => void;
|
|
21
|
+
installed: Set<string>; // ids like "npm:pkg-name"
|
|
22
|
+
onInstall: (id: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One package row with an install / installed control. */
|
|
26
|
+
function PackageRow({
|
|
27
|
+
name,
|
|
28
|
+
description,
|
|
29
|
+
link,
|
|
30
|
+
installed,
|
|
31
|
+
onInstall,
|
|
32
|
+
}: {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
link?: string;
|
|
36
|
+
installed: boolean;
|
|
37
|
+
onInstall: () => void;
|
|
38
|
+
}) {
|
|
39
|
+
const { t } = useTranslation();
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className="flex items-center gap-3 rounded-lg border px-3 py-2"
|
|
43
|
+
style={{ borderColor: "var(--card-border)", backgroundColor: "var(--card-bg)" }}
|
|
44
|
+
>
|
|
45
|
+
<div className="min-w-0 flex-1">
|
|
46
|
+
<div className="flex items-center gap-2">
|
|
47
|
+
<span className="truncate text-sm font-medium" style={{ color: "var(--page-text)" }}>{name}</span>
|
|
48
|
+
<a
|
|
49
|
+
href={link ?? `https://www.npmjs.com/package/${name}`}
|
|
50
|
+
target="_blank"
|
|
51
|
+
rel="noreferrer"
|
|
52
|
+
className="shrink-0"
|
|
53
|
+
style={{ color: "var(--subtle-text)" }}
|
|
54
|
+
title={t("settings.view_on_npm")}
|
|
55
|
+
>
|
|
56
|
+
<ExternalLink className="h-3 w-3" />
|
|
57
|
+
</a>
|
|
58
|
+
</div>
|
|
59
|
+
{description && (
|
|
60
|
+
<p className="truncate text-xs" style={{ color: "var(--muted-text)" }}>{description}</p>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
{installed ? (
|
|
64
|
+
<span className="flex shrink-0 items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium"
|
|
65
|
+
style={{ borderColor: "rgba(16,185,129,0.4)", color: "#10b981" }}>
|
|
66
|
+
<Check className="h-3.5 w-3.5" />
|
|
67
|
+
{t("settings.installed")}
|
|
68
|
+
</span>
|
|
69
|
+
) : (
|
|
70
|
+
<button
|
|
71
|
+
onClick={onInstall}
|
|
72
|
+
className="flex shrink-0 items-center gap-1 rounded-lg border border-blue-600/50 bg-blue-600/10 px-3 py-1.5 text-xs font-medium text-blue-400 hover:bg-blue-600/20"
|
|
73
|
+
>
|
|
74
|
+
<Plus className="h-3.5 w-3.5" />
|
|
75
|
+
{t("settings.install")}
|
|
76
|
+
</button>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Package browser modal with two tabs:
|
|
84
|
+
* - Recommended: the curated list from src/data/recommended-packages.ts
|
|
85
|
+
* - Search: fuzzy npm-registry search for any pi package
|
|
86
|
+
* Installing writes "npm:<name>" into settings.packages.
|
|
87
|
+
*/
|
|
88
|
+
export function PackageBrowser({ open, onClose, installed, onInstall }: PackageBrowserProps) {
|
|
89
|
+
const { t } = useTranslation();
|
|
90
|
+
const [tab, setTab] = useState<Tab>("recommended");
|
|
91
|
+
const [query, setQuery] = useState("");
|
|
92
|
+
const [results, setResults] = useState<PackageSearchResult[]>([]);
|
|
93
|
+
const [loading, setLoading] = useState(false);
|
|
94
|
+
const [justAdded, setJustAdded] = useState<Set<string>>(new Set());
|
|
95
|
+
const [filter, setFilter] = useState<PackageFilter>("all");
|
|
96
|
+
const debounceRef = useRef<number | undefined>(undefined);
|
|
97
|
+
|
|
98
|
+
const runSearch = useCallback((q: string) => {
|
|
99
|
+
setLoading(true);
|
|
100
|
+
fetch(`/api/pi/packages/search?q=${encodeURIComponent(q)}`)
|
|
101
|
+
.then((r) => r.json())
|
|
102
|
+
.then((d: { results?: PackageSearchResult[] }) => setResults(d.results ?? []))
|
|
103
|
+
.catch(() => setResults([]))
|
|
104
|
+
.finally(() => setLoading(false));
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
// Load popular packages the first time the Search tab is opened; debounce typing.
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!open || tab !== "search") return;
|
|
110
|
+
window.clearTimeout(debounceRef.current);
|
|
111
|
+
debounceRef.current = window.setTimeout(() => runSearch(query), 350);
|
|
112
|
+
return () => window.clearTimeout(debounceRef.current);
|
|
113
|
+
}, [query, open, tab, runSearch]);
|
|
114
|
+
|
|
115
|
+
const isInstalledId = (name: string) => {
|
|
116
|
+
const id = `npm:${name}`;
|
|
117
|
+
return installed.has(id) || justAdded.has(id);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleInstall = (id: string) => {
|
|
121
|
+
onInstall(id);
|
|
122
|
+
setJustAdded((prev) => new Set(prev).add(id));
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Search-tab results after the installed/available filter.
|
|
126
|
+
const filtered = results.filter((pkg) => {
|
|
127
|
+
if (filter === "installed") return isInstalledId(pkg.name);
|
|
128
|
+
if (filter === "available") return !isInstalledId(pkg.name);
|
|
129
|
+
return true;
|
|
130
|
+
});
|
|
131
|
+
const installedCount = results.filter((p) => isInstalledId(p.name)).length;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<Modal open={open} onClose={onClose} title={t("settings.browse_packages")} size="lg">
|
|
135
|
+
<div className="space-y-4">
|
|
136
|
+
{/* Tabs */}
|
|
137
|
+
<div className="flex gap-1 rounded-lg p-1" style={{ backgroundColor: "var(--card-bg)" }}>
|
|
138
|
+
{(["recommended", "search"] as Tab[]).map((tKey) => (
|
|
139
|
+
<button
|
|
140
|
+
key={tKey}
|
|
141
|
+
onClick={() => setTab(tKey)}
|
|
142
|
+
className={`flex flex-1 items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${tab === tKey ? "bg-blue-600/15 text-blue-400" : "text-gray-400 hover:text-gray-200"}`}
|
|
143
|
+
>
|
|
144
|
+
{tKey === "recommended" ? <Sparkles className="h-3.5 w-3.5" /> : <Search className="h-3.5 w-3.5" />}
|
|
145
|
+
{t(`settings.tab_${tKey}`)}
|
|
146
|
+
</button>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{tab === "recommended" ? (
|
|
151
|
+
<div className="max-h-[55vh] space-y-1.5 overflow-y-auto">
|
|
152
|
+
{RECOMMENDED_PACKAGES.map((pkg) => (
|
|
153
|
+
<PackageRow
|
|
154
|
+
key={pkg.id}
|
|
155
|
+
name={pkg.name}
|
|
156
|
+
description={t(pkg.descKey)}
|
|
157
|
+
installed={installed.has(pkg.id) || justAdded.has(pkg.id)}
|
|
158
|
+
onInstall={() => handleInstall(pkg.id)}
|
|
159
|
+
/>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
) : (
|
|
163
|
+
<>
|
|
164
|
+
{/* Search box */}
|
|
165
|
+
<div className="relative">
|
|
166
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2" style={{ color: "var(--subtle-text)" }} />
|
|
167
|
+
<input
|
|
168
|
+
type="text"
|
|
169
|
+
autoFocus
|
|
170
|
+
value={query}
|
|
171
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
172
|
+
placeholder={t("settings.search_packages_placeholder")}
|
|
173
|
+
className="w-full rounded-lg border py-2 pl-9 pr-3 text-sm outline-none focus:ring-1 focus:ring-blue-500"
|
|
174
|
+
style={{ backgroundColor: "var(--card-bg)", borderColor: "var(--card-border)", color: "var(--page-text)" }}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{/* Installed / available filter */}
|
|
179
|
+
<div className="flex gap-1 rounded-lg p-1" style={{ backgroundColor: "var(--card-bg)" }}>
|
|
180
|
+
{(["all", "available", "installed"] as PackageFilter[]).map((f) => {
|
|
181
|
+
const count = f === "all" ? results.length : f === "installed" ? installedCount : results.length - installedCount;
|
|
182
|
+
return (
|
|
183
|
+
<button
|
|
184
|
+
key={f}
|
|
185
|
+
onClick={() => setFilter(f)}
|
|
186
|
+
className={`flex-1 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${filter === f ? "bg-blue-600/15 text-blue-400" : "text-gray-400 hover:text-gray-200"}`}
|
|
187
|
+
>
|
|
188
|
+
{t(`settings.filter_${f}`)} ({count})
|
|
189
|
+
</button>
|
|
190
|
+
);
|
|
191
|
+
})}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Results */}
|
|
195
|
+
<div className="max-h-[42vh] space-y-1.5 overflow-y-auto">
|
|
196
|
+
{loading && results.length === 0 ? (
|
|
197
|
+
<div className="flex items-center justify-center py-10">
|
|
198
|
+
<Loader2 className="h-5 w-5 animate-spin" style={{ color: "var(--muted-text)" }} />
|
|
199
|
+
</div>
|
|
200
|
+
) : filtered.length === 0 ? (
|
|
201
|
+
<p className="py-10 text-center text-sm" style={{ color: "var(--muted-text)" }}>
|
|
202
|
+
{t("settings.no_packages_found")}
|
|
203
|
+
</p>
|
|
204
|
+
) : (
|
|
205
|
+
filtered.map((pkg) => (
|
|
206
|
+
<PackageRow
|
|
207
|
+
key={pkg.name}
|
|
208
|
+
name={pkg.name}
|
|
209
|
+
description={pkg.description}
|
|
210
|
+
link={pkg.link}
|
|
211
|
+
installed={isInstalledId(pkg.name)}
|
|
212
|
+
onInstall={() => handleInstall(`npm:${pkg.name}`)}
|
|
213
|
+
/>
|
|
214
|
+
))
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
</>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
</Modal>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
} from "@/lib/config";
|
|
12
12
|
import type { PiConfig, UpdateCheckResult } from "@/types";
|
|
13
13
|
import { cn } from "@/lib/utils";
|
|
14
|
+
import { RECOMMENDED_PACKAGES } from "@/data/recommended-packages";
|
|
15
|
+
import { PackageBrowser } from "./PackageBrowser";
|
|
14
16
|
import {
|
|
15
17
|
Download,
|
|
16
18
|
Upload,
|
|
@@ -102,6 +104,7 @@ export function SettingsPage() {
|
|
|
102
104
|
const [newPackage, setNewPackage] = useState("");
|
|
103
105
|
const [importError, setImportError] = useState("");
|
|
104
106
|
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
|
107
|
+
const [showPackageBrowser, setShowPackageBrowser] = useState(false);
|
|
105
108
|
const [fontSize, setFontSize] = useState(() => {
|
|
106
109
|
const saved = Number(localStorage.getItem(FONT_SIZE_KEY));
|
|
107
110
|
return saved >= 12 && saved <= 24 ? saved : 16;
|
|
@@ -569,6 +572,15 @@ export function SettingsPage() {
|
|
|
569
572
|
</Card>
|
|
570
573
|
|
|
571
574
|
<Card icon={Package} title={t("settings.packages")}>
|
|
575
|
+
<div className="mb-4">
|
|
576
|
+
<button
|
|
577
|
+
onClick={() => setShowPackageBrowser(true)}
|
|
578
|
+
className="flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-500"
|
|
579
|
+
>
|
|
580
|
+
<Plus className="h-4 w-4" />
|
|
581
|
+
{t("settings.browse_packages")}
|
|
582
|
+
</button>
|
|
583
|
+
</div>
|
|
572
584
|
{(settings?.packages ?? []).length > 0 && (
|
|
573
585
|
<div className="mb-4 flex flex-wrap gap-1.5">
|
|
574
586
|
{(settings?.packages ?? []).map((pkg) => (
|
|
@@ -590,33 +602,84 @@ export function SettingsPage() {
|
|
|
590
602
|
{(settings?.packages ?? []).length === 0 && (
|
|
591
603
|
<p className="mb-4 text-sm text-gray-500">{t("settings.no_packages")}</p>
|
|
592
604
|
)}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
605
|
+
|
|
606
|
+
{/* Recommended packages — one-click install */}
|
|
607
|
+
{(() => {
|
|
608
|
+
const installed = new Set(settings?.packages ?? []);
|
|
609
|
+
const recommended = RECOMMENDED_PACKAGES.filter((p) => !installed.has(p.id));
|
|
610
|
+
if (recommended.length === 0) return null;
|
|
611
|
+
return (
|
|
612
|
+
<div className="mb-4">
|
|
613
|
+
<p className="mb-2 text-xs font-medium" style={{ color: "var(--muted-text)" }}>
|
|
614
|
+
{t("settings.recommended_packages")}
|
|
615
|
+
</p>
|
|
616
|
+
<div className="space-y-1.5">
|
|
617
|
+
{recommended.map((pkg) => (
|
|
618
|
+
<div
|
|
619
|
+
key={pkg.id}
|
|
620
|
+
className="flex items-center gap-3 rounded-lg border border-gray-700 bg-gray-800/50 px-3 py-2"
|
|
621
|
+
>
|
|
622
|
+
<div className="min-w-0 flex-1">
|
|
623
|
+
<p className="truncate text-sm text-gray-200">{pkg.name}</p>
|
|
624
|
+
<p className="truncate text-xs text-gray-500">{t(pkg.descKey)}</p>
|
|
625
|
+
</div>
|
|
626
|
+
<button
|
|
627
|
+
onClick={() => addPackage(pkg.id)}
|
|
628
|
+
className="flex shrink-0 items-center gap-1 rounded-lg border border-blue-600/50 bg-blue-600/10 px-3 py-1.5 text-xs font-medium text-blue-400 hover:bg-blue-600/20"
|
|
629
|
+
>
|
|
630
|
+
<Plus className="h-3.5 w-3.5" />
|
|
631
|
+
{t("settings.install")}
|
|
632
|
+
</button>
|
|
633
|
+
</div>
|
|
634
|
+
))}
|
|
635
|
+
</div>
|
|
636
|
+
<button
|
|
637
|
+
onClick={() => {
|
|
638
|
+
const list = settings?.packages ?? [];
|
|
639
|
+
const toAdd = recommended.map((p) => p.id);
|
|
640
|
+
updateSettings({ packages: [...list, ...toAdd] });
|
|
641
|
+
}}
|
|
642
|
+
className="mt-2 flex items-center gap-1.5 rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 text-xs text-gray-300 hover:bg-gray-700"
|
|
643
|
+
>
|
|
644
|
+
<Plus className="h-3.5 w-3.5" />
|
|
645
|
+
{t("settings.install_all_recommended")}
|
|
646
|
+
</button>
|
|
647
|
+
</div>
|
|
648
|
+
);
|
|
649
|
+
})()}
|
|
650
|
+
|
|
651
|
+
<details className="mb-1">
|
|
652
|
+
<summary className="cursor-pointer text-xs text-gray-500 hover:text-gray-300">
|
|
653
|
+
{t("settings.add_custom_package")}
|
|
654
|
+
</summary>
|
|
655
|
+
<div className="mt-2 flex max-w-md gap-2">
|
|
656
|
+
<input
|
|
657
|
+
type="text"
|
|
658
|
+
value={newPackage}
|
|
659
|
+
onChange={(e) => setNewPackage(e.target.value)}
|
|
660
|
+
placeholder={t("settings.package_placeholder")}
|
|
661
|
+
className="flex-1 rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 text-sm text-white placeholder-gray-500"
|
|
662
|
+
onKeyDown={(e) => {
|
|
663
|
+
if (e.key === "Enter" && newPackage.trim()) {
|
|
664
|
+
addPackage(newPackage.trim());
|
|
665
|
+
setNewPackage("");
|
|
666
|
+
}
|
|
667
|
+
}}
|
|
668
|
+
/>
|
|
669
|
+
<button
|
|
670
|
+
onClick={() => {
|
|
671
|
+
if (newPackage.trim()) {
|
|
672
|
+
addPackage(newPackage.trim());
|
|
673
|
+
setNewPackage("");
|
|
674
|
+
}
|
|
675
|
+
}}
|
|
676
|
+
className="flex items-center gap-1 rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 text-sm text-gray-300 hover:bg-gray-700"
|
|
677
|
+
>
|
|
678
|
+
<Plus className="h-3.5 w-3.5" />
|
|
679
|
+
{t("settings.add")}
|
|
680
|
+
</button>
|
|
681
|
+
</div>
|
|
682
|
+
</details>
|
|
620
683
|
</Card>
|
|
621
684
|
|
|
622
685
|
<Card icon={Download} title={t("settings.import_export")}>
|
|
@@ -655,6 +718,13 @@ export function SettingsPage() {
|
|
|
655
718
|
</div>
|
|
656
719
|
)}
|
|
657
720
|
|
|
721
|
+
<PackageBrowser
|
|
722
|
+
open={showPackageBrowser}
|
|
723
|
+
onClose={() => setShowPackageBrowser(false)}
|
|
724
|
+
installed={new Set(settings?.packages ?? [])}
|
|
725
|
+
onInstall={(id) => addPackage(id)}
|
|
726
|
+
/>
|
|
727
|
+
|
|
658
728
|
{/* Reset Confirm */}
|
|
659
729
|
<Modal
|
|
660
730
|
open={showResetConfirm}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Version changelog shown in the "What's new" dialog. Newest first.
|
|
2
|
+
// `items` are i18n keys so the entries localize with the active language.
|
|
3
|
+
// When cutting a release, prepend a new entry here.
|
|
4
|
+
|
|
5
|
+
export interface ChangelogEntry {
|
|
6
|
+
version: string;
|
|
7
|
+
date: string; // ISO date
|
|
8
|
+
itemKeys: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CHANGELOG: ChangelogEntry[] = [
|
|
12
|
+
{
|
|
13
|
+
version: "0.8.0",
|
|
14
|
+
date: "2026-08-27",
|
|
15
|
+
itemKeys: ["changelog.0_8_0_1", "changelog.0_8_0_2", "changelog.0_8_0_3"],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
version: "0.7.0",
|
|
19
|
+
date: "2026-08-27",
|
|
20
|
+
itemKeys: [
|
|
21
|
+
"changelog.0_7_0_1",
|
|
22
|
+
"changelog.0_7_0_2",
|
|
23
|
+
"changelog.0_7_0_3",
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
version: "0.6.2",
|
|
28
|
+
date: "2026-08-26",
|
|
29
|
+
itemKeys: ["changelog.0_6_2_1"],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
version: "0.6.1",
|
|
33
|
+
date: "2026-08-26",
|
|
34
|
+
itemKeys: ["changelog.0_6_1_1"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
version: "0.6.0",
|
|
38
|
+
date: "2026-08-26",
|
|
39
|
+
itemKeys: [
|
|
40
|
+
"changelog.0_6_0_1",
|
|
41
|
+
"changelog.0_6_0_2",
|
|
42
|
+
"changelog.0_6_0_3",
|
|
43
|
+
"changelog.0_6_0_4",
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
version: "0.5.0",
|
|
48
|
+
date: "2026-08-26",
|
|
49
|
+
itemKeys: [
|
|
50
|
+
"changelog.0_5_0_1",
|
|
51
|
+
"changelog.0_5_0_2",
|
|
52
|
+
"changelog.0_5_0_3",
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
];
|
|
@@ -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
|
@@ -710,3 +710,25 @@ table { border-collapse: collapse; }
|
|
|
710
710
|
transform: translateY(-1px);
|
|
711
711
|
box-shadow: 0 8px 22px color-mix(in srgb, #229ED9 22%, transparent);
|
|
712
712
|
}
|
|
713
|
+
|
|
714
|
+
/* ─── Changelog trigger (version strip) ─────────────────── */
|
|
715
|
+
.changelog-trigger {
|
|
716
|
+
display: inline-flex;
|
|
717
|
+
align-items: center;
|
|
718
|
+
gap: 5px;
|
|
719
|
+
padding: 2px 6px;
|
|
720
|
+
border-radius: 6px;
|
|
721
|
+
border: 1px solid transparent;
|
|
722
|
+
color: var(--muted-text);
|
|
723
|
+
font-family: var(--font-mono);
|
|
724
|
+
font-size: 9px;
|
|
725
|
+
letter-spacing: .06em;
|
|
726
|
+
text-transform: uppercase;
|
|
727
|
+
cursor: pointer;
|
|
728
|
+
transition: color 150ms ease, border-color 150ms ease, background 150ms ease;
|
|
729
|
+
}
|
|
730
|
+
.changelog-trigger:hover {
|
|
731
|
+
color: var(--signal-cyan);
|
|
732
|
+
border-color: color-mix(in srgb, var(--signal-cyan) 30%, var(--card-border));
|
|
733
|
+
background: color-mix(in srgb, var(--signal-cyan) 8%, transparent);
|
|
734
|
+
}
|
|
@@ -126,7 +126,24 @@ const en: Record<string, string> = {
|
|
|
126
126
|
"official_usage.no_result": "No quota result yet",
|
|
127
127
|
"official_usage.no_result_desc": "Enter an official usage endpoint and API key, then save to test the query and display quota usage.",
|
|
128
128
|
"app.subtitle": "Configuration Manager",
|
|
129
|
-
"app.version": "pi-switch v0.
|
|
129
|
+
"app.version": "pi-switch v0.7.0",
|
|
130
|
+
"changelog.button": "Changelog",
|
|
131
|
+
"changelog.title": "What's New",
|
|
132
|
+
"changelog.0_8_0_1": "Settings: added a package browser dialog with \"Recommended\" and \"Search\" tabs.",
|
|
133
|
+
"changelog.0_8_0_2": "Search tab fuzzy-searches npm for pi packages, filterable by All/Available/Installed.",
|
|
134
|
+
"changelog.0_8_0_3": "One-click install with an installed-state indicator.",
|
|
135
|
+
"changelog.0_7_0_1": "Added a floating \"User Guide\" button (bottom-right) with a project overview and per-module how-to.",
|
|
136
|
+
"changelog.0_7_0_2": "Added \"Join Telegram group\" entry points (sidebar + user guide dialog).",
|
|
137
|
+
"changelog.0_7_0_3": "Added this \"What’s New\" changelog dialog.",
|
|
138
|
+
"changelog.0_6_2_1": "Subagents: edit a custom agent’s model and thinking level directly from the panel.",
|
|
139
|
+
"changelog.0_6_1_1": "Fix: one-click optimize now runs in an async process, no longer freezing the dev server or auto-reloading the page.",
|
|
140
|
+
"changelog.0_6_0_1": "Memory: added \"Optimize Memory\" to consolidate all three memory files at once.",
|
|
141
|
+
"changelog.0_6_0_2": "Memory: added auto-write model config plus capacity limits and overflow strategy.",
|
|
142
|
+
"changelog.0_6_0_3": "Memory: added per-file capacity progress bars and live optimize progress.",
|
|
143
|
+
"changelog.0_6_0_4": "Settings: removed the GitHub Copilot config section.",
|
|
144
|
+
"changelog.0_5_0_1": "Added the \"Model Speed Test\" module, replacing \"Official Usage\".",
|
|
145
|
+
"changelog.0_5_0_2": "Speed test: one-click fetch models (stored separately) and a slow mode to avoid rate limits.",
|
|
146
|
+
"changelog.0_5_0_3": "Providers page hides built-in providers for a cleaner UI.",
|
|
130
147
|
|
|
131
148
|
// Subagents
|
|
132
149
|
"subagents.summary": "{0} agents · {1} chains",
|
|
@@ -459,6 +476,30 @@ const en: Record<string, string> = {
|
|
|
459
476
|
"settings.packages": "Extensions & Packages",
|
|
460
477
|
"settings.package_placeholder": "npm:package-name",
|
|
461
478
|
"settings.add": "Add",
|
|
479
|
+
"settings.install": "Install",
|
|
480
|
+
"settings.browse_packages": "Browse & add packages",
|
|
481
|
+
"settings.tab_recommended": "Recommended",
|
|
482
|
+
"settings.tab_search": "Search",
|
|
483
|
+
"settings.filter_all": "All",
|
|
484
|
+
"settings.filter_available": "Available",
|
|
485
|
+
"settings.filter_installed": "Installed",
|
|
486
|
+
"settings.search_packages_placeholder": "Search packages (fuzzy)…",
|
|
487
|
+
"settings.no_packages_found": "No matching packages found",
|
|
488
|
+
"settings.installed": "Installed",
|
|
489
|
+
"settings.view_on_npm": "View on npm",
|
|
490
|
+
"settings.recommended_packages": "Recommended packages (one-click install)",
|
|
491
|
+
"settings.install_all_recommended": "Install all recommended",
|
|
492
|
+
"settings.add_custom_package": "Add another package manually…",
|
|
493
|
+
"pkg.hermes_memory": "Long-term memory (read/write + consolidation)",
|
|
494
|
+
"pkg.context_mode": "Large-output handling & knowledge base",
|
|
495
|
+
"pkg.subagents": "Subagents & multi-step orchestration",
|
|
496
|
+
"pkg.web_access": "Web fetch & search",
|
|
497
|
+
"pkg.smart_fetch": "Browser-fingerprint web fetch",
|
|
498
|
+
"pkg.rtk_optimizer": "RTK optimization & conventions",
|
|
499
|
+
"pkg.puppeteer": "Browser automation",
|
|
500
|
+
"pkg.intercom": "Cross-session coordination",
|
|
501
|
+
"pkg.prompt_template": "Custom prompt templates & models",
|
|
502
|
+
"pkg.notify": "Cross-platform notifications",
|
|
462
503
|
"settings.no_packages": "No packages installed",
|
|
463
504
|
"settings.import_export": "Import & Export",
|
|
464
505
|
"settings.export": "Export Config",
|
|
@@ -124,7 +124,24 @@ const ja: Record<string, string> = {
|
|
|
124
124
|
"official_usage.no_result": "利用枠データはまだありません",
|
|
125
125
|
"official_usage.no_result_desc": "公式使用量 URL と API Key を入力して保存すると、照会テストを行い利用枠を表示します。",
|
|
126
126
|
"app.subtitle": "設定マネージャー",
|
|
127
|
-
"app.version": "pi-switch v0.
|
|
127
|
+
"app.version": "pi-switch v0.7.0",
|
|
128
|
+
"changelog.button": "変更履歴",
|
|
129
|
+
"changelog.title": "更新情報",
|
|
130
|
+
"changelog.0_8_0_1": "設定:「おすすめ」と「検索」タブを備えたパッケージ検索ダイアログを追加。",
|
|
131
|
+
"changelog.0_8_0_2": "検索タブは npm の pi パッケージをあいまい検索し、すべて/未インストール/インストール済みで絞り込み可能。",
|
|
132
|
+
"changelog.0_8_0_3": "ワンクリックインストールとインストール済み表示。",
|
|
133
|
+
"changelog.0_7_0_1": "右下にフローティング「使い方」ボタンを追加。プロジェクト概要と各モジュールの使い方を表示。",
|
|
134
|
+
"changelog.0_7_0_2": "「Telegram グループに参加」の入口を追加(サイドバー + 使い方ダイアログ)。",
|
|
135
|
+
"changelog.0_7_0_3": "この「更新情報」ダイアログを追加。",
|
|
136
|
+
"changelog.0_6_2_1": "サブエージェント:カスタムエージェントのモデルと思考レベルをパネルから直接編集可能に。",
|
|
137
|
+
"changelog.0_6_1_1": "修正:メモリ最適化を非同期プロセス化し、開発サーバーの停止やページ自動リロードを解消。",
|
|
138
|
+
"changelog.0_6_0_1": "メモリ:3 つのメモリファイルを一括統合する「メモリを最適化」を追加。",
|
|
139
|
+
"changelog.0_6_0_2": "メモリ:自動書き込みモデル設定、容量上限、上限超過戦略を追加。",
|
|
140
|
+
"changelog.0_6_0_3": "メモリ:ファイル別の容量プログレスバーと最適化のリアルタイム進捗を追加。",
|
|
141
|
+
"changelog.0_6_0_4": "設定:GitHub Copilot 設定セクションを削除。",
|
|
142
|
+
"changelog.0_5_0_1": "「モデル速度計測」モジュールを追加し、「公式使用量」を置き換え。",
|
|
143
|
+
"changelog.0_5_0_2": "計測:モデルのワンクリック取得(別途保存)とレート制限回避の低速モード。",
|
|
144
|
+
"changelog.0_5_0_3": "プロバイダーページで組み込みプロバイダーを非表示にしUIを簡素化。",
|
|
128
145
|
|
|
129
146
|
// Subagents
|
|
130
147
|
"subagents.summary": "{0} エージェント · {1} チェーン",
|
|
@@ -451,6 +468,30 @@ const ja: Record<string, string> = {
|
|
|
451
468
|
"settings.packages": "拡張機能とパッケージ",
|
|
452
469
|
"settings.package_placeholder": "npm:package-name",
|
|
453
470
|
"settings.add": "追加",
|
|
471
|
+
"settings.install": "インストール",
|
|
472
|
+
"settings.browse_packages": "パッケージを検索して追加",
|
|
473
|
+
"settings.tab_recommended": "おすすめ",
|
|
474
|
+
"settings.tab_search": "検索",
|
|
475
|
+
"settings.filter_all": "すべて",
|
|
476
|
+
"settings.filter_available": "未インストール",
|
|
477
|
+
"settings.filter_installed": "インストール済み",
|
|
478
|
+
"settings.search_packages_placeholder": "パッケージを検索(あいまい)…",
|
|
479
|
+
"settings.no_packages_found": "一致するパッケージがありません",
|
|
480
|
+
"settings.installed": "インストール済み",
|
|
481
|
+
"settings.view_on_npm": "npm で見る",
|
|
482
|
+
"settings.recommended_packages": "おすすめパッケージ(ワンクリック)",
|
|
483
|
+
"settings.install_all_recommended": "おすすめを一括インストール",
|
|
484
|
+
"settings.add_custom_package": "他のパッケージを手動追加…",
|
|
485
|
+
"pkg.hermes_memory": "長期メモリ(読み書き・統合)",
|
|
486
|
+
"pkg.context_mode": "大出力処理と知識ベース",
|
|
487
|
+
"pkg.subagents": "サブエージェントと多段編成",
|
|
488
|
+
"pkg.web_access": "Web取得と検索",
|
|
489
|
+
"pkg.smart_fetch": "ブラウザ指紋レベルのWeb取得",
|
|
490
|
+
"pkg.rtk_optimizer": "RTK最適化と規約",
|
|
491
|
+
"pkg.puppeteer": "ブラウザ自動化",
|
|
492
|
+
"pkg.intercom": "セッション間連携",
|
|
493
|
+
"pkg.prompt_template": "カスタムプロンプトとモデル",
|
|
494
|
+
"pkg.notify": "クロスプラットフォーム通知",
|
|
454
495
|
"settings.no_packages": "パッケージがインストールされていません",
|
|
455
496
|
"settings.import_export": "インポートとエクスポート",
|
|
456
497
|
"settings.export": "設定をエクスポート",
|
|
@@ -124,7 +124,24 @@ const zhCN: Record<string, string> = {
|
|
|
124
124
|
"official_usage.no_result": "暂未获取额度数据",
|
|
125
125
|
"official_usage.no_result_desc": "填写官方用量接口和 API Key,保存后会立即测试查询并显示额度。",
|
|
126
126
|
"app.subtitle": "配置管理器",
|
|
127
|
-
"app.version": "pi-switch v0.
|
|
127
|
+
"app.version": "pi-switch v0.7.0",
|
|
128
|
+
"changelog.button": "更新日志",
|
|
129
|
+
"changelog.title": "版本更新说明",
|
|
130
|
+
"changelog.0_8_0_1": "设置:新增扩展包浏览弹窗,含「推荐安装」与「搜索」两个标签页。",
|
|
131
|
+
"changelog.0_8_0_2": "搜索标签支持模糊搜索 npm 上的 pi 扩展包,并可按全部/未安装/已安装筛选。",
|
|
132
|
+
"changelog.0_8_0_3": "扩展包一键安装,已安装状态自动标识。",
|
|
133
|
+
"changelog.0_7_0_1": "新增右下角浮动「使用说明」按钮,弹窗含项目总览与各板块使用指引。",
|
|
134
|
+
"changelog.0_7_0_2": "新增「加入 Telegram 交流群」入口(侧边栏 + 使用说明弹窗)。",
|
|
135
|
+
"changelog.0_7_0_3": "新增本「版本更新说明」弹窗。",
|
|
136
|
+
"changelog.0_6_2_1": "子代理:支持在面板里直接编辑自定义代理的模型和思考级别。",
|
|
137
|
+
"changelog.0_6_1_1": "修复:一键优化改用异步进程,不再导致开发服务器卡顿和页面自动刷新。",
|
|
138
|
+
"changelog.0_6_0_1": "记忆:新增「一键优化记忆」,一次整合三个记忆文件。",
|
|
139
|
+
"changelog.0_6_0_2": "记忆:新增自动写入记忆所用模型的配置,及容量上限与超限策略设置。",
|
|
140
|
+
"changelog.0_6_0_3": "记忆:新增各记忆文件的容量进度条与优化实时进度。",
|
|
141
|
+
"changelog.0_6_0_4": "设置:移除 GitHub Copilot 配置板块。",
|
|
142
|
+
"changelog.0_5_0_1": "新增「模型测速」模块,替代原「官方用量查询」。",
|
|
143
|
+
"changelog.0_5_0_2": "测速支持一键获取模型(独立本地存储)与慢速模式防限流。",
|
|
144
|
+
"changelog.0_5_0_3": "提供商页隐藏内置供应商,精简界面。",
|
|
128
145
|
|
|
129
146
|
// Subagents
|
|
130
147
|
"subagents.summary": "{0} 个代理 · {1} 个流水线",
|
|
@@ -451,6 +468,30 @@ const zhCN: Record<string, string> = {
|
|
|
451
468
|
"settings.packages": "扩展与包",
|
|
452
469
|
"settings.package_placeholder": "npm:package-name",
|
|
453
470
|
"settings.add": "添加",
|
|
471
|
+
"settings.install": "安装",
|
|
472
|
+
"settings.browse_packages": "浏览并添加扩展包",
|
|
473
|
+
"settings.tab_recommended": "推荐安装",
|
|
474
|
+
"settings.tab_search": "搜索",
|
|
475
|
+
"settings.filter_all": "全部",
|
|
476
|
+
"settings.filter_available": "未安装",
|
|
477
|
+
"settings.filter_installed": "已安装",
|
|
478
|
+
"settings.search_packages_placeholder": "搜索扩展包(模糊匹配)…",
|
|
479
|
+
"settings.no_packages_found": "没有找到匹配的扩展包",
|
|
480
|
+
"settings.installed": "已安装",
|
|
481
|
+
"settings.view_on_npm": "在 npm 查看",
|
|
482
|
+
"settings.recommended_packages": "推荐扩展包(点击安装)",
|
|
483
|
+
"settings.install_all_recommended": "一键安装全部推荐",
|
|
484
|
+
"settings.add_custom_package": "手动添加其他包…",
|
|
485
|
+
"pkg.hermes_memory": "长期记忆系统(记忆读写与整合)",
|
|
486
|
+
"pkg.context_mode": "大输出处理与知识库检索",
|
|
487
|
+
"pkg.subagents": "子代理与多步编排",
|
|
488
|
+
"pkg.web_access": "网页抓取与搜索",
|
|
489
|
+
"pkg.smart_fetch": "浏览器指纹级网页抓取",
|
|
490
|
+
"pkg.rtk_optimizer": "RTK 优化与规范",
|
|
491
|
+
"pkg.puppeteer": "浏览器自动化",
|
|
492
|
+
"pkg.intercom": "多会话协作通信",
|
|
493
|
+
"pkg.prompt_template": "自定义提示词模板与模型",
|
|
494
|
+
"pkg.notify": "跨平台通知",
|
|
454
495
|
"settings.no_packages": "未安装任何包",
|
|
455
496
|
"settings.import_export": "导入与导出",
|
|
456
497
|
"settings.export": "导出配置",
|
|
@@ -123,7 +123,24 @@ const zhTW: Record<string, string> = {
|
|
|
123
123
|
"official_usage.no_result": "尚未取得額度資料",
|
|
124
124
|
"official_usage.no_result_desc": "填寫官方用量介面與 API Key,儲存後會立即測試查詢並顯示額度。",
|
|
125
125
|
"app.subtitle": "設定管理器",
|
|
126
|
-
"app.version": "pi-switch v0.
|
|
126
|
+
"app.version": "pi-switch v0.7.0",
|
|
127
|
+
"changelog.button": "更新日誌",
|
|
128
|
+
"changelog.title": "版本更新說明",
|
|
129
|
+
"changelog.0_8_0_1": "設定:新增擴充包瀏覽彈窗,含「推薦安裝」與「搜尋」兩個標籤頁。",
|
|
130
|
+
"changelog.0_8_0_2": "搜尋標籤支援模糊搜尋 npm 上的 pi 擴充包,並可按全部/未安裝/已安裝篩選。",
|
|
131
|
+
"changelog.0_8_0_3": "擴充包一鍵安裝,已安裝狀態自動標示。",
|
|
132
|
+
"changelog.0_7_0_1": "新增右下角浮動「使用說明」按鈕,彈窗含專案總覽與各板塊使用指引。",
|
|
133
|
+
"changelog.0_7_0_2": "新增「加入 Telegram 交流群」入口(側邊欄 + 使用說明彈窗)。",
|
|
134
|
+
"changelog.0_7_0_3": "新增本「版本更新說明」彈窗。",
|
|
135
|
+
"changelog.0_6_2_1": "子代理:支援在面板裡直接編輯自訂代理的模型和思考級別。",
|
|
136
|
+
"changelog.0_6_1_1": "修復:一鍵優化改用非同步進程,不再導致開發伺服器卡頓和頁面自動刷新。",
|
|
137
|
+
"changelog.0_6_0_1": "記憶:新增「一鍵優化記憶」,一次整合三個記憶檔。",
|
|
138
|
+
"changelog.0_6_0_2": "記憶:新增自動寫入記憶所用模型的設定,及容量上限與超限策略設置。",
|
|
139
|
+
"changelog.0_6_0_3": "記憶:新增各記憶檔的容量進度條與優化即時進度。",
|
|
140
|
+
"changelog.0_6_0_4": "設定:移除 GitHub Copilot 設定板塊。",
|
|
141
|
+
"changelog.0_5_0_1": "新增「模型測速」模組,替代原「官方用量查詢」。",
|
|
142
|
+
"changelog.0_5_0_2": "測速支援一鍵獲取模型(獨立本地儲存)與慢速模式防限流。",
|
|
143
|
+
"changelog.0_5_0_3": "提供商頁隱藏內建供應商,精簡介面。",
|
|
127
144
|
|
|
128
145
|
// Subagents
|
|
129
146
|
"subagents.summary": "{0} 個代理 · {1} 個流水線",
|
|
@@ -450,6 +467,30 @@ const zhTW: Record<string, string> = {
|
|
|
450
467
|
"settings.packages": "擴充功能與套件",
|
|
451
468
|
"settings.package_placeholder": "npm:package-name",
|
|
452
469
|
"settings.add": "新增",
|
|
470
|
+
"settings.install": "安裝",
|
|
471
|
+
"settings.browse_packages": "瀏覽並新增擴充包",
|
|
472
|
+
"settings.tab_recommended": "推薦安裝",
|
|
473
|
+
"settings.tab_search": "搜尋",
|
|
474
|
+
"settings.filter_all": "全部",
|
|
475
|
+
"settings.filter_available": "未安裝",
|
|
476
|
+
"settings.filter_installed": "已安裝",
|
|
477
|
+
"settings.search_packages_placeholder": "搜尋擴充包(模糊匹配)…",
|
|
478
|
+
"settings.no_packages_found": "沒有找到匹配的擴充包",
|
|
479
|
+
"settings.installed": "已安裝",
|
|
480
|
+
"settings.view_on_npm": "在 npm 檢視",
|
|
481
|
+
"settings.recommended_packages": "推薦擴充包(點擊安裝)",
|
|
482
|
+
"settings.install_all_recommended": "一鍵安裝全部推薦",
|
|
483
|
+
"settings.add_custom_package": "手動新增其他包…",
|
|
484
|
+
"pkg.hermes_memory": "長期記憶系統(記憶讀寫與整合)",
|
|
485
|
+
"pkg.context_mode": "大輸出處理與知識庫檢索",
|
|
486
|
+
"pkg.subagents": "子代理與多步編排",
|
|
487
|
+
"pkg.web_access": "網頁抓取與搜尋",
|
|
488
|
+
"pkg.smart_fetch": "瀏覽器指紋級網頁抓取",
|
|
489
|
+
"pkg.rtk_optimizer": "RTK 優化與規範",
|
|
490
|
+
"pkg.puppeteer": "瀏覽器自動化",
|
|
491
|
+
"pkg.intercom": "多會話協作通訊",
|
|
492
|
+
"pkg.prompt_template": "自訂提示詞模板與模型",
|
|
493
|
+
"pkg.notify": "跨平台通知",
|
|
453
494
|
"settings.no_packages": "未安裝任何套件",
|
|
454
495
|
"settings.import_export": "匯入與匯出",
|
|
455
496
|
"settings.export": "匯出設定",
|
package/vite.config.ts
CHANGED
|
@@ -148,6 +148,17 @@ function piApiPlugin(): Plugin {
|
|
|
148
148
|
res.setHeader("Content-Type", "application/json");
|
|
149
149
|
res.end(JSON.stringify(data));
|
|
150
150
|
},
|
|
151
|
+
"GET /api/pi/packages/search"(req, res) {
|
|
152
|
+
const parsed = new URL(req.url ?? "", "http://localhost");
|
|
153
|
+
const q = parsed.searchParams.get("q") ?? "";
|
|
154
|
+
pi.searchPackages(q).then((results: unknown) => {
|
|
155
|
+
res.setHeader("Content-Type", "application/json");
|
|
156
|
+
res.end(JSON.stringify({ results }));
|
|
157
|
+
}).catch(() => {
|
|
158
|
+
res.setHeader("Content-Type", "application/json");
|
|
159
|
+
res.end(JSON.stringify({ results: [] }));
|
|
160
|
+
});
|
|
161
|
+
},
|
|
151
162
|
"POST /api/pi/subagents/update-agent"(req, res) {
|
|
152
163
|
let body = "";
|
|
153
164
|
req.on("data", (chunk: string) => (body += chunk));
|