story-kit: merge 174_story_constrain_thinking_traces_in_chat_panel
This commit is contained in:
@@ -13,6 +13,56 @@ import { StagePanel } from "./StagePanel";
|
||||
|
||||
const { useCallback, useEffect, useRef, useState } = React;
|
||||
|
||||
/** Fixed-height thinking trace block that auto-scrolls to bottom as text arrives. */
|
||||
function ThinkingBlock({ text }: { text: string }) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = scrollRef.current;
|
||||
if (el) {
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
}, [text]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="chat-thinking-block"
|
||||
ref={scrollRef}
|
||||
style={{
|
||||
maxHeight: "96px",
|
||||
overflowY: "auto",
|
||||
background: "#161b22",
|
||||
border: "1px solid #2d333b",
|
||||
borderRadius: "6px",
|
||||
padding: "6px 10px",
|
||||
fontSize: "0.78em",
|
||||
fontFamily: "monospace",
|
||||
color: "#6e7681",
|
||||
fontStyle: "italic",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
lineHeight: "1.4",
|
||||
marginBottom: "8px",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.8em",
|
||||
color: "#444c56",
|
||||
marginBottom: "4px",
|
||||
fontStyle: "normal",
|
||||
letterSpacing: "0.04em",
|
||||
textTransform: "uppercase",
|
||||
}}
|
||||
>
|
||||
thinking
|
||||
</span>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const NARROW_BREAKPOINT = 900;
|
||||
|
||||
function formatToolActivity(toolName: string): string {
|
||||
@@ -64,6 +114,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
const [availableModels, setAvailableModels] = useState<string[]>([]);
|
||||
const [claudeModels, setClaudeModels] = useState<string[]>([]);
|
||||
const [streamingContent, setStreamingContent] = useState("");
|
||||
const [streamingThinking, setStreamingThinking] = useState("");
|
||||
const [showApiKeyDialog, setShowApiKeyDialog] = useState(false);
|
||||
const [apiKeyInput, setApiKeyInput] = useState("");
|
||||
const [hasAnthropicKey, setHasAnthropicKey] = useState(false);
|
||||
@@ -208,9 +259,13 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
onToken: (content) => {
|
||||
setStreamingContent((prev: string) => prev + content);
|
||||
},
|
||||
onThinkingToken: (content) => {
|
||||
setStreamingThinking((prev: string) => prev + content);
|
||||
},
|
||||
onUpdate: (history) => {
|
||||
setMessages(history);
|
||||
setStreamingContent("");
|
||||
setStreamingThinking("");
|
||||
const last = history[history.length - 1];
|
||||
if (last?.role === "assistant" && !last.tool_calls) {
|
||||
setLoading(false);
|
||||
@@ -303,7 +358,8 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
lastScrollTopRef.current = currentScrollTop;
|
||||
};
|
||||
|
||||
const autoScrollKey = messages.length + streamingContent.length;
|
||||
const autoScrollKey =
|
||||
messages.length + streamingContent.length + streamingThinking.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -351,6 +407,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
setStreamingContent("");
|
||||
}
|
||||
|
||||
setStreamingThinking("");
|
||||
setLoading(false);
|
||||
setActivityStatus(null);
|
||||
} catch (e) {
|
||||
@@ -395,6 +452,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
}
|
||||
setLoading(true);
|
||||
setStreamingContent("");
|
||||
setStreamingThinking("");
|
||||
setActivityStatus(null);
|
||||
|
||||
try {
|
||||
@@ -471,6 +529,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
|
||||
clearMessages();
|
||||
setStreamingContent("");
|
||||
setStreamingThinking("");
|
||||
setLoading(false);
|
||||
setActivityStatus(null);
|
||||
setClaudeSessionId(null);
|
||||
@@ -761,6 +820,9 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{loading && streamingThinking && (
|
||||
<ThinkingBlock text={streamingThinking} />
|
||||
)}
|
||||
{loading && streamingContent && (
|
||||
<div
|
||||
style={{
|
||||
@@ -817,21 +879,23 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{loading && (activityStatus != null || !streamingContent) && (
|
||||
<div
|
||||
data-testid="activity-indicator"
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
color: "#888",
|
||||
fontSize: "0.9em",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
<span className="pulse">
|
||||
{activityStatus ?? "Thinking..."}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{loading &&
|
||||
(activityStatus != null ||
|
||||
(!streamingContent && !streamingThinking)) && (
|
||||
<div
|
||||
data-testid="activity-indicator"
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
color: "#888",
|
||||
fontSize: "0.9em",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
<span className="pulse">
|
||||
{activityStatus ?? "Thinking..."}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1075,7 +1139,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
stateVersion={agentStateVersion}
|
||||
/>
|
||||
|
||||
<StagePanel title="Done" items={pipeline.done} />
|
||||
<StagePanel title="Done" items={pipeline.done ?? []} />
|
||||
<StagePanel title="To Merge" items={pipeline.merge} />
|
||||
<StagePanel title="QA" items={pipeline.qa} />
|
||||
<StagePanel title="Current" items={pipeline.current} />
|
||||
|
||||
Reference in New Issue
Block a user