2sagaco48 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/diagnostics.mjs +141 -0
- package/dist/compute.worker-j5ahIpF-.js +1 -0
- package/dist/graph.worker-DG6iGCB9.js +1 -0
- package/dist/index.html +2314 -0
- package/flatten-guide.md +248 -0
- package/index.html +12 -0
- package/package.json +38 -0
- package/src/App.orig.tsx +204 -0
- package/src/App.orig1.tsx +27 -0
- package/src/App.orig2.tsx +25 -0
- package/src/App.tsx +3 -0
- package/src/BaseApp.tsx +18 -0
- package/src/INTEGRATION_LEDGER.md +22 -0
- package/src/PERSIST_CANARY.orig.txt +1 -0
- package/src/PERSIST_CANARY.txt +1 -0
- package/src/REGRESSION_LEDGER.md +651 -0
- package/src/TEMPLATE_PIPELINE_AUDIT.md +208 -0
- package/src/WILLIAMS_PERSONA_GUIDE.md +62 -0
- package/src/chat/VeritasChat.tsx +69 -0
- package/src/chat/synthesis.ts +31 -0
- package/src/chat/tier.ts +45 -0
- package/src/components/AdaptersPage.orig.tsx +99 -0
- package/src/components/AdaptersPage.tsx +3 -0
- package/src/components/AdversarialPanel.tsx +136 -0
- package/src/components/AnchorBaselinePanel.tsx +75 -0
- package/src/components/AnswerPanel.tsx +60 -0
- package/src/components/ArtifactPanel.tsx +43 -0
- package/src/components/ChatApp.orig.tsx +1582 -0
- package/src/components/ChatApp.orig1.tsx +722 -0
- package/src/components/ChatApp.tsx +95 -0
- package/src/components/ChatAugmentPanels.tsx +73 -0
- package/src/components/CitationLedgerPanel.tsx +313 -0
- package/src/components/ComputeSandboxPanel.tsx +130 -0
- package/src/components/ConfigPanel.tsx +110 -0
- package/src/components/ControlPlanePage.tsx +195 -0
- package/src/components/CreativeTreeLifePage.tsx +234 -0
- package/src/components/CreativeTreeOfLifePanel.tsx +149 -0
- package/src/components/DebugTracePanel.tsx +137 -0
- package/src/components/DeepReasoningTrace.tsx +48 -0
- package/src/components/EntitySheetPanel.tsx +51 -0
- package/src/components/FailureModesPanel.tsx +104 -0
- package/src/components/GBSDashboard.tsx +239 -0
- package/src/components/GraphView.tsx +116 -0
- package/src/components/HUD.tsx +125 -0
- package/src/components/HypothesisPanel.tsx +67 -0
- package/src/components/InnovationGenomeEngine.tsx +792 -0
- package/src/components/InnovationPersonaGuide.tsx +348 -0
- package/src/components/InnovationPersonaPanel.tsx +289 -0
- package/src/components/LiveResourceHUD.tsx +109 -0
- package/src/components/LongWriterPanel.tsx +112 -0
- package/src/components/MainPipelineV10Bridge.tsx +72 -0
- package/src/components/MarkdownLite.tsx +63 -0
- package/src/components/MemoryInspector.tsx +38 -0
- package/src/components/MemoryMonitor.tsx +52 -0
- package/src/components/MemoryStressPanel.tsx +176 -0
- package/src/components/ModulesPage.tsx +171 -0
- package/src/components/PipelineDebugConsole.orig.tsx +1856 -0
- package/src/components/PipelineDebugConsole.tsx +3 -0
- package/src/components/PreFlightHUD.tsx +67 -0
- package/src/components/PrismaFetchTracePanel.tsx +87 -0
- package/src/components/ReportOSPanel.tsx +165 -0
- package/src/components/ResourceEstimatorPage.tsx +327 -0
- package/src/components/RichText.tsx +181 -0
- package/src/components/SagaOsPanel.tsx +605 -0
- package/src/components/SagaOsReference.tsx +421 -0
- package/src/components/SagaOsV2Panel.tsx +594 -0
- package/src/components/SagaStyleTab.tsx +382 -0
- package/src/components/SageBlueprintPanel.tsx +416 -0
- package/src/components/SharedChatInput.tsx +77 -0
- package/src/components/StatFinancePanel.tsx +190 -0
- package/src/components/StylePersonaPanel.tsx +128 -0
- package/src/components/TemplatesPage.tsx +300 -0
- package/src/components/TestPanel.tsx +63 -0
- package/src/components/TraceLog.tsx +89 -0
- package/src/components/V15CalibrationAugment.tsx +1134 -0
- package/src/components/V15CalibrationDialog.tsx +1871 -0
- package/src/components/V15Overlay.orig.tsx +120 -0
- package/src/components/V15Overlay.tsx +4 -0
- package/src/components/V15OverlayWrapper.tsx +362 -0
- package/src/components/V15Toggle.tsx +34 -0
- package/src/console/DiagnosticRecoveryPanel.tsx +358 -0
- package/src/console/EngineeringConsole.tsx +103 -0
- package/src/console/FictionSystemPanels.tsx +882 -0
- package/src/console/InspectorPanels.tsx +254 -0
- package/src/console/ManuscriptGraderPanel.tsx +182 -0
- package/src/console/RecursiveReviserPanel.tsx +245 -0
- package/src/console/SeedControlPanel.tsx +135 -0
- package/src/console/StoryControllerPanel.tsx +443 -0
- package/src/console/engineBridge.ts +258 -0
- package/src/console/gemini.ts +106 -0
- package/src/console/grading.ts +250 -0
- package/src/console/util.ts +45 -0
- package/src/fiction/continuity.ts +372 -0
- package/src/fiction/crash-protection.ts +111 -0
- package/src/fiction/diagnostics.ts +208 -0
- package/src/fiction/diff.ts +74 -0
- package/src/fiction/entity-state-machine.ts +214 -0
- package/src/fiction/nive-engine.ts +404 -0
- package/src/fiction/pipeline-selftest.ts +132 -0
- package/src/fiction/seed-control.ts +193 -0
- package/src/fiction/story-engine.ts +326 -0
- package/src/fiction/types.ts +68 -0
- package/src/flatten-guide.md +379 -0
- package/src/index.css +14 -0
- package/src/index.orig.css +13 -0
- package/src/lib/academic-sources.base.ts +188 -0
- package/src/lib/academic-sources.orig.ts +28 -0
- package/src/lib/academic-sources.ts +10 -0
- package/src/lib/advanced-math.ts +119 -0
- package/src/lib/adversarial-engine.base.ts +409 -0
- package/src/lib/adversarial-engine.ts +82 -0
- package/src/lib/ai-revision.ts +623 -0
- package/src/lib/app-state.tsx +426 -0
- package/src/lib/artifact-registry.ts +97 -0
- package/src/lib/artifacts.ts +180 -0
- package/src/lib/atlas-dr.ts +167 -0
- package/src/lib/browser-mtls.ts +127 -0
- package/src/lib/browser-queue.ts +221 -0
- package/src/lib/browser-search-scraper.base.ts +382 -0
- package/src/lib/browser-search-scraper.orig.ts +90 -0
- package/src/lib/browser-search-scraper.ts +10 -0
- package/src/lib/calc-interceptor.ts +82 -0
- package/src/lib/citation-lane-tap.ts +78 -0
- package/src/lib/citation-ledger-store.ts +369 -0
- package/src/lib/citation-ledger.ts +203 -0
- package/src/lib/compute-sandbox.ts +215 -0
- package/src/lib/compute.worker.ts +153 -0
- package/src/lib/connectors/gemini.ts +120 -0
- package/src/lib/connectors/jina.ts +233 -0
- package/src/lib/connectors/marketdata.ts +51 -0
- package/src/lib/connectors/prismafetch.base.ts +175 -0
- package/src/lib/connectors/prismafetch.ts +1 -0
- package/src/lib/connectors/serpapi.ts +65 -0
- package/src/lib/connectors/wikidata.ts +44 -0
- package/src/lib/constraints.ts +331 -0
- package/src/lib/continuation-detector.base.ts +153 -0
- package/src/lib/continuation-detector.ts +48 -0
- package/src/lib/contradraft.ts +168 -0
- package/src/lib/cors-proxy.ts +120 -0
- package/src/lib/coverage.ts +81 -0
- package/src/lib/debug/architecture-prescription.ts +556 -0
- package/src/lib/debug/covea-repair.ts +543 -0
- package/src/lib/debug/helios-ground.ts +319 -0
- package/src/lib/debug/intent-decomposer.ts +281 -0
- package/src/lib/debug/intent-lattice.ts +349 -0
- package/src/lib/debug/pipeline-diagnosis.ts +1149 -0
- package/src/lib/debug/pipeline-trace-bus.ts +424 -0
- package/src/lib/debug/prompt-forge.ts +678 -0
- package/src/lib/debug/repair-sites.ts +513 -0
- package/src/lib/debug/research-phase.ts +266 -0
- package/src/lib/debug/retrieval-context.ts +76 -0
- package/src/lib/debug/retrieval-hardener.ts +142 -0
- package/src/lib/debug/scraper-debug-runner.ts +576 -0
- package/src/lib/debug/scraper-forensics.ts +418 -0
- package/src/lib/debug/scraper-lane-roadmap.ts +136 -0
- package/src/lib/debug/self-test.ts +415 -0
- package/src/lib/debug/step-attribution.ts +211 -0
- package/src/lib/debug/template-rubric.ts +301 -0
- package/src/lib/debug/unified-innovation.ts +126 -0
- package/src/lib/debug/veritas-hybrid-scraper.ts +335 -0
- package/src/lib/defense-registry.ts +104 -0
- package/src/lib/defenses.ts +162 -0
- package/src/lib/deterministic-citation-ledger.ts +174 -0
- package/src/lib/elo-registry.base.ts +117 -0
- package/src/lib/elo-registry.ts +1 -0
- package/src/lib/entity-resolver.ts +291 -0
- package/src/lib/failure-modes.ts +267 -0
- package/src/lib/feature-registry.ts +69 -0
- package/src/lib/flaw-registry.ts +143 -0
- package/src/lib/flaws/_template.ts +52 -0
- package/src/lib/flaws/builtins.ts +63 -0
- package/src/lib/flaws/finance.ts +169 -0
- package/src/lib/flaws/fixers.ts +10 -0
- package/src/lib/flaws/index.ts +32 -0
- package/src/lib/flaws/legal.ts +157 -0
- package/src/lib/flaws/medical.ts +188 -0
- package/src/lib/flaws/original-defenses-pack.ts +81 -0
- package/src/lib/flaws/sample-declarative-pack.json +38 -0
- package/src/lib/flaws/selftest.ts +201 -0
- package/src/lib/flaws/software-extended.ts +125 -0
- package/src/lib/flaws/software-rn-webgl.ts +182 -0
- package/src/lib/flaws/statistics-advanced.ts +46 -0
- package/src/lib/flaws/statistics.ts +61 -0
- package/src/lib/gbse/config.ts +20 -0
- package/src/lib/gbse/engine.ts +226 -0
- package/src/lib/gbse/graph.ts +118 -0
- package/src/lib/gbse/graph.worker.ts +21 -0
- package/src/lib/gbse/tests.ts +117 -0
- package/src/lib/gbse/tiers.ts +65 -0
- package/src/lib/gbse/types.ts +94 -0
- package/src/lib/innovation-genome-engine-v2.orig.ts +1001 -0
- package/src/lib/innovation-genome-engine-v2.ts +3 -0
- package/src/lib/innovation-genome-engine.orig.ts +330 -0
- package/src/lib/innovation-genome-engine.ts +3 -0
- package/src/lib/innovation-genome-v10.orig.ts +314 -0
- package/src/lib/innovation-genome-v10.ts +2 -0
- package/src/lib/innovation-genome-v3.orig.ts +2015 -0
- package/src/lib/innovation-genome-v3.ts +2 -0
- package/src/lib/innovation-genome-v4.orig.ts +1959 -0
- package/src/lib/innovation-genome-v4.ts +2 -0
- package/src/lib/innovation-genome-v5.orig.ts +1210 -0
- package/src/lib/innovation-genome-v5.ts +2 -0
- package/src/lib/innovation-genome-v7.orig.ts +2549 -0
- package/src/lib/innovation-genome-v7.ts +2 -0
- package/src/lib/innovation-genome-v8.orig.ts +1485 -0
- package/src/lib/innovation-genome-v8.ts +2 -0
- package/src/lib/innovation-genome-v9.orig.ts +643 -0
- package/src/lib/innovation-genome-v9.ts +2 -0
- package/src/lib/jina.base.ts +293 -0
- package/src/lib/jina.ts +1 -0
- package/src/lib/live-telemetry.ts +61 -0
- package/src/lib/longwriter.ts +84 -0
- package/src/lib/manuscript-grade-v2.ts +298 -0
- package/src/lib/memory-governor.ts +173 -0
- package/src/lib/memory-stress-tests.ts +248 -0
- package/src/lib/model-intelligence.ts +48 -0
- package/src/lib/model-rotator.ts +75 -0
- package/src/lib/models.orig.ts +513 -0
- package/src/lib/models.ts +151 -0
- package/src/lib/n-deep.base.ts +592 -0
- package/src/lib/n-deep.ts +51 -0
- package/src/lib/nih-simulator.ts +56 -0
- package/src/lib/nih-vulnerability-fixes.ts +460 -0
- package/src/lib/omega-templates.base.ts +294 -0
- package/src/lib/omega-templates.ts +225 -0
- package/src/lib/omni-nexus.ts +64 -0
- package/src/lib/oracle-adapters.ts +67 -0
- package/src/lib/oracle-registry.ts +163 -0
- package/src/lib/orchestrator.ts +357 -0
- package/src/lib/output-boundary.ts +235 -0
- package/src/lib/overrides/vite-native-chaos-harness.ts +154 -0
- package/src/lib/overrides/vite-native-contract-plane.ts +245 -0
- package/src/lib/overrides/vite-native-doctor-plane.ts +208 -0
- package/src/lib/overrides/vite-native-knowledge-store.ts +267 -0
- package/src/lib/overrides/vite-native-policy-plane.ts +190 -0
- package/src/lib/overrides/vite-native-replay-plane.ts +197 -0
- package/src/lib/overrides/vite-native-runtime-plane.ts +106 -0
- package/src/lib/overrides/vite-native-scraper.ts +848 -0
- package/src/lib/overrides/vite-native-selftest-functional.ts +217 -0
- package/src/lib/overrides/vite-native-selftest.ts +183 -0
- package/src/lib/overrides/vite-native-snapshot-plane.ts +123 -0
- package/src/lib/philosophy-toolkit.ts +75 -0
- package/src/lib/pipeline.orig.ts +659 -0
- package/src/lib/pipeline.ts +42 -0
- package/src/lib/precache.ts +128 -0
- package/src/lib/py-sandbox.ts +205 -0
- package/src/lib/quality-score.ts +96 -0
- package/src/lib/quant-engine.ts +231 -0
- package/src/lib/quant-lib.ts +80 -0
- package/src/lib/reportos.ts +175 -0
- package/src/lib/research-os.ts +306 -0
- package/src/lib/resource-estimator.ts +114 -0
- package/src/lib/rpm-governor.ts +93 -0
- package/src/lib/runtime-estimator.ts +135 -0
- package/src/lib/sage/empty-space-v6.ts +560 -0
- package/src/lib/sage/empty-space-v7.ts +222 -0
- package/src/lib/sage/empty-space.ts +733 -0
- package/src/lib/sage/engine.ts +786 -0
- package/src/lib/sage/index.ts +17 -0
- package/src/lib/sage/judge.ts +246 -0
- package/src/lib/sage/prng.ts +53 -0
- package/src/lib/sage/reality-grounding.ts +380 -0
- package/src/lib/sage/repetition.ts +151 -0
- package/src/lib/sage/saga-os-v2-engines.ts +1870 -0
- package/src/lib/sage/saga-os-v2.ts +274 -0
- package/src/lib/sage/saga-os-v3-core.ts +1491 -0
- package/src/lib/sage/saga-os-v3-release.ts +739 -0
- package/src/lib/sage/saga-os-v4-aureate.ts +1161 -0
- package/src/lib/sage/saga-os-v4.ts +212 -0
- package/src/lib/sage/saga-os-v5.ts +205 -0
- package/src/lib/sage/saga-os-v6.ts +346 -0
- package/src/lib/sage/saga-os-v7.ts +314 -0
- package/src/lib/sage/saga-os-v8.ts +505 -0
- package/src/lib/sage/saga-os.ts +329 -0
- package/src/lib/sage/taxonomy.ts +403 -0
- package/src/lib/sage/unified-roll.ts +524 -0
- package/src/lib/sage/worked-sketches.ts +44 -0
- package/src/lib/sage.ts +166 -0
- package/src/lib/scraper-debug-bus.ts +53 -0
- package/src/lib/scraper-enhanced.orig.ts +153 -0
- package/src/lib/scraper-enhanced.ts +10 -0
- package/src/lib/scraper-hardener.base.orig.ts +155 -0
- package/src/lib/scraper-hardener.base.ts +317 -0
- package/src/lib/scraper-hardener.ts +1 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.orig.ts +419 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.ts +13 -0
- package/src/lib/scraper-vnext/arbiter-omega.orig.ts +688 -0
- package/src/lib/scraper-vnext/arbiter-omega.ts +13 -0
- package/src/lib/scraper-vnext/canonical-portfolio-augments.ts +479 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.orig.ts +1529 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.ts +13 -0
- package/src/lib/scraper-vnext/conclave-omega.ts +1830 -0
- package/src/lib/scraper-vnext/content-extractor-v2.ts +226 -0
- package/src/lib/scraper-vnext/diagnostics-suite.ts +36 -0
- package/src/lib/scraper-vnext/epistemic-packer.ts +97 -0
- package/src/lib/scraper-vnext/fusion-v2.ts +65 -0
- package/src/lib/scraper-vnext/hydra-reader.orig.ts +1216 -0
- package/src/lib/scraper-vnext/hydra-reader.ts +13 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.orig.ts +91 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.ts +13 -0
- package/src/lib/scraper-vnext/nexus-consensus.orig.ts +905 -0
- package/src/lib/scraper-vnext/nexus-consensus.ts +13 -0
- package/src/lib/scraper-vnext/portfolio-consensus-adjudicator.ts +804 -0
- package/src/lib/scraper-vnext/portfolio-consensus-memory.ts +377 -0
- package/src/lib/scraper-vnext/portfolio-terminal-governor.ts +548 -0
- package/src/lib/scraper-vnext/query-strategist.orig.ts +224 -0
- package/src/lib/scraper-vnext/query-strategist.ts +423 -0
- package/src/lib/scraper-vnext/retrieval-accelerator.ts +708 -0
- package/src/lib/scraper-vnext/retrieval-audit-augments.ts +477 -0
- package/src/lib/scraper-vnext/retrieval-control-plane.ts +904 -0
- package/src/lib/scraper-vnext/retrieval-policy-augments.ts +358 -0
- package/src/lib/scraper-vnext/safe-fetch-v2.ts +157 -0
- package/src/lib/scraper-vnext/sentinel-omega.ts +56 -0
- package/src/lib/scraper-vnext/sentinel-orchestrator.ts +69 -0
- package/src/lib/scraper-vnext/sibyl-oracle.orig.ts +413 -0
- package/src/lib/scraper-vnext/sibyl-oracle.ts +13 -0
- package/src/lib/scraper-vnext/smart-read-v2.ts +55 -0
- package/src/lib/scraper-vnext/spa-rescue-bridge.ts +608 -0
- package/src/lib/scraper-vnext/strata-engine.orig.ts +2666 -0
- package/src/lib/scraper-vnext/strata-engine.ts +13 -0
- package/src/lib/scraper-vnext/structured-source-adapter.orig.ts +369 -0
- package/src/lib/scraper-vnext/structured-source-adapter.ts +34 -0
- package/src/lib/scraper-vnext/terminal-complete.ts +224 -0
- package/src/lib/scraper-vnext/terminal-final.ts +277 -0
- package/src/lib/scraper-vnext/terminal-saturation.ts +345 -0
- package/src/lib/scraper-vnext/terminal-wire.ts +425 -0
- package/src/lib/scraper-vnext/vanguard-titanium.orig.ts +467 -0
- package/src/lib/scraper-vnext/vanguard-titanium.ts +17 -0
- package/src/lib/search-cache.ts +138 -0
- package/src/lib/sloop-runner.base.ts +223 -0
- package/src/lib/sloop-runner.ts +104 -0
- package/src/lib/sloop.ts +114 -0
- package/src/lib/small-model-adapters.ts +82 -0
- package/src/lib/sscp.ts +86 -0
- package/src/lib/system-map.ts +54 -0
- package/src/lib/uni-node.ts +86 -0
- package/src/lib/universal-rigor-guard.ts +433 -0
- package/src/lib/v15-gate-testbed.ts +274 -0
- package/src/lib/v15-gemini.base.ts +89 -0
- package/src/lib/v15-gemini.ts +1 -0
- package/src/lib/v15-grounding.orig.ts +241 -0
- package/src/lib/v15-grounding.ts +79 -0
- package/src/lib/v15-pipeline.base.ts +769 -0
- package/src/lib/v15-pipeline.orig.ts +1154 -0
- package/src/lib/v15-pipeline.ts +674 -0
- package/src/lib/v15-questions.ts +64 -0
- package/src/lib/v15-rate-limiter.orig.ts +91 -0
- package/src/lib/v15-rate-limiter.ts +48 -0
- package/src/lib/v15-state.base.ts +87 -0
- package/src/lib/v15-state.ts +28 -0
- package/src/lib/visual-table-generator.tsx +207 -0
- package/src/lib/wasm-runtime.ts +133 -0
- package/src/lib/williams-persona-guide.ts +265 -0
- package/src/lib/williams-style.base.ts +464 -0
- package/src/lib/williams-style.ts +59 -0
- package/src/lib/worker-pool.ts +96 -0
- package/src/lib/writing-tiers.ts +125 -0
- package/src/main.orig.tsx +10 -0
- package/src/main.tsx +10 -0
- package/src/utils/cn.orig.ts +6 -0
- package/src/utils/cn.ts +6 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +31 -0
- package/unify.mjs +537 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,1856 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PipelineDebugConsole.tsx — NET-NEW WORKSPACE COMPONENT (Type C seam)
|
|
3
|
+
* ===========================================================================
|
|
4
|
+
* TURN-4 REVISION — additive, minimum-diff, preserves every turn-2 tab.
|
|
5
|
+
*
|
|
6
|
+
* Tabs (in this order):
|
|
7
|
+
* Timeline phase-classified event log (unchanged)
|
|
8
|
+
* Phases per-phase gantt + roles (unchanged)
|
|
9
|
+
* Passes repair-pass ledger (unchanged)
|
|
10
|
+
* Sources citation ledger + scraper lane trail (unchanged; now dedup)
|
|
11
|
+
* I/O redacted request + full outcome + draft/final (unchanged)
|
|
12
|
+
* Probe run V15 or production baseline from here (unchanged)
|
|
13
|
+
* Attribution ★ NEW · which pipeline step wrote which final sentence
|
|
14
|
+
* Diagnosis ★ NEW · pipeline-architectural fixes (not prompt tweaks)
|
|
15
|
+
* Genome+Tpl ★ NEW · Williams / Innovation Genome v1+v2 / template rubric
|
|
16
|
+
* COVEA ★ NEW · targeted repair audit (accept/reject with reason)
|
|
17
|
+
* Forge (turn-2) generic rubric scoring — kept as a supporting tool
|
|
18
|
+
*
|
|
19
|
+
* The Forge tab is retained but explicitly demoted with a banner explaining
|
|
20
|
+
* that Diagnosis (not Forge) is the correct entry point per turn-3 clarification.
|
|
21
|
+
* ===========================================================================
|
|
22
|
+
*/
|
|
23
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
24
|
+
import { createPortal } from "react-dom";
|
|
25
|
+
import {
|
|
26
|
+
PHASES,
|
|
27
|
+
PHASE_MAP,
|
|
28
|
+
subscribeTrace,
|
|
29
|
+
getRuns,
|
|
30
|
+
clearRuns,
|
|
31
|
+
registerExternalRun,
|
|
32
|
+
exportRun,
|
|
33
|
+
type RunRecord,
|
|
34
|
+
type PhaseId,
|
|
35
|
+
} from "@/lib/debug/pipeline-trace-bus";
|
|
36
|
+
import {
|
|
37
|
+
scoreDeterministic,
|
|
38
|
+
judgeWithLLM,
|
|
39
|
+
diagnose,
|
|
40
|
+
loadTrajectory,
|
|
41
|
+
recordTrajectory,
|
|
42
|
+
clearTrajectory,
|
|
43
|
+
buildOproMetaPrompt,
|
|
44
|
+
buildExternalAuditBundle,
|
|
45
|
+
type ScoreReport,
|
|
46
|
+
type PromptDiagnosis,
|
|
47
|
+
type TrajectoryPoint,
|
|
48
|
+
} from "@/lib/debug/prompt-forge";
|
|
49
|
+
import { runV15OnQuestion, runBaselineOnQuestion } from "@/lib/v15-pipeline";
|
|
50
|
+
import { getGeminiKey, getV15Enabled } from "@/lib/v15-state";
|
|
51
|
+
import {
|
|
52
|
+
loadTemplateRubric,
|
|
53
|
+
computeGenomeSnapshot,
|
|
54
|
+
readLiveGenome,
|
|
55
|
+
OMEGA_TEMPLATES,
|
|
56
|
+
ARCHETYPES,
|
|
57
|
+
type TemplateRubric,
|
|
58
|
+
} from "@/lib/debug/template-rubric";
|
|
59
|
+
import {
|
|
60
|
+
buildStages,
|
|
61
|
+
attributeSentences,
|
|
62
|
+
bucketByOrigin,
|
|
63
|
+
type AttributionReport,
|
|
64
|
+
} from "@/lib/debug/step-attribution";
|
|
65
|
+
import {
|
|
66
|
+
diagnoseRun,
|
|
67
|
+
diagnoseFromInputs,
|
|
68
|
+
bundleForExternalReview,
|
|
69
|
+
exportRepairOrderFor,
|
|
70
|
+
type DiagnosisReport,
|
|
71
|
+
} from "@/lib/debug/pipeline-diagnosis";
|
|
72
|
+
import { buildForensics, exportForensics, isPlaceholderUrl, type ForensicsReport } from "@/lib/debug/scraper-forensics";
|
|
73
|
+
import { prescribe, renderPrescription, exportPrescription, type Prescription } from "@/lib/debug/architecture-prescription";
|
|
74
|
+
import { runSelfTests, type SelfTestReport } from "@/lib/debug/self-test";
|
|
75
|
+
import { SCRAPER_LANE_ROADMAP } from "@/lib/debug/scraper-lane-roadmap";
|
|
76
|
+
import {
|
|
77
|
+
runScraperDebug,
|
|
78
|
+
type ScraperDebugRun,
|
|
79
|
+
type LaneId,
|
|
80
|
+
type LaneRunRecord,
|
|
81
|
+
} from "@/lib/debug/scraper-debug-runner";
|
|
82
|
+
import { geminiGenerate } from '@/lib/v15-gemini';
|
|
83
|
+
import { getGeminiKey as getKey } from "@/lib/v15-state";
|
|
84
|
+
|
|
85
|
+
type TabId =
|
|
86
|
+
| "scraper-debug"
|
|
87
|
+
| "timeline"
|
|
88
|
+
| "phases"
|
|
89
|
+
| "passes"
|
|
90
|
+
| "sources"
|
|
91
|
+
| "scrapers"
|
|
92
|
+
| "io"
|
|
93
|
+
| "probe"
|
|
94
|
+
| "attribution"
|
|
95
|
+
| "diagnosis"
|
|
96
|
+
| "genome"
|
|
97
|
+
| "research"
|
|
98
|
+
| "covea"
|
|
99
|
+
| "architecture"
|
|
100
|
+
| "selftest"
|
|
101
|
+
| "forge";
|
|
102
|
+
|
|
103
|
+
const TABS: { id: TabId; label: string }[] = [
|
|
104
|
+
{ id: "scraper-debug", label: "Scraper Debug 🔬" },
|
|
105
|
+
{ id: "timeline", label: "Timeline" },
|
|
106
|
+
{ id: "phases", label: "Phases" },
|
|
107
|
+
{ id: "passes", label: "Passes" },
|
|
108
|
+
{ id: "sources", label: "Sources" },
|
|
109
|
+
{ id: "scrapers", label: "Scrapers ★" },
|
|
110
|
+
{ id: "io", label: "I/O" },
|
|
111
|
+
{ id: "probe", label: "Probe" },
|
|
112
|
+
{ id: "attribution", label: "Attribution ★" },
|
|
113
|
+
{ id: "diagnosis", label: "Diagnosis ★" },
|
|
114
|
+
{ id: "genome", label: "Genome+Tpl ★" },
|
|
115
|
+
{ id: "research", label: "Research ★" },
|
|
116
|
+
{ id: "covea", label: "COVEA ★" },
|
|
117
|
+
{ id: "architecture", label: "Architecture ★" },
|
|
118
|
+
{ id: "selftest", label: "Self-Test ★" },
|
|
119
|
+
{ id: "forge", label: "Forge" },
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
function useTraceTick(): number {
|
|
123
|
+
const [tick, setTick] = useState(0);
|
|
124
|
+
useEffect(() => subscribeTrace(() => setTick((t) => t + 1)), []);
|
|
125
|
+
return tick;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function copy(text: string) {
|
|
129
|
+
try {
|
|
130
|
+
navigator.clipboard?.writeText(text);
|
|
131
|
+
} catch {
|
|
132
|
+
/* clipboard unavailable */
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function download(name: string, text: string) {
|
|
136
|
+
try {
|
|
137
|
+
const blob = new Blob([text], { type: "application/json" });
|
|
138
|
+
const a = document.createElement("a");
|
|
139
|
+
a.href = URL.createObjectURL(blob);
|
|
140
|
+
a.download = name;
|
|
141
|
+
a.click();
|
|
142
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
|
143
|
+
} catch {
|
|
144
|
+
/* download unavailable */
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const ms = (n: number) => (n < 1000 ? `${n}ms` : `${(n / 1000).toFixed(2)}s`);
|
|
148
|
+
|
|
149
|
+
/* ── Small primitives ─────────────────────────────────────────────────── */
|
|
150
|
+
|
|
151
|
+
function Pill({ color, children }: { color: string; children: React.ReactNode }) {
|
|
152
|
+
return (
|
|
153
|
+
<span
|
|
154
|
+
className="inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide"
|
|
155
|
+
style={{ background: `${color}1a`, color, border: `1px solid ${color}55` }}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
</span>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function ScoreBar({ score, max = 10 }: { score: number; max?: number }) {
|
|
163
|
+
const pct = Math.max(0, Math.min(100, (score / max) * 100));
|
|
164
|
+
const color = score >= 9 ? "#10b981" : score >= 7.5 ? "#84cc16" : score >= 6 ? "#eab308" : score >= 4 ? "#f97316" : "#ef4444";
|
|
165
|
+
return (
|
|
166
|
+
<div className="h-2 w-full overflow-hidden rounded-full bg-zinc-200">
|
|
167
|
+
<div className="h-full rounded-full transition-all" style={{ width: `${pct}%`, background: color }} />
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function Empty({ children }: { children: React.ReactNode }) {
|
|
173
|
+
return (
|
|
174
|
+
<div className="rounded-lg border border-dashed border-zinc-300 bg-zinc-50 p-6 text-center text-xs text-zinc-500">
|
|
175
|
+
{children}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* ── Tab: Timeline (unchanged from turn 2) ────────────────────────────── */
|
|
181
|
+
|
|
182
|
+
function TimelineTab({ run }: { run: RunRecord | null }) {
|
|
183
|
+
const [filter, setFilter] = useState<PhaseId | "all">("all");
|
|
184
|
+
const [q, setQ] = useState("");
|
|
185
|
+
const [follow, setFollow] = useState(true);
|
|
186
|
+
const endRef = useRef<HTMLDivElement>(null);
|
|
187
|
+
|
|
188
|
+
const events = useMemo(() => {
|
|
189
|
+
if (!run) return [];
|
|
190
|
+
return run.events.filter(
|
|
191
|
+
(e) => (filter === "all" || e.phase === filter) && (!q || e.message.toLowerCase().includes(q.toLowerCase()))
|
|
192
|
+
);
|
|
193
|
+
}, [run, filter, q, run?.events.length]);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (follow) endRef.current?.scrollIntoView({ block: "end" });
|
|
197
|
+
}, [events.length, follow]);
|
|
198
|
+
|
|
199
|
+
if (!run) return <Empty>No run selected. Run a question in the app or use the <b>Probe</b> tab.</Empty>;
|
|
200
|
+
const active = new Set(run.events.map((e) => e.phase));
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div className="flex h-full flex-col gap-2">
|
|
204
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
205
|
+
<button onClick={() => setFilter("all")} className={`rounded px-2 py-1 text-[10px] font-bold ${filter === "all" ? "bg-zinc-900 text-white" : "bg-zinc-100 text-zinc-600"}`}>
|
|
206
|
+
ALL ({run.events.length})
|
|
207
|
+
</button>
|
|
208
|
+
{PHASES.filter((p) => active.has(p.id)).map((p) => (
|
|
209
|
+
<button key={p.id} onClick={() => setFilter(p.id)} title={p.role} className="rounded px-2 py-1 text-[10px] font-bold" style={filter === p.id ? { background: p.color, color: "#fff" } : { background: `${p.color}1a`, color: p.color }}>
|
|
210
|
+
{p.label} ({run.phaseStats[p.id]?.count ?? 0})
|
|
211
|
+
</button>
|
|
212
|
+
))}
|
|
213
|
+
<input value={q} onChange={(e) => setQ(e.target.value)} placeholder="search events…" className="ml-auto w-48 rounded border border-zinc-300 px-2 py-1 text-[11px]" />
|
|
214
|
+
<label className="flex items-center gap-1 text-[10px] text-zinc-600">
|
|
215
|
+
<input type="checkbox" checked={follow} onChange={(e) => setFollow(e.target.checked)} /> follow
|
|
216
|
+
</label>
|
|
217
|
+
</div>
|
|
218
|
+
<div className="min-h-0 flex-1 overflow-auto rounded-lg border border-zinc-200 bg-zinc-950 p-2 font-mono text-[11px] leading-relaxed">
|
|
219
|
+
{events.length === 0 ? (
|
|
220
|
+
<div className="p-4 text-zinc-500">No events match.</div>
|
|
221
|
+
) : (
|
|
222
|
+
events.map((e) => {
|
|
223
|
+
const meta = PHASE_MAP[e.phase];
|
|
224
|
+
return (
|
|
225
|
+
<div key={e.seq} className="flex gap-2 border-b border-zinc-900 py-0.5 hover:bg-zinc-900">
|
|
226
|
+
<span className="w-16 shrink-0 text-right text-zinc-600">{ms(e.dt)}</span>
|
|
227
|
+
<span className="w-28 shrink-0 truncate font-bold" style={{ color: meta.color }} title={meta.role}>
|
|
228
|
+
{e.lane ? `${meta.label}/${e.lane}` : meta.label}
|
|
229
|
+
</span>
|
|
230
|
+
<span className="w-16 shrink-0 text-zinc-600">{e.source}</span>
|
|
231
|
+
<span className="whitespace-pre-wrap break-words text-zinc-200">{e.message}</span>
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
})
|
|
235
|
+
)}
|
|
236
|
+
<div ref={endRef} />
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ── Tab: Phases (unchanged) ──────────────────────────────────────────── */
|
|
243
|
+
function PhasesTab({ run }: { run: RunRecord | null }) {
|
|
244
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
245
|
+
const total = Math.max(1, (run.endedAt ?? Date.now()) - run.startedAt);
|
|
246
|
+
const seen = PHASES.filter((p) => run.phaseStats[p.id]);
|
|
247
|
+
return (
|
|
248
|
+
<div className="space-y-3 overflow-auto">
|
|
249
|
+
<div className="rounded-lg border border-zinc-200 bg-white p-3">
|
|
250
|
+
<div className="mb-2 text-[11px] font-bold uppercase tracking-wide text-zinc-500">Execution gantt · total {ms(total)}</div>
|
|
251
|
+
<div className="space-y-1.5">
|
|
252
|
+
{seen.map((p) => {
|
|
253
|
+
const st = run.phaseStats[p.id]!;
|
|
254
|
+
const left = (st.firstDt / total) * 100;
|
|
255
|
+
const width = Math.max(0.8, ((st.lastDt - st.firstDt) / total) * 100);
|
|
256
|
+
return (
|
|
257
|
+
<div key={p.id} className="flex items-center gap-2">
|
|
258
|
+
<div className="w-32 shrink-0 truncate text-[11px] font-semibold" style={{ color: p.color }}>{p.label}</div>
|
|
259
|
+
<div className="relative h-4 flex-1 rounded bg-zinc-100">
|
|
260
|
+
<div className="absolute top-0 h-4 rounded" style={{ left: `${left}%`, width: `${width}%`, background: p.color }} title={`${ms(st.firstDt)} → ${ms(st.lastDt)}`} />
|
|
261
|
+
</div>
|
|
262
|
+
<div className="w-32 shrink-0 text-right font-mono text-[10px] text-zinc-500">{st.count}× · {ms(st.lastDt - st.firstDt)}</div>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
})}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
269
|
+
{seen.map((p) => {
|
|
270
|
+
const st = run.phaseStats[p.id]!;
|
|
271
|
+
return (
|
|
272
|
+
<div key={p.id} className="rounded-lg border border-zinc-200 bg-white p-3">
|
|
273
|
+
<div className="mb-1 flex items-center justify-between">
|
|
274
|
+
<Pill color={p.color}>{p.label}</Pill>
|
|
275
|
+
<span className="font-mono text-[10px] text-zinc-500">{st.count} event(s) · {ms(st.firstDt)}→{ms(st.lastDt)}</span>
|
|
276
|
+
</div>
|
|
277
|
+
<p className="text-[11px] leading-snug text-zinc-600">{p.role}</p>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
})}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* ── Tab: Passes (unchanged) ──────────────────────────────────────────── */
|
|
287
|
+
function PassesTab({ run }: { run: RunRecord | null }) {
|
|
288
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
289
|
+
if (run.passes.length === 0) return <Empty>No repair passes recorded.</Empty>;
|
|
290
|
+
return (
|
|
291
|
+
<div className="overflow-auto">
|
|
292
|
+
<table className="w-full border-collapse text-[11px]">
|
|
293
|
+
<thead className="sticky top-0 bg-zinc-100">
|
|
294
|
+
<tr>{["#", "Guard", "Δ", "Verdict", "Crit/Major", "Len", "Note"].map((h) => <th key={h} className="border-b border-zinc-300 px-2 py-1.5 text-left font-bold uppercase tracking-wide text-zinc-600">{h}</th>)}</tr>
|
|
295
|
+
</thead>
|
|
296
|
+
<tbody>
|
|
297
|
+
{run.passes.map((p, i) => {
|
|
298
|
+
const prev = run.passes[i - 1];
|
|
299
|
+
const delta = prev && Number.isFinite(p.guardScore) && Number.isFinite(prev.guardScore) ? p.guardScore - prev.guardScore : null;
|
|
300
|
+
return (
|
|
301
|
+
<tr key={i} className={p.accepted ? "" : "bg-red-50"}>
|
|
302
|
+
<td className="border-b border-zinc-200 px-2 py-1 font-mono">{p.index}</td>
|
|
303
|
+
<td className="border-b border-zinc-200 px-2 py-1 font-mono font-bold">{Number.isFinite(p.guardScore) ? p.guardScore.toFixed(2) : "—"}</td>
|
|
304
|
+
<td className="border-b border-zinc-200 px-2 py-1 font-mono" style={{ color: delta == null ? "#94a3b8" : delta >= 0 ? "#16a34a" : "#dc2626" }}>{delta == null ? "—" : `${delta >= 0 ? "+" : ""}${delta.toFixed(2)}`}</td>
|
|
305
|
+
<td className="border-b border-zinc-200 px-2 py-1">{p.accepted ? <Pill color="#16a34a">accepted</Pill> : <Pill color="#dc2626">rejected</Pill>}</td>
|
|
306
|
+
<td className="border-b border-zinc-200 px-2 py-1 font-mono">{p.critical != null ? `${p.critical}/${p.major ?? 0}` : "—"}</td>
|
|
307
|
+
<td className="border-b border-zinc-200 px-2 py-1 font-mono">{p.textLength ?? "—"}</td>
|
|
308
|
+
<td className="border-b border-zinc-200 px-2 py-1 text-zinc-600">{p.note}</td>
|
|
309
|
+
</tr>
|
|
310
|
+
);
|
|
311
|
+
})}
|
|
312
|
+
</tbody>
|
|
313
|
+
</table>
|
|
314
|
+
</div>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* ── Tab: Sources (unchanged) ─────────────────────────────────────────── */
|
|
319
|
+
function SourcesTab({ run }: { run: RunRecord | null }) {
|
|
320
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
321
|
+
const scraperEvents = run.events.filter((e) => e.source === "scraper");
|
|
322
|
+
const byStage = new Map<string, typeof run.sources>();
|
|
323
|
+
for (const s of run.sources) { const b = byStage.get(s.stage) ?? []; b.push(s); byStage.set(s.stage, b); }
|
|
324
|
+
return (
|
|
325
|
+
<div className="space-y-3 overflow-auto">
|
|
326
|
+
<div className="rounded-lg border border-sky-200 bg-sky-50 p-2 text-[10px] leading-snug text-sky-800">
|
|
327
|
+
Sources are grouped by the stage at which each was <b>first admitted</b> to the citation ledger.
|
|
328
|
+
</div>
|
|
329
|
+
{run.sources.length === 0 ? (
|
|
330
|
+
<Empty>No sources in <code>outcome.citationAudit.entries</code>.</Empty>
|
|
331
|
+
) : (
|
|
332
|
+
[...byStage.entries()].map(([stage, list]) => (
|
|
333
|
+
<div key={stage} className="rounded-lg border border-zinc-200 bg-white">
|
|
334
|
+
<div className="flex items-center justify-between border-b border-zinc-200 px-3 py-1.5">
|
|
335
|
+
<Pill color="#0ea5e9">{stage}</Pill>
|
|
336
|
+
<span className="font-mono text-[10px] text-zinc-500">{list.length} source(s)</span>
|
|
337
|
+
</div>
|
|
338
|
+
<div className="divide-y divide-zinc-100">
|
|
339
|
+
{list.map((s, i) => (
|
|
340
|
+
<div key={i} className="px-3 py-2">
|
|
341
|
+
<div className="truncate text-[11px] font-semibold text-zinc-800">{s.title || "(untitled)"}</div>
|
|
342
|
+
{s.url && <a href={s.url} target="_blank" rel="noreferrer" className="block truncate font-mono text-[10px] text-sky-600 hover:underline">{s.url}</a>}
|
|
343
|
+
{s.snippet && <p className="mt-1 line-clamp-3 text-[10px] leading-snug text-zinc-500">{s.snippet}</p>}
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
))
|
|
349
|
+
)}
|
|
350
|
+
<div className="rounded-lg border border-zinc-200 bg-white">
|
|
351
|
+
<div className="border-b border-zinc-200 px-3 py-1.5 text-[11px] font-bold uppercase tracking-wide text-zinc-500">Scraper lane log ({scraperEvents.length})</div>
|
|
352
|
+
<div className="max-h-64 overflow-auto bg-zinc-950 p-2 font-mono text-[10px]">
|
|
353
|
+
{scraperEvents.length === 0 ? (
|
|
354
|
+
<div className="text-zinc-500">No lane events captured via <code>scraper-debug-bus</code>. If you see grounding progress lines in the Timeline but no entries here, the lanes are relaying via <code>onProgress</code> only — that is normal for the package's native scraper paths.</div>
|
|
355
|
+
) : (
|
|
356
|
+
scraperEvents.map((e) => (
|
|
357
|
+
<div key={e.seq} className="flex gap-2 py-0.5">
|
|
358
|
+
<span className="w-14 shrink-0 text-right text-zinc-600">{ms(e.dt)}</span>
|
|
359
|
+
<span className="w-32 shrink-0 truncate font-bold text-cyan-400">{e.lane}</span>
|
|
360
|
+
<span className="break-words text-zinc-300">{e.message}</span>
|
|
361
|
+
</div>
|
|
362
|
+
))
|
|
363
|
+
)}
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* ── Tab: I/O (unchanged) ─────────────────────────────────────────────── */
|
|
371
|
+
function IOTab({ run }: { run: RunRecord | null }) {
|
|
372
|
+
const [view, setView] = useState<"input" | "output" | "draft" | "final">("input");
|
|
373
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
374
|
+
const body =
|
|
375
|
+
view === "input" ? JSON.stringify(run.input ?? {}, null, 2) :
|
|
376
|
+
view === "output" ? JSON.stringify(run.output ?? {}, null, 2) :
|
|
377
|
+
view === "draft" ? run.draftText ?? "(no draft captured)" :
|
|
378
|
+
run.finalText ?? "(no final text captured)";
|
|
379
|
+
return (
|
|
380
|
+
<div className="flex h-full flex-col gap-2">
|
|
381
|
+
<div className="flex items-center gap-1.5">
|
|
382
|
+
{(["input", "output", "draft", "final"] as const).map((v) => (
|
|
383
|
+
<button key={v} onClick={() => setView(v)} className={`rounded px-2 py-1 text-[10px] font-bold uppercase ${view === v ? "bg-zinc-900 text-white" : "bg-zinc-100 text-zinc-600"}`}>{v}</button>
|
|
384
|
+
))}
|
|
385
|
+
<span className="ml-2 font-mono text-[10px] text-zinc-500">{body.length.toLocaleString()} chars</span>
|
|
386
|
+
<button onClick={() => copy(body)} className="ml-auto rounded bg-zinc-100 px-2 py-1 text-[10px] font-bold text-zinc-700">Copy</button>
|
|
387
|
+
<button onClick={() => download(`${run.id}.json`, exportRun(run))} className="rounded bg-indigo-600 px-2 py-1 text-[10px] font-bold text-white">Export run JSON</button>
|
|
388
|
+
</div>
|
|
389
|
+
{view === "input" && <div className="rounded border border-amber-200 bg-amber-50 px-2 py-1 text-[10px] text-amber-800">Secrets redacted. Functions shown as <code>«fn name»</code>.</div>}
|
|
390
|
+
<pre className="min-h-0 flex-1 overflow-auto whitespace-pre-wrap break-words rounded-lg border border-zinc-200 bg-zinc-950 p-3 font-mono text-[11px] leading-relaxed text-zinc-200">{body}</pre>
|
|
391
|
+
</div>
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* ── Tab: Probe (unchanged) ───────────────────────────────────────────── */
|
|
396
|
+
function ProbeTab() {
|
|
397
|
+
const [question, setQuestion] = useState("");
|
|
398
|
+
const [busy, setBusy] = useState<null | "v15" | "baseline">(null);
|
|
399
|
+
const [webSearch, setWebSearch] = useState(true);
|
|
400
|
+
const [maxDepth, setMaxDepth] = useState(3);
|
|
401
|
+
const [advancedGates, setAdvancedGates] = useState(true);
|
|
402
|
+
const [runJudge, setRunJudge] = useState(true);
|
|
403
|
+
const [log, setLog] = useState<string[]>([]);
|
|
404
|
+
const hasKey = !!getGeminiKey();
|
|
405
|
+
|
|
406
|
+
const go = useCallback(async (mode: "v15" | "baseline") => {
|
|
407
|
+
if (!question.trim() || busy) return;
|
|
408
|
+
setBusy(mode);
|
|
409
|
+
setLog([`▶ ${mode} start`]);
|
|
410
|
+
const onProgress = (s: string) => setLog((l) => [...l.slice(-200), s]);
|
|
411
|
+
try {
|
|
412
|
+
const apiKey = getGeminiKey();
|
|
413
|
+
if (mode === "v15") {
|
|
414
|
+
await runV15OnQuestion({ apiKey, question: question.trim(), profile: { webSearch } as any, maxDepth, advancedGates, runJudge, onProgress } as any);
|
|
415
|
+
} else {
|
|
416
|
+
await runBaselineOnQuestion({ apiKey, question: question.trim(), onProgress } as any);
|
|
417
|
+
}
|
|
418
|
+
setLog((l) => [...l, `✔ ${mode} complete — see Timeline / Attribution / Diagnosis`]);
|
|
419
|
+
} catch (e) {
|
|
420
|
+
setLog((l) => [...l, `✘ ${e instanceof Error ? e.message : String(e)}`]);
|
|
421
|
+
} finally { setBusy(null); }
|
|
422
|
+
}, [question, busy, webSearch, maxDepth, advancedGates, runJudge]);
|
|
423
|
+
|
|
424
|
+
return (
|
|
425
|
+
<div className="flex h-full flex-col gap-3 overflow-auto">
|
|
426
|
+
<div className="rounded-lg border border-indigo-200 bg-indigo-50 p-3 text-[11px] leading-snug text-indigo-900">
|
|
427
|
+
<b>Production vs V15.</b> V15 toggle: <b>{getV15Enabled() ? "ON" : "OFF"}</b>. Baseline = single draft + judge. V15 = full rigor stack + COVEA post-repair.
|
|
428
|
+
</div>
|
|
429
|
+
{!hasKey && <div className="rounded-lg border border-red-200 bg-red-50 p-3 text-[11px] text-red-800">No Gemini API key configured.</div>}
|
|
430
|
+
<textarea value={question} onChange={(e) => setQuestion(e.target.value)} rows={4} placeholder="Question to run through the pipeline…" className="w-full resize-y rounded-lg border border-zinc-300 p-2 font-mono text-[12px]" />
|
|
431
|
+
<div className="flex flex-wrap items-center gap-3 text-[11px]">
|
|
432
|
+
<label className="flex items-center gap-1.5"><input type="checkbox" checked={webSearch} onChange={(e) => setWebSearch(e.target.checked)} /> web grounding</label>
|
|
433
|
+
<label className="flex items-center gap-1.5"><input type="checkbox" checked={advancedGates} onChange={(e) => setAdvancedGates(e.target.checked)} /> advanced gates</label>
|
|
434
|
+
<label className="flex items-center gap-1.5"><input type="checkbox" checked={runJudge} onChange={(e) => setRunJudge(e.target.checked)} /> judge panel</label>
|
|
435
|
+
<label className="flex items-center gap-1.5">maxDepth<input type="number" min={1} max={8} value={maxDepth} onChange={(e) => setMaxDepth(Number(e.target.value))} className="w-14 rounded border border-zinc-300 px-1 py-0.5" /></label>
|
|
436
|
+
<div className="ml-auto flex gap-2">
|
|
437
|
+
<button disabled={!!busy || !question.trim()} onClick={() => go("baseline")} className="rounded bg-zinc-700 px-3 py-1.5 text-[11px] font-bold text-white disabled:opacity-40">{busy === "baseline" ? "Running…" : "Run Production Baseline"}</button>
|
|
438
|
+
<button disabled={!!busy || !question.trim()} onClick={() => go("v15")} className="rounded bg-indigo-600 px-3 py-1.5 text-[11px] font-bold text-white disabled:opacity-40">{busy === "v15" ? "Running…" : "Run V15 Pipeline"}</button>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
<div className="min-h-40 flex-1 overflow-auto rounded-lg border border-zinc-200 bg-zinc-950 p-2 font-mono text-[10px] text-zinc-300">
|
|
442
|
+
{log.length === 0 ? <span className="text-zinc-600">Live progress here; full trace lands in Timeline.</span> : log.map((l, i) => <div key={i}>{l}</div>)}
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* ── Tab: Attribution ★ ───────────────────────────────────────────────── */
|
|
449
|
+
|
|
450
|
+
function AttributionTab({ run }: { run: RunRecord | null }) {
|
|
451
|
+
const report: AttributionReport | null = useMemo(() => {
|
|
452
|
+
if (!run || !run.finalText) return null;
|
|
453
|
+
const out = (run.output ?? {}) as any;
|
|
454
|
+
const stages = buildStages({
|
|
455
|
+
bestOfNCandidates: out.bestOfNCandidates,
|
|
456
|
+
draft: out.draft,
|
|
457
|
+
passHistory: out.passHistory,
|
|
458
|
+
polishText: out.polishApplied ? out.fixed : undefined,
|
|
459
|
+
coveaText: (run.covea as any)?.repairedDraft,
|
|
460
|
+
});
|
|
461
|
+
return attributeSentences(run.finalText, stages);
|
|
462
|
+
}, [run?.id, run?.finalText]);
|
|
463
|
+
|
|
464
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
465
|
+
if (!report) return <Empty>No final text or stage snapshots — attribution needs at least a draft and a final text.</Empty>;
|
|
466
|
+
|
|
467
|
+
const buckets = bucketByOrigin(report);
|
|
468
|
+
|
|
469
|
+
return (
|
|
470
|
+
<div className="space-y-3 overflow-auto">
|
|
471
|
+
<div className="rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-[11px] leading-snug text-emerald-900">
|
|
472
|
+
<b>Attribution</b> maps every sentence in the final output back to the earliest pipeline stage where a
|
|
473
|
+
sufficiently-similar sentence appeared. Sentences with no matching origin are ones the model added ex nihilo
|
|
474
|
+
(usually adversarial-repair or polish; sometimes hallucinated).
|
|
475
|
+
</div>
|
|
476
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
477
|
+
<div className="rounded-lg border border-zinc-200 bg-white p-3">
|
|
478
|
+
<div className="mb-1 text-[10px] font-bold uppercase tracking-wide text-zinc-500">Origin counts (who wrote it)</div>
|
|
479
|
+
{Object.keys(report.originCounts).length === 0 ? <div className="text-[11px] text-zinc-500">no attributions</div> :
|
|
480
|
+
Object.entries(report.originCounts).sort((a, b) => b[1] - a[1]).map(([k, v]) => (
|
|
481
|
+
<div key={k} className="flex justify-between border-b border-zinc-100 py-0.5 font-mono text-[10px]"><span className="truncate text-zinc-700">{k}</span><span className="font-bold">{v}</span></div>
|
|
482
|
+
))}
|
|
483
|
+
</div>
|
|
484
|
+
<div className="rounded-lg border border-zinc-200 bg-white p-3">
|
|
485
|
+
<div className="mb-1 text-[10px] font-bold uppercase tracking-wide text-zinc-500">Modification counts (who edited it)</div>
|
|
486
|
+
{Object.keys(report.modifiedCounts).length === 0 ? <div className="text-[11px] text-zinc-500">no modifications tracked</div> :
|
|
487
|
+
Object.entries(report.modifiedCounts).sort((a, b) => b[1] - a[1]).map(([k, v]) => (
|
|
488
|
+
<div key={k} className="flex justify-between border-b border-zinc-100 py-0.5 font-mono text-[10px]"><span className="truncate text-zinc-700">{k}</span><span className="font-bold">{v}</span></div>
|
|
489
|
+
))}
|
|
490
|
+
{report.unattributed > 0 && <div className="mt-2 rounded bg-amber-50 px-2 py-1 text-[10px] text-amber-800">{report.unattributed} sentence(s) unattributed — the model added them ex nihilo (potential fabrication site)</div>}
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
<div className="space-y-2">
|
|
494
|
+
{buckets.map((b, i) => (
|
|
495
|
+
<details key={i} className="rounded-lg border border-zinc-200 bg-white" open={i < 2}>
|
|
496
|
+
<summary className="cursor-pointer px-3 py-1.5 text-[11px] font-bold text-zinc-800">
|
|
497
|
+
{b.stage ? <span style={{ color: "#7c3aed" }}>{b.stage.label}</span> : <span className="text-amber-700">(unattributed — potential fabrication)</span>}
|
|
498
|
+
<span className="ml-2 font-mono text-[10px] text-zinc-500">{b.sentences.length} sentence(s)</span>
|
|
499
|
+
</summary>
|
|
500
|
+
<div className="divide-y divide-zinc-100">
|
|
501
|
+
{b.sentences.map((s) => (
|
|
502
|
+
<div key={s.index} className="px-3 py-1.5 text-[11px]">
|
|
503
|
+
<div className="text-zinc-800">{s.sentence}</div>
|
|
504
|
+
{s.modifiedByStages.length > 0 && (
|
|
505
|
+
<div className="mt-1 flex flex-wrap gap-1">
|
|
506
|
+
<span className="text-[10px] text-zinc-500">modified by:</span>
|
|
507
|
+
{s.modifiedByStages.map((m, j) => <span key={j} className="rounded bg-violet-100 px-1 py-0.5 font-mono text-[9px] text-violet-800">{m.label}</span>)}
|
|
508
|
+
</div>
|
|
509
|
+
)}
|
|
510
|
+
</div>
|
|
511
|
+
))}
|
|
512
|
+
</div>
|
|
513
|
+
</details>
|
|
514
|
+
))}
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/* ── Tab: Scrapers ★ — per-lane retrieval forensics ───────────────────── */
|
|
521
|
+
|
|
522
|
+
function ScrapersTab({ run }: { run: RunRecord | null }) {
|
|
523
|
+
const [expanded, setExpanded] = useState<string | null>(null);
|
|
524
|
+
const rep: ForensicsReport | null = useMemo(() => {
|
|
525
|
+
if (!run) return null;
|
|
526
|
+
try { return buildForensics(run); } catch { return null; }
|
|
527
|
+
}, [run?.id, run?.events.length, run?.output]);
|
|
528
|
+
|
|
529
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
530
|
+
if (!rep) return <Empty>Forensics unavailable for this run.</Empty>;
|
|
531
|
+
|
|
532
|
+
const t = rep.totals;
|
|
533
|
+
return (
|
|
534
|
+
<div className="space-y-3 overflow-auto">
|
|
535
|
+
<div className="rounded-lg border border-cyan-300 bg-cyan-50 p-3 text-[11px] leading-snug text-cyan-900">
|
|
536
|
+
<b>Per-lane retrieval forensics.</b> Every row is parsed from a real emitter line — no lane is shown that
|
|
537
|
+
did not report. <b>clean source</b> := <code>!quarantined && !hardQuarantined && content.length ≥ 80</code>{" "}
|
|
538
|
+
(verified in package source). <b>atoms</b> are epistemic claims, not documents — a lane can pack many atoms
|
|
539
|
+
and zero sources, which is the zero-yield defect.
|
|
540
|
+
</div>
|
|
541
|
+
|
|
542
|
+
<div className="grid grid-cols-3 gap-2 sm:grid-cols-6">
|
|
543
|
+
{[
|
|
544
|
+
{ k: "lanes", v: t.lanesSeen, c: "#0891b2" },
|
|
545
|
+
{ k: "queries", v: t.queriesDispatched, c: "#0891b2" },
|
|
546
|
+
{ k: "ledger", v: t.ledgerEntries, c: "#0891b2" },
|
|
547
|
+
{ k: "real URLs", v: t.realUrlCount, c: "#059669" },
|
|
548
|
+
{ k: "placeholder", v: t.placeholderUrlCount, c: t.placeholderUrlCount > 0 ? "#dc2626" : "#71717a" },
|
|
549
|
+
{ k: "zero-yield", v: rep.zeroYieldLanes.length, c: rep.zeroYieldLanes.length > 0 ? "#dc2626" : "#71717a" },
|
|
550
|
+
].map((m) => (
|
|
551
|
+
<div key={m.k} className="rounded border bg-white p-2" style={{ borderColor: `${m.c}55` }}>
|
|
552
|
+
<div className="text-[9px] font-bold uppercase tracking-wide" style={{ color: m.c }}>{m.k}</div>
|
|
553
|
+
<div className="text-xl font-black text-zinc-900">{m.v}</div>
|
|
554
|
+
</div>
|
|
555
|
+
))}
|
|
556
|
+
</div>
|
|
557
|
+
|
|
558
|
+
<div className="flex flex-wrap gap-2">
|
|
559
|
+
<button onClick={() => copy(exportForensics(run, rep))} className="rounded bg-cyan-700 px-2 py-1 text-[10px] font-bold text-white">Copy forensics JSON</button>
|
|
560
|
+
<button onClick={() => download(`${run.id}-forensics.json`, exportForensics(run, rep))} className="rounded bg-zinc-900 px-2 py-1 text-[10px] font-bold text-white">Download</button>
|
|
561
|
+
{rep.allRealUrls.length > 0 && (
|
|
562
|
+
<button onClick={() => copy(rep.allRealUrls.join("\n"))} className="rounded bg-emerald-700 px-2 py-1 text-[10px] font-bold text-white">Copy {rep.allRealUrls.length} real URL(s)</button>
|
|
563
|
+
)}
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
{rep.winner && (
|
|
567
|
+
<div className="rounded border border-emerald-300 bg-emerald-50 px-3 py-1.5 text-[11px] text-emerald-900">
|
|
568
|
+
Portfolio winner: <b>{rep.winner.lane}</b> · sources={rep.winner.sources} · lanes={rep.winner.lanes}
|
|
569
|
+
</div>
|
|
570
|
+
)}
|
|
571
|
+
|
|
572
|
+
{t.placeholderUrlCount > 0 && (
|
|
573
|
+
<div className="rounded-lg border-2 border-red-300 bg-red-50 p-3">
|
|
574
|
+
<div className="text-[10px] font-bold uppercase tracking-wide text-red-800">
|
|
575
|
+
{t.placeholderUrlCount} non-resolvable citation URL(s)
|
|
576
|
+
</div>
|
|
577
|
+
<p className="mt-1 text-[11px] leading-snug text-red-900">
|
|
578
|
+
These were manufactured by <code>{"`source-${sourceIndex+1}`"}</code> at{" "}
|
|
579
|
+
<b>v15-grounding.orig.ts:113</b> (vanguard) and <b>:131</b> (palisade). They are string concatenations of an
|
|
580
|
+
array index — not URLs — and can never resolve. See the Diagnosis tab for the exact repair.
|
|
581
|
+
</p>
|
|
582
|
+
<div className="mt-1 flex flex-wrap gap-1">
|
|
583
|
+
{rep.allPlaceholderUrls.map((u) => (
|
|
584
|
+
<code key={u} className="rounded bg-white px-1.5 py-0.5 text-[10px] text-red-800 ring-1 ring-red-200">{u}</code>
|
|
585
|
+
))}
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
)}
|
|
589
|
+
|
|
590
|
+
{rep.zeroYieldLanes.length > 0 && (
|
|
591
|
+
<div className="rounded-lg border-2 border-amber-300 bg-amber-50 p-3">
|
|
592
|
+
<div className="text-[10px] font-bold uppercase tracking-wide text-amber-800">Zero-yield lanes (D14)</div>
|
|
593
|
+
<p className="mt-1 text-[11px] text-amber-900">Packed epistemic atoms but returned zero real documents. The acceptance predicate at <b>v15-grounding.orig.ts:116</b> counts claims, not sources.</p>
|
|
594
|
+
<ul className="mt-1 space-y-0.5">
|
|
595
|
+
{rep.zeroYieldLanes.map((l, i) => (
|
|
596
|
+
<li key={i} className="font-mono text-[10px] text-amber-900">
|
|
597
|
+
{l.lane}[{l.section ?? "-"}]: {l.atomsPacked} atoms / {l.sourcesPacked} sources / util={l.utilizationPct ?? "?"}%
|
|
598
|
+
</li>
|
|
599
|
+
))}
|
|
600
|
+
</ul>
|
|
601
|
+
</div>
|
|
602
|
+
)}
|
|
603
|
+
|
|
604
|
+
<div className="space-y-2">
|
|
605
|
+
{rep.lanes.length === 0 && <Empty>No lane telemetry captured. Lanes report through <code>onProgress</code>; if the Timeline shows grounding lines but this is empty, the emitter format changed.</Empty>}
|
|
606
|
+
{rep.lanes.map((l, i) => {
|
|
607
|
+
const id = `${l.lane}-${l.section}-${i}`;
|
|
608
|
+
const open = expanded === id;
|
|
609
|
+
const bad = (l.atomsPacked ?? 0) > 0 && (l.sourcesPacked ?? 0) === 0;
|
|
610
|
+
return (
|
|
611
|
+
<div key={id} className="rounded-lg border bg-white" style={{ borderColor: bad ? "#fca5a5" : l.outcome === "winner" ? "#6ee7b7" : "#e4e4e7" }}>
|
|
612
|
+
<button onClick={() => setExpanded(open ? null : id)} className="flex w-full items-center gap-2 px-3 py-1.5 text-left">
|
|
613
|
+
<Pill color={l.outcome === "winner" ? "#059669" : l.outcome === "quarantined" ? "#dc2626" : l.outcome === "fulfilled" ? "#0891b2" : "#71717a"}>{l.lane}</Pill>
|
|
614
|
+
{l.section && <span className="text-[10px] text-zinc-500">[{l.section}]</span>}
|
|
615
|
+
<span className="font-mono text-[10px] text-zinc-500">
|
|
616
|
+
{l.atomsPacked != null && `${l.atomsPacked}a`}
|
|
617
|
+
{l.sourcesPacked != null && ` / ${l.sourcesPacked}s`}
|
|
618
|
+
{l.utilizationPct != null && ` / ${l.utilizationPct}%`}
|
|
619
|
+
{l.attested != null && ` / att=${l.attested} sup=${l.supported}`}
|
|
620
|
+
{l.cleanSources != null && ` / clean=${l.cleanSources}`}
|
|
621
|
+
{l.quarantined != null && ` / quar=${l.quarantined}`}
|
|
622
|
+
{l.fusedResults != null && ` / fused=${l.fusedResults}`}
|
|
623
|
+
{l.enrichedResults != null && ` / enriched=${l.enrichedResults}`}
|
|
624
|
+
{l.proof && ` / proof=${l.proof}`}
|
|
625
|
+
{l.seedsCrawled != null && ` / ${l.seedsCrawled} seeds`}
|
|
626
|
+
{l.durationMs != null && ` / ${l.durationMs}ms`}
|
|
627
|
+
</span>
|
|
628
|
+
<span className="ml-auto flex items-center gap-1.5 text-[10px]">
|
|
629
|
+
{l.realUrls.length > 0 && <span className="rounded bg-emerald-100 px-1.5 py-0.5 font-bold text-emerald-800">{l.realUrls.length} url</span>}
|
|
630
|
+
{l.placeholderUrls.length > 0 && <span className="rounded bg-red-100 px-1.5 py-0.5 font-bold text-red-800">{l.placeholderUrls.length} fake</span>}
|
|
631
|
+
{l.failures.length > 0 && <span className="rounded bg-red-100 px-1.5 py-0.5 font-bold text-red-800">{l.failures.length} fail</span>}
|
|
632
|
+
<span className="text-zinc-400">{l.citations.length} cite · {l.rawLines.length} log</span>
|
|
633
|
+
<span className="text-zinc-400">{open ? "▾" : "▸"}</span>
|
|
634
|
+
</span>
|
|
635
|
+
</button>
|
|
636
|
+
{open && (
|
|
637
|
+
<div className="space-y-2 border-t border-zinc-100 px-3 py-2">
|
|
638
|
+
{l.query && <div className="text-[10px] text-zinc-600"><b>query:</b> {l.query}</div>}
|
|
639
|
+
{l.adapters.length > 0 && <div className="text-[10px] text-zinc-600"><b>adapters:</b> {l.adapters.join(", ")}</div>}
|
|
640
|
+
{l.citations.length > 0 && (
|
|
641
|
+
<div>
|
|
642
|
+
<div className="mb-0.5 text-[9px] font-bold uppercase text-zinc-500">Citations this lane admitted</div>
|
|
643
|
+
<div className="divide-y divide-zinc-100 rounded border border-zinc-200">
|
|
644
|
+
{l.citations.map((cst, j) => (
|
|
645
|
+
<div key={j} className="px-2 py-1">
|
|
646
|
+
<div className="truncate text-[10px] font-semibold text-zinc-800">{cst.title || "(untitled)"}</div>
|
|
647
|
+
{cst.url && (isPlaceholderUrl(cst.url)
|
|
648
|
+
? <code className="text-[10px] text-red-700">⚠ {cst.url} — not resolvable</code>
|
|
649
|
+
: <a href={cst.url} target="_blank" rel="noreferrer" className="block truncate font-mono text-[10px] text-sky-600 hover:underline">{cst.url}</a>)}
|
|
650
|
+
<div className="text-[9px] text-zinc-400">stage: {cst.stage}</div>
|
|
651
|
+
</div>
|
|
652
|
+
))}
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
)}
|
|
656
|
+
{l.failures.length > 0 && (
|
|
657
|
+
<div>
|
|
658
|
+
<div className="mb-0.5 text-[9px] font-bold uppercase text-red-600">Transport / retrieval failures ({l.failures.length})</div>
|
|
659
|
+
<div className="max-h-36 overflow-auto rounded bg-red-50 p-2 font-mono text-[10px] text-red-900">
|
|
660
|
+
{l.failures.map((f, j) => <div key={j}>{f}</div>)}
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
)}
|
|
664
|
+
<div>
|
|
665
|
+
<div className="mb-0.5 text-[9px] font-bold uppercase text-zinc-500">Raw emissions ({l.rawLines.length})</div>
|
|
666
|
+
<div className="max-h-48 overflow-auto rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-300">
|
|
667
|
+
{l.rawLines.map((r, j) => (
|
|
668
|
+
<div key={j}><span className="text-zinc-600">{ms(r.dt)}</span> {r.message}</div>
|
|
669
|
+
))}
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
</div>
|
|
673
|
+
)}
|
|
674
|
+
</div>
|
|
675
|
+
);
|
|
676
|
+
})}
|
|
677
|
+
</div>
|
|
678
|
+
|
|
679
|
+
{rep.unattributedCitations.length > 0 && (
|
|
680
|
+
<div className="rounded-lg border border-zinc-300 bg-zinc-50 p-3">
|
|
681
|
+
<div className="text-[10px] font-bold uppercase tracking-wide text-zinc-600">
|
|
682
|
+
{rep.unattributedCitations.length} citation(s) no lane window could claim
|
|
683
|
+
</div>
|
|
684
|
+
<p className="mt-1 text-[10px] text-zinc-600">Surfaced rather than hidden — usually admitted outside any lane's reported window.</p>
|
|
685
|
+
{rep.unattributedCitations.map((cst, i) => (
|
|
686
|
+
<div key={i} className="mt-1 text-[10px]">
|
|
687
|
+
<span className="text-zinc-700">{cst.title || "(untitled)"}</span>{" "}
|
|
688
|
+
<code className={isPlaceholderUrl(cst.url) ? "text-red-700" : "text-sky-700"}>{cst.url}</code>
|
|
689
|
+
</div>
|
|
690
|
+
))}
|
|
691
|
+
</div>
|
|
692
|
+
)}
|
|
693
|
+
|
|
694
|
+
<details className="rounded-lg border border-zinc-300 bg-white">
|
|
695
|
+
<summary className="cursor-pointer px-3 py-2 text-[11px] font-bold text-zinc-800">
|
|
696
|
+
Per-lane repair + verification roadmap ({SCRAPER_LANE_ROADMAP.length})
|
|
697
|
+
</summary>
|
|
698
|
+
<div className="divide-y divide-zinc-100 border-t border-zinc-100">
|
|
699
|
+
{SCRAPER_LANE_ROADMAP.map((r) => (
|
|
700
|
+
<div key={r.lane} className="p-3">
|
|
701
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
702
|
+
<Pill color={r.status === "hardened" ? "#059669" : r.status === "transport-limited" ? "#dc2626" : "#71717a"}>{r.lane}</Pill>
|
|
703
|
+
<span className="text-[10px] text-zinc-500">{r.role}</span>
|
|
704
|
+
<code className="ml-auto text-[9px] text-zinc-500">{r.workspaceFile}</code>
|
|
705
|
+
</div>
|
|
706
|
+
<div className="mt-1 text-[10px] text-red-800"><b>Observed:</b> {r.observedFailure}</div>
|
|
707
|
+
<div className="mt-1 text-[10px] text-emerald-800"><b>Intervention:</b> {r.intervention}</div>
|
|
708
|
+
<div className="mt-1 text-[10px] text-sky-800"><b>Falsification test:</b> {r.test}</div>
|
|
709
|
+
</div>
|
|
710
|
+
))}
|
|
711
|
+
</div>
|
|
712
|
+
</details>
|
|
713
|
+
</div>
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/* ── Repair-site card ─────────────────────────────────────────────────── */
|
|
718
|
+
|
|
719
|
+
function RepairSiteCard({ s }: { s: any }) {
|
|
720
|
+
const reachColor =
|
|
721
|
+
s.reachability === "workspace-seam" || s.reachability === "alias-seam" ? "#059669"
|
|
722
|
+
: s.reachability === "post-pass" ? "#0891b2" : "#dc2626";
|
|
723
|
+
return (
|
|
724
|
+
<div className="rounded border-2 bg-white p-2" style={{ borderColor: `${reachColor}66` }}>
|
|
725
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
726
|
+
<code className="rounded bg-zinc-900 px-1.5 py-0.5 text-[10px] font-bold text-emerald-300">
|
|
727
|
+
{s.file}:{s.line}
|
|
728
|
+
</code>
|
|
729
|
+
<Pill color={reachColor}>{s.reachability}</Pill>
|
|
730
|
+
<span className="text-[10px] text-zinc-500">{s.symbol}</span>
|
|
731
|
+
<span className="ml-auto rounded bg-amber-100 px-1.5 py-0.5 text-[9px] font-bold text-amber-800">+{s.expectedLift.toFixed(1)} lift</span>
|
|
732
|
+
</div>
|
|
733
|
+
<div className="mt-1.5">
|
|
734
|
+
<div className="text-[9px] font-bold uppercase text-zinc-500">Current code</div>
|
|
735
|
+
<pre className="overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-1.5 font-mono text-[10px] text-zinc-200">{s.currentCode}</pre>
|
|
736
|
+
</div>
|
|
737
|
+
<p className="mt-1.5 text-[10px] leading-snug text-zinc-700"><b>Mechanism:</b> {s.mechanism}</p>
|
|
738
|
+
<div className="mt-1.5 rounded bg-emerald-50 p-1.5">
|
|
739
|
+
<div className="text-[9px] font-bold uppercase text-emerald-700">Deterministic — {s.deterministic.action}</div>
|
|
740
|
+
<div className="text-[10px] text-emerald-900">{s.deterministic.change}</div>
|
|
741
|
+
<div className="mt-0.5 text-[10px] text-emerald-700"><b>Verify:</b> {s.deterministic.verify}</div>
|
|
742
|
+
<div className="text-[10px] text-emerald-700"><b>Fallback:</b> {s.deterministic.fallback}</div>
|
|
743
|
+
</div>
|
|
744
|
+
<div className="mt-1 rounded bg-violet-50 p-1.5">
|
|
745
|
+
<div className="text-[9px] font-bold uppercase text-violet-700">LLM — {s.llm.action}</div>
|
|
746
|
+
<div className="text-[10px] text-violet-900">{s.llm.change}</div>
|
|
747
|
+
</div>
|
|
748
|
+
<div className="mt-1 flex flex-wrap items-center gap-2">
|
|
749
|
+
<code className="rounded bg-zinc-100 px-1.5 py-0.5 text-[9px] text-zinc-600">{s.verifyCmd}</code>
|
|
750
|
+
<button onClick={() => copy(s.verifyCmd)} className="rounded bg-zinc-200 px-1.5 py-0.5 text-[9px] font-bold text-zinc-700">copy</button>
|
|
751
|
+
<span className="text-[9px] text-zinc-400">hook: {s.durableHook}</span>
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/* ── Tab: Diagnosis ★ ─────────────────────────────────────────────────── */
|
|
758
|
+
|
|
759
|
+
function DiagnosisTab({ run }: { run: RunRecord | null }) {
|
|
760
|
+
const [target, setTarget] = useState(9.0);
|
|
761
|
+
const [mode, setMode] = useState<"run" | "inputs">("run");
|
|
762
|
+
// Standalone-input mode fields
|
|
763
|
+
const [iPrompt, setIPrompt] = useState("");
|
|
764
|
+
const [iOutput, setIOutput] = useState("");
|
|
765
|
+
const [iTpl, setITpl] = useState("OMEGA-STRATEGY");
|
|
766
|
+
const [iStyle, setIStyle] = useState("");
|
|
767
|
+
const [iPersona, setIPersona] = useState("");
|
|
768
|
+
const [iDepth, setIDepth] = useState(4);
|
|
769
|
+
const [iRunJson, setIRunJson] = useState("");
|
|
770
|
+
const [inputsReport, setInputsReport] = useState<DiagnosisReport | null>(null);
|
|
771
|
+
|
|
772
|
+
const runReport: DiagnosisReport | null = useMemo(
|
|
773
|
+
() => (run ? diagnoseRun(run, target) : null),
|
|
774
|
+
[run?.id, run?.output, run?.finalText, target]
|
|
775
|
+
);
|
|
776
|
+
const report = mode === "inputs" ? inputsReport : runReport;
|
|
777
|
+
|
|
778
|
+
const rubric = useMemo<TemplateRubric | null>(() => {
|
|
779
|
+
if (!report) return null;
|
|
780
|
+
return loadTemplateRubric(report.templateId, report.styleOverride, report.williamsPersona);
|
|
781
|
+
}, [report?.templateId, report?.styleOverride, report?.williamsPersona]);
|
|
782
|
+
|
|
783
|
+
const runDiagnoseInputs = () => {
|
|
784
|
+
setInputsReport(
|
|
785
|
+
diagnoseFromInputs({
|
|
786
|
+
prompt: iPrompt,
|
|
787
|
+
output: iOutput,
|
|
788
|
+
settings: {
|
|
789
|
+
templateId: iTpl,
|
|
790
|
+
styleOverride: iStyle || null,
|
|
791
|
+
williamsPersona: iPersona || null,
|
|
792
|
+
maxDepth: iDepth,
|
|
793
|
+
},
|
|
794
|
+
pastedRunJson: iRunJson,
|
|
795
|
+
targetScore: target,
|
|
796
|
+
})
|
|
797
|
+
);
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
const prefillFromRun = () => {
|
|
801
|
+
if (!run) return;
|
|
802
|
+
setIPrompt(run.question || "");
|
|
803
|
+
setIOutput(run.finalText || "");
|
|
804
|
+
const s = ((run.output ?? {}) as any).runSettings ?? {};
|
|
805
|
+
if (s.templateId) setITpl(s.templateId);
|
|
806
|
+
if (s.styleOverride) setIStyle(s.styleOverride);
|
|
807
|
+
if (s.williamsPersona) setIPersona(s.williamsPersona);
|
|
808
|
+
setIRunJson(exportRun(run));
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
return (
|
|
812
|
+
<div className="space-y-3 overflow-auto">
|
|
813
|
+
<div className="rounded-lg border-2 border-violet-300 bg-violet-50 p-3">
|
|
814
|
+
<div className="mb-1 flex items-center gap-2">
|
|
815
|
+
<span className="text-[10px] font-bold uppercase tracking-wide text-violet-800">Pipeline Diagnosis</span>
|
|
816
|
+
<div className="ml-auto flex gap-1">
|
|
817
|
+
<button onClick={() => setMode("run")} className={`rounded px-2 py-0.5 text-[10px] font-bold ${mode === "run" ? "bg-violet-700 text-white" : "bg-white text-violet-700"}`}>From selected run</button>
|
|
818
|
+
<button onClick={() => setMode("inputs")} className={`rounded px-2 py-0.5 text-[10px] font-bold ${mode === "inputs" ? "bg-violet-700 text-white" : "bg-white text-violet-700"}`}>From prompt + settings + output</button>
|
|
819
|
+
</div>
|
|
820
|
+
</div>
|
|
821
|
+
<p className="text-[11px] leading-snug text-violet-900">
|
|
822
|
+
Diagnoses the <b>pipeline architecture</b>, not the prompt. Each defect resolves to verified{" "}
|
|
823
|
+
<code>file:line</code> coordinates with the current code, the causal mechanism, a deterministic route, an
|
|
824
|
+
LLM route, and a backup for each. Coordinates are machine-checkable — run{" "}
|
|
825
|
+
<code>node materialize.mjs --verify-sites</code> to detect drift.
|
|
826
|
+
</p>
|
|
827
|
+
</div>
|
|
828
|
+
|
|
829
|
+
{mode === "inputs" && (
|
|
830
|
+
<div className="space-y-2 rounded-lg border border-zinc-300 bg-white p-3">
|
|
831
|
+
<div className="flex items-center gap-2">
|
|
832
|
+
<span className="text-[10px] font-bold uppercase text-zinc-500">Inputs</span>
|
|
833
|
+
<button onClick={prefillFromRun} disabled={!run} className="rounded bg-zinc-100 px-2 py-0.5 text-[10px] font-bold text-zinc-700 disabled:opacity-40">Prefill from selected run</button>
|
|
834
|
+
<button onClick={runDiagnoseInputs} disabled={!iOutput.trim()} className="ml-auto rounded bg-violet-600 px-3 py-1 text-[11px] font-bold text-white disabled:opacity-40">Diagnose</button>
|
|
835
|
+
</div>
|
|
836
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
837
|
+
<div>
|
|
838
|
+
<label className="text-[9px] font-bold uppercase text-zinc-500">Prompt</label>
|
|
839
|
+
<textarea value={iPrompt} onChange={(e) => setIPrompt(e.target.value)} rows={5} className="w-full resize-y rounded border border-zinc-300 p-2 font-mono text-[11px]" />
|
|
840
|
+
</div>
|
|
841
|
+
<div>
|
|
842
|
+
<label className="text-[9px] font-bold uppercase text-zinc-500">Output ({iOutput.length.toLocaleString()} chars)</label>
|
|
843
|
+
<textarea value={iOutput} onChange={(e) => setIOutput(e.target.value)} rows={5} className="w-full resize-y rounded border border-zinc-300 p-2 font-mono text-[11px]" />
|
|
844
|
+
</div>
|
|
845
|
+
</div>
|
|
846
|
+
<div className="flex flex-wrap items-center gap-2 text-[10px]">
|
|
847
|
+
<label>template <select value={iTpl} onChange={(e) => setITpl(e.target.value)} className="rounded border border-zinc-300 px-1 py-0.5">{(OMEGA_TEMPLATES as any[]).map((t) => <option key={t.id} value={t.id}>{t.id}</option>)}</select></label>
|
|
848
|
+
<label>style <input value={iStyle} onChange={(e) => setIStyle(e.target.value)} placeholder="--bain-pe" className="w-28 rounded border border-zinc-300 px-1 py-0.5" /></label>
|
|
849
|
+
<label>williams <select value={iPersona} onChange={(e) => setIPersona(e.target.value)} className="rounded border border-zinc-300 px-1 py-0.5"><option value="">(none)</option>{(ARCHETYPES as readonly any[]).map((a) => <option key={a.name} value={a.name}>{a.name}</option>)}</select></label>
|
|
850
|
+
<label>maxDepth <input type="number" min={1} max={8} value={iDepth} onChange={(e) => setIDepth(Number(e.target.value))} className="w-14 rounded border border-zinc-300 px-1 py-0.5" /></label>
|
|
851
|
+
</div>
|
|
852
|
+
<details>
|
|
853
|
+
<summary className="cursor-pointer text-[10px] font-bold text-zinc-600">Optional: paste exported run JSON to unlock retrieval/telemetry detectors</summary>
|
|
854
|
+
<textarea value={iRunJson} onChange={(e) => setIRunJson(e.target.value)} rows={4} placeholder="paste the JSON from I/O → Export run JSON" className="mt-1 w-full resize-y rounded border border-zinc-300 p-2 font-mono text-[10px]" />
|
|
855
|
+
</details>
|
|
856
|
+
</div>
|
|
857
|
+
)}
|
|
858
|
+
|
|
859
|
+
{!report && <Empty>{mode === "inputs" ? "Enter an output and press Diagnose." : "No run selected."}</Empty>}
|
|
860
|
+
{report && (
|
|
861
|
+
<>
|
|
862
|
+
<div className="rounded-lg border border-zinc-200 bg-white p-3">
|
|
863
|
+
<div className="flex flex-wrap items-center gap-3 text-[11px]">
|
|
864
|
+
<span>Template: <b>{report.templateId}</b></span>
|
|
865
|
+
<span>Style: <b>{report.styleOverride ?? "none"}</b></span>
|
|
866
|
+
<span>Williams: <b>{report.williamsPersona ?? "none"}</b></span>
|
|
867
|
+
<span>Guard: <b>{report.finalScore ?? "n/a"}</b> → target</span>
|
|
868
|
+
<label className="flex items-center gap-1.5"><input type="number" step={0.5} min={5} max={10} value={target} onChange={(e) => setTarget(Number(e.target.value))} className="w-16 rounded border border-zinc-300 px-1 py-0.5" /></label>
|
|
869
|
+
<div className="ml-auto flex gap-1.5">
|
|
870
|
+
{run && mode === "run" && <button onClick={() => copy(bundleForExternalReview(report, run, rubric))} className="rounded bg-zinc-900 px-2 py-1 text-[10px] font-bold text-white">Copy review bundle</button>}
|
|
871
|
+
<button onClick={() => copy(exportRepairOrderFor(report, mode === "inputs" ? iPrompt : run?.question ?? ""))} className="rounded bg-emerald-700 px-2 py-1 text-[10px] font-bold text-white">Copy repair order JSON</button>
|
|
872
|
+
</div>
|
|
873
|
+
</div>
|
|
874
|
+
<div className="mt-2 flex flex-wrap gap-2 text-[10px]">
|
|
875
|
+
<span className="rounded bg-emerald-100 px-2 py-0.5 font-bold text-emerald-800">{report.reachabilitySplit.sidecarToday} fixable in src/ today</span>
|
|
876
|
+
<span className="rounded bg-cyan-100 px-2 py-0.5 font-bold text-cyan-800">{report.reachabilitySplit.postPass} via COVEA post-pass</span>
|
|
877
|
+
<span className="rounded bg-red-100 px-2 py-0.5 font-bold text-red-800">{report.reachabilitySplit.needsMaterialize} need materialize.mjs</span>
|
|
878
|
+
</div>
|
|
879
|
+
{report.repairFiles.length > 0 && (
|
|
880
|
+
<div className="mt-2">
|
|
881
|
+
<div className="text-[9px] font-bold uppercase text-zinc-500">Files to touch</div>
|
|
882
|
+
{report.repairFiles.map((f, i) => (
|
|
883
|
+
<div key={i} className="font-mono text-[10px] text-zinc-700">{f.count}× <b>{f.file}</b> <span className="text-zinc-400">({f.reach})</span></div>
|
|
884
|
+
))}
|
|
885
|
+
</div>
|
|
886
|
+
)}
|
|
887
|
+
</div>
|
|
888
|
+
|
|
889
|
+
{report.allRepairSites.length > 0 && (
|
|
890
|
+
<div className="space-y-2">
|
|
891
|
+
<div className="text-[10px] font-bold uppercase tracking-wide text-zinc-600">
|
|
892
|
+
Verified repair coordinates ({report.allRepairSites.length}) — highest lift first
|
|
893
|
+
</div>
|
|
894
|
+
{report.allRepairSites.map((s, i) => <RepairSiteCard key={i} s={s} />)}
|
|
895
|
+
</div>
|
|
896
|
+
)}
|
|
897
|
+
|
|
898
|
+
{report.diagnoses.length === 0 && <Empty>No pipeline defects detected by the active detector set.</Empty>}
|
|
899
|
+
|
|
900
|
+
{report.diagnoses.map((d, i) => (
|
|
901
|
+
<div key={i} className="rounded-lg border border-zinc-200 bg-white">
|
|
902
|
+
<div className="flex items-center gap-2 border-b border-zinc-200 px-3 py-2">
|
|
903
|
+
<Pill color="#dc2626">{d.defect}</Pill>
|
|
904
|
+
<span className="font-mono text-[10px] text-zinc-500">→ step: <b className="text-zinc-800">{d.step}</b></span>
|
|
905
|
+
</div>
|
|
906
|
+
<div className="space-y-2 px-3 py-2">
|
|
907
|
+
{d.evidence.map((e, j) => <div key={j} className="rounded bg-zinc-50 px-2 py-1 font-mono text-[10px] text-zinc-700">{e}</div>)}
|
|
908
|
+
{d.advice.map((scope, k) => (
|
|
909
|
+
<div key={k} className="rounded border border-zinc-100 bg-zinc-50 p-2">
|
|
910
|
+
<div className="mb-1 text-[10px] font-bold uppercase tracking-wide text-zinc-500">Scope: {scope.scope}</div>
|
|
911
|
+
{scope.routes.map((r, m) => (
|
|
912
|
+
<div key={m} className="mt-1 rounded border border-white bg-white p-2">
|
|
913
|
+
<div className="mb-0.5 flex items-center gap-1.5">
|
|
914
|
+
<Pill color={r.kind === "deterministic" ? "#059669" : "#8b5cf6"}>{r.kind}</Pill>
|
|
915
|
+
<span className="text-[11px] font-bold text-zinc-800">{r.action}</span>
|
|
916
|
+
{r.applicableInSidecar && <span className="ml-auto rounded bg-emerald-100 px-1.5 py-0.5 text-[9px] font-bold text-emerald-800">sidecar-ready</span>}
|
|
917
|
+
</div>
|
|
918
|
+
<div className="text-[10px] text-zinc-700"><b>How:</b> {r.how}</div>
|
|
919
|
+
<div className="text-[10px] text-zinc-500"><b>Requires:</b> {r.requires.join(", ")}</div>
|
|
920
|
+
<div className="text-[10px] text-zinc-500"><b>Backup:</b> {r.backup}</div>
|
|
921
|
+
{r.hook && <div className="mt-1 font-mono text-[9px] text-zinc-400">hook: {r.hook}</div>}
|
|
922
|
+
</div>
|
|
923
|
+
))}
|
|
924
|
+
</div>
|
|
925
|
+
))}
|
|
926
|
+
</div>
|
|
927
|
+
</div>
|
|
928
|
+
))}
|
|
929
|
+
|
|
930
|
+
{report.playbook.length > 0 && (
|
|
931
|
+
<div className="rounded-lg border-2 border-emerald-300 bg-emerald-50 p-3">
|
|
932
|
+
<div className="mb-2 text-[10px] font-bold uppercase tracking-wide text-emerald-800">Prioritised playbook (apply top→bottom)</div>
|
|
933
|
+
<ol className="space-y-1">
|
|
934
|
+
{report.playbook.map((p, i) => (
|
|
935
|
+
<li key={i} className="text-[11px] text-emerald-900">
|
|
936
|
+
<span className="mr-1 font-mono">{p.rank}.</span>
|
|
937
|
+
<Pill color={p.route.kind === "deterministic" ? "#059669" : "#8b5cf6"}>{p.route.kind}</Pill>{" "}
|
|
938
|
+
<b>{p.defect}</b> → <code>{p.step}</code>{" "}
|
|
939
|
+
<span className="text-emerald-700">(+{p.expectedLift.toFixed(2)} lift)</span>
|
|
940
|
+
<div className="ml-6 text-[10px] text-emerald-800">{p.route.action}</div>
|
|
941
|
+
</li>
|
|
942
|
+
))}
|
|
943
|
+
</ol>
|
|
944
|
+
</div>
|
|
945
|
+
)}
|
|
946
|
+
|
|
947
|
+
{report.unresolvedIssues.length > 0 && (
|
|
948
|
+
<div className="rounded-lg border border-amber-200 bg-amber-50 p-2 text-[10px] leading-snug text-amber-800">
|
|
949
|
+
<b>Unmeasured / unresolved:</b>
|
|
950
|
+
<ul className="ml-4 list-disc">{report.unresolvedIssues.map((u, i) => <li key={i}>{u}</li>)}</ul>
|
|
951
|
+
</div>
|
|
952
|
+
)}
|
|
953
|
+
</>
|
|
954
|
+
)}
|
|
955
|
+
</div>
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/* ── Tab: Genome + Templates ★ ────────────────────────────────────────── */
|
|
960
|
+
|
|
961
|
+
function GenomeTemplateTab({ run }: { run: RunRecord | null }) {
|
|
962
|
+
const [tplId, setTplId] = useState<string>(() => (OMEGA_TEMPLATES as any[])[0]?.id ?? "OMEGA-STRATEGY");
|
|
963
|
+
const [williams, setWilliams] = useState<string>((ARCHETYPES as readonly any[])[0]?.name ?? "");
|
|
964
|
+
const rubric = useMemo(() => loadTemplateRubric(tplId, null, williams || null), [tplId, williams]);
|
|
965
|
+
const live = readLiveGenome();
|
|
966
|
+
const runGenome = (run?.genome ?? null) as any;
|
|
967
|
+
const snapshotSample = useMemo(() => (run?.question ? computeGenomeSnapshot(run.question) : null), [run?.question]);
|
|
968
|
+
|
|
969
|
+
return (
|
|
970
|
+
<div className="space-y-3 overflow-auto">
|
|
971
|
+
<div className="rounded-lg border border-purple-200 bg-purple-50 p-3 text-[11px] leading-snug text-purple-900">
|
|
972
|
+
<b>Williams · Innovation Genome v1+v2 · Report Templates.</b> These are the CRITERIA a 10-rated report must
|
|
973
|
+
satisfy. The Diagnosis tab uses this rubric as the target.
|
|
974
|
+
</div>
|
|
975
|
+
|
|
976
|
+
<div className="flex flex-wrap items-center gap-2 rounded border border-zinc-200 bg-white p-2 text-[11px]">
|
|
977
|
+
<label>Template: <select value={tplId} onChange={(e) => setTplId(e.target.value)} className="rounded border border-zinc-300 px-1 py-0.5 text-[11px]">{(OMEGA_TEMPLATES as any[]).map((t) => <option key={t.id} value={t.id}>{t.id}</option>)}</select></label>
|
|
978
|
+
<label>Williams: <select value={williams} onChange={(e) => setWilliams(e.target.value)} className="rounded border border-zinc-300 px-1 py-0.5 text-[11px]">{(ARCHETYPES as readonly any[]).map((a) => <option key={a.name} value={a.name}>{a.name}</option>)}</select></label>
|
|
979
|
+
</div>
|
|
980
|
+
|
|
981
|
+
{rubric && (
|
|
982
|
+
<div className="rounded-lg border border-zinc-200 bg-white">
|
|
983
|
+
<div className="border-b border-zinc-200 px-3 py-1.5 text-[11px] font-bold text-zinc-800">Template rubric: <span className="text-violet-700">{rubric.templateId}</span> · Williams: <span className="text-emerald-700">{rubric.archetype}</span></div>
|
|
984
|
+
<div className="p-3 text-[10px] leading-snug text-zinc-700">
|
|
985
|
+
<div className="mb-1"><b>Persona directive (verbatim, no truncation):</b></div>
|
|
986
|
+
<pre className="max-h-40 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{rubric.personaDirective}</pre>
|
|
987
|
+
<div className="mt-2 mb-1"><b>Template build prompt (verbatim, no truncation):</b></div>
|
|
988
|
+
<pre className="max-h-56 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{rubric.buildPrompt}</pre>
|
|
989
|
+
<div className="mt-2 mb-1"><b>Ten-point contract for a 10-rated report:</b></div>
|
|
990
|
+
<ol className="ml-4 list-decimal space-y-0.5">
|
|
991
|
+
{rubric.tenPointContract.map((c, i) => <li key={i} className="text-[11px] text-zinc-800">{c}</li>)}
|
|
992
|
+
</ol>
|
|
993
|
+
<div className="mt-2 mb-1"><b>Required sections:</b></div>
|
|
994
|
+
<div className="grid gap-1 sm:grid-cols-2">
|
|
995
|
+
{rubric.sections.map((s) => (
|
|
996
|
+
<div key={s.id} className="rounded border border-zinc-200 bg-zinc-50 p-1.5">
|
|
997
|
+
<div className="text-[10px] font-bold text-zinc-800">{s.title}</div>
|
|
998
|
+
<div className="text-[10px] text-zinc-600">{s.purpose || "(no explicit hint)"}</div>
|
|
999
|
+
{s.requiredMarkers.length > 0 && <div className="mt-0.5 text-[9px] text-emerald-700">requires: {s.requiredMarkers.join(", ")}</div>}
|
|
1000
|
+
{s.antiMarkers.length > 0 && <div className="text-[9px] text-red-700">anti: {s.antiMarkers.join(", ")}</div>}
|
|
1001
|
+
</div>
|
|
1002
|
+
))}
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
</div>
|
|
1006
|
+
)}
|
|
1007
|
+
|
|
1008
|
+
<div className="rounded-lg border border-zinc-200 bg-white">
|
|
1009
|
+
<div className="border-b border-zinc-200 px-3 py-1.5 text-[11px] font-bold text-zinc-800">Innovation Genome — this run</div>
|
|
1010
|
+
<div className="p-3 text-[10px] leading-snug">
|
|
1011
|
+
{!runGenome && !snapshotSample && <div className="text-zinc-500">No genome captured for this run.</div>}
|
|
1012
|
+
{runGenome && (
|
|
1013
|
+
<>
|
|
1014
|
+
<div className="mb-1 font-bold text-purple-800">v1 directive (verbatim, no truncation):</div>
|
|
1015
|
+
<pre className="max-h-32 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{runGenome.directiveV1}</pre>
|
|
1016
|
+
<div className="mt-2 mb-1 font-bold text-purple-800">v2 directive (verbatim, no truncation):</div>
|
|
1017
|
+
<pre className="max-h-32 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{runGenome.directiveV2}</pre>
|
|
1018
|
+
<div className="mt-2 grid grid-cols-3 gap-2">
|
|
1019
|
+
<div><b className="text-zinc-500">domain:</b> {runGenome.domain}</div>
|
|
1020
|
+
<div><b className="text-zinc-500">seed:</b> {runGenome.seed}</div>
|
|
1021
|
+
<div><b className="text-zinc-500">v1 persona:</b> {runGenome.v1?.persona?.name ?? "n/a"}</div>
|
|
1022
|
+
</div>
|
|
1023
|
+
</>
|
|
1024
|
+
)}
|
|
1025
|
+
{!runGenome && snapshotSample && (
|
|
1026
|
+
<div>
|
|
1027
|
+
<div className="mb-1 text-zinc-500">Preview (not this run — computed for the question):</div>
|
|
1028
|
+
<pre className="max-h-32 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{snapshotSample.directiveV1}</pre>
|
|
1029
|
+
<pre className="mt-1 max-h-32 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{snapshotSample.directiveV2}</pre>
|
|
1030
|
+
</div>
|
|
1031
|
+
)}
|
|
1032
|
+
</div>
|
|
1033
|
+
</div>
|
|
1034
|
+
|
|
1035
|
+
{Boolean(live.v4 || live.v10Discovery) && (
|
|
1036
|
+
<div className="rounded-lg border border-zinc-200 bg-white p-3 text-[10px]">
|
|
1037
|
+
<div className="mb-1 font-bold text-zinc-700">Live genome (window._VERITAS_V*)</div>
|
|
1038
|
+
<pre className="max-h-40 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{JSON.stringify(live, null, 2).slice(0, 2000)}</pre>
|
|
1039
|
+
</div>
|
|
1040
|
+
)}
|
|
1041
|
+
</div>
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function ResearchTab({ run }: { run: RunRecord | null }) {
|
|
1046
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
1047
|
+
const r = (run.research ?? null) as any;
|
|
1048
|
+
if (!r) return <Empty>No separated prewriting research was attached to this run. New V15/baseline wrapper runs attach it before report generation.</Empty>;
|
|
1049
|
+
return (
|
|
1050
|
+
<div className="space-y-3 overflow-auto text-[10px]">
|
|
1051
|
+
<div className="rounded border border-cyan-300 bg-cyan-50 p-3 text-cyan-900">
|
|
1052
|
+
<b>Independent prewriting research.</b> Search strategy and evidence were completed before the report writer ran.
|
|
1053
|
+
Queries are hypotheses; only URL-backed sources are evidence.
|
|
1054
|
+
</div>
|
|
1055
|
+
<div className="grid grid-cols-4 gap-2">
|
|
1056
|
+
{[
|
|
1057
|
+
["status", r.status ?? "n/a"],
|
|
1058
|
+
["queries", r.queries?.length ?? 0],
|
|
1059
|
+
["sources", r.sources?.length ?? 0],
|
|
1060
|
+
["duration", r.endedAt && r.startedAt ? ms(r.endedAt - r.startedAt) : "n/a"],
|
|
1061
|
+
].map(([label, value]) => (
|
|
1062
|
+
<div key={String(label)} className="rounded border border-zinc-200 bg-white p-2 text-center">
|
|
1063
|
+
<div className="font-bold text-zinc-800">{String(value)}</div><div className="text-[9px] text-zinc-500">{label}</div>
|
|
1064
|
+
</div>
|
|
1065
|
+
))}
|
|
1066
|
+
</div>
|
|
1067
|
+
<div className="rounded border border-zinc-200 bg-white p-3">
|
|
1068
|
+
<div className="mb-1 font-bold text-zinc-700">Unified v1 + v2 expansion path (full words)</div>
|
|
1069
|
+
<pre className="max-h-56 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-zinc-200">{r.innovation?.directive ?? "(missing)"}</pre>
|
|
1070
|
+
</div>
|
|
1071
|
+
<div className="rounded border border-zinc-200 bg-white p-3">
|
|
1072
|
+
<div className="mb-1 font-bold text-zinc-700">Query register</div>
|
|
1073
|
+
{(r.queries ?? []).map((q: any) => (
|
|
1074
|
+
<div key={q.id} className="mb-1 rounded bg-zinc-50 p-2">
|
|
1075
|
+
<b>{q.id} [{q.kind}] [{q.pathNode}]</b> <code>{q.query}</code>
|
|
1076
|
+
<div className="text-zinc-500">{q.source} · {q.rationale}</div>
|
|
1077
|
+
</div>
|
|
1078
|
+
))}
|
|
1079
|
+
</div>
|
|
1080
|
+
<div className="rounded border border-zinc-200 bg-white p-3">
|
|
1081
|
+
<div className="mb-1 font-bold text-zinc-700">URL-backed sources</div>
|
|
1082
|
+
{(r.sources ?? []).length ? (r.sources ?? []).map((s: any, i: number) => (
|
|
1083
|
+
<div key={s.url ?? i} className="mb-1">
|
|
1084
|
+
<b>[R{i + 1}] {s.title}</b> · <a href={s.url} target="_blank" rel="noreferrer" className="text-sky-700 underline">{s.url}</a>
|
|
1085
|
+
</div>
|
|
1086
|
+
)) : <div className="text-red-700">EVIDENCE_STARVED — no source survived.</div>}
|
|
1087
|
+
</div>
|
|
1088
|
+
{r.rejectedQueries?.length > 0 && (
|
|
1089
|
+
<div className="rounded border border-amber-200 bg-amber-50 p-3">
|
|
1090
|
+
<b>Rejected LLM queries</b>
|
|
1091
|
+
<pre className="mt-1 max-h-40 overflow-auto whitespace-pre-wrap">{JSON.stringify(r.rejectedQueries, null, 2)}</pre>
|
|
1092
|
+
</div>
|
|
1093
|
+
)}
|
|
1094
|
+
<details className="rounded border border-zinc-200 bg-white">
|
|
1095
|
+
<summary className="cursor-pointer p-2 font-bold">Full dossier / execution receipt</summary>
|
|
1096
|
+
<pre className="max-h-96 overflow-auto whitespace-pre-wrap bg-zinc-950 p-2 text-zinc-200">{r.dossier}</pre>
|
|
1097
|
+
</details>
|
|
1098
|
+
</div>
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/* ── Tab: COVEA ★ ─────────────────────────────────────────────────────── */
|
|
1103
|
+
|
|
1104
|
+
function CoveaTab({ run }: { run: RunRecord | null }) {
|
|
1105
|
+
const covea = (run?.covea ?? null) as any;
|
|
1106
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
1107
|
+
if (!covea) return <Empty>No COVEA pass ran for this run. COVEA runs post-completion when the pipeline surfaces CoVe, adversarial, citation, or placeholder failures.</Empty>;
|
|
1108
|
+
return (
|
|
1109
|
+
<div className="space-y-3 overflow-auto">
|
|
1110
|
+
<div className="rounded-lg border border-violet-300 bg-violet-50 p-3 text-[11px] leading-snug text-violet-900">
|
|
1111
|
+
<b>COVEA</b> = deterministic containment + targeted CoVe/Adversarial repair. Never a full rewrite.
|
|
1112
|
+
Deterministic path strips invalid citation tags (including grouped tags), converts bare placeholders to
|
|
1113
|
+
explicit open items, and appends ledger-derived References without any model call. LLM path remains
|
|
1114
|
+
≤1 paragraph per patch, ≤20% total edit budget, ≥60% region retention.
|
|
1115
|
+
</div>
|
|
1116
|
+
<div className="grid grid-cols-4 gap-2 text-[11px]">
|
|
1117
|
+
<div className="rounded border border-emerald-200 bg-emerald-50 p-2"><div className="text-[9px] font-bold uppercase text-emerald-700">accepted</div><div className="text-xl font-black text-emerald-900">{covea.acceptedCount}</div></div>
|
|
1118
|
+
<div className="rounded border border-red-200 bg-red-50 p-2"><div className="text-[9px] font-bold uppercase text-red-700">rejected</div><div className="text-xl font-black text-red-900">{covea.rejectedCount}</div></div>
|
|
1119
|
+
<div className="rounded border border-zinc-200 bg-zinc-50 p-2"><div className="text-[9px] font-bold uppercase text-zinc-500">skipped</div><div className="text-xl font-black text-zinc-800">{covea.skippedCount}</div></div>
|
|
1120
|
+
<div className="rounded border border-sky-200 bg-sky-50 p-2"><div className="text-[9px] font-bold uppercase text-sky-700">edit %</div><div className="text-xl font-black text-sky-900">{(covea.charsChangedPct * 100).toFixed(1)}%</div></div>
|
|
1121
|
+
</div>
|
|
1122
|
+
{Array.isArray(covea.deterministicActions) && covea.deterministicActions.length > 0 && (
|
|
1123
|
+
<div className="rounded-lg border border-emerald-300 bg-emerald-50 p-3">
|
|
1124
|
+
<div className="text-[10px] font-bold uppercase tracking-wide text-emerald-800">
|
|
1125
|
+
Deterministic containment ({covea.deterministicActions.length})
|
|
1126
|
+
</div>
|
|
1127
|
+
<ul className="mt-1 ml-4 list-disc space-y-0.5 text-[10px] text-emerald-900">
|
|
1128
|
+
{covea.deterministicActions.map((a: string, i: number) => <li key={i}>{a}</li>)}
|
|
1129
|
+
</ul>
|
|
1130
|
+
</div>
|
|
1131
|
+
)}
|
|
1132
|
+
{Array.isArray(covea.patches) && covea.patches.map((p: any, i: number) => (
|
|
1133
|
+
<div key={i} className="rounded-lg border border-zinc-200 bg-white">
|
|
1134
|
+
<div className="flex items-center gap-2 border-b border-zinc-200 px-3 py-1.5">
|
|
1135
|
+
<Pill color={p.accepted ? "#059669" : "#dc2626"}>{p.accepted ? "accepted" : "rejected"}</Pill>
|
|
1136
|
+
<span className="font-mono text-[10px] text-zinc-500">{p.target?.kind} · {p.model} · {p.latencyMs}ms · attempts={p.attempts}</span>
|
|
1137
|
+
{!p.accepted && <span className="ml-auto font-mono text-[10px] text-red-700">{p.rejectReason}</span>}
|
|
1138
|
+
</div>
|
|
1139
|
+
<div className="grid gap-2 p-2 sm:grid-cols-2">
|
|
1140
|
+
<div>
|
|
1141
|
+
<div className="mb-0.5 text-[9px] font-bold uppercase text-zinc-500">before</div>
|
|
1142
|
+
<pre className="whitespace-pre-wrap rounded bg-red-50 p-2 text-[10px] text-red-900">{p.beforeText}</pre>
|
|
1143
|
+
</div>
|
|
1144
|
+
<div>
|
|
1145
|
+
<div className="mb-0.5 text-[9px] font-bold uppercase text-zinc-500">after</div>
|
|
1146
|
+
<pre className="whitespace-pre-wrap rounded bg-emerald-50 p-2 text-[10px] text-emerald-900">{p.afterText}</pre>
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>
|
|
1149
|
+
<div className="border-t border-zinc-100 bg-zinc-50 px-3 py-1 text-[10px] text-zinc-700"><b>reason:</b> {p.target?.reason}</div>
|
|
1150
|
+
</div>
|
|
1151
|
+
))}
|
|
1152
|
+
{Array.isArray(covea.errors) && covea.errors.length > 0 && (
|
|
1153
|
+
<div className="rounded border border-amber-200 bg-amber-50 p-2 text-[10px] text-amber-800">
|
|
1154
|
+
<b>Errors ({covea.errors.length}) — LLM route unavailable for these; deterministic annotation applied:</b>
|
|
1155
|
+
<ul className="ml-4 list-disc">{covea.errors.map((e: string, i: number) => <li key={i}>{e}</li>)}</ul>
|
|
1156
|
+
</div>
|
|
1157
|
+
)}
|
|
1158
|
+
</div>
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/* ── Tab: Architecture ★ — what is required for a repeatable 9+ ───────── */
|
|
1163
|
+
|
|
1164
|
+
function ArchitectureTab({ run }: { run: RunRecord | null }) {
|
|
1165
|
+
const p: Prescription | null = useMemo(() => (run ? prescribe(run) : null), [run?.id, run?.output, run?.finalText, run?.events.length]);
|
|
1166
|
+
if (!run) return <Empty>No run selected.</Empty>;
|
|
1167
|
+
if (!p) return <Empty>Run has no outcome yet.</Empty>;
|
|
1168
|
+
|
|
1169
|
+
const planeColor: Record<string, string> = {
|
|
1170
|
+
retrieval: "#0891b2", synthesis: "#7c3aed", repair: "#dc2626", evaluation: "#eab308",
|
|
1171
|
+
};
|
|
1172
|
+
const presenceColor: Record<string, string> = {
|
|
1173
|
+
present: "#059669", absent: "#dc2626", degraded: "#f59e0b", unmeasured: "#71717a",
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
return (
|
|
1177
|
+
<div className="space-y-3 overflow-auto">
|
|
1178
|
+
<div className="rounded-lg border-2 border-indigo-300 bg-indigo-50 p-3">
|
|
1179
|
+
<div className="mb-1 text-[10px] font-bold uppercase tracking-wide text-indigo-800">Architecture Prescription</div>
|
|
1180
|
+
<p className="text-[11px] leading-snug text-indigo-900">
|
|
1181
|
+
These are <b>preconditions</b>, not additive lifts. The achievable score is{" "}
|
|
1182
|
+
<code>MIN(ceilingWithout)</code> over the ABSENT components — if retrieval emits{" "}
|
|
1183
|
+
<code>source-3</code> as a URL, no downstream repair or judge can produce a 9.
|
|
1184
|
+
</p>
|
|
1185
|
+
<div className="mt-2 flex flex-wrap items-center gap-3 text-[11px]">
|
|
1186
|
+
<span>observed guard: <b>{p.observedGuard ?? "n/a"}</b></span>
|
|
1187
|
+
<span>judge: <b>{p.observedJudge ?? "unavailable"}</b></span>
|
|
1188
|
+
<span>floor: <b>{p.deterministicFloor ?? "n/a"}</b></span>
|
|
1189
|
+
<span className="rounded bg-indigo-700 px-2 py-0.5 font-bold text-white">projected ceiling {p.projectedCeiling.toFixed(1)}/10</span>
|
|
1190
|
+
<div className="ml-auto flex gap-1.5">
|
|
1191
|
+
<button onClick={() => copy(renderPrescription(p))} className="rounded bg-zinc-900 px-2 py-1 text-[10px] font-bold text-white">Copy prescription</button>
|
|
1192
|
+
<button onClick={() => download(`${run.id}-architecture.json`, exportPrescription(p))} className="rounded bg-indigo-700 px-2 py-1 text-[10px] font-bold text-white">Export JSON</button>
|
|
1193
|
+
</div>
|
|
1194
|
+
</div>
|
|
1195
|
+
{p.bindingConstraint && (
|
|
1196
|
+
<div className="mt-2 rounded border border-red-300 bg-red-50 p-2 text-[11px] text-red-900">
|
|
1197
|
+
<b>Binding constraint: {p.bindingConstraint.name}</b> ({p.bindingConstraint.plane}) — nothing downstream
|
|
1198
|
+
can raise the score past <b>{p.bindingConstraint.ceilingWithout.toFixed(1)}</b> while this is absent. Fix this first.
|
|
1199
|
+
</div>
|
|
1200
|
+
)}
|
|
1201
|
+
<div className="mt-2 flex flex-wrap gap-2 text-[10px]">
|
|
1202
|
+
<span className="rounded bg-emerald-100 px-2 py-0.5 font-bold text-emerald-800">{p.workspaceToday.length} fixable in src/ today</span>
|
|
1203
|
+
<span className="rounded bg-red-100 px-2 py-0.5 font-bold text-red-800">{p.needsPackageEdit.length} need package edit</span>
|
|
1204
|
+
<span className="rounded bg-zinc-100 px-2 py-0.5 font-bold text-zinc-600">{p.unmeasured.length} unmeasured</span>
|
|
1205
|
+
</div>
|
|
1206
|
+
</div>
|
|
1207
|
+
|
|
1208
|
+
{p.ordered.length === 0 && (
|
|
1209
|
+
<div className="rounded-lg border-2 border-emerald-300 bg-emerald-50 p-3 text-[11px] text-emerald-900">
|
|
1210
|
+
<b>No absent component detected.</b> The architecture required for 9+ is fully installed for this run.
|
|
1211
|
+
Any remaining score gap is content quality, not missing machinery.
|
|
1212
|
+
</div>
|
|
1213
|
+
)}
|
|
1214
|
+
|
|
1215
|
+
{p.ordered.map((a, i) => {
|
|
1216
|
+
const c = a.component;
|
|
1217
|
+
return (
|
|
1218
|
+
<div key={c.id} className="rounded-lg border-2 bg-white" style={{ borderColor: `${presenceColor[a.presence]}66` }}>
|
|
1219
|
+
<div className="flex flex-wrap items-center gap-2 border-b border-zinc-100 px-3 py-2">
|
|
1220
|
+
<span className="font-mono text-[10px] font-bold text-zinc-400">#{i + 1}</span>
|
|
1221
|
+
<Pill color={planeColor[c.plane] ?? "#71717a"}>{c.plane}</Pill>
|
|
1222
|
+
<span className="text-[12px] font-bold text-zinc-900">{c.name}</span>
|
|
1223
|
+
<Pill color={presenceColor[a.presence]}>{a.presence}</Pill>
|
|
1224
|
+
<span className="ml-auto rounded bg-amber-100 px-2 py-0.5 text-[10px] font-bold text-amber-800">
|
|
1225
|
+
ceiling {c.ceilingWithout.toFixed(1)} without
|
|
1226
|
+
</span>
|
|
1227
|
+
</div>
|
|
1228
|
+
<div className="space-y-1.5 px-3 py-2">
|
|
1229
|
+
<p className="text-[11px] text-zinc-700"><b>Guarantees:</b> {c.guarantees}</p>
|
|
1230
|
+
<div className="rounded bg-zinc-50 px-2 py-1 font-mono text-[10px] text-zinc-700"><b>Observed:</b> {a.evidence}</div>
|
|
1231
|
+
<div className="rounded border border-emerald-200 bg-emerald-50 p-2">
|
|
1232
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
1233
|
+
<code className="rounded bg-zinc-900 px-1.5 py-0.5 text-[10px] font-bold text-emerald-300">{c.patch.file}</code>
|
|
1234
|
+
{c.patch.workspaceReachable
|
|
1235
|
+
? <span className="rounded bg-emerald-200 px-1.5 py-0.5 text-[9px] font-bold text-emerald-900">src/ today</span>
|
|
1236
|
+
: <span className="rounded bg-red-200 px-1.5 py-0.5 text-[9px] font-bold text-red-900">needs materialize</span>}
|
|
1237
|
+
</div>
|
|
1238
|
+
<div className="mt-1 font-mono text-[10px] text-zinc-600">anchor: <code>{c.patch.anchor}</code></div>
|
|
1239
|
+
<div className="mt-1 text-[10px] text-emerald-900"><b>Change:</b> {c.patch.change}</div>
|
|
1240
|
+
<div className="mt-0.5 text-[10px] text-emerald-700"><b>Verify:</b> {c.patch.verify}</div>
|
|
1241
|
+
</div>
|
|
1242
|
+
</div>
|
|
1243
|
+
</div>
|
|
1244
|
+
);
|
|
1245
|
+
})}
|
|
1246
|
+
|
|
1247
|
+
{p.unmeasured.length > 0 && (
|
|
1248
|
+
<div className="rounded-lg border border-zinc-300 bg-zinc-50 p-3 text-[10px] leading-snug text-zinc-700">
|
|
1249
|
+
<b>Unmeasured on this run ({p.unmeasured.length}) — neither passing nor failing:</b>
|
|
1250
|
+
<ul className="ml-4 list-disc">
|
|
1251
|
+
{p.unmeasured.map((a) => <li key={a.component.id}><b>{a.component.name}</b>: {a.evidence}</li>)}
|
|
1252
|
+
</ul>
|
|
1253
|
+
</div>
|
|
1254
|
+
)}
|
|
1255
|
+
|
|
1256
|
+
<div className="rounded border border-amber-200 bg-amber-50 p-2 text-[10px] leading-snug text-amber-800">
|
|
1257
|
+
<b>Honesty:</b> <code>ceilingWithout</code> values are estimates calibrated against 4 observed runs
|
|
1258
|
+
(guard 4.4 / 4.88 / 6.25 / 8.1). They are NOT measured bounds. Detection is exact — it reads the run record.
|
|
1259
|
+
Never report a projected ceiling as an achieved score.
|
|
1260
|
+
</div>
|
|
1261
|
+
</div>
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/* ── Tab: Self-Test ★ — executable verification of the pure primitives ──── */
|
|
1266
|
+
|
|
1267
|
+
function SelfTestTab() {
|
|
1268
|
+
const [report, setReport] = useState<SelfTestReport | null>(null);
|
|
1269
|
+
const [busy, setBusy] = useState(false);
|
|
1270
|
+
const [err, setErr] = useState<string | null>(null);
|
|
1271
|
+
|
|
1272
|
+
const run = async () => {
|
|
1273
|
+
setBusy(true); setErr(null);
|
|
1274
|
+
try { setReport(await runSelfTests()); }
|
|
1275
|
+
catch (e) { setErr(e instanceof Error ? e.message : String(e)); }
|
|
1276
|
+
finally { setBusy(false); }
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
const bySuite = useMemo(() => {
|
|
1280
|
+
if (!report) return [];
|
|
1281
|
+
return report.suites.map((s) => ({ suite: s, results: report.results.filter((r) => r.suite === s) }));
|
|
1282
|
+
}, [report]);
|
|
1283
|
+
|
|
1284
|
+
return (
|
|
1285
|
+
<div className="space-y-3 overflow-auto">
|
|
1286
|
+
<div className="rounded-lg border border-teal-300 bg-teal-50 p-3 text-[11px] leading-snug text-teal-900">
|
|
1287
|
+
<b>Executable verification.</b> Every function under test is pure — no network, no model, no clock — so
|
|
1288
|
+
these are real assertions. The authoring model could only compile-verify this file; <b>clicking Run is what
|
|
1289
|
+
turns these into evidence.</b> A test that has never been run is a claim, not a verification.
|
|
1290
|
+
</div>
|
|
1291
|
+
|
|
1292
|
+
<div className="flex items-center gap-2">
|
|
1293
|
+
<button onClick={run} disabled={busy} className="rounded bg-teal-700 px-3 py-1.5 text-[11px] font-bold text-white disabled:opacity-40">
|
|
1294
|
+
{busy ? "Running…" : "Run self-tests"}
|
|
1295
|
+
</button>
|
|
1296
|
+
{report && (
|
|
1297
|
+
<>
|
|
1298
|
+
<span className="rounded bg-emerald-100 px-2 py-1 text-[11px] font-bold text-emerald-800">{report.passed} passed</span>
|
|
1299
|
+
<span className={`rounded px-2 py-1 text-[11px] font-bold ${report.failed > 0 ? "bg-red-100 text-red-800" : "bg-zinc-100 text-zinc-500"}`}>{report.failed} failed</span>
|
|
1300
|
+
<span className="font-mono text-[10px] text-zinc-500">{report.suites.length} suites · {new Date(report.ranAt).toLocaleTimeString()}</span>
|
|
1301
|
+
<button onClick={() => copy(JSON.stringify(report, null, 2))} className="ml-auto rounded bg-zinc-900 px-2 py-1 text-[10px] font-bold text-white">Copy report</button>
|
|
1302
|
+
</>
|
|
1303
|
+
)}
|
|
1304
|
+
</div>
|
|
1305
|
+
|
|
1306
|
+
{err && <div className="rounded-lg border border-red-200 bg-red-50 p-2 text-[11px] text-red-800">{err}</div>}
|
|
1307
|
+
{!report && !busy && <Empty>Not yet run. Click <b>Run self-tests</b> to produce real evidence.</Empty>}
|
|
1308
|
+
|
|
1309
|
+
{bySuite.map(({ suite, results }) => {
|
|
1310
|
+
const failed = results.filter((r) => !r.passed).length;
|
|
1311
|
+
return (
|
|
1312
|
+
<div key={suite} className="rounded-lg border bg-white" style={{ borderColor: failed > 0 ? "#fca5a5" : "#6ee7b7" }}>
|
|
1313
|
+
<div className="flex items-center gap-2 border-b border-zinc-100 px-3 py-1.5">
|
|
1314
|
+
<Pill color={failed > 0 ? "#dc2626" : "#059669"}>{suite}</Pill>
|
|
1315
|
+
<span className="font-mono text-[10px] text-zinc-500">{results.length - failed}/{results.length} passed</span>
|
|
1316
|
+
</div>
|
|
1317
|
+
<div className="divide-y divide-zinc-50">
|
|
1318
|
+
{results.map((r, i) => (
|
|
1319
|
+
<div key={i} className="flex gap-2 px-3 py-1">
|
|
1320
|
+
<span className={`shrink-0 font-mono text-[11px] font-bold ${r.passed ? "text-emerald-600" : "text-red-600"}`}>{r.passed ? "PASS" : "FAIL"}</span>
|
|
1321
|
+
<span className="text-[11px] text-zinc-800">{r.name}</span>
|
|
1322
|
+
{r.detail && <span className="ml-auto shrink-0 font-mono text-[9px] text-zinc-400">{r.detail.slice(0, 90)}</span>}
|
|
1323
|
+
</div>
|
|
1324
|
+
))}
|
|
1325
|
+
</div>
|
|
1326
|
+
</div>
|
|
1327
|
+
);
|
|
1328
|
+
})}
|
|
1329
|
+
</div>
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/* ── Tab: Scraper Debug 🔬 ────────────────────────────────────────────── */
|
|
1334
|
+
|
|
1335
|
+
const ALL_LANE_IDS: LaneId[] = [
|
|
1336
|
+
"helios", "structured-adapter", "canonical-portfolio", "arbiter", "sibyl", "strata", "nexus", "hydra", "native-vnext", "veritas-hybrid",
|
|
1337
|
+
];
|
|
1338
|
+
|
|
1339
|
+
function LaneBadge({ lane }: { lane: LaneRunRecord }) {
|
|
1340
|
+
const color = lane.status === "done" ? (lane.error ? "#dc2626" : "#059669")
|
|
1341
|
+
: lane.status === "running" ? "#f59e0b"
|
|
1342
|
+
: lane.status === "error" ? "#dc2626"
|
|
1343
|
+
: "#94a3b8";
|
|
1344
|
+
return (
|
|
1345
|
+
<span className="inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-bold" style={{ background: `${color}22`, color }}>
|
|
1346
|
+
{lane.laneId}
|
|
1347
|
+
{lane.status === "running" && <span className="animate-spin">⟳</span>}
|
|
1348
|
+
{lane.status === "done" && !lane.error && <span>✓ {lane.acceptedCount}↑ {lane.rejectedCount}↓</span>}
|
|
1349
|
+
{lane.status === "error" && <span>✗</span>}
|
|
1350
|
+
</span>
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function ItemRow({ item, idx }: { item: ReturnType<typeof useScraperItems>[number]; idx: number }) {
|
|
1355
|
+
const [open, setOpen] = useState(false);
|
|
1356
|
+
return (
|
|
1357
|
+
<div className={`rounded border ${item.accepted ? "border-emerald-200 bg-emerald-50" : "border-red-200 bg-red-50"}`}>
|
|
1358
|
+
<button onClick={() => setOpen(o => !o)} className="flex w-full items-start gap-2 p-2 text-left">
|
|
1359
|
+
<span className={`shrink-0 rounded px-1 py-0.5 text-[9px] font-bold ${item.accepted ? "bg-emerald-600 text-white" : "bg-red-600 text-white"}`}>
|
|
1360
|
+
{item.accepted ? "ACCEPT" : item.rejectReason ?? "REJECT"}
|
|
1361
|
+
</span>
|
|
1362
|
+
<div className="min-w-0">
|
|
1363
|
+
<div className="truncate text-[11px] font-semibold text-zinc-800">{idx + 1}. {item.title || "(untitled)"}</div>
|
|
1364
|
+
<div className="truncate font-mono text-[10px] text-sky-600">{item.absoluteUrl || item.url || "no-url"}</div>
|
|
1365
|
+
<div className="text-[10px] text-zinc-500">coherence={item.facetCoherence.toFixed(2)}</div>
|
|
1366
|
+
</div>
|
|
1367
|
+
<span className="ml-auto shrink-0 text-[10px] text-zinc-400">{open ? "▾" : "▸"}</span>
|
|
1368
|
+
</button>
|
|
1369
|
+
{open && (
|
|
1370
|
+
<div className="border-t border-zinc-200 p-2 space-y-1">
|
|
1371
|
+
<div className="text-[10px] text-zinc-700"><b>Snippet:</b> {item.snippet || "(empty)"}</div>
|
|
1372
|
+
<details>
|
|
1373
|
+
<summary className="cursor-pointer text-[10px] font-bold text-zinc-500">Raw JSON</summary>
|
|
1374
|
+
<pre className="max-h-48 overflow-auto rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200 mt-1">{JSON.stringify(item.rawJson, null, 2)}</pre>
|
|
1375
|
+
</details>
|
|
1376
|
+
</div>
|
|
1377
|
+
)}
|
|
1378
|
+
</div>
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function useScraperItems() { return [] as any[]; }
|
|
1383
|
+
|
|
1384
|
+
function ScraperDebugTab() {
|
|
1385
|
+
const [prompt, setPrompt] = useState("");
|
|
1386
|
+
const [enabledLanes, setEnabledLanes] = useState<LaneId[]>(["structured-adapter", "canonical-portfolio", "nexus", "hydra"]);
|
|
1387
|
+
const [parallel, setParallel] = useState(false);
|
|
1388
|
+
const [run, setRun] = useState<ScraperDebugRun | null>(null);
|
|
1389
|
+
const [running, setRunning] = useState(false);
|
|
1390
|
+
const [analysing, setAnalysing] = useState(false);
|
|
1391
|
+
const [selectedLane, setSelectedLane] = useState<LaneId | "combined" | null>(null);
|
|
1392
|
+
const [jsonView, setJsonView] = useState<"raw" | "items">("items");
|
|
1393
|
+
const apiKey = getKey();
|
|
1394
|
+
|
|
1395
|
+
const toggleLane = (id: LaneId) =>
|
|
1396
|
+
setEnabledLanes(prev => prev.includes(id) ? prev.filter(l => l !== id) : [...prev, id]);
|
|
1397
|
+
|
|
1398
|
+
const startRun = async () => {
|
|
1399
|
+
if (!prompt.trim() || running) return;
|
|
1400
|
+
setRun(null);
|
|
1401
|
+
setRunning(true);
|
|
1402
|
+
try {
|
|
1403
|
+
await runScraperDebug({
|
|
1404
|
+
prompt: prompt.trim(),
|
|
1405
|
+
lanes: enabledLanes,
|
|
1406
|
+
parallel,
|
|
1407
|
+
onProgress: (r) => setRun({ ...r }),
|
|
1408
|
+
});
|
|
1409
|
+
} catch (e) {
|
|
1410
|
+
console.error("ScraperDebug:", e);
|
|
1411
|
+
}
|
|
1412
|
+
setRunning(false);
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
const analyseWithGemini = async () => {
|
|
1416
|
+
if (!run || !apiKey || analysing) return;
|
|
1417
|
+
setAnalysing(true);
|
|
1418
|
+
const summary = [
|
|
1419
|
+
`PROMPT: ${run.cleanPrompt}`,
|
|
1420
|
+
`IFL FACETS: ${run.iflFacets.join(" | ")}`,
|
|
1421
|
+
`IFL QUERIES: ${run.iflQueries.join("\n")}`,
|
|
1422
|
+
``,
|
|
1423
|
+
`LANE RESULTS:`,
|
|
1424
|
+
...run.lanes.map(l =>
|
|
1425
|
+
`Lane [${l.laneId}]: status=${l.status} accepted=${l.acceptedCount} rejected=${l.rejectedCount} error=${l.error ?? "none"}\n` +
|
|
1426
|
+
` dispatched: ${l.inputQuery}\n` +
|
|
1427
|
+
` alternatives: ${l.iflAlternatives.join(" | ")}\n` +
|
|
1428
|
+
` gate log: ${l.workspaceGateLog.slice(0, 5).join(" · ")}\n` +
|
|
1429
|
+
` rejected reasons: ${[...new Set(l.items.filter(i => !i.accepted).map(i => i.rejectReason))].join(", ") || "none"}\n` +
|
|
1430
|
+
` accepted titles: ${l.items.filter(i => i.accepted).slice(0, 4).map(i => i.title).join(" | ") || "none"}`
|
|
1431
|
+
),
|
|
1432
|
+
``,
|
|
1433
|
+
`TOTAL ACCEPTED: ${run.combinedAccepted.length}`,
|
|
1434
|
+
`TOTAL REJECTED: ${run.combinedRejected.length}`,
|
|
1435
|
+
].join("\n");
|
|
1436
|
+
|
|
1437
|
+
const analysisPrompt = [
|
|
1438
|
+
"You are a scraper pipeline diagnostician. Below is a full debug dump of one retrieval run. Each lane dispatched specific queries, the IFL lattice shows the facets extracted from the prompt, and the results show what was accepted/rejected.",
|
|
1439
|
+
"",
|
|
1440
|
+
"Your task:",
|
|
1441
|
+
"1. Explain precisely WHY each lane returned few or zero accepted results. Be specific: which query strings were dispatched, and what about them caused off-topic or empty results?",
|
|
1442
|
+
"2. Identify the root-cause patterns (query too broad, query not domain-anchored, transport failure, relevance gate too strict, etc.).",
|
|
1443
|
+
"3. Suggest specific, concrete changes to the dispatched queries or IFL facets that would improve recall while keeping relevance high.",
|
|
1444
|
+
"4. Rate the overall retrieval health on a 1-10 scale and explain.",
|
|
1445
|
+
"",
|
|
1446
|
+
"DEBUG DUMP:",
|
|
1447
|
+
summary,
|
|
1448
|
+
].join("\n");
|
|
1449
|
+
|
|
1450
|
+
try {
|
|
1451
|
+
const res: any = await geminiGenerate({ apiKey, model: "gemini-2.0-flash-lite", prompt: analysisPrompt, maxOutputTokens: 1500 });
|
|
1452
|
+
const text = String(res?.text ?? res?.output ?? "(no output)");
|
|
1453
|
+
setRun(prev => prev ? { ...prev, analysis: text } : null);
|
|
1454
|
+
} catch (e) {
|
|
1455
|
+
setRun(prev => prev ? { ...prev, analysis: `Error: ${e instanceof Error ? e.message : String(e)}` } : null);
|
|
1456
|
+
}
|
|
1457
|
+
setAnalysing(false);
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1460
|
+
const displayLane = selectedLane && selectedLane !== "combined"
|
|
1461
|
+
? run?.lanes.find(l => l.laneId === selectedLane) ?? null
|
|
1462
|
+
: null;
|
|
1463
|
+
const displayItems = selectedLane === "combined"
|
|
1464
|
+
? [...(run?.combinedAccepted ?? []), ...(run?.combinedRejected ?? [])]
|
|
1465
|
+
: displayLane?.items ?? [];
|
|
1466
|
+
|
|
1467
|
+
return (
|
|
1468
|
+
<div className="flex h-full flex-col gap-2 overflow-hidden">
|
|
1469
|
+
{/* Config row */}
|
|
1470
|
+
<div className="rounded-lg border border-sky-300 bg-sky-50 p-3 text-[11px] leading-snug text-sky-900 shrink-0">
|
|
1471
|
+
<b>Scraper Debug 🔬</b> — Runs only the retrieval/grounding phase, fully isolated from model generation.
|
|
1472
|
+
Every query dispatched, every API endpoint called, every result and its accept/reject reason is recorded.
|
|
1473
|
+
Wire Gemini to get AI diagnosis of why lanes return irrelevant content.
|
|
1474
|
+
</div>
|
|
1475
|
+
<div className="flex flex-wrap items-end gap-2 shrink-0">
|
|
1476
|
+
<div className="flex-1 min-w-[260px]">
|
|
1477
|
+
<label className="block text-[10px] font-bold uppercase text-zinc-500 mb-0.5">Research prompt</label>
|
|
1478
|
+
<textarea
|
|
1479
|
+
value={prompt}
|
|
1480
|
+
onChange={e => setPrompt(e.target.value)}
|
|
1481
|
+
rows={2}
|
|
1482
|
+
className="w-full resize-y rounded border border-zinc-300 p-2 font-mono text-[11px]"
|
|
1483
|
+
placeholder="Find me a cannabis product that doesn't exist but solves a large unmet demand…"
|
|
1484
|
+
/>
|
|
1485
|
+
</div>
|
|
1486
|
+
<div>
|
|
1487
|
+
<label className="block text-[10px] font-bold uppercase text-zinc-500 mb-0.5">Lanes</label>
|
|
1488
|
+
<div className="flex flex-wrap gap-1">
|
|
1489
|
+
{ALL_LANE_IDS.map(id => (
|
|
1490
|
+
<button
|
|
1491
|
+
key={id}
|
|
1492
|
+
onClick={() => toggleLane(id)}
|
|
1493
|
+
className={`rounded px-2 py-0.5 text-[10px] font-bold ${enabledLanes.includes(id) ? "bg-sky-600 text-white" : "bg-zinc-200 text-zinc-500"}`}
|
|
1494
|
+
>{id}</button>
|
|
1495
|
+
))}
|
|
1496
|
+
</div>
|
|
1497
|
+
<label className="mt-1 flex items-center gap-1.5 text-[10px] text-zinc-600 cursor-pointer">
|
|
1498
|
+
<input type="checkbox" checked={parallel} onChange={e => setParallel(e.target.checked)} />
|
|
1499
|
+
Parallel (faster, mixed logs)
|
|
1500
|
+
</label>
|
|
1501
|
+
</div>
|
|
1502
|
+
<div className="flex flex-col gap-1">
|
|
1503
|
+
<button
|
|
1504
|
+
onClick={startRun}
|
|
1505
|
+
disabled={running || !prompt.trim()}
|
|
1506
|
+
className="rounded bg-sky-700 px-4 py-2 text-[11px] font-bold text-white disabled:opacity-40"
|
|
1507
|
+
>{running ? "Running…" : "Run Scraper Debug"}</button>
|
|
1508
|
+
{run && (
|
|
1509
|
+
<button
|
|
1510
|
+
onClick={analyseWithGemini}
|
|
1511
|
+
disabled={analysing || !apiKey}
|
|
1512
|
+
className="rounded bg-violet-700 px-4 py-2 text-[11px] font-bold text-white disabled:opacity-40"
|
|
1513
|
+
title={!apiKey ? "Set Gemini key in V15 panel" : ""}
|
|
1514
|
+
>{analysing ? "Analysing…" : "Gemini Analyse"}</button>
|
|
1515
|
+
)}
|
|
1516
|
+
</div>
|
|
1517
|
+
</div>
|
|
1518
|
+
|
|
1519
|
+
{run && (
|
|
1520
|
+
<div className="flex min-h-0 flex-1 gap-2 overflow-hidden">
|
|
1521
|
+
{/* Left: run overview */}
|
|
1522
|
+
<div className="w-56 shrink-0 flex flex-col gap-1 overflow-auto">
|
|
1523
|
+
<div className="rounded border border-zinc-200 bg-white p-2 text-[10px]">
|
|
1524
|
+
<div className="font-bold text-zinc-700">Run: <span className="font-mono">{run.runId}</span></div>
|
|
1525
|
+
<div className="text-zinc-500">{run.status} · {run.elapsedMs ? `${(run.elapsedMs / 1000).toFixed(1)}s` : "…"}</div>
|
|
1526
|
+
<div className="mt-1 text-emerald-700 font-bold">✓ {run.combinedAccepted.length} accepted</div>
|
|
1527
|
+
<div className="text-red-700 font-bold">✗ {run.combinedRejected.length} rejected</div>
|
|
1528
|
+
</div>
|
|
1529
|
+
<div className="rounded border border-zinc-200 bg-white p-2 text-[10px] space-y-0.5">
|
|
1530
|
+
<div className="font-bold text-zinc-500 uppercase">IFL facets</div>
|
|
1531
|
+
{run.iflFacets.map((f, i) => <div key={i} className="font-mono text-[10px] text-zinc-700">{f}</div>)}
|
|
1532
|
+
</div>
|
|
1533
|
+
<button
|
|
1534
|
+
onClick={() => setSelectedLane("combined")}
|
|
1535
|
+
className={`rounded border px-2 py-1 text-[10px] font-bold text-left ${selectedLane === "combined" ? "border-sky-400 bg-sky-50 text-sky-800" : "border-zinc-200 bg-white text-zinc-700"}`}
|
|
1536
|
+
>Combined ({run.combinedAccepted.length + run.combinedRejected.length})</button>
|
|
1537
|
+
{run.lanes.map(l => (
|
|
1538
|
+
<button
|
|
1539
|
+
key={l.laneId}
|
|
1540
|
+
onClick={() => setSelectedLane(l.laneId)}
|
|
1541
|
+
className={`rounded border px-2 py-1 text-[10px] text-left ${selectedLane === l.laneId ? "border-sky-400 bg-sky-50" : "border-zinc-200 bg-white"}`}
|
|
1542
|
+
>
|
|
1543
|
+
<LaneBadge lane={l} />
|
|
1544
|
+
</button>
|
|
1545
|
+
))}
|
|
1546
|
+
</div>
|
|
1547
|
+
|
|
1548
|
+
{/* Right: lane detail */}
|
|
1549
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2 overflow-auto">
|
|
1550
|
+
{run.analysis && (
|
|
1551
|
+
<div className="rounded-lg border border-violet-300 bg-violet-50 p-3 text-[11px] text-violet-900 shrink-0">
|
|
1552
|
+
<div className="font-bold mb-1">Gemini Diagnosis</div>
|
|
1553
|
+
<pre className="whitespace-pre-wrap font-sans text-[11px]">{run.analysis}</pre>
|
|
1554
|
+
</div>
|
|
1555
|
+
)}
|
|
1556
|
+
|
|
1557
|
+
{/* IFL queries panel */}
|
|
1558
|
+
<details className="shrink-0 rounded border border-zinc-200 bg-white">
|
|
1559
|
+
<summary className="cursor-pointer px-3 py-1.5 text-[11px] font-bold text-zinc-700">IFL per-section queries ({run.iflQueries.length})</summary>
|
|
1560
|
+
<div className="p-2 space-y-0.5">
|
|
1561
|
+
{run.iflQueries.map((q, i) => <div key={i} className="font-mono text-[10px] text-zinc-700">{q}</div>)}
|
|
1562
|
+
</div>
|
|
1563
|
+
<div className="border-t border-zinc-100 px-3 py-1.5">
|
|
1564
|
+
<div className="text-[10px] font-bold text-zinc-500 mb-0.5">Lattice directive (shown to model)</div>
|
|
1565
|
+
<pre className="max-h-40 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{run.latticeDirective}</pre>
|
|
1566
|
+
</div>
|
|
1567
|
+
</details>
|
|
1568
|
+
|
|
1569
|
+
{/* Lane detail */}
|
|
1570
|
+
{displayLane && (
|
|
1571
|
+
<div className="space-y-2 shrink-0">
|
|
1572
|
+
<div className="rounded border border-zinc-200 bg-white p-3">
|
|
1573
|
+
<div className="flex items-center gap-2 mb-2">
|
|
1574
|
+
<LaneBadge lane={displayLane} />
|
|
1575
|
+
<span className="text-[10px] text-zinc-500">{displayLane.elapsedMs != null ? `${(displayLane.elapsedMs / 1000).toFixed(2)}s` : "…"}</span>
|
|
1576
|
+
</div>
|
|
1577
|
+
<div className="grid grid-cols-1 gap-1 text-[10px]">
|
|
1578
|
+
<div><b>Dispatched query:</b> <code className="rounded bg-zinc-100 px-1">{displayLane.inputQuery}</code></div>
|
|
1579
|
+
{displayLane.iflAlternatives.length > 1 && (
|
|
1580
|
+
<div><b>Alternatives:</b> {displayLane.iflAlternatives.slice(1).map((q, i) => <code key={i} className="mr-1 rounded bg-zinc-100 px-1">{q}</code>)}</div>
|
|
1581
|
+
)}
|
|
1582
|
+
{displayLane.requestUrls.length > 0 && (
|
|
1583
|
+
<details>
|
|
1584
|
+
<summary className="cursor-pointer font-bold text-zinc-500">Request URLs ({displayLane.requestUrls.length})</summary>
|
|
1585
|
+
{displayLane.requestUrls.map((u, i) => (
|
|
1586
|
+
<div key={i} className="mt-0.5">
|
|
1587
|
+
<a href={u} target="_blank" rel="noreferrer" className="break-all font-mono text-[10px] text-sky-600 hover:underline">{u}</a>
|
|
1588
|
+
</div>
|
|
1589
|
+
))}
|
|
1590
|
+
</details>
|
|
1591
|
+
)}
|
|
1592
|
+
{displayLane.workspaceGateLog.length > 0 && (
|
|
1593
|
+
<details>
|
|
1594
|
+
<summary className="cursor-pointer font-bold text-zinc-500">Workspace gate log ({displayLane.workspaceGateLog.length})</summary>
|
|
1595
|
+
<div className="max-h-40 overflow-auto rounded bg-zinc-950 p-2 mt-1">
|
|
1596
|
+
{displayLane.workspaceGateLog.map((l, i) => <div key={i} className="font-mono text-[10px] text-zinc-300">{l}</div>)}
|
|
1597
|
+
</div>
|
|
1598
|
+
</details>
|
|
1599
|
+
)}
|
|
1600
|
+
{displayLane.error && <div className="rounded bg-red-50 border border-red-200 px-2 py-1 text-red-800"><b>Error:</b> {displayLane.error}</div>}
|
|
1601
|
+
</div>
|
|
1602
|
+
</div>
|
|
1603
|
+
<div className="flex items-center gap-2">
|
|
1604
|
+
<span className="text-[10px] font-bold text-zinc-500">View:</span>
|
|
1605
|
+
{(["items", "raw"] as const).map(v => (
|
|
1606
|
+
<button key={v} onClick={() => setJsonView(v)} className={`rounded px-2 py-0.5 text-[10px] font-bold ${jsonView === v ? "bg-zinc-900 text-white" : "bg-zinc-100 text-zinc-600"}`}>{v}</button>
|
|
1607
|
+
))}
|
|
1608
|
+
<button onClick={() => copy(JSON.stringify(displayLane.rawOutput, null, 2))} className="ml-auto rounded bg-zinc-100 px-2 py-0.5 text-[10px] font-bold text-zinc-700">Copy raw JSON</button>
|
|
1609
|
+
</div>
|
|
1610
|
+
{jsonView === "raw" ? (
|
|
1611
|
+
<pre className="max-h-96 overflow-auto rounded bg-zinc-950 p-2 font-mono text-[10px] text-zinc-200">{JSON.stringify(displayLane.rawOutput, null, 2)}</pre>
|
|
1612
|
+
) : (
|
|
1613
|
+
<div className="space-y-1">
|
|
1614
|
+
{displayItems.length === 0 && <Empty>No items returned by this lane.</Empty>}
|
|
1615
|
+
{displayItems.map((item, i) => <ItemRow key={i} item={item} idx={i} />)}
|
|
1616
|
+
</div>
|
|
1617
|
+
)}
|
|
1618
|
+
</div>
|
|
1619
|
+
)}
|
|
1620
|
+
|
|
1621
|
+
{selectedLane === "combined" && (
|
|
1622
|
+
<div className="space-y-1">
|
|
1623
|
+
<div className="flex items-center gap-2 text-[10px] font-bold text-zinc-600">
|
|
1624
|
+
<span className="text-emerald-700">✓ Accepted: {run.combinedAccepted.length}</span>
|
|
1625
|
+
<span className="text-red-700">✗ Rejected: {run.combinedRejected.length}</span>
|
|
1626
|
+
<button onClick={() => copy(JSON.stringify([...run.combinedAccepted, ...run.combinedRejected], null, 2))} className="ml-auto rounded bg-zinc-100 px-2 py-0.5 text-[10px] font-bold text-zinc-700">Copy all</button>
|
|
1627
|
+
</div>
|
|
1628
|
+
{displayItems.map((item, i) => <ItemRow key={i} item={item} idx={i} />)}
|
|
1629
|
+
</div>
|
|
1630
|
+
)}
|
|
1631
|
+
|
|
1632
|
+
{!selectedLane && (
|
|
1633
|
+
<Empty>Select a lane or "Combined" from the left panel to inspect its inputs and outputs.</Empty>
|
|
1634
|
+
)}
|
|
1635
|
+
</div>
|
|
1636
|
+
</div>
|
|
1637
|
+
)}
|
|
1638
|
+
|
|
1639
|
+
{!run && !running && (
|
|
1640
|
+
<Empty>Enter a prompt, select lanes, and click <b>Run Scraper Debug</b>. No model generation occurs — only retrieval.</Empty>
|
|
1641
|
+
)}
|
|
1642
|
+
</div>
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/* ── Tab: Forge (turn-2, demoted — kept as reference) ─────────────────── */
|
|
1647
|
+
|
|
1648
|
+
function ForgeTab({ run }: { run: RunRecord | null }) {
|
|
1649
|
+
const [prompt, setPrompt] = useState("");
|
|
1650
|
+
const [output, setOutput] = useState("");
|
|
1651
|
+
const [target, setTarget] = useState(9.0);
|
|
1652
|
+
const [det, setDet] = useState<ScoreReport | null>(null);
|
|
1653
|
+
const [llm, setLlm] = useState<ScoreReport | null>(null);
|
|
1654
|
+
const [llmExtra, setLlmExtra] = useState<{ gradient: string; patch: string; region: string | null } | null>(null);
|
|
1655
|
+
const [diag, setDiag] = useState<PromptDiagnosis | null>(null);
|
|
1656
|
+
const [traj, setTraj] = useState<TrajectoryPoint[]>(() => loadTrajectory());
|
|
1657
|
+
const [busy, setBusy] = useState(false);
|
|
1658
|
+
const [err, setErr] = useState<string | null>(null);
|
|
1659
|
+
|
|
1660
|
+
const pullFromRun = () => { if (!run) return; setPrompt(run.question || ""); setOutput(run.finalText || ""); };
|
|
1661
|
+
const analyze = () => {
|
|
1662
|
+
setErr(null);
|
|
1663
|
+
if (!output.trim()) { setErr("Paste an output."); return; }
|
|
1664
|
+
const r = scoreDeterministic(output, prompt);
|
|
1665
|
+
setDet(r); setDiag(diagnose(r, target));
|
|
1666
|
+
setTraj(recordTrajectory(`det · ${new Date().toLocaleTimeString()}`, r));
|
|
1667
|
+
};
|
|
1668
|
+
const judge = async () => {
|
|
1669
|
+
setErr(null);
|
|
1670
|
+
if (!output.trim()) { setErr("Paste an output."); return; }
|
|
1671
|
+
setBusy(true);
|
|
1672
|
+
try {
|
|
1673
|
+
const res = await judgeWithLLM(prompt, output);
|
|
1674
|
+
setLlm(res.report); setLlmExtra({ gradient: res.gradient, patch: res.patch, region: res.primaryRegion });
|
|
1675
|
+
setTraj(recordTrajectory(`judge · ${res.model}`, res.report));
|
|
1676
|
+
if (!det) { const d = scoreDeterministic(output, prompt); setDet(d); setDiag(diagnose(d, target)); }
|
|
1677
|
+
} catch (e) { setErr(e instanceof Error ? e.message : String(e)); } finally { setBusy(false); }
|
|
1678
|
+
};
|
|
1679
|
+
const divergence = det && llm ? Math.abs(det.overall - llm.overall) : null;
|
|
1680
|
+
|
|
1681
|
+
return (
|
|
1682
|
+
<div className="flex h-full flex-col gap-3 overflow-auto">
|
|
1683
|
+
<div className="rounded-lg border-2 border-amber-300 bg-amber-50 p-3 text-[11px] leading-snug text-amber-900">
|
|
1684
|
+
<b>⚠ Forge is a prompt-level scorer.</b> Per turn-3 clarification, the correct entry point for improving
|
|
1685
|
+
output is the <b>Diagnosis ★</b> tab, which proposes PIPELINE-architectural changes (not prompt tweaks).
|
|
1686
|
+
Forge is retained as a supporting rubric-scoring tool only.
|
|
1687
|
+
</div>
|
|
1688
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
1689
|
+
<div>
|
|
1690
|
+
<div className="mb-1 flex items-center justify-between"><label className="text-[10px] font-bold uppercase text-zinc-500">Prompt</label><button onClick={pullFromRun} disabled={!run} className="rounded bg-zinc-100 px-2 py-0.5 text-[10px] font-bold text-zinc-700 disabled:opacity-40">Pull from run</button></div>
|
|
1691
|
+
<textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} rows={6} className="w-full resize-y rounded-lg border border-zinc-300 p-2 font-mono text-[11px]" />
|
|
1692
|
+
</div>
|
|
1693
|
+
<div>
|
|
1694
|
+
<label className="mb-1 block text-[10px] font-bold uppercase text-zinc-500">Output ({output.length.toLocaleString()} chars)</label>
|
|
1695
|
+
<textarea value={output} onChange={(e) => setOutput(e.target.value)} rows={6} className="w-full resize-y rounded-lg border border-zinc-300 p-2 font-mono text-[11px]" />
|
|
1696
|
+
</div>
|
|
1697
|
+
</div>
|
|
1698
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
1699
|
+
<label className="flex items-center gap-1.5 text-[11px]">target<input type="number" step={0.5} min={5} max={10} value={target} onChange={(e) => setTarget(Number(e.target.value))} className="w-16 rounded border border-zinc-300 px-1 py-0.5" /></label>
|
|
1700
|
+
<button onClick={analyze} className="rounded bg-zinc-900 px-3 py-1.5 text-[11px] font-bold text-white">Score (deterministic)</button>
|
|
1701
|
+
<button onClick={judge} disabled={busy} className="rounded bg-violet-600 px-3 py-1.5 text-[11px] font-bold text-white disabled:opacity-40">{busy ? "Judging…" : "Score with LLM judge"}</button>
|
|
1702
|
+
<button onClick={() => { clearTrajectory(); setTraj([]); }} className="ml-auto rounded bg-zinc-100 px-2 py-1 text-[10px] font-bold text-zinc-600">Clear trajectory</button>
|
|
1703
|
+
</div>
|
|
1704
|
+
{err && <div className="rounded-lg border border-red-200 bg-red-50 p-2 text-[11px] text-red-800">{err}</div>}
|
|
1705
|
+
{det && (
|
|
1706
|
+
<div className="grid gap-2 sm:grid-cols-3">
|
|
1707
|
+
<div className="rounded-lg border border-zinc-200 bg-white p-3"><div className="text-[10px] font-bold uppercase text-zinc-500">Deterministic</div><div className="text-3xl font-black">{det.overall.toFixed(2)}</div><ScoreBar score={det.overall} /></div>
|
|
1708
|
+
<div className="rounded-lg border border-zinc-200 bg-white p-3"><div className="text-[10px] font-bold uppercase text-zinc-500">LLM judge</div><div className="text-3xl font-black">{llm ? llm.overall.toFixed(2) : "—"}</div>{llm && <ScoreBar score={llm.overall} />}</div>
|
|
1709
|
+
<div className="rounded-lg border p-3" style={divergence != null && divergence > 1.5 ? { borderColor: "#fca5a5", background: "#fef2f2" } : { borderColor: "#e4e4e7", background: "#fff" }}>
|
|
1710
|
+
<div className="text-[10px] font-bold uppercase text-zinc-500">Divergence</div><div className="text-3xl font-black">{divergence != null ? divergence.toFixed(2) : "—"}</div>
|
|
1711
|
+
<div className="mt-1 text-[10px] text-zinc-600">{divergence == null ? "run both scorers" : divergence > 1.5 ? "⚠ large — one scorer is wrong" : "scorers agree"}</div>
|
|
1712
|
+
</div>
|
|
1713
|
+
</div>
|
|
1714
|
+
)}
|
|
1715
|
+
{diag && (
|
|
1716
|
+
<div className="rounded-lg border border-emerald-300 bg-emerald-50 p-3">
|
|
1717
|
+
<div className="mb-2 text-[11px] font-black uppercase text-emerald-800">Prompt-level patch (deterministic route)</div>
|
|
1718
|
+
<pre className="max-h-56 overflow-auto whitespace-pre-wrap rounded bg-zinc-950 p-2 font-mono text-[10px] text-emerald-300">{diag.patchBlock}</pre>
|
|
1719
|
+
<div className="mt-2 flex flex-wrap gap-2">
|
|
1720
|
+
<button onClick={() => copy(diag.patchBlock)} className="rounded bg-emerald-700 px-2 py-0.5 text-[10px] font-bold text-white">Copy prompt patch</button>
|
|
1721
|
+
{llmExtra?.patch && <button onClick={() => copy(llmExtra.patch)} className="rounded bg-violet-700 px-2 py-0.5 text-[10px] font-bold text-white">Copy judge patch</button>}
|
|
1722
|
+
<button onClick={() => copy(buildExternalAuditBundle({ prompt, output, deterministic: det!, llm, diagnosis: diag }))} className="rounded bg-zinc-900 px-2 py-0.5 text-[10px] font-bold text-white">Copy external audit bundle</button>
|
|
1723
|
+
<button onClick={() => copy(buildOproMetaPrompt(traj, prompt, target))} className="rounded bg-sky-700 px-2 py-0.5 text-[10px] font-bold text-white">Copy OPRO meta-prompt</button>
|
|
1724
|
+
</div>
|
|
1725
|
+
</div>
|
|
1726
|
+
)}
|
|
1727
|
+
</div>
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
/* ── Shell (unchanged plumbing; new tabs wired in) ────────────────────── */
|
|
1732
|
+
|
|
1733
|
+
export function PipelineDebugConsole() {
|
|
1734
|
+
const [open, setOpen] = useState(false);
|
|
1735
|
+
const [tab, setTab] = useState<TabId>("timeline");
|
|
1736
|
+
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
1737
|
+
useTraceTick();
|
|
1738
|
+
|
|
1739
|
+
const runs = getRuns();
|
|
1740
|
+
const run = runs.find((r) => r.id === selectedId) ?? runs[0] ?? null;
|
|
1741
|
+
const liveCount = runs.filter((r) => r.status === "running").length;
|
|
1742
|
+
|
|
1743
|
+
useEffect(() => {
|
|
1744
|
+
const onKey = (e: KeyboardEvent) => {
|
|
1745
|
+
if (e.key === "D" && e.shiftKey && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((o) => !o); }
|
|
1746
|
+
};
|
|
1747
|
+
window.addEventListener("keydown", onKey);
|
|
1748
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1749
|
+
}, []);
|
|
1750
|
+
|
|
1751
|
+
if (typeof document === "undefined") return null;
|
|
1752
|
+
|
|
1753
|
+
const launcher = (
|
|
1754
|
+
<button onClick={() => setOpen(true)} title="Pipeline Debug Console (⌘/Ctrl+Shift+D)" className="fixed bottom-4 right-4 z-[9997] flex items-center gap-2 rounded-full bg-zinc-900 px-4 py-2.5 text-[11px] font-bold text-white shadow-2xl">
|
|
1755
|
+
<span className="relative flex h-2 w-2">
|
|
1756
|
+
{liveCount > 0 && <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />}
|
|
1757
|
+
<span className={`relative inline-flex h-2 w-2 rounded-full ${liveCount > 0 ? "bg-emerald-400" : "bg-zinc-500"}`} />
|
|
1758
|
+
</span>
|
|
1759
|
+
DEBUG {runs.length > 0 && <span className="rounded bg-white/15 px-1.5 py-0.5 font-mono text-[10px]">{runs.length}</span>}
|
|
1760
|
+
</button>
|
|
1761
|
+
);
|
|
1762
|
+
if (!open) return createPortal(launcher, document.body);
|
|
1763
|
+
|
|
1764
|
+
return createPortal(
|
|
1765
|
+
<div className="fixed inset-0 z-[9999] flex flex-col bg-zinc-100">
|
|
1766
|
+
<div className="flex shrink-0 items-center gap-3 border-b border-zinc-300 bg-white px-4 py-2">
|
|
1767
|
+
<div className="grid h-7 w-7 place-items-center rounded-lg bg-gradient-to-br from-zinc-900 to-zinc-600 text-[10px] font-black text-white">DBG</div>
|
|
1768
|
+
<div>
|
|
1769
|
+
<div className="text-[13px] font-bold leading-tight text-zinc-900">Pipeline Debug Console</div>
|
|
1770
|
+
<div className="text-[10px] leading-tight text-zinc-500">V15 rigor stack + production baseline · step attribution · COVEA repair · pipeline-architectural diagnosis</div>
|
|
1771
|
+
</div>
|
|
1772
|
+
<div className="ml-auto flex items-center gap-2">
|
|
1773
|
+
<button onClick={clearRuns} className="rounded bg-zinc-100 px-2 py-1 text-[10px] font-bold text-zinc-600">Clear runs</button>
|
|
1774
|
+
<button onClick={() => setOpen(false)} className="rounded bg-zinc-900 px-3 py-1.5 text-[11px] font-bold text-white">Close</button>
|
|
1775
|
+
</div>
|
|
1776
|
+
</div>
|
|
1777
|
+
<div className="flex min-h-0 flex-1">
|
|
1778
|
+
<div className="flex w-64 shrink-0 flex-col border-r border-zinc-300 bg-white">
|
|
1779
|
+
<div className="flex items-center justify-between border-b border-zinc-200 px-3 py-1.5">
|
|
1780
|
+
<span className="text-[10px] font-bold uppercase text-zinc-500">Runs</span>
|
|
1781
|
+
<PasteExternal onDone={(id) => setSelectedId(id)} />
|
|
1782
|
+
</div>
|
|
1783
|
+
<div className="min-h-0 flex-1 overflow-auto">
|
|
1784
|
+
{runs.length === 0 ? <div className="p-3 text-[10px] leading-snug text-zinc-500">No runs yet. Run a question or open <b>Probe</b>.</div> :
|
|
1785
|
+
runs.map((r) => (
|
|
1786
|
+
<button key={r.id} onClick={() => setSelectedId(r.id)} className={`block w-full border-b border-zinc-100 px-3 py-2 text-left hover:bg-zinc-50 ${run?.id === r.id ? "bg-indigo-50 ring-1 ring-inset ring-indigo-200" : ""}`}>
|
|
1787
|
+
<div className="flex items-center gap-1.5">
|
|
1788
|
+
<Pill color={r.mode === "v15" ? "#4f46e5" : r.mode === "baseline" ? "#525252" : "#0891b2"}>{r.mode}</Pill>
|
|
1789
|
+
<span className="font-mono text-[9px]" style={{ color: r.status === "running" ? "#16a34a" : r.status === "failed" ? "#dc2626" : "#71717a" }}>{r.status}</span>
|
|
1790
|
+
<span className="ml-auto font-mono text-[9px] text-zinc-400">{r.events.length}ev</span>
|
|
1791
|
+
</div>
|
|
1792
|
+
<div className="mt-0.5 line-clamp-2 text-[10px] leading-snug text-zinc-700">{r.question || "(no question)"}</div>
|
|
1793
|
+
<div className="mt-0.5 flex gap-2 font-mono text-[9px] text-zinc-400">
|
|
1794
|
+
<span>{new Date(r.startedAt).toLocaleTimeString()}</span>
|
|
1795
|
+
{r.endedAt && <span>{ms(r.endedAt - r.startedAt)}</span>}
|
|
1796
|
+
{r.guardScore != null && <span>g{r.guardScore.toFixed(1)}</span>}
|
|
1797
|
+
{r.covea != null && <span className="text-violet-600">covea</span>}
|
|
1798
|
+
{r.genome != null && <span className="text-purple-600">gen</span>}
|
|
1799
|
+
</div>
|
|
1800
|
+
</button>
|
|
1801
|
+
))}
|
|
1802
|
+
</div>
|
|
1803
|
+
</div>
|
|
1804
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
1805
|
+
<div className="flex shrink-0 gap-1 overflow-x-auto border-b border-zinc-300 bg-white px-3 pt-2">
|
|
1806
|
+
{TABS.map((t) => (
|
|
1807
|
+
<button key={t.id} onClick={() => setTab(t.id)} className={`shrink-0 rounded-t px-3 py-1.5 text-[11px] font-bold ${tab === t.id ? "bg-zinc-100 text-zinc-900 ring-1 ring-inset ring-zinc-300" : "text-zinc-500 hover:text-zinc-800"}`}>{t.label}</button>
|
|
1808
|
+
))}
|
|
1809
|
+
</div>
|
|
1810
|
+
<div className="min-h-0 flex-1 overflow-hidden p-3">
|
|
1811
|
+
{tab === "scraper-debug" && <ScraperDebugTab />}
|
|
1812
|
+
{tab === "timeline" && <TimelineTab run={run} />}
|
|
1813
|
+
{tab === "phases" && <PhasesTab run={run} />}
|
|
1814
|
+
{tab === "passes" && <PassesTab run={run} />}
|
|
1815
|
+
{tab === "sources" && <SourcesTab run={run} />}
|
|
1816
|
+
{tab === "scrapers" && <ScrapersTab run={run} />}
|
|
1817
|
+
{tab === "io" && <IOTab run={run} />}
|
|
1818
|
+
{tab === "probe" && <ProbeTab />}
|
|
1819
|
+
{tab === "attribution" && <AttributionTab run={run} />}
|
|
1820
|
+
{tab === "diagnosis" && <DiagnosisTab run={run} />}
|
|
1821
|
+
{tab === "genome" && <GenomeTemplateTab run={run} />}
|
|
1822
|
+
{tab === "research" && <ResearchTab run={run} />}
|
|
1823
|
+
{tab === "covea" && <CoveaTab run={run} />}
|
|
1824
|
+
{tab === "architecture" && <ArchitectureTab run={run} />}
|
|
1825
|
+
{tab === "selftest" && <SelfTestTab />}
|
|
1826
|
+
{tab === "forge" && <ForgeTab run={run} />}
|
|
1827
|
+
</div>
|
|
1828
|
+
</div>
|
|
1829
|
+
</div>
|
|
1830
|
+
</div>,
|
|
1831
|
+
document.body
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
function PasteExternal({ onDone }: { onDone: (id: string) => void }) {
|
|
1836
|
+
const [open, setOpen] = useState(false);
|
|
1837
|
+
const [q, setQ] = useState("");
|
|
1838
|
+
const [t, setT] = useState("");
|
|
1839
|
+
if (!open) return <button onClick={() => setOpen(true)} className="rounded bg-cyan-600 px-1.5 py-0.5 text-[9px] font-bold text-white">+ paste</button>;
|
|
1840
|
+
return (
|
|
1841
|
+
<div className="fixed inset-0 z-[10000] grid place-items-center bg-black/40 p-6" onClick={() => setOpen(false)}>
|
|
1842
|
+
<div className="w-full max-w-xl rounded-xl bg-white p-4 shadow-2xl" onClick={(e) => e.stopPropagation()}>
|
|
1843
|
+
<div className="mb-2 text-[12px] font-bold text-zinc-900">Register external output</div>
|
|
1844
|
+
<p className="mb-2 text-[10px] leading-snug text-zinc-500">Paste an output produced elsewhere. Diagnosis + Attribution still work; pipeline trace will be empty (that absence is shown honestly).</p>
|
|
1845
|
+
<input value={q} onChange={(e) => setQ(e.target.value)} placeholder="prompt / question" className="mb-2 w-full rounded border border-zinc-300 px-2 py-1 text-[11px]" />
|
|
1846
|
+
<textarea value={t} onChange={(e) => setT(e.target.value)} rows={8} placeholder="output text" className="w-full resize-y rounded border border-zinc-300 p-2 font-mono text-[11px]" />
|
|
1847
|
+
<div className="mt-2 flex justify-end gap-2">
|
|
1848
|
+
<button onClick={() => setOpen(false)} className="rounded bg-zinc-100 px-3 py-1.5 text-[11px] font-bold text-zinc-600">Cancel</button>
|
|
1849
|
+
<button onClick={() => { if (!t.trim()) return; onDone(registerExternalRun(q, t)); setOpen(false); setQ(""); setT(""); }} className="rounded bg-cyan-600 px-3 py-1.5 text-[11px] font-bold text-white">Register</button>
|
|
1850
|
+
</div>
|
|
1851
|
+
</div>
|
|
1852
|
+
</div>
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
export default PipelineDebugConsole;
|