import { useState, useEffect, useRef, useCallback } from "react";
import { connectToWebSocket } from "../services/websocket";
import { v4 as uuidv4 } from 'uuid';
const useCodeAssistant = ({ pageContainerRef }) => {
// State variables for the assistant's UI and status
const [chatHistory, setChatHistory] = useState([]);
const [thinkingProcess, setThinkingProcess] = useState([]);
const [selectedFolder, setSelectedFolder] = useState(null);
const [connectionStatus, setConnectionStatus] = useState("disconnected");
const [isProcessing, setIsProcessing] = useState(false);
const [isPaused, setIsPaused] = useState(false); // Corrected this line
const [errorMessage, setErrorMessage] = useState("");
const [showErrorModal, setShowErrorModal] = useState(false);
const [sessionId, setSessionId] = useState(null);
// Refs for the WebSocket connection and directory handle
const ws = useRef(null);
const initialized = useRef(false);
const dirHandleRef = useRef(null);
// --- WebSocket Message Handlers ---
const handleChatMessage = useCallback((message) => {
setChatHistory((prev) => [...prev, { isUser: false, text: message.content }]);
setIsProcessing(false);
}, []);
const handleThinkingLog = useCallback((message) => {
setThinkingProcess((prev) => [
...prev,
{ type: "remote", message: message.content, round: message.round },
]);
}, []);
const handleError = useCallback((message) => {
setErrorMessage(message.content);
setShowErrorModal(true);
setIsProcessing(false);
}, []);
const handleStatusUpdate = useCallback((message) => {
setIsProcessing(message.processing);
setIsPaused(message.paused); // Corrected this line
setConnectionStatus(message.status);
}, []);
const handleListDirectoryRequest = useCallback(async (message) => {
const { request_id, round } = message;
const dirHandle = dirHandleRef.current;
if (!dirHandle) {
const errorMsg = "No folder selected by user.";
console.warn(errorMsg);
ws.current.send(JSON.stringify({ type: "error", content: errorMsg, request_id }));
return;
}
// setThinkingProcess((prev) => [
// ...prev,
// { type: "system", message: `Scanning directory...`, round },
// ]);
try {
const files = [];
// Recursive function to walk through directories
async function walkDirectory(handle, path = '') {
for await (const entry of handle.values()) {
const entryPath = `${path}/${entry.name}`;
if (entry.kind === "file") {
const file = await entry.getFile();
files.push({
name: file.name,
path: entryPath,
size: file.size,
lastModified: file.lastModified,
});
} else if (entry.kind === "directory") {
await walkDirectory(entry, entryPath); // Recurse into subdirectory
}
}
}
await walkDirectory(dirHandle);
ws.current.send(
JSON.stringify({
type: "list_directory_response",
files,
request_id,
round,
session_id: sessionId,
})
);
setThinkingProcess((prev) => [
...prev,
{
type: "local",
message: `Sent list of files (${files.length}) to server.`,
round,
},
]);
} catch (error) {
console.error("Failed to list directory:", error);
ws.current.send(
JSON.stringify({
type: "error",
content: "Failed to access folder contents.",
request_id,
round,
session_id: sessionId,
})
);
}
}, [sessionId]);
const handleReadFilesRequest = useCallback(async (message) => {
console.log(message);
const { filenames, request_id, round } = message;
const dirHandle = dirHandleRef.current;
if (!dirHandle) {
ws.current.send(JSON.stringify({ type: "error", content: "No folder selected.", request_id, round, session_id: sessionId }));
return;
}
setThinkingProcess((prev) => [
...prev,
{ type: "local", message: `Reading content of ${filenames.length} files...`, round },
]);
const filesData = [];
for (const filename of filenames) {
try {
const fileHandle = await dirHandle.getFileHandle(filename);
const file = await fileHandle.getFile();
const content = await file.text();
filesData.push({ filename, content });
setThinkingProcess((prev) => [
...prev,
{ type: "local", message: `Read file: ${filename}`, round },
]);
} catch (error) {
console.error(`Failed to read file ${filename}:`, error);
ws.current.send(JSON.stringify({
type: "error",
content: `Could not read file: ${filename}`,
request_id,
round,
session_id: sessionId,
}));
}
}
ws.current.send(JSON.stringify({
type: "file_content_response",
files: filesData,
request_id,
round,
session_id: sessionId,
}));
setThinkingProcess((prev) => [
...prev,
{ type: "local", message: `Sent content for ${filesData.length} files to server.`, round },
]);
}, [sessionId]);
const handleExecuteCommandRequest = useCallback((message) => {
const { command, request_id, round } = message;
const output = `Simulated output for command: '${command}'`;
ws.current.send(JSON.stringify({
type: "execute_command_response",
command,
output,
request_id,
round,
session_id: sessionId,
}));
setThinkingProcess((prev) => [
...prev,
{ type: "system", message: `Simulated execution of command: '${command}'`, round },
]);
}, [sessionId]);
// Main message handler that routes messages to the correct function
const handleIncomingMessage = useCallback((message) => {
switch (message.type) {
case "chat_message":
handleChatMessage(message);
break;
case "thinking_log":
handleThinkingLog(message);
break;
case "error":
handleError(message);
break;
case "status_update":
handleStatusUpdate(message);
break;
case "list_directory":
handleListDirectoryRequest(message);
break;
case "get_file_content":
handleReadFilesRequest(message);
break;
case "execute_command":
handleExecuteCommandRequest(message);
break;
default:
console.log("Unknown message type:", message);
}
}, [handleChatMessage, handleThinkingLog, handleError, handleStatusUpdate, handleListDirectoryRequest, handleReadFilesRequest, handleExecuteCommandRequest]);
// --- WebSocket Connection Setup ---
useEffect(() => {
if (initialized.current) return;
initialized.current = true;
const setupConnection = async () => {
try {
const { ws: newWs, sessionId: newSessionId } = await connectToWebSocket(
handleIncomingMessage,
() => setConnectionStatus("connected"),
() => {
setConnectionStatus("disconnected");
setIsProcessing(false);
},
(error) => {
setConnectionStatus("error");
setErrorMessage(`Failed to connect: ${error.message}`);
setShowErrorModal(true);
}
);
ws.current = newWs;
setSessionId(newSessionId);
} catch (error) {
console.error("Setup failed:", error);
}
};
setupConnection();
return () => {
if (ws.current) {
ws.current.close();
}
};
}, [handleIncomingMessage]);
// Send chat message to server
const handleSendChat = useCallback(async (text) => {
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
setChatHistory((prev) => [...prev, { isUser: true, text }]);
setIsProcessing(true);
// Removed the extra call to getSessionId as it is already in state
ws.current.send(JSON.stringify({ type: "chat_message",content: text,round:0, session_id: sessionId }));
}
}, [sessionId]);
// Open folder picker and store handle
const handleSelectFolder = useCallback(async (directoryHandle) => {
if (!window.showDirectoryPicker) {
return;
}
try {
dirHandleRef.current = directoryHandle;
setSelectedFolder(directoryHandle.name);
// Send the initial message to the server with a unique request_id
const request_id = uuidv4();
ws.current.send(JSON.stringify({ type: "select_folder_response", path: directoryHandle.name, request_id, session_id: sessionId }));
setThinkingProcess((prev) => [
...prev,
{ type: "user", message: `Selected local folder: ${directoryHandle.name}` },
]);
} catch (error) {
console.error("Folder selection canceled or failed:", error);
}
}, [sessionId]);
// Control functions
const handlePause = useCallback(() => {
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
ws.current.send(JSON.stringify({ type: "control", command: "pause", session_id: sessionId }));
}
}, [sessionId]);
const handleStop = useCallback(() => {
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
ws.current.send(JSON.stringify({ type: "control", command: "stop", session_id: sessionId }));
}
}, [sessionId]);
return {
chatHistory,
thinkingProcess,
selectedFolder,
connectionStatus,
isProcessing,
isPaused,
errorMessage,
showErrorModal,
handleSendChat,
handleSelectFolder,
handlePause,
handleStop,
setShowErrorModal,
};
};
export default useCodeAssistant;