Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Surf Spot Finder (Animated Annotation)</title> | |
<style> | |
/* Basic simulation of Streamlit layout and theme */ | |
body { | |
font-family: "Source Sans Pro", sans-serif; /* Simulating Streamlit default */ | |
margin: 0; | |
padding: 0; | |
background-color: #FFFFFF; /* White background */ | |
color: #161616; /* Text color */ | |
display: flex; | |
min-height: 100vh; | |
} | |
.sidebar { | |
width: 300px; /* Typical sidebar width */ | |
background-color: #F0F2F6; /* Secondary background color */ | |
padding: 20px; | |
overflow-y: auto; /* Allow scrolling if content overflows */ | |
flex-shrink: 0; /* Prevent sidebar from shrinking */ | |
position: sticky; | |
top: 0; | |
height: 100vh; /* Make it full height */ | |
box-sizing: border-box; /* Include padding in width */ | |
position: relative; /* Important: Needed for z-index to create a stacking context */ | |
z-index: 10; /* Important: Ensure sidebar content is above main content */ | |
} | |
.main-content { | |
flex-grow: 1; /* Main content takes remaining space */ | |
padding: 20px; | |
max-width: 900px; /* Limit main content width */ | |
margin: 0 auto; /* Center main content */ | |
box-sizing: border-box; /* Include padding in width */ | |
/* Main content doesn't strictly need z-index here, | |
as sidebar's higher z-index handles stacking */ | |
} | |
h1, h2, h3, h4, h5, h6 { | |
color: #161616; /* Dark text for headings */ | |
} | |
button { | |
background-color: #00d230; /* Primary color */ | |
color: white; | |
padding: 10px 20px; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
font-size: 1rem; | |
margin-top: 15px; | |
width: 100%; /* Make button fill sidebar width */ | |
box-sizing: border-box; | |
transition: border-color 0.5s ease; /* Transition for highlight */ | |
} | |
button:disabled { | |
background-color: #cccccc; | |
cursor: not-allowed; | |
} | |
input[type="text"], | |
input[type="number"], | |
input[type="date"], | |
select { | |
width: 100%; | |
padding: 8px; | |
margin-top: 5px; | |
margin-bottom: 10px; | |
border: 1px solid #cccccc; | |
border-radius: 4px; | |
box-sizing: border-box; /* Include padding and border in the element's total width and height */ | |
transition: border-color 0.5s ease; /* Transition for highlight */ | |
} | |
.st-columns { | |
display: flex; | |
gap: 10px; /* Space between columns */ | |
/* Removed margin-bottom from here, added to annotated-field */ | |
transition: border-color 0.5s ease; /* Transition for highlight */ | |
} | |
.st-columns > div { | |
flex: 1; /* Distribute space */ | |
} | |
.st-columns > div:first-child { | |
flex: 3; /* Simulate 3:1 ratio */ | |
} | |
.st-expander { | |
border: 1px solid #cccccc; | |
border-radius: 5px; | |
margin-bottom: 15px; | |
overflow: hidden; /* Hide overflow from inner content like data editor */ | |
} | |
.st-expander-header { | |
padding: 10px; | |
background-color: #F8F9FA; /* Slightly different background for header */ | |
cursor: pointer; | |
font-weight: bold; | |
} | |
.st-expander-content { | |
padding: 10px; | |
border-top: 1px solid #cccccc; | |
/* By default, show content as if expander is open */ | |
display: block; | |
} | |
.st-info { | |
background-color: #e6f7ff; /* Light blue */ | |
border-left: 5px solid #2196F3; /* Blue border */ | |
padding: 15px; | |
margin-bottom: 15px; | |
border-radius: 4px; | |
} | |
.st-success { | |
color: green; | |
font-weight: bold; | |
margin-left: 5px; | |
} | |
.st-error { | |
color: red; | |
font-weight: bold; | |
margin-left: 5px; | |
} | |
table { | |
width: 100%; | |
border-collapse: collapse; | |
margin-top: 10px; | |
margin-bottom: 10px; | |
font-size: 0.9em; /* Smaller text in tables */ | |
transition: border-color 0.5s ease; /* Transition for highlight */ | |
} | |
th, td { | |
border: 1px solid #dddddd; | |
text-align: left; | |
padding: 8px; | |
vertical-align: top; /* Align content to top */ | |
} | |
th { | |
background-color: #f2f2f2; | |
} | |
td:first-child { | |
width: 80%; /* Criteria column wider */ | |
} | |
td:last-child { | |
width: 20%; /* Points column narrower */ | |
} | |
textarea { | |
width: 100%; | |
padding: 8px; | |
margin-top: 0; /* Adjust spacing */ | |
margin-bottom: 0; /* Adjust spacing */ | |
border: 1px solid #cccccc; | |
border-radius: 4px; | |
box-sizing: border-box; | |
min-height: 50px; /* Make textareas smaller in table */ | |
resize: vertical; /* Allow vertical resize */ | |
font-size: 1em; /* Reset font size for input */ | |
transition: border-color 0.5s ease; /* Transition for highlight */ | |
} | |
td input[type="number"] { | |
width: 60px; /* Smaller width for point input */ | |
padding: 4px; | |
margin: 0; | |
transition: border-color 0.5s ease; /* Transition for highlight */ | |
} | |
/* Simulate data editor add row button */ | |
.add-row-button { | |
display: inline-block; | |
background-color: #e9e9e9; | |
border: 1px solid #cccccc; | |
border-radius: 4px; | |
padding: 4px 8px; | |
font-size: 0.9em; | |
cursor: pointer; | |
margin-top: 5px; | |
margin-bottom: 10px; | |
} | |
a { | |
color: #00d230; /* Primary color for links */ | |
text-decoration: none; | |
} | |
a:hover { | |
text-decoration: underline; | |
} | |
.st-checkbox { | |
margin-top: 10px; | |
margin-bottom: 10px; | |
position: relative; /* Needed for absolute positioning inside */ | |
display: inline-block; /* Make it an inline block to contain bubble */ | |
} | |
/* Highlight the checkbox input itself */ | |
.st-checkbox input[type="checkbox"] { | |
transition: outline-color 0.5s ease; /* Highlight checkbox outline */ | |
} | |
.st-checkbox input[type="checkbox"].highlight { | |
outline: 2px solid red; /* Use outline for checkbox */ | |
outline-offset: 2px; /* Keep outline separate from element */ | |
border-color: red; /* Also highlight border just in case */ | |
} | |
label { | |
font-weight: bold; | |
display: block; /* Make labels block elements */ | |
margin-bottom: 5px; | |
} | |
/* Highlighting Style */ | |
.highlight { | |
border-color: red ; /* Use important to override default styles */ | |
} | |
.highlight.st-columns { | |
/* Highlight borders of inputs within st-columns */ | |
} | |
.highlight.st-columns input, | |
.highlight.st-columns select { | |
border-color: red ; | |
} | |
/* Container for Annotated Fields */ | |
.annotated-field { | |
position: relative; /* Parent for absolute positioning of bubble */ | |
margin-bottom: 25px; /* Add space below each field group to make room for bubble */ | |
} | |
/* Adjust margin for the very last annotated field if needed */ | |
.sidebar .annotated-field:last-of-type { | |
margin-bottom: 15px; /* Less margin after the button */ | |
} | |
/* Speech Bubble Style */ | |
.speech-bubble { | |
position: absolute; | |
background-color: #ffffcc; /* Light yellow */ | |
border: 1px solid #ffcc00; /* Orange border */ | |
padding: 8px 12px; /* More padding */ | |
border-radius: 8px; /* Rounder corners */ | |
font-size: 0.85em; /* Slightly larger font */ | |
z-index: 10; /* Higher z-index within the sidebar's stacking context */ | |
width: 180px; /* Fixed width */ | |
box-shadow: 2px 2px 5px rgba(0,0,0,0.2); | |
pointer-events: none; /* Allow clicks to pass through */ | |
line-height: 1.4; | |
/* Position bubble to the right */ | |
left: calc(100% + 15px); /* 15px space from the right edge of parent */ | |
top: 50%; /* Vertically center */ | |
transform: translateY(-50%); /* Adjust vertical centering */ | |
opacity: 0; /* Initially hidden */ | |
visibility: hidden; /* Initially hidden */ | |
transition: opacity 0.5s ease, visibility 0.5s ease; /* Animation */ | |
} | |
/* Speech Bubble Pointer (Points Left) */ | |
.speech-bubble::before { | |
content: ''; | |
position: absolute; | |
width: 0; | |
height: 0; | |
border-top: 8px solid transparent; | |
border-bottom: 8px solid transparent; | |
border-right: 8px solid #ffcc00; /* Border color */ | |
left: -8px; /* Position at the edge */ | |
top: 50%; | |
transform: translateY(-50%); /* Vertically center pointer */ | |
} | |
.speech-bubble::after { | |
content: ''; | |
position: absolute; | |
width: 0; | |
height: 0; | |
/* Inner pointer */ | |
border-top: 7px solid transparent; | |
border-bottom: 7px solid transparent; | |
border-right: 7px solid #ffffcc; /* Background color */ | |
left: -7px; /* Position slightly inside border */ | |
top: 50%; | |
transform: translateY(-50%); /* Vertically center pointer */ | |
} | |
/* Adjust bubble position for date/time columns (positioned below) */ | |
.annotated-field .st-columns + .speech-bubble { | |
top: auto; /* Override top: 50% */ | |
bottom: -20px; /* Position below the container */ | |
left: 50%; /* Center horizontally */ | |
transform: translateX(-50%); | |
width: 280px; /* Wider bubble */ | |
} | |
.annotated-field .st-columns + .speech-bubble::before, | |
.annotated-field .st-columns + .speech-bubble::after { | |
left: 50%; | |
top: -8px; /* Position above */ | |
bottom: auto; /* Override bottom */ | |
transform: translateX(-50%) rotate(90deg); /* Rotate to point up */ | |
border-left: 8px solid transparent; border-right: 8px solid transparent; border-bottom: 8px solid #ffcc00; border-top: none; /* Redefine border for upward pointer */ | |
} | |
.annotated-field .st-columns + .speech-bubble::after { | |
left: 50%; | |
top: -7px; /* Position above */ | |
bottom: auto; | |
transform: translateX(-50%) rotate(90deg); | |
border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 7px solid #ffffcc; border-top: none; | |
} | |
/* Position bubble for Data Editor Table (positioned to the right, slightly down) */ | |
.annotated-field table + .speech-bubble { | |
top: 10px; /* Position slightly below the table top */ | |
left: calc(100% + 15px); transform: none; width: 200px; | |
} | |
/* Position bubble for the checkbox (positioned to the right) */ | |
.annotated-field .st-checkbox + .speech-bubble { | |
top: 50%; /* Vertically center with the checkbox */ | |
left: calc(100% + 15px); | |
transform: translateY(-50%); | |
width: 150px; | |
} | |
/* Position bubble for the button (positioned to the right) */ | |
.annotated-field button.highlight + .speech-bubble { | |
top: 50%; /* Vertically center with the button */ | |
left: calc(100% + 15px); | |
transform: translateY(-50%); | |
width: 150px; | |
} | |
/* Styling for tool expanders */ | |
.tool-description { | |
font-size: 0.9em; | |
color: #555; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- Sidebar Simulation --> | |
<div class="sidebar"> | |
<h3>Configuration</h3> | |
<p style="font-size: 0.9em;">Built using <a href="https://github.com/mozilla-ai/any-agent">Any-Agent</a></p> | |
<div class="annotated-field"> | |
<label for="location">Enter a location</label> | |
<div class="st-columns"> | |
<div><input type="text" id="location" value="Los Angeles California, US"></div> | |
<div><span class="st-success">✅</span></div> | |
</div> | |
<div class="speech-bubble">Enter the surf spot location (e.g., city, state, country). Used to find nearby surf spots.</div> | |
</div> | |
<div class="annotated-field"> | |
<label for="max-driving-hours">Enter the maximum driving hours</label> | |
<input type="number" id="max-driving-hours" value="2" min="1"> | |
<div class="speech-bubble">Sets the maximum driving distance from the location, in hours.</div> | |
</div> | |
<div class="annotated-field"> | |
<div class="st-columns" id="date-time-inputs"> | |
<div> | |
<label for="date-input">Select a date in the future</label> | |
<input type="date" id="date-input" value="2024-12-25"> | |
</div> | |
<div> | |
<label for="time-select">Select a time</label> | |
<select id="time-select"> | |
<option value="09:00" selected>09:00</option> | |
<option value="10:00">10:00</option> | |
<!-- ... other times ... --> | |
</select> | |
</div> | |
</div> | |
<div class="speech-bubble"> | |
<span class="speech-bubble-pointer"></span> | |
Select the date and time for which you want the surf and wind forecasts. Must be in the future. | |
</div> | |
</div> | |
<div class="annotated-field"> | |
<label for="framework-select">Select the agent framework to use</label> | |
<select id="framework-select"> | |
<option>OPENAI</option> | |
<option>LANGCHAIN</option> | |
<option selected>ANYAGENT</option> | |
<!-- ... other frameworks ... --> | |
</select> | |
<div class="speech-bubble">Choose the underlying AI agent framework (e.g., LangChain, OpenAI Assistants, Any-Agent).</div> | |
</div> | |
<div class="annotated-field"> | |
<label for="model-select">Select the model to use</label> | |
<select id="model-select"> | |
<option>openai/gpt-4.1-nano</option> | |
<option selected>openai/gpt-4.1-mini</option> | |
<option>openai/gpt-4o</option> | |
<option>gemini/gemini-2.0-flash-lite</option> | |
<!-- ... other models ... --> | |
</select> | |
<div class="speech-bubble">Select the specific large language model the agent will use for reasoning and responses.</div> | |
</div> | |
<!-- Custom Evaluation Expander Simulation --> | |
<div class="st-expander"> | |
<div class="st-expander-header">Custom Evaluation</div> | |
<div class="st-expander-content"> | |
<div class="annotated-field"> | |
<label for="eval-model-select">Select the model to use for LLM-as-a-Judge evaluation</label> | |
<select id="eval-model-select"> | |
<option>openai/gpt-4.1-nano</option> | |
<option>openai/gpt-4.1-mini</option> | |
<option selected>openai/gpt-4o</option> | |
<!-- ... other models ... --> | |
</select> | |
<div class="speech-bubble">Choose the LLM that will act as the judge to evaluate the agent's performance based on your criteria.</div> | |
</div> | |
<p style="font-weight: bold;">Checkpoints</p> | |
<div class="annotated-field"> | |
<table id="checkpoints-table"> | |
<thead> | |
<tr> | |
<th>Criteria</th> | |
<th>Points</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>Check if the agent considered at least three surf spot options</td> | |
<td>1</td> | |
</tr> | |
<tr> | |
<td>Check if the agent gathered wind forecasts for each surf spot being evaluated.</td> | |
<td>1</td> | |
</tr> | |
<tr> | |
<td>Check if the agent used any web search tools to explore which surf spots should be considered</td> | |
<td>1</td> | |
</tr> | |
<!-- ... simulate more rows ... --> | |
<tr> | |
<td><textarea placeholder="Enter criteria here..."></textarea></td> | |
<td><input type="number" value="1" min="0"></td> | |
</tr> | |
</tbody> | |
</table> | |
<div class="speech-bubble"> | |
Define specific checkpoints the agent should meet and assign points. The judge LLM scores the trace. | |
</div> | |
<div class="add-row-button"> | |
+ Add row <!-- This button is part of the data editor simulation --> | |
</div> | |
</div> | |
<div class="annotated-field"> | |
<div class="st-checkbox" id="run-evaluation-checkbox"> | |
<label><input type="checkbox" checked> Run Evaluation</label> | |
</div> | |
<div class="speech-bubble">Toggle whether the custom evaluation runs after the agent finishes.</div> | |
</div> | |
</div> | |
</div> | |
<div class="annotated-field"> | |
<button type="button" id="run-button">Run Agent 🤖</button> | |
<div class="speech-bubble">Click to start the agent with the current configuration.</div> | |
</div> | |
</div> | |
<!-- Main Content Simulation (Initial State) --> | |
<div class="main-content"> | |
<h1>🏄 Surf Spot Finder</h1> | |
<p>Find the best surfing spots based on your location and preferences! <a href="https://github.com/mozilla-ai/surf-spot-finder">Github Repo</a></p> | |
<div class="st-info"> | |
👈 Configure your search parameters in the sidebar and click Run to start! | |
</div> | |
<h3>🛠️ Available Tools</h3> | |
<p> | |
The AI Agent built for this project has a few tools available for use in order to find the perfect surf spot. | |
The agent is given the freedom to use (or not use) these tools in order to accomplish the task. | |
</p> | |
<!-- Simulated Tool Expanders --> | |
<div class="st-expander"> | |
<div class="st-expander-header">🌤️ get_wave_forecast</div> | |
<div class="st-expander-content tool-description"> | |
Fetches the wave forecast for a given latitude and longitude. | |
Uses the <a href="https://open-meteo.com/en/docs/marine-weather-api">Open-Meteo Marine Weather API</a>. | |
It provides data like wave height, direction, and period. | |
</div> | |
</div> | |
<div class="st-expander"> | |
<div class="st-expander-header">🌤️ get_wind_forecast</div> | |
<div class="st-expander-content tool-description"> | |
Fetches the wind forecast for a given latitude and longitude. | |
Uses the <a href="https://open-meteo.com/en/docs/forecast-api">Open-Meteo Forecast API</a>. | |
It provides data like wind speed, direction, and gusts. | |
</div> | |
</div> | |
<div class="st-expander"> | |
<div class="st-expander-header">📍 get_area_lat_lon</div> | |
<div class="st-expander-content tool-description"> | |
Gets the latitude and longitude for a given area name. | |
Uses the <a href="https://nominatim.org/release-docs/develop/api/Search/">Nominatim API</a>. | |
</div> | |
</div> | |
<div class="st-expander"> | |
<div class="st-expander-header">🌐 search_web</div> | |
<div class="st-expander-content tool-description"> | |
Search the web for information. Returns a list of snippets from the search results. | |
</div> | |
</div> | |
<div class="st-expander"> | |
<div class="st-expander-header">🌐 visit_webpage</div> | |
<div class="st-expander-content tool-description"> | |
Visit a webpage and extract its main content. | |
</div> | |
</div> | |
<!-- Note about potentially missing tools --> | |
<p style="font-size: 0.8em; color: #888;"> | |
<span style="color: orange;">▲</span> Some tools may not be listed depending on configuration. Please check the code for more details. | |
</p> | |
<h3>📊 Custom Evaluation</h3> | |
<p> | |
The Surf Spot Finder includes a powerful evaluation system that allows you to customize how the agent's performance is assessed. | |
You can find these settings in the sidebar under the "Custom Evaluation" expander. | |
</p> | |
<!-- Learn More Expander Simulation --> | |
<div class="st-expander"> | |
<div class="st-expander-header">Learn more about Custom Evaluation</div> | |
<div class="st-expander-content"> | |
<h4>What is Custom Evaluation?</h4> | |
<p class="tool-description"> | |
The Custom Evaluation feature uses an LLM-as-a-Judge approach to evaluate how well the agent performs its task. | |
An LLM will be given the complete agent trace (not just the final answer), and will assess the agent's performance based on the criteria you set. | |
You can customize: | |
</p> | |
<ul> | |
<li class="tool-description"><b>Evaluation Model</b>: Choose which LLM should act as the judge</li> | |
<li class="tool-description"><b>Evaluation Criteria</b>: Define specific checkpoints that the agent should meet</li> | |
<li class="tool-description"><b>Scoring System</b>: Assign points to each criterion</li> | |
</ul> | |
<h4>How to Use Custom Evaluation</h4> | |
<p class="tool-description"> | |
1. <b>Select an Evaluation Model</b>: Choose which LLM you want to use as the judge<br> | |
2. <b>Edit Checkpoints</b>: Use the data editor to: | |
</p> | |
<ul> | |
<li class="tool-description">Add new evaluation criteria</li> | |
<li class="tool-description">Modify existing criteria</li> | |
<li class="tool-description">Adjust point values</li> | |
<li class="tool-description">Remove criteria you don't want to evaluate</li> | |
</ul> | |
<h4>Example Criteria</h4> | |
<p class="tool-description">You can evaluate things like:</p> | |
<ul> | |
<li class="tool-description">Tool usage and success</li> | |
<li class="tool-description">Order of operations</li> | |
<li class="tool-description">Quality of final recommendations</li> | |
<li class="tool-description">Response completeness</li> | |
<li class="tool-description">Number of steps taken</li> | |
</ul> | |
<p class="tool-description">The evaluation results will be displayed after each agent run, showing how well the agent met your custom criteria.</p> | |
</div> | |
</div> | |
</div> | |
<script> | |
async function sleep(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
async function animateSidebarFields() { | |
const annotatedFields = document.querySelectorAll('.sidebar .annotated-field'); | |
const animationDelay = 3000; // Duration each bubble/highlight stays visible | |
const fadeDelay = 500; // Duration of fade transition | |
for (const field of annotatedFields) { | |
// Find the input element(s) or container to highlight | |
// Include specific inputs within columns/tables | |
const highlightTargets = []; | |
const directInputs = field.querySelectorAll('input, select, button'); | |
directInputs.forEach(input => highlightTargets.push(input)); | |
const columns = field.querySelector('.st-columns'); | |
if (columns) highlightTargets.push(columns); // Highlight the container | |
const table = field.querySelector('table'); | |
if (table) highlightTargets.push(table); // Highlight the table container | |
const bubble = field.querySelector('.speech-bubble'); | |
if (!highlightTargets.length && !bubble) { | |
console.warn("Annotated field found without highlight target or bubble:", field); | |
continue; // Skip if nothing to highlight or show | |
} | |
// Add highlight class(es) | |
highlightTargets.forEach(target => { | |
// Special handling for checkbox input within the label | |
if (target.closest('.st-checkbox')) { | |
target.querySelector('input[type="checkbox"]').classList.add('highlight'); | |
} else { | |
target.classList.add('highlight'); | |
// If it's st-columns, highlight children inputs/selects too | |
if (target.classList.contains('st-columns')) { | |
target.querySelectorAll('input, select').forEach(childInput => childInput.classList.add('highlight')); | |
} | |
/* | |
// If it's a table, highlight textareas/inputs within td - handled by highlighting the table container itself | |
if (target.tagName === 'TABLE') { | |
target.querySelectorAll('td textarea, td input[type="number"]').forEach(cellInput => cellInput.classList.add('highlight')); | |
} | |
*/ | |
} | |
}); | |
// Show speech bubble | |
if (bubble) { | |
bubble.style.opacity = '1'; | |
bubble.style.visibility = 'visible'; | |
} | |
// Simulate user interaction delay | |
await sleep(animationDelay); | |
// Remove highlight class(es) | |
highlightTargets.forEach(target => { | |
if (target.closest('.st-checkbox')) { | |
target.querySelector('input[type="checkbox"]').classList.remove('highlight'); | |
} else { | |
target.classList.remove('highlight'); | |
if (target.classList.contains('st-columns')) { | |
target.querySelectorAll('input, select').forEach(childInput => childInput.classList.remove('highlight')); | |
} | |
/* | |
// If it's a table, remove highlight from children - Handled by removing from table container | |
if (target.tagName === 'TABLE') { | |
target.querySelectorAll('td textarea, td input[type="number"]').forEach(cellInput => cellInput.classList.remove('highlight')); | |
} | |
*/ | |
} | |
}); | |
// Hide speech bubble | |
if (bubble) { | |
bubble.style.opacity = '0'; | |
// No need to set visibility: 'hidden' immediately, transition handles it | |
} | |
await sleep(fadeDelay); // Delay for fade out transition before moving to next field | |
} | |
// Optional: Loop the animation or stop | |
// animateSidebarFields(); // Uncomment to loop | |
} | |
// Start the animation when the page loads | |
window.addEventListener('load', animateSidebarFields); | |
</script> | |
</body> | |
</html> |