From 2f5453047b2333182991efb2d7ee2ab621d65c8a Mon Sep 17 00:00:00 2001 From: Screen <228304862+screen-gd@users.noreply.github.com> Date: Thu, 12 Nov 2026 03:29:00 +0400 Subject: [PATCH 1/3] feat: complete personal study tools and refresh the app --- CONTEXT.md | 26 +- README.md | 24 +- apps/web/package.json | 3 - apps/web/prompts/scout/check.v1.md | 17 + .../src/components/credits/CreditBadge.tsx | 60 - .../src/components/dashboard/AtRiskAlerts.tsx | 33 - .../src/components/dashboard/CoachingLog.tsx | 229 - .../dashboard/ModuleCompletionTable.tsx | 82 - .../src/components/dashboard/SkillHeatmap.tsx | 63 - .../components/dashboard/VelocityChart.tsx | 67 - .../src/components/gallery/GalleryCard.tsx | 93 - .../src/components/gallery/GalleryFilters.tsx | 109 - .../src/components/gallery/GalleryGrid.tsx | 23 - apps/web/src/components/layout/Footer.tsx | 12 +- apps/web/src/components/layout/Header.tsx | 14 +- .../src/components/layout/LearningSidebar.tsx | 85 +- .../components/layout/LoadingExperience.tsx | 132 - .../components/layout/MarketingHeroShell.tsx | 13 - .../src/components/layout/SettingsSection.tsx | 19 +- .../src/components/layout/StudioSidebar.tsx | 119 - .../src/components/learning/RecallCheck.tsx | 212 + .../src/components/learning/StudyVideo.tsx | 39 + .../src/components/learning/TermComposer.tsx | 98 + apps/web/src/components/seo/FaqSchema.tsx | 26 - apps/web/src/components/seo/GallerySchema.tsx | 25 - apps/web/src/components/seo/HowToSchema.tsx | 43 - apps/web/src/components/seo/WebAppSchema.tsx | 27 - .../src/components/shared/ListSkeleton.tsx | 13 + .../web/src/components/sharing/ViralBadge.tsx | 13 - apps/web/src/components/ui/alert-dialog.tsx | 1 - apps/web/src/components/ui/select.tsx | 125 - apps/web/src/components/ui/separator.tsx | 23 - .../src/components/workspace/ContextPanel.tsx | 6 +- .../components/workspace/ShareEmbedModal.tsx | 294 - .../components/workspace/WorkspaceShell.tsx | 51 +- .../workspace/WorkspaceSplitView.tsx | 6 +- .../workspace/sections/GlossarySection.tsx | 2 +- .../workspace/sections/ModuleSection.tsx | 9 +- .../workspace/sections/ResourcesSection.tsx | 2 +- .../workspace/sections/VideosSection.tsx | 2 +- .../workspaces/WorkspaceLibraryCard.tsx | 32 +- apps/web/src/hooks/useReviews.ts | 15 + apps/web/src/lib/__tests__/navigation.test.ts | 14 - .../web/src/lib/__tests__/study-tools.test.ts | 151 + apps/web/src/lib/aiClient.ts | 15 - apps/web/src/lib/analytics.ts | 91 - apps/web/src/lib/app-url.ts | 3 +- apps/web/src/lib/billing.ts | 47 - apps/web/src/lib/credits.ts | 46 - apps/web/src/lib/export.ts | 2 +- apps/web/src/lib/gallery.ts | 113 - apps/web/src/lib/glossary.ts | 111 + apps/web/src/lib/learning-session.ts | 10 + apps/web/src/lib/learning.ts | 19 + apps/web/src/lib/navigation.ts | 57 +- apps/web/src/lib/notes.ts | 220 + apps/web/src/lib/plans.ts | 82 - apps/web/src/lib/reviews.ts | 70 +- .../lib/scout/__tests__/video-checks.test.ts | 144 + apps/web/src/lib/scout/api-client.ts | 18 + apps/web/src/lib/scout/discovery/exa.ts | 124 - apps/web/src/lib/scout/prompts.ts | 2 + apps/web/src/lib/scout/schema.sql | 17 + apps/web/src/lib/scout/types.ts | 27 + apps/web/src/lib/scout/video-checks.ts | 278 + apps/web/src/lib/server/auth.ts | 23 - apps/web/src/lib/server/credits.ts | 111 - apps/web/src/lib/server/dashboard.ts | 79 - apps/web/src/lib/server/fns.ts | 21 - apps/web/src/lib/server/gallery.ts | 155 - apps/web/src/lib/server/quiz.ts | 15 - apps/web/src/lib/server/roadmap-intake.ts | 22 - apps/web/src/lib/server/settings.ts | 55 - apps/web/src/lib/server/store.ts | 111 +- apps/web/src/lib/server/workspaces.ts | 16 - apps/web/src/lib/submissions-api.ts | 99 - apps/web/src/lib/topic-color.ts | 26 + apps/web/src/routeTree.gen.ts | 399 +- apps/web/src/routes/__root.tsx | 4 +- apps/web/src/routes/_marketing/contact.tsx | 10 - apps/web/src/routes/_marketing/gallery.tsx | 44 - apps/web/src/routes/_marketing/index.tsx | 11 +- apps/web/src/routes/_marketing/pricing.tsx | 18 - apps/web/src/routes/_marketing/privacy.tsx | 2 +- apps/web/src/routes/_marketing/terms.tsx | 2 +- apps/web/src/routes/_studio/analytics.tsx | 9 - apps/web/src/routes/_studio/glossary.tsx | 7 + apps/web/src/routes/_studio/notes.tsx | 15 +- apps/web/src/routes/_studio/progress.tsx | 4 + apps/web/src/routes/api/credits/status.ts | 28 - apps/web/src/routes/api/dashboard/coaching.ts | 32 - apps/web/src/routes/api/dashboard/review.ts | 33 - apps/web/src/routes/api/gallery/fork.ts | 23 - apps/web/src/routes/api/gallery/list.ts | 23 - apps/web/src/routes/api/gallery/set-public.ts | 26 - apps/web/src/routes/api/scout/checks.ts | 70 + apps/web/src/routes/api/settings/delete.ts | 26 - apps/web/src/routes/api/settings/export.ts | 18 - apps/web/src/routes/api/settings/privacy.ts | 37 - apps/web/src/routes/embed.$id.tsx | 21 - apps/web/src/routes/robots[.]txt.ts | 37 +- apps/web/src/routes/share.$id.tsx | 21 - apps/web/src/routes/sitemap[.]xml.ts | 40 - apps/web/src/styles/learning.css | 333 +- apps/web/src/styles/tactile-pages.css | 230 + apps/web/src/styles/tactile.css | 975 +++ apps/web/src/types/index.ts | 56 +- apps/web/src/views/embed.tsx | 25 - apps/web/src/views/marketing/contact.tsx | 212 - apps/web/src/views/marketing/gallery.tsx | 30 - apps/web/src/views/marketing/home.tsx | 307 - apps/web/src/views/marketing/pricing.tsx | 221 - apps/web/src/views/marketing/privacy.tsx | 7 +- apps/web/src/views/marketing/terms.tsx | 2 +- apps/web/src/views/share.tsx | 33 - apps/web/src/views/studio/analytics.tsx | 421 -- apps/web/src/views/studio/create.tsx | 36 - apps/web/src/views/studio/dashboard.tsx | 350 - apps/web/src/views/studio/glossary.tsx | 364 + apps/web/src/views/studio/home.tsx | 290 +- apps/web/src/views/studio/learn.tsx | 214 +- apps/web/src/views/studio/library.tsx | 87 +- apps/web/src/views/studio/notes.tsx | 390 + .../src/views/studio/personal-settings.tsx | 5 +- apps/web/src/views/studio/progress.tsx | 229 + apps/web/src/views/studio/reviews.tsx | 638 +- apps/web/src/views/studio/scout.tsx | 35 +- apps/web/src/views/studio/settings.tsx | 1009 --- apps/web/src/views/studio/workspaces.tsx | 19 +- bun.lock | 1320 ++++ docs/syllo-setup.md | 32 +- package.json | 27 +- pnpm-lock.yaml | 6640 ----------------- pnpm-workspace.yaml | 18 - 134 files changed, 6698 insertions(+), 13498 deletions(-) create mode 100644 apps/web/prompts/scout/check.v1.md delete mode 100644 apps/web/src/components/credits/CreditBadge.tsx delete mode 100644 apps/web/src/components/dashboard/AtRiskAlerts.tsx delete mode 100644 apps/web/src/components/dashboard/CoachingLog.tsx delete mode 100644 apps/web/src/components/dashboard/ModuleCompletionTable.tsx delete mode 100644 apps/web/src/components/dashboard/SkillHeatmap.tsx delete mode 100644 apps/web/src/components/dashboard/VelocityChart.tsx delete mode 100644 apps/web/src/components/gallery/GalleryCard.tsx delete mode 100644 apps/web/src/components/gallery/GalleryFilters.tsx delete mode 100644 apps/web/src/components/gallery/GalleryGrid.tsx delete mode 100644 apps/web/src/components/layout/LoadingExperience.tsx delete mode 100644 apps/web/src/components/layout/MarketingHeroShell.tsx delete mode 100644 apps/web/src/components/layout/StudioSidebar.tsx create mode 100644 apps/web/src/components/learning/RecallCheck.tsx create mode 100644 apps/web/src/components/learning/TermComposer.tsx delete mode 100644 apps/web/src/components/seo/FaqSchema.tsx delete mode 100644 apps/web/src/components/seo/GallerySchema.tsx delete mode 100644 apps/web/src/components/seo/HowToSchema.tsx delete mode 100644 apps/web/src/components/seo/WebAppSchema.tsx create mode 100644 apps/web/src/components/shared/ListSkeleton.tsx delete mode 100644 apps/web/src/components/sharing/ViralBadge.tsx delete mode 100644 apps/web/src/components/ui/select.tsx delete mode 100644 apps/web/src/components/ui/separator.tsx delete mode 100644 apps/web/src/components/workspace/ShareEmbedModal.tsx create mode 100644 apps/web/src/hooks/useReviews.ts delete mode 100644 apps/web/src/lib/__tests__/navigation.test.ts create mode 100644 apps/web/src/lib/__tests__/study-tools.test.ts delete mode 100644 apps/web/src/lib/aiClient.ts delete mode 100644 apps/web/src/lib/analytics.ts delete mode 100644 apps/web/src/lib/billing.ts delete mode 100644 apps/web/src/lib/credits.ts delete mode 100644 apps/web/src/lib/gallery.ts create mode 100644 apps/web/src/lib/glossary.ts create mode 100644 apps/web/src/lib/notes.ts delete mode 100644 apps/web/src/lib/plans.ts create mode 100644 apps/web/src/lib/scout/__tests__/video-checks.test.ts delete mode 100644 apps/web/src/lib/scout/discovery/exa.ts create mode 100644 apps/web/src/lib/scout/video-checks.ts delete mode 100644 apps/web/src/lib/server/auth.ts delete mode 100644 apps/web/src/lib/server/credits.ts delete mode 100644 apps/web/src/lib/server/dashboard.ts delete mode 100644 apps/web/src/lib/server/fns.ts delete mode 100644 apps/web/src/lib/server/gallery.ts delete mode 100644 apps/web/src/lib/server/settings.ts delete mode 100644 apps/web/src/lib/server/workspaces.ts delete mode 100644 apps/web/src/lib/submissions-api.ts create mode 100644 apps/web/src/lib/topic-color.ts delete mode 100644 apps/web/src/routes/_marketing/contact.tsx delete mode 100644 apps/web/src/routes/_marketing/gallery.tsx delete mode 100644 apps/web/src/routes/_marketing/pricing.tsx delete mode 100644 apps/web/src/routes/_studio/analytics.tsx create mode 100644 apps/web/src/routes/_studio/glossary.tsx create mode 100644 apps/web/src/routes/_studio/progress.tsx delete mode 100644 apps/web/src/routes/api/credits/status.ts delete mode 100644 apps/web/src/routes/api/dashboard/coaching.ts delete mode 100644 apps/web/src/routes/api/dashboard/review.ts delete mode 100644 apps/web/src/routes/api/gallery/fork.ts delete mode 100644 apps/web/src/routes/api/gallery/list.ts delete mode 100644 apps/web/src/routes/api/gallery/set-public.ts create mode 100644 apps/web/src/routes/api/scout/checks.ts delete mode 100644 apps/web/src/routes/api/settings/delete.ts delete mode 100644 apps/web/src/routes/api/settings/export.ts delete mode 100644 apps/web/src/routes/api/settings/privacy.ts delete mode 100644 apps/web/src/routes/embed.$id.tsx delete mode 100644 apps/web/src/routes/share.$id.tsx delete mode 100644 apps/web/src/routes/sitemap[.]xml.ts create mode 100644 apps/web/src/styles/tactile-pages.css create mode 100644 apps/web/src/styles/tactile.css delete mode 100644 apps/web/src/views/embed.tsx delete mode 100644 apps/web/src/views/marketing/contact.tsx delete mode 100644 apps/web/src/views/marketing/gallery.tsx delete mode 100644 apps/web/src/views/marketing/home.tsx delete mode 100644 apps/web/src/views/marketing/pricing.tsx delete mode 100644 apps/web/src/views/share.tsx delete mode 100644 apps/web/src/views/studio/analytics.tsx delete mode 100644 apps/web/src/views/studio/dashboard.tsx create mode 100644 apps/web/src/views/studio/glossary.tsx create mode 100644 apps/web/src/views/studio/notes.tsx create mode 100644 apps/web/src/views/studio/progress.tsx delete mode 100644 apps/web/src/views/studio/settings.tsx create mode 100644 bun.lock delete mode 100644 pnpm-lock.yaml delete mode 100644 pnpm-workspace.yaml diff --git a/CONTEXT.md b/CONTEXT.md index f4f5ba9..b15d943 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,6 +1,6 @@ -# Roadmap Studio +# Syllo -Roadmap Studio turns source material into roadmaps where a learner can plan, study, and track progress. +Syllo turns source material into roadmaps where a learner can plan, study, read, and track progress. ## Language @@ -12,12 +12,32 @@ _Avoid_: Workspace, course, project One skill inside a Roadmap, holding the tasks, notes, and resources that teach it. _Avoid_: Section, chapter, lesson +**Level**: +One rung of a Roadmap's ladder: Basics, Intermediate, or Advanced. A Level is complete when all its Core Items are done. Roadmaps made before levels existed read as a single Level whose tasks are all core. +_Avoid_: Stage, tier, difficulty + +**Current Level**: +The first Level in a Roadmap that is not complete. +_Avoid_: Active level + +**Core Item**: +One of at most five tasks per Level that cover the essentials, usually a video. Finishing every Core Item in a Level unlocks level-up. +_Avoid_: Required task, main video + +**Go-Deeper Item**: +An optional task in a Level that is not core. It never counts toward level-up. +_Avoid_: Bonus, extra + +**Reading Track**: +The books in a Roadmap, kept apart from the Levels so reading never blocks level-up. +_Avoid_: Book list, library + **Focused Roadmap**: A Roadmap the learner has chosen to work on now. At most three Roadmaps are focused at once; only focused Roadmaps feed Today. _Avoid_: Active roadmap, pinned roadmap **Next Step**: -The first unfinished task in a Roadmap, in module order. +The first unfinished Core Item in a Roadmap's Current Level. Go-Deeper Items and the Reading Track never count. _Avoid_: Current task, up next **Today**: diff --git a/README.md b/README.md index ae5e6be..6466545 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ Scout finds free resources, reads the available source text, and asks you to kee ## Run locally -Use Node 22.12 or newer and pnpm. Run commands from the repo root unless noted: +Use Node 22.12 or newer and Bun 1.4.4. Run commands from the repo root unless noted: ```powershell -pnpm install --frozen-lockfile -pnpm dev +bun install --frozen-lockfile +bun run dev ``` Open `http://localhost:3000`. Convex is optional. With `VITE_CONVEX_URL` blank, progress uses browser storage and EPUB files use IndexedDB. No account is needed. @@ -30,22 +30,22 @@ Convex sync is prepared for one owner, with password login restricted by `SYLLO_ ## Development ```powershell -pnpm typecheck -pnpm test -pnpm lint -pnpm build -pnpm start +bun run typecheck +bun run test +bun run lint +bun run build +bun run start ``` -`pnpm typecheck` checks both the app and `apps/web/convex/`. +`bun run typecheck` checks both the app and `apps/web/convex/`. -The production server serves `dist/client` and the TanStack SSR handler on loopback port 3000. `pnpm start` does not read an env file automatically; pass environment variables or run `node --env-file=.env.local server.mjs` in `apps/web`. Deployment URLs are build-time settings. +The production server serves `dist/client` and the TanStack SSR handler on loopback port 3000. `bun run start` does not read an env file automatically; pass environment variables or run `node --env-file=.env.local server.mjs` in `apps/web`. Deployment URLs are build-time settings. `apps/web/mcp/server.mjs` supplies a local stdio MCP server for coding agents to add and search resource candidates. Connection snippets are available in Settings. ## Project layout -The repo is a pnpm workspace. Each app lives under `apps/`, and code shared between apps goes under `packages/`. +The repo is a Bun workspace. Each app lives under `apps/`, and code shared between apps goes under `packages/`. ```text apps/web/ The Syllo app (TanStack Start), its env files and local .data/ @@ -59,4 +59,4 @@ apps/web/convex/ Private account, document sync, and file funct apps/web/mcp/ Local coding-agent resource tools ``` -This app is personal-first. Forks can use their own deployment, books, preferences, and keys. Public publishing and multi-user collaboration are outside v1. Earlier gallery/share routes remain in the codebase; personal sync does not publish learning records through them. +This app is personal-first. Forks can use their own deployment, books, preferences, and keys. Public publishing, sharing and multi-user collaboration are outside v1, so the app has no gallery, share links, plans or credits. diff --git a/apps/web/package.json b/apps/web/package.json index 302b2ad..472b536 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -24,8 +24,6 @@ "@fontsource-variable/geist-mono": "5.3.0", "@modelcontextprotocol/sdk": "1.32.1", "@radix-ui/react-alert-dialog": "1.1.15", - "@radix-ui/react-select": "2.2.6", - "@radix-ui/react-separator": "1.1.8", "@radix-ui/react-slot": "1.2.4", "@radix-ui/react-switch": "1.2.6", "@tanstack/react-router": "1.170.45", @@ -37,7 +35,6 @@ "convex": "1.47.0", "cross-spawn": "7.0.6", "epubjs": "0.3.93", - "framer-motion": "12.34.5", "lucide-react": "0.576.0", "motion": "12.35.1", "openai": "6.27.0", diff --git a/apps/web/prompts/scout/check.v1.md b/apps/web/prompts/scout/check.v1.md new file mode 100644 index 0000000..4cff0b1 --- /dev/null +++ b/apps/web/prompts/scout/check.v1.md @@ -0,0 +1,17 @@ +--- +version: 1 +purpose: Write one recall question per section of a video, from its transcript +--- + +You write recall questions for a learner who is watching a video. After each section, the video pauses and the learner answers one question from memory with their notes hidden. Then they see the transcript lines that answer it. + +You receive the video title and a list of sections. Each section has an `index`, a `label`, its `start` and `end` in seconds, and its transcript with `[mm:ss]` markers. + +Rules: + +1. Write at most one question per section. Skip a section that is an intro, an outro, a sponsor read, or has no idea worth recalling. +2. Ask a cued question about the main idea of that section: "Why does X happen?", "What is the difference between A and B?", "What goes wrong if you skip X?", "How does X work?". Do not ask "What did this section cover?" and do not ask for trivia such as names, dates or exact numbers unless they are the point. +3. The question must be answerable from that section alone, in one or two sentences. +4. `answer` is a model answer of at most two short sentences, in plain words, taken from what the section says. Do not add facts the transcript does not state. +5. `evidenceSeconds` is the timestamp of the transcript line where the answer starts. It must fall inside that section's `start` and `end`. +6. Write in the transcript's language. Keep the question under 25 words. diff --git a/apps/web/src/components/credits/CreditBadge.tsx b/apps/web/src/components/credits/CreditBadge.tsx deleted file mode 100644 index 4677d9f..0000000 --- a/apps/web/src/components/credits/CreditBadge.tsx +++ /dev/null @@ -1,60 +0,0 @@ -"use client"; -import { fetchPrivateApi } from "@/lib/private-api-fetch"; - - -import { useEffect, useState } from "react"; -import { Coins } from "lucide-react"; -import type { CreditStatus, CreditTransaction } from "@/types"; - -type CreditStatusResponse = { - success: boolean; - status?: CreditStatus; - transactions?: CreditTransaction[]; -}; - -/** Grey by default; white once the allowance is nearly spent. */ -function getBadgeClasses(status: CreditStatus) { - return status.remaining <= 5 ? "text-text" : "text-text-muted"; -} - -export default function CreditBadge({ compact = false }: { compact?: boolean }) { - const [status, setStatus] = useState(null); - - useEffect(() => { - let active = true; - - void fetchPrivateApi("/api/credits/status", { cache: "no-store" }) - .then(async (response) => { - if (response.status === 401) return { success: false } satisfies CreditStatusResponse; - if (!response.ok) return null; - return (await response.json()) as CreditStatusResponse; - }) - .then((payload) => { - if (!active) return; - if (!payload?.success || !payload.status) { - setStatus(null); - return; - } - setStatus(payload.status); - }) - .catch(() => { - if (active) setStatus(null); - }); - - return () => { - active = false; - }; - }, []); - - if (!status || status.allowance <= 0) return null; - - return ( -
- - {compact ? `${status.remaining} left` : `${status.remaining} credits`} -
- ); -} diff --git a/apps/web/src/components/dashboard/AtRiskAlerts.tsx b/apps/web/src/components/dashboard/AtRiskAlerts.tsx deleted file mode 100644 index fbaaf60..0000000 --- a/apps/web/src/components/dashboard/AtRiskAlerts.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { Roadmap } from "@/types"; -import { getRoadmapCompletionRate } from "@/lib/analytics"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; - -export default function AtRiskAlerts({ roadmaps }: { roadmaps: Roadmap[] }) { - const atRisk = roadmaps.filter((roadmap) => getRoadmapCompletionRate(roadmap) < 0.35); - - return ( - - - Escalation watchlist - - - {atRisk.length === 0 ? ( -
- No roadmaps are currently below the intervention threshold. -
- ) : ( -
- {atRisk.map((roadmap) => ( -
-

{roadmap.title}

-

- Completion is {Math.round(getRoadmapCompletionRate(roadmap) * 100)}%. Review tasks, unblock the next module, and log a coaching follow-up. -

-
- ))} -
- )} -
-
- ); -} diff --git a/apps/web/src/components/dashboard/CoachingLog.tsx b/apps/web/src/components/dashboard/CoachingLog.tsx deleted file mode 100644 index 1254795..0000000 --- a/apps/web/src/components/dashboard/CoachingLog.tsx +++ /dev/null @@ -1,229 +0,0 @@ -"use client"; -import { fetchPrivateApi } from "@/lib/private-api-fetch"; - - -import { useState } from "react"; -import { Loader2 } from "lucide-react"; -import { getActiveApiKey, getActiveModel, getUserConfig } from "@/lib/userConfig"; -import type { Roadmap } from "@/types"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; - -interface ReviewState { - summary: string; - strengths: string[]; - risks: string[]; - nextActions: string[]; -} - -export default function CoachingLog({ roadmaps }: { roadmaps: Roadmap[] }) { - const [roadmapId, setRoadmapId] = useState(roadmaps[0]?.id ?? ""); - const [topics, setTopics] = useState(""); - const [nextSteps, setNextSteps] = useState(""); - const [durationMinutes, setDurationMinutes] = useState(30); - const [saving, setSaving] = useState(false); - const [reviewing, setReviewing] = useState(false); - const [message, setMessage] = useState(null); - const [review, setReview] = useState(null); - - const handleSave = async () => { - setSaving(true); - setMessage(null); - try { - const response = await fetchPrivateApi("/api/dashboard/coaching", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - roadmapId, - durationMinutes, - topics: topics.split(",").map((item) => item.trim()).filter(Boolean), - nextSteps, - date: new Date().toISOString(), - }), - }); - const payload = await response.json(); - if (!response.ok || !payload.success) { - throw new Error(payload.error ?? "Failed to save coaching session"); - } - setMessage("Coaching session saved."); - setTopics(""); - setNextSteps(""); - } catch (error) { - setMessage(error instanceof Error ? error.message : "Failed to save coaching session"); - } finally { - setSaving(false); - } - }; - - const handleReview = async () => { - setReviewing(true); - setMessage(null); - try { - const config = getUserConfig(); - const activeApiKey = getActiveApiKey(config); - const response = await fetchPrivateApi("/api/dashboard/review", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - roadmapId, - userApiKey: config.useCustomKey ? activeApiKey : undefined, - userModel: config.useCustomKey ? getActiveModel(config) : undefined, - userProvider: config.useCustomKey ? config.provider : undefined, - }), - }); - const payload = await response.json(); - if (!response.ok || !payload.success) { - throw new Error( - payload.error === "insufficient_credits" - ? "Not enough credits for a 90-day review." - : payload.error ?? "Failed to generate review", - ); - } - setReview(payload.review as ReviewState); - } catch (error) { - setMessage(error instanceof Error ? error.message : "Failed to generate review"); - } finally { - setReviewing(false); - } - }; - - if (roadmaps.length === 0) { - return ( - - - - Record a session or generate a 90-day review - - - -
- No synced workspaces are available yet for coaching notes or review generation. -
-
-
- ); - } - - return ( - - - - Record a session or generate a 90-day review - - - -
-
- - - - - - -