diff --git a/ui/client-app/src/App.js b/ui/client-app/src/App.js index 8a9f0d9..ae36ee6 100644 --- a/ui/client-app/src/App.js +++ b/ui/client-app/src/App.js @@ -2,6 +2,7 @@ import Navbar from "./components/Navbar"; import HomePage from "./pages/HomePage"; // Import the new HomePage import VoiceChatPage from "./pages/VoiceChatPage"; +import CodingAssistantPage from "./pages/CodingAssistantPage"; import LoginPage from "./pages/LoginPage"; const Icon = ({ path, onClick, className }) => ( @@ -34,6 +35,8 @@ return ; case "voice-chat": return ; + case "coding-assistant" : + return case "login": return ; default: diff --git a/ui/client-app/src/App.js b/ui/client-app/src/App.js index 8a9f0d9..ae36ee6 100644 --- a/ui/client-app/src/App.js +++ b/ui/client-app/src/App.js @@ -2,6 +2,7 @@ import Navbar from "./components/Navbar"; import HomePage from "./pages/HomePage"; // Import the new HomePage import VoiceChatPage from "./pages/VoiceChatPage"; +import CodingAssistantPage from "./pages/CodingAssistantPage"; import LoginPage from "./pages/LoginPage"; const Icon = ({ path, onClick, className }) => ( @@ -34,6 +35,8 @@ return ; case "voice-chat": return ; + case "coding-assistant" : + return case "login": return ; default: diff --git a/ui/client-app/src/components/ChatArea.js b/ui/client-app/src/components/ChatArea.js new file mode 100644 index 0000000..be3cf35 --- /dev/null +++ b/ui/client-app/src/components/ChatArea.js @@ -0,0 +1,57 @@ +// src/components/ChatArea.js +import React, { useState, useRef, useEffect } from "react"; +import ChatWindow from "./ChatWindow"; + +const ChatArea = ({ chatHistory, onSendMessage, isProcessing }) => { + const [inputValue, setInputValue] = useState(""); + const inputRef = useRef(null); + + const handleSendMessage = (e) => { + e.preventDefault(); + if (inputValue.trim() !== "") { + onSendMessage(inputValue); + setInputValue(""); + } + }; + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [isProcessing]); + + return ( +
+ {/* Chat history display area */} +
+ +
+ + {/* Message input and send button area */} +
+ setInputValue(e.target.value)} + disabled={isProcessing} + className="flex-grow p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" + placeholder={isProcessing ? "AI is thinking..." : "Type a message..."} + /> + +
+
+ ); +}; + +export default ChatArea; \ No newline at end of file diff --git a/ui/client-app/src/App.js b/ui/client-app/src/App.js index 8a9f0d9..ae36ee6 100644 --- a/ui/client-app/src/App.js +++ b/ui/client-app/src/App.js @@ -2,6 +2,7 @@ import Navbar from "./components/Navbar"; import HomePage from "./pages/HomePage"; // Import the new HomePage import VoiceChatPage from "./pages/VoiceChatPage"; +import CodingAssistantPage from "./pages/CodingAssistantPage"; import LoginPage from "./pages/LoginPage"; const Icon = ({ path, onClick, className }) => ( @@ -34,6 +35,8 @@ return ; case "voice-chat": return ; + case "coding-assistant" : + return case "login": return ; default: diff --git a/ui/client-app/src/components/ChatArea.js b/ui/client-app/src/components/ChatArea.js new file mode 100644 index 0000000..be3cf35 --- /dev/null +++ b/ui/client-app/src/components/ChatArea.js @@ -0,0 +1,57 @@ +// src/components/ChatArea.js +import React, { useState, useRef, useEffect } from "react"; +import ChatWindow from "./ChatWindow"; + +const ChatArea = ({ chatHistory, onSendMessage, isProcessing }) => { + const [inputValue, setInputValue] = useState(""); + const inputRef = useRef(null); + + const handleSendMessage = (e) => { + e.preventDefault(); + if (inputValue.trim() !== "") { + onSendMessage(inputValue); + setInputValue(""); + } + }; + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [isProcessing]); + + return ( +
+ {/* Chat history display area */} +
+ +
+ + {/* Message input and send button area */} +
+ setInputValue(e.target.value)} + disabled={isProcessing} + className="flex-grow p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" + placeholder={isProcessing ? "AI is thinking..." : "Type a message..."} + /> + +
+
+ ); +}; + +export default ChatArea; \ No newline at end of file diff --git a/ui/client-app/src/components/CodeFolderAccess.js b/ui/client-app/src/components/CodeFolderAccess.js new file mode 100644 index 0000000..37d4617 --- /dev/null +++ b/ui/client-app/src/components/CodeFolderAccess.js @@ -0,0 +1,42 @@ +// src/components/CodeFolderAccess.js +import React from "react"; + +const CodeFolderAccess = ({ selectedFolder, onSelectFolder }) => { + const handleFolderChange = async () => { + // This is a placeholder. In a real application, you would use a browser API + // or a desktop framework like Electron to access the file system. + // Example using the File System Access API (requires user gesture) + try { + const directoryHandle = await window.showDirectoryPicker(); + const folderPath = directoryHandle.name; // Simplified path, you might get a full path in a real app + onSelectFolder(folderPath); + } catch (err) { + console.error("Failed to select directory:", err); + // You might want to handle user cancellation here + } + }; + + return ( +
+ {/* Button to trigger the folder selection dialog */} + + + {/* Display the selected folder path */} +
+

