Browse Source

Merge pull request #4 from alexchenzl/curtask

change display of input area and buttons according to if there's a cu…
Ashu 7 months ago
parent
commit
b57cf9e978

+ 4 - 0
README.md

@@ -84,6 +84,10 @@ To run the NanoBrowser Agent Server via script (MacOS), follow these steps:
    ```bash
    ./install.sh
    ```
+3. Run the NanoBrowser Agent Server:
+   ```bash
+   uv run nanobrowser
+   ```
 
 ### Run the NanoBrowser Agent Server Manually
 

+ 19 - 0
extension/src/background.js

@@ -15,6 +15,12 @@ function connectWebSocket() {
   webSocket.onopen = () => {
     console.log('WebSocket connected');
     broadcastConnectionStatus(true);
+    // Request current task status upon connection
+    const getCurrentTaskMessage = {
+      kind: "get_task",
+      data: {}
+    };
+    webSocket.send(JSON.stringify(getCurrentTaskMessage));
     keepAlive();
   };
   
@@ -35,6 +41,12 @@ function connectWebSocket() {
           type: 'state',
           data: message.data
         });
+      } else if (kind === 'current_task') {
+        // Broadcast current task to sidebar
+        broadcastToSidebar({
+          type: 'current_task',
+          data: message.data
+        });
       } else if (kind === 'ack') {
         // console.log('ACK:', message);
       }
@@ -113,6 +125,13 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
             console.warn('Attempted to cancel task without taskId');
             sendResponse({ success: false });
         }
+    } else if (message.type === 'GET_CURRENT_TASK' && webSocket) {
+        const getCurrentTaskMessage = {
+            kind: "get_task",
+            data: {}
+        };
+        webSocket.send(JSON.stringify(getCurrentTaskMessage));
+        sendResponse({ success: true });
     }
     return true;
 });

+ 2 - 2
extension/src/sidebar.html

@@ -14,8 +14,8 @@
         <div class="bottom-container">
             <div id="connection-status" class="connection-status" style="display: none;">Not Connected</div>
             <div class="input-container">
-                <textarea id="chat-input" placeholder="Type your message..."></textarea>
-                <button id="send-button">Send</button>
+                <textarea id="chat-input" placeholder="Type your message..." disabled></textarea>
+                <button id="send-button" disabled>Send</button>
                 <button id="stop-button" style="display: none;">Stop</button>
             </div>
         </div>

+ 33 - 5
extension/src/sidebar.js

