diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 0eaae2b7..40ec22b5 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -202,6 +202,7 @@ export namespace Config { session_interrupt: z.string().optional().default("esc").describe("Interrupt current session"), session_compact: z.string().optional().default("c").describe("Compact the session"), tool_details: z.string().optional().default("d").describe("Toggle tool details"), + thinking_blocks: z.string().optional().default("b").describe("Toggle thinking blocks"), model_list: z.string().optional().default("m").describe("List available models"), theme_list: z.string().optional().default("t").describe("List available themes"), file_list: z.string().optional().default("f").describe("List files"), diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 5565f3e0..5f7bf000 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -1007,7 +1007,7 @@ export namespace Session { async process(stream: StreamTextResult, never>) { try { let currentText: MessageV2.TextPart | undefined - // let reasoningMap: Record = {} + let reasoningMap: Record = {} for await (const value of stream.fullStream) { log.info("part", { @@ -1017,7 +1017,6 @@ export namespace Session { case "start": break - /* case "reasoning-start": if (value.id in reasoningMap) { continue @@ -1055,7 +1054,6 @@ export namespace Session { delete reasoningMap[value.id] } break - */ case "tool-input-start": const part = await updatePart({ diff --git a/packages/tui/internal/app/state.go b/packages/tui/internal/app/state.go index 47802235..283cbd15 100644 --- a/packages/tui/internal/app/state.go +++ b/packages/tui/internal/app/state.go @@ -32,6 +32,8 @@ type State struct { MessagesRight bool `toml:"messages_right"` SplitDiff bool `toml:"split_diff"` MessageHistory []Prompt `toml:"message_history"` + ShowToolDetails *bool `toml:"show_tool_details"` + ShowThinkingBlocks *bool `toml:"show_thinking_blocks"` } func NewState() *State { diff --git a/packages/tui/internal/commands/command.go b/packages/tui/internal/commands/command.go index 516caab7..fff54754 100644 --- a/packages/tui/internal/commands/command.go +++ b/packages/tui/internal/commands/command.go @@ -119,6 +119,7 @@ const ( SessionCompactCommand CommandName = "session_compact" SessionExportCommand CommandName = "session_export" ToolDetailsCommand CommandName = "tool_details" + ThinkingBlocksCommand CommandName = "thinking_blocks" ModelListCommand CommandName = "model_list" AgentListCommand CommandName = "agent_list" ModelCycleRecentCommand CommandName = "model_cycle_recent" @@ -245,6 +246,12 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry { Keybindings: parseBindings("d"), Trigger: []string{"details"}, }, + { + Name: ThinkingBlocksCommand, + Description: "toggle thinking blocks", + Keybindings: parseBindings("b"), + Trigger: []string{"thinking"}, + }, { Name: ModelListCommand, Description: "list models", diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go index 84c3b983..bbfd557a 100644 --- a/packages/tui/internal/components/chat/messages.go +++ b/packages/tui/internal/components/chat/messages.go @@ -33,6 +33,7 @@ type MessagesComponent interface { HalfPageUp() (tea.Model, tea.Cmd) HalfPageDown() (tea.Model, tea.Cmd) ToolDetailsVisible() bool + ThinkingBlocksVisible() bool GotoTop() (tea.Model, tea.Cmd) GotoBottom() (tea.Model, tea.Cmd) CopyLastMessage() (tea.Model, tea.Cmd) @@ -41,20 +42,21 @@ type MessagesComponent interface { } type messagesComponent struct { - width, height int - app *app.App - header string - viewport viewport.Model - clipboard []string - cache *PartCache - loading bool - showToolDetails bool - rendering bool - dirty bool - tail bool - partCount int - lineCount int - selection *selection + width, height int + app *app.App + header string + viewport viewport.Model + clipboard []string + cache *PartCache + loading bool + showToolDetails bool + showThinkingBlocks bool + rendering bool + dirty bool + tail bool + partCount int + lineCount int + selection *selection } type selection struct { @@ -94,6 +96,7 @@ func (s selection) coords(offset int) *selection { } type ToggleToolDetailsMsg struct{} +type ToggleThinkingBlocksMsg struct{} func (m *messagesComponent) Init() tea.Cmd { return tea.Batch(m.viewport.Init()) @@ -160,7 +163,12 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, m.renderView() case ToggleToolDetailsMsg: m.showToolDetails = !m.showToolDetails - return m, m.renderView() + m.app.State.ShowToolDetails = &m.showToolDetails + return m, tea.Batch(m.renderView(), m.app.SaveState()) + case ToggleThinkingBlocksMsg: + m.showThinkingBlocks = !m.showThinkingBlocks + m.app.State.ShowThinkingBlocks = &m.showThinkingBlocks + return m, tea.Batch(m.renderView(), m.app.SaveState()) case app.SessionLoadedMsg, app.SessionClearedMsg: m.cache.Clear() m.tail = true @@ -561,32 +569,34 @@ func (m *messagesComponent) renderView() tea.Cmd { if reverted { continue } - text := "..." - if part.Text != "" { - text = part.Text + if !m.showThinkingBlocks { + continue + } + if part.Text != "" { + text := part.Text + content = renderText( + m.app, + message.Info, + text, + casted.ModelID, + m.showToolDetails, + width, + "", + true, + []opencode.FilePart{}, + []opencode.AgentPart{}, + ) + content = lipgloss.PlaceHorizontal( + m.width, + lipgloss.Center, + content, + styles.WhitespaceStyle(t.Background()), + ) + partCount++ + lineCount += lipgloss.Height(content) + 1 + blocks = append(blocks, content) + hasContent = true } - content = renderText( - m.app, - message.Info, - text, - casted.ModelID, - m.showToolDetails, - width, - "", - true, - []opencode.FilePart{}, - []opencode.AgentPart{}, - ) - content = lipgloss.PlaceHorizontal( - m.width, - lipgloss.Center, - content, - styles.WhitespaceStyle(t.Background()), - ) - partCount++ - lineCount += lipgloss.Height(content) + 1 - blocks = append(blocks, content) - hasContent = true } } @@ -1006,6 +1016,10 @@ func (m *messagesComponent) ToolDetailsVisible() bool { return m.showToolDetails } +func (m *messagesComponent) ThinkingBlocksVisible() bool { + return m.showThinkingBlocks +} + func (m *messagesComponent) GotoTop() (tea.Model, tea.Cmd) { m.viewport.GotoTop() return m, nil @@ -1202,11 +1216,23 @@ func NewMessagesComponent(app *app.App) MessagesComponent { vp.MouseWheelDelta = 4 } + // Default to showing tool details, hidden thinking blocks + showToolDetails := true + if app.State.ShowToolDetails != nil { + showToolDetails = *app.State.ShowToolDetails + } + + showThinkingBlocks := false + if app.State.ShowThinkingBlocks != nil { + showThinkingBlocks = *app.State.ShowThinkingBlocks + } + return &messagesComponent{ - app: app, - viewport: vp, - showToolDetails: true, - cache: NewPartCache(), - tail: true, + app: app, + viewport: vp, + showToolDetails: showToolDetails, + showThinkingBlocks: showThinkingBlocks, + cache: NewPartCache(), + tail: true, } } diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go index 499d67c6..1899df35 100644 --- a/packages/tui/internal/tui/tui.go +++ b/packages/tui/internal/tui/tui.go @@ -1142,6 +1142,13 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) { } cmds = append(cmds, util.CmdHandler(chat.ToggleToolDetailsMsg{})) cmds = append(cmds, toast.NewInfoToast(message)) + case commands.ThinkingBlocksCommand: + message := "Thinking blocks are now visible" + if a.messages.ThinkingBlocksVisible() { + message = "Thinking blocks are now hidden" + } + cmds = append(cmds, util.CmdHandler(chat.ToggleThinkingBlocksMsg{})) + cmds = append(cmds, toast.NewInfoToast(message)) case commands.ModelListCommand: modelDialog := dialog.NewModelDialog(a.app) a.modal = modelDialog diff --git a/packages/web/src/components/share/part.module.css b/packages/web/src/components/share/part.module.css index 3dd32142..15a43da3 100644 --- a/packages/web/src/components/share/part.module.css +++ b/packages/web/src/components/share/part.module.css @@ -136,13 +136,14 @@ flex-grow: 1; max-width: var(--md-tool-width); - & > [data-component="assistant-reasoning-markdown"] { + [data-component="assistant-reasoning-markdown"] { align-self: flex-start; font-size: 0.875rem; border: 1px solid var(--sl-color-blue-high); padding: 0.5rem calc(0.5rem + 3px); border-radius: 0.25rem; position: relative; + margin-top: 0.5rem; [data-component="copy-button"] { top: 0.5rem; diff --git a/packages/web/src/components/share/part.tsx b/packages/web/src/components/share/part.tsx index 5cfd3a04..597b67d6 100644 --- a/packages/web/src/components/share/part.tsx +++ b/packages/web/src/components/share/part.tsx @@ -152,18 +152,23 @@ export function Part(props: PartProps) { )} {` | ${props.message.modelID}`} {props.message.mode && ( - - {` | ${props.message.mode}`} - + {` | ${props.message.mode}`} )} )} )} {props.message.role === "assistant" && props.part.type === "reasoning" && ( -
-
- +
+
+ Thinking +
+
+ +
+ +
+
)} @@ -182,9 +187,7 @@ export function Part(props: PartProps) { )} {` | ${props.message.modelID}`} {props.message.mode && ( - - {` | ${props.message.mode}`} - + {` | ${props.message.mode}`} )}