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) =>
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
-
})), {
|
|
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",
|
package/dist/server/index.js
CHANGED
|
@@ -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.
|
|
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",
|