@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
@@ -0,0 +1,459 @@
1
+ import { create } from "zustand";
2
+ import type {
3
+ PiConfig,
4
+ Provider,
5
+ Model,
6
+ PiSettings,
7
+ PiAuth,
8
+ PiModelsJson,
9
+ CustomProviderConfig,
10
+ } from "@/types";
11
+ import { BUILTIN_PROVIDERS } from "@/data/builtin-providers";
12
+
13
+ // ─── API Helper ──────────────────────────────────────────
14
+
15
+ const API_BASE = "/api/pi";
16
+
17
+ async function apiGet<T>(path: string): Promise<T> {
18
+ const res = await fetch(`${API_BASE}${path}`);
19
+ if (!res.ok) throw new Error(`API ${path}: ${res.status}`);
20
+ return res.json();
21
+ }
22
+
23
+ async function apiPost(path: string, data: unknown): Promise<boolean> {
24
+ const res = await fetch(`${API_BASE}${path}`, {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify(data),
28
+ });
29
+ if (!res.ok) return false;
30
+ const result = await res.json();
31
+ return result.success === true;
32
+ }
33
+
34
+ // ─── Built-in Provider Helpers (Client-side) ────────────
35
+
36
+ function getCustomProviders(modelsJson: PiModelsJson | null): Provider[] {
37
+ if (!modelsJson) return [];
38
+ return Object.entries(modelsJson.providers).map(([id, cfg]) => ({
39
+ id,
40
+ name: id.charAt(0).toUpperCase() + id.slice(1),
41
+ type: "custom" as const,
42
+ baseUrl: cfg.baseUrl,
43
+ api: cfg.api,
44
+ apiKey: cfg.apiKey,
45
+ authHeader: cfg.authHeader,
46
+ headers: cfg.headers,
47
+ compat: cfg.compat,
48
+ hasAuth: !!cfg.apiKey,
49
+ authMethod: (cfg.apiKey ? "file" : "none") as "file" | "none",
50
+ models: (cfg.models ?? []).map((m) => ({ ...m, enabled: true })),
51
+ }));
52
+ }
53
+
54
+ function mergeProviders(auth: PiAuth, customModels: PiModelsJson | null): Provider[] {
55
+ const builtins = BUILTIN_PROVIDERS.map((p) => ({
56
+ ...p,
57
+ hasAuth: p.hasAuth || !!auth[p.id],
58
+ authMethod: auth[p.id] ? "file" : p.authMethod,
59
+ }));
60
+ const customs = getCustomProviders(customModels);
61
+ return [...builtins, ...customs];
62
+ }
63
+
64
+ // ─── State Types ─────────────────────────────────────────
65
+
66
+ interface UsageData {
67
+ dailyAggregates: {
68
+ date: string;
69
+ totalTokens: number;
70
+ totalCost: number;
71
+ totalRequests: number;
72
+ inputTokens: number;
73
+ outputTokens: number;
74
+ }[];
75
+ providerSummaries: {
76
+ providerId: string;
77
+ totalTokens: number;
78
+ totalCost: number;
79
+ totalRequests: number;
80
+ }[];
81
+ modelSummaries: {
82
+ modelId: string;
83
+ providerId: string;
84
+ totalTokens: number;
85
+ totalCost: number;
86
+ totalRequests: number;
87
+ avgTokensPerRequest: number;
88
+ }[];
89
+ totals: {
90
+ totalTokens: number;
91
+ totalCost: number;
92
+ totalRequests: number;
93
+ };
94
+ }
95
+
96
+ interface ConfigState {
97
+ // Raw config from pi files
98
+ settings: PiSettings | null;
99
+ auth: PiAuth | null;
100
+ modelsJson: PiModelsJson | null;
101
+
102
+ // Derived
103
+ allProviders: Provider[];
104
+ allModels: (Model & { providerId: string; providerName: string })[];
105
+
106
+ // Usage data from session files
107
+ usage: UsageData | null;
108
+
109
+ // Lifecycle
110
+ initialized: boolean;
111
+ loading: boolean;
112
+ error: string | null;
113
+
114
+ // Actions
115
+ init: () => Promise<void>;
116
+ refreshUsage: () => Promise<void>;
117
+
118
+ // Settings
119
+ updateSettings: (settings: Partial<PiSettings>) => Promise<void>;
120
+ setDefaultProvider: (provider: string) => Promise<void>;
121
+ setDefaultModel: (model: string) => Promise<void>;
122
+ setTheme: (theme: PiSettings["theme"]) => Promise<void>;
123
+ addEnabledModel: (modelRef: string) => Promise<void>;
124
+ removeEnabledModel: (modelRef: string) => Promise<void>;
125
+ addPackage: (pkg: string) => Promise<void>;
126
+ removePackage: (pkg: string) => Promise<void>;
127
+
128
+ // Auth
129
+ setProviderAuth: (providerId: string, key: string) => Promise<void>;
130
+ removeProviderAuth: (providerId: string) => Promise<void>;
131
+
132
+ // Model CRUD (for custom providers)
133
+ toggleModel: (providerId: string, modelId: string) => void;
134
+ updateModel: (providerId: string, modelId: string, updates: Partial<Model>) => void;
135
+ addModel: (providerId: string, model: Model) => void;
136
+ removeModel: (providerId: string, modelId: string) => void;
137
+
138
+ // Custom provider CRUD
139
+ addCustomProvider: (id: string, cfg: CustomProviderConfig) => Promise<void>;
140
+ updateCustomProvider: (id: string, cfg: Partial<CustomProviderConfig>) => Promise<void>;
141
+ removeCustomProvider: (id: string) => Promise<void>;
142
+
143
+ // Import/Export (to localStorage for backup, writes back to pi files)
144
+ importConfig: (config: PiConfig) => Promise<void>;
145
+ resetToDefaults: () => Promise<void>;
146
+ }
147
+
148
+ export const useConfigStore = create<ConfigState>((set, get) => ({
149
+ settings: null,
150
+ auth: null,
151
+ modelsJson: null,
152
+ allProviders: [],
153
+ allModels: [],
154
+ usage: null,
155
+ initialized: false,
156
+ loading: true,
157
+ error: null,
158
+
159
+ // ─── Init ───────────────────────────────────────────────
160
+
161
+ init: async () => {
162
+ set({ loading: true, error: null });
163
+ try {
164
+ const [settings, auth, modelsJson, usage] = await Promise.all([
165
+ apiGet<PiSettings>("/settings"),
166
+ apiGet<PiAuth>("/auth"),
167
+ apiGet<PiModelsJson>("/models"),
168
+ apiGet<UsageData>("/usage"),
169
+ ]);
170
+
171
+ const allProviders = mergeProviders(auth ?? {}, modelsJson);
172
+ const allModels = allProviders.flatMap((p) =>
173
+ p.models.map((m) => ({
174
+ ...m,
175
+ providerId: p.id,
176
+ providerName: p.name,
177
+ }))
178
+ );
179
+
180
+ set({
181
+ settings,
182
+ auth,
183
+ modelsJson,
184
+ allProviders,
185
+ allModels,
186
+ usage,
187
+ initialized: true,
188
+ loading: false,
189
+ });
190
+ } catch (e: any) {
191
+ set({
192
+ error: e.message || "Failed to load pi configuration",
193
+ loading: false,
194
+ initialized: true,
195
+ });
196
+ }
197
+ },
198
+
199
+ // ─── Refresh Usage ──────────────────────────────────────
200
+
201
+ refreshUsage: async () => {
202
+ try {
203
+ const usage = await apiGet<UsageData>("/usage");
204
+ set({ usage });
205
+ } catch {
206
+ // ignore refresh errors
207
+ }
208
+ },
209
+
210
+ // ─── Settings ───────────────────────────────────────────
211
+
212
+ updateSettings: async (partial) => {
213
+ const { settings } = get();
214
+ if (!settings) return;
215
+ const updated = { ...settings, ...partial };
216
+ const ok = await apiPost("/settings", updated);
217
+ if (ok) set({ settings: updated });
218
+ },
219
+
220
+ setDefaultProvider: async (provider) => {
221
+ await get().updateSettings({ defaultProvider: provider });
222
+ },
223
+
224
+ setDefaultModel: async (model) => {
225
+ await get().updateSettings({ defaultModel: model });
226
+ },
227
+
228
+ setTheme: async (theme) => {
229
+ await get().updateSettings({ theme });
230
+ },
231
+
232
+ addEnabledModel: async (modelRef) => {
233
+ const { settings } = get();
234
+ if (!settings) return;
235
+ const list = settings.enabledModels ?? [];
236
+ if (!list.includes(modelRef)) {
237
+ await get().updateSettings({ enabledModels: [...list, modelRef] });
238
+ }
239
+ },
240
+
241
+ removeEnabledModel: async (modelRef) => {
242
+ const { settings } = get();
243
+ if (!settings) return;
244
+ const list = (settings.enabledModels ?? []).filter((m) => m !== modelRef);
245
+ await get().updateSettings({ enabledModels: list });
246
+ },
247
+
248
+ addPackage: async (pkg) => {
249
+ const { settings } = get();
250
+ if (!settings) return;
251
+ const list = settings.packages ?? [];
252
+ if (!list.includes(pkg)) {
253
+ await get().updateSettings({ packages: [...list, pkg] });
254
+ }
255
+ },
256
+
257
+ removePackage: async (pkg) => {
258
+ const { settings } = get();
259
+ if (!settings) return;
260
+ const list = (settings.packages ?? []).filter((p) => p !== pkg);
261
+ await get().updateSettings({ packages: list });
262
+ },
263
+
264
+ // ─── Auth ───────────────────────────────────────────────
265
+
266
+ setProviderAuth: async (providerId, key) => {
267
+ const { auth } = get();
268
+ const updated = { ...(auth ?? {}), [providerId]: { type: "api_key" as const, key } };
269
+ const ok = await apiPost("/auth", updated);
270
+ if (ok) {
271
+ set({ auth: updated });
272
+ // Recompute providers with updated auth state
273
+ const { modelsJson } = get();
274
+ set({ allProviders: mergeProviders(updated, modelsJson) });
275
+ }
276
+ },
277
+
278
+ removeProviderAuth: async (providerId) => {
279
+ const { auth } = get();
280
+ if (!auth) return;
281
+ const { [providerId]: _, ...rest } = auth;
282
+ const ok = await apiPost("/auth", rest);
283
+ if (ok) {
284
+ set({ auth: rest });
285
+ const { modelsJson } = get();
286
+ set({ allProviders: mergeProviders(rest, modelsJson) });
287
+ }
288
+ },
289
+
290
+ // ─── Model CRUD (client-side only, stored in modelsJson) ─
291
+
292
+ toggleModel: (providerId, modelId) => {
293
+ const { modelsJson } = get();
294
+ if (!modelsJson) return;
295
+ const p = modelsJson.providers[providerId];
296
+ if (!p?.models) return;
297
+ const newModels = p.models.map((m) =>
298
+ m.id === modelId ? { ...m, enabled: !(m.enabled ?? true) } : m
299
+ );
300
+ const newProviders = {
301
+ ...modelsJson.providers,
302
+ [providerId]: { ...p, models: newModels },
303
+ };
304
+ const updated = { providers: newProviders };
305
+ set({ modelsJson: updated });
306
+ // Persist
307
+ apiPost("/models", updated);
308
+ // Recompute providers
309
+ const { auth } = get();
310
+ set({ allProviders: mergeProviders(auth ?? {}, updated) });
311
+ },
312
+
313
+ updateModel: (providerId, modelId, updates) => {
314
+ const { modelsJson } = get();
315
+ if (!modelsJson) return;
316
+ const isBuiltin = !modelsJson.providers[providerId];
317
+ const newProviders = { ...(modelsJson.providers ?? {}) };
318
+
319
+ if (!newProviders[providerId]) {
320
+ newProviders[providerId] = { models: [] };
321
+ }
322
+ const existingModels = newProviders[providerId]!.models ?? [];
323
+ const idx = existingModels.findIndex((m: any) => m.id === modelId);
324
+ if (idx >= 0) {
325
+ existingModels[idx] = { ...existingModels[idx], ...updates } as Model;
326
+ } else if (isBuiltin) {
327
+ // Store as override
328
+ newProviders[providerId] = {
329
+ ...newProviders[providerId],
330
+ models: [...existingModels, updates as Model],
331
+ };
332
+ } else {
333
+ existingModels.push(updates as Model);
334
+ }
335
+ newProviders[providerId] = { ...newProviders[providerId], models: existingModels };
336
+ const updated = { providers: newProviders };
337
+ set({ modelsJson: updated });
338
+ apiPost("/models", updated);
339
+ const { auth } = get();
340
+ set({ allProviders: mergeProviders(auth ?? {}, updated) });
341
+ },
342
+
343
+ addModel: (providerId, model) => {
344
+ const { modelsJson } = get();
345
+ if (!modelsJson) return;
346
+ const newProviders = { ...(modelsJson.providers ?? {}) };
347
+ if (!newProviders[providerId]) {
348
+ newProviders[providerId] = { models: [] };
349
+ }
350
+ newProviders[providerId] = {
351
+ ...newProviders[providerId],
352
+ models: [...(newProviders[providerId]!.models ?? []), { ...model, enabled: true }],
353
+ };
354
+ const updated = { providers: newProviders };
355
+ set({ modelsJson: updated });
356
+ apiPost("/models", updated);
357
+ const { auth } = get();
358
+ const newAllProviders = mergeProviders(auth ?? {}, updated);
359
+ const newAllModels = newAllProviders.flatMap((p) =>
360
+ p.models.map((m) => ({ ...m, providerId: p.id, providerName: p.name }))
361
+ );
362
+ set({ allProviders: newAllProviders, allModels: newAllModels });
363
+ },
364
+
365
+ removeModel: (providerId, modelId) => {
366
+ const { modelsJson } = get();
367
+ if (!modelsJson) return;
368
+ const p = modelsJson.providers[providerId];
369
+ if (!p?.models) return;
370
+ const newModels = p.models.filter((m: any) => m.id !== modelId);
371
+ const newProviders = {
372
+ ...modelsJson.providers,
373
+ [providerId]: { ...p, models: newModels },
374
+ };
375
+ const updated = { providers: newProviders };
376
+ set({ modelsJson: updated });
377
+ apiPost("/models", updated);
378
+ const { auth } = get();
379
+ set({ allProviders: mergeProviders(auth ?? {}, updated) });
380
+ },
381
+
382
+ // ─── Custom Provider CRUD ──────────────────────────────
383
+
384
+ addCustomProvider: async (id, cfg) => {
385
+ const { modelsJson } = get();
386
+ if (!modelsJson) return;
387
+ const newProviders = { ...modelsJson.providers, [id]: cfg };
388
+ const updated = { providers: newProviders };
389
+ const ok = await apiPost("/models", updated);
390
+ if (ok) {
391
+ set({ modelsJson: updated });
392
+ const { auth } = get();
393
+ set({ allProviders: mergeProviders(auth ?? {}, updated) });
394
+ }
395
+ },
396
+
397
+ updateCustomProvider: async (id, cfg) => {
398
+ const { modelsJson } = get();
399
+ if (!modelsJson) return;
400
+ const existing = modelsJson.providers[id];
401
+ if (!existing) return;
402
+ const newProviders = {
403
+ ...modelsJson.providers,
404
+ [id]: { ...existing, ...cfg },
405
+ };
406
+ const updated = { providers: newProviders };
407
+ const ok = await apiPost("/models", updated);
408
+ if (ok) {
409
+ set({ modelsJson: updated });
410
+ const { auth } = get();
411
+ set({ allProviders: mergeProviders(auth ?? {}, updated) });
412
+ }
413
+ },
414
+
415
+ removeCustomProvider: async (id) => {
416
+ const { modelsJson } = get();
417
+ if (!modelsJson) return;
418
+ const { [id]: _, ...rest } = modelsJson.providers;
419
+ const updated = { providers: rest };
420
+ const ok = await apiPost("/models", updated);
421
+ if (ok) {
422
+ set({ modelsJson: updated });
423
+ const { auth } = get();
424
+ set({ allProviders: mergeProviders(auth ?? {}, updated) });
425
+ }
426
+ },
427
+
428
+ // ─── Import/Export ─────────────────────────────────────
429
+
430
+ importConfig: async (config) => {
431
+ // Write all three config files through the API
432
+ await Promise.all([
433
+ apiPost("/settings", config.settings),
434
+ apiPost("/auth", config.auth),
435
+ apiPost("/models", config.modelsJson ?? { providers: {} }),
436
+ ]);
437
+ // Reload
438
+ await get().init();
439
+ },
440
+
441
+ resetToDefaults: async () => {
442
+ // Write empty/default configs
443
+ await Promise.all([
444
+ apiPost("/settings", {
445
+ lastChangelogVersion: "0.80.3",
446
+ defaultProvider: "",
447
+ defaultModel: "",
448
+ theme: "dark",
449
+ hideThinkingBlock: true,
450
+ retry: { enabled: true },
451
+ packages: [],
452
+ enabledModels: [],
453
+ }),
454
+ apiPost("/auth", {}),
455
+ apiPost("/models", { providers: {} }),
456
+ ]);
457
+ await get().init();
458
+ },
459
+ }));
@@ -0,0 +1,187 @@
1
+ // ─── Core Types ───────────────────────────────────────────
2
+
3
+ export interface ModelCost {
4
+ input: number;
5
+ output: number;
6
+ cacheRead: number;
7
+ cacheWrite: number;
8
+ }
9
+
10
+ export interface ThinkingLevelMap {
11
+ off?: string | null;
12
+ minimal?: string | null;
13
+ low?: string | null;
14
+ medium?: string | null;
15
+ high?: string | null;
16
+ xhigh?: string | null;
17
+ }
18
+
19
+ export interface ModelCompat {
20
+ supportsStore?: boolean;
21
+ supportsDeveloperRole?: boolean;
22
+ supportsReasoningEffort?: boolean;
23
+ supportsUsageInStreaming?: boolean;
24
+ maxTokensField?: "max_completion_tokens" | "max_tokens";
25
+ requiresToolResultName?: boolean;
26
+ requiresAssistantAfterToolResult?: boolean;
27
+ requiresThinkingAsText?: boolean;
28
+ requiresReasoningContentOnAssistantMessages?: boolean;
29
+ thinkingFormat?: string;
30
+ cacheControlFormat?: "anthropic";
31
+ supportsEagerToolInputStreaming?: boolean;
32
+ supportsLongCacheRetention?: boolean;
33
+ sendSessionAffinityHeaders?: boolean;
34
+ supportsCacheControlOnTools?: boolean;
35
+ forceAdaptiveThinking?: boolean;
36
+ allowEmptySignature?: boolean;
37
+ openRouterRouting?: Record<string, unknown>;
38
+ }
39
+
40
+ export interface Model {
41
+ id: string;
42
+ name?: string;
43
+ api?: ApiType;
44
+ baseUrl?: string;
45
+ reasoning?: boolean;
46
+ thinkingLevelMap?: ThinkingLevelMap;
47
+ input?: ("text" | "image" | "audio")[];
48
+ cost?: ModelCost;
49
+ contextWindow?: number;
50
+ maxTokens?: number;
51
+ compat?: ModelCompat;
52
+ headers?: Record<string, string>;
53
+ enabled?: boolean;
54
+ }
55
+
56
+ export type ApiType =
57
+ | "openai-completions"
58
+ | "openai-responses"
59
+ | "anthropic-messages"
60
+ | "google-generative-ai"
61
+ | "google-vertex"
62
+ | "bedrock-converse-stream"
63
+ | "mistral-conversations"
64
+ | "azure-openai-responses"
65
+ | "openai-codex-responses";
66
+
67
+ export interface ProviderAuth {
68
+ type: "api_key" | "oauth";
69
+ key?: string;
70
+ env?: Record<string, string>;
71
+ }
72
+
73
+ export interface ProviderOAuth {
74
+ name: string;
75
+ }
76
+
77
+ export interface Provider {
78
+ id: string;
79
+ name: string;
80
+ type: "builtin" | "custom";
81
+ baseUrl?: string;
82
+ api?: ApiType;
83
+ apiKey?: string;
84
+ authHeader?: boolean;
85
+ headers?: Record<string, string>;
86
+ models: Model[];
87
+ oauth?: ProviderOAuth;
88
+ compat?: ModelCompat;
89
+
90
+ // Auth state
91
+ hasAuth: boolean;
92
+ authMethod?: "env" | "file" | "cli" | "none";
93
+ }
94
+
95
+ // ─── Pi Config Structure ──────────────────────────────────
96
+
97
+ export interface PiSettings {
98
+ lastChangelogVersion?: string;
99
+ defaultProvider?: string;
100
+ defaultModel?: string;
101
+ defaultThinkingLevel?: string;
102
+ defaultProjectTrust?: string;
103
+ theme?: "light" | "dark" | "light/dark";
104
+ hideThinkingBlock?: boolean;
105
+ retry?: { enabled: boolean };
106
+ packages?: string[];
107
+ terminal?: { showTerminalProgress?: boolean };
108
+ warnings?: Record<string, boolean>;
109
+ treeFilterMode?: string;
110
+ doubleEscapeAction?: string;
111
+ enabledModels?: string[];
112
+ }
113
+
114
+ export interface PiAuth {
115
+ [providerId: string]: ProviderAuth;
116
+ }
117
+
118
+ export interface CustomProviderConfig {
119
+ baseUrl?: string;
120
+ api?: ApiType;
121
+ apiKey?: string;
122
+ authHeader?: boolean;
123
+ headers?: Record<string, string>;
124
+ models?: Model[];
125
+ compat?: ModelCompat;
126
+ modelOverrides?: Record<string, Partial<Model>>;
127
+ }
128
+
129
+ export interface PiModelsJson {
130
+ providers: Record<string, CustomProviderConfig>;
131
+ }
132
+
133
+ export interface PiConfig {
134
+ settings: PiSettings;
135
+ auth: PiAuth;
136
+ modelsJson: PiModelsJson | null;
137
+ }
138
+
139
+ // ─── Usage / Dashboard Types ──────────────────────────────
140
+
141
+ export interface UsageRecord {
142
+ date: string; // ISO date YYYY-MM-DD
143
+ providerId: string;
144
+ modelId: string;
145
+ inputTokens: number;
146
+ outputTokens: number;
147
+ cacheReadTokens: number;
148
+ cacheWriteTokens: number;
149
+ requests: number;
150
+ cost: number;
151
+ }
152
+
153
+ export interface DailyAggregate {
154
+ date: string;
155
+ totalTokens: number;
156
+ totalCost: number;
157
+ totalRequests: number;
158
+ inputTokens: number;
159
+ outputTokens: number;
160
+ }
161
+
162
+ export interface ModelUsageSummary {
163
+ modelId: string;
164
+ providerId: string;
165
+ modelName: string;
166
+ totalTokens: number;
167
+ totalCost: number;
168
+ totalRequests: number;
169
+ avgTokensPerRequest: number;
170
+ }
171
+
172
+ export interface ProviderUsageSummary {
173
+ providerId: string;
174
+ providerName: string;
175
+ totalTokens: number;
176
+ totalCost: number;
177
+ totalRequests: number;
178
+ modelCount: number;
179
+ }
180
+
181
+ // ─── Config Import/Export ─────────────────────────────────
182
+
183
+ export interface ExportPayload {
184
+ version: string;
185
+ exportedAt: string;
186
+ config: PiConfig;
187
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "skipLibCheck": true,
7
+ "moduleResolution": "bundler",
8
+ "allowImportingTsExtensions": true,
9
+ "isolatedModules": true,
10
+ "moduleDetection": "force",
11
+ "noEmit": true,
12
+ "jsx": "react-jsx",
13
+ "strict": true,
14
+ "noUnusedLocals": false,
15
+ "noUnusedParameters": false,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "noUncheckedIndexedAccess": true,
18
+ "baseUrl": ".",
19
+ "paths": {
20
+ "@/*": ["src/*"]
21
+ }
22
+ },
23
+ "include": ["src"]
24
+ }