feat(ui): centralize interaction preferences

Expose interaction defaults in Settings and reuse the same registry for command palette actions.
This commit is contained in:
Shantur Rathore
2026-03-11 10:53:28 +00:00
parent 0d9da40102
commit 627ff2d42b
11 changed files with 850 additions and 183 deletions

View File

@@ -45,7 +45,6 @@ export const SettingsScreen: Component = () => {
<div class="settings-screen-frame">
<Dialog.Content class="modal-surface settings-screen-shell">
<Dialog.Title class="sr-only">{t("settings.title")}</Dialog.Title>
<Dialog.Description class="sr-only">{t("settings.description")}</Dialog.Description>
<aside class="settings-screen-nav">
<div class="settings-screen-nav-header">
@@ -55,7 +54,6 @@ export const SettingsScreen: Component = () => {
</span>
<div>
<h2 class="settings-screen-title">{t("settings.title")}</h2>
<p class="settings-screen-subtitle">{t("settings.description")}</p>
</div>
</div>
</div>

View File

@@ -1,7 +1,10 @@
import type { Component } from "solid-js"
import { Check, Laptop, Moon, Sun } from "lucide-solid"
import { Select } from "@kobalte/core/select"
import { createEffect, createMemo, createSignal, For, type Component } from "solid-js"
import { Check, ChevronDown, Laptop, Moon, Sun } from "lucide-solid"
import { useI18n } from "../../lib/i18n"
import { useTheme, type ThemeMode } from "../../lib/theme"
import { useConfig } from "../../stores/preferences"
import { getBehaviorSettings, type BehaviorSetting } from "../../lib/settings/behavior-registry"
const themeModeOptions: Array<{ value: ThemeMode; icon: typeof Laptop }> = [
{ value: "system", icon: Laptop },
@@ -12,6 +15,200 @@ const themeModeOptions: Array<{ value: ThemeMode; icon: typeof Laptop }> = [
export const AppearanceSettingsSection: Component = () => {
const { t } = useI18n()
const { themeMode, setThemeMode } = useTheme()
const {
preferences,
updatePreferences,
toggleShowThinkingBlocks,
toggleKeyboardShortcutHints,
toggleShowTimelineTools,
toggleUsageMetrics,
toggleAutoCleanupBlankSessions,
togglePromptSubmitOnEnter,
setDiffViewMode,
setToolOutputExpansion,
setDiagnosticsExpansion,
setThinkingBlocksExpansion,
setToolInputsVisibility,
} = useConfig()
const behaviorSettings = createMemo(() =>
getBehaviorSettings({
preferences,
updatePreferences,
toggleShowThinkingBlocks,
toggleKeyboardShortcutHints,
toggleShowTimelineTools,
toggleUsageMetrics,
toggleAutoCleanupBlankSessions,
togglePromptSubmitOnEnter,
setDiffViewMode,
setToolOutputExpansion,
setDiagnosticsExpansion,
setThinkingBlocksExpansion,
setToolInputsVisibility,
}),
)
const [overrides, setOverrides] = createSignal<Map<string, unknown>>(new Map())
const setOverride = (id: string, value: unknown) => {
setOverrides((prev) => {
const next = new Map(prev)
next.set(id, value)
return next
})
}
createEffect(() => {
const current = overrides()
if (current.size === 0) return
const prefs = preferences()
const settings = behaviorSettings()
let changed = false
const next = new Map(current)
for (const setting of settings) {
if (!next.has(setting.id)) continue
const overrideValue = next.get(setting.id)
const actualValue = setting.get(prefs)
if (Object.is(actualValue, overrideValue)) {
next.delete(setting.id)
changed = true
}
}
if (changed) {
setOverrides(next)
}
})
const readSettingValue = (setting: BehaviorSetting) => {
const current = overrides()
if (current.has(setting.id)) return current.get(setting.id)
return setting.get(preferences())
}
type SelectOption = { value: string; label: string }
const BehaviorRow: Component<{ setting: BehaviorSetting }> = (props) => {
const setting = props.setting
const disabled = createMemo(() => (setting.disabled ? Boolean(setting.disabled()) : false))
if (setting.kind === "toggle") {
const options = createMemo<SelectOption[]>(() => [
{ value: "true", label: t("settings.common.enabled") },
{ value: "false", label: t("settings.common.disabled") },
])
const currentValue = createMemo(() => String(Boolean(readSettingValue(setting))))
const selectedOption = createMemo(() => options().find((opt) => opt.value === currentValue()))
return (
<div class={`settings-toggle-row ${disabled() ? "opacity-60" : ""}`}>
<div>
<div class="settings-toggle-title">{t(setting.titleKey)}</div>
<div class="settings-toggle-caption">{t(setting.subtitleKey)}</div>
</div>
<Select<SelectOption>
value={selectedOption()}
onChange={(opt) => {
if (!opt) return
const next = opt.value === "true"
setOverride(setting.id, next)
setting.set(next)
}}
options={options()}
optionValue="value"
optionTextValue="label"
disabled={disabled()}
itemComponent={(itemProps) => (
<Select.Item item={itemProps.item} class="selector-option">
<Select.ItemLabel class="selector-option-label">{itemProps.item.rawValue.label}</Select.ItemLabel>
</Select.Item>
)}
>
<Select.Trigger class="selector-trigger" aria-label={t(setting.titleKey)}>
<div class="flex-1 min-w-0">
<Select.Value<SelectOption>>
{(state) => (
<span class="selector-trigger-primary selector-trigger-primary--align-left">
{state.selectedOption()?.label}
</span>
)}
</Select.Value>
</div>
<Select.Icon class="selector-trigger-icon">
<ChevronDown class="w-3 h-3" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content class="selector-popover">
<Select.Listbox class="selector-listbox" />
</Select.Content>
</Select.Portal>
</Select>
</div>
)
}
const enumSetting = setting as Extract<BehaviorSetting, { kind: "enum" }>
const options = createMemo<SelectOption[]>(() =>
enumSetting.options.map((opt: { value: string; labelKey: string }) => ({
value: String(opt.value),
label: t(opt.labelKey),
})),
)
const currentValue = createMemo(() => String(readSettingValue(setting) ?? ""))
const selectedOption = createMemo(() => options().find((opt) => opt.value === currentValue()))
return (
<div class={`settings-toggle-row ${disabled() ? "opacity-60" : ""}`}>
<div>
<div class="settings-toggle-title">{t(setting.titleKey)}</div>
<div class="settings-toggle-caption">{t(setting.subtitleKey)}</div>
</div>
<Select<SelectOption>
value={selectedOption()}
onChange={(opt) => {
if (!opt) return
setOverride(setting.id, opt.value)
enumSetting.set(opt.value as any)
}}
options={options()}
optionValue="value"
optionTextValue="label"
disabled={disabled()}
itemComponent={(itemProps) => (
<Select.Item item={itemProps.item} class="selector-option">
<Select.ItemLabel class="selector-option-label">{itemProps.item.rawValue.label}</Select.ItemLabel>
</Select.Item>
)}
>
<Select.Trigger class="selector-trigger" aria-label={t(setting.titleKey)}>
<div class="flex-1 min-w-0">
<Select.Value<SelectOption>>
{(state) => (
<span class="selector-trigger-primary selector-trigger-primary--align-left">
{state.selectedOption()?.label}
</span>
)}
</Select.Value>
</div>
<Select.Icon class="selector-trigger-icon">
<ChevronDown class="w-3 h-3" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content class="selector-popover">
<Select.Listbox class="selector-listbox" />
</Select.Content>
</Select.Portal>
</Select>
</div>
)
}
const modeLabel = (mode: ThemeMode) => {
if (mode === "system") return t("theme.mode.system")
@@ -54,6 +251,20 @@ export const AppearanceSettingsSection: Component = () => {
})}
</div>
</div>
<div class="settings-card">
<div class="settings-card-header">
<div>
<h3 class="settings-card-title">{t("settings.appearance.behavior.title")}</h3>
<p class="settings-card-subtitle">{t("settings.appearance.behavior.subtitle")}</p>
</div>
<span class="settings-scope-badge">{t("settings.scope.device")}</span>
</div>
<div class="settings-stack">
<For each={behaviorSettings()}>{(setting) => <BehaviorRow setting={setting} />}</For>
</div>
</div>
</div>
)
}

View File

@@ -14,7 +14,7 @@ import { getLogger } from "../logger"
import { requestData } from "../opencode-api"
import { emitSessionSidebarRequest } from "../session-sidebar-events"
import { tGlobal } from "../i18n"
import { runtimeEnv } from "../runtime-env"
import { registerBehaviorCommands } from "../settings/behavior-registry"
const log = getLogger("actions")
@@ -427,178 +427,19 @@ export function useCommands(options: UseCommandsOptions) {
},
})
commandRegistry.register({
id: "prompt-submit-shortcut",
label: () =>
options.preferences().promptSubmitOnEnter
? tGlobal("commands.promptSubmitShortcut.label.swapped")
: tGlobal("commands.promptSubmitShortcut.label.default"),
description: () => tGlobal("commands.promptSubmitShortcut.description"),
category: "Input & Focus",
keywords: () => splitKeywords("commands.promptSubmitShortcut.keywords"),
action: options.togglePromptSubmitOnEnter,
})
commandRegistry.register({
id: "thinking",
label: () => tGlobal(options.preferences().showThinkingBlocks ? "commands.thinkingBlocks.label.hide" : "commands.thinkingBlocks.label.show"),
description: () => tGlobal("commands.thinkingBlocks.description"),
category: "System",
keywords: () => ["/thinking", ...splitKeywords("commands.thinkingBlocks.keywords")],
action: options.toggleShowThinkingBlocks,
})
commandRegistry.register({
id: "timeline-tools",
label: () => tGlobal(options.preferences().showTimelineTools ? "commands.timelineToolCalls.label.hide" : "commands.timelineToolCalls.label.show"),
description: () => tGlobal("commands.timelineToolCalls.description"),
category: "System",
keywords: () => splitKeywords("commands.timelineToolCalls.keywords"),
action: options.toggleShowTimelineTools,
})
commandRegistry.register({
id: "keyboard-shortcut-hints",
label: () =>
tGlobal(
options.preferences().showKeyboardShortcutHints
? "commands.keyboardShortcutHints.label.hide"
: "commands.keyboardShortcutHints.label.show",
),
description: () =>
tGlobal(
runtimeEnv.host === "web"
? "commands.keyboardShortcutHints.description.disabledWeb"
: "commands.keyboardShortcutHints.description",
),
category: "System",
keywords: () => splitKeywords("commands.keyboardShortcutHints.keywords"),
disabled: () => runtimeEnv.host === "web",
action: options.toggleKeyboardShortcutHints,
})
commandRegistry.register({
id: "thinking-default-visibility",
label: () => {
const mode = options.preferences().thinkingBlocksExpansion ?? "expanded"
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
return tGlobal("commands.thinkingBlocksDefault.label", { state })
},
description: () => tGlobal("commands.thinkingBlocksDefault.description"),
category: "System",
keywords: () => ["/thinking", ...splitKeywords("commands.thinkingBlocksDefault.keywords")],
action: () => {
const mode = options.preferences().thinkingBlocksExpansion ?? "expanded"
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
options.setThinkingBlocksExpansion(next)
},
})
commandRegistry.register({
id: "diff-view-split",
label: () => {
const prefix = (options.preferences().diffViewMode || "split") === "split" ? "✓ " : ""
return `${prefix}${tGlobal("commands.diffViewSplit.label")}`
},
description: () => tGlobal("commands.diffViewSplit.description"),
category: "System",
keywords: () => splitKeywords("commands.diffViewSplit.keywords"),
action: () => options.setDiffViewMode("split"),
})
commandRegistry.register({
id: "diff-view-unified",
label: () => {
const prefix = (options.preferences().diffViewMode || "split") === "unified" ? "✓ " : ""
return `${prefix}${tGlobal("commands.diffViewUnified.label")}`
},
description: () => tGlobal("commands.diffViewUnified.description"),
category: "System",
keywords: () => splitKeywords("commands.diffViewUnified.keywords"),
action: () => options.setDiffViewMode("unified"),
})
commandRegistry.register({
id: "tool-output-default-visibility",
label: () => {
const mode = options.preferences().toolOutputExpansion || "expanded"
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
return tGlobal("commands.toolOutputsDefault.label", { state })
},
description: () => tGlobal("commands.toolOutputsDefault.description"),
category: "System",
keywords: () => splitKeywords("commands.toolOutputsDefault.keywords"),
action: () => {
const mode = options.preferences().toolOutputExpansion || "expanded"
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
options.setToolOutputExpansion(next)
},
})
commandRegistry.register({
id: "diagnostics-default-visibility",
label: () => {
const mode = options.preferences().diagnosticsExpansion || "expanded"
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
return tGlobal("commands.diagnosticsDefault.label", { state })
},
description: () => tGlobal("commands.diagnosticsDefault.description"),
category: "System",
keywords: () => splitKeywords("commands.diagnosticsDefault.keywords"),
action: () => {
const mode = options.preferences().diagnosticsExpansion || "expanded"
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
options.setDiagnosticsExpansion(next)
},
})
commandRegistry.register({
id: "tool-inputs-visibility",
label: () => {
const mode = options.preferences().toolInputsVisibility || "hidden"
const state =
mode === "expanded"
? tGlobal("commands.common.expanded")
: mode === "collapsed"
? tGlobal("commands.common.collapsed")
: tGlobal("commands.common.hidden")
return tGlobal("commands.toolInputsVisibility.label", { state })
},
description: () => tGlobal("commands.toolInputsVisibility.description"),
category: "System",
keywords: () => splitKeywords("commands.toolInputsVisibility.keywords"),
action: () => {
const mode = options.preferences().toolInputsVisibility || "hidden"
const next: ToolInputsVisibilityPreference =
mode === "hidden" ? "collapsed" : mode === "collapsed" ? "expanded" : "hidden"
options.setToolInputsVisibility(next)
},
})
commandRegistry.register({
id: "token-usage-visibility",
label: () => {
const visible = options.preferences().showUsageMetrics ?? true
const state = visible ? tGlobal("commands.common.visible") : tGlobal("commands.common.hidden")
return tGlobal("commands.tokenUsageDisplay.label", { state })
},
description: () => tGlobal("commands.tokenUsageDisplay.description"),
category: "System",
keywords: () => splitKeywords("commands.tokenUsageDisplay.keywords"),
action: options.toggleUsageMetrics,
})
commandRegistry.register({
id: "auto-cleanup-blank-sessions",
label: () => {
const enabled = options.preferences().autoCleanupBlankSessions
const state = enabled ? tGlobal("commands.common.enabled") : tGlobal("commands.common.disabled")
return tGlobal("commands.autoCleanupBlankSessions.label", { state })
},
description: () => tGlobal("commands.autoCleanupBlankSessions.description"),
category: "System",
keywords: () => splitKeywords("commands.autoCleanupBlankSessions.keywords"),
action: options.toggleAutoCleanupBlankSessions,
registerBehaviorCommands((command) => commandRegistry.register(command), {
preferences: options.preferences,
toggleShowThinkingBlocks: options.toggleShowThinkingBlocks,
toggleKeyboardShortcutHints: options.toggleKeyboardShortcutHints,
toggleShowTimelineTools: options.toggleShowTimelineTools,
toggleUsageMetrics: options.toggleUsageMetrics,
toggleAutoCleanupBlankSessions: options.toggleAutoCleanupBlankSessions,
togglePromptSubmitOnEnter: options.togglePromptSubmitOnEnter,
setDiffViewMode: options.setDiffViewMode,
setToolOutputExpansion: options.setToolOutputExpansion,
setDiagnosticsExpansion: options.setDiagnosticsExpansion,
setThinkingBlocksExpansion: options.setThinkingBlocksExpansion,
setToolInputsVisibility: options.setToolInputsVisibility,
})
commandRegistry.register({

View File

@@ -57,7 +57,6 @@ export const settingsMessages = {
"contextUsagePanel.unavailable": "--",
"settings.title": "Settings",
"settings.description": "Manage appearance, notifications, remote access, and OpenCode runtime options.",
"settings.navigationAriaLabel": "Settings sections",
"settings.close": "Close settings",
"settings.content.eyebrow": "Workspace preferences",
@@ -70,6 +69,7 @@ export const settingsMessages = {
"settings.scope.device": "This device",
"settings.scope.server": "Server setting",
"settings.common.enabled": "Enabled",
"settings.common.disabled": "Disabled",
"settings.section.appearance.title": "Appearance",
"settings.section.appearance.subtitle": "Adjust how the app looks on this device.",
"settings.appearance.theme.title": "Theme",
@@ -112,4 +112,31 @@ export const settingsMessages = {
"settings.section.opencode.subtitle": "Choose the OpenCode binary and environment used for new instances.",
"settings.opencode.runtime.title": "Runtime",
"settings.opencode.runtime.subtitle": "Configure which OpenCode binary new instances launch with.",
"settings.appearance.behavior.title": "Interaction",
"settings.appearance.behavior.subtitle": "Message, diff, and input defaults.",
"settings.behavior.keyboardHints.title": "Keyboard shortcut hints",
"settings.behavior.keyboardHints.subtitle": "Show keyboard shortcut hints across the UI.",
"settings.behavior.thinking.title": "Thinking sections",
"settings.behavior.thinking.subtitle": "Show or hide AI thinking sections in messages.",
"settings.behavior.thinkingDefault.title": "Thinking default",
"settings.behavior.thinkingDefault.subtitle": "Choose whether thinking sections start expanded or collapsed.",
"settings.behavior.timelineTools.title": "Timeline tool calls",
"settings.behavior.timelineTools.subtitle": "Show or hide tool call entries in the message timeline.",
"settings.behavior.diffView.title": "Diff view",
"settings.behavior.diffView.subtitle": "Choose how tool-call diffs are displayed.",
"settings.behavior.diffView.option.split": "Split",
"settings.behavior.diffView.option.unified": "Unified",
"settings.behavior.toolOutputsDefault.title": "Tool outputs default",
"settings.behavior.toolOutputsDefault.subtitle": "Choose whether tool outputs start expanded or collapsed.",
"settings.behavior.diagnosticsDefault.title": "Diagnostics default",
"settings.behavior.diagnosticsDefault.subtitle": "Choose whether diagnostics output starts expanded or collapsed.",
"settings.behavior.toolInputsVisibility.title": "Tool inputs visibility",
"settings.behavior.toolInputsVisibility.subtitle": "Set default visibility for tool call input arguments.",
"settings.behavior.usageMetrics.title": "Token usage metrics",
"settings.behavior.usageMetrics.subtitle": "Show or hide token and cost stats for assistant messages.",
"settings.behavior.autoCleanup.title": "Auto-cleanup blank sessions",
"settings.behavior.autoCleanup.subtitle": "Automatically clean up blank sessions when creating new ones.",
"settings.behavior.promptSubmit.title": "Enter to submit",
"settings.behavior.promptSubmit.subtitle": "Use Enter to submit prompts; Cmd/Ctrl+Enter inserts a new line.",
} as const

View File

@@ -57,7 +57,6 @@ export const settingsMessages = {
"contextUsagePanel.unavailable": "--",
"settings.title": "Settings",
"settings.description": "Manage appearance, notifications, remote access, and OpenCode runtime options.",
"settings.navigationAriaLabel": "Settings sections",
"settings.close": "Close settings",
"settings.content.eyebrow": "Workspace preferences",
@@ -70,6 +69,7 @@ export const settingsMessages = {
"settings.scope.device": "This device",
"settings.scope.server": "Server setting",
"settings.common.enabled": "Enabled",
"settings.common.disabled": "Desactivado",
"settings.section.appearance.title": "Appearance",
"settings.section.appearance.subtitle": "Adjust how the app looks on this device.",
"settings.appearance.theme.title": "Theme",
@@ -112,4 +112,31 @@ export const settingsMessages = {
"settings.section.opencode.subtitle": "Choose the OpenCode binary and environment used for new instances.",
"settings.opencode.runtime.title": "Runtime",
"settings.opencode.runtime.subtitle": "Configure which OpenCode binary new instances launch with.",
"settings.appearance.behavior.title": "Interaccion",
"settings.appearance.behavior.subtitle": "Valores predeterminados de mensajes, diffs y entrada.",
"settings.behavior.keyboardHints.title": "Sugerencias de atajos de teclado",
"settings.behavior.keyboardHints.subtitle": "Muestra sugerencias de atajos de teclado en toda la interfaz.",
"settings.behavior.thinking.title": "Secciones de pensamiento",
"settings.behavior.thinking.subtitle": "Muestra u oculta las secciones de pensamiento de la IA en los mensajes.",
"settings.behavior.thinkingDefault.title": "Pensamiento por defecto",
"settings.behavior.thinkingDefault.subtitle": "Elige si las secciones de pensamiento comienzan expandidas o contraidas.",
"settings.behavior.timelineTools.title": "Llamadas de herramientas en la linea de tiempo",
"settings.behavior.timelineTools.subtitle": "Muestra u oculta entradas de llamadas de herramientas en la linea de tiempo de mensajes.",
"settings.behavior.diffView.title": "Vista de diferencias",
"settings.behavior.diffView.subtitle": "Elige como se muestran los diffs de llamadas de herramientas.",
"settings.behavior.diffView.option.split": "Dividida",
"settings.behavior.diffView.option.unified": "Unificada",
"settings.behavior.toolOutputsDefault.title": "Salidas de herramientas por defecto",
"settings.behavior.toolOutputsDefault.subtitle": "Elige si las salidas de herramientas comienzan expandidas o contraidas.",
"settings.behavior.diagnosticsDefault.title": "Diagnosticos por defecto",
"settings.behavior.diagnosticsDefault.subtitle": "Elige si la salida de diagnosticos comienza expandida o contraida.",
"settings.behavior.toolInputsVisibility.title": "Visibilidad de entradas de herramientas",
"settings.behavior.toolInputsVisibility.subtitle": "Establece la visibilidad por defecto de los argumentos de entrada de las llamadas de herramientas.",
"settings.behavior.usageMetrics.title": "Metricas de uso de tokens",
"settings.behavior.usageMetrics.subtitle": "Muestra u oculta estadisticas de tokens y costo en mensajes del asistente.",
"settings.behavior.autoCleanup.title": "Limpieza automatica de sesiones en blanco",
"settings.behavior.autoCleanup.subtitle": "Limpia automaticamente las sesiones en blanco al crear nuevas.",
"settings.behavior.promptSubmit.title": "Enter para enviar",
"settings.behavior.promptSubmit.subtitle": "Usa Enter para enviar; Cmd/Ctrl+Enter inserta una nueva linea.",
} as const

View File

@@ -57,7 +57,6 @@ export const settingsMessages = {
"contextUsagePanel.unavailable": "--",
"settings.title": "Settings",
"settings.description": "Manage appearance, notifications, remote access, and OpenCode runtime options.",
"settings.navigationAriaLabel": "Settings sections",
"settings.close": "Close settings",
"settings.content.eyebrow": "Workspace preferences",
@@ -70,6 +69,7 @@ export const settingsMessages = {
"settings.scope.device": "This device",
"settings.scope.server": "Server setting",
"settings.common.enabled": "Enabled",
"settings.common.disabled": "Desactive",
"settings.section.appearance.title": "Appearance",
"settings.section.appearance.subtitle": "Adjust how the app looks on this device.",
"settings.appearance.theme.title": "Theme",
@@ -112,4 +112,31 @@ export const settingsMessages = {
"settings.section.opencode.subtitle": "Choose the OpenCode binary and environment used for new instances.",
"settings.opencode.runtime.title": "Runtime",
"settings.opencode.runtime.subtitle": "Configure which OpenCode binary new instances launch with.",
"settings.appearance.behavior.title": "Interaction",
"settings.appearance.behavior.subtitle": "Parametres par defaut pour les messages, les diffs et la saisie.",
"settings.behavior.keyboardHints.title": "Indications de raccourcis clavier",
"settings.behavior.keyboardHints.subtitle": "Afficher des indications de raccourcis clavier dans toute l'interface.",
"settings.behavior.thinking.title": "Sections de reflexion",
"settings.behavior.thinking.subtitle": "Afficher ou masquer les sections de reflexion de l'IA dans les messages.",
"settings.behavior.thinkingDefault.title": "Etat initial de la reflexion",
"settings.behavior.thinkingDefault.subtitle": "Choisir si les sections de reflexion commencent developpees ou reduites.",
"settings.behavior.timelineTools.title": "Appels d'outils dans la chronologie",
"settings.behavior.timelineTools.subtitle": "Afficher ou masquer les entrees d'appels d'outils dans la chronologie des messages.",
"settings.behavior.diffView.title": "Vue du diff",
"settings.behavior.diffView.subtitle": "Choisir comment les diffs des appels d'outils sont affiches.",
"settings.behavior.diffView.option.split": "Scinde",
"settings.behavior.diffView.option.unified": "Unifie",
"settings.behavior.toolOutputsDefault.title": "Etat initial des sorties d'outils",
"settings.behavior.toolOutputsDefault.subtitle": "Choisir si les sorties d'outils commencent developpees ou reduites.",
"settings.behavior.diagnosticsDefault.title": "Etat initial des diagnostics",
"settings.behavior.diagnosticsDefault.subtitle": "Choisir si la sortie des diagnostics commence developpee ou reduite.",
"settings.behavior.toolInputsVisibility.title": "Visibilite des entrees d'outils",
"settings.behavior.toolInputsVisibility.subtitle": "Definir la visibilite par defaut des arguments d'entree des appels d'outils.",
"settings.behavior.usageMetrics.title": "Metriques d'utilisation des tokens",
"settings.behavior.usageMetrics.subtitle": "Afficher ou masquer les stats de tokens et de cout pour les messages de l'assistant.",
"settings.behavior.autoCleanup.title": "Nettoyage auto des sessions vides",
"settings.behavior.autoCleanup.subtitle": "Nettoyer automatiquement les sessions vides lors de la creation de nouvelles.",
"settings.behavior.promptSubmit.title": "Entrer pour envoyer",
"settings.behavior.promptSubmit.subtitle": "Utiliser Entrer pour envoyer; Cmd/Ctrl+Entrer insere une nouvelle ligne.",
} as const

View File

@@ -57,7 +57,6 @@ export const settingsMessages = {
"contextUsagePanel.unavailable": "--",
"settings.title": "Settings",
"settings.description": "Manage appearance, notifications, remote access, and OpenCode runtime options.",
"settings.navigationAriaLabel": "Settings sections",
"settings.close": "Close settings",
"settings.content.eyebrow": "Workspace preferences",
@@ -70,6 +69,7 @@ export const settingsMessages = {
"settings.scope.device": "This device",
"settings.scope.server": "Server setting",
"settings.common.enabled": "Enabled",
"settings.common.disabled": "無効",
"settings.section.appearance.title": "Appearance",
"settings.section.appearance.subtitle": "Adjust how the app looks on this device.",
"settings.appearance.theme.title": "Theme",
@@ -112,4 +112,31 @@ export const settingsMessages = {
"settings.section.opencode.subtitle": "Choose the OpenCode binary and environment used for new instances.",
"settings.opencode.runtime.title": "Runtime",
"settings.opencode.runtime.subtitle": "Configure which OpenCode binary new instances launch with.",
"settings.appearance.behavior.title": "操作",
"settings.appearance.behavior.subtitle": "メッセージ、差分、入力の既定値。",
"settings.behavior.keyboardHints.title": "キーボードショートカットのヒント",
"settings.behavior.keyboardHints.subtitle": "UI全体でキーボードショートカットのヒントを表示します。",
"settings.behavior.thinking.title": "思考セクション",
"settings.behavior.thinking.subtitle": "メッセージ内のAIの思考セクションを表示/非表示にします。",
"settings.behavior.thinkingDefault.title": "思考の既定",
"settings.behavior.thinkingDefault.subtitle": "思考セクションを最初に展開/折りたたみのどちらで表示するかを選びます。",
"settings.behavior.timelineTools.title": "タイムラインのツール呼び出し",
"settings.behavior.timelineTools.subtitle": "メッセージタイムラインでツール呼び出しを表示/非表示にします。",
"settings.behavior.diffView.title": "差分表示",
"settings.behavior.diffView.subtitle": "ツール呼び出しの差分の表示方法を選びます。",
"settings.behavior.diffView.option.split": "分割",
"settings.behavior.diffView.option.unified": "統合",
"settings.behavior.toolOutputsDefault.title": "ツール出力の既定",
"settings.behavior.toolOutputsDefault.subtitle": "ツール出力を最初に展開/折りたたみのどちらで表示するかを選びます。",
"settings.behavior.diagnosticsDefault.title": "診断の既定",
"settings.behavior.diagnosticsDefault.subtitle": "診断出力を最初に展開/折りたたみのどちらで表示するかを選びます。",
"settings.behavior.toolInputsVisibility.title": "ツール入力の表示",
"settings.behavior.toolInputsVisibility.subtitle": "ツール呼び出しの入力引数の既定の表示状態を設定します。",
"settings.behavior.usageMetrics.title": "トークン使用量メトリクス",
"settings.behavior.usageMetrics.subtitle": "アシスタントのメッセージにトークン数とコストの統計を表示/非表示にします。",
"settings.behavior.autoCleanup.title": "空のセッションを自動クリーンアップ",
"settings.behavior.autoCleanup.subtitle": "新しいセッション作成時に空のセッションを自動的にクリーンアップします。",
"settings.behavior.promptSubmit.title": "Enterで送信",
"settings.behavior.promptSubmit.subtitle": "Enterで送信し、Cmd/Ctrl+Enterで改行します。",
} as const

View File

@@ -57,7 +57,6 @@ export const settingsMessages = {
"contextUsagePanel.unavailable": "--",
"settings.title": "Settings",
"settings.description": "Manage appearance, notifications, remote access, and OpenCode runtime options.",
"settings.navigationAriaLabel": "Settings sections",
"settings.close": "Close settings",
"settings.content.eyebrow": "Workspace preferences",
@@ -70,6 +69,7 @@ export const settingsMessages = {
"settings.scope.device": "This device",
"settings.scope.server": "Server setting",
"settings.common.enabled": "Enabled",
"settings.common.disabled": "Отключено",
"settings.section.appearance.title": "Appearance",
"settings.section.appearance.subtitle": "Adjust how the app looks on this device.",
"settings.appearance.theme.title": "Theme",
@@ -112,4 +112,31 @@ export const settingsMessages = {
"settings.section.opencode.subtitle": "Choose the OpenCode binary and environment used for new instances.",
"settings.opencode.runtime.title": "Runtime",
"settings.opencode.runtime.subtitle": "Configure which OpenCode binary new instances launch with.",
"settings.appearance.behavior.title": "Взаимодействие",
"settings.appearance.behavior.subtitle": "Значения по умолчанию для сообщений, диффов и ввода.",
"settings.behavior.keyboardHints.title": "Подсказки сочетаний клавиш",
"settings.behavior.keyboardHints.subtitle": "Показывать подсказки сочетаний клавиш по всему интерфейсу.",
"settings.behavior.thinking.title": "Разделы размышлений",
"settings.behavior.thinking.subtitle": "Показывать или скрывать разделы размышлений ИИ в сообщениях.",
"settings.behavior.thinkingDefault.title": "Размышления по умолчанию",
"settings.behavior.thinkingDefault.subtitle": "Выберите, начинать ли разделы размышлений развернутыми или свернутыми.",
"settings.behavior.timelineTools.title": "Вызовы инструментов в таймлайне",
"settings.behavior.timelineTools.subtitle": "Показывать или скрывать записи вызовов инструментов в таймлайне сообщений.",
"settings.behavior.diffView.title": "Вид диффа",
"settings.behavior.diffView.subtitle": "Выберите, как отображаются диффы вызовов инструментов.",
"settings.behavior.diffView.option.split": "Раздельный",
"settings.behavior.diffView.option.unified": "Единый",
"settings.behavior.toolOutputsDefault.title": "Выводы инструментов по умолчанию",
"settings.behavior.toolOutputsDefault.subtitle": "Выберите, начинать ли выводы инструментов развернутыми или свернутыми.",
"settings.behavior.diagnosticsDefault.title": "Диагностика по умолчанию",
"settings.behavior.diagnosticsDefault.subtitle": "Выберите, начинать ли вывод диагностики развернутым или свернутым.",
"settings.behavior.toolInputsVisibility.title": "Видимость входных данных инструмента",
"settings.behavior.toolInputsVisibility.subtitle": "Задайте видимость по умолчанию для входных аргументов вызовов инструментов.",
"settings.behavior.usageMetrics.title": "Метрики использования токенов",
"settings.behavior.usageMetrics.subtitle": "Показывать или скрывать статистику токенов и стоимости в сообщениях ассистента.",
"settings.behavior.autoCleanup.title": "Автоочистка пустых сессий",
"settings.behavior.autoCleanup.subtitle": "Автоматически очищать пустые сессии при создании новых.",
"settings.behavior.promptSubmit.title": "Enter для отправки",
"settings.behavior.promptSubmit.subtitle": "Enter отправляет; Cmd/Ctrl+Enter вставляет новую строку.",
} as const

View File

@@ -57,7 +57,6 @@ export const settingsMessages = {
"contextUsagePanel.unavailable": "--",
"settings.title": "Settings",
"settings.description": "Manage appearance, notifications, remote access, and OpenCode runtime options.",
"settings.navigationAriaLabel": "Settings sections",
"settings.close": "Close settings",
"settings.content.eyebrow": "Workspace preferences",
@@ -70,6 +69,7 @@ export const settingsMessages = {
"settings.scope.device": "This device",
"settings.scope.server": "Server setting",
"settings.common.enabled": "Enabled",
"settings.common.disabled": "已禁用",
"settings.section.appearance.title": "Appearance",
"settings.section.appearance.subtitle": "Adjust how the app looks on this device.",
"settings.appearance.theme.title": "Theme",
@@ -112,4 +112,31 @@ export const settingsMessages = {
"settings.section.opencode.subtitle": "Choose the OpenCode binary and environment used for new instances.",
"settings.opencode.runtime.title": "Runtime",
"settings.opencode.runtime.subtitle": "Configure which OpenCode binary new instances launch with.",
"settings.appearance.behavior.title": "交互",
"settings.appearance.behavior.subtitle": "消息、差异与输入的默认值。",
"settings.behavior.keyboardHints.title": "键盘快捷键提示",
"settings.behavior.keyboardHints.subtitle": "在整个界面中显示键盘快捷键提示。",
"settings.behavior.thinking.title": "思考区块",
"settings.behavior.thinking.subtitle": "在消息中显示或隐藏AI的思考区块。",
"settings.behavior.thinkingDefault.title": "思考默认状态",
"settings.behavior.thinkingDefault.subtitle": "选择思考区块默认是展开还是折叠。",
"settings.behavior.timelineTools.title": "时间线工具调用",
"settings.behavior.timelineTools.subtitle": "在消息时间线中显示或隐藏工具调用条目。",
"settings.behavior.diffView.title": "差异视图",
"settings.behavior.diffView.subtitle": "选择工具调用差异的显示方式。",
"settings.behavior.diffView.option.split": "分栏",
"settings.behavior.diffView.option.unified": "统一",
"settings.behavior.toolOutputsDefault.title": "工具输出默认状态",
"settings.behavior.toolOutputsDefault.subtitle": "选择工具输出默认是展开还是折叠。",
"settings.behavior.diagnosticsDefault.title": "诊断默认状态",
"settings.behavior.diagnosticsDefault.subtitle": "选择诊断输出默认是展开还是折叠。",
"settings.behavior.toolInputsVisibility.title": "工具输入可见性",
"settings.behavior.toolInputsVisibility.subtitle": "设置工具调用输入参数的默认可见性。",
"settings.behavior.usageMetrics.title": "令牌用量指标",
"settings.behavior.usageMetrics.subtitle": "显示或隐藏助手消息的令牌与成本统计。",
"settings.behavior.autoCleanup.title": "自动清理空会话",
"settings.behavior.autoCleanup.subtitle": "创建新会话时自动清理空会话。",
"settings.behavior.promptSubmit.title": "回车发送",
"settings.behavior.promptSubmit.subtitle": "使用回车发送Cmd/Ctrl+回车插入新行。",
} as const

View File

@@ -0,0 +1,452 @@
import type { Accessor } from "solid-js"
import type {
Preferences,
ExpansionPreference,
ToolInputsVisibilityPreference,
} from "../../stores/preferences"
import type { Command } from "../commands"
import { tGlobal } from "../i18n"
import { runtimeEnv } from "../runtime-env"
export type BehaviorSettingKind = "toggle" | "enum"
export type BehaviorToggleSetting = {
kind: "toggle"
id: string
titleKey: string
subtitleKey: string
get: (preferences: Preferences) => boolean
set: (next: boolean) => void
disabled?: () => boolean
}
export type BehaviorEnumSetting<T extends string = string> = {
kind: "enum"
id: string
titleKey: string
subtitleKey: string
get: (preferences: Preferences) => T
set: (next: T) => void
options: Array<{ value: T; labelKey: string }>
disabled?: () => boolean
}
export type BehaviorSetting = BehaviorToggleSetting | BehaviorEnumSetting
export type BehaviorRegistryActions = {
preferences: Accessor<Preferences>
updatePreferences?: (updates: Partial<Preferences>) => void
toggleShowThinkingBlocks: () => void
toggleKeyboardShortcutHints: () => void
toggleShowTimelineTools: () => void
toggleUsageMetrics: () => void
toggleAutoCleanupBlankSessions: () => void
togglePromptSubmitOnEnter: () => void
setDiffViewMode: (mode: "split" | "unified") => void
setToolOutputExpansion: (mode: ExpansionPreference) => void
setDiagnosticsExpansion: (mode: ExpansionPreference) => void
setThinkingBlocksExpansion: (mode: ExpansionPreference) => void
setToolInputsVisibility: (mode: ToolInputsVisibilityPreference) => void
}
function splitKeywords(key: string): string[] {
return tGlobal(key)
.split(",")
.map((value) => value.trim())
.filter(Boolean)
}
function setBooleanByToggle(getCurrent: () => boolean, toggle: () => void, next: boolean) {
if (getCurrent() === next) return
toggle()
}
export function getBehaviorSettings(actions: BehaviorRegistryActions): BehaviorSetting[] {
const prefs = actions.preferences
const updatePreferences = actions.updatePreferences
return [
{
kind: "toggle",
id: "behavior.keyboardShortcutHints",
titleKey: "settings.behavior.keyboardHints.title",
subtitleKey: "settings.behavior.keyboardHints.subtitle",
get: (p) => Boolean(p.showKeyboardShortcutHints ?? true),
set: (next) => {
if (updatePreferences) {
updatePreferences({ showKeyboardShortcutHints: next })
return
}
setBooleanByToggle(
() => Boolean(prefs().showKeyboardShortcutHints ?? true),
actions.toggleKeyboardShortcutHints,
next,
)
},
disabled: () => runtimeEnv.host === "web",
},
{
kind: "toggle",
id: "behavior.thinkingBlocks",
titleKey: "settings.behavior.thinking.title",
subtitleKey: "settings.behavior.thinking.subtitle",
get: (p) => Boolean(p.showThinkingBlocks),
set: (next) => {
if (updatePreferences) {
updatePreferences({ showThinkingBlocks: next })
return
}
setBooleanByToggle(
() => Boolean(prefs().showThinkingBlocks),
actions.toggleShowThinkingBlocks,
next,
)
},
},
{
kind: "enum",
id: "behavior.thinkingBlocksDefault",
titleKey: "settings.behavior.thinkingDefault.title",
subtitleKey: "settings.behavior.thinkingDefault.subtitle",
get: (p) => (p.thinkingBlocksExpansion ?? "expanded") as ExpansionPreference,
set: (next) => {
if (updatePreferences) {
updatePreferences({ thinkingBlocksExpansion: next as ExpansionPreference })
return
}
actions.setThinkingBlocksExpansion(next as ExpansionPreference)
},
options: [
{ value: "expanded", labelKey: "commands.common.expanded" },
{ value: "collapsed", labelKey: "commands.common.collapsed" },
],
},
{
kind: "toggle",
id: "behavior.timelineToolCalls",
titleKey: "settings.behavior.timelineTools.title",
subtitleKey: "settings.behavior.timelineTools.subtitle",
get: (p) => Boolean(p.showTimelineTools),
set: (next) => {
if (updatePreferences) {
updatePreferences({ showTimelineTools: next })
return
}
setBooleanByToggle(
() => Boolean(prefs().showTimelineTools),
actions.toggleShowTimelineTools,
next,
)
},
},
{
kind: "enum",
id: "behavior.diffViewMode",
titleKey: "settings.behavior.diffView.title",
subtitleKey: "settings.behavior.diffView.subtitle",
get: (p) => (p.diffViewMode ?? "split") as "split" | "unified",
set: (next) => {
if (updatePreferences) {
updatePreferences({ diffViewMode: next as "split" | "unified" })
return
}
actions.setDiffViewMode(next as "split" | "unified")
},
options: [
{ value: "split", labelKey: "settings.behavior.diffView.option.split" },
{ value: "unified", labelKey: "settings.behavior.diffView.option.unified" },
],
},
{
kind: "enum",
id: "behavior.toolOutputsDefault",
titleKey: "settings.behavior.toolOutputsDefault.title",
subtitleKey: "settings.behavior.toolOutputsDefault.subtitle",
get: (p) => (p.toolOutputExpansion ?? "expanded") as ExpansionPreference,
set: (next) => {
if (updatePreferences) {
updatePreferences({ toolOutputExpansion: next as ExpansionPreference })
return
}
actions.setToolOutputExpansion(next as ExpansionPreference)
},
options: [
{ value: "expanded", labelKey: "commands.common.expanded" },
{ value: "collapsed", labelKey: "commands.common.collapsed" },
],
},
{
kind: "enum",
id: "behavior.diagnosticsDefault",
titleKey: "settings.behavior.diagnosticsDefault.title",
subtitleKey: "settings.behavior.diagnosticsDefault.subtitle",
get: (p) => (p.diagnosticsExpansion ?? "expanded") as ExpansionPreference,
set: (next) => {
if (updatePreferences) {
updatePreferences({ diagnosticsExpansion: next as ExpansionPreference })
return
}
actions.setDiagnosticsExpansion(next as ExpansionPreference)
},
options: [
{ value: "expanded", labelKey: "commands.common.expanded" },
{ value: "collapsed", labelKey: "commands.common.collapsed" },
],
},
{
kind: "enum",
id: "behavior.toolInputsVisibility",
titleKey: "settings.behavior.toolInputsVisibility.title",
subtitleKey: "settings.behavior.toolInputsVisibility.subtitle",
get: (p) => (p.toolInputsVisibility ?? "hidden") as ToolInputsVisibilityPreference,
set: (next) => {
if (updatePreferences) {
updatePreferences({ toolInputsVisibility: next as ToolInputsVisibilityPreference })
return
}
actions.setToolInputsVisibility(next as ToolInputsVisibilityPreference)
},
options: [
{ value: "hidden", labelKey: "commands.common.hidden" },
{ value: "collapsed", labelKey: "commands.common.collapsed" },
{ value: "expanded", labelKey: "commands.common.expanded" },
],
},
{
kind: "toggle",
id: "behavior.usageMetrics",
titleKey: "settings.behavior.usageMetrics.title",
subtitleKey: "settings.behavior.usageMetrics.subtitle",
get: (p) => Boolean(p.showUsageMetrics ?? true),
set: (next) => {
if (updatePreferences) {
updatePreferences({ showUsageMetrics: next })
return
}
setBooleanByToggle(
() => Boolean(prefs().showUsageMetrics ?? true),
actions.toggleUsageMetrics,
next,
)
},
},
{
kind: "toggle",
id: "behavior.autoCleanupBlankSessions",
titleKey: "settings.behavior.autoCleanup.title",
subtitleKey: "settings.behavior.autoCleanup.subtitle",
get: (p) => Boolean(p.autoCleanupBlankSessions),
set: (next) => {
if (updatePreferences) {
updatePreferences({ autoCleanupBlankSessions: next })
return
}
setBooleanByToggle(
() => Boolean(prefs().autoCleanupBlankSessions),
actions.toggleAutoCleanupBlankSessions,
next,
)
},
},
{
kind: "toggle",
id: "behavior.promptSubmitOnEnter",
titleKey: "settings.behavior.promptSubmit.title",
subtitleKey: "settings.behavior.promptSubmit.subtitle",
get: (p) => Boolean(p.promptSubmitOnEnter),
set: (next) => {
if (updatePreferences) {
updatePreferences({ promptSubmitOnEnter: next })
return
}
setBooleanByToggle(
() => Boolean(prefs().promptSubmitOnEnter),
actions.togglePromptSubmitOnEnter,
next,
)
},
},
]
}
export function getBehaviorCommands(actions: BehaviorRegistryActions): Command[] {
return [
{
id: "prompt-submit-shortcut",
label: () =>
actions.preferences().promptSubmitOnEnter
? tGlobal("commands.promptSubmitShortcut.label.swapped")
: tGlobal("commands.promptSubmitShortcut.label.default"),
description: () => tGlobal("commands.promptSubmitShortcut.description"),
category: "Input & Focus",
keywords: () => splitKeywords("commands.promptSubmitShortcut.keywords"),
action: actions.togglePromptSubmitOnEnter,
},
{
id: "thinking",
label: () =>
tGlobal(
actions.preferences().showThinkingBlocks
? "commands.thinkingBlocks.label.hide"
: "commands.thinkingBlocks.label.show",
),
description: () => tGlobal("commands.thinkingBlocks.description"),
category: "System",
keywords: () => ["/thinking", ...splitKeywords("commands.thinkingBlocks.keywords")],
action: actions.toggleShowThinkingBlocks,
},
{
id: "timeline-tools",
label: () =>
tGlobal(
actions.preferences().showTimelineTools
? "commands.timelineToolCalls.label.hide"
: "commands.timelineToolCalls.label.show",
),
description: () => tGlobal("commands.timelineToolCalls.description"),
category: "System",
keywords: () => splitKeywords("commands.timelineToolCalls.keywords"),
action: actions.toggleShowTimelineTools,
},
{
id: "keyboard-shortcut-hints",
label: () =>
tGlobal(
actions.preferences().showKeyboardShortcutHints
? "commands.keyboardShortcutHints.label.hide"
: "commands.keyboardShortcutHints.label.show",
),
description: () =>
tGlobal(
runtimeEnv.host === "web"
? "commands.keyboardShortcutHints.description.disabledWeb"
: "commands.keyboardShortcutHints.description",
),
category: "System",
keywords: () => splitKeywords("commands.keyboardShortcutHints.keywords"),
disabled: () => runtimeEnv.host === "web",
action: actions.toggleKeyboardShortcutHints,
},
{
id: "thinking-default-visibility",
label: () => {
const mode = actions.preferences().thinkingBlocksExpansion ?? "expanded"
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
return tGlobal("commands.thinkingBlocksDefault.label", { state })
},
description: () => tGlobal("commands.thinkingBlocksDefault.description"),
category: "System",
keywords: () => ["/thinking", ...splitKeywords("commands.thinkingBlocksDefault.keywords")],
action: () => {
const mode = actions.preferences().thinkingBlocksExpansion ?? "expanded"
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
actions.setThinkingBlocksExpansion(next)
},
},
{
id: "diff-view-split",
label: () => {
const prefix = (actions.preferences().diffViewMode || "split") === "split" ? "✓ " : ""
return `${prefix}${tGlobal("commands.diffViewSplit.label")}`
},
description: () => tGlobal("commands.diffViewSplit.description"),
category: "System",
keywords: () => splitKeywords("commands.diffViewSplit.keywords"),
action: () => actions.setDiffViewMode("split"),
},
{
id: "diff-view-unified",
label: () => {
const prefix = (actions.preferences().diffViewMode || "split") === "unified" ? "✓ " : ""
return `${prefix}${tGlobal("commands.diffViewUnified.label")}`
},
description: () => tGlobal("commands.diffViewUnified.description"),
category: "System",
keywords: () => splitKeywords("commands.diffViewUnified.keywords"),
action: () => actions.setDiffViewMode("unified"),
},
{
id: "tool-output-default-visibility",
label: () => {
const mode = actions.preferences().toolOutputExpansion || "expanded"
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
return tGlobal("commands.toolOutputsDefault.label", { state })
},
description: () => tGlobal("commands.toolOutputsDefault.description"),
category: "System",
keywords: () => splitKeywords("commands.toolOutputsDefault.keywords"),
action: () => {
const mode = actions.preferences().toolOutputExpansion || "expanded"
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
actions.setToolOutputExpansion(next)
},
},
{
id: "diagnostics-default-visibility",
label: () => {
const mode = actions.preferences().diagnosticsExpansion || "expanded"
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
return tGlobal("commands.diagnosticsDefault.label", { state })
},
description: () => tGlobal("commands.diagnosticsDefault.description"),
category: "System",
keywords: () => splitKeywords("commands.diagnosticsDefault.keywords"),
action: () => {
const mode = actions.preferences().diagnosticsExpansion || "expanded"
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
actions.setDiagnosticsExpansion(next)
},
},
{
id: "tool-inputs-visibility",
label: () => {
const mode = actions.preferences().toolInputsVisibility || "hidden"
const state =
mode === "expanded"
? tGlobal("commands.common.expanded")
: mode === "collapsed"
? tGlobal("commands.common.collapsed")
: tGlobal("commands.common.hidden")
return tGlobal("commands.toolInputsVisibility.label", { state })
},
description: () => tGlobal("commands.toolInputsVisibility.description"),
category: "System",
keywords: () => splitKeywords("commands.toolInputsVisibility.keywords"),
action: () => {
const mode = actions.preferences().toolInputsVisibility || "hidden"
const next: ToolInputsVisibilityPreference =
mode === "hidden" ? "collapsed" : mode === "collapsed" ? "expanded" : "hidden"
actions.setToolInputsVisibility(next)
},
},
{
id: "token-usage-visibility",
label: () => {
const visible = actions.preferences().showUsageMetrics ?? true
const state = visible ? tGlobal("commands.common.visible") : tGlobal("commands.common.hidden")
return tGlobal("commands.tokenUsageDisplay.label", { state })
},
description: () => tGlobal("commands.tokenUsageDisplay.description"),
category: "System",
keywords: () => splitKeywords("commands.tokenUsageDisplay.keywords"),
action: actions.toggleUsageMetrics,
},
{
id: "auto-cleanup-blank-sessions",
label: () => {
const enabled = actions.preferences().autoCleanupBlankSessions
const state = enabled ? tGlobal("commands.common.enabled") : tGlobal("commands.common.disabled")
return tGlobal("commands.autoCleanupBlankSessions.label", { state })
},
description: () => tGlobal("commands.autoCleanupBlankSessions.description"),
category: "System",
keywords: () => splitKeywords("commands.autoCleanupBlankSessions.keywords"),
action: actions.toggleAutoCleanupBlankSessions,
},
]
}
export function registerBehaviorCommands(register: (command: Command) => void, actions: BehaviorRegistryActions) {
const commands = getBehaviorCommands(actions)
commands.forEach((command) => register(command))
}

View File

@@ -15,6 +15,9 @@
ring-color: var(--accent-primary);
}
.selector-trigger:disabled,
.selector-trigger[aria-disabled="true"],
.selector-trigger[data-disabled],
.selector-trigger-disabled {
@apply opacity-50 cursor-not-allowed;
}