@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
@@ -7,117 +7,110 @@ import {
7
7
  Plug,
8
8
  Users,
9
9
  Globe,
10
+ ChevronDown,
11
+ X,
12
+ Orbit,
13
+ Gauge,
10
14
  } from "lucide-react";
11
15
  import { cn } from "@/lib/utils";
12
16
  import { useTranslation, LANGUAGES } from "@/lib/i18n";
13
17
  import { useState } from "react";
14
18
 
15
19
  const navItems = [
16
- { to: "/", icon: LayoutDashboard, key: "nav.dashboard" },
17
- { to: "/sessions", icon: History, key: "nav.sessions" },
18
- { to: "/memory", icon: Brain, key: "nav.memory" },
19
- { to: "/providers", icon: Plug, key: "nav.providers_models" },
20
- { to: "/subagents", icon: Users, key: "nav.subagents" },
21
- { to: "/settings", icon: Settings, key: "nav.settings" },
20
+ { to: "/", icon: LayoutDashboard, key: "nav.dashboard", code: "01" },
21
+ { to: "/sessions", icon: History, key: "nav.sessions", code: "02" },
22
+ { to: "/memory", icon: Brain, key: "nav.memory", code: "03" },
23
+ { to: "/providers", icon: Plug, key: "nav.providers_models", code: "04" },
24
+ { to: "/subagents", icon: Users, key: "nav.subagents", code: "05" },
25
+ { to: "/settings", icon: Settings, key: "nav.settings", code: "06" },
26
+ { to: "/speed-test", icon: Gauge, key: "nav.speed_test", code: "07" },
22
27
  ];
23
28
 
