Spaces:
Running
Running
<html> | |
<head> | |
<title>Surf Spot Finder (Animated Workflow Simulation)</title> | |
<style> | |
:root { | |
--primary-color: #00d230; /* theme.primaryColor */ | |
--background-color: #FFFFFF; /* theme.backgroundColor */ | |
--secondary-background-color: #F0F2F6; /* theme.secondaryBackgroundColor */ | |
--text-color: #161616; /* theme.textColor */ | |
--font-family: "sans serif", Arial, sans-serif; | |
--sidebar-width: 360px; | |
--border-radius: 6px; | |
--input-border-color: #ccc; | |
--input-focus-border-color: var(--primary-color); | |
} | |
body { | |
display: flex; | |
flex-direction: column; | |
font-family: var(--font-family); | |
background-color: var(--background-color); | |
color: var(--text-color); | |
margin: 0; | |
height: 100vh; | |
overflow: hidden; | |
} | |
.app-header { | |
padding: 10px 20px; | |
background-color: var(--secondary-background-color); | |
border-bottom: 1px solid #ddd; | |
font-size: 1.2em; | |
color: var(--primary-color); | |
text-align: center; | |
} | |
.app-container { | |
display: flex; | |
flex-grow: 1; | |
overflow: hidden; | |
} | |
/* Animation Controls */ | |
#animation-controls { | |
padding: 15px 20px; | |
background-color: #e0f7fa; | |
border-bottom: 1px solid #b2ebf2; | |
display: flex; | |
align-items: center; | |
gap: 15px; | |
} | |
#animation-controls button { background-color: #00796b; } | |
#animation-controls button:hover:not(:disabled) { background-color: #004d40; } | |
#animation-status { font-style: italic; color: #004d40; } | |
/* Sidebar */ | |
.sidebar { | |
width: var(--sidebar-width); | |
min-width: var(--sidebar-width); | |
background-color: var(--secondary-background-color); | |
padding: 20px; | |
overflow-y: auto; | |
border-right: 1px solid #ddd; | |
} | |
.sidebar h3 { color: var(--primary-color); margin-top: 0; border-bottom: 1px solid #ddd; padding-bottom: 5px;} | |
.sidebar .st-input-container, .sidebar .st-selectbox-container, .sidebar .st-date-input-container, .sidebar .st-time-input-container, .sidebar .st-checkbox-container { | |
margin-bottom: 18px; | |
} | |
.sidebar label { | |
display: block; | |
margin-bottom: 5px; | |
font-size: 0.9em; | |
font-weight: bold; | |
} | |
.sidebar input[type="text"], .sidebar input[type="number"], .sidebar input[type="date"], .sidebar select { | |
width: 100%; | |
padding: 8px 10px; | |
border-radius: var(--border-radius); | |
border: 1px solid var(--input-border-color); | |
box-sizing: border-box; | |
font-family: var(--font-family); | |
} | |
.sidebar input:focus, .sidebar select:focus { | |
border-color: var(--input-focus-border-color); | |
box-shadow: 0 0 0 2px rgba(0, 210, 48, 0.2); | |
outline: none; | |
} | |
.sidebar .st-button { | |
width: 100%; | |
padding: 10px; | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
border-radius: var(--border-radius); | |
cursor: pointer; | |
font-size: 1em; | |
transition: background-color 0.2s; | |
} | |
.sidebar .st-button:hover:not(:disabled) { background-color: #00a020; } | |
.sidebar .st-button:disabled { background-color: #ccc; cursor: not-allowed; } | |
.sidebar .st-expander { | |
border: 1px solid #ddd; | |
border-radius: var(--border-radius); | |
margin-bottom: 15px; | |
background-color: #fff; | |
} | |
.sidebar .st-expander-header { | |
padding: 10px; | |
cursor: pointer; | |
font-weight: bold; | |
border-bottom: 1px solid #eee; | |
display: flex; justify-content: space-between; align-items: center; | |
} | |
.sidebar .st-expander-header::after { content: 'βΌ'; font-size: 0.8em; } | |
.sidebar .st-expander.expanded .st-expander-header::after { content: 'β²'; } | |
.sidebar .st-expander-content { | |
padding: 10px; | |
display: none; /* Toggled by JS */ | |
} | |
.sidebar .st-expander.expanded .st-expander-content { display: block; } | |
.sidebar .st-data-editor { border: 1px solid #ccc; padding: 5px; min-height: 80px; background-color: #f9f9f9; font-size:0.9em; } | |
/* Main Content Area */ | |
.main-content { | |
flex-grow: 1; | |
padding: 25px; | |
overflow-y: auto; | |
} | |
.main-content h1, .main-content h2, .main-content h3 { | |
color: #1B365D; /* From previous simulation, good for headers */ | |
} | |
.main-content .st-info, .main-content .st-success, .main-content .st-error, .main-content .st-code, .main-content .st-status { | |
padding: 12px 15px; | |
margin: 15px 0; | |
border-radius: var(--border-radius); | |
border-left-width: 5px; | |
border-left-style: solid; | |
} | |
.main-content .st-info { background-color: #e7f3fe; border-left-color: #2196F3; } | |
.main-content .st-success { background-color: #e8f5e9; border-left-color: #4CAF50; } | |
.main-content .st-error { background-color: #ffebee; border-left-color: #f44336; } | |
.main-content .st-code { background-color: #f5f5f5; border: 1px solid #e0e0e0; padding: 10px; white-space: pre-wrap; font-family: monospace;} | |
.main-content .st-status { background-color: #fff8e1; border-left-color: #ffc107; } | |
.main-content .st-expander { | |
border: 1px solid #ddd; border-radius: var(--border-radius); margin: 20px 0; | |
} | |
.main-content .st-expander-header { | |
padding: 12px 15px; cursor: pointer; font-weight: bold; background-color: #f9f9f9; | |
border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; | |
} | |
.main-content .st-expander-header::after { content: 'βΌ'; font-size: 0.8em; } | |
.main-content .st-expander.expanded .st-expander-header::after { content: 'β²'; } | |
.main-content .st-expander-content { padding: 15px; display: none; } | |
.main-content .st-expander.expanded .st-expander-content { display: block; } | |
.results-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px;} | |
.trace-span { border: 1px solid #eee; padding: 10px; margin-bottom: 10px; border-radius: var(--border-radius); } | |
.trace-span-header { display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 5px; } | |
.trace-span-status-ok { color: green; } | |
.trace-span-status-error { color: red; } | |
.trace-span-details { font-size: 0.9em; color: #555; white-space: pre-wrap; word-break: break-all;} | |
.evaluation-results-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } | |
.progress-bar-container { background-color: #e0e0e0; border-radius: var(--border-radius); overflow: hidden; height: 20px; margin: 5px 0;} | |
.progress-bar { background-color: var(--primary-color); height: 100%; width: 0%; transition: width 0.5s ease-out; } | |
/* Animation specific styles */ | |
.sim-focus { border-color: var(--primary-color) ; box-shadow: 0 0 0 3px rgba(0, 210, 48, 0.3) ; } | |
.sim-active-button { background-color: #00a020 ; transform: scale(0.98); } | |
.sim-selectbox-option { background-color: #e0ffe0 ; } | |
.sim-expander-toggle { background-color: #f0f0f0; } | |
.gif-simulation-overlay { | |
position: fixed; top: 0; left: 0; width: 100%; height: 100%; | |
background-color: rgba(0,0,0,0.5); color: white; | |
display: flex; flex-direction: column; justify-content: center; align-items: center; | |
z-index: 1000; font-size: 1.5em; | |
} | |
.gif-simulation-overlay .progress-bar-container { width: 50%; margin-top: 20px; } | |
</style> | |
</head> | |
<body> | |
<div class="app-header">π Surf Spot Finder - Animated Simulation</div> | |
<div id="animation-controls"> | |
<button id="start-animation-btn">Run Full Workflow Animation</button> | |
<button id="download-gif-btn">Download GIF (Simulated)</button> | |
<span id="animation-status"></span> | |
</div> | |
<div class="app-container"> | |
<div class="sidebar" id="sidebar"> | |
<!-- Sidebar content will be rendered here by JS --> | |
</div> | |
<div class="main-content" id="main-content"> | |
<!-- Main content will be rendered here by JS --> | |
</div> | |
</div> | |
<script> | |
// --- State Variables --- | |
let simState = { | |
location: "Los Angeles California, US", | |
maxDrivingHours: 2, | |
date: new Date(new Date().setDate(new Date().getDate() + 1)), // Tomorrow | |
time: "09:00", | |
framework: "OPENAI", // Default, assuming AgentFramework.OPENAI.name | |
modelId: "openai/gpt-4.1-mini", | |
evalModelId: "openai/gpt-4o", | |
evalCheckpoints: [ | |
{ criteria: "Agent considered at least three surf spots", points: 1, id: "cp1" }, | |
{ criteria: "Agent gathered wind forecasts", points: 1, id: "cp2" }, | |
], | |
runEvaluation: true, | |
// For agent run | |
currentQuery: "", | |
agentTraceSpans: [], | |
finalAgentOutput: "", | |
executionTime: 0, | |
estimatedCost: 0, | |
totalTokens: 0, | |
evaluationResults: null | |
}; | |
let isAnimating = false; | |
let currentStatusMessages = []; | |
// --- DOM Elements --- | |
const animationStatusEl = document.getElementById('animation-status'); | |
const sidebarEl = document.getElementById('sidebar'); | |
const mainContentEl = document.getElementById('main-content'); | |
const startAnimationBtn = document.getElementById('start-animation-btn'); | |
const downloadGifBtn = document.getElementById('download-gif-btn'); | |
// --- Helper & Simulation Functions --- | |
async function simulateDelay(ms) { /* ... as before ... */ | |
if (!isAnimating) return; | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
async function simulateType(element, text, charDelay = 50) { /* ... as before ... */ | |
if (!isAnimating) { element.value = text; return; } | |
element.classList.add('sim-focus'); | |
element.value = ''; | |
for (const char of text) { | |
element.value += char; | |
await simulateDelay(charDelay + Math.random() * 10); | |
} | |
element.classList.remove('sim-focus'); | |
} | |
async function simulateClick(buttonElement, actionDelay = 200) { /* ... as before ... */ | |
if (!isAnimating) return; | |
buttonElement.classList.add('sim-active-button'); | |
await simulateDelay(actionDelay); | |
buttonElement.classList.remove('sim-active-button'); | |
await simulateDelay(50); | |
} | |
async function simulateSelect(selectElement, value, displayDelay = 500) { | |
if (!isAnimating) { selectElement.value = value; return; } | |
selectElement.classList.add('sim-focus'); | |
// Briefly show each option leading up to the target for visual effect | |
const options = Array.from(selectElement.options); | |
const targetIndex = options.findIndex(opt => opt.value === value); | |
if (targetIndex === -1) { | |
console.warn(`Value ${value} not found in select`); | |
selectElement.value = value; // try to set it anyway | |
selectElement.classList.remove('sim-focus'); | |
return; | |
} | |
for (let i = 0; i <= targetIndex; i++) { | |
selectElement.value = options[i].value; | |
options[i].classList.add('sim-selectbox-option'); | |
await simulateDelay(50 + Math.random()*20); | |
if (i < targetIndex) options[i].classList.remove('sim-selectbox-option'); | |
} | |
await simulateDelay(displayDelay); | |
options[targetIndex].classList.remove('sim-selectbox-option'); | |
selectElement.classList.remove('sim-focus'); | |
} | |
async function simulateCheckbox(checkboxElement, checked, displayDelay = 300) { | |
if (!isAnimating) { checkboxElement.checked = checked; return; } | |
checkboxElement.classList.add('sim-focus'); | |
await simulateDelay(displayDelay / 2); | |
checkboxElement.checked = checked; | |
await simulateDelay(displayDelay / 2); | |
checkboxElement.classList.remove('sim-focus'); | |
} | |
async function simulateExpanderToggle(expanderHeaderElement, expand = true) { | |
if (!isAnimating) { | |
const content = expanderHeaderElement.nextElementSibling; | |
expanderHeaderElement.parentElement.classList.toggle('expanded', expand); | |
if (content) content.style.display = expand ? 'block' : 'none'; | |
return; | |
} | |
expanderHeaderElement.classList.add('sim-expander-toggle'); | |
await simulateDelay(200); | |
const content = expanderHeaderElement.nextElementSibling; | |
expanderHeaderElement.parentElement.classList.toggle('expanded', expand); | |
if (content) content.style.display = expand ? 'block' : 'none'; | |
await simulateDelay(200); | |
expanderHeaderElement.classList.remove('sim-expander-toggle'); | |
} | |
function downloadFile(filename, content, mimeType) { /* ... as before ... */ | |
const blob = new Blob([content], { type: mimeType }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = filename; | |
document.body.appendChild(a); | |
a.click(); | |
URL.revokeObjectURL(url); | |
document.body.removeChild(a); | |
if(!isAnimating) alert(`Simulated download: ${filename}`); | |
} | |
// --- UI Rendering Functions --- | |
function renderSidebar() { | |
// Simplified: In a real Streamlit app, these are Python calls. Here, we build HTML. | |
sidebarEl.innerHTML = ` | |
<h3>Configuration</h3> | |
<div class="st-input-container"> | |
<label for="sim-location">Enter a location</label> | |
<input type="text" id="sim-location" value="${simState.location}"> | |
<div id="sim-location-status" style="font-size:0.8em; text-align:right;"></div> | |
</div> | |
<div class="st-input-container"> | |
<label for="sim-maxDrivingHours">Enter the maximum driving hours</label> | |
<input type="number" id="sim-maxDrivingHours" value="${simState.maxDrivingHours}" min="1"> | |
</div> | |
<div style="display: flex; gap: 10px;"> | |
<div class="st-date-input-container" style="flex:2;"> | |
<label for="sim-date">Select a date</label> | |
<input type="date" id="sim-date" value="${simState.date.toISOString().split('T')[0]}"> | |
</div> | |
<div class="st-time-input-container" style="flex:1;"> | |
<label for="sim-time">Select a time</label> | |
<select id="sim-time"> | |
${Array.from({length: 24}, (_, i) => i).map(h => | |
`<option value="${String(h).padStart(2, '0')}:00" ${simState.time === `${String(h).padStart(2, '0')}:00` ? 'selected' : ''}>${String(h).padStart(2, '0')}:00</option>` | |
).join('')} | |
</select> | |
</div> | |
</div> | |
<div class="st-selectbox-container"> | |
<label for="sim-framework">Select the agent framework to use</label> | |
<select id="sim-framework"> | |
<option value="ANYAGENT" ${simState.framework === 'ANYAGENT' ? 'selected':''}>ANYAGENT</option> | |
<option value="OPENAI" ${simState.framework === 'OPENAI' ? 'selected':''}>OPENAI</option> | |
<option value="LANGCHAIN" ${simState.framework === 'LANGCHAIN' ? 'selected':''}>LANGCHAIN</option> | |
</select> | |
</div> | |
<div class="st-selectbox-container"> | |
<label for="sim-modelId">Select the model to use</label> | |
<select id="sim-modelId"> | |
<option value="openai/gpt-4.1-nano" ${simState.modelId === 'openai/gpt-4.1-nano' ? 'selected':''}>openai/gpt-4.1-nano</option> | |
<option value="openai/gpt-4.1-mini" ${simState.modelId === 'openai/gpt-4.1-mini' ? 'selected':''}>openai/gpt-4.1-mini</option> | |
<option value="openai/gpt-4o" ${simState.modelId === 'openai/gpt-4o' ? 'selected':''}>openai/gpt-4o</option> | |
</select> | |
</div> | |
<div class="st-expander" id="sim-eval-expander"> | |
<div class="st-expander-header" onclick="toggleExpander('sim-eval-expander')">Custom Evaluation</div> | |
<div class="st-expander-content"> | |
<div class="st-selectbox-container"> | |
<label for="sim-evalModelId">Evaluation Model</label> | |
<select id="sim-evalModelId"> | |
<option value="openai/gpt-4.1-mini" ${simState.evalModelId === 'openai/gpt-4.1-mini' ? 'selected':''}>openai/gpt-4.1-mini</option> | |
<option value="openai/gpt-4o" ${simState.evalModelId === 'openai/gpt-4o' ? 'selected':''}>openai/gpt-4o</option> | |
</select> | |
</div> | |
<label>Checkpoints (Simplified Edit):</label> | |
<div class="st-data-editor" id="sim-evalCheckpoints"> | |
${simState.evalCheckpoints.map(cp => `<div>- ${cp.criteria} (${cp.points} pts) <input type="text" value="${cp.criteria}" data-id="${cp.id}" class="sim-checkpoint-crit"><input type="number" value="${cp.points}" data-id="${cp.id}" class="sim-checkpoint-pts" style="width:50px;"></div>`).join('')} | |
</div> | |
<div class="st-checkbox-container"> | |
<input type="checkbox" id="sim-runEvaluation" ${simState.runEvaluation ? 'checked' : ''}> | |
<label for="sim-runEvaluation">Run Evaluation</label> | |
</div> | |
</div> | |
</div> | |
<button class="st-button" id="sim-run-agent-btn">Run Agent π€</button> | |
`; | |
// Add event listeners for manual interaction (if not animating) | |
document.getElementById('sim-run-agent-btn').onclick = () => { if (!isAnimating) manualRunAgent(); }; | |
// More listeners for inputs can be added if full manual mode is desired | |
} | |
function toggleExpander(expanderId) { // For manual clicks | |
if (isAnimating) return; | |
const expander = document.getElementById(expanderId); | |
expander.classList.toggle('expanded'); | |
const content = expander.querySelector('.st-expander-content'); | |
if (content) content.style.display = expander.classList.contains('expanded') ? 'block' : 'none'; | |
} | |
function renderInitialMainContent() { | |
mainContentEl.innerHTML = ` | |
<h1>π Surf Spot Finder</h1> | |
<p>Find the best surfing spots based on your location and preferences! This is a simulation of the <a href="#" target="_blank" rel="noopener noreferrer">[Original Github Repo]</a>.</p> | |
<div class="st-info">π Configure your search parameters in the sidebar and click Run Agent to start (or run the full animation)!</div> | |
<h3>π οΈ Available Tools (Simulated)</h3> | |
<div class="st-expander"> | |
<div class="st-expander-header" onclick="toggleExpander(this.parentElement)">π€οΈ Weather Tools</div> | |
<div class="st-expander-content"> | |
<p><strong>get_wind_forecast:</strong> Gets wind conditions for a spot.</p> | |
<p><strong>get_wave_forecast:</strong> Gets wave conditions for a spot.</p> | |
</div> | |
</div> | |
<div class="st-expander"> | |
<div class="st-expander-header" onclick="toggleExpander(this.parentElement)">π Location Tools</div> | |
<div class="st-expander-content"> | |
<p><strong>get_area_lat_lon:</strong> Converts area name to lat/lon.</p> | |
<p><strong>get_surfing_spots:</strong> Finds surf spots near lat/lon.</p> | |
<p><strong>driving_hours_to_meters:</strong> Converts driving hours to meters.</p> | |
</div> | |
</div> | |
<h3>π Custom Evaluation</h3> | |
<p>The Surf Spot Finder includes a powerful evaluation system. Configure it in the sidebar!</p> | |
`; | |
} | |
function renderAgentRunView() { | |
let html = `<h2>π Running Surf Spot Finder...</h2>`; | |
html += `<div class="st-code" id="sim-query-display">Query: ${simState.currentQuery}</div>`; | |
html += `<div class="st-status" id="sim-status-display">Agent is running...</div>`; | |
// After run is complete | |
if (simState.finalAgentOutput) { | |
html = `<h2>π Results</h2>`; // Overwrite "Running..." | |
html += `<div class="results-grid"> | |
<div class="st-info">β±οΈ Execution Time: ${simState.executionTime.toFixed(2)}s</div> | |
<div class="st-info">π° Estimated Cost: $${simState.estimatedCost.toFixed(6)}</div> | |
<div class="st-info">π¦ Total Tokens: ${simState.totalTokens}</div> | |
</div>`; | |
html += `<h3>Final Output</h3><div class="st-success">${simState.finalAgentOutput}</div>`; | |
html += `<div class="st-expander expanded" id="sim-agent-trace-expander"> | |
<div class="st-expander-header" onclick="toggleExpander('sim-agent-trace-expander')">π§© Agent Trace</div> | |
<div class="st-expander-content" id="sim-agent-trace-content"> | |
${simState.agentTraceSpans.map(span => ` | |
<div class="trace-span"> | |
<div class="trace-span-header"> | |
<span>${span.name}</span> | |
<span class="${span.status === 'OK' ? 'trace-span-status-ok' : 'trace-span-status-error'}">β ${span.status}</span> | |
</div> | |
<div class="trace-span-details"> | |
${span.input ? `<strong>Input:</strong> ${JSON.stringify(span.input).substring(0,150)}...<br>` : ''} | |
${span.output ? `<strong>Output:</strong> ${JSON.stringify(span.output).substring(0,150)}...` : ''} | |
</div> | |
</div> | |
`).join('')} | |
</div> | |
</div>`; | |
if (simState.runEvaluation && simState.evaluationResults) { | |
const { criteriaResults, overallScore, totalPossiblePoints } = simState.evaluationResults; | |
html += `<h2>π Evaluation Results</h2> | |
<div class="evaluation-results-grid"> | |
<div><h4>Criteria Results</h4> | |
${criteriaResults.map(cr => ` | |
<div class="${cr.passed ? 'st-success' : 'st-error'}" style="margin-bottom:5px; padding:8px;"> | |
${cr.passed ? 'β ' : 'β'} ${cr.criteria} (${cr.points} pts) | |
</div>`).join('')} | |
</div> | |
<div><h4>Overall Score</h4> | |
<h3>${overallScore} / ${totalPossiblePoints}</h3> | |
<div class="progress-bar-container"><div class="progress-bar" style="width:${(overallScore/totalPossiblePoints)*100}%"></div></div> | |
<strong>${((overallScore/totalPossiblePoints)*100).toFixed(1)}%</strong> | |
</div> | |
</div>`; | |
} | |
} | |
mainContentEl.innerHTML = html; | |
} | |
function updateStatusDisplay(message, isRunning = true) { | |
const statusEl = document.getElementById('sim-status-display'); | |
if (statusEl) { | |
currentStatusMessages.push(message); | |
if (currentStatusMessages.length > 3) currentStatusMessages.shift(); // Keep last 3 | |
statusEl.innerHTML = currentStatusMessages.join('<br>') + (isRunning ? '<br><em>Agent is active...</em>' : '<br><strong>Agent run finished!</strong>'); | |
if (!isRunning) statusEl.classList.replace('st-status', 'st-success'); | |
} | |
} | |
// --- Core Simulation Logic --- | |
async function manualRunAgent() { // For manual button click | |
if (isAnimating) return; | |
// Gather inputs from DOM | |
simState.location = document.getElementById('sim-location').value; | |
simState.maxDrivingHours = parseInt(document.getElementById('sim-maxDrivingHours').value); | |
simState.date = new Date(document.getElementById('sim-date').value + 'T' + document.getElementById('sim-time').value); | |
simState.framework = document.getElementById('sim-framework').value; | |
simState.modelId = document.getElementById('sim-modelId').value; | |
simState.evalModelId = document.getElementById('sim-evalModelId').value; | |
// Simplified checkpoint reading for manual | |
simState.evalCheckpoints = Array.from(document.querySelectorAll('#sim-evalCheckpoints .sim-checkpoint-crit')).map((inp,idx) => ({ | |
id: `cp${idx+1}`, | |
criteria: inp.value, | |
points: parseInt(document.querySelectorAll('#sim-evalCheckpoints .sim-checkpoint-pts')[idx].value) || 1 | |
})); | |
simState.runEvaluation = document.getElementById('sim-runEvaluation').checked; | |
await _runAgentLogic(); | |
} | |
async function _runAgentLogic() { | |
simState.currentQuery = `Find surf spots near ${simState.location} within ${simState.maxDrivingHours} hours drive for ${simState.date.toDateString()} at ${simState.time}.`; | |
simState.agentTraceSpans = []; | |
simState.finalAgentOutput = ""; | |
simState.executionTime = 0; | |
simState.estimatedCost = 0; | |
simState.totalTokens = 0; | |
simState.evaluationResults = null; | |
currentStatusMessages = []; | |
renderAgentRunView(); // Initial view with query and "Running..." | |
const startTime = Date.now(); | |
// Simulate agent steps | |
await simulateAgentStep("Tool: get_area_lat_lon", {location: simState.location}, {lat: 36.97, lon: -122.03}, "OK"); // Santa Cruz approx | |
await simulateAgentStep("Tool: driving_hours_to_meters", {hours: simState.maxDrivingHours}, {meters: simState.maxDrivingHours * 50000}, "OK"); // Rough conversion | |
await simulateAgentStep("Tool: get_surfing_spots", {lat: 36.97, lon: -122.03, radius: simState.maxDrivingHours * 50000}, {spots: ["Steamer Lane", "Cowell's Beach", "Pleasure Point"]}, "OK"); | |
await simulateAgentStep("Tool: get_wave_forecast", {spot: "Steamer Lane", date: simState.date.toISOString()}, {waveHeight: "4-6ft", period: "12s"}, "OK"); | |
await simulateAgentStep("Tool: get_wind_forecast", {spot: "Steamer Lane", date: simState.date.toISOString()}, {speed: "10mph", direction: "NW"}, "OK"); | |
simState.finalAgentOutput = `Recommended Spot: Steamer Lane. Forecast for ${simState.date.toDateString()} ${simState.time}: Waves 4-6ft (12s period), Wind 10mph NW. Other options: Cowell's Beach, Pleasure Point.`; | |
simState.executionTime = (Date.now() - startTime) / 1000 + (Math.random() * 2); // Add some randomness | |
simState.estimatedCost = 0.0015 + Math.random() * 0.001; | |
simState.totalTokens = 1200 + Math.floor(Math.random() * 500); | |
updateStatusDisplay("Agent finished processing.", false); | |
if (simState.runEvaluation) { | |
await _runEvaluationLogic(); | |
} | |
renderAgentRunView(); // Final render with all results | |
} | |
async function simulateAgentStep(name, input, output, status) { | |
if (!isAnimating && !document.hidden) await simulateDelay(500 + Math.random() * 500); // Only delay if visible and not animating full flow | |
else if (isAnimating) await simulateDelay(300 + Math.random() * 200); | |
const span = { name, input, output, status }; | |
simState.agentTraceSpans.push(span); | |
updateStatusDisplay(`Step: ${name} (${status}) - Input: ${JSON.stringify(input).substring(0,30)}... Output: ${JSON.stringify(output).substring(0,30)}...`); | |
if (isAnimating) renderAgentRunView(); // Re-render trace during animation | |
} | |
async function _runEvaluationLogic() { | |
if (isAnimating) await simulateDelay(1000); // Simulate evaluation time | |
let overallScore = 0; | |
let totalPossiblePoints = 0; | |
const criteriaResults = simState.evalCheckpoints.map(cp => { | |
const passed = Math.random() > 0.3; // 70% chance of passing for simulation | |
if (passed) overallScore += cp.points; | |
totalPossiblePoints += cp.points; | |
return { criteria: cp.criteria, points: cp.points, passed }; | |
}); | |
simState.evaluationResults = { criteriaResults, overallScore, totalPossiblePoints }; | |
} | |
// --- Animation Workflow --- | |
startAnimationBtn.addEventListener('click', async () => { | |
if (isAnimating) { | |
isAnimating = false; | |
animationStatusEl.textContent = "Animation stopped by user."; | |
startAnimationBtn.textContent = "Run Full Workflow Animation"; | |
document.querySelectorAll('.sidebar button, .sidebar input, .sidebar select').forEach(el => el.disabled = false); | |
return; | |
} | |
isAnimating = true; | |
startAnimationBtn.textContent = "Stop Animation"; | |
animationStatusEl.textContent = "Animation running..."; | |
document.querySelectorAll('.sidebar button, .sidebar input, .sidebar select').forEach(el => el.disabled = true); | |
startAnimationBtn.disabled = false; // Keep stop button enabled | |
_resetSimStateForAnimation(); | |
renderSidebar(); // Initial render for animation | |
renderInitialMainContent(); | |
try { | |
await animateFullWorkflow(); | |
if (isAnimating) animationStatusEl.textContent = "Animation completed!"; | |
} catch (error) { | |
if (isAnimating) { | |
console.error("Animation error:", error); | |
animationStatusEl.textContent = "Animation encountered an error: " + error.message; | |
} | |
} finally { | |
isAnimating = false; | |
startAnimationBtn.textContent = "Run Full Workflow Animation"; | |
document.querySelectorAll('.sidebar button, .sidebar input, .sidebar select').forEach(el => el.disabled = false); | |
startAnimationBtn.disabled = false; | |
} | |
}); | |
function _resetSimStateForAnimation() { | |
simState = { // Reset to defaults | |
location: "", maxDrivingHours: 1, date: new Date(new Date().setDate(new Date().getDate() + 2)), time: "10:00", | |
framework: "ANYAGENT", modelId: "openai/gpt-4o", evalModelId: "openai/gpt-4o", | |
evalCheckpoints: [ { criteria: "Agent used location tool", points: 1, id: "acp1" }, { criteria: "Agent found spots", points: 2, id: "acp2" } ], | |
runEvaluation: true, currentQuery: "", agentTraceSpans: [], finalAgentOutput: "", | |
executionTime: 0, estimatedCost: 0, totalTokens: 0, evaluationResults: null | |
}; | |
currentStatusMessages = []; | |
} | |
async function animateFullWorkflow() { | |
animationStatusEl.textContent = "Animating: Sidebar Inputs..."; | |
// Location | |
const locationInput = document.getElementById('sim-location'); | |
await simulateType(locationInput, "Santa Cruz, California"); | |
simState.location = locationInput.value; | |
document.getElementById('sim-location-status').textContent = 'β Validated'; // Simulate validation | |
await simulateDelay(300); | |
// Max Driving Hours | |
const hoursInput = document.getElementById('sim-maxDrivingHours'); | |
await simulateType(hoursInput, "1"); // Type "1" | |
simState.maxDrivingHours = parseInt(hoursInput.value); | |
await simulateDelay(300); | |
// Date & Time (simplified selection for animation) | |
const dateInput = document.getElementById('sim-date'); | |
const futureDate = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; // 3 days in future | |
dateInput.value = futureDate; // Just set it | |
simState.date = new Date(futureDate); | |
dateInput.classList.add('sim-focus'); await simulateDelay(200); dateInput.classList.remove('sim-focus'); | |
const timeSelect = document.getElementById('sim-time'); | |
await simulateSelect(timeSelect, "10:00"); | |
simState.time = timeSelect.value; | |
simState.date.setHours(10,0,0,0); // Update date object with time | |
// Framework & Model | |
await simulateSelect(document.getElementById('sim-framework'), "OPENAI"); | |
simState.framework = document.getElementById('sim-framework').value; | |
await simulateSelect(document.getElementById('sim-modelId'), "openai/gpt-4o"); | |
simState.modelId = document.getElementById('sim-modelId').value; | |
// Custom Evaluation | |
const evalExpanderHeader = document.getElementById('sim-eval-expander').firstElementChild; | |
await simulateExpanderToggle(evalExpanderHeader, true); // Expand | |
await simulateDelay(200); | |
await simulateSelect(document.getElementById('sim-evalModelId'), "openai/gpt-4o"); | |
simState.evalModelId = document.getElementById('sim-evalModelId').value; | |
// Simulate editing a checkpoint | |
const firstCritInput = document.querySelector('#sim-evalCheckpoints .sim-checkpoint-crit'); | |
if (firstCritInput) { | |
await simulateType(firstCritInput, "Agent correctly identified tide times"); | |
simState.evalCheckpoints[0].criteria = firstCritInput.value; | |
} | |
await simulateCheckbox(document.getElementById('sim-runEvaluation'), true); | |
simState.runEvaluation = true; | |
await simulateDelay(500); | |
await simulateExpanderToggle(evalExpanderHeader, false); // Collapse | |
animationStatusEl.textContent = "Animating: Running Agent..."; | |
await simulateClick(document.getElementById('sim-run-agent-btn')); | |
await _runAgentLogic(); // This will handle rendering the main content | |
if (isAnimating) animationStatusEl.textContent = "Animation: Workflow Complete!"; | |
} | |
// --- GIF Download Simulation --- | |
downloadGifBtn.addEventListener('click', async () => { | |
if (isAnimating) { | |
alert("Please stop the main animation before simulating GIF download."); | |
return; | |
} | |
animationStatusEl.textContent = "Simulating GIF Download..."; | |
downloadGifBtn.disabled = true; | |
const overlay = document.createElement('div'); | |
overlay.className = 'gif-simulation-overlay'; | |
overlay.innerHTML = ` | |
<div>π₯ Simulating GIF Recording...</div> | |
<div class="progress-bar-container" style="width:300px; height: 20px; background: #555;"> | |
<div class="progress-bar" id="gif-sim-progress" style="background: lightgreen; width:0%;"></div> | |
</div> | |
<p style="font-size:0.7em;">(This is a placeholder. Actual GIF generation is complex.)</p> | |
`; | |
document.body.appendChild(overlay); | |
const progressBar = document.getElementById('gif-sim-progress'); | |
for (let i = 0; i <= 100; i+=10) { | |
progressBar.style.width = i + '%'; | |
await new Promise(r => setTimeout(r, 150)); | |
} | |
downloadFile("simulated_animation.txt", "This is a placeholder for an animated GIF of the workflow.\nActual client-side GIF generation of complex animations is computationally intensive.", "text/plain"); | |
document.body.removeChild(overlay); | |
animationStatusEl.textContent = "GIF Download Simulation Complete."; | |
downloadGifBtn.disabled = false; | |
}); | |
// --- Initial Page Load --- | |
renderSidebar(); | |
renderInitialMainContent(); | |
</script> | |
</body> | |
</html> |