Polish thinking streaming UI and ordering
This commit is contained in:
@@ -635,23 +635,7 @@ function Timeline({ entries }: { entries: TimelineEntry[] }) {
|
||||
<div style={{ marginBottom: 6 }}>
|
||||
{items.map((item, i) => {
|
||||
if (item.kind === "thought") {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
fontSize: "0.85rem",
|
||||
color: "#6b7280",
|
||||
fontStyle: "italic",
|
||||
padding: "8px 12px",
|
||||
background: "#f9fafb",
|
||||
borderLeft: "2px solid #d1d5db",
|
||||
marginBottom: 8,
|
||||
whiteSpace: "pre-wrap",
|
||||
}}
|
||||
>
|
||||
Thinking: {item.text}
|
||||
</div>
|
||||
);
|
||||
return <TimelineThought key={i} text={item.text} />;
|
||||
}
|
||||
if (item.kind === "text") {
|
||||
return <TimelineText key={i} text={item.text} />;
|
||||
@@ -2903,3 +2887,87 @@ export function ChatPanel({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TimelineThought({ text }: { text: string }) {
|
||||
// Auto-expand if the thought is actively streaming (i.e., less than a full turn old)
|
||||
// but let the user collapse it. To keep it simple, we default to false (collapsed),
|
||||
// but if the component mounts and text is very short (just starting to stream), we could
|
||||
// expand it. A better UX is to collapse it by default but allow expanding.
|
||||
// However, you asked that the final message display before the final thinking hides the response.
|
||||
// Wait, the prompt: "most tools collapse the thinking text once the next action starts to make the chat less noisy. But also gives the user to the option to expand it."
|
||||
|
||||
// We can track if we are the "latest" thought, but an easier way is to just use a ref
|
||||
// to see if we're actively receiving new props (streaming).
|
||||
|
||||
const [expanded, setExpanded] = React.useState(true);
|
||||
const textLenRef = React.useRef(text.length);
|
||||
|
||||
React.useEffect(() => {
|
||||
// If text stops growing for a bit, auto-collapse
|
||||
let t = setTimeout(() => {
|
||||
if (text.length === textLenRef.current) {
|
||||
setExpanded(false);
|
||||
}
|
||||
}, 1500);
|
||||
return () => clearTimeout(t);
|
||||
}, [text]);
|
||||
|
||||
React.useEffect(() => {
|
||||
textLenRef.current = text.length;
|
||||
}, [text]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
margin: "8px 0",
|
||||
fontFamily: "var(--font-inter),ui-sans-serif,sans-serif",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
padding: 0,
|
||||
cursor: "pointer",
|
||||
fontSize: "0.85rem",
|
||||
color: "#9ca3af",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
|
||||
transition: "transform 0.15s ease",
|
||||
display: "inline-block",
|
||||
fontSize: "0.6rem",
|
||||
}}
|
||||
>
|
||||
▶
|
||||
</span>
|
||||
Thinking...
|
||||
</button>
|
||||
{expanded && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 6,
|
||||
marginLeft: 4,
|
||||
padding: "8px 12px",
|
||||
borderLeft: "2px solid #e5e7eb",
|
||||
fontSize: "0.85rem",
|
||||
color: "#6b7280",
|
||||
fontStyle: "italic",
|
||||
background: "#f9fafb",
|
||||
whiteSpace: "pre-wrap",
|
||||
borderRadius: "0 4px 4px 0",
|
||||
}}
|
||||
>
|
||||
{text.trim()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user