diff --git a/src/dialogs/resume/import.tsx b/src/dialogs/resume/import.tsx index eab4913a3..3176671d4 100644 --- a/src/dialogs/resume/import.tsx +++ b/src/dialogs/resume/import.tsx @@ -3,7 +3,7 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { DownloadSimpleIcon, FileIcon, UploadSimpleIcon } from "@phosphor-icons/react"; import { useMutation } from "@tanstack/react-query"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { useForm, useWatch } from "react-hook-form"; import { toast } from "sonner"; import z from "zod"; @@ -66,10 +66,11 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { const { enabled: isAIEnabled, provider, model, apiKey, baseURL } = useAIStore(); const closeDialog = useDialogStore((state) => state.closeDialog); - const inputRef = useRef(null); const prevTypeRef = useRef(""); + const inputRef = useRef(null); + const [isLoading, setIsLoading] = useState(false); - const { mutateAsync: importResume, isPending } = useMutation(orpc.resume.import.mutationOptions()); + const { mutateAsync: importResume } = useMutation(orpc.resume.import.mutationOptions()); const form = useForm({ resolver: zodResolver(formSchema), @@ -100,6 +101,8 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { const onSubmit = async (values: FormValues) => { if (values.type === "") return; + setIsLoading(true); + const toastId = toast.loading(t`Importing your resume...`, { description: t`This may take a few minutes, depending on the response of the AI provider. Please do not close the window or refresh the page.`, }); @@ -126,13 +129,8 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { } if (values.type === "pdf") { - if (!isAIEnabled) { - toast.error(t`This feature requires AI Integration to be enabled. Please enable it in the settings.`, { - id: toastId, - description: null, - }); - return; - } + if (!isAIEnabled) + throw new Error(t`This feature requires AI Integration to be enabled. Please enable it in the settings.`); const arrayBuffer = await values.file.arrayBuffer(); const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); @@ -147,13 +145,8 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { } if (values.type === "docx") { - if (!isAIEnabled) { - toast.error(t`This feature requires AI Integration to be enabled. Please enable it in the settings.`, { - id: toastId, - description: null, - }); - return; - } + if (!isAIEnabled) + throw new Error(t`This feature requires AI Integration to be enabled. Please enable it in the settings.`); const arrayBuffer = await values.file.arrayBuffer(); const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); @@ -172,7 +165,7 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { }); } - if (!data) return; + if (!data) throw new Error("No data was returned from the AI provider."); await importResume({ data }); toast.success(t`Your resume has been imported successfully.`, { id: toastId, description: null }); @@ -183,6 +176,8 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { } else { toast.error(t`An unknown error occurred while importing your resume.`, { id: toastId, description: null }); } + } finally { + setIsLoading(false); } }; @@ -279,9 +274,9 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { /> - diff --git a/src/integrations/orpc/router/ai.ts b/src/integrations/orpc/router/ai.ts index e463da141..b01ce3d0b 100644 --- a/src/integrations/orpc/router/ai.ts +++ b/src/integrations/orpc/router/ai.ts @@ -82,9 +82,7 @@ export const aiRouter = { const result = await generateText({ model, maxRetries: 0, - output: Output.object({ - schema: resumeDataSchema.omit({ picture: true, metadata: true, customSections: true }), - }), + output: Output.object({ schema: resumeDataSchema }), messages: [ { role: "system", @@ -139,9 +137,7 @@ export const aiRouter = { const result = await generateText({ model, maxRetries: 0, - output: Output.object({ - schema: resumeDataSchema.omit({ picture: true, metadata: true, customSections: true }), - }), + output: Output.object({ schema: resumeDataSchema }), messages: [ { role: "system", content: docxParserSystemPrompt }, { diff --git a/src/schema/resume/data.ts b/src/schema/resume/data.ts index 8bae12b36..6b6bfc199 100644 --- a/src/schema/resume/data.ts +++ b/src/schema/resume/data.ts @@ -61,13 +61,13 @@ export const customFieldSchema = z.object({ id: z.string().describe("The unique identifier for the custom field. Usually generated as a UUID."), icon: iconSchema, text: z.string().describe("The text to display for the custom field."), - link: z.url().or(z.literal("")).describe("If the custom field should be a link, the URL to link to.").catch(""), + link: z.string().describe("If the custom field should be a link, the URL to link to.").catch(""), }); export const basicsSchema = z.object({ name: z.string().describe("The full name of the author of the resume."), headline: z.string().describe("The headline of the author of the resume."), - email: z.email().or(z.literal("")).describe("The email address of the author of the resume."), + email: z.string().describe("The email address of the author of the resume."), phone: z.string().describe("The phone number of the author of the resume."), location: z.string().describe("The location of the author of the resume."), website: urlSchema.describe("The website of the author of the resume."),