"use client"; import { useCallback, useMemo, useState } from "react"; import { extractSuggestedTasks } from "@/lib/extraction-client"; import { StateNotice } from "@/components/StateNotice"; import { Button, Card } from "@/components/ui/Primitives"; import { createNoteTask } from "@/lib/notes-client"; import { toast } from "@/lib/toast"; import { getEmptyState, toUserFacingState, type UserFacingState } from "@/lib/user-facing-states"; import type { NoteTask } from "@/lib/types"; export function ExtractedTasksPanel({ noteId, workspaceId, noteBody, persistedTasks, onTaskAccepted, }: { noteId: string; workspaceId: string; noteBody: string; persistedTasks: NoteTask[]; onTaskAccepted: () => void; }) { const [proposals, setProposals] = useState([]); const [scanning, setScanning] = useState(false); const [acceptingId, setAcceptingId] = useState(null); const [noticeState, setNoticeState] = useState(null); const persistedTitles = useMemo( () => new Set(persistedTasks.map((t) => t.title.trim().toLowerCase())), [persistedTasks], ); const handleScan = useCallback(async () => { setScanning(true); setNoticeState(null); try { const extracted = await extractSuggestedTasks(noteBody); const filtered = extracted.filter((t) => !persistedTitles.has(t.title.trim().toLowerCase())); setProposals(filtered); if (filtered.length === 0) { setNoticeState(getEmptyState("extraction", "suggested tasks")); } } catch (e) { const state = toUserFacingState(e, "extraction"); setNoticeState(state); toast.error(state.message); } finally { setScanning(false); } }, [noteBody, persistedTitles]); const handleAccept = useCallback( async (task: NoteTask) => { setAcceptingId(task.id); try { await createNoteTask({ id: crypto.randomUUID(), workspaceId, noteId, title: task.title, source: "extracted", }); setProposals((p) => p.filter((x) => x.id !== task.id)); toast.success("Task added"); onTaskAccepted(); } catch (e) { const state = toUserFacingState(e, "backend"); setNoticeState(state); toast.error(state.message); } finally { setAcceptingId(null); } }, [noteId, workspaceId, onTaskAccepted], ); const handleDismiss = useCallback((taskId: string) => { setProposals((p) => p.filter((x) => x.id !== taskId)); }, []); return (
Suggested tasks (AI)

Runs extraction on demand. Accept adds a backend task; dismiss only hides the suggestion for this session.

{noticeState ? ( void handleScan() : undefined} /> ) : null} {proposals.length === 0 ? null : (
    {proposals.map((task) => (
  • {task.title}
  • ))}
)}
); }