@raingor/pi-web-switch 0.2.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.
Files changed (41) hide show
  1. package/README.ja.md +137 -0
  2. package/README.md +277 -0
  3. package/README.zh-CN.md +176 -0
  4. package/index.html +13 -0
  5. package/package.json +44 -0
  6. package/pi-package/index.ts +100 -0
  7. package/pi-package/skills/pi-web-switch/SKILL.md +60 -0
  8. package/public/pi.svg +4 -0
  9. package/server/pi-reader.ts +678 -0
  10. package/src/App.tsx +25 -0
  11. package/src/components/dashboard/DashboardPage.tsx +607 -0
  12. package/src/components/layout/AppShell.tsx +18 -0
  13. package/src/components/layout/Sidebar.tsx +116 -0
  14. package/src/components/models/ModelsPage.tsx +570 -0
  15. package/src/components/providers/ProvidersPage.tsx +466 -0
  16. package/src/components/sessions/MemoryPage.tsx +177 -0
  17. package/src/components/sessions/SessionsPage.tsx +347 -0
  18. package/src/components/settings/SettingsPage.tsx +351 -0
  19. package/src/components/ui/Badge.tsx +29 -0
  20. package/src/components/ui/EmptyState.tsx +20 -0
  21. package/src/components/ui/Modal.tsx +41 -0
  22. package/src/components/ui/StatCard.tsx +37 -0
  23. package/src/data/builtin-providers.ts +148 -0
  24. package/src/data/mock-config.ts +261 -0
  25. package/src/data/mock-usage.ts +153 -0
  26. package/src/index.css +217 -0
  27. package/src/lib/config.ts +56 -0
  28. package/src/lib/currency.ts +48 -0
  29. package/src/lib/i18n.tsx +98 -0
  30. package/src/lib/translations/en.ts +168 -0
  31. package/src/lib/translations/index.ts +14 -0
  32. package/src/lib/translations/ja.ts +158 -0
  33. package/src/lib/translations/zh-CN.ts +158 -0
  34. package/src/lib/translations/zh-TW.ts +158 -0
  35. package/src/lib/utils.ts +51 -0
  36. package/src/main.tsx +106 -0
  37. package/src/store/config-store.ts +459 -0
  38. package/src/types/index.ts +187 -0
  39. package/src/vite-env.d.ts +1 -0
  40. package/tsconfig.json +24 -0
  41. package/vite.config.ts +172 -0
