diff --git a/.changeset/onboarding-first-agent-flow.md b/.changeset/onboarding-first-agent-flow.md new file mode 100644 index 0000000000..74334f2f94 --- /dev/null +++ b/.changeset/onboarding-first-agent-flow.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": patch +--- + +Update first-run onboarding to include an optional first-agent step and clearer temporary-agent task guidance. diff --git a/docs/screenshots/onboarding-improve/01-provider-ai-setup.png b/docs/screenshots/onboarding-improve/01-provider-ai-setup.png new file mode 100644 index 0000000000..1e601f558c Binary files /dev/null and b/docs/screenshots/onboarding-improve/01-provider-ai-setup.png differ diff --git a/docs/screenshots/onboarding-improve/01-real-first-run-dashboard.png b/docs/screenshots/onboarding-improve/01-real-first-run-dashboard.png new file mode 100644 index 0000000000..8094f0909f Binary files /dev/null and b/docs/screenshots/onboarding-improve/01-real-first-run-dashboard.png differ diff --git a/docs/screenshots/onboarding-improve/02-provider-github.png b/docs/screenshots/onboarding-improve/02-provider-github.png new file mode 100644 index 0000000000..c6eecc2e1b Binary files /dev/null and b/docs/screenshots/onboarding-improve/02-provider-github.png differ diff --git a/docs/screenshots/onboarding-improve/02-real-auth-step.png b/docs/screenshots/onboarding-improve/02-real-auth-step.png new file mode 100644 index 0000000000..8094f0909f Binary files /dev/null and b/docs/screenshots/onboarding-improve/02-real-auth-step.png differ diff --git a/docs/screenshots/onboarding-improve/03-provider-project-setup.png b/docs/screenshots/onboarding-improve/03-provider-project-setup.png new file mode 100644 index 0000000000..243657d67a Binary files /dev/null and b/docs/screenshots/onboarding-improve/03-provider-project-setup.png differ diff --git a/docs/screenshots/onboarding-improve/03-real-project-registration-empty.png b/docs/screenshots/onboarding-improve/03-real-project-registration-empty.png new file mode 100644 index 0000000000..ae3b8c88a4 Binary files /dev/null and b/docs/screenshots/onboarding-improve/03-real-project-registration-empty.png differ diff --git a/docs/screenshots/onboarding-improve/04-provider-agent-step.png b/docs/screenshots/onboarding-improve/04-provider-agent-step.png new file mode 100644 index 0000000000..3ee02dd6d1 Binary files /dev/null and b/docs/screenshots/onboarding-improve/04-provider-agent-step.png differ diff --git a/docs/screenshots/onboarding-improve/04-real-project-registration-filled.png b/docs/screenshots/onboarding-improve/04-real-project-registration-filled.png new file mode 100644 index 0000000000..db1395e7b3 Binary files /dev/null and b/docs/screenshots/onboarding-improve/04-real-project-registration-filled.png differ diff --git a/docs/screenshots/onboarding-improve/05-provider-first-task.png b/docs/screenshots/onboarding-improve/05-provider-first-task.png new file mode 100644 index 0000000000..2b2bcd0518 Binary files /dev/null and b/docs/screenshots/onboarding-improve/05-provider-first-task.png differ diff --git a/docs/screenshots/onboarding-improve/05-real-first-agent-ceo-default.png b/docs/screenshots/onboarding-improve/05-real-first-agent-ceo-default.png new file mode 100644 index 0000000000..1c866ba6ba Binary files /dev/null and b/docs/screenshots/onboarding-improve/05-real-first-agent-ceo-default.png differ diff --git a/docs/screenshots/onboarding-improve/06-real-first-agent-engineer-template.png b/docs/screenshots/onboarding-improve/06-real-first-agent-engineer-template.png new file mode 100644 index 0000000000..e8114935df Binary files /dev/null and b/docs/screenshots/onboarding-improve/06-real-first-agent-engineer-template.png differ diff --git a/docs/screenshots/onboarding-improve/07-real-skip-complete-dashboard.png b/docs/screenshots/onboarding-improve/07-real-skip-complete-dashboard.png new file mode 100644 index 0000000000..5237f5a4dd Binary files /dev/null and b/docs/screenshots/onboarding-improve/07-real-skip-complete-dashboard.png differ diff --git a/docs/screenshots/onboarding-improve/08-real-create-auth-step.png b/docs/screenshots/onboarding-improve/08-real-create-auth-step.png new file mode 100644 index 0000000000..8094f0909f Binary files /dev/null and b/docs/screenshots/onboarding-improve/08-real-create-auth-step.png differ diff --git a/docs/screenshots/onboarding-improve/09-real-create-project-empty.png b/docs/screenshots/onboarding-improve/09-real-create-project-empty.png new file mode 100644 index 0000000000..ae3b8c88a4 Binary files /dev/null and b/docs/screenshots/onboarding-improve/09-real-create-project-empty.png differ diff --git a/docs/screenshots/onboarding-improve/10-real-create-project-filled.png b/docs/screenshots/onboarding-improve/10-real-create-project-filled.png new file mode 100644 index 0000000000..d4772ba0c8 Binary files /dev/null and b/docs/screenshots/onboarding-improve/10-real-create-project-filled.png differ diff --git a/docs/screenshots/onboarding-improve/11-real-create-agent-ceo-default.png b/docs/screenshots/onboarding-improve/11-real-create-agent-ceo-default.png new file mode 100644 index 0000000000..c143ffcd1a Binary files /dev/null and b/docs/screenshots/onboarding-improve/11-real-create-agent-ceo-default.png differ diff --git a/docs/screenshots/onboarding-improve/12-real-create-agent-complete-dashboard.png b/docs/screenshots/onboarding-improve/12-real-create-agent-complete-dashboard.png new file mode 100644 index 0000000000..81af09f5eb Binary files /dev/null and b/docs/screenshots/onboarding-improve/12-real-create-agent-complete-dashboard.png differ diff --git a/packages/dashboard/app/components/AppModals.tsx b/packages/dashboard/app/components/AppModals.tsx index 8de8a83e6b..442edf3372 100644 --- a/packages/dashboard/app/components/AppModals.tsx +++ b/packages/dashboard/app/components/AppModals.tsx @@ -489,6 +489,7 @@ export function AppModals({ onOpenGitHubImport={handleOpenGitHubImport} firstCreatedTask={firstCreatedTask} onViewTask={handleOnboardingViewTask} + agentOnboardingEnabled={agentOnboardingEnabled} /> )} diff --git a/packages/dashboard/app/components/ModelOnboardingModal.css b/packages/dashboard/app/components/ModelOnboardingModal.css index 866796b06d..aa8104484a 100644 --- a/packages/dashboard/app/components/ModelOnboardingModal.css +++ b/packages/dashboard/app/components/ModelOnboardingModal.css @@ -1,6 +1,10 @@ /* ===== Model Onboarding Modal ===== */ .model-onboarding-modal { + /* + FNXC:Onboarding 2026-06-22-04:16: + First-time provider/GitHub onboarding should visually match new-project onboarding, including spacing, button treatment, contrast, and persistent Discord help access. + */ /* Resizable via the native CSS grip. The user's chosen size is persisted by useModalResizePersist. Min/max keep the dimensions sane so a value saved on a 4K display still renders on a laptop. */ @@ -15,7 +19,7 @@ } .model-onboarding-modal:not([style*="width"]) { - width: min(1100px, calc(100vw - 40px)); + width: min(960px, calc(100vw - 40px)); } .model-onboarding-modal:not([style*="height"]) { height: min(85vh, calc(100dvh - 40px)); @@ -25,8 +29,8 @@ display: flex; align-items: center; justify-content: space-between; - padding: 20px 24px 12px; - border-bottom: 1px solid var(--border); + padding: var(--space-xl) var(--space-xl) 0; + gap: var(--space-md); } .model-onboarding-header .modal-close { @@ -46,12 +50,14 @@ } .model-onboarding-title { - font-size: 18px; - font-weight: 600; + font-size: 24px; + font-weight: 800; margin: 0; display: flex; align-items: center; gap: 8px; + color: var(--text); + line-height: 1.1; } .model-onboarding-title svg { @@ -77,7 +83,7 @@ align-items: center; justify-content: center; gap: 0; - padding: 16px 24px; + padding: var(--space-md) var(--space-xl); border-bottom: 1px solid var(--border); } @@ -157,10 +163,10 @@ } .model-onboarding-step-connector { - width: 40px; + width: 30px; height: 2px; background: var(--border); - margin: 0 12px; + margin: 0 var(--space-sm); transition: background-color var(--transition-fast); } @@ -170,19 +176,19 @@ /* Content area */ .model-onboarding-content { - padding: 20px 24px; + padding: var(--space-md) var(--space-xl); overflow-y: auto; flex: 1; } .model-onboarding-description { - color: var(--text-muted); + color: var(--text); margin-bottom: 16px; line-height: 1.5; } .onboarding-helper-text { - color: var(--text-dim); + color: var(--text-muted); font-size: 12px; margin: 4px 0 0 0; line-height: 1.4; @@ -873,6 +879,19 @@ gap: calc(var(--space-xl) - var(--space-xs)); } +.model-onboarding-agent-step .setup-wizard-agent-intro { + color: var(--text); +} + +.model-onboarding-agent-step .setup-wizard-agent-preset-description, +.model-onboarding-agent-step .setup-wizard-agent-preview-title-row p { + color: var(--text-muted); +} + +.model-onboarding-agent-step .setup-wizard-agent-preview-list dd { + color: var(--text); +} + /* === Onboarding First Task Inline Form === */ .onboarding-first-task-form { display: flex; @@ -1134,22 +1153,25 @@ .model-onboarding-footer { display: flex; align-items: center; - justify-content: space-between; - padding: 16px 24px; + justify-content: flex-end; + gap: var(--space-sm); + padding: var(--space-md) var(--space-xl) var(--space-lg); border-top: 1px solid var(--border); + background: var(--surface); } .model-onboarding-footer .btn-primary { display: flex; align-items: center; - gap: 6px; + gap: var(--space-sm); + padding: 10px 24px; + font-size: 14px; + font-weight: 600; } -/* When the footer holds only the "Get Started" button (final "complete" - step), push it to the right edge instead of hugging the left under - `justify-content: space-between`. */ -.model-onboarding-footer > .btn-primary:only-child { - margin-inline-start: auto; +.model-onboarding-help-link { + margin-right: auto; + text-decoration: none; } .onboarding-skip-step-link { @@ -1384,6 +1406,22 @@ min-width: 0; } + .model-onboarding-help-link { + margin-right: 0; + width: 100%; + justify-content: center; + } + + .model-onboarding-agent-step .setup-wizard-agent-layout { + grid-template-columns: 1fr; + } + + .model-onboarding-agent-step .setup-wizard-agent-preset-list { + max-height: none; + overflow-y: visible; + padding-right: 0; + } + .onboarding-step-wrapper { flex-direction: row; } diff --git a/packages/dashboard/app/components/ModelOnboardingModal.tsx b/packages/dashboard/app/components/ModelOnboardingModal.tsx index 4f3fd4943d..766a245bd4 100644 --- a/packages/dashboard/app/components/ModelOnboardingModal.tsx +++ b/packages/dashboard/app/components/ModelOnboardingModal.tsx @@ -1,6 +1,7 @@ import "./ModelOnboardingModal.css"; -import { useState, useEffect, useCallback, useRef } from "react"; -import { X, Loader2, CheckCircle, Key, Zap, GitPullRequest, Rocket, Plus } from "lucide-react"; +import "./SetupWizardModal.css"; +import { lazy, Suspense, useState, useEffect, useCallback, useRef, useMemo, type KeyboardEvent } from "react"; +import { X, Loader2, CheckCircle, Key, Zap, GitPullRequest, Rocket, Plus, Sparkles, UserRound } from "lucide-react"; import { getErrorMessage, type Task } from "@fusion/core"; import type { AuthProvider, ManualOAuthCodeInfo, ModelInfo, CustomProvider, CustomProviderConfig, OAuthDeviceCodeInfo } from "../api"; import { @@ -15,8 +16,10 @@ import { fetchModels, updateGlobalSettings, createTask, + createAgent, fetchCustomProviders, createCustomProvider, + type AgentOnboardingSummary, } from "../api"; import type { ToastType } from "../hooks/useToast"; import { useModalResizePersist } from "../hooks/useModalResizePersist"; @@ -36,6 +39,19 @@ import { filterVisibleOnboardingAndSettingsProviders } from "./providerVisibilit import { useShellConnection } from "../hooks/useShellConnection"; import { useConfirm } from "../hooks/useConfirm"; import { useTranslation } from "react-i18next"; +import { AgentAvatar } from "./AgentAvatar"; +import { ErrorBoundary } from "./ErrorBoundary"; +import { AGENT_PRESETS, getPresetById } from "./agent-presets"; +import { + buildAgentCreatePayload, + mapOnboardingSummaryToAgentDraft, + mapPresetToAgentDraft, + type AgentDraftValues, +} from "./agent-presets/agentCreatePayload"; + +const ExperimentalAgentOnboardingModal = lazy(() => + import("./ExperimentalAgentOnboardingModal").then((m) => ({ default: m.ExperimentalAgentOnboardingModal })), +); const mapLegacyCustomProviderToConfig = ( provider: CustomProvider | CustomProviderConfig, @@ -527,6 +543,8 @@ export interface ModelOnboardingModalProps { firstCreatedTask?: Task | null; /** Optional callback when user wants to open the created task detail */ onViewTask?: (task: Task) => void; + /** Enables the AI interview entry point while template creation and skip remain available. */ + agentOnboardingEnabled?: boolean; } /** Outcome states for OAuth login attempts */ @@ -564,9 +582,19 @@ export function ModelOnboardingModal({ onOpenGitHubImport, firstCreatedTask, onViewTask, + agentOnboardingEnabled = false, }: ModelOnboardingModalProps) { const { t } = useTranslation("app"); const { confirm } = useConfirm(); + /* + FNXC:Onboarding 2026-06-22-04:16: + First-time provider/GitHub setup must pass through the same optional first-agent flow before first-task creation. + Users can create or skip a persistent coordinating agent because Fusion automatically spawns temporary agents to plan, execute, review, and merge tasks. + */ + const ceoPreset = useMemo( + () => getPresetById("ceo") ?? AGENT_PRESETS[0]!, + [], + ); // Initialize from persisted state if available (allows resume from last step) const persistedState = getOnboardingState(); const persistedStep = persistedState?.currentStep; @@ -589,6 +617,11 @@ export function ModelOnboardingModal({ const [isCreatingFirstTask, setIsCreatingFirstTask] = useState(false); const [taskCreationError, setTaskCreationError] = useState(null); const [inlineCreatedTask, setInlineCreatedTask] = useState(null); + const [selectedAgentPresetId, setSelectedAgentPresetId] = useState(ceoPreset.id); + const [agentDraft, setAgentDraft] = useState(() => mapPresetToAgentDraft(ceoPreset)); + const [isCreatingAgent, setIsCreatingAgent] = useState(false); + const [agentCreationError, setAgentCreationError] = useState(null); + const [isAgentInterviewOpen, setIsAgentInterviewOpen] = useState(false); const [authProviders, setAuthProviders] = useState([]); const [ghCliStatus, setGhCliStatus] = useState(undefined); const [authLoading, setAuthLoading] = useState(true); @@ -617,6 +650,7 @@ export function ModelOnboardingModal({ const onboardingContentRef = useRef(null); const modalRef = useRef(null); const pollIntervalRef = useRef | null>(null); + const agentErrorRef = useRef(null); const [loginOutcomes, setLoginOutcomes] = useState>({}); const lastAutoCopiedDeviceCodesRef = useRef>({}); const [isGithubSkipped, setIsGithubSkipped] = useState(() => { @@ -670,6 +704,7 @@ export function ModelOnboardingModal({ { key: "ai-setup" as const, label: t("setup.stepAiSetup", "AI Setup") }, { key: "github" as const, label: t("setup.stepGithub", "GitHub") }, { key: "project-setup" as const, label: t("setup.stepProject", "Project") }, + { key: "agent" as const, label: t("setup.stepAgent", "Agent") }, { key: "first-task" as const, label: t("setup.stepFirstTask", "First Task") }, ]; @@ -712,6 +747,12 @@ export function ModelOnboardingModal({ previousCreatedTaskRef.current = firstCreatedTask; }, [firstCreatedTask]); + useEffect(() => { + if (agentCreationError) { + agentErrorRef.current?.focus(); + } + }, [agentCreationError]); + // Auto-mark unconnected providers as skipped when leaving ai-setup step // Only skip if NO providers are connected (if at least one is connected, others remain "Not connected") const prevStepRef = useRef(initialStep); @@ -1079,6 +1120,61 @@ export function ModelOnboardingModal({ handleSkip(); }, [handleSkip]); + const handleAgentPresetSelect = useCallback((presetId: string) => { + const preset = getPresetById(presetId); + if (!preset) return; + setSelectedAgentPresetId(preset.id); + setAgentDraft(mapPresetToAgentDraft(preset)); + setAgentCreationError(null); + }, []); + + const handleAgentPresetKeyDown = useCallback((event: KeyboardEvent, presetId: string) => { + const currentIndex = AGENT_PRESETS.findIndex((preset) => preset.id === presetId); + if (currentIndex < 0) return; + + const lastIndex = AGENT_PRESETS.length - 1; + let nextIndex: number | null = null; + if (event.key === "ArrowDown" || event.key === "ArrowRight") { + nextIndex = currentIndex === lastIndex ? 0 : currentIndex + 1; + } else if (event.key === "ArrowUp" || event.key === "ArrowLeft") { + nextIndex = currentIndex === 0 ? lastIndex : currentIndex - 1; + } else if (event.key === "Home") { + nextIndex = 0; + } else if (event.key === "End") { + nextIndex = lastIndex; + } + + if (nextIndex === null) return; + event.preventDefault(); + const nextPreset = AGENT_PRESETS[nextIndex]; + handleAgentPresetSelect(nextPreset.id); + requestAnimationFrame(() => { + document.querySelector(`[data-model-onboarding-agent-preset-id="${nextPreset.id}"]`)?.focus(); + }); + }, [handleAgentPresetSelect]); + + const handleApplyAgentDraft = useCallback((draft: AgentOnboardingSummary) => { + setSelectedAgentPresetId(""); + setAgentDraft(mapOnboardingSummaryToAgentDraft(draft)); + setAgentCreationError(null); + }, []); + + const handleCreateFirstAgent = useCallback(async () => { + const targetProjectId = projectId?.trim(); + if (!targetProjectId || !agentDraft.name.trim()) return; + + setIsCreatingAgent(true); + setAgentCreationError(null); + try { + await createAgent(buildAgentCreatePayload(agentDraft), targetProjectId); + handleNext(); + } catch (err) { + setAgentCreationError(err instanceof Error ? err.message : t("setup.firstAgentCreateError", "Failed to create agent")); + } finally { + setIsCreatingAgent(false); + } + }, [agentDraft, handleNext, projectId, t]); + // OAuth login handler const handleLogin = useCallback( async (providerId: string) => { @@ -1709,6 +1805,8 @@ export function ModelOnboardingModal({ const connectedAiProviders = aiProviders.filter((provider) => provider.authenticated); const hasAiProvider = connectedAiProviders.length > 0; const hasProjectSelected = Boolean(projectId); + const selectedAgentPreset = selectedAgentPresetId ? getPresetById(selectedAgentPresetId) : undefined; + const isAgentActionDisabled = isCreatingAgent || !hasProjectSelected; // True when on GitHub step but skipped AI setup (no AI provider connected) const aiSetupSkipped = step === "github" && !hasAiProvider; @@ -2101,6 +2199,11 @@ export function ModelOnboardingModal({ {t("setup.titleSetUpProject", "Set Up Your Project")} )} + {step === "agent" && ( + <> + {t("setup.titleCreateFirstAgent", "Create Your First Agent")} {t("setup.optionalBadge", "Optional")} + + )} {step === "first-task" && ( <> {t("setup.titleCreateFirstTask", "Create Your First Task")} @@ -2124,7 +2227,7 @@ export function ModelOnboardingModal({ )} - {/* Step indicator - 4 progress steps + complete */} + {/* Step indicator - progress steps + complete */}
{steps.map((s, index) => { // A step is done/skipped only once we have progressed beyond it. @@ -2690,6 +2793,109 @@ export function ModelOnboardingModal({
)} + {step === "agent" && ( +
+

+ {t("setup.firstAgentIntro", "You do not need to create or assign an agent for Fusion to build tasks. Fusion automatically spawns temporary agents to plan, execute, review, and merge task work. A persistent agent is optional and can coordinate work, help create tasks, and keep direction across sessions.")} +

+ + {!hasProjectSelected && ( +
+

+ {t("setup.projectMustBeSelectedForAgent", "Set up a project before creating your first agent. You can also skip this and create tasks without a persistent agent.")} +

+ +
+ )} + +
+
+
+ {t("setup.firstAgentTemplates", "Templates")} +
+
+ {AGENT_PRESETS.map((preset) => { + const selected = selectedAgentPresetId === preset.id; + return ( + + ); + })} +
+
+ +
+
+ {t("setup.firstAgentPreview", "Preview")} +
+
+
+ +
+

{agentDraft.name || t("setup.firstAgentDraftName", "Draft agent")}

+

{agentDraft.title || selectedAgentPreset?.title || t("setup.firstAgentCustomDraft", "Custom agent draft")}

+
+
+
+
+
{t("agents.fieldRole", "Role")}
+
{agentDraft.role}
+
+
+
{t("agents.fieldInstructionsText", "Inline Instructions")}
+
{agentDraft.instructionsText || t("setup.firstAgentNoInstructions", "No inline instructions yet")}
+
+
+ {agentOnboardingEnabled && ( + + )} +
+
+
+ + {agentCreationError && ( +
+ {agentCreationError} +
+ )} +
+ )} + {step === "first-task" && (

@@ -2855,6 +3061,14 @@ export function ModelOnboardingModal({ {/* Footer */}

+ + {t("setup.needHelp", "Need help?")} + {step === "ai-setup" && ( <> + + + + )} + {step === "first-task" && !showTaskCreated && ( <>
+ {agentOnboardingEnabled && isAgentInterviewOpen && ( + + {t("setup.firstAgentInterviewLoadError", "AI interview could not load. You can still create an agent from a template or skip this step.")} + + + )} + > + + setIsAgentInterviewOpen(false)} + onUseDraft={handleApplyAgentDraft} + projectId={projectId} + existingAgents={[]} + mode="create" + /> + + + )} ); } diff --git a/packages/dashboard/app/components/SetupWizardModal.tsx b/packages/dashboard/app/components/SetupWizardModal.tsx index 18d56189dc..102562ab8f 100644 --- a/packages/dashboard/app/components/SetupWizardModal.tsx +++ b/packages/dashboard/app/components/SetupWizardModal.tsx @@ -65,7 +65,7 @@ export function SetupWizardModal({ agentOnboardingEnabled = false, }: SetupWizardModalProps) { const { t } = useTranslation("app"); - const helpUrl = "https://github.com/runfusion/fusion/discussions"; + const helpUrl = "https://discord.gg/ksrfuy7WYR"; /* FNXC:Onboarding 2026-06-22-03:11: First-run project setup must offer a first persistent agent after registration, defaulting to the CEO preset while still letting users choose another template or skip creation. @@ -678,7 +678,7 @@ export function SetupWizardModal({ className="btn setup-wizard-help-link" href={helpUrl} target="_blank" - rel="noreferrer" + rel="noopener noreferrer" > {t("setup.needHelp", "Need help?")} diff --git a/packages/dashboard/app/components/__tests__/App.test.tsx b/packages/dashboard/app/components/__tests__/App.test.tsx index 8eec896811..502640a1f0 100644 --- a/packages/dashboard/app/components/__tests__/App.test.tsx +++ b/packages/dashboard/app/components/__tests__/App.test.tsx @@ -258,7 +258,7 @@ vi.mock("../../components/model-onboarding-state", () => ({ getOnboardingCompletedAt: (...args: unknown[]) => mockGetOnboardingCompletedAt(...args), getSkippedSteps: (...args: unknown[]) => mockGetSkippedSteps(...args), getStepData: (...args: unknown[]) => mockGetStepData(...args), - ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "first-task"], + ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "agent", "first-task"], })); // Mock CustomModelDropdown for onboarding modal tests diff --git a/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx b/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx index da15ef14d8..b5f4ace23b 100644 --- a/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx @@ -17,6 +17,7 @@ const mockFetchModels = vi.fn(); const mockFetchGlobalSettings = vi.fn(); const mockUpdateGlobalSettings = vi.fn(); const mockCreateTask = vi.fn(); +const mockCreateAgent = vi.fn(); const mockFetchCustomProviders = vi.fn(); const mockCreateCustomProvider = vi.fn(); const mockFetchCursorCliStatus = vi.fn(); @@ -36,6 +37,7 @@ vi.mock("../../api", () => ({ fetchGlobalSettings: (...args: unknown[]) => mockFetchGlobalSettings(...args), updateGlobalSettings: (...args: unknown[]) => mockUpdateGlobalSettings(...args), createTask: (...args: unknown[]) => mockCreateTask(...args), + createAgent: (...args: unknown[]) => mockCreateAgent(...args), fetchCustomProviders: (...args: unknown[]) => mockFetchCustomProviders(...args), createCustomProvider: (...args: unknown[]) => mockCreateCustomProvider(...args), fetchCursorCliStatus: (...args: unknown[]) => mockFetchCursorCliStatus(...args), @@ -98,7 +100,7 @@ vi.mock("../model-onboarding-state", () => ({ markStepSkipped: (...args: unknown[]) => mockMarkStepSkipped(...args), getSkippedSteps: (...args: unknown[]) => mockGetSkippedSteps(...args), getStepData: (...args: unknown[]) => mockGetStepData(...args), - ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "first-task"], + ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "agent", "first-task"], })); const mockTrackOnboardingEvent = vi.fn(); @@ -138,6 +140,8 @@ vi.mock("lucide-react", async (importOriginal) => { GitPullRequest: () => GitPullRequest, Rocket: () => Rocket, Plus: () => Plus, + Sparkles: () => Sparkles, + UserRound: () => UserRound, ChevronRight: () => ChevronRight, }; }); @@ -180,6 +184,10 @@ async function navigateToProjectSetupStep() { async function navigateToFirstTaskStep() { await navigateToProjectSetupStep(); fireEvent.click(screen.getByText("Next →")); + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeTruthy(); + }); + fireEvent.click(screen.getByText("Skip for now")); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeTruthy(); }); @@ -194,6 +202,7 @@ beforeEach(() => { mockFetchGlobalSettings.mockResolvedValue({}); mockUpdateGlobalSettings.mockResolvedValue({}); mockCreateTask.mockResolvedValue({ id: "FN-TEST", description: "test task" }); + mockCreateAgent.mockResolvedValue({ id: "agent-1" }); mockFetchCustomProviders.mockResolvedValue({ providers: [] }); mockCreateCustomProvider.mockResolvedValue({ provider: {} }); mockLoginProvider.mockResolvedValue({ url: "https://auth.example.com/login" }); @@ -1869,6 +1878,12 @@ describe("ModelOnboardingModal", () => { fireEvent.click(screen.getByText("Next →")); + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeTruthy(); + }); + + fireEvent.click(screen.getByText("Skip for now")); + await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeTruthy(); }); @@ -2450,6 +2465,12 @@ describe("ModelOnboardingModal", () => { fireEvent.click(screen.getByText("← Back")); + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeTruthy(); + }); + + fireEvent.click(screen.getByText("← Back")); + await waitFor(() => { expect(screen.getByText("Set Up Your Project")).toBeTruthy(); }); @@ -2459,6 +2480,7 @@ describe("ModelOnboardingModal", () => { await waitFor(() => { expect(screen.getByText("Connect GitHub")).toBeTruthy(); }); + }); }); @@ -2582,7 +2604,7 @@ describe("ModelOnboardingModal", () => { expect(aiSetupIndicator).toHaveClass("done"); expect(githubIndicator).toHaveClass("done"); expect(firstTaskIndicator).toHaveClass("done"); - expect(document.querySelectorAll(".model-onboarding-step-connector.done")).toHaveLength(3); + expect(document.querySelectorAll(".model-onboarding-step-connector.done")).toHaveLength(4); // Click Get Started to close fireEvent.click(screen.getByText("Get Started")); @@ -2877,6 +2899,12 @@ describe("ModelOnboardingModal", () => { // Navigate back to see if the model dropdown has the saved value fireEvent.click(screen.getByText("← Back")); + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeTruthy(); + }); + + fireEvent.click(screen.getByText("← Back")); + await waitFor(() => { expect(screen.getByText("Set Up Your Project")).toBeTruthy(); }); @@ -3209,6 +3237,13 @@ describe("ModelOnboardingModal", () => { fireEvent.click(screen.getByText("Next →")); + // Then advance to optional Agent step before First Task + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeTruthy(); + }); + + fireEvent.click(screen.getByText("Skip for now")); + // Then advance to First Task step await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeTruthy(); @@ -3273,6 +3308,11 @@ describe("ModelOnboardingModal", () => { }); fireEvent.click(screen.getByText("Next →")); + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeTruthy(); + }); + + fireEvent.click(screen.getByText("Skip for now")); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeTruthy(); }); @@ -3320,6 +3360,12 @@ describe("ModelOnboardingModal", () => { fireEvent.click(screen.getByText("Next →")); + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeTruthy(); + }); + + fireEvent.click(screen.getByText("Skip for now")); + // Then advance to First Task step await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeTruthy(); @@ -4572,4 +4618,3 @@ describe("Custom providers disclosure", () => { }); }); }); - diff --git a/packages/dashboard/app/components/__tests__/OnboardingResumeCard.test.tsx b/packages/dashboard/app/components/__tests__/OnboardingResumeCard.test.tsx index ede5ee8e4c..a4273655fa 100644 --- a/packages/dashboard/app/components/__tests__/OnboardingResumeCard.test.tsx +++ b/packages/dashboard/app/components/__tests__/OnboardingResumeCard.test.tsx @@ -5,7 +5,7 @@ import { OnboardingResumeCard } from "../OnboardingResumeCard"; // Mock the model-onboarding-state module vi.mock("../model-onboarding-state", () => ({ getOnboardingResumeStep: vi.fn(), - ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "first-task"], + ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "agent", "first-task"], })); const mockTrackOnboardingEvent = vi.fn(); @@ -184,7 +184,7 @@ describe("OnboardingResumeCard", () => { }); render(); // Uses singular "step" for 1 completed - expect(screen.getByText(/1 of 4 step complete/)).toBeInTheDocument(); + expect(screen.getByText(/1 of 5 step complete/)).toBeInTheDocument(); }); it("shows completed step count text with 2 completed steps (plural)", () => { @@ -195,7 +195,7 @@ describe("OnboardingResumeCard", () => { }); render(); // Uses plural "steps" for 2 completed - expect(screen.getByText(/2 of 4 steps complete/)).toBeInTheDocument(); + expect(screen.getByText(/2 of 5 steps complete/)).toBeInTheDocument(); }); }); diff --git a/packages/dashboard/app/components/__tests__/PostOnboardingRecommendations.test.tsx b/packages/dashboard/app/components/__tests__/PostOnboardingRecommendations.test.tsx index 2bbc0b776b..f1fe5ad073 100644 --- a/packages/dashboard/app/components/__tests__/PostOnboardingRecommendations.test.tsx +++ b/packages/dashboard/app/components/__tests__/PostOnboardingRecommendations.test.tsx @@ -18,7 +18,7 @@ vi.mock("../model-onboarding-state", () => ({ isOnboardingCompleted: (...args: unknown[]) => mockIsOnboardingCompleted(...args), isPostOnboardingDismissed: (...args: unknown[]) => mockIsPostOnboardingDismissed(...args), dismissPostOnboardingRecommendations: (...args: unknown[]) => mockDismissPostOnboardingRecommendations(...args), - ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "first-task"], + ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "agent", "first-task"], })); vi.mock("../PluginSlot", () => ({ diff --git a/packages/dashboard/app/components/__tests__/SetupWizardModal.test.tsx b/packages/dashboard/app/components/__tests__/SetupWizardModal.test.tsx index fadae7d435..7f221698db 100644 --- a/packages/dashboard/app/components/__tests__/SetupWizardModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/SetupWizardModal.test.tsx @@ -162,7 +162,7 @@ describe("SetupWizardModal", () => { expect(screen.getByText("Set Token & Continue")).toBeDefined(); expect(screen.getByRole("link", { name: "Need help?" })).toHaveAttribute( "href", - "https://github.com/runfusion/fusion/discussions" + "https://discord.gg/ksrfuy7WYR" ); }); diff --git a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx index 93468d469c..d9c07dd250 100644 --- a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx +++ b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx @@ -142,7 +142,7 @@ vi.mock("../../components/model-onboarding-state", () => ({ getOnboardingCompletedAt: () => null, getSkippedSteps: () => [], getStepData: () => null, - ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "first-task"], + ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "agent", "first-task"], })); vi.mock("../../components/Board", () => ({ diff --git a/packages/dashboard/app/components/__tests__/onboarding-flow.test.tsx b/packages/dashboard/app/components/__tests__/onboarding-flow.test.tsx index 4e4fbc3323..f72a5c13c5 100644 --- a/packages/dashboard/app/components/__tests__/onboarding-flow.test.tsx +++ b/packages/dashboard/app/components/__tests__/onboarding-flow.test.tsx @@ -17,6 +17,7 @@ const mockSaveApiKey = vi.fn(); const mockClearApiKey = vi.fn(); const mockUpdateGlobalSettings = vi.fn(); const mockCreateTask = vi.fn(); +const mockCreateAgent = vi.fn(); vi.mock("../../api", () => ({ fetchAuthStatus: (...args: unknown[]) => mockFetchAuthStatus(...args), @@ -28,6 +29,7 @@ vi.mock("../../api", () => ({ clearApiKey: (...args: unknown[]) => mockClearApiKey(...args), updateGlobalSettings: (...args: unknown[]) => mockUpdateGlobalSettings(...args), createTask: (...args: unknown[]) => mockCreateTask(...args), + createAgent: (...args: unknown[]) => mockCreateAgent(...args), })); const mockGetOnboardingState = vi.fn(); @@ -56,7 +58,7 @@ vi.mock("../model-onboarding-state", () => ({ isOnboardingCompleted: (...args: unknown[]) => mockIsOnboardingCompleted(...args), isPostOnboardingDismissed: (...args: unknown[]) => mockIsPostOnboardingDismissed(...args), dismissPostOnboardingRecommendations: (...args: unknown[]) => mockDismissPostOnboardingRecommendations(...args), - ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "first-task"], + ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "agent", "first-task"], })); vi.mock("../CustomModelDropdown", () => ({ @@ -196,7 +198,8 @@ async function advanceThroughSteps(_renderResult: RenderResult, actions: Array<" } else { const skipButton = screen.queryByRole("button", { name: "Skip setup →" }) - ?? screen.queryByRole("button", { name: "Skip GitHub →" }); + ?? screen.queryByRole("button", { name: "Skip GitHub →" }) + ?? screen.queryByRole("button", { name: "Skip for now" }); expect(skipButton).toBeTruthy(); fireEvent.click(skipButton!); @@ -334,6 +337,7 @@ beforeEach(() => { mockClearApiKey.mockResolvedValue({ success: true }); mockUpdateGlobalSettings.mockResolvedValue({}); mockCreateTask.mockResolvedValue(createdTaskMock); + mockCreateAgent.mockResolvedValue({ id: "agent-1" }); }); afterEach(() => { @@ -539,6 +543,11 @@ describe("onboarding flow integration", () => { }); fireEvent.click(screen.getByRole("button", { name: "Next →" })); + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByRole("button", { name: "Skip for now" })); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); }); @@ -586,7 +595,7 @@ describe("onboarding flow integration", () => { it("dismissal flow: dismissing on first-task step saves skipped GitHub state", async () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "skip", "next"]); + await advanceThroughSteps(renderResult, ["next", "skip", "next", "skip"]); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); @@ -626,7 +635,7 @@ describe("onboarding flow integration", () => { expect(screen.getByText("Continue Setup")).toBeInTheDocument(); expect(screen.getByText(/GitHub/)).toBeInTheDocument(); - expect(screen.getByText(/1 of 4 step complete/)).toBeInTheDocument(); + expect(screen.getByText(/1 of 5 step complete/)).toBeInTheDocument(); }); }); @@ -682,7 +691,7 @@ describe("onboarding flow integration", () => { expect(screen.getByText("Connect GitHub")).toBeInTheDocument(); }); - await advanceThroughSteps(renderResult, ["next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "skip"]); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); @@ -802,7 +811,7 @@ describe("onboarding flow integration", () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); @@ -821,7 +830,7 @@ describe("onboarding flow integration", () => { expect(screen.getByText("Anthropic")).toBeInTheDocument(); }); - await advanceThroughSteps(renderResult, ["skip", "skip", "next"]); + await advanceThroughSteps(renderResult, ["skip", "skip", "next", "skip"]); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); @@ -842,7 +851,7 @@ describe("onboarding flow integration", () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); @@ -869,7 +878,7 @@ describe("onboarding flow integration", () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); @@ -881,7 +890,7 @@ describe("onboarding flow integration", () => { it("skip warnings: Import from GitHub CTA shows connection requirement note when GitHub not connected", async () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); @@ -900,7 +909,7 @@ describe("onboarding flow integration", () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); @@ -918,7 +927,7 @@ describe("onboarding flow integration", () => { it("task creation flow: inline task creation shows success state and marks onboarding complete", async () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); await simulateFirstTaskCreation(renderResult, "Ship onboarding telemetry"); await waitFor(() => { @@ -938,7 +947,7 @@ describe("onboarding flow integration", () => { it("task creation flow: View Task button navigates and completes onboarding", async () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); await simulateFirstTaskCreation(renderResult, "View task flow"); await waitFor(() => { @@ -989,6 +998,12 @@ describe("onboarding flow integration", () => { fireEvent.click(screen.getByRole("button", { name: "Next →" })); + await waitFor(() => { + expect(screen.getByText("Create Your First Agent")).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByRole("button", { name: "Skip for now" })); + await waitFor(() => { expect(screen.getByText("Create Your First Task")).toBeInTheDocument(); }); @@ -1013,7 +1028,7 @@ describe("onboarding flow integration", () => { it("task creation flow: Finish Setup button (without creating task) completes onboarding", async () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); fireEvent.click(screen.getByRole("button", { name: "Finish Setup" })); @@ -1027,7 +1042,7 @@ describe("onboarding flow integration", () => { it("task creation flow: Create a New Task CTA completes onboarding and triggers external task dialog", async () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); fireEvent.click(screen.getByRole("button", { name: /Create a New Task/i })); @@ -1049,7 +1064,7 @@ describe("onboarding flow integration", () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); fireEvent.click(screen.getByRole("button", { name: /Import from GitHub/i })); @@ -1064,7 +1079,7 @@ describe("onboarding flow integration", () => { it("task creation flow: validation error on empty description does not call createTask", async () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); fireEvent.click(screen.getByTestId("onboarding-first-task-submit")); @@ -1076,7 +1091,7 @@ describe("onboarding flow integration", () => { mockCreateTask.mockRejectedValueOnce(new Error("Task API unavailable")); const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); await simulateFirstTaskCreation(renderResult, "Retryable task"); await waitFor(() => { @@ -1090,7 +1105,7 @@ describe("onboarding flow integration", () => { it("task creation flow: Get Started button on complete step closes the modal", async () => { const renderResult = renderModal(); - await advanceThroughSteps(renderResult, ["next", "next", "next"]); + await advanceThroughSteps(renderResult, ["next", "next", "next", "skip"]); fireEvent.click(screen.getByRole("button", { name: "Finish Setup" })); diff --git a/packages/dashboard/app/components/model-onboarding-state.ts b/packages/dashboard/app/components/model-onboarding-state.ts index 47c57f5e35..9f16829fc2 100644 --- a/packages/dashboard/app/components/model-onboarding-state.ts +++ b/packages/dashboard/app/components/model-onboarding-state.ts @@ -5,7 +5,7 @@ * from where they left off if they dismiss the modal without completing. */ -export type OnboardingStep = "ai-setup" | "github" | "project-setup" | "first-task" | "complete"; +export type OnboardingStep = "ai-setup" | "github" | "project-setup" | "agent" | "first-task" | "complete"; interface OnboardingState { currentStep: OnboardingStep | string; // string allows for future unknown steps @@ -42,7 +42,7 @@ const DEFAULT_POST_ONBOARDING_DISMISSED_AT: string | undefined = undefined; * Ordered onboarding flow steps before completion. * Keep this list in sync with ModelOnboardingModal's stepper rendering and navigation. */ -export const ONBOARDING_FLOW_STEPS = ["ai-setup", "github", "project-setup", "first-task"] as const; +export const ONBOARDING_FLOW_STEPS = ["ai-setup", "github", "project-setup", "agent", "first-task"] as const; /** * Step labels for display in the resume card. @@ -52,6 +52,7 @@ export const ONBOARDING_STEP_LABELS: Record = { "ai-setup": "AI Setup", github: "GitHub", "project-setup": "Project", + agent: "Agent", "first-task": "First Task", complete: "Complete", }; diff --git a/packages/dashboard/app/hooks/__tests__/useAuthOnboarding.test.ts b/packages/dashboard/app/hooks/__tests__/useAuthOnboarding.test.ts index afd7f6c0db..24ff282656 100644 --- a/packages/dashboard/app/hooks/__tests__/useAuthOnboarding.test.ts +++ b/packages/dashboard/app/hooks/__tests__/useAuthOnboarding.test.ts @@ -14,7 +14,7 @@ vi.mock("../../api", () => ({ vi.mock("../../components/model-onboarding-state", () => ({ isOnboardingCompleted: (...args: unknown[]) => mockIsOnboardingCompleted(...args), - ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "first-task"], + ONBOARDING_FLOW_STEPS: ["ai-setup", "github", "project-setup", "agent", "first-task"], })); vi.mock("../../components/onboarding-events", () => ({ diff --git a/packages/i18n/locales/en/app.json b/packages/i18n/locales/en/app.json index 097c35527b..defaa455f2 100644 --- a/packages/i18n/locales/en/app.json +++ b/packages/i18n/locales/en/app.json @@ -6679,6 +6679,7 @@ "profileName": "Profile name", "projectDirectory": "Project Directory", "projectMustBeSelected": "A project must be selected before you can create tasks or import from GitHub.", + "projectMustBeSelectedForAgent": "Set up a project before creating your first agent. You can also skip this and create tasks without a persistent agent.", "projectName": "Project Name", "projectNameHintClone": "By default this follows the destination folder name unless you edit it.", "projectNameHintExisting": "By default this follows the selected directory name unless you edit it.", @@ -6760,6 +6761,7 @@ "statusNotConnected": "Not connected", "statusRetry": "Retry", "statusSkipped": "Skipped", + "stepAgent": "Agent", "stepAiSetup": "AI Setup", "stepFirstTask": "First Task", "stepGithub": "GitHub", @@ -6773,6 +6775,7 @@ "titleAiSetup": "Set Up AI", "titleAllSet": "All Set!", "titleConnectGitHub": "Connect GitHub", + "titleCreateFirstAgent": "Create Your First Agent", "titleCreateFirstTask": "Create Your First Task", "titleSetUpProject": "Set Up Your Project", "tokenEnvVar": "The token was set via the {{env}} environment variable when starting the dashboard.", diff --git a/packages/i18n/locales/es/app.json b/packages/i18n/locales/es/app.json index 0448215f4e..5d75f383f1 100644 --- a/packages/i18n/locales/es/app.json +++ b/packages/i18n/locales/es/app.json @@ -6679,6 +6679,7 @@ "profileName": "Nombre del perfil", "projectDirectory": "Directorio del proyecto", "projectMustBeSelected": "Debes seleccionar un proyecto antes de crear tareas o importar desde GitHub.", + "projectMustBeSelectedForAgent": "", "projectName": "Nombre del proyecto", "projectNameHintClone": "De forma predeterminada, esto sigue el nombre de la carpeta de destino a menos que lo edite.", "projectNameHintExisting": "De forma predeterminada, esto sigue el nombre del directorio seleccionado a menos que lo edite.", @@ -6760,6 +6761,7 @@ "statusNotConnected": "No conectado", "statusRetry": "Reintentar", "statusSkipped": "Omitido", + "stepAgent": "", "stepAiSetup": "Configuración IA", "stepFirstTask": "Primera tarea", "stepGithub": "GitHub", @@ -6773,6 +6775,7 @@ "titleAiSetup": "Configurar IA", "titleAllSet": "¡Todo listo!", "titleConnectGitHub": "Conectar GitHub", + "titleCreateFirstAgent": "", "titleCreateFirstTask": "Crear tu primera tarea", "titleSetUpProject": "Configurar tu proyecto", "tokenEnvVar": "El token se estableció a través de la variable de entorno {{env}} al iniciar el panel.", diff --git a/packages/i18n/locales/fr/app.json b/packages/i18n/locales/fr/app.json index fb72c9a073..45006b35ab 100644 --- a/packages/i18n/locales/fr/app.json +++ b/packages/i18n/locales/fr/app.json @@ -6679,6 +6679,7 @@ "profileName": "Nom du profil", "projectDirectory": "Répertoire du projet", "projectMustBeSelected": "Un projet doit être sélectionné avant de pouvoir créer des tâches ou importer depuis GitHub.", + "projectMustBeSelectedForAgent": "", "projectName": "Nom du projet", "projectNameHintClone": "Par défaut, cela suit le nom du dossier de destination sauf si vous l'éditez.", "projectNameHintExisting": "Par défaut, cela suit le nom du répertoire sélectionné sauf si vous l'éditez.", @@ -6760,6 +6761,7 @@ "statusNotConnected": "Non connecté", "statusRetry": "Réessayer", "statusSkipped": "Ignoré", + "stepAgent": "", "stepAiSetup": "Configuration IA", "stepFirstTask": "Première tâche", "stepGithub": "GitHub", @@ -6773,6 +6775,7 @@ "titleAiSetup": "Configurer l'IA", "titleAllSet": "C'est parti !", "titleConnectGitHub": "Connecter GitHub", + "titleCreateFirstAgent": "", "titleCreateFirstTask": "Créer votre première tâche", "titleSetUpProject": "Configurer votre projet", "tokenEnvVar": "Le jeton a été défini via la variable d'environnement {{env}} au démarrage du tableau de bord.", diff --git a/packages/i18n/locales/ko/app.json b/packages/i18n/locales/ko/app.json index 4daa14941c..b5ec26c300 100644 --- a/packages/i18n/locales/ko/app.json +++ b/packages/i18n/locales/ko/app.json @@ -6679,6 +6679,7 @@ "profileName": "프로필 이름", "projectDirectory": "프로젝트 디렉터리", "projectMustBeSelected": "작업을 생성하거나 GitHub에서 가져오려면 먼저 프로젝트를 선택해야 합니다.", + "projectMustBeSelectedForAgent": "", "projectName": "프로젝트 이름", "projectNameHintClone": "기본적으로 대상 폴더 이름을 따르며, 직접 편집할 수 있습니다.", "projectNameHintExisting": "기본적으로 선택된 디렉터리 이름을 따르며, 직접 편집할 수 있습니다.", @@ -6760,6 +6761,7 @@ "statusNotConnected": "연결되지 않음", "statusRetry": "재시도", "statusSkipped": "건너뜀", + "stepAgent": "", "stepAiSetup": "AI 설정", "stepFirstTask": "첫 번째 작업", "stepGithub": "GitHub", @@ -6773,6 +6775,7 @@ "titleAiSetup": "AI 설정", "titleAllSet": "모두 완료!", "titleConnectGitHub": "GitHub 연결", + "titleCreateFirstAgent": "", "titleCreateFirstTask": "첫 번째 작업 만들기", "titleSetUpProject": "프로젝트 설정", "tokenEnvVar": "대시보드 시작 시 {{env}} 환경 변수를 통해 토큰이 설정되었습니다.", diff --git a/packages/i18n/locales/zh-CN/app.json b/packages/i18n/locales/zh-CN/app.json index 26bbdca63d..d2c1dacc1c 100644 --- a/packages/i18n/locales/zh-CN/app.json +++ b/packages/i18n/locales/zh-CN/app.json @@ -6679,6 +6679,7 @@ "profileName": "配置文件名称", "projectDirectory": "项目目录", "projectMustBeSelected": "在创建任务或从 GitHub 导入之前,必须先选择一个项目。", + "projectMustBeSelectedForAgent": "", "projectName": "项目名称", "projectNameHintClone": "默认情况下,这遵循目标文件夹名称,除非您编辑它。", "projectNameHintExisting": "默认情况下,这遵循选定的目录名称,除非您编辑它。", @@ -6760,6 +6761,7 @@ "statusNotConnected": "未连接", "statusRetry": "重试", "statusSkipped": "已跳过", + "stepAgent": "", "stepAiSetup": "AI 设置", "stepFirstTask": "第一个任务", "stepGithub": "GitHub", @@ -6773,6 +6775,7 @@ "titleAiSetup": "设置 AI", "titleAllSet": "一切就绪!", "titleConnectGitHub": "连接 GitHub", + "titleCreateFirstAgent": "", "titleCreateFirstTask": "创建您的第一个任务", "titleSetUpProject": "设置您的项目", "tokenEnvVar": "启动仪表板时通过 {{env}} 环境变量设置令牌。", diff --git a/packages/i18n/locales/zh-TW/app.json b/packages/i18n/locales/zh-TW/app.json index b10576d77c..c12e09769f 100644 --- a/packages/i18n/locales/zh-TW/app.json +++ b/packages/i18n/locales/zh-TW/app.json @@ -6679,6 +6679,7 @@ "profileName": "設定檔名稱", "projectDirectory": "專案目錄", "projectMustBeSelected": "在建立任務或從 GitHub 匯入之前,必須先選擇一個專案。", + "projectMustBeSelectedForAgent": "", "projectName": "專案名稱", "projectNameHintClone": "預設情況下,除非您編輯,否則這遵循目的地資料夾名稱。", "projectNameHintExisting": "預設情況下,除非您編輯,否則這遵循選定的目錄名稱。", @@ -6760,6 +6761,7 @@ "statusNotConnected": "未連線", "statusRetry": "重試", "statusSkipped": "已略過", + "stepAgent": "", "stepAiSetup": "AI 設定", "stepFirstTask": "第一個任務", "stepGithub": "GitHub", @@ -6773,6 +6775,7 @@ "titleAiSetup": "設定 AI", "titleAllSet": "一切就緒!", "titleConnectGitHub": "連線 GitHub", + "titleCreateFirstAgent": "", "titleCreateFirstTask": "建立您的第一個任務", "titleSetUpProject": "設定您的專案", "tokenEnvVar": "啟動儀表板時通過 {{env}} 環境變數設定令牌。", diff --git a/packages/i18n/src/resources.d.ts b/packages/i18n/src/resources.d.ts index 6204189789..df4110888e 100644 --- a/packages/i18n/src/resources.d.ts +++ b/packages/i18n/src/resources.d.ts @@ -6683,6 +6683,7 @@ export default interface Resources { "profileName": "Profile name", "projectDirectory": "Project Directory", "projectMustBeSelected": "A project must be selected before you can create tasks or import from GitHub.", + "projectMustBeSelectedForAgent": "Set up a project before creating your first agent. You can also skip this and create tasks without a persistent agent.", "projectName": "Project Name", "projectNameHintClone": "By default this follows the destination folder name unless you edit it.", "projectNameHintExisting": "By default this follows the selected directory name unless you edit it.", @@ -6765,6 +6766,7 @@ export default interface Resources { "statusNotConnected": "Not connected", "statusRetry": "Retry", "statusSkipped": "Skipped", + "stepAgent": "Agent", "stepAiSetup": "AI Setup", "stepFirstTask": "First Task", "stepGithub": "GitHub", @@ -6778,6 +6780,7 @@ export default interface Resources { "titleAiSetup": "Set Up AI", "titleAllSet": "All Set!", "titleConnectGitHub": "Connect GitHub", + "titleCreateFirstAgent": "Create Your First Agent", "titleCreateFirstTask": "Create Your First Task", "titleSetUpProject": "Set Up Your Project", "tokenEnvVar": "The token was set via the {{env}} environment variable when starting the dashboard.",