mirror of https://github.com/openclaw/openclaw.git
feat(webchat): add toggle to hide tool calls and thinking blocks (#20317) thanks @nmccready
Merged via maintainer override after review.\n\nRed required checks are unrelated to this PR; local inspection found no blocker in the diff.
This commit is contained in:
parent
e5a42c0bec
commit
f4aff83c51
|
|
@ -161,6 +161,7 @@ export const en: TranslationMap = {
|
||||||
disconnected: "Disconnected from gateway.",
|
disconnected: "Disconnected from gateway.",
|
||||||
refreshTitle: "Refresh chat data",
|
refreshTitle: "Refresh chat data",
|
||||||
thinkingToggle: "Toggle assistant thinking/working output",
|
thinkingToggle: "Toggle assistant thinking/working output",
|
||||||
|
toolCallsToggle: "Toggle tool calls and tool results",
|
||||||
focusToggle: "Toggle focus mode (hide sidebar + page header)",
|
focusToggle: "Toggle focus mode (hide sidebar + page header)",
|
||||||
hideCronSessions: "Hide cron sessions",
|
hideCronSessions: "Hide cron sessions",
|
||||||
showCronSessions: "Show cron sessions",
|
showCronSessions: "Show cron sessions",
|
||||||
|
|
|
||||||
|
|
@ -990,3 +990,6 @@
|
||||||
background: var(--panel-strong);
|
background: var(--panel-strong);
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile dropdown toggle — hidden on desktop */
|
||||||
|
/* Mobile gear toggle + dropdown are hidden by default in layout.css */
|
||||||
|
|
|
||||||
|
|
@ -1030,3 +1030,16 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile chat controls — hidden on desktop, shown in layout.mobile.css */
|
||||||
|
.chat-mobile-controls-wrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-controls-mobile-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-controls-dropdown {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -316,23 +316,77 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide the entire content-header on mobile chat — controls are in mobile gear menu */
|
||||||
.content--chat .content-header {
|
.content--chat .content-header {
|
||||||
display: flex;
|
display: none;
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content--chat {
|
.content--chat {
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content--chat .content-header > div:first-child,
|
/* Show the mobile gear toggle (lives in topbar now) */
|
||||||
.content--chat .page-meta,
|
.chat-mobile-controls-wrapper {
|
||||||
.content--chat .chat-controls {
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-mobile-controls-wrapper .chat-controls-mobile-toggle {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The dropdown panel — anchored below the gear in topbar */
|
||||||
|
.chat-mobile-controls-wrapper .chat-controls-dropdown {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--card, #161b22);
|
||||||
|
border: 1px solid var(--border, #30363d);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-mobile-controls-wrapper .chat-controls-dropdown.open {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__session {
|
||||||
|
min-width: unset;
|
||||||
|
max-width: unset;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__session select {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-mobile-controls-wrapper .chat-controls-dropdown .chat-controls__thinking {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-mobile-controls-wrapper .chat-controls-dropdown .btn--icon {
|
||||||
|
min-width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 4px 4px 16px;
|
padding: 4px 4px 16px;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,24 @@ export function renderChatControls(state: AppViewState) {
|
||||||
const disableThinkingToggle = state.onboarding;
|
const disableThinkingToggle = state.onboarding;
|
||||||
const disableFocusToggle = state.onboarding;
|
const disableFocusToggle = state.onboarding;
|
||||||
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||||
|
const showToolCalls = state.onboarding ? true : state.settings.chatShowToolCalls;
|
||||||
const focusActive = state.onboarding ? true : state.settings.chatFocusMode;
|
const focusActive = state.onboarding ? true : state.settings.chatFocusMode;
|
||||||
|
const toolCallsIcon = html`
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
const refreshIcon = html`
|
const refreshIcon = html`
|
||||||
<svg
|
<svg
|
||||||
width="18"
|
width="18"
|
||||||
|
|
@ -252,6 +269,23 @@ export function renderChatControls(state: AppViewState) {
|
||||||
>
|
>
|
||||||
${icons.brain}
|
${icons.brain}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn--sm btn--icon ${showToolCalls ? "active" : ""}"
|
||||||
|
?disabled=${disableThinkingToggle}
|
||||||
|
@click=${() => {
|
||||||
|
if (disableThinkingToggle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.applySettings({
|
||||||
|
...state.settings,
|
||||||
|
chatShowToolCalls: !state.settings.chatShowToolCalls,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
aria-pressed=${showToolCalls}
|
||||||
|
title=${disableThinkingToggle ? t("chat.onboardingDisabled") : t("chat.toolCallsToggle")}
|
||||||
|
>
|
||||||
|
${toolCallsIcon}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn--sm btn--icon ${focusActive ? "active" : ""}"
|
class="btn btn--sm btn--icon ${focusActive ? "active" : ""}"
|
||||||
?disabled=${disableFocusToggle}
|
?disabled=${disableFocusToggle}
|
||||||
|
|
@ -289,6 +323,163 @@ export function renderChatControls(state: AppViewState) {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobile-only gear toggle + dropdown for chat controls.
|
||||||
|
* Rendered in the topbar so it doesn't consume content-header space.
|
||||||
|
* Hidden on desktop via CSS.
|
||||||
|
*/
|
||||||
|
export function renderChatMobileToggle(state: AppViewState) {
|
||||||
|
const sessionGroups = resolveSessionOptionGroups(state, state.sessionKey, state.sessionsResult);
|
||||||
|
const disableThinkingToggle = state.onboarding;
|
||||||
|
const disableFocusToggle = state.onboarding;
|
||||||
|
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||||
|
const showToolCalls = state.onboarding ? true : state.settings.chatShowToolCalls;
|
||||||
|
const focusActive = state.onboarding ? true : state.settings.chatFocusMode;
|
||||||
|
const toolCallsIcon = html`
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
const focusIcon = html`
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M4 7V4h3"></path>
|
||||||
|
<path d="M20 7V4h-3"></path>
|
||||||
|
<path d="M4 17v3h3"></path>
|
||||||
|
<path d="M20 17v3h-3"></path>
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="chat-mobile-controls-wrapper">
|
||||||
|
<button
|
||||||
|
class="btn btn--sm btn--icon chat-controls-mobile-toggle"
|
||||||
|
@click=${(e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const btn = e.currentTarget as HTMLElement;
|
||||||
|
const dropdown = btn.nextElementSibling as HTMLElement;
|
||||||
|
if (dropdown) {
|
||||||
|
const isOpen = dropdown.classList.toggle("open");
|
||||||
|
if (isOpen) {
|
||||||
|
const close = () => {
|
||||||
|
dropdown.classList.remove("open");
|
||||||
|
document.removeEventListener("click", close);
|
||||||
|
};
|
||||||
|
setTimeout(() => document.addEventListener("click", close, { once: true }), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title="Chat settings"
|
||||||
|
aria-label="Chat settings"
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="chat-controls-dropdown" @click=${(e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}>
|
||||||
|
<div class="chat-controls">
|
||||||
|
<label class="field chat-controls__session">
|
||||||
|
<select
|
||||||
|
.value=${state.sessionKey}
|
||||||
|
@change=${(e: Event) => {
|
||||||
|
const next = (e.target as HTMLSelectElement).value;
|
||||||
|
switchChatSession(state, next);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${sessionGroups.map(
|
||||||
|
(group) => html`
|
||||||
|
<optgroup label=${group.label}>
|
||||||
|
${group.options.map(
|
||||||
|
(opt) => html`
|
||||||
|
<option value=${opt.key} title=${opt.title}>
|
||||||
|
${opt.label}
|
||||||
|
</option>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</optgroup>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div class="chat-controls__thinking">
|
||||||
|
<button
|
||||||
|
class="btn btn--sm btn--icon ${showThinking ? "active" : ""}"
|
||||||
|
?disabled=${disableThinkingToggle}
|
||||||
|
@click=${() => {
|
||||||
|
if (!disableThinkingToggle) {
|
||||||
|
state.applySettings({
|
||||||
|
...state.settings,
|
||||||
|
chatShowThinking: !state.settings.chatShowThinking,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-pressed=${showThinking}
|
||||||
|
title=${t("chat.thinkingToggle")}
|
||||||
|
>
|
||||||
|
${icons.brain}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn--sm btn--icon ${showToolCalls ? "active" : ""}"
|
||||||
|
?disabled=${disableThinkingToggle}
|
||||||
|
@click=${() => {
|
||||||
|
if (!disableThinkingToggle) {
|
||||||
|
state.applySettings({
|
||||||
|
...state.settings,
|
||||||
|
chatShowToolCalls: !state.settings.chatShowToolCalls,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-pressed=${showToolCalls}
|
||||||
|
title=${t("chat.toolCallsToggle")}
|
||||||
|
>
|
||||||
|
${toolCallsIcon}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn--sm btn--icon ${focusActive ? "active" : ""}"
|
||||||
|
?disabled=${disableFocusToggle}
|
||||||
|
@click=${() => {
|
||||||
|
if (!disableFocusToggle) {
|
||||||
|
state.applySettings({
|
||||||
|
...state.settings,
|
||||||
|
chatFocusMode: !state.settings.chatFocusMode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-pressed=${focusActive}
|
||||||
|
title=${t("chat.focusToggle")}
|
||||||
|
>
|
||||||
|
${focusIcon}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function switchChatSession(state: AppViewState, nextSessionKey: string) {
|
function switchChatSession(state: AppViewState, nextSessionKey: string) {
|
||||||
state.sessionKey = nextSessionKey;
|
state.sessionKey = nextSessionKey;
|
||||||
state.chatMessage = "";
|
state.chatMessage = "";
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { refreshChatAvatar } from "./app-chat.ts";
|
||||||
import { renderUsageTab } from "./app-render-usage-tab.ts";
|
import { renderUsageTab } from "./app-render-usage-tab.ts";
|
||||||
import {
|
import {
|
||||||
renderChatControls,
|
renderChatControls,
|
||||||
|
renderChatMobileToggle,
|
||||||
renderChatSessionSelect,
|
renderChatSessionSelect,
|
||||||
renderTab,
|
renderTab,
|
||||||
renderSidebarConnectionStatus,
|
renderSidebarConnectionStatus,
|
||||||
|
|
@ -307,6 +308,7 @@ export function renderApp(state: AppViewState) {
|
||||||
const navDrawerOpen = Boolean(state.navDrawerOpen && !chatFocus && !state.onboarding);
|
const navDrawerOpen = Boolean(state.navDrawerOpen && !chatFocus && !state.onboarding);
|
||||||
const navCollapsed = Boolean(state.settings.navCollapsed && !navDrawerOpen);
|
const navCollapsed = Boolean(state.settings.navCollapsed && !navDrawerOpen);
|
||||||
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||||
|
const showToolCalls = state.onboarding ? true : state.settings.chatShowToolCalls;
|
||||||
const assistantAvatarUrl = resolveAssistantAvatarUrl(state);
|
const assistantAvatarUrl = resolveAssistantAvatarUrl(state);
|
||||||
const chatAvatarUrl = state.chatAvatarUrl ?? assistantAvatarUrl ?? null;
|
const chatAvatarUrl = state.chatAvatarUrl ?? assistantAvatarUrl ?? null;
|
||||||
const configValue =
|
const configValue =
|
||||||
|
|
@ -438,7 +440,10 @@ export function renderApp(state: AppViewState) {
|
||||||
<span class="topbar-search__label">${t("common.search")}</span>
|
<span class="topbar-search__label">${t("common.search")}</span>
|
||||||
<kbd class="topbar-search__kbd">⌘K</kbd>
|
<kbd class="topbar-search__kbd">⌘K</kbd>
|
||||||
</button>
|
</button>
|
||||||
<div class="topbar-status">${renderTopbarThemeModeToggle(state)}</div>
|
<div class="topbar-status">
|
||||||
|
${isChat ? renderChatMobileToggle(state) : nothing}
|
||||||
|
${renderTopbarThemeModeToggle(state)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -1346,6 +1351,7 @@ export function renderApp(state: AppViewState) {
|
||||||
},
|
},
|
||||||
thinkingLevel: state.chatThinkingLevel,
|
thinkingLevel: state.chatThinkingLevel,
|
||||||
showThinking,
|
showThinking,
|
||||||
|
showToolCalls,
|
||||||
loading: state.chatLoading,
|
loading: state.chatLoading,
|
||||||
sending: state.chatSending,
|
sending: state.chatSending,
|
||||||
compactionStatus: state.compactionStatus,
|
compactionStatus: state.compactionStatus,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ type SettingsHost = {
|
||||||
themeMode: ThemeMode;
|
themeMode: ThemeMode;
|
||||||
chatFocusMode: boolean;
|
chatFocusMode: boolean;
|
||||||
chatShowThinking: boolean;
|
chatShowThinking: boolean;
|
||||||
|
chatShowToolCalls: boolean;
|
||||||
splitRatio: number;
|
splitRatio: number;
|
||||||
navCollapsed: boolean;
|
navCollapsed: boolean;
|
||||||
navWidth: number;
|
navWidth: number;
|
||||||
|
|
@ -95,6 +96,7 @@ const createHost = (tab: Tab): SettingsHost => ({
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ export function renderMessageGroup(
|
||||||
opts: {
|
opts: {
|
||||||
onOpenSidebar?: (content: string) => void;
|
onOpenSidebar?: (content: string) => void;
|
||||||
showReasoning: boolean;
|
showReasoning: boolean;
|
||||||
|
showToolCalls?: boolean;
|
||||||
assistantName?: string;
|
assistantName?: string;
|
||||||
assistantAvatar?: string | null;
|
assistantAvatar?: string | null;
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
|
|
@ -165,6 +166,7 @@ export function renderMessageGroup(
|
||||||
{
|
{
|
||||||
isStreaming: group.isStreaming && index === group.messages.length - 1,
|
isStreaming: group.isStreaming && index === group.messages.length - 1,
|
||||||
showReasoning: opts.showReasoning,
|
showReasoning: opts.showReasoning,
|
||||||
|
showToolCalls: opts.showToolCalls ?? true,
|
||||||
},
|
},
|
||||||
opts.onOpenSidebar,
|
opts.onOpenSidebar,
|
||||||
),
|
),
|
||||||
|
|
@ -619,7 +621,7 @@ function jsonSummaryLabel(parsed: unknown): string {
|
||||||
|
|
||||||
function renderGroupedMessage(
|
function renderGroupedMessage(
|
||||||
message: unknown,
|
message: unknown,
|
||||||
opts: { isStreaming: boolean; showReasoning: boolean },
|
opts: { isStreaming: boolean; showReasoning: boolean; showToolCalls?: boolean },
|
||||||
onOpenSidebar?: (content: string) => void,
|
onOpenSidebar?: (content: string) => void,
|
||||||
) {
|
) {
|
||||||
const m = message as Record<string, unknown>;
|
const m = message as Record<string, unknown>;
|
||||||
|
|
@ -632,7 +634,7 @@ function renderGroupedMessage(
|
||||||
typeof m.toolCallId === "string" ||
|
typeof m.toolCallId === "string" ||
|
||||||
typeof m.tool_call_id === "string";
|
typeof m.tool_call_id === "string";
|
||||||
|
|
||||||
const toolCards = extractToolCards(message);
|
const toolCards = (opts.showToolCalls ?? true) ? extractToolCards(message) : [];
|
||||||
const hasToolCards = toolCards.length > 0;
|
const hasToolCards = toolCards.length > 0;
|
||||||
const images = extractImages(message);
|
const images = extractImages(message);
|
||||||
const hasImages = images.length > 0;
|
const hasImages = images.length > 0;
|
||||||
|
|
@ -656,7 +658,9 @@ function renderGroupedMessage(
|
||||||
return renderCollapsedToolCards(toolCards, onOpenSidebar);
|
return renderCollapsedToolCards(toolCards, onOpenSidebar);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!markdown && !hasToolCards && !hasImages) {
|
// Suppress empty bubbles when tool cards are the only content and toggle is off
|
||||||
|
const visibleToolCards = hasToolCards && (opts.showToolCalls ?? true);
|
||||||
|
if (!markdown && !visibleToolCards && !hasImages) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -157,6 +158,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -186,6 +188,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -202,6 +205,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -232,6 +236,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -250,6 +255,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -275,6 +281,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -289,6 +296,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -316,6 +324,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||||
themeMode: "light",
|
themeMode: "light",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 320,
|
navWidth: 320,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export type UiSettings = {
|
||||||
themeMode: ThemeMode;
|
themeMode: ThemeMode;
|
||||||
chatFocusMode: boolean;
|
chatFocusMode: boolean;
|
||||||
chatShowThinking: boolean;
|
chatShowThinking: boolean;
|
||||||
|
chatShowToolCalls: boolean;
|
||||||
splitRatio: number; // Sidebar split ratio (0.4 to 0.7, default 0.6)
|
splitRatio: number; // Sidebar split ratio (0.4 to 0.7, default 0.6)
|
||||||
navCollapsed: boolean; // Collapsible sidebar state
|
navCollapsed: boolean; // Collapsible sidebar state
|
||||||
navWidth: number; // Sidebar width when expanded (240–400px)
|
navWidth: number; // Sidebar width when expanded (240–400px)
|
||||||
|
|
@ -131,6 +132,7 @@ export function loadSettings(): UiSettings {
|
||||||
themeMode: "system",
|
themeMode: "system",
|
||||||
chatFocusMode: false,
|
chatFocusMode: false,
|
||||||
chatShowThinking: true,
|
chatShowThinking: true,
|
||||||
|
chatShowToolCalls: true,
|
||||||
splitRatio: 0.6,
|
splitRatio: 0.6,
|
||||||
navCollapsed: false,
|
navCollapsed: false,
|
||||||
navWidth: 220,
|
navWidth: 220,
|
||||||
|
|
@ -173,6 +175,10 @@ export function loadSettings(): UiSettings {
|
||||||
typeof parsed.chatShowThinking === "boolean"
|
typeof parsed.chatShowThinking === "boolean"
|
||||||
? parsed.chatShowThinking
|
? parsed.chatShowThinking
|
||||||
: defaults.chatShowThinking,
|
: defaults.chatShowThinking,
|
||||||
|
chatShowToolCalls:
|
||||||
|
typeof parsed.chatShowToolCalls === "boolean"
|
||||||
|
? parsed.chatShowToolCalls
|
||||||
|
: defaults.chatShowToolCalls,
|
||||||
splitRatio:
|
splitRatio:
|
||||||
typeof parsed.splitRatio === "number" &&
|
typeof parsed.splitRatio === "number" &&
|
||||||
parsed.splitRatio >= 0.4 &&
|
parsed.splitRatio >= 0.4 &&
|
||||||
|
|
@ -214,6 +220,7 @@ function persistSettings(next: UiSettings) {
|
||||||
themeMode: next.themeMode,
|
themeMode: next.themeMode,
|
||||||
chatFocusMode: next.chatFocusMode,
|
chatFocusMode: next.chatFocusMode,
|
||||||
chatShowThinking: next.chatShowThinking,
|
chatShowThinking: next.chatShowThinking,
|
||||||
|
chatShowToolCalls: next.chatShowToolCalls,
|
||||||
splitRatio: next.splitRatio,
|
splitRatio: next.splitRatio,
|
||||||
navCollapsed: next.navCollapsed,
|
navCollapsed: next.navCollapsed,
|
||||||
navWidth: next.navWidth,
|
navWidth: next.navWidth,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ function createProps(overrides: Partial<ChatProps> = {}): ChatProps {
|
||||||
onSessionKeyChange: () => undefined,
|
onSessionKeyChange: () => undefined,
|
||||||
thinkingLevel: null,
|
thinkingLevel: null,
|
||||||
showThinking: false,
|
showThinking: false,
|
||||||
|
showToolCalls: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
sending: false,
|
sending: false,
|
||||||
canAbort: false,
|
canAbort: false,
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ function createProps(overrides: Partial<ChatProps> = {}): ChatProps {
|
||||||
onSessionKeyChange: () => undefined,
|
onSessionKeyChange: () => undefined,
|
||||||
thinkingLevel: null,
|
thinkingLevel: null,
|
||||||
showThinking: false,
|
showThinking: false,
|
||||||
|
showToolCalls: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
sending: false,
|
sending: false,
|
||||||
canAbort: false,
|
canAbort: false,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ export type ChatProps = {
|
||||||
onSessionKeyChange: (next: string) => void;
|
onSessionKeyChange: (next: string) => void;
|
||||||
thinkingLevel: string | null;
|
thinkingLevel: string | null;
|
||||||
showThinking: boolean;
|
showThinking: boolean;
|
||||||
|
showToolCalls: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
sending: boolean;
|
sending: boolean;
|
||||||
canAbort?: boolean;
|
canAbort?: boolean;
|
||||||
|
|
@ -932,6 +933,7 @@ export function renderChat(props: ChatProps) {
|
||||||
return renderMessageGroup(item, {
|
return renderMessageGroup(item, {
|
||||||
onOpenSidebar: props.onOpenSidebar,
|
onOpenSidebar: props.onOpenSidebar,
|
||||||
showReasoning,
|
showReasoning,
|
||||||
|
showToolCalls: props.showToolCalls,
|
||||||
assistantName: props.assistantName,
|
assistantName: props.assistantName,
|
||||||
assistantAvatar: assistantIdentity.avatar,
|
assistantAvatar: assistantIdentity.avatar,
|
||||||
basePath: props.basePath,
|
basePath: props.basePath,
|
||||||
|
|
@ -1409,7 +1411,7 @@ function buildChatItems(props: ChatProps): Array<ChatItem | MessageGroup> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.showThinking && normalized.role.toLowerCase() === "toolresult") {
|
if (!props.showToolCalls && normalized.role.toLowerCase() === "toolresult") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1438,7 +1440,7 @@ function buildChatItems(props: ChatProps): Array<ChatItem | MessageGroup> {
|
||||||
startedAt: segments[i].ts,
|
startedAt: segments[i].ts,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (i < tools.length) {
|
if (i < tools.length && props.showToolCalls) {
|
||||||
items.push({
|
items.push({
|
||||||
kind: "message",
|
kind: "message",
|
||||||
key: messageKey(tools[i], i + history.length),
|
key: messageKey(tools[i], i + history.length),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue