diff --git a/ai-hub/app/core/pipelines/file_selector.py b/ai-hub/app/core/pipelines/file_selector.py index 0c421a4..aec19c3 100644 --- a/ai-hub/app/core/pipelines/file_selector.py +++ b/ai-hub/app/core/pipelines/file_selector.py @@ -1,18 +1,14 @@ import dspy import json -from typing import List -from app.db import models - -# Assuming SelectFiles and other necessary imports are defined as in the previous example +from typing import List, Dict, Any class SelectFiles(dspy.Signature): """ Based on the user's question, communication history, and the code folder's file list, identify the files that are most relevant to answer the question. """ question = dspy.InputField(desc="The user's current question.") - chat_history = dspy.InputField(desc="The ongoing dialogue between the user and the AI.") - code_folder_filename_list = dspy.InputField(desc="A JSON array of objects. Each object represents a file and contains its name, path, size, last modified timestamp, and created timestamp.") - answer = dspy.OutputField(format=list, desc="A list of strings containing the names of the most relevant files to examine further.") + retrieved_files = dspy.InputField(desc="A JSON object containing details about a retrieval request, including an array of file objects. Each file object contains its ID, path, name, content, type, and timestamps.") + answer = dspy.OutputField(format=list, desc="A list of strings containing the file paths of the most relevant files to examine further.") class CodeRagFileSelector(dspy.Module): """ @@ -20,27 +16,18 @@ """ def __init__(self): super().__init__() - self.select_files = dspy.Predict(SelectFiles) + # Assign the system prompt directly to the dspy.Predict instance. + self.select_files = dspy.Predict(SelectFiles, system_prompt="You are a helpful AI assistant. Based on the retrieved files and the user's question, determine which files are needed to answer the question. The content field for some files may not be empty, while others are. Your final answer must be a JSON array of strings containing the file paths. It's VERY IMPORTANT to ensure the answer is a parsable JSON array.") - async def forward(self, question: str, history: List[models.Message], file_list: List[dict]) -> List[str]: - # Format history for the signature - history_text = self._default_history_formatter(history) - - # Convert the list of dictionaries to a JSON string - file_list_json_string = json.dumps(file_list, indent=2) + async def forward(self, question: str, retrieved_data: Dict[str, Any]) -> List[str]: + # Convert the entire retrieved_data dictionary to a JSON string + retrieved_data_json_string = json.dumps(retrieved_data, indent=2) # Call the predictor with the necessary inputs prediction = await self.select_files.acall( question=question, - chat_history=history_text, - code_folder_filename_list=file_list_json_string + retrieved_files=retrieved_data_json_string ) - - # The output is expected to be a list of strings - return prediction.answer - def _default_history_formatter(self, history: List[models.Message]) -> str: - return "\n".join( - f"{'Human' if msg.sender == 'user' else 'Assistant'}: {msg.content}" - for msg in history - ) \ No newline at end of file + # The prediction.answer should now be a parsable list due to the prompt's instructions. + return prediction.answer \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/question_decider.py b/ai-hub/app/core/pipelines/question_decider.py new file mode 100644 index 0000000..d2c996f --- /dev/null +++ b/ai-hub/app/core/pipelines/question_decider.py @@ -0,0 +1,67 @@ +import dspy +import json +from typing import List, Dict, Any + +class QuestionDecider(dspy.Signature): + """ + Based on the user's question, chat history, and the content of the retrieved files, decide whether you have enough information to answer the question or if you need to request more files. + If you have enough information, your decision should be 'answer' and you should provide a detailed answer. + If you need more information, your decision should be 'files' and you should provide a list of file paths that are needed. + """ + question = dspy.InputField(desc="The user's current question.") + chat_history = dspy.InputField(desc="The ongoing dialogue between the user and the AI.") + retrieved_data = dspy.InputField(desc="A JSON object containing the content of the retrieved files relevant to the question.") + decision = dspy.OutputField( + desc="Your decision, which must be either 'answer' or 'files'." + ) + answer = dspy.OutputField( + desc="If the decision is 'answer', this is your final, complete answer. If the decision is 'files', this is a JSON-formatted list of file paths needed to answer the question." + ) + +class CodeRagQuestionDecider(dspy.Module): + """ + A pipeline to decide whether to answer a question or request more files. + """ + def __init__(self): + super().__init__() + # Assign the system prompt directly to the dspy.Predict instance + system_prompt = "You are a helpful AI assistant. Your job is to decide whether to answer the user's question based on the provided context or to request more files. If you have enough information, respond with 'answer' and provide a detailed answer. If not, respond with 'files' and provide a JSON-formatted list of file paths needed." + self.decider = dspy.Predict(QuestionDecider, system_prompt=system_prompt) + + async def forward(self, question: str, history: List[str], retrieved_data: Dict[str, Any]) -> str | List[str]: + # Format the chat history for the signature + history_text = "\n".join(history) + + # Convert the retrieved_data to a JSON string + retrieved_data_json_string = json.dumps(retrieved_data, indent=2) + + # Use the decider to determine the next action + prediction = await self.decider.acall( + question=question, + chat_history=history_text, + retrieved_data=retrieved_data_json_string + ) + + # Check the decision and return the appropriate output + if prediction.decision.lower() == 'answer': + # The AI has enough information, so return the final answer. + return prediction.answer + elif prediction.decision.lower() == 'files': + # The AI needs more files, so parse and return the list of file paths. + try: + # The LLM's output for files should be a JSON array string + return json.loads(prediction.answer) + except (json.JSONDecodeError, TypeError) as e: + # Fallback for malformed JSON output from the LLM + print(f"Warning: Failed to parse files list from LLM: {e}") + print(f"Raw LLM output: {prediction.answer}") + # Return an empty list or a list parsed from lines, depending on how you want to handle bad output. + # Assuming the model returns a string like 'file1.py,file2.py' or '["file1.py", "file2.py"]' + if isinstance(prediction.answer, str): + cleaned_string = prediction.answer.strip("[]'\" \n\t") + return [item.strip() for item in cleaned_string.split(',') if item.strip()] + return [] + else: + # Handle unexpected decisions from the LLM + print(f"Warning: Unexpected decision from LLM: {prediction.decision}") + return [] \ No newline at end of file diff --git a/ai-hub/app/core/retrievers/file_retriever.py b/ai-hub/app/core/retrievers/file_retriever.py index 8e316af..a811c40 100644 --- a/ai-hub/app/core/retrievers/file_retriever.py +++ b/ai-hub/app/core/retrievers/file_retriever.py @@ -1,5 +1,5 @@ from typing import Dict, Any, Optional -from app.db import models +from app.db import file_retriever_models from sqlalchemy.orm import Session,joinedload import uuid @@ -29,11 +29,11 @@ return None # Fetch the request and its related files in a single query using join - request = db.query(models.FileRetrievalRequest).filter( - models.FileRetrievalRequest.id == request_uuid + request = db.query(file_retriever_models.FileRetrievalRequest).filter( + file_retriever_models.FileRetrievalRequest.id == request_uuid ).options( # Eagerly load the retrieved_files to avoid N+1 query problem - joinedload(models.FileRetrievalRequest.retrieved_files) + joinedload(file_retriever_models.FileRetrievalRequest.retrieved_files) ).first() if not request: diff --git a/ai-hub/app/core/services/workspace.py b/ai-hub/app/core/services/workspace.py index aa1e3c5..e6790a2 100644 --- a/ai-hub/app/core/services/workspace.py +++ b/ai-hub/app/core/services/workspace.py @@ -2,15 +2,19 @@ import json import uuid import logging +from datetime import datetime import ast # Import the Abstract Syntax Trees module from typing import Dict, Any, Callable, Awaitable, List from fastapi import WebSocket,Depends from sqlalchemy.orm import Session,joinedload from app.db import models +from app.db import file_retriever_models from app.db.session import SessionLocal from app.core.providers.factory import get_llm_provider from app.core.pipelines.file_selector import CodeRagFileSelector from app.core.pipelines.dspy_rag import DspyRagPipeline +from app.core.pipelines.question_decider import CodeRagQuestionDecider +from app.core.retrievers.file_retriever import FileRetriever # A type hint for our handler functions MessageHandler = Callable[[WebSocket, Dict[str, Any]], Awaitable[None]] # Configure logging @@ -24,7 +28,7 @@ def __init__(self): # The dispatcher map: keys are message types, values are handler functions self.message_handlers: Dict[str, MessageHandler] = { - "select_folder_response": self.handle_select_folder_response, + # "select_folder_response": self.handle_select_folder_response, "list_directory_response": self.handle_list_directory_response, "file_content_response": self.handle_files_content_response, "execute_command_response": self.handle_command_output, @@ -41,32 +45,168 @@ # Per-websocket session state management self.sessions: Dict[str, Dict[str, Any]] = {} self.db = SessionLocal() + self.file_retriever = FileRetriever() - def generate_request_id(self) -> str: - """Generates a unique request ID.""" - return str(uuid.uuid4()) + # --- New helper function for reuse --- + async def _update_file_content(self, request_id: uuid.UUID, files_with_content: List[Dict[str, Any]]): + """ + Updates the content of existing file records in the database. + + This function is called after the client has sent the content for the + files selected by the AI. It iterates through the provided file data, + finds the corresponding database record, and updates its 'content' field. + """ + if not files_with_content: + logger.warning("No files with content provided to update.") + return + + logger.info(f"Starting content update for {len(files_with_content)} files for request {request_id}") + + try: + # Fetch all files for the given request ID to build a quick lookup map + retrieved_files = self.db.query(file_retriever_models.RetrievedFile).filter_by( + request_id=request_id + ).all() + file_map = {file.file_path: file for file in retrieved_files} + + updated_count = 0 + for file_info in files_with_content: + file_path = file_info.get("filepath") + content = file_info.get("content") + + if not file_path or content is None: + logger.warning("Skipping file with missing filename or content.") + continue + + # Find the corresponding record in our map + if file_path in file_map: + db_file = file_map[file_path] + db_file.content = content + updated_count += 1 + logger.debug(f"Updated content for file: {file_path}") + else: + logger.warning(f"File {file_path} not found in database for request {request_id}, skipping content update.") + + # Commit the changes to the database + self.db.commit() + logger.info(f"Successfully updated content for {updated_count} files.") + + except Exception as e: + self.db.rollback() + logger.error(f"Failed to update file content for request {request_id}: {e}") + raise + + async def _get_or_create_file_request(self, session_id: int, path: str, prompt: str) -> file_retriever_models.FileRetrievalRequest: + """ + Retrieves an existing FileRetrievalRequest or creates a new one if it doesn't exist. + """ + file_request = self.db.query(file_retriever_models.FileRetrievalRequest).filter_by( + session_id=session_id, directory_path=path + ).first() + + if not file_request: + file_request = file_retriever_models.FileRetrievalRequest( + session_id=session_id, + question=prompt, + directory_path=path + ) + self.db.add(file_request) + self.db.commit() + self.db.refresh(file_request) + else: + # If file_request is found, update it with the latest prompt + file_request.question = prompt + self.db.commit() + self.db.refresh(file_request) + return file_request + + async def _get_file_request_by_id(self, request_id: uuid.UUID) -> file_retriever_models.FileRetrievalRequest: + """ + Retrieves a FileRetrievalRequest by its ID. + """ + return self.db.query(file_retriever_models.FileRetrievalRequest).filter_by(id=request_id).first() + + async def _store_retrieved_files(self, request_id: uuid.UUID, files: List[Dict[str, Any]]): + """ + Synchronizes the database's retrieved files with the client's file list. + + This function compares existing files against new ones and performs + updates, additions, or deletions as necessary. + """ + # 1. Get existing files from the database for this request + existing_files = self.db.query(file_retriever_models.RetrievedFile).filter_by(request_id=request_id).all() + existing_files_map = {file.file_path: file for file in existing_files} + + # Keep track of which existing files are also in the incoming list + incoming_file_paths = set() + + # 2. Iterate through incoming files to handle updates and additions + for file_info in files: + file_path = file_info.get("path") + last_modified_timestamp_ms = file_info.get("lastModified") + + if not file_path or last_modified_timestamp_ms is None: + logger.warning("Skipping file with missing path or timestamp.") + continue + + last_modified_datetime = datetime.fromtimestamp(last_modified_timestamp_ms / 1000.0) + incoming_file_paths.add(file_path) + + # Check if the file already exists in the database + if file_path in existing_files_map: + db_file = existing_files_map[file_path] + # Compare the last modified timestamps + if last_modified_datetime > db_file.last_updated: + # Case: File has been updated, so override the existing record. + logger.info(f"Updating file {file_path}. New timestamp: {last_modified_datetime}") + db_file.last_updated = last_modified_datetime + # The content remains empty for now, as it will be fetched later. + else: + # Case: File is identical or older, do nothing. + logger.debug(f"File {file_path} is identical or older, skipping.") + + else: + # Case: This is a newly introduced file. + logger.info(f"Adding new file: {file_path}") + new_file = file_retriever_models.RetrievedFile( + request_id=request_id, + file_path=file_path, + file_name=file_info.get("name", ""), + content="", # Content is deliberately left empty. + type="original", + last_updated=last_modified_datetime, + ) + self.db.add(new_file) + + # 3. Purge non-existing files + # Find files in the database that were not in the incoming list + files_to_purge = [ + file for file in existing_files if file.file_path not in incoming_file_paths + ] + if files_to_purge: + logger.info(f"Purging {len(files_to_purge)} non-existing files.") + for file in files_to_purge: + self.db.delete(file) + + # 4. Commit all changes (updates, additions, and deletions) in a single transaction + self.db.commit() + logger.info("File synchronization complete.") + + # def generate_request_id(self) -> str: + # """Generates a unique request ID.""" + # return str(uuid.uuid4()) async def send_command(self, websocket: WebSocket, command_name: str, data: Dict[str, Any] = {}): - """Helper to send a command to the client with a unique request_id and round number.""" if command_name not in self.command_map: raise ValueError(f"Unknown command: {command_name}") - request_id = self.generate_request_id() - session_state = self.sessions.get(websocket.scope["client"], {"round": 0}) - session_state["round"] += 1 - message_to_send = { "type": self.command_map[command_name]["type"], - "request_id": request_id, - "round": session_state["round"], **data, } await websocket.send_text(json.dumps(message_to_send)) - print(f"Sent command '{command_name}' to client (request_id: {request_id}, round: {session_state['round']})") - self.sessions[websocket.scope["client"]] = session_state - async def dispatch_message(self, websocket: WebSocket, message: Dict[str, Any]): """ Routes an incoming message to the appropriate handler based on its 'type'. @@ -83,36 +223,62 @@ handler = self.message_handlers.get(message_type) if handler: + logger.debug(f"Dispatching to handler for message type: {message_type}") await handler(websocket, message) else: print(f"Warning: No handler found for message type: {message_type}") await websocket.send_text(json.dumps({"type": "error", "content": f"Unknown message type: {message_type}"})) - async def handle_select_folder_response(self, websocket:WebSocket, data: Dict[str, Any]): - """Handles the client's response to a select folder response.""" - path = data.get("path") - request_id = data.get("request_id") - - print(f"Received folder selected (request_id: {request_id}): Path: {path}") + # async def handle_select_folder_response(self, websocket:WebSocket, data: Dict[str, Any]): + # """Handles the client's response to a select folder response.""" + # path = data.get("path") + # request_id = data.get("request_id") + # if request_id is None: + # await websocket.send_text(json.dumps({ + # "type": "error", + # "content": "Error: request_id is missing in the response." + # })) + # return + # print(f"Received folder selected (request_id: {request_id}): Path: {path}") # After a folder is selected, the next step is to list its contents. # This now uses the send_command helper. - await self.send_command(websocket, "list_directory", data={"path": path}) + # await self.send_command(websocket, "list_directory", data={"path": path, "request_id": request_id}) async def handle_list_directory_response(self, websocket: WebSocket, data: Dict[str, Any]): """Handles the client's response to a list_directory request.""" + logger.debug(f"List directory response data: {data}") files = data.get("files", []) - provider_name = data.get("provider_name", "gemini") + if not files: + await websocket.send_text(json.dumps({ + "type": "error", + "content": "Error: No files found in the directory." + })) + return + request_id = data.get("request_id") + if not request_id: + await websocket.send_text(json.dumps({ + "type": "error", + "content": "Error: request_id is missing in the response." + })) + return + file_request = await self._get_file_request_by_id(uuid.UUID(request_id)) + if not file_request: + await websocket.send_text(json.dumps({ + "type": "error", + "content": f"Error: No matching file request found for request_id {request_id}." + })) + return + await self._store_retrieved_files(request_id=uuid.UUID(request_id), files=files) + provider_name = data.get("provider_name", "gemini") llm_provider = get_llm_provider(provider_name) cfs = CodeRagFileSelector() with dspy.context(lm=llm_provider): raw_answer_text = await cfs( - question="Please help to refactor my code", - # The history will be retrieved from a database in a real application - history="", - file_list=files + question=file_request.question, + retrieved_data = self.file_retriever.retrieve_by_request_id(self.db, request_id=request_id) ) try: @@ -135,7 +301,7 @@ })) # After getting the AI's selected files, we send a command to the client to get their content. - await self.send_command(websocket, "get_file_content", data={"filenames": answer_text}) + await self.send_command(websocket, "get_file_content", data={"filepaths": answer_text, "request_id": request_id}) async def handle_files_content_response(self, websocket: WebSocket, data: Dict[str, Any]): """Handles the content of a list of files sent by the client.""" @@ -149,21 +315,18 @@ return print(f"Received content for {len(files_data)} files (request_id: {request_id}).") - - for file_info in files_data: - filename = file_info.get("filename") - content = file_info.get("content") - - if filename and content: - print(f"Processing content for '{filename}'. Content length: {len(content)}") - # The AI would analyze this content to determine the next action, e.g., - # generate a plan, perform a refactoring, or ask for more information. - await websocket.send_text(json.dumps({ - "type": "thinking_log", - "content": f"Analyzing the content of file: {filename}" - })) - else: - print(f"Warning: Malformed file data in response for request_id: {request_id}") + await self._update_file_content(request_id=uuid.UUID(request_id), files_with_content=files_data) + data = self.file_retriever.retrieve_by_request_id(self.db, request_id=request_id) + with dspy.context(lm=get_llm_provider("gemini")): + crqd = CodeRagQuestionDecider() + answer_text = await crqd( + question=data.get("question",""), + history="", + retrieved_data= data) + await websocket.send_text(json.dumps({ + "type": "chat_message", + "content": answer_text + })) async def handle_command_output(self, websocket: WebSocket, data: Dict[str, Any]): """Handles the output from a command executed by the client.""" @@ -206,15 +369,16 @@ self.db.commit() self.db.refresh(user_message) - # path = data.get("path", "") - # if path: - # # If the path is provided, list the directory contents first. - # await self.send_command(websocket, "list_directory", data={"path": path}) - # return + path = data.get("path", "") + if path: + # If file path is provided, initiate file retrieval process. + file_request = await self._get_or_create_file_request(session_id, path, prompt) + await self.send_command(websocket, "list_directory", data={"request_id": str(file_request.id)}) + return llm_provider = get_llm_provider(provider_name) - chat = DspyRagPipeline(retrievers=[]) + chat = DspyRagPipeline() with dspy.context(lm=llm_provider): - answer_text = await chat(question=prompt, history=session.messages, db=self.db) + answer_text = await chat(question=prompt, history=session.messages, context_chunks=[]) # Save assistant's response assistant_message = models.Message(session_id=session_id, sender="assistant", content=answer_text) self.db.add(assistant_message) diff --git a/ui/client-app/src/hooks/useCodeAssistant.js b/ui/client-app/src/hooks/useCodeAssistant.js index 1dc69e8..d1fb64f 100644 --- a/ui/client-app/src/hooks/useCodeAssistant.js +++ b/ui/client-app/src/hooks/useCodeAssistant.js @@ -9,7 +9,7 @@ const [selectedFolder, setSelectedFolder] = useState(null); const [connectionStatus, setConnectionStatus] = useState("disconnected"); const [isProcessing, setIsProcessing] = useState(false); - const [isPaused, setIsPaused] = useState(false); // Corrected this line + const [isPaused, setIsPaused] = useState(false); const [errorMessage, setErrorMessage] = useState(""); const [showErrorModal, setShowErrorModal] = useState(false); const [sessionId, setSessionId] = useState(null); @@ -40,12 +40,12 @@ const handleStatusUpdate = useCallback((message) => { setIsProcessing(message.processing); - setIsPaused(message.paused); // Corrected this line + setIsPaused(message.paused); setConnectionStatus(message.status); }, []); const handleListDirectoryRequest = useCallback(async (message) => { - const { request_id, round } = message; + const { request_id } = message; const dirHandle = dirHandleRef.current; if (!dirHandle) { const errorMsg = "No folder selected by user."; @@ -55,8 +55,8 @@ } // setThinkingProcess((prev) => [ - // ...prev, - // { type: "system", message: `Scanning directory...`, round }, + //  ...prev, + //  { type: "system", message: `Scanning directory...`, round }, // ]); try { @@ -73,6 +73,7 @@ path: entryPath, size: file.size, lastModified: file.lastModified, + created: file.created, }); } else if (entry.kind === "directory") { await walkDirectory(entry, entryPath); // Recurse into subdirectory @@ -86,8 +87,7 @@ JSON.stringify({ type: "list_directory_response", files, - request_id, - round, + request_id: request_id, session_id: sessionId, }) ); @@ -97,7 +97,6 @@ { type: "local", message: `Sent list of files (${files.length}) to server.`, - round, }, ]); } catch (error) { @@ -107,66 +106,99 @@ type: "error", content: "Failed to access folder contents.", request_id, - round, session_id: sessionId, }) ); } }, [sessionId]); + // Helper function to recursively get a file handle from a full path + const getFileHandleFromPath = async (dirHandle, filePath) => { + // Split the path and filter out any empty parts to prevent the error + const pathParts = filePath.split('/').filter(Boolean); + let currentHandle = dirHandle; + for (let i = 0; i < pathParts.length; i++) { + const part = pathParts[i]; + try { + if (i === pathParts.length - 1) { + // Last part is the file name + return await currentHandle.getFileHandle(part); + } else { + // Part is a directory + currentHandle = await currentHandle.getDirectoryHandle(part); + } + } catch (error) { + console.error(`Error navigating to path part '${part}':`, error); + // Return null or re-throw based on desired error handling + return null; + } + } + return null; + }; + + const handleReadFilesRequest = useCallback(async (message) => { console.log(message); - const { filenames, request_id, round } = message; + const { filepaths, request_id } = message; const dirHandle = dirHandleRef.current; if (!dirHandle) { - ws.current.send(JSON.stringify({ type: "error", content: "No folder selected.", request_id, round, session_id: sessionId })); + ws.current.send(JSON.stringify({ type: "error", content: "No folder selected.", request_id, session_id: sessionId })); return; } - - setThinkingProcess((prev) => [ - ...prev, - { type: "local", message: `Reading content of ${filenames.length} files...`, round }, - ]); - + const filesData = []; - for (const filename of filenames) { + const readFiles = []; // Array to store names of successfully read files + + for (const filepath of filepaths) { try { - const fileHandle = await dirHandle.getFileHandle(filename); + const fileHandle = await getFileHandleFromPath(dirHandle, filepath); const file = await fileHandle.getFile(); const content = await file.text(); - filesData.push({ filename, content }); - setThinkingProcess((prev) => [ - ...prev, - { type: "local", message: `Read file: ${filename}`, round }, - ]); + filesData.push({ filepath, content }); + readFiles.push(filepath); // Add successfully read file to the list } catch (error) { - console.error(`Failed to read file ${filename}:`, error); + console.error(`Failed to read file ${filepath}:`, error); ws.current.send(JSON.stringify({ type: "error", - content: `Could not read file: ${filename}`, - request_id, - round, + content: `Could not read file: ${filepath}`, + request_id: request_id, session_id: sessionId, })); } } - + ws.current.send(JSON.stringify({ type: "file_content_response", files: filesData, - request_id, - round, + request_id: request_id, session_id: sessionId, })); - - setThinkingProcess((prev) => [ - ...prev, - { type: "local", message: `Sent content for ${filesData.length} files to server.`, round }, - ]); + + // Consolidate all thinking process updates into a single call + setThinkingProcess((prev) => { + const newMessages = [ + { type: "local", message: `Reading content of ${filepaths.length} files...` } + ]; + + // Add a message summarizing the files that were read + if (readFiles.length > 0) { + const displayMessage = readFiles.length > 10 + ? `Read ${readFiles.length} files successfully.` + : `Read files successfully: [${readFiles.join(', ')}]`; + newMessages.push({ type: "local", message: displayMessage }); + } + + newMessages.push({ + type: "local", + message: `Sent content for ${filesData.length} files to server.` + }); + + return [...prev, ...newMessages]; + }); }, [sessionId]); const handleExecuteCommandRequest = useCallback((message) => { - const { command, request_id, round } = message; + const { command, request_id } = message; const output = `Simulated output for command: '${command}'`; ws.current.send(JSON.stringify({ @@ -174,13 +206,12 @@ command, output, request_id, - round, session_id: sessionId, })); setThinkingProcess((prev) => [ ...prev, - { type: "system", message: `Simulated execution of command: '${command}'`, round }, + { type: "system", message: `Simulated execution of command: '${command}'` }, ]); }, [sessionId]); @@ -255,7 +286,7 @@ 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 })); + ws.current.send(JSON.stringify({ type: "chat_message",content: text, session_id: sessionId, path: dirHandleRef .current ? dirHandleRef.current.name : null })); } }, [sessionId]); @@ -270,8 +301,8 @@ 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 })); + // const request_id = uuidv4(); + // ws.current.send(JSON.stringify({ type: "select_folder_response", path: directoryHandle.name, request_id, session_id: sessionId })); setThinkingProcess((prev) => [ ...prev,