@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 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
- if (!staticName) return false;
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.4",
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.4",
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",