@liguoshuai/pi-web-chat 2.18.13 → 2.19.1
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/package.json +3 -3
- package/public/app.js +41 -5
- package/public/index.html +2 -3
- package/public/style.css +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liguoshuai/pi-web-chat",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.1",
|
|
4
4
|
"description": "A ChatGPT/Gemini-style web UI for the pi coding agent, powered by pi's RPC mode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"express": "^4.21.2",
|
|
44
44
|
"ws": "^8.18.0",
|
|
45
|
-
"@liguoshuai/pi-chat-
|
|
46
|
-
"@liguoshuai/pi-chat-
|
|
45
|
+
"@liguoshuai/pi-chat-protocol": "2.19.1",
|
|
46
|
+
"@liguoshuai/pi-chat-server": "2.19.1"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "node --check server.js && node --check bin/pi-web-chat.js && node --check public/markdown.js && node --check public/app.js",
|
package/public/app.js
CHANGED
|
@@ -168,8 +168,17 @@ async function loadServerConfig() {
|
|
|
168
168
|
}
|
|
169
169
|
if (data.version) {
|
|
170
170
|
state.version = data.version;
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
}
|
|
172
|
+
if (data.piVersion) {
|
|
173
|
+
state.piVersion = data.piVersion;
|
|
174
|
+
}
|
|
175
|
+
const piVerEl = $("#piVersion");
|
|
176
|
+
if (piVerEl) {
|
|
177
|
+
piVerEl.textContent = state.piVersion ? ` v${state.piVersion}` : "";
|
|
178
|
+
}
|
|
179
|
+
const verEl = $("#appVersion");
|
|
180
|
+
if (verEl) {
|
|
181
|
+
verEl.textContent = state.version ? ` v${state.version}` : "";
|
|
173
182
|
}
|
|
174
183
|
if (data.defaultModel) {
|
|
175
184
|
state.defaultModel = data.defaultModel;
|
|
@@ -757,6 +766,7 @@ function reconstructFromEntries(entries, timing = null) {
|
|
|
757
766
|
lastUserTs = msgTs;
|
|
758
767
|
turnIndex++;
|
|
759
768
|
thinkingIdx = 0; // reset thinking index for the new turn
|
|
769
|
+
const isSteer = Boolean(m.isSteer || e.isSteer || e.customType === "steer" || m.customType === "steer");
|
|
760
770
|
// Extract optional images from user message content array
|
|
761
771
|
let images = [];
|
|
762
772
|
if (Array.isArray(m.content)) {
|
|
@@ -768,7 +778,7 @@ function reconstructFromEntries(entries, timing = null) {
|
|
|
768
778
|
url: c.data ? `data:${c.mimeType || "image/png"};base64,${c.data}` : (c.url || "")
|
|
769
779
|
}));
|
|
770
780
|
}
|
|
771
|
-
out.push({ role: "user", text: extractContentText(m.content), images, ts: msgTs });
|
|
781
|
+
out.push({ role: "user", text: extractContentText(m.content), images, ts: msgTs, isSteer });
|
|
772
782
|
} else if (m.role === "assistant") {
|
|
773
783
|
let turnDurationMs = null;
|
|
774
784
|
// Try timing data first, then fall back to timestamp heuristic.
|
|
@@ -836,10 +846,19 @@ function extractContentText(content) {
|
|
|
836
846
|
.join("\n\n");
|
|
837
847
|
}
|
|
838
848
|
|
|
839
|
-
function appendSystemNotice(text) {
|
|
849
|
+
function appendSystemNotice(text, replaceIfLast = true) {
|
|
840
850
|
if (!text) return;
|
|
841
851
|
const chatInner = $("#chat-inner");
|
|
842
852
|
if (!chatInner) return;
|
|
853
|
+
const lastChild = chatInner.lastElementChild;
|
|
854
|
+
if (replaceIfLast && lastChild && lastChild.classList.contains("system-notice-divider")) {
|
|
855
|
+
const textEl = lastChild.querySelector(".system-notice-text");
|
|
856
|
+
if (textEl) {
|
|
857
|
+
textEl.textContent = text;
|
|
858
|
+
scrollBottom();
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
843
862
|
const node = el("div", { class: "system-notice-divider" }, [
|
|
844
863
|
el("span", { class: "system-notice-text", text })
|
|
845
864
|
]);
|
|
@@ -1651,6 +1670,9 @@ function refreshStreamingContent() {
|
|
|
1651
1670
|
el("span", { class: "thinking-spinner" }),
|
|
1652
1671
|
el("span", { class: "thinking-label", text: "正在思考中…" })
|
|
1653
1672
|
]));
|
|
1673
|
+
content.appendChild(el("div", { class: "streaming-cursor-row" }, [
|
|
1674
|
+
el("span", { class: "typing-cursor" })
|
|
1675
|
+
]));
|
|
1654
1676
|
}
|
|
1655
1677
|
}
|
|
1656
1678
|
scrollBottom();
|
|
@@ -1663,7 +1685,7 @@ function refreshStreamingContent() {
|
|
|
1663
1685
|
const lastItem = items[items.length - 1];
|
|
1664
1686
|
if (lastItem.type === "text") {
|
|
1665
1687
|
const lastEl = content.lastElementChild;
|
|
1666
|
-
if (lastEl && !lastEl.classList.contains("thinking-block") && !lastEl.classList.contains("tool-block")) {
|
|
1688
|
+
if (lastEl && !lastEl.classList.contains("thinking-block") && !lastEl.classList.contains("tool-block") && !lastEl.classList.contains("streaming-cursor-row")) {
|
|
1667
1689
|
const showCursor = state.streaming;
|
|
1668
1690
|
lastEl.innerHTML = renderMarkdown(lastItem.text) + (showCursor ? '<span class="typing-cursor"></span>' : "");
|
|
1669
1691
|
scrollBottom();
|
|
@@ -1675,6 +1697,7 @@ function refreshStreamingContent() {
|
|
|
1675
1697
|
// Full rebuild (new items added or structure changed)
|
|
1676
1698
|
content.innerHTML = "";
|
|
1677
1699
|
const lastIdx = items.length - 1;
|
|
1700
|
+
let hasText = false;
|
|
1678
1701
|
for (let i = 0; i < items.length; i++) {
|
|
1679
1702
|
const item = items[i];
|
|
1680
1703
|
const isLast = (i === lastIdx);
|
|
@@ -1684,10 +1707,16 @@ function refreshStreamingContent() {
|
|
|
1684
1707
|
} else if (item.type === "tool") {
|
|
1685
1708
|
if (item.tc?.block) content.appendChild(item.tc.block);
|
|
1686
1709
|
} else if (item.type === "text") {
|
|
1710
|
+
hasText = true;
|
|
1687
1711
|
const showCursor = state.streaming && isLast;
|
|
1688
1712
|
content.appendChild(el("div", { html: renderMarkdown(item.text) + (showCursor ? '<span class="typing-cursor"></span>' : "") }));
|
|
1689
1713
|
}
|
|
1690
1714
|
}
|
|
1715
|
+
if (state.streaming && !hasText) {
|
|
1716
|
+
content.appendChild(el("div", { class: "streaming-cursor-row" }, [
|
|
1717
|
+
el("span", { class: "typing-cursor" })
|
|
1718
|
+
]));
|
|
1719
|
+
}
|
|
1691
1720
|
lastRenderedItemCount = items.length;
|
|
1692
1721
|
scrollBottom();
|
|
1693
1722
|
}
|
|
@@ -3071,6 +3100,13 @@ function submitSteer() {
|
|
|
3071
3100
|
return;
|
|
3072
3101
|
}
|
|
3073
3102
|
|
|
3103
|
+
// If there was an active streaming assistant block before steering, finalize it
|
|
3104
|
+
// so that subsequent assistant chunks render in a new block below this steer message!
|
|
3105
|
+
if (state.streamingMsg) {
|
|
3106
|
+
finalizeStreamingMsg();
|
|
3107
|
+
state.streaming = true; // resume streaming flag for subsequent response
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3074
3110
|
appendMessageNode("user", { text, isSteer: true, ts: Date.now() });
|
|
3075
3111
|
ta.value = "";
|
|
3076
3112
|
autoResize();
|
package/public/index.html
CHANGED
|
@@ -30,11 +30,10 @@
|
|
|
30
30
|
<div id="connStatus" class="conn-status" title="点击可重新连接"><span id="connDot" style="color: var(--danger);">●</span> <span id="connLabel">连接中…</span></div>
|
|
31
31
|
<div class="sidebar-bottom-meta">
|
|
32
32
|
<div class="sidebar-bottom-links">
|
|
33
|
-
<a href="https://pi.dev" target="_blank" rel="noopener">pi
|
|
33
|
+
<a href="https://pi.dev" target="_blank" rel="noopener">pi<span id="piVersion"></span></a>
|
|
34
34
|
<span class="sidebar-link-sep">·</span>
|
|
35
|
-
<a href="https://github.com/liguoshuai-1990/pi-
|
|
35
|
+
<a href="https://github.com/liguoshuai-1990/pi-chat" target="_blank" rel="noopener">pi-chat<span id="appVersion"></span></a>
|
|
36
36
|
</div>
|
|
37
|
-
<span id="appVersion" class="app-version" title="pi-web-chat 版本"></span>
|
|
38
37
|
</div>
|
|
39
38
|
</div>
|
|
40
39
|
<div class="sidebar-resizer" id="sidebarResizer" title="拖拽调整侧边栏宽度,双击恢复默认"></div>
|
package/public/style.css
CHANGED
|
@@ -342,7 +342,6 @@ body {
|
|
|
342
342
|
margin-top: 6px;
|
|
343
343
|
display: flex;
|
|
344
344
|
align-items: center;
|
|
345
|
-
justify-content: space-between;
|
|
346
345
|
gap: 8px;
|
|
347
346
|
}
|
|
348
347
|
.sidebar-bottom-links {
|
|
@@ -928,6 +927,13 @@ body {
|
|
|
928
927
|
to { opacity: 1; transform: scale(1.25); }
|
|
929
928
|
}
|
|
930
929
|
|
|
930
|
+
.streaming-cursor-row {
|
|
931
|
+
margin-top: 6px;
|
|
932
|
+
line-height: 1;
|
|
933
|
+
}
|
|
934
|
+
.streaming-cursor-row .typing-cursor::after {
|
|
935
|
+
margin-left: 0;
|
|
936
|
+
}
|
|
931
937
|
.typing-cursor::after {
|
|
932
938
|
content: "▋"; display: inline-block; margin-left: 2px;
|
|
933
939
|
animation: blink 1s steps(2) infinite; color: var(--accent);
|