@narumitw/pi-webui 0.28.0 โ 0.29.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/README.md +8 -4
- package/package.json +14 -5
- package/src/server.ts +12 -11
- package/src/web/app.css +2 -0
- package/src/web/app.js +102 -995
- package/src/web/image-drag.js +0 -73
- package/src/web/index.html +3 -119
- package/src/web/markdown.js +0 -69
- package/src/web/transcript.js +0 -258
- package/src/web/ui/app.jsx +908 -0
- package/src/web/ui/client.js +690 -0
- package/src/web/ui/overlays.jsx +126 -0
- package/src/web/ui/styles.css +573 -0
- package/src/web/ui/view-helpers.js +68 -0
- package/src/web/styles.css +0 -946
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@narumitw/pi-webui) [](https://pi.dev) [](./LICENSE)
|
|
4
4
|
|
|
5
|
-
`@narumitw/pi-webui` adds a private,
|
|
5
|
+
`@narumitw/pi-webui` adds a private, focused browser companion to the current terminal-owned [Pi Coding Agent](https://pi.dev) session. It displays Pi's semantic conversation and tool activity as they happen and can send text or sanitized images back into that same session.
|
|
6
6
|
|
|
7
7
|
This package is intentionally different from the broader, separately maintained `@narumitw/pi-web` application. WebUI has one current-session chat page and no session manager, shell, file browser, git UI, control room, or task board.
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ This package is intentionally different from the broader, separately maintained
|
|
|
16
16
|
- Accepts pasted, dropped, or selected PNG, JPEG, WebP, GIF, BMP, TIFF, HEIC/HEIF, and AVIF images, strips metadata server-side, applies Pi-compatible size limits, and provides ordered thumbnails plus an enlarged preview.
|
|
17
17
|
- Reconnects from an ordered event cursor and replaces state from an authoritative snapshot after a gap.
|
|
18
18
|
- Keeps a failed browser draft and prevents rapid duplicate submission with request IDs.
|
|
19
|
-
- Uses
|
|
19
|
+
- Uses React with Radix Primitives, Themes, Colors, and Icons for accessible controls, disclosures, overlays, adaptive color, and consistent iconography. Published browser assets are bundled locally; runtime use needs no CDN, remote service, browser storage, or automatically launched browser.
|
|
20
20
|
|
|
21
21
|
## ๐ฆ Install
|
|
22
22
|
|
|
@@ -168,7 +168,10 @@ src/server.ts authenticated loopback HTTP/SSE server and raw attachment p
|
|
|
168
168
|
src/image-limits.ts shared configurable defaults, ceilings, and provider constraints
|
|
169
169
|
src/images.ts bounded provider-ready image processing
|
|
170
170
|
src/pi-settings.ts effective Pi image settings reader
|
|
171
|
-
src/web/
|
|
171
|
+
src/web/ui/ React source using Radix UI and the browser protocol client
|
|
172
|
+
src/web/app.js generated, bundled browser application
|
|
173
|
+
src/web/app.css generated Radix Themes, Colors, and local presentation styles
|
|
174
|
+
src/web/index.html minimal authenticated browser shell
|
|
172
175
|
```
|
|
173
176
|
|
|
174
177
|
## ๐งช Development
|
|
@@ -176,13 +179,14 @@ src/web/ framework-free browser page
|
|
|
176
179
|
From the repository root:
|
|
177
180
|
|
|
178
181
|
```bash
|
|
182
|
+
npm --workspace @narumitw/pi-webui run build:web
|
|
179
183
|
npm --workspace @narumitw/pi-webui run check
|
|
180
184
|
npm test
|
|
181
185
|
just try webui
|
|
182
186
|
just pack webui
|
|
183
187
|
```
|
|
184
188
|
|
|
185
|
-
The package preview must contain its manifest, license, README, TypeScript source, and static
|
|
189
|
+
Edit browser code under `src/web/ui/`, then run `build:web`; `check:web` (included in the package typecheck) rejects stale generated assets. The package preview must contain its manifest, license, README, TypeScript and browser source, and bundled static assets, but no tests, fixtures, cache, build scripts, or `node_modules`.
|
|
186
190
|
|
|
187
191
|
## ๐ Keywords
|
|
188
192
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-webui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.29.1",
|
|
4
|
+
"description": "Local Radix UI web companion for the current Pi terminal session.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -24,13 +24,21 @@
|
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
27
|
+
"build:web": "node scripts/build-web.mjs",
|
|
28
|
+
"check": "biome check --vcs-use-ignore-file=false src test scripts package.json tsconfig.json && npm run typecheck",
|
|
29
|
+
"check:web": "node scripts/build-web.mjs --check",
|
|
30
|
+
"format": "biome check --write --vcs-use-ignore-file=false src test scripts package.json tsconfig.json && npm run build:web",
|
|
31
|
+
"typecheck": "npm run check:web && tsc --noEmit"
|
|
30
32
|
},
|
|
31
33
|
"dependencies": {
|
|
34
|
+
"@radix-ui/colors": "^3.0.0",
|
|
35
|
+
"@radix-ui/react-icons": "^1.3.2",
|
|
36
|
+
"@radix-ui/themes": "^3.3.0",
|
|
32
37
|
"bmp-js": "^0.1.0",
|
|
33
38
|
"heic-decode": "^2.1.0",
|
|
39
|
+
"radix-ui": "^1.6.6",
|
|
40
|
+
"react": "^19.2.8",
|
|
41
|
+
"react-dom": "^19.2.8",
|
|
34
42
|
"sharp": "^0.35.3"
|
|
35
43
|
},
|
|
36
44
|
"peerDependencies": {
|
|
@@ -43,6 +51,7 @@
|
|
|
43
51
|
"@earendil-works/pi-ai": "0.80.10",
|
|
44
52
|
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
45
53
|
"@earendil-works/pi-tui": "0.80.10",
|
|
54
|
+
"esbuild": "^0.25.12",
|
|
46
55
|
"typescript": "7.0.2"
|
|
47
56
|
},
|
|
48
57
|
"repository": {
|
package/src/server.ts
CHANGED
|
@@ -23,6 +23,7 @@ const REQUEST_ID = /^[A-Za-z0-9_-]{1,120}$/;
|
|
|
23
23
|
const MAX_REQUESTS = 128;
|
|
24
24
|
const DEFAULT_MAX_DRAFT_TEXT_BYTES = 1024 * 1024;
|
|
25
25
|
const SSE_FLUSH_TIMEOUT_MS = 250;
|
|
26
|
+
const CSP_NONCE_PLACEHOLDER = "__PI_CSP_NONCE__";
|
|
26
27
|
|
|
27
28
|
export interface WebSendRequest {
|
|
28
29
|
requestId: string;
|
|
@@ -80,6 +81,7 @@ export class WebUIServer {
|
|
|
80
81
|
readonly origin: string;
|
|
81
82
|
private bootstrapToken?: string;
|
|
82
83
|
private readonly sessionSecret = token();
|
|
84
|
+
private readonly cspNonce = token();
|
|
83
85
|
private readonly cookieName = `pi_webui_${randomBytes(8).toString("hex")}`;
|
|
84
86
|
private readonly sockets = new Set<Socket>();
|
|
85
87
|
private readonly sseClients = new Set<SseClient>();
|
|
@@ -231,17 +233,12 @@ export class WebUIServer {
|
|
|
231
233
|
await this.asset(response, "index.html", "text/html; charset=utf-8");
|
|
232
234
|
return;
|
|
233
235
|
}
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
["/app.js", "/state.js", "/markdown.js", "/transcript.js", "/image-drag.js"].includes(
|
|
237
|
-
url.pathname,
|
|
238
|
-
)
|
|
239
|
-
) {
|
|
240
|
-
await this.asset(response, url.pathname.slice(1), "text/javascript; charset=utf-8");
|
|
236
|
+
if (request.method === "GET" && url.pathname === "/app.js") {
|
|
237
|
+
await this.asset(response, "app.js", "text/javascript; charset=utf-8");
|
|
241
238
|
return;
|
|
242
239
|
}
|
|
243
|
-
if (request.method === "GET" && url.pathname === "/
|
|
244
|
-
await this.asset(response, "
|
|
240
|
+
if (request.method === "GET" && url.pathname === "/app.css") {
|
|
241
|
+
await this.asset(response, "app.css", "text/css; charset=utf-8");
|
|
245
242
|
return;
|
|
246
243
|
}
|
|
247
244
|
if (request.method === "GET" && url.pathname === "/api/state") {
|
|
@@ -745,7 +742,11 @@ export class WebUIServer {
|
|
|
745
742
|
}
|
|
746
743
|
|
|
747
744
|
private async asset(response: ServerResponse, name: string, contentType: string): Promise<void> {
|
|
748
|
-
const
|
|
745
|
+
const asset = await readAsset(name);
|
|
746
|
+
const content =
|
|
747
|
+
name === "index.html"
|
|
748
|
+
? Buffer.from(asset.toString("utf8").replaceAll(CSP_NONCE_PLACEHOLDER, this.cspNonce))
|
|
749
|
+
: asset;
|
|
749
750
|
response.writeHead(200, { "Content-Type": contentType, "Content-Length": content.byteLength });
|
|
750
751
|
response.end(content);
|
|
751
752
|
}
|
|
@@ -754,7 +755,7 @@ export class WebUIServer {
|
|
|
754
755
|
response.setHeader("Cache-Control", "no-store");
|
|
755
756
|
response.setHeader(
|
|
756
757
|
"Content-Security-Policy",
|
|
757
|
-
|
|
758
|
+
`default-src 'self'; script-src 'self'; style-src 'self' 'nonce-${this.cspNonce}'; img-src 'self' data: blob:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'`,
|
|
758
759
|
);
|
|
759
760
|
response.setHeader("Referrer-Policy", "no-referrer");
|
|
760
761
|
response.setHeader("X-Content-Type-Options", "nosniff");
|