feat: Add Auto Task UI - intelligent self-executing task interface
- autotask.html: Auto Task interface with intent input, plan preview, decisions - autotask.css: Premium styling for Auto Task system - autotask.js: Client-side logic, WebSocket handling, approval workflow
This commit is contained in:
parent
6e504a5742
commit
a63d0bdd34
7 changed files with 2449 additions and 6 deletions
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dialog Designer - General Bots</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10/dist/ext/ws.js"></script>
|
||||
<script src="js/vendor/htmx.min.js"></script>
|
||||
<script src="js/vendor/htmx-ws.js"></script>
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<style>
|
||||
:root {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Editor - General Bots</title>
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<script src="js/vendor/htmx.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>General Bots Suite</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<script src="js/vendor/htmx.min.js"></script>
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<style>
|
||||
:root {
|
||||
|
|
|
|||
1077
ui/suite/tasks/autotask.css
Normal file
1077
ui/suite/tasks/autotask.css
Normal file
File diff suppressed because it is too large
Load diff
492
ui/suite/tasks/autotask.html
Normal file
492
ui/suite/tasks/autotask.html
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
<div class="autotask-container">
|
||||
<!-- Header -->
|
||||
<div class="autotask-header">
|
||||
<div class="header-content">
|
||||
<h1 class="autotask-title">
|
||||
<span class="autotask-icon">⚡</span>
|
||||
Auto Tasks
|
||||
<span class="beta-badge">PREMIUM</span>
|
||||
</h1>
|
||||
<p class="autotask-subtitle">Intelligent self-executing tasks powered by AI</p>
|
||||
</div>
|
||||
<div class="header-stats" id="autotask-stats">
|
||||
<span class="stat-item">
|
||||
<span class="stat-value" id="stat-running">0</span>
|
||||
<span class="stat-label">Running</span>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="stat-value" id="stat-pending">0</span>
|
||||
<span class="stat-label">Pending</span>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="stat-value" id="stat-completed">0</span>
|
||||
<span class="stat-label">Completed</span>
|
||||
</span>
|
||||
<span class="stat-item highlight">
|
||||
<span class="stat-value" id="stat-approval">0</span>
|
||||
<span class="stat-label">Need Approval</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Intent Input Section -->
|
||||
<div class="intent-section">
|
||||
<div class="intent-header">
|
||||
<span class="section-icon">💡</span>
|
||||
<h2>What would you like to accomplish?</h2>
|
||||
</div>
|
||||
<form class="intent-form" id="intent-form" hx-post="/api/autotask/compile" hx-target="#compilation-result" hx-indicator="#compile-spinner">
|
||||
<div class="intent-input-wrapper">
|
||||
<textarea
|
||||
name="intent"
|
||||
id="intent-input"
|
||||
class="intent-input"
|
||||
placeholder="Describe your task in natural language... Examples: • Make a financial CRM for Deloitte with client management and reporting • Create a website that collects leads and sends them to Salesforce • Build an automated email campaign for product launch • Analyze sales data and generate weekly reports"
|
||||
rows="4"
|
||||
required
|
||||
></textarea>
|
||||
<div class="intent-actions">
|
||||
<select name="execution_mode" class="execution-mode-select">
|
||||
<option value="semi-automatic">Semi-Automatic (Recommended)</option>
|
||||
<option value="supervised">Supervised (Step-by-step)</option>
|
||||
<option value="fully-automatic">Fully Automatic</option>
|
||||
<option value="dry-run">Dry Run (Simulate Only)</option>
|
||||
</select>
|
||||
<select name="priority" class="priority-select">
|
||||
<option value="medium">Medium Priority</option>
|
||||
<option value="critical">Critical</option>
|
||||
<option value="high">High</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="background">Background</option>
|
||||
</select>
|
||||
<button type="submit" class="btn-compile">
|
||||
<span class="btn-icon">🚀</span>
|
||||
Compile & Plan
|
||||
<span class="spinner" id="compile-spinner"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Compilation Result -->
|
||||
<div id="compilation-result" class="compilation-result"></div>
|
||||
</div>
|
||||
|
||||
<!-- Active Tasks Section -->
|
||||
<div class="tasks-section">
|
||||
<div class="section-header">
|
||||
<div class="section-title">
|
||||
<span class="section-icon">📋</span>
|
||||
<h2>Active Tasks</h2>
|
||||
</div>
|
||||
<div class="section-actions">
|
||||
<div class="filter-tabs">
|
||||
<button class="filter-tab active" data-filter="all" onclick="filterTasks('all', this)">
|
||||
All <span class="count" id="count-all">0</span>
|
||||
</button>
|
||||
<button class="filter-tab" data-filter="running" onclick="filterTasks('running', this)">
|
||||
Running <span class="count" id="count-running">0</span>
|
||||
</button>
|
||||
<button class="filter-tab" data-filter="approval" onclick="filterTasks('approval', this)">
|
||||
Need Approval <span class="count" id="count-approval">0</span>
|
||||
</button>
|
||||
<button class="filter-tab" data-filter="decision" onclick="filterTasks('decision', this)">
|
||||
Decisions <span class="count" id="count-decision">0</span>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-refresh" onclick="refreshTasks()" title="Refresh">
|
||||
<span>🔄</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Task List -->
|
||||
<div class="task-list" id="task-list" hx-get="/api/autotask/list" hx-trigger="load, every 5s" hx-swap="innerHTML">
|
||||
<div class="loading-placeholder">
|
||||
<div class="spinner-large"></div>
|
||||
<p>Loading tasks...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pending Decisions Modal -->
|
||||
<div class="modal" id="decision-modal" style="display: none;">
|
||||
<div class="modal-backdrop" onclick="closeDecisionModal()"></div>
|
||||
<div class="modal-content decision-modal-content">
|
||||
<div class="modal-header">
|
||||
<h3><span class="modal-icon">🤔</span> Decision Required</h3>
|
||||
<button class="modal-close" onclick="closeDecisionModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body" id="decision-content">
|
||||
<!-- Decision content loaded dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Approval Modal -->
|
||||
<div class="modal" id="approval-modal" style="display: none;">
|
||||
<div class="modal-backdrop" onclick="closeApprovalModal()"></div>
|
||||
<div class="modal-content approval-modal-content">
|
||||
<div class="modal-header">
|
||||
<h3><span class="modal-icon">✅</span> Approval Required</h3>
|
||||
<button class="modal-close" onclick="closeApprovalModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body" id="approval-content">
|
||||
<!-- Approval content loaded dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Simulation Preview Modal -->
|
||||
<div class="modal" id="simulation-modal" style="display: none;">
|
||||
<div class="modal-backdrop" onclick="closeSimulationModal()"></div>
|
||||
<div class="modal-content simulation-modal-content">
|
||||
<div class="modal-header">
|
||||
<h3><span class="modal-icon">🔮</span> Impact Simulation</h3>
|
||||
<button class="modal-close" onclick="closeSimulationModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body" id="simulation-content">
|
||||
<!-- Simulation content loaded dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Task Item Template (for reference, actual rendering done server-side) -->
|
||||
<template id="task-item-template">
|
||||
<div class="autotask-item" data-task-id="${id}" data-status="${status}">
|
||||
<div class="task-header">
|
||||
<div class="task-info">
|
||||
<div class="task-status-badge status-${status}">${status}</div>
|
||||
<h4 class="task-title">${title}</h4>
|
||||
<span class="task-priority priority-${priority}">${priority}</span>
|
||||
</div>
|
||||
<div class="task-meta">
|
||||
<span class="task-time" title="Created">${created_at}</span>
|
||||
<span class="task-mode">${execution_mode}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: ${progress}%"></div>
|
||||
</div>
|
||||
<span class="progress-text">${current_step}/${total_steps} steps (${progress}%)</span>
|
||||
</div>
|
||||
|
||||
<div class="task-details">
|
||||
<p class="task-intent">${intent}</p>
|
||||
|
||||
<!-- Current Step Info -->
|
||||
<div class="current-step" style="display: ${show_current_step}">
|
||||
<div class="step-indicator">
|
||||
<span class="step-icon">▶️</span>
|
||||
<span class="step-name">Step ${current_step}: ${current_step_name}</span>
|
||||
</div>
|
||||
<div class="step-status">${current_step_status}</div>
|
||||
</div>
|
||||
|
||||
<!-- Pending Decision Alert -->
|
||||
<div class="pending-decision-alert" style="display: ${show_decision}">
|
||||
<span class="alert-icon">🤔</span>
|
||||
<span class="alert-text">${decision_count} decision(s) pending</span>
|
||||
<button class="btn-view-decisions" onclick="viewDecisions('${id}')">View & Decide</button>
|
||||
</div>
|
||||
|
||||
<!-- Pending Approval Alert -->
|
||||
<div class="pending-approval-alert" style="display: ${show_approval}">
|
||||
<span class="alert-icon">⚠️</span>
|
||||
<span class="alert-text">${approval_count} approval(s) required</span>
|
||||
<button class="btn-view-approvals" onclick="viewApprovals('${id}')">Review & Approve</button>
|
||||
</div>
|
||||
|
||||
<!-- Risk Summary -->
|
||||
<div class="risk-summary" style="display: ${show_risk}">
|
||||
<span class="risk-icon risk-${risk_level}">⚡</span>
|
||||
<span class="risk-text">Risk Level: ${risk_level}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-actions">
|
||||
<button class="btn-action btn-view" onclick="viewTaskDetails('${id}')" title="View Details">
|
||||
<span>👁️</span> Details
|
||||
</button>
|
||||
<button class="btn-action btn-simulate" onclick="simulateTask('${id}')" title="Simulate Impact" style="display: ${show_simulate}">
|
||||
<span>🔮</span> Simulate
|
||||
</button>
|
||||
<button class="btn-action btn-pause" onclick="pauseTask('${id}')" title="Pause" style="display: ${show_pause}">
|
||||
<span>⏸️</span> Pause
|
||||
</button>
|
||||
<button class="btn-action btn-resume" onclick="resumeTask('${id}')" title="Resume" style="display: ${show_resume}">
|
||||
<span>▶️</span> Resume
|
||||
</button>
|
||||
<button class="btn-action btn-cancel" onclick="cancelTask('${id}')" title="Cancel">
|
||||
<span>❌</span> Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Execution Log (expandable) -->
|
||||
<details class="execution-log">
|
||||
<summary>Execution Log (${log_count} entries)</summary>
|
||||
<div class="log-entries" id="log-${id}">
|
||||
<!-- Log entries loaded on expand -->
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Compilation Result Template -->
|
||||
<template id="compilation-result-template">
|
||||
<div class="compiled-plan">
|
||||
<div class="plan-header">
|
||||
<div class="plan-info">
|
||||
<h3>${plan_name}</h3>
|
||||
<p>${plan_description}</p>
|
||||
</div>
|
||||
<div class="plan-meta">
|
||||
<span class="confidence-badge confidence-${confidence_level}">
|
||||
Confidence: ${confidence}%
|
||||
</span>
|
||||
<span class="risk-badge risk-${risk_level}">
|
||||
Risk: ${risk_level}
|
||||
</span>
|
||||
<span class="duration-badge">
|
||||
Est. Duration: ${estimated_duration}
|
||||
</span>
|
||||
<span class="cost-badge">
|
||||
Est. Cost: $${estimated_cost}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alternatives (if ambiguous) -->
|
||||
<div class="alternatives-section" style="display: ${show_alternatives}">
|
||||
<h4>🔀 Alternative Approaches</h4>
|
||||
<p class="alternatives-note</h4>">The intent was ambiguous. Please choose your preferred approach:</p>
|
||||
<div class="alternatives-list">
|
||||
${alternatives_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Execution Plan Steps -->
|
||||
<div class="plan-steps">
|
||||
<h4>📋 Execution Plan (${step_count} steps)</h4>
|
||||
<div class="steps-list">
|
||||
${steps_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resource Estimates -->
|
||||
<div class="resource-estimates">
|
||||
<h4>📊 Resource Estimates</h4>
|
||||
<div class="estimates-grid">
|
||||
<div class="estimate-item">
|
||||
<span class="estimate-icon">💻</span>
|
||||
<span class="estimate-label">Compute</span>
|
||||
<span class="estimate-value">${compute_hours} hours</span>
|
||||
</div>
|
||||
<div class="estimate-item">
|
||||
<span class="estimate-icon">💾</span>
|
||||
<span class="estimate-label">Storage</span>
|
||||
<span class="estimate-value">${storage_gb} GB</span>
|
||||
</div>
|
||||
<div class="estimate-item">
|
||||
<span class="estimate-icon">🌐</span>
|
||||
<span class="estimate-label">API Calls</span>
|
||||
<span class="estimate-value">${api_calls}</span>
|
||||
</div>
|
||||
<div class="estimate-item">
|
||||
<span class="estimate-icon">🤖</span>
|
||||
<span class="estimate-label">LLM Tokens</span>
|
||||
<span class="estimate-value">${llm_tokens}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MCP Servers & APIs -->
|
||||
<div class="integrations-section" style="display: ${show_integrations}">
|
||||
<h4></h4>🔌 Integrations Required</h4>
|
||||
<div class="integrations-list">
|
||||
<div class="mcp-servers" style="display: ${show_mcp}">
|
||||
<h5>MCP Servers</h5>
|
||||
<div class="integration-tags">${mcp_servers_html}</div>
|
||||
</div>
|
||||
<div class="external-apis" style="display: ${show_apis}">
|
||||
<h5>External APIs</h5>
|
||||
<div class="integration-tags">${external_apis_html}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Risk Assessment -->
|
||||
<div class="risk-assessment" style="display: ${show_risks}">
|
||||
<h4>⚠️ Risk Assessment</h4>
|
||||
<div class="risks-list">
|
||||
${risks_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Generated BASIC Code (collapsible) -->
|
||||
<details class="generated-code">
|
||||
<summary>📝 Generated BASIC Program (${code_lines} lines)</summary>
|
||||
<pre class="code-preview"><code>${basic_program}</code></pre>
|
||||
<button class="btn-copy-code" onclick="copyGeneratedCode()">📋 Copy Code</button>
|
||||
</details>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="plan-actions">
|
||||
<button class="btn-secondary" onclick="discardPlan()">
|
||||
<span>🗑️</span> Discard
|
||||
</button>
|
||||
<button class="btn-secondary" onclick="editPlan()">
|
||||
<span>✏️</span> Edit Plan
|
||||
</button>
|
||||
<button class="btn-secondary" onclick="simulatePlan('${plan_id}')">
|
||||
<span>🔮</span> Simulate First
|
||||
</button>
|
||||
<button class="btn-primary" onclick="executePlan('${plan_id}')" ${execute_disabled}>
|
||||
<span>🚀</span> Execute Plan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Decision Option Template -->
|
||||
<template id="decision-option-template">
|
||||
<div class="decision-option" data-option-id="${id}">
|
||||
<div class="option-header">
|
||||
<input type="radio" name="decision_choice" value="${id}" id="option-${id}">
|
||||
<label for="option-${id}">
|
||||
<span class="option-label">${label}</span>
|
||||
<span class="recommended-badge" style="display: ${show_recommended}">Recommended</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="option-description">${description}</p>
|
||||
<div class="option-details">
|
||||
<div class="pros">
|
||||
<h5>✅ Pros</h5>
|
||||
<ul>${pros_html}</ul>
|
||||
</div>
|
||||
<div class="cons">
|
||||
<h5>❌ Cons</h5>
|
||||
<ul>${cons_html}</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-impact">
|
||||
<span class="impact-item</h5>">💰 Cost: ${cost_change}</span>
|
||||
<span class="impact-item">⏱️ Time: ${time_change}</span>
|
||||
<span class="impact-item risk-${risk_level}">⚠️ Risk: ${risk_level}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Approval Request Template -->
|
||||
<template id="approval-request-template">
|
||||
<div class="approval-request" data-approval-id="${id}">
|
||||
<div class="approval-header">
|
||||
<span class="approval-type type-${approval_type}">${approval_type}</span>
|
||||
<span class="approval-risk risk-${risk_level}">${risk_level} Risk</span>
|
||||
</div>
|
||||
<h4>${title}</h4>
|
||||
<p class="approval-description">${description}</p>
|
||||
|
||||
<div class="approval-impact">
|
||||
<h5>Impact Summary</h5>
|
||||
<p>${impact_summary}</p>
|
||||
</div>
|
||||
|
||||
<div class="simulation-preview" style="display: ${show_simulation}">
|
||||
<h5>Simulation Result</h5>
|
||||
<div class="simulation-summary">${simulation_summary}</div>
|
||||
</div>
|
||||
|
||||
<div class="approval-meta">
|
||||
<span class="meta-item">Step: ${step_name}</span>
|
||||
<span class="meta-item">Expires: ${expires_at}</span>
|
||||
<span class="meta-item">Default: ${default_action}</span>
|
||||
</div>
|
||||
|
||||
<div class="approval-actions">
|
||||
<button class="btn-reject" onclick="rejectApproval('${id}')">
|
||||
<span>❌</span> Reject
|
||||
</button>
|
||||
<button class="btn-defer" onclick="deferApproval('${id}')">
|
||||
<span>⏸️</span> Defer
|
||||
</button>
|
||||
<button class="btn-approve" onclick="approveApproval('${id}')">
|
||||
<span>✅</span> Approve
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Simulation Result Template -->
|
||||
<template id="simulation-result-template">
|
||||
<div class="simulation-result">
|
||||
<div class="simulation-header">
|
||||
<div class="simulation-status status-${success}">
|
||||
<span class="status-icon">${status_icon}</span>
|
||||
<span class="status-text">${status_text}</span>
|
||||
</div>
|
||||
<div class="simulation-confidence">
|
||||
Confidence: ${confidence}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="impact-overview">
|
||||
<h4>Impact Assessment</h4>
|
||||
<div class="impact-grid">
|
||||
<div class="impact-card risk-${data_risk}">
|
||||
<span class="impact-icon">💾</span>
|
||||
<span class="impact-label">Data Impact</span>
|
||||
<span class="impact-value">${data_impact_summary}</span>
|
||||
</div>
|
||||
<div class="impact-card risk-${cost_risk}">
|
||||
<span class="impact-icon">💰</span>
|
||||
<span class="impact-label">Cost Impact</span>
|
||||
<span class="impact-value">${cost_impact_summary}</span>
|
||||
</div>
|
||||
<div class="impact-card risk-${time_risk}">
|
||||
<span class="impact-icon">⏱️</span>
|
||||
<span class="impact-label">Time Impact</span>
|
||||
<span class="impact-value">${time_impact_summary}</span>
|
||||
</div>
|
||||
<div class="impact-card risk-${security_risk}">
|
||||
<span class="impact-icon">🔒</span>
|
||||
<span class="impact-label">Security Impact</span>
|
||||
<span class="impact-value">${security_impact_summary}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-outcomes">
|
||||
<h4>Step-by-Step Predictions</h4>
|
||||
<div class="outcomes-list">
|
||||
${step_outcomes_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="side-effects" style="display: ${show_side_effects}">
|
||||
<h4>⚠️ Potential Side Effects</h4>
|
||||
<div class="side-effects-list">
|
||||
${side_effects_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recommendations" style="display: ${show_recommendations}">
|
||||
<h4>💡 Recommendations</h4>
|
||||
<div class="recommendations-list">
|
||||
${recommendations_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="simulation-actions">
|
||||
<button class="btn-secondary" onclick="closeSimulationModal()">
|
||||
<span>↩️</span> Back
|
||||
</button>
|
||||
<button class="btn-primary" onclick="proceedAfterSimulation('${task_id}')" ${proceed_disabled}>
|
||||
<span>🚀</span> Proceed with Execution
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
874
ui/suite/tasks/autotask.js
Normal file
874
ui/suite/tasks/autotask.js
Normal file
|
|
@ -0,0 +1,874 @@
|
|||
/* =============================================================================
|
||||
AUTO TASK JAVASCRIPT - Intelligent Self-Executing Task Interface
|
||||
Premium VIP Mode Functionality
|
||||
============================================================================= */
|
||||
|
||||
// =============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// =============================================================================
|
||||
|
||||
const AutoTaskState = {
|
||||
currentFilter: 'all',
|
||||
tasks: [],
|
||||
compiledPlan: null,
|
||||
pendingDecisions: [],
|
||||
pendingApprovals: [],
|
||||
refreshInterval: null,
|
||||
wsConnection: null
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// INITIALIZATION
|
||||
// =============================================================================
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initAutoTask();
|
||||
});
|
||||
|
||||
function initAutoTask() {
|
||||
// Initialize WebSocket for real-time updates
|
||||
initWebSocket();
|
||||
|
||||
// Start auto-refresh
|
||||
startAutoRefresh();
|
||||
|
||||
// Setup event listeners
|
||||
setupEventListeners();
|
||||
|
||||
// Load initial stats
|
||||
updateStats();
|
||||
|
||||
// Setup keyboard shortcuts
|
||||
setupKeyboardShortcuts();
|
||||
|
||||
console.log('AutoTask initialized');
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// WEBSOCKET CONNECTION
|
||||
// =============================================================================
|
||||
|
||||
function initWebSocket() {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws/autotask`;
|
||||
|
||||
try {
|
||||
AutoTaskState.wsConnection = new WebSocket(wsUrl);
|
||||
|
||||
AutoTaskState.wsConnection.onopen = function() {
|
||||
console.log('AutoTask WebSocket connected');
|
||||
};
|
||||
|
||||
AutoTaskState.wsConnection.onmessage = function(event) {
|
||||
handleWebSocketMessage(JSON.parse(event.data));
|
||||
};
|
||||
|
||||
AutoTaskState.wsConnection.onclose = function() {
|
||||
console.log('AutoTask WebSocket disconnected, reconnecting...');
|
||||
setTimeout(initWebSocket, 5000);
|
||||
};
|
||||
|
||||
AutoTaskState.wsConnection.onerror = function(error) {
|
||||
console.error('AutoTask WebSocket error:', error);
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn('WebSocket not available, using polling');
|
||||
}
|
||||
}
|
||||
|
||||
function handleWebSocketMessage(data) {
|
||||
switch (data.type) {
|
||||
case 'task_update':
|
||||
updateTaskInList(data.task);
|
||||
break;
|
||||
case 'step_progress':
|
||||
updateStepProgress(data.taskId, data.step, data.progress);
|
||||
break;
|
||||
case 'decision_required':
|
||||
showDecisionNotification(data.decision);
|
||||
break;
|
||||
case 'approval_required':
|
||||
showApprovalNotification(data.approval);
|
||||
break;
|
||||
case 'task_completed':
|
||||
onTaskCompleted(data.task);
|
||||
break;
|
||||
case 'task_failed':
|
||||
onTaskFailed(data.task, data.error);
|
||||
break;
|
||||
case 'stats_update':
|
||||
updateStatsFromData(data.stats);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EVENT LISTENERS
|
||||
// =============================================================================
|
||||
|
||||
function setupEventListeners() {
|
||||
// Intent form submission
|
||||
const intentForm = document.getElementById('intent-form');
|
||||
if (intentForm) {
|
||||
intentForm.addEventListener('htmx:afterSwap', function(event) {
|
||||
if (event.detail.target.id === 'compilation-result') {
|
||||
onCompilationComplete(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Task list updates
|
||||
const taskList = document.getElementById('task-list');
|
||||
if (taskList) {
|
||||
taskList.addEventListener('htmx:afterSwap', function() {
|
||||
updateStats();
|
||||
highlightPendingItems();
|
||||
});
|
||||
}
|
||||
|
||||
// Expand log entries on details open
|
||||
document.addEventListener('toggle', function(event) {
|
||||
if (event.target.classList.contains('execution-log') && event.target.open) {
|
||||
const taskId = event.target.closest('.autotask-item')?.dataset.taskId;
|
||||
if (taskId) {
|
||||
loadExecutionLogs(taskId);
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
function setupKeyboardShortcuts() {
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Alt + N: Focus on intent input
|
||||
if (e.altKey && e.key === 'n') {
|
||||
e.preventDefault();
|
||||
document.getElementById('intent-input')?.focus();
|
||||
}
|
||||
|
||||
// Alt + R: Refresh tasks
|
||||
if (e.altKey && e.key === 'r') {
|
||||
e.preventDefault();
|
||||
refreshTasks();
|
||||
}
|
||||
|
||||
// Escape: Close any open modal
|
||||
if (e.key === 'Escape') {
|
||||
closeAllModals();
|
||||
}
|
||||
|
||||
// Alt + 1-4: Switch filters
|
||||
if (e.altKey && e.key >= '1' && e.key <= '4') {
|
||||
e.preventDefault();
|
||||
const filters = ['all', 'running', 'approval', 'decision'];
|
||||
const index = parseInt(e.key) - 1;
|
||||
const tabs = document.querySelectorAll('.filter-tab');
|
||||
if (tabs[index]) {
|
||||
tabs[index].click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AUTO REFRESH
|
||||
// =============================================================================
|
||||
|
||||
function startAutoRefresh() {
|
||||
// Refresh every 5 seconds
|
||||
AutoTaskState.refreshInterval = setInterval(function() {
|
||||
if (!document.hidden) {
|
||||
updateStats();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function stopAutoRefresh() {
|
||||
if (AutoTaskState.refreshInterval) {
|
||||
clearInterval(AutoTaskState.refreshInterval);
|
||||
AutoTaskState.refreshInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATS MANAGEMENT
|
||||
// =============================================================================
|
||||
|
||||
function updateStats() {
|
||||
fetch('/api/autotask/stats')
|
||||
.then(response => response.json())
|
||||
.then(stats => {
|
||||
updateStatsFromData(stats);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to fetch stats:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function updateStatsFromData(stats) {
|
||||
// Header stats
|
||||
document.getElementById('stat-running').textContent = stats.running || 0;
|
||||
document.getElementById('stat-pending').textContent = stats.pending || 0;
|
||||
document.getElementById('stat-completed').textContent = stats.completed || 0;
|
||||
document.getElementById('stat-approval').textContent = stats.pending_approval || 0;
|
||||
|
||||
// Filter counts
|
||||
document.getElementById('count-all').textContent = stats.total || 0;
|
||||
document.getElementById('count-running').textContent = stats.running || 0;
|
||||
document.getElementById('count-approval').textContent = stats.pending_approval || 0;
|
||||
document.getElementById('count-decision').textContent = stats.pending_decision || 0;
|
||||
|
||||
// Highlight if approvals needed
|
||||
const approvalStat = document.querySelector('.stat-item.highlight');
|
||||
if (approvalStat && stats.pending_approval > 0) {
|
||||
approvalStat.classList.add('attention');
|
||||
} else if (approvalStat) {
|
||||
approvalStat.classList.remove('attention');
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TASK FILTERING
|
||||
// =============================================================================
|
||||
|
||||
function filterTasks(filter, button) {
|
||||
AutoTaskState.currentFilter = filter;
|
||||
|
||||
// Update active tab
|
||||
document.querySelectorAll('.filter-tab').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
button.classList.add('active');
|
||||
|
||||
// Trigger HTMX request
|
||||
htmx.ajax('GET', `/api/autotask/list?filter=${filter}`, {
|
||||
target: '#task-list',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
}
|
||||
|
||||
function refreshTasks() {
|
||||
const filter = AutoTaskState.currentFilter;
|
||||
htmx.ajax('GET', `/api/autotask/list?filter=${filter}`, {
|
||||
target: '#task-list',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
updateStats();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMPILATION HANDLING
|
||||
// =============================================================================
|
||||
|
||||
function onCompilationComplete(event) {
|
||||
const result = event.detail.target.querySelector('.compiled-plan');
|
||||
if (result) {
|
||||
// Scroll to result
|
||||
result.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
// Store compiled plan
|
||||
const planId = result.dataset?.planId;
|
||||
if (planId) {
|
||||
AutoTaskState.compiledPlan = planId;
|
||||
}
|
||||
|
||||
// Syntax highlight the code
|
||||
highlightBasicCode();
|
||||
}
|
||||
}
|
||||
|
||||
function highlightBasicCode() {
|
||||
const codeBlocks = document.querySelectorAll('.code-preview code');
|
||||
codeBlocks.forEach(block => {
|
||||
// Basic syntax highlighting for BASIC keywords
|
||||
let html = block.innerHTML;
|
||||
|
||||
// Keywords
|
||||
const keywords = [
|
||||
'PLAN_START', 'PLAN_END', 'STEP', 'SET', 'GET', 'IF', 'THEN', 'ELSE', 'END IF',
|
||||
'FOR EACH', 'NEXT', 'WHILE', 'WEND', 'TALK', 'HEAR', 'LLM', 'CREATE_TASK',
|
||||
'RUN_PYTHON', 'RUN_JAVASCRIPT', 'RUN_BASH', 'USE_MCP', 'POST', 'GET', 'PUT',
|
||||
'PATCH', 'DELETE HTTP', 'REQUIRE_APPROVAL', 'SIMULATE_IMPACT', 'AUDIT_LOG',
|
||||
'SEND_MAIL', 'SAVE', 'UPDATE', 'INSERT', 'DELETE', 'FIND'
|
||||
];
|
||||
|
||||
keywords.forEach(keyword => {
|
||||
const regex = new RegExp(`\\b${keyword}\\b`, 'g');
|
||||
html = html.replace(regex, `<span class="keyword">${keyword}</span>`);
|
||||
});
|
||||
|
||||
// Comments
|
||||
html = html.replace(/(\'[^\n]*)/g, '<span class="comment">$1</span>');
|
||||
|
||||
// Strings
|
||||
html = html.replace(/("[^"]*")/g, '<span class="string">$1</span>');
|
||||
|
||||
// Numbers
|
||||
html = html.replace(/\b(\d+)\b/g, '<span class="number">$1</span>');
|
||||
|
||||
block.innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
function copyGeneratedCode() {
|
||||
const code = document.querySelector('.code-preview code')?.textContent;
|
||||
if (code) {
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
showToast('Code copied to clipboard', 'success');
|
||||
}).catch(() => {
|
||||
showToast('Failed to copy code', 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function discardPlan() {
|
||||
if (confirm('Are you sure you want to discard this plan?')) {
|
||||
document.getElementById('compilation-result').innerHTML = '';
|
||||
AutoTaskState.compiledPlan = null;
|
||||
document.getElementById('intent-input').value = '';
|
||||
document.getElementById('intent-input').focus();
|
||||
}
|
||||
}
|
||||
|
||||
function editPlan() {
|
||||
// TODO: Implement plan editor
|
||||
showToast('Plan editor coming soon!', 'info');
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PLAN EXECUTION
|
||||
// =============================================================================
|
||||
|
||||
function simulatePlan(planId) {
|
||||
showSimulationModal();
|
||||
|
||||
fetch(`/api/autotask/simulate/${planId}`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
renderSimulationResult(result);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('simulation-content').innerHTML = `
|
||||
<div class="error-message">
|
||||
<span class="error-icon">❌</span>
|
||||
<p>Failed to simulate plan: ${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
function executePlan(planId) {
|
||||
const executionMode = document.querySelector('[name="execution_mode"]')?.value || 'semi-automatic';
|
||||
const priority = document.querySelector('[name="priority"]')?.value || 'medium';
|
||||
|
||||
if (!confirm('Are you sure you want to execute this plan?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/autotask/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
plan_id: planId,
|
||||
execution_mode: executionMode,
|
||||
priority: priority
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
showToast('Task execution started!', 'success');
|
||||
document.getElementById('compilation-result').innerHTML = '';
|
||||
document.getElementById('intent-input').value = '';
|
||||
refreshTasks();
|
||||
} else {
|
||||
showToast(`Failed to start execution: ${result.error}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showToast(`Failed to execute plan: ${error.message}`, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TASK ACTIONS
|
||||
// =============================================================================
|
||||
|
||||
function viewTaskDetails(taskId) {
|
||||
window.location.href = `/suite/tasks/detail/${taskId}`;
|
||||
}
|
||||
|
||||
function simulateTask(taskId) {
|
||||
showSimulationModal();
|
||||
|
||||
fetch(`/api/autotask/${taskId}/simulate`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
result.task_id = taskId;
|
||||
renderSimulationResult(result);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('simulation-content').innerHTML = `
|
||||
<div class="error-message">
|
||||
<span class="error-icon">❌</span>
|
||||
<p>Failed to simulate task: ${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
function pauseTask(taskId) {
|
||||
fetch(`/api/autotask/${taskId}/pause`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
showToast('Task paused', 'success');
|
||||
refreshTasks();
|
||||
} else {
|
||||
showToast(`Failed to pause task: ${result.error}`, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resumeTask(taskId) {
|
||||
fetch(`/api/autotask/${taskId}/resume`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
showToast('Task resumed', 'success');
|
||||
refreshTasks();
|
||||
} else {
|
||||
showToast(`Failed to resume task: ${result.error}`, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cancelTask(taskId) {
|
||||
if (!confirm('Are you sure you want to cancel this task? This may not be reversible.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/autotask/${taskId}/cancel`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
showToast('Task cancelled', 'success');
|
||||
refreshTasks();
|
||||
} else {
|
||||
showToast(`Failed to cancel task: ${result.error}`, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateTaskInList(task) {
|
||||
const taskElement = document.querySelector(`[data-task-id="${task.id}"]`);
|
||||
if (taskElement) {
|
||||
// Update status badge
|
||||
const statusBadge = taskElement.querySelector('.task-status-badge');
|
||||
if (statusBadge) {
|
||||
statusBadge.className = `task-status-badge status-${task.status}`;
|
||||
statusBadge.textContent = task.status.replace(/-/g, ' ');
|
||||
}
|
||||
|
||||
// Update progress
|
||||
const progressFill = taskElement.querySelector('.progress-fill');
|
||||
const progressText = taskElement.querySelector('.progress-text');
|
||||
if (progressFill && progressText) {
|
||||
progressFill.style.width = `${task.progress}%`;
|
||||
progressText.textContent = `${task.current_step}/${task.total_steps} steps (${Math.round(task.progress)}%)`;
|
||||
}
|
||||
|
||||
// Update data attribute
|
||||
taskElement.dataset.status = task.status;
|
||||
}
|
||||
}
|
||||
|
||||
function updateStepProgress(taskId, step, progress) {
|
||||
const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
|
||||
if (taskElement) {
|
||||
const currentStep = taskElement.querySelector('.current-step');
|
||||
if (currentStep) {
|
||||
currentStep.querySelector('.step-name').textContent = `Step ${step.order}: ${step.name}`;
|
||||
currentStep.querySelector('.step-status').textContent = `${Math.round(progress)}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DECISIONS
|
||||
// =============================================================================
|
||||
|
||||
function viewDecisions(taskId) {
|
||||
showDecisionModal();
|
||||
|
||||
fetch(`/api/autotask/${taskId}/decisions`)
|
||||
.then(response => response.json())
|
||||
.then(decisions => {
|
||||
renderDecisions(taskId, decisions);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('decision-content').innerHTML = `
|
||||
<div class="error-message">
|
||||
<span class="error-icon">❌</span>
|
||||
<p>Failed to load decisions: ${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
function renderDecisions(taskId, decisions) {
|
||||
const container = document.getElementById('decision-content');
|
||||
|
||||
if (!decisions || decisions.length === 0) {
|
||||
container.innerHTML = '<p class="no-decisions">No pending decisions.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="decisions-list">';
|
||||
|
||||
decisions.forEach(decision => {
|
||||
html += `
|
||||
<div class="decision-item" data-decision-id="${decision.id}">
|
||||
<h4>${decision.title}</h4>
|
||||
<p class="decision-description">${decision.description}</p>
|
||||
|
||||
<div class="decision-options">
|
||||
${decision.options.map(opt => `
|
||||
<div class="decision-option ${opt.recommended ? 'recommended' : ''}" data-option-id="${opt.id}">
|
||||
<div class="option-header">
|
||||
<input type="radio" name="decision_${decision.id}" value="${opt.id}" id="opt_${opt.id}" ${opt.recommended ? 'checked' : ''}>
|
||||
<label for="opt_${opt.id}">
|
||||
<span class="option-label">${opt.label}</span>
|
||||
${opt.recommended ? '<span class="recommended-badge">Recommended</span>' : ''}
|
||||
</label>
|
||||
</div>
|
||||
<p class="option-description">${opt.description}</p>
|
||||
<div class="option-impact">
|
||||
<span class="impact-cost">💰 ${opt.estimated_impact.cost_change >= 0 ? '+' : ''}$${opt.estimated_impact.cost_change}</span>
|
||||
<span class="impact-time">⏱️ ${opt.estimated_impact.time_change_minutes >= 0 ? '+' : ''}${opt.estimated_impact.time_change_minutes}m</span>
|
||||
<span class="impact-risk risk-${opt.risk_level.toLowerCase()}">⚠️ ${opt.risk_level}</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<div class="decision-actions">
|
||||
<button class="btn-secondary" onclick="skipDecision('${taskId}', '${decision.id}')">Skip</button>
|
||||
<button class="btn-primary" onclick="submitDecision('${taskId}', '${decision.id}')">Submit Decision</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function submitDecision(taskId, decisionId) {
|
||||
const selectedOption = document.querySelector(`input[name="decision_${decisionId}"]:checked`)?.value;
|
||||
|
||||
if (!selectedOption) {
|
||||
showToast('Please select an option', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/autotask/${taskId}/decide`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
decision_id: decisionId,
|
||||
option_id: selectedOption
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
showToast('Decision submitted', 'success');
|
||||
closeDecisionModal();
|
||||
refreshTasks();
|
||||
} else {
|
||||
showToast(`Failed to submit decision: ${result.error}`, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function skipDecision(taskId, decisionId) {
|
||||
if (!confirm('Are you sure you want to skip this decision? The default option will be used.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/autotask/${taskId}/decide`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
decision_id: decisionId,
|
||||
skip: true
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
showToast('Decision skipped', 'info');
|
||||
closeDecisionModal();
|
||||
refreshTasks();
|
||||
} else {
|
||||
showToast(`Failed to skip decision: ${result.error}`, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showDecisionNotification(decision) {
|
||||
showToast(`Decision required: ${decision.title}`, 'warning', 10000);
|
||||
updateStats();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// APPROVALS
|
||||
// =============================================================================
|
||||
|
||||
function viewApprovals(taskId) {
|
||||
showApprovalModal();
|
||||
|
||||
fetch(`/api/autotask/${taskId}/approvals`)
|
||||
.then(response => response.json())
|
||||
.then(approvals => {
|
||||
renderApprovals(taskId, approvals);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('approval-content').innerHTML = `
|
||||
<div class="error-message">
|
||||
<span class="error-icon">❌</span>
|
||||
<p>Failed to load approvals: ${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
function renderApprovals(taskId, approvals) {
|
||||
const container = document.getElementById('approval-content');
|
||||
|
||||
if (!approvals || approvals.length === 0) {
|
||||
container.innerHTML = '<p class="no-approvals">No pending approvals.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="approvals-list">';
|
||||
|
||||
approvals.forEach(approval => {
|
||||
html += `
|
||||
<div class="approval-item" data-approval-id="${approval.id}">
|
||||
<div class="approval-header">
|
||||
<span class="approval-type type-${approval.approval_type.toLowerCase().replace(/_/g, '-')}">${approval.approval_type.replace(/_/g, ' ')}</span>
|
||||
<span class="approval-risk risk-${approval.risk_level.toLowerCase()}">${approval.risk_level} Risk</span>
|
||||
</div>
|
||||
|
||||
<h4>${approval.title}</h4>
|
||||
<p class="approval-description">${approval.description}</p>
|
||||
|
||||
<div class="approval-impact">
|
||||
<h5>Impact Summary</h5>
|
||||
<p>${approval.impact_summary}</p>
|
||||
</div>
|
||||
|
||||
${approval.simulation_result ? `
|
||||
<div class="simulation-preview">
|
||||
<h5>Simulation Result</h5>
|
||||
<div class="simulation-summary">
|
||||
<span class="sim-risk risk-${approval.simulation_result.risk_level.toLowerCase()}">Risk: ${approval.simulation_result.risk_level}</span>
|
||||
<span class="sim-confidence">Confidence: ${Math.round(approval.simulation_result.confidence * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="approval-meta">
|
||||
<span>Step: ${approval.step_name || 'N/A'}</span>
|
||||
<span>Expires: ${formatRelativeTime(approval.expires_at)}</span>
|
||||
<span>Default: ${approval.default_action}</span>
|
||||
</div>
|
||||
|
||||
<div class="approval-actions">
|
||||
<button class="btn-reject" onclick="rejectApproval('${taskId}', '${approval.id}')">
|
||||
<span>❌</span> Reject
|
||||
</button>
|
||||
<button class="btn-defer" onclick="deferApproval('${taskId}', '${approval.id}')">
|
||||
<span>⏸️</span> Defer
|
||||
</button>
|
||||
<button class="btn-approve" onclick="approveApproval('${taskId}', '${approval.id}')">
|
||||
<span>✅</span> Approve
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function approveApproval(taskId, approvalId) {
|
||||
submitApprovalDecision(taskId, approvalId, 'approve');
|
||||
}
|
||||
|
||||
function rejectApproval(taskId, approvalId) {
|
||||
if (!confirm('Are you sure you want to reject this action?')) {
|
||||
return;
|
||||
}
|
||||
submitApprovalDecision(taskId, approvalId, 'reject');
|
||||
}
|
||||
|
||||
function deferApproval(taskId, approvalId) {
|
||||
submitApprovalDecision(taskId, approvalId, 'defer');
|
||||
}
|
||||
|
||||
function submitApprovalDecision(taskId, approvalId, action) {
|
||||
fetch(`/api/autotask/${taskId}/approve`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
approval_id: approvalId,
|
||||
action: action
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
const messages = {
|
||||
'approve': 'Approval granted',
|
||||
'reject': 'Approval rejected',
|
||||
'defer': 'Approval deferred'
|
||||
};
|
||||
showToast(messages[action], 'success');
|
||||
closeApprovalModal();
|
||||
refreshTasks();
|
||||
} else {
|
||||
showToast(`Failed to ${action}: ${result.error}`, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showApprovalNotification(approval) {
|
||||
showToast(`Approval required: ${approval.title}`, 'warning', 10000);
|
||||
updateStats();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SIMULATION
|
||||
// =============================================================================
|
||||
|
||||
function renderSimulationResult(result) {
|
||||
const container = document.getElementById('simulation-content');
|
||||
|
||||
const statusIcon = result.success ? '✅' : '⚠️';
|
||||
const statusText = result.success ? 'Simulation Successful' : 'Simulation Found Issues';
|
||||
|
||||
let html = `
|
||||
<div class="simulation-result">
|
||||
<div class="simulation-header">
|
||||
<div class="simulation-status status-${result.success}">
|
||||
<span class="status-icon">${statusIcon}</span>
|
||||
<span class="status-text">${statusText}</span>
|
||||
</div>
|
||||
<div class="simulation-confidence">
|
||||
Confidence: ${Math.round(result.confidence * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="impact-overview">
|
||||
<h4>Impact Assessment</h4>
|
||||
<div class="impact-grid">
|
||||
<div class="impact-card">
|
||||
<span class="impact-icon">💾</span>
|
||||
<span class="impact-label">Data Impact</span>
|
||||
<span class="impact-value">${result.impact.data_impact.records_modified} records modified</span>
|
||||
</div>
|
||||
<div class="impact-card">
|
||||
<span class="impact-icon">💰</span>
|
||||
<span class="impact-label">Cost Impact</span>
|
||||
<span class="impact-value">$${result.impact.cost_impact.total_estimated_cost.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="impact-card">
|
||||
<span class="impact-icon">⏱️</span>
|
||||
<span class="impact-label">Time Impact</span>
|
||||
<span class="impact-value">${formatDuration(result.impact.time_impact.estimated_duration_seconds)}</span>
|
||||
</div>
|
||||
<div class="impact-card risk-${result.impact.security_impact.risk_level.toLowerCase()}">
|
||||
<span class="impact-icon">🔒</span>
|
||||
<span class="impact-label">Security Impact</span>
|
||||
<span class="impact-value">${result.impact.security_impact.risk_level}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-outcomes">
|
||||
<h4>Step-by-Step Predictions</h4>
|
||||
<div class="outcomes-list">
|
||||
${result.step_outcomes.map(step => `
|
||||
<div class="outcome-item ${step.would_succeed ? 'success' : 'warning'}">
|
||||
<span class="outcome-icon">${step.would_succeed ? '✅' : '⚠️'}</span>
|
||||
<span class="outcome-name">${step.step_name}</span>
|
||||
<span class="outcome-probability">${Math.round(step.success_probability * 100)}% success</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${result.side_effects.length > 0 ? `
|
||||
<div class="side-effects">
|
||||
<h4>⚠️ Potential Side Effects</h4>
|
||||
<div class="side-effects-list">
|
||||
${result.side_effects.map(effect => `
|
||||
<div class="side-effect-item severity-${effect.severity.toLowerCase()}">
|
||||
<span class="effect-description">${effect.description}</span>
|
||||
${effect.mitigation ? `<span class="effect-mitigation">Mitigation: ${effect.mitigation}</span>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${result.recommendations.length > 0 ? `
|
||||
<div class="recommendations">
|
||||
<h4>💡 Recommendations</h4>
|
||||
<div class="recommendations-list">
|
||||
${result.recommendations.map(rec => `
|
||||
<div class="recommendation-item">
|
||||
<span class="rec-description">${rec.description}</span>
|
||||
${rec.action ? `<button class="btn-apply-rec" onclick="applyRecommendation('${rec.id}')">${rec.action}</button>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="simulation-actions">
|
||||
<button class="btn-secondary" onclick="closeSimulationModal()">
|
||||
<span>↩️</span> Back
|
||||
</button>
|
||||
<button class="btn-primary" onclick="proceedAfterSimulation('${result.task_id}')" ${result.impact.risk_score > 0.8 ? 'disabled' : ''}>
|
||||
<span>🚀</span> Proceed with Execution
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function proc
|
||||
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Compliance Report - General Bots Suite</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10/dist/ext/ws.js"></script>
|
||||
<script src="js/vendor/htmx.min.js"></script>
|
||||
<script src="js/vendor/htmx-ws.js"></script>
|
||||
<link rel="stylesheet" href="../css/app.css">
|
||||
<style>
|
||||
:root {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue