diff --git a/api/main.py b/api/main.py index 6b90a0e..1a91ead 100644 --- a/api/main.py +++ b/api/main.py @@ -105,9 +105,9 @@ AGENTS = [ "motto": "Guardian of every row", "capabilities": ["PostgreSQL", "MySQL", "MongoDB", "Cassandra", "Neo4j"], "suggested_prompts": [ - "Hoeveel data zit er in de databases?", - "Wat staat er in PostgreSQL sales_orders?", - "MongoDB supplychain overzicht", + "How much data is in the databases?", + "What is in PostgreSQL sales_orders?", + "MongoDB supplychain overview", ], }, { @@ -332,21 +332,21 @@ async def ask_llm( log: Any | None = None, ) -> str | None: agent = next(a for a in AGENTS if a["id"] == agent_id) - system = f"""Je bent {agent['name']}, een autonomous ops agent in het Dell ATC data lab. -Specialisatie: {agent['role']}. -Motto: {agent.get('motto', '')} + system = f"""You are {agent['name']}, an autonomous ops agent in the Dell ATC data lab. +Specialization: {agent['role']}. +Motto: {agent.get('motto', '')}. -Je antwoordt namens je domein maar hebt zicht op de HELE lab stack: Docker, databases, lakehouse (Trino/Spark/Kafka Connect), ETL (Airflow/Kafka), Hadoop HDFS, en GPU/vLLM. +You respond on behalf of your domain but have visibility into the FULL lab stack: Docker, databases, lakehouse (Trino/Spark/Kafka Connect), ETL (Airflow/Kafka), Hadoop HDFS, and GPU/vLLM. -Regels: -- Antwoord in dezelfde taal als de gebruiker (Nederlands of Engels). -- Je hebt volledige zicht op de HELE cluster: alle VMs, zones, connectors, GPU, Hadoop, ObjectScale en Command Center. -- Gebruik ALLEEN de live data hieronder — verzin geen hosts, poorten, cijfers of connector namen. -- Gebruik exact de container/connector namen uit de data (bijv. mysql-hr-connector, niet "Debezium"). -- Als iets DOWN of 0 GB is, zeg dat eerlijk. -- Kort en behulpzaam (max ~10 zinnen); bullet lists mogen als het overzicht helpt. +Rules: +- Always respond in English. +- You have full visibility into the entire cluster: all VMs, zones, connectors, GPU, Hadoop, ObjectScale and Command Center. +- Use ONLY the live data below — do not invent hosts, ports, numbers or connector names. +- Use exact container/connector names from the data (e.g. mysql-hr-connector, not "Debezium"). +- If something is DOWN or 0 GB, say so honestly. +- Be concise and helpful (max ~10 sentences); bullet lists are fine when they aid clarity. ---- LIVE LAB DATA (primary domain eerst, daarna volledige stack) --- +--- LIVE LAB DATA (primary domain first, then full stack) --- {context} """ if log: diff --git a/ui/src/components/features/ActivityStream.tsx b/ui/src/components/features/ActivityStream.tsx index ed54630..0b818dd 100644 --- a/ui/src/components/features/ActivityStream.tsx +++ b/ui/src/components/features/ActivityStream.tsx @@ -32,7 +32,7 @@ export function ActivityStream({ feed, agents, filterAgentId, opsOnly }: Props)
{items.length === 0 && (

- {opsOnly ? 'Geen operationele events — antwords staan in Chat.' : 'No activity yet — agents are on standby.'} + {opsOnly ? 'No operational events — responses appear in Chat.' : 'No activity yet — agents are on standby.'}

)} {items.map((e) => { diff --git a/ui/src/components/features/ChatDrawer.tsx b/ui/src/components/features/ChatDrawer.tsx index d0fee32..7ed27d5 100644 --- a/ui/src/components/features/ChatDrawer.tsx +++ b/ui/src/components/features/ChatDrawer.tsx @@ -53,7 +53,7 @@ export function ChatDrawer({ Chat & Activity {unread > 0 && ( - {unread} bericht{unread !== 1 ? 'en' : ''} + {unread} message{unread !== 1 ? 's' : ''} )} {approvals.length > 0 && ( diff --git a/ui/src/components/features/CommandBar.tsx b/ui/src/components/features/CommandBar.tsx index 5d9ba6c..e644e1a 100644 --- a/ui/src/components/features/CommandBar.tsx +++ b/ui/src/components/features/CommandBar.tsx @@ -21,9 +21,9 @@ export function CommandBar({ busy, selectedAgent, onSubmit }: Props) { } const suggestions = selectedAgent?.suggested_prompts?.slice(0, 3) || [ - 'Hoeveel data zit er in de databases?', - 'Wat staat er in PostgreSQL?', - 'MongoDB supplychain overzicht', + 'How much data is in the databases?', + 'What is in PostgreSQL?', + 'MongoDB supplychain overview', ] return ( diff --git a/ui/src/components/features/CommsPanel.tsx b/ui/src/components/features/CommsPanel.tsx index a759a5e..f3b2c8e 100644 --- a/ui/src/components/features/CommsPanel.tsx +++ b/ui/src/components/features/CommsPanel.tsx @@ -58,7 +58,7 @@ export function CommsPanel({ messages, agents, selectedAgent, busy }: Props) { {busy && (
- Agent verzamelt cluster-data en vraagt Llama 70B… verwacht ~30–90 sec + Agent gathering cluster data and querying Llama 70B… expect ~30–90 sec
)}
diff --git a/ui/src/components/features/DataQualityView.tsx b/ui/src/components/features/DataQualityView.tsx index 2ece40d..298f52f 100644 --- a/ui/src/components/features/DataQualityView.tsx +++ b/ui/src/components/features/DataQualityView.tsx @@ -37,8 +37,36 @@ type ColumnProfile = { top_values?: { value: string; count: number }[] } -type GxCheck = { suite: string; expectation: string; success: boolean; result: string; column?: string } -type SodaCheck = { suite: string; name: string; check: string; outcome: string; detail: string } +type Violations = { + violation_count?: number + violation_pct?: number + sample_rows?: { row_index: number; values: Record }[] + location_hint?: string + affected_columns?: string[] +} + +type DqCheck = { + id?: string + suite: string + success?: boolean + status?: string + outcome?: string + expectation?: string + name?: string + metric?: string + check?: string + test?: string + rule?: string + monitor?: string + result?: string + detail?: string + column?: string + violations?: Violations + meta?: Record +} + +type GxCheck = DqCheck & { expectation: string; success: boolean; result: string } +type SodaCheck = DqCheck & { name: string; check: string; outcome: string; detail: string } type DocStructure = { pages: number @@ -67,10 +95,24 @@ type AssessResult = { dimensions: Dimension[] column_profiles: ColumnProfile[] action_items: { priority: string; dimension: string; score: number; action: string }[] - checks?: { great_expectations: GxCheck[]; soda_core: SodaCheck[] } - checks_summary: { - great_expectations: { total: number; passed: number } - soda_core: { total: number; warnings: number } + checks?: { + great_expectations: GxCheck[] + soda_core: SodaCheck[] + deequ?: DqCheck[] + pandera?: DqCheck[] + dbt_expectations?: DqCheck[] + monte_carlo?: DqCheck[] + affirm?: DqCheck[] + } + checks_summary: Record + executive_summary?: { + headline: string + dataset: string + checks_run: number + issues_found: number + flagged_columns: number + tools: string[] + recommendation: string } docling?: { used: boolean; parse_id?: string; document_structure?: DocStructure; stats?: Record; images?: DocImage[] } rag_ingest?: { ok: boolean; duplicate?: boolean; chunks?: number; message?: string; error?: string } @@ -140,8 +182,9 @@ export function DataQualityView() { const [reports, setReports] = useState([]) const [error, setError] = useState(null) const [expandedCol, setExpandedCol] = useState(null) - const [showGx, setShowGx] = useState(false) - const [showSoda, setShowSoda] = useState(false) + const [expandedCheck, setExpandedCheck] = useState(null) + const [expandedDim, setExpandedDim] = useState(null) + const [activeTool, setActiveTool] = useState('great_expectations') const loadMeta = useCallback(async () => { try { @@ -240,7 +283,7 @@ export function DataQualityView() { {caps && (
d.label)} icon={Layers} /> - +
@@ -287,8 +330,8 @@ export function DataQualityView() { - - 0} /> + s + (x.total || 0), 0))} sub="across 7 tools" /> + 0} />
{assess.docling?.used && assess.docling.document_structure && ( @@ -316,38 +359,69 @@ export function DataQualityView() {
)} + {assess.executive_summary && ( +
+

Executive Summary — Customer Report

+

{assess.executive_summary.headline}

+

{assess.executive_summary.dataset}

+
+ {assess.executive_summary.checks_run} checks run + 0 ? 'text-warning' : 'text-success'}>{assess.executive_summary.issues_found} issues found + {assess.executive_summary.flagged_columns} flagged columns +
+

+ Recommendation: {assess.executive_summary.recommendation} +

+
+ )} +
Full HTML report ↗ - - + + JSON export ↗ +
- {showGx && assess.checks?.great_expectations && ( - ({ - name: c.column ? `${c.expectation} [${c.column}]` : c.expectation, - status: c.success ? 'pass' : 'fail', - detail: c.result, - }))} /> - )} - {showSoda && assess.checks?.soda_core && ( - ({ - name: c.name, - status: c.outcome, - detail: `${c.check} — ${c.detail}`, - }))} /> - )} +
+

Data Quality Tools — click any row for error details

+
+ {([ + ['great_expectations', 'Great Expectations'], + ['soda_core', 'Soda Core'], + ['deequ', 'AWS Deequ'], + ['pandera', 'Pandera'], + ['dbt_expectations', 'dbt Expectations'], + ['monte_carlo', 'Monte Carlo'], + ['affirm', 'Affirm'], + ] as const).map(([key, label]) => { + const items = assess.checks?.[key] || [] + if (!items.length) return null + const summary = assess.checks_summary?.[key] + const badge = summary?.passed !== undefined ? `${summary.passed}/${summary.total}` : `${items.length}` + return ( + + ) + })} +
+ {assess.checks && ( + + )} +

6 Maturity Dimensions

{assess.dimensions.map((d) => ( - + setExpandedDim(expandedDim === d.id ? null : d.id)} /> ))}
@@ -383,7 +457,7 @@ export function DataQualityView() {

Docling extracts text, tables, images and document structure from PDF, PowerPoint, Word, Excel and images. - Resultaat: Markdown, HTML, JSON met pagina's, plaatjes, tabellen en outline. + Output: Markdown, HTML, JSON with pages, images, tables and document outline.

- 20 ? parse.filename.slice(0, 18) + '…' : parse.filename} sub={parse.status} /> - + 20 ? parse.filename.slice(0, 18) + '…' : parse.filename} sub={parse.status} /> + @@ -498,7 +572,7 @@ function ImageGallery({ images, parseId }: { images: DocImage[]; parseId: string return (

- Images gedetecteerd ({images.length}) — no embedded export + Images detected ({images.length}) — no embedded export

Re-upload the document to extract images (embedded mode).

@@ -508,7 +582,7 @@ function ImageGallery({ images, parseId }: { images: DocImage[]; parseId: string

- Images die Docling ziet ({available.length}) + Images extracted by Docling ({available.length})

{available.map((img) => ( @@ -601,9 +675,13 @@ function DocStructurePanel({ structure, title }: { structure: DocStructure; titl ) } -function DimensionCard({ dimension: d }: { dimension: Dimension }) { +function normalizeChecks(items: DqCheck[]): DqCheck[] { + return items.map((c, i) => ({ ...c, id: c.id || `${c.suite}_${i}` })) +} + +function DimensionCard({ dimension: d, expanded, onToggle }: { dimension: Dimension; expanded?: boolean; onToggle?: () => void }) { return ( -
+ ) } @@ -662,24 +746,75 @@ function ColumnTable({ profiles, expandedCol, onToggle }: { profiles: ColumnProf ) } -function CheckTable({ title, rows }: { title: string; rows: { name: string; status: string; detail: string }[] }) { +function checkLabel(c: DqCheck): string { + const base = c.expectation || c.name || c.metric || c.check || c.test || c.rule || c.monitor || 'check' + return c.column ? `${base} [${c.column}]` : base +} + +function checkStatus(c: DqCheck): string { + return c.status || (c.success === false ? 'fail' : c.success ? 'pass' : c.outcome || 'unknown') +} + +function checkDetail(c: DqCheck): string { + return c.result || c.detail || '' +} + +function CheckTable({ title, checks, expandedId, onToggle }: { title: string; checks: DqCheck[]; expandedId: string | null; onToggle: (id: string | null) => void }) { return (
-

{title}

+

{title}

- {rows.map((r, i) => ( - - - - - ))} + {checks.map((c) => { + const id = c.id || checkLabel(c) + const status = checkStatus(c) + const viol = c.violations + return ( + + onToggle(expandedId === id ? null : id)}> + + + + {expandedId === id && ( + + + + )} + + ) + })}
- - {r.status} - - {r.name} - {r.detail}
+ + {status} + + {checkLabel(c)} + {viol?.violation_count ? {viol.violation_count} violations : null} + {checkDetail(c)}
+

Error location & samples

+ {viol?.violation_count ? ( + <> +

{viol.violation_count} rows affected ({viol.violation_pct}%){viol.location_hint ? ` · ${viol.location_hint}` : ''}

+ {viol.sample_rows && viol.sample_rows.length > 0 && ( +
+ + {Object.keys(viol.sample_rows[0].values).map((k) => )} + + {viol.sample_rows.map((s) => ( + + + {Object.values(s.values).map((v, j) => )} + + ))} + +
Row #{k}
{s.row_index}{v === null ? 'NULL' : String(v)}
+
+ )} + + ) : ( +

No row-level violations — check passed.

+ )} + {c.meta &&
{JSON.stringify(c.meta, null, 2)}
} +
diff --git a/ui/src/components/features/InfraQuickAccess.tsx b/ui/src/components/features/InfraQuickAccess.tsx index da41d5b..3627d42 100644 --- a/ui/src/components/features/InfraQuickAccess.tsx +++ b/ui/src/components/features/InfraQuickAccess.tsx @@ -43,7 +43,7 @@ export function InfraQuickAccess({

Infrastructure & Apps

-

Klik voor inspector · Shell kopieert SSH en opent live terminal · UI opent de applicatie

+

Click for inspector · Shell copies SSH and opens live terminal · UI opens the app

{workload && ( diff --git a/ui/src/components/features/InspectorPanel.tsx b/ui/src/components/features/InspectorPanel.tsx index f9adbfc..4d77e1d 100644 --- a/ui/src/components/features/InspectorPanel.tsx +++ b/ui/src/components/features/InspectorPanel.tsx @@ -95,16 +95,16 @@ export function InspectorPanel({ {!d && !agent && (
-

Snel starten

+

Quick start

  1. Select an infrastructure card or topology node
  2. -
  3. Klik Shell voor SSH + live terminal output
  4. -
  5. Klik UI om Airflow, Trino, Kafka UI, etc. te openen
  6. -
  7. Stel vragen via Chat onderaan — agents zien de hele cluster
  8. +
  9. Click Shell for SSH + live terminal output
  10. +
  11. Click UI to open Airflow, Trino, Kafka UI, etc.
  12. +
  13. Ask questions via Chat below — agents see the full cluster
-

Agents & domeinen

+

Agents & domains

{agents.filter((a) => !a.supervisor).map((a) => { const meta = getAgentMeta(a.id) @@ -201,7 +201,7 @@ export function InspectorPanel({ {infra && ( )}
- setInput(e.target.value)} placeholder={`Vraag over ${d.label}…`} disabled={busy} className="text-xs" /> + setInput(e.target.value)} placeholder={`Ask about ${d.label}…`} disabled={busy} className="text-xs" />
diff --git a/ui/src/components/features/TerminalDock.tsx b/ui/src/components/features/TerminalDock.tsx index 7c41461..f439529 100644 --- a/ui/src/components/features/TerminalDock.tsx +++ b/ui/src/components/features/TerminalDock.tsx @@ -49,11 +49,11 @@ export function TerminalDock({ subjectId, subjectLabel, lines, busy, expanded, o
{infra && (

- $ {infra.ssh} (gekopieerd bij Shell-knop) + $ {infra.ssh} (copied via Shell button)

)} {lines.length === 0 && ( -

Selecteer een node of agent · klik Shell of Probe om output te zien

+

Select a node or agent · click Shell or Probe to see output

)} {lines.map((line) => (
diff --git a/ui/src/components/features/WorkbenchPanel.tsx b/ui/src/components/features/WorkbenchPanel.tsx index f5f0bde..4915bfa 100644 --- a/ui/src/components/features/WorkbenchPanel.tsx +++ b/ui/src/components/features/WorkbenchPanel.tsx @@ -55,7 +55,7 @@ export function WorkbenchPanel({ mode, agent, lines, busy, onSendPrompt }: Props e.preventDefault() }} className="flex h-2.5 shrink-0 cursor-ns-resize items-center justify-center border-b border-border/50 bg-surface-overlay/60 hover:bg-docker/10" - title="Sleep omhoog/omlaag om grootte aan te passen" + title="Drag up/down to resize" >