summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/routes')
-rw-r--r--makima/frontend/src/routes/files.tsx88
-rw-r--r--makima/frontend/src/routes/listen.tsx277
-rw-r--r--makima/frontend/src/routes/mesh.tsx37
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>