Add thinking expansion preference and step finish styling

This commit is contained in:
Shantur Rathore
2025-11-27 13:39:03 +00:00
parent 5c82a2d653
commit c123714271
7 changed files with 74 additions and 7 deletions

View File

@@ -59,7 +59,7 @@ export function setupStorageIPC() {
return await readConfigWithCache() return await readConfigWithCache()
} catch (error) { } catch (error) {
// Return empty config if file doesn't exist // Return empty config if file doesn't exist
return JSON.stringify({ preferences: { showThinkingBlocks: false }, recentFolders: [] }, null, 2) return JSON.stringify({ preferences: { showThinkingBlocks: false, thinkingBlocksExpansion: "expanded" }, recentFolders: [] }, null, 2)
} }
}) })

View File

@@ -10,6 +10,7 @@ const AgentModelSelectionsSchema = z.record(z.string(), AgentModelSelectionSchem
const PreferencesSchema = z.object({ const PreferencesSchema = z.object({
showThinkingBlocks: z.boolean().default(false), showThinkingBlocks: z.boolean().default(false),
thinkingBlocksExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),
lastUsedBinary: z.string().optional(), lastUsedBinary: z.string().optional(),
environmentVariables: z.record(z.string()).default({}), environmentVariables: z.record(z.string()).default({}),
modelRecents: z.array(ModelPreferenceSchema).default([]), modelRecents: z.array(ModelPreferenceSchema).default([]),

View File

@@ -51,6 +51,7 @@ const App: Component = () => {
setDiffViewMode, setDiffViewMode,
setToolOutputExpansion, setToolOutputExpansion,
setDiagnosticsExpansion, setDiagnosticsExpansion,
setThinkingBlocksExpansion,
} = useConfig() } = useConfig()
const [escapeInDebounce, setEscapeInDebounce] = createSignal(false) const [escapeInDebounce, setEscapeInDebounce] = createSignal(false)
const [launchErrorBinary, setLaunchErrorBinary] = createSignal<string | null>(null) const [launchErrorBinary, setLaunchErrorBinary] = createSignal<string | null>(null)
@@ -210,6 +211,7 @@ const App: Component = () => {
setDiffViewMode, setDiffViewMode,
setToolOutputExpansion, setToolOutputExpansion,
setDiagnosticsExpansion, setDiagnosticsExpansion,
setThinkingBlocksExpansion,
handleNewInstanceRequest, handleNewInstanceRequest,
handleCloseInstance, handleCloseInstance,
handleNewSession, handleNewSession,

View File

@@ -23,6 +23,10 @@ const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).h
const messageItemCache = new Map<string, ContentDisplayItem>() const messageItemCache = new Map<string, ContentDisplayItem>()
const toolItemCache = new Map<string, ToolDisplayItem>() const toolItemCache = new Map<string, ToolDisplayItem>()
const USER_BORDER_COLOR = "var(--message-user-border)"
const ASSISTANT_BORDER_COLOR = "var(--message-assistant-border)"
const TOOL_BORDER_COLOR = "var(--message-tool-border)"
type ToolCallPart = Extract<ClientPart, { type: "tool" }> type ToolCallPart = Extract<ClientPart, { type: "tool" }>
type ToolState = import("@opencode-ai/sdk").ToolState type ToolState = import("@opencode-ai/sdk").ToolState
@@ -139,6 +143,7 @@ interface StepDisplayItem {
key: string key: string
part: ClientPart part: ClientPart
messageInfo?: MessageInfo messageInfo?: MessageInfo
accentColor?: string
} }
type ReasoningDisplayItem = { type ReasoningDisplayItem = {
@@ -147,6 +152,7 @@ type ReasoningDisplayItem = {
part: ClientPart part: ClientPart
messageInfo?: MessageInfo messageInfo?: MessageInfo
showAgentMeta?: boolean showAgentMeta?: boolean
defaultExpanded: boolean
} }
type MessageBlockItem = ContentDisplayItem | ToolDisplayItem | StepDisplayItem | ReasoningDisplayItem type MessageBlockItem = ContentDisplayItem | ToolDisplayItem | StepDisplayItem | ReasoningDisplayItem
@@ -261,6 +267,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
const infoMap = messageInfoMap() const infoMap = messageInfoMap()
const showThinking = preferences().showThinkingBlocks const showThinking = preferences().showThinkingBlocks
const showUsageMetrics = showUsagePreference() const showUsageMetrics = showUsagePreference()
const thinkingDefaultExpanded = (preferences().thinkingBlocksExpansion ?? "expanded") === "expanded"
const revert = revertTarget() const revert = revertTarget()
const instanceId = props.instanceId const instanceId = props.instanceId
const blocks: MessageDisplayBlock[] = [] const blocks: MessageDisplayBlock[] = []
@@ -284,6 +291,8 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
let segmentIndex = 0 let segmentIndex = 0
let pendingParts: ClientPart[] = [] let pendingParts: ClientPart[] = []
let agentMetaAttached = record.role !== "assistant" let agentMetaAttached = record.role !== "assistant"
const defaultAccentColor = record.role === "user" ? USER_BORDER_COLOR : ASSISTANT_BORDER_COLOR
let lastAccentColor = defaultAccentColor
const flushContent = () => { const flushContent = () => {
if (pendingParts.length === 0) return if (pendingParts.length === 0) return
@@ -317,6 +326,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
} }
items.push(cached) items.push(cached)
usedMessageKeys.add(segmentKey) usedMessageKeys.add(segmentKey)
lastAccentColor = defaultAccentColor
pendingParts = [] pendingParts = []
} }
@@ -349,6 +359,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
} }
items.push(toolItem) items.push(toolItem)
usedToolKeys.add(cacheKey) usedToolKeys.add(cacheKey)
lastAccentColor = TOOL_BORDER_COLOR
return return
} }
@@ -361,7 +372,9 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
flushContent() flushContent()
if (showUsageMetrics) { if (showUsageMetrics) {
const key = makeInstanceCacheKey(instanceId, `${record.id}:${part.id ?? partIndex}:${part.type}`) const key = makeInstanceCacheKey(instanceId, `${record.id}:${part.id ?? partIndex}:${part.type}`)
items.push({ type: part.type, key, part, messageInfo }) const accentColor = lastAccentColor || defaultAccentColor
items.push({ type: part.type, key, part, messageInfo, accentColor })
lastAccentColor = accentColor
} }
return return
} }
@@ -374,7 +387,15 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
if (showAgentMeta) { if (showAgentMeta) {
agentMetaAttached = true agentMetaAttached = true
} }
items.push({ type: "reasoning", key, part, messageInfo, showAgentMeta }) items.push({
type: "reasoning",
key,
part,
messageInfo,
showAgentMeta,
defaultExpanded: thinkingDefaultExpanded,
})
lastAccentColor = ASSISTANT_BORDER_COLOR
} }
return return
} }
@@ -707,6 +728,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
part={(item as StepDisplayItem).part} part={(item as StepDisplayItem).part}
messageInfo={(item as StepDisplayItem).messageInfo} messageInfo={(item as StepDisplayItem).messageInfo}
showUsage={showUsagePreference()} showUsage={showUsagePreference()}
borderColor={(item as StepDisplayItem).accentColor}
/> />
</Match> </Match>
<Match when={item.type === "reasoning"}> <Match when={item.type === "reasoning"}>
@@ -716,6 +738,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
instanceId={props.instanceId} instanceId={props.instanceId}
sessionId={props.sessionId} sessionId={props.sessionId}
showAgentMeta={(item as ReasoningDisplayItem).showAgentMeta} showAgentMeta={(item as ReasoningDisplayItem).showAgentMeta}
defaultExpanded={(item as ReasoningDisplayItem).defaultExpanded}
/> />
</Match> </Match>
</Switch> </Switch>
@@ -764,6 +787,7 @@ interface StepCardProps {
messageInfo?: MessageInfo messageInfo?: MessageInfo
showAgentMeta?: boolean showAgentMeta?: boolean
showUsage?: boolean showUsage?: boolean
borderColor?: string
} }
function StepCard(props: StepCardProps) { function StepCard(props: StepCardProps) {
@@ -815,6 +839,8 @@ function StepCard(props: StepCardProps) {
} }
} }
const finishStyle = () => (props.borderColor ? { "border-left-color": props.borderColor } : undefined)
const renderUsageChips = (usage: NonNullable<ReturnType<typeof usageStats>>) => ( const renderUsageChips = (usage: NonNullable<ReturnType<typeof usageStats>>) => (
<div class="message-step-usage"> <div class="message-step-usage">
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]"> <div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
@@ -850,7 +876,7 @@ function StepCard(props: StepCardProps) {
return null return null
} }
return ( return (
<div class={`message-step-card message-step-finish`}> <div class={`message-step-card message-step-finish message-step-finish-flush`} style={finishStyle()}>
{renderUsageChips(usage)} {renderUsageChips(usage)}
</div> </div>
) )
@@ -886,10 +912,15 @@ interface ReasoningCardProps {
instanceId: string instanceId: string
sessionId: string sessionId: string
showAgentMeta?: boolean showAgentMeta?: boolean
defaultExpanded?: boolean
} }
function ReasoningCard(props: ReasoningCardProps) { function ReasoningCard(props: ReasoningCardProps) {
const [expanded, setExpanded] = createSignal(false) const [expanded, setExpanded] = createSignal(Boolean(props.defaultExpanded))
createEffect(() => {
setExpanded(Boolean(props.defaultExpanded))
})
const timestamp = () => { const timestamp = () => {
const value = props.messageInfo?.time?.created ?? (props.part as any)?.time?.start ?? Date.now() const value = props.messageInfo?.time?.created ?? (props.part as any)?.time?.start ?? Date.now()

View File

@@ -24,6 +24,7 @@ export interface UseCommandsOptions {
setDiffViewMode: (mode: "split" | "unified") => void setDiffViewMode: (mode: "split" | "unified") => void
setToolOutputExpansion: (mode: ExpansionPreference) => void setToolOutputExpansion: (mode: ExpansionPreference) => void
setDiagnosticsExpansion: (mode: ExpansionPreference) => void setDiagnosticsExpansion: (mode: ExpansionPreference) => void
setThinkingBlocksExpansion: (mode: ExpansionPreference) => void
handleNewInstanceRequest: () => void handleNewInstanceRequest: () => void
handleCloseInstance: (instanceId: string) => Promise<void> handleCloseInstance: (instanceId: string) => Promise<void>
handleNewSession: (instanceId: string) => Promise<void> handleNewSession: (instanceId: string) => Promise<void>
@@ -385,10 +386,26 @@ export function useCommands(options: UseCommandsOptions) {
label: () => `${options.preferences().showThinkingBlocks ? "Hide" : "Show"} Thinking Blocks`, label: () => `${options.preferences().showThinkingBlocks ? "Hide" : "Show"} Thinking Blocks`,
description: "Show/hide AI thinking process", description: "Show/hide AI thinking process",
category: "System", category: "System",
keywords: ["/thinking", "toggle", "show", "hide"], keywords: ["/thinking", "thinking", "reasoning", "toggle", "show", "hide"],
action: options.toggleShowThinkingBlocks, action: options.toggleShowThinkingBlocks,
}) })
commandRegistry.register({
id: "thinking-default-visibility",
label: () => {
const mode = options.preferences().thinkingBlocksExpansion ?? "expanded"
return `Thinking Blocks Default · ${mode === "expanded" ? "Expanded" : "Collapsed"}`
},
description: "Toggle whether thinking blocks start expanded",
category: "System",
keywords: ["/thinking", "thinking", "reasoning", "expand", "collapse", "default"],
action: () => {
const mode = options.preferences().thinkingBlocksExpansion ?? "expanded"
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
options.setThinkingBlocksExpansion(next)
},
})
commandRegistry.register({ commandRegistry.register({
id: "diff-view-split", id: "diff-view-split",
label: () => `${(options.preferences().diffViewMode || "split") === "split" ? "✓ " : ""}Use Split Diff View`, label: () => `${(options.preferences().diffViewMode || "split") === "split" ? "✓ " : ""}Use Split Diff View`,

View File

@@ -29,6 +29,7 @@ export type ExpansionPreference = "expanded" | "collapsed"
export interface Preferences { export interface Preferences {
showThinkingBlocks: boolean showThinkingBlocks: boolean
thinkingBlocksExpansion: ExpansionPreference
lastUsedBinary?: string lastUsedBinary?: string
environmentVariables: Record<string, string> environmentVariables: Record<string, string>
modelRecents: ModelPreference[] modelRecents: ModelPreference[]
@@ -56,6 +57,7 @@ const MAX_RECENT_MODELS = 5
const defaultPreferences: Preferences = { const defaultPreferences: Preferences = {
showThinkingBlocks: false, showThinkingBlocks: false,
thinkingBlocksExpansion: "expanded",
environmentVariables: {}, environmentVariables: {},
modelRecents: [], modelRecents: [],
diffViewMode: "split", diffViewMode: "split",
@@ -88,6 +90,7 @@ function normalizePreferences(pref?: Partial<Preferences> & { agentModelSelectio
return { return {
showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultPreferences.showThinkingBlocks, showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultPreferences.showThinkingBlocks,
thinkingBlocksExpansion: sanitized.thinkingBlocksExpansion ?? defaultPreferences.thinkingBlocksExpansion,
lastUsedBinary: sanitized.lastUsedBinary ?? defaultPreferences.lastUsedBinary, lastUsedBinary: sanitized.lastUsedBinary ?? defaultPreferences.lastUsedBinary,
environmentVariables, environmentVariables,
modelRecents, modelRecents,
@@ -269,6 +272,11 @@ function setDiagnosticsExpansion(mode: ExpansionPreference): void {
updatePreferences({ diagnosticsExpansion: mode }) updatePreferences({ diagnosticsExpansion: mode })
} }
function setThinkingBlocksExpansion(mode: ExpansionPreference): void {
if (preferences().thinkingBlocksExpansion === mode) return
updatePreferences({ thinkingBlocksExpansion: mode })
}
function toggleShowThinkingBlocks(): void { function toggleShowThinkingBlocks(): void {
updatePreferences({ showThinkingBlocks: !preferences().showThinkingBlocks }) updatePreferences({ showThinkingBlocks: !preferences().showThinkingBlocks })
} }
@@ -381,6 +389,7 @@ interface ConfigContextValue {
setDiffViewMode: typeof setDiffViewMode setDiffViewMode: typeof setDiffViewMode
setToolOutputExpansion: typeof setToolOutputExpansion setToolOutputExpansion: typeof setToolOutputExpansion
setDiagnosticsExpansion: typeof setDiagnosticsExpansion setDiagnosticsExpansion: typeof setDiagnosticsExpansion
setThinkingBlocksExpansion: typeof setThinkingBlocksExpansion
addRecentFolder: typeof addRecentFolder addRecentFolder: typeof addRecentFolder
removeRecentFolder: typeof removeRecentFolder removeRecentFolder: typeof removeRecentFolder
addOpenCodeBinary: typeof addOpenCodeBinary addOpenCodeBinary: typeof addOpenCodeBinary
@@ -412,6 +421,7 @@ const configContextValue: ConfigContextValue = {
setDiffViewMode, setDiffViewMode,
setToolOutputExpansion, setToolOutputExpansion,
setDiagnosticsExpansion, setDiagnosticsExpansion,
setThinkingBlocksExpansion,
addRecentFolder, addRecentFolder,
removeRecentFolder, removeRecentFolder,
addOpenCodeBinary, addOpenCodeBinary,
@@ -480,7 +490,9 @@ export {
setDiffViewMode, setDiffViewMode,
setToolOutputExpansion, setToolOutputExpansion,
setDiagnosticsExpansion, setDiagnosticsExpansion,
setThinkingBlocksExpansion,
themePreference, themePreference,
setThemePreference, setThemePreference,
recordWorkspaceLaunch, recordWorkspaceLaunch,
} }

View File

@@ -27,6 +27,10 @@
margin-bottom: 0.125rem; margin-bottom: 0.125rem;
} }
.message-step-finish-flush {
margin-top: -0.125rem;
}
.message-step-usage { .message-step-usage {
@apply flex flex-wrap items-center gap-1 text-[10px] text-[var(--text-muted)]; @apply flex flex-wrap items-center gap-1 text-[10px] text-[var(--text-muted)];
} }