@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.
Files changed (37) hide show
  1. package/dist/index.html +18 -0
  2. package/index.html +2 -2
  3. package/package.json +7 -38
  4. package/public/apple-touch-icon.png +0 -0
  5. package/public/icon-192.png +0 -0
  6. package/public/icon-512.png +0 -0
  7. package/public/manifest.webmanifest +2 -2
  8. package/public/pi.svg +41 -6
  9. package/public/sw.js +51 -51
  10. package/server/pi-reader.ts +353 -237
  11. package/src/App.tsx +2 -0
  12. package/src/components/dashboard/DashboardPage.tsx +341 -56
  13. package/src/components/layout/AppShell.tsx +45 -13
  14. package/src/components/layout/Sidebar.tsx +78 -85
  15. package/src/components/providers/ProvidersModelsPage.tsx +125 -131
  16. package/src/components/sessions/MemoryPage.tsx +34 -13
  17. package/src/components/sessions/SessionsPage.tsx +20 -40
  18. package/src/components/settings/SettingsPage.tsx +6 -1
  19. package/src/components/speedtest/ModelSpeedTestPage.tsx +429 -0
  20. package/src/components/ui/EmptyState.tsx +7 -6
  21. package/src/components/ui/Modal.tsx +33 -25
  22. package/src/components/ui/StatCard.tsx +10 -13
  23. package/src/data/builtin-providers.test.ts +109 -0
  24. package/src/data/builtin-providers.ts +67 -44
  25. package/src/data/model-catalog.test.ts +122 -0
  26. package/src/data/model-catalog.ts +697 -478
  27. package/src/index.css +624 -210
  28. package/src/lib/translations/en.ts +88 -14
  29. package/src/lib/translations/ja.ts +88 -14
  30. package/src/lib/translations/zh-CN.ts +88 -14
  31. package/src/lib/translations/zh-TW.ts +87 -14
  32. package/src/main.tsx +97 -44
  33. package/src/types/index.ts +0 -1
  34. package/vite.config.ts +65 -164
  35. package/dist-electron/main/main.cjs +0 -2453
  36. package/src/data/mock-config.ts +0 -247
  37. package/src/data/mock-usage.ts +0 -151
@@ -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="#2563eb" />
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 class="bg-gray-950 text-gray-100 antialiased">
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.2",
4
+ "version": "0.4.4",
5
5
  "type": "module",
6
- "main": "dist-electron/main/main.cjs",
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
- "electron:dev": "node scripts/electron-dev.mjs",
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-electron": "^1.1.0",
80
- "vite-plugin-pwa": "^1.3.0"
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
- "dist-electron/**/*"
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
Binary file
Binary file
@@ -6,8 +6,8 @@
6
6
  "scope": "/",
7
7
  "display": "standalone",
8
8
  "orientation": "any",
9
- "background_color": "#0a0a0a",
10
- "theme_color": "#2563eb",
9
+ "background_color": "#05090d",
10
+ "theme_color": "#05090d",
11
11
  "icons": [
12
12
  {
13
13
  "src": "/icon-192.png",
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
- <linearGradient id="bg" x1="0" y1="0" x2="0.3" y2="1">
4
- <stop offset="0" stop-color="#3b82f6"/>
5
- <stop offset="1" stop-color="#1d4ed8"/>
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
- <rect x="0" y="0" width="512" height="512" rx="113" fill="url(#bg)"/>
9
- <text x="256" y="270" text-anchor="middle" dominant-baseline="central"
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="240" font-weight="500" fill="#ffffff">π</text>
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 (index.html / SPA routes): NETWORK-FIRST.
4
- // Every refresh must hit the network so deploys show up on a plain
5
- // Cmd+R reload; the cached copy is only a fallback for offline use.
6
- // - Static assets (hashed js/css): CACHE-FIRST — safe because Vite
7
- // fingerprints filenames, so a new deploy means new URLs.
8
- // - API calls: NETWORK-FIRST, fallback to cache.
9
- // Bump CACHE_VERSION when the SW logic changes to purge old caches.
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-v2";
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
- "/manifest.webmanifest",
19
- "/icon-192.png",
20
- "/icon-512.png",
21
- "/apple-touch-icon.png",
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((k) => !k.startsWith(CACHE_VERSION))
43
- .map((k) => caches.delete(k))
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
- // API calls: network-first, fallback to cache.
60
- if (url.pathname.startsWith("/api/")) {
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
- // HTML navigation: network-first so a plain refresh always gets the
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.status === 200 && response.type === "basic") {
69
+ if (response && response.ok && response.type === "basic") {
80
70
  const copy = response.clone();
81
- caches.open(RUNTIME_CACHE).then((cache) => cache.put("/index.html", copy));
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(() => caches.match("/index.html"))
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
- // Static assets: cache-first, then network (and cache the result).
91
- event.respondWith(
92
- caches.match(request).then((cached) => {
93
- if (cached) return cached;
94
- return fetch(request).then((response) => {
95
- if (!response || response.status !== 200 || response.type !== "basic") {
96
- return response;
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
  });