@proflandrigan/shards 1.1.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/README.md +475 -0
- package/package.json +37 -0
- package/src/agents/academic.md +276 -0
- package/src/agents/ai-engineer.md +377 -0
- package/src/agents/analytics-engineer.md +364 -0
- package/src/agents/applied-ml-scientist.md +410 -0
- package/src/agents/backend-engineer.md +255 -0
- package/src/agents/bi-engineer.md +333 -0
- package/src/agents/data-analyst.md +343 -0
- package/src/agents/data-engineer.md +260 -0
- package/src/agents/data-modeller.md +386 -0
- package/src/agents/data-scientist.md +366 -0
- package/src/agents/deep-learning-engineer.md +389 -0
- package/src/agents/ml-engineer.md +424 -0
- package/src/agents/mlops-engineer.md +339 -0
- package/src/agents/researcher.md +187 -0
- package/src/agents/specific_instructions/academic/critical_review.md +263 -0
- package/src/agents/specific_instructions/academic/report.md +113 -0
- package/src/agents/specific_instructions/ai_engineer/advise.md +162 -0
- package/src/agents/specific_instructions/ai_engineer/bi_engineer_handoff.md +86 -0
- package/src/agents/specific_instructions/ai_engineer/experiment.md +471 -0
- package/src/agents/specific_instructions/ai_engineer/experiment_ui_mode.md +44 -0
- package/src/agents/specific_instructions/ai_engineer/phases/index.md +45 -0
- package/src/agents/specific_instructions/ai_engineer/phases/phase-1.md +55 -0
- package/src/agents/specific_instructions/ai_engineer/phases/phase-2.md +86 -0
- package/src/agents/specific_instructions/ai_engineer/phases/phase-3.md +96 -0
- package/src/agents/specific_instructions/ai_engineer/phases/phase-4.md +138 -0
- package/src/agents/specific_instructions/ai_engineer/phases/phase-5.md +157 -0
- package/src/agents/specific_instructions/ai_engineer/phases/phase-6.md +196 -0
- package/src/agents/specific_instructions/ai_engineer/phases/phase-7.md +313 -0
- package/src/agents/specific_instructions/ai_engineer/phases.md +1011 -0
- package/src/agents/specific_instructions/ai_engineer/prompt_lab.md +161 -0
- package/src/agents/specific_instructions/ai_engineer/prompt_lab_ui_mode.md +28 -0
- package/src/agents/specific_instructions/ai_engineer/research.md +393 -0
- package/src/agents/specific_instructions/ai_engineer/research_ui_mode.md +66 -0
- package/src/agents/specific_instructions/ai_engineer/review.md +159 -0
- package/src/agents/specific_instructions/ai_engineer/validation_checklist.md +182 -0
- package/src/agents/specific_instructions/analytics_engineer/advise.md +155 -0
- package/src/agents/specific_instructions/analytics_engineer/bi_engineer_handoff.md +91 -0
- package/src/agents/specific_instructions/analytics_engineer/data_analyst_handoff.md +84 -0
- package/src/agents/specific_instructions/analytics_engineer/deep_phases.md +818 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/index.md +24 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-1.md +77 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-2.md +106 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-3.md +93 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-4.md +79 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-5.md +61 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-6.md +45 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-7.md +235 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-8.md +221 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_quick/index.md +19 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_quick/phase-1.md +47 -0
- package/src/agents/specific_instructions/analytics_engineer/phases_quick/phase-2.md +78 -0
- package/src/agents/specific_instructions/analytics_engineer/quick_phases.md +112 -0
- package/src/agents/specific_instructions/analytics_engineer/review.md +167 -0
- package/src/agents/specific_instructions/analytics_engineer/service_mode.md +369 -0
- package/src/agents/specific_instructions/analytics_engineer/ui_mode.md +45 -0
- package/src/agents/specific_instructions/analytics_engineer/update.md +162 -0
- package/src/agents/specific_instructions/analytics_engineer/validation_checklist.md +121 -0
- package/src/agents/specific_instructions/applied_ml_scientist/advise.md +143 -0
- package/src/agents/specific_instructions/applied_ml_scientist/phases/index.md +21 -0
- package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-1.md +51 -0
- package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-2.md +66 -0
- package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-3.md +113 -0
- package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-4.md +104 -0
- package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-5.md +156 -0
- package/src/agents/specific_instructions/applied_ml_scientist/phases.md +428 -0
- package/src/agents/specific_instructions/applied_ml_scientist/research.md +379 -0
- package/src/agents/specific_instructions/applied_ml_scientist/review.md +142 -0
- package/src/agents/specific_instructions/applied_ml_scientist/validation_checklist.md +136 -0
- package/src/agents/specific_instructions/backend_engineer/clean.md +149 -0
- package/src/agents/specific_instructions/backend_engineer/review.md +91 -0
- package/src/agents/specific_instructions/backend_engineer/review_checklist.md +54 -0
- package/src/agents/specific_instructions/backend_engineer/service_mode.md +67 -0
- package/src/agents/specific_instructions/bi_engineer/advise.md +137 -0
- package/src/agents/specific_instructions/bi_engineer/data_analyst_handoff.md +77 -0
- package/src/agents/specific_instructions/bi_engineer/incoming_handoff.md +45 -0
- package/src/agents/specific_instructions/bi_engineer/phases/index.md +20 -0
- package/src/agents/specific_instructions/bi_engineer/phases/phase-1.md +164 -0
- package/src/agents/specific_instructions/bi_engineer/phases/phase-2.md +92 -0
- package/src/agents/specific_instructions/bi_engineer/phases/phase-3.md +121 -0
- package/src/agents/specific_instructions/bi_engineer/phases/phase-4.md +106 -0
- package/src/agents/specific_instructions/bi_engineer/phases.md +451 -0
- package/src/agents/specific_instructions/bi_engineer/review.md +166 -0
- package/src/agents/specific_instructions/bi_engineer/update.md +147 -0
- package/src/agents/specific_instructions/bi_engineer/validation_checklist.md +124 -0
- package/src/agents/specific_instructions/data_analyst/advise.md +138 -0
- package/src/agents/specific_instructions/data_analyst/explain.md +221 -0
- package/src/agents/specific_instructions/data_analyst/incoming_handoff.md +40 -0
- package/src/agents/specific_instructions/data_analyst/phases/index.md +20 -0
- package/src/agents/specific_instructions/data_analyst/phases/phase-1.md +159 -0
- package/src/agents/specific_instructions/data_analyst/phases/phase-2.md +112 -0
- package/src/agents/specific_instructions/data_analyst/phases/phase-3.md +265 -0
- package/src/agents/specific_instructions/data_analyst/phases/phase-4.md +100 -0
- package/src/agents/specific_instructions/data_analyst/phases.md +501 -0
- package/src/agents/specific_instructions/data_analyst/review.md +138 -0
- package/src/agents/specific_instructions/data_analyst/ui_mode.md +26 -0
- package/src/agents/specific_instructions/data_analyst/update.md +144 -0
- package/src/agents/specific_instructions/data_analyst/validation_checklist.md +95 -0
- package/src/agents/specific_instructions/data_engineer/advise.md +137 -0
- package/src/agents/specific_instructions/data_engineer/phases.md +466 -0
- package/src/agents/specific_instructions/data_engineer/phases_deep/index.md +23 -0
- package/src/agents/specific_instructions/data_engineer/phases_deep/phase-1.md +49 -0
- package/src/agents/specific_instructions/data_engineer/phases_deep/phase-2.md +93 -0
- package/src/agents/specific_instructions/data_engineer/phases_deep/phase-3.md +55 -0
- package/src/agents/specific_instructions/data_engineer/phases_deep/phase-4.md +48 -0
- package/src/agents/specific_instructions/data_engineer/phases_deep/phase-5.md +40 -0
- package/src/agents/specific_instructions/data_engineer/phases_deep/phase-6.md +102 -0
- package/src/agents/specific_instructions/data_engineer/phases_deep/phase-7.md +87 -0
- package/src/agents/specific_instructions/data_engineer/phases_quick/index.md +19 -0
- package/src/agents/specific_instructions/data_engineer/phases_quick/phase-1.md +45 -0
- package/src/agents/specific_instructions/data_engineer/phases_quick/phase-2.md +54 -0
- package/src/agents/specific_instructions/data_engineer/review.md +135 -0
- package/src/agents/specific_instructions/data_engineer/validation_checklist.md +136 -0
- package/src/agents/specific_instructions/data_modeller/advise.md +137 -0
- package/src/agents/specific_instructions/data_modeller/phases.md +581 -0
- package/src/agents/specific_instructions/data_modeller/phases_deep/index.md +23 -0
- package/src/agents/specific_instructions/data_modeller/phases_deep/phase-1.md +52 -0
- package/src/agents/specific_instructions/data_modeller/phases_deep/phase-2.md +113 -0
- package/src/agents/specific_instructions/data_modeller/phases_deep/phase-3.md +47 -0
- package/src/agents/specific_instructions/data_modeller/phases_deep/phase-4.md +51 -0
- package/src/agents/specific_instructions/data_modeller/phases_deep/phase-5.md +45 -0
- package/src/agents/specific_instructions/data_modeller/phases_deep/phase-6.md +105 -0
- package/src/agents/specific_instructions/data_modeller/phases_deep/phase-7.md +136 -0
- package/src/agents/specific_instructions/data_modeller/phases_quick/index.md +19 -0
- package/src/agents/specific_instructions/data_modeller/phases_quick/phase-1.md +47 -0
- package/src/agents/specific_instructions/data_modeller/phases_quick/phase-2.md +65 -0
- package/src/agents/specific_instructions/data_modeller/review.md +141 -0
- package/src/agents/specific_instructions/data_modeller/service_mode.md +218 -0
- package/src/agents/specific_instructions/data_modeller/validation_checklist.md +125 -0
- package/src/agents/specific_instructions/data_scientist/advise.md +158 -0
- package/src/agents/specific_instructions/data_scientist/bi_engineer_handoff.md +63 -0
- package/src/agents/specific_instructions/data_scientist/experiment.md +482 -0
- package/src/agents/specific_instructions/data_scientist/experiment_ui_mode.md +44 -0
- package/src/agents/specific_instructions/data_scientist/explain.md +247 -0
- package/src/agents/specific_instructions/data_scientist/greenfield_data.md +35 -0
- package/src/agents/specific_instructions/data_scientist/ml_engineer_handoff.md +52 -0
- package/src/agents/specific_instructions/data_scientist/notebook_walkthrough.md +76 -0
- package/src/agents/specific_instructions/data_scientist/phases/index.md +24 -0
- package/src/agents/specific_instructions/data_scientist/phases/phase-1.md +45 -0
- package/src/agents/specific_instructions/data_scientist/phases/phase-2.md +67 -0
- package/src/agents/specific_instructions/data_scientist/phases/phase-3.md +89 -0
- package/src/agents/specific_instructions/data_scientist/phases/phase-4.md +143 -0
- package/src/agents/specific_instructions/data_scientist/phases/phase-5.md +71 -0
- package/src/agents/specific_instructions/data_scientist/phases/phase-6.md +239 -0
- package/src/agents/specific_instructions/data_scientist/phases/phase-7.md +207 -0
- package/src/agents/specific_instructions/data_scientist/phases.md +651 -0
- package/src/agents/specific_instructions/data_scientist/research.md +345 -0
- package/src/agents/specific_instructions/data_scientist/research_ui_mode.md +52 -0
- package/src/agents/specific_instructions/data_scientist/review.md +136 -0
- package/src/agents/specific_instructions/data_scientist/service_mode.md +247 -0
- package/src/agents/specific_instructions/data_scientist/validation_checklist.md +183 -0
- package/src/agents/specific_instructions/deep_learning_engineer/advise.md +145 -0
- package/src/agents/specific_instructions/deep_learning_engineer/phases/index.md +21 -0
- package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-1.md +74 -0
- package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-2.md +98 -0
- package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-3.md +76 -0
- package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-4.md +128 -0
- package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-5.md +292 -0
- package/src/agents/specific_instructions/deep_learning_engineer/phases.md +567 -0
- package/src/agents/specific_instructions/deep_learning_engineer/research.md +389 -0
- package/src/agents/specific_instructions/deep_learning_engineer/review.md +155 -0
- package/src/agents/specific_instructions/deep_learning_engineer/validation_checklist.md +147 -0
- package/src/agents/specific_instructions/ml_engineer/advise.md +174 -0
- package/src/agents/specific_instructions/ml_engineer/bi_engineer_handoff.md +71 -0
- package/src/agents/specific_instructions/ml_engineer/experiment.md +474 -0
- package/src/agents/specific_instructions/ml_engineer/experiment_ui_mode.md +44 -0
- package/src/agents/specific_instructions/ml_engineer/notebook_walkthrough.md +75 -0
- package/src/agents/specific_instructions/ml_engineer/phases/index.md +25 -0
- package/src/agents/specific_instructions/ml_engineer/phases/phase-1.md +49 -0
- package/src/agents/specific_instructions/ml_engineer/phases/phase-2.md +75 -0
- package/src/agents/specific_instructions/ml_engineer/phases/phase-3.md +124 -0
- package/src/agents/specific_instructions/ml_engineer/phases/phase-4.md +279 -0
- package/src/agents/specific_instructions/ml_engineer/phases/phase-5.md +160 -0
- package/src/agents/specific_instructions/ml_engineer/phases/phase-6-5.md +170 -0
- package/src/agents/specific_instructions/ml_engineer/phases/phase-6.md +295 -0
- package/src/agents/specific_instructions/ml_engineer/phases/phase-7.md +337 -0
- package/src/agents/specific_instructions/ml_engineer/phases.md +1068 -0
- package/src/agents/specific_instructions/ml_engineer/research.md +437 -0
- package/src/agents/specific_instructions/ml_engineer/research_ui_mode.md +71 -0
- package/src/agents/specific_instructions/ml_engineer/review.md +187 -0
- package/src/agents/specific_instructions/ml_engineer/service_mode.md +273 -0
- package/src/agents/specific_instructions/ml_engineer/validation_checklist.md +185 -0
- package/src/agents/specific_instructions/mlops_engineer/advise.md +139 -0
- package/src/agents/specific_instructions/mlops_engineer/phases/index.md +23 -0
- package/src/agents/specific_instructions/mlops_engineer/phases/phase-1.md +52 -0
- package/src/agents/specific_instructions/mlops_engineer/phases/phase-2.md +86 -0
- package/src/agents/specific_instructions/mlops_engineer/phases/phase-3.md +105 -0
- package/src/agents/specific_instructions/mlops_engineer/phases/phase-4.md +128 -0
- package/src/agents/specific_instructions/mlops_engineer/phases/phase-5.md +106 -0
- package/src/agents/specific_instructions/mlops_engineer/phases/phase-6.md +128 -0
- package/src/agents/specific_instructions/mlops_engineer/phases/phase-7.md +144 -0
- package/src/agents/specific_instructions/mlops_engineer/phases.md +671 -0
- package/src/agents/specific_instructions/mlops_engineer/review.md +164 -0
- package/src/agents/specific_instructions/mlops_engineer/service_mode.md +81 -0
- package/src/agents/specific_instructions/mlops_engineer/validation_checklist.md +151 -0
- package/src/agents/specific_instructions/researcher/critical_review.md +292 -0
- package/src/agents/specific_instructions/researcher/review_checklist.md +67 -0
- package/src/agents/specific_instructions/researcher/service_mode.md +224 -0
- package/src/agents/specific_instructions/shared/auto_verify_mode.md +141 -0
- package/src/agents/specific_instructions/shared/autonomous_research.md +1289 -0
- package/src/agents/specific_instructions/shared/behavioral_rules.md +36 -0
- package/src/agents/specific_instructions/shared/diverge_protocol.md +387 -0
- package/src/agents/specific_instructions/shared/engineering_guidelines.md +136 -0
- package/src/agents/specific_instructions/shared/experiment_versioning.md +184 -0
- package/src/agents/specific_instructions/shared/goal_mode.md +187 -0
- package/src/agents/specific_instructions/shared/incremental_testing.md +139 -0
- package/src/agents/specific_instructions/shared/intent_discovery.md +223 -0
- package/src/agents/specific_instructions/shared/join_path_protocol.md +168 -0
- package/src/agents/specific_instructions/shared/knowledge_checkpoint.md +83 -0
- package/src/agents/specific_instructions/shared/knowledge_harvest.md +220 -0
- package/src/agents/specific_instructions/shared/knowledge_retrieval.md +100 -0
- package/src/agents/specific_instructions/shared/notebook_walkthrough_protocol.md +367 -0
- package/src/agents/specific_instructions/shared/reviewer_verdict_protocol.md +74 -0
- package/src/agents/specific_instructions/shared/swarm_protocol.md +97 -0
- package/src/agents/specific_instructions/shared/validation_protocol.md +139 -0
- package/src/agents/specific_instructions/syn/arbiter.md +140 -0
- package/src/agents/specific_instructions/syn/brainstorm.md +550 -0
- package/src/agents/specific_instructions/syn/code_review.md +232 -0
- package/src/agents/specific_instructions/syn/diff.md +239 -0
- package/src/agents/specific_instructions/syn/final_review.md +65 -0
- package/src/agents/specific_instructions/syn/fixer.md +240 -0
- package/src/agents/specific_instructions/syn/free_form.md +130 -0
- package/src/agents/specific_instructions/syn/knowledge.md +468 -0
- package/src/agents/specific_instructions/syn/notebook_walkthrough.md +78 -0
- package/src/agents/specific_instructions/syn/panel_review.md +634 -0
- package/src/agents/specific_instructions/syn/pm.md +453 -0
- package/src/agents/specific_instructions/syn/pr_review.md +255 -0
- package/src/agents/specific_instructions/syn/slides.md +417 -0
- package/src/agents/syn.md +729 -0
- package/src/commands/academic.md +41 -0
- package/src/commands/ai-engineer.md +45 -0
- package/src/commands/analytics-engineer.md +48 -0
- package/src/commands/applied-ml-scientist.md +45 -0
- package/src/commands/backend-engineer.md +35 -0
- package/src/commands/bi-engineer.md +40 -0
- package/src/commands/brainstorm.md +24 -0
- package/src/commands/data-analyst.md +38 -0
- package/src/commands/data-engineer.md +37 -0
- package/src/commands/data-modeller.md +38 -0
- package/src/commands/data-scientist.md +38 -0
- package/src/commands/deep-learning-engineer.md +47 -0
- package/src/commands/end.md +49 -0
- package/src/commands/knowledge.md +24 -0
- package/src/commands/ml-engineer.md +42 -0
- package/src/commands/mlops-engineer.md +47 -0
- package/src/commands/notebook-walkthrough.md +58 -0
- package/src/commands/researcher.md +40 -0
- package/src/commands/resume.md +57 -0
- package/src/commands/review-pr.md +26 -0
- package/src/commands/shards-guide.md +41 -0
- package/src/commands/shards-ui.md +32 -0
- package/src/commands/shards.md +41 -0
- package/src/docs/01-getting-started/concepts.md +109 -0
- package/src/docs/01-getting-started/first-session.md +79 -0
- package/src/docs/01-getting-started/install.md +61 -0
- package/src/docs/02-agents/academic.md +71 -0
- package/src/docs/02-agents/ai-engineer.md +78 -0
- package/src/docs/02-agents/analytics-engineer.md +58 -0
- package/src/docs/02-agents/applied-ml-scientist.md +59 -0
- package/src/docs/02-agents/backend-engineer.md +58 -0
- package/src/docs/02-agents/bi-engineer.md +65 -0
- package/src/docs/02-agents/data-analyst.md +67 -0
- package/src/docs/02-agents/data-engineer.md +57 -0
- package/src/docs/02-agents/data-modeller.md +51 -0
- package/src/docs/02-agents/data-scientist.md +78 -0
- package/src/docs/02-agents/deep-learning-engineer.md +64 -0
- package/src/docs/02-agents/ml-engineer.md +80 -0
- package/src/docs/02-agents/mlops-engineer.md +59 -0
- package/src/docs/02-agents/overview.md +62 -0
- package/src/docs/02-agents/researcher.md +73 -0
- package/src/docs/02-agents/syn.md +88 -0
- package/src/docs/03-protocols/auto-verify.md +82 -0
- package/src/docs/03-protocols/autonomous-research.md +59 -0
- package/src/docs/03-protocols/behavioral-rules.md +35 -0
- package/src/docs/03-protocols/diverge.md +50 -0
- package/src/docs/03-protocols/engineering-guidelines.md +56 -0
- package/src/docs/03-protocols/experiment-versioning.md +38 -0
- package/src/docs/03-protocols/gate-pattern.md +65 -0
- package/src/docs/03-protocols/incremental-testing.md +68 -0
- package/src/docs/03-protocols/join-path.md +46 -0
- package/src/docs/03-protocols/knowledge-ledger.md +70 -0
- package/src/docs/03-protocols/reviewer-verdicts.md +39 -0
- package/src/docs/03-protocols/swarm.md +40 -0
- package/src/docs/03-protocols/validation.md +174 -0
- package/src/docs/04-ui/activity-bar.md +70 -0
- package/src/docs/04-ui/chat-pane.md +80 -0
- package/src/docs/04-ui/code-intel.md +62 -0
- package/src/docs/04-ui/file-editing.md +61 -0
- package/src/docs/04-ui/git.md +54 -0
- package/src/docs/04-ui/keybindings.md +79 -0
- package/src/docs/04-ui/knowledge-map.md +76 -0
- package/src/docs/04-ui/overview.md +93 -0
- package/src/docs/04-ui/panels.md +49 -0
- package/src/docs/04-ui/pinboard-selection.md +66 -0
- package/src/docs/04-ui/quick-open-palette.md +56 -0
- package/src/docs/04-ui/sessions.md +81 -0
- package/src/docs/04-ui/settings-permissions.md +56 -0
- package/src/docs/05-commands/reference.md +59 -0
- package/src/docs/06-outputs/directory-map.md +116 -0
- package/src/docs/07-workflows/ai-eval-first.md +57 -0
- package/src/docs/07-workflows/deep-study-to-production.md +76 -0
- package/src/docs/07-workflows/diverge-exploration.md +77 -0
- package/src/docs/07-workflows/quick-analysis.md +45 -0
- package/src/docs/08-integrations/claude-code-auto-mode.md +191 -0
- package/src/docs/08-integrations/google-slides.md +175 -0
- package/src/docs/README.md +30 -0
- package/src/docs/manifest.json +108 -0
- package/src/templates/analysis-template.md +20 -0
- package/src/templates/branch-report.md +46 -0
- package/src/templates/diff-report.md +88 -0
- package/src/templates/knowledge-index.md +7 -0
- package/src/templates/model-card-schema.json +186 -0
- package/src/templates/model-card-schema.md +88 -0
- package/src/templates/model-card.md +124 -0
- package/src/templates/project-plan.md +47 -0
- package/src/templates/project-specs.md +81 -0
- package/src/templates/report-template.md +43 -0
- package/src/templates/study-template.md +25 -0
- package/src/ui/cc-readonly.js +181 -0
- package/src/ui/chat-session.js +466 -0
- package/src/ui/css/base.css +136 -0
- package/src/ui/css/brainstorm.css +525 -0
- package/src/ui/css/chat.css +1405 -0
- package/src/ui/css/editor.css +546 -0
- package/src/ui/css/eval-dashboard.css +157 -0
- package/src/ui/css/experiment.css +237 -0
- package/src/ui/css/guide.css +186 -0
- package/src/ui/css/knowledge-map.css +383 -0
- package/src/ui/css/layout.css +431 -0
- package/src/ui/css/model-card.css +161 -0
- package/src/ui/css/notebook-walkthrough.css +271 -0
- package/src/ui/css/pr-review.css +403 -0
- package/src/ui/css/prompt-lab.css +325 -0
- package/src/ui/css/sessions.css +258 -0
- package/src/ui/css/sidebar.css +661 -0
- package/src/ui/css/terminal.css +113 -0
- package/src/ui/css/theme-light.css +542 -0
- package/src/ui/index.html +389 -0
- package/src/ui/js/agents.js +32 -0
- package/src/ui/js/bookmarks.js +230 -0
- package/src/ui/js/chat.js +1776 -0
- package/src/ui/js/code-intel.js +328 -0
- package/src/ui/js/command-palette.js +142 -0
- package/src/ui/js/events.js +591 -0
- package/src/ui/js/explorer.js +317 -0
- package/src/ui/js/file-view.js +477 -0
- package/src/ui/js/git.js +536 -0
- package/src/ui/js/guide.js +198 -0
- package/src/ui/js/hud.js +75 -0
- package/src/ui/js/init.js +351 -0
- package/src/ui/js/knowledge-map.js +906 -0
- package/src/ui/js/markdown.js +114 -0
- package/src/ui/js/monaco.js +164 -0
- package/src/ui/js/notebook-walkthrough.js +272 -0
- package/src/ui/js/notebook.js +448 -0
- package/src/ui/js/panels.js +2681 -0
- package/src/ui/js/pinboard.js +186 -0
- package/src/ui/js/quick-open.js +164 -0
- package/src/ui/js/selection-context.js +131 -0
- package/src/ui/js/sessions.js +256 -0
- package/src/ui/js/settings.js +476 -0
- package/src/ui/js/split-view.js +82 -0
- package/src/ui/js/state.js +343 -0
- package/src/ui/js/table.js +161 -0
- package/src/ui/js/tabs.js +284 -0
- package/src/ui/js/tabular.js +125 -0
- package/src/ui/js/terminal.js +354 -0
- package/src/ui/js/timeline.js +137 -0
- package/src/ui/js/utils.js +293 -0
- package/src/ui/notebook-kernel.py +790 -0
- package/src/ui/open-browser.js +55 -0
- package/src/ui/permission-pattern.js +42 -0
- package/src/ui/relay.js +513 -0
- package/src/ui/server.js +3072 -0
- package/src/ui/session-index.js +225 -0
- package/src/ui/shards_icon.png +0 -0
- package/src/ui/spawn-server.js +41 -0
- package/src/ui/symbol-index.js +813 -0
- package/src/ui/ui-push.js +177 -0
- package/tools/gate-hook/VALIDATION_SPEC.md +273 -0
- package/tools/gate-hook/__tests__/auto-verify.test.js +343 -0
- package/tools/gate-hook/auto-allowlist.js +179 -0
- package/tools/gate-hook/auto-state.js +68 -0
- package/tools/gate-hook/classify.js +21 -0
- package/tools/gate-hook/log.js +57 -0
- package/tools/gate-hook/parser.js +205 -0
- package/tools/gate-hook/sql-guard.js +230 -0
- package/tools/gate-hook/state.js +170 -0
- package/tools/gate-hook/sweep.js +139 -0
- package/tools/gate-hook/transcript.js +45 -0
- package/tools/gate-hook/validation.js +321 -0
- package/tools/gate-hook.js +475 -0
- package/tools/install.js +914 -0
- package/tools/shards-gates.js +311 -0
- package/tools/shards-sessions.js +261 -0
- package/tools/shards-ui.js +377 -0
|
@@ -0,0 +1,2681 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// Agent-pushed panel tabs (data-viewer, dag, diagram, chart, etc.)
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
// ─── Theme re-render helper (called from init.js / settings.js) ──────────────
|
|
6
|
+
|
|
7
|
+
function rerenderActiveDiagramPanel() {
|
|
8
|
+
var activeKey = splitMode ? currentFileInPane : activeTabId;
|
|
9
|
+
if (!activeKey) return;
|
|
10
|
+
var p = openPanels[activeKey];
|
|
11
|
+
if (!p || (p.panel !== 'diagram' && p.panel !== 'dag')) return;
|
|
12
|
+
if (p._diagramState) p._diagramState.lastContent = null; // force Mermaid re-init with new theme
|
|
13
|
+
renderPanelPane(activeKey);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ─── Data normalization ────────────────────────────────────────────────────────
|
|
17
|
+
// Accepts any data shape the server or agent may send; returns { columns, data }
|
|
18
|
+
|
|
19
|
+
function normalizePanelData(raw) {
|
|
20
|
+
if (!raw) return { columns: [], data: [] };
|
|
21
|
+
// Already normalized by server (CSV/JSON file parse or prior normalization)
|
|
22
|
+
if (raw.columns && Array.isArray(raw.columns) && raw.data && Array.isArray(raw.data)) {
|
|
23
|
+
return raw;
|
|
24
|
+
}
|
|
25
|
+
// Inline JSON array of objects
|
|
26
|
+
if (Array.isArray(raw)) {
|
|
27
|
+
if (raw.length === 0) return { columns: [], data: [] };
|
|
28
|
+
if (typeof raw[0] === 'object' && raw[0] !== null) return flattenToTable(raw);
|
|
29
|
+
// Array of primitives — single column
|
|
30
|
+
return { columns: ['value'], data: raw.map(function(v) { return { value: v }; }) };
|
|
31
|
+
}
|
|
32
|
+
// Single object — wrap as one row
|
|
33
|
+
if (typeof raw === 'object') return flattenToTable([raw]);
|
|
34
|
+
return { columns: [], data: [] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Panel tab lifecycle ──────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
function openPanelTab(panelId, panelInfo) {
|
|
40
|
+
if (openPanels[panelId]) {
|
|
41
|
+
// Update existing panel
|
|
42
|
+
var p = openPanels[panelId];
|
|
43
|
+
if (panelInfo.title) p.title = panelInfo.title;
|
|
44
|
+
if (panelInfo.agent) p.agent = panelInfo.agent;
|
|
45
|
+
p.rawData = panelInfo.data !== undefined ? panelInfo.data : p.rawData;
|
|
46
|
+
renderWsTabs();
|
|
47
|
+
// Re-render if this panel is currently active
|
|
48
|
+
var activeKey = splitMode ? currentFileInPane : activeTabId;
|
|
49
|
+
if (activeKey === panelId) renderPanelPane(panelId);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
openPanels[panelId] = {
|
|
54
|
+
panelId: panelId,
|
|
55
|
+
panel: panelInfo.panel,
|
|
56
|
+
title: panelInfo.title || panelInfo.panel,
|
|
57
|
+
agent: panelInfo.agent || null,
|
|
58
|
+
rawData: panelInfo.data !== undefined ? panelInfo.data : null,
|
|
59
|
+
tabulatorInstance: null,
|
|
60
|
+
};
|
|
61
|
+
panelTabOrder.push(panelId);
|
|
62
|
+
|
|
63
|
+
// Switch to the new panel
|
|
64
|
+
if (splitMode) {
|
|
65
|
+
currentFileInPane = panelId;
|
|
66
|
+
renderWsTabs();
|
|
67
|
+
renderPanelPane(panelId);
|
|
68
|
+
} else {
|
|
69
|
+
activeTabId = panelId;
|
|
70
|
+
renderWsTabs();
|
|
71
|
+
showActiveContent();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function closePanelTab(panelId) {
|
|
76
|
+
var p = openPanels[panelId];
|
|
77
|
+
if (!p) return;
|
|
78
|
+
|
|
79
|
+
// Destroy tabulator instance if it exists
|
|
80
|
+
if (p.tabulatorInstance) {
|
|
81
|
+
if (activeTabulatorInstance === p.tabulatorInstance) {
|
|
82
|
+
activeTabulatorInstance = null;
|
|
83
|
+
activeTabularColumns = null;
|
|
84
|
+
}
|
|
85
|
+
try { p.tabulatorInstance.destroy(); } catch(e) {}
|
|
86
|
+
p.tabulatorInstance = null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
delete openPanels[panelId];
|
|
90
|
+
var ptIdx = panelTabOrder.indexOf(panelId);
|
|
91
|
+
if (ptIdx !== -1) panelTabOrder.splice(ptIdx, 1);
|
|
92
|
+
|
|
93
|
+
// Switch away if this panel was active
|
|
94
|
+
var activeKey = splitMode ? currentFileInPane : activeTabId;
|
|
95
|
+
if (activeKey === panelId) {
|
|
96
|
+
if (splitMode) {
|
|
97
|
+
currentFileInPane = fileTabOrder.length > 0 ? fileTabOrder[0] :
|
|
98
|
+
(panelTabOrder.length > 0 ? panelTabOrder[0] : null);
|
|
99
|
+
} else {
|
|
100
|
+
activeTabId = panelTabOrder.length > 0 ? panelTabOrder[0] :
|
|
101
|
+
(fileTabOrder.length > 0 ? fileTabOrder[0] : 'chat');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
renderWsTabs();
|
|
106
|
+
showActiveContent();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─── Live update (Story 2.3) ──────────────────────────────────────────────────
|
|
110
|
+
// Called on ui-panel-update SSE; re-renders data preserving sort state
|
|
111
|
+
|
|
112
|
+
function updatePanelData(panelId, newData) {
|
|
113
|
+
var p = openPanels[panelId];
|
|
114
|
+
if (!p) return;
|
|
115
|
+
var oldRawData = p.rawData;
|
|
116
|
+
p.rawData = newData;
|
|
117
|
+
|
|
118
|
+
// Only do a live update if this panel is currently visible
|
|
119
|
+
var activeKey = splitMode ? currentFileInPane : activeTabId;
|
|
120
|
+
if (activeKey !== panelId) return;
|
|
121
|
+
|
|
122
|
+
if (p.panel === 'diagram' || p.panel === 'dag') {
|
|
123
|
+
var oldContent = typeof oldRawData === 'string' ? oldRawData : JSON.stringify(oldRawData, null, 2);
|
|
124
|
+
var newContent = typeof newData === 'string' ? newData : JSON.stringify(newData, null, 2);
|
|
125
|
+
if (oldContent === newContent) return;
|
|
126
|
+
if (p._diagramState) {
|
|
127
|
+
p._diagramState.lastContent = null; // force re-fit on changed content
|
|
128
|
+
}
|
|
129
|
+
renderPanelPane(panelId);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (p.panel === 'experiment-dashboard') {
|
|
134
|
+
cleanupExperimentDashboard(p);
|
|
135
|
+
renderExperimentDashboard(document.getElementById('file-rendered-view'), p);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (p.panel === 'prompt-lab') {
|
|
140
|
+
cleanupPromptLab(p);
|
|
141
|
+
renderPromptLab(document.getElementById('file-rendered-view'), p);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (p.panel === 'eval-dashboard') {
|
|
146
|
+
cleanupEvalDashboard(p);
|
|
147
|
+
renderEvalDashboard(document.getElementById('file-rendered-view'), p);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (p.panel === 'model-card') {
|
|
152
|
+
cleanupModelCard(p);
|
|
153
|
+
renderModelCard(document.getElementById('file-rendered-view'), p);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (p.panel === 'knowledge-map') {
|
|
158
|
+
cleanupKnowledgeMap(p);
|
|
159
|
+
renderKnowledgeMapPanel(document.getElementById('file-rendered-view'), p);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (p.panel === 'guide') {
|
|
164
|
+
cleanupGuide(p);
|
|
165
|
+
renderGuidePanel(document.getElementById('file-rendered-view'), p);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (p.panel === 'pr-review') {
|
|
170
|
+
renderPRReviewPanel(document.getElementById('file-rendered-view'), p);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (p.panel === 'notebook-walkthrough') {
|
|
175
|
+
if (typeof cleanupNotebookWalkthrough === 'function') cleanupNotebookWalkthrough(p);
|
|
176
|
+
if (typeof renderNotebookWalkthrough === 'function') {
|
|
177
|
+
renderNotebookWalkthrough(document.getElementById('file-rendered-view'), p);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (p.panel === 'brainstorm') {
|
|
183
|
+
cleanupBrainstorm(p);
|
|
184
|
+
renderBrainstorm(document.getElementById('file-rendered-view'), p);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (p.panel === 'data-viewer' && p.tabulatorInstance) {
|
|
189
|
+
var sorters = [];
|
|
190
|
+
try { sorters = p.tabulatorInstance.getSorters() || []; } catch(e) {}
|
|
191
|
+
var tableData = normalizePanelData(newData);
|
|
192
|
+
p.tabulatorInstance.replaceData(tableData.data).then(function() {
|
|
193
|
+
if (sorters.length > 0) {
|
|
194
|
+
try { p.tabulatorInstance.setSort(sorters); } catch(e) {}
|
|
195
|
+
}
|
|
196
|
+
document.getElementById('table-row-count').textContent = tableData.data.length + ' rows';
|
|
197
|
+
}).catch(function() {});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ─── Panel rendering ──────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
function renderPanelPane(panelId) {
|
|
204
|
+
var p = openPanels[panelId];
|
|
205
|
+
if (!p) { renderEmptyFilePane(); return; }
|
|
206
|
+
|
|
207
|
+
// Set header, hide file-specific controls
|
|
208
|
+
document.getElementById('file-path-display').textContent = p.title;
|
|
209
|
+
document.getElementById('edit-btn').style.display = 'none';
|
|
210
|
+
document.getElementById('save-btn').style.display = 'none';
|
|
211
|
+
|
|
212
|
+
var renderedView = document.getElementById('file-rendered-view');
|
|
213
|
+
var editorEl = document.getElementById('file-editor');
|
|
214
|
+
var tableView = document.getElementById('table-view');
|
|
215
|
+
|
|
216
|
+
renderedView.classList.remove('visible');
|
|
217
|
+
renderedView.classList.remove('diagram-active');
|
|
218
|
+
renderedView.innerHTML = '';
|
|
219
|
+
editorEl.style.display = 'none';
|
|
220
|
+
disposeMonacoInstance();
|
|
221
|
+
|
|
222
|
+
if (p.panel === 'data-viewer') {
|
|
223
|
+
tableView.classList.add('visible');
|
|
224
|
+
document.getElementById('table-download-csv').style.display = 'inline-block';
|
|
225
|
+
|
|
226
|
+
// Always destroy stale instance — container DOM may have been overwritten
|
|
227
|
+
// by a file tab's initTabulator since we last rendered this panel
|
|
228
|
+
if (p.tabulatorInstance) {
|
|
229
|
+
if (activeTabulatorInstance === p.tabulatorInstance) {
|
|
230
|
+
activeTabulatorInstance = null;
|
|
231
|
+
activeTabularColumns = null;
|
|
232
|
+
}
|
|
233
|
+
try { p.tabulatorInstance.destroy(); } catch(e) {}
|
|
234
|
+
p.tabulatorInstance = null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var tableData = normalizePanelData(p.rawData);
|
|
238
|
+
initPanelTabulator(p, tableData);
|
|
239
|
+
} else if (p.panel === 'experiment-dashboard') {
|
|
240
|
+
tableView.classList.remove('visible');
|
|
241
|
+
renderedView.classList.add('visible');
|
|
242
|
+
renderExperimentDashboard(renderedView, p);
|
|
243
|
+
} else if (p.panel === 'prompt-lab') {
|
|
244
|
+
tableView.classList.remove('visible');
|
|
245
|
+
renderedView.classList.add('visible');
|
|
246
|
+
renderPromptLab(renderedView, p);
|
|
247
|
+
} else if (p.panel === 'eval-dashboard') {
|
|
248
|
+
tableView.classList.remove('visible');
|
|
249
|
+
renderedView.classList.add('visible');
|
|
250
|
+
renderEvalDashboard(renderedView, p);
|
|
251
|
+
} else if (p.panel === 'model-card') {
|
|
252
|
+
tableView.classList.remove('visible');
|
|
253
|
+
renderedView.classList.add('visible');
|
|
254
|
+
renderModelCard(renderedView, p);
|
|
255
|
+
} else if (p.panel === 'chart') {
|
|
256
|
+
tableView.classList.remove('visible');
|
|
257
|
+
renderedView.classList.add('visible');
|
|
258
|
+
renderChartPanel(renderedView, p);
|
|
259
|
+
} else if (p.panel === 'diagram' || p.panel === 'dag') {
|
|
260
|
+
tableView.classList.remove('visible');
|
|
261
|
+
renderedView.classList.add('visible');
|
|
262
|
+
renderDiagramPanel(renderedView, p);
|
|
263
|
+
} else if (p.panel === 'knowledge-map') {
|
|
264
|
+
tableView.classList.remove('visible');
|
|
265
|
+
renderedView.classList.add('visible');
|
|
266
|
+
renderKnowledgeMapPanel(renderedView, p);
|
|
267
|
+
} else if (p.panel === 'guide') {
|
|
268
|
+
tableView.classList.remove('visible');
|
|
269
|
+
renderedView.classList.add('visible');
|
|
270
|
+
renderGuidePanel(renderedView, p);
|
|
271
|
+
} else if (p.panel === 'pr-review') {
|
|
272
|
+
tableView.classList.remove('visible');
|
|
273
|
+
renderedView.classList.add('visible');
|
|
274
|
+
renderPRReviewPanel(renderedView, p);
|
|
275
|
+
} else if (p.panel === 'notebook-walkthrough') {
|
|
276
|
+
tableView.classList.remove('visible');
|
|
277
|
+
renderedView.classList.add('visible');
|
|
278
|
+
if (typeof renderNotebookWalkthrough === 'function') {
|
|
279
|
+
renderNotebookWalkthrough(renderedView, p);
|
|
280
|
+
} else {
|
|
281
|
+
renderedView.innerHTML = '<div class="no-file-msg">notebook-walkthrough renderer not loaded.</div>';
|
|
282
|
+
}
|
|
283
|
+
} else if (p.panel === 'brainstorm') {
|
|
284
|
+
tableView.classList.remove('visible');
|
|
285
|
+
renderedView.classList.add('visible');
|
|
286
|
+
renderBrainstorm(renderedView, p);
|
|
287
|
+
} else {
|
|
288
|
+
tableView.classList.remove('visible');
|
|
289
|
+
renderedView.classList.add('visible');
|
|
290
|
+
renderedView.innerHTML =
|
|
291
|
+
'<div class="no-file-msg">Panel type <strong>' + esc(p.panel) + '</strong> is not yet supported in this version.</div>';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ─── Chart Panel (Plotly.js) ──────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
function renderChartPanel(container, panel) {
|
|
298
|
+
var data = panel.rawData;
|
|
299
|
+
if (!data) {
|
|
300
|
+
container.innerHTML = '<div class="no-file-msg">No chart data provided.</div>';
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Ensure container has a div for Plotly
|
|
305
|
+
var chartId = 'chart-' + panel.panelId;
|
|
306
|
+
container.innerHTML = '<div id="' + chartId + '" style="width:100%;height:100%"></div>';
|
|
307
|
+
|
|
308
|
+
if (typeof Plotly === 'undefined') {
|
|
309
|
+
container.innerHTML = '<div class="no-file-msg">Plotly.js not loaded. Cannot render chart.</div>';
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Support both full Plotly spec { data, layout } and just the data array
|
|
314
|
+
var plotlyData = Array.isArray(data) ? data : (data.data || []);
|
|
315
|
+
var plotlyLayout = data.layout || {
|
|
316
|
+
title: panel.title,
|
|
317
|
+
paper_bgcolor: 'transparent',
|
|
318
|
+
plot_bgcolor: 'transparent',
|
|
319
|
+
font: { color: document.documentElement.getAttribute('data-theme') !== 'light' ? '#b0b0c8' : '#333' },
|
|
320
|
+
margin: { t: 40, r: 20, b: 40, l: 60 }
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
Plotly.newPlot(chartId, plotlyData, plotlyLayout, { responsive: true, displayModeBar: false });
|
|
325
|
+
} catch(e) {
|
|
326
|
+
container.innerHTML = '<div class="no-file-msg">Failed to render Plotly chart: ' + esc(e.message) + '</div>';
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ─── Diagram Panel (Mermaid.js) — pan / zoom ──────────────────────────────────
|
|
331
|
+
|
|
332
|
+
function renderDiagramPanel(container, panel) {
|
|
333
|
+
var data = panel.rawData;
|
|
334
|
+
if (!data) {
|
|
335
|
+
container.innerHTML = '<div class="no-file-msg">No diagram data provided.</div>';
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
var content = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
340
|
+
|
|
341
|
+
if (typeof mermaid === 'undefined') {
|
|
342
|
+
container.innerHTML = '<div class="no-file-msg">Mermaid.js not loaded. Cannot render diagram.</div>';
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Decide whether to restore viewport or fit-to-view
|
|
347
|
+
var isRestore = panel._diagramState && panel._diagramState.lastContent === content;
|
|
348
|
+
if (!panel._diagramState) {
|
|
349
|
+
panel._diagramState = { panX: 0, panY: 0, scale: 1, lastContent: content };
|
|
350
|
+
}
|
|
351
|
+
panel._diagramState.lastContent = content;
|
|
352
|
+
|
|
353
|
+
var diagramId = 'mermaid-' + panel.panelId;
|
|
354
|
+
|
|
355
|
+
// Build DOM: viewport > canvas > mermaid + controls
|
|
356
|
+
container.innerHTML = '';
|
|
357
|
+
container.classList.add('diagram-active');
|
|
358
|
+
|
|
359
|
+
var viewport = document.createElement('div');
|
|
360
|
+
viewport.className = 'diagram-viewport';
|
|
361
|
+
|
|
362
|
+
var canvas = document.createElement('div');
|
|
363
|
+
canvas.className = 'diagram-canvas';
|
|
364
|
+
|
|
365
|
+
var mermaidDiv = document.createElement('div');
|
|
366
|
+
mermaidDiv.className = 'mermaid';
|
|
367
|
+
mermaidDiv.id = diagramId;
|
|
368
|
+
mermaidDiv.textContent = content;
|
|
369
|
+
|
|
370
|
+
canvas.appendChild(mermaidDiv);
|
|
371
|
+
viewport.appendChild(canvas);
|
|
372
|
+
|
|
373
|
+
var controls = document.createElement('div');
|
|
374
|
+
controls.className = 'diagram-controls';
|
|
375
|
+
controls.innerHTML =
|
|
376
|
+
'<button class="diagram-ctrl-btn" title="Zoom in" data-action="zoom-in">+</button>' +
|
|
377
|
+
'<span class="diagram-zoom-label">100%</span>' +
|
|
378
|
+
'<button class="diagram-ctrl-btn" title="Zoom out" data-action="zoom-out">−</button>' +
|
|
379
|
+
'<button class="diagram-ctrl-btn" title="Fit to view" data-action="fit">▦</button>';
|
|
380
|
+
viewport.appendChild(controls);
|
|
381
|
+
container.appendChild(viewport);
|
|
382
|
+
|
|
383
|
+
mermaid.initialize({
|
|
384
|
+
startOnLoad: false,
|
|
385
|
+
theme: document.documentElement.getAttribute('data-theme') !== 'light' ? 'dark' : 'default',
|
|
386
|
+
securityLevel: 'loose',
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
var result = mermaid.run({ nodes: [document.getElementById(diagramId)] });
|
|
391
|
+
// mermaid.run returns a promise in v11+
|
|
392
|
+
if (result && typeof result.then === 'function') {
|
|
393
|
+
result.then(function() {
|
|
394
|
+
if (isRestore) {
|
|
395
|
+
applyDiagramTransform(canvas, panel._diagramState, controls);
|
|
396
|
+
} else {
|
|
397
|
+
fitDiagramToView(viewport, canvas, panel._diagramState, controls);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
} else {
|
|
401
|
+
// Fallback: run synchronously completed
|
|
402
|
+
if (isRestore) {
|
|
403
|
+
applyDiagramTransform(canvas, panel._diagramState, controls);
|
|
404
|
+
} else {
|
|
405
|
+
fitDiagramToView(viewport, canvas, panel._diagramState, controls);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} catch(e) {
|
|
409
|
+
container.innerHTML = '<div class="no-file-msg">Failed to render Mermaid diagram: ' + esc(e.message) + '</div>';
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
initDiagramPanZoom(viewport, canvas, panel._diagramState, controls);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function applyDiagramTransform(canvas, state, controls) {
|
|
417
|
+
canvas.style.transform = 'translate(' + state.panX + 'px, ' + state.panY + 'px) scale(' + state.scale + ')';
|
|
418
|
+
var label = controls.querySelector('.diagram-zoom-label');
|
|
419
|
+
if (label) label.textContent = Math.round(state.scale * 100) + '%';
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function fitDiagramToView(viewport, canvas, state, controls) {
|
|
423
|
+
canvas.style.transform = 'none';
|
|
424
|
+
var svg = canvas.querySelector('svg');
|
|
425
|
+
if (!svg) return;
|
|
426
|
+
|
|
427
|
+
var svgRect = svg.getBoundingClientRect();
|
|
428
|
+
var vpRect = viewport.getBoundingClientRect();
|
|
429
|
+
var pad = 40;
|
|
430
|
+
|
|
431
|
+
if (svgRect.width === 0 || svgRect.height === 0) return;
|
|
432
|
+
|
|
433
|
+
var scaleX = (vpRect.width - pad * 2) / svgRect.width;
|
|
434
|
+
var scaleY = (vpRect.height - pad * 2) / svgRect.height;
|
|
435
|
+
var scale = Math.min(scaleX, scaleY, 1.5);
|
|
436
|
+
scale = Math.max(scale, 0.1);
|
|
437
|
+
|
|
438
|
+
var scaledW = svgRect.width * scale;
|
|
439
|
+
var scaledH = svgRect.height * scale;
|
|
440
|
+
state.panX = (vpRect.width - scaledW) / 2;
|
|
441
|
+
state.panY = (vpRect.height - scaledH) / 2;
|
|
442
|
+
state.scale = scale;
|
|
443
|
+
|
|
444
|
+
applyDiagramTransform(canvas, state, controls);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function initDiagramPanZoom(viewport, canvas, state, controls) {
|
|
448
|
+
var MIN_SCALE = 0.1;
|
|
449
|
+
var MAX_SCALE = 5;
|
|
450
|
+
var isPanning = false;
|
|
451
|
+
var startX, startY, startPanX, startPanY;
|
|
452
|
+
|
|
453
|
+
viewport.addEventListener('pointerdown', function(e) {
|
|
454
|
+
if (e.button !== 0) return;
|
|
455
|
+
if (e.target.closest('.diagram-controls')) return;
|
|
456
|
+
isPanning = true;
|
|
457
|
+
startX = e.clientX;
|
|
458
|
+
startY = e.clientY;
|
|
459
|
+
startPanX = state.panX;
|
|
460
|
+
startPanY = state.panY;
|
|
461
|
+
viewport.style.cursor = 'grabbing';
|
|
462
|
+
viewport.setPointerCapture(e.pointerId);
|
|
463
|
+
e.preventDefault();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
viewport.addEventListener('pointermove', function(e) {
|
|
467
|
+
if (!isPanning) return;
|
|
468
|
+
state.panX = startPanX + (e.clientX - startX);
|
|
469
|
+
state.panY = startPanY + (e.clientY - startY);
|
|
470
|
+
applyDiagramTransform(canvas, state, controls);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
viewport.addEventListener('pointerup', function(e) {
|
|
474
|
+
if (!isPanning) return;
|
|
475
|
+
isPanning = false;
|
|
476
|
+
viewport.style.cursor = '';
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
viewport.addEventListener('pointercancel', function() {
|
|
480
|
+
isPanning = false;
|
|
481
|
+
viewport.style.cursor = '';
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
viewport.addEventListener('wheel', function(e) {
|
|
485
|
+
e.preventDefault();
|
|
486
|
+
var rect = viewport.getBoundingClientRect();
|
|
487
|
+
var mouseX = e.clientX - rect.left;
|
|
488
|
+
var mouseY = e.clientY - rect.top;
|
|
489
|
+
|
|
490
|
+
var zoomFactor = e.deltaY < 0 ? 1.1 : 0.9;
|
|
491
|
+
var newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, state.scale * zoomFactor));
|
|
492
|
+
|
|
493
|
+
var scaleChange = newScale / state.scale;
|
|
494
|
+
state.panX = mouseX - scaleChange * (mouseX - state.panX);
|
|
495
|
+
state.panY = mouseY - scaleChange * (mouseY - state.panY);
|
|
496
|
+
state.scale = newScale;
|
|
497
|
+
|
|
498
|
+
applyDiagramTransform(canvas, state, controls);
|
|
499
|
+
}, { passive: false });
|
|
500
|
+
|
|
501
|
+
controls.addEventListener('click', function(e) {
|
|
502
|
+
var btn = e.target.closest('[data-action]');
|
|
503
|
+
if (!btn) return;
|
|
504
|
+
var action = btn.dataset.action;
|
|
505
|
+
var rect = viewport.getBoundingClientRect();
|
|
506
|
+
var cx = rect.width / 2;
|
|
507
|
+
var cy = rect.height / 2;
|
|
508
|
+
|
|
509
|
+
if (action === 'zoom-in') {
|
|
510
|
+
var ns = Math.min(MAX_SCALE, state.scale * 1.25);
|
|
511
|
+
var sc = ns / state.scale;
|
|
512
|
+
state.panX = cx - sc * (cx - state.panX);
|
|
513
|
+
state.panY = cy - sc * (cy - state.panY);
|
|
514
|
+
state.scale = ns;
|
|
515
|
+
applyDiagramTransform(canvas, state, controls);
|
|
516
|
+
} else if (action === 'zoom-out') {
|
|
517
|
+
var ns2 = Math.max(MIN_SCALE, state.scale * 0.8);
|
|
518
|
+
var sc2 = ns2 / state.scale;
|
|
519
|
+
state.panX = cx - sc2 * (cx - state.panX);
|
|
520
|
+
state.panY = cy - sc2 * (cy - state.panY);
|
|
521
|
+
state.scale = ns2;
|
|
522
|
+
applyDiagramTransform(canvas, state, controls);
|
|
523
|
+
} else if (action === 'fit') {
|
|
524
|
+
fitDiagramToView(viewport, canvas, state, controls);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ─── Panel Tabulator init ─────────────────────────────────────────────────────
|
|
530
|
+
|
|
531
|
+
function initPanelTabulator(panel, tableData) {
|
|
532
|
+
// If the global active instance belongs to a file (not this panel), leave it —
|
|
533
|
+
// it will be cleaned up by the next renderFilePane call. We just overwrite the
|
|
534
|
+
// global pointer after creating the panel instance.
|
|
535
|
+
activeTabularColumns = tableData.columns;
|
|
536
|
+
|
|
537
|
+
var columns = tableData.columns.map(function(col) {
|
|
538
|
+
return {
|
|
539
|
+
title: col,
|
|
540
|
+
field: col,
|
|
541
|
+
headerFilter: 'input',
|
|
542
|
+
sorter: 'string',
|
|
543
|
+
resizable: true,
|
|
544
|
+
editor: false,
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
var container = document.getElementById('table-container');
|
|
549
|
+
container.innerHTML = '';
|
|
550
|
+
|
|
551
|
+
// Hide edit buttons — panels are always read-only
|
|
552
|
+
document.getElementById('table-edit-btns').className = '';
|
|
553
|
+
|
|
554
|
+
if (typeof Tabulator === 'undefined') {
|
|
555
|
+
container.innerHTML = '<div class="no-file-msg">Tabulator not loaded. Cannot render data viewer.</div>';
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
var instance = new Tabulator(container, {
|
|
560
|
+
data: tableData.data,
|
|
561
|
+
columns: columns.length > 0 ? columns : [{ title: '(no columns)', field: '_empty' }],
|
|
562
|
+
layout: 'fitDataFill',
|
|
563
|
+
height: '100%',
|
|
564
|
+
virtualDom: true,
|
|
565
|
+
selectableRows: false,
|
|
566
|
+
placeholder: 'No data',
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
instance.on('dataLoaded', function(data) {
|
|
570
|
+
document.getElementById('table-row-count').textContent = data.length + ' rows';
|
|
571
|
+
});
|
|
572
|
+
instance.on('dataFiltered', function(filters, rows) {
|
|
573
|
+
document.getElementById('table-row-count').textContent = rows.length + ' rows';
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Wire global search to this instance
|
|
577
|
+
var searchInput = document.getElementById('table-search');
|
|
578
|
+
searchInput.value = '';
|
|
579
|
+
searchInput.oninput = function() {
|
|
580
|
+
var term = searchInput.value.toLowerCase();
|
|
581
|
+
if (!term) {
|
|
582
|
+
instance.clearFilter();
|
|
583
|
+
} else {
|
|
584
|
+
instance.setFilter(function(rowData) {
|
|
585
|
+
return Object.values(rowData).some(function(v) {
|
|
586
|
+
return v != null && String(v).toLowerCase().includes(term);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
panel.tabulatorInstance = instance;
|
|
593
|
+
activeTabulatorInstance = instance;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ─── Experiment Dashboard ────────────────────────────────────────────────────
|
|
597
|
+
|
|
598
|
+
function cleanupExperimentDashboard(panel) {
|
|
599
|
+
if (panel._expTabulator) {
|
|
600
|
+
try { panel._expTabulator.destroy(); } catch(e) {}
|
|
601
|
+
panel._expTabulator = null;
|
|
602
|
+
}
|
|
603
|
+
var barEl = document.getElementById('exp-bar-' + panel.panelId);
|
|
604
|
+
var lineEl = document.getElementById('exp-line-' + panel.panelId);
|
|
605
|
+
if (typeof Plotly !== 'undefined') {
|
|
606
|
+
if (barEl) try { Plotly.purge(barEl); } catch(e) {}
|
|
607
|
+
if (lineEl) try { Plotly.purge(lineEl); } catch(e) {}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function renderExperimentDashboard(container, panel) {
|
|
612
|
+
var d = panel.rawData;
|
|
613
|
+
if (!d) {
|
|
614
|
+
container.innerHTML = '<div class="no-file-msg">No experiment data provided.</div>';
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Detect AR (autonomous-research) mode vs. classic experiment mode.
|
|
619
|
+
// AR uses `iterationBudget` in constraints and marks `mode: "autonomous-research"`.
|
|
620
|
+
// Classic experiment mode uses `plannedCount`. The dashboard renders both, with
|
|
621
|
+
// AR-specific enrichments (auto-decision colors, cost accounting, convergence).
|
|
622
|
+
var isAR = d.mode === 'autonomous-research';
|
|
623
|
+
var constraints = d.constraints || {};
|
|
624
|
+
var cost = d.costAccounting || {};
|
|
625
|
+
var convergence = d.convergence || {};
|
|
626
|
+
var exps = d.experiments || [];
|
|
627
|
+
|
|
628
|
+
// Preserve comparison state across re-renders
|
|
629
|
+
panel._expState = panel._expState || { selectedRows: [] };
|
|
630
|
+
|
|
631
|
+
var html = '<div class="experiment-dashboard' + (isAR ? ' ar-mode' : '') + '">';
|
|
632
|
+
|
|
633
|
+
// ── Progress bar ──
|
|
634
|
+
var total = isAR
|
|
635
|
+
? (constraints.iterationBudget || d.plannedCount || 0)
|
|
636
|
+
: (d.plannedCount || 0);
|
|
637
|
+
var done = exps.length;
|
|
638
|
+
var status = d.status || 'setup';
|
|
639
|
+
var current = d.currentExperiment || null;
|
|
640
|
+
var label = isAR ? 'iterations' : 'experiments';
|
|
641
|
+
|
|
642
|
+
html += '<div class="exp-progress">';
|
|
643
|
+
html += '<div class="exp-progress-label">';
|
|
644
|
+
html += '<span><span class="exp-status-badge ' + esc(status) + '">' + esc(status) + '</span></span>';
|
|
645
|
+
if (isAR && d.preset) {
|
|
646
|
+
html += '<span class="exp-preset-badge">' + esc(d.preset) + '</span>';
|
|
647
|
+
}
|
|
648
|
+
html += '<span>' + done + ' / ' + total + ' ' + label + '</span>';
|
|
649
|
+
if (isAR && convergence.detected && convergence.reason) {
|
|
650
|
+
html += '<span class="exp-converge-badge" title="Convergence">' + esc(convergence.reason) + '</span>';
|
|
651
|
+
}
|
|
652
|
+
html += '</div>';
|
|
653
|
+
html += '<div class="exp-progress-bar">';
|
|
654
|
+
if (isAR) {
|
|
655
|
+
// AR: color-code each completed iteration by auto-decision
|
|
656
|
+
for (var i = 1; i <= total; i++) {
|
|
657
|
+
var cls = 'pending';
|
|
658
|
+
if (i <= done) {
|
|
659
|
+
var ent = exps[i - 1];
|
|
660
|
+
if (ent && ent.autoDecision === 'green') cls = 'completed ar-green';
|
|
661
|
+
else if (ent && ent.autoDecision === 'red') cls = 'completed ar-red';
|
|
662
|
+
else if (ent && ent.autoDecision === 'yellow') cls = 'completed ar-yellow';
|
|
663
|
+
else cls = 'completed';
|
|
664
|
+
} else if (status === 'running' && current === i) {
|
|
665
|
+
cls = 'active';
|
|
666
|
+
}
|
|
667
|
+
html += '<div class="exp-progress-segment ' + cls + '"></div>';
|
|
668
|
+
}
|
|
669
|
+
} else {
|
|
670
|
+
for (var i = 1; i <= total; i++) {
|
|
671
|
+
var cls = 'pending';
|
|
672
|
+
if (i <= done) cls = 'completed';
|
|
673
|
+
else if (status === 'running' && current === i) cls = 'active';
|
|
674
|
+
html += '<div class="exp-progress-segment ' + cls + '"></div>';
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
html += '</div></div>';
|
|
678
|
+
|
|
679
|
+
// ── AR cost/convergence strip ──
|
|
680
|
+
if (isAR) {
|
|
681
|
+
var ceiling = constraints.costCeiling || {};
|
|
682
|
+
var dollarsSpent = cost.dollarsSpent || 0;
|
|
683
|
+
var tokensIn = cost.tokensIn || 0;
|
|
684
|
+
var tokensOut = cost.tokensOut || 0;
|
|
685
|
+
var reviewerTasks = cost.reviewerTasksSpawned || 0;
|
|
686
|
+
var pctDollars = (ceiling.dollars && ceiling.dollars > 0)
|
|
687
|
+
? (dollarsSpent / ceiling.dollars * 100) : null;
|
|
688
|
+
var pctTokens = (ceiling.tokens && ceiling.tokens > 0)
|
|
689
|
+
? ((tokensIn + tokensOut) / ceiling.tokens * 100) : null;
|
|
690
|
+
var warnCls = '';
|
|
691
|
+
if (pctDollars >= 80 || pctTokens >= 80) warnCls = ' ar-cost-warn';
|
|
692
|
+
else if (pctDollars >= 50 || pctTokens >= 50) warnCls = ' ar-cost-caution';
|
|
693
|
+
|
|
694
|
+
html += '<div class="ar-cost-strip' + warnCls + '">';
|
|
695
|
+
html += '<span class="ar-cost-item"><strong>$' + dollarsSpent.toFixed(4) + '</strong>';
|
|
696
|
+
if (ceiling.dollars) html += ' / $' + ceiling.dollars;
|
|
697
|
+
html += '</span>';
|
|
698
|
+
html += '<span class="ar-cost-item"><strong>' + (tokensIn + tokensOut) + '</strong> tokens';
|
|
699
|
+
if (ceiling.tokens) html += ' / ' + ceiling.tokens;
|
|
700
|
+
html += '</span>';
|
|
701
|
+
html += '<span class="ar-cost-item"><strong>' + reviewerTasks + '</strong> reviewer tasks</span>';
|
|
702
|
+
if (d.lastGreenCommit) {
|
|
703
|
+
html += '<span class="ar-cost-item" title="lastGreenCommit">green @ '
|
|
704
|
+
+ esc(String(d.lastGreenCommit).slice(0, 7)) + '</span>';
|
|
705
|
+
}
|
|
706
|
+
html += '</div>';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// ── Charts ──
|
|
710
|
+
var barId = 'exp-bar-' + panel.panelId;
|
|
711
|
+
var lineId = 'exp-line-' + panel.panelId;
|
|
712
|
+
html += '<div class="exp-charts">';
|
|
713
|
+
html += '<div id="' + barId + '"></div>';
|
|
714
|
+
html += '<div id="' + lineId + '"></div>';
|
|
715
|
+
html += '</div>';
|
|
716
|
+
|
|
717
|
+
// ── Comparison controls ──
|
|
718
|
+
html += '<div class="exp-compare-bar">';
|
|
719
|
+
html += '<button id="exp-compare-btn-' + panel.panelId + '" class="exp-compare-action-btn" data-panel-id="' + esc(panel.panelId) + '" disabled>Compare Selected</button>';
|
|
720
|
+
html += '<span id="exp-compare-count-' + panel.panelId + '" class="exp-compare-hint">Select 2 rows to compare</span>';
|
|
721
|
+
html += '</div>';
|
|
722
|
+
html += '<div id="exp-compare-view-' + panel.panelId + '"></div>';
|
|
723
|
+
|
|
724
|
+
// ── Results table ──
|
|
725
|
+
html += '<div class="exp-results-table" id="exp-table-' + panel.panelId + '"></div>';
|
|
726
|
+
|
|
727
|
+
html += '</div>';
|
|
728
|
+
container.innerHTML = html;
|
|
729
|
+
|
|
730
|
+
container.querySelectorAll('.exp-compare-action-btn').forEach(function(btn) {
|
|
731
|
+
btn.addEventListener('click', function() { expCompare(btn.dataset.panelId); });
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// ── Render Plotly charts ──
|
|
735
|
+
if (typeof Plotly !== 'undefined' && exps.length > 0) {
|
|
736
|
+
var isDark = document.documentElement.getAttribute('data-theme') !== 'light';
|
|
737
|
+
var fontColor = isDark ? '#b0b0c8' : '#333';
|
|
738
|
+
var gridColor = isDark ? '#1e1e32' : '#e0e0e0';
|
|
739
|
+
|
|
740
|
+
// Bar chart — deltas
|
|
741
|
+
var barNames = exps.map(function(e) { return e.name || ('Exp ' + e.index); });
|
|
742
|
+
var barDeltas = exps.map(function(e) { return e.metrics && e.metrics.outcome ? e.metrics.outcome.delta : 0; });
|
|
743
|
+
var barColors = barDeltas.map(function(v) { return v >= 0 ? '#4a9' : '#c84a4a'; });
|
|
744
|
+
|
|
745
|
+
var barShapes = [];
|
|
746
|
+
if (d.successThreshold != null && d.baseline && d.baseline.value != null) {
|
|
747
|
+
barShapes.push({
|
|
748
|
+
type: 'line', yref: 'y', y0: d.successThreshold - d.baseline.value,
|
|
749
|
+
y1: d.successThreshold - d.baseline.value, x0: -0.5, x1: barNames.length - 0.5,
|
|
750
|
+
line: { color: '#c8a84a', width: 1, dash: 'dash' }
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
Plotly.newPlot(barId, [{
|
|
755
|
+
x: barNames, y: barDeltas, type: 'bar',
|
|
756
|
+
marker: { color: barColors },
|
|
757
|
+
hovertemplate: '%{x}: %{y:+.4f}<extra></extra>'
|
|
758
|
+
}], {
|
|
759
|
+
title: { text: 'Outcome Metric Delta', font: { size: 13, color: fontColor } },
|
|
760
|
+
paper_bgcolor: 'transparent', plot_bgcolor: 'transparent',
|
|
761
|
+
font: { color: fontColor }, margin: { t: 36, r: 16, b: 40, l: 50 },
|
|
762
|
+
xaxis: { gridcolor: gridColor }, yaxis: { gridcolor: gridColor, zeroline: true, zerolinecolor: '#6a6a88' },
|
|
763
|
+
shapes: barShapes
|
|
764
|
+
}, { responsive: true, displayModeBar: false });
|
|
765
|
+
|
|
766
|
+
// Line chart — cumulative outcome value
|
|
767
|
+
var lineX = ['Baseline'].concat(barNames);
|
|
768
|
+
var lineY = [d.baseline ? d.baseline.value : 0];
|
|
769
|
+
for (var j = 0; j < exps.length; j++) {
|
|
770
|
+
lineY.push(exps[j].metrics && exps[j].metrics.outcome ? exps[j].metrics.outcome.after : lineY[lineY.length - 1]);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
var lineShapes = [];
|
|
774
|
+
if (d.successThreshold != null) {
|
|
775
|
+
lineShapes.push({
|
|
776
|
+
type: 'line', yref: 'y', y0: d.successThreshold, y1: d.successThreshold,
|
|
777
|
+
x0: -0.5, x1: lineX.length - 0.5,
|
|
778
|
+
line: { color: '#c8a84a', width: 1, dash: 'dash' }
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
Plotly.newPlot(lineId, [{
|
|
783
|
+
x: lineX, y: lineY, type: 'scatter', mode: 'lines+markers',
|
|
784
|
+
line: { color: '#6a8ac8', width: 2 }, marker: { size: 6 },
|
|
785
|
+
hovertemplate: '%{x}: %{y:.4f}<extra></extra>'
|
|
786
|
+
}], {
|
|
787
|
+
title: { text: 'Outcome Metric Over Time', font: { size: 13, color: fontColor } },
|
|
788
|
+
paper_bgcolor: 'transparent', plot_bgcolor: 'transparent',
|
|
789
|
+
font: { color: fontColor }, margin: { t: 36, r: 16, b: 40, l: 50 },
|
|
790
|
+
xaxis: { gridcolor: gridColor }, yaxis: { gridcolor: gridColor },
|
|
791
|
+
shapes: lineShapes
|
|
792
|
+
}, { responsive: true, displayModeBar: false });
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ── Results Tabulator table ──
|
|
796
|
+
if (typeof Tabulator !== 'undefined' && exps.length > 0) {
|
|
797
|
+
var tableRows = exps.map(function(e) {
|
|
798
|
+
var om = e.metrics && e.metrics.outcome ? e.metrics.outcome : {};
|
|
799
|
+
return {
|
|
800
|
+
index: e.index,
|
|
801
|
+
name: e.name || '',
|
|
802
|
+
before: om.before != null ? om.before : '',
|
|
803
|
+
after: om.after != null ? om.after : '',
|
|
804
|
+
delta: om.delta != null ? om.delta : '',
|
|
805
|
+
dsVerdict: e.dsVerdict || '',
|
|
806
|
+
outcome: e.outcome || '',
|
|
807
|
+
recommendation: e.recommendation || '',
|
|
808
|
+
// AR-specific fields (undefined on classic experiment runs — columns hidden below)
|
|
809
|
+
autoDecision: e.autoDecision || '',
|
|
810
|
+
reverted: e.reverted === true ? 'yes' : (e.reverted === false ? 'no' : ''),
|
|
811
|
+
evalType: e.evalType || '',
|
|
812
|
+
hypothesisSource: e.hypothesisSource || '',
|
|
813
|
+
reviewerVerdict: e.reviewerVerdict || ''
|
|
814
|
+
};
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
var columns = [
|
|
818
|
+
{ title: '#', field: 'index', width: 40, hozAlign: 'center' },
|
|
819
|
+
{ title: 'Name', field: 'name', minWidth: 120 },
|
|
820
|
+
{ title: 'Before', field: 'before', hozAlign: 'right', width: 80 },
|
|
821
|
+
{ title: 'After', field: 'after', hozAlign: 'right', width: 80 },
|
|
822
|
+
{ title: 'Delta', field: 'delta', hozAlign: 'right', width: 80,
|
|
823
|
+
formatter: function(cell) {
|
|
824
|
+
var v = cell.getValue();
|
|
825
|
+
if (v === '' || v == null) return '';
|
|
826
|
+
var cls = v > 0 ? 'exp-delta-positive' : (v < 0 ? 'exp-delta-negative' : '');
|
|
827
|
+
return '<span class="' + cls + '">' + (v > 0 ? '+' : '') + v + '</span>';
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
];
|
|
831
|
+
|
|
832
|
+
if (isAR) {
|
|
833
|
+
columns.push(
|
|
834
|
+
{ title: 'Auto', field: 'autoDecision', width: 80,
|
|
835
|
+
formatter: function(cell) {
|
|
836
|
+
var v = cell.getValue();
|
|
837
|
+
if (!v) return '';
|
|
838
|
+
return '<span class="ar-auto-badge ar-' + esc(v) + '">' + esc(v) + '</span>';
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
{ title: 'Kept?', field: 'reverted', width: 70,
|
|
842
|
+
formatter: function(cell) {
|
|
843
|
+
var v = cell.getValue();
|
|
844
|
+
if (v === 'yes') return '<span class="ar-reverted">reverted</span>';
|
|
845
|
+
if (v === 'no') return '<span class="ar-kept">kept</span>';
|
|
846
|
+
return '';
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
{ title: 'Eval', field: 'evalType', width: 70 },
|
|
850
|
+
{ title: 'Source', field: 'hypothesisSource', width: 110 },
|
|
851
|
+
{ title: 'Reviewer', field: 'reviewerVerdict', width: 110,
|
|
852
|
+
formatter: function(cell) {
|
|
853
|
+
var v = cell.getValue();
|
|
854
|
+
if (!v) return '';
|
|
855
|
+
return '<span class="ar-reviewer-verdict ar-rv-' + esc(v.toLowerCase()) + '">' + esc(v) + '</span>';
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
} else {
|
|
860
|
+
columns.push(
|
|
861
|
+
{ title: 'DS Verdict', field: 'dsVerdict', minWidth: 150,
|
|
862
|
+
formatter: function(cell) {
|
|
863
|
+
var v = cell.getValue();
|
|
864
|
+
return v.length > 80 ? v.substring(0, 80) + '...' : v;
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
{ title: 'Outcome', field: 'outcome', width: 100,
|
|
868
|
+
formatter: function(cell) {
|
|
869
|
+
var v = cell.getValue();
|
|
870
|
+
var cls = v.toLowerCase();
|
|
871
|
+
return '<span class="exp-outcome-badge ' + cls + '">' + esc(v) + '</span>';
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
{ title: 'Recommendation', field: 'recommendation', width: 120,
|
|
875
|
+
formatter: function(cell) {
|
|
876
|
+
var v = cell.getValue();
|
|
877
|
+
var cls = v.toLowerCase();
|
|
878
|
+
return '<span class="exp-rec-badge ' + cls + '">' + esc(v) + '</span>';
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
var tblContainer = document.getElementById('exp-table-' + panel.panelId);
|
|
885
|
+
var tbl = new Tabulator(tblContainer, {
|
|
886
|
+
data: tableRows,
|
|
887
|
+
layout: 'fitDataFill',
|
|
888
|
+
height: '100%',
|
|
889
|
+
selectableRows: 'highlight',
|
|
890
|
+
columns: columns
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
// Track selection for comparison
|
|
894
|
+
tbl.on('rowSelected', function() { expUpdateCompareUI(panel.panelId, tbl); });
|
|
895
|
+
tbl.on('rowDeselected', function() { expUpdateCompareUI(panel.panelId, tbl); });
|
|
896
|
+
panel._expTabulator = tbl;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// ── Comparison helpers ──
|
|
901
|
+
|
|
902
|
+
function expUpdateCompareUI(panelId, tbl) {
|
|
903
|
+
var selected = tbl.getSelectedData();
|
|
904
|
+
var btn = document.getElementById('exp-compare-btn-' + panelId);
|
|
905
|
+
var count = document.getElementById('exp-compare-count-' + panelId);
|
|
906
|
+
if (btn) btn.disabled = selected.length !== 2;
|
|
907
|
+
if (count) count.textContent = selected.length === 2 ? '2 selected' : 'Select 2 rows to compare';
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function expCompare(panelId) {
|
|
911
|
+
var p = openPanels[panelId];
|
|
912
|
+
if (!p || !p._expTabulator) return;
|
|
913
|
+
var selected = p._expTabulator.getSelectedData();
|
|
914
|
+
if (selected.length !== 2) return;
|
|
915
|
+
|
|
916
|
+
var d = p.rawData;
|
|
917
|
+
var exps = d.experiments || [];
|
|
918
|
+
var a = exps.find(function(e) { return e.index === selected[0].index; });
|
|
919
|
+
var b = exps.find(function(e) { return e.index === selected[1].index; });
|
|
920
|
+
if (!a || !b) return;
|
|
921
|
+
|
|
922
|
+
var view = document.getElementById('exp-compare-view-' + panelId);
|
|
923
|
+
if (!view) return;
|
|
924
|
+
|
|
925
|
+
view.innerHTML = '<div class="exp-compare-split">' +
|
|
926
|
+
expCompareColumn(a) + expCompareColumn(b) +
|
|
927
|
+
'</div>';
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function expCompareColumn(exp) {
|
|
931
|
+
var html = '<div>';
|
|
932
|
+
html += '<h4>Experiment ' + exp.index + ': ' + esc(exp.name || '') + '</h4>';
|
|
933
|
+
html += '<table><tr><th>Metric</th><th>Before</th><th>After</th><th>Delta</th></tr>';
|
|
934
|
+
|
|
935
|
+
var om = exp.metrics && exp.metrics.outcome ? exp.metrics.outcome : {};
|
|
936
|
+
html += '<tr><td><strong>Outcome</strong></td>';
|
|
937
|
+
html += '<td>' + (om.before != null ? om.before : '-') + '</td>';
|
|
938
|
+
html += '<td>' + (om.after != null ? om.after : '-') + '</td>';
|
|
939
|
+
html += '<td class="' + (om.delta > 0 ? 'exp-delta-positive' : om.delta < 0 ? 'exp-delta-negative' : '') + '">';
|
|
940
|
+
html += (om.delta != null ? (om.delta > 0 ? '+' : '') + om.delta : '-') + '</td></tr>';
|
|
941
|
+
|
|
942
|
+
var secondary = (exp.metrics && exp.metrics.secondary) || [];
|
|
943
|
+
for (var i = 0; i < secondary.length; i++) {
|
|
944
|
+
var s = secondary[i];
|
|
945
|
+
html += '<tr><td>' + esc(s.name) + '</td>';
|
|
946
|
+
html += '<td>' + (s.before != null ? s.before : '-') + '</td>';
|
|
947
|
+
html += '<td>' + (s.after != null ? s.after : '-') + '</td>';
|
|
948
|
+
var dCls = s.delta > 0 ? 'exp-delta-positive' : s.delta < 0 ? 'exp-delta-negative' : '';
|
|
949
|
+
html += '<td class="' + dCls + '">' + (s.delta != null ? (s.delta > 0 ? '+' : '') + s.delta : '-') + '</td></tr>';
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
html += '</table>';
|
|
953
|
+
html += '<div style="margin-top:8px;font-size:11px"><strong>Recommendation:</strong> ' + esc(exp.recommendation || '-') + '</div>';
|
|
954
|
+
html += '</div>';
|
|
955
|
+
return html;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// ═══════════════════════════════════════════════════════════════
|
|
959
|
+
// Eval Dashboard panel
|
|
960
|
+
// ═══════════════════════════════════════════════════════════════
|
|
961
|
+
|
|
962
|
+
function cleanupEvalDashboard(panel) {
|
|
963
|
+
if (panel._evalTabulator) {
|
|
964
|
+
try { panel._evalTabulator.destroy(); } catch(e) {}
|
|
965
|
+
panel._evalTabulator = null;
|
|
966
|
+
}
|
|
967
|
+
if (panel._evalPromptTabulator) {
|
|
968
|
+
try { panel._evalPromptTabulator.destroy(); } catch(e) {}
|
|
969
|
+
panel._evalPromptTabulator = null;
|
|
970
|
+
}
|
|
971
|
+
if (panel._evalInfraTabulator) {
|
|
972
|
+
try { panel._evalInfraTabulator.destroy(); } catch(e) {}
|
|
973
|
+
panel._evalInfraTabulator = null;
|
|
974
|
+
}
|
|
975
|
+
var chartEl = document.getElementById('eval-chart-' + panel.panelId);
|
|
976
|
+
if (typeof Plotly !== 'undefined' && chartEl) {
|
|
977
|
+
try { Plotly.purge(chartEl); } catch(e) {}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function renderEvalDashboard(container, panel) {
|
|
982
|
+
var d = panel.rawData;
|
|
983
|
+
if (!d) {
|
|
984
|
+
container.innerHTML = '<div class="no-file-msg">No evaluation data provided.</div>';
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
var summary = d.summary || {};
|
|
989
|
+
var dims = d.dimensions || [];
|
|
990
|
+
var cost = d.cost || {};
|
|
991
|
+
var variant = d.variant || 'ai-engineer';
|
|
992
|
+
var status = d.status || 'running';
|
|
993
|
+
|
|
994
|
+
var html = '<div class="eval-dashboard">';
|
|
995
|
+
|
|
996
|
+
// ── Summary header ──
|
|
997
|
+
var verdict = (summary.overallVerdict || 'PARTIAL').toLowerCase();
|
|
998
|
+
html += '<div class="eval-summary-header">';
|
|
999
|
+
html += '<span class="eval-verdict-badge ' + verdict + '">' + esc(summary.overallVerdict || 'PENDING') + '</span>';
|
|
1000
|
+
html += '<span class="eval-summary-stat"><strong>' + (summary.passed || 0) + '</strong> passed</span>';
|
|
1001
|
+
html += '<span class="eval-summary-stat"><strong>' + (summary.failed || 0) + '</strong> failed</span>';
|
|
1002
|
+
html += '<span class="eval-status-badge ' + esc(status) + '">' + esc(status) + '</span>';
|
|
1003
|
+
html += '</div>';
|
|
1004
|
+
|
|
1005
|
+
// ── Dimensions table ──
|
|
1006
|
+
html += '<p class="eval-section-label">Evaluation Dimensions</p>';
|
|
1007
|
+
html += '<div class="eval-dimensions-table" id="eval-dims-' + panel.panelId + '"></div>';
|
|
1008
|
+
|
|
1009
|
+
// ── Variant-specific sections ──
|
|
1010
|
+
if (variant === 'ai-engineer') {
|
|
1011
|
+
// Prompt comparison
|
|
1012
|
+
var prompts = d.prompts || [];
|
|
1013
|
+
if (prompts.length > 0) {
|
|
1014
|
+
html += '<p class="eval-section-label">Prompt Comparison</p>';
|
|
1015
|
+
html += '<div class="eval-prompt-compare" id="eval-prompts-' + panel.panelId + '"></div>';
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Safety cards
|
|
1019
|
+
var safety = d.safety || {};
|
|
1020
|
+
var safetyKeys = Object.keys(safety);
|
|
1021
|
+
if (safetyKeys.length > 0) {
|
|
1022
|
+
html += '<p class="eval-section-label">Safety</p>';
|
|
1023
|
+
html += '<div class="eval-safety-cards">';
|
|
1024
|
+
for (var si = 0; si < safetyKeys.length; si++) {
|
|
1025
|
+
var sk = safetyKeys[si];
|
|
1026
|
+
var sv = safety[sk];
|
|
1027
|
+
var rate = sv.passRate != null ? sv.passRate : 0;
|
|
1028
|
+
var rateStr = (rate * 100).toFixed(1) + '%';
|
|
1029
|
+
var rateCls = rate >= 0.95 ? 'high' : rate >= 0.8 ? 'medium' : 'low';
|
|
1030
|
+
html += '<div class="eval-safety-card">';
|
|
1031
|
+
html += '<div class="eval-safety-card-label">' + esc(sk.replace(/([A-Z])/g, ' $1').trim()) + '</div>';
|
|
1032
|
+
html += '<div class="eval-safety-card-value ' + rateCls + '">' + rateStr + '</div>';
|
|
1033
|
+
html += '<div class="eval-safety-card-count">' + (sv.total || 0) + ' tests</div>';
|
|
1034
|
+
html += '</div>';
|
|
1035
|
+
}
|
|
1036
|
+
html += '</div>';
|
|
1037
|
+
}
|
|
1038
|
+
} else if (variant === 'ml-engineer') {
|
|
1039
|
+
// Baseline vs Candidate chart
|
|
1040
|
+
var baseline = d.baseline;
|
|
1041
|
+
var bestCandidate = d.bestCandidate;
|
|
1042
|
+
if (baseline && bestCandidate) {
|
|
1043
|
+
html += '<p class="eval-section-label">Baseline vs Best Candidate</p>';
|
|
1044
|
+
html += '<div class="eval-charts"><div id="eval-chart-' + panel.panelId + '"></div></div>';
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Infrastructure readiness
|
|
1048
|
+
var infra = d.infrastructure || [];
|
|
1049
|
+
if (infra.length > 0) {
|
|
1050
|
+
html += '<p class="eval-section-label">Infrastructure Readiness</p>';
|
|
1051
|
+
html += '<div class="eval-infra-table" id="eval-infra-' + panel.panelId + '"></div>';
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// ── Cost cards ──
|
|
1056
|
+
if (cost.perRequest != null || cost.per1kTokens != null || cost.monthlyProjected != null) {
|
|
1057
|
+
html += '<p class="eval-section-label">Cost</p>';
|
|
1058
|
+
html += '<div class="eval-cost-cards">';
|
|
1059
|
+
if (cost.perRequest != null) {
|
|
1060
|
+
html += '<div class="eval-cost-card"><div class="eval-cost-card-label">Per Request</div>';
|
|
1061
|
+
html += '<div class="eval-cost-card-value">$' + cost.perRequest.toFixed(4) + '</div></div>';
|
|
1062
|
+
}
|
|
1063
|
+
if (cost.per1kTokens != null) {
|
|
1064
|
+
html += '<div class="eval-cost-card"><div class="eval-cost-card-label">Per 1k Tokens</div>';
|
|
1065
|
+
html += '<div class="eval-cost-card-value">$' + cost.per1kTokens.toFixed(4) + '</div></div>';
|
|
1066
|
+
}
|
|
1067
|
+
if (cost.monthlyProjected != null) {
|
|
1068
|
+
html += '<div class="eval-cost-card"><div class="eval-cost-card-label">Monthly Projection</div>';
|
|
1069
|
+
html += '<div class="eval-cost-card-value">$' + cost.monthlyProjected.toLocaleString() + '</div></div>';
|
|
1070
|
+
}
|
|
1071
|
+
if (cost.budget != null) {
|
|
1072
|
+
html += '<div class="eval-cost-card"><div class="eval-cost-card-label">Budget</div>';
|
|
1073
|
+
html += '<div class="eval-cost-card-value">$' + cost.budget.toLocaleString() + '</div></div>';
|
|
1074
|
+
}
|
|
1075
|
+
html += '</div>';
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
html += '</div>';
|
|
1079
|
+
container.innerHTML = html;
|
|
1080
|
+
|
|
1081
|
+
// ── Render dimensions Tabulator ──
|
|
1082
|
+
if (typeof Tabulator !== 'undefined' && dims.length > 0) {
|
|
1083
|
+
var dimsEl = document.getElementById('eval-dims-' + panel.panelId);
|
|
1084
|
+
if (dimsEl) {
|
|
1085
|
+
panel._evalTabulator = new Tabulator(dimsEl, {
|
|
1086
|
+
data: dims,
|
|
1087
|
+
layout: 'fitDataFill',
|
|
1088
|
+
height: '100%',
|
|
1089
|
+
columns: [
|
|
1090
|
+
{ title: 'Dimension', field: 'dimension', minWidth: 120 },
|
|
1091
|
+
{ title: 'Metric', field: 'metric', minWidth: 100 },
|
|
1092
|
+
{ title: 'Target', field: 'target', hozAlign: 'right', width: 90 },
|
|
1093
|
+
{ title: 'Actual', field: 'actual', hozAlign: 'right', width: 90 },
|
|
1094
|
+
{ title: 'Unit', field: 'unit', width: 80 },
|
|
1095
|
+
{ title: 'Verdict', field: 'verdict', width: 80, hozAlign: 'center',
|
|
1096
|
+
formatter: function(cell) {
|
|
1097
|
+
var v = (cell.getValue() || '').toLowerCase();
|
|
1098
|
+
return '<span class="eval-cell-' + (v === 'pass' ? 'pass' : 'fail') + '">' + esc(v.toUpperCase()) + '</span>';
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
]
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// ── AI Engineer: Prompt comparison Tabulator ──
|
|
1107
|
+
if (variant === 'ai-engineer' && typeof Tabulator !== 'undefined') {
|
|
1108
|
+
var prompts = d.prompts || [];
|
|
1109
|
+
var promptsEl = document.getElementById('eval-prompts-' + panel.panelId);
|
|
1110
|
+
if (promptsEl && prompts.length > 0) {
|
|
1111
|
+
// Build columns: fixed cols + one col per unique dimension
|
|
1112
|
+
var dimNames = [];
|
|
1113
|
+
for (var pi = 0; pi < prompts.length; pi++) {
|
|
1114
|
+
var pDims = prompts[pi].dimensions || [];
|
|
1115
|
+
for (var di = 0; di < pDims.length; di++) {
|
|
1116
|
+
if (dimNames.indexOf(pDims[di].dimension) === -1) dimNames.push(pDims[di].dimension);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
var promptRows = prompts.map(function(p) {
|
|
1121
|
+
var row = {
|
|
1122
|
+
name: p.name || '',
|
|
1123
|
+
version: p.version || '',
|
|
1124
|
+
model: p.model || '',
|
|
1125
|
+
costPerCall: p.costPerCall != null ? '$' + p.costPerCall.toFixed(4) : '',
|
|
1126
|
+
costPer1kTokens: p.costPer1kTokens != null ? '$' + p.costPer1kTokens.toFixed(4) : '',
|
|
1127
|
+
latencyP95: p.latencyP95ms != null ? p.latencyP95ms + 'ms' : ''
|
|
1128
|
+
};
|
|
1129
|
+
var pDims = p.dimensions || [];
|
|
1130
|
+
for (var di = 0; di < dimNames.length; di++) {
|
|
1131
|
+
var match = pDims.find(function(dd) { return dd.dimension === dimNames[di]; });
|
|
1132
|
+
if (match) {
|
|
1133
|
+
row['dim_' + di] = (match.actual != null ? match.actual : '') + ' ' + (match.verdict || '');
|
|
1134
|
+
row['dim_' + di + '_verdict'] = match.verdict || '';
|
|
1135
|
+
} else {
|
|
1136
|
+
row['dim_' + di] = '';
|
|
1137
|
+
row['dim_' + di + '_verdict'] = '';
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return row;
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
var promptCols = [
|
|
1144
|
+
{ title: 'Prompt', field: 'name', minWidth: 120 },
|
|
1145
|
+
{ title: 'Version', field: 'version', width: 70 },
|
|
1146
|
+
{ title: 'Model', field: 'model', minWidth: 100 },
|
|
1147
|
+
{ title: 'Cost/Call', field: 'costPerCall', hozAlign: 'right', width: 90 },
|
|
1148
|
+
{ title: 'Cost/1k Tokens', field: 'costPer1kTokens', hozAlign: 'right', width: 110 },
|
|
1149
|
+
{ title: 'Latency P95', field: 'latencyP95', hozAlign: 'right', width: 90 }
|
|
1150
|
+
];
|
|
1151
|
+
for (var di = 0; di < dimNames.length; di++) {
|
|
1152
|
+
(function(idx, name) {
|
|
1153
|
+
promptCols.push({
|
|
1154
|
+
title: name, field: 'dim_' + idx, hozAlign: 'center', minWidth: 90,
|
|
1155
|
+
formatter: function(cell) {
|
|
1156
|
+
var row = cell.getRow().getData();
|
|
1157
|
+
var v = (row['dim_' + idx + '_verdict'] || '').toLowerCase();
|
|
1158
|
+
var cls = v === 'pass' ? 'eval-cell-pass' : v === 'fail' ? 'eval-cell-fail' : '';
|
|
1159
|
+
return '<span class="' + cls + '">' + esc(cell.getValue()) + '</span>';
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
})(di, dimNames[di]);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
panel._evalPromptTabulator = new Tabulator(promptsEl, {
|
|
1166
|
+
data: promptRows,
|
|
1167
|
+
layout: 'fitDataFill',
|
|
1168
|
+
height: '100%',
|
|
1169
|
+
columns: promptCols
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// ── ML Engineer: Baseline vs Candidate Plotly chart ──
|
|
1175
|
+
if (variant === 'ml-engineer' && typeof Plotly !== 'undefined') {
|
|
1176
|
+
var baseline = d.baseline;
|
|
1177
|
+
var bestCandidate = d.bestCandidate;
|
|
1178
|
+
var chartEl = document.getElementById('eval-chart-' + panel.panelId);
|
|
1179
|
+
if (baseline && bestCandidate && chartEl) {
|
|
1180
|
+
var isDark = document.documentElement.getAttribute('data-theme') !== 'light';
|
|
1181
|
+
var fontColor = isDark ? '#b0b0c8' : '#333';
|
|
1182
|
+
var gridColor = isDark ? '#1e1e32' : '#e0e0e0';
|
|
1183
|
+
|
|
1184
|
+
var bMetrics = baseline.metrics || {};
|
|
1185
|
+
var cMetrics = bestCandidate.metrics || {};
|
|
1186
|
+
var metricKeys = Object.keys(cMetrics);
|
|
1187
|
+
|
|
1188
|
+
Plotly.newPlot(chartEl, [
|
|
1189
|
+
{
|
|
1190
|
+
x: metricKeys, y: metricKeys.map(function(k) { return bMetrics[k] || 0; }),
|
|
1191
|
+
type: 'bar', name: baseline.model || 'Baseline',
|
|
1192
|
+
marker: { color: '#6a6a88' }
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
x: metricKeys, y: metricKeys.map(function(k) { return cMetrics[k] || 0; }),
|
|
1196
|
+
type: 'bar', name: bestCandidate.model || 'Best Candidate',
|
|
1197
|
+
marker: { color: '#4a9' }
|
|
1198
|
+
}
|
|
1199
|
+
], {
|
|
1200
|
+
barmode: 'group',
|
|
1201
|
+
title: { text: baseline.model + ' vs ' + bestCandidate.model, font: { size: 13, color: fontColor } },
|
|
1202
|
+
paper_bgcolor: 'transparent', plot_bgcolor: 'transparent',
|
|
1203
|
+
font: { color: fontColor }, margin: { t: 36, r: 16, b: 40, l: 50 },
|
|
1204
|
+
xaxis: { gridcolor: gridColor }, yaxis: { gridcolor: gridColor },
|
|
1205
|
+
legend: { orientation: 'h', y: -0.15 }
|
|
1206
|
+
}, { responsive: true, displayModeBar: false });
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// ── ML Engineer: Infrastructure Tabulator ──
|
|
1211
|
+
if (variant === 'ml-engineer' && typeof Tabulator !== 'undefined') {
|
|
1212
|
+
var infra = d.infrastructure || [];
|
|
1213
|
+
var infraEl = document.getElementById('eval-infra-' + panel.panelId);
|
|
1214
|
+
if (infraEl && infra.length > 0) {
|
|
1215
|
+
panel._evalInfraTabulator = new Tabulator(infraEl, {
|
|
1216
|
+
data: infra,
|
|
1217
|
+
layout: 'fitDataFill',
|
|
1218
|
+
height: '100%',
|
|
1219
|
+
columns: [
|
|
1220
|
+
{ title: 'Dimension', field: 'dimension', minWidth: 120 },
|
|
1221
|
+
{ title: 'Actual', field: 'actual', hozAlign: 'right', width: 100 },
|
|
1222
|
+
{ title: 'Budget', field: 'budget', hozAlign: 'right', width: 100 },
|
|
1223
|
+
{ title: 'Verdict', field: 'verdict', width: 80, hozAlign: 'center',
|
|
1224
|
+
formatter: function(cell) {
|
|
1225
|
+
var v = (cell.getValue() || '').toLowerCase();
|
|
1226
|
+
return '<span class="eval-cell-' + (v === 'pass' ? 'pass' : 'fail') + '">' + esc(v.toUpperCase()) + '</span>';
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
]
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1236
|
+
// Model Card panel
|
|
1237
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1238
|
+
|
|
1239
|
+
function cleanupModelCard(panel) {
|
|
1240
|
+
if (panel._mcMetricsTabulator) {
|
|
1241
|
+
try { panel._mcMetricsTabulator.destroy(); } catch(e) {}
|
|
1242
|
+
panel._mcMetricsTabulator = null;
|
|
1243
|
+
}
|
|
1244
|
+
if (panel._mcQuantTabulator) {
|
|
1245
|
+
try { panel._mcQuantTabulator.destroy(); } catch(e) {}
|
|
1246
|
+
panel._mcQuantTabulator = null;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function renderModelCard(container, panel) {
|
|
1251
|
+
var d = panel.rawData;
|
|
1252
|
+
if (!d) {
|
|
1253
|
+
container.innerHTML = '<div class="no-file-msg">No model card data provided.</div>';
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
var md = d.modelDetails || {};
|
|
1258
|
+
var iu = d.intendedUse || {};
|
|
1259
|
+
var fac = d.factors || {};
|
|
1260
|
+
var met = d.metrics || {};
|
|
1261
|
+
var evData = d.evaluationData || {};
|
|
1262
|
+
var trData = d.trainingData || {};
|
|
1263
|
+
var qa = d.quantitativeAnalyses || {};
|
|
1264
|
+
var eth = d.ethicalConsiderations || {};
|
|
1265
|
+
var cav = d.caveatsAndRecommendations || {};
|
|
1266
|
+
var evalSum = d.evalSummary || {};
|
|
1267
|
+
|
|
1268
|
+
var html = '<div class="model-card-panel">';
|
|
1269
|
+
|
|
1270
|
+
// ── Header ──
|
|
1271
|
+
html += '<div class="model-card-header">';
|
|
1272
|
+
html += '<h2 class="model-card-title">' + esc(md.name || d.projectName || 'Untitled Model') + '</h2>';
|
|
1273
|
+
html += '<div class="model-card-meta">';
|
|
1274
|
+
if (md.version) html += '<span class="model-card-version-badge">' + esc(md.version) + '</span>';
|
|
1275
|
+
if (md.type) html += '<span class="model-card-meta-item"><strong>Type:</strong> ' + esc(md.type) + '</span>';
|
|
1276
|
+
if (md.date) html += '<span class="model-card-meta-item"><strong>Date:</strong> ' + esc(md.date) + '</span>';
|
|
1277
|
+
if (md.owner) html += '<span class="model-card-meta-item"><strong>Owner:</strong> ' + esc(md.owner) + '</span>';
|
|
1278
|
+
if (md.framework) html += '<span class="model-card-meta-item"><strong>Framework:</strong> ' + esc(md.framework) + '</span>';
|
|
1279
|
+
html += '</div></div>';
|
|
1280
|
+
|
|
1281
|
+
// ── Sections ──
|
|
1282
|
+
|
|
1283
|
+
// 1. Model Details
|
|
1284
|
+
html += mcSection('Model Details', false, function() {
|
|
1285
|
+
var body = '';
|
|
1286
|
+
if (md.license) body += '<p><strong>License:</strong> ' + esc(md.license) + '</p>';
|
|
1287
|
+
var refs = md.references || [];
|
|
1288
|
+
if (refs.length > 0) {
|
|
1289
|
+
body += '<p><strong>References:</strong></p><ul>';
|
|
1290
|
+
for (var i = 0; i < refs.length; i++) body += '<li>' + esc(refs[i]) + '</li>';
|
|
1291
|
+
body += '</ul>';
|
|
1292
|
+
}
|
|
1293
|
+
return body || '<p>No additional details.</p>';
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
// 2. Intended Use
|
|
1297
|
+
html += mcSection('Intended Use', false, function() {
|
|
1298
|
+
var body = '';
|
|
1299
|
+
if (iu.primaryUse) body += '<p><strong>Primary use:</strong> ' + esc(iu.primaryUse) + '</p>';
|
|
1300
|
+
if (iu.primaryUsers) body += '<p><strong>Primary users:</strong> ' + esc(iu.primaryUsers) + '</p>';
|
|
1301
|
+
var oos = iu.outOfScopeUses || [];
|
|
1302
|
+
if (oos.length > 0) {
|
|
1303
|
+
body += '<p><strong>Out-of-scope uses:</strong></p><ul>';
|
|
1304
|
+
for (var i = 0; i < oos.length; i++) body += '<li>' + esc(oos[i]) + '</li>';
|
|
1305
|
+
body += '</ul>';
|
|
1306
|
+
}
|
|
1307
|
+
return body || '<p>Not specified.</p>';
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// 3. Factors
|
|
1311
|
+
html += mcSection('Factors', false, function() {
|
|
1312
|
+
var body = '';
|
|
1313
|
+
var rf = fac.relevantFactors || [];
|
|
1314
|
+
var ef = fac.evaluationFactors || [];
|
|
1315
|
+
if (rf.length > 0) {
|
|
1316
|
+
body += '<p><strong>Relevant factors:</strong></p><ul>';
|
|
1317
|
+
for (var i = 0; i < rf.length; i++) body += '<li>' + esc(rf[i]) + '</li>';
|
|
1318
|
+
body += '</ul>';
|
|
1319
|
+
}
|
|
1320
|
+
if (ef.length > 0) {
|
|
1321
|
+
body += '<p><strong>Evaluation factors:</strong></p><ul>';
|
|
1322
|
+
for (var i = 0; i < ef.length; i++) body += '<li>' + esc(ef[i]) + '</li>';
|
|
1323
|
+
body += '</ul>';
|
|
1324
|
+
}
|
|
1325
|
+
return body || '<p>Not specified.</p>';
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
// 4. Metrics (rendered as inline table + optional Tabulator)
|
|
1329
|
+
html += mcSection('Metrics', false, function() {
|
|
1330
|
+
var body = '';
|
|
1331
|
+
var pm = met.performanceMeasures || [];
|
|
1332
|
+
if (pm.length > 0) {
|
|
1333
|
+
body += '<table class="model-card-table"><tr><th>Measure</th><th>Value</th><th>Description</th><th>Rationale</th></tr>';
|
|
1334
|
+
for (var i = 0; i < pm.length; i++) {
|
|
1335
|
+
body += '<tr><td>' + esc(pm[i].name || '') + '</td><td>' + esc(String(pm[i].value || '')) + '</td>';
|
|
1336
|
+
body += '<td>' + esc(pm[i].description || '') + '</td><td>' + esc(pm[i].rationale || '') + '</td></tr>';
|
|
1337
|
+
}
|
|
1338
|
+
body += '</table>';
|
|
1339
|
+
}
|
|
1340
|
+
var dt = met.decisionThresholds || [];
|
|
1341
|
+
if (dt.length > 0) {
|
|
1342
|
+
body += '<p><strong>Decision Thresholds:</strong></p>';
|
|
1343
|
+
body += '<table class="model-card-table"><tr><th>Threshold</th><th>Value</th><th>Rationale</th></tr>';
|
|
1344
|
+
for (var i = 0; i < dt.length; i++) {
|
|
1345
|
+
body += '<tr><td>' + esc(dt[i].name || '') + '</td><td>' + esc(String(dt[i].threshold || '')) + '</td>';
|
|
1346
|
+
body += '<td>' + esc(dt[i].rationale || '') + '</td></tr>';
|
|
1347
|
+
}
|
|
1348
|
+
body += '</table>';
|
|
1349
|
+
}
|
|
1350
|
+
return body || '<p>No metrics defined.</p>';
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
// 5. Evaluation Data
|
|
1354
|
+
html += mcSection('Evaluation Data', false, function() {
|
|
1355
|
+
return mcDataSection(evData);
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
// 6. Training Data
|
|
1359
|
+
html += mcSection('Training Data', false, function() {
|
|
1360
|
+
return mcDataSection(trData);
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
// 7. Quantitative Analyses
|
|
1364
|
+
html += mcSection('Quantitative Analyses', false, function() {
|
|
1365
|
+
var body = '';
|
|
1366
|
+
var ur = qa.unitaryResults || [];
|
|
1367
|
+
if (ur.length > 0) {
|
|
1368
|
+
body += '<p><strong>Unitary Results:</strong></p>';
|
|
1369
|
+
body += '<table class="model-card-table"><tr><th>Metric</th><th>Value</th><th>Subset</th></tr>';
|
|
1370
|
+
for (var i = 0; i < ur.length; i++) {
|
|
1371
|
+
body += '<tr><td>' + esc(ur[i].metric || '') + '</td><td>' + esc(String(ur[i].value || '')) + '</td>';
|
|
1372
|
+
body += '<td>' + esc(ur[i].subset || '') + '</td></tr>';
|
|
1373
|
+
}
|
|
1374
|
+
body += '</table>';
|
|
1375
|
+
}
|
|
1376
|
+
var ir = qa.intersectionalResults || [];
|
|
1377
|
+
if (ir.length > 0) {
|
|
1378
|
+
body += '<p><strong>Intersectional Results:</strong></p>';
|
|
1379
|
+
body += '<table class="model-card-table"><tr><th>Metric</th><th>Value</th><th>Factors</th></tr>';
|
|
1380
|
+
for (var i = 0; i < ir.length; i++) {
|
|
1381
|
+
body += '<tr><td>' + esc(ir[i].metric || '') + '</td><td>' + esc(String(ir[i].value || '')) + '</td>';
|
|
1382
|
+
body += '<td>' + esc((ir[i].factors || []).join(', ')) + '</td></tr>';
|
|
1383
|
+
}
|
|
1384
|
+
body += '</table>';
|
|
1385
|
+
}
|
|
1386
|
+
return body || '<p>No quantitative analyses available.</p>';
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
// 8. Ethical Considerations (highlighted)
|
|
1390
|
+
html += mcSection('Ethical Considerations', true, function() {
|
|
1391
|
+
var body = '';
|
|
1392
|
+
var risks = eth.risks || [];
|
|
1393
|
+
if (risks.length > 0) {
|
|
1394
|
+
body += '<p><strong>Risks:</strong></p><ul>';
|
|
1395
|
+
for (var i = 0; i < risks.length; i++) body += '<li>' + esc(risks[i]) + '</li>';
|
|
1396
|
+
body += '</ul>';
|
|
1397
|
+
}
|
|
1398
|
+
var mits = eth.mitigations || [];
|
|
1399
|
+
if (mits.length > 0) {
|
|
1400
|
+
body += '<p><strong>Mitigations:</strong></p><ul>';
|
|
1401
|
+
for (var i = 0; i < mits.length; i++) body += '<li>' + esc(mits[i]) + '</li>';
|
|
1402
|
+
body += '</ul>';
|
|
1403
|
+
}
|
|
1404
|
+
if (eth.academicReview) {
|
|
1405
|
+
body += '<p><strong>Academic Review:</strong></p><p>' + esc(eth.academicReview) + '</p>';
|
|
1406
|
+
}
|
|
1407
|
+
return body || '<p>No ethical considerations documented.</p>';
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
// 9. Caveats and Recommendations
|
|
1411
|
+
html += mcSection('Caveats and Recommendations', false, function() {
|
|
1412
|
+
var body = '';
|
|
1413
|
+
var cavs = cav.caveats || [];
|
|
1414
|
+
if (cavs.length > 0) {
|
|
1415
|
+
body += '<p><strong>Caveats:</strong></p><ul>';
|
|
1416
|
+
for (var i = 0; i < cavs.length; i++) body += '<li>' + esc(cavs[i]) + '</li>';
|
|
1417
|
+
body += '</ul>';
|
|
1418
|
+
}
|
|
1419
|
+
var recs = cav.recommendations || [];
|
|
1420
|
+
if (recs.length > 0) {
|
|
1421
|
+
body += '<p><strong>Recommendations:</strong></p><ul>';
|
|
1422
|
+
for (var i = 0; i < recs.length; i++) body += '<li>' + esc(recs[i]) + '</li>';
|
|
1423
|
+
body += '</ul>';
|
|
1424
|
+
}
|
|
1425
|
+
return body || '<p>No caveats or recommendations.</p>';
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
// ── Eval Summary footer ──
|
|
1429
|
+
var evalDims = evalSum.dimensions || [];
|
|
1430
|
+
if (evalSum.overallVerdict || evalDims.length > 0) {
|
|
1431
|
+
html += '<div class="model-card-eval-footer">';
|
|
1432
|
+
html += '<p class="eval-section-label">Evaluation Summary</p>';
|
|
1433
|
+
if (evalSum.overallVerdict) {
|
|
1434
|
+
var v = evalSum.overallVerdict.toLowerCase();
|
|
1435
|
+
html += '<span class="eval-verdict-badge ' + v + '">' + esc(evalSum.overallVerdict) + '</span>';
|
|
1436
|
+
}
|
|
1437
|
+
if (evalDims.length > 0) {
|
|
1438
|
+
html += '<div class="model-card-eval-dims">';
|
|
1439
|
+
for (var i = 0; i < evalDims.length; i++) {
|
|
1440
|
+
var ev = evalDims[i];
|
|
1441
|
+
var cls = (ev.verdict || '').toLowerCase() === 'pass' ? 'pass' : 'fail';
|
|
1442
|
+
html += '<span class="model-card-eval-dim ' + cls + '">' + esc(ev.dimension) + ': ' + esc(ev.verdict || '') + '</span>';
|
|
1443
|
+
}
|
|
1444
|
+
html += '</div>';
|
|
1445
|
+
}
|
|
1446
|
+
html += '</div>';
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// ── Export button ──
|
|
1450
|
+
html += '<button class="model-card-export-btn" data-panel-id="' + esc(panel.panelId) + '">Export as Markdown</button>';
|
|
1451
|
+
|
|
1452
|
+
html += '</div>';
|
|
1453
|
+
container.innerHTML = html;
|
|
1454
|
+
|
|
1455
|
+
container.querySelectorAll('.model-card-export-btn').forEach(function(btn) {
|
|
1456
|
+
btn.addEventListener('click', function() { mcExportMarkdown(btn.dataset.panelId); });
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// Model card section builder
|
|
1461
|
+
function mcSection(title, isEthical, contentFn) {
|
|
1462
|
+
var cls = 'model-card-section' + (isEthical ? ' ethical' : '');
|
|
1463
|
+
return '<details class="' + cls + '" open>' +
|
|
1464
|
+
'<summary>' + esc(title) + '</summary>' +
|
|
1465
|
+
'<div class="model-card-section-body">' + contentFn() + '</div>' +
|
|
1466
|
+
'</details>';
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// Dataset info helper
|
|
1470
|
+
function mcDataSection(data) {
|
|
1471
|
+
var body = '';
|
|
1472
|
+
var ds = data.datasets;
|
|
1473
|
+
if (ds) {
|
|
1474
|
+
var arr = Array.isArray(ds) ? ds : [ds];
|
|
1475
|
+
body += '<p><strong>Datasets:</strong> ' + arr.map(function(d) { return esc(d); }).join(', ') + '</p>';
|
|
1476
|
+
}
|
|
1477
|
+
if (data.size) body += '<p><strong>Size:</strong> ' + esc(String(data.size)) + '</p>';
|
|
1478
|
+
if (data.preprocessing) body += '<p><strong>Preprocessing:</strong> ' + esc(data.preprocessing) + '</p>';
|
|
1479
|
+
if (data.motivation) body += '<p><strong>Motivation:</strong> ' + esc(data.motivation) + '</p>';
|
|
1480
|
+
return body || '<p>Not specified.</p>';
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// Export model card as markdown
|
|
1484
|
+
function mcExportMarkdown(panelId) {
|
|
1485
|
+
var p = openPanels[panelId];
|
|
1486
|
+
if (!p || !p.rawData) return;
|
|
1487
|
+
var md = modelCardToMarkdown(p.rawData);
|
|
1488
|
+
var blob = new Blob([md], { type: 'text/markdown' });
|
|
1489
|
+
var url = URL.createObjectURL(blob);
|
|
1490
|
+
var a = document.createElement('a');
|
|
1491
|
+
a.href = url;
|
|
1492
|
+
a.download = (p.rawData.projectName || 'model-card') + '-model-card.md';
|
|
1493
|
+
document.body.appendChild(a);
|
|
1494
|
+
a.click();
|
|
1495
|
+
document.body.removeChild(a);
|
|
1496
|
+
URL.revokeObjectURL(url);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function modelCardToMarkdown(d) {
|
|
1500
|
+
var md = d.modelDetails || {};
|
|
1501
|
+
var iu = d.intendedUse || {};
|
|
1502
|
+
var fac = d.factors || {};
|
|
1503
|
+
var met = d.metrics || {};
|
|
1504
|
+
var evData = d.evaluationData || {};
|
|
1505
|
+
var trData = d.trainingData || {};
|
|
1506
|
+
var qa = d.quantitativeAnalyses || {};
|
|
1507
|
+
var eth = d.ethicalConsiderations || {};
|
|
1508
|
+
var cav = d.caveatsAndRecommendations || {};
|
|
1509
|
+
var evalSum = d.evalSummary || {};
|
|
1510
|
+
var cost = evalSum.cost || {};
|
|
1511
|
+
|
|
1512
|
+
var lines = [];
|
|
1513
|
+
lines.push('# Model Card: ' + (md.name || d.projectName || 'Untitled'));
|
|
1514
|
+
lines.push('');
|
|
1515
|
+
lines.push('- **Version:** ' + (md.version || 'N/A'));
|
|
1516
|
+
lines.push('- **Type:** ' + (md.type || 'N/A'));
|
|
1517
|
+
lines.push('- **Owner:** ' + (md.owner || 'N/A'));
|
|
1518
|
+
lines.push('- **Date:** ' + (md.date || 'N/A'));
|
|
1519
|
+
lines.push('- **Framework:** ' + (md.framework || 'N/A'));
|
|
1520
|
+
lines.push('');
|
|
1521
|
+
lines.push('---');
|
|
1522
|
+
lines.push('');
|
|
1523
|
+
|
|
1524
|
+
// Model Details
|
|
1525
|
+
lines.push('## Model Details');
|
|
1526
|
+
lines.push('');
|
|
1527
|
+
if (md.license) lines.push('**License:** ' + md.license);
|
|
1528
|
+
var refs = md.references || [];
|
|
1529
|
+
if (refs.length > 0) {
|
|
1530
|
+
lines.push('');
|
|
1531
|
+
lines.push('**References:**');
|
|
1532
|
+
for (var i = 0; i < refs.length; i++) lines.push('- ' + refs[i]);
|
|
1533
|
+
}
|
|
1534
|
+
lines.push('');
|
|
1535
|
+
lines.push('---');
|
|
1536
|
+
lines.push('');
|
|
1537
|
+
|
|
1538
|
+
// Intended Use
|
|
1539
|
+
lines.push('## Intended Use');
|
|
1540
|
+
lines.push('');
|
|
1541
|
+
if (iu.primaryUse) lines.push('- **Primary use:** ' + iu.primaryUse);
|
|
1542
|
+
if (iu.primaryUsers) lines.push('- **Primary users:** ' + iu.primaryUsers);
|
|
1543
|
+
var oos = iu.outOfScopeUses || [];
|
|
1544
|
+
if (oos.length > 0) {
|
|
1545
|
+
lines.push('- **Out-of-scope uses:**');
|
|
1546
|
+
for (var i = 0; i < oos.length; i++) lines.push(' - ' + oos[i]);
|
|
1547
|
+
}
|
|
1548
|
+
lines.push('');
|
|
1549
|
+
lines.push('---');
|
|
1550
|
+
lines.push('');
|
|
1551
|
+
|
|
1552
|
+
// Factors
|
|
1553
|
+
lines.push('## Factors');
|
|
1554
|
+
lines.push('');
|
|
1555
|
+
var rf = fac.relevantFactors || [];
|
|
1556
|
+
if (rf.length > 0) {
|
|
1557
|
+
lines.push('- **Relevant factors:** ' + rf.join(', '));
|
|
1558
|
+
}
|
|
1559
|
+
var ef = fac.evaluationFactors || [];
|
|
1560
|
+
if (ef.length > 0) {
|
|
1561
|
+
lines.push('- **Evaluation factors:** ' + ef.join(', '));
|
|
1562
|
+
}
|
|
1563
|
+
lines.push('');
|
|
1564
|
+
lines.push('---');
|
|
1565
|
+
lines.push('');
|
|
1566
|
+
|
|
1567
|
+
// Metrics
|
|
1568
|
+
lines.push('## Metrics');
|
|
1569
|
+
lines.push('');
|
|
1570
|
+
var pm = met.performanceMeasures || [];
|
|
1571
|
+
if (pm.length > 0) {
|
|
1572
|
+
lines.push('| Measure | Value | Description | Rationale |');
|
|
1573
|
+
lines.push('|---------|-------|-------------|-----------|');
|
|
1574
|
+
for (var i = 0; i < pm.length; i++) {
|
|
1575
|
+
lines.push('| ' + (pm[i].name || '') + ' | ' + (pm[i].value || '') + ' | ' + (pm[i].description || '') + ' | ' + (pm[i].rationale || '') + ' |');
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
var dt = met.decisionThresholds || [];
|
|
1579
|
+
if (dt.length > 0) {
|
|
1580
|
+
lines.push('');
|
|
1581
|
+
lines.push('### Decision Thresholds');
|
|
1582
|
+
lines.push('');
|
|
1583
|
+
lines.push('| Threshold | Value | Rationale |');
|
|
1584
|
+
lines.push('|-----------|-------|-----------|');
|
|
1585
|
+
for (var i = 0; i < dt.length; i++) {
|
|
1586
|
+
lines.push('| ' + (dt[i].name || '') + ' | ' + (dt[i].threshold || '') + ' | ' + (dt[i].rationale || '') + ' |');
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
lines.push('');
|
|
1590
|
+
lines.push('---');
|
|
1591
|
+
lines.push('');
|
|
1592
|
+
|
|
1593
|
+
// Evaluation Data
|
|
1594
|
+
lines.push('## Evaluation Data');
|
|
1595
|
+
lines.push('');
|
|
1596
|
+
lines.push(mdDataSection(evData));
|
|
1597
|
+
lines.push('---');
|
|
1598
|
+
lines.push('');
|
|
1599
|
+
|
|
1600
|
+
// Training Data
|
|
1601
|
+
lines.push('## Training Data');
|
|
1602
|
+
lines.push('');
|
|
1603
|
+
lines.push(mdDataSection(trData));
|
|
1604
|
+
lines.push('---');
|
|
1605
|
+
lines.push('');
|
|
1606
|
+
|
|
1607
|
+
// Quantitative Analyses
|
|
1608
|
+
lines.push('## Quantitative Analyses');
|
|
1609
|
+
lines.push('');
|
|
1610
|
+
var ur = qa.unitaryResults || [];
|
|
1611
|
+
if (ur.length > 0) {
|
|
1612
|
+
lines.push('### Unitary Results');
|
|
1613
|
+
lines.push('');
|
|
1614
|
+
lines.push('| Metric | Value | Subset |');
|
|
1615
|
+
lines.push('|--------|-------|--------|');
|
|
1616
|
+
for (var i = 0; i < ur.length; i++) {
|
|
1617
|
+
lines.push('| ' + (ur[i].metric || '') + ' | ' + (ur[i].value || '') + ' | ' + (ur[i].subset || '') + ' |');
|
|
1618
|
+
}
|
|
1619
|
+
lines.push('');
|
|
1620
|
+
}
|
|
1621
|
+
var ir = qa.intersectionalResults || [];
|
|
1622
|
+
if (ir.length > 0) {
|
|
1623
|
+
lines.push('### Intersectional Results');
|
|
1624
|
+
lines.push('');
|
|
1625
|
+
lines.push('| Metric | Value | Factors |');
|
|
1626
|
+
lines.push('|--------|-------|---------|');
|
|
1627
|
+
for (var i = 0; i < ir.length; i++) {
|
|
1628
|
+
lines.push('| ' + (ir[i].metric || '') + ' | ' + (ir[i].value || '') + ' | ' + (ir[i].factors || []).join(', ') + ' |');
|
|
1629
|
+
}
|
|
1630
|
+
lines.push('');
|
|
1631
|
+
}
|
|
1632
|
+
lines.push('---');
|
|
1633
|
+
lines.push('');
|
|
1634
|
+
|
|
1635
|
+
// Ethical Considerations
|
|
1636
|
+
lines.push('## Ethical Considerations');
|
|
1637
|
+
lines.push('');
|
|
1638
|
+
var risks = eth.risks || [];
|
|
1639
|
+
if (risks.length > 0) {
|
|
1640
|
+
lines.push('**Risks:**');
|
|
1641
|
+
for (var i = 0; i < risks.length; i++) lines.push('- ' + risks[i]);
|
|
1642
|
+
lines.push('');
|
|
1643
|
+
}
|
|
1644
|
+
var mits = eth.mitigations || [];
|
|
1645
|
+
if (mits.length > 0) {
|
|
1646
|
+
lines.push('**Mitigations:**');
|
|
1647
|
+
for (var i = 0; i < mits.length; i++) lines.push('- ' + mits[i]);
|
|
1648
|
+
lines.push('');
|
|
1649
|
+
}
|
|
1650
|
+
if (eth.academicReview) {
|
|
1651
|
+
lines.push('**Academic Review:**');
|
|
1652
|
+
lines.push('');
|
|
1653
|
+
lines.push(eth.academicReview);
|
|
1654
|
+
lines.push('');
|
|
1655
|
+
}
|
|
1656
|
+
lines.push('---');
|
|
1657
|
+
lines.push('');
|
|
1658
|
+
|
|
1659
|
+
// Caveats and Recommendations
|
|
1660
|
+
lines.push('## Caveats and Recommendations');
|
|
1661
|
+
lines.push('');
|
|
1662
|
+
var cavs = cav.caveats || [];
|
|
1663
|
+
if (cavs.length > 0) {
|
|
1664
|
+
lines.push('**Caveats:**');
|
|
1665
|
+
for (var i = 0; i < cavs.length; i++) lines.push('- ' + cavs[i]);
|
|
1666
|
+
lines.push('');
|
|
1667
|
+
}
|
|
1668
|
+
var recs = cav.recommendations || [];
|
|
1669
|
+
if (recs.length > 0) {
|
|
1670
|
+
lines.push('**Recommendations:**');
|
|
1671
|
+
for (var i = 0; i < recs.length; i++) lines.push('- ' + recs[i]);
|
|
1672
|
+
lines.push('');
|
|
1673
|
+
}
|
|
1674
|
+
lines.push('---');
|
|
1675
|
+
lines.push('');
|
|
1676
|
+
|
|
1677
|
+
// Eval Summary
|
|
1678
|
+
var evalDims = evalSum.dimensions || [];
|
|
1679
|
+
if (evalSum.overallVerdict || evalDims.length > 0) {
|
|
1680
|
+
lines.push('## Evaluation Summary');
|
|
1681
|
+
lines.push('');
|
|
1682
|
+
if (evalSum.overallVerdict) lines.push('- **Overall verdict:** ' + evalSum.overallVerdict);
|
|
1683
|
+
lines.push('');
|
|
1684
|
+
if (evalDims.length > 0) {
|
|
1685
|
+
lines.push('| Dimension | Metric | Target | Actual | Verdict |');
|
|
1686
|
+
lines.push('|-----------|--------|--------|--------|---------|');
|
|
1687
|
+
for (var i = 0; i < evalDims.length; i++) {
|
|
1688
|
+
var ev = evalDims[i];
|
|
1689
|
+
lines.push('| ' + (ev.dimension || '') + ' | ' + (ev.metric || '') + ' | ' + (ev.target || '') + ' | ' + (ev.actual || '') + ' | ' + (ev.verdict || '') + ' |');
|
|
1690
|
+
}
|
|
1691
|
+
lines.push('');
|
|
1692
|
+
}
|
|
1693
|
+
if (cost.perRequest != null || cost.per1kTokens != null || cost.monthlyProjected != null) {
|
|
1694
|
+
lines.push('### Cost Profile');
|
|
1695
|
+
lines.push('');
|
|
1696
|
+
if (cost.perRequest != null) lines.push('- **Per request:** $' + cost.perRequest.toFixed(4));
|
|
1697
|
+
if (cost.per1kTokens != null) lines.push('- **Per 1k tokens:** $' + cost.per1kTokens.toFixed(4));
|
|
1698
|
+
if (cost.monthlyProjected != null) lines.push('- **Monthly projection:** $' + cost.monthlyProjected);
|
|
1699
|
+
if (cost.budget != null) lines.push('- **Budget:** $' + cost.budget);
|
|
1700
|
+
lines.push('');
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
return lines.join('\n');
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
function mdDataSection(data) {
|
|
1708
|
+
var lines = [];
|
|
1709
|
+
var ds = data.datasets;
|
|
1710
|
+
if (ds) {
|
|
1711
|
+
var arr = Array.isArray(ds) ? ds : [ds];
|
|
1712
|
+
lines.push('- **Datasets:** ' + arr.join(', '));
|
|
1713
|
+
}
|
|
1714
|
+
if (data.size) lines.push('- **Size:** ' + data.size);
|
|
1715
|
+
if (data.preprocessing) lines.push('- **Preprocessing:** ' + data.preprocessing);
|
|
1716
|
+
if (data.motivation) lines.push('- **Motivation:** ' + data.motivation);
|
|
1717
|
+
lines.push('');
|
|
1718
|
+
return lines.join('\n');
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1722
|
+
// Prompt Laboratory panel
|
|
1723
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1724
|
+
|
|
1725
|
+
function cleanupPromptLab(panel) {
|
|
1726
|
+
if (panel._plEditor) {
|
|
1727
|
+
try { panel._plEditor.dispose(); } catch(e) {}
|
|
1728
|
+
panel._plEditor = null;
|
|
1729
|
+
}
|
|
1730
|
+
if (panel._plDiffEditor) {
|
|
1731
|
+
try {
|
|
1732
|
+
var model = panel._plDiffEditor.getModel();
|
|
1733
|
+
if (model) {
|
|
1734
|
+
if (model.original) model.original.dispose();
|
|
1735
|
+
if (model.modified) model.modified.dispose();
|
|
1736
|
+
}
|
|
1737
|
+
panel._plDiffEditor.dispose();
|
|
1738
|
+
} catch(e) {}
|
|
1739
|
+
panel._plDiffEditor = null;
|
|
1740
|
+
}
|
|
1741
|
+
if (panel._plOriginalModel) {
|
|
1742
|
+
try { panel._plOriginalModel.dispose(); } catch(e) {}
|
|
1743
|
+
panel._plOriginalModel = null;
|
|
1744
|
+
}
|
|
1745
|
+
if (panel._plModifiedModel) {
|
|
1746
|
+
try { panel._plModifiedModel.dispose(); } catch(e) {}
|
|
1747
|
+
panel._plModifiedModel = null;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function renderPromptLab(container, panel) {
|
|
1752
|
+
var d = panel.rawData;
|
|
1753
|
+
if (!d) {
|
|
1754
|
+
container.innerHTML = '<div class="pl-empty">No prompt lab data. The AI Engineer will push this panel when a project has prompts.</div>';
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// Preserve editor state across re-renders
|
|
1759
|
+
panel._plState = panel._plState || { diffMode: false, resultsCollapsed: false };
|
|
1760
|
+
var state = panel._plState;
|
|
1761
|
+
|
|
1762
|
+
var prompts = d.prompts || [];
|
|
1763
|
+
var active = d.activePrompt || (prompts.length > 0 ? prompts[0].name : '');
|
|
1764
|
+
var status = d.status || 'idle';
|
|
1765
|
+
var testRuns = d.testRuns || [];
|
|
1766
|
+
var syncHistory = d.syncHistory || [];
|
|
1767
|
+
var pid = panel.panelId;
|
|
1768
|
+
|
|
1769
|
+
var html = '<div class="prompt-lab">';
|
|
1770
|
+
|
|
1771
|
+
// ── Toolbar ──
|
|
1772
|
+
html += '<div class="pl-toolbar">';
|
|
1773
|
+
html += '<select id="pl-select-' + esc(pid) + '" class="pl-select-prompt" data-panel-id="' + esc(pid) + '">';
|
|
1774
|
+
for (var i = 0; i < prompts.length; i++) {
|
|
1775
|
+
var sel = prompts[i].name === active ? ' selected' : '';
|
|
1776
|
+
html += '<option value="' + esc(prompts[i].name) + '"' + sel + '>' + esc(prompts[i].filename) + ' (v' + esc(prompts[i].currentVersion) + ')</option>';
|
|
1777
|
+
}
|
|
1778
|
+
if (prompts.length === 0) html += '<option value="">No prompts found</option>';
|
|
1779
|
+
html += '</select>';
|
|
1780
|
+
|
|
1781
|
+
html += '<span class="pl-status-badge ' + esc(status) + '">' + esc(status) + '</span>';
|
|
1782
|
+
html += '<span class="pl-toolbar-spacer"></span>';
|
|
1783
|
+
|
|
1784
|
+
html += '<button class="pl-btn pl-action-diff' + (state.diffMode ? ' active' : '') + '" data-panel-id="' + esc(pid) + '" title="Toggle diff view">Diff</button>';
|
|
1785
|
+
html += '<button class="pl-btn primary pl-action-run" data-panel-id="' + esc(pid) + '"' + (status !== 'idle' ? ' disabled' : '') + '>Run Test</button>';
|
|
1786
|
+
html += '<button class="pl-btn sync pl-action-sync" data-panel-id="' + esc(pid) + '"' + (status !== 'idle' ? ' disabled' : '') + '>Sync to Project</button>';
|
|
1787
|
+
html += '</div>';
|
|
1788
|
+
|
|
1789
|
+
// ── Body: editor + sidebar ──
|
|
1790
|
+
html += '<div class="pl-body">';
|
|
1791
|
+
|
|
1792
|
+
// Editor column
|
|
1793
|
+
html += '<div class="pl-editor-col">';
|
|
1794
|
+
html += '<div class="pl-editor-container" id="pl-editor-' + esc(pid) + '"></div>';
|
|
1795
|
+
html += '</div>';
|
|
1796
|
+
|
|
1797
|
+
// Version sidebar
|
|
1798
|
+
html += '<div class="pl-sidebar" id="pl-sidebar-' + esc(pid) + '">';
|
|
1799
|
+
html += '</div>';
|
|
1800
|
+
|
|
1801
|
+
html += '</div>'; // end pl-body
|
|
1802
|
+
|
|
1803
|
+
// ── Results area ──
|
|
1804
|
+
html += '<div class="pl-results" id="pl-results-' + esc(pid) + '" style="' + (state.resultsCollapsed ? 'display:none' : '') + '">';
|
|
1805
|
+
html += '</div>';
|
|
1806
|
+
|
|
1807
|
+
html += '</div>'; // end prompt-lab
|
|
1808
|
+
container.innerHTML = html;
|
|
1809
|
+
|
|
1810
|
+
container.querySelectorAll('.pl-select-prompt').forEach(function(el) {
|
|
1811
|
+
el.addEventListener('change', function() { plSelectPrompt(el.dataset.panelId); });
|
|
1812
|
+
});
|
|
1813
|
+
container.querySelectorAll('.pl-action-diff').forEach(function(btn) {
|
|
1814
|
+
btn.addEventListener('click', function() { plToggleDiff(btn.dataset.panelId); });
|
|
1815
|
+
});
|
|
1816
|
+
container.querySelectorAll('.pl-action-run').forEach(function(btn) {
|
|
1817
|
+
btn.addEventListener('click', function() { plRunTest(btn.dataset.panelId); });
|
|
1818
|
+
});
|
|
1819
|
+
container.querySelectorAll('.pl-action-sync').forEach(function(btn) {
|
|
1820
|
+
btn.addEventListener('click', function() { plSyncToProject(btn.dataset.panelId); });
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
// Render sidebar
|
|
1824
|
+
plRenderVersionSidebar(
|
|
1825
|
+
document.getElementById('pl-sidebar-' + pid),
|
|
1826
|
+
prompts, active, syncHistory, pid
|
|
1827
|
+
);
|
|
1828
|
+
|
|
1829
|
+
// Render results
|
|
1830
|
+
plRenderResults(document.getElementById('pl-results-' + pid), testRuns);
|
|
1831
|
+
|
|
1832
|
+
// Initialize Monaco editor
|
|
1833
|
+
var editorContainer = document.getElementById('pl-editor-' + pid);
|
|
1834
|
+
if (!editorContainer) return;
|
|
1835
|
+
|
|
1836
|
+
// Determine content to show
|
|
1837
|
+
var content = d.editedContent || d.originalContent || '';
|
|
1838
|
+
var originalContent = d.originalContent || '';
|
|
1839
|
+
panel._plOriginalContent = originalContent;
|
|
1840
|
+
panel._plEditedContent = content;
|
|
1841
|
+
|
|
1842
|
+
if (state.diffMode) {
|
|
1843
|
+
plInitDiffEditor(panel, editorContainer, originalContent, content);
|
|
1844
|
+
} else {
|
|
1845
|
+
plInitEditor(panel, editorContainer, content);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
function plInitEditor(panel, container, content) {
|
|
1850
|
+
loadMonaco().then(function() {
|
|
1851
|
+
if (panel._plEditor || panel._plDiffEditor) return; // already initialized
|
|
1852
|
+
var editor = createMonacoEditor(container, {
|
|
1853
|
+
value: content,
|
|
1854
|
+
language: 'markdown',
|
|
1855
|
+
wordWrap: 'on',
|
|
1856
|
+
readOnly: false,
|
|
1857
|
+
});
|
|
1858
|
+
panel._plEditor = editor;
|
|
1859
|
+
|
|
1860
|
+
// Track edits
|
|
1861
|
+
editor.onDidChangeModelContent(function() {
|
|
1862
|
+
panel._plEditedContent = editor.getValue();
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1865
|
+
// Keyboard shortcuts
|
|
1866
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function() {
|
|
1867
|
+
plRunTest(panel.panelId);
|
|
1868
|
+
});
|
|
1869
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function() {
|
|
1870
|
+
// Save draft — prevent default, content is auto-tracked
|
|
1871
|
+
});
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
function plInitDiffEditor(panel, container, original, modified) {
|
|
1876
|
+
loadMonaco().then(function() {
|
|
1877
|
+
if (panel._plEditor || panel._plDiffEditor) return;
|
|
1878
|
+
var originalModel = monaco.editor.createModel(original, 'markdown');
|
|
1879
|
+
var modifiedModel = monaco.editor.createModel(modified, 'markdown');
|
|
1880
|
+
panel._plOriginalModel = originalModel;
|
|
1881
|
+
panel._plModifiedModel = modifiedModel;
|
|
1882
|
+
|
|
1883
|
+
var diffEditor = monaco.editor.createDiffEditor(container, {
|
|
1884
|
+
theme: currentMonacoTheme(),
|
|
1885
|
+
automaticLayout: true,
|
|
1886
|
+
readOnly: false,
|
|
1887
|
+
renderSideBySide: true,
|
|
1888
|
+
minimap: { enabled: false },
|
|
1889
|
+
scrollBeyondLastLine: false,
|
|
1890
|
+
fontSize: 13,
|
|
1891
|
+
fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace",
|
|
1892
|
+
lineHeight: 20,
|
|
1893
|
+
scrollbar: { verticalScrollbarSize: 5, horizontalScrollbarSize: 5 },
|
|
1894
|
+
overviewRulerLanes: 0,
|
|
1895
|
+
padding: { top: 8, bottom: 8 },
|
|
1896
|
+
});
|
|
1897
|
+
diffEditor.setModel({ original: originalModel, modified: modifiedModel });
|
|
1898
|
+
panel._plDiffEditor = diffEditor;
|
|
1899
|
+
|
|
1900
|
+
// Track edits on modified side
|
|
1901
|
+
modifiedModel.onDidChangeContent(function() {
|
|
1902
|
+
panel._plEditedContent = modifiedModel.getValue();
|
|
1903
|
+
});
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
function plSelectPrompt(panelId) {
|
|
1908
|
+
var p = openPanels[panelId];
|
|
1909
|
+
if (!p || !p.rawData) return;
|
|
1910
|
+
var sel = document.getElementById('pl-select-' + panelId);
|
|
1911
|
+
if (!sel) return;
|
|
1912
|
+
var promptName = sel.value;
|
|
1913
|
+
plLoadPrompt(panelId, promptName);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
function plLoadPrompt(panelId, promptName) {
|
|
1917
|
+
var p = openPanels[panelId];
|
|
1918
|
+
if (!p || !p.rawData) return;
|
|
1919
|
+
var d = p.rawData;
|
|
1920
|
+
|
|
1921
|
+
// Find the prompt in data
|
|
1922
|
+
var prompt = null;
|
|
1923
|
+
for (var i = 0; i < (d.prompts || []).length; i++) {
|
|
1924
|
+
if (d.prompts[i].name === promptName) { prompt = d.prompts[i]; break; }
|
|
1925
|
+
}
|
|
1926
|
+
if (!prompt) return;
|
|
1927
|
+
|
|
1928
|
+
// Build the file path
|
|
1929
|
+
var promptsDir = d.promptsDir || 'prompts';
|
|
1930
|
+
var filePath = (d.projectDir ? d.projectDir + '/' : '') + promptsDir + '/' + prompt.filename;
|
|
1931
|
+
|
|
1932
|
+
authFetch('/browse/file/text?path=' + encodeURIComponent(filePath))
|
|
1933
|
+
.then(function(res) { return res.ok ? res.text() : Promise.reject('File not found'); })
|
|
1934
|
+
.then(function(content) {
|
|
1935
|
+
p._plOriginalContent = content;
|
|
1936
|
+
p._plEditedContent = content;
|
|
1937
|
+
d.activePrompt = promptName;
|
|
1938
|
+
d.originalContent = content;
|
|
1939
|
+
d.editedContent = content;
|
|
1940
|
+
|
|
1941
|
+
// Update editor
|
|
1942
|
+
if (p._plEditor) {
|
|
1943
|
+
p._plEditor.setValue(content);
|
|
1944
|
+
} else if (p._plDiffEditor) {
|
|
1945
|
+
// Re-render in diff mode with new content
|
|
1946
|
+
cleanupPromptLab(p);
|
|
1947
|
+
var container = document.getElementById('pl-editor-' + panelId);
|
|
1948
|
+
if (container) {
|
|
1949
|
+
container.innerHTML = '';
|
|
1950
|
+
plInitDiffEditor(p, container, content, content);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
// Re-render sidebar to update active state
|
|
1955
|
+
var sidebar = document.getElementById('pl-sidebar-' + panelId);
|
|
1956
|
+
if (sidebar) {
|
|
1957
|
+
plRenderVersionSidebar(sidebar, d.prompts || [], promptName, d.syncHistory || [], panelId);
|
|
1958
|
+
}
|
|
1959
|
+
})
|
|
1960
|
+
.catch(function(err) {
|
|
1961
|
+
console.warn('plLoadPrompt failed:', err);
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function plToggleDiff(panelId) {
|
|
1966
|
+
var p = openPanels[panelId];
|
|
1967
|
+
if (!p) return;
|
|
1968
|
+
p._plState = p._plState || { diffMode: false, resultsCollapsed: false };
|
|
1969
|
+
p._plState.diffMode = !p._plState.diffMode;
|
|
1970
|
+
|
|
1971
|
+
// Capture current content before cleanup
|
|
1972
|
+
var editedContent = p._plEditedContent || '';
|
|
1973
|
+
var originalContent = p._plOriginalContent || '';
|
|
1974
|
+
cleanupPromptLab(p);
|
|
1975
|
+
|
|
1976
|
+
var container = document.getElementById('pl-editor-' + panelId);
|
|
1977
|
+
if (!container) return;
|
|
1978
|
+
container.innerHTML = '';
|
|
1979
|
+
|
|
1980
|
+
if (p._plState.diffMode) {
|
|
1981
|
+
plInitDiffEditor(p, container, originalContent, editedContent);
|
|
1982
|
+
} else {
|
|
1983
|
+
plInitEditor(p, container, editedContent);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// Update diff button active state
|
|
1987
|
+
var toolbar = container.closest('.prompt-lab');
|
|
1988
|
+
if (toolbar) {
|
|
1989
|
+
var btns = toolbar.querySelectorAll('.pl-btn');
|
|
1990
|
+
for (var i = 0; i < btns.length; i++) {
|
|
1991
|
+
if (btns[i].textContent === 'Diff') {
|
|
1992
|
+
btns[i].classList.toggle('active', p._plState.diffMode);
|
|
1993
|
+
break;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
function plRunTest(panelId) {
|
|
2000
|
+
var p = openPanels[panelId];
|
|
2001
|
+
if (!p || !p.rawData) return;
|
|
2002
|
+
var d = p.rawData;
|
|
2003
|
+
if (d.status !== 'idle') return;
|
|
2004
|
+
|
|
2005
|
+
var promptName = d.activePrompt || '';
|
|
2006
|
+
var content = p._plEditedContent || '';
|
|
2007
|
+
if (!promptName || !content) return;
|
|
2008
|
+
|
|
2009
|
+
var message = '[PROMPT-LAB] Run evaluation for prompt "' + promptName + '" with content:\n---\n' + content + '\n---';
|
|
2010
|
+
|
|
2011
|
+
// Find active session
|
|
2012
|
+
var sessionId = activeSessionId;
|
|
2013
|
+
|
|
2014
|
+
authFetch('/chat/send', {
|
|
2015
|
+
method: 'POST',
|
|
2016
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2017
|
+
body: JSON.stringify({ message: message, sessionId: sessionId })
|
|
2018
|
+
}).then(function(res) {
|
|
2019
|
+
if (!res.ok) console.warn('plRunTest: chat/send failed');
|
|
2020
|
+
}).catch(function(err) {
|
|
2021
|
+
console.warn('plRunTest error:', err);
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
// Optimistic status update
|
|
2025
|
+
var badge = document.querySelector('#pl-select-' + panelId);
|
|
2026
|
+
if (badge) {
|
|
2027
|
+
var statusBadge = badge.parentNode.querySelector('.pl-status-badge');
|
|
2028
|
+
if (statusBadge) {
|
|
2029
|
+
statusBadge.className = 'pl-status-badge testing';
|
|
2030
|
+
statusBadge.textContent = 'testing';
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function plSyncToProject(panelId) {
|
|
2036
|
+
var p = openPanels[panelId];
|
|
2037
|
+
if (!p || !p.rawData) return;
|
|
2038
|
+
var d = p.rawData;
|
|
2039
|
+
if (d.status !== 'idle') return;
|
|
2040
|
+
|
|
2041
|
+
var promptName = d.activePrompt || '';
|
|
2042
|
+
var content = p._plEditedContent || '';
|
|
2043
|
+
if (!promptName || !content) return;
|
|
2044
|
+
|
|
2045
|
+
var message = '[PROMPT-LAB] Sync prompt "' + promptName + '" to project. Edited content:\n---\n' + content + '\n---\nWrite to prompts/ with incremented version, update project-specs.md, and commit.';
|
|
2046
|
+
|
|
2047
|
+
var sessionId = activeSessionId;
|
|
2048
|
+
|
|
2049
|
+
authFetch('/chat/send', {
|
|
2050
|
+
method: 'POST',
|
|
2051
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2052
|
+
body: JSON.stringify({ message: message, sessionId: sessionId })
|
|
2053
|
+
}).then(function(res) {
|
|
2054
|
+
if (!res.ok) console.warn('plSyncToProject: chat/send failed');
|
|
2055
|
+
}).catch(function(err) {
|
|
2056
|
+
console.warn('plSyncToProject error:', err);
|
|
2057
|
+
});
|
|
2058
|
+
|
|
2059
|
+
// Optimistic status update
|
|
2060
|
+
var badge = document.querySelector('#pl-select-' + panelId);
|
|
2061
|
+
if (badge) {
|
|
2062
|
+
var statusBadge = badge.parentNode.querySelector('.pl-status-badge');
|
|
2063
|
+
if (statusBadge) {
|
|
2064
|
+
statusBadge.className = 'pl-status-badge syncing';
|
|
2065
|
+
statusBadge.textContent = 'syncing';
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
function plRenderResults(container, testRuns) {
|
|
2071
|
+
if (!container) return;
|
|
2072
|
+
if (!testRuns || testRuns.length === 0) {
|
|
2073
|
+
container.innerHTML = '<div class="pl-results-header"><span class="pl-results-title">Test Results</span></div>' +
|
|
2074
|
+
'<div style="font-size:11px;color:#6a6a88">No test runs yet. Edit a prompt and click "Run Test".</div>';
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
// Show latest run
|
|
2079
|
+
var run = testRuns[testRuns.length - 1];
|
|
2080
|
+
var html = '<div class="pl-results-header">';
|
|
2081
|
+
html += '<span class="pl-results-title">Test Results — ' + esc(run.promptName || '') + '</span>';
|
|
2082
|
+
html += '<span style="font-size:10px;color:#6a6a88">' + esc(run.timestamp || '') + '</span>';
|
|
2083
|
+
html += '</div>';
|
|
2084
|
+
|
|
2085
|
+
// Status
|
|
2086
|
+
if (run.status === 'running') {
|
|
2087
|
+
html += '<div style="font-size:11px;color:#c8a84a">Evaluation running...</div>';
|
|
2088
|
+
container.innerHTML = html;
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
if (run.status === 'error') {
|
|
2092
|
+
html += '<div style="font-size:11px;color:#c84a4a">Error: ' + esc(run.error || 'Unknown error') + '</div>';
|
|
2093
|
+
container.innerHTML = html;
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// Metrics cards
|
|
2098
|
+
var metrics = run.metrics || {};
|
|
2099
|
+
var metricKeys = Object.keys(metrics);
|
|
2100
|
+
if (metricKeys.length > 0) {
|
|
2101
|
+
html += '<div class="pl-metrics">';
|
|
2102
|
+
for (var i = 0; i < metricKeys.length; i++) {
|
|
2103
|
+
var key = metricKeys[i];
|
|
2104
|
+
var val = metrics[key];
|
|
2105
|
+
var valStr = typeof val === 'object' ? JSON.stringify(val) : String(val);
|
|
2106
|
+
html += '<div class="pl-metric-card">';
|
|
2107
|
+
html += '<div class="pl-metric-name">' + esc(key) + '</div>';
|
|
2108
|
+
html += '<div class="pl-metric-value">' + esc(valStr) + '</div>';
|
|
2109
|
+
html += '</div>';
|
|
2110
|
+
}
|
|
2111
|
+
html += '</div>';
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// Sample outputs
|
|
2115
|
+
var samples = run.sampleOutputs || [];
|
|
2116
|
+
if (samples.length > 0) {
|
|
2117
|
+
html += '<div class="pl-samples">';
|
|
2118
|
+
for (var j = 0; j < samples.length && j < 5; j++) {
|
|
2119
|
+
var s = samples[j];
|
|
2120
|
+
html += '<div class="pl-sample-card">';
|
|
2121
|
+
if (s.score != null) html += '<span class="pl-sample-score">' + s.score + '</span>';
|
|
2122
|
+
html += '<div class="pl-sample-label">Input</div>';
|
|
2123
|
+
html += '<div class="pl-sample-text">' + esc(s.input || '') + '</div>';
|
|
2124
|
+
html += '<div class="pl-sample-label" style="margin-top:4px">Output</div>';
|
|
2125
|
+
html += '<div class="pl-sample-text">' + esc(s.output || '') + '</div>';
|
|
2126
|
+
html += '</div>';
|
|
2127
|
+
}
|
|
2128
|
+
html += '</div>';
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
container.innerHTML = html;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
function plRenderVersionSidebar(container, prompts, activePrompt, syncHistory, panelId) {
|
|
2135
|
+
if (!container) return;
|
|
2136
|
+
var html = '<div class="pl-sidebar-title">Prompts</div>';
|
|
2137
|
+
|
|
2138
|
+
for (var i = 0; i < prompts.length; i++) {
|
|
2139
|
+
var pr = prompts[i];
|
|
2140
|
+
var isActive = pr.name === activePrompt;
|
|
2141
|
+
html += '<div class="pl-prompt-item pl-load-prompt' + (isActive ? ' active' : '') + '" data-panel-id="' + esc(panelId) + '" data-prompt-name="' + esc(pr.name) + '">';
|
|
2142
|
+
html += '<div class="pl-prompt-name">' + esc(pr.filename) + '</div>';
|
|
2143
|
+
html += '<div class="pl-prompt-meta">v' + esc(pr.currentVersion) + ' · ' + esc(pr.model || '') + '</div>';
|
|
2144
|
+
html += '</div>';
|
|
2145
|
+
|
|
2146
|
+
// Version list (expanded for active prompt)
|
|
2147
|
+
if (isActive && pr.versions && pr.versions.length > 0) {
|
|
2148
|
+
html += '<div class="pl-version-list">';
|
|
2149
|
+
for (var j = 0; j < pr.versions.length; j++) {
|
|
2150
|
+
var v = pr.versions[j];
|
|
2151
|
+
var isCurrent = v.version === pr.currentVersion;
|
|
2152
|
+
var scoreStr = '';
|
|
2153
|
+
if (v.evaluationScore) {
|
|
2154
|
+
var scoreKeys = Object.keys(v.evaluationScore);
|
|
2155
|
+
if (scoreKeys.length > 0) scoreStr = scoreKeys[0] + ': ' + v.evaluationScore[scoreKeys[0]];
|
|
2156
|
+
}
|
|
2157
|
+
html += '<div class="pl-version-item' + (isCurrent ? ' current' : '') + '">';
|
|
2158
|
+
html += '<span>v' + esc(v.version) + (isCurrent ? ' (current)' : '') + '</span>';
|
|
2159
|
+
html += '<span>' + esc(scoreStr) + '</span>';
|
|
2160
|
+
html += '</div>';
|
|
2161
|
+
}
|
|
2162
|
+
html += '</div>';
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// Sync history
|
|
2167
|
+
if (syncHistory.length > 0) {
|
|
2168
|
+
html += '<div class="pl-sync-history">';
|
|
2169
|
+
html += '<div class="pl-sidebar-title">Sync History</div>';
|
|
2170
|
+
for (var k = syncHistory.length - 1; k >= 0 && k >= syncHistory.length - 5; k--) {
|
|
2171
|
+
var sh = syncHistory[k];
|
|
2172
|
+
html += '<div class="pl-sync-entry">';
|
|
2173
|
+
html += esc(sh.promptName) + ' <span class="version">v' + esc(sh.fromVersion) + ' → v' + esc(sh.toVersion) + '</span>';
|
|
2174
|
+
html += '</div>';
|
|
2175
|
+
}
|
|
2176
|
+
html += '</div>';
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
if (prompts.length === 0) {
|
|
2180
|
+
html += '<div style="padding:12px;font-size:11px;color:#6a6a88">No prompts in project.</div>';
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
container.innerHTML = html;
|
|
2184
|
+
|
|
2185
|
+
container.querySelectorAll('.pl-load-prompt').forEach(function(el) {
|
|
2186
|
+
el.addEventListener('click', function() { plLoadPrompt(el.dataset.panelId, el.dataset.promptName); });
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
// ─── PR Review Panel ──────────────────────────────────────────────────────────
|
|
2191
|
+
|
|
2192
|
+
function renderPRReviewPanel(container, panel) {
|
|
2193
|
+
var data = panel.rawData;
|
|
2194
|
+
|
|
2195
|
+
// Ensure addressed map exists on panel object (persists across re-renders)
|
|
2196
|
+
if (!panel._addressedMap) panel._addressedMap = {};
|
|
2197
|
+
|
|
2198
|
+
if (!data) {
|
|
2199
|
+
container.innerHTML = '<div class="pr-review"><div class="pr-empty"><h3>Loading PR comments...</h3><p>Fetching from GitHub.</p></div></div>';
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
if (data.ghMissing) {
|
|
2204
|
+
container.innerHTML =
|
|
2205
|
+
'<div class="pr-review"><div class="pr-gh-missing">' +
|
|
2206
|
+
'<h3>GitHub CLI not installed</h3>' +
|
|
2207
|
+
'<p>Install it with <code>brew install gh</code> and authenticate with <code>gh auth login</code>.</p>' +
|
|
2208
|
+
'</div></div>';
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
if (data.error) {
|
|
2213
|
+
container.innerHTML =
|
|
2214
|
+
'<div class="pr-review"><div class="pr-empty"><h3>Could not load PR</h3><p>' + esc(data.error) + '</p></div></div>';
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
var threads = data.threads || [];
|
|
2219
|
+
var generalComments = data.generalComments || [];
|
|
2220
|
+
var reviews = data.reviews || [];
|
|
2221
|
+
|
|
2222
|
+
// Count addressed
|
|
2223
|
+
var addressedCount = Object.values(panel._addressedMap).filter(Boolean).length;
|
|
2224
|
+
var totalThreads = threads.length;
|
|
2225
|
+
var pct = totalThreads > 0 ? Math.round((addressedCount / totalThreads) * 100) : 0;
|
|
2226
|
+
|
|
2227
|
+
// Determine review decision display
|
|
2228
|
+
var prInfo = panel._prInfo || {};
|
|
2229
|
+
var reviewDecision = prInfo.reviewDecision || '';
|
|
2230
|
+
var decisionClass = reviewDecision === 'APPROVED' ? 'approved' :
|
|
2231
|
+
reviewDecision === 'CHANGES_REQUESTED' ? 'changes' :
|
|
2232
|
+
reviewDecision === 'REVIEW_REQUIRED' ? 'review-required' : 'pending';
|
|
2233
|
+
var decisionLabel = reviewDecision === 'APPROVED' ? 'Approved' :
|
|
2234
|
+
reviewDecision === 'CHANGES_REQUESTED' ? 'Changes Requested' :
|
|
2235
|
+
reviewDecision === 'REVIEW_REQUIRED' ? 'Review Required' : 'Pending';
|
|
2236
|
+
|
|
2237
|
+
var html = '<div class="pr-review">';
|
|
2238
|
+
|
|
2239
|
+
// Header
|
|
2240
|
+
html += '<div class="pr-header">';
|
|
2241
|
+
html += '<div class="pr-header-title">';
|
|
2242
|
+
if (prInfo.url) {
|
|
2243
|
+
html += '<a href="' + esc(prInfo.url) + '" target="_blank" rel="noopener">PR #' + esc(String(data.pr || '')) + '</a>';
|
|
2244
|
+
if (prInfo.title) html += ' — ' + esc(prInfo.title);
|
|
2245
|
+
} else {
|
|
2246
|
+
html += 'PR #' + esc(String(data.pr || ''));
|
|
2247
|
+
}
|
|
2248
|
+
html += '</div>';
|
|
2249
|
+
html += '<div class="pr-header-meta">';
|
|
2250
|
+
if (reviewDecision) {
|
|
2251
|
+
html += '<span class="pr-review-badge ' + decisionClass + '">' + esc(decisionLabel) + '</span>';
|
|
2252
|
+
}
|
|
2253
|
+
html += '<button class="pr-btn pr-refresh-btn" data-panel-id="' + esc(panel.panelId) + '">Refresh</button>';
|
|
2254
|
+
html += '</div>';
|
|
2255
|
+
html += '</div>';
|
|
2256
|
+
|
|
2257
|
+
// Progress bar
|
|
2258
|
+
if (totalThreads > 0) {
|
|
2259
|
+
html += '<div class="pr-progress-bar">';
|
|
2260
|
+
html += '<span class="pr-progress-label">' + addressedCount + ' / ' + totalThreads + ' addressed</span>';
|
|
2261
|
+
html += '<div class="pr-progress-track"><div class="pr-progress-fill" style="width:' + pct + '%"></div></div>';
|
|
2262
|
+
html += '</div>';
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
html += '<div class="pr-content" id="pr-content-' + esc(panel.panelId) + '">';
|
|
2266
|
+
|
|
2267
|
+
// Review summaries (non-COMMENTED reviews with a body)
|
|
2268
|
+
var summaryReviews = reviews.filter(function(r) { return r.body && r.body.trim(); });
|
|
2269
|
+
if (summaryReviews.length > 0) {
|
|
2270
|
+
html += '<div class="pr-section-header">Reviews</div>';
|
|
2271
|
+
for (var ri = 0; ri < summaryReviews.length; ri++) {
|
|
2272
|
+
var rv = summaryReviews[ri];
|
|
2273
|
+
var rvClass = rv.state === 'APPROVED' ? 'approved' : rv.state === 'CHANGES_REQUESTED' ? 'changes' : 'pending';
|
|
2274
|
+
html += '<div class="pr-review-card">';
|
|
2275
|
+
html += '<div class="pr-review-card-header">';
|
|
2276
|
+
html += '<span class="pr-review-badge ' + rvClass + '">' + esc(rv.state.replace(/_/g, ' ')) + '</span>';
|
|
2277
|
+
html += '<span class="pr-review-card-author">' + esc(rv.author) + '</span>';
|
|
2278
|
+
html += '<span class="pr-review-card-date">' + esc(formatPRDate(rv.submittedAt)) + '</span>';
|
|
2279
|
+
html += '</div>';
|
|
2280
|
+
if (rv.body) html += '<div class="pr-review-card-body">' + renderPRMarkdown(rv.body) + '</div>';
|
|
2281
|
+
html += '</div>';
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// Inline review threads
|
|
2286
|
+
if (threads.length > 0) {
|
|
2287
|
+
html += '<div class="pr-section-header">Review Threads (' + threads.length + ')</div>';
|
|
2288
|
+
for (var ti = 0; ti < threads.length; ti++) {
|
|
2289
|
+
var thread = threads[ti];
|
|
2290
|
+
var isAddressed = !!panel._addressedMap[thread.threadId];
|
|
2291
|
+
var isCollapsed = panel._collapsedMap && panel._collapsedMap[thread.threadId];
|
|
2292
|
+
var threadClass = 'pr-thread-card' + (isAddressed ? ' addressed' : '') + (isCollapsed ? ' collapsed' : '');
|
|
2293
|
+
html += '<div class="' + threadClass + '" id="pr-thread-' + esc(thread.threadId) + '" data-thread-id="' + esc(thread.threadId) + '">';
|
|
2294
|
+
|
|
2295
|
+
// Thread header (file + line, clickable to open file)
|
|
2296
|
+
html += '<div class="pr-thread-header pr-toggle-thread" data-panel-id="' + esc(panel.panelId) + '" data-thread-id="' + esc(thread.threadId) + '">';
|
|
2297
|
+
html += '<span class="pr-thread-file pr-open-file" title="Open file at line ' + thread.line + '" data-file="' + esc(thread.file) + '" data-line="' + esc(String(thread.line || '')) + '">' + esc(thread.file) + '</span>';
|
|
2298
|
+
if (thread.line) html += '<span class="pr-thread-line">:' + thread.line + '</span>';
|
|
2299
|
+
html += '<span class="pr-thread-count">' + thread.comments.length + ' comment' + (thread.comments.length !== 1 ? 's' : '') + '</span>';
|
|
2300
|
+
html += '<div class="pr-addressed-toggle pr-stop-propagation">';
|
|
2301
|
+
html += '<input type="checkbox" class="pr-addressed-checkbox" id="pr-addr-' + esc(thread.threadId) + '" ' + (isAddressed ? 'checked' : '') +
|
|
2302
|
+
' data-panel-id="' + esc(panel.panelId) + '" data-thread-id="' + esc(thread.threadId) + '">';
|
|
2303
|
+
html += '<label class="pr-addressed-label" for="pr-addr-' + esc(thread.threadId) + '">Done</label>';
|
|
2304
|
+
html += '</div>';
|
|
2305
|
+
html += '<span class="pr-thread-collapse-icon">▼</span>';
|
|
2306
|
+
html += '</div>';
|
|
2307
|
+
|
|
2308
|
+
// Thread body
|
|
2309
|
+
html += '<div class="pr-thread-body">';
|
|
2310
|
+
|
|
2311
|
+
// Diff hunk
|
|
2312
|
+
if (thread.diffHunk) {
|
|
2313
|
+
html += '<div class="pr-diff-hunk">';
|
|
2314
|
+
var hunkLines = thread.diffHunk.split('\n');
|
|
2315
|
+
for (var hl = 0; hl < hunkLines.length; hl++) {
|
|
2316
|
+
var ln = hunkLines[hl];
|
|
2317
|
+
var lnClass = ln.startsWith('+') ? 'add' : ln.startsWith('-') ? 'del' : ln.startsWith('@') ? 'meta' : 'ctx';
|
|
2318
|
+
html += '<span class="pr-diff-hunk-line ' + lnClass + '">' + esc(ln) + '</span>';
|
|
2319
|
+
}
|
|
2320
|
+
html += '</div>';
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// Comments
|
|
2324
|
+
html += '<div class="pr-comment-list">';
|
|
2325
|
+
for (var ci = 0; ci < thread.comments.length; ci++) {
|
|
2326
|
+
var comment = thread.comments[ci];
|
|
2327
|
+
html += '<div class="pr-comment' + (comment.isReply ? ' reply' : '') + '">';
|
|
2328
|
+
html += '<div class="pr-comment-header">';
|
|
2329
|
+
html += '<span class="pr-comment-author">' + esc(comment.author) + '</span>';
|
|
2330
|
+
html += '<span class="pr-comment-date">' + esc(formatPRDate(comment.createdAt)) + '</span>';
|
|
2331
|
+
html += '</div>';
|
|
2332
|
+
html += '<div class="pr-comment-body">' + renderPRMarkdown(comment.body) + '</div>';
|
|
2333
|
+
html += '</div>';
|
|
2334
|
+
}
|
|
2335
|
+
html += '</div>';
|
|
2336
|
+
|
|
2337
|
+
html += '</div>'; // thread body
|
|
2338
|
+
html += '</div>'; // thread card
|
|
2339
|
+
}
|
|
2340
|
+
} else {
|
|
2341
|
+
html += '<div class="pr-empty"><h3>No review threads</h3><p>No inline code review comments found on this PR.</p></div>';
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
// General comments
|
|
2345
|
+
if (generalComments.length > 0) {
|
|
2346
|
+
html += '<div class="pr-section-header">PR Comments (' + generalComments.length + ')</div>';
|
|
2347
|
+
for (var gi = 0; gi < generalComments.length; gi++) {
|
|
2348
|
+
var gc = generalComments[gi];
|
|
2349
|
+
html += '<div class="pr-general-comment">';
|
|
2350
|
+
html += '<div class="pr-comment-header">';
|
|
2351
|
+
html += '<span class="pr-comment-author">' + esc(gc.author) + '</span>';
|
|
2352
|
+
html += '<span class="pr-comment-date">' + esc(formatPRDate(gc.createdAt)) + '</span>';
|
|
2353
|
+
html += '</div>';
|
|
2354
|
+
html += '<div class="pr-comment-body">' + renderPRMarkdown(gc.body) + '</div>';
|
|
2355
|
+
html += '</div>';
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
html += '</div>'; // pr-content
|
|
2360
|
+
html += '</div>'; // pr-review
|
|
2361
|
+
|
|
2362
|
+
container.innerHTML = html;
|
|
2363
|
+
|
|
2364
|
+
container.querySelectorAll('.pr-refresh-btn').forEach(function(btn) {
|
|
2365
|
+
btn.addEventListener('click', function() { refreshPRReviewPanel(btn.dataset.panelId); });
|
|
2366
|
+
});
|
|
2367
|
+
container.querySelectorAll('.pr-toggle-thread').forEach(function(el) {
|
|
2368
|
+
el.addEventListener('click', function() { prToggleThread(el.dataset.panelId, el.dataset.threadId); });
|
|
2369
|
+
});
|
|
2370
|
+
container.querySelectorAll('.pr-open-file').forEach(function(el) {
|
|
2371
|
+
el.addEventListener('click', function(ev) {
|
|
2372
|
+
ev.stopPropagation();
|
|
2373
|
+
var lineStr = el.dataset.line;
|
|
2374
|
+
var line = lineStr ? parseInt(lineStr, 10) : 0;
|
|
2375
|
+
prOpenFileLine(el.dataset.file, line);
|
|
2376
|
+
});
|
|
2377
|
+
});
|
|
2378
|
+
container.querySelectorAll('.pr-stop-propagation').forEach(function(el) {
|
|
2379
|
+
el.addEventListener('click', function(ev) { ev.stopPropagation(); });
|
|
2380
|
+
});
|
|
2381
|
+
container.querySelectorAll('.pr-addressed-checkbox').forEach(function(el) {
|
|
2382
|
+
el.addEventListener('change', function() { prToggleAddressed(el.dataset.panelId, el.dataset.threadId, el.checked); });
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
function prToggleThread(panelId, threadId) {
|
|
2387
|
+
var p = openPanels[panelId];
|
|
2388
|
+
if (!p) return;
|
|
2389
|
+
if (!p._collapsedMap) p._collapsedMap = {};
|
|
2390
|
+
p._collapsedMap[threadId] = !p._collapsedMap[threadId];
|
|
2391
|
+
var card = document.getElementById('pr-thread-' + threadId);
|
|
2392
|
+
if (card) card.classList.toggle('collapsed', !!p._collapsedMap[threadId]);
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
function prToggleAddressed(panelId, threadId, checked) {
|
|
2396
|
+
var p = openPanels[panelId];
|
|
2397
|
+
if (!p) return;
|
|
2398
|
+
if (!p._addressedMap) p._addressedMap = {};
|
|
2399
|
+
p._addressedMap[threadId] = checked;
|
|
2400
|
+
// Update addressed class on the card
|
|
2401
|
+
var card = document.getElementById('pr-thread-' + threadId);
|
|
2402
|
+
if (card) card.classList.toggle('addressed', checked);
|
|
2403
|
+
// Recompute progress bar
|
|
2404
|
+
var threads = (p.rawData && p.rawData.threads) || [];
|
|
2405
|
+
var total = threads.length;
|
|
2406
|
+
var addressed = Object.values(p._addressedMap).filter(Boolean).length;
|
|
2407
|
+
var pct = total > 0 ? Math.round((addressed / total) * 100) : 0;
|
|
2408
|
+
var fill = document.querySelector('#pr-content-' + CSS.escape(panelId) + ' ~ .pr-progress-bar .pr-progress-fill') ||
|
|
2409
|
+
document.querySelector('.pr-progress-fill');
|
|
2410
|
+
if (fill) fill.style.width = pct + '%';
|
|
2411
|
+
var label = document.querySelector('.pr-progress-label');
|
|
2412
|
+
if (label) label.textContent = addressed + ' / ' + total + ' addressed';
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
function prOpenFileLine(filePath, line) {
|
|
2416
|
+
if (typeof openFileFromExplorer !== 'function') return;
|
|
2417
|
+
openFileFromExplorer(filePath).then(function() {
|
|
2418
|
+
setTimeout(function() {
|
|
2419
|
+
var inst = typeof activeMonacoInstance !== 'undefined' ? activeMonacoInstance : null;
|
|
2420
|
+
if (inst && line) {
|
|
2421
|
+
inst.revealLineInCenter(line);
|
|
2422
|
+
inst.setPosition({ lineNumber: line, column: 1 });
|
|
2423
|
+
inst.focus();
|
|
2424
|
+
}
|
|
2425
|
+
}, 200);
|
|
2426
|
+
}).catch(function() {});
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
function refreshPRReviewPanel(panelId) {
|
|
2430
|
+
var p = openPanels[panelId];
|
|
2431
|
+
if (!p || !p.rawData || !p.rawData.pr) return;
|
|
2432
|
+
var prNum = p.rawData.pr;
|
|
2433
|
+
authFetch('/git/pr-comments?pr=' + prNum).then(function(res) { return res.json(); }).then(function(data) {
|
|
2434
|
+
p.rawData = Object.assign({}, p.rawData, data);
|
|
2435
|
+
var container = document.getElementById('file-rendered-view');
|
|
2436
|
+
if (container) renderPRReviewPanel(container, p);
|
|
2437
|
+
}).catch(function() {});
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
function formatPRDate(iso) {
|
|
2441
|
+
if (!iso) return '';
|
|
2442
|
+
try {
|
|
2443
|
+
var d = new Date(iso);
|
|
2444
|
+
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' +
|
|
2445
|
+
d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
|
2446
|
+
} catch { return iso; }
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
function renderPRMarkdown(text) {
|
|
2450
|
+
if (!text) return '';
|
|
2451
|
+
if (typeof marked !== 'undefined') {
|
|
2452
|
+
try { return marked.parse(text); } catch {}
|
|
2453
|
+
}
|
|
2454
|
+
// Minimal fallback: escape and preserve newlines
|
|
2455
|
+
return '<p>' + esc(text).replace(/\n\n+/g, '</p><p>').replace(/\n/g, '<br>') + '</p>';
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2459
|
+
// Brainstorm panel — live multi-specialist fan-out from Syn
|
|
2460
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2461
|
+
//
|
|
2462
|
+
// Data shape (written by syn/brainstorm.md to
|
|
2463
|
+
// brainstorm/brainstorm_<project>.state.json):
|
|
2464
|
+
//
|
|
2465
|
+
// {
|
|
2466
|
+
// "mode": "brainstorm",
|
|
2467
|
+
// "project": "<slug>",
|
|
2468
|
+
// "created": "<ISO>",
|
|
2469
|
+
// "phase": "intake|gathering|synthesis|facilitation",
|
|
2470
|
+
// "problem": "<initial idea>",
|
|
2471
|
+
// "context": { "environment": ..., "data": ..., "compute": ..., "openness": ... },
|
|
2472
|
+
// "specialists": [
|
|
2473
|
+
// { "name": "data-scientist", "status": "queued|thinking|responded|skipped",
|
|
2474
|
+
// "started_at": "...", "responded_at": "...",
|
|
2475
|
+
// "headline": "...", "ideas_count": 3 }
|
|
2476
|
+
// ],
|
|
2477
|
+
// "synthesis": { "quick_wins": [...], "bold_bets": [...], "wildcards": [...],
|
|
2478
|
+
// "themes": [...], "recommended_start": "...",
|
|
2479
|
+
// "recommendation_rationale": "..." } | null,
|
|
2480
|
+
// "facilitation_log": [ { "ts": "...", "actor": "...", "text": "..." } ],
|
|
2481
|
+
// "outcome": { "decision": "single|multi-workstream", ... } | null
|
|
2482
|
+
// }
|
|
2483
|
+
|
|
2484
|
+
function cleanupBrainstorm(panel) {
|
|
2485
|
+
// No persistent sub-components yet (no Tabulator, no Plotly); placeholder for
|
|
2486
|
+
// symmetry with other panel cleanup helpers.
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
function renderBrainstorm(container, panel) {
|
|
2491
|
+
var d = panel.rawData;
|
|
2492
|
+
if (!d) {
|
|
2493
|
+
container.innerHTML = '<div class="no-file-msg">No brainstorm state available yet.</div>';
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
// Defensive defaults — agent may write state mid-phase before all fields exist.
|
|
2498
|
+
var phase = d.phase || 'intake';
|
|
2499
|
+
var problem = d.problem || '(problem not captured yet)';
|
|
2500
|
+
var specialists = Array.isArray(d.specialists) ? d.specialists : [];
|
|
2501
|
+
var synthesis = d.synthesis || null;
|
|
2502
|
+
var log = Array.isArray(d.facilitation_log) ? d.facilitation_log : [];
|
|
2503
|
+
var outcome = d.outcome || null;
|
|
2504
|
+
var ctx = d.context || {};
|
|
2505
|
+
|
|
2506
|
+
var html = '<div class="brainstorm-panel">';
|
|
2507
|
+
|
|
2508
|
+
// ── Header ──
|
|
2509
|
+
html += '<div class="brain-header">';
|
|
2510
|
+
html += '<div class="brain-title">' + esc(d.project || 'brainstorm') + '</div>';
|
|
2511
|
+
html += '<div class="brain-phase-pill brain-phase-' + esc(phase) + '">' + esc(phase) + '</div>';
|
|
2512
|
+
html += '</div>';
|
|
2513
|
+
|
|
2514
|
+
html += '<div class="brain-problem"><span class="brain-problem-label">Problem</span>'
|
|
2515
|
+
+ '<div class="brain-problem-body">' + esc(problem) + '</div></div>';
|
|
2516
|
+
|
|
2517
|
+
// ── Context strip (compact) ──
|
|
2518
|
+
var ctxItems = [];
|
|
2519
|
+
if (ctx.environment) ctxItems.push(['env', ctx.environment]);
|
|
2520
|
+
if (ctx.data) ctxItems.push(['data', ctx.data]);
|
|
2521
|
+
if (ctx.compute) ctxItems.push(['compute', ctx.compute]);
|
|
2522
|
+
if (ctx.openness) ctxItems.push(['openness', ctx.openness]);
|
|
2523
|
+
if (ctxItems.length > 0) {
|
|
2524
|
+
html += '<div class="brain-context-strip">';
|
|
2525
|
+
for (var c = 0; c < ctxItems.length; c++) {
|
|
2526
|
+
html += '<div class="brain-context-item">'
|
|
2527
|
+
+ '<span class="brain-context-key">' + esc(ctxItems[c][0]) + '</span>'
|
|
2528
|
+
+ '<span class="brain-context-val">' + esc(String(ctxItems[c][1])) + '</span>'
|
|
2529
|
+
+ '</div>';
|
|
2530
|
+
}
|
|
2531
|
+
html += '</div>';
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// ── Specialist progress bar ──
|
|
2535
|
+
var total = specialists.length;
|
|
2536
|
+
var done = specialists.filter(function(s) { return s.status === 'responded'; }).length;
|
|
2537
|
+
var pending = specialists.filter(function(s) { return s.status === 'thinking'; }).length;
|
|
2538
|
+
if (total > 0) {
|
|
2539
|
+
html += '<div class="brain-progress">';
|
|
2540
|
+
html += '<div class="brain-progress-label">'
|
|
2541
|
+
+ '<span><strong>' + done + '</strong> / ' + total + ' shards checked in</span>'
|
|
2542
|
+
+ (pending > 0 ? '<span class="brain-progress-pending">' + pending + ' thinking…</span>' : '')
|
|
2543
|
+
+ '</div>';
|
|
2544
|
+
html += '<div class="brain-progress-bar">';
|
|
2545
|
+
for (var i = 0; i < total; i++) {
|
|
2546
|
+
var s = specialists[i];
|
|
2547
|
+
var cls = 'brain-prog-seg ' + (s.status || 'queued');
|
|
2548
|
+
html += '<div class="' + cls + '" title="' + esc(s.name || '') + ' — ' + esc(s.status || '') + '"></div>';
|
|
2549
|
+
}
|
|
2550
|
+
html += '</div></div>';
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
// ── Specialist grid ──
|
|
2554
|
+
html += '<div class="brain-section-label">Specialist input</div>';
|
|
2555
|
+
if (specialists.length === 0) {
|
|
2556
|
+
html += '<div class="brain-empty">Waiting for Syn to spawn the chorus…</div>';
|
|
2557
|
+
} else {
|
|
2558
|
+
html += '<div class="brain-specialist-grid">';
|
|
2559
|
+
for (var si = 0; si < specialists.length; si++) {
|
|
2560
|
+
var sp = specialists[si];
|
|
2561
|
+
var status = sp.status || 'queued';
|
|
2562
|
+
html += '<div class="brain-card brain-card-' + esc(status) + '">';
|
|
2563
|
+
html += '<div class="brain-card-head">';
|
|
2564
|
+
html += '<span class="brain-card-name">' + esc(sp.name || '') + '</span>';
|
|
2565
|
+
html += '<span class="brain-card-status">';
|
|
2566
|
+
if (status === 'thinking') html += '<span class="brain-spinner"></span>';
|
|
2567
|
+
html += esc(status);
|
|
2568
|
+
html += '</span>';
|
|
2569
|
+
html += '</div>';
|
|
2570
|
+
if (sp.headline) {
|
|
2571
|
+
html += '<div class="brain-card-headline">' + esc(sp.headline) + '</div>';
|
|
2572
|
+
} else if (status === 'thinking') {
|
|
2573
|
+
html += '<div class="brain-card-headline brain-card-pending">thinking…</div>';
|
|
2574
|
+
} else if (status === 'queued') {
|
|
2575
|
+
html += '<div class="brain-card-headline brain-card-pending">queued</div>';
|
|
2576
|
+
}
|
|
2577
|
+
if (typeof sp.ideas_count === 'number' && sp.ideas_count > 0) {
|
|
2578
|
+
html += '<div class="brain-card-meta">' + sp.ideas_count + ' idea'
|
|
2579
|
+
+ (sp.ideas_count === 1 ? '' : 's') + '</div>';
|
|
2580
|
+
}
|
|
2581
|
+
html += '</div>';
|
|
2582
|
+
}
|
|
2583
|
+
html += '</div>';
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// ── Synthesis (appears when phase >= synthesis) ──
|
|
2587
|
+
if (synthesis) {
|
|
2588
|
+
html += '<div class="brain-section-label">Synthesis</div>';
|
|
2589
|
+
html += '<div class="brain-synthesis">';
|
|
2590
|
+
html += brainSynthBucket('Quick wins', 'quick-wins', synthesis.quick_wins);
|
|
2591
|
+
html += brainSynthBucket('Bold bets', 'bold-bets', synthesis.bold_bets);
|
|
2592
|
+
html += brainSynthBucket('Wildcards', 'wildcards', synthesis.wildcards);
|
|
2593
|
+
if (Array.isArray(synthesis.themes) && synthesis.themes.length > 0) {
|
|
2594
|
+
html += '<div class="brain-themes"><span class="brain-themes-label">Themes:</span> ';
|
|
2595
|
+
html += synthesis.themes.map(function(t) {
|
|
2596
|
+
return '<span class="brain-theme-chip">' + esc(t) + '</span>';
|
|
2597
|
+
}).join('');
|
|
2598
|
+
html += '</div>';
|
|
2599
|
+
}
|
|
2600
|
+
if (synthesis.recommended_start) {
|
|
2601
|
+
html += '<div class="brain-recommend">';
|
|
2602
|
+
html += '<div class="brain-recommend-label">Recommended starting point</div>';
|
|
2603
|
+
html += '<div class="brain-recommend-title">' + esc(synthesis.recommended_start) + '</div>';
|
|
2604
|
+
if (synthesis.recommendation_rationale) {
|
|
2605
|
+
html += '<div class="brain-recommend-rationale">'
|
|
2606
|
+
+ esc(synthesis.recommendation_rationale) + '</div>';
|
|
2607
|
+
}
|
|
2608
|
+
html += '</div>';
|
|
2609
|
+
}
|
|
2610
|
+
html += '</div>';
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
// ── Facilitation log (Phase 3) ──
|
|
2614
|
+
if (log.length > 0) {
|
|
2615
|
+
html += '<div class="brain-section-label">Facilitation log</div>';
|
|
2616
|
+
html += '<div class="brain-log">';
|
|
2617
|
+
for (var li = log.length - 1; li >= 0; li--) {
|
|
2618
|
+
var entry = log[li];
|
|
2619
|
+
html += '<div class="brain-log-entry">';
|
|
2620
|
+
html += '<span class="brain-log-actor brain-actor-' + esc(entry.actor || 'syn') + '">'
|
|
2621
|
+
+ esc(entry.actor || '') + '</span>';
|
|
2622
|
+
html += '<span class="brain-log-text">' + esc(entry.text || '') + '</span>';
|
|
2623
|
+
if (entry.ts) {
|
|
2624
|
+
html += '<span class="brain-log-ts">' + esc(brainFormatTs(entry.ts)) + '</span>';
|
|
2625
|
+
}
|
|
2626
|
+
html += '</div>';
|
|
2627
|
+
}
|
|
2628
|
+
html += '</div>';
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// ── Outcome banner ──
|
|
2632
|
+
if (outcome) {
|
|
2633
|
+
html += '<div class="brain-outcome brain-outcome-' + esc(outcome.decision || 'unknown') + '">';
|
|
2634
|
+
html += '<span class="brain-outcome-label">Outcome</span>';
|
|
2635
|
+
if (outcome.decision === 'single') {
|
|
2636
|
+
html += '<span class="brain-outcome-detail">Escalated to <strong>'
|
|
2637
|
+
+ esc(outcome.direction || '?') + '</strong>';
|
|
2638
|
+
if (outcome.target_dir) html += ' → <code>' + esc(outcome.target_dir) + '</code>';
|
|
2639
|
+
html += '</span>';
|
|
2640
|
+
} else if (outcome.decision === 'multi-workstream') {
|
|
2641
|
+
var n = outcome.workstream_count
|
|
2642
|
+
|| (Array.isArray(outcome.workstreams) ? outcome.workstreams.length : 0);
|
|
2643
|
+
html += '<span class="brain-outcome-detail">Multi-workstream — <strong>'
|
|
2644
|
+
+ n + '</strong> workstream' + (n === 1 ? '' : 's') + '</span>';
|
|
2645
|
+
} else {
|
|
2646
|
+
html += '<span class="brain-outcome-detail">' + esc(JSON.stringify(outcome)) + '</span>';
|
|
2647
|
+
}
|
|
2648
|
+
html += '</div>';
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
html += '</div>';
|
|
2652
|
+
container.innerHTML = html;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
function brainSynthBucket(label, cls, items) {
|
|
2656
|
+
if (!Array.isArray(items) || items.length === 0) return '';
|
|
2657
|
+
var html = '<div class="brain-bucket brain-bucket-' + cls + '">';
|
|
2658
|
+
html += '<div class="brain-bucket-label">' + esc(label) + '</div>';
|
|
2659
|
+
html += '<ul class="brain-bucket-list">';
|
|
2660
|
+
for (var i = 0; i < items.length; i++) {
|
|
2661
|
+
var item = items[i];
|
|
2662
|
+
if (typeof item === 'string') {
|
|
2663
|
+
html += '<li><strong>' + esc(item) + '</strong></li>';
|
|
2664
|
+
} else {
|
|
2665
|
+
html += '<li>';
|
|
2666
|
+
html += '<strong>' + esc(item.title || '') + '</strong>';
|
|
2667
|
+
if (item.rationale) html += ' — ' + esc(item.rationale);
|
|
2668
|
+
html += '</li>';
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
html += '</ul></div>';
|
|
2672
|
+
return html;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
function brainFormatTs(iso) {
|
|
2676
|
+
if (!iso) return '';
|
|
2677
|
+
try {
|
|
2678
|
+
var d = new Date(iso);
|
|
2679
|
+
return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
|
2680
|
+
} catch (e) { return iso; }
|
|
2681
|
+
}
|