From e348706fa162b39f6f671e67b5bc12295cd45d0d Mon Sep 17 00:00:00 2001 From: "cyrus@tinyhumans.ai" Date: Thu, 9 Apr 2026 15:50:49 +0530 Subject: [PATCH 1/4] feat(settings,skills): reorganize settings and skills pages for consistency and usability - Remove dead code: TauriCommandsPanel, useSettingsAnimation, SettingsPanelLayout, SettingsBackButton, ProfilePanel, AdvancedPanel, SkillsPanel, SkillsGrid (~1900 lines) - Standardize settings panel padding to p-4 space-y-4 across all panels - Add breadcrumb navigation to SettingsHeader with route-derived breadcrumbs - Decompose oversized panels: LocalModelPanel, AutocompletePanel, CronJobsPanel, ScreenIntelligencePanel into sub-components in dedicated subdirectories - Deduplicate skills management: move browser access toggle to Skills page, remove redundant /settings/skills route - Unify skill card layout: UnifiedSkillCard component with overflow menu for secondary actions, consistent status/CTA patterns across all skill types - Add skill search bar and category filter with grouped results Closes #396 --- .claude/memory.md | 11 + app/src/components/SkillsGrid.tsx | 217 --- .../settings/SettingsSectionPage.tsx | 9 +- .../components/SettingsBackButton.tsx | 31 - .../settings/components/SettingsHeader.tsx | 38 + .../components/SettingsPanelLayout.tsx | 26 - .../settings/hooks/useSettingsAnimation.ts | 57 - .../settings/hooks/useSettingsNavigation.ts | 91 +- .../components/settings/panels/AIPanel.tsx | 9 +- .../settings/panels/AccessibilityPanel.tsx | 3 +- .../settings/panels/AdvancedPanel.tsx | 40 - .../settings/panels/AgentChatPanel.tsx | 9 +- .../settings/panels/AutocompletePanel.tsx | 345 +---- .../settings/panels/BillingPanel.tsx | 5 +- .../settings/panels/ConnectionsPanel.tsx | 9 +- .../settings/panels/CronJobsPanel.tsx | 305 +--- .../settings/panels/DeveloperOptionsPanel.tsx | 25 +- .../settings/panels/LocalModelPanel.tsx | 917 ++---------- .../settings/panels/MemoryDataPanel.tsx | 9 +- .../settings/panels/MemoryDebugPanel.tsx | 11 +- .../settings/panels/MessagingPanel.tsx | 9 +- .../settings/panels/PrivacyPanel.tsx | 9 +- .../settings/panels/ProfilePanel.tsx | 34 - .../settings/panels/RecoveryPhrasePanel.tsx | 9 +- .../panels/ScreenIntelligencePanel.tsx | 236 +-- .../settings/panels/SkillsPanel.tsx | 246 ---- .../settings/panels/TauriCommandsPanel.tsx | 1268 ----------------- .../settings/panels/TeamInvitesPanel.tsx | 9 +- .../settings/panels/TeamManagementPanel.tsx | 23 +- .../settings/panels/TeamMembersPanel.tsx | 11 +- .../components/settings/panels/TeamPanel.tsx | 9 +- .../components/settings/panels/ToolsPanel.tsx | 15 +- .../components/settings/panels/VoicePanel.tsx | 9 +- .../settings/panels/WebhooksDebugPanel.tsx | 11 +- .../panels/autocomplete/AppFilterSection.tsx | 137 ++ .../autocomplete/CompletionStyleSection.tsx | 210 +++ .../settings/panels/cron/CoreJobList.tsx | 141 ++ .../panels/cron/RuntimeSkillCronList.tsx | 173 +++ .../local-model/DeviceCapabilitySection.tsx | 130 ++ .../local-model/ModelDownloadSection.tsx | 375 +++++ .../panels/local-model/ModelStatusSection.tsx | 384 +++++ .../PermissionsSection.tsx | 120 ++ .../SessionAndVisionSection.tsx | 133 ++ app/src/components/skills/SkillCard.tsx | 340 +++++ .../components/skills/SkillCategoryFilter.tsx | 40 + app/src/components/skills/SkillSearchBar.tsx | 52 + app/src/pages/Settings.tsx | 22 - app/src/pages/Skills.tsx | 698 ++++----- 48 files changed, 3073 insertions(+), 3947 deletions(-) delete mode 100644 app/src/components/SkillsGrid.tsx delete mode 100644 app/src/components/settings/components/SettingsBackButton.tsx delete mode 100644 app/src/components/settings/components/SettingsPanelLayout.tsx delete mode 100644 app/src/components/settings/hooks/useSettingsAnimation.ts delete mode 100644 app/src/components/settings/panels/AdvancedPanel.tsx delete mode 100644 app/src/components/settings/panels/ProfilePanel.tsx delete mode 100644 app/src/components/settings/panels/SkillsPanel.tsx delete mode 100644 app/src/components/settings/panels/TauriCommandsPanel.tsx create mode 100644 app/src/components/settings/panels/autocomplete/AppFilterSection.tsx create mode 100644 app/src/components/settings/panels/autocomplete/CompletionStyleSection.tsx create mode 100644 app/src/components/settings/panels/cron/CoreJobList.tsx create mode 100644 app/src/components/settings/panels/cron/RuntimeSkillCronList.tsx create mode 100644 app/src/components/settings/panels/local-model/DeviceCapabilitySection.tsx create mode 100644 app/src/components/settings/panels/local-model/ModelDownloadSection.tsx create mode 100644 app/src/components/settings/panels/local-model/ModelStatusSection.tsx create mode 100644 app/src/components/settings/panels/screen-intelligence/PermissionsSection.tsx create mode 100644 app/src/components/settings/panels/screen-intelligence/SessionAndVisionSection.tsx create mode 100644 app/src/components/skills/SkillCard.tsx create mode 100644 app/src/components/skills/SkillCategoryFilter.tsx create mode 100644 app/src/components/skills/SkillSearchBar.tsx diff --git a/.claude/memory.md b/.claude/memory.md index 650b1aa3b09..554f7e579f7 100644 --- a/.claude/memory.md +++ b/.claude/memory.md @@ -88,6 +88,17 @@ Quick reference for anyone starting with Claude on this project. Updated by the - **Phased rollout** — Phase 1 = banners + limit modal + hook. Phase 2 = onboarding upsell + analytics. Phase 3 = remote config + A/B testing. - **"5-hour" label stragglers in Conversations.tsx** — `LimitPill` label and its hover tooltip still say "5h" / "5-hour". Commit 8c52236's "10-hour" terminology refactor missed those two spots. +## Settings & Skills Reorganization (Issue #396) + +- **Settings is NOT a modal** — It's a full route (`/settings/*`) with nested ``. The `.claude/rules/15-settings-modal-system.md` doc is outdated. +- **SettingsHeader breadcrumbs** — All panels now receive `breadcrumbs` from `useSettingsNavigation()` hook. The hook derives breadcrumbs from the current route path. When adding a new settings panel, destructure `breadcrumbs` from the hook and pass to ``. +- **Standard settings padding** — All settings panel content areas use `p-4 space-y-4`. Don't deviate. +- **Dead code removed** — `TauriCommandsPanel`, `useSettingsAnimation`, `SettingsPanelLayout`, `SettingsBackButton`, `ProfilePanel`, `AdvancedPanel`, `SkillsPanel`, `SkillsGrid` were all deleted. Don't re-create them. +- **Skills page is the single management surface** — Browser Access toggle moved from SkillsPanel to the Skills page. There is no `/settings/skills` route anymore. +- **Panel decomposition** — LocalModelPanel, AutocompletePanel, CronJobsPanel, ScreenIntelligencePanel were split into sub-components in subdirectories. Each orchestrator is ≤ ~300 lines. +- **UnifiedSkillCard** — All skill types (built-in, channels, 3rd party) use `UnifiedSkillCard` from `app/src/components/skills/SkillCard.tsx`. Secondary actions use an overflow menu. `data-testid` attributes (`skill-sync-button-*`, `skill-debug-button-*`) must be preserved. +- **SkillSearchBar + SkillCategoryFilter** — New components in `app/src/components/skills/` for search and category filtering on the Skills page. + ## Environment - **Core sidecar port** — `7788` (default). Check with `lsof -i :7788`. diff --git a/app/src/components/SkillsGrid.tsx b/app/src/components/SkillsGrid.tsx deleted file mode 100644 index 6decd7db65a..00000000000 --- a/app/src/components/SkillsGrid.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { useAvailableSkills, useSkillConnectionStatus } from '../lib/skills/hooks'; -import { deriveSkillSyncSummaryText } from '../pages/skillsSyncUi'; -import { IS_DEV } from '../utils/config'; -import { DefaultIcon, SKILL_ICONS, type SkillListEntry, STATUS_DISPLAY } from './skills/shared'; -import SkillSetupModal from './skills/SkillSetupModal'; - -interface SkillRowProps { - skillId: string; - name: string; - icon?: React.ReactElement; - skillType?: 'openhuman' | 'openclaw'; - syncSummaryText: string | null; - onConnect: (e: React.MouseEvent) => void; -} - -function SkillTypeBadge({ type }: { type?: string }) { - if (!type) return null; - const isOpenclaw = type === 'openclaw'; - return ( - - {type} - - ); -} - -function SkillRow({ skillId, name, icon, skillType, syncSummaryText, onConnect }: SkillRowProps) { - const connectionStatus = useSkillConnectionStatus(skillId); - const statusDisplay = STATUS_DISPLAY[connectionStatus] || STATUS_DISPLAY.offline; - - return ( - - -
-
- {icon || } -
- {name} - -
- - -
-
- {statusDisplay.text} -
- - - {syncSummaryText ?? 'No syncs yet'} - - - - - - - - ); -} - -export default function SkillsGrid() { - const navigate = useNavigate(); - const [skillsList, setSkillsList] = useState([]); - const [loading, setLoading] = useState(true); - const [setupModalOpen, setSetupModalOpen] = useState(false); - const [activeSkillId, setActiveSkillId] = useState(null); - const [activeSkillName, setActiveSkillName] = useState(''); - const [activeSkillDescription, setActiveSkillDescription] = useState(''); - const [activeSkillHasSetup, setActiveSkillHasSetup] = useState(false); - const [activeSkillType, setActiveSkillType] = useState<'openhuman' | 'openclaw'>('openhuman'); - - const { skills: availableSkills, loading: registryLoading } = useAvailableSkills(); - - // Derive SkillListEntry from registry - const derivedSkillsList: SkillListEntry[] = useMemo(() => { - return availableSkills - .filter(e => { - if (e.id.includes('_')) return false; - if (!IS_DEV && e.ignore_in_production) return false; - return true; - }) - .map(e => { - const setup = e.setup as { required?: boolean; oauth?: unknown } | undefined; - const hasSetup = !!setup && (setup.required === true || !!setup.oauth); - return { - id: e.id, - name: e.name || e.id, - description: e.description || '', - icon: SKILL_ICONS[e.id], - ignoreInProduction: e.ignore_in_production, - hasSetup, - skill_type: 'openhuman' as const, - }; - }); - }, [availableSkills]); - - // Update local state when registry loads (for compatibility with existing code below) - if (!registryLoading && skillsList.length === 0 && derivedSkillsList.length > 0) { - setSkillsList(derivedSkillsList); - setLoading(false); - } - - const sortedSkillsList = useMemo(() => { - const list = skillsList.length > 0 ? skillsList : derivedSkillsList; - return [...list] - .sort((a, b) => a.name.localeCompare(b.name)) - .filter(s => IS_DEV || !s.ignoreInProduction); - }, [skillsList, derivedSkillsList]); - - if (loading || skillsList.length === 0) { - return null; - } - - const handleConnect = (skill: SkillListEntry) => { - setActiveSkillId(skill.id); - setActiveSkillName(skill.name); - setActiveSkillDescription(skill.description); - setActiveSkillHasSetup(skill.hasSetup); - setActiveSkillType(skill.skill_type ?? 'openhuman'); - setSetupModalOpen(true); - }; - - return ( - <> -
-
-

Available Skills

-
-
navigate('/skills')}> -
- - - - - - - - - - - {sortedSkillsList.map(skill => { - const syncSummaryText = deriveSkillSyncSummaryText(undefined, undefined); - - return ( - { - e.stopPropagation(); - handleConnect(skill); - }} - /> - ); - })} - -
- - Skill - - - - Status - - - - Sync - -
-
-
- View all skills -
-
-
- - {setupModalOpen && activeSkillId && ( - { - setSetupModalOpen(false); - setActiveSkillId(null); - }} - /> - )} - - ); -} diff --git a/app/src/components/settings/SettingsSectionPage.tsx b/app/src/components/settings/SettingsSectionPage.tsx index 5830f2a6530..e90263f4655 100644 --- a/app/src/components/settings/SettingsSectionPage.tsx +++ b/app/src/components/settings/SettingsSectionPage.tsx @@ -19,11 +19,16 @@ interface SettingsSectionPageProps { } const SettingsSectionPage = ({ title, description, items }: SettingsSectionPageProps) => { - const { navigateBack, navigateToSettings } = useSettingsNavigation(); + const { navigateBack, navigateToSettings, breadcrumbs } = useSettingsNavigation(); return (
- +
{description &&

{description}

} diff --git a/app/src/components/settings/components/SettingsBackButton.tsx b/app/src/components/settings/components/SettingsBackButton.tsx deleted file mode 100644 index 5a424b09e0e..00000000000 --- a/app/src/components/settings/components/SettingsBackButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -interface SettingsBackButtonProps { - onClick: () => void; - title?: string; - className?: string; -} - -const SettingsBackButton = ({ - onClick, - title = 'Settings', - className = '', -}: SettingsBackButtonProps) => { - return ( -
- -
- ); -}; - -export default SettingsBackButton; diff --git a/app/src/components/settings/components/SettingsHeader.tsx b/app/src/components/settings/components/SettingsHeader.tsx index 513eb201349..9e3007e9c0c 100644 --- a/app/src/components/settings/components/SettingsHeader.tsx +++ b/app/src/components/settings/components/SettingsHeader.tsx @@ -1,8 +1,14 @@ +interface BreadcrumbItem { + label: string; + onClick?: () => void; +} + interface SettingsHeaderProps { className?: string; title?: string; showBackButton?: boolean; onBack?: () => void; + breadcrumbs?: BreadcrumbItem[]; } const SettingsHeader = ({ @@ -10,6 +16,7 @@ const SettingsHeader = ({ title = 'Settings', showBackButton = false, onBack, + breadcrumbs, }: SettingsHeaderProps) => { return (
@@ -35,6 +42,37 @@ const SettingsHeader = ({ )} + {/* Breadcrumbs */} + {breadcrumbs && breadcrumbs.length > 0 && ( +
+ {breadcrumbs.map((crumb, i) => ( +
+ {crumb.onClick ? ( + + ) : ( + {crumb.label} + )} + + + +
+ ))} +
+ )} + {/* Title */}

{title}

diff --git a/app/src/components/settings/components/SettingsPanelLayout.tsx b/app/src/components/settings/components/SettingsPanelLayout.tsx deleted file mode 100644 index c406d1ae2bb..00000000000 --- a/app/src/components/settings/components/SettingsPanelLayout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { ReactNode } from 'react'; - -import SettingsBackButton from './SettingsBackButton'; - -interface SettingsPanelLayoutProps { - title: string; - onBack: () => void; - children: ReactNode; - className?: string; -} - -const SettingsPanelLayout = ({ - title, - onBack, - children, - className = '', -}: SettingsPanelLayoutProps) => { - return ( -
- -
{children}
-
- ); -}; - -export default SettingsPanelLayout; diff --git a/app/src/components/settings/hooks/useSettingsAnimation.ts b/app/src/components/settings/hooks/useSettingsAnimation.ts deleted file mode 100644 index 7ac02cf579b..00000000000 --- a/app/src/components/settings/hooks/useSettingsAnimation.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useEffect, useState } from 'react'; - -export type AnimationState = 'entering' | 'entered' | 'exiting' | 'exited'; - -interface SettingsAnimationHook { - isVisible: boolean; - animationState: AnimationState; - startEntry: () => void; - startExit: () => void; -} - -export const useSettingsAnimation = (duration = 300): SettingsAnimationHook => { - const [animationState, setAnimationState] = useState('exited'); - - const isVisible = animationState === 'entering' || animationState === 'entered'; - - const startEntry = () => { - setAnimationState('entering'); - setTimeout(() => { - setAnimationState('entered'); - }, duration); - }; - - const startExit = () => { - setAnimationState('exiting'); - setTimeout(() => { - setAnimationState('exited'); - }, duration); - }; - - return { isVisible, animationState, startEntry, startExit }; -}; - -// Hook for panel slide animations (slide from right) -export const usePanelAnimation = (isActive: boolean, duration = 300) => { - const [mounted, setMounted] = useState(isActive); - - useEffect(() => { - if (isActive) { - setMounted(true); - } else { - const timer = setTimeout(() => setMounted(false), duration); - return () => clearTimeout(timer); - } - }, [isActive, duration]); - - const getPanelClasses = () => { - const baseClasses = 'transition-all duration-300 ease-[cubic-bezier(0.25,0.46,0.45,0.94)]'; - if (!mounted) return `${baseClasses} opacity-0`; - - return isActive - ? `${baseClasses} opacity-100 translate-x-0` - : `${baseClasses} opacity-0 translate-x-full`; - }; - - return { mounted, panelClasses: getPanelClasses() }; -}; diff --git a/app/src/components/settings/hooks/useSettingsNavigation.ts b/app/src/components/settings/hooks/useSettingsNavigation.ts index 4545d855e38..794a00ea79f 100644 --- a/app/src/components/settings/hooks/useSettingsNavigation.ts +++ b/app/src/components/settings/hooks/useSettingsNavigation.ts @@ -12,15 +12,12 @@ export type SettingsRoute = | 'screen-intelligence' | 'autocomplete' | 'privacy' - | 'profile' - | 'advanced' | 'billing' | 'team' | 'team-members' | 'team-invites' | 'developer-options' | 'accessibility' - | 'skills' | 'ai' | 'local-model' | 'voice' @@ -30,12 +27,18 @@ export type SettingsRoute = | 'webhooks-debug' | 'agent-chat'; +export interface BreadcrumbItem { + label: string; + onClick?: () => void; +} + interface SettingsNavigationHook { currentRoute: SettingsRoute; navigateToSettings: (route?: SettingsRoute | string) => void; navigateToTeamManagement: (teamId: string) => void; navigateBack: () => void; closeSettings: () => void; + breadcrumbs: BreadcrumbItem[]; } export const useSettingsNavigation = (): SettingsNavigationHook => { @@ -74,12 +77,9 @@ export const useSettingsNavigation = (): SettingsNavigationHook => { if (path.includes('/settings/screen-intelligence')) return 'screen-intelligence'; if (path.includes('/settings/autocomplete')) return 'autocomplete'; if (path.includes('/settings/privacy')) return 'privacy'; - if (path.includes('/settings/profile')) return 'profile'; - if (path.includes('/settings/advanced')) return 'advanced'; if (path.includes('/settings/billing')) return 'billing'; if (path.includes('/settings/developer-options')) return 'developer-options'; if (path.includes('/settings/accessibility')) return 'accessibility'; - if (path.includes('/settings/skills')) return 'skills'; if (path.includes('/settings/ai')) return 'ai'; if (path.includes('/settings/local-model')) return 'local-model'; if (path.includes('/settings/voice')) return 'voice'; @@ -123,11 +123,90 @@ export const useSettingsNavigation = (): SettingsNavigationHook => { goBackWithFallback('/home'); }, [goBackWithFallback]); + const settingsCrumb: BreadcrumbItem = { label: 'Settings', onClick: () => navigate('/settings') }; + + const accountCrumb: BreadcrumbItem = { + label: 'Account & Security', + onClick: () => navigate('/settings/account'), + }; + + const automationCrumb: BreadcrumbItem = { + label: 'Automation & Channels', + onClick: () => navigate('/settings/automation'), + }; + + const aiToolsCrumb: BreadcrumbItem = { + label: 'AI & Skills', + onClick: () => navigate('/settings/ai-tools'), + }; + + const teamCrumb: BreadcrumbItem = { label: 'Team', onClick: () => navigate('/settings/team') }; + + const developerCrumb: BreadcrumbItem = { + label: 'Developer Options', + onClick: () => navigate('/settings/developer-options'), + }; + + const getBreadcrumbs = (): BreadcrumbItem[] => { + switch (currentRoute) { + // Section pages + case 'account': + case 'automation': + case 'ai-tools': + return [settingsCrumb]; + + // Leaf panels under account + case 'billing': + case 'recovery-phrase': + case 'team': + case 'connections': + return [settingsCrumb, accountCrumb]; + + // Leaf panels under automation + case 'accessibility': + case 'screen-intelligence': + case 'autocomplete': + case 'messaging': + case 'cron-jobs': + return [settingsCrumb, automationCrumb]; + + // Leaf panels under ai-tools + case 'voice': + case 'local-model': + case 'ai': + return [settingsCrumb, aiToolsCrumb]; + + // Team sub-pages + case 'team-members': + case 'team-invites': + return [settingsCrumb, accountCrumb, teamCrumb]; + + // Developer sub-pages + case 'webhooks-debug': + case 'memory-data': + case 'memory-debug': + return [settingsCrumb, developerCrumb]; + + // Other leaf pages + case 'privacy': + case 'agent-chat': + case 'developer-options': + return [settingsCrumb]; + + case 'home': + default: + return []; + } + }; + + const breadcrumbs = getBreadcrumbs(); + return { currentRoute, navigateToSettings, navigateToTeamManagement, navigateBack, closeSettings, + breadcrumbs, }; }; diff --git a/app/src/components/settings/panels/AIPanel.tsx b/app/src/components/settings/panels/AIPanel.tsx index 7712dd998be..16e6c1a64bc 100644 --- a/app/src/components/settings/panels/AIPanel.tsx +++ b/app/src/components/settings/panels/AIPanel.tsx @@ -12,7 +12,7 @@ import SettingsHeader from '../components/SettingsHeader'; import { useSettingsNavigation } from '../hooks/useSettingsNavigation'; const AIPanel = () => { - const { navigateBack, navigateToSettings } = useSettingsNavigation(); + const { navigateBack, navigateToSettings, breadcrumbs } = useSettingsNavigation(); const [aiConfig, setAiConfig] = useState(null); const [loading, setLoading] = useState(false); const [refreshingComponent, setRefreshingComponent] = useState<'soul' | 'tools' | 'all' | null>( @@ -75,7 +75,12 @@ const AIPanel = () => { return (
- +
diff --git a/app/src/components/settings/panels/AccessibilityPanel.tsx b/app/src/components/settings/panels/AccessibilityPanel.tsx index bf3a66c2467..0c5f2a013aa 100644 --- a/app/src/components/settings/panels/AccessibilityPanel.tsx +++ b/app/src/components/settings/panels/AccessibilityPanel.tsx @@ -45,7 +45,7 @@ const PermissionBadge = ({ label, value }: { label: string; value: string }) => }; const AccessibilityPanel = () => { - const { navigateBack } = useSettingsNavigation(); + const { navigateBack, breadcrumbs } = useSettingsNavigation(); const dispatch = useAppDispatch(); const { status, @@ -113,6 +113,7 @@ const AccessibilityPanel = () => { title="Accessibility Automation" showBackButton={true} onBack={navigateBack} + breadcrumbs={breadcrumbs} />
diff --git a/app/src/components/settings/panels/AdvancedPanel.tsx b/app/src/components/settings/panels/AdvancedPanel.tsx deleted file mode 100644 index 8d5a7018ca4..00000000000 --- a/app/src/components/settings/panels/AdvancedPanel.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import SettingsHeader from '../components/SettingsHeader'; -import { useSettingsNavigation } from '../hooks/useSettingsNavigation'; - -const AdvancedPanel = () => { - const { navigateBack } = useSettingsNavigation(); - - return ( -
- - -
-
-
- - - - -
-

Coming soon

-
-
-
- ); -}; - -export default AdvancedPanel; diff --git a/app/src/components/settings/panels/AgentChatPanel.tsx b/app/src/components/settings/panels/AgentChatPanel.tsx index ef7ad8bed51..386411b2c0c 100644 --- a/app/src/components/settings/panels/AgentChatPanel.tsx +++ b/app/src/components/settings/panels/AgentChatPanel.tsx @@ -9,7 +9,7 @@ type ChatMessage = { role: 'user' | 'agent'; text: string }; const STORAGE_KEY = 'openhuman.settings.agentChat.history'; const AgentChatPanel = () => { - const { navigateBack } = useSettingsNavigation(); + const { navigateBack, breadcrumbs } = useSettingsNavigation(); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [modelOverride, setModelOverride] = useState(''); @@ -73,7 +73,12 @@ const AgentChatPanel = () => { return (
- +
diff --git a/app/src/components/settings/panels/AutocompletePanel.tsx b/app/src/components/settings/panels/AutocompletePanel.tsx index d6734f26e45..618a71850f3 100644 --- a/app/src/components/settings/panels/AutocompletePanel.tsx +++ b/app/src/components/settings/panels/AutocompletePanel.tsx @@ -18,6 +18,8 @@ import { } from '../../../utils/tauriCommands'; import SettingsHeader from '../components/SettingsHeader'; import { useSettingsNavigation } from '../hooks/useSettingsNavigation'; +import AppFilterSection from './autocomplete/AppFilterSection'; +import CompletionStyleSection from './autocomplete/CompletionStyleSection'; const DEFAULT_CONFIG: AutocompleteConfig = { enabled: true, @@ -65,7 +67,7 @@ const parseAutocompleteConfig = (raw: unknown): AutocompleteConfig => { }; const AutocompletePanel = () => { - const { navigateBack } = useSettingsNavigation(); + const { navigateBack, breadcrumbs } = useSettingsNavigation(); const [status, setStatus] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); @@ -226,7 +228,7 @@ const AutocompletePanel = () => { }; const refreshStatus = async (showSpinner = false) => { - if (!isTauri()) return; + if (!isTauri()) return null; if (showSpinner) { setIsLoading(true); setError(null); @@ -240,9 +242,9 @@ const AutocompletePanel = () => { } return response.result; } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to refresh autocomplete status'; - appendUiLog(`refresh status failed: ${message}`); - setError(message); + const msg = err instanceof Error ? err.message : 'Failed to refresh autocomplete status'; + appendUiLog(`refresh status failed: ${msg}`); + setError(msg); return null; } finally { if (showSpinner) { @@ -292,9 +294,9 @@ const AutocompletePanel = () => { setMessage('Autocomplete settings saved.'); await refreshStatus(); } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to save autocomplete settings'; - appendUiLog(`save settings failed: ${message}`); - setError(message); + const msg = err instanceof Error ? err.message : 'Failed to save autocomplete settings'; + appendUiLog(`save settings failed: ${msg}`); + setError(msg); } finally { setIsSaving(false); } @@ -322,9 +324,9 @@ const AutocompletePanel = () => { setMessage('Autocomplete did not start.'); } } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to start autocomplete'; - appendUiLog(`start failed: ${message}`); - setError(message); + const msg = err instanceof Error ? err.message : 'Failed to start autocomplete'; + appendUiLog(`start failed: ${msg}`); + setError(msg); } }; @@ -342,9 +344,9 @@ const AutocompletePanel = () => { appendUiLog('runtime still reports running after stop'); } } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to stop autocomplete'; - appendUiLog(`stop failed: ${message}`); - setError(message); + const msg = err instanceof Error ? err.message : 'Failed to stop autocomplete'; + appendUiLog(`stop failed: ${msg}`); + setError(msg); } }; @@ -369,9 +371,9 @@ const AutocompletePanel = () => { ); await refreshStatus(); } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to fetch current suggestion'; - appendUiLog(`get suggestion failed: ${message}`); - setError(message); + const msg = err instanceof Error ? err.message : 'Failed to fetch current suggestion'; + appendUiLog(`get suggestion failed: ${msg}`); + setError(msg); } }; @@ -394,9 +396,9 @@ const AutocompletePanel = () => { await refreshStatus(); await waitForAcceptedHistoryEntry(response.result.value); } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to accept suggestion'; - appendUiLog(`accept failed: ${message}`); - setError(message); + const msg = err instanceof Error ? err.message : 'Failed to accept suggestion'; + appendUiLog(`accept failed: ${msg}`); + setError(msg); } }; @@ -412,9 +414,9 @@ const AutocompletePanel = () => { `focus app=${response.result.app_name ?? 'n/a'} role=${response.result.role ?? 'n/a'} chars=${String(response.result.context.length)}` ); } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to inspect focused element'; - appendUiLog(`debug focus failed: ${message}`); - setError(message); + const msg = err instanceof Error ? err.message : 'Failed to inspect focused element'; + appendUiLog(`debug focus failed: ${msg}`); + setError(msg); } }; @@ -434,255 +436,58 @@ const AutocompletePanel = () => { return (
- +
-
-

Runtime

-
-
Platform supported: {status?.platform_supported ? 'yes' : 'no'}
-
Enabled: {status?.enabled ? 'yes' : 'no'}
-
Running: {status?.running ? 'yes' : 'no'}
-
Phase: {status?.phase ?? 'unknown'}
-
Debounce: {status?.debounce_ms ?? 0}ms
-
Model: {status?.model_id ?? 'n/a'}
-
App: {status?.app_name ?? 'n/a'}
-
Last error: {status?.last_error ?? 'none'}
-
Current suggestion: {status?.suggestion?.value ?? 'none'}
-
-
- - - -
-
- -
-

Settings

- - - - - - -
-
Style Instructions
-