Files
HartOMat/frontend/src/components/workflows/WorkflowAuthoringSectionSelector.tsx
T
HartmutandClaude Sonnet 4.6 d2e4934cca feat: workflow graph system — blocks 1-20 complete checkpoint
Full graph-based workflow execution engine with:
- WorkflowGraphRuntime with two-phase dispatch (collect → fire Celery tasks)
- Node registry with contract validation, socket types, execution kinds
- WorkflowRuntimeServices: preflight, context resolution, shadow-mode A/B
- WorkflowRouter: CRUD + dispatch/preflight/run-history API endpoints
- OutputTypeContracts: workflow binding, rollout-mode resolution
- Admin router: output-type workflow binding endpoints
- Frontend: complete workflow editor with drag-drop canvas, node inspector,
  module bundles, reference bundles, preflight panel, validation banner,
  authoring guidance, blueprint templates, shadow/rollout gate UI
- Tests: comprehensive coverage for all workflow modules
- Docs: NODE_CONTRACT_AUDIT and VALIDATION_ERROR_INVENTORY

All 20 blocks from NEXT_20_BLOCK_BATCH_PLAN completed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 19:09:54 +02:00

104 lines
3.7 KiB
TypeScript

import { ArrowRight } from 'lucide-react'
import type {
WorkflowAuthoringSection,
WorkflowAuthoringSectionConfig,
} from './workflowAuthoringSections'
type WorkflowAuthoringSectionSelectorProps = {
sections: WorkflowAuthoringSectionConfig[]
activeSection: WorkflowAuthoringSection
onSelectSection: (section: WorkflowAuthoringSection) => void
variant?: 'pill' | 'card'
}
export function WorkflowAuthoringSectionSelector({
sections,
activeSection,
onSelectSection,
variant = 'pill',
}: WorkflowAuthoringSectionSelectorProps) {
if (variant === 'card') {
return (
<div className="grid gap-2 sm:grid-cols-2">
{sections.map(section => {
const Icon = section.icon
const isActive = activeSection === section.key
const toneLabel = section.tone === 'Escape Hatch' ? 'Direct' : section.tone
return (
<button
key={section.key}
type="button"
onClick={() => onSelectSection(section.key)}
aria-label={section.label}
className={`min-h-[76px] rounded-2xl border px-3 py-2.5 text-left transition-colors ${
isActive
? 'border-accent/40 bg-accent-light'
: 'border-border-default bg-surface hover:bg-surface-hover'
}`}
title={section.helper}
>
<div className="flex h-full items-start justify-between gap-2">
<div className="min-w-0">
<span className="inline-flex items-center gap-2 text-sm font-medium text-content">
<Icon size={14} />
{section.label}
</span>
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[11px] text-content-muted">
<span
className={`rounded-full px-2 py-0.5 font-medium ${
section.tone === 'Escape Hatch'
? 'border border-border-default bg-surface text-content-muted'
: 'bg-slate-100 text-slate-700 dark:bg-slate-900/40 dark:text-slate-300'
}`}
>
{toneLabel}
</span>
{section.summaryLabel && (
<span className="rounded-full border border-border-default bg-surface px-2 py-0.5">
{section.summaryLabel}
</span>
)}
</div>
<p className="mt-1 line-clamp-1 text-[11px] text-content-muted">
{section.helper}
</p>
</div>
<div className="flex shrink-0 items-center gap-1.5 self-center">
{isActive && <ArrowRight size={14} className="text-content-secondary" />}
</div>
</div>
</button>
)
})}
</div>
)
}
return (
<div className="flex flex-wrap gap-2">
{sections.map(section => {
const Icon = section.icon
const isActive = activeSection === section.key
return (
<button
key={section.key}
type="button"
onClick={() => onSelectSection(section.key)}
className={`inline-flex items-center gap-1 rounded-full px-3 py-1.5 text-xs font-medium transition-colors ${
isActive
? 'bg-accent text-white'
: 'border border-border-default bg-surface text-content-secondary hover:bg-surface-hover'
}`}
title={section.helper}
>
<Icon size={12} />
{section.label}
</button>
)
})}
</div>
)
}