+ Selected Folder: +

+

+ {selectedFolder || "No folder selected"} +

+
+
+ ); +}; + +export default CodeFolderAccess; \ No newline at end of file diff --git a/ui/client-app/src/App.js b/ui/client-app/src/App.js index 8a9f0d9..ae36ee6 100644 --- a/ui/client-app/src/App.js +++ b/ui/client-app/src/App.js @@ -2,6 +2,7 @@ import Navbar from "./components/Navbar"; import HomePage from "./pages/HomePage"; // Import the new HomePage import VoiceChatPage from "./pages/VoiceChatPage"; +import CodingAssistantPage from "./pages/CodingAssistantPage"; import LoginPage from "./pages/LoginPage"; const Icon = ({ path, onClick, className }) => ( @@ -34,6 +35,8 @@ return ; case "voice-chat": return ; + case "coding-assistant" : + return case "login": return ; default: diff --git a/ui/client-app/src/components/ChatArea.js b/ui/client-app/src/components/ChatArea.js new file mode 100644 index 0000000..be3cf35 --- /dev/null +++ b/ui/client-app/src/components/ChatArea.js @@ -0,0 +1,57 @@ +// src/components/ChatArea.js +import React, { useState, useRef, useEffect } from "react"; +import ChatWindow from "./ChatWindow"; + +const ChatArea = ({ chatHistory, onSendMessage, isProcessing }) => { + const [inputValue, setInputValue] = useState(""); + const inputRef = useRef(null); + + const handleSendMessage = (e) => { + e.preventDefault(); + if (inputValue.trim() !== "") { + onSendMessage(inputValue); + setInputValue(""); + } + }; + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [isProcessing]); + + return ( +
+ {/* Chat history display area */} +
+ +
+ + {/* Message input and send button area */} +
+ setInputValue(e.target.value)} + disabled={isProcessing} + className="flex-grow p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" + placeholder={isProcessing ? "AI is thinking..." : "Type a message..."} + /> + +
+
+ ); +}; + +export default ChatArea; \ No newline at end of file diff --git a/ui/client-app/src/components/CodeFolderAccess.js b/ui/client-app/src/components/CodeFolderAccess.js new file mode 100644 index 0000000..37d4617 --- /dev/null +++ b/ui/client-app/src/components/CodeFolderAccess.js @@ -0,0 +1,42 @@ +// src/components/CodeFolderAccess.js +import React from "react"; + +const CodeFolderAccess = ({ selectedFolder, onSelectFolder }) => { + const handleFolderChange = async () => { + // This is a placeholder. In a real application, you would use a browser API + // or a desktop framework like Electron to access the file system. + // Example using the File System Access API (requires user gesture) + try { + const directoryHandle = await window.showDirectoryPicker(); + const folderPath = directoryHandle.name; // Simplified path, you might get a full path in a real app + onSelectFolder(folderPath); + } catch (err) { + console.error("Failed to select directory:", err); + // You might want to handle user cancellation here + } + }; + + return ( +
+ {/* Button to trigger the folder selection dialog */} + + + {/* Display the selected folder path */} +
+

+ Selected Folder: +

+

+ {selectedFolder || "No folder selected"} +

+
+
+ ); +}; + +export default CodeFolderAccess; \ No newline at end of file diff --git a/ui/client-app/src/components/InteractionLog.js b/ui/client-app/src/components/InteractionLog.js new file mode 100644 index 0000000..4057b18 --- /dev/null +++ b/ui/client-app/src/components/InteractionLog.js @@ -0,0 +1,40 @@ +// src/components/InteractionLog.js +import React from "react"; + +const InteractionLog = ({ logs }) => { + return ( +
+
+ {logs.length > 0 ? ( + logs.map((log, index) => ( +
+

+ {log.type.charAt(0).toUpperCase() + log.type.slice(1)}: +

+
+                {log.message}
+              
+
+ )) + ) : ( +

+ No interactions yet. +

+ )} +
+
+ ); +}; + +export default InteractionLog; \ No newline at end of file diff --git a/ui/client-app/src/App.js b/ui/client-app/src/App.js index 8a9f0d9..ae36ee6 100644 --- a/ui/client-app/src/App.js +++ b/ui/client-app/src/App.js @@ -2,6 +2,7 @@ import Navbar from "./components/Navbar"; import HomePage from "./pages/HomePage"; // Import the new HomePage import VoiceChatPage from "./pages/VoiceChatPage"; +import CodingAssistantPage from "./pages/CodingAssistantPage"; import LoginPage from "./pages/LoginPage"; const Icon = ({ path, onClick, className }) => ( @@ -34,6 +35,8 @@ return ; case "voice-chat": return ; + case "coding-assistant" : + return case "login": return ; default: diff --git a/ui/client-app/src/components/ChatArea.js b/ui/client-app/src/components/ChatArea.js new file mode 100644 index 0000000..be3cf35 --- /dev/null +++ b/ui/client-app/src/components/ChatArea.js @@ -0,0 +1,57 @@ +// src/components/ChatArea.js +import React, { useState, useRef, useEffect } from "react"; +import ChatWindow from "./ChatWindow"; + +const ChatArea = ({ chatHistory, onSendMessage, isProcessing }) => { + const [inputValue, setInputValue] = useState(""); + const inputRef = useRef(null); + + const handleSendMessage = (e) => { + e.preventDefault(); + if (inputValue.trim() !== "") { + onSendMessage(inputValue); + setInputValue(""); + } + }; + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [isProcessing]); + + return ( +
+ {/* Chat history display area */} +
+ +
+ + {/* Message input and send button area */} +
+ setInputValue(e.target.value)} + disabled={isProcessing} + className="flex-grow p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" + placeholder={isProcessing ? "AI is thinking..." : "Type a message..."} + /> + +
+
+ ); +}; + +export default ChatArea; \ No newline at end of file diff --git a/ui/client-app/src/components/CodeFolderAccess.js b/ui/client-app/src/components/CodeFolderAccess.js new file mode 100644 index 0000000..37d4617 --- /dev/null +++ b/ui/client-app/src/components/CodeFolderAccess.js @@ -0,0 +1,42 @@ +// src/components/CodeFolderAccess.js +import React from "react"; + +const CodeFolderAccess = ({ selectedFolder, onSelectFolder }) => { + const handleFolderChange = async () => { + // This is a placeholder. In a real application, you would use a browser API + // or a desktop framework like Electron to access the file system. + // Example using the File System Access API (requires user gesture) + try { + const directoryHandle = await window.showDirectoryPicker(); + const folderPath = directoryHandle.name; // Simplified path, you might get a full path in a real app + onSelectFolder(folderPath); + } catch (err) { + console.error("Failed to select directory:", err); + // You might want to handle user cancellation here + } + }; + + return ( +
+ {/* Button to trigger the folder selection dialog */} + + + {/* Display the selected folder path */} +
+

+ Selected Folder: +

+

+ {selectedFolder || "No folder selected"} +

