"use client"; import { useEffect, useState } from "react"; import { completeTask, createTask, failTask, fireTaskNow, listTasks, parseTask, type ParseTaskResult, type Task, type TaskType, } from "../../lib/api"; const CHARACTER_ID = "seed-character-genki"; const USER_ID = "seed-user-primary"; const TASK_TYPE_LABELS: Record = { ONE_TIME: "單次提醒", RECURRING: "週期提醒", CONDITIONAL: "條件提醒", QUERY_ORGANIZE: "查資料/整理", TODO_TRACKING: "代辦追蹤", }; const STATUS_LABELS: Record = { PENDING: "尚未觸發", TRIGGERED: "已觸發,等候回應", COMPLETED: "已完成", FAILED: "失敗", CANCELLED: "已取消", }; function isOverdue(task: Task): boolean { return task.status === "TRIGGERED" && task.overdueNudgeCount > 0; } // J-8 委託介面:自然語言輸入→(辨識成功即直接建立/辨識失敗則手動表單建立)→列表→觸發→完成或失敗, // 對應計畫§委託子系統「角色不只陪聊,還能接受委託」。 export function TasksView() { const [text, setText] = useState(""); const [tasks, setTasks] = useState([]); const [parseResult, setParseResult] = useState(null); const [manualDraft, setManualDraft] = useState<{ content: string; triggerAt: string } | null>(null); const [pending, setPending] = useState(false); const [error, setError] = useState(null); async function refresh() { try { setTasks(await listTasks(CHARACTER_ID, USER_ID)); } catch { setError("無法連線到 api 服務,委託清單暫時無法顯示"); } } useEffect(() => { void refresh(); }, []); async function handleParse() { const trimmed = text.trim(); if (!trimmed || pending) { return; } setPending(true); setError(null); setParseResult(null); setManualDraft(null); try { const result = await parseTask(CHARACTER_ID, USER_ID, trimmed); setParseResult(result); if (result.created) { setText(""); await refresh(); } else { // 辨識失敗:退回手動表單,讓使用者自己補齊內容後透過 createTask() 建立。 setManualDraft({ content: trimmed, triggerAt: "" }); } } catch { setError("委託解析失敗,請確認 api 服務是否已啟動"); } finally { setPending(false); } } async function handleManualCreate() { if (!manualDraft || !manualDraft.content.trim() || pending) { return; } setPending(true); setError(null); try { await createTask(CHARACTER_ID, USER_ID, { type: "TODO_TRACKING", content: manualDraft.content.trim(), rawText: text.trim() || manualDraft.content.trim(), triggerAt: manualDraft.triggerAt ? new Date(manualDraft.triggerAt).toISOString() : undefined, }); setText(""); setManualDraft(null); setParseResult(null); await refresh(); } catch { setError("委託建立失敗,請確認 api 服務是否已啟動"); } finally { setPending(false); } } async function handleAction(action: (taskId: string) => Promise, taskId: string) { setPending(true); setError(null); try { await action(taskId); await refresh(); } catch { setError("操作失敗,請確認 api 服務是否已啟動"); } finally { setPending(false); } } return (
setText(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") { void handleParse(); } }} placeholder="三點提醒我開會" className="flex-1 rounded-full border bg-transparent px-3 py-2 text-sm outline-none" style={{ borderColor: "var(--line)", color: "var(--ink)" }} disabled={pending} />
{parseResult?.created && (

已建立委託:{parseResult.task?.content} {parseResult.performanceLine ? `——${parseResult.performanceLine}` : ""}

)} {manualDraft && (

沒辨識出明確時間,改用手動表單建立(分類為「代辦追蹤」):

setManualDraft({ ...manualDraft, content: event.target.value })} placeholder="委託內容" className="rounded-full border bg-transparent px-3 py-1.5" style={{ borderColor: "var(--line)", color: "var(--ink)" }} /> setManualDraft({ ...manualDraft, triggerAt: event.target.value })} className="rounded-full border bg-transparent px-3 py-1.5" style={{ borderColor: "var(--line)", color: "var(--ink)" }} />
)} {error && (

{error}

)}
{tasks.length === 0 && (

目前沒有委託。

)} {tasks.map((task) => (
{task.content} {isOverdue(task) ? "逾期未回應" : STATUS_LABELS[task.status]}
{TASK_TYPE_LABELS[task.type]} {task.triggerAt ? `.${new Date(task.triggerAt).toLocaleString("zh-TW")}` : ""} {task.triggerMessage && 她說:{task.triggerMessage}} {(task.status === "PENDING" || task.status === "TRIGGERED") && (
{task.status === "PENDING" && ( )}
)}
))}
); }