diff options
| author | soryu <soryu@soryu.co> | 2026-05-17 21:22:34 +0100 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-05-17 21:22:34 +0100 |
| commit | 857e717e6343fa5c2ae96664bdc64741d5ba6830 (patch) | |
| tree | 0f3898d9e2e2a3c312358dbf70c44f4ab1cf3648 /makima/frontend/src/routes | |
| parent | ce29ae801bcc5a0ba76d5a8d1565242ab267a47d (diff) | |
| download | soryu-857e717e6343fa5c2ae96664bdc64741d5ba6830.tar.gz soryu-857e717e6343fa5c2ae96664bdc64741d5ba6830.zip | |
chore: remove LLM module + all dependent surfacesremove-llm
Wholesale removal of the LLM integration layer. ~14,200 LOC deleted
across backend and frontend. All chat-driven UIs go with it.
## Backend
- Delete `src/llm/` (7,400 LOC): claude/groq clients, contract_tools,
contract_evaluator, discuss_tools, mesh_tools, phase_guidance,
task_output, templates, markdown round-trip, tools, transcript_analyzer.
- Delete handlers wholly dependent on LLM:
- `chat.rs` (file-level LLM chat at /files/{id}/chat)
- `mesh_chat.rs` (mesh & task LLM chat + history)
- `templates.rs` (/contract-types listing)
- Strip LLM uses from `mesh_daemon.rs`:
- `compute_action_directive` (used phase_guidance::check_deliverables_met
to nudge supervisors with "all tasks done" messages). The auto-PR
path below still fires when all tasks finish, so no behaviour lost.
- `crate::llm::markdown_to_body` → inline 1-line replacement that
wraps markdown content in a single BodyElement::Markdown. The
editor re-parses on display, so round-trip is preserved.
- Drop routes: /files/{id}/chat, /mesh/chat, /mesh/chat/history,
/mesh/tasks/{id}/chat, /contract-types.
- Drop the matching openapi registrations.
## Frontend
- Delete components that were LLM-only:
- `mesh/UnifiedMeshChatInput.tsx`
- `listen/DiscussContractModal.tsx`
- `listen/TranscriptAnalysisPanel.tsx`
- `listen/ContractPickerModal.tsx`
- `files/CliInput.tsx`
- Delete the entire /listen page (its primary value-add was
voice → LLM analysis → contract creation; without LLM the page is
just a transcript display with no obvious user purpose).
- Delete `hooks/useMeshChatHistory.ts` and `lib/listenApi.ts`
(transcript-analysis API client to the already-Phase-5-removed
listen handlers).
- Strip api.ts of LLM exports: LlmModel, ChatMessage/Request/Response,
UserQuestion/Answer, chatWithFile, MeshChat* types & functions,
getMeshChatHistory, clearMeshChatHistory, chatWithMeshContext,
ContractTypeTemplate, listContractTypes, chatWithContract,
getContractChatHistory, clearContractChatHistory, discussContract,
PhaseDefinition, DeliverableDefinition.
- mesh.tsx: drop UnifiedMeshChatInput render + the chatContext memo +
handleTaskUpdatedFromCli (only consumer was the input).
- files.tsx: drop CliInput render + handleGenerateFromElement +
handleBodyUpdate + handleClearFocus + suggestedPrompt state (all
CliInput-only).
- NavStrip: drop the /listen link.
- main.tsx: drop the /listen route.
## Net diff: 37 files changed, 58 insertions, 14,281 deletions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'makima/frontend/src/routes')
| -rw-r--r-- | makima/frontend/src/routes/files.tsx | 88 | ||||
| -rw-r--r-- | makima/frontend/src/routes/listen.tsx | 277 | ||||
| -rw-r--r-- | makima/frontend/src/routes/mesh.tsx | 37 |
3 files changed, 17 insertions, 385 deletions
diff --git a/makima/frontend/src/routes/files.tsx b/makima/frontend/src/routes/files.tsx index b232aa0..5d757a4 100644 --- a/makima/frontend/src/routes/files.tsx +++ b/makima/frontend/src/routes/files.tsx @@ -3,7 +3,6 @@ import { useParams, useNavigate } from "react-router"; import { Masthead } from "../components/Masthead"; import { FileList } from "../components/files/FileList"; import { FileDetail, type FocusedElement } from "../components/files/FileDetail"; -import { CliInput } from "../components/files/CliInput"; import { ConflictNotification } from "../components/files/ConflictNotification"; import { UpdateNotification } from "../components/files/UpdateNotification"; import { useFiles } from "../hooks/useFiles"; @@ -57,7 +56,6 @@ function FilesPageContent() { const [remoteUpdate, setRemoteUpdate] = useState<FileUpdateEvent | null>(null); const [remoteFileData, setRemoteFileData] = useState<FileDetailType | null>(null); const [focusedElement, setFocusedElement] = useState<FocusedElement | null>(null); - const [suggestedPrompt, setSuggestedPrompt] = useState<string | null>(null); const [createdTask, setCreatedTask] = useState<Task | null>(null); // Contract selection modal state for task creation const [showContractModal, setShowContractModal] = useState(false); @@ -246,18 +244,8 @@ function FilesPageContent() { [editFile, fileDetail, updateHasLocalChanges] ); - const handleBodyUpdate = useCallback( - (body: BodyElement[], summary: string | null) => { - if (fileDetail) { - setFileDetail({ - ...fileDetail, - body, - summary, - }); - } - }, - [fileDetail] - ); + // handleBodyUpdate was the CliInput callback for AI-driven body + // rewrites. CliInput is gone with the LLM module. const handleBodyElementUpdate = useCallback( async (index: number, element: BodyElement) => { @@ -423,9 +411,7 @@ function FilesPageContent() { setFocusedElement(element); }, []); - const handleClearFocus = useCallback(() => { - setFocusedElement(null); - }, []); + // handleClearFocus was passed to CliInput; both gone now. // Convert element to a different type const handleConvertElement = useCallback( @@ -506,55 +492,10 @@ function FilesPageContent() { [fileDetail, id, editFile, updateHasLocalChanges, focusedElement] ); - // Generate from element - focus on it and pre-fill a prompt - const handleGenerateFromElement = useCallback( - (index: number, action: string) => { - if (!fileDetail) return; - - const element = fileDetail.body[index]; - if (!element) return; - - // Get preview text - let preview = ""; - switch (element.type) { - case "heading": - case "paragraph": - preview = element.text.slice(0, 50); - break; - case "code": - preview = element.content.slice(0, 50); - break; - case "list": - preview = element.items[0]?.slice(0, 40) || ""; - break; - default: - preview = "Element"; - } - - // Focus on the element - setFocusedElement({ - index, - type: element.type, - preview: preview + (preview.length >= 50 ? "..." : ""), - }); - - // Set suggested prompt based on action - let prompt = ""; - switch (action) { - case "elaborate": - prompt = "Elaborate and expand on this content"; - break; - case "summarize": - prompt = "Summarize this content"; - break; - case "extract_actions": - prompt = "Extract action items from this content"; - break; - } - setSuggestedPrompt(prompt); - }, - [fileDetail] - ); + // handleGenerateFromElement was an LLM elaborate/summarise/extract + // affordance that piped a suggested prompt into CliInput. Both + // CliInput and the LLM module are gone; the handler + its prop on + // FileDetail are removed. // Create a mesh task from an element - shows contract selection modal const handleCreateTaskFromElement = useCallback( @@ -762,7 +703,6 @@ function FilesPageContent() { onBodyElementDelete={handleBodyElementDelete} onBodyElementDuplicate={handleBodyElementDuplicate} onConvertElement={handleConvertElement} - onGenerateFromElement={handleGenerateFromElement} onCreateTaskFromElement={handleCreateTaskFromElement} onEditingChange={updateIsActivelyEditing} hasPendingRemoteUpdate={!!remoteUpdate} @@ -779,16 +719,10 @@ function FilesPageContent() { onClearVersionSelection={clearSelectedVersion} /> </div> - <div className="shrink-0"> - <CliInput - fileId={id} - onUpdate={handleBodyUpdate} - focusedElement={focusedElement} - onClearFocus={handleClearFocus} - suggestedPrompt={suggestedPrompt} - onClearSuggestedPrompt={() => setSuggestedPrompt(null)} - /> - </div> + {/* CliInput (file-level LLM chat) removed alongside the LLM + module. The file detail view is now a pure read/edit + surface; AI-driven file editing was the primary value of + this composer and went with the LLM removal. */} </div> ) : id && detailLoading ? ( <div className="panel h-full flex items-center justify-center"> diff --git a/makima/frontend/src/routes/listen.tsx b/makima/frontend/src/routes/listen.tsx deleted file mode 100644 index a53cbd9..0000000 --- a/makima/frontend/src/routes/listen.tsx +++ /dev/null @@ -1,277 +0,0 @@ -import { useState, useCallback, useMemo, useEffect, useRef } from "react"; -import { Masthead } from "../components/Masthead"; -import { SpeakerPanel } from "../components/listen/SpeakerPanel"; -import { TranscriptPanel } from "../components/listen/TranscriptPanel"; -import { ControlPanel, type ContractOption } from "../components/listen/ControlPanel"; -import { TranscriptAnalysisPanel } from "../components/listen/TranscriptAnalysisPanel"; -import { DiscussContractModal } from "../components/listen/DiscussContractModal"; -import { useMicrophone } from "../hooks/useMicrophone"; -import { useWebSocket } from "../hooks/useWebSocket"; -import { listContracts, type CreatedContractInfo } from "../lib/api"; -import { useAuth } from "../contexts/AuthContext"; - -export default function ListenPage() { - const [isListening, setIsListening] = useState(false); - const [activeSpeaker, setActiveSpeaker] = useState<string | null>(null); - const [permissionRequested, setPermissionRequested] = useState(false); - const isListeningRef = useRef(false); - - // Contract selection state - const [contracts, setContracts] = useState<ContractOption[]>([]); - const [selectedContractId, setSelectedContractId] = useState<string | null>(null); - const [contractsLoading, setContractsLoading] = useState(true); - const { session, isAuthenticated } = useAuth(); - - // Saved transcript state for analysis - const [savedTranscript, setSavedTranscript] = useState<{ - fileId: string; - contractId: string; - } | null>(null); - - // Discuss contract modal state - const [isDiscussModalOpen, setIsDiscussModalOpen] = useState(false); - - // Fetch contracts on mount - useEffect(() => { - if (!isAuthenticated) { - setContractsLoading(false); - return; - } - - async function fetchContracts() { - try { - const response = await listContracts(); - setContracts( - response.contracts.map((c) => ({ - id: c.id, - name: c.name, - })) - ); - } catch (err) { - console.error("Failed to fetch contracts:", err); - } finally { - setContractsLoading(false); - } - } - fetchContracts(); - }, [isAuthenticated]); - - // Keep ref in sync with state for use in callbacks - useEffect(() => { - isListeningRef.current = isListening; - }, [isListening]); - - const ws = useWebSocket({ - onTranscript: (transcript) => { - // Track active speaker - if (!transcript.isFinal) { - setActiveSpeaker(transcript.speaker); - } - }, - onStopped: () => { - setIsListening(false); - setActiveSpeaker(null); - }, - onTranscriptSaved: (fileId, contractId) => { - // Store the saved transcript info for analysis - setSavedTranscript({ fileId, contractId }); - }, - }); - - const wsRef = useRef(ws); - useEffect(() => { - wsRef.current = ws; - }, [ws]); - - const handleAudioData = useCallback((samples: Float32Array) => { - if (wsRef.current.isConnected && isListeningRef.current) { - wsRef.current.sendAudio(samples); - } - }, []); - - const mic = useMicrophone({ - sampleRate: 16000, - onAudioData: handleAudioData, - }); - - // Request microphone permission on page load - useEffect(() => { - if (!permissionRequested) { - setPermissionRequested(true); - mic.requestPermission(); - } - }, [permissionRequested, mic.requestPermission]); - - // Derive unique speakers from transcripts - const speakers = useMemo(() => { - const speakerSet = new Set<string>(); - ws.transcripts.forEach((t) => speakerSet.add(t.speaker)); - - return Array.from(speakerSet).map((speaker) => ({ - id: speaker, - label: speaker, - isActive: speaker === activeSpeaker, - })); - }, [ws.transcripts, activeSpeaker]); - - // Clear active speaker after a delay - useEffect(() => { - if (activeSpeaker) { - const timer = setTimeout(() => setActiveSpeaker(null), 1500); - return () => clearTimeout(timer); - } - }, [activeSpeaker]); - - const handleToggle = useCallback(async () => { - if (isListening) { - // Stop listening - mic.stop(); - ws.stopSession("user_stopped"); - setIsListening(false); - setActiveSpeaker(null); - return; - } - - // If permission was denied or errored, try requesting again - if (mic.status === "denied" || mic.status === "error") { - const permitted = await mic.requestPermission(); - if (!permitted) { - return; - } - } - - // Start listening - start the microphone - const micStarted = await mic.start(); - if (!micStarted) { - // Microphone permission denied or error - return; - } - - // Microphone started, now connect to WebSocket - const connected = await ws.connect(); - if (!connected) { - // Connection failed - stop the microphone - mic.stop(); - return; - } - - // Both microphone and WebSocket are ready - start the session - // Pass contract_id and auth token if available - const authToken = session?.access_token || null; - ws.startSession(mic.sampleRate, mic.channels, selectedContractId, authToken); - setIsListening(true); - }, [isListening, mic, ws, selectedContractId, session]); - - const handleNew = useCallback(() => { - // Stop current session - backend auto-saves transcript on disconnect - mic.stop(); - if (ws.isConnected) { - ws.stopSession("new_session"); - } - ws.clearTranscripts(); - ws.disconnect(); - setIsListening(false); - setActiveSpeaker(null); - setSavedTranscript(null); - }, [mic, ws]); - - const handleCloseAnalysis = useCallback(() => { - setSavedTranscript(null); - }, []); - - // Get current transcript context for discussion - const transcriptContext = useMemo(() => { - if (ws.transcripts.length === 0) return undefined; - return ws.transcripts - .map(t => `[${t.speaker}]: ${t.text}`) - .join("\n"); - }, [ws.transcripts]); - - const handleOpenDiscussModal = useCallback(() => { - setIsDiscussModalOpen(true); - }, []); - - const handleContractCreated = useCallback((contract: CreatedContractInfo) => { - // Add to contracts list and select it - setContracts(prev => [ - { id: contract.id, name: contract.name }, - ...prev, - ]); - setSelectedContractId(contract.id); - // Close the modal after a short delay to show success - setTimeout(() => setIsDiscussModalOpen(false), 2000); - }, []); - - const error = ws.error || mic.error; - - return ( - <div className="relative z-10 h-screen flex flex-col overflow-hidden"> - <Masthead showTicker={false} showNav /> - - <main className="flex-1 p-4 md:p-6 grid grid-cols-1 md:grid-cols-[300px_1fr] grid-rows-[minmax(0,1fr)_auto] md:grid-rows-[minmax(0,1fr)_auto] gap-4 min-h-0 overflow-hidden"> - {/* Speaker Panel - top left on desktop, hidden on mobile */} - <div className="hidden md:block row-span-1 min-h-0 overflow-hidden"> - <SpeakerPanel speakers={speakers} /> - </div> - - {/* Transcript Panel - right side, spans 2 rows on desktop */} - <div className="md:row-span-2 min-h-0 overflow-hidden"> - <TranscriptPanel transcripts={ws.transcripts} /> - </div> - - {/* Control Panel - bottom left on desktop */} - <div className="md:col-start-1 md:row-start-2 shrink-0"> - <ControlPanel - isListening={isListening} - isConnected={ws.isConnected} - micStatus={mic.status} - micVolume={mic.volume} - hasTranscripts={ws.transcripts.length > 0} - onToggle={handleToggle} - onNew={handleNew} - error={error} - contracts={contracts} - selectedContractId={selectedContractId} - onContractChange={setSelectedContractId} - onDiscussContract={handleOpenDiscussModal} - contractsLoading={contractsLoading} - connectionStatus={ws.status} - /> - </div> - </main> - - {/* Transcript Analysis Panel - shown after recording stops and transcript is saved */} - {savedTranscript && !isListening && ( - <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"> - <div className="w-full max-w-2xl mx-4 max-h-[90vh] overflow-hidden"> - <TranscriptAnalysisPanel - fileId={savedTranscript.fileId} - contractId={savedTranscript.contractId} - selectedContractId={selectedContractId} - onContractCreated={(response) => { - // Refresh contracts list and select the new contract - setContracts((prev) => [ - { id: response.contractId, name: response.contractName }, - ...prev, - ]); - setSelectedContractId(response.contractId); - }} - onContractUpdated={() => { - // Keep the current selection - }} - onClose={handleCloseAnalysis} - /> - </div> - </div> - )} - - {/* Discuss Contract Modal */} - <DiscussContractModal - isOpen={isDiscussModalOpen} - onClose={() => setIsDiscussModalOpen(false)} - transcriptContext={transcriptContext} - onContractCreated={handleContractCreated} - /> - </div> - ); -} diff --git a/makima/frontend/src/routes/mesh.tsx b/makima/frontend/src/routes/mesh.tsx index f210227..362b2e0 100644 --- a/makima/frontend/src/routes/mesh.tsx +++ b/makima/frontend/src/routes/mesh.tsx @@ -4,11 +4,10 @@ import { Masthead } from "../components/Masthead"; import { TaskList } from "../components/mesh/TaskList"; import { TaskDetail } from "../components/mesh/TaskDetail"; import { TaskOutput } from "../components/mesh/TaskOutput"; -import { UnifiedMeshChatInput } from "../components/mesh/UnifiedMeshChatInput"; import { ContractCompleteQuestion } from "../components/mesh/ContractCompleteQuestion"; import { useTasks } from "../hooks/useTasks"; import { useTaskSubscription, type TaskUpdateEvent, type TaskOutputEvent } from "../hooks/useTaskSubscription"; -import type { TaskWithSubtasks, MeshChatContext, ContractSummary, ContractWithRelations, DaemonDirectory, TaskSummary, RepositoryHistoryEntry } from "../lib/api"; +import type { TaskWithSubtasks, ContractSummary, ContractWithRelations, DaemonDirectory, TaskSummary, RepositoryHistoryEntry } from "../lib/api"; import { startTask as startTaskApi, stopTask as stopTaskApi, getTaskOutput, listContracts, getContract, getDaemonDirectories, continueTask as continueTaskApi, resumeSupervisor, branchTask, getRepositorySuggestions, getTaskDiff } from "../lib/api"; import { DirectoryInput } from "../components/mesh/DirectoryInput"; import { useAuth } from "../contexts/AuthContext"; @@ -650,28 +649,8 @@ export default function MeshPage() { setShowRepoSuggestions(false); }, []); - // Callback when task is updated via CLI - const handleTaskUpdatedFromCli = useCallback(async () => { - if (id) { - const updated = await fetchTask(id); - if (updated) { - setTaskDetail(updated); - } - } - // Also refresh the task list - fetchTasks(); - }, [id, fetchTask, fetchTasks]); - - // Calculate chat context based on current view - const chatContext: MeshChatContext = useMemo(() => { - if (!id) { - return { type: "mesh" }; - } - if (taskDetail?.parentTaskId) { - return { type: "subtask", taskId: id, parentTaskId: taskDetail.parentTaskId }; - } - return { type: "task", taskId: id }; - }, [id, taskDetail?.parentTaskId]); + // handleTaskUpdatedFromCli + chatContext drove the deleted + // UnifiedMeshChatInput; gone with the LLM module. // Handle resizing of the split panel const handleResizeStart = useCallback((e: MouseEvent) => { @@ -904,13 +883,9 @@ export default function MeshPage() { </div> )} - {/* Mesh Chat Input - always rendered to persist state across navigation */} - <div className="shrink-0"> - <UnifiedMeshChatInput - context={chatContext} - onUpdate={id ? handleTaskUpdatedFromCli : fetchTasks} - /> - </div> + {/* UnifiedMeshChatInput removed alongside the LLM module. The + mesh page is now a pure task viewer; spawning / editing + tasks via natural language chat went with LLM removal. */} </div> </main> |