24
- export function Sidebar() {
29
+ interface SidebarProps {
30
+ mobileOpen?: boolean;
31
+ onClose?: () => void;
32
+ }
33
+
34
+ export function Sidebar({ mobileOpen = false, onClose }: SidebarProps) {
25
35
  const { t, lang, setLang } = useTranslation();
26
36
  const [langOpen, setLangOpen] = useState(false);
27
37
 
28
38
  return (
29
- <aside
30
- className="flex h-screen w-64 flex-col border-r"
31
- style={{
32
- backgroundColor: "var(--sidebar-bg)",
33
- borderColor: "var(--sidebar-border)",
34
- }}
35
- >
36
- {/* Logo — pt-12 leaves room for the macOS traffic-light buttons in the
37
- immersive (hiddenInset) title bar so they don't overlap the logo.
38
- WebkitAppRegion: drag restores window dragging since the native
39
- title bar is hidden. */}
40
- <div
41
- className="flex items-center gap-3 border-b px-6 pt-12 pb-5"
42
- style={{
43
- borderColor: "var(--sidebar-border)",
44
- userSelect: "none",
45
- WebkitUserSelect: "none",
46
- // Electron-only: lets the user drag the frameless (hiddenInset) window
47
- // from the sidebar header. TypeScript doesn't know the non-standard
48
- // -webkit-app-region property, so cast it explicitly.
49
- ...({ WebkitAppRegion: "drag" } as React.CSSProperties),
50
- }}
51
- >
52
- <img src="/pi.svg" alt="pi-switch" className="h-9 w-9 rounded-lg" />
53
- <div>
54
- <h1 className="text-base font-semibold" style={{ color: "var(--page-text)" }}>pi-switch</h1>
55
- <p className="text-xs" style={{ color: "var(--subtle-text)" }}>{t("app.subtitle")}</p>
39
+ <aside className={cn("command-sidebar", mobileOpen && "is-open")}>
40
+ <div className="sidebar-edge" aria-hidden="true" />
41
+
42
+ <div className="brand-block">
43
+ <div className="brand-mark">
44
+ <img src="/pi.svg" alt="pi-switch" />
45
+ <span className="brand-orbit" aria-hidden="true" />
46
+ </div>
47
+ <div className="min-w-0">
48
+ <div className="brand-name">pi-switch</div>
49
+ <div className="brand-subtitle">{t("app.subtitle")}</div>
56
50
  </div>
51
+ <button className="sidebar-close" aria-label="Close navigation" onClick={onClose}>
52
+ <X className="h-4 w-4" />
53
+ </button>
54
+ </div>
55
+
56
+ <div className="sidebar-system-label">
57
+ <span>CONTROL MATRIX</span>
58
+ <span className="sidebar-label-line" />
57
59
  </div>
58
60
 
59
- {/* Navigation */}
60
- <nav className="flex-1 space-y-1 px-3 py-4">
61
- {navItems.map(({ to, icon: Icon, key }) => (
61
+ <nav className="command-nav">
62
+ {navItems.map(({ to, icon: Icon, key, code }) => (
62
63
  <NavLink
63
64
  key={to}
64
65
  to={to}
65
66
  end={to === "/"}
66
- className={({ isActive }) =>
67
- cn(
68
- "flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
69
- )
70
- }
71
- style={({ isActive }) => ({
72
- backgroundColor: isActive ? "var(--sidebar-active-bg)" : "transparent",
73
- color: isActive ? "var(--sidebar-active-text)" : "var(--sidebar-text)",
74
- })}
67
+ onClick={onClose}
68
+ className={({ isActive }) => cn("command-nav-item", isActive && "is-active")}
75
69
  >
76
- <Icon className="h-4 w-4" />
77
- {t(key)}
70
+ <span className="nav-code">{code}</span>
71
+ <span className="nav-icon"><Icon className="h-4 w-4" /></span>
72
+ <span className="nav-label">{t(key)}</span>
73
+ <span className="nav-signal" aria-hidden="true" />
78
74
  </NavLink>
79
75
  ))}
80
76
  </nav>
81
77
 
82
- {/* Language Switcher */}
83
- <div className="border-t px-3 py-3" style={{ borderColor: "var(--sidebar-border)" }}>
84
- <button
85
- onClick={() => setLangOpen(!langOpen)}
86
- className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors"
87
- style={{ color: "var(--sidebar-text)" }}
88
- >
89
- <Globe className="h-4 w-4" />
90
- <span className="flex-1 text-left">{LANGUAGES.find((l) => l.code === lang)?.nativeLabel || "English"}</span>
91
- </button>
92
- {langOpen && (
93
- <div className="mt-1 space-y-0.5 px-1">
94
- {LANGUAGES.map((l) => (
95
- <button
96
- key={l.code}
97
- onClick={() => { setLang(l.code); setLangOpen(false); }}
98
- className={cn(
99
- "flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors",
100
- lang === l.code ? "bg-blue-600/10 text-blue-400" : ""
101
- )}
102
- style={{
103
- color: lang === l.code ? "var(--sidebar-active-text)" : "var(--sidebar-text)",
104
- backgroundColor: lang === l.code ? "var(--sidebar-active-bg)" : "transparent",
105
- }}
106
- >
107
- {l.nativeLabel}
108
- </button>
109
- ))}
78
+ <div className="sidebar-footer">
79
+ <div className="node-status">
80
+ <div className="node-status-icon"><Orbit className="h-4 w-4" /></div>
81
+ <div>
82
+ <span className="node-status-label">LOCAL NODE</span>
83
+ <span className="node-status-value"><i /> SYSTEM ONLINE</span>
110
84
  </div>
111
- )}
112
- </div>
85
+ </div>
113
86
 
114
- {/* Version */}
115
- <div
116
- className="border-t px-6 py-3"
117
- style={{ borderColor: "var(--sidebar-border)" }}
118
- >
119
- <p className="text-xs" style={{ color: "var(--subtle-text)" }}>{t("app.version")}</p>
87
+ <div className="language-panel">
88
+ <button onClick={() => setLangOpen(!langOpen)} className="language-trigger">
89
+ <Globe className="h-4 w-4" />
90
+ <span>{LANGUAGES.find((l) => l.code === lang)?.nativeLabel || "English"}</span>
91
+ <ChevronDown className={cn("ml-auto h-3.5 w-3.5 transition-transform", langOpen && "rotate-180")} />
92
+ </button>
93
+ {langOpen && (
94
+ <div className="language-menu">
95
+ {LANGUAGES.map((l) => (
96
+ <button
97
+ key={l.code}
98
+ onClick={() => { setLang(l.code); setLangOpen(false); }}
99
+ className={cn("language-option", lang === l.code && "is-selected")}
100
+ >
101
+ <span>{l.nativeLabel}</span>
102
+ {lang === l.code && <i />}
103
+ </button>
104
+ ))}
105
+ </div>
106
+ )}
107
+ </div>
108
+
109
+ <div className="version-strip">
110
+ <span>{t("app.version")}</span>
111
+ <span>BUILD // STABLE</span>
112
+ </div>
120
113
  </div>
121
114
  </aside>
122
115
  );
123
- }
116
+ }