"use client"; import { useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Dna, ArrowRight, CircleNotch as LoaderCircle, FileText, CheckCircle as CircleCheck, Circle, ShareNetwork } from '@phosphor-icons/react'; import toast from 'react-hot-toast'; import { ClayToggle } from "@/components/ui/ClayToggle"; import { ClaySegmented } from "@/components/ui/ClaySegmented"; import { CriticalButton } from "@/components/ui/CriticalButton"; import { PipelineResults } from "@/components/results/PipelineResults"; import { createShareLink, getPipelineStatusV2 } from "@/lib/api"; import { getSupabase } from "@/lib/supabase"; import { extractErrorMessage } from "@/lib/errors"; import { buildShareUrl, buildShareMessage, buildShareDetails, type ShareDetails } from "@/lib/share"; import { ShareDialog } from "@/components/share/ShareDialog"; type WizardStep = "input" | "running"; type AlignMode = "global" | "local"; function getStoredAlignMode(): AlignMode { if (typeof window === "undefined") return "global"; return sessionStorage.getItem("blast_align_mode") === "local" ? "local" : "global"; } const ALL_STEPS = [ { id: "blast", label: "BLAST & UniProt", essential: true }, { id: "msa", label: "Multiple Sequence Alignment", essential: false }, { id: "phylo", label: "Phylogenetic Tree", essential: false }, { id: "domains", label: "Domain Architecture", essential: false }, { id: "interpret",label: "AI Interpretation", essential: true }, ]; export function AnalysisWizard() { const [currentStep, setCurrentStep] = useState("input"); const [sequence, setSequence] = useState(""); const [jobId, setJobId] = useState(null); const [enabledSteps, setEnabledSteps] = useState( ALL_STEPS.filter(s => s.essential).map(s => s.id) ); const [alignMode, setAlignMode] = useState(getStoredAlignMode); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [done, setDone] = useState(false); const [shareOpen, setShareOpen] = useState(false); const [shareCreating, setShareCreating] = useState(false); const [shareLink, setShareLink] = useState(''); const [shareDetails, setShareDetails] = useState(undefined); function toggleStep(id: string) { setEnabledSteps(prev => prev.includes(id) ? prev.filter(s => s !== id) : [...prev, id] ); } async function handleSubmit() { if (!sequence.trim()) { setError("Paste a protein sequence first."); return; } setLoading(true); setError(null); try { const supabase = getSupabase(); const { data: { session } } = await supabase.auth.getSession(); const headers: Record = { "Content-Type": "application/json" }; if (session?.access_token) { headers["Authorization"] = `Bearer ${session.access_token}`; } const res = await fetch("/api/backend/api/pipeline/v2/run", { method: "POST", headers, body: JSON.stringify({ sequence: sequence.trim(), steps: enabledSteps, alignment_mode: alignMode }), }); if (!res.ok) throw new Error((await res.json()).detail || "Submit failed"); const data = await res.json(); setJobId(data.job_id); setCurrentStep("running"); } catch (e: any) { setError(e.message); } finally { setLoading(false); } } async function handleShare() { if (!jobId) return; setShareCreating(true); try { const { url } = await createShareLink(jobId); const link = buildShareUrl(url); let details; try { const status = await getPipelineStatusV2(jobId); details = buildShareDetails(status?.context ?? null); } catch { // Details are optional — share still works without them. } setShareLink(link); setShareDetails(details); setShareOpen(true); } catch (e) { toast.error(extractErrorMessage(e, 'Failed to create share link')); } finally { setShareCreating(false); } } return (
{currentStep === "running" ? : } Sequence Input
{done ? : currentStep === "running" ? : } Results
{currentStep === "input" ? (

Paste Your Sequence

FASTA or raw amino acid sequence. Choose which analyses to run.

{DEMOS.map(({ label, seq }) => ( ))}