diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3bea1ef..5461cae 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { Suspense, lazy, useState } from "react"; +import { Suspense, lazy } from "react"; import { AppProvider, useApp } from "./context/AppContext"; import { ToastProvider } from "./components/ui/Toast"; import { ErrorBoundary } from "./components/ui/ErrorBoundary"; @@ -11,9 +11,9 @@ import { AskView } from "./views/AskView"; import { SkeletonStack } from "./components/ui/Skeleton"; import { apiBase } from "./api/client"; import { resolveApiKey } from "./lib/auth"; -import { hasCompletedOnboarding } from "./lib/onboarding"; import { BugReportSection } from "./components/layout/BugReportSection"; import { useApiHealth } from "./hooks/useApiHealth"; +import { IconSpark } from "./components/ui/icons"; const ExploreView = lazy(() => import("./views/ExploreView").then((m) => ({ default: m.ExploreView })), @@ -102,7 +102,7 @@ function TopbarActions() { onClick={() => setAssistantOpen(true)} aria-label="Open Cortex Assist" > - ✦ Assist + Assist typeof window !== "undefined" && !hasCompletedOnboarding(), - ); + const { setAssistantOpen, setView, setWorkspaceId, showOnboarding, setShowOnboarding } = + useApp(); return (
@@ -136,6 +134,10 @@ function AppChrome() { setShowOnboarding(false)} onOpenCopilot={() => setAssistantOpen(true)} + onFinishAsk={(workspace, query) => { + setWorkspaceId(workspace); + setView("ask", { q: query }); + }} /> ) : null} diff --git a/frontend/src/components/layout/WorkspaceBar.tsx b/frontend/src/components/layout/WorkspaceBar.tsx index eaacd06..98d8742 100644 --- a/frontend/src/components/layout/WorkspaceBar.tsx +++ b/frontend/src/components/layout/WorkspaceBar.tsx @@ -1,14 +1,14 @@ import { useId, useState } from "react"; import { useApp } from "../../context/AppContext"; import { fetchHealth, hasApiKeyConfigured } from "../../api/client"; -import { persistApiKey } from "../../lib/auth"; -import { setClientApiKey } from "../../api/client"; import { useToast } from "../ui/Toast"; +import { IconLock } from "../ui/icons"; const PRESETS = ["local-dev", "acme-demo"] as const; export function WorkspaceBar() { - const { workspaceId, setWorkspaceId, apiKey, setApiKey, saveApiKey } = useApp(); + const { workspaceId, setWorkspaceId, apiKey, setApiKey, saveApiKey, clearApiKey, replayOnboarding } = + useApp(); const { showToast } = useToast(); const [expanded, setExpanded] = useState(false); const [showKey, setShowKey] = useState(false); @@ -44,8 +44,7 @@ export function WorkspaceBar() { function handleClear(): void { setApiKey(""); - persistApiKey(""); - setClientApiKey(""); + clearApiKey(); setTestOk(null); showToast("API key cleared"); } @@ -89,7 +88,7 @@ export function WorkspaceBar() { className={`connection-bar__status ${secured ? "connection-bar__status--secured" : ""}`} aria-hidden > - {secured ? "πŸ”’" : "β—‡"} + {secured ? : "β—‡"} Connection @@ -139,6 +138,9 @@ export function WorkspaceBar() { > {testing ? "Testing…" : "Test connection"} +
{secured ? (

diff --git a/frontend/src/components/onboarding/OnboardingModal.tsx b/frontend/src/components/onboarding/OnboardingModal.tsx index f4a0ac4..4b432d8 100644 --- a/frontend/src/components/onboarding/OnboardingModal.tsx +++ b/frontend/src/components/onboarding/OnboardingModal.tsx @@ -1,69 +1,174 @@ import { useState } from "react"; import { markOnboardingComplete } from "../../lib/onboarding"; +import { useApp } from "../../context/AppContext"; +import { IconSpark } from "../ui/icons"; type Props = { onComplete: () => void; onOpenCopilot: () => void; + onFinishAsk: (workspace: string, query: string) => void; }; -const STEPS = [ - { - title: "Your organization's living memory", - body: "Cortex captures decisions β€” not documents β€” from Slack, GitHub, Jira, and more. Ask natural questions and get answers with who decided, why, and what's affected.", - icon: "β—ˆ", - }, - { - title: "Ask in plain language", - body: 'Try questions like "Why did we choose CockroachDB for payments?" or "What affects checkout?" Cortex searches structured memory, not scattered threads.', - icon: "?", - }, - { - title: "Meet Assist", - body: "Cortex Assist guides you, runs searches, and explains results. Use the memory map to see how people, systems, and decisions connect over time.", - icon: "✦", - }, +const WORKSPACE_PRESETS = [ + { id: "local-dev", label: "Demo workspace", hint: "Pre-seeded decisions for demos" }, + { id: "oss-tiangolo-fastapi", label: "OSS: FastAPI", hint: "Open-source graph import" }, + { id: "oss-adr", label: "OSS: ADRs", hint: "Architecture decision records" }, ] as const; -export function OnboardingModal({ onComplete, onOpenCopilot }: Props) { +const DEMO_QUERY = "Why CockroachDB for payments?"; + +export function OnboardingModal({ onComplete, onOpenCopilot, onFinishAsk }: Props) { + const { setWorkspaceId, apiKey, setApiKey, saveApiKey } = useApp(); const [step, setStep] = useState(0); - const current = STEPS[step]; - const isLast = step === STEPS.length - 1; + const [workspace, setWorkspace] = useState("local-dev"); + const isLast = step === 3; - function finish() { - markOnboardingComplete(); + function finishToAsk(): void { + markOnboardingComplete(workspace); + setWorkspaceId(workspace); onComplete(); - if (isLast) onOpenCopilot(); + onFinishAsk(workspace, DEMO_QUERY); + } + + function finishToAssist(): void { + markOnboardingComplete(workspace); + setWorkspaceId(workspace); + onComplete(); + onOpenCopilot(); } return ( -

+
- {STEPS.map((_, i) => ( + {[0, 1, 2, 3].map((i) => ( ))}
- - {current.icon} - -

- {current.title} -

-

{current.body}

+ + {step === 0 ? ( + <> + + β—ˆ + +

+ Your organization's living memory +

+

+ Cortex captures decisions β€” not documents β€” from Slack, GitHub, Jira, + and more. Ask natural questions and get answers with who decided, why, and what's + affected. +

+ + ) : null} + + {step === 1 ? ( + <> +

+ Choose a workspace +

+

+ Workspaces isolate organizational memory. Start with the demo workspace or pick an OSS + preset. +

+
+ {WORKSPACE_PRESETS.map((w) => ( + + ))} +
+ + ) : null} + + {step === 2 ? ( + <> +

+ Try a demo search +

+

+ We'll open Search with a sample question so you can see trust scores, coverage, and + decision stories in seconds. +

+
{DEMO_QUERY}
+ + ) : null} + + {step === 3 ? ( + <> +

+ Connection (optional) +

+

+ The public demo works without a key. If your deployment requires authentication, add + your API key now β€” you can always change it under Connection later. +

+ + setApiKey(e.target.value)} + placeholder="Optional" + autoComplete="off" + /> + + ) : null} +