package/vite.config.ts ADDED
@@ -0,0 +1,172 @@
1
+ import { defineConfig, type Plugin } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import tailwindcss from "@tailwindcss/vite";
4
+ import path from "path";
5
+ import type { Connect } from "vite";
6
+
7
+ // ─── Pi Config API Plugin ───────────────────────────────
8
+
9
+ function piApiPlugin(): Plugin {
10
+ // Lazy-load the server-side module (Node.js only)
11
+ let pi: typeof import("./server/pi-reader");
12
+ let builtins: typeof import("./src/data/builtin-providers");
13
+
14
+ return {
15
+ name: "pi-api",
16
+ configureServer(server) {
17
+ // Load server-side modules
18
+ pi = require("./server/pi-reader");
19
+ builtins = require("./src/data/builtin-providers");
20
+
21
+ const routes: Record<string, (req: Connect.IncomingMessage, res: any) => void> = {
22
+ "GET /api/pi/settings"(_, res) {
23
+ const data = pi.readSettings();
24
+ res.setHeader("Content-Type", "application/json");
25
+ res.end(JSON.stringify(data ?? {}));
26
+ },
27
+ "POST /api/pi/settings"(req, res) {
28
+ let body = "";
29
+ req.on("data", (chunk: string) => (body += chunk));
30
+ req.on("end", () => {
31
+ const ok = pi.writeSettings(JSON.parse(body));
32
+ res.setHeader("Content-Type", "application/json");
33
+ res.end(JSON.stringify({ success: ok }));
34
+ });
35
+ },
36
+ "GET /api/pi/auth"(_, res) {
37
+ const data = pi.readAuth();
38
+ res.setHeader("Content-Type", "application/json");
39
+ res.end(JSON.stringify(data ?? {}));
40
+ },
41
+ "POST /api/pi/auth"(req, res) {
42
+ let body = "";
43
+ req.on("data", (chunk: string) => (body += chunk));
44
+ req.on("end", () => {
45
+ const ok = pi.writeAuth(JSON.parse(body));
46
+ res.setHeader("Content-Type", "application/json");
47
+ res.end(JSON.stringify({ success: ok }));
48
+ });
49
+ },
50
+ "GET /api/pi/models"(_, res) {
51
+ const data = pi.readModels();
52
+ res.setHeader("Content-Type", "application/json");
53
+ res.end(JSON.stringify(data ?? { providers: {} }));
54
+ },
55
+ "POST /api/pi/models"(req, res) {
56
+ let body = "";
57
+ req.on("data", (chunk: string) => (body += chunk));
58
+ req.on("end", () => {
59
+ const ok = pi.writeModels(JSON.parse(body));
60
+ res.setHeader("Content-Type", "application/json");
61
+ res.end(JSON.stringify({ success: ok }));
62
+ });
63
+ },
64
+ "GET /api/pi/builtin-providers"(_, res) {
65
+ res.setHeader("Content-Type", "application/json");
66
+ res.end(JSON.stringify(builtins.getBuiltinProviders()));
67
+ },
68
+ "GET /api/pi/usage"(_, res) {
69
+ const records = pi.readAllUsage();
70
+ const usage = {
71
+ records,
72
+ dailyAggregates: pi.getDailyAggregates(records),
73
+ providerSummaries: pi.getProviderSummaries(records),
74
+ modelSummaries: pi.getModelSummaries(records),
75
+ totals: pi.getTotals(records),
76
+ };
77
+ res.setHeader("Content-Type", "application/json");
78
+ res.end(JSON.stringify(usage));
79
+ },
80
+ "GET /api/pi/sessions"(_, res) {
81
+ const sessions = pi.listSessions();
82
+ res.setHeader("Content-Type", "application/json");
83
+ res.end(JSON.stringify(sessions));
84
+ },
85
+ "GET /api/pi/memory"(_, res) {
86
+ const memory = pi.readMemoryFiles();
87
+ res.setHeader("Content-Type", "application/json");
88
+ res.end(JSON.stringify(memory));
89
+ },
90
+ };
91
+
92
+ // Middleware: match API routes
93
+ server.middlewares.use((req, res, next) => {
94
+ const method = req.method!;
95
+ const url = req.url!;
96
+ // Only handle /api/pi/* paths
97
+ if (!url.startsWith("/api/pi/")) return next();
98
+
99
+ // Strip query string
100
+ const pathOnly = url.split("?")[0];
101
+
102
+ // Handle DELETE /api/pi/session?path=...
103
+ if (method === "DELETE" && pathOnly === "/api/pi/session") {
104
+ const parsedUrl = new URL(url, "http://localhost");
105
+ const filePath = parsedUrl.searchParams.get("path");
106
+ if (!filePath) {
107
+ res.statusCode = 400;
108
+ res.setHeader("Content-Type", "application/json");
109
+ return res.end(JSON.stringify({ success: false, error: "Missing path" }));
110
+ }
111
+ const decodedPath = decodeURIComponent(filePath);
112
+ const ok = pi.deleteSessionFile(decodedPath);
113
+ res.setHeader("Content-Type", "application/json");
114
+ return res.end(JSON.stringify({ success: ok }));
115
+ }
116
+
117
+ // Handle GET /api/pi/usage-range?range=today|7d|30d|custom&from=...&to=...
118
+ if (method === "GET" && pathOnly === "/api/pi/usage-range") {
119
+ const parsedUrl = new URL(url, "http://localhost");
120
+ const range = parsedUrl.searchParams.get("range") || "today";
121
+ const fromParam = parsedUrl.searchParams.get("from") || "";
122
+ const toParam = parsedUrl.searchParams.get("to") || "";
123
+
124
+ const now = new Date();
125
+ const localDateStr = (dt: Date) =>
126
+ `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, "0")}-${String(dt.getDate()).padStart(2, "0")}`;
127
+ let fromDate: string;
128
+ let toDate = localDateStr(now);
129
+
130
+ if (range === "today") {
131
+ fromDate = toDate;
132
+ } else if (range === "7d") {
133
+ const d = new Date(now); d.setDate(d.getDate() - 6); fromDate = localDateStr(d);
134
+ } else if (range === "30d") {
135
+ const d = new Date(now); d.setDate(d.getDate() - 29); fromDate = localDateStr(d);
136
+ } else if (range === "custom" && fromParam) {
137
+ fromDate = fromParam;
138
+ if (toParam) toDate = toParam;
139
+ } else {
140
+ fromDate = toDate;
141
+ }
142
+
143
+ const allRecords = pi.readAllUsage();
144
+ const usage = pi.getUsageByRange(allRecords, fromDate, toDate);
145
+ res.setHeader("Content-Type", "application/json");
146
+ return res.end(JSON.stringify(usage));
147
+ }
148
+
149
+ const key = `${method} ${pathOnly}`;
150
+ const handler = routes[key];
151
+ if (handler) {
152
+ handler(req, res);
153
+ } else {
154
+ res.statusCode = 404;
155
+ res.setHeader("Content-Type", "application/json");
156
+ res.end(JSON.stringify({ error: "Not found" }));
157
+ }
158
+ });
159
+ },
160
+ };
161
+ }
162
+
163
+ // ─── Vite Config ────────────────────────────────────────
164
+
165
+ export default defineConfig({
166
+ plugins: [react(), tailwindcss(), piApiPlugin()],
167
+ resolve: {
168
+ alias: {
169
+ "@": path.resolve(__dirname, "src"),
170
+ },
171
+ },
172
+ });