@firstpick/pi-package-webui 0.5.4 → 0.5.6
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 +1 -1
- package/bin/pi-webui.mjs +55 -3
- package/package.json +4 -3
- package/public/app.js +1155 -100
- package/public/index.html +2 -2
- package/public/styles.css +307 -1
- package/tests/http-endpoints-harness.test.mjs +15 -1
- package/tests/mobile-static.test.mjs +49 -14
- package/tests/streaming-ui-coupling.test.mjs +175 -0
package/README.md
CHANGED
|
@@ -133,7 +133,7 @@ Environment variables:
|
|
|
133
133
|
- Multi-tab Pi sessions with isolated processes, working directories, prompt drafts, activity state, per-tab settings, and a workspace dashboard for common actions.
|
|
134
134
|
- Unified command palette (`Ctrl/Cmd+K`) for commands, tabs, models, sessions, settings, app controls, and frequent Web UI actions.
|
|
135
135
|
- Automatic tab naming from the first prompt, with `--name <name>` still available for an explicit initial tab name.
|
|
136
|
-
- Streaming chat transcript with Markdown, thinking output, tool/bash cards, queue and compaction events, edit-and-retry from user prompts, transcript search, copy buttons, and guarded abort controls that require holding Esc or the Abort button for 3 seconds.
|
|
136
|
+
- Streaming chat transcript with Markdown, copy buttons for fenced code blocks, rendered Mermaid diagrams from fenced `mermaid`/`mmd` code blocks, thinking output, tool/bash cards, queue and compaction events, edit-and-retry from user prompts, transcript search, copy buttons, and guarded abort controls that require holding Esc or the Abort button for 3 seconds.
|
|
137
137
|
- Prompt composer with uploads, drag/drop/paste, inline image support, generated text attachments for long input or clipboard text, editable text attachments, slash-command autocomplete, and `@` file/path references with live suggestions.
|
|
138
138
|
- Leading `!` and `!!` user-bash commands from the composer, serialized per tab; `!` keeps output in the next model context and `!!` excludes it.
|
|
139
139
|
- Browser-native Pi dialogs for `/model`, `/settings`, `/theme`, `/fork`, `/clone`, `/name`, `/resume`, `/tree`, `/login`, `/logout`, `/scoped-models`, `/tools`, and `/skills`, plus native-command adapter output for `/copy`, `/session`, `/new`, `/compact`, `/reload`, and `/export`.
|
package/bin/pi-webui.mjs
CHANGED
|
@@ -200,6 +200,7 @@ const MIME_TYPES = new Map([
|
|
|
200
200
|
[".html", "text/html; charset=utf-8"],
|
|
201
201
|
[".jsonl", "application/x-ndjson; charset=utf-8"],
|
|
202
202
|
[".js", "text/javascript; charset=utf-8"],
|
|
203
|
+
[".mjs", "text/javascript; charset=utf-8"],
|
|
203
204
|
[".css", "text/css; charset=utf-8"],
|
|
204
205
|
[".svg", "image/svg+xml"],
|
|
205
206
|
[".png", "image/png"],
|
|
@@ -4056,6 +4057,15 @@ function normalizeStaticPath(urlPath) {
|
|
|
4056
4057
|
return name;
|
|
4057
4058
|
}
|
|
4058
4059
|
|
|
4060
|
+
function mermaidStaticPath(urlPath) {
|
|
4061
|
+
const prefix = "/vendor/mermaid/";
|
|
4062
|
+
if (!String(urlPath || "").startsWith(prefix)) return undefined;
|
|
4063
|
+
const relative = urlPath.slice(prefix.length);
|
|
4064
|
+
if (relative === "mermaid.esm.min.mjs") return path.join(packageRoot, "node_modules", "mermaid", "dist", relative);
|
|
4065
|
+
if (/^chunks\/mermaid\.esm\.min\/[A-Za-z0-9._-]+\.mjs$/.test(relative)) return path.join(packageRoot, "node_modules", "mermaid", "dist", relative);
|
|
4066
|
+
return undefined;
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4059
4069
|
const compressWithBrotli = promisify(brotliCompress);
|
|
4060
4070
|
const compressWithGzip = promisify(gzip);
|
|
4061
4071
|
const STATIC_COMPRESSIBLE_EXTENSIONS = new Set([".html", ".css", ".js", ".mjs", ".svg", ".json", ".webmanifest"]);
|
|
@@ -4096,9 +4106,8 @@ function requestEtagMatches(req, etag) {
|
|
|
4096
4106
|
async function serveStatic(req, res, url) {
|
|
4097
4107
|
if (req.method !== "GET") return false;
|
|
4098
4108
|
const staticName = normalizeStaticPath(url.pathname);
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
const filePath = path.join(publicDir, staticName);
|
|
4109
|
+
const filePath = staticName ? path.join(publicDir, staticName) : mermaidStaticPath(url.pathname);
|
|
4110
|
+
if (!filePath) return false;
|
|
4102
4111
|
const ext = path.extname(filePath);
|
|
4103
4112
|
const asset = await loadStaticAsset(filePath);
|
|
4104
4113
|
const headers = {
|
|
@@ -4697,6 +4706,11 @@ function extensionStatusMap(tab) {
|
|
|
4697
4706
|
return tab.extensionStatuses;
|
|
4698
4707
|
}
|
|
4699
4708
|
|
|
4709
|
+
function extensionWidgetMap(tab) {
|
|
4710
|
+
if (!tab.extensionWidgets) tab.extensionWidgets = new Map();
|
|
4711
|
+
return tab.extensionWidgets;
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4700
4714
|
function rememberExtensionStatusEvent(tab, event) {
|
|
4701
4715
|
if (event?.type !== "extension_ui_request" || event.method !== "setStatus" || !event.statusKey) return;
|
|
4702
4716
|
const statuses = extensionStatusMap(tab);
|
|
@@ -4704,10 +4718,23 @@ function rememberExtensionStatusEvent(tab, event) {
|
|
|
4704
4718
|
else statuses.delete(String(event.statusKey));
|
|
4705
4719
|
}
|
|
4706
4720
|
|
|
4721
|
+
function rememberExtensionWidgetEvent(tab, event) {
|
|
4722
|
+
if (event?.type !== "extension_ui_request" || event.method !== "setWidget") return;
|
|
4723
|
+
const widgetKey = event.widgetKey || event.id;
|
|
4724
|
+
if (!widgetKey) return;
|
|
4725
|
+
const widgets = extensionWidgetMap(tab);
|
|
4726
|
+
if (Array.isArray(event.widgetLines)) widgets.set(String(widgetKey), { ...event, widgetKey: String(widgetKey), widgetLines: event.widgetLines.map((line) => String(line)) });
|
|
4727
|
+
else widgets.delete(String(widgetKey));
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4707
4730
|
function clearExtensionStatuses(tab) {
|
|
4708
4731
|
tab?.extensionStatuses?.clear();
|
|
4709
4732
|
}
|
|
4710
4733
|
|
|
4734
|
+
function clearExtensionWidgets(tab) {
|
|
4735
|
+
tab?.extensionWidgets?.clear();
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4711
4738
|
function replayExtensionStatuses(tab, res) {
|
|
4712
4739
|
for (const [statusKey, statusText] of extensionStatusMap(tab)) {
|
|
4713
4740
|
sendSse(res, {
|
|
@@ -4725,6 +4752,24 @@ function replayExtensionStatuses(tab, res) {
|
|
|
4725
4752
|
}
|
|
4726
4753
|
}
|
|
4727
4754
|
|
|
4755
|
+
function replayExtensionWidgets(tab, res) {
|
|
4756
|
+
const pendingExtensionUiRequestCount = pendingExtensionUiRequests(tab).length;
|
|
4757
|
+
for (const [widgetKey, request] of extensionWidgetMap(tab)) {
|
|
4758
|
+
sendSse(res, {
|
|
4759
|
+
...request,
|
|
4760
|
+
type: "extension_ui_request",
|
|
4761
|
+
id: randomUUID(),
|
|
4762
|
+
method: "setWidget",
|
|
4763
|
+
widgetKey,
|
|
4764
|
+
tabId: tab.id,
|
|
4765
|
+
tabTitle: tab.title,
|
|
4766
|
+
replayed: true,
|
|
4767
|
+
tabActivity: tabActivitySnapshot(tab),
|
|
4768
|
+
pendingExtensionUiRequestCount,
|
|
4769
|
+
});
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4728
4773
|
function bashQueueForTab(tab) {
|
|
4729
4774
|
if (!tab.bashQueue) tab.bashQueue = [];
|
|
4730
4775
|
return tab.bashQueue;
|
|
@@ -5021,8 +5066,10 @@ function attachRpcToTab(tab, rpc) {
|
|
|
5021
5066
|
if (event?.type === "pi_process_exit" || event?.type === "pi_process_error") {
|
|
5022
5067
|
clearPendingExtensionUiRequests(tab);
|
|
5023
5068
|
clearExtensionStatuses(tab);
|
|
5069
|
+
clearExtensionWidgets(tab);
|
|
5024
5070
|
} else {
|
|
5025
5071
|
rememberExtensionStatusEvent(tab, scopedEvent);
|
|
5072
|
+
rememberExtensionWidgetEvent(tab, scopedEvent);
|
|
5026
5073
|
trackPendingExtensionUiRequest(tab, scopedEvent);
|
|
5027
5074
|
}
|
|
5028
5075
|
scopedEvent = { ...scopedEvent, tabActivity: tabActivitySnapshot(tab), pendingExtensionUiRequestCount: pendingExtensionUiRequests(tab).length };
|
|
@@ -5059,6 +5106,7 @@ async function createTab({ id: requestedId, index, title, titleSource, conversat
|
|
|
5059
5106
|
activity: createTabActivity(createdAt),
|
|
5060
5107
|
pendingExtensionUiRequests: new Map(),
|
|
5061
5108
|
extensionStatuses: new Map(),
|
|
5109
|
+
extensionWidgets: new Map(),
|
|
5062
5110
|
webuiHelperRequests: new Map(),
|
|
5063
5111
|
webuiHelperResponseIds: new Set(),
|
|
5064
5112
|
bashQueue: [],
|
|
@@ -5648,6 +5696,7 @@ async function updateTabCwd(id, cwd) {
|
|
|
5648
5696
|
resetTabActivity(tab);
|
|
5649
5697
|
clearPendingExtensionUiRequests(tab);
|
|
5650
5698
|
clearExtensionStatuses(tab);
|
|
5699
|
+
clearExtensionWidgets(tab);
|
|
5651
5700
|
const rpc = new PiRpcProcess({ ...piCommand, cwd: tab.cwd });
|
|
5652
5701
|
attachRpcToTab(tab, rpc);
|
|
5653
5702
|
rpc.start();
|
|
@@ -5685,6 +5734,7 @@ async function restartTabRpc(tab, reason = "reload") {
|
|
|
5685
5734
|
resetTabActivity(tab);
|
|
5686
5735
|
clearPendingExtensionUiRequests(tab);
|
|
5687
5736
|
clearExtensionStatuses(tab);
|
|
5737
|
+
clearExtensionWidgets(tab);
|
|
5688
5738
|
const rpc = new PiRpcProcess({ ...piCommand, cwd: tab.cwd });
|
|
5689
5739
|
attachRpcToTab(tab, rpc);
|
|
5690
5740
|
rpc.start();
|
|
@@ -7373,6 +7423,7 @@ const server = createServer(async (req, res) => {
|
|
|
7373
7423
|
activeRun: publicAppRunnerState(tab.appRunner),
|
|
7374
7424
|
});
|
|
7375
7425
|
replayExtensionStatuses(tab, res);
|
|
7426
|
+
replayExtensionWidgets(tab, res);
|
|
7376
7427
|
replayPendingExtensionUiRequests(tab, res);
|
|
7377
7428
|
const keepAlive = setInterval(() => res.write(": keepalive\n\n"), 15000);
|
|
7378
7429
|
req.on("close", () => {
|
|
@@ -7933,6 +7984,7 @@ const server = createServer(async (req, res) => {
|
|
|
7933
7984
|
rememberTabState(tab, response.data);
|
|
7934
7985
|
clearPendingExtensionUiRequests(tab);
|
|
7935
7986
|
clearExtensionStatuses(tab);
|
|
7987
|
+
clearExtensionWidgets(tab);
|
|
7936
7988
|
}
|
|
7937
7989
|
sendJson(res, response.success === false ? 400 : 200, responseWithTab(response, tab));
|
|
7938
7990
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firstpick/pi-package-webui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Pi Web UI companion package with a local browser UI CLI plus /webui-start and /webui-status commands.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/Firstp1ck/npm-packages/tree/main/pi-package-webui#readme",
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"test": "node tests/run-all.mjs"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@earendil-works/pi-coding-agent": "^0.79.8"
|
|
56
|
+
"@earendil-works/pi-coding-agent": "^0.79.8",
|
|
57
|
+
"mermaid": "^11.15.0"
|
|
57
58
|
},
|
|
58
59
|
"optionalDependencies": {
|
|
59
60
|
"@firstpick/pi-extension-btw": "^0.1.0",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"@firstpick/pi-extension-safety-guard": "^0.2.3",
|
|
64
65
|
"@firstpick/pi-extension-setup-skills": "^0.1.8",
|
|
65
66
|
"@firstpick/pi-extension-stats": "^0.2.6",
|
|
66
|
-
"@firstpick/pi-extension-todo-progress": "^0.2.
|
|
67
|
+
"@firstpick/pi-extension-todo-progress": "^0.2.5",
|
|
67
68
|
"@firstpick/pi-extension-tools": "^0.1.6",
|
|
68
69
|
"@firstpick/pi-extension-workflows": "^0.1.0",
|
|
69
70
|
"@firstpick/pi-package-remote-webui": "^0.1.2",
|