@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.
- package/README.ja.md +137 -0
- package/README.md +277 -0
- package/README.zh-CN.md +176 -0
- package/index.html +13 -0
- package/package.json +44 -0
- package/pi-package/index.ts +100 -0
- package/pi-package/skills/pi-web-switch/SKILL.md +60 -0
- package/public/pi.svg +4 -0
- package/server/pi-reader.ts +678 -0
- package/src/App.tsx +25 -0
- package/src/components/dashboard/DashboardPage.tsx +607 -0
- package/src/components/layout/AppShell.tsx +18 -0
- package/src/components/layout/Sidebar.tsx +116 -0
- package/src/components/models/ModelsPage.tsx +570 -0
- package/src/components/providers/ProvidersPage.tsx +466 -0
- package/src/components/sessions/MemoryPage.tsx +177 -0
- package/src/components/sessions/SessionsPage.tsx +347 -0
- package/src/components/settings/SettingsPage.tsx +351 -0
- package/src/components/ui/Badge.tsx +29 -0
- package/src/components/ui/EmptyState.tsx +20 -0
- package/src/components/ui/Modal.tsx +41 -0
- package/src/components/ui/StatCard.tsx +37 -0
- package/src/data/builtin-providers.ts +148 -0
- package/src/data/mock-config.ts +261 -0
- package/src/data/mock-usage.ts +153 -0
- package/src/index.css +217 -0
- package/src/lib/config.ts +56 -0
- package/src/lib/currency.ts +48 -0
- package/src/lib/i18n.tsx +98 -0
- package/src/lib/translations/en.ts +168 -0
- package/src/lib/translations/index.ts +14 -0
- package/src/lib/translations/ja.ts +158 -0
- package/src/lib/translations/zh-CN.ts +158 -0
- package/src/lib/translations/zh-TW.ts +158 -0
- package/src/lib/utils.ts +51 -0
- package/src/main.tsx +106 -0
- package/src/store/config-store.ts +459 -0
- package/src/types/index.ts +187 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +24 -0
- 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
|
+
});
|