@raingor/pi-web-switch 0.4.0 → 0.4.1
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/package.json +5 -1
- package/public/sw.js +28 -5
- package/server/agent-session-manager.ts +827 -0
- package/server/chat-api-plugin.ts +488 -0
- package/server/pi-reader.ts +242 -1
- package/src/App.tsx +2 -0
- package/src/components/chat/ChatInput.tsx +863 -0
- package/src/components/chat/ChatPage.tsx +617 -0
- package/src/components/chat/ChatWindow.tsx +338 -0
- package/src/components/chat/MessageView.tsx +595 -0
- package/src/components/dashboard/DashboardPage.tsx +45 -5
- package/src/components/layout/AppShell.tsx +12 -5
- package/src/components/layout/Sidebar.tsx +2 -0
- package/src/components/providers/ProvidersModelsPage.tsx +28 -10
- package/src/components/sessions/SessionsPage.tsx +466 -148
- package/src/hooks/useAgentSession.ts +1104 -0
- package/src/index.css +24 -0
- package/src/lib/translations/en.ts +69 -0
- package/src/lib/translations/ja.ts +69 -0
- package/src/lib/translations/zh-CN.ts +69 -0
- package/src/lib/translations/zh-TW.ts +69 -0
- package/src/main.tsx +4 -2
- package/src/store/config-store.ts +41 -0
- package/src/types/chat.ts +217 -0
- package/vite.config.ts +141 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Chat-related type definitions — ported and simplified from pi-web's lib/types.ts
|
|
2
|
+
|
|
3
|
+
export interface TextContent {
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ImageContent {
|
|
9
|
+
type: "image";
|
|
10
|
+
source: {
|
|
11
|
+
type: "base64" | "url";
|
|
12
|
+
media_type?: string;
|
|
13
|
+
data?: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ThinkingContent {
|
|
19
|
+
type: "thinking";
|
|
20
|
+
thinking: string;
|
|
21
|
+
deferred?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ToolCallContent {
|
|
25
|
+
type: "toolCall";
|
|
26
|
+
toolCallId: string;
|
|
27
|
+
toolName: string;
|
|
28
|
+
input: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type AssistantContentBlock = TextContent | ImageContent | ThinkingContent | ToolCallContent;
|
|
32
|
+
|
|
33
|
+
export interface UserMessage {
|
|
34
|
+
role: "user";
|
|
35
|
+
content: string | (TextContent | ImageContent)[];
|
|
36
|
+
timestamp?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AssistantMessage {
|
|
40
|
+
role: "assistant";
|
|
41
|
+
content: AssistantContentBlock[];
|
|
42
|
+
model: string;
|
|
43
|
+
provider: string;
|
|
44
|
+
stopReason?: string;
|
|
45
|
+
errorMessage?: string;
|
|
46
|
+
timestamp?: number;
|
|
47
|
+
usage?: {
|
|
48
|
+
input: number;
|
|
49
|
+
output: number;
|
|
50
|
+
cacheRead: number;
|
|
51
|
+
cacheWrite: number;
|
|
52
|
+
cost: {
|
|
53
|
+
input: number;
|
|
54
|
+
output: number;
|
|
55
|
+
cacheRead: number;
|
|
56
|
+
cacheWrite: number;
|
|
57
|
+
total: number;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ToolResultMessage {
|
|
63
|
+
role: "toolResult";
|
|
64
|
+
toolCallId: string;
|
|
65
|
+
toolName?: string;
|
|
66
|
+
content: (TextContent | ImageContent)[];
|
|
67
|
+
isError?: boolean;
|
|
68
|
+
details?: unknown;
|
|
69
|
+
timestamp?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface CustomMessage {
|
|
73
|
+
role: "custom";
|
|
74
|
+
customType: string;
|
|
75
|
+
content: string | (TextContent | ImageContent)[];
|
|
76
|
+
display: boolean;
|
|
77
|
+
details?: unknown;
|
|
78
|
+
timestamp?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface BashExecutionMessage {
|
|
82
|
+
role: "bashExecution";
|
|
83
|
+
command: string;
|
|
84
|
+
output: string;
|
|
85
|
+
exitCode?: number;
|
|
86
|
+
cancelled?: boolean;
|
|
87
|
+
truncated?: boolean;
|
|
88
|
+
fullOutputPath?: string;
|
|
89
|
+
excludeFromContext?: boolean;
|
|
90
|
+
timestamp?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type AgentMessage = UserMessage | AssistantMessage | ToolResultMessage | CustomMessage | BashExecutionMessage;
|
|
94
|
+
|
|
95
|
+
export interface SessionInfo {
|
|
96
|
+
path: string;
|
|
97
|
+
id: string;
|
|
98
|
+
cwd: string;
|
|
99
|
+
name?: string;
|
|
100
|
+
created: string;
|
|
101
|
+
modified: string;
|
|
102
|
+
messageCount: number;
|
|
103
|
+
firstMessage: string;
|
|
104
|
+
parentSessionId?: string;
|
|
105
|
+
projectRoot?: string;
|
|
106
|
+
worktreeBranch?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface SessionContext {
|
|
110
|
+
messages: AgentMessage[];
|
|
111
|
+
entryIds: string[];
|
|
112
|
+
thinkingLevel: string;
|
|
113
|
+
model: { provider: string; modelId: string } | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface SessionTreeNode {
|
|
117
|
+
entry: { id: string; type: string };
|
|
118
|
+
children: SessionTreeNode[];
|
|
119
|
+
label?: string;
|
|
120
|
+
compressedEntryIds?: string[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface SessionData {
|
|
124
|
+
sessionId: string;
|
|
125
|
+
filePath: string;
|
|
126
|
+
tree: SessionTreeNode[];
|
|
127
|
+
leafId: string | null;
|
|
128
|
+
context: SessionContext;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ContextUsage {
|
|
132
|
+
percent: number | null;
|
|
133
|
+
contextWindow: number;
|
|
134
|
+
tokens: number | null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface SessionStatsInfo {
|
|
138
|
+
sessionFile?: string;
|
|
139
|
+
sessionId: string;
|
|
140
|
+
sessionName?: string;
|
|
141
|
+
userMessages: number;
|
|
142
|
+
assistantMessages: number;
|
|
143
|
+
toolCalls: number;
|
|
144
|
+
toolResults: number;
|
|
145
|
+
totalMessages: number;
|
|
146
|
+
tokens: {
|
|
147
|
+
input: number;
|
|
148
|
+
output: number;
|
|
149
|
+
cacheRead: number;
|
|
150
|
+
cacheWrite: number;
|
|
151
|
+
total: number;
|
|
152
|
+
};
|
|
153
|
+
cost: number;
|
|
154
|
+
contextUsage?: ContextUsage;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface AttachedImage {
|
|
158
|
+
data: string;
|
|
159
|
+
mimeType: string;
|
|
160
|
+
previewUrl: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface ChatInputHandle {
|
|
164
|
+
insertText: (text: string) => void;
|
|
165
|
+
insertIfEmpty: (content: string) => void;
|
|
166
|
+
prependText: (text: string) => void;
|
|
167
|
+
addImages: (files: File[]) => void;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface ModelEntry {
|
|
171
|
+
id: string;
|
|
172
|
+
name: string;
|
|
173
|
+
provider: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface ModelsResponse {
|
|
177
|
+
models: Record<string, string>;
|
|
178
|
+
modelList?: ModelEntry[];
|
|
179
|
+
defaultModel?: { provider: string; modelId: string } | null;
|
|
180
|
+
thinkingLevels?: Record<string, string[]>;
|
|
181
|
+
modelError?: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface AgentStateResponse {
|
|
185
|
+
isStreaming?: boolean;
|
|
186
|
+
isPromptRunning?: boolean;
|
|
187
|
+
isBashRunning?: boolean;
|
|
188
|
+
isCompacting?: boolean;
|
|
189
|
+
contextUsage?: ContextUsage | null;
|
|
190
|
+
systemPrompt?: string;
|
|
191
|
+
thinkingLevel?: string;
|
|
192
|
+
model?: { id: string; provider: string };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface NoticeItem {
|
|
196
|
+
id: string;
|
|
197
|
+
message: string;
|
|
198
|
+
type: "info" | "success" | "warning" | "error";
|
|
199
|
+
exiting?: boolean;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export type AgentPhase =
|
|
203
|
+
| { kind: "waiting_model" }
|
|
204
|
+
| { kind: "running_command" }
|
|
205
|
+
| { kind: "running_tools"; tools: { id: string; name: string }[] }
|
|
206
|
+
| null;
|
|
207
|
+
|
|
208
|
+
export interface QueuedMessages {
|
|
209
|
+
steering: string[];
|
|
210
|
+
followUp: string[];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface SlashCommandInfo {
|
|
214
|
+
name: string;
|
|
215
|
+
description?: string;
|
|
216
|
+
source: "extension" | "prompt" | "skill";
|
|
217
|
+
}
|
package/vite.config.ts
CHANGED
|
@@ -3,6 +3,7 @@ import react from "@vitejs/plugin-react";
|
|
|
3
3
|
import tailwindcss from "@tailwindcss/vite";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import type { Connect } from "vite";
|
|
6
|
+
import { chatApiPlugin } from "./server/chat-api-plugin";
|
|
6
7
|
|
|
7
8
|
// ─── Pi Config API Plugin ───────────────────────────────
|
|
8
9
|
|
|
@@ -306,6 +307,145 @@ function piApiPlugin(): Plugin {
|
|
|
306
307
|
return res.end(JSON.stringify(usage));
|
|
307
308
|
}
|
|
308
309
|
|
|
310
|
+
// Handle GET /api/pi/cindy-usage-range?range=today|7d|30d|custom&from=...&to=...
|
|
311
|
+
if (method === "GET" && pathOnly === "/api/pi/cindy-usage-range") {
|
|
312
|
+
const parsedUrl = new URL(url, "http://localhost");
|
|
313
|
+
const range = parsedUrl.searchParams.get("range") || "today";
|
|
314
|
+
const fromParam = parsedUrl.searchParams.get("from") || "";
|
|
315
|
+
const toParam = parsedUrl.searchParams.get("to") || "";
|
|
316
|
+
|
|
317
|
+
const now = new Date();
|
|
318
|
+
const localDateStr = (dt: Date) =>
|
|
319
|
+
`${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, "0")}-${String(dt.getDate()).padStart(2, "0")}`;
|
|
320
|
+
let fromDate: string;
|
|
321
|
+
let toDate = localDateStr(now);
|
|
322
|
+
|
|
323
|
+
if (range === "today") {
|
|
324
|
+
fromDate = toDate;
|
|
325
|
+
} else if (range === "7d") {
|
|
326
|
+
const d = new Date(now); d.setDate(d.getDate() - 6); fromDate = localDateStr(d);
|
|
327
|
+
} else if (range === "30d") {
|
|
328
|
+
const d = new Date(now); d.setDate(d.getDate() - 29); fromDate = localDateStr(d);
|
|
329
|
+
} else if (range === "custom" && fromParam) {
|
|
330
|
+
fromDate = fromParam;
|
|
331
|
+
if (toParam) toDate = toParam;
|
|
332
|
+
} else {
|
|
333
|
+
fromDate = toDate;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const allRecords = pi.readCindyUsage();
|
|
337
|
+
const usage = pi.getUsageByRange(allRecords, fromDate, toDate);
|
|
338
|
+
res.setHeader("Content-Type", "application/json");
|
|
339
|
+
return res.end(JSON.stringify(usage));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Handle GET /api/pi/claude-usage-range?range=today|7d|30d|custom&from=...&to=...
|
|
343
|
+
if (method === "GET" && pathOnly === "/api/pi/claude-usage-range") {
|
|
344
|
+
const parsedUrl = new URL(url, "http://localhost");
|
|
345
|
+
const range = parsedUrl.searchParams.get("range") || "today";
|
|
346
|
+
const fromParam = parsedUrl.searchParams.get("from") || "";
|
|
347
|
+
const toParam = parsedUrl.searchParams.get("to") || "";
|
|
348
|
+
|
|
349
|
+
const now = new Date();
|
|
350
|
+
const localDateStr = (dt: Date) =>
|
|
351
|
+
`${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, "0")}-${String(dt.getDate()).padStart(2, "0")}`;
|
|
352
|
+
let fromDate: string;
|
|
353
|
+
let toDate = localDateStr(now);
|
|
354
|
+
|
|
355
|
+
if (range === "today") {
|
|
356
|
+
fromDate = toDate;
|
|
357
|
+
} else if (range === "7d") {
|
|
358
|
+
const d = new Date(now); d.setDate(d.getDate() - 6); fromDate = localDateStr(d);
|
|
359
|
+
} else if (range === "30d") {
|
|
360
|
+
const d = new Date(now); d.setDate(d.getDate() - 29); fromDate = localDateStr(d);
|
|
361
|
+
} else if (range === "custom" && fromParam) {
|
|
362
|
+
fromDate = fromParam;
|
|
363
|
+
if (toParam) toDate = toParam;
|
|
364
|
+
} else {
|
|
365
|
+
fromDate = toDate;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const allRecords = pi.readClaudeUsage();
|
|
369
|
+
const usage = pi.getUsageByRange(allRecords, fromDate, toDate);
|
|
370
|
+
res.setHeader("Content-Type", "application/json");
|
|
371
|
+
return res.end(JSON.stringify(usage));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Handle GET /api/pi/codex-usage-range?range=today|7d|30d|custom&from=...&to=...
|
|
375
|
+
if (method === "GET" && pathOnly === "/api/pi/codex-usage-range") {
|
|
376
|
+
const parsedUrl = new URL(url, "http://localhost");
|
|
377
|
+
const range = parsedUrl.searchParams.get("range") || "today";
|
|
378
|
+
const fromParam = parsedUrl.searchParams.get("from") || "";
|
|
379
|
+
const toParam = parsedUrl.searchParams.get("to") || "";
|
|
380
|
+
|
|
381
|
+
const now = new Date();
|
|
382
|
+
const localDateStr = (dt: Date) =>
|
|
383
|
+
`${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, "0")}-${String(dt.getDate()).padStart(2, "0")}`;
|
|
384
|
+
let fromDate: string;
|
|
385
|
+
let toDate = localDateStr(now);
|
|
386
|
+
|
|
387
|
+
if (range === "today") {
|
|
388
|
+
fromDate = toDate;
|
|
389
|
+
} else if (range === "7d") {
|
|
390
|
+
const d = new Date(now); d.setDate(d.getDate() - 6); fromDate = localDateStr(d);
|
|
391
|
+
} else if (range === "30d") {
|
|
392
|
+
const d = new Date(now); d.setDate(d.getDate() - 29); fromDate = localDateStr(d);
|
|
393
|
+
} else if (range === "custom" && fromParam) {
|
|
394
|
+
fromDate = fromParam;
|
|
395
|
+
if (toParam) toDate = toParam;
|
|
396
|
+
} else {
|
|
397
|
+
fromDate = toDate;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const allRecords = pi.readCodexUsage();
|
|
401
|
+
const usage = pi.getUsageByRange(allRecords, fromDate, toDate);
|
|
402
|
+
res.setHeader("Content-Type", "application/json");
|
|
403
|
+
return res.end(JSON.stringify(usage));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Helper: resolve date range params
|
|
407
|
+
const resolveDateRange = (range: string, fromParam: string, toParam: string) => {
|
|
408
|
+
const now = new Date();
|
|
409
|
+
const localDateStr = (dt: Date) =>
|
|
410
|
+
`${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, "0")}-${String(dt.getDate()).padStart(2, "0")}`;
|
|
411
|
+
let fromDate: string;
|
|
412
|
+
let toDate = localDateStr(now);
|
|
413
|
+
if (range === "today") fromDate = toDate;
|
|
414
|
+
else if (range === "7d") { const d = new Date(now); d.setDate(d.getDate() - 6); fromDate = localDateStr(d); }
|
|
415
|
+
else if (range === "30d") { const d = new Date(now); d.setDate(d.getDate() - 29); fromDate = localDateStr(d); }
|
|
416
|
+
else if (range === "custom" && fromParam) { fromDate = fromParam; if (toParam) toDate = toParam; }
|
|
417
|
+
else fromDate = toDate;
|
|
418
|
+
return { fromDate, toDate };
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// Handle GET /api/pi/all-usage-range
|
|
422
|
+
if (method === "GET" && pathOnly === "/api/pi/all-usage-range") {
|
|
423
|
+
const parsedUrl = new URL(url, "http://localhost");
|
|
424
|
+
const range = parsedUrl.searchParams.get("range") || "today";
|
|
425
|
+
const fromParam = parsedUrl.searchParams.get("from") || "";
|
|
426
|
+
const toParam = parsedUrl.searchParams.get("to") || "";
|
|
427
|
+
const { fromDate, toDate } = resolveDateRange(range, fromParam, toParam);
|
|
428
|
+
const allRecords = pi.readAllCombinedUsage();
|
|
429
|
+
const usage = pi.getUsageByRange(allRecords, fromDate, toDate);
|
|
430
|
+
res.setHeader("Content-Type", "application/json");
|
|
431
|
+
return res.end(JSON.stringify(usage));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Handle provider-filtered endpoints: /api/pi/{provider}-usage-range
|
|
435
|
+
const providerMatch = pathOnly.match(/^\/api\/pi\/(opencode|gemini|grok)-usage-range$/);
|
|
436
|
+
if (method === "GET" && providerMatch) {
|
|
437
|
+
const providerId = providerMatch[1]!;
|
|
438
|
+
const parsedUrl = new URL(url, "http://localhost");
|
|
439
|
+
const range = parsedUrl.searchParams.get("range") || "today";
|
|
440
|
+
const fromParam = parsedUrl.searchParams.get("from") || "";
|
|
441
|
+
const toParam = parsedUrl.searchParams.get("to") || "";
|
|
442
|
+
const { fromDate, toDate } = resolveDateRange(range, fromParam, toParam);
|
|
443
|
+
const allRecords = pi.filterByProvider(pi.readAllCombinedUsage(), providerId);
|
|
444
|
+
const usage = pi.getUsageByRange(allRecords, fromDate, toDate);
|
|
445
|
+
res.setHeader("Content-Type", "application/json");
|
|
446
|
+
return res.end(JSON.stringify(usage));
|
|
447
|
+
}
|
|
448
|
+
|
|
309
449
|
const key = `${method} ${pathOnly}`;
|
|
310
450
|
const handler = routes[key];
|
|
311
451
|
if (handler) {
|
|
@@ -327,6 +467,7 @@ export default defineConfig({
|
|
|
327
467
|
react(),
|
|
328
468
|
tailwindcss(),
|
|
329
469
|
piApiPlugin(),
|
|
470
|
+
chatApiPlugin(),
|
|
330
471
|
],
|
|
331
472
|
resolve: {
|
|
332
473
|
alias: {
|