From 87044a747b47bd83249d61a45842c7f7b2eae56d Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 11 Jan 2026 05:52:14 +0000 Subject: Contract system --- makima/frontend/src/routes/files.tsx | 221 ++++++++++++++++++++++++++++++----- 1 file changed, 194 insertions(+), 27 deletions(-) (limited to 'makima/frontend/src/routes/files.tsx') diff --git a/makima/frontend/src/routes/files.tsx b/makima/frontend/src/routes/files.tsx index 3ba2d52..6cfb3ca 100644 --- a/makima/frontend/src/routes/files.tsx +++ b/makima/frontend/src/routes/files.tsx @@ -12,8 +12,8 @@ import { useFileSubscription, type FileUpdateEvent, } from "../hooks/useFileSubscription"; -import type { FileDetail as FileDetailType, BodyElement, Task } from "../lib/api"; -import { createTask } from "../lib/api"; +import type { FileDetail as FileDetailType, BodyElement, Task, ContractSummary } from "../lib/api"; +import { createTask, listContracts } from "../lib/api"; import { useAuth } from "../contexts/AuthContext"; export default function FilesPage() { @@ -59,6 +59,14 @@ function FilesPageContent() { const [focusedElement, setFocusedElement] = useState(null); const [suggestedPrompt, setSuggestedPrompt] = useState(null); const [createdTask, setCreatedTask] = useState(null); + // Contract selection modal state for task creation + const [showContractModal, setShowContractModal] = useState(false); + const [contracts, setContracts] = useState([]); + const [contractsLoading, setContractsLoading] = useState(false); + const [pendingTaskData, setPendingTaskData] = useState<{ name: string; plan: string } | null>(null); + // Contract selection modal state for file creation + const [showFileContractModal, setShowFileContractModal] = useState(false); + const [pendingFileData, setPendingFileData] = useState<{ name: string; body?: BodyElement[] } | null>(null); const pendingUpdateRef = useRef(false); // Track the last version we sent to detect our own updates const lastSentVersionRef = useRef(null); @@ -548,10 +556,10 @@ function FilesPageContent() { [fileDetail] ); - // Create a mesh task from an element + // Create a mesh task from an element - shows contract selection modal const handleCreateTaskFromElement = useCallback( async (index: number, selectedText?: string) => { - if (!fileDetail) return; + if (!fileDetail || contractsLoading) return; const element = fileDetail.body[index]; if (!element) return; @@ -578,57 +586,98 @@ function FilesPageContent() { // Create a task name from the content const name = content.slice(0, 60) + (content.length > 60 ? "..." : ""); + // Store pending task data and show contract selection modal + setPendingTaskData({ name, plan: content }); + setContractsLoading(true); + try { + const response = await listContracts(); + setContracts(response.contracts); + setShowContractModal(true); + } catch (e) { + console.error("Failed to load contracts:", e); + } finally { + setContractsLoading(false); + } + }, + [fileDetail, contractsLoading] + ); + + // Create task with selected contract + const handleCreateTaskWithContract = useCallback( + async (contractId: string) => { + if (!pendingTaskData || !fileDetail) return; + setShowContractModal(false); try { const task = await createTask({ - name, - plan: content, + contractId, + name: pendingTaskData.name, + plan: pendingTaskData.plan, description: `Created from ${fileDetail.name}`, }); setCreatedTask(task); + setPendingTaskData(null); } catch (err) { console.error("Failed to create task:", err); } }, - [fileDetail] + [pendingTaskData, fileDetail] ); + // Open contract selection modal for file creation const handleCreate = useCallback(async () => { - if (creating) return; + if (creating || contractsLoading) return; + setContractsLoading(true); + try { + const response = await listContracts(); + setContracts(response.contracts); + setPendingFileData({ name: `Untitled ${new Date().toLocaleDateString()}` }); + setShowFileContractModal(true); + } catch (e) { + console.error("Failed to load contracts:", e); + } finally { + setContractsLoading(false); + } + }, [creating, contractsLoading]); + + // Create file with selected contract + const handleCreateFileWithContract = useCallback(async (contractId: string) => { + if (creating || !pendingFileData) return; + setShowFileContractModal(false); setCreating(true); try { const newFile = await saveFile({ - name: `Untitled ${new Date().toLocaleDateString()}`, + contractId, + name: pendingFileData.name, + body: pendingFileData.body, transcript: [], }); if (newFile) { + // If there's body content, update it + if (pendingFileData.body && pendingFileData.body.length > 0) { + await editFile(newFile.id, { body: pendingFileData.body, version: newFile.version }); + } navigate(`/files/${newFile.id}`); } } finally { setCreating(false); + setPendingFileData(null); } - }, [creating, saveFile, navigate]); + }, [creating, pendingFileData, saveFile, editFile, navigate]); const handleUploadMarkdown = useCallback(async (name: string, body: BodyElement[]) => { - if (creating) return; - setCreating(true); + if (creating || contractsLoading) return; + setContractsLoading(true); try { - const newFile = await saveFile({ - name, - transcript: [], - }); - if (newFile) { - // Update with the parsed body - const updated = await editFile(newFile.id, { body, version: newFile.version }); - if (updated) { - navigate(`/files/${updated.id}`); - } else { - navigate(`/files/${newFile.id}`); - } - } + const response = await listContracts(); + setContracts(response.contracts); + setPendingFileData({ name, body }); + setShowFileContractModal(true); + } catch (e) { + console.error("Failed to load contracts:", e); } finally { - setCreating(false); + setContractsLoading(false); } - }, [creating, saveFile, editFile, navigate]); + }, [creating, contractsLoading]); // Conflict resolution handlers const handleConflictReload = useCallback(async () => { @@ -808,6 +857,124 @@ function FilesPageContent() { )} + + {/* Contract Selection Modal for Task Creation */} + {showContractModal && ( +
+
+
+

Select Contract for Task

+ +
+
+ {contracts.length === 0 ? ( +
+

No contracts found. Create a contract first.

+ +
+ ) : ( +
+ {contracts.map((contract) => ( + + ))} +
+ )} +
+
+
+ )} + + {/* Contract Selection Modal for File Creation */} + {showFileContractModal && ( +
+
+
+

Select Contract for File

+ +
+
+ {contracts.length === 0 ? ( +
+

No contracts found. Create a contract first.

+ +
+ ) : ( +
+ {contracts.map((contract) => ( + + ))} +
+ )} +
+
+
+ )} ); } -- cgit v1.2.3