Files
CodeNomad/packages/ui/src/lib/settings/behavior-registry.ts
Shantur Rathore 627ff2d42b feat(ui): centralize interaction preferences
Expose interaction defaults in Settings and reuse the same registry for command palette actions.
2026-03-11 10:53:28 +00:00

453 lines
16 KiB
TypeScript

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))
}