From 12c37c663b62385414f685dca9b3622ad584da77 Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Mon, 31 Mar 2025 23:41:18 -0700 Subject: [PATCH] only 1 agent run wip --- backend/agent/api.py | 35 ++ backend/agent/workspace/README.md | 35 -- backend/agent/workspace/index.html | 83 +++-- backend/agent/workspace/script.js | 170 ++++++++-- backend/agent/workspace/styles.css | 312 +++++++----------- .../processor/llm_response_processor.py | 4 +- .../processor/xml/xml_results_adder.py | 2 +- backend/agentpress/thread_manager.py | 63 ++-- 8 files changed, 390 insertions(+), 314 deletions(-) delete mode 100644 backend/agent/workspace/README.md diff --git a/backend/agent/api.py b/backend/agent/api.py index 52f557e7..c0b1645f 100644 --- a/backend/agent/api.py +++ b/backend/agent/api.py @@ -84,6 +84,31 @@ async def restore_running_agent_runs(): "completed_at": datetime.now(timezone.utc).isoformat() }).eq("id", run['id']).execute() +async def check_for_active_project_agent_run(client, project_id: str): + """ + Check if there is an active agent run for any thread in the given project. + + Args: + client: The Supabase client + project_id: The project ID to check + + Raises: + HTTPException: If an agent run is already active for the project + """ + # Get all threads from this project + project_threads = await client.table('threads').select('thread_id').eq('project_id', project_id).execute() + project_thread_ids = [t['thread_id'] for t in project_threads.data] + + # Check if there are any active agent runs for any thread in this project + if project_thread_ids: + active_runs = await client.table('agent_runs').select('id').in_('thread_id', project_thread_ids).eq('status', 'running').execute() + + if active_runs.data and len(active_runs.data) > 0: + raise HTTPException( + status_code=409, + detail="Another agent is already running for this project. Please wait for it to complete or stop it before starting a new one." + ) + @router.post("/thread/{thread_id}/agent/start") async def start_agent(thread_id: str, user_id: str = Depends(get_current_user_id)): """Start an agent for a specific thread in the background.""" @@ -94,6 +119,16 @@ async def start_agent(thread_id: str, user_id: str = Depends(get_current_user_id # Verify user has access to this thread await verify_thread_access(client, thread_id, user_id) + # Get the project_id for this thread + thread_result = await client.table('threads').select('project_id').eq('thread_id', thread_id).execute() + if not thread_result.data: + raise HTTPException(status_code=404, detail="Thread not found") + + project_id = thread_result.data[0]['project_id'] + + # Check if there is already an active agent run for this project + await check_for_active_project_agent_run(client, project_id) + # Create a new agent run agent_run = await client.table('agent_runs').insert({ "thread_id": thread_id, diff --git a/backend/agent/workspace/README.md b/backend/agent/workspace/README.md deleted file mode 100644 index 8b705d12..00000000 --- a/backend/agent/workspace/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Brand Identity Business Card - -This is a modern, interactive business card design implemented with HTML, CSS, and JavaScript. - -## Features - -- Interactive flip animation to show front and back of the card -- Color theme customization with predefined color options -- Responsive design that works on different screen sizes -- Modern, clean aesthetic with attention to typography and spacing -- Social media icons and contact information - -## Usage - -1. Open `index.html` in any modern web browser -2. Click the "Flip Card" button to see both sides of the business card -3. Use the color dots below to change the card's color theme -4. You can also double-click on the card to flip it - -## Customization - -To customize this business card for your own use: - -1. Edit the HTML to change the name, title, and contact information -2. Modify the logo and branding elements in the CSS -3. Add or remove social media icons as needed -4. Adjust the color palette by changing the color option values - -## Technologies Used - -- HTML5 -- CSS3 (with variables, flexbox, and transitions) -- JavaScript -- Font Awesome for icons -- Google Fonts (Montserrat and Playfair Display) \ No newline at end of file diff --git a/backend/agent/workspace/index.html b/backend/agent/workspace/index.html index dd5bf1c8..63ba0a8a 100644 --- a/backend/agent/workspace/index.html +++ b/backend/agent/workspace/index.html @@ -3,55 +3,52 @@ - Brand Identity Business Card + Web Development Demo - -
-
-
- -
Creative Solutions
-
-
-
-
-

ALEX JOHNSON

-

Creative Director

-
-
-

