pi-web-ui 0.24.0 → 0.26.0

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.
@@ -18,7 +18,7 @@ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentS
18
18
  import { Type } from "typebox";
19
19
  import { serializeMessage, serializeStreamingMessage, } from "./serialize.js";
20
20
  import { loadCommands, saveCommandsFile, TerminalManager, } from "./terminals.js";
21
- import { findVisionModels, transcribeImages, } from "./vision-bridge.js";
21
+ import { buildVisionBridgePrompt, findVisionModels, SYSTEM_PROMPT, transcribeImages, } from "./vision-bridge.js";
22
22
  const SNAPSHOT_INTERVAL_MS = 60;
23
23
  const WIDGET_REFRESH_MS = 2000;
24
24
  const WIDGET_WIDTH = 80;
@@ -907,6 +907,8 @@ class ClientStateStore {
907
907
  disabledExtensions: s?.settings?.disabledExtensions ?? [],
908
908
  visionBridgeEnabled: s?.settings?.visionBridgeEnabled ?? true,
909
909
  visionBridgeModel: s?.settings?.visionBridgeModel ?? null,
910
+ visionBridgePromptMode: s?.settings?.visionBridgePromptMode === "replace" ? "replace" : "append",
911
+ visionBridgePrompt: s?.settings?.visionBridgePrompt ?? "",
910
912
  };
911
913
  }
912
914
  /** Persist the client's settings-panel state (partial merge). */
@@ -921,6 +923,10 @@ class ClientStateStore {
921
923
  disabledExtensions: settings.disabledExtensions ?? cur.disabledExtensions ?? [],
922
924
  visionBridgeEnabled: settings.visionBridgeEnabled ?? cur.visionBridgeEnabled ?? true,
923
925
  visionBridgeModel: settings.visionBridgeModel ?? cur.visionBridgeModel ?? null,
926
+ visionBridgePromptMode: settings.visionBridgePromptMode ??
927
+ cur.visionBridgePromptMode ??
928
+ "append",
929
+ visionBridgePrompt: settings.visionBridgePrompt ?? cur.visionBridgePrompt ?? "",
924
930
  };
925
931
  this.save();
926
932
  }
