@ashwin-pc/pi-web 0.1.2
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/.pi/extensions/auto-session-name.ts +82 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/bin/pi-web.js +35 -0
- package/contexts/web-ui.md +18 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/index-BNqFbA4l.css +1 -0
- package/dist/assets/index-RfnHuF-b.js +79 -0
- package/dist/icon.svg +4 -0
- package/dist/index.html +174 -0
- package/dist/manifest.webmanifest +1 -0
- package/dist/pwa-192x192.png +0 -0
- package/dist/pwa-512x512.png +0 -0
- package/dist/registerSW.js +1 -0
- package/dist/sw.js +128 -0
- package/dist/workbox-a24bf94b.js +3645 -0
- package/docs/frontend-architecture.md +72 -0
- package/package.json +55 -0
- package/server/extensions.ts +121 -0
- package/server/mock.ts +402 -0
- package/server/settings.ts +155 -0
- package/server/types.ts +76 -0
- package/server.ts +1789 -0
- package/supervisor.ts +205 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
|
|
4
|
+
export type PiWebModelSetting = {
|
|
5
|
+
provider: string;
|
|
6
|
+
id: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type PiWebSettings = {
|
|
10
|
+
version: 1;
|
|
11
|
+
appearance: {
|
|
12
|
+
density: "comfortable" | "compact";
|
|
13
|
+
};
|
|
14
|
+
composer: {
|
|
15
|
+
queueMode: "steer" | "followUp";
|
|
16
|
+
expanded: boolean;
|
|
17
|
+
};
|
|
18
|
+
defaults: {
|
|
19
|
+
model?: PiWebModelSetting;
|
|
20
|
+
thinkingLevel?: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type PiWebSettingsPatch = Partial<{
|
|
25
|
+
appearance: Partial<{
|
|
26
|
+
density: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
composer: Partial<{
|
|
29
|
+
queueMode: unknown;
|
|
30
|
+
expanded: unknown;
|
|
31
|
+
}>;
|
|
32
|
+
defaults: Partial<{
|
|
33
|
+
model: unknown;
|
|
34
|
+
thinkingLevel: unknown;
|
|
35
|
+
}>;
|
|
36
|
+
}>;
|
|
37
|
+
|
|
38
|
+
export const defaultPiWebSettings: PiWebSettings = {
|
|
39
|
+
version: 1,
|
|
40
|
+
appearance: {
|
|
41
|
+
density: "comfortable",
|
|
42
|
+
},
|
|
43
|
+
composer: {
|
|
44
|
+
queueMode: "steer",
|
|
45
|
+
expanded: false,
|
|
46
|
+
},
|
|
47
|
+
defaults: {},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function cloneSettings(value: PiWebSettings): PiWebSettings {
|
|
51
|
+
return JSON.parse(JSON.stringify(value)) as PiWebSettings;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
55
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeModel(value: unknown) {
|
|
59
|
+
if (!isRecord(value)) return undefined;
|
|
60
|
+
const provider = typeof value.provider === "string" ? value.provider.trim() : "";
|
|
61
|
+
const id = typeof value.id === "string" ? value.id.trim() : "";
|
|
62
|
+
return provider && id ? { provider, id } : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function normalizeSettings(value: unknown): PiWebSettings {
|
|
66
|
+
const settings = cloneSettings(defaultPiWebSettings);
|
|
67
|
+
if (!isRecord(value)) return settings;
|
|
68
|
+
|
|
69
|
+
const appearance = isRecord(value.appearance) ? value.appearance : undefined;
|
|
70
|
+
if (appearance?.density === "compact" || appearance?.density === "comfortable") {
|
|
71
|
+
settings.appearance.density = appearance.density;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const composer = isRecord(value.composer) ? value.composer : undefined;
|
|
75
|
+
if (composer?.queueMode === "followUp" || composer?.queueMode === "steer") {
|
|
76
|
+
settings.composer.queueMode = composer.queueMode;
|
|
77
|
+
}
|
|
78
|
+
if (typeof composer?.expanded === "boolean") settings.composer.expanded = composer.expanded;
|
|
79
|
+
|
|
80
|
+
const defaults = isRecord(value.defaults) ? value.defaults : undefined;
|
|
81
|
+
const model = normalizeModel(defaults?.model);
|
|
82
|
+
if (model) settings.defaults.model = model;
|
|
83
|
+
if (typeof defaults?.thinkingLevel === "string" && defaults.thinkingLevel.trim()) {
|
|
84
|
+
settings.defaults.thinkingLevel = defaults.thinkingLevel.trim();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return settings;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function applySettingsPatch(current: PiWebSettings, patch: unknown): PiWebSettings {
|
|
91
|
+
if (!isRecord(patch)) return cloneSettings(current);
|
|
92
|
+
const next = cloneSettings(current);
|
|
93
|
+
|
|
94
|
+
if (isRecord(patch.appearance)) {
|
|
95
|
+
if (patch.appearance.density === "comfortable" || patch.appearance.density === "compact") {
|
|
96
|
+
next.appearance.density = patch.appearance.density;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (isRecord(patch.composer)) {
|
|
101
|
+
if (patch.composer.queueMode === "steer" || patch.composer.queueMode === "followUp") {
|
|
102
|
+
next.composer.queueMode = patch.composer.queueMode;
|
|
103
|
+
}
|
|
104
|
+
if (typeof patch.composer.expanded === "boolean") next.composer.expanded = patch.composer.expanded;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (isRecord(patch.defaults)) {
|
|
108
|
+
if ("model" in patch.defaults) {
|
|
109
|
+
const model = normalizeModel(patch.defaults.model);
|
|
110
|
+
if (model) next.defaults.model = model;
|
|
111
|
+
else delete next.defaults.model;
|
|
112
|
+
}
|
|
113
|
+
if ("thinkingLevel" in patch.defaults) {
|
|
114
|
+
if (typeof patch.defaults.thinkingLevel === "string" && patch.defaults.thinkingLevel.trim()) {
|
|
115
|
+
next.defaults.thinkingLevel = patch.defaults.thinkingLevel.trim();
|
|
116
|
+
} else {
|
|
117
|
+
delete next.defaults.thinkingLevel;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return normalizeSettings(next);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function createSettingsStore(file: string) {
|
|
126
|
+
let cached: PiWebSettings | undefined;
|
|
127
|
+
|
|
128
|
+
async function read() {
|
|
129
|
+
if (cached) return cloneSettings(cached);
|
|
130
|
+
try {
|
|
131
|
+
cached = normalizeSettings(JSON.parse(await readFile(file, "utf-8")));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
134
|
+
console.warn(`Could not read pi-web settings at ${file}:`, error);
|
|
135
|
+
}
|
|
136
|
+
cached = cloneSettings(defaultPiWebSettings);
|
|
137
|
+
}
|
|
138
|
+
return cloneSettings(cached);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function write(settings: PiWebSettings) {
|
|
142
|
+
cached = normalizeSettings(settings);
|
|
143
|
+
await mkdir(dirname(file), { recursive: true });
|
|
144
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
145
|
+
await writeFile(tmp, `${JSON.stringify(cached, null, 2)}\n`, "utf-8");
|
|
146
|
+
await rename(tmp, file);
|
|
147
|
+
return cloneSettings(cached);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function patch(value: unknown) {
|
|
151
|
+
return write(applySettingsPatch(await read(), value));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { file, read, write, patch };
|
|
155
|
+
}
|
package/server/types.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface PiWebModel {
|
|
2
|
+
provider: string;
|
|
3
|
+
id: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
reasoning?: boolean;
|
|
6
|
+
contextWindow?: number;
|
|
7
|
+
maxTokens?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PiWebSession {
|
|
11
|
+
sessionId: string;
|
|
12
|
+
sessionFile: string;
|
|
13
|
+
isStreaming: boolean;
|
|
14
|
+
isCompacting?: boolean;
|
|
15
|
+
pendingMessageCount?: number;
|
|
16
|
+
model: PiWebModel;
|
|
17
|
+
thinkingLevel: string;
|
|
18
|
+
messages: unknown[];
|
|
19
|
+
agent: { state: { messages: unknown[] } };
|
|
20
|
+
sessionName?: string;
|
|
21
|
+
sessionManager: {
|
|
22
|
+
newSession(): void;
|
|
23
|
+
setSessionFile?(path: string): void;
|
|
24
|
+
buildSessionContext(): { messages: unknown[] };
|
|
25
|
+
getSessionName?(): string | undefined;
|
|
26
|
+
getSessionDir?(): string;
|
|
27
|
+
getLeafId?(): string | null;
|
|
28
|
+
getEntry?(id: string): unknown;
|
|
29
|
+
getBranch?(fromId?: string): unknown[];
|
|
30
|
+
getTree?(): unknown[];
|
|
31
|
+
getLabel?(id: string): string | undefined;
|
|
32
|
+
branch?(entryId: string): void;
|
|
33
|
+
resetLeaf?(): void;
|
|
34
|
+
appendLabelChange?(targetId: string, label: string | undefined): string;
|
|
35
|
+
};
|
|
36
|
+
modelRegistry: {
|
|
37
|
+
getAvailable(): PiWebModel[];
|
|
38
|
+
find(provider: string, id: string): PiWebModel | undefined;
|
|
39
|
+
};
|
|
40
|
+
extensionRunner?: {
|
|
41
|
+
getRegisteredCommands?(): unknown[];
|
|
42
|
+
};
|
|
43
|
+
promptTemplates?: unknown[];
|
|
44
|
+
resourceLoader?: {
|
|
45
|
+
getPrompts?(): { prompts: unknown[] };
|
|
46
|
+
getSkills?(): { skills: unknown[] };
|
|
47
|
+
};
|
|
48
|
+
bindExtensions?(bindings: unknown): Promise<void>;
|
|
49
|
+
getAvailableThinkingLevels(): string[];
|
|
50
|
+
getSessionName?(): string | undefined;
|
|
51
|
+
getContextUsage?(): { tokens: number | null; contextWindow: number; percent: number | null } | undefined;
|
|
52
|
+
setSessionName?(name: string): void;
|
|
53
|
+
setModel(model: unknown): Promise<void>;
|
|
54
|
+
setThinkingLevel(level: string): void;
|
|
55
|
+
reload?(): Promise<void>;
|
|
56
|
+
navigateTree?(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean; aborted?: boolean; summaryEntry?: unknown }>;
|
|
57
|
+
abortBranchSummary?(): void;
|
|
58
|
+
compact?(customInstructions?: string): Promise<unknown>;
|
|
59
|
+
abortCompaction?(): void;
|
|
60
|
+
prompt(message: string, options?: { images?: unknown[]; streamingBehavior?: string }): Promise<void>;
|
|
61
|
+
abort(): Promise<void>;
|
|
62
|
+
clearQueue?(): void;
|
|
63
|
+
subscribe?(listener: (event: unknown) => void): (() => void) | undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PiWebSessionInfo {
|
|
67
|
+
id: string;
|
|
68
|
+
path: string;
|
|
69
|
+
name: string;
|
|
70
|
+
firstMessage: string;
|
|
71
|
+
created: Date;
|
|
72
|
+
modified: Date;
|
|
73
|
+
messageCount: number;
|
|
74
|
+
allMessagesText: string;
|
|
75
|
+
cwd: string;
|
|
76
|
+
}
|