+
+
+ ); +}; + +export default CodeFolderAccess; \ No newline at end of file diff --git a/ui/client-app/src/components/InteractionLog.js b/ui/client-app/src/components/InteractionLog.js new file mode 100644 index 0000000..4057b18 --- /dev/null +++ b/ui/client-app/src/components/InteractionLog.js @@ -0,0 +1,40 @@ +// src/components/InteractionLog.js +import React from "react"; + +const InteractionLog = ({ logs }) => { + return ( +
+
+ {logs.length > 0 ? ( + logs.map((log, index) => ( +
+

+ {log.type.charAt(0).toUpperCase() + log.type.slice(1)}: +

+
+                {log.message}
+              
+
+ )) + ) : ( +

+ No interactions yet. +

+ )} +
+
+ ); +}; + +export default InteractionLog; \ No newline at end of file diff --git a/ui/client-app/src/components/Navbar.js b/ui/client-app/src/components/Navbar.js index 13220ea..0166164 100644 --- a/ui/client-app/src/components/Navbar.js +++ b/ui/client-app/src/components/Navbar.js @@ -4,6 +4,7 @@ const navItems = [ { name: "Home", icon: "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" , page: "home" }, { name: "Voice Chat", icon:"M12 1a3 3 0 0 1 3 3v7a3 3 0 1 1-6 0V4a3 3 0 0 1 3-3zm5 10a5 5 0 0 1-10 0H5a7 7 0 0 0 14 0h-2zm-5 11v-4h-2v4h2z", page: "voice-chat" }, + { name: "Coding Assistant", icon: "M9 16l-4-4 4-4M15 16l4-4-4-4", page: "coding-assistant" }, { name: "History", icon: "M22 12h-4l-3 9L9 3l-3 9H2", page: "history" , disabled: true}, { name: "Favorites", icon: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z", page: "favorites" , disabled: true}, ]; diff --git a/ui/client-app/src/App.js b/ui/client-app/src/App.js index 8a9f0d9..ae36ee6 100644 --- a/ui/client-app/src/App.js +++ b/ui/client-app/src/App.js @@ -2,6 +2,7 @@ import Navbar from "./components/Navbar"; import HomePage from "./pages/HomePage"; // Import the new HomePage import VoiceChatPage from "./pages/VoiceChatPage"; +import CodingAssistantPage from "./pages/CodingAssistantPage"; import LoginPage from "./pages/LoginPage"; const Icon = ({ path, onClick, className }) => ( @@ -34,6 +35,8 @@ return ; case "voice-chat": return ; + case "coding-assistant" : + return case "login": return ; default: diff --git a/ui/client-app/src/components/ChatArea.js b/ui/client-app/src/components/ChatArea.js new file mode 100644 index 0000000..be3cf35 --- /dev/null +++ b/ui/client-app/src/components/ChatArea.js @@ -0,0 +1,57 @@ +// src/components/ChatArea.js +import React, { useState, useRef, useEffect } from "react"; +import ChatWindow from "./ChatWindow"; + +const ChatArea = ({ chatHistory, onSendMessage, isProcessing }) => { + const [inputValue, setInputValue] = useState(""); + const inputRef = useRef(null); + + const handleSendMessage = (e) => { + e.preventDefault(); + if (inputValue.trim() !== "") { + onSendMessage(inputValue); + setInputValue(""); + } + }; + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [isProcessing]); + + return ( +
+ {/* Chat history display area */} +
+ +
+ + {/* Message input and send button area */} +
+ setInputValue(e.target.value)} + disabled={isProcessing} + className="flex-grow p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" + placeholder={isProcessing ? "AI is thinking..." : "Type a message..."} + /> + +
+
+ ); +}; + +export default ChatArea; \ No newline at end of file diff --git a/ui/client-app/src/components/CodeFolderAccess.js b/ui/client-app/src/components/CodeFolderAccess.js new file mode 100644 index 0000000..37d4617 --- /dev/null +++ b/ui/client-app/src/components/CodeFolderAccess.js @@ -0,0 +1,42 @@ +// src/components/CodeFolderAccess.js +import React from "react"; + +const CodeFolderAccess = ({ selectedFolder, onSelectFolder }) => { + const handleFolderChange = async () => { + // This is a placeholder. In a real application, you would use a browser API + // or a desktop framework like Electron to access the file system. + // Example using the File System Access API (requires user gesture) + try { + const directoryHandle = await window.showDirectoryPicker(); + const folderPath = directoryHandle.name; // Simplified path, you might get a full path in a real app + onSelectFolder(folderPath); + } catch (err) { + console.error("Failed to select directory:", err); + // You might want to handle user cancellation here + } + }; + + return ( +
+ {/* Button to trigger the folder selection dialog */} + + + {/* Display the selected folder path */} +
+

+ Selected Folder: +

+

+ {selectedFolder || "No folder selected"} +

