auto kill other active agent runs on start of new

This commit is contained in:
marko-kraemer 2025-03-31 23:49:19 -07:00
parent 12c37c663b
commit 6f9db5ba88
4 changed files with 480 additions and 7 deletions

View File

@ -87,13 +87,14 @@ async def restore_running_agent_runs():
async def check_for_active_project_agent_run(client, project_id: str): 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. Check if there is an active agent run for any thread in the given project.
If found, returns the ID of the active run, otherwise returns None.
Args: Args:
client: The Supabase client client: The Supabase client
project_id: The project ID to check project_id: The project ID to check
Raises: Returns:
HTTPException: If an agent run is already active for the project str or None: The ID of the active agent run if found, None otherwise
""" """
# Get all threads from this project # Get all threads from this project
project_threads = await client.table('threads').select('thread_id').eq('project_id', project_id).execute() project_threads = await client.table('threads').select('thread_id').eq('project_id', project_id).execute()
@ -104,10 +105,9 @@ async def check_for_active_project_agent_run(client, project_id: str):
active_runs = await client.table('agent_runs').select('id').in_('thread_id', project_thread_ids).eq('status', 'running').execute() 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: if active_runs.data and len(active_runs.data) > 0:
raise HTTPException( return active_runs.data[0]['id']
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." return None
)
@router.post("/thread/{thread_id}/agent/start") @router.post("/thread/{thread_id}/agent/start")
async def start_agent(thread_id: str, user_id: str = Depends(get_current_user_id)): async def start_agent(thread_id: str, user_id: str = Depends(get_current_user_id)):
@ -127,7 +127,12 @@ async def start_agent(thread_id: str, user_id: str = Depends(get_current_user_id
project_id = thread_result.data[0]['project_id'] project_id = thread_result.data[0]['project_id']
# Check if there is already an active agent run for this project # Check if there is already an active agent run for this project
await check_for_active_project_agent_run(client, project_id) active_run_id = await check_for_active_project_agent_run(client, project_id)
# If there's an active run, stop it first
if active_run_id:
logger.info(f"Stopping existing agent run {active_run_id} before starting new one")
await stop_agent_run(active_run_id)
# Create a new agent run # Create a new agent run
agent_run = await client.table('agent_runs').insert({ agent_run = await client.table('agent_runs').insert({

View File

@ -0,0 +1,99 @@
document.addEventListener('DOMContentLoaded', function() {
const galleryContainer = document.getElementById('galleryContainer');
const galleryImages = document.getElementById('galleryImages');
const prevBtn = document.getElementById('galleryPrev');
const nextBtn = document.getElementById('galleryNext');
const imageUpload = document.getElementById('imageUpload');
const uploadBtn = document.getElementById('uploadImageBtn');
// Sample images for demo
const demoImages = [
'https://source.unsplash.com/random/300x200?nature,1',
'https://source.unsplash.com/random/300x200?city,1',
'https://source.unsplash.com/random/300x200?technology,1',
'https://source.unsplash.com/random/300x200?animals,1'
];
// Load images from localStorage or use demo images
let images = JSON.parse(localStorage.getItem('galleryImages')) || demoImages;
let currentIndex = 0;
// Initialize gallery
renderGallery();
// Event listeners
prevBtn.addEventListener('click', showPreviousImage);
nextBtn.addEventListener('click', showNextImage);
uploadBtn.addEventListener('click', handleImageUpload);
function renderGallery() {
if (images.length === 0) {
galleryImages.innerHTML = '<p class="gallery-empty">No images in gallery. Add some!</p>';
prevBtn.disabled = true;
nextBtn.disabled = true;
return;
}
showImage(currentIndex);
updateButtons();
}
function showImage(index) {
galleryImages.innerHTML = '';
const img = document.createElement('img');
img.src = images[index];
img.alt = `Gallery image ${index + 1}`;
img.className = 'gallery-img';
const counter = document.createElement('div');
counter.className = 'gallery-counter';
counter.textContent = `${index + 1} / ${images.length}`;
galleryImages.appendChild(img);
galleryImages.appendChild(counter);
}
function showPreviousImage() {
currentIndex = (currentIndex - 1 + images.length) % images.length;
showImage(currentIndex);
updateButtons();
}
function showNextImage() {
currentIndex = (currentIndex + 1) % images.length;
showImage(currentIndex);
updateButtons();
}
function updateButtons() {
prevBtn.disabled = images.length <= 1;
nextBtn.disabled = images.length <= 1;
}
function handleImageUpload() {
const imageUrl = imageUpload.value.trim();
if (imageUrl) {
// Simple validation - check if it looks like a URL
if (imageUrl.startsWith('http') && (imageUrl.endsWith('.jpg') ||
imageUrl.endsWith('.jpeg') || imageUrl.endsWith('.png') ||
imageUrl.endsWith('.gif') || imageUrl.includes('unsplash'))) {
addImage(imageUrl);
imageUpload.value = '';
} else {
alert('Please enter a valid image URL (ending with .jpg, .jpeg, .png, or .gif)');
}
}
}
function addImage(url) {
images.push(url);
saveImages();
currentIndex = images.length - 1; // Show the newly added image
renderGallery();
}
function saveImages() {
localStorage.setItem('galleryImages', JSON.stringify(images));
}
});

View File

@ -0,0 +1,315 @@
// Test Runner Script
console.log("Test Runner initialized");
// Function to run all tests automatically
function autoRunTests() {
console.log("Auto-running tests...");
// Tests to run in sequence
const tests = [
testColorChanger,
testCounter,
testTodoList,
testWeatherWidget,
testGallery
];
// Run tests sequentially
runTestSequence(tests, 0);
}
// Run tests in sequence
function runTestSequence(tests, index) {
if (index >= tests.length) {
console.log("All tests completed!");
return;
}
console.log(`Running test ${index + 1}/${tests.length}...`);
// Run current test
const currentTest = tests[index];
currentTest(() => {
// When test completes, run next test
runTestSequence(tests, index + 1);
});
}
// Test Color Changer
function testColorChanger(callback) {
console.log("Testing Color Changer...");
const changeColorBtn = document.getElementById('changeColorBtn');
const colorDisplay = document.getElementById('colorDisplay');
if (!changeColorBtn || !colorDisplay) {
console.error("Color changer elements not found!");
if (callback) callback();
return;
}
// Get initial state
const initialColor = colorDisplay.style.backgroundColor;
console.log(`Initial color: ${initialColor || 'none'}`);
// Click button
changeColorBtn.click();
// Check result
setTimeout(() => {
const newColor = colorDisplay.style.backgroundColor;
console.log(`New color: ${newColor}`);
if (newColor && newColor !== initialColor) {
console.log("✅ Color changer test passed!");
} else {
console.error("❌ Color changer test failed!");
}
if (callback) callback();
}, 500);
}
// Test Counter
function testCounter(callback) {
console.log("Testing Counter...");
const decrementBtn = document.getElementById('decrementBtn');
const incrementBtn = document.getElementById('incrementBtn');
const counterValue = document.getElementById('counterValue');
if (!decrementBtn || !incrementBtn || !counterValue) {
console.error("Counter elements not found!");
if (callback) callback();
return;
}
// Get initial value
const initialValue = parseInt(counterValue.textContent);
console.log(`Initial counter value: ${initialValue}`);
// Test increment
incrementBtn.click();
setTimeout(() => {
const afterIncrement = parseInt(counterValue.textContent);
console.log(`After increment: ${afterIncrement}`);
if (afterIncrement === initialValue + 1) {
console.log("✅ Increment test passed!");
} else {
console.error("❌ Increment test failed!");
}
// Test decrement
decrementBtn.click();
decrementBtn.click();
setTimeout(() => {
const afterDecrement = parseInt(counterValue.textContent);
console.log(`After decrement: ${afterDecrement}`);
if (afterDecrement === initialValue - 1) {
console.log("✅ Decrement test passed!");
} else {
console.error("❌ Decrement test failed!");
}
if (callback) callback();
}, 500);
}, 500);
}
// Test Todo List
function testTodoList(callback) {
console.log("Testing Todo List...");
const todoInput = document.getElementById('todoInput');
const addTodoBtn = document.getElementById('addTodoBtn');
const todoList = document.getElementById('todoList');
if (!todoInput || !addTodoBtn || !todoList) {
console.error("Todo list elements not found!");
if (callback) callback();
return;
}
// Get initial todo count
const initialCount = todoList.querySelectorAll('.todo-item').length;
console.log(`Initial todo count: ${initialCount}`);
// Add a new todo
const testTodoText = `Test Todo ${Date.now()}`;
todoInput.value = testTodoText;
addTodoBtn.click();
setTimeout(() => {
// Check if todo was added
const newCount = todoList.querySelectorAll('.todo-item').length;
console.log(`New todo count: ${newCount}`);
if (newCount === initialCount + 1) {
console.log("✅ Add todo test passed!");
// Get the last todo item
const lastTodo = todoList.querySelector('.todo-item:last-child');
const checkbox = lastTodo.querySelector('input[type="checkbox"]');
const deleteBtn = lastTodo.querySelector('.delete-btn');
// Test toggle functionality
checkbox.click();
setTimeout(() => {
if (lastTodo.classList.contains('completed')) {
console.log("✅ Toggle todo test passed!");
} else {
console.error("❌ Toggle todo test failed!");
}
// Test delete functionality
deleteBtn.click();
setTimeout(() => {
const finalCount = todoList.querySelectorAll('.todo-item').length;
console.log(`Final todo count: ${finalCount}`);
if (finalCount === initialCount) {
console.log("✅ Delete todo test passed!");
} else {
console.error("❌ Delete todo test failed!");
}
if (callback) callback();
}, 500);
}, 500);
} else {
console.error("❌ Add todo test failed!");
if (callback) callback();
}
}, 500);
}
// Test Weather Widget
function testWeatherWidget(callback) {
console.log("Testing Weather Widget...");
const weatherCity = document.getElementById('weatherCity');
const weatherForm = document.getElementById('weatherForm');
const weatherDisplay = document.getElementById('weatherDisplay');
if (!weatherCity || !weatherForm || !weatherDisplay) {
console.error("Weather widget elements not found!");
if (callback) callback();
return;
}
// Check if initial weather is displayed
const initialWeatherInfo = weatherDisplay.querySelector('.weather-info');
if (initialWeatherInfo) {
console.log("✅ Initial weather display test passed!");
} else {
console.log("❓ No initial weather displayed, will test form submission");
}
// Test form submission
weatherCity.value = "Tokyo";
// Create and dispatch submit event
const submitEvent = new Event('submit');
weatherForm.dispatchEvent(submitEvent);
console.log("Weather form submitted with city: Tokyo");
// Wait for API response
setTimeout(() => {
const weatherInfo = weatherDisplay.querySelector('.weather-info');
if (weatherInfo) {
const cityName = weatherInfo.querySelector('h3');
const temperature = weatherInfo.querySelector('.temperature');
if (cityName && temperature) {
console.log("✅ Weather API test passed!");
console.log(`City: ${cityName.textContent}, Temperature: ${temperature.textContent}`);
} else {
console.error("❌ Weather display structure test failed!");
}
} else {
console.error("❌ Weather API test failed!");
}
if (callback) callback();
}, 2000);
}
// Test Gallery
function testGallery(callback) {
console.log("Testing Image Gallery...");
const galleryImages = document.getElementById('galleryImages');
const prevBtn = document.getElementById('galleryPrev');
const nextBtn = document.getElementById('galleryNext');
const imageUpload = document.getElementById('imageUpload');
const uploadBtn = document.getElementById('uploadImageBtn');
if (!galleryImages || !prevBtn || !nextBtn || !imageUpload || !uploadBtn) {
console.error("Gallery elements not found!");
if (callback) callback();
return;
}
// Check if initial gallery is displayed
const initialImage = galleryImages.querySelector('.gallery-img');
if (initialImage) {
console.log("✅ Initial gallery display test passed!");
// Test navigation
const initialCounter = galleryImages.querySelector('.gallery-counter').textContent;
console.log(`Initial image: ${initialCounter}`);
// Test next button
nextBtn.click();
setTimeout(() => {
const newCounter = galleryImages.querySelector('.gallery-counter').textContent;
console.log(`After next: ${newCounter}`);
if (newCounter !== initialCounter) {
console.log("✅ Gallery navigation test passed!");
} else {
console.error("❌ Gallery navigation test failed!");
}
// Test image upload
const testImageUrl = "https://source.unsplash.com/random/300x200?test";
imageUpload.value = testImageUrl;
uploadBtn.click();
setTimeout(() => {
const finalCounter = galleryImages.querySelector('.gallery-counter').textContent;
console.log(`After upload: ${finalCounter}`);
if (finalCounter.includes(testImageUrl)) {
console.log("✅ Image upload test passed!");
} else {
console.log("⚠️ Image upload test inconclusive - can't verify image URL");
}
if (callback) callback();
}, 500);
}, 500);
} else {
console.log("⚠️ No initial gallery image displayed");
if (callback) callback();
}
}
// Start tests when page is fully loaded
document.addEventListener('DOMContentLoaded', function() {
console.log("DOM loaded, waiting for all resources...");
// Wait a bit longer to ensure all scripts are initialized
setTimeout(autoRunTests, 1000);
});

View File

@ -0,0 +1,54 @@
document.addEventListener('DOMContentLoaded', function() {
const weatherWidget = document.getElementById('weatherWidget');
const weatherCity = document.getElementById('weatherCity');
const weatherForm = document.getElementById('weatherForm');
const weatherDisplay = document.getElementById('weatherDisplay');
weatherForm.addEventListener('submit', function(e) {
e.preventDefault();
const city = weatherCity.value.trim();
if (city) {
fetchWeather(city);
}
});
async function fetchWeather(city) {
try {
weatherDisplay.innerHTML = '<p>Loading weather data...</p>';
// Using OpenWeatherMap API with a free tier (you would need to replace with your own API key in production)
const apiKey = 'demo'; // Using demo mode for example purposes
const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`);
if (!response.ok) {
throw new Error('City not found or API limit reached');
}
const data = await response.json();
displayWeather(data);
} catch (error) {
weatherDisplay.innerHTML = `<p class="weather-error">Error: ${error.message}</p>`;
}
}
function displayWeather(data) {
// For demo purposes, we'll show a simplified version since the API key is demo
weatherDisplay.innerHTML = `
<div class="weather-info">
<h3>${data.name}, ${data.sys.country}</h3>
<div class="weather-main">
<span class="temperature">${Math.round(data.main.temp)}°C</span>
<span class="description">${data.weather[0].description}</span>
</div>
<div class="weather-details">
<p>Feels like: ${Math.round(data.main.feels_like)}°C</p>
<p>Humidity: ${data.main.humidity}%</p>
<p>Wind: ${data.wind.speed} m/s</p>
</div>
</div>
`;
}
// Initialize with a default city
fetchWeather('London');
});