feat: toggle tool details visible

This commit is contained in:
adamdotdevin
2025-08-11 11:58:40 -05:00
parent 3c71fda648
commit 5e777fd2a2
8 changed files with 103 additions and 58 deletions

View File

@@ -202,6 +202,7 @@ export namespace Config {
session_interrupt: z.string().optional().default("esc").describe("Interrupt current session"), session_interrupt: z.string().optional().default("esc").describe("Interrupt current session"),
session_compact: z.string().optional().default("<leader>c").describe("Compact the session"), session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
tool_details: z.string().optional().default("<leader>d").describe("Toggle tool details"), tool_details: z.string().optional().default("<leader>d").describe("Toggle tool details"),
thinking_blocks: z.string().optional().default("<leader>b").describe("Toggle thinking blocks"),
model_list: z.string().optional().default("<leader>m").describe("List available models"), model_list: z.string().optional().default("<leader>m").describe("List available models"),
theme_list: z.string().optional().default("<leader>t").describe("List available themes"), theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
file_list: z.string().optional().default("<leader>f").describe("List files"), file_list: z.string().optional().default("<leader>f").describe("List files"),

View File

@@ -1007,7 +1007,7 @@ export namespace Session {
async process(stream: StreamTextResult<Record<string, AITool>, never>) { async process(stream: StreamTextResult<Record<string, AITool>, never>) {
try { try {
let currentText: MessageV2.TextPart | undefined let currentText: MessageV2.TextPart | undefined
// let reasoningMap: Record<string, MessageV2.ReasoningPart> = {} let reasoningMap: Record<string, MessageV2.ReasoningPart> = {}
for await (const value of stream.fullStream) { for await (const value of stream.fullStream) {
log.info("part", { log.info("part", {
@@ -1017,7 +1017,6 @@ export namespace Session {
case "start": case "start":
break break
/*
case "reasoning-start": case "reasoning-start":
if (value.id in reasoningMap) { if (value.id in reasoningMap) {
continue continue
@@ -1055,7 +1054,6 @@ export namespace Session {
delete reasoningMap[value.id] delete reasoningMap[value.id]
} }
break break
*/
case "tool-input-start": case "tool-input-start":
const part = await updatePart({ const part = await updatePart({

View File

@@ -32,6 +32,8 @@ type State struct {
MessagesRight bool `toml:"messages_right"` MessagesRight bool `toml:"messages_right"`
SplitDiff bool `toml:"split_diff"` SplitDiff bool `toml:"split_diff"`
MessageHistory []Prompt `toml:"message_history"` MessageHistory []Prompt `toml:"message_history"`
ShowToolDetails *bool `toml:"show_tool_details"`
ShowThinkingBlocks *bool `toml:"show_thinking_blocks"`
} }
func NewState() *State { func NewState() *State {

View File

@@ -119,6 +119,7 @@ const (
SessionCompactCommand CommandName = "session_compact" SessionCompactCommand CommandName = "session_compact"
SessionExportCommand CommandName = "session_export" SessionExportCommand CommandName = "session_export"
ToolDetailsCommand CommandName = "tool_details" ToolDetailsCommand CommandName = "tool_details"
ThinkingBlocksCommand CommandName = "thinking_blocks"
ModelListCommand CommandName = "model_list" ModelListCommand CommandName = "model_list"
AgentListCommand CommandName = "agent_list" AgentListCommand CommandName = "agent_list"
ModelCycleRecentCommand CommandName = "model_cycle_recent" ModelCycleRecentCommand CommandName = "model_cycle_recent"
@@ -245,6 +246,12 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
Keybindings: parseBindings("<leader>d"), Keybindings: parseBindings("<leader>d"),
Trigger: []string{"details"}, Trigger: []string{"details"},
}, },
{
Name: ThinkingBlocksCommand,
Description: "toggle thinking blocks",
Keybindings: parseBindings("<leader>b"),
Trigger: []string{"thinking"},
},
{ {
Name: ModelListCommand, Name: ModelListCommand,
Description: "list models", Description: "list models",

View File

@@ -33,6 +33,7 @@ type MessagesComponent interface {
HalfPageUp() (tea.Model, tea.Cmd) HalfPageUp() (tea.Model, tea.Cmd)
HalfPageDown() (tea.Model, tea.Cmd) HalfPageDown() (tea.Model, tea.Cmd)
ToolDetailsVisible() bool ToolDetailsVisible() bool
ThinkingBlocksVisible() bool
GotoTop() (tea.Model, tea.Cmd) GotoTop() (tea.Model, tea.Cmd)
GotoBottom() (tea.Model, tea.Cmd) GotoBottom() (tea.Model, tea.Cmd)
CopyLastMessage() (tea.Model, tea.Cmd) CopyLastMessage() (tea.Model, tea.Cmd)
@@ -41,20 +42,21 @@ type MessagesComponent interface {
} }
type messagesComponent struct { type messagesComponent struct {
width, height int width, height int
app *app.App app *app.App
header string header string
viewport viewport.Model viewport viewport.Model
clipboard []string clipboard []string
cache *PartCache cache *PartCache
loading bool loading bool
showToolDetails bool showToolDetails bool
rendering bool showThinkingBlocks bool
dirty bool rendering bool
tail bool dirty bool
partCount int tail bool
lineCount int partCount int
selection *selection lineCount int
selection *selection
} }
type selection struct { type selection struct {
@@ -94,6 +96,7 @@ func (s selection) coords(offset int) *selection {
} }
type ToggleToolDetailsMsg struct{} type ToggleToolDetailsMsg struct{}
type ToggleThinkingBlocksMsg struct{}
func (m *messagesComponent) Init() tea.Cmd { func (m *messagesComponent) Init() tea.Cmd {
return tea.Batch(m.viewport.Init()) 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() return m, m.renderView()
case ToggleToolDetailsMsg: case ToggleToolDetailsMsg:
m.showToolDetails = !m.showToolDetails 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: case app.SessionLoadedMsg, app.SessionClearedMsg:
m.cache.Clear() m.cache.Clear()
m.tail = true m.tail = true
@@ -561,32 +569,34 @@ func (m *messagesComponent) renderView() tea.Cmd {
if reverted { if reverted {
continue continue
} }
text := "..." if !m.showThinkingBlocks {
if part.Text != "" { continue
text = part.Text }
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 return m.showToolDetails
} }
func (m *messagesComponent) ThinkingBlocksVisible() bool {
return m.showThinkingBlocks
}
func (m *messagesComponent) GotoTop() (tea.Model, tea.Cmd) { func (m *messagesComponent) GotoTop() (tea.Model, tea.Cmd) {
m.viewport.GotoTop() m.viewport.GotoTop()
return m, nil return m, nil
@@ -1202,11 +1216,23 @@ func NewMessagesComponent(app *app.App) MessagesComponent {
vp.MouseWheelDelta = 4 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{ return &messagesComponent{
app: app, app: app,
viewport: vp, viewport: vp,
showToolDetails: true, showToolDetails: showToolDetails,
cache: NewPartCache(), showThinkingBlocks: showThinkingBlocks,
tail: true, cache: NewPartCache(),
tail: true,
} }
} }

View File

@@ -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, util.CmdHandler(chat.ToggleToolDetailsMsg{}))
cmds = append(cmds, toast.NewInfoToast(message)) 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: case commands.ModelListCommand:
modelDialog := dialog.NewModelDialog(a.app) modelDialog := dialog.NewModelDialog(a.app)
a.modal = modelDialog a.modal = modelDialog

View File

@@ -136,13 +136,14 @@
flex-grow: 1; flex-grow: 1;
max-width: var(--md-tool-width); max-width: var(--md-tool-width);
& > [data-component="assistant-reasoning-markdown"] { [data-component="assistant-reasoning-markdown"] {
align-self: flex-start; align-self: flex-start;
font-size: 0.875rem; font-size: 0.875rem;
border: 1px solid var(--sl-color-blue-high); border: 1px solid var(--sl-color-blue-high);
padding: 0.5rem calc(0.5rem + 3px); padding: 0.5rem calc(0.5rem + 3px);
border-radius: 0.25rem; border-radius: 0.25rem;
position: relative; position: relative;
margin-top: 0.5rem;
[data-component="copy-button"] { [data-component="copy-button"] {
top: 0.5rem; top: 0.5rem;

View File

@@ -152,18 +152,23 @@ export function Part(props: PartProps) {
)} )}
{` | ${props.message.modelID}`} {` | ${props.message.modelID}`}
{props.message.mode && ( {props.message.mode && (
<span style={{ "font-weight": "bold", color: "var(--sl-color-accent)" }}> <span style={{ color: "var(--sl-color-accent)" }}>{` | ${props.message.mode}`}</span>
{` | ${props.message.mode}`}
</span>
)} )}
</Footer> </Footer>
)} )}
</div> </div>
)} )}
{props.message.role === "assistant" && props.part.type === "reasoning" && ( {props.message.role === "assistant" && props.part.type === "reasoning" && (
<div data-component="assistant-reasoning"> <div data-component="tool">
<div data-component="assistant-reasoning-markdown"> <div data-component="tool-title">
<ContentMarkdown expand={props.last} text={props.part.text || "Thinking..."} /> <span data-slot="name">Thinking</span>
</div>
<div data-component="assistant-reasoning">
<ResultsButton showCopy="Show details" hideCopy="Hide details">
<div data-component="assistant-reasoning-markdown">
<ContentMarkdown expand text={props.part.text || "Thinking..."} />
</div>
</ResultsButton>
</div> </div>
</div> </div>
)} )}
@@ -182,9 +187,7 @@ export function Part(props: PartProps) {
)} )}
{` | ${props.message.modelID}`} {` | ${props.message.modelID}`}
{props.message.mode && ( {props.message.mode && (
<span style={{ "font-weight": "bold", color: "var(--sl-color-accent)" }}> <span style={{ color: "var(--sl-color-accent)" }}>{` | ${props.message.mode}`}</span>
{` | ${props.message.mode}`}
</span>
)} )}
</div> </div>
</div> </div>