@narumitw/pi-webui 0.20.2 → 0.22.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.
- package/README.md +44 -12
- package/package.json +3 -1
- package/src/attachments.ts +715 -0
- package/src/conversation.ts +23 -6
- package/src/drafts.ts +226 -0
- package/src/image-limits.ts +36 -0
- package/src/image-pipeline.ts +325 -0
- package/src/images.ts +182 -140
- package/src/pi-settings.ts +11 -6
- package/src/runtime.ts +138 -34
- package/src/sent-images.ts +279 -0
- package/src/server.ts +490 -30
- package/src/settings.ts +102 -7
- package/src/vendor.d.ts +30 -0
- package/src/web/app.js +558 -100
- package/src/web/image-drag.js +86 -0
- package/src/web/index.html +25 -2
- package/src/web/state.js +178 -8
- package/src/web/styles.css +176 -11
- package/src/web/transcript.js +66 -9
package/src/web/transcript.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { renderMarkdown } from "./markdown.js";
|
|
2
2
|
|
|
3
|
-
export function createTranscriptRenderer({
|
|
3
|
+
export function createTranscriptRenderer({
|
|
4
|
+
documentRef = document,
|
|
5
|
+
list,
|
|
6
|
+
onReattach = () => undefined,
|
|
7
|
+
onForget = () => undefined,
|
|
8
|
+
}) {
|
|
4
9
|
const messages = new Map();
|
|
5
10
|
|
|
6
11
|
return {
|
|
7
|
-
render(nextMessages, tools) {
|
|
12
|
+
render(nextMessages, tools, sentImages = { items: [] }) {
|
|
8
13
|
const toolById = new Map(tools.map((tool) => [tool.id, tool]));
|
|
14
|
+
const retainedImageIds = new Set((sentImages.items ?? []).map((item) => item.id));
|
|
9
15
|
const retained = new Set();
|
|
10
16
|
const changed = [];
|
|
11
17
|
let cursor = list.firstChild;
|
|
@@ -16,7 +22,18 @@ export function createTranscriptRenderer({ documentRef = document, list }) {
|
|
|
16
22
|
view = createMessageView(message.role, documentRef);
|
|
17
23
|
messages.set(message.id, view);
|
|
18
24
|
}
|
|
19
|
-
if (
|
|
25
|
+
if (
|
|
26
|
+
updateMessageView(
|
|
27
|
+
view,
|
|
28
|
+
message,
|
|
29
|
+
toolById,
|
|
30
|
+
retainedImageIds,
|
|
31
|
+
{ onReattach, onForget },
|
|
32
|
+
documentRef,
|
|
33
|
+
)
|
|
34
|
+
) {
|
|
35
|
+
changed.push(message.id);
|
|
36
|
+
}
|
|
20
37
|
if (view.node !== cursor) list.insertBefore(view.node, cursor);
|
|
21
38
|
cursor = view.node.nextSibling;
|
|
22
39
|
}
|
|
@@ -41,6 +58,11 @@ export function toolPhaseLabel(tool) {
|
|
|
41
58
|
return "Requested";
|
|
42
59
|
}
|
|
43
60
|
|
|
61
|
+
export function retainedImageStatus(block, retainedImageIds) {
|
|
62
|
+
if (!block?.retainedImageId) return "none";
|
|
63
|
+
return retainedImageIds?.has(block.retainedImageId) ? "eligible" : "expired";
|
|
64
|
+
}
|
|
65
|
+
|
|
44
66
|
export function toolCommandPreview(tool) {
|
|
45
67
|
const command = tool?.args?.command;
|
|
46
68
|
if (typeof command !== "string") return "";
|
|
@@ -67,7 +89,7 @@ function createMessageView(role, documentRef) {
|
|
|
67
89
|
return { node, heading, body, blocks: new Map(), role: "", final: undefined };
|
|
68
90
|
}
|
|
69
91
|
|
|
70
|
-
function updateMessageView(view, message, toolById, documentRef) {
|
|
92
|
+
function updateMessageView(view, message, toolById, retainedImageIds, actions, documentRef) {
|
|
71
93
|
let changed = false;
|
|
72
94
|
const role = knownRole(message.role);
|
|
73
95
|
if (view.role !== role) {
|
|
@@ -93,7 +115,18 @@ function updateMessageView(view, message, toolById, documentRef) {
|
|
|
93
115
|
view.blocks.set(key, blockView);
|
|
94
116
|
changed = true;
|
|
95
117
|
}
|
|
96
|
-
if (
|
|
118
|
+
if (
|
|
119
|
+
updateBlockView(
|
|
120
|
+
blockView,
|
|
121
|
+
block,
|
|
122
|
+
toolById.get(block.id),
|
|
123
|
+
retainedImageIds,
|
|
124
|
+
actions,
|
|
125
|
+
documentRef,
|
|
126
|
+
)
|
|
127
|
+
) {
|
|
128
|
+
changed = true;
|
|
129
|
+
}
|
|
97
130
|
if (blockView.node !== cursor) view.body.insertBefore(blockView.node, cursor);
|
|
98
131
|
cursor = blockView.node.nextSibling;
|
|
99
132
|
}
|
|
@@ -141,7 +174,7 @@ function createBlockView(block, documentRef) {
|
|
|
141
174
|
return { type: block.type, node, value: undefined };
|
|
142
175
|
}
|
|
143
176
|
|
|
144
|
-
function updateBlockView(view, block, tool, documentRef) {
|
|
177
|
+
function updateBlockView(view, block, tool, retainedImageIds, actions, documentRef) {
|
|
145
178
|
if (block.type === "text") {
|
|
146
179
|
if (view.value === block.text) return false;
|
|
147
180
|
view.value = block.text;
|
|
@@ -155,10 +188,34 @@ function updateBlockView(view, block, tool, documentRef) {
|
|
|
155
188
|
return true;
|
|
156
189
|
}
|
|
157
190
|
if (block.type === "image") {
|
|
191
|
+
const status = retainedImageStatus(block, retainedImageIds);
|
|
158
192
|
const label = `Image${block.mimeType ? ` · ${block.mimeType}` : ""}`;
|
|
159
|
-
|
|
160
|
-
view.value
|
|
161
|
-
view.
|
|
193
|
+
const value = `${label}:${status}`;
|
|
194
|
+
if (view.value === value) return false;
|
|
195
|
+
view.value = value;
|
|
196
|
+
const text = documentRef.createElement("span");
|
|
197
|
+
text.textContent = label;
|
|
198
|
+
view.node.replaceChildren(text);
|
|
199
|
+
if (status === "eligible") {
|
|
200
|
+
const attach = documentRef.createElement("button");
|
|
201
|
+
attach.type = "button";
|
|
202
|
+
attach.className = "message-image-action";
|
|
203
|
+
attach.textContent = "Attach again";
|
|
204
|
+
attach.setAttribute("aria-label", `Attach image again: ${label}`);
|
|
205
|
+
attach.addEventListener("click", () => actions.onReattach(block.retainedImageId));
|
|
206
|
+
const forget = documentRef.createElement("button");
|
|
207
|
+
forget.type = "button";
|
|
208
|
+
forget.className = "message-image-action subtle";
|
|
209
|
+
forget.textContent = "Forget";
|
|
210
|
+
forget.setAttribute("aria-label", `Forget retained image: ${label}`);
|
|
211
|
+
forget.addEventListener("click", () => actions.onForget(block.retainedImageId));
|
|
212
|
+
view.node.append(attach, forget);
|
|
213
|
+
} else if (status === "expired") {
|
|
214
|
+
const expired = documentRef.createElement("span");
|
|
215
|
+
expired.className = "message-image-expired";
|
|
216
|
+
expired.textContent = "Expired";
|
|
217
|
+
view.node.append(expired);
|
|
218
|
+
}
|
|
162
219
|
return true;
|
|
163
220
|
}
|
|
164
221
|
if (block.type === "toolCall") return updateToolView(view, block, tool);
|