import type { AIProvider } from "@reactive-resume/ai/types"; import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { ArrowRightIcon, ChatCircleDotsIcon, FilePlusIcon, GearSixIcon } from "@phosphor-icons/react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { Link, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import { toast } from "sonner"; import { useIsClient } from "usehooks-ts"; import { Badge } from "@reactive-resume/ui/components/badge"; import { Button } from "@reactive-resume/ui/components/button"; import { Label } from "@reactive-resume/ui/components/label"; import { Spinner } from "@reactive-resume/ui/components/spinner"; import { Combobox } from "@/components/ui/combobox"; import { useHasUsableAiProvider } from "@/features/settings/integrations/hooks/use-has-usable-ai-provider"; import { getOrpcErrorMessage } from "@/libs/error-message"; import { orpc } from "@/libs/orpc/client"; type NewThreadSetupProps = { resumeId?: string; }; function providerLabel(provider: { label: string; provider: AIProvider; model: string }) { return `${provider.label} · ${provider.provider} · ${provider.model}`; } function isAgentConfigError(error: unknown) { if (!error || typeof error !== "object") return false; const message = (error as { message?: unknown }).message; const status = (error as { status?: unknown; code?: unknown }).status ?? (error as { code?: unknown }).code; if (status === "PRECONDITION_FAILED" || status === 412) return true; return typeof message === "string" && /REDIS_URL|ENCRYPTION_SECRET/.test(message); } export function NewThreadSetup({ resumeId }: NewThreadSetupProps) { const isClient = useIsClient(); const navigate = useNavigate(); const { usableProviders, isLoading: isLoadingProviders, error: providersError } = useHasUsableAiProvider(); const { data: resumes, isLoading: isLoadingResumes } = useQuery( orpc.resume.list.queryOptions({ input: { sort: "lastUpdatedAt", tags: [] } }), ); const { mutate: createThread, isPending } = useMutation(orpc.agent.threads.create.mutationOptions()); const [aiProviderIdOverride, setAiProviderIdOverride] = useState(undefined); const [sourceResumeIdOverride, setSourceResumeIdOverride] = useState(undefined); const aiProviderId = aiProviderIdOverride ?? usableProviders[0]?.id ?? null; const sourceResumeId = sourceResumeIdOverride ?? resumeId ?? null; const providerOptions = usableProviders.map((provider) => ({ value: provider.id, label: providerLabel(provider), keywords: [provider.label, provider.provider, provider.model], })); const resumeOptions = [ { value: "__scratch__", label: t`Create from scratch` }, ...(resumes?.map((resume) => ({ value: resume.id, label: resume.name, keywords: [resume.name, resume.slug, ...resume.tags], })) ?? []), ]; const selectedResumeValue = sourceResumeId ?? "__scratch__"; const canCreate = !!aiProviderId && usableProviders.length > 0; if (!isClient) return null; return (

Start a thread

Choose a model and resume draft.

{providersError ? (
{isAgentConfigError(providersError) ? ( AI agent setup is unavailable until REDIS_URL and ENCRYPTION_SECRET are configured. ) : ( AI agent setup is unavailable right now. Please try again in a moment. )}
) : null}
{providerOptions.length === 0 && !isLoadingProviders ? (
Set up an AI provider to get started.
) : null}
setSourceResumeIdOverride(value && value !== "__scratch__" ? value : null)} />
{sourceResumeId ? Duplicate as AI Draft : Blank draft}
); }