| <!DOCTYPE html> |
| <html lang="en"> |
| {{template "views/partials/head" .}} |
|
|
| <body class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]"> |
| <div class="flex flex-col min-h-screen" x-data="agentJobs()" x-init="init()"> |
|
|
| {{template "views/partials/navbar" .}} |
|
|
| <div class="container mx-auto px-4 py-8 flex-grow"> |
| |
| <div class="hero-section"> |
| <div class="hero-content flex justify-between items-center"> |
| <div> |
| <h1 class="hero-title"> |
| Agent Jobs |
| </h1> |
| <p class="hero-subtitle">Manage agent tasks and monitor job execution</p> |
| </div> |
| <a href="/agent-jobs/tasks/new" class="bg-[var(--color-primary)] hover:bg-[var(--color-primary-hover)] text-white px-6 py-3 rounded-lg transition-colors" x-show="hasMCPModels"> |
| <i class="fas fa-plus mr-2"></i>Create Task |
| </a> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-[var(--color-bg-secondary)] border border-[var(--color-accent-border)]/20 rounded-xl p-12 mb-8" x-show="!hasModels"> |
| <div class="text-center max-w-4xl mx-auto"> |
| <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-[var(--color-accent)]/10 border border-[var(--color-accent-border)]/20 mb-6"> |
| <i class="text-[var(--color-accent)] text-2xl fas fa-robot"></i> |
| </div> |
| <h2 class="h2 mb-4"> |
| No Models Installed |
| </h2> |
| <p class="text-xl text-[var(--color-text-secondary)] mb-8"> |
| To use Agent Jobs, you need to install a model first. Agent Jobs require models with MCP (Model Context Protocol) configuration. |
| </p> |
| |
| |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-10"> |
| <div class="bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded-lg p-4"> |
| <div class="w-10 h-10 bg-[var(--color-primary-light)] rounded-lg flex items-center justify-center mx-auto mb-3"> |
| <i class="fas fa-images text-[var(--color-primary)] text-xl"></i> |
| </div> |
| <h3 class="text-sm font-semibold text-[var(--color-text-primary)] mb-2">Model Gallery</h3> |
| <p class="text-xs text-[var(--color-text-secondary)]">Browse and install pre-configured models</p> |
| </div> |
| <div class="bg-[var(--color-bg-primary)] border border-[var(--color-accent-border)]/20 rounded-lg p-4"> |
| <div class="w-10 h-10 bg-[var(--color-accent-light)] rounded-lg flex items-center justify-center mx-auto mb-3"> |
| <i class="fas fa-upload text-[var(--color-accent)] text-xl"></i> |
| </div> |
| <h3 class="text-sm font-semibold text-[var(--color-text-primary)] mb-2">Import Models</h3> |
| <p class="text-xs text-[var(--color-text-secondary)]">Upload your own model files</p> |
| </div> |
| <div class="bg-[var(--color-bg-primary)] border border-green-500/20 rounded-lg p-4"> |
| <div class="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center mx-auto mb-3"> |
| <i class="fas fa-code text-green-400 text-xl"></i> |
| </div> |
| <h3 class="text-sm font-semibold text-[var(--color-text-primary)] mb-2">API Download</h3> |
| <p class="text-xs text-[var(--color-text-secondary)]">Use the API to download models programmatically</p> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-[var(--color-bg-primary)] border border-[var(--color-accent-border)]/20 rounded-xl p-6 mb-8 text-left"> |
| <h3 class="text-lg font-bold text-[var(--color-text-primary)] mb-4 flex items-center"> |
| <i class="fas fa-rocket text-[var(--color-accent)] mr-2"></i> |
| How to Get Started |
| </h3> |
| <div class="space-y-4"> |
| <div class="flex items-start"> |
| <div class="flex-shrink-0 w-8 h-8 rounded-full bg-[var(--color-accent)]/20 flex items-center justify-center mr-3 mt-0.5"> |
| <span class="text-[var(--color-accent)] font-bold text-sm">1</span> |
| </div> |
| <div class="flex-1"> |
| <p class="text-[var(--color-text-primary)] font-medium mb-2">Browse the Model Gallery</p> |
| <p class="text-[var(--color-text-secondary)] text-sm">Explore our curated collection of pre-configured models. Find models for chat, image generation, audio processing, and more.</p> |
| </div> |
| </div> |
| <div class="flex items-start"> |
| <div class="flex-shrink-0 w-8 h-8 rounded-full bg-[var(--color-accent)]/20 flex items-center justify-center mr-3 mt-0.5"> |
| <span class="text-[var(--color-accent)] font-bold text-sm">2</span> |
| </div> |
| <div class="flex-1"> |
| <p class="text-[var(--color-text-primary)] font-medium mb-2">Install a Model</p> |
| <p class="text-[var(--color-text-secondary)] text-sm">Click on a model from the gallery to install it, or use the import feature to upload your own model files.</p> |
| </div> |
| </div> |
| <div class="flex items-start"> |
| <div class="flex-shrink-0 w-8 h-8 rounded-full bg-[var(--color-accent)]/20 flex items-center justify-center mr-3 mt-0.5"> |
| <span class="text-[var(--color-accent)] font-bold text-sm">3</span> |
| </div> |
| <div class="flex-1"> |
| <p class="text-[var(--color-text-primary)] font-medium mb-2">Configure MCP</p> |
| <p class="text-[var(--color-text-secondary)] text-sm">After installing a model, configure MCP (Model Context Protocol) to enable Agent Jobs functionality.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="flex flex-wrap justify-center gap-4"> |
| <a href="/browse/" |
| class="inline-flex items-center bg-[var(--color-accent)] hover:bg-[var(--color-accent)]/90 text-white py-3 px-6 rounded-lg font-semibold transition-colors"> |
| <i class="fas fa-images mr-2"></i> |
| Browse Model Gallery |
| </a> |
| <a href="/import-model" |
| class="inline-flex items-center bg-[var(--color-primary)] hover:bg-[var(--color-primary)]/90 text-white py-3 px-6 rounded-lg font-semibold transition-colors"> |
| <i class="fas fa-upload mr-2"></i> |
| Import Model |
| </a> |
| <a href="https://localai.io/basics/getting_started/" target="_blank" |
| class="inline-flex items-center bg-[var(--color-bg-secondary)] hover:bg-[var(--color-bg-secondary)]/80 border border-[var(--color-accent-border)]/20 text-[var(--color-text-primary)] py-3 px-6 rounded-lg font-semibold transition-colors"> |
| <i class="fas fa-graduation-cap mr-2"></i> |
| Getting Started |
| <i class="fas fa-external-link-alt ml-2 text-sm"></i> |
| </a> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-[var(--color-bg-secondary)] border border-yellow-500/20 rounded-xl p-12 mb-8" x-show="hasModels && !hasMCPModels"> |
| <div class="text-center max-w-4xl mx-auto"> |
| <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-yellow-500/10 border border-yellow-500/20 mb-6"> |
| <i class="text-yellow-500 text-2xl fas fa-exclamation-triangle"></i> |
| </div> |
| <h2 class="h2 mb-4"> |
| MCP Configuration Required |
| </h2> |
| <p class="text-xl text-[var(--color-text-secondary)] mb-8"> |
| You have models installed, but none have MCP (Model Context Protocol) enabled. Agent Jobs require MCP to function. |
| </p> |
| |
| |
| <div class="bg-[var(--color-bg-primary)] border border-yellow-500/20 rounded-xl p-6 mb-8 text-left"> |
| <h3 class="text-lg font-bold text-[var(--color-text-primary)] mb-4 flex items-center"> |
| <i class="fas fa-list text-yellow-500 mr-2"></i> |
| Available Models |
| </h3> |
| <div class="space-y-3"> |
| <template x-for="model in availableModels" :key="model.name"> |
| <div class="flex items-center justify-between p-3 bg-[#0A0E1A] rounded-lg border border-[var(--color-primary-border)]/10"> |
| <div class="flex items-center space-x-3"> |
| <i class="fas fa-cube text-[var(--color-primary)]"></i> |
| <span class="text-[var(--color-text-primary)] font-medium" x-text="model.name"></span> |
| </div> |
| <a :href="'/models/edit/' + model.name" |
| class="inline-flex items-center bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded-lg transition-colors text-sm"> |
| <i class="fas fa-edit mr-2"></i> |
| Configure MCP |
| </a> |
| </div> |
| </template> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-[var(--color-bg-primary)] border border-yellow-500/20 rounded-xl p-6 mb-8 text-left"> |
| <h3 class="text-lg font-bold text-[var(--color-text-primary)] mb-4 flex items-center"> |
| <i class="fas fa-cog text-yellow-500 mr-2"></i> |
| How to Enable MCP |
| </h3> |
| <div class="space-y-4"> |
| <div class="flex items-start"> |
| <div class="flex-shrink-0 w-8 h-8 rounded-full bg-yellow-500/20 flex items-center justify-center mr-3 mt-0.5"> |
| <span class="text-yellow-500 font-bold text-sm">1</span> |
| </div> |
| <div class="flex-1"> |
| <p class="text-[var(--color-text-primary)] font-medium mb-2">Edit a Model Configuration</p> |
| <p class="text-[var(--color-text-secondary)] text-sm">Click "Configure MCP" on any model above, or navigate to the model editor to add MCP configuration.</p> |
| </div> |
| </div> |
| <div class="flex items-start"> |
| <div class="flex-shrink-0 w-8 h-8 rounded-full bg-yellow-500/20 flex items-center justify-center mr-3 mt-0.5"> |
| <span class="text-yellow-500 font-bold text-sm">2</span> |
| </div> |
| <div class="flex-1"> |
| <p class="text-[var(--color-text-primary)] font-medium mb-2">Add MCP Configuration</p> |
| <p class="text-[var(--color-text-secondary)] text-sm">In the model YAML, add MCP server or stdio configuration. See the documentation for detailed examples.</p> |
| </div> |
| </div> |
| <div class="flex items-start"> |
| <div class="flex-shrink-0 w-8 h-8 rounded-full bg-yellow-500/20 flex items-center justify-center mr-3 mt-0.5"> |
| <span class="text-yellow-500 font-bold text-sm">3</span> |
| </div> |
| <div class="flex-1"> |
| <p class="text-[var(--color-text-primary)] font-medium mb-2">Save and Return</p> |
| <p class="text-[var(--color-text-secondary)] text-sm">After saving the MCP configuration, return to this page to create your first Agent Job task.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="flex flex-wrap justify-center gap-4"> |
| <a href="https://localai.io/features/mcp/" target="_blank" |
| class="inline-flex items-center bg-yellow-600 hover:bg-yellow-700 text-white py-3 px-6 rounded-lg font-semibold transition-colors"> |
| <i class="fas fa-book mr-2"></i> |
| MCP Documentation |
| <i class="fas fa-external-link-alt ml-2 text-sm"></i> |
| </a> |
| <a href="/manage" |
| class="inline-flex items-center bg-[var(--color-primary)] hover:bg-[var(--color-primary)]/90 text-white py-3 px-6 rounded-lg font-semibold transition-colors"> |
| <i class="fas fa-cog mr-2"></i> |
| Manage Models |
| </a> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-[var(--color-bg-secondary)] border border-[var(--color-primary-border)]/20 rounded-xl p-8 mb-8" x-show="hasMCPModels"> |
| <h2 class="text-2xl font-semibold text-[var(--color-text-primary)] mb-6">Tasks</h2> |
| <div class="overflow-x-auto"> |
| <table class="w-full"> |
| <thead> |
| <tr class="border-b border-[var(--color-primary-border)]/20"> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Name</th> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Model</th> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Cron</th> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Status</th> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Actions</th> |
| </tr> |
| </thead> |
| <tbody> |
| <template x-for="task in tasks" :key="task.id"> |
| <tr class="border-b border-[var(--color-primary-border)]/10 hover:bg-[var(--color-bg-primary)]"> |
| <td class="py-3 px-4"> |
| <a :href="'/agent-jobs/tasks/' + task.id" |
| class="font-semibold text-[var(--color-primary)] hover:text-[var(--color-primary)]/80 hover:underline" |
| x-text="task.name"></a> |
| <div class="text-sm text-[var(--color-text-secondary)]" x-text="task.description || 'No description'"></div> |
| </td> |
| <td class="py-3 px-4"> |
| <div class="flex items-center space-x-2"> |
| <a :href="'/chat/' + task.model + '?mcp=true'" |
| class="text-[var(--color-primary)] hover:text-[var(--color-primary)]/80 hover:underline" |
| x-text="task.model"></a> |
| <a :href="'/models/edit/' + task.model" |
| class="text-yellow-400 hover:text-yellow-300" |
| title="Edit model configuration"> |
| <i class="fas fa-edit text-sm"></i> |
| </a> |
| </div> |
| </td> |
| <td class="py-3 px-4"> |
| <span x-show="task.cron" class="text-[var(--color-primary)]" x-text="task.cron"></span> |
| <span x-show="!task.cron" class="text-[var(--color-text-secondary)]">-</span> |
| </td> |
| <td class="py-3 px-4"> |
| <span :class="task.enabled ? 'bg-green-500' : 'bg-gray-500'" |
| class="px-2 py-1 rounded text-xs text-white" |
| x-text="task.enabled ? 'Enabled' : 'Disabled'"></span> |
| </td> |
| <td class="py-3 px-4"> |
| <div class="flex space-x-2"> |
| <button @click="showExecuteModal(task)" |
| class="text-blue-400 hover:text-blue-300" |
| title="Execute task"> |
| <i class="fas fa-play"></i> |
| </button> |
| <a :href="'/agent-jobs/tasks/' + task.id + '/edit'" |
| class="text-yellow-400 hover:text-yellow-300" |
| title="Edit task"> |
| <i class="fas fa-edit"></i> |
| </a> |
| <button @click="deleteTask(task.id)" |
| class="text-red-400 hover:text-red-300" |
| title="Delete task"> |
| <i class="fas fa-trash"></i> |
| </button> |
| </div> |
| </td> |
| </tr> |
| </template> |
| <tr x-show="tasks.length === 0"> |
| <td colspan="5" class="py-8 text-center text-[var(--color-text-secondary)]"> |
| No tasks found. <a href="/agent-jobs/tasks/new" class="text-blue-400 hover:text-blue-300">Create one</a> |
| </td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-[var(--color-bg-secondary)] border border-[var(--color-primary-border)]/20 rounded-xl p-8" x-show="hasMCPModels"> |
| <div class="flex justify-between items-center mb-6"> |
| <h2 class="text-2xl font-semibold text-[var(--color-text-primary)]">Job History</h2> |
| <div class="flex space-x-4"> |
| <select x-model="jobFilter" @change="fetchJobs()" |
| class="bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded px-4 py-2 text-[var(--color-text-primary)]"> |
| <option value="">All Status</option> |
| <option value="pending">Pending</option> |
| <option value="running">Running</option> |
| <option value="completed">Completed</option> |
| <option value="failed">Failed</option> |
| <option value="cancelled">Cancelled</option> |
| </select> |
| <button @click="clearJobHistory()" |
| class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg transition-colors" |
| title="Clear all job history"> |
| <i class="fas fa-trash mr-2"></i>Clear History |
| </button> |
| </div> |
| </div> |
| <div class="overflow-x-auto"> |
| <table class="w-full"> |
| <thead> |
| <tr class="border-b border-[var(--color-primary-border)]/20"> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Job ID</th> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Task</th> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Status</th> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Created</th> |
| <th class="text-left py-3 px-4 text-[var(--color-text-secondary)]">Actions</th> |
| </tr> |
| </thead> |
| <tbody> |
| <template x-for="job in jobs" :key="job.id"> |
| <tr class="border-b border-[var(--color-primary-border)]/10 hover:bg-[var(--color-bg-primary)]"> |
| <td class="py-3 px-4"> |
| <a :href="'/agent-jobs/jobs/' + job.id" |
| class="font-mono text-sm text-[var(--color-primary)] hover:text-[var(--color-primary)]/80 hover:underline" |
| x-text="job.id.substring(0, 8) + '...'" |
| :title="job.id"></a> |
| </td> |
| <td class="py-3 px-4"> |
| <a :href="'/agent-jobs/tasks/' + job.task_id" |
| class="text-[var(--color-primary)] hover:text-[var(--color-primary)]/80 hover:underline" |
| x-text="getTaskName(job.task_id)" |
| :title="'Task ID: ' + job.task_id"></a> |
| </td> |
| <td class="py-3 px-4"> |
| <span :class="{ |
| 'bg-yellow-500': job.status === 'pending', |
| 'bg-blue-500': job.status === 'running', |
| 'bg-green-500': job.status === 'completed', |
| 'bg-red-500': job.status === 'failed', |
| 'bg-gray-500': job.status === 'cancelled' |
| }" |
| class="px-2 py-1 rounded text-xs text-white" |
| x-text="job.status"></span> |
| </td> |
| <td class="py-3 px-4 text-[var(--color-text-secondary)] text-sm" x-text="formatDate(job.created_at)"></td> |
| <td class="py-3 px-4"> |
| <button x-show="job.status === 'pending' || job.status === 'running'" |
| @click="cancelJob(job.id)" |
| class="text-red-400 hover:text-red-300"> |
| <i class="fas fa-stop"></i> |
| </button> |
| </td> |
| </tr> |
| </template> |
| <tr x-show="jobs.length === 0"> |
| <td colspan="5" class="py-8 text-center text-[var(--color-text-secondary)]">No jobs found</td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div x-show="showExecuteTaskModal" |
| x-cloak |
| @click.away="showExecuteTaskModal = false; selectedTaskForExecution = null; executionParameters = {}; executionParametersText = ''" |
| class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> |
| <div class="bg-[var(--color-bg-secondary)] border border-[var(--color-primary-border)]/20 rounded-xl max-w-2xl w-full mx-4 max-h-[90vh] flex flex-col"> |
| <div class="flex justify-between items-center p-8 pb-6 border-b border-[var(--color-primary-border)]/20"> |
| <h3 class="text-2xl font-semibold text-[var(--color-text-primary)]">Execute Task</h3> |
| <button @click="showExecuteTaskModal = false; selectedTaskForExecution = null; executionParameters = {}; executionParametersText = ''; executionMultimedia = {images: '', videos: '', audios: '', files: ''}; executeModalTab = 'parameters'" |
| class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]"> |
| <i class="fas fa-times text-xl"></i> |
| </button> |
| </div> |
| <template x-if="selectedTaskForExecution"> |
| <div class="flex flex-col flex-1 min-h-0"> |
| <div class="flex-1 overflow-y-auto px-8 py-6 space-y-4"> |
| <div> |
| <label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Task</label> |
| <div class="text-[var(--color-text-secondary)]" x-text="selectedTaskForExecution.name"></div> |
| </div> |
| |
| |
| <div class="border-b border-[var(--color-primary-border)]/20"> |
| <div class="flex space-x-4"> |
| <button @click="executeModalTab = 'parameters'" |
| :class="executeModalTab === 'parameters' ? 'border-b-2 border-[var(--color-primary)] text-[var(--color-primary)]' : 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'" |
| class="px-4 py-2 font-medium transition-colors"> |
| Parameters |
| </button> |
| <button @click="executeModalTab = 'multimedia'" |
| :class="executeModalTab === 'multimedia' ? 'border-b-2 border-[var(--color-primary)] text-[var(--color-primary)]' : 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'" |
| class="px-4 py-2 font-medium transition-colors"> |
| Multimedia |
| </button> |
| </div> |
| </div> |
| |
| |
| <div x-show="executeModalTab === 'parameters'"> |
| <label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Parameters</label> |
| <p class="text-xs text-[var(--color-text-secondary)] mb-3"> |
| Enter parameters as key-value pairs (one per line, format: key=value). |
| These will be used to template the prompt. |
| </p> |
| <textarea x-model="executionParametersText" |
| rows="6" |
| placeholder="user_name=Alice job_title=Software Engineer task_description=Review code changes" |
| class="w-full bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded px-4 py-2 text-[var(--color-text-primary)] font-mono text-sm focus:border-[var(--color-primary-border)] focus:ring-2 focus:ring-[#38BDF8]/50"></textarea> |
| <p class="text-xs text-[var(--color-text-secondary)] mt-1"> |
| Example: <code class="bg-[var(--color-bg-primary)] px-1 py-0.5 rounded text-[var(--color-primary)]">user_name=Alice</code> |
| </p> |
| </div> |
| |
| |
| <div x-show="executeModalTab === 'multimedia'" class="space-y-4"> |
| <p class="text-xs text-[var(--color-text-secondary)] mb-3"> |
| Provide multimedia content as URLs or base64-encoded data URIs. You can also upload files which will be converted to base64. |
| </p> |
| |
| |
| <div> |
| <label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Images</label> |
| <textarea x-model="executionMultimedia.images" |
| rows="3" |
| placeholder="https://example.com/image.png data:image/png;base64,iVBORw0KG..." |
| class="w-full bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded px-4 py-2 text-[var(--color-text-primary)] font-mono text-sm focus:border-[var(--color-primary-border)] focus:ring-2 focus:ring-[#38BDF8]/50"></textarea> |
| <input type="file" @change="handleFileUpload($event, 'image')" accept="image/*" multiple |
| class="mt-2 text-sm text-[var(--color-text-secondary)] file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-[var(--color-primary)] file:text-white hover:file:bg-[var(--color-primary-hover)]"> |
| </div> |
| |
| |
| <div> |
| <label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Videos</label> |
| <textarea x-model="executionMultimedia.videos" |
| rows="3" |
| placeholder="https://example.com/video.mp4 data:video/mp4;base64,..." |
| class="w-full bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded px-4 py-2 text-[var(--color-text-primary)] font-mono text-sm focus:border-[var(--color-primary-border)] focus:ring-2 focus:ring-[#38BDF8]/50"></textarea> |
| <input type="file" @change="handleFileUpload($event, 'video')" accept="video/*" multiple |
| class="mt-2 text-sm text-[var(--color-text-secondary)] file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-[var(--color-primary)] file:text-white hover:file:bg-[var(--color-primary-hover)]"> |
| </div> |
| |
| |
| <div> |
| <label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Audios</label> |
| <textarea x-model="executionMultimedia.audios" |
| rows="3" |
| placeholder="https://example.com/audio.mp3 data:audio/mpeg;base64,..." |
| class="w-full bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded px-4 py-2 text-[var(--color-text-primary)] font-mono text-sm focus:border-[var(--color-primary-border)] focus:ring-2 focus:ring-[#38BDF8]/50"></textarea> |
| <input type="file" @change="handleFileUpload($event, 'audio')" accept="audio/*" multiple |
| class="mt-2 text-sm text-[var(--color-text-secondary)] file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-[var(--color-primary)] file:text-white hover:file:bg-[var(--color-primary-hover)]"> |
| </div> |
| |
| |
| <div> |
| <label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Files</label> |
| <textarea x-model="executionMultimedia.files" |
| rows="3" |
| placeholder="https://example.com/file.pdf data:application/pdf;base64,..." |
| class="w-full bg-[var(--color-bg-primary)] border border-[var(--color-primary-border)]/20 rounded px-4 py-2 text-[var(--color-text-primary)] font-mono text-sm focus:border-[var(--color-primary-border)] focus:ring-2 focus:ring-[#38BDF8]/50"></textarea> |
| <input type="file" @change="handleFileUpload($event, 'file')" multiple |
| class="mt-2 text-sm text-[var(--color-text-secondary)] file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-[var(--color-primary)] file:text-white hover:file:bg-[var(--color-primary-hover)]"> |
| </div> |
| </div> |
| </div> |
| |
| <div class="flex justify-end space-x-4 p-8 pt-6 border-t border-[var(--color-primary-border)]/20 bg-[var(--color-bg-secondary)]"> |
| <button @click="showExecuteTaskModal = false; selectedTaskForExecution = null; executionParameters = {}; executionParametersText = ''; executionMultimedia = {images: '', videos: '', audios: '', files: ''}; executeModalTab = 'parameters'" |
| class="px-4 py-2 bg-[var(--color-bg-primary)] hover:bg-[#0A0E1A] text-[var(--color-text-primary)] rounded-lg transition-colors"> |
| Cancel |
| </button> |
| <button @click="executeTaskWithParameters()" |
| class="px-4 py-2 bg-[var(--color-primary)] hover:bg-[var(--color-primary-hover)] text-white rounded-lg transition-colors"> |
| <i class="fas fa-play mr-2"></i>Execute |
| </button> |
| </div> |
| </div> |
| </template> |
| </div> |
| </div> |
|
|
| |
| <script id="models-data" type="application/json"> |
| {{ if .ModelsConfig }} |
| [ |
| {{ range $index, $cfg := .ModelsConfig }} |
| {{ if $index }},{{ end }}{ |
| "name": "{{ $cfg.Name }}", |
| "hasMCP": {{ if or (ne $cfg.MCP.Servers "") (ne $cfg.MCP.Stdio "") }}true{{ else }}false{{ end }} |
| } |
| {{ end }} |
| ] |
| {{ else }}[]{{ end }} |
| </script> |
|
|
| <script> |
| function agentJobs() { |
| return { |
| tasks: [], |
| jobs: [], |
| jobFilter: '', |
| loading: false, |
| showExecuteTaskModal: false, |
| selectedTaskForExecution: null, |
| executionParameters: {}, |
| executionParametersText: '', |
| executionMultimedia: { |
| images: '', |
| videos: '', |
| audios: '', |
| files: '' |
| }, |
| executeModalTab: 'parameters', |
| modelsConfig: [], |
| hasModels: false, |
| hasMCPModels: false, |
| availableModels: [], |
| |
| init() { |
| |
| this.checkModels(); |
| this.fetchTasks(); |
| this.fetchJobs(); |
| |
| setInterval(() => { |
| this.fetchJobs(); |
| }, 2000); |
| }, |
| |
| checkModels() { |
| |
| const modelsDataElement = document.getElementById('models-data'); |
| let modelsData = []; |
| if (modelsDataElement) { |
| try { |
| modelsData = JSON.parse(modelsDataElement.textContent); |
| } catch (e) { |
| console.error('Failed to parse models data:', e); |
| } |
| } |
| |
| this.modelsConfig = modelsData; |
| this.hasModels = modelsData.length > 0; |
| |
| |
| const mcpModels = modelsData.filter(m => m.hasMCP); |
| this.hasMCPModels = mcpModels.length > 0; |
| |
| |
| this.availableModels = modelsData.filter(m => !m.hasMCP); |
| }, |
| |
| async fetchTasks() { |
| try { |
| const response = await fetch('/api/agent/tasks'); |
| this.tasks = await response.json(); |
| } catch (error) { |
| console.error('Failed to fetch tasks:', error); |
| } |
| }, |
| |
| async fetchJobs() { |
| try { |
| let url = '/api/agent/jobs?limit=50'; |
| if (this.jobFilter) { |
| url += '&status=' + this.jobFilter; |
| } |
| const response = await fetch(url); |
| this.jobs = await response.json(); |
| } catch (error) { |
| console.error('Failed to fetch jobs:', error); |
| } |
| }, |
| |
| showExecuteModal(task) { |
| this.selectedTaskForExecution = task; |
| this.executionParameters = {}; |
| this.executionParametersText = ''; |
| this.showExecuteTaskModal = true; |
| }, |
| |
| parseParameters(text) { |
| const params = {}; |
| if (!text || !text.trim()) { |
| return params; |
| } |
| const lines = text.split('\n'); |
| for (const line of lines) { |
| const trimmed = line.trim(); |
| if (!trimmed) continue; |
| const equalIndex = trimmed.indexOf('='); |
| if (equalIndex > 0) { |
| const key = trimmed.substring(0, equalIndex).trim(); |
| const value = trimmed.substring(equalIndex + 1).trim(); |
| if (key) { |
| params[key] = value; |
| } |
| } |
| } |
| return params; |
| }, |
| |
| async executeTaskWithParameters() { |
| if (!this.selectedTaskForExecution) return; |
| |
| |
| this.executionParameters = this.parseParameters(this.executionParametersText); |
| |
| |
| const parseMultimedia = (text) => { |
| if (!text || !text.trim()) return []; |
| return text.split('\n') |
| .map(line => line.trim()) |
| .filter(line => line.length > 0); |
| }; |
| |
| const requestBody = { |
| task_id: this.selectedTaskForExecution.id, |
| parameters: this.executionParameters, |
| images: parseMultimedia(this.executionMultimedia.images), |
| videos: parseMultimedia(this.executionMultimedia.videos), |
| audios: parseMultimedia(this.executionMultimedia.audios), |
| files: parseMultimedia(this.executionMultimedia.files) |
| }; |
| |
| try { |
| const response = await fetch('/api/agent/jobs/execute', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(requestBody) |
| }); |
| if (response.ok) { |
| this.showExecuteTaskModal = false; |
| this.selectedTaskForExecution = null; |
| this.executionParameters = {}; |
| this.executionParametersText = ''; |
| this.executionMultimedia = {images: '', videos: '', audios: '', files: ''}; |
| this.executeModalTab = 'parameters'; |
| this.fetchJobs(); |
| } else { |
| const error = await response.json(); |
| alert('Failed to execute task: ' + (error.error || 'Unknown error')); |
| } |
| } catch (error) { |
| console.error('Failed to execute task:', error); |
| alert('Failed to execute task: ' + error.message); |
| } |
| }, |
| |
| handleFileUpload(event, type) { |
| const files = event.target.files; |
| if (!files || files.length === 0) return; |
| |
| const dataURIs = []; |
| let processed = 0; |
| |
| for (let i = 0; i < files.length; i++) { |
| const file = files[i]; |
| const reader = new FileReader(); |
| |
| reader.onload = (e) => { |
| const dataURI = e.target.result; |
| dataURIs.push(dataURI); |
| processed++; |
| |
| if (processed === files.length) { |
| |
| const current = this.executionMultimedia[type + 's'] || ''; |
| const newContent = current ? current + '\n' + dataURIs.join('\n') : dataURIs.join('\n'); |
| this.executionMultimedia[type + 's'] = newContent; |
| } |
| }; |
| |
| reader.readAsDataURL(file); |
| } |
| }, |
| |
| async deleteTask(taskId) { |
| if (!confirm('Are you sure you want to delete this task?')) return; |
| try { |
| const response = await fetch('/api/agent/tasks/' + taskId, { |
| method: 'DELETE' |
| }); |
| if (response.ok) { |
| this.fetchTasks(); |
| } |
| } catch (error) { |
| console.error('Failed to delete task:', error); |
| } |
| }, |
| |
| viewJob(jobId) { |
| window.location.href = '/agent-jobs/jobs/' + jobId; |
| }, |
| |
| async cancelJob(jobId) { |
| try { |
| const response = await fetch('/api/agent/jobs/' + jobId + '/cancel', { |
| method: 'POST' |
| }); |
| if (response.ok) { |
| this.fetchJobs(); |
| } |
| } catch (error) { |
| console.error('Failed to cancel job:', error); |
| } |
| }, |
| |
| formatDate(dateStr) { |
| if (!dateStr) return '-'; |
| const date = new Date(dateStr); |
| return date.toLocaleString(); |
| }, |
| |
| getTaskName(taskId) { |
| const task = this.tasks.find(t => t.id === taskId); |
| return task ? task.name : taskId.substring(0, 8) + '...'; |
| }, |
| |
| async clearJobHistory() { |
| if (!confirm('Are you sure you want to clear all job history? This action cannot be undone.')) return; |
| try { |
| |
| const response = await fetch('/api/agent/jobs?limit=10000'); |
| if (response.ok) { |
| const jobs = await response.json(); |
| |
| let deleted = 0; |
| let failed = 0; |
| for (const job of jobs) { |
| try { |
| const deleteResponse = await fetch('/api/agent/jobs/' + job.id, { |
| method: 'DELETE' |
| }); |
| if (deleteResponse.ok) { |
| deleted++; |
| } else { |
| failed++; |
| } |
| } catch (error) { |
| console.error('Failed to delete job:', job.id, error); |
| failed++; |
| } |
| } |
| |
| this.fetchJobs(); |
| if (failed > 0) { |
| alert(`Cleared ${deleted} jobs. ${failed} jobs could not be deleted.`); |
| } else { |
| alert(`Successfully cleared ${deleted} jobs.`); |
| } |
| } |
| } catch (error) { |
| console.error('Failed to clear job history:', error); |
| alert('Failed to clear job history: ' + error.message); |
| } |
| } |
| } |
| } |
| </script> |
| </div> |
| </body> |
| </html> |
|
|
|
|