diff --git a/packages/electron-app/electron/main/storage.ts b/packages/electron-app/electron/main/storage.ts index e137fc61..a93cc19e 100644 --- a/packages/electron-app/electron/main/storage.ts +++ b/packages/electron-app/electron/main/storage.ts @@ -59,7 +59,7 @@ export function setupStorageIPC() { return await readConfigWithCache() } catch (error) { // 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) } }) diff --git a/packages/server/src/config/schema.ts b/packages/server/src/config/schema.ts index 517534c6..a2c88f02 100644 --- a/packages/server/src/config/schema.ts +++ b/packages/server/src/config/schema.ts @@ -10,6 +10,7 @@ const AgentModelSelectionsSchema = z.record(z.string(), AgentModelSelectionSchem const PreferencesSchema = z.object({ showThinkingBlocks: z.boolean().default(false), + thinkingBlocksExpansion: z.enum(["expanded", "collapsed"]).default("expanded"), lastUsedBinary: z.string().optional(), environmentVariables: z.record(z.string()).default({}), modelRecents: z.array(ModelPreferenceSchema).default([]), diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 1dd3cb13..d811b498 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -51,6 +51,7 @@ const App: Component = () => { setDiffViewMode, setToolOutputExpansion, setDiagnosticsExpansion, + setThinkingBlocksExpansion, } = useConfig() const [escapeInDebounce, setEscapeInDebounce] = createSignal(false) const [launchErrorBinary, setLaunchErrorBinary] = createSignal(null) @@ -210,6 +211,7 @@ const App: Component = () => { setDiffViewMode, setToolOutputExpansion, setDiagnosticsExpansion, + setThinkingBlocksExpansion, handleNewInstanceRequest, handleCloseInstance, handleNewSession, diff --git a/packages/ui/src/components/message-stream-v2.tsx b/packages/ui/src/components/message-stream-v2.tsx index 4d76edb8..3fa1fe16 100644 --- a/packages/ui/src/components/message-stream-v2.tsx +++ b/packages/ui/src/components/message-stream-v2.tsx @@ -23,6 +23,10 @@ const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).h const messageItemCache = new Map() const toolItemCache = new Map() +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 type ToolState = import("@opencode-ai/sdk").ToolState @@ -139,6 +143,7 @@ interface StepDisplayItem { key: string part: ClientPart messageInfo?: MessageInfo + accentColor?: string } type ReasoningDisplayItem = { @@ -147,6 +152,7 @@ type ReasoningDisplayItem = { part: ClientPart messageInfo?: MessageInfo showAgentMeta?: boolean + defaultExpanded: boolean } type MessageBlockItem = ContentDisplayItem | ToolDisplayItem | StepDisplayItem | ReasoningDisplayItem @@ -261,6 +267,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { const infoMap = messageInfoMap() const showThinking = preferences().showThinkingBlocks const showUsageMetrics = showUsagePreference() + const thinkingDefaultExpanded = (preferences().thinkingBlocksExpansion ?? "expanded") === "expanded" const revert = revertTarget() const instanceId = props.instanceId const blocks: MessageDisplayBlock[] = [] @@ -284,6 +291,8 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { let segmentIndex = 0 let pendingParts: ClientPart[] = [] let agentMetaAttached = record.role !== "assistant" + const defaultAccentColor = record.role === "user" ? USER_BORDER_COLOR : ASSISTANT_BORDER_COLOR + let lastAccentColor = defaultAccentColor const flushContent = () => { if (pendingParts.length === 0) return @@ -317,6 +326,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { } items.push(cached) usedMessageKeys.add(segmentKey) + lastAccentColor = defaultAccentColor pendingParts = [] } @@ -349,6 +359,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { } items.push(toolItem) usedToolKeys.add(cacheKey) + lastAccentColor = TOOL_BORDER_COLOR return } @@ -361,7 +372,9 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { flushContent() if (showUsageMetrics) { 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 } @@ -374,7 +387,15 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { if (showAgentMeta) { 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 } @@ -707,6 +728,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { part={(item as StepDisplayItem).part} messageInfo={(item as StepDisplayItem).messageInfo} showUsage={showUsagePreference()} + borderColor={(item as StepDisplayItem).accentColor} /> @@ -716,6 +738,7 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { instanceId={props.instanceId} sessionId={props.sessionId} showAgentMeta={(item as ReasoningDisplayItem).showAgentMeta} + defaultExpanded={(item as ReasoningDisplayItem).defaultExpanded} /> @@ -764,6 +787,7 @@ interface StepCardProps { messageInfo?: MessageInfo showAgentMeta?: boolean showUsage?: boolean + borderColor?: string } 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>) => (
@@ -850,7 +876,7 @@ function StepCard(props: StepCardProps) { return null } return ( -
+
{renderUsageChips(usage)}
) @@ -886,10 +912,15 @@ interface ReasoningCardProps { instanceId: string sessionId: string showAgentMeta?: boolean + defaultExpanded?: boolean } function ReasoningCard(props: ReasoningCardProps) { - const [expanded, setExpanded] = createSignal(false) + const [expanded, setExpanded] = createSignal(Boolean(props.defaultExpanded)) + + createEffect(() => { + setExpanded(Boolean(props.defaultExpanded)) + }) const timestamp = () => { const value = props.messageInfo?.time?.created ?? (props.part as any)?.time?.start ?? Date.now() diff --git a/packages/ui/src/lib/hooks/use-commands.ts b/packages/ui/src/lib/hooks/use-commands.ts index f2127072..0bc52575 100644 --- a/packages/ui/src/lib/hooks/use-commands.ts +++ b/packages/ui/src/lib/hooks/use-commands.ts @@ -24,6 +24,7 @@ export interface UseCommandsOptions { setDiffViewMode: (mode: "split" | "unified") => void setToolOutputExpansion: (mode: ExpansionPreference) => void setDiagnosticsExpansion: (mode: ExpansionPreference) => void + setThinkingBlocksExpansion: (mode: ExpansionPreference) => void handleNewInstanceRequest: () => void handleCloseInstance: (instanceId: string) => Promise handleNewSession: (instanceId: string) => Promise @@ -385,10 +386,26 @@ export function useCommands(options: UseCommandsOptions) { label: () => `${options.preferences().showThinkingBlocks ? "Hide" : "Show"} Thinking Blocks`, description: "Show/hide AI thinking process", category: "System", - keywords: ["/thinking", "toggle", "show", "hide"], + keywords: ["/thinking", "thinking", "reasoning", "toggle", "show", "hide"], 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({ id: "diff-view-split", label: () => `${(options.preferences().diffViewMode || "split") === "split" ? "✓ " : ""}Use Split Diff View`, diff --git a/packages/ui/src/stores/preferences.tsx b/packages/ui/src/stores/preferences.tsx index a1847f3b..59611498 100644 --- a/packages/ui/src/stores/preferences.tsx +++ b/packages/ui/src/stores/preferences.tsx @@ -29,6 +29,7 @@ export type ExpansionPreference = "expanded" | "collapsed" export interface Preferences { showThinkingBlocks: boolean + thinkingBlocksExpansion: ExpansionPreference lastUsedBinary?: string environmentVariables: Record modelRecents: ModelPreference[] @@ -56,6 +57,7 @@ const MAX_RECENT_MODELS = 5 const defaultPreferences: Preferences = { showThinkingBlocks: false, + thinkingBlocksExpansion: "expanded", environmentVariables: {}, modelRecents: [], diffViewMode: "split", @@ -88,6 +90,7 @@ function normalizePreferences(pref?: Partial & { agentModelSelectio return { showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultPreferences.showThinkingBlocks, + thinkingBlocksExpansion: sanitized.thinkingBlocksExpansion ?? defaultPreferences.thinkingBlocksExpansion, lastUsedBinary: sanitized.lastUsedBinary ?? defaultPreferences.lastUsedBinary, environmentVariables, modelRecents, @@ -269,6 +272,11 @@ function setDiagnosticsExpansion(mode: ExpansionPreference): void { updatePreferences({ diagnosticsExpansion: mode }) } +function setThinkingBlocksExpansion(mode: ExpansionPreference): void { + if (preferences().thinkingBlocksExpansion === mode) return + updatePreferences({ thinkingBlocksExpansion: mode }) +} + function toggleShowThinkingBlocks(): void { updatePreferences({ showThinkingBlocks: !preferences().showThinkingBlocks }) } @@ -381,6 +389,7 @@ interface ConfigContextValue { setDiffViewMode: typeof setDiffViewMode setToolOutputExpansion: typeof setToolOutputExpansion setDiagnosticsExpansion: typeof setDiagnosticsExpansion + setThinkingBlocksExpansion: typeof setThinkingBlocksExpansion addRecentFolder: typeof addRecentFolder removeRecentFolder: typeof removeRecentFolder addOpenCodeBinary: typeof addOpenCodeBinary @@ -412,6 +421,7 @@ const configContextValue: ConfigContextValue = { setDiffViewMode, setToolOutputExpansion, setDiagnosticsExpansion, + setThinkingBlocksExpansion, addRecentFolder, removeRecentFolder, addOpenCodeBinary, @@ -480,7 +490,9 @@ export { setDiffViewMode, setToolOutputExpansion, setDiagnosticsExpansion, + setThinkingBlocksExpansion, themePreference, setThemePreference, recordWorkspaceLaunch, -} + } + diff --git a/packages/ui/src/styles/messaging/message-base.css b/packages/ui/src/styles/messaging/message-base.css index 7a1ec9e4..c0c9461a 100644 --- a/packages/ui/src/styles/messaging/message-base.css +++ b/packages/ui/src/styles/messaging/message-base.css @@ -27,6 +27,10 @@ margin-bottom: 0.125rem; } +.message-step-finish-flush { + margin-top: -0.125rem; +} + .message-step-usage { @apply flex flex-wrap items-center gap-1 text-[10px] text-[var(--text-muted)]; }