(555) 123-4567

-

alex@acmecreative.com

-

123 Design Street, Creative City

-

www.acmecreative.com

-
-
- -
-
+
+

Welcome to My Web App

+

This is a simple demonstration of web development capabilities.

+
-
- -
-
-
-
-
-
-
-
-
+
+
+

Interactive Demo

+

Click the button below to see some magic happen!

+ +
+
+ +
+

Counter Demo

+

A simple counter implementation:

+
+ + 0 + +
+
+
+

To-Do List

+

Add and manage your tasks:

+
+
+ + +
+
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/backend/agent/workspace/script.js b/backend/agent/workspace/script.js index 1b2e0f63..0bff6161 100644 --- a/backend/agent/workspace/script.js +++ b/backend/agent/workspace/script.js @@ -1,26 +1,158 @@ document.addEventListener('DOMContentLoaded', function() { - const cardContainer = document.querySelector('.card-container'); - const flipBtn = document.getElementById('flip-btn'); - const colorOptions = document.querySelectorAll('.color-option'); + // Color changing functionality + const changeColorBtn = document.getElementById('changeColorBtn'); + const colorDisplay = document.getElementById('colorDisplay'); - // Set initial primary color - document.documentElement.style.setProperty('--primary-color', '#1a237e'); - - // Flip card functionality - flipBtn.addEventListener('click', function() { - cardContainer.classList.toggle('flipped'); + changeColorBtn.addEventListener('click', function() { + // Generate a random color + const randomColor = getRandomColor(); + colorDisplay.style.backgroundColor = randomColor; + colorDisplay.textContent = randomColor; + colorDisplay.style.color = getContrastColor(randomColor); + colorDisplay.style.display = 'flex'; + colorDisplay.style.justifyContent = 'center'; + colorDisplay.style.alignItems = 'center'; + colorDisplay.style.fontWeight = 'bold'; }); - // Color change functionality - colorOptions.forEach(option => { - option.addEventListener('click', function() { - const color = this.getAttribute('data-color'); - document.documentElement.style.setProperty('--primary-color', color); + // Counter functionality + const decrementBtn = document.getElementById('decrementBtn'); + const incrementBtn = document.getElementById('incrementBtn'); + const counterValue = document.getElementById('counterValue'); + + let count = 0; + + decrementBtn.addEventListener('click', function() { + count--; + updateCounter(); + }); + + incrementBtn.addEventListener('click', function() { + count++; + updateCounter(); + }); + + function updateCounter() { + counterValue.textContent = count; + // Add some visual feedback + counterValue.style.color = count < 0 ? 'red' : count > 0 ? 'green' : '#333'; + } + + // Todo List functionality + const todoInput = document.getElementById('todoInput'); + const addTodoBtn = document.getElementById('addTodoBtn'); + const todoList = document.getElementById('todoList'); + + // Load todos from localStorage + let todos = JSON.parse(localStorage.getItem('todos')) || []; + + // Render existing todos + renderTodos(); + + addTodoBtn.addEventListener('click', addTodo); + todoInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + addTodo(); + } + }); + + function addTodo() { + const todoText = todoInput.value.trim(); + if (todoText) { + const todo = { + id: Date.now(), + text: todoText, + completed: false + }; + + todos.push(todo); + saveTodos(); + renderTodos(); + todoInput.value = ''; + todoInput.focus(); + } + } + + function toggleTodo(id) { + todos = todos.map(todo => { + if (todo.id === id) { + todo.completed = !todo.completed; + } + return todo; }); - }); + saveTodos(); + renderTodos(); + } - // Double click to flip card - cardContainer.addEventListener('dblclick', function() { - this.classList.toggle('flipped'); - }); + function deleteTodo(id) { + todos = todos.filter(todo => todo.id !== id); + saveTodos(); + renderTodos(); + } + + function renderTodos() { + todoList.innerHTML = ''; + + if (todos.length === 0) { + const emptyMessage = document.createElement('li'); + emptyMessage.textContent = 'No tasks yet. Add one above!'; + emptyMessage.style.textAlign = 'center'; + emptyMessage.style.padding = '10px'; + emptyMessage.style.color = '#888'; + todoList.appendChild(emptyMessage); + return; + } + + todos.forEach(todo => { + const todoItem = document.createElement('li'); + todoItem.className = `todo-item ${todo.completed ? 'completed' : ''}`; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.checked = todo.completed; + checkbox.addEventListener('change', () => toggleTodo(todo.id)); + + const todoText = document.createElement('span'); + todoText.className = 'todo-text'; + todoText.textContent = todo.text; + + const deleteBtn = document.createElement('button'); + deleteBtn.className = 'delete-btn'; + deleteBtn.textContent = 'Delete'; + deleteBtn.addEventListener('click', () => deleteTodo(todo.id)); + + todoItem.appendChild(checkbox); + todoItem.appendChild(todoText); + todoItem.appendChild(deleteBtn); + + todoList.appendChild(todoItem); + }); + } + + function saveTodos() { + localStorage.setItem('todos', JSON.stringify(todos)); + } + + // Helper functions + function getRandomColor() { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; + } + + function getContrastColor(hexColor) { + // Convert hex to RGB + const r = parseInt(hexColor.substr(1, 2), 16); + const g = parseInt(hexColor.substr(3, 2), 16); + const b = parseInt(hexColor.substr(5, 2), 16); + + // Calculate luminance + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + + // Return black or white based on luminance + return luminance > 0.5 ? '#000000' : '#FFFFFF'; + } }); \ No newline at end of file diff --git a/backend/agent/workspace/styles.css b/backend/agent/workspace/styles.css index d5f7ebe5..2f4ef7ab 100644 --- a/backend/agent/workspace/styles.css +++ b/backend/agent/workspace/styles.css @@ -5,226 +5,170 @@ } body { - font-family: 'Montserrat', sans-serif; - background-color: #f5f5f5; - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; color: #333; -} - -.container { - display: flex; - flex-direction: column; - align-items: center; - gap: 30px; -} - -.card-container { - width: 350px; - height: 200px; - perspective: 1000px; -} - -.card { - position: absolute; - width: 100%; - height: 100%; - transform-style: preserve-3d; - transition: transform 0.8s; - border-radius: 10px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); + background-color: #f5f5f5; padding: 20px; } -.card.front { - background-color: var(--primary-color, #1a237e); - color: white; - display: flex; - flex-direction: column; - justify-content: space-between; - backface-visibility: hidden; -} - -.card.back { +.container { + max-width: 800px; + margin: 0 auto; background-color: white; - transform: rotateY(180deg); - backface-visibility: hidden; - display: flex; - flex-direction: column; - justify-content: space-between; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + overflow: hidden; } -.card-container.flipped .card.front { - transform: rotateY(180deg); +header { + background: linear-gradient(135deg, #6e8efb, #a777e3); + color: white; + padding: 30px 20px; + text-align: center; } -.card-container.flipped .card.back { - transform: rotateY(0deg); -} - -.logo { - display: flex; - align-items: center; - gap: 10px; +header h1 { margin-bottom: 10px; } -.logo-symbol { - width: 40px; - height: 40px; +main { + padding: 20px; +} + +.card { background-color: white; - color: var(--primary-color, #1a237e); - border-radius: 50%; - display: flex; - justify-content: center; - align-items: center; - font-weight: bold; - font-size: 24px; - font-family: 'Playfair Display', serif; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); } -.logo-text { - font-size: 24px; - font-weight: 700; - letter-spacing: 2px; -} - -.tagline { - font-size: 14px; - font-weight: 300; - letter-spacing: 1px; - margin-top: -5px; -} - -.pattern { - position: absolute; - bottom: 0; - right: 0; - width: 150px; - height: 80px; - background: linear-gradient(135deg, transparent 25%, rgba(255, 255, 255, 0.1) 25%, - rgba(255, 255, 255, 0.1) 50%, transparent 50%, - transparent 75%, rgba(255, 255, 255, 0.1) 75%); - background-size: 20px 20px; - border-radius: 0 0 10px 0; -} - -.info { - padding: 10px 0; -} - -.name { - font-size: 18px; - font-weight: 600; - color: var(--primary-color, #1a237e); - letter-spacing: 1px; -} - -.title { - font-size: 14px; - color: #666; +.card h2 { margin-bottom: 15px; + color: #6e8efb; } -.divider { - width: 40px; - height: 3px; - background-color: var(--primary-color, #1a237e); - margin-bottom: 15px; -} - -.contact { - font-size: 12px; - line-height: 1.6; -} - -.contact p { - display: flex; - align-items: center; - gap: 8px; -} - -.contact i { - color: var(--primary-color, #1a237e); - width: 15px; -} - -.social-icons { - display: flex; - gap: 15px; - margin-top: 10px; - justify-content: flex-end; - padding: 0 10px 10px 0; -} - -.social-icons i { - color: var(--primary-color, #1a237e); - font-size: 16px; - cursor: pointer; - transition: transform 0.3s; -} - -.social-icons i:hover { - transform: translateY(-3px); -} - -.controls { - display: flex; - flex-direction: column; - align-items: center; - gap: 15px; -} - -#flip-btn { - background-color: var(--primary-color, #1a237e); +button { + background-color: #6e8efb; color: white; border: none; - padding: 10px 20px; - border-radius: 5px; + padding: 8px 16px; + border-radius: 4px; cursor: pointer; - font-family: 'Montserrat', sans-serif; - font-weight: 500; + font-size: 16px; transition: background-color 0.3s; } -#flip-btn:hover { - background-color: #0d1642; +button:hover { + background-color: #5d7ce0; } -.color-options { +.color-box { + height: 100px; + background-color: #f0f0f0; + margin-top: 15px; + border-radius: 4px; + transition: background-color 0.5s; +} + +.counter-container { display: flex; - gap: 10px; + align-items: center; + justify-content: center; + margin-top: 15px; } -.color-option { - width: 25px; - height: 25px; - border-radius: 50%; - cursor: pointer; - transition: transform 0.3s; - border: 2px solid white; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +#counterValue { + font-size: 24px; + margin: 0 20px; + min-width: 40px; + text-align: center; } -.color-option:hover { - transform: scale(1.1); +/* Todo List Styles */ +.todo-container { + margin-top: 15px; } -@media (max-width: 400px) { - .card-container { - width: 300px; - height: 180px; +.todo-input-container { + display: flex; + margin-bottom: 15px; +} + +#todoInput { + flex: 1; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px 0 0 4px; + font-size: 16px; +} + +#addTodoBtn { + border-radius: 0 4px 4px 0; +} + +.todo-list { + list-style-type: none; + padding: 0; +} + +.todo-item { + display: flex; + align-items: center; + padding: 10px; + border-bottom: 1px solid #eee; + animation: fadeIn 0.3s ease-in; +} + +.todo-item:last-child { + border-bottom: none; +} + +.todo-text { + flex: 1; + margin-left: 10px; +} + +.todo-item.completed .todo-text { + text-decoration: line-through; + color: #888; +} + +.delete-btn { + background-color: #ff6b6b; + margin-left: 10px; + padding: 4px 8px; + font-size: 14px; +} + +.delete-btn:hover { + background-color: #ff5252; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } +} + +footer { + text-align: center; + padding: 20px; + background-color: #f9f9f9; + color: #777; + font-size: 14px; +} + +@media (max-width: 600px) { + .container { + border-radius: 0; } - .logo-symbol { - width: 35px; - height: 35px; - font-size: 20px; + header { + padding: 20px 15px; } - .logo-text { - font-size: 20px; + .card { + padding: 15px; } } \ No newline at end of file diff --git a/backend/agentpress/processor/llm_response_processor.py b/backend/agentpress/processor/llm_response_processor.py index 21985a79..d185f6f1 100644 --- a/backend/agentpress/processor/llm_response_processor.py +++ b/backend/agentpress/processor/llm_response_processor.py @@ -180,7 +180,7 @@ class LLMResponseProcessor: if 'tool_calls' in response: logger.debug(f"Found {len(response['tool_calls'])} tool calls in response") if execute_tools: - await self._execute_tool_calls(response) + await self._process_tool_call(response) else: logger.info("Tool execution disabled, skipping tool calls") else: @@ -190,7 +190,7 @@ class LLMResponseProcessor: logger.error(f"Error processing response: {str(e)}", exc_info=True) raise - async def _execute_tool_calls(self, response: Dict[str, Any]) -> None: + async def _process_tool_call(self, response: Dict[str, Any]) -> None: """Execute tool calls from the response.""" logger.info(f"Executing tool calls for thread {self.thread_id}") try: diff --git a/backend/agentpress/processor/xml/xml_results_adder.py b/backend/agentpress/processor/xml/xml_results_adder.py index b41ec01c..0389bb04 100644 --- a/backend/agentpress/processor/xml/xml_results_adder.py +++ b/backend/agentpress/processor/xml/xml_results_adder.py @@ -110,7 +110,7 @@ class XMLResultsAdder(ResultsAdderBase): # Fallback if we can't find the root tag result_message = { "role": "user", - "content": f">Result for {result['name']}:\n{result['content']}>" + "content": f"Result for {result['name']}:\n{result['content']}" } await self.add_message(thread_id, result_message) diff --git a/backend/agentpress/thread_manager.py b/backend/agentpress/thread_manager.py index 766b0e7f..f8384adf 100644 --- a/backend/agentpress/thread_manager.py +++ b/backend/agentpress/thread_manager.py @@ -268,15 +268,26 @@ class ThreadManager: ) -> Union[Dict[str, Any], AsyncGenerator]: """Run a conversation thread with specified parameters.""" logger.info(f"Starting thread execution for thread {thread_id}") - logger.debug(f"Parameters: model={model_name}, temperature={temperature}, stream={stream}") + logger.debug(f"Parameters: model={model_name}, temperature={temperature}, max_tokens={max_tokens}, " + f"tool_choice={tool_choice}, native_tool_calling={native_tool_calling}, " + f"xml_tool_calling={xml_tool_calling}, execute_tools={execute_tools}, stream={stream}, " + f"execute_tools_on_stream={execute_tools_on_stream}, " + f"parallel_tool_execution={parallel_tool_execution}") try: - # Validate tool calling configuration + # 1. Get messages from thread + messages = await self.get_messages(thread_id) + + # 2. Prepare messages for LLM + add temporary message if it exists + prepared_messages = [system_message] + messages + if temporary_message: + prepared_messages.append(temporary_message) + logger.debug("Added temporary message to prepared messages") + if native_tool_calling and xml_tool_calling: logger.error("Invalid configuration: Cannot use both native and XML tool calling") raise ValueError("Cannot use both native LLM tool calling and XML tool calling simultaneously") - # Initialize tool components if any tool calling is enabled if native_tool_calling or xml_tool_calling: logger.debug("Initializing tool components") if tool_parser is None: @@ -291,12 +302,6 @@ class ThreadManager: results_adder = XMLResultsAdder(self) if xml_tool_calling else StandardResultsAdder(self) logger.debug(f"Using {results_adder.__class__.__name__} for results adding") - messages = await self.get_messages(thread_id) - prepared_messages = [system_message] + messages - if temporary_message: - prepared_messages.append(temporary_message) - logger.debug("Added temporary message to prepared messages") - openapi_tool_schemas = None if native_tool_calling: openapi_tool_schemas = self.tool_registry.get_openapi_schemas() @@ -309,6 +314,17 @@ class ThreadManager: available_functions = {} logger.debug("No tool calling enabled") + logger.info("Making LLM API call") + llm_response = await self._run_thread_completion( + messages=prepared_messages, + model_name=model_name, + temperature=temperature, + max_tokens=max_tokens, + tools=openapi_tool_schemas, + tool_choice=tool_choice if native_tool_calling else None, + stream=stream + ) + response_processor = LLMResponseProcessor( thread_id=thread_id, available_functions=available_functions, @@ -321,17 +337,6 @@ class ThreadManager: results_adder=results_adder ) - logger.info("Making LLM API call") - llm_response = await self._run_thread_completion( - messages=prepared_messages, - model_name=model_name, - temperature=temperature, - max_tokens=max_tokens, - tools=openapi_tool_schemas, - tool_choice=tool_choice if native_tool_calling else None, - stream=stream - ) - if stream: logger.info("Processing streaming response") return response_processor.process_stream( @@ -339,15 +344,13 @@ class ThreadManager: execute_tools=execute_tools, execute_tools_on_stream=execute_tools_on_stream ) - - logger.info("Processing non-streaming response") - await response_processor.process_response( - response=llm_response, - execute_tools=execute_tools - ) - - logger.info("Thread execution completed successfully") - return llm_response + else: + logger.info("Processing non-streaming response") + await response_processor.process_response( + response=llm_response, + execute_tools=execute_tools + ) + return llm_response except Exception as e: logger.error(f"Error in run_thread: {str(e)}", exc_info=True) @@ -382,4 +385,4 @@ class ThreadManager: return response except Exception as e: logger.error(f"Failed to make LLM API call: {str(e)}", exc_info=True) - raise + raise \ No newline at end of file