@raingor/pi-web-switch 0.4.2 → 0.4.4
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 +18 -0
- package/index.html +2 -2
- package/package.json +7 -38
- package/public/apple-touch-icon.png +0 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/manifest.webmanifest +2 -2
- package/public/pi.svg +41 -6
- package/public/sw.js +51 -51
- package/server/pi-reader.ts +353 -237
- package/src/App.tsx +2 -0
- package/src/components/dashboard/DashboardPage.tsx +341 -56
- package/src/components/layout/AppShell.tsx +45 -13
- package/src/components/layout/Sidebar.tsx +78 -85
- package/src/components/providers/ProvidersModelsPage.tsx +125 -131
- package/src/components/sessions/MemoryPage.tsx +34 -13
- package/src/components/sessions/SessionsPage.tsx +20 -40
- package/src/components/settings/SettingsPage.tsx +6 -1
- package/src/components/speedtest/ModelSpeedTestPage.tsx +429 -0
- package/src/components/ui/EmptyState.tsx +7 -6
- package/src/components/ui/Modal.tsx +33 -25
- package/src/components/ui/StatCard.tsx +10 -13
- package/src/data/builtin-providers.test.ts +109 -0
- package/src/data/builtin-providers.ts +67 -44
- package/src/data/model-catalog.test.ts +122 -0
- package/src/data/model-catalog.ts +697 -478
- package/src/index.css +624 -210
- package/src/lib/translations/en.ts +88 -14
- package/src/lib/translations/ja.ts +88 -14
- package/src/lib/translations/zh-CN.ts +88 -14
- package/src/lib/translations/zh-TW.ts +87 -14
- package/src/main.tsx +97 -44
- package/src/types/index.ts +0 -1
- package/vite.config.ts +65 -164
- package/dist-electron/main/main.cjs +0 -2453
- package/src/data/mock-config.ts +0 -247
- package/src/data/mock-usage.ts +0 -151
package/dist/index.html
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
|
+
<title>pi-switch — pi Configuration Manager</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="./pi.svg" />
|
|
8
|
+
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
9
|
+
<link rel="manifest" href="./manifest.webmanifest" />
|
|
10
|
+
<meta name="theme-color" content="#05090d" />
|
|
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-H-GOgQ3d.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="./assets/main-CpBGy80y.css">
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
package/index.html
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/pi.svg" />
|
|
8
8
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
9
9
|
<link rel="manifest" href="/manifest.webmanifest" />
|
|
10
|
-
<meta name="theme-color" content="#
|
|
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
12
|
</head>
|
|
13
|
-
<body
|
|
13
|
+
<body>
|
|
14
14
|
<div id="root"></div>
|
|
15
15
|
<script type="module" src="/src/main.tsx"></script>
|
|
16
16
|
</body>
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raingor/pi-web-switch",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist
|
|
6
|
+
"main": "dist/index.html",
|
|
7
7
|
"description": "Web UI for pi coding agent — live configuration management, session browser, and memory viewer",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"pi-package",
|
|
@@ -27,10 +27,7 @@
|
|
|
27
27
|
"dev": "vite",
|
|
28
28
|
"build": "tsc -b && vite build",
|
|
29
29
|
"preview": "vite preview",
|
|
30
|
-
"
|
|
31
|
-
"electron:build": "tsc -b && vite build && vite build --config vite.electron.config.ts && vite build --config vite.preload.config.ts && electron-builder",
|
|
32
|
-
"electron:preview": "vite build && vite build --config vite.electron.config.ts && vite build --config vite.preload.config.ts && env -u ELECTRON_RUN_AS_NODE npx electron .",
|
|
33
|
-
"tray:icon": "node scripts/generate-tray-icon.mjs"
|
|
30
|
+
"test": "vitest run"
|
|
34
31
|
},
|
|
35
32
|
"files": [
|
|
36
33
|
"pi-package",
|
|
@@ -69,15 +66,12 @@
|
|
|
69
66
|
"@types/react": "^19.1.2",
|
|
70
67
|
"@types/react-dom": "^19.1.2",
|
|
71
68
|
"@vitejs/plugin-react": "^4.4.1",
|
|
72
|
-
"electron": "^43.2.0",
|
|
73
|
-
"electron-builder": "^26.15.3",
|
|
74
|
-
"electron-vite": "^5.0.0",
|
|
75
69
|
"tailwindcss": "^4.1.4",
|
|
76
70
|
"typescript": "~5.8.3",
|
|
77
71
|
"undici": "^8.9.0",
|
|
78
72
|
"vite": "^6.3.2",
|
|
79
|
-
"vite-plugin-
|
|
80
|
-
"
|
|
73
|
+
"vite-plugin-pwa": "^1.3.0",
|
|
74
|
+
"vitest": "3.2.4"
|
|
81
75
|
},
|
|
82
76
|
"build": {
|
|
83
77
|
"appId": "com.raingor.pi-web-switch",
|
|
@@ -86,32 +80,7 @@
|
|
|
86
80
|
"output": "release"
|
|
87
81
|
},
|
|
88
82
|
"files": [
|
|
89
|
-
"dist/**/*"
|
|
90
|
-
|
|
91
|
-
],
|
|
92
|
-
"mac": {
|
|
93
|
-
"target": [
|
|
94
|
-
{
|
|
95
|
-
"target": "dmg"
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"target": "zip"
|
|
99
|
-
}
|
|
100
|
-
],
|
|
101
|
-
"icon": "build/icon.icns",
|
|
102
|
-
"extendInfo": {
|
|
103
|
-
"LSMinimumSystemVersion": "11.0.0",
|
|
104
|
-
"CFBundleName": "pi-web-switch"
|
|
105
|
-
},
|
|
106
|
-
"category": "public.app-category.developer-tools"
|
|
107
|
-
},
|
|
108
|
-
"win": {
|
|
109
|
-
"target": "nsis",
|
|
110
|
-
"icon": "build/icon.ico"
|
|
111
|
-
},
|
|
112
|
-
"linux": {
|
|
113
|
-
"target": "AppImage",
|
|
114
|
-
"icon": "build/icon.png"
|
|
115
|
-
}
|
|
83
|
+
"dist/**/*"
|
|
84
|
+
]
|
|
116
85
|
}
|
|
117
86
|
}
|
|
Binary file
|
package/public/icon-192.png
CHANGED
|
Binary file
|
package/public/icon-512.png
CHANGED
|
Binary file
|
package/public/pi.svg
CHANGED
|
@@ -1,12 +1,47 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" fill="none">
|
|
2
2
|
<defs>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<stop offset="
|
|
3
|
+
<!-- Brand blue diagonal gradient -->
|
|
4
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
5
|
+
<stop offset="0" stop-color="#6ba6fb"/>
|
|
6
|
+
<stop offset="0.5" stop-color="#2563eb"/>
|
|
7
|
+
<stop offset="1" stop-color="#1e40af"/>
|
|
6
8
|
</linearGradient>
|
|
9
|
+
<!-- Soft top gloss for a tactile, premium feel -->
|
|
10
|
+
<linearGradient id="gloss" x1="0" y1="0" x2="0" y2="1">
|
|
11
|
+
<stop offset="0" stop-color="#ffffff" stop-opacity="0.28"/>
|
|
12
|
+
<stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
|
|
13
|
+
</linearGradient>
|
|
14
|
+
<!-- Subtle vertical shading on the pi glyph -->
|
|
15
|
+
<linearGradient id="piGrad" x1="0" y1="0" x2="0" y2="1">
|
|
16
|
+
<stop offset="0" stop-color="#ffffff"/>
|
|
17
|
+
<stop offset="1" stop-color="#d9e6ff"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<!-- Toggle knob highlight -->
|
|
20
|
+
<radialGradient id="knob" cx="0.35" cy="0.3" r="0.85">
|
|
21
|
+
<stop offset="0" stop-color="#ffffff"/>
|
|
22
|
+
<stop offset="1" stop-color="#cdd9f2"/>
|
|
23
|
+
</radialGradient>
|
|
24
|
+
<filter id="softShadow" x="-30%" y="-30%" width="160%" height="160%">
|
|
25
|
+
<feDropShadow dx="0" dy="7" stdDeviation="11" flood-color="#0a2a6b" flood-opacity="0.38"/>
|
|
26
|
+
</filter>
|
|
27
|
+
<filter id="knobShadow" x="-60%" y="-60%" width="220%" height="220%">
|
|
28
|
+
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#0a2a6b" flood-opacity="0.35"/>
|
|
29
|
+
</filter>
|
|
7
30
|
</defs>
|
|
8
|
-
|
|
9
|
-
|
|
31
|
+
|
|
32
|
+
<!-- Squircle background -->
|
|
33
|
+
<rect x="0" y="0" width="512" height="512" rx="115" fill="url(#bg)"/>
|
|
34
|
+
<!-- Top gloss highlight -->
|
|
35
|
+
<rect x="0" y="0" width="512" height="250" rx="115" fill="url(#gloss)"/>
|
|
36
|
+
|
|
37
|
+
<!-- Toggle switch track (behind the pi) -->
|
|
38
|
+
<rect x="92" y="217" width="328" height="78" rx="39" fill="#ffffff" opacity="0.20"/>
|
|
39
|
+
<rect x="92" y="217" width="328" height="78" rx="39" fill="none" stroke="#ffffff" stroke-opacity="0.25" stroke-width="2"/>
|
|
40
|
+
<!-- Toggle knob in the "on" position -->
|
|
41
|
+
<circle cx="378" cy="256" r="30" fill="url(#knob)" filter="url(#knobShadow)"/>
|
|
42
|
+
|
|
43
|
+
<!-- Pi glyph -->
|
|
44
|
+
<text x="256" y="256" text-anchor="middle" dominant-baseline="central"
|
|
10
45
|
font-family="-apple-system, 'SF Pro Display', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
|
|
11
|
-
font-size="
|
|
46
|
+
font-size="258" font-weight="700" fill="url(#piGrad)" filter="url(#softShadow)">π</text>
|
|
12
47
|
</svg>
|
package/public/sw.js
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
// ─── pi-web-switch Service Worker ───────────────────────
|
|
2
2
|
// Strategy:
|
|
3
|
-
// - HTML navigation
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
3
|
+
// - HTML navigation: NETWORK-FIRST with `cache: no-store` so Cmd+R always
|
|
4
|
+
// receives the current index.html instead of an HTTP/SW cached shell.
|
|
5
|
+
// - API calls: NETWORK-ONLY. Configuration/auth data must never be restored
|
|
6
|
+
// from a stale service-worker cache.
|
|
7
|
+
// - Static assets: CACHE-FIRST. Vite fingerprints JS/CSS filenames, so a new
|
|
8
|
+
// build receives a new URL and cannot collide with an older asset.
|
|
9
|
+
//
|
|
10
|
+
// v3 also purges the v1 cache-first HTML shell that can remain registered on
|
|
11
|
+
// the same localhost origin and cause a blank page after a normal refresh.
|
|
10
12
|
|
|
11
|
-
const CACHE_VERSION = "pi-web-switch-
|
|
13
|
+
const CACHE_VERSION = "pi-web-switch-v3";
|
|
12
14
|
const STATIC_CACHE = `${CACHE_VERSION}-static`;
|
|
13
15
|
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
|
16
|
+
const CACHE_PREFIX = "pi-web-switch-";
|
|
17
|
+
const SCOPE_URL = self.registration.scope;
|
|
18
|
+
const OFFLINE_INDEX_URL = new URL("./index.html", SCOPE_URL).href;
|
|
14
19
|
|
|
15
|
-
// NOTE: index.html is intentionally NOT precached. Pre-caching it would
|
|
16
|
-
// make Cmd+R reloads serve the stale page after a deploy.
|
|
17
20
|
const PRECACHE_URLS = [
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
];
|
|
21
|
+
"./manifest.webmanifest",
|
|
22
|
+
"./icon-192.png",
|
|
23
|
+
"./icon-512.png",
|
|
24
|
+
"./apple-touch-icon.png",
|
|
25
|
+
].map((path) => new URL(path, SCOPE_URL).href);
|
|
23
26
|
|
|
24
|
-
// ─── Install: precache core assets ─────────────────────
|
|
25
27
|
self.addEventListener("install", (event) => {
|
|
26
28
|
event.waitUntil(
|
|
27
29
|
caches
|
|
@@ -31,7 +33,6 @@ self.addEventListener("install", (event) => {
|
|
|
31
33
|
);
|
|
32
34
|
});
|
|
33
35
|
|
|
34
|
-
// ─── Activate: clean old caches ────────────────────────
|
|
35
36
|
self.addEventListener("activate", (event) => {
|
|
36
37
|
event.waitUntil(
|
|
37
38
|
caches
|
|
@@ -39,66 +40,65 @@ self.addEventListener("activate", (event) => {
|
|
|
39
40
|
.then((keys) =>
|
|
40
41
|
Promise.all(
|
|
41
42
|
keys
|
|
42
|
-
.filter((
|
|
43
|
-
.map((
|
|
43
|
+
.filter((key) => key.startsWith(CACHE_PREFIX) && !key.startsWith(CACHE_VERSION))
|
|
44
|
+
.map((key) => caches.delete(key))
|
|
44
45
|
)
|
|
45
46
|
)
|
|
46
47
|
.then(() => self.clients.claim())
|
|
47
48
|
);
|
|
48
49
|
});
|
|
49
50
|
|
|
50
|
-
// ─── Fetch: strategy by request type ───────────────────
|
|
51
51
|
self.addEventListener("fetch", (event) => {
|
|
52
52
|
const { request } = event;
|
|
53
|
-
|
|
54
|
-
// Only handle GET; ignore cross-origin and chrome-extension.
|
|
55
53
|
if (request.method !== "GET") return;
|
|
54
|
+
|
|
56
55
|
const url = new URL(request.url);
|
|
57
56
|
if (url.origin !== self.location.origin) return;
|
|
58
57
|
|
|
59
|
-
//
|
|
60
|
-
if (url.pathname.
|
|
61
|
-
event.respondWith(
|
|
62
|
-
fetch(request)
|
|
63
|
-
.then((response) => {
|
|
64
|
-
const copy = response.clone();
|
|
65
|
-
caches.open(RUNTIME_CACHE).then((cache) => cache.put(request, copy));
|
|
66
|
-
return response;
|
|
67
|
-
})
|
|
68
|
-
.catch(() => caches.match(request))
|
|
69
|
-
);
|
|
58
|
+
// Never cache local configuration, authentication, or usage responses.
|
|
59
|
+
if (url.pathname.includes("/api/")) {
|
|
60
|
+
event.respondWith(fetch(request, { cache: "no-store" }));
|
|
70
61
|
return;
|
|
71
62
|
}
|
|
72
63
|
|
|
73
|
-
//
|
|
74
|
-
// latest build. Fall back to the last cached copy only when offline.
|
|
64
|
+
// Always revalidate the SPA shell. The cached copy is offline fallback only.
|
|
75
65
|
if (request.mode === "navigate") {
|
|
76
66
|
event.respondWith(
|
|
77
|
-
fetch(request)
|
|
67
|
+
fetch(request, { cache: "no-store" })
|
|
78
68
|
.then((response) => {
|
|
79
|
-
if (response && response.
|
|
69
|
+
if (response && response.ok && response.type === "basic") {
|
|
80
70
|
const copy = response.clone();
|
|
81
|
-
|
|
71
|
+
event.waitUntil(
|
|
72
|
+
caches.open(RUNTIME_CACHE).then((cache) => cache.put(OFFLINE_INDEX_URL, copy))
|
|
73
|
+
);
|
|
82
74
|
}
|
|
83
75
|
return response;
|
|
84
76
|
})
|
|
85
|
-
.catch(() =>
|
|
77
|
+
.catch(async () => {
|
|
78
|
+
const cached = await caches.match(OFFLINE_INDEX_URL);
|
|
79
|
+
return cached ?? new Response("Offline", {
|
|
80
|
+
status: 503,
|
|
81
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
82
|
+
});
|
|
83
|
+
})
|
|
86
84
|
);
|
|
87
85
|
return;
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
// Fingerprinted build assets are safe to cache by URL.
|
|
89
|
+
if (url.pathname.includes("/assets/")) {
|
|
90
|
+
event.respondWith(
|
|
91
|
+
caches.match(request).then(async (cached) => {
|
|
92
|
+
if (cached) return cached;
|
|
93
|
+
const response = await fetch(request);
|
|
94
|
+
if (response && response.ok && response.type === "basic") {
|
|
95
|
+
const copy = response.clone();
|
|
96
|
+
event.waitUntil(
|
|
97
|
+
caches.open(RUNTIME_CACHE).then((cache) => cache.put(request, copy))
|
|
98
|
+
);
|
|
97
99
|
}
|
|
98
|
-
const copy = response.clone();
|
|
99
|
-
caches.open(RUNTIME_CACHE).then((cache) => cache.put(request, copy));
|
|
100
100
|
return response;
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
104
|
});
|