From 7ac61ec3d32ed295d4456feef95f773e6c0d8ae4 Mon Sep 17 00:00:00 2001 From: Ibrahim Kazimov <74775400+ibrahimkzmv@users.noreply.github.com> Date: Wed, 20 May 2026 15:58:42 +0300 Subject: [PATCH 1/2] Add team-run DAG dashboard UI to web app Signed-off-by: Ibrahim Kazimov <74775400+ibrahimkzmv@users.noreply.github.com> --- apps/web/index.html | 10 ++ apps/web/src/App.tsx | 71 ++-------- .../web/src/components/dashboard/DagEdges.tsx | 56 ++++++++ apps/web/src/components/dashboard/DagNode.tsx | 46 ++++++ .../src/components/dashboard/DagViewport.tsx | 132 ++++++++++++++++++ .../src/components/dashboard/DetailsPanel.tsx | 121 ++++++++++++++++ .../src/components/dashboard/LiveOutput.tsx | 34 +++++ .../components/dashboard/TeamRunDashboard.tsx | 77 ++++++++++ .../src/components/dashboard/status-styles.ts | 70 ++++++++++ apps/web/src/data/demo-team-run.ts | 72 ++++++++++ apps/web/src/index.css | 71 ++++++++++ apps/web/src/lib/layout-tasks.ts | 93 ++++++++++++ apps/web/src/types/team-run.ts | 34 +++++ 13 files changed, 824 insertions(+), 63 deletions(-) create mode 100644 apps/web/src/components/dashboard/DagEdges.tsx create mode 100644 apps/web/src/components/dashboard/DagNode.tsx create mode 100644 apps/web/src/components/dashboard/DagViewport.tsx create mode 100644 apps/web/src/components/dashboard/DetailsPanel.tsx create mode 100644 apps/web/src/components/dashboard/LiveOutput.tsx create mode 100644 apps/web/src/components/dashboard/TeamRunDashboard.tsx create mode 100644 apps/web/src/components/dashboard/status-styles.ts create mode 100644 apps/web/src/data/demo-team-run.ts create mode 100644 apps/web/src/lib/layout-tasks.ts create mode 100644 apps/web/src/types/team-run.ts diff --git a/apps/web/index.html b/apps/web/index.html index e82dc16..a2e369c 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -4,6 +4,16 @@ OMA Forge + + + +
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index e511fd7..8e4bdf9 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,72 +1,17 @@ -import { useEffect, useState } from 'react' +import { TeamRunDashboard } from './components/dashboard/TeamRunDashboard.tsx' +import { demoTeamRun } from './data/demo-team-run.ts' -type HealthResponse = { - ok: boolean - runtime: string - orchestrator: unknown -} -export default function App() { - const [health, setHealth] = useState(null) - const [healthError, setHealthError] = useState(null) - useEffect(() => { - fetch('/api/health') - .then((res) => { - if (!res.ok) throw new Error(`HTTP ${res.status}`) - return res.json() as Promise - }) - .then(setHealth) - .catch((err: unknown) => { - setHealthError(err instanceof Error ? err.message : 'Failed to reach OMA Core') - }) - }, []) +export default function App() { + return ( -
-
-

- Open Multi Agent -

-

OMA Forge

-

- Local development environment for building, running, and debugging OMA workflows. -

+
+
+ OMA Forge
- -
-
-

Scaffold

-

- This repo is an early v0.1 scaffold. Workflow loading, live DAG visualization, and - trace inspection will land in follow-up changes. -

- -
- OMA Core: - {health ? ( - connected ({health.runtime}) - ) : healthError ? ( - {healthError} - ) : ( - checking… - )} -
- -

- Runtime:{' '} - - open-multi-agent - {' '} - (@open-multi-agent/core) -

-
-
+
) } diff --git a/apps/web/src/components/dashboard/DagEdges.tsx b/apps/web/src/components/dashboard/DagEdges.tsx new file mode 100644 index 0000000..5316c84 --- /dev/null +++ b/apps/web/src/components/dashboard/DagEdges.tsx @@ -0,0 +1,56 @@ +import type { LayoutTasksResult } from '../../lib/layout-tasks.ts' +import type { TaskExecutionRecord } from '../../types/team-run.ts' + +function makeEdgePath(x1: number, y1: number, x2: number, y2: number): string { + return `M ${x1} ${y1} C ${x1 + 42} ${y1}, ${x2 - 42} ${y2}, ${x2} ${y2}` +} + +type DagEdgesProps = { + readonly tasks: readonly TaskExecutionRecord[] + readonly layout: LayoutTasksResult +} + +export function DagEdges({ tasks, layout }: DagEdgesProps) { + const { positions, width, height, nodeW, nodeH } = layout + + return ( + + + + + + + {tasks.flatMap((task) => { + const to = positions.get(task.id) + if (!to) return [] + + return (task.dependsOn ?? []).flatMap((depId) => { + const from = positions.get(depId) + if (!from) return [] + + return ( + + ) + }) + })} + + ) +} diff --git a/apps/web/src/components/dashboard/DagNode.tsx b/apps/web/src/components/dashboard/DagNode.tsx new file mode 100644 index 0000000..0061b68 --- /dev/null +++ b/apps/web/src/components/dashboard/DagNode.tsx @@ -0,0 +1,46 @@ +import type { TaskExecutionRecord } from '../../types/team-run.ts' +import { durationText, statusStyles } from './status-styles.ts' + +type DagNodeProps = { + readonly task: TaskExecutionRecord + readonly index: number + readonly x: number + readonly y: number + readonly onSelect: (task: TaskExecutionRecord) => void +} + +export function DagNode({ task, index, x, y, onSelect }: DagNodeProps) { + const status = statusStyles[task.status] ?? statusStyles.pending + const nodeId = `#NODE_${String(index + 1).padStart(3, '0')}` + const chips = [task.assignee ? task.assignee.toUpperCase() : 'UNASSIGNED', status.chip] + + return ( + + ) +} diff --git a/apps/web/src/components/dashboard/DagViewport.tsx b/apps/web/src/components/dashboard/DagViewport.tsx new file mode 100644 index 0000000..20569d8 --- /dev/null +++ b/apps/web/src/components/dashboard/DagViewport.tsx @@ -0,0 +1,132 @@ +import { useEffect, useRef, useState, type ReactNode } from 'react' + +type ViewTransform = { + readonly scale: number + readonly x: number + readonly y: number +} + +const MIN_SCALE = 0.4 +const MAX_SCALE = 2.5 +/** Higher = faster zoom per scroll tick. */ +const ZOOM_INTENSITY = 0.0035 + +type DagViewportProps = { + readonly width: number + readonly height: number + readonly children: ReactNode +} + +function clampScale(scale: number): number { + return Math.min(Math.max(MIN_SCALE, scale), MAX_SCALE) +} + +/** Zoom toward cursor for `translate(tx, ty) scale(s)` with origin top-left. */ +function zoomTowardPoint( + prev: ViewTransform, + pointerX: number, + pointerY: number, + nextScale: number, +): ViewTransform { + const ratio = nextScale / prev.scale + return { + scale: nextScale, + x: pointerX - (pointerX - prev.x) * ratio, + y: pointerY - (pointerY - prev.y) * ratio, + } +} + +function wheelDeltaPixels(event: WheelEvent): number { + let delta = event.deltaY + if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) { + delta *= 16 + } else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + delta *= event.currentTarget instanceof Element + ? (event.currentTarget as Element).clientHeight + : 800 + } + return delta +} + +export function DagViewport({ width, height, children }: DagViewportProps) { + const viewportRef = useRef(null) + const transformRef = useRef({ scale: 1, x: 0, y: 0 }) + const [transform, setTransform] = useState(transformRef.current) + const dragging = useRef(false) + const last = useRef({ x: 0, y: 0 }) + + const applyTransform = (next: ViewTransform) => { + transformRef.current = next + setTransform(next) + } + + useEffect(() => { + const viewport = viewportRef.current + if (!viewport) return + + const onWheel = (event: WheelEvent) => { + event.preventDefault() + + const rect = viewport.getBoundingClientRect() + const pointerX = event.clientX - rect.left + const pointerY = event.clientY - rect.top + const delta = wheelDeltaPixels(event) + const prev = transformRef.current + const factor = Math.exp(-delta * ZOOM_INTENSITY) + const nextScale = clampScale(prev.scale * factor) + + if (nextScale === prev.scale) return + + applyTransform(zoomTowardPoint(prev, pointerX, pointerY, nextScale)) + } + + viewport.addEventListener('wheel', onWheel, { passive: false }) + return () => viewport.removeEventListener('wheel', onWheel) + }, []) + + const onMouseDown = (e: React.MouseEvent) => { + if (e.button !== 0) return + dragging.current = true + last.current = { x: e.clientX, y: e.clientY } + viewportRef.current?.classList.add('cursor-grabbing') + } + + const onMouseMove = (e: React.MouseEvent) => { + if (!dragging.current) return + const dx = e.clientX - last.current.x + const dy = e.clientY - last.current.y + last.current = { x: e.clientX, y: e.clientY } + applyTransform({ + ...transformRef.current, + x: transformRef.current.x + dx, + y: transformRef.current.y + dy, + }) + } + + const onMouseUp = () => { + dragging.current = false + viewportRef.current?.classList.remove('cursor-grabbing') + } + + return ( +
+
+ {children} +
+
+ ) +} diff --git a/apps/web/src/components/dashboard/DetailsPanel.tsx b/apps/web/src/components/dashboard/DetailsPanel.tsx new file mode 100644 index 0000000..9ee5808 --- /dev/null +++ b/apps/web/src/components/dashboard/DetailsPanel.tsx @@ -0,0 +1,121 @@ +import type { TaskExecutionRecord } from '../../types/team-run.ts' +import { statusStyles } from './status-styles.ts' +import { LiveOutput } from './LiveOutput.tsx' + +type DetailsPanelProps = { + readonly open: boolean + readonly goal: string + readonly tasks: readonly TaskExecutionRecord[] + readonly selected: TaskExecutionRecord | null + readonly onClose: () => void +} + +export function DetailsPanel({ open, goal, tasks, selected, onClose }: DetailsPanelProps) { + const metrics = selected?.metrics + const statusLabel = selected + ? (statusStyles[selected.status] ?? statusStyles.pending).chip + : '-' + const usage = metrics?.tokenUsage ?? { input_tokens: 0, output_tokens: 0 } + const inTokens = usage.input_tokens + const outTokens = usage.output_tokens + const total = inTokens + outTokens + const ratio = total > 0 ? Math.round((inTokens / total) * 100) : 0 + + return ( + + ) +} diff --git a/apps/web/src/components/dashboard/LiveOutput.tsx b/apps/web/src/components/dashboard/LiveOutput.tsx new file mode 100644 index 0000000..14eeb7e --- /dev/null +++ b/apps/web/src/components/dashboard/LiveOutput.tsx @@ -0,0 +1,34 @@ +import type { TaskExecutionRecord } from '../../types/team-run.ts' + +type LiveOutputProps = { + readonly tasks: readonly TaskExecutionRecord[] +} + +const terminalStatuses = new Set(['completed', 'failed', 'skipped', 'blocked']) + +export function LiveOutput({ tasks }: LiveOutputProps) { + const finished = tasks.every((task) => terminalStatuses.has(task.status)) + + return ( +
+

+ {finished + ? '[SYSTEM] Task graph execution finished.' + : '[SYSTEM] Task graph execution in progress.'} +

+ {tasks.map((task) => ( +

+ [{task.assignee?.toUpperCase() ?? 'UNASSIGNED'}] {task.title} {'->'}{' '} + {task.status.toUpperCase()} +

+ ))} +
+ ) +} diff --git a/apps/web/src/components/dashboard/TeamRunDashboard.tsx b/apps/web/src/components/dashboard/TeamRunDashboard.tsx new file mode 100644 index 0000000..78c41b3 --- /dev/null +++ b/apps/web/src/components/dashboard/TeamRunDashboard.tsx @@ -0,0 +1,77 @@ +import { useCallback, useMemo, useState } from 'react' +import { layoutTasks } from '../../lib/layout-tasks.ts' +import type { TaskExecutionRecord, TeamRunResult } from '../../types/team-run.ts' +import { DagEdges } from './DagEdges.tsx' +import { DagNode } from './DagNode.tsx' +import { DagViewport } from './DagViewport.tsx' +import { DetailsPanel } from './DetailsPanel.tsx' + +type TeamRunDashboardProps = { + readonly result: TeamRunResult +} + +export function TeamRunDashboard({ result }: TeamRunDashboardProps) { + const tasks = result.tasks ?? [] + const goal = result.goal ?? '' + const layout = useMemo(() => layoutTasks(tasks), [tasks]) + const [panelOpen, setPanelOpen] = useState(false) + const [selected, setSelected] = useState(null) + + const handleSelect = useCallback((task: TaskExecutionRecord) => { + setSelected(task) + setPanelOpen(true) + }, []) + + const handleClosePanel = useCallback(() => { + setPanelOpen(false) + }, []) + + const handleBackdropClick = useCallback( + (e: React.MouseEvent) => { + const target = e.target as HTMLElement + if (target.closest('.node') || target.closest('aside')) return + setPanelOpen(false) + }, + [], + ) + + return ( + <> +
+ + +
+ {tasks.map((task, idx) => { + const pos = layout.positions.get(task.id) + if (!pos) return null + return ( + + ) + })} +
+
+ +
+
+ + ) +} diff --git a/apps/web/src/components/dashboard/status-styles.ts b/apps/web/src/components/dashboard/status-styles.ts new file mode 100644 index 0000000..607942c --- /dev/null +++ b/apps/web/src/components/dashboard/status-styles.ts @@ -0,0 +1,70 @@ +import type { TaskExecutionRecord, TaskStatus } from '../../types/team-run.ts' + +export type StatusStyle = { + readonly border: string + readonly icon: string + readonly iconColor: string + readonly container: string + readonly statusColor: string + readonly chip: string + readonly spin?: boolean +} + +export const statusStyles: Record = { + completed: { + border: 'border-tertiary', + icon: 'check_circle', + iconColor: 'text-tertiary', + container: 'bg-surface-container-lowest node-active-glow', + statusColor: 'text-on-surface-variant', + chip: 'STABLE', + }, + failed: { + border: 'border-error', + icon: 'error', + iconColor: 'text-error', + container: 'bg-surface-container-lowest', + statusColor: 'text-error', + chip: 'FAILED', + }, + blocked: { + border: 'border-outline', + icon: 'lock', + iconColor: 'text-outline', + container: 'bg-surface-container-low opacity-60 grayscale', + statusColor: 'text-on-surface-variant', + chip: 'BLOCKED', + }, + skipped: { + border: 'border-outline', + icon: 'skip_next', + iconColor: 'text-outline', + container: 'bg-surface-container-low opacity-60', + statusColor: 'text-on-surface-variant', + chip: 'SKIPPED', + }, + in_progress: { + border: 'border-secondary', + icon: 'sync', + iconColor: 'text-secondary', + container: + 'bg-surface-container-low node-active-glow border border-outline-variant/20 shadow-[0_0_20px_rgba(253,192,3,0.1)]', + statusColor: 'text-secondary', + chip: 'ACTIVE_STREAM', + spin: true, + }, + pending: { + border: 'border-outline', + icon: 'hourglass_empty', + iconColor: 'text-outline', + container: 'bg-surface-container-low opacity-60 grayscale', + statusColor: 'text-on-surface-variant', + chip: 'WAITING', + }, +} + +export function durationText(task: TaskExecutionRecord): string { + const ms = task.metrics?.durationMs ?? 0 + const seconds = Math.max(0, ms / 1000).toFixed(1) + return task.status === 'completed' ? `DONE (${seconds}s)` : task.status.toUpperCase() +} diff --git a/apps/web/src/data/demo-team-run.ts b/apps/web/src/data/demo-team-run.ts new file mode 100644 index 0000000..b02a5d9 --- /dev/null +++ b/apps/web/src/data/demo-team-run.ts @@ -0,0 +1,72 @@ +import type { TeamRunResult } from '../types/team-run.ts' + +const now = Date.now() + +export const demoTeamRun: TeamRunResult = { + goal: 'Build a REST API for multi-agent task orchestration with live DAG visualization', + tasks: [ + { + id: 'research', + title: 'Research requirements', + assignee: 'analyst', + status: 'completed', + dependsOn: [], + metrics: { + startMs: now - 120_000, + endMs: now - 95_000, + durationMs: 25_000, + tokenUsage: { input_tokens: 1842, output_tokens: 956 }, + toolCalls: [{ name: 'web_search' }], + }, + }, + { + id: 'design', + title: 'Design API surface', + assignee: 'architect', + status: 'completed', + dependsOn: ['research'], + metrics: { + startMs: now - 90_000, + endMs: now - 52_000, + durationMs: 38_000, + tokenUsage: { input_tokens: 3201, output_tokens: 1420 }, + toolCalls: [], + }, + }, + { + id: 'implement', + title: 'Implement orchestrator routes', + assignee: 'engineer', + status: 'in_progress', + dependsOn: ['design'], + metrics: { + startMs: now - 48_000, + endMs: now, + durationMs: 48_000, + tokenUsage: { input_tokens: 5102, output_tokens: 2894 }, + toolCalls: [{ name: 'read_file' }, { name: 'edit_file' }], + }, + }, + { + id: 'tests', + title: 'Write integration tests', + assignee: 'qa', + status: 'pending', + dependsOn: ['implement'], + }, + { + id: 'docs', + title: 'Publish developer docs', + assignee: 'writer', + status: 'blocked', + dependsOn: ['implement'], + }, + { + id: 'synthesize', + title: 'Synthesize report', + assignee: 'synthesizer', + status: 'blocked', + dependsOn: ['tests', 'docs'], + }, + ], +} diff --git a/apps/web/src/index.css b/apps/web/src/index.css index d4b5078..af6bb52 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1 +1,72 @@ @import 'tailwindcss'; + +@theme { + --color-surface: #060e20; + --color-on-surface: #dee5ff; + --color-background: #060e20; + --color-on-background: #dee5ff; + --color-primary: #81ecff; + --color-on-primary: #005762; + --color-primary-dim: #00d4ec; + --color-primary-container: #00e3fd; + --color-on-primary-container: #004d57; + --color-secondary: #fdc003; + --color-on-secondary: #553e00; + --color-secondary-dim: #ecb200; + --color-secondary-container: #785900; + --color-on-secondary-container: #fff6ec; + --color-tertiary: #b8ffbb; + --color-on-tertiary: #006727; + --color-tertiary-dim: #4bee74; + --color-tertiary-container: #5cfd80; + --color-on-tertiary-container: #005d22; + --color-error: #ff716c; + --color-on-error: #490006; + --color-error-dim: #d7383b; + --color-error-container: #9f0519; + --color-on-error-container: #ffa8a3; + --color-outline: #6d758c; + --color-outline-variant: #40485d; + --color-on-surface-variant: #a3aac4; + --color-surface-variant: #192540; + --color-surface-dim: #060e20; + --color-surface-bright: #1f2b49; + --color-surface-container: #0f1930; + --color-surface-container-low: #091328; + --color-surface-container-lowest: #000000; + --color-surface-container-high: #141f38; + --color-surface-container-highest: #192540; + --color-surface-tint: #81ecff; + + --font-headline: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif; + --font-body: 'Inter', ui-sans-serif, system-ui, sans-serif; + --font-label: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif; + + --radius-lg: 0px; + --radius-xl: 0px; + --radius-2xl: 0px; + --radius-3xl: 0px; +} + +@layer base { + body { + @apply bg-surface text-on-surface font-body antialiased; + } + + ::selection { + @apply bg-primary text-on-primary; + } +} + +.material-symbols-outlined { + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; +} + +.grid-pattern { + background-image: radial-gradient(circle, #40485d 1px, transparent 1px); + background-size: 24px 24px; +} + +.node-active-glow { + box-shadow: 0 0 15px rgba(129, 236, 255, 0.15); +} diff --git a/apps/web/src/lib/layout-tasks.ts b/apps/web/src/lib/layout-tasks.ts new file mode 100644 index 0000000..9e42b7d --- /dev/null +++ b/apps/web/src/lib/layout-tasks.ts @@ -0,0 +1,93 @@ +export interface LayoutTaskInput { + readonly id: string + readonly dependsOn?: readonly string[] +} + +export interface LayoutTasksResult { + readonly positions: ReadonlyMap + readonly width: number + readonly height: number + readonly nodeW: number + readonly nodeH: number +} + +/** Topological column layout for the team-run DAG canvas. */ +export function layoutTasks( + taskList: readonly T[], +): LayoutTasksResult { + const byId = new Map(taskList.map((task) => [task.id, task])) + const children = new Map(taskList.map((task) => [task.id, []])) + const indegree = new Map() + + for (const task of taskList) { + const deps = (task.dependsOn ?? []).filter((dep) => byId.has(dep)) + indegree.set(task.id, deps.length) + for (const depId of deps) { + children.get(depId)!.push(task.id) + } + } + + const levels = new Map() + const queue: string[] = [] + let processed = 0 + for (const task of taskList) { + if ((indegree.get(task.id) ?? 0) === 0) { + levels.set(task.id, 0) + queue.push(task.id) + } + } + + while (queue.length > 0) { + const currentId = queue.shift()! + processed += 1 + const baseLevel = levels.get(currentId) ?? 0 + for (const childId of children.get(currentId) ?? []) { + const nextLevel = Math.max(levels.get(childId) ?? 0, baseLevel + 1) + levels.set(childId, nextLevel) + indegree.set(childId, (indegree.get(childId) ?? 1) - 1) + if ((indegree.get(childId) ?? 0) === 0) { + queue.push(childId) + } + } + } + + if (processed !== taskList.length) { + throw new Error('Task dependency graph contains a cycle') + } + + for (const task of taskList) { + if (!levels.has(task.id)) levels.set(task.id, 0) + } + + const cols = new Map() + for (const task of taskList) { + const level = levels.get(task.id) ?? 0 + if (!cols.has(level)) cols.set(level, []) + cols.get(level)!.push(task) + } + + const sortedLevels = Array.from(cols.keys()).sort((a, b) => a - b) + const nodeW = 256 + const nodeH = 142 + const colGap = 96 + const rowGap = 72 + const padX = 120 + const padY = 100 + const positions = new Map() + let maxRows = 1 + for (const level of sortedLevels) maxRows = Math.max(maxRows, cols.get(level)!.length) + + for (const level of sortedLevels) { + const colTasks = cols.get(level)! + colTasks.forEach((task, idx) => { + positions.set(task.id, { + x: padX + level * (nodeW + colGap), + y: padY + idx * (nodeH + rowGap), + }) + }) + } + + const width = Math.max(1600, padX * 2 + sortedLevels.length * (nodeW + colGap)) + const height = Math.max(700, padY * 2 + maxRows * (nodeH + rowGap)) + return { positions, width, height, nodeW, nodeH } +} diff --git a/apps/web/src/types/team-run.ts b/apps/web/src/types/team-run.ts new file mode 100644 index 0000000..aa5beae --- /dev/null +++ b/apps/web/src/types/team-run.ts @@ -0,0 +1,34 @@ +export type TaskStatus = + | 'pending' + | 'in_progress' + | 'completed' + | 'failed' + | 'blocked' + | 'skipped' + +export interface TokenUsage { + readonly input_tokens: number + readonly output_tokens: number +} + +export interface TaskExecutionMetrics { + readonly startMs: number + readonly endMs: number + readonly durationMs: number + readonly tokenUsage: TokenUsage + readonly toolCalls: readonly unknown[] +} + +export interface TaskExecutionRecord { + readonly id: string + readonly title: string + readonly assignee?: string + readonly status: TaskStatus + readonly dependsOn: readonly string[] + readonly metrics?: TaskExecutionMetrics +} + +export interface TeamRunResult { + readonly goal?: string + readonly tasks?: readonly TaskExecutionRecord[] +} From 8924efa568e7c49578b777ad561553e3dfbb5204 Mon Sep 17 00:00:00 2001 From: Ibrahim Kazimov <74775400+ibrahimkzmv@users.noreply.github.com> Date: Wed, 20 May 2026 16:12:31 +0300 Subject: [PATCH 2/2] fix: update node details panel to scroll independently of the page Signed-off-by: Ibrahim Kazimov <74775400+ibrahimkzmv@users.noreply.github.com> --- apps/web/src/App.tsx | 2 +- apps/web/src/components/dashboard/DagViewport.tsx | 2 +- apps/web/src/components/dashboard/DetailsPanel.tsx | 4 ++-- apps/web/src/components/dashboard/LiveOutput.tsx | 2 +- apps/web/src/components/dashboard/TeamRunDashboard.tsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 8e4bdf9..8999e29 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -7,7 +7,7 @@ export default function App() { return ( -
+
OMA Forge
diff --git a/apps/web/src/components/dashboard/DagViewport.tsx b/apps/web/src/components/dashboard/DagViewport.tsx index 20569d8..0f517c2 100644 --- a/apps/web/src/components/dashboard/DagViewport.tsx +++ b/apps/web/src/components/dashboard/DagViewport.tsx @@ -111,7 +111,7 @@ export function DagViewport({ width, height, children }: DagViewportProps) { return (

@@ -110,7 +110,7 @@ export function DetailsPanel({ open, goal, tasks, selected, onClose }: DetailsPa

-
+

LIVE_AGENT_OUTPUT

diff --git a/apps/web/src/components/dashboard/LiveOutput.tsx b/apps/web/src/components/dashboard/LiveOutput.tsx index 14eeb7e..2a1dfb0 100644 --- a/apps/web/src/components/dashboard/LiveOutput.tsx +++ b/apps/web/src/components/dashboard/LiveOutput.tsx @@ -10,7 +10,7 @@ export function LiveOutput({ tasks }: LiveOutputProps) { const finished = tasks.every((task) => terminalStatuses.has(task.status)) return ( -
+

{finished ? '[SYSTEM] Task graph execution finished.' diff --git a/apps/web/src/components/dashboard/TeamRunDashboard.tsx b/apps/web/src/components/dashboard/TeamRunDashboard.tsx index 78c41b3..498641a 100644 --- a/apps/web/src/components/dashboard/TeamRunDashboard.tsx +++ b/apps/web/src/components/dashboard/TeamRunDashboard.tsx @@ -38,7 +38,7 @@ export function TeamRunDashboard({ result }: TeamRunDashboardProps) { return ( <>