209 lines
6.0 KiB
Svelte
209 lines
6.0 KiB
Svelte
<script>
|
||
import { onMount, onDestroy } from "svelte";
|
||
import KanbanBoard from "../components/opc/KanbanBoard.svelte";
|
||
import TaskModal from "../components/opc/TaskModal.svelte";
|
||
import AgentPanel from "../components/opc/AgentPanel.svelte";
|
||
import { getTasks, getAgents } from "../lib/opc-api.js";
|
||
import * as opcWs from "../lib/opc-ws.js";
|
||
|
||
let tasks = $state([]);
|
||
let agents = $state([]);
|
||
let loading = $state(true);
|
||
let showTaskModal = $state(false);
|
||
let editingTask = $state(null);
|
||
let showAgentPanel = $state(
|
||
typeof localStorage !== 'undefined'
|
||
? localStorage.getItem('opc_showAgentPanel') !== 'false'
|
||
: true
|
||
);
|
||
let unsubscribe = null;
|
||
|
||
// Persist agent panel visibility
|
||
$effect(() => {
|
||
if (typeof localStorage !== 'undefined') {
|
||
localStorage.setItem('opc_showAgentPanel', showAgentPanel);
|
||
}
|
||
});
|
||
|
||
async function loadData() {
|
||
loading = true;
|
||
try {
|
||
const [tasksRes, agentsRes] = await Promise.all([
|
||
getTasks(),
|
||
getAgents()
|
||
]);
|
||
tasks = tasksRes.items || [];
|
||
agents = agentsRes.items || [];
|
||
} catch (e) {
|
||
console.error("Failed to load data:", e);
|
||
} finally {
|
||
loading = false;
|
||
}
|
||
}
|
||
|
||
function handleWebSocketMessage(message) {
|
||
if (message.type === "task_update") {
|
||
const { task_id, action, data } = message;
|
||
|
||
if (action === "created") {
|
||
tasks = [...tasks, data];
|
||
} else if (action === "updated" || action === "moved") {
|
||
tasks = tasks.map(t => t.id === task_id ? data : t);
|
||
} else if (action === "deleted") {
|
||
tasks = tasks.filter(t => t.id !== task_id);
|
||
}
|
||
} else if (message.type === "agent_execution") {
|
||
// Could show agent status in UI
|
||
console.log("Agent execution update:", message);
|
||
}
|
||
}
|
||
|
||
function handleCreateTask() {
|
||
editingTask = null;
|
||
showTaskModal = true;
|
||
}
|
||
|
||
function handleEditTask(task) {
|
||
editingTask = task;
|
||
showTaskModal = true;
|
||
}
|
||
|
||
function handleDeleteTask(taskId) {
|
||
tasks = tasks.filter(t => t.id !== taskId);
|
||
}
|
||
|
||
function handleAssignTask(taskId, assignedTo, assignedType) {
|
||
const task = tasks.find(t => t.id === taskId);
|
||
if (task) {
|
||
task.assigned_to = assignedTo;
|
||
task.assigned_type = assignedType;
|
||
tasks = [...tasks];
|
||
}
|
||
}
|
||
|
||
function handleTaskMoved(taskId, newStatus) {
|
||
const task = tasks.find(t => t.id === taskId);
|
||
if (task) {
|
||
task.status = newStatus;
|
||
tasks = [...tasks];
|
||
}
|
||
}
|
||
|
||
function handleModalClose() {
|
||
showTaskModal = false;
|
||
editingTask = null;
|
||
}
|
||
|
||
function handleModalSave() {
|
||
loadData();
|
||
}
|
||
|
||
onMount(() => {
|
||
loadData();
|
||
|
||
// Connect WebSocket
|
||
opcWs.connect();
|
||
unsubscribe = opcWs.subscribe(handleWebSocketMessage);
|
||
});
|
||
|
||
onDestroy(() => {
|
||
if (unsubscribe) {
|
||
unsubscribe();
|
||
}
|
||
opcWs.disconnect();
|
||
});
|
||
</script>
|
||
|
||
<div class="space-y-6 h-full flex flex-col">
|
||
<!-- Header -->
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h1 class="text-2xl font-bold text-surface-900 dark:text-surface-100">
|
||
OPC Management
|
||
</h1>
|
||
<p class="text-surface-600 dark:text-surface-400 mt-1">
|
||
Manage your tasks with AI agents
|
||
</p>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button
|
||
onclick={() => showAgentPanel = !showAgentPanel}
|
||
class="px-4 py-2 bg-surface-200 dark:bg-surface-700 text-surface-900 dark:text-surface-100 rounded-lg hover:bg-surface-300 dark:hover:bg-surface-600 flex items-center gap-2"
|
||
>
|
||
<span>🤖</span>
|
||
<span>{showAgentPanel ? "Hide" : "Show"} Agents</span>
|
||
</button>
|
||
<button
|
||
onclick={handleCreateTask}
|
||
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 flex items-center gap-2"
|
||
>
|
||
<span>➕</span>
|
||
<span>New Task</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stats -->
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
<div class="bg-white dark:bg-surface-800 rounded-lg p-4 border border-surface-200 dark:border-surface-700">
|
||
<div class="text-sm text-surface-600 dark:text-surface-400">Total Tasks</div>
|
||
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100 mt-1">
|
||
{tasks.length}
|
||
</div>
|
||
</div>
|
||
<div class="bg-white dark:bg-surface-800 rounded-lg p-4 border border-surface-200 dark:border-surface-700">
|
||
<div class="text-sm text-surface-600 dark:text-surface-400">Parking Lot</div>
|
||
<div class="text-2xl font-bold text-slate-600 dark:text-slate-400 mt-1">
|
||
{tasks.filter(t => t.status === "parking_lot").length}
|
||
</div>
|
||
</div>
|
||
<div class="bg-white dark:bg-surface-800 rounded-lg p-4 border border-surface-200 dark:border-surface-700">
|
||
<div class="text-sm text-surface-600 dark:text-surface-400">In Progress</div>
|
||
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400 mt-1">
|
||
{tasks.filter(t => t.status === "in_progress").length}
|
||
</div>
|
||
</div>
|
||
<div class="bg-white dark:bg-surface-800 rounded-lg p-4 border border-surface-200 dark:border-surface-700">
|
||
<div class="text-sm text-surface-600 dark:text-surface-400">Done</div>
|
||
<div class="text-2xl font-bold text-emerald-600 dark:text-emerald-400 mt-1">
|
||
{tasks.filter(t => t.status === "done").length}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Kanban Board -->
|
||
{#if loading}
|
||
<div class="flex-1 flex items-center justify-center">
|
||
<div class="text-surface-600 dark:text-surface-400">Loading...</div>
|
||
</div>
|
||
{:else}
|
||
<div class="flex-1 overflow-hidden">
|
||
<KanbanBoard
|
||
{tasks}
|
||
{agents}
|
||
onEdit={handleEditTask}
|
||
onDelete={handleDeleteTask}
|
||
onAssign={handleAssignTask}
|
||
onTaskMoved={handleTaskMoved}
|
||
/>
|
||
</div>
|
||
{/if}
|
||
|
||
<!-- Agent Panel -->
|
||
{#if showAgentPanel}
|
||
<div class="mt-6">
|
||
<AgentPanel />
|
||
</div>
|
||
{/if}
|
||
</div>
|
||
|
||
<!-- Task Modal -->
|
||
{#if showTaskModal}
|
||
<TaskModal
|
||
task={editingTask}
|
||
{agents}
|
||
onClose={handleModalClose}
|
||
onSave={handleModalSave}
|
||
/>
|
||
{/if}
|