-
Notifications
You must be signed in to change notification settings - Fork 0
Various improvements to chat interface #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ScriptSmith
wants to merge
22
commits into
main
Choose a base branch
from
chat-fixes
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+925
−728
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
592df5e
Fix overwriting final round text
ScriptSmith a54706b
Separate reasoning blocks
ScriptSmith 9436b8f
Fix double spacing
ScriptSmith 97ab202
Separate ContentRound component
ScriptSmith 112fe82
Vertical border per-round
ScriptSmith 79f1399
Display artifacts within rounds
ScriptSmith 9dbc160
Don't jump to bottom at end
ScriptSmith de05d7f
Fix virtualizer pushing scroll down
ScriptSmith 408d3d3
Remove tool call indicator
ScriptSmith 3bf7049
Add compact mode
ScriptSmith 25bcb21
Show rounds on hover
ScriptSmith 793e574
Review fixes
ScriptSmith 87e5e03
Storybook fixes
ScriptSmith 3b41e25
Better constructs for rounds
ScriptSmith 923967a
Show thinking / processing interstitials between rounds
ScriptSmith fd98901
Higher-level management of streaming status indications
ScriptSmith 49db7ab
Chat UI details as two buttons
ScriptSmith 1a43f95
Make thinking indicator nicer
ScriptSmith 1e9762f
Fix missing reasoning indicator
ScriptSmith a5450a9
Update compact UI
ScriptSmith 99ddb1f
Fix storybook
ScriptSmith 4cdd07c
Review fixes
ScriptSmith File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| import { memo, useState, useCallback, useMemo } from "react"; | ||
| import type { ToolExecutionRound, Artifact, DisplaySelectionData } from "@/components/chat-types"; | ||
| import { Artifact as ArtifactComponent } from "@/components/Artifact"; | ||
| import { ReasoningSection } from "@/components/ReasoningSection/ReasoningSection"; | ||
| import { StreamingMarkdown } from "@/components/StreamingMarkdown/StreamingMarkdown"; | ||
| import { ExecutionSummaryBar, ExecutionTimeline } from "@/components/ToolExecution"; | ||
| import { useCompactMode } from "@/stores/chatUIStore"; | ||
|
|
||
| interface ContentRoundProps { | ||
| /** Round's reasoning content */ | ||
| reasoning?: string | null; | ||
| /** Round's text content */ | ||
| content?: string | null; | ||
| /** Whether text content is actively streaming */ | ||
| isStreaming?: boolean; | ||
| /** Whether reasoning is actively streaming */ | ||
| isReasoningStreaming?: boolean; | ||
| /** Token count for reasoning (shown in ReasoningSection) */ | ||
| reasoningTokenCount?: number; | ||
| /** Tool execution round for this round (if tools were called) */ | ||
| toolExecutionRound?: ToolExecutionRound; | ||
| /** Whether tool execution is still in progress for this round */ | ||
| isToolsStreaming?: boolean; | ||
| /** Artifact click handler for tool execution timeline */ | ||
| onArtifactClick?: (artifact: Artifact) => void; | ||
| /** Display selection if display_artifacts was called in this round */ | ||
| displaySelection?: DisplaySelectionData | null; | ||
| /** All output artifacts across all rounds (for resolving display selection IDs) */ | ||
| allOutputArtifacts?: Artifact[]; | ||
| } | ||
|
|
||
| /** | ||
| * A single round of model output: reasoning → content → tool execution summary. | ||
| * | ||
| * Used in multi-round tool calling to render each iteration as a distinct block | ||
| * with consistent spacing, replacing raw `<hr>` separators. | ||
| */ | ||
| function ContentRoundComponent({ | ||
| reasoning, | ||
| content, | ||
| isStreaming = false, | ||
| isReasoningStreaming = false, | ||
| reasoningTokenCount, | ||
| toolExecutionRound, | ||
| isToolsStreaming = false, | ||
| onArtifactClick, | ||
| displaySelection, | ||
| allOutputArtifacts, | ||
| }: ContentRoundProps) { | ||
| const [toolsExpanded, setToolsExpanded] = useState(false); | ||
| const handleToggleTools = useCallback(() => setToolsExpanded((p) => !p), []); | ||
| const compactMode = useCompactMode(); | ||
|
|
||
| // Resolve display selection to actual artifacts | ||
| const displayedArtifacts = useMemo(() => { | ||
| if (!displaySelection?.artifactIds.length || !allOutputArtifacts) return []; | ||
| const displayed: Artifact[] = []; | ||
| for (const id of displaySelection.artifactIds) { | ||
| const artifact = allOutputArtifacts.find((a) => a.id === id); | ||
| if (artifact) displayed.push(artifact); | ||
| } | ||
| return displayed; | ||
| }, [displaySelection, allOutputArtifacts]); | ||
|
|
||
| const hasContent = !!content?.trim(); | ||
| const hasReasoning = !!reasoning; | ||
| const hasTools = !!toolExecutionRound; | ||
| const hasDisplayedArtifacts = displayedArtifacts.length > 0; | ||
|
|
||
| if (!hasContent && !hasReasoning && !hasTools && !hasDisplayedArtifacts) return null; | ||
|
|
||
| if (compactMode) { | ||
| // Compact: show content + artifacts only; collapse reasoning/tool-only rounds | ||
| if (hasContent || hasDisplayedArtifacts) { | ||
| const layoutClass = | ||
| displaySelection?.layout === "gallery" ? "grid grid-cols-2 gap-3" : "space-y-3"; | ||
| return ( | ||
| <div className="space-y-1 border-l-2 border-transparent pl-3 transition-colors hover:border-zinc-200 dark:hover:border-zinc-700"> | ||
| {hasContent && <StreamingMarkdown content={content!} isStreaming={isStreaming} />} | ||
| {hasDisplayedArtifacts && ( | ||
| <div className={layoutClass}> | ||
| {displayedArtifacts.map((artifact) => ( | ||
| <ArtifactComponent key={artifact.id} artifact={artifact} /> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| // No content in compact mode — parent manages status indicators | ||
| return null; | ||
| } | ||
|
|
||
| const layoutClass = | ||
| displaySelection?.layout === "gallery" ? "grid grid-cols-2 gap-3" : "space-y-3"; | ||
|
|
||
| return ( | ||
| <div className="space-y-1 border-l-2 border-transparent pl-3 transition-colors hover:border-zinc-200 dark:hover:border-zinc-700"> | ||
| {hasReasoning && ( | ||
| <ReasoningSection | ||
| content={reasoning!} | ||
| isStreaming={isReasoningStreaming} | ||
| tokenCount={reasoningTokenCount} | ||
| /> | ||
| )} | ||
| {hasContent && <StreamingMarkdown content={content!} isStreaming={isStreaming} />} | ||
| {hasTools && ( | ||
| <div className="mt-1.5"> | ||
| <ExecutionSummaryBar | ||
| rounds={[toolExecutionRound!]} | ||
| isExpanded={toolsExpanded} | ||
| onToggle={handleToggleTools} | ||
| isStreaming={isToolsStreaming} | ||
| /> | ||
| {toolsExpanded && ( | ||
| <ExecutionTimeline rounds={[toolExecutionRound!]} onArtifactClick={onArtifactClick} /> | ||
| )} | ||
| </div> | ||
| )} | ||
| {hasDisplayedArtifacts && ( | ||
| <div className={layoutClass}> | ||
| {displayedArtifacts.map((artifact) => ( | ||
| <ArtifactComponent key={artifact.id} artifact={artifact} /> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export const ContentRound = memo(ContentRoundComponent); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.