diff --git a/packages/web/src/components/generate-srcbook-modal.tsx b/packages/web/src/components/generate-srcbook-modal.tsx index 5545dfa6..84b07a36 100644 --- a/packages/web/src/components/generate-srcbook-modal.tsx +++ b/packages/web/src/components/generate-srcbook-modal.tsx @@ -1,11 +1,12 @@ import { useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; -import { useNavigate } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { Sparkles, Loader2 } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { generateSrcbook } from '@/lib/server'; const EXAMPLES = [ 'Cover the basics of using Prisma, the popular TypeScript database ORM, with example code', @@ -16,19 +17,17 @@ const EXAMPLES = [ export default function GenerateSrcbookModal({ open, setOpen, - onGenerate, + openSrcbook, hasOpenaiKey, }: { open: boolean; setOpen: (open: boolean) => void; - onGenerate: (query: string) => Promise; + openSrcbook: (path: string) => void; hasOpenaiKey: boolean; }) { const [query, setQuery] = useState(''); const [status, setStatus] = useState<'idle' | 'loading'>('idle'); - const [error, setError] = useState(''); - - const navigate = useNavigate(); + const [error, setError] = useState<'generic' | 'api_key' | null>(null); useHotkeys( 'mod+enter', @@ -40,13 +39,31 @@ export default function GenerateSrcbookModal({ ); const generate = async () => { - if (!query) return; + if (!query) { + return; + } + + setError(null); setStatus('loading'); - const result = await onGenerate(query); - if (result) { + + // Some errors will be handled by the API handler and return with + // {error: true, result: {message: string}}} + // Some example errors that we expect are: + // - the generated text from the LLM did not parse correctly into Srcbook format + // - the API key is invalid + // - rate limits or out-of-credits issues + const { result, error } = await generateSrcbook({ query }); + + if (error) { console.error(result); - setError(result); setStatus('idle'); + if (/Incorrect API key provided/.test(result)) { + setError('api_key'); + } else { + setError('generic'); + } + } else { + openSrcbook(result.dir); } }; @@ -77,22 +94,8 @@ export default function GenerateSrcbookModal({

Generate

)} - {error.length > 0 && ( -
- Something went wrong, please try again. -
- )} - {!hasOpenaiKey && ( -
-

API key required

- -
- )} + {error !== null && } + {!hasOpenaiKey && }

Examples

{EXAMPLES.map((example) => ( @@ -110,3 +113,44 @@ export default function GenerateSrcbookModal({ ); } + +function APIKeyWarning() { + return ( +
+

API key required

+ + Settings + +
+ ); +} + +function ErrorMessage({ type, onRetry }: { type: 'api_key' | 'generic'; onRetry: () => void }) { + return ( +
+ {type === 'api_key' ? : } +
+ ); +} + +function APIKeyError() { + return ( +
+

Invalid API key

+ + Settings + +
+ ); +} + +function GenericError(props: { onRetry: () => void }) { + return ( +
+

Something went wrong

+ +
+ ); +} diff --git a/packages/web/src/routes/home.tsx b/packages/web/src/routes/home.tsx index 9603944a..c430ee71 100644 --- a/packages/web/src/routes/home.tsx +++ b/packages/web/src/routes/home.tsx @@ -6,7 +6,6 @@ import { loadSessions, createSrcbook, importSrcbook, - generateSrcbook, loadSrcbookExamples, } from '@/lib/server'; import type { ExampleSrcbookType, SessionType } from '@/types'; @@ -61,33 +60,19 @@ export default function Home() { setShowDelete(true); } + async function openSrcbook(path: string) { + const { result: srcbook } = await createSession({ path }); + navigate(`/srcbooks/${srcbook.id}`); + } + async function onCreateSrcbook(language: CodeLanguageType) { const { result } = await createSrcbook({ path: baseDir, name: 'Untitled', language: language }); - const { result: srcbook } = await createSession({ path: result.path }); - return navigate(`/srcbooks/${srcbook.id}`); + openSrcbook(result.path); } async function openExampleSrcbook(example: ExampleSrcbookType) { const { result } = await importSrcbook({ path: example.path }); - const { result: srcbook } = await createSession({ path: result.dir }); - return navigate(`/srcbooks/${srcbook.id}`); - } - - // Some errors will be handled by the API handler and return with - // {error: true, result: {message: string}}} - // Some example errors that we expect are: - // - the generated text from the LLM didn't not parse correctly into Srcbook format - // - the API key is invalid - // - rate limits or out-of-credits issues - async function onGenerateSrcbook(query: string) { - const { result, error } = await generateSrcbook({ query }); - if (error === true) { - return result; - } - - // We know at this point that we have a valid Srcbook from the LLM - const { result: srcbook } = await createSession({ path: result.dir }); - return navigate(`/srcbooks/${srcbook.id}`); + openSrcbook(result.dir); } return ( @@ -100,7 +85,7 @@ export default function Home() {