+
+
+ ); +}; + +export default CodeFolderAccess; \ No newline at end of file diff --git a/ui/client-app/src/components/InteractionLog.js b/ui/client-app/src/components/InteractionLog.js new file mode 100644 index 0000000..4057b18 --- /dev/null +++ b/ui/client-app/src/components/InteractionLog.js @@ -0,0 +1,40 @@ +// src/components/InteractionLog.js +import React from "react"; + +const InteractionLog = ({ logs }) => { + return ( +
+
+ {logs.length > 0 ? ( + logs.map((log, index) => ( +
+

+ {log.type.charAt(0).toUpperCase() + log.type.slice(1)}: +

+
+                {log.message}
+              
+
+ )) + ) : ( +

+ No interactions yet. +

+ )} +
+
+ ); +}; + +export default InteractionLog; \ No newline at end of file diff --git a/ui/client-app/src/components/Navbar.js b/ui/client-app/src/components/Navbar.js index 13220ea..0166164 100644 --- a/ui/client-app/src/components/Navbar.js +++ b/ui/client-app/src/components/Navbar.js @@ -4,6 +4,7 @@ const navItems = [ { name: "Home", icon: "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" , page: "home" }, { name: "Voice Chat", icon:"M12 1a3 3 0 0 1 3 3v7a3 3 0 1 1-6 0V4a3 3 0 0 1 3-3zm5 10a5 5 0 0 1-10 0H5a7 7 0 0 0 14 0h-2zm-5 11v-4h-2v4h2z", page: "voice-chat" }, + { name: "Coding Assistant", icon: "M9 16l-4-4 4-4M15 16l4-4-4-4", page: "coding-assistant" }, { name: "History", icon: "M22 12h-4l-3 9L9 3l-3 9H2", page: "history" , disabled: true}, { name: "Favorites", icon: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z", page: "favorites" , disabled: true}, ]; diff --git a/ui/client-app/src/hooks/useCodeAssistant.js b/ui/client-app/src/hooks/useCodeAssistant.js new file mode 100644 index 0000000..1be2c81 --- /dev/null +++ b/ui/client-app/src/hooks/useCodeAssistant.js @@ -0,0 +1,146 @@ +// src/hooks/useCodeAssistant.js +import { useState, useEffect, useRef, useCallback } from "react"; + +const useCodeAssistant = ({ pageContainerRef }) => { + 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); + const [errorMessage, setErrorMessage] = useState(""); + const [showErrorModal, setShowErrorModal] = useState(false); + + const ws = useRef(null); + + // Initialize WebSocket connection + useEffect(() => { + // Replace with your WebSocket server URL + const websocketUrl = "wss://your-llm-assistant-server.com"; + ws.current = new WebSocket(websocketUrl); + + ws.current.onopen = () => { + console.log("WebSocket connected"); + setConnectionStatus("connected"); + }; + + ws.current.onmessage = (event) => { + const message = JSON.parse(event.data); + handleIncomingMessage(message); + }; + + ws.current.onclose = () => { + console.log("WebSocket disconnected"); + setConnectionStatus("disconnected"); + setIsProcessing(false); + }; + + ws.current.onerror = (error) => { + console.error("WebSocket error:", error); + setConnectionStatus("error"); + setErrorMessage("WebSocket connection failed. Please check the server."); + setShowErrorModal(true); + }; + + return () => { + ws.current.close(); + }; + }, []); + + // Handle incoming messages from the server + const handleIncomingMessage = useCallback((message) => { + switch (message.type) { + case "chat_message": + setChatHistory((prevHistory) => [...prevHistory, { isUser: false, text: message.content }]); + setIsProcessing(false); + break; + case "thinking_log": + setThinkingProcess((prevProcess) => [ + ...prevProcess, + { type: message.subtype, message: message.content }, + ]); + break; + case "file_lookup_request": + // In a real app, this would trigger a local file read and send the content back. + // For this hook, we just log the request. + setThinkingProcess((prevProcess) => [ + ...prevProcess, + { type: "system", message: `AI requested file: ${message.filename}` }, + ]); + // To send a file back: + // ws.current.send(JSON.stringify({ type: "file_content", content: "..." })); + break; + case "error": + setErrorMessage(message.content); + setShowErrorModal(true); + setIsProcessing(false); + break; + case "status_update": + setIsProcessing(message.processing); + setIsPaused(message.paused); + setConnectionStatus(message.status); + break; + default: + console.log("Unknown message type:", message); + } + }, []); + + // Send a chat message to the server + const handleSendChat = useCallback( + (text) => { + if (ws.current && ws.current.readyState === WebSocket.OPEN) { + setChatHistory((prevHistory) => [...prevHistory, { isUser: true, text }]); + setIsProcessing(true); + ws.current.send(JSON.stringify({ type: "chat_message", content: text })); + } + }, + [] + ); + + // Send the selected folder path to the server + const handleSelectFolder = useCallback( + (folderPath) => { + if (ws.current && ws.current.readyState === WebSocket.OPEN) { + setSelectedFolder(folderPath); + ws.current.send(JSON.stringify({ type: "select_folder", path: folderPath })); + setThinkingProcess((prevProcess) => [ + ...prevProcess, + { type: "user", message: `Selected local folder: ${folderPath}` }, + ]); + } + }, + [] + ); + + // Pause the AI's processing + const handlePause = useCallback(() => { + if (ws.current && ws.current.readyState === WebSocket.OPEN) { + ws.current.send(JSON.stringify({ type: "control", command: "pause" })); + } + }, []); + + // Stop the AI's processing + const handleStop = useCallback(() => { + if (ws.current && ws.current.readyState === WebSocket.OPEN) { + ws.current.send(JSON.stringify({ type: "control", command: "stop" })); + } + }, []); + + return { + chatHistory, + thinkingProcess, + selectedFolder, + connectionStatus, + isProcessing, + isPaused, + errorMessage, + showErrorModal, + handleSendChat, + handleSelectFolder, + handlePause, + handleStop, + setShowErrorModal, + }; +}; + +export default useCodeAssistant; \ No newline at end of file diff --git a/ui/client-app/src/App.js b/ui/client-app/src/App.js index 8a9f0d9..ae36ee6 100644 --- a/ui/client-app/src/App.js +++ b/ui/client-app/src/App.js @@ -2,6 +2,7 @@ import Navbar from "./components/Navbar"; import HomePage from "./pages/HomePage"; // Import the new HomePage import VoiceChatPage from "./pages/VoiceChatPage"; +import CodingAssistantPage from "./pages/CodingAssistantPage"; import LoginPage from "./pages/LoginPage"; const Icon = ({ path, onClick, className }) => ( @@ -34,6 +35,8 @@ return ; case "voice-chat": return ; + case "coding-assistant" : + return case "login": return ; default: diff --git a/ui/client-app/src/components/ChatArea.js b/ui/client-app/src/components/ChatArea.js new file mode 100644 index 0000000..be3cf35 --- /dev/null +++ b/ui/client-app/src/components/ChatArea.js @@ -0,0 +1,57 @@ +// src/components/ChatArea.js +import React, { useState, useRef, useEffect } from "react"; +import ChatWindow from "./ChatWindow"; + +const ChatArea = ({ chatHistory, onSendMessage, isProcessing }) => { + const [inputValue, setInputValue] = useState(""); + const inputRef = useRef(null); + + const handleSendMessage = (e) => { + e.preventDefault(); + if (inputValue.trim() !== "") { + onSendMessage(inputValue); + setInputValue(""); + } + }; + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [isProcessing]); + + return ( +
+ {/* Chat history display area */} +
+ +
+ + {/* Message input and send button area */} +
+ setInputValue(e.target.value)} + disabled={isProcessing} + className="flex-grow p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" + placeholder={isProcessing ? "AI is thinking..." : "Type a message..."} + /> + +
+
+ ); +}; + +export default ChatArea; \ No newline at end of file diff --git a/ui/client-app/src/components/CodeFolderAccess.js b/ui/client-app/src/components/CodeFolderAccess.js new file mode 100644 index 0000000..37d4617 --- /dev/null +++ b/ui/client-app/src/components/CodeFolderAccess.js @@ -0,0 +1,42 @@ +// src/components/CodeFolderAccess.js +import React from "react"; + +const CodeFolderAccess = ({ selectedFolder, onSelectFolder }) => { + const handleFolderChange = async () => { + // This is a placeholder. In a real application, you would use a browser API + // or a desktop framework like Electron to access the file system. + // Example using the File System Access API (requires user gesture) + try { + const directoryHandle = await window.showDirectoryPicker(); + const folderPath = directoryHandle.name; // Simplified path, you might get a full path in a real app + onSelectFolder(folderPath); + } catch (err) { + console.error("Failed to select directory:", err); + // You might want to handle user cancellation here + } + }; + + return ( +
+ {/* Button to trigger the folder selection dialog */} + + + {/* Display the selected folder path */} +
+

