mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-29 18:40:38 +00:00
feat: integrate SetupWizard into desktop boot flow
SetupWizard orchestrates the pick→connect→ingest→ready state machine with a step indicator; SetupScreen now transitions to the wizard after all boot checks pass (phase === 'ready') instead of immediately calling onReady(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1b9cdc44c2
commit
c299e05e7b
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Loader2, CheckCircle2, XCircle, Cpu, Server, Database } from 'lucide-react';
|
||||
import { getSetupStatus, type SetupStatus } from '../lib/api';
|
||||
import { SetupWizard } from './setup/SetupWizard';
|
||||
|
||||
const STEPS = [
|
||||
{ key: 'ollama_ready', label: 'Inference Engine', icon: Cpu, detail: 'Starting Ollama...' },
|
||||
@@ -70,14 +71,15 @@ function StepRow({
|
||||
|
||||
export function SetupScreen({ onReady }: { onReady: () => void }) {
|
||||
const [status, setStatus] = useState<SetupStatus | null>(null);
|
||||
const [showWizard, setShowWizard] = useState(false);
|
||||
|
||||
const poll = useCallback(async () => {
|
||||
const s = await getSetupStatus();
|
||||
if (s) setStatus(s);
|
||||
if (s?.phase === 'ready') {
|
||||
setTimeout(onReady, 600);
|
||||
setTimeout(() => setShowWizard(true), 600);
|
||||
}
|
||||
}, [onReady]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
poll();
|
||||
@@ -85,6 +87,10 @@ export function SetupScreen({ onReady }: { onReady: () => void }) {
|
||||
return () => clearInterval(interval);
|
||||
}, [poll]);
|
||||
|
||||
if (showWizard) {
|
||||
return <SetupWizard onComplete={onReady} />;
|
||||
}
|
||||
|
||||
const activeStep: StepKey | null =
|
||||
status && !status.ollama_ready
|
||||
? 'ollama_ready'
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { useState } from 'react';
|
||||
import { SourcePicker } from './SourcePicker';
|
||||
import { SourceConnectFlow } from './SourceConnectFlow';
|
||||
import { IngestDashboard } from './IngestDashboard';
|
||||
import { ReadyScreen } from './ReadyScreen';
|
||||
import type { WizardStep } from '../../types/connectors';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SetupWizard
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function SetupWizard({ onComplete }: { onComplete: (firstQuery?: string) => void }) {
|
||||
const [step, setStep] = useState<WizardStep>('pick');
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const [connectedIds, setConnectedIds] = useState<string[]>([]);
|
||||
|
||||
const handlePick = (ids: string[]) => {
|
||||
setSelectedIds(ids);
|
||||
setStep('connect');
|
||||
};
|
||||
|
||||
const handleConnectComplete = () => {
|
||||
// connectedIds: those actually in selectedIds that were not skipped.
|
||||
// We don't track skip state here — IngestDashboard receives all selected
|
||||
// (skipped ones will just fail gracefully on sync status fetch).
|
||||
setConnectedIds(selectedIds);
|
||||
setStep('ingest');
|
||||
};
|
||||
|
||||
const handleIngestReady = () => {
|
||||
setStep('ready');
|
||||
};
|
||||
|
||||
const handleStart = (query?: string) => {
|
||||
onComplete(query);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 flex items-center justify-center"
|
||||
style={{ background: 'var(--color-bg)' }}
|
||||
>
|
||||
<div
|
||||
className="w-full max-w-2xl mx-6 p-8 rounded-2xl"
|
||||
style={{
|
||||
background: 'var(--color-bg-secondary)',
|
||||
border: '1px solid var(--color-border)',
|
||||
maxHeight: '90vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{/* Step indicator */}
|
||||
<div className="flex items-center gap-2 mb-8">
|
||||
{(['pick', 'connect', 'ingest', 'ready'] as WizardStep[]).map((s, i) => (
|
||||
<div key={s} className="flex items-center gap-2">
|
||||
<div
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-bold transition-all"
|
||||
style={{
|
||||
background:
|
||||
s === step
|
||||
? 'var(--color-accent)'
|
||||
: ['pick', 'connect', 'ingest', 'ready'].indexOf(s) <
|
||||
['pick', 'connect', 'ingest', 'ready'].indexOf(step)
|
||||
? 'var(--color-accent-subtle)'
|
||||
: 'var(--color-bg-tertiary)',
|
||||
color:
|
||||
s === step
|
||||
? 'white'
|
||||
: ['pick', 'connect', 'ingest', 'ready'].indexOf(s) <
|
||||
['pick', 'connect', 'ingest', 'ready'].indexOf(step)
|
||||
? 'var(--color-accent)'
|
||||
: 'var(--color-text-tertiary)',
|
||||
}}
|
||||
>
|
||||
{i + 1}
|
||||
</div>
|
||||
{i < 3 && (
|
||||
<div
|
||||
className="w-8 h-0.5 rounded-full"
|
||||
style={{
|
||||
background:
|
||||
['pick', 'connect', 'ingest', 'ready'].indexOf(s) <
|
||||
['pick', 'connect', 'ingest', 'ready'].indexOf(step)
|
||||
? 'var(--color-accent)'
|
||||
: 'var(--color-border)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Step content */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{step === 'pick' && <SourcePicker onContinue={handlePick} />}
|
||||
{step === 'connect' && (
|
||||
<SourceConnectFlow
|
||||
selectedIds={selectedIds}
|
||||
onComplete={handleConnectComplete}
|
||||
/>
|
||||
)}
|
||||
{step === 'ingest' && (
|
||||
<IngestDashboard
|
||||
connectedIds={connectedIds}
|
||||
onReady={handleIngestReady}
|
||||
/>
|
||||
)}
|
||||
{step === 'ready' && (
|
||||
<ReadyScreen
|
||||
connectedSources={connectedIds}
|
||||
onStart={handleStart}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user