@@ -1,9 +1,10 @@
-function setInputsEnabled(enabled) {
+function setInputsEnabled(enabled, show_stop_button=false) {
     const chatInput = document.getElementById('chat-input');
     const sendButton = document.getElementById('send-button');
     const stopButton = document.getElementById('stop-button');
 
     chatInput.disabled = !enabled;
+    sendButton.disabled = !enabled;
     
     // Add visual styling for disabled state
     if (enabled) {
@@ -16,8 +17,13 @@ function setInputsEnabled(enabled) {
         chatInput.style.backgroundColor = '#f5f5f5';
         chatInput.style.color = '#999';
         // Show stop button, hide send button
-        sendButton.style.display = 'none';
-        stopButton.style.display = 'block';
+        if (show_stop_button) {
+            sendButton.style.display = 'none';
+            stopButton.style.display = 'block';
+        } else {
+            sendButton.style.display = 'block';
+            stopButton.style.display = 'none';
+        }
     }
 }
 
@@ -56,8 +62,8 @@ document.addEventListener('DOMContentLoaded', async () => {
         const text = chatInput.value.trim();
         if (!text) return;
 
-        // Disable inputs when sending message
-        setInputsEnabled(false);
+        // Disable inputs and show stop button
+        setInputsEnabled(false, true);
 
         // Add user message to chat
         addMessage({
@@ -97,6 +103,17 @@ document.addEventListener('DOMContentLoaded', async () => {
             updateConnectionStatus(message.data.isConnected);
         } else if (message.type === 'state') {
             handleTaskState(message.data);
+        } else if (message.type === 'current_task') {
+            // Handle current task message
+            if (message.data.task_id) {  // Will be false for "", null, undefined
+                currentTaskId = message.data.task_id;
+                // disable inputs and show stop button
+                setInputsEnabled(false, true);
+            } else {
+                currentTaskId = null;
+                // enable inputs
+                setInputsEnabled(true);
+            }
         }
     });
 
@@ -105,6 +122,10 @@ document.addEventListener('DOMContentLoaded', async () => {
         connectionStatus.textContent = isConnected ? 'Connected' : 'Not Connected';
         connectionStatus.style.display = 'block';
         connectionStatus.className = `connection-status ${isConnected ? 'connected' : 'disconnected'}`;
+        if (!isConnected) {
+            // disable inputs and but keep send button visible
+            setInputsEnabled(false, false);
+        }
     }
 
     // Event listeners
@@ -149,6 +170,13 @@ document.addEventListener('DOMContentLoaded', async () => {
     function handleTaskComplete() {
         currentTaskId = null;
     }
+
+    // Request current task status when sidebar opens
+    chrome.runtime.sendMessage({
+        type: 'GET_CURRENT_TASK'
+    }).catch(err => {
+        console.error('Failed to get current task status:', err);
+    });
 });
 
 // Helper function for generating fallback ID

+ 8 - 0
src/nanobrowser/lib/websocket/message.py

@@ -15,6 +15,8 @@ class WebSocketMessageKind(Enum):
     hb: Application-level heartbeat
     ack: Heartbeat acknowledgment
     error: Error message
+    get_task: Request the current running task ID
+    current_task: Response with current task ID
     """
     HEARTBEAT = "hb"       # application heartbeat
     ACK = "ack"            # heartbeat acknowledgment
@@ -22,6 +24,8 @@ class WebSocketMessageKind(Enum):
     CANCEL = "cancel"      # cancel task
     TASK_STATE = "state"   # task state update
     ERROR = "error"        # error message
+    GET_CURRENT_TASK = "get_task"  # Get the current running task
+    CURRENT_TASK = "current_task"  # Response with current task
 
 class WebSocketMessage(BaseModel):
     kind: WebSocketMessageKind
@@ -61,3 +65,7 @@ class ErrorMessage(BaseModel):
     message: str
     timestamp: str
 
+class CurrentTaskMessage(BaseModel):
+    """Message to respond with the current running task ID"""
+    task_id: Optional[str]
+

+ 18 - 1
src/nanobrowser/lib/websocket/server.py

@@ -8,7 +8,7 @@ from websockets.server import WebSocketServerProtocol
 from ..agent.executor import Executor
 from ..agent.event.base import Event
 from .message import (
-    CreateTaskMessage, TaskStateMessage, WebSocketMessage, WebSocketMessageKind, ErrorMessage
+    CreateTaskMessage, TaskStateMessage, WebSocketMessage, WebSocketMessageKind, ErrorMessage, CurrentTaskMessage
 )
 from .task import TaskManager, Task
 from ..utils.path_manager import PathManager
@@ -77,6 +77,9 @@ class WebSocketServer:
                             data=message.data
                         )
                         await websocket.send(json.dumps(ack_message.model_dump(mode='json')))
+                    elif message.kind == WebSocketMessageKind.GET_CURRENT_TASK:
+                        logger.debug("Received get_current_task message")
+                        await self._handle_get_current_task(websocket)
                     else:
                         logger.error(f"Unknown message kind: {message.kind}")
 
@@ -147,6 +150,20 @@ class WebSocketServer:
         )
         await websocket.send(json.dumps(message.model_dump(mode='json')))
 
+    async def _handle_get_current_task(self, websocket: WebSocketServerProtocol):
+        """Handle get_current_task message"""
+        current_task = self._task_manager.current_task
+        task_id = current_task.id if current_task else ''
+
+        current_task_msg = CurrentTaskMessage(
+            task_id=task_id
+        )
+        
+        message = WebSocketMessage(
+            kind=WebSocketMessageKind.CURRENT_TASK,
+            data=current_task_msg.model_dump()
+        )
+        await websocket.send(json.dumps(message.model_dump(mode='json')))
 
 async def start_server(host: str, port: int, base_dir: Path, executor: Executor):
     server = WebSocketServer(base_dir, executor)