import { useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { toast } from "sonner"; import { AlertCircle, ArrowLeft, Brain, CheckCircle2, History, Loader2, Sparkles, Target, } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Badge, BadgeProps } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { useProject } from "@/hooks/useProjects"; import { useResearchSessions, useStartResearch, } from "@/hooks/useResearch"; import { ResearchRunResult } from "@/lib/api/research"; const startResearchSchema = z.object({ source_name: z.string().min(1, "소스를 선택하세요"), url: z .string() .url("올바른 URL 형식이 아닙니다") .or(z.literal("")) .optional(), goal: z .string() .min(3, "최소 3자") .max(500, "최대 500자"), max_depth: z.number().int().min(0).max(10), max_steps: z.number().int().min(1).max(50), max_branch: z.number().int().min(1).max(30), min_relevance: z.number().min(0).max(1), same_domain_only: z.boolean(), }); type StartResearchFormValues = z.infer; function sessionStatusVariant(status?: string | null): BadgeProps["variant"] { switch (status) { case "completed": return "success"; case "failed": return "destructive"; case "canceled": return "secondary"; case "running": case "pending": return "default"; default: return "outline"; } } export default function ResearchPage() { const navigate = useNavigate(); const { projectId } = useParams<{ projectId: string }>(); const { t } = useTranslation(); const projectName = projectId ?? ""; const { data: project, isLoading: projectLoading, isError, error, refetch } = useProject(projectName); const sessions = useResearchSessions(projectName); const startResearch = useStartResearch(projectName); const [lastResult, setLastResult] = useState(null); const { register, handleSubmit, setValue, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(startResearchSchema), defaultValues: { source_name: "", url: "", goal: "Semantic ontology exploration", max_depth: 2, max_steps: 12, max_branch: 8, min_relevance: 0.35, same_domain_only: true, }, }); const onSubmit = async (values: StartResearchFormValues) => { try { setLastResult(null); const res = await startResearch.mutateAsync({ project_name: projectName, source_name: values.source_name, url: values.url || undefined, goal: values.goal, max_depth: values.max_depth, max_steps: values.max_steps, max_branch: values.max_branch, min_relevance: values.min_relevance, same_domain_only: values.same_domain_only, }); setLastResult(res); toast.success(t("research.completed", "자율 연구가 완료되었습니다")); } catch (e) { toast.error( t("research.failed", "실패: {{msg}}", { msg: (e as Error).message, }), ); } }; const sources = project?.sources ?? []; return (

{t("research.title", "자율 연구")}

{project && (

{project.name} {" "} · {project.domain}

)}
{isError && (
{(error as Error).message}
)}
{t("research.formTitle", "자율 연구 설정")} {t( "research.formDesc", "AI가 시드에서 시작해 스스로 링크를 따라가며 온톨로지를 확장합니다", )}
{projectLoading ? ( ) : ( )} {errors.source_name && (

{errors.source_name.message}

)}