feat(settings): collapse by default (#714)

* feat: collapse settings added for console

* ran migrations

* fix: added back debug to store
This commit is contained in:
Emir Karabeg
2025-07-16 20:52:16 -07:00
committed by GitHub
parent 60e905c520
commit c436c2e378
9 changed files with 5717 additions and 114 deletions

View File

@@ -11,10 +11,10 @@ const logger = createLogger('UserSettingsAPI')
const SettingsSchema = z.object({
theme: z.enum(['system', 'light', 'dark']).optional(),
debugMode: z.boolean().optional(),
autoConnect: z.boolean().optional(),
autoFillEnvVars: z.boolean().optional(),
autoPan: z.boolean().optional(),
consoleExpandedByDefault: z.boolean().optional(),
telemetryEnabled: z.boolean().optional(),
telemetryNotifiedUser: z.boolean().optional(),
emailPreferences: z
@@ -30,10 +30,10 @@ const SettingsSchema = z.object({
// Default settings values
const defaultSettings = {
theme: 'system',
debugMode: false,
autoConnect: true,
autoFillEnvVars: true,
autoPan: true,
consoleExpandedByDefault: true,
telemetryEnabled: true,
telemetryNotifiedUser: false,
emailPreferences: {},
@@ -64,10 +64,10 @@ export async function GET() {
{
data: {
theme: userSettings.theme,
debugMode: userSettings.debugMode,
autoConnect: userSettings.autoConnect,
autoFillEnvVars: userSettings.autoFillEnvVars,
autoPan: userSettings.autoPan,
consoleExpandedByDefault: userSettings.consoleExpandedByDefault,
telemetryEnabled: userSettings.telemetryEnabled,
telemetryNotifiedUser: userSettings.telemetryNotifiedUser,
emailPreferences: userSettings.emailPreferences ?? {},

View File

@@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button'
import { createLogger } from '@/lib/logs/console-logger'
import { getBlock } from '@/blocks'
import type { ConsoleEntry as ConsoleEntryType } from '@/stores/panel/console/types'
import { useGeneralStore } from '@/stores/settings/general/store'
import { CodeDisplay } from '../code-display/code-display'
import { JSONView } from '../json-view/json-view'
@@ -164,7 +165,8 @@ const ImagePreview = ({
}
export function ConsoleEntry({ entry, consoleWidth }: ConsoleEntryProps) {
const [isExpanded, setIsExpanded] = useState(true) // Default expanded
const isConsoleExpandedByDefault = useGeneralStore((state) => state.isConsoleExpandedByDefault)
const [isExpanded, setIsExpanded] = useState(isConsoleExpandedByDefault)
const [showCopySuccess, setShowCopySuccess] = useState(false)
const [showInput, setShowInput] = useState(false) // State for input/output toggle
const [isPlaying, setIsPlaying] = useState(false)

View File

@@ -16,10 +16,11 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { useGeneralStore } from '@/stores/settings/general/store'
const TOOLTIPS = {
debugMode: 'Enable visual debugging information during execution.',
autoConnect: 'Automatically connect nodes.',
autoFillEnvVars: 'Automatically fill API keys.',
autoPan: 'Automatically pan to active blocks during workflow execution.',
consoleExpandedByDefault:
'Show console entries expanded by default. When disabled, entries will be collapsed by default.',
}
export function General() {
@@ -29,15 +30,26 @@ export function General() {
const error = useGeneralStore((state) => state.error)
const theme = useGeneralStore((state) => state.theme)
const isAutoConnectEnabled = useGeneralStore((state) => state.isAutoConnectEnabled)
const isDebugModeEnabled = useGeneralStore((state) => state.isDebugModeEnabled)
const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
const isAutoPanEnabled = useGeneralStore((state) => state.isAutoPanEnabled)
const isConsoleExpandedByDefault = useGeneralStore((state) => state.isConsoleExpandedByDefault)
// Loading states
const isAutoConnectLoading = useGeneralStore((state) => state.isAutoConnectLoading)
const isAutoFillEnvVarsLoading = useGeneralStore((state) => state.isAutoFillEnvVarsLoading)
const isAutoPanLoading = useGeneralStore((state) => state.isAutoPanLoading)
const isConsoleExpandedByDefaultLoading = useGeneralStore(
(state) => state.isConsoleExpandedByDefaultLoading
)
const isThemeLoading = useGeneralStore((state) => state.isThemeLoading)
const setTheme = useGeneralStore((state) => state.setTheme)
const toggleAutoConnect = useGeneralStore((state) => state.toggleAutoConnect)
const toggleDebugMode = useGeneralStore((state) => state.toggleDebugMode)
const toggleAutoFillEnvVars = useGeneralStore((state) => state.toggleAutoFillEnvVars)
const toggleAutoPan = useGeneralStore((state) => state.toggleAutoPan)
const toggleConsoleExpandedByDefault = useGeneralStore(
(state) => state.toggleConsoleExpandedByDefault
)
const loadSettings = useGeneralStore((state) => state.loadSettings)
useEffect(() => {
@@ -47,31 +59,31 @@ export function General() {
loadData()
}, [loadSettings, retryCount])
const handleThemeChange = (value: 'system' | 'light' | 'dark') => {
setTheme(value)
const handleThemeChange = async (value: 'system' | 'light' | 'dark') => {
await setTheme(value)
}
const handleDebugModeChange = (checked: boolean) => {
if (checked !== isDebugModeEnabled) {
toggleDebugMode()
const handleAutoConnectChange = async (checked: boolean) => {
if (checked !== isAutoConnectEnabled && !isAutoConnectLoading) {
await toggleAutoConnect()
}
}
const handleAutoConnectChange = (checked: boolean) => {
if (checked !== isAutoConnectEnabled) {
toggleAutoConnect()
const handleAutoFillEnvVarsChange = async (checked: boolean) => {
if (checked !== isAutoFillEnvVarsEnabled && !isAutoFillEnvVarsLoading) {
await toggleAutoFillEnvVars()
}
}
const handleAutoFillEnvVarsChange = (checked: boolean) => {
if (checked !== isAutoFillEnvVarsEnabled) {
toggleAutoFillEnvVars()
const handleAutoPanChange = async (checked: boolean) => {
if (checked !== isAutoPanEnabled && !isAutoPanLoading) {
await toggleAutoPan()
}
}
const handleAutoPanChange = (checked: boolean) => {
if (checked !== isAutoPanEnabled) {
toggleAutoPan()
const handleConsoleExpandedByDefaultChange = async (checked: boolean) => {
if (checked !== isConsoleExpandedByDefault && !isConsoleExpandedByDefaultLoading) {
await toggleConsoleExpandedByDefault()
}
}
@@ -111,7 +123,11 @@ export function General() {
Theme
</Label>
</div>
<Select value={theme} onValueChange={handleThemeChange} disabled={isLoading}>
<Select
value={theme}
onValueChange={handleThemeChange}
disabled={isLoading || isThemeLoading}
>
<SelectTrigger id='theme-select' className='w-[180px]'>
<SelectValue placeholder='Select theme' />
</SelectTrigger>
@@ -122,35 +138,6 @@ export function General() {
</SelectContent>
</Select>
</div>
<div className='flex items-center justify-between py-1'>
<div className='flex items-center gap-2'>
<Label htmlFor='debug-mode' className='font-medium'>
Debug mode
</Label>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='sm'
className='h-7 p-1 text-gray-500'
aria-label='Learn more about debug mode'
disabled={isLoading}
>
<Info className='h-5 w-5' />
</Button>
</TooltipTrigger>
<TooltipContent side='top' className='max-w-[300px] p-3'>
<p className='text-sm'>{TOOLTIPS.debugMode}</p>
</TooltipContent>
</Tooltip>
</div>
<Switch
id='debug-mode'
checked={isDebugModeEnabled}
onCheckedChange={handleDebugModeChange}
disabled={isLoading}
/>
</div>
<div className='flex items-center justify-between py-1'>
<div className='flex items-center gap-2'>
<Label htmlFor='auto-connect' className='font-medium'>
@@ -163,7 +150,7 @@ export function General() {
size='sm'
className='h-7 p-1 text-gray-500'
aria-label='Learn more about auto-connect feature'
disabled={isLoading}
disabled={isLoading || isAutoConnectLoading}
>
<Info className='h-5 w-5' />
</Button>
@@ -177,7 +164,7 @@ export function General() {
id='auto-connect'
checked={isAutoConnectEnabled}
onCheckedChange={handleAutoConnectChange}
disabled={isLoading}
disabled={isLoading || isAutoConnectLoading}
/>
</div>
<div className='flex items-center justify-between py-1'>
@@ -192,7 +179,7 @@ export function General() {
size='sm'
className='h-7 p-1 text-gray-500'
aria-label='Learn more about auto-fill environment variables'
disabled={isLoading}
disabled={isLoading || isAutoFillEnvVarsLoading}
>
<Info className='h-5 w-5' />
</Button>
@@ -206,13 +193,13 @@ export function General() {
id='auto-fill-env-vars'
checked={isAutoFillEnvVarsEnabled}
onCheckedChange={handleAutoFillEnvVarsChange}
disabled={isLoading}
disabled={isLoading || isAutoFillEnvVarsLoading}
/>
</div>
{/* <div className='flex items-center justify-between py-1'>
<div className='flex items-center justify-between py-1'>
<div className='flex items-center gap-2'>
<Label htmlFor='auto-pan' className='font-medium'>
Auto-pan during execution
<Label htmlFor='console-expanded-by-default' className='font-medium'>
Console expanded by default
</Label>
<Tooltip>
<TooltipTrigger asChild>
@@ -220,24 +207,24 @@ export function General() {
variant='ghost'
size='sm'
className='h-7 p-1 text-gray-500'
aria-label='Learn more about auto-pan feature'
disabled={isLoading}
aria-label='Learn more about console expanded by default'
disabled={isLoading || isConsoleExpandedByDefaultLoading}
>
<Info className='h-5 w-5' />
</Button>
</TooltipTrigger>
<TooltipContent side='top' className='max-w-[300px] p-3'>
<p className='text-sm'>{TOOLTIPS.autoPan}</p>
<p className='text-sm'>{TOOLTIPS.consoleExpandedByDefault}</p>
</TooltipContent>
</Tooltip>
</div>
<Switch
id='auto-pan'
checked={isAutoPanEnabled}
onCheckedChange={handleAutoPanChange}
disabled={isLoading}
id='console-expanded-by-default'
checked={isConsoleExpandedByDefault}
onCheckedChange={handleConsoleExpandedByDefaultChange}
disabled={isLoading || isConsoleExpandedByDefaultLoading}
/>
</div> */}
</div>
</>
)}
</div>

View File

@@ -0,0 +1,2 @@
ALTER TABLE "settings" ADD COLUMN "console_expanded_by_default" boolean DEFAULT true NOT NULL;--> statement-breakpoint
ALTER TABLE "settings" DROP COLUMN "debug_mode";

File diff suppressed because it is too large Load Diff

View File

@@ -379,6 +379,13 @@
"when": 1752708227343,
"tag": "0054_naive_raider",
"breakpoints": true
},
{
"idx": 55,
"version": "7",
"when": 1752720748565,
"tag": "0055_amused_ender_wiggin",
"breakpoints": true
}
]
}

View File

@@ -392,10 +392,10 @@ export const settings = pgTable('settings', {
// General settings
theme: text('theme').notNull().default('system'),
debugMode: boolean('debug_mode').notNull().default(false),
autoConnect: boolean('auto_connect').notNull().default(true),
autoFillEnvVars: boolean('auto_fill_env_vars').notNull().default(true),
autoPan: boolean('auto_pan').notNull().default(true),
consoleExpandedByDefault: boolean('console_expanded_by_default').notNull().default(true),
// Privacy settings
telemetryEnabled: boolean('telemetry_enabled').notNull().default(true),

View File

@@ -1,7 +1,7 @@
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { createLogger } from '@/lib/logs/console-logger'
import type { GeneralStore } from './types'
import type { General, GeneralStore, UserSettings } from './types'
const logger = createLogger('GeneralStore')
@@ -15,50 +15,115 @@ export const useGeneralStore = create<GeneralStore>()(
let lastLoadTime = 0
let errorRetryCount = 0
return {
const store: General = {
isAutoConnectEnabled: true,
isDebugModeEnabled: false,
isAutoFillEnvVarsEnabled: true,
isAutoPanEnabled: true,
theme: 'system',
isConsoleExpandedByDefault: true,
isDebugModeEnabled: false,
theme: 'system' as const,
telemetryEnabled: true,
telemetryNotifiedUser: false,
isLoading: false,
error: null,
// Individual loading states
isAutoConnectLoading: false,
isAutoFillEnvVarsLoading: false,
isAutoPanLoading: false,
isConsoleExpandedByDefaultLoading: false,
isThemeLoading: false,
isTelemetryLoading: false,
}
// Basic Actions
toggleAutoConnect: () => {
// Optimistic update helper
const updateSettingOptimistic = async <K extends keyof UserSettings>(
key: K,
value: UserSettings[K],
loadingKey: keyof General,
stateKey: keyof General
) => {
// Prevent multiple simultaneous updates
if ((get() as any)[loadingKey]) return
const originalValue = (get() as any)[stateKey]
// Optimistic update
set({ [stateKey]: value, [loadingKey]: true } as any)
try {
await get().updateSetting(key, value)
set({ [loadingKey]: false } as any)
} catch (error) {
// Rollback on error
set({ [stateKey]: originalValue, [loadingKey]: false } as any)
logger.error(`Failed to update ${String(key)}, rolled back:`, error)
}
}
return {
...store,
// Basic Actions with optimistic updates
toggleAutoConnect: async () => {
if (get().isAutoConnectLoading) return
const newValue = !get().isAutoConnectEnabled
set({ isAutoConnectEnabled: newValue })
get().updateSetting('autoConnect', newValue)
await updateSettingOptimistic(
'autoConnect',
newValue,
'isAutoConnectLoading',
'isAutoConnectEnabled'
)
},
toggleAutoFillEnvVars: async () => {
if (get().isAutoFillEnvVarsLoading) return
const newValue = !get().isAutoFillEnvVarsEnabled
await updateSettingOptimistic(
'autoFillEnvVars',
newValue,
'isAutoFillEnvVarsLoading',
'isAutoFillEnvVarsEnabled'
)
},
toggleAutoPan: async () => {
if (get().isAutoPanLoading) return
const newValue = !get().isAutoPanEnabled
await updateSettingOptimistic(
'autoPan',
newValue,
'isAutoPanLoading',
'isAutoPanEnabled'
)
},
toggleConsoleExpandedByDefault: async () => {
if (get().isConsoleExpandedByDefaultLoading) return
const newValue = !get().isConsoleExpandedByDefault
await updateSettingOptimistic(
'consoleExpandedByDefault',
newValue,
'isConsoleExpandedByDefaultLoading',
'isConsoleExpandedByDefault'
)
},
toggleDebugMode: () => {
const newValue = !get().isDebugModeEnabled
set({ isDebugModeEnabled: newValue })
get().updateSetting('debugMode', newValue)
set({ isDebugModeEnabled: !get().isDebugModeEnabled })
},
toggleAutoFillEnvVars: () => {
const newValue = !get().isAutoFillEnvVarsEnabled
set({ isAutoFillEnvVarsEnabled: newValue })
get().updateSetting('autoFillEnvVars', newValue)
setTheme: async (theme) => {
if (get().isThemeLoading) return
await updateSettingOptimistic('theme', theme, 'isThemeLoading', 'theme')
},
toggleAutoPan: () => {
const newValue = !get().isAutoPanEnabled
set({ isAutoPanEnabled: newValue })
get().updateSetting('autoPan', newValue)
},
setTheme: (theme) => {
set({ theme })
get().updateSetting('theme', theme)
},
setTelemetryEnabled: (enabled) => {
set({ telemetryEnabled: enabled })
get().updateSetting('telemetryEnabled', enabled)
setTelemetryEnabled: async (enabled) => {
if (get().isTelemetryLoading) return
await updateSettingOptimistic(
'telemetryEnabled',
enabled,
'isTelemetryLoading',
'telemetryEnabled'
)
},
setTelemetryNotifiedUser: (notified) => {
@@ -101,9 +166,9 @@ export const useGeneralStore = create<GeneralStore>()(
set({
isAutoConnectEnabled: data.autoConnect,
isDebugModeEnabled: data.debugMode,
isAutoFillEnvVarsEnabled: data.autoFillEnvVars,
isAutoPanEnabled: data.autoPan ?? true, // Default to true if undefined
isConsoleExpandedByDefault: data.consoleExpandedByDefault ?? true, // Default to true if undefined
theme: data.theme,
telemetryEnabled: data.telemetryEnabled,
telemetryNotifiedUser: data.telemetryNotifiedUser,
@@ -146,22 +211,14 @@ export const useGeneralStore = create<GeneralStore>()(
}
set({ error: null })
lastLoadTime = Date.now()
errorRetryCount = 0
} catch (error) {
logger.error(`Error updating setting ${key}:`, error)
set({ error: error instanceof Error ? error.message : 'Unknown error' })
if (errorRetryCount < MAX_ERROR_RETRIES) {
errorRetryCount++
logger.debug(`Retry attempt ${errorRetryCount} after error`)
get().loadSettings(true)
} else {
logger.warn(
`Max retries (${MAX_ERROR_RETRIES}) exceeded, skipping automatic loadSettings`
)
}
// Don't auto-retry on individual setting updates to avoid conflicts
throw error
}
},
}

View File

@@ -1,22 +1,31 @@
export interface General {
isAutoConnectEnabled: boolean
isDebugModeEnabled: boolean
isAutoFillEnvVarsEnabled: boolean
isAutoPanEnabled: boolean
isConsoleExpandedByDefault: boolean
isDebugModeEnabled: boolean
theme: 'system' | 'light' | 'dark'
telemetryEnabled: boolean
telemetryNotifiedUser: boolean
isLoading: boolean
error: string | null
// Individual loading states for optimistic updates
isAutoConnectLoading: boolean
isAutoFillEnvVarsLoading: boolean
isAutoPanLoading: boolean
isConsoleExpandedByDefaultLoading: boolean
isThemeLoading: boolean
isTelemetryLoading: boolean
}
export interface GeneralActions {
toggleAutoConnect: () => void
toggleAutoConnect: () => Promise<void>
toggleAutoFillEnvVars: () => Promise<void>
toggleAutoPan: () => Promise<void>
toggleConsoleExpandedByDefault: () => Promise<void>
toggleDebugMode: () => void
toggleAutoFillEnvVars: () => void
toggleAutoPan: () => void
setTheme: (theme: 'system' | 'light' | 'dark') => void
setTelemetryEnabled: (enabled: boolean) => void
setTheme: (theme: 'system' | 'light' | 'dark') => Promise<void>
setTelemetryEnabled: (enabled: boolean) => Promise<void>
setTelemetryNotifiedUser: (notified: boolean) => void
loadSettings: (force?: boolean) => Promise<void>
updateSetting: <K extends keyof UserSettings>(key: K, value: UserSettings[K]) => Promise<void>
@@ -26,10 +35,10 @@ export type GeneralStore = General & GeneralActions
export type UserSettings = {
theme: 'system' | 'light' | 'dark'
debugMode: boolean
autoConnect: boolean
autoFillEnvVars: boolean
autoPan: boolean
consoleExpandedByDefault: boolean
telemetryEnabled: boolean
telemetryNotifiedUser: boolean
}