@@ -1108,6 +1114,30 @@ export class ClientSession {
1108
1114
  * a question doesn't re-burn tokens on re-transcribing identical screenshots.
1109
1115
  */
1110
1116
  visionBridgeCache = new Map();
1117
+ /** Most recent built-in (default) system prompt observed by the
1118
+ * resource-loader override — surfaced via settings_state so the
1119
+ * replace-mode editor can show the prompt it would otherwise replace.
1120
+ * Only non-empty when the user has a system-prompt file. */
1121
+ lastBaseSystemPrompt = "";
1122
+ /** The system prompt the replace-mode editor should show as its seed:
1123
+ * the user's system-prompt file content if one exists, otherwise the
1124
+ * SDK's built-in default actually in effect (agent.state.systemPrompt,
1125
+ * which the loader rebuilds at session init). If the user HAS a custom
1126
+ * prompt the seed is only cosmetic — an unmodified seed is saved as
1127
+ * empty and the server falls back to the true base. */
1128
+ effectiveDefaultSystemPrompt() {
1129
+ if (this.lastBaseSystemPrompt)
1130
+ return this.lastBaseSystemPrompt;
1131
+ try {
1132
+ const sp = this.session.agent.state.systemPrompt;
1133
+ if (typeof sp === "string" && sp)
1134
+ return sp;
1135
+ }
1136
+ catch {
1137
+ // Session not ready yet.
1138
+ }
1139
+ return "";
1140
+ }
1111
1141
  /** Web-facing extension UI context (widgets, notifications). */
1112
1142
  webUi = new WebUIContext((msg) => this.emit(msg));
1113
1143
  widgetsTimer = null;
@@ -1197,10 +1227,17 @@ export class ClientSession {
1197
1227
  // 新对话(新 runtime)也会自动带上当前设置。
1198
1228
  resourceLoaderOptions: {
1199
1229
  // 系统提示词:replace 模式整体替换;append 模式追加到提示词末尾。
1200
- systemPromptOverride: (base) => this.settings.promptMode === "replace" &&
1201
- this.settings.customSystemPrompt.trim()
1202
- ? this.settings.customSystemPrompt
1203
- : base,
1230
+ systemPromptOverride: (base) => {
1231
+ // Remember the built-in default so the settings panel can show
1232
+ // it when the user edits in replace mode.
1233
+ if (typeof base === "string" && base) {
1234
+ this.lastBaseSystemPrompt = base;
1235
+ }
1236
+ return this.settings.promptMode === "replace" &&
1237
+ this.settings.customSystemPrompt.trim()
1238
+ ? this.settings.customSystemPrompt
1239
+ : base;
1240
+ },
1204
1241
  appendSystemPromptOverride: (base) => {
1205
1242
  const out = [...base];
1206
1243
  const custom = this.settings.customSystemPrompt.trim();
@@ -2531,6 +2568,13 @@ export class ClientSession {
2531
2568
  customSystemPrompt: this.settings.customSystemPrompt,
2532
2569
  visionBridgeEnabled: this.settings.visionBridgeEnabled,
2533
2570
  visionBridgeModel: this.settings.visionBridgeModel,
2571
+ visionBridgePromptMode: this.settings.visionBridgePromptMode,
2572
+ visionBridgePrompt: this.settings.visionBridgePrompt,
2573
+ // The built-in prompts, so the replace-mode editors can prefill the
2574
+ // text they would otherwise replace (empty until the resource-loader
2575
+ // has run once for the system prompt).
2576
+ defaultSystemPrompt: this.effectiveDefaultSystemPrompt(),
2577
+ visionBridgeDefaultPrompt: SYSTEM_PROMPT,
2534
2578
  visionModels: this.collectVisionModels(),
2535
2579
  disabledSkills: [...this.settings.disabledSkills],
2536
2580
  disabledExtensions: [...this.settings.disabledExtensions],
@@ -2577,6 +2621,12 @@ export class ClientSession {
2577
2621
  if (partial.visionBridgeModel !== undefined) {
2578
2622
  this.settings.visionBridgeModel = partial.visionBridgeModel ?? null;
2579
2623
  }
2624
+ if (partial.visionBridgePromptMode !== undefined) {
2625
+ this.settings.visionBridgePromptMode = partial.visionBridgePromptMode;
2626
+ }
2627
+ if (partial.visionBridgePrompt !== undefined) {
2628
+ this.settings.visionBridgePrompt = partial.visionBridgePrompt;
2629
+ }
2580
2630
  this.stateStore.saveSettings(this.clientId, this.settings);
2581
2631
  this.pushSettings();
2582
2632
  if (needsReload)
@@ -2619,6 +2669,8 @@ export class ClientSession {
2619
2669
  // Presets don't capture vision-bridge prefs — keep the current ones.
2620
2670
  visionBridgeEnabled: this.settings.visionBridgeEnabled,
2621
2671
  visionBridgeModel: this.settings.visionBridgeModel,
2672
+ visionBridgePromptMode: this.settings.visionBridgePromptMode,
2673
+ visionBridgePrompt: this.settings.visionBridgePrompt,
2622
2674
  };
2623
2675
  this.stateStore.saveSettings(this.clientId, this.settings);
2624
2676
  this.pushSettings();
@@ -2918,9 +2970,14 @@ export class ClientSession {
2918
2970
  else {
2919
2971
  // Batch hash so re-sending identical images (edit & re-ask) reuses
2920
2972
  // the transcript instead of re-burning tokens on the vision API.
2973
+ // The active transcription prompt is part of the key: changing
2974
+ // the custom prompt must invalidate cached transcripts made with
2975
+ // the old prompt.
2921
2976
  const batchHash = bridgedImages
2922
2977
  .map((b) => `${b.att.name ?? "img"}:${b.raw.slice(0, 48)}`)
2923
- .join("|");
2978
+ .join("|") +
2979
+ "::" +
2980
+ buildVisionBridgePrompt(this.settings.visionBridgePromptMode, this.settings.visionBridgePrompt);
2924
2981
  let transcript = this.visionBridgeCache.get(batchHash);
2925
2982
  if (transcript === undefined) {
2926
2983
  this.emit({
@@ -2934,7 +2991,10 @@ export class ClientSession {
2934
2991
  data: b.raw,
2935
2992
  mimeType: b.mimeType,
2936
2993
  name: b.att.name,
2937
- })), { model: chosenModel ?? undefined });
2994
+ })), {
2995
+ model: chosenModel ?? undefined,
2996
+ systemPrompt: buildVisionBridgePrompt(this.settings.visionBridgePromptMode, this.settings.visionBridgePrompt),
2997
+ });
2938
2998
  this.visionBridgeCache.set(batchHash, transcript);
2939
2999
  this.emit({
2940
3000
  type: "notice",
@@ -474,6 +474,8 @@ wss.on("connection", (ws) => {
474
474
  disabledExtensions: msg.disabledExtensions,
475
475
  visionBridgeEnabled: msg.visionBridgeEnabled,
476
476
  visionBridgeModel: msg.visionBridgeModel,
477
+ visionBridgePromptMode: msg.visionBridgePromptMode,
478
+ visionBridgePrompt: msg.visionBridgePrompt,
477
479
  });
478
480
  break;
479
481
  case "save_preset":
@@ -31,8 +31,11 @@ export function findVisionModels(runtime) {
31
31
  * Evidence-first transcription prompt, modeled on modlens' output contract:
32
32
  * full verbatim text, reading-order layout blocks, entities/relations, chart
33
33
  * axes & data. Emphasizes honesty over hallucination.
34
+ *
35
+ * Exported so the settings panel can offer a custom prompt (append to this
36
+ * default or replace it entirely).
34
37
  */
35
- const SYSTEM_PROMPT = `You are a vision bridge for a text-only language model. You receive one or more images and must transcribe them into precise, structured text evidence so another model that cannot see images can answer questions about them accurately.
38
+ export const SYSTEM_PROMPT = `You are a vision bridge for a text-only language model. You receive one or more images and must transcribe them into precise, structured text evidence so another model that cannot see images can answer questions about them accurately.
36
39
 
37
40
  Follow these rules:
38
41
  1. Transcribe ALL visible text verbatim, preserving wording, spelling, punctuation and line breaks. This is the most important part — the reader relies on your transcription, not on the image.
@@ -42,6 +45,21 @@ Follow these rules:
42
45
  5. If part of the image is too blurry/low-resolution to read, say "(读不清)" or "unclear" for that part — NEVER invent or guess content you cannot see.
43
46
  6. If there are multiple images, address them in order (图 1 / Image 1, 图 2 / Image 2, ...).
44
47
  7. Output only the transcript. No preamble, no commentary about the image itself.`;
48
+ /**
49
+ * Assemble the final vision-model system prompt from the settings-panel prefs.
50
+ * mode "append": custom text appended after the default prompt (empty custom =
51
+ * pure default). mode "replace": custom text REPLACES the default prompt, but
52
+ * an empty custom text still falls back to the default (never send an empty
53
+ * system prompt to the vision model).
54
+ */
55
+ export function buildVisionBridgePrompt(mode, custom) {
56
+ const text = custom?.trim() ?? "";
57
+ if (mode === "replace" && text)
58
+ return text;
59
+ if (text)
60
+ return `${SYSTEM_PROMPT}\n\n${text}`;
61
+ return SYSTEM_PROMPT;
62
+ }
45
63
  /** Per-batch user instruction appended after the images. */
46
64
  function buildUserPrompt(count) {
47
65
  if (count <= 1) {
@@ -77,7 +95,7 @@ export async function transcribeImages(runtime, images, options = {}) {
77
95
  : "image/png",
78
96
  }));
79
97
  const context = {
80
- systemPrompt: SYSTEM_PROMPT,
98
+ systemPrompt: options.systemPrompt ?? SYSTEM_PROMPT,
81
99
  messages: [
82
100
  {
83
101
  role: "user",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-web-ui",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
5
5
  "license": "MIT",
6
6
  "type": "module",