diff --git a/src/renderer/src/components/cmd-j/palette-results.ts b/src/renderer/src/components/cmd-j/palette-results.ts index f6295856569..aac89d2c39c 100644 --- a/src/renderer/src/components/cmd-j/palette-results.ts +++ b/src/renderer/src/components/cmd-j/palette-results.ts @@ -1,5 +1,4 @@ -import type { LucideIcon } from 'lucide-react' -import type { SettingsNavSection } from '@/lib/settings-navigation-types' +import type { SettingsNavIcon, SettingsNavSection } from '@/lib/settings-navigation-types' import type { CmdJQuickAction } from './quick-actions' export type CmdJSettingsResult = { @@ -7,7 +6,7 @@ export type CmdJSettingsResult = { kind: 'settings' title: string description: string - icon: LucideIcon + icon: SettingsNavIcon sectionId: string targetSectionId?: string order: number diff --git a/src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx b/src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx index 9f3797ac5dc..e7615f28cd0 100644 --- a/src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx +++ b/src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx @@ -224,7 +224,7 @@ export function FeatureWallSetupChecklist( const setupSteps = getFeatureWallSetupStepsForSection('setup') return ( -
+
({ ...group, sections: generalNavSections.filter((section) => section.group === group.id) - })).filter((group) => group.sections.length > 0) + })).filter((group) => group.sections.length > 0 || group.id === 'setup') const repoNavSections = visibleNavSections .filter((section) => section.id.startsWith('repo-')) .map((section) => { @@ -910,8 +910,6 @@ function Settings(): React.JSX.Element {
) : ( - {settingsSearchQuery.trim() === '' ? : null} - ) : null} + + {isSectionMounted('setup-guide') ? : null} + + s.openModal) - const progress = useSetupGuideProgress(true, false, false) - const setupSteps = useMemo(() => getFeatureWallSetupSteps(), []) - const unfinishedSteps = useMemo( - () => setupSteps.filter((step) => !progress.stepDone[step.id]), - [progress.stepDone, setupSteps] - ) - const completedStepCount = useMemo( - () => setupSteps.filter((step) => progress.stepDone[step.id]).length, - [progress.stepDone, setupSteps] - ) - const firstUnfinishedStepId: FeatureWallSetupStepId = getFirstIncompleteFeatureWallSetupStepId( - progress.stepDone - ) - const setupComplete = unfinishedSteps.length === 0 - - return ( -
-
-
- - - -
-
-

- Onboarding checklist -

-

- {completedStepCount}/{setupSteps.length} setup tasks complete -

-
-
-

- {setupComplete ? 'Status' : 'To do'} -

-
- {setupComplete ? ( - - All core workflows complete - - ) : ( - <> - {unfinishedSteps.slice(0, 3).map((step) => ( - - {step.name} - - ))} - {unfinishedSteps.length > 3 ? ( - - +{unfinishedSteps.length - 3} more - - ) : null} - - )} -
-
-
-
- -
-
- ) -} diff --git a/src/renderer/src/components/settings/SettingsSetupGuidePane.tsx b/src/renderer/src/components/settings/SettingsSetupGuidePane.tsx new file mode 100644 index 00000000000..b2e106554a1 --- /dev/null +++ b/src/renderer/src/components/settings/SettingsSetupGuidePane.tsx @@ -0,0 +1,58 @@ +import { useEffect, useMemo, useState } from 'react' +import { + getFeatureWallSetupSteps, + getFirstIncompleteFeatureWallSetupStepId +} from '../../../../shared/feature-wall-setup-steps' +import type { FeatureWallSetupStepId } from '../../../../shared/feature-wall-setup-steps' +import { FeatureWallSetupChecklist } from '../feature-wall/FeatureWallSetupChecklist' +import { useSetupGuideProgress } from '../setup-guide/use-setup-guide-progress' + +export function SettingsSetupGuidePane(): React.JSX.Element { + const setupSteps = useMemo(() => getFeatureWallSetupSteps(), []) + const [activeStepId, setActiveStepId] = useState( + () => setupSteps[0]?.id ?? 'default-agent' + ) + const [userSelectedStep, setUserSelectedStep] = useState(false) + const [orchestrationSkillInstalled, setOrchestrationSkillInstalled] = useState(false) + const [browserUseSkillInstalled, setBrowserUseSkillInstalled] = useState(false) + const progress = useSetupGuideProgress( + true, + orchestrationSkillInstalled, + browserUseSkillInstalled + ) + const activeStep = setupSteps.find((step) => step.id === activeStepId) ?? setupSteps[0] ?? null + + useEffect(() => { + if (userSelectedStep) { + return + } + setActiveStepId(getFirstIncompleteFeatureWallSetupStepId(progress.stepDone)) + }, [progress.stepDone, userSelectedStep]) + + useEffect(() => { + if (!activeStep || userSelectedStep || !progress.stepDone[activeStep.id]) { + return + } + const nextUnfinishedStepId = getFirstIncompleteFeatureWallSetupStepId(progress.stepDone) + if (nextUnfinishedStepId !== activeStep.id) { + setActiveStepId(nextUnfinishedStepId) + } + }, [activeStep, progress.stepDone, userSelectedStep]) + + const handleSelectStep = (id: FeatureWallSetupStepId): void => { + setUserSelectedStep(true) + setActiveStepId(id) + } + + return ( +
+ +
+ ) +} diff --git a/src/renderer/src/components/settings/SettingsSidebar.test.tsx b/src/renderer/src/components/settings/SettingsSidebar.test.tsx index 9f4a9278e00..0b22d486578 100644 --- a/src/renderer/src/components/settings/SettingsSidebar.test.tsx +++ b/src/renderer/src/components/settings/SettingsSidebar.test.tsx @@ -2,6 +2,7 @@ import { renderToStaticMarkup } from 'react-dom/server' import { Bot, Mic, Network } from 'lucide-react' import { describe, expect, it, vi } from 'vitest' import { SettingsSidebar } from './SettingsSidebar' +import { TooltipProvider } from '../ui/tooltip' vi.mock('@/hooks/useShortcutLabel', () => ({ useShortcutLabel: () => '⌘F' @@ -9,52 +10,54 @@ vi.mock('@/hooks/useShortcutLabel', () => ({ function renderSidebar(): string { return renderToStaticMarkup( - + + + ) } diff --git a/src/renderer/src/components/settings/SettingsSidebar.tsx b/src/renderer/src/components/settings/SettingsSidebar.tsx index 1890394981c..f572ffc4902 100644 --- a/src/renderer/src/components/settings/SettingsSidebar.tsx +++ b/src/renderer/src/components/settings/SettingsSidebar.tsx @@ -1,17 +1,21 @@ import type { RefObject } from 'react' -import { ArrowLeft, Search, Server, type LucideIcon, type LucideProps } from 'lucide-react' +import { ArrowLeft, Search, Server } from 'lucide-react' +import logo from '../../../../../resources/logo.svg' import type { RepoIcon } from '../../../../shared/repo-icon' -import type { SettingsNavInstallStatus } from '@/lib/settings-navigation-types' +import type { SettingsNavIcon, SettingsNavInstallStatus } from '@/lib/settings-navigation-types' import { useShortcutLabel } from '@/hooks/useShortcutLabel' import { cn } from '@/lib/utils' import { RepoIconGlyph } from '../repo/repo-icon' import { Button } from '../ui/button' import { Input } from '../ui/input' +import { SetupGuideProgressRing } from '../setup-guide/SetupGuideProgressRing' +import { useSettingsSetupGuideProgress } from './settings-setup-guide-progress' +import type { SettingsSetupGuideProgress } from './settings-setup-guide-progress' type NavSection = { id: string title: string - icon: LucideIcon | ((props: LucideProps) => React.JSX.Element) + icon: SettingsNavIcon badge?: string installStatus?: SettingsNavInstallStatus } @@ -48,6 +52,64 @@ type SettingsSidebarProps = { ) => void } +type SettingsSetupGuideRowProps = { + progress: SettingsSetupGuideProgress + setupActive: boolean + onSelect: (modifiers: { + metaKey: boolean + ctrlKey: boolean + shiftKey: boolean + altKey: boolean + }) => void +} + +function SettingsSetupGuideNavRow({ + progress, + setupActive, + onSelect +}: SettingsSetupGuideRowProps): React.JSX.Element { + return ( + + ) +} + export function SettingsSidebar({ activeSectionId, generalGroups, @@ -59,6 +121,9 @@ export function SettingsSidebar({ onSearchChange, onSelectSection }: SettingsSidebarProps): React.JSX.Element { + const setupGuideProgress = useSettingsSetupGuideProgress(true) + const setupActive = activeSectionId === 'setup-guide' + const showSetupGuideTopRow = setupGuideProgress.doneCount < setupGuideProgress.total const searchShortcutHint = useShortcutLabel('settings.search') const navItemClassName = (isActive: boolean): string => cn( @@ -119,6 +184,16 @@ export function SettingsSidebar({
+ {showSetupGuideTopRow ? ( +
+ onSelectSection('setup-guide', modifiers)} + /> +
+ ) : null} +
{generalGroups.map((group) => ( @@ -127,9 +202,11 @@ export function SettingsSidebar({ {group.title}

- {group.sections.map((section) => { - const Icon = section.icon - const isActive = activeSectionId === section.id + {group.sections + .filter((section) => section.id !== 'setup-guide') + .map((section) => { + const Icon = section.icon + const isActive = activeSectionId === section.id return (