diff --git a/src/components/ChainOfThought.css b/src/components/ChainOfThought.css index 1468237..7a30fe4 100644 --- a/src/components/ChainOfThought.css +++ b/src/components/ChainOfThought.css @@ -54,16 +54,6 @@ .cot__b { font-size: var(--fs-xs); line-height: var(--lh-normal); color: var(--zk-muted); overflow-wrap: anywhere; } .cot__b b { color: var(--zk-ink); font-weight: var(--fw-semi); } -.cot__tool { - display: inline-flex; align-items: center; gap: 6px; - margin: 3px 6px 0 0; padding: 2px 9px; - border-radius: var(--r-pill); background: var(--zk-white); border: 1px solid var(--zk-line); - font-family: var(--font-mono); font-size: 11.5px; color: var(--zk-muted); -} -.cot__tool::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--zk-good, #1f9d63); } -.cot__tool.is-empty { color: var(--zk-grey); } -.cot__tool.is-empty::before { background: var(--zk-grey); } - .cot__foot { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--sp-2); } .cot__conf { font-size: 11.5px; font-weight: var(--fw-semi); color: var(--zk-blue-dark); background: var(--zk-tint-blue); border: 1px solid var(--zk-blue-light); padding: 1px 9px; border-radius: var(--r-pill); } -.cot__model { font-family: var(--font-mono); font-size: 11px; color: var(--zk-grey); padding: 2px 0; } + diff --git a/src/components/ChainOfThought.jsx b/src/components/ChainOfThought.jsx index 22fd699..e2c15b9 100644 --- a/src/components/ChainOfThought.jsx +++ b/src/components/ChainOfThought.jsx @@ -6,77 +6,34 @@ import './ChainOfThought.css' * * This is the trust surface. A person signing off on — or overriding — a * machine's decision needs the answer to one question first: WHY did it do - * that. Before this, the reasoning existed (the employees write it, and the - * platform records the tools they called in tbl_ai_decisions) but the console - * showed only the conclusion. An operator either trusted it blind or opened - * the raw record. Neither is oversight. + * that. Before this, the reasoning existed (the employees write it as workflow + * fields, so it is already in the audit trail) but the console showed only the + * conclusion. An operator either trusted it blind or went digging. Neither is + * oversight. * - * The chain reads the way the employee actually ran: what it CHECKED (the - * tools it called, each marked for whether it came back with anything), what - * it REASONED, and what it DECIDED. It is assembled from real fields, never - * narrated after the fact — the reasoning is the employee's own text, the - * tools are the calls it actually made, the decision is the activity it - * committed and the state it moved the lead to. + * The chain reads the way the employee's own record reads: what it REASONED and + * what it DECIDED. It is assembled from real fields already on the step — the + * employee's own words for the reasoning, the activity it committed and the + * state it moved the lead to for the decision — never narrated after the fact. + * A step may carry more than one reasoning field (attribution AND eligibility, + * say); each is its own rung. * - * A tool that returned nothing is drawn with a hollow marker. That is not - * cosmetic: a licence check that came back empty and was read as "unlicensed" - * is exactly how a real lead was wrongly dropped, and an operator scanning the - * chain should see the empty result the machine reasoned from. + * The finding — the first sentence, since the employees write the conclusion + * first — is lifted out and stays visible, so the "why" can be read without + * opening the chain. */ - -/** The finding is the first sentence — the employees write the conclusion - * first — lifted out so it can be read without opening the chain. */ function findingOf(text) { const m = String(text).match(/^(.{24,200}?[.!?])(\s|$)/) return m ? m[1].trim() : null } -/** - * Tool ids are how the runtime names a call, not how a person reads one. - * `retrieve_kb_7f9f…` is a knowledge lookup; `call_agentic_tool_29601` is a - * registered tool by number. Give each a name an operator recognises; fall - * back to a de-slugged version of whatever it is. - */ -function toolLabel(name) { - const n = String(name) - if (n.startsWith('retrieve_kb')) return 'Knowledge base' - if (n.startsWith('search_knowledge')) return 'Knowledge base' - if (n === 'get_task_context') return 'Task context' - if (n === 'lookup_partner') return 'Partner registry' - if (n.startsWith('call_agentic_tool')) return 'Agentic tool' - return n - .replace(/_[0-9a-f-]{8,}$/i, '') - .replace(/_\d+$/, '') - .replace(/_/g, ' ') - .replace(/^\w/, (c) => c.toUpperCase()) -} - -/** One pill per distinct tool. The same knowledge base queried three times is - * one thing checked, not three; an operator wants the surfaces consulted, not - * the call count. A tool is "empty" only if EVERY call to it came back empty. */ -function distinctTools(tools) { - const seen = new Map() - for (const t of tools) { - const label = toolLabel(t.name) - if (!seen.has(label)) seen.set(label, { name: label, empty: t.empty }) - else if (!t.empty) seen.get(label).empty = false - } - return [...seen.values()] -} - -export default function ChainOfThought({ reasoning, tools, decided, confidence, model }) { +export default function ChainOfThought({ reasoning, decided, confidence }) { const [open, setOpen] = useState(false) - const toolPills = tools && tools.length ? distinctTools(tools) : [] const primary = reasoning.length ? reasoning[0][1] : '' const finding = findingOf(primary) - // Only the steps that actually have something behind them. A step with no - // evidence is not drawn — a hollow "Checked" with no tools would be noise. - const steps = [] - if (toolPills.length) steps.push('checked') - if (reasoning.length) steps.push('reasoned') - steps.push('decided') + const steps = reasoning.length + 1 // each reasoning rung, plus Decided return (