Add blinking cursor to active streaming text
This commit is contained in:
@@ -546,7 +546,7 @@ const MessageBubble = React.memo(function MessageBubble({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isUser && msg.timeline && msg.timeline.length > 0 && (
|
{!isUser && msg.timeline && msg.timeline.length > 0 && (
|
||||||
<Timeline entries={msg.timeline} />
|
<Timeline entries={msg.timeline} isActiveStream={sending && msgIndex === messages.length - 1} />
|
||||||
)}
|
)}
|
||||||
{/*
|
{/*
|
||||||
Render the legacy bottom content bubble ONLY when:
|
Render the legacy bottom content bubble ONLY when:
|
||||||
@@ -600,7 +600,7 @@ const MessageBubble = React.memo(function MessageBubble({
|
|||||||
* name with a ×N counter. The flow visually mirrors what actually
|
* name with a ×N counter. The flow visually mirrors what actually
|
||||||
* happened: thought → tools → thought → tools → ... → final summary.
|
* happened: thought → tools → thought → tools → ... → final summary.
|
||||||
*/
|
*/
|
||||||
function Timeline({ entries }: { entries: TimelineEntry[] }) {
|
function Timeline({ entries, isActiveStream }: { entries: TimelineEntry[], isActiveStream?: boolean }) {
|
||||||
// Walk the entries and emit a renderable list. Adjacent same-category
|
// Walk the entries and emit a renderable list. Adjacent same-category
|
||||||
// tool entries get bundled into a TimelineToolGroup; thought and
|
// tool entries get bundled into a TimelineToolGroup; thought and
|
||||||
// text entries pass through as-is.
|
// text entries pass through as-is.
|
||||||
@@ -634,11 +634,12 @@ function Timeline({ entries }: { entries: TimelineEntry[] }) {
|
|||||||
return (
|
return (
|
||||||
<div style={{ marginBottom: 6 }}>
|
<div style={{ marginBottom: 6 }}>
|
||||||
{items.map((item, i) => {
|
{items.map((item, i) => {
|
||||||
|
const isLast = i === items.length - 1;
|
||||||
if (item.kind === "thought") {
|
if (item.kind === "thought") {
|
||||||
return <TimelineText key={i} text={item.text} />;
|
return <TimelineText key={i} text={item.text} isStreaming={isActiveStream && isLast} />;
|
||||||
}
|
}
|
||||||
if (item.kind === "text") {
|
if (item.kind === "text") {
|
||||||
return <TimelineText key={i} text={item.text} />;
|
return <TimelineText key={i} text={item.text} isStreaming={isActiveStream && isLast} />;
|
||||||
}
|
}
|
||||||
if (item.kind === "toolGroup") {
|
if (item.kind === "toolGroup") {
|
||||||
return (
|
return (
|
||||||
@@ -660,7 +661,7 @@ function Timeline({ entries }: { entries: TimelineEntry[] }) {
|
|||||||
* bubble so each round of multi-tool-loop output reads as a discrete
|
* bubble so each round of multi-tool-loop output reads as a discrete
|
||||||
* step instead of concatenating into a wall of text.
|
* step instead of concatenating into a wall of text.
|
||||||
*/
|
*/
|
||||||
function TimelineText({ text }: { text: string }) {
|
function TimelineText({ text, isStreaming }: { text: string; isStreaming?: boolean }) {
|
||||||
const proseWrap: React.CSSProperties = {
|
const proseWrap: React.CSSProperties = {
|
||||||
overflowWrap: "anywhere",
|
overflowWrap: "anywhere",
|
||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
@@ -684,7 +685,7 @@ function TimelineText({ text }: { text: string }) {
|
|||||||
<span
|
<span
|
||||||
style={proseWrap}
|
style={proseWrap}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: renderMarkdown(stripRawToolLogs(text)),
|
__html: renderMarkdown(stripRawToolLogs(text)) + (isStreaming ? `<span class="animate-pulse" style="display:inline-block; width:6px; height:13px; background-color:#9ca3af; vertical-align:-1px; margin-left:2px; border-radius:1px;"></span>` : ""),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user