only 1 agent run wip

This commit is contained in:
marko-kraemer 2025-03-31 23:41:18 -07:00
parent 296a68e8d1
commit 12c37c663b
8 changed files with 390 additions and 314 deletions

View File

@ -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,

View File

@ -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)

View File

@ -3,55 +3,52 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brand Identity Business Card</title>
<title>Web Development Demo</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Playfair+Display:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="card-container">
<div class="card front">
<div class="logo">
<div class="logo-symbol">A</div>
<div class="logo-text">ACME</div>
</div>
<div class="tagline">Creative Solutions</div>
<div class="pattern"></div>
</div>
<div class="card back">
<div class="info">
<h2 class="name">ALEX JOHNSON</h2>
<p class="title">Creative Director</p>
<div class="divider"></div>
<div class="contact">
<p><i class="fas fa-phone"></i> (555) 123-4567</p>
<p><i class="fas fa-envelope"></i> alex@acmecreative.com</p>
<p><i class="fas fa-map-marker-alt"></i> 123 Design Street, Creative City</p>
<p><i class="fas fa-globe"></i> www.acmecreative.com</p>
</div>
</div>
<div class="social-icons">
<i class="fab fa-linkedin"></i>
<i class="fab fa-twitter"></i>
<i class="fab fa-instagram"></i>
<i class="fab fa-behance"></i>
</div>
</div>
</div>
<header>
<h1>Welcome to My Web App</h1>
<p>This is a simple demonstration of web development capabilities.</p>
</header>
<div class="controls">
<button id="flip-btn">Flip Card</button>
<div class="color-options">
<div class="color-option" data-color="#1a237e" style="background-color: #1a237e;"></div>
<div class="color-option" data-color="#006064" style="background-color: #006064;"></div>
<div class="color-option" data-color="#4a148c" style="background-color: #4a148c;"></div>
<div class="color-option" data-color="#880e4f" style="background-color: #880e4f;"></div>
<div class="color-option" data-color="#bf360c" style="background-color: #bf360c;"></div>
</div>
</div>
</div>
<main>
<section class="card">
<h2>Interactive Demo</h2>
<p>Click the button below to see some magic happen!</p>
<button id="changeColorBtn">Change Colors</button>
<div id="colorDisplay" class="color-box"></div>
</section>
<section class="card">
<h2>Counter Demo</h2>
<p>A simple counter implementation:</p>
<div class="counter-container">
<button id="decrementBtn">-</button>
<span id="counterValue">0</span>
<button id="incrementBtn">+</button>
</div>
</section>
<section class="card">
<h2>To-Do List</h2>
<p>Add and manage your tasks:</p>
<div class="todo-container">
<div class="todo-input-container">
<input type="text" id="todoInput" placeholder="Enter a new task...">
<button id="addTodoBtn">Add</button>
</div>
<ul id="todoList" class="todo-list"></ul>
</div>
</section>
</main>
<footer>
<p>Created as a demonstration of web development capabilities</p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@ -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';
}
});

View File

@ -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;
}
}

View File

@ -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:

View File

@ -110,7 +110,7 @@ class XMLResultsAdder(ResultsAdderBase):
# Fallback if we can't find the root tag
result_message = {
"role": "user",
"content": f"<tool_result>>Result for {result['name']}:\n{result['content']}</<tool_result>>"
"content": f"<tool_result>Result for {result['name']}:\n{result['content']}</tool_result>"
}
await self.add_message(thread_id, result_message)

View File

@ -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