+ Selected Folder: +

+

+ {selectedFolder || "No folder selected"} +

+
+
+ ); +}; + +export default CodeFolderAccess; \ No newline at end of file diff --git a/ui/client-app/src/components/InteractionLog.js b/ui/client-app/src/components/InteractionLog.js new file mode 100644 index 0000000..4057b18 --- /dev/null +++ b/ui/client-app/src/components/InteractionLog.js @@ -0,0 +1,40 @@ +// src/components/InteractionLog.js +import React from "react"; + +const InteractionLog = ({ logs }) => { + return ( +
+
+ {logs.length > 0 ? ( + logs.map((log, index) => ( +
+

+ {log.type.charAt(0).toUpperCase() + log.type.slice(1)}: +

+
+                {log.message}
+              
+
+ )) + ) : ( +

+ No interactions yet. +

+ )} +
+
+ ); +}; + +export default InteractionLog; \ No newline at end of file diff --git a/ui/client-app/src/components/Navbar.js b/ui/client-app/src/components/Navbar.js index 13220ea..0166164 100644 --- a/ui/client-app/src/components/Navbar.js +++ b/ui/client-app/src/components/Navbar.js @@ -4,6 +4,7 @@ const navItems = [ { name: "Home", icon: "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" , page: "home" }, { name: "Voice Chat", icon:"M12 1a3 3 0 0 1 3 3v7a3 3 0 1 1-6 0V4a3 3 0 0 1 3-3zm5 10a5 5 0 0 1-10 0H5a7 7 0 0 0 14 0h-2zm-5 11v-4h-2v4h2z", page: "voice-chat" }, + { name: "Coding Assistant", icon: "M9 16l-4-4 4-4M15 16l4-4-4-4", page: "coding-assistant" }, { name: "History", icon: "M22 12h-4l-3 9L9 3l-3 9H2", page: "history" , disabled: true}, { name: "Favorites", icon: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z", page: "favorites" , disabled: true}, ]; diff --git a/ui/client-app/src/hooks/useCodeAssistant.js b/ui/client-app/src/hooks/useCodeAssistant.js new file mode 100644 index 0000000..1be2c81 --- /dev/null +++ b/ui/client-app/src/hooks/useCodeAssistant.js @@ -0,0 +1,146 @@ +// src/hooks/useCodeAssistant.js +import { useState, useEffect, useRef, useCallback } from "react"; + +const useCodeAssistant = ({ pageContainerRef }) => { + 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); + const [errorMessage, setErrorMessage] = useState(""); + const [showErrorModal, setShowErrorModal] = useState(false); + + const ws = useRef(null); + + // Initialize WebSocket connection + useEffect(() => { + // Replace with your WebSocket server URL + const websocketUrl = "wss://your-llm-assistant-server.com"; + ws.current = new WebSocket(websocketUrl); + + ws.current.onopen = () => { + console.log("WebSocket connected"); + setConnectionStatus("connected"); + }; + + ws.current.onmessage = (event) => { + const message = JSON.parse(event.data); + handleIncomingMessage(message); + }; + + ws.current.onclose = () => { + console.log("WebSocket disconnected"); + setConnectionStatus("disconnected"); + setIsProcessing(false); + }; + + ws.current.onerror = (error) => { + console.error("WebSocket error:", error); + setConnectionStatus("error"); + setErrorMessage("WebSocket connection failed. Please check the server."); + setShowErrorModal(true); + }; + + return () => { + ws.current.close(); + }; + }, []); + + // Handle incoming messages from the server + const handleIncomingMessage = useCallback((message) => { + switch (message.type) { + case "chat_message": + setChatHistory((prevHistory) => [...prevHistory, { isUser: false, text: message.content }]); + setIsProcessing(false); + break; + case "thinking_log": + setThinkingProcess((prevProcess) => [ + ...prevProcess, + { type: message.subtype, message: message.content }, + ]); + break; + case "file_lookup_request": + // In a real app, this would trigger a local file read and send the content back. + // For this hook, we just log the request. + setThinkingProcess((prevProcess) => [ + ...prevProcess, + { type: "system", message: `AI requested file: ${message.filename}` }, + ]); + // To send a file back: + // ws.current.send(JSON.stringify({ type: "file_content", content: "..." })); + break; + case "error": + setErrorMessage(message.content); + setShowErrorModal(true); + setIsProcessing(false); + break; + case "status_update": + setIsProcessing(message.processing); + setIsPaused(message.paused); + setConnectionStatus(message.status); + break; + default: + console.log("Unknown message type:", message); + } + }, []); + + // Send a chat message to the server + const handleSendChat = useCallback( + (text) => { + if (ws.current && ws.current.readyState === WebSocket.OPEN) { + setChatHistory((prevHistory) => [...prevHistory, { isUser: true, text }]); + setIsProcessing(true); + ws.current.send(JSON.stringify({ type: "chat_message", content: text })); + } + }, + [] + ); + + // Send the selected folder path to the server + const handleSelectFolder = useCallback( + (folderPath) => { + if (ws.current && ws.current.readyState === WebSocket.OPEN) { + setSelectedFolder(folderPath); + ws.current.send(JSON.stringify({ type: "select_folder", path: folderPath })); + setThinkingProcess((prevProcess) => [ + ...prevProcess, + { type: "user", message: `Selected local folder: ${folderPath}` }, + ]); + } + }, + [] + ); + + // Pause the AI's processing + const handlePause = useCallback(() => { + if (ws.current && ws.current.readyState === WebSocket.OPEN) { + ws.current.send(JSON.stringify({ type: "control", command: "pause" })); + } + }, []); + + // Stop the AI's processing + const handleStop = useCallback(() => { + if (ws.current && ws.current.readyState === WebSocket.OPEN) { + ws.current.send(JSON.stringify({ type: "control", command: "stop" })); + } + }, []); + + return { + chatHistory, + thinkingProcess, + selectedFolder, + connectionStatus, + isProcessing, + isPaused, + errorMessage, + showErrorModal, + handleSendChat, + handleSelectFolder, + handlePause, + handleStop, + setShowErrorModal, + }; +}; + +export default useCodeAssistant; \ No newline at end of file diff --git a/ui/client-app/src/pages/CodingAssistantPage.js b/ui/client-app/src/pages/CodingAssistantPage.js new file mode 100644 index 0000000..05532d5 --- /dev/null +++ b/ui/client-app/src/pages/CodingAssistantPage.js @@ -0,0 +1,95 @@ +import React, { useState, useRef, useEffect, useCallback } from "react"; +// Import the components you'll create for each section +import ChatArea from "../components/ChatArea"; +import CodeFolderAccess from "../components/CodeFolderAccess"; +import InteractionLog from "../components/InteractionLog"; +// import Controls from "../components/Controls"; + +// A custom hook to manage WebSocket connection and state +import useCodeAssistant from "../hooks/useCodeAssistant"; + +const CodeAssistantPage = () => { + // Reference for the main container to manage scrolling + const pageContainerRef = useRef(null); + + // Use a custom hook to handle the core logic + const { + chatHistory, + thinkingProcess, + connectionStatus, + selectedFolder, + isProcessing, + isPaused, + errorMessage, + showErrorModal, + handleSendChat, + handleSelectFolder, + handlePause, + handleStop, + setShowErrorModal, + } = useCodeAssistant({ pageContainerRef }); + + // Scroll to the bottom of the page when new content is added + useEffect(() => { + if (pageContainerRef.current) { + pageContainerRef.current.scrollTop = pageContainerRef.current.scrollHeight; + } + }, [chatHistory, thinkingProcess]); + + return ( +
+ {/* Main content area */} +
+
+ {/* Left Column: Chat and Code Access */} +
+ {/* Area 1: Chat with LLM */} +
+

Chat with the LLM

+ +
+ + {/* Area 2: Code Folder Lookup */} +
+

Code Folder Access

+ +
+
+ + {/* Right Column: Thinking Process and Interaction Log */} +
+

AI Thinking Process

+ +
+
+
+ + {/* Controls at the bottom + */} + + {/* Error Modal */} + {showErrorModal && ( +
+
+

Error

+

{errorMessage}

+ +
+
+ )} +
+ ); +}; + +export default CodeAssistantPage; \ No newline at end of file