Skip to content

Commit 0a02442

Browse files
committed
fix(acp): sync new responding indicator with active prompt state
1 parent 808b73a commit 0a02442

File tree

3 files changed

+95
-21
lines changed

3 files changed

+95
-21
lines changed

src/pages/acp/acp.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,14 +487,37 @@ export default function AcpPageInclude() {
487487
elements.$cancelBtn.style.display = value ? "flex" : "none";
488488
}
489489
updateStatusDot(value ? "connecting" : "connected");
490+
if (currentView === "chat") {
491+
syncTimeline();
492+
}
490493
}
491494

492495
// ─── Event Handlers ───
496+
function getActiveAgentMessageId() {
497+
if (!isPrompting || !client.session) return null;
498+
499+
const messages = client.session.messages;
500+
for (let index = messages.length - 1; index >= 0; index--) {
501+
if (messages[index].role === "agent") {
502+
return messages[index].id;
503+
}
504+
}
505+
506+
return null;
507+
}
508+
493509
function createTimelineElement(entry) {
494510
const cwd = client.session?.cwd || $form.getValues().cwd || "";
511+
const activeAgentMessageId = getActiveAgentMessageId();
495512
switch (entry.type) {
496513
case "message":
497-
return ChatMessage({ message: entry.message, cwd });
514+
return ChatMessage({
515+
message: entry.message,
516+
cwd,
517+
isResponding:
518+
entry.message.role === "agent" &&
519+
entry.message.id === activeAgentMessageId,
520+
});
498521
case "tool_call":
499522
return ToolCallCard({ toolCall: entry.toolCall });
500523
case "plan":
@@ -516,10 +539,15 @@ export default function AcpPageInclude() {
516539
if ($empty) $empty.remove();
517540
}
518541

542+
const activeAgentMessageId = getActiveAgentMessageId();
519543
entries.forEach((entry) => {
520544
const entryWithContext = {
521545
...entry,
522546
cwd: client.session?.cwd || $form.getValues().cwd || "",
547+
isResponding:
548+
entry.type === "message" &&
549+
entry.message.role === "agent" &&
550+
entry.message.id === activeAgentMessageId,
523551
};
524552
if (timelineElements.has(entry.entryId)) {
525553
timelineElements.get(entry.entryId).update(entryWithContext);

src/pages/acp/acp.scss

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -602,25 +602,52 @@
602602
}
603603
}
604604

605-
&.streaming {
606-
.acp-message-content::after {
607-
content: "";
608-
display: inline-block;
609-
width: 2px;
610-
height: 1em;
611-
margin-left: 2px;
612-
background: var(--active-color);
613-
animation: acp-blink 0.8s step-end infinite;
614-
vertical-align: text-bottom;
615-
}
616-
}
617-
618605
.acp-message-meta {
606+
display: flex;
607+
align-items: center;
608+
gap: 8px;
609+
flex-wrap: wrap;
619610
font-size: 0.65rem;
620611
opacity: 0.35;
621612
margin-top: 4px;
622613
padding: 0 2px;
623614
color: var(--secondary-text-color);
615+
616+
.acp-streaming-indicator {
617+
display: inline-flex;
618+
align-items: center;
619+
gap: 4px;
620+
padding: 2px 7px;
621+
border-radius: 999px;
622+
background: color-mix(
623+
in srgb,
624+
var(--active-color),
625+
transparent 88%
626+
);
627+
color: var(--active-color);
628+
opacity: 0.95;
629+
}
630+
631+
.acp-streaming-dot {
632+
width: 4px;
633+
height: 4px;
634+
border-radius: 50%;
635+
background: currentColor;
636+
animation: acp-streaming-pulse 1.2s ease-in-out infinite;
637+
638+
&:nth-child(2) {
639+
animation-delay: 0.18s;
640+
}
641+
642+
&:nth-child(3) {
643+
animation-delay: 0.36s;
644+
}
645+
}
646+
647+
.acp-streaming-label {
648+
font-weight: 600;
649+
letter-spacing: 0.02em;
650+
}
624651
}
625652
}
626653

@@ -1193,13 +1220,15 @@
11931220
}
11941221
}
11951222

1196-
@keyframes acp-blink {
1223+
@keyframes acp-streaming-pulse {
11971224
0%,
11981225
100% {
1199-
opacity: 1;
1226+
opacity: 0.35;
1227+
transform: scale(0.9);
12001228
}
1201-
50% {
1202-
opacity: 0;
1229+
40% {
1230+
opacity: 1;
1231+
transform: scale(1);
12031232
}
12041233
}
12051234

src/pages/acp/components/chatMessage.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,13 @@ async function resolveExistingPath(candidates = []) {
165165
return null;
166166
}
167167

168-
export default function ChatMessage({ message, cwd = "" }) {
168+
export default function ChatMessage({
169+
message,
170+
cwd = "",
171+
isResponding = false,
172+
}) {
169173
let messageCwd = cwd;
174+
let messageResponding = isResponding;
170175

171176
const $content = <div className="acp-message-content"></div>;
172177
const $meta = <div className="acp-message-meta"></div>;
@@ -255,9 +260,20 @@ export default function ChatMessage({ message, cwd = "" }) {
255260
hour: "2-digit",
256261
minute: "2-digit",
257262
});
258-
$meta.textContent = message.streaming ? `${timeStr} · streaming` : timeStr;
263+
$meta.innerHTML = "";
264+
$meta.append(<span className="acp-message-time">{timeStr}</span>);
265+
if (messageResponding) {
266+
$meta.append(
267+
<span className="acp-streaming-indicator">
268+
<span className="acp-streaming-dot"></span>
269+
<span className="acp-streaming-dot"></span>
270+
<span className="acp-streaming-dot"></span>
271+
<span className="acp-streaming-label">Responding</span>
272+
</span>,
273+
);
274+
}
259275
$role.textContent = message.role === "user" ? "You" : "Agent";
260-
$el.classList.toggle("streaming", Boolean(message.streaming));
276+
$el.classList.toggle("streaming", Boolean(messageResponding));
261277
}
262278

263279
renderContent();
@@ -275,6 +291,7 @@ export default function ChatMessage({ message, cwd = "" }) {
275291
$el.update = (msg) => {
276292
message = msg.message || msg;
277293
if (msg.cwd) messageCwd = msg.cwd;
294+
if ("isResponding" in msg) messageResponding = Boolean(msg.isResponding);
278295
renderContent();
279296
renderMeta();
280297
};

0 commit comments

Comments
 (0)