@agenthippo-ai/agenthippo 0.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/LICENSE +21 -0
- package/bin/agenthippo.js +2 -0
- package/out/agentpack/github.js +547 -0
- package/out/agentpack/index.js +66 -0
- package/out/agentpack/install.js +297 -0
- package/out/agentpack/manager.js +441 -0
- package/out/agentpack/manifest.js +135 -0
- package/out/agentpack/paths.js +475 -0
- package/out/agentpack/publish.js +248 -0
- package/out/agentpack/stores.js +554 -0
- package/out/agentpack/types.js +5 -0
- package/out/analytics/dashboard/artifactManager.js +439 -0
- package/out/analytics/dashboard/fleetDisplayUtils.js +159 -0
- package/out/analytics/dashboard/fleetOrchestrator.js +142 -0
- package/out/analytics/dashboard/fleetRegistry.js +79 -0
- package/out/analytics/dashboard/fleetRowDefaults.js +87 -0
- package/out/analytics/dashboard/fleetScriptAdapter.js +146 -0
- package/out/analytics/dashboard/fleetStatusUtils.js +30 -0
- package/out/analytics/dashboard/fleetTitleUtils.js +68 -0
- package/out/analytics/dashboard/fleetTypes.js +29 -0
- package/out/analytics/dashboard/index.js +17 -0
- package/out/analytics/dashboard/indexingService.js +422 -0
- package/out/analytics/dashboard/localFleetAdapter.js +222 -0
- package/out/analytics/dashboard/performanceMonitor.js +76 -0
- package/out/analytics/dashboard/processManager.js +209 -0
- package/out/analytics/dashboard/provider.js +3885 -0
- package/out/analytics/dashboard/queryRunner.js +994 -0
- package/out/analytics/dashboard/refreshPolicy.js +16 -0
- package/out/analytics/dashboard/scheduledFleetFingerprint.js +22 -0
- package/out/analytics/dashboard/scheduledFleetStorage.js +241 -0
- package/out/analytics/dashboard/scheduledFleetUtils.js +22 -0
- package/out/analytics/dashboard/sessionEventFleetStats.js +118 -0
- package/out/analytics/dashboard/sessionJsonlFleet.js +204 -0
- package/out/analytics/dashboard/teamViewDiscovery.js +106 -0
- package/out/analytics/dashboard/viewsConfigMerge.js +221 -0
- package/out/analytics/dashboard/webview/analytics-panel.html +155 -0
- package/out/analytics/dashboard/webview/codicon.ttf +0 -0
- package/out/analytics/dashboard/webview/index.html +78 -0
- package/out/analytics/dashboard/webview/panel.js +2295 -0
- package/out/analytics/dashboard/webview/runtime-bridge.js +82 -0
- package/out/analytics/dashboard/webview/styles.css +1009 -0
- package/out/analytics/lifecycle.js +43 -0
- package/out/analytics/service.js +1279 -0
- package/out/analytics/sessionEvents.js +231 -0
- package/out/auth/bootstrapApply.js +72 -0
- package/out/auth/brokerClient.js +126 -0
- package/out/auth/callbackUri.js +59 -0
- package/out/auth/callbackUriParse.js +59 -0
- package/out/auth/githubStoreGate.js +70 -0
- package/out/auth/loopbackAuthServer.js +165 -0
- package/out/auth/pkce.js +9 -0
- package/out/auth/provider.js +239 -0
- package/out/auth/sessionStore.js +193 -0
- package/out/auth/signInFlow.js +182 -0
- package/out/auth/types.js +12 -0
- package/out/auth-broker/config.js +62 -0
- package/out/auth-broker/jwt.js +105 -0
- package/out/auth-broker/pkce.js +57 -0
- package/out/auth-broker/server.js +539 -0
- package/out/auth-broker/store.js +273 -0
- package/out/base-prompts/gateway/openclaw-starter.json5 +144 -0
- package/out/base-prompts/gateway/slack-socket-mode-minimal.manifest.json +44 -0
- package/out/base-prompts/mcp-servers/.mcp.json +13 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +142 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +13 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +1192 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +23 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +12 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +102 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +137 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +118 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +209 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +431 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +270 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +18 -0
- package/out/base-prompts/rules/AGENTS.md +204 -0
- package/out/base-prompts/skills/agent-browser/SKILL.md +102 -0
- package/out/base-prompts/skills/agent-builder/SKILL.md +73 -0
- package/out/base-prompts/skills/agent-builder/references/agent-pack.md +63 -0
- package/out/base-prompts/skills/agent-builder/references/crewai.md +66 -0
- package/out/base-prompts/skills/agent-builder/references/langchain.md +60 -0
- package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +73 -0
- package/out/base-prompts/skills/agent-engine-creator/SKILL.md +586 -0
- package/out/base-prompts/skills/agent-engine-installer/SKILL.md +106 -0
- package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +259 -0
- package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +189 -0
- package/out/base-prompts/skills/agenthippo-cli/SKILL.md +69 -0
- package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +63 -0
- package/out/base-prompts/skills/agentpack-creator/SKILL.md +375 -0
- package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +261 -0
- package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +195 -0
- package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +391 -0
- package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +294 -0
- package/out/base-prompts/skills/agentpack-testbench/SKILL.md +135 -0
- package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +202 -0
- package/out/base-prompts/skills/algorithmic-art/SKILL.md +405 -0
- package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +599 -0
- package/out/base-prompts/skills/debug-agent/SKILL.md +83 -0
- package/out/base-prompts/skills/desktop-automation/SKILL.md +166 -0
- package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +441 -0
- package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +280 -0
- package/out/base-prompts/skills/docx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/docx/SKILL.md +197 -0
- package/out/base-prompts/skills/docx/docx-js.md +350 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +159 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +29 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +69 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +951 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +274 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/out/base-prompts/skills/docx/ooxml.md +610 -0
- package/out/base-prompts/skills/docx/scripts/__init__.py +1 -0
- package/out/base-prompts/skills/docx/scripts/document.py +1276 -0
- package/out/base-prompts/skills/docx/scripts/templates/comments.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/people.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/utilities.py +374 -0
- package/out/base-prompts/skills/gateway-outbound/SKILL.md +136 -0
- package/out/base-prompts/skills/mcp-builder/LICENSE.txt +202 -0
- package/out/base-prompts/skills/mcp-builder/SKILL.md +328 -0
- package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +602 -0
- package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/out/base-prompts/skills/mcp-builder/scripts/connections.py +151 -0
- package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/out/base-prompts/skills/openclaw/SKILL.md +406 -0
- package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +61 -0
- package/out/base-prompts/skills/pdf/LICENSE.txt +30 -0
- package/out/base-prompts/skills/pdf/SKILL.md +294 -0
- package/out/base-prompts/skills/pdf/forms.md +205 -0
- package/out/base-prompts/skills/pdf/reference.md +612 -0
- package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +41 -0
- package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/out/base-prompts/skills/pptx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/pptx/SKILL.md +484 -0
- package/out/base-prompts/skills/pptx/html2pptx.md +625 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +159 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +29 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +69 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +951 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/out/base-prompts/skills/pptx/ooxml.md +427 -0
- package/out/base-prompts/skills/pptx/scripts/html2pptx.js +984 -0
- package/out/base-prompts/skills/pptx/scripts/inventory.py +1020 -0
- package/out/base-prompts/skills/pptx/scripts/rearrange.py +231 -0
- package/out/base-prompts/skills/pptx/scripts/replace.py +385 -0
- package/out/base-prompts/skills/pptx/scripts/thumbnail.py +450 -0
- package/out/base-prompts/skills/prompt-lab/SKILL.md +149 -0
- package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/skill-creator/LICENSE.txt +202 -0
- package/out/base-prompts/skills/skill-creator/SKILL.md +371 -0
- package/out/base-prompts/skills/skill-creator/references/output-patterns.md +82 -0
- package/out/base-prompts/skills/skill-creator/references/workflows.md +28 -0
- package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +303 -0
- package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +110 -0
- package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/out/base-prompts/skills/skill-installer/SKILL.md +251 -0
- package/out/base-prompts/skills/skill-installer/references/store-api.md +219 -0
- package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +83 -0
- package/out/base-prompts/skills/spotlight-insights/SKILL.md +101 -0
- package/out/base-prompts/skills/spotlight-optimize/SKILL.md +93 -0
- package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/spotlight-query/SKILL.md +314 -0
- package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +163 -0
- package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +744 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +14 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +7 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +5 -0
- package/out/base-prompts/skills/spotlight-report/SKILL.md +86 -0
- package/out/base-prompts/skills/spotlight-visualize/SKILL.md +92 -0
- package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/xlsx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/xlsx/SKILL.md +289 -0
- package/out/base-prompts/skills/xlsx/recalc.py +178 -0
- package/out/branding.js +23 -0
- package/out/cli/commands/anywhere.js +66 -0
- package/out/cli/commands/ask.js +243 -0
- package/out/cli/commands/auth-broker.js +24 -0
- package/out/cli/commands/auth.js +219 -0
- package/out/cli/commands/chat.js +354 -0
- package/out/cli/commands/deploy.js +223 -0
- package/out/cli/commands/gateway.js +160 -0
- package/out/cli/commands/pack.js +220 -0
- package/out/cli/commands/probe-custom-engine.js +82 -0
- package/out/cli/commands/serve.js +583 -0
- package/out/cli/commands/store.js +919 -0
- package/out/cli/credential-context.js +113 -0
- package/out/cli/deploy-init.js +2087 -0
- package/out/cli/deps.js +238 -0
- package/out/cli/index.js +390 -0
- package/out/cli/pack-loader.js +376 -0
- package/out/cli/run-custom-engine-runner.js +318 -0
- package/out/cli/runtime.js +409 -0
- package/out/cli/serve-hardening.js +325 -0
- package/out/cli/state-sync.js +135 -0
- package/out/cli/utils.js +220 -0
- package/out/cli/vscode-shim.js +86 -0
- package/out/engines/base.js +159 -0
- package/out/engines/claude.js +1215 -0
- package/out/engines/codex.js +759 -0
- package/out/engines/custom.js +632 -0
- package/out/engines/customEngineIpc.js +95 -0
- package/out/engines/customEngineRunnerEmbed.js +107 -0
- package/out/engines/customRegistry.js +274 -0
- package/out/engines/embeddedCustomEngineRunner.generated.js +9 -0
- package/out/engines/engineApiKey.js +138 -0
- package/out/engines/factory.js +45 -0
- package/out/engines/install.js +372 -0
- package/out/engines/persistentEngineWorker.js +320 -0
- package/out/engines/publish.js +341 -0
- package/out/engines/types.js +17 -0
- package/out/examples/run-headless-repl.js +291 -0
- package/out/examples/run-headless.js +187 -0
- package/out/extension.js +4472 -0
- package/out/gateway/bridge.js +805 -0
- package/out/gateway/cli-agent-id.js +36 -0
- package/out/gateway/client.js +303 -0
- package/out/gateway/client.test.js +395 -0
- package/out/gateway/gateway-scope-errors.js +14 -0
- package/out/gateway/gatewayBackup.js +76 -0
- package/out/gateway/gatewayRouteIndex.js +371 -0
- package/out/gateway/gatewaySessionStore.js +243 -0
- package/out/gateway/index.js +31 -0
- package/out/gateway/lifecycle.js +3056 -0
- package/out/gateway/openclaw-channel-routing.js +317 -0
- package/out/gateway/outboundHandles.js +373 -0
- package/out/gateway/outboundPaths.js +72 -0
- package/out/gateway/outboundSend.js +254 -0
- package/out/gateway/protocol.js +48 -0
- package/out/gateway/resetMappings.js +52 -0
- package/out/gateway/service.js +1244 -0
- package/out/gateway/sessionRouting.js +77 -0
- package/out/headless/index.js +505 -0
- package/out/headless/sync.js +473 -0
- package/out/mcp/publish.js +143 -0
- package/out/mcp.js +597 -0
- package/out/permissions.js +108 -0
- package/out/permissions.mapping.yaml +68 -0
- package/out/registry/index.js +332 -0
- package/out/remote/handleRemoteChat.js +122 -0
- package/out/remote/remotePackStatusBar.js +103 -0
- package/out/remote/serveClient.js +216 -0
- package/out/remoteSkills.js +191 -0
- package/out/runtime/index.js +32 -0
- package/out/runtime/interfaces/config-provider.js +32 -0
- package/out/runtime/interfaces/edit-tracker.js +22 -0
- package/out/runtime/interfaces/index.js +29 -0
- package/out/runtime/interfaces/logger.js +40 -0
- package/out/runtime/interfaces/permission-handler.js +66 -0
- package/out/runtime/interfaces/session-store.js +342 -0
- package/out/runtime/vscode/index.js +20 -0
- package/out/runtime/vscode/vscode-config-provider.js +77 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +120 -0
- package/out/runtime/vscode/vscode-logger.js +30 -0
- package/out/runtime/vscode/vscode-permission-handler.js +72 -0
- package/out/runtime/vscode/vscode-session-store.js +22 -0
- package/out/scripts/agentide-litellm.ps1 +501 -0
- package/out/scripts/agentide-litellm.sh +700 -0
- package/out/scripts/agentide_litellm_hooks_v1.py +604 -0
- package/out/scripts/analytics/agent-fleet-widget.js +954 -0
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +160 -0
- package/out/scripts/analytics/default-seed-scripts.json +12 -0
- package/out/scripts/analytics/duckdbLocator.js +29 -0
- package/out/scripts/analytics/fleet-local-adapter.js +246 -0
- package/out/scripts/analytics/fleet-workflow.sh +263 -0
- package/out/scripts/analytics/generate-default-charts.py +685 -0
- package/out/scripts/analytics/hotspot-analysis-widget.js +909 -0
- package/out/scripts/analytics/index-traces.py +1394 -0
- package/out/scripts/analytics/install-analytics.sh +286 -0
- package/out/scripts/analytics/live-fleet-4-collector.cjs +315 -0
- package/out/scripts/analytics/live-fleet-4-daemon.mjs +134 -0
- package/out/scripts/analytics/live-fleet-4-widget.cjs +433 -0
- package/out/scripts/analytics/otel-collector.yaml +55 -0
- package/out/scripts/analytics/query-analytics.ps1 +396 -0
- package/out/scripts/analytics/query-analytics.sh +2551 -0
- package/out/scripts/analytics/session-comparison-widget.js +1468 -0
- package/out/scripts/analytics/session-timeline-widget.js +2137 -0
- package/out/scripts/analytics/setup-analytics.ps1 +432 -0
- package/out/scripts/analytics/setup-analytics.sh +854 -0
- package/out/scripts/analytics/start-analytics.ps1 +169 -0
- package/out/scripts/analytics/start-analytics.sh +323 -0
- package/out/scripts/analytics/token-cost-flamegraph-widget.js +954 -0
- package/out/scripts/analytics/token-cost-trend-widget.js +1062 -0
- package/out/scripts/analytics/workflows-collector.cjs +315 -0
- package/out/scripts/analytics/workflows-daemon.mjs +186 -0
- package/out/scripts/analytics/workflows-widget.cjs +433 -0
- package/out/scripts/analytics/wow-model-widget.js +191 -0
- package/out/scripts/gateway/Dockerfile.serve +45 -0
- package/out/scripts/gateway/docker-serve.sh +248 -0
- package/out/scripts/gateway/setup-gateway-server.ps1 +953 -0
- package/out/scripts/gateway/setup-gateway-server.sh +935 -0
- package/out/scripts/gateway/setup-gateway.ps1 +1813 -0
- package/out/scripts/gateway/setup-gateway.sh +1541 -0
- package/out/scripts/setup-cli.ps1 +86 -0
- package/out/scripts/setup-cli.sh +117 -0
- package/out/scripts/tools-manifest.json +83 -0
- package/out/scripts/vendor-tools.mjs +234 -0
- package/out/session.js +272 -0
- package/out/shared/agentEngines.js +170 -0
- package/out/shared/index.js +109 -0
- package/out/shared/models.js +235 -0
- package/out/shared/providers.js +202 -0
- package/out/skills/publish.js +178 -0
- package/out/spotlight/install.js +339 -0
- package/out/spotlight/publish.js +544 -0
- package/out/spotlight/yaml.js +31 -0
- package/out/store/github/setup.js +235 -0
- package/out/store/github/templates/build-index.ts +339 -0
- package/out/store/github/templates/build-index.yml +97 -0
- package/out/store/github/templates/package.json +11 -0
- package/out/store/index.js +755 -0
- package/out/store/oramaIndex.js +884 -0
- package/out/store/shared.js +241 -0
- package/out/sync.js +1827 -0
- package/out/terminalLogger.js +358 -0
- package/out/test/agentEnvFile.test.js +97 -0
- package/out/test/agentYaml.test.js +373 -0
- package/out/test/agentpack.test.js +639 -0
- package/out/test/analytics-lifecycle.test.js +174 -0
- package/out/test/analytics.test.js +524 -0
- package/out/test/auth-bootstrapApply.test.js +102 -0
- package/out/test/auth-broker.test.js +434 -0
- package/out/test/auth-callback.test.js +62 -0
- package/out/test/branding.test.js +229 -0
- package/out/test/cli-agent-id.test.js +55 -0
- package/out/test/credential-context.test.js +105 -0
- package/out/test/customEngineInstall.test.js +100 -0
- package/out/test/customRegistry.test.js +122 -0
- package/out/test/deploy-init.test.js +532 -0
- package/out/test/deps-auto-install.test.js +81 -0
- package/out/test/engine-integration.test.js +297 -0
- package/out/test/enginePaths.test.js +195 -0
- package/out/test/enginePublish.test.js +82 -0
- package/out/test/fleetDisplayUtils.test.js +96 -0
- package/out/test/gateway-client.test.js +381 -0
- package/out/test/gateway-lifecycle.test.js +608 -0
- package/out/test/gateway-outbound.test.js +531 -0
- package/out/test/gateway-reset-mappings.test.js +221 -0
- package/out/test/gateway-route-index.test.js +235 -0
- package/out/test/gateway-session-routing.test.js +47 -0
- package/out/test/gateway-session-store.test.js +216 -0
- package/out/test/github.test.js +232 -0
- package/out/test/headless-sync.test.js +279 -0
- package/out/test/helpers/manifest-fixtures.js +27 -0
- package/out/test/mcp-obo.test.js +82 -0
- package/out/test/mcp.test.js +307 -0
- package/out/test/openclaw-channel-routing.test.js +191 -0
- package/out/test/pathSafety.test.js +63 -0
- package/out/test/permissions.test.js +175 -0
- package/out/test/persistent-engine-worker.test.js +220 -0
- package/out/test/refreshPolicy.test.js +60 -0
- package/out/test/registry.test.js +351 -0
- package/out/test/runtime.test.js +349 -0
- package/out/test/scheduledFleetStorage.test.js +72 -0
- package/out/test/serve-hardening.test.js +192 -0
- package/out/test/serveClient.test.js +173 -0
- package/out/test/sessionJsonlFleet.test.js +78 -0
- package/out/test/shared.test.js +173 -0
- package/out/test/skills-mcp.test.js +299 -0
- package/out/test/spotlightPublish.test.js +299 -0
- package/out/test/spotlightYaml.test.js +58 -0
- package/out/test/state-sync-peer.test.js +74 -0
- package/out/test/store.test.js +331 -0
- package/out/test/stores.test.js +504 -0
- package/out/test/sync-switch.test.js +251 -0
- package/out/test/teamViewDiscovery.test.js +90 -0
- package/out/test/toolPath.test.js +120 -0
- package/out/test/utils.test.js +308 -0
- package/out/test/viewsConfigMerge.test.js +180 -0
- package/out/test/windows-command-line.test.js +75 -0
- package/out/utils/agentEnvFile.js +131 -0
- package/out/utils/binary.js +175 -0
- package/out/utils/index.js +37 -0
- package/out/utils/network.js +74 -0
- package/out/utils/pathSafety.js +60 -0
- package/out/utils/paths.js +116 -0
- package/out/utils/pid.js +180 -0
- package/out/utils/pythonRunner.js +258 -0
- package/out/utils/resolveNodeBinary.js +168 -0
- package/out/utils/toolPath.js +83 -0
- package/out/utils/windowsCommandLine.js +29 -0
- package/out/utils.js +915 -0
- package/out/workspaceFolders.js +59 -0
- package/package.json +41 -0
|
@@ -0,0 +1,2295 @@
|
|
|
1
|
+
/* -----------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
3
|
+
* -----------------------------------------------------------------------------
|
|
4
|
+
* Spotlight Dashboard - Default Panel Module
|
|
5
|
+
* Handles artifact display, rendering, and interactions.
|
|
6
|
+
* Uses panelApi (provided by runtime-bridge.js) instead of raw VS Code API.
|
|
7
|
+
*
|
|
8
|
+
* This file is seeded to .agent-hippo/analytics/panel/panel.js on first use.
|
|
9
|
+
* Users can edit the seeded copy to customize the dashboard UI.
|
|
10
|
+
* ----------------------------------------------------------------------------- */
|
|
11
|
+
|
|
12
|
+
// @ts-check
|
|
13
|
+
/// <reference lib="dom" />
|
|
14
|
+
|
|
15
|
+
// @ts-ignore - provided by runtime-bridge.js
|
|
16
|
+
const panelApi = window.panelApi;
|
|
17
|
+
|
|
18
|
+
// Seed/version marker for deciding whether a user's locally seeded panel.js
|
|
19
|
+
// is compatible with the extension runtime (e.g. message forwarding fixes).
|
|
20
|
+
const AGENTIDE_PANEL_JS_VERSION = '2026.05.30';
|
|
21
|
+
try { window.__AGENTIDE_PANEL_JS_VERSION__ = AGENTIDE_PANEL_JS_VERSION; } catch { /* no-op */ }
|
|
22
|
+
|
|
23
|
+
// -----------------------------------------------------------------------------
|
|
24
|
+
// State
|
|
25
|
+
// -----------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
const PANEL_ID = 'default';
|
|
28
|
+
|
|
29
|
+
/** Host ↔ panel / iframe ↔ panel: view-owned export JSON live updates. */
|
|
30
|
+
const VIEW_EXPORT_SNAPSHOT = 'viewExportSnapshot';
|
|
31
|
+
const REQUEST_VIEW_EXPORT_SNAPSHOT = 'requestViewExportSnapshot';
|
|
32
|
+
/** @deprecated Renamed; widgets may still emit this until updated. */
|
|
33
|
+
const LEGACY_REQUEST_LIVE_FLEET_SNAPSHOT = 'requestLiveFleetSnapshot';
|
|
34
|
+
const LEGACY_VIEW_EXPORT_SNAPSHOT = 'liveFleetSnapshot';
|
|
35
|
+
const ADD_VIEW_OPTION_VALUE = '__add_view__';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {{ order: number, hidden: boolean, height?: number, expanded?: boolean }} PanelChartState
|
|
39
|
+
* @typedef {{ version: 1, charts: Record<string, PanelChartState>, summaryHidden?: boolean }} PanelState
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** @returns {PanelState} */
|
|
43
|
+
function createDefaultPanelState() {
|
|
44
|
+
return { version: 1, charts: {}, summaryHidden: false };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @type {{ artifacts: object[], galleryIgnorePatterns: string[], summaryCards: object[] | null, manifest: object | null, isRefreshing: boolean, scope: 'global'|'workspace', projectId: string, agentId: string, agentIds: string[], views: Array<{ id: string, name: string, scope: 'global'|'workspace', system: boolean, layout?: 'fleet-dashboard'|'gallery', refreshOnNavigate?: 'always'|'when-empty', preserveFleetIframe?: boolean }>, activeViewId: string, disabledCharts: Set<string>, panelState: PanelState, panelStateLoaded: boolean, lastArtifactCatalogSignature: string, fleetAutoRefreshIntervalSeconds: number, fleetRefreshVersion: number }} */
|
|
48
|
+
const state = {
|
|
49
|
+
artifacts: [],
|
|
50
|
+
galleryIgnorePatterns: [],
|
|
51
|
+
summaryCards: null,
|
|
52
|
+
manifest: null,
|
|
53
|
+
isRefreshing: false,
|
|
54
|
+
scope: 'global',
|
|
55
|
+
projectId: '',
|
|
56
|
+
agentId: '',
|
|
57
|
+
agentIds: [],
|
|
58
|
+
views: [],
|
|
59
|
+
activeViewId: 'global',
|
|
60
|
+
disabledCharts: new Set(),
|
|
61
|
+
panelState: createDefaultPanelState(),
|
|
62
|
+
panelStateLoaded: false,
|
|
63
|
+
lastArtifactCatalogSignature: '',
|
|
64
|
+
fleetAutoRefreshIntervalSeconds: 30,
|
|
65
|
+
fleetRefreshVersion: 0,
|
|
66
|
+
surfaceMode: 'spotlight',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function applySurfaceMode(mode) {
|
|
70
|
+
const team = mode === 'team';
|
|
71
|
+
state.surfaceMode = team ? 'team' : 'spotlight';
|
|
72
|
+
document.body.classList.toggle('spotlight-surface-team', team);
|
|
73
|
+
applyTeamViewSelectVisibility();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Fleet view without preserveFleetIframe: host bumps fleetRefreshVersion and this panel reloads agent-fleet.html.
|
|
78
|
+
* preserveFleetIframe views receive viewExportSnapshot postMessages instead.
|
|
79
|
+
* Session/export JSON shape: extensions/agentide/src/analytics/dashboard/FLEET_DATA_CONTRACT.md
|
|
80
|
+
*/
|
|
81
|
+
/** Timer for Fleet view auto-refresh; cleared when view changes or interval is 0. */
|
|
82
|
+
let fleetAutoRefreshTimerId = null;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Only auto-refresh Fleet when the user is at the top of the widget.
|
|
86
|
+
* We check both the outer card scroll container and the embedded iframe
|
|
87
|
+
* document scroll position because either can own scrolling depending on
|
|
88
|
+
* current layout/height.
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
function isFleetWidgetAtTop() {
|
|
92
|
+
const fleetCard = /** @type {HTMLElement | null} */ (document.querySelector('.fleet-dashboard-full.artifact-card'));
|
|
93
|
+
if (!fleetCard) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const contentEl = /** @type {HTMLElement | null} */ (fleetCard.querySelector('.artifact-content'));
|
|
98
|
+
if (contentEl && contentEl.scrollTop > 4) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const iframe = /** @type {HTMLIFrameElement | null} */ (fleetCard.querySelector('.html-preview iframe'));
|
|
103
|
+
if (!iframe) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const iframeDoc = iframe.contentDocument;
|
|
109
|
+
const bodyTop = iframeDoc?.body?.scrollTop ?? 0;
|
|
110
|
+
const docTop = iframeDoc?.documentElement?.scrollTop ?? 0;
|
|
111
|
+
const winTop = iframe.contentWindow?.scrollY ?? 0;
|
|
112
|
+
return bodyTop <= 4 && docTop <= 4 && winTop <= 4;
|
|
113
|
+
} catch {
|
|
114
|
+
// If iframe scroll is unavailable, avoid blocking auto-refresh.
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Start or stop Fleet auto-refresh based on current view and configured interval.
|
|
121
|
+
* Call after activeViewId or fleetAutoRefreshIntervalSeconds changes.
|
|
122
|
+
*/
|
|
123
|
+
/** @returns {{ id: string, name: string, scope: 'global'|'workspace', system: boolean, layout?: string, refreshOnNavigate?: string, preserveFleetIframe?: boolean } | undefined} */
|
|
124
|
+
function getActiveViewConfig() {
|
|
125
|
+
return state.views.find(view => view.id === state.activeViewId);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function activeViewUsesFleetDashboardLayout() {
|
|
129
|
+
return getActiveViewConfig()?.layout === 'fleet-dashboard';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function activeViewPreservesFleetIframe() {
|
|
133
|
+
return getActiveViewConfig()?.preserveFleetIframe === true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Iframe-only snapshot views — no gallery Open/Delete footer.
|
|
138
|
+
* Config-driven via `preserveFleetIframe` (covers Workflows and any fleet-dashboard view).
|
|
139
|
+
* The id pattern is a back-compat fallback for legacy live-fleet views whose view.yaml
|
|
140
|
+
* predates the flag and derives `preserveFleetIframe === false` host-side.
|
|
141
|
+
*/
|
|
142
|
+
function activeViewIsLiveFleetSnapshot() {
|
|
143
|
+
if (activeViewPreservesFleetIframe()) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return /^live-fleet-\d+$/.test(String(state.activeViewId || ''));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function applyFleetAutoRefresh() {
|
|
150
|
+
if (fleetAutoRefreshTimerId !== null) {
|
|
151
|
+
clearInterval(fleetAutoRefreshTimerId);
|
|
152
|
+
fleetAutoRefreshTimerId = null;
|
|
153
|
+
}
|
|
154
|
+
const intervalSec = state.fleetAutoRefreshIntervalSeconds;
|
|
155
|
+
const isLiveFleetView = state.activeViewId === 'fleet' || activeViewPreservesFleetIframe();
|
|
156
|
+
if (isLiveFleetView && typeof intervalSec === 'number' && intervalSec > 0) {
|
|
157
|
+
const ms = intervalSec * 1000;
|
|
158
|
+
fleetAutoRefreshTimerId = setInterval(() => {
|
|
159
|
+
if (!isFleetWidgetAtTop()) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (activeViewPreservesFleetIframe()) {
|
|
163
|
+
panelApi.postMessage({ type: REQUEST_VIEW_EXPORT_SNAPSHOT });
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
panelApi.postMessage({ type: 'refreshView' });
|
|
167
|
+
}, ms);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function applyTeamViewSelectVisibility() {
|
|
172
|
+
const viewSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('view-select'));
|
|
173
|
+
if (!viewSelect) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (state.surfaceMode === 'team') {
|
|
177
|
+
if (state.views.length > 1) {
|
|
178
|
+
viewSelect.style.setProperty('display', 'inline-block', 'important');
|
|
179
|
+
} else {
|
|
180
|
+
viewSelect.style.setProperty('display', 'none', 'important');
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
viewSelect.style.removeProperty('display');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function applyViewOptions() {
|
|
188
|
+
const viewSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('view-select'));
|
|
189
|
+
if (!viewSelect) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const previous = state.activeViewId || viewSelect.value || '';
|
|
193
|
+
viewSelect.innerHTML = '';
|
|
194
|
+
|
|
195
|
+
if (state.surfaceMode !== 'team') {
|
|
196
|
+
const addViewOption = document.createElement('option');
|
|
197
|
+
addViewOption.value = ADD_VIEW_OPTION_VALUE;
|
|
198
|
+
addViewOption.textContent = '+ Add view…';
|
|
199
|
+
viewSelect.appendChild(addViewOption);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const view of state.views) {
|
|
203
|
+
const option = document.createElement('option');
|
|
204
|
+
option.value = view.id;
|
|
205
|
+
option.textContent = view.name;
|
|
206
|
+
viewSelect.appendChild(option);
|
|
207
|
+
}
|
|
208
|
+
const next = state.views.some((view) => view.id === state.activeViewId)
|
|
209
|
+
? state.activeViewId
|
|
210
|
+
: previous;
|
|
211
|
+
if (next && state.views.some((view) => view.id === next)) {
|
|
212
|
+
viewSelect.value = next;
|
|
213
|
+
state.activeViewId = next;
|
|
214
|
+
}
|
|
215
|
+
applyTeamViewSelectVisibility();
|
|
216
|
+
applyShareButtonState();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const SHARE_ICON_HTML = '<span class="btn-icon" aria-hidden="true">↑</span>';
|
|
220
|
+
const INSTALL_ICON_HTML = '<span class="btn-icon" aria-hidden="true">↓</span>';
|
|
221
|
+
|
|
222
|
+
function onInstallViewClick() {
|
|
223
|
+
panelApi.postMessage({ type: 'installView' });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function onShareViewClick() {
|
|
227
|
+
const activeView = getActiveView();
|
|
228
|
+
// Always forward to the extension host so built-in views get the explanatory toast.
|
|
229
|
+
panelApi.postMessage({
|
|
230
|
+
type: 'shareView',
|
|
231
|
+
viewId: activeView?.id ?? '',
|
|
232
|
+
bundle: 'full',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function onShareScriptsOnlyClick() {
|
|
237
|
+
const activeView = getActiveView();
|
|
238
|
+
panelApi.postMessage({
|
|
239
|
+
type: 'shareView',
|
|
240
|
+
viewId: activeView?.id ?? '',
|
|
241
|
+
bundle: 'scripts-only',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Insert share toolbar control when user's panel.html predates the share button.
|
|
247
|
+
*/
|
|
248
|
+
function ensureShareToolbarButton() {
|
|
249
|
+
let shareBtn = document.getElementById('share-view-btn');
|
|
250
|
+
if (!shareBtn) {
|
|
251
|
+
const refreshBtn = document.getElementById('refresh-charts-btn');
|
|
252
|
+
if (!refreshBtn?.parentElement) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
let iconGroup = refreshBtn.closest('.toolbar-icon-group');
|
|
256
|
+
if (!iconGroup) {
|
|
257
|
+
iconGroup = document.createElement('div');
|
|
258
|
+
iconGroup.className = 'toolbar-icon-group';
|
|
259
|
+
refreshBtn.parentElement.insertBefore(iconGroup, refreshBtn);
|
|
260
|
+
iconGroup.appendChild(refreshBtn);
|
|
261
|
+
}
|
|
262
|
+
shareBtn = document.createElement('button');
|
|
263
|
+
shareBtn.id = 'share-view-btn';
|
|
264
|
+
shareBtn.className = 'toolbar-btn icon-only';
|
|
265
|
+
shareBtn.title = 'Publish current view to store (full bundle)';
|
|
266
|
+
shareBtn.setAttribute('aria-label', shareBtn.title);
|
|
267
|
+
shareBtn.innerHTML = SHARE_ICON_HTML;
|
|
268
|
+
iconGroup.appendChild(shareBtn);
|
|
269
|
+
} else {
|
|
270
|
+
const refreshBtn = document.getElementById('refresh-charts-btn');
|
|
271
|
+
const icon = shareBtn.querySelector('.btn-icon');
|
|
272
|
+
if (icon && icon.textContent !== '↑') {
|
|
273
|
+
icon.outerHTML = SHARE_ICON_HTML;
|
|
274
|
+
}
|
|
275
|
+
if (refreshBtn && shareBtn.compareDocumentPosition(refreshBtn) & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
276
|
+
refreshBtn.insertAdjacentElement('afterend', shareBtn);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const boundShareBtn = document.getElementById('share-view-btn');
|
|
280
|
+
if (boundShareBtn && !boundShareBtn.dataset.boundShareClick) {
|
|
281
|
+
boundShareBtn.dataset.boundShareClick = '1';
|
|
282
|
+
boundShareBtn.addEventListener('click', onShareViewClick);
|
|
283
|
+
}
|
|
284
|
+
applyShareButtonState();
|
|
285
|
+
ensureInstallToolbarButton();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Insert install toolbar control when user's panel.html predates the install button.
|
|
290
|
+
*/
|
|
291
|
+
function ensureInstallToolbarButton() {
|
|
292
|
+
let installBtn = document.getElementById('install-view-btn');
|
|
293
|
+
if (!installBtn) {
|
|
294
|
+
const shareBtn = document.getElementById('share-view-btn');
|
|
295
|
+
if (!shareBtn?.parentElement) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
let iconGroup = shareBtn.closest('.toolbar-icon-group');
|
|
299
|
+
if (!iconGroup) {
|
|
300
|
+
iconGroup = document.createElement('div');
|
|
301
|
+
iconGroup.className = 'toolbar-icon-group';
|
|
302
|
+
shareBtn.parentElement.insertBefore(iconGroup, shareBtn);
|
|
303
|
+
iconGroup.appendChild(shareBtn);
|
|
304
|
+
}
|
|
305
|
+
installBtn = document.createElement('button');
|
|
306
|
+
installBtn.id = 'install-view-btn';
|
|
307
|
+
installBtn.className = 'toolbar-btn icon-only';
|
|
308
|
+
installBtn.title = 'Install Spotlight view from store';
|
|
309
|
+
installBtn.setAttribute('aria-label', installBtn.title);
|
|
310
|
+
installBtn.innerHTML = INSTALL_ICON_HTML;
|
|
311
|
+
shareBtn.insertAdjacentElement('afterend', installBtn);
|
|
312
|
+
} else {
|
|
313
|
+
const shareBtn = document.getElementById('share-view-btn');
|
|
314
|
+
const icon = installBtn.querySelector('.btn-icon');
|
|
315
|
+
if (icon && icon.textContent !== '↓') {
|
|
316
|
+
icon.outerHTML = INSTALL_ICON_HTML;
|
|
317
|
+
}
|
|
318
|
+
if (shareBtn && installBtn.compareDocumentPosition(shareBtn) & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
319
|
+
shareBtn.insertAdjacentElement('afterend', installBtn);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const boundInstallBtn = document.getElementById('install-view-btn');
|
|
323
|
+
if (boundInstallBtn && !boundInstallBtn.dataset.boundInstallClick) {
|
|
324
|
+
boundInstallBtn.dataset.boundInstallClick = '1';
|
|
325
|
+
boundInstallBtn.addEventListener('click', onInstallViewClick);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function applyShareButtonState() {
|
|
330
|
+
const shareBtn = document.getElementById('share-view-btn');
|
|
331
|
+
if (!shareBtn) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const activeView = getActiveView();
|
|
335
|
+
const canShare = Boolean(activeView && !activeView.system);
|
|
336
|
+
shareBtn.classList.toggle('share-unavailable', !canShare);
|
|
337
|
+
shareBtn.title = canShare
|
|
338
|
+
? 'Publish current view to store (full bundle)'
|
|
339
|
+
: 'Built-in views cannot be shared (duplicate or create a custom view)';
|
|
340
|
+
shareBtn.setAttribute('aria-label', shareBtn.title);
|
|
341
|
+
|
|
342
|
+
const scriptsOnlyItem = document.getElementById('share-scripts-only-menu-item');
|
|
343
|
+
if (scriptsOnlyItem) {
|
|
344
|
+
// Keep clickable so the host can show the same built-in-view explanation.
|
|
345
|
+
scriptsOnlyItem.disabled = false;
|
|
346
|
+
scriptsOnlyItem.classList.toggle('share-unavailable', !canShare);
|
|
347
|
+
scriptsOnlyItem.title = canShare
|
|
348
|
+
? 'Publish widget scripts only (no charts or exports)'
|
|
349
|
+
: 'Built-in views cannot be shared (duplicate or create a custom view)';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function getActiveView() {
|
|
354
|
+
return state.views.find((view) => view.id === state.activeViewId);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @param {string} nextViewId
|
|
359
|
+
*/
|
|
360
|
+
function switchToView(nextViewId) {
|
|
361
|
+
if (!nextViewId || nextViewId === state.activeViewId) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
state.activeViewId = nextViewId;
|
|
365
|
+
state.panelStateLoaded = false;
|
|
366
|
+
state.panelState = createDefaultPanelState();
|
|
367
|
+
const viewSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('view-select'));
|
|
368
|
+
if (viewSelect && state.views.some((view) => view.id === nextViewId)) {
|
|
369
|
+
viewSelect.value = nextViewId;
|
|
370
|
+
}
|
|
371
|
+
applyFleetAutoRefresh();
|
|
372
|
+
applyShareButtonState();
|
|
373
|
+
panelApi.postMessage({ type: 'setActiveView', viewId: nextViewId });
|
|
374
|
+
panelApi.postMessage({ type: 'requestPanelState', panelId: PANEL_ID });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Show the "New view name" modal. Used instead of prompt() because
|
|
379
|
+
* window.prompt() is not available in VS Code webviews.
|
|
380
|
+
* @param {() => void} [onResetSelect] - Optional callback to reset view dropdown (e.g. when opened from "+ Add view…").
|
|
381
|
+
*/
|
|
382
|
+
function showNewViewModal(onResetSelect) {
|
|
383
|
+
const overlay = document.getElementById('new-view-modal');
|
|
384
|
+
const input = document.getElementById('new-view-name-input');
|
|
385
|
+
const okBtn = document.getElementById('new-view-modal-ok');
|
|
386
|
+
const cancelBtn = document.getElementById('new-view-modal-cancel');
|
|
387
|
+
if (!overlay || !input || !okBtn || !cancelBtn) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (typeof onResetSelect === 'function') {
|
|
391
|
+
onResetSelect();
|
|
392
|
+
}
|
|
393
|
+
input.value = '';
|
|
394
|
+
overlay.style.display = 'flex';
|
|
395
|
+
overlay.setAttribute('aria-hidden', 'false');
|
|
396
|
+
input.focus();
|
|
397
|
+
|
|
398
|
+
function hide() {
|
|
399
|
+
overlay.style.display = 'none';
|
|
400
|
+
overlay.setAttribute('aria-hidden', 'true');
|
|
401
|
+
overlay.removeEventListener('click', handleOverlayClick);
|
|
402
|
+
document.removeEventListener('keydown', handleKeydown);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function submit() {
|
|
406
|
+
const trimmed = input.value.trim();
|
|
407
|
+
hide();
|
|
408
|
+
if (trimmed) {
|
|
409
|
+
panelApi.postMessage({ type: 'createView', name: trimmed });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function handleOverlayClick(e) {
|
|
414
|
+
if (e.target === overlay) {
|
|
415
|
+
hide();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function handleKeydown(e) {
|
|
420
|
+
if (e.key === 'Escape') {
|
|
421
|
+
hide();
|
|
422
|
+
e.preventDefault();
|
|
423
|
+
} else if (e.key === 'Enter') {
|
|
424
|
+
submit();
|
|
425
|
+
e.preventDefault();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
overlay.addEventListener('click', handleOverlayClick);
|
|
430
|
+
document.addEventListener('keydown', handleKeydown);
|
|
431
|
+
|
|
432
|
+
okBtn.onclick = () => submit();
|
|
433
|
+
cancelBtn.onclick = () => hide();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function promptCreateView() {
|
|
437
|
+
showNewViewModal();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function initViews() {
|
|
441
|
+
const viewSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('view-select'));
|
|
442
|
+
viewSelect?.addEventListener('change', () => {
|
|
443
|
+
const nextViewId = viewSelect.value || '';
|
|
444
|
+
if (!nextViewId) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (nextViewId === ADD_VIEW_OPTION_VALUE) {
|
|
448
|
+
showNewViewModal(() => {
|
|
449
|
+
viewSelect.value = state.activeViewId || '';
|
|
450
|
+
});
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (nextViewId === state.activeViewId) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
switchToView(nextViewId);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function refreshCurrentView() {
|
|
461
|
+
const btn = document.getElementById('refresh-charts-btn');
|
|
462
|
+
if (btn && !btn.classList.contains('refreshing')) {
|
|
463
|
+
setRefreshButtonState(true);
|
|
464
|
+
panelApi.postMessage({ type: 'refreshView' });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// -----------------------------------------------------------------------------
|
|
469
|
+
// Utility Functions
|
|
470
|
+
// -----------------------------------------------------------------------------
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Format a date as relative time (e.g., "2 min ago")
|
|
474
|
+
* @param {Date} date
|
|
475
|
+
* @returns {string}
|
|
476
|
+
*/
|
|
477
|
+
function formatTimeAgo(date) {
|
|
478
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
479
|
+
if (seconds < 60) {
|
|
480
|
+
return 'just now';
|
|
481
|
+
}
|
|
482
|
+
if (seconds < 3600) {
|
|
483
|
+
return `${Math.floor(seconds / 60)} min ago`;
|
|
484
|
+
}
|
|
485
|
+
if (seconds < 86400) {
|
|
486
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
487
|
+
}
|
|
488
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Format file size in human-readable form
|
|
493
|
+
* @param {number} bytes
|
|
494
|
+
* @returns {string}
|
|
495
|
+
*/
|
|
496
|
+
function formatSize(bytes) {
|
|
497
|
+
if (bytes < 1024) {
|
|
498
|
+
return `${bytes} B`;
|
|
499
|
+
}
|
|
500
|
+
if (bytes < 1024 * 1024) {
|
|
501
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
502
|
+
}
|
|
503
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Escape HTML special characters
|
|
508
|
+
* @param {string} text
|
|
509
|
+
* @returns {string}
|
|
510
|
+
*/
|
|
511
|
+
function escapeHtml(text) {
|
|
512
|
+
const div = document.createElement('div');
|
|
513
|
+
div.textContent = text;
|
|
514
|
+
return div.innerHTML;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Simple markdown rendering
|
|
519
|
+
* @param {string} text
|
|
520
|
+
* @returns {string}
|
|
521
|
+
*/
|
|
522
|
+
function renderMarkdown(text) {
|
|
523
|
+
return text
|
|
524
|
+
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code class="language-$1">$2</code></pre>')
|
|
525
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
526
|
+
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
527
|
+
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
|
528
|
+
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
|
529
|
+
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
|
530
|
+
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
|
531
|
+
.replace(/\n/g, '<br>');
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* @param {string} value
|
|
536
|
+
* @returns {string}
|
|
537
|
+
*/
|
|
538
|
+
function normalizeFilename(value) {
|
|
539
|
+
return String(value || '').trim().toLowerCase();
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* @param {unknown} raw
|
|
544
|
+
* @returns {PanelState}
|
|
545
|
+
*/
|
|
546
|
+
function normalizePanelState(raw) {
|
|
547
|
+
const parsed = raw && typeof raw === 'object' ? /** @type {Record<string, unknown>} */ (raw) : {};
|
|
548
|
+
const rawCharts = parsed.charts && typeof parsed.charts === 'object'
|
|
549
|
+
? /** @type {Record<string, unknown>} */ (parsed.charts)
|
|
550
|
+
: {};
|
|
551
|
+
/** @type {Record<string, PanelChartState>} */
|
|
552
|
+
const charts = {};
|
|
553
|
+
let nextOrder = 1;
|
|
554
|
+
for (const [key, value] of Object.entries(rawCharts)) {
|
|
555
|
+
const filenameKey = normalizeFilename(key);
|
|
556
|
+
if (!filenameKey) {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
const entry = value && typeof value === 'object' ? /** @type {Record<string, unknown>} */ (value) : {};
|
|
560
|
+
const parsedOrder = Number(entry.order);
|
|
561
|
+
const order = Number.isFinite(parsedOrder) && parsedOrder > 0 ? parsedOrder : nextOrder;
|
|
562
|
+
const hidden = entry.hidden === true;
|
|
563
|
+
const height = Number(entry.height);
|
|
564
|
+
const expanded = entry.expanded === true;
|
|
565
|
+
charts[filenameKey] = {
|
|
566
|
+
order,
|
|
567
|
+
hidden,
|
|
568
|
+
height: Number.isFinite(height) && height >= 120 ? height : undefined,
|
|
569
|
+
expanded,
|
|
570
|
+
};
|
|
571
|
+
nextOrder = Math.max(nextOrder, order + 1);
|
|
572
|
+
}
|
|
573
|
+
const summaryHidden = parsed.summaryHidden === true;
|
|
574
|
+
return { version: 1, charts, summaryHidden };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* @returns {number}
|
|
579
|
+
*/
|
|
580
|
+
function getNextChartOrder() {
|
|
581
|
+
const entries = Object.values(state.panelState.charts);
|
|
582
|
+
if (entries.length === 0) {
|
|
583
|
+
return 1;
|
|
584
|
+
}
|
|
585
|
+
return entries.reduce((max, item) => Math.max(max, item.order), 0) + 1;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Ensure every discovered chart has a panel-state entry.
|
|
590
|
+
* @param {Array<object>} artifacts
|
|
591
|
+
* @returns {boolean}
|
|
592
|
+
*/
|
|
593
|
+
function ensurePanelStateForArtifacts(artifacts) {
|
|
594
|
+
let changed = false;
|
|
595
|
+
let nextOrder = getNextChartOrder();
|
|
596
|
+
for (const artifact of artifacts) {
|
|
597
|
+
if (artifact.type !== 'chart' && artifact.type !== 'image') {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
const key = normalizeFilename(String(artifact.filename || ''));
|
|
601
|
+
if (!key) {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (!state.panelState.charts[key]) {
|
|
605
|
+
state.panelState.charts[key] = { order: nextOrder++, hidden: false, height: undefined, expanded: false };
|
|
606
|
+
changed = true;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return changed;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* @param {string} filename
|
|
614
|
+
* @returns {PanelChartState}
|
|
615
|
+
*/
|
|
616
|
+
function getChartPanelState(filename) {
|
|
617
|
+
const key = normalizeFilename(filename);
|
|
618
|
+
if (!state.panelState.charts[key]) {
|
|
619
|
+
state.panelState.charts[key] = { order: getNextChartOrder(), hidden: false, height: undefined, expanded: false };
|
|
620
|
+
}
|
|
621
|
+
return state.panelState.charts[key];
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function setChartHeight(filename, heightPx) {
|
|
625
|
+
const chartState = getChartPanelState(filename);
|
|
626
|
+
chartState.height = heightPx >= 120 && heightPx <= 2000 ? heightPx : undefined;
|
|
627
|
+
savePanelState();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function setChartExpanded(filename, expanded) {
|
|
631
|
+
const chartState = getChartPanelState(filename);
|
|
632
|
+
chartState.expanded = expanded === true;
|
|
633
|
+
savePanelState();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Auto-fit Fleet iframe content on first load when no explicit panel height
|
|
638
|
+
* has been saved yet. This prevents an oversized empty area at the bottom
|
|
639
|
+
* while preserving click-to-expand behavior.
|
|
640
|
+
* @param {HTMLElement} card
|
|
641
|
+
*/
|
|
642
|
+
function autoFitFleetCardHeight(card) {
|
|
643
|
+
const filename = card?.dataset?.filename || '';
|
|
644
|
+
if (!filename) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const chartState = getChartPanelState(filename);
|
|
648
|
+
if (typeof chartState.height === 'number' && Number.isFinite(chartState.height)) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const content = /** @type {HTMLElement | null} */ (card.querySelector('.artifact-content'));
|
|
652
|
+
const iframe = /** @type {HTMLIFrameElement | null} */ (card.querySelector('.html-preview iframe'));
|
|
653
|
+
if (!content || !iframe) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
let docHeight = 0;
|
|
657
|
+
try {
|
|
658
|
+
const doc = iframe.contentDocument;
|
|
659
|
+
const body = doc?.body;
|
|
660
|
+
const html = doc?.documentElement;
|
|
661
|
+
docHeight = Math.max(
|
|
662
|
+
body?.scrollHeight || 0,
|
|
663
|
+
body?.offsetHeight || 0,
|
|
664
|
+
html?.scrollHeight || 0,
|
|
665
|
+
html?.offsetHeight || 0,
|
|
666
|
+
html?.clientHeight || 0,
|
|
667
|
+
);
|
|
668
|
+
} catch {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (!Number.isFinite(docHeight) || docHeight <= 0) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const computed = window.getComputedStyle(content);
|
|
675
|
+
const paddingTop = Number.parseFloat(computed.paddingTop || '0') || 0;
|
|
676
|
+
const paddingBottom = Number.parseFloat(computed.paddingBottom || '0') || 0;
|
|
677
|
+
const targetHeight = Math.round(Math.max(320, Math.min(2000, docHeight + paddingTop + paddingBottom)));
|
|
678
|
+
content.style.height = `${targetHeight}px`;
|
|
679
|
+
content.style.minHeight = `${targetHeight}px`;
|
|
680
|
+
content.style.overflow = 'hidden';
|
|
681
|
+
syncEmbeddedPreviewSize(content);
|
|
682
|
+
setChartHeight(filename, targetHeight);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Keep embedded HTML previews sized to the resized card and trigger a resize
|
|
687
|
+
* event inside iframe widgets so responsive layouts can relayout.
|
|
688
|
+
* @param {HTMLElement} content
|
|
689
|
+
*/
|
|
690
|
+
function syncEmbeddedPreviewSize(content) {
|
|
691
|
+
const preview = content.querySelector('.html-preview');
|
|
692
|
+
if (preview instanceof HTMLElement) {
|
|
693
|
+
preview.style.height = '100%';
|
|
694
|
+
preview.style.minHeight = '0';
|
|
695
|
+
}
|
|
696
|
+
const iframe = content.querySelector('.html-preview iframe');
|
|
697
|
+
if (iframe instanceof HTMLIFrameElement) {
|
|
698
|
+
iframe.style.height = '100%';
|
|
699
|
+
iframe.style.minHeight = '0';
|
|
700
|
+
iframe.style.display = 'block';
|
|
701
|
+
try {
|
|
702
|
+
iframe.contentWindow?.dispatchEvent(new Event('resize'));
|
|
703
|
+
} catch {
|
|
704
|
+
// Cross-origin frame or transient load state; ignore.
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** @param {Element | null} card @param {string} filename */
|
|
710
|
+
function attachResizeHandle(card, filename) {
|
|
711
|
+
if (!card || !filename) return;
|
|
712
|
+
const resizeHandle = card.querySelector('.resize-handle');
|
|
713
|
+
const contentEl = card.querySelector('.artifact-content');
|
|
714
|
+
if (!resizeHandle || !contentEl) return;
|
|
715
|
+
const cardEl = /** @type {HTMLElement} */ (card);
|
|
716
|
+
const content = /** @type {HTMLElement} */ (contentEl);
|
|
717
|
+
const minH = 120;
|
|
718
|
+
const maxH = 2000;
|
|
719
|
+
resizeHandle.addEventListener('mousedown', (e) => {
|
|
720
|
+
e.preventDefault();
|
|
721
|
+
e.stopPropagation();
|
|
722
|
+
const startY = e.clientY;
|
|
723
|
+
const rect = content.getBoundingClientRect();
|
|
724
|
+
const startHeight = rect.height;
|
|
725
|
+
let didResize = false;
|
|
726
|
+
let rafId = 0;
|
|
727
|
+
const schedulePreviewResize = () => {
|
|
728
|
+
if (rafId) {
|
|
729
|
+
cancelAnimationFrame(rafId);
|
|
730
|
+
}
|
|
731
|
+
rafId = requestAnimationFrame(() => {
|
|
732
|
+
rafId = 0;
|
|
733
|
+
syncEmbeddedPreviewSize(content);
|
|
734
|
+
});
|
|
735
|
+
};
|
|
736
|
+
const onMove = (moveEvent) => {
|
|
737
|
+
const dy = moveEvent.clientY - startY;
|
|
738
|
+
if (Math.abs(dy) > 2) {
|
|
739
|
+
didResize = true;
|
|
740
|
+
}
|
|
741
|
+
let h = Math.round(startHeight + dy);
|
|
742
|
+
h = Math.max(minH, Math.min(maxH, h));
|
|
743
|
+
content.style.height = `${h}px`;
|
|
744
|
+
content.style.minHeight = `${h}px`;
|
|
745
|
+
content.style.overflow = 'hidden';
|
|
746
|
+
schedulePreviewResize();
|
|
747
|
+
};
|
|
748
|
+
const onUp = () => {
|
|
749
|
+
document.removeEventListener('mousemove', onMove);
|
|
750
|
+
document.removeEventListener('mouseup', onUp);
|
|
751
|
+
if (rafId) {
|
|
752
|
+
cancelAnimationFrame(rafId);
|
|
753
|
+
rafId = 0;
|
|
754
|
+
}
|
|
755
|
+
syncEmbeddedPreviewSize(content);
|
|
756
|
+
if (didResize) {
|
|
757
|
+
const finalHeight = content.getBoundingClientRect().height;
|
|
758
|
+
setChartHeight(filename, Math.round(finalHeight));
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const nextExpanded = !cardEl.classList.contains('expanded');
|
|
762
|
+
cardEl.classList.toggle('expanded', nextExpanded);
|
|
763
|
+
setChartExpanded(filename, nextExpanded);
|
|
764
|
+
};
|
|
765
|
+
document.addEventListener('mousemove', onMove);
|
|
766
|
+
document.addEventListener('mouseup', onUp);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function toggleSummarySection() {
|
|
771
|
+
state.panelState.summaryHidden = !state.panelState.summaryHidden;
|
|
772
|
+
applySummarySectionVisibility();
|
|
773
|
+
savePanelState();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function applySummarySectionVisibility() {
|
|
777
|
+
const section = document.getElementById('summary-section');
|
|
778
|
+
const btn = document.getElementById('summary-toggle-btn');
|
|
779
|
+
if (section) {
|
|
780
|
+
section.classList.toggle('collapsed', state.panelState.summaryHidden === true);
|
|
781
|
+
}
|
|
782
|
+
if (btn) {
|
|
783
|
+
btn.textContent = state.panelState.summaryHidden ? 'Show' : 'Hide';
|
|
784
|
+
btn.title = state.panelState.summaryHidden ? 'Show summary section' : 'Hide summary section';
|
|
785
|
+
btn.setAttribute('aria-label', state.panelState.summaryHidden ? 'Show summary' : 'Hide summary');
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function savePanelState() {
|
|
790
|
+
panelApi.postMessage({
|
|
791
|
+
type: 'savePanelState',
|
|
792
|
+
panelId: PANEL_ID,
|
|
793
|
+
state: state.panelState,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* @param {string} filename
|
|
799
|
+
* @param {'up' | 'down'} direction
|
|
800
|
+
* @param {string[]} activeChartKeys
|
|
801
|
+
*/
|
|
802
|
+
function moveChartInPanelState(filename, direction, activeChartKeys) {
|
|
803
|
+
const targetKey = normalizeFilename(filename);
|
|
804
|
+
if (!targetKey) {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
getChartPanelState(filename);
|
|
808
|
+
const orderedKeys = Object.entries(state.panelState.charts)
|
|
809
|
+
.sort((a, b) => a[1].order - b[1].order || a[0].localeCompare(b[0]))
|
|
810
|
+
.map(([key]) => key);
|
|
811
|
+
const index = orderedKeys.indexOf(targetKey);
|
|
812
|
+
if (index < 0) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const normalizedActiveKeys = new Set(
|
|
816
|
+
(activeChartKeys || []).map(key => normalizeFilename(key)).filter(Boolean),
|
|
817
|
+
);
|
|
818
|
+
normalizedActiveKeys.add(targetKey);
|
|
819
|
+
const step = direction === 'up' ? -1 : 1;
|
|
820
|
+
let swapIndex = index + step;
|
|
821
|
+
while (swapIndex >= 0 && swapIndex < orderedKeys.length) {
|
|
822
|
+
if (normalizedActiveKeys.has(orderedKeys[swapIndex])) {
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
swapIndex += step;
|
|
826
|
+
}
|
|
827
|
+
if (swapIndex < 0 || swapIndex >= orderedKeys.length || swapIndex === index) {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const [entry] = orderedKeys.splice(index, 1);
|
|
831
|
+
orderedKeys.splice(swapIndex, 0, entry);
|
|
832
|
+
orderedKeys.forEach((key, orderIndex) => {
|
|
833
|
+
const chart = state.panelState.charts[key];
|
|
834
|
+
chart.order = orderIndex + 1;
|
|
835
|
+
});
|
|
836
|
+
savePanelState();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* @param {string} filename
|
|
841
|
+
*/
|
|
842
|
+
function toggleChartHiddenInPanelState(filename) {
|
|
843
|
+
const chartState = getChartPanelState(filename);
|
|
844
|
+
chartState.hidden = !chartState.hidden;
|
|
845
|
+
savePanelState();
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// -----------------------------------------------------------------------------
|
|
849
|
+
// Summary Cards
|
|
850
|
+
// -----------------------------------------------------------------------------
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Render summary cards in the header
|
|
854
|
+
* @param {Array<{label: string, value: string, icon: string}>} cards
|
|
855
|
+
*/
|
|
856
|
+
function renderSummaryCards(cards) {
|
|
857
|
+
const container = document.getElementById('summary-cards');
|
|
858
|
+
if (!container) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (!cards || cards.length === 0) {
|
|
862
|
+
renderSummaryPlaceholders();
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
container.style.display = 'grid';
|
|
867
|
+
container.classList.remove('loading');
|
|
868
|
+
container.innerHTML = cards.map(card => `
|
|
869
|
+
<div class="metric-card">
|
|
870
|
+
<div class="metric-icon">${card.icon}</div>
|
|
871
|
+
<div class="metric-value">${escapeHtml(card.value)}</div>
|
|
872
|
+
<div class="metric-label">${escapeHtml(card.label)}</div>
|
|
873
|
+
</div>
|
|
874
|
+
`).join('');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Render summary placeholders while data is loading.
|
|
879
|
+
*/
|
|
880
|
+
function renderSummaryPlaceholders() {
|
|
881
|
+
const container = document.getElementById('summary-cards');
|
|
882
|
+
if (!container) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
container.style.display = 'grid';
|
|
887
|
+
container.classList.add('loading');
|
|
888
|
+
container.innerHTML = Array.from({ length: 4 }).map(() => `
|
|
889
|
+
<div class="metric-card skeleton">
|
|
890
|
+
<div class="skeleton-line skeleton-icon"></div>
|
|
891
|
+
<div class="skeleton-line skeleton-value"></div>
|
|
892
|
+
<div class="skeleton-line skeleton-label"></div>
|
|
893
|
+
</div>
|
|
894
|
+
`).join('');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// -----------------------------------------------------------------------------
|
|
898
|
+
// Gallery Rendering
|
|
899
|
+
// -----------------------------------------------------------------------------
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Show a specific gallery state
|
|
903
|
+
* @param {'loading' | 'empty' | 'generating' | 'failed' | 'content'} stateName
|
|
904
|
+
*/
|
|
905
|
+
function showGalleryState(stateName) {
|
|
906
|
+
const states = ['loading-state', 'empty-state', 'generating-state', 'failed-state', 'gallery-content'];
|
|
907
|
+
for (const id of states) {
|
|
908
|
+
const el = document.getElementById(id);
|
|
909
|
+
if (el) {
|
|
910
|
+
el.style.display = id === `${stateName}-state` || id === `${stateName}` ? 'flex' : 'none';
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// gallery-content needs 'block' not 'flex'
|
|
915
|
+
const content = document.getElementById('gallery-content');
|
|
916
|
+
if (content) {
|
|
917
|
+
content.style.display = stateName === 'content' ? 'block' : 'none';
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Render the artifact gallery
|
|
923
|
+
* @param {Array<object>} artifacts
|
|
924
|
+
* @param {{ forceFreshHtml?: boolean }} [options]
|
|
925
|
+
*/
|
|
926
|
+
function renderGallery(artifacts, options) {
|
|
927
|
+
const forceFreshHtml = options?.forceFreshHtml === true;
|
|
928
|
+
const container = document.getElementById('gallery-content');
|
|
929
|
+
if (!container) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
// Preserve existing HTML iframes across incremental refreshes when the
|
|
933
|
+
// artifact content version (modifiedAt) did not change.
|
|
934
|
+
/** @type {Map<string, HTMLIFrameElement>} */
|
|
935
|
+
const reusableHtmlPreviews = new Map();
|
|
936
|
+
container.querySelectorAll('.artifact-card').forEach((cardEl) => {
|
|
937
|
+
const card = /** @type {HTMLElement} */ (cardEl);
|
|
938
|
+
const path = card.dataset.path || '';
|
|
939
|
+
const modifiedAt = card.dataset.modifiedAt || '';
|
|
940
|
+
if (!path || !modifiedAt) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const iframe = /** @type {HTMLIFrameElement | null} */ (card.querySelector('.html-preview iframe'));
|
|
944
|
+
if (!iframe) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
reusableHtmlPreviews.set(`${path}::${modifiedAt}`, iframe);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
const layoutChanged = ensurePanelStateForArtifacts(artifacts);
|
|
951
|
+
if (layoutChanged && state.panelStateLoaded) {
|
|
952
|
+
savePanelState();
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const visibleArtifacts = artifacts.filter((artifact) => {
|
|
956
|
+
const filename = String(artifact.filename || '').trim().toLowerCase();
|
|
957
|
+
// Hide artifacts matching galleryIgnorePatterns (suffix or exact name); list is from disabled-charts.yaml
|
|
958
|
+
const ignored = state.galleryIgnorePatterns.some((p) => {
|
|
959
|
+
const pattern = String(p).trim().toLowerCase();
|
|
960
|
+
return pattern && (filename.endsWith(pattern) || filename === pattern);
|
|
961
|
+
});
|
|
962
|
+
if (ignored) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
// Live Fleet iframe views: export JSON is pushed via viewExportSnapshot — skip gallery cards (Open/Delete).
|
|
966
|
+
if (activeViewIsLiveFleetSnapshot() && artifact.type !== 'chart' && artifact.type !== 'image') {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
if (artifact.type !== 'chart' && artifact.type !== 'image') {
|
|
970
|
+
return true;
|
|
971
|
+
}
|
|
972
|
+
return !state.disabledCharts.has(filename);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
const chartArtifacts = visibleArtifacts
|
|
976
|
+
.filter((artifact) => artifact.type === 'chart' || artifact.type === 'image')
|
|
977
|
+
.map((artifact) => ({
|
|
978
|
+
...artifact,
|
|
979
|
+
_panel: getChartPanelState(String(artifact.filename || '')),
|
|
980
|
+
}))
|
|
981
|
+
.sort((a, b) => {
|
|
982
|
+
if (a._panel.hidden !== b._panel.hidden) {
|
|
983
|
+
return a._panel.hidden ? 1 : -1;
|
|
984
|
+
}
|
|
985
|
+
if (a._panel.order !== b._panel.order) {
|
|
986
|
+
return a._panel.order - b._panel.order;
|
|
987
|
+
}
|
|
988
|
+
return new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime();
|
|
989
|
+
});
|
|
990
|
+
const interactiveChartKeys = chartArtifacts.map((artifact) => normalizeFilename(String(artifact.filename || '')));
|
|
991
|
+
const nonChartArtifacts = visibleArtifacts
|
|
992
|
+
.filter((artifact) => artifact.type !== 'chart')
|
|
993
|
+
.sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime());
|
|
994
|
+
const orderedArtifacts = [...chartArtifacts, ...nonChartArtifacts];
|
|
995
|
+
|
|
996
|
+
if (orderedArtifacts.length === 0) {
|
|
997
|
+
showGalleryState('empty');
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// Export JSON alongside the chart is normal for live fleet views — do not fall back to gallery layout.
|
|
1002
|
+
const nonChartNonExportArtifacts = nonChartArtifacts.filter((artifact) => artifact.type !== 'export');
|
|
1003
|
+
const fleetHtmlCharts = chartArtifacts.filter((artifact) => artifact.format === 'html');
|
|
1004
|
+
const isFleetSingleHtml =
|
|
1005
|
+
(fleetHtmlCharts.length >= 1 && activeViewIsLiveFleetSnapshot())
|
|
1006
|
+
|| (activeViewUsesFleetDashboardLayout() &&
|
|
1007
|
+
chartArtifacts.length === 1 &&
|
|
1008
|
+
chartArtifacts[0].format === 'html' &&
|
|
1009
|
+
nonChartNonExportArtifacts.length === 0);
|
|
1010
|
+
if (isFleetSingleHtml) {
|
|
1011
|
+
const artifact = fleetHtmlCharts[0];
|
|
1012
|
+
const path = artifact.localPath || '';
|
|
1013
|
+
const modifiedAt = String(artifact.modifiedAt || '');
|
|
1014
|
+
const fleetFilename = String(artifact.filename || '').trim() || '__fleet__';
|
|
1015
|
+
const chartState = getChartPanelState(fleetFilename);
|
|
1016
|
+
const fleetExpandedClass = chartState.expanded ? ' expanded' : '';
|
|
1017
|
+
const fleetHeightStyle = chartState.height
|
|
1018
|
+
? `style="height: ${chartState.height}px; min-height: ${chartState.height}px; overflow: auto;"`
|
|
1019
|
+
: '';
|
|
1020
|
+
const existingFleetCard = /** @type {HTMLElement | null} */ (
|
|
1021
|
+
container.querySelector('.fleet-dashboard-full.artifact-card')
|
|
1022
|
+
);
|
|
1023
|
+
// preserveFleetIframe views: keep the iframe mounted; data updates arrive via viewExportSnapshot postMessage.
|
|
1024
|
+
let fleetIframeHasSnapshotShell = false;
|
|
1025
|
+
if (activeViewPreservesFleetIframe() && existingFleetCard) {
|
|
1026
|
+
const fleetIframe = existingFleetCard.querySelector('.html-preview iframe');
|
|
1027
|
+
if (fleetIframe instanceof HTMLIFrameElement) {
|
|
1028
|
+
try {
|
|
1029
|
+
const doc = fleetIframe.contentDocument;
|
|
1030
|
+
fleetIframeHasSnapshotShell =
|
|
1031
|
+
doc?.body?.dataset?.fleetSnapshotShell === '1'
|
|
1032
|
+
|| doc?.documentElement?.hasAttribute('data-live-fleet-shell');
|
|
1033
|
+
} catch {
|
|
1034
|
+
fleetIframeHasSnapshotShell = false;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (
|
|
1039
|
+
activeViewPreservesFleetIframe()
|
|
1040
|
+
&& existingFleetCard
|
|
1041
|
+
&& existingFleetCard.dataset.path === path
|
|
1042
|
+
&& existingFleetCard.querySelector('.html-preview iframe')
|
|
1043
|
+
&& fleetIframeHasSnapshotShell
|
|
1044
|
+
&& !forceFreshHtml
|
|
1045
|
+
) {
|
|
1046
|
+
showGalleryState('content');
|
|
1047
|
+
existingFleetCard.dataset.modifiedAt = modifiedAt;
|
|
1048
|
+
attachResizeHandle(existingFleetCard, fleetFilename);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const reusable = forceFreshHtml ? undefined : reusableHtmlPreviews.get(`${path}::${modifiedAt}`);
|
|
1052
|
+
showGalleryState('content');
|
|
1053
|
+
container.innerHTML = `
|
|
1054
|
+
<div class="fleet-dashboard-full artifact-card${fleetExpandedClass}" data-path="${escapeHtml(path)}" data-filename="${escapeHtml(fleetFilename)}" data-modified-at="${escapeHtml(modifiedAt)}">
|
|
1055
|
+
<div class="artifact-content" ${fleetHeightStyle}>
|
|
1056
|
+
<div class="html-preview">${reusable ? '' : '<div class="html-preview-loading">Loading Fleet Operations...</div>'}</div>
|
|
1057
|
+
</div>
|
|
1058
|
+
<div class="resize-handle" data-filename="${escapeHtml(fleetFilename)}" title="Drag to resize height" role="separator"></div>
|
|
1059
|
+
</div>
|
|
1060
|
+
`;
|
|
1061
|
+
if (reusable) {
|
|
1062
|
+
const host = container.querySelector('.html-preview');
|
|
1063
|
+
if (host) {
|
|
1064
|
+
host.classList.add('ready');
|
|
1065
|
+
host.appendChild(reusable);
|
|
1066
|
+
}
|
|
1067
|
+
} else {
|
|
1068
|
+
requestHtmlPreviewContents([artifact]);
|
|
1069
|
+
}
|
|
1070
|
+
const fleetCard = container.querySelector('.artifact-card');
|
|
1071
|
+
attachResizeHandle(fleetCard, fleetFilename);
|
|
1072
|
+
if (fleetCard instanceof HTMLElement) {
|
|
1073
|
+
fleetCard.addEventListener('click', (event) => {
|
|
1074
|
+
const target = /** @type {HTMLElement | null} */ (event.target instanceof HTMLElement ? event.target : null);
|
|
1075
|
+
if (target?.closest('button,.resize-handle')) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const nextExpanded = !fleetCard.classList.contains('expanded');
|
|
1079
|
+
fleetCard.classList.toggle('expanded', nextExpanded);
|
|
1080
|
+
setChartExpanded(fleetFilename, nextExpanded);
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
showGalleryState('content');
|
|
1087
|
+
|
|
1088
|
+
const html = orderedArtifacts.map(artifact => {
|
|
1089
|
+
const timeAgo = formatTimeAgo(new Date(artifact.modifiedAt));
|
|
1090
|
+
const sizeFormatted = formatSize(artifact.size);
|
|
1091
|
+
const isChart = artifact.type === 'chart';
|
|
1092
|
+
const isImage = artifact.type === 'image';
|
|
1093
|
+
const isVisual = isChart || isImage;
|
|
1094
|
+
const chartState = isVisual ? artifact._panel : undefined;
|
|
1095
|
+
const isHidden = chartState?.hidden === true;
|
|
1096
|
+
const isExpanded = chartState?.expanded === true;
|
|
1097
|
+
const order = Number.isFinite(chartState?.order) ? chartState.order : undefined;
|
|
1098
|
+
|
|
1099
|
+
// Render based on format
|
|
1100
|
+
let content = '';
|
|
1101
|
+
switch (artifact.format) {
|
|
1102
|
+
case 'png':
|
|
1103
|
+
case 'svg':
|
|
1104
|
+
case 'jpg':
|
|
1105
|
+
content = `<img src="${artifact.webviewUri}" alt="${escapeHtml(artifact.filename)}" loading="lazy" />`;
|
|
1106
|
+
break;
|
|
1107
|
+
case 'html':
|
|
1108
|
+
content = `
|
|
1109
|
+
<div class="html-preview" data-path="${escapeHtml(artifact.localPath)}">
|
|
1110
|
+
<div class="html-preview-loading">Loading preview...</div>
|
|
1111
|
+
</div>
|
|
1112
|
+
`;
|
|
1113
|
+
break;
|
|
1114
|
+
case 'md':
|
|
1115
|
+
content = `<div class="markdown-preview" data-path="${escapeHtml(artifact.localPath)}">Loading...</div>`;
|
|
1116
|
+
break;
|
|
1117
|
+
case 'csv':
|
|
1118
|
+
case 'json':
|
|
1119
|
+
content = `<div class="data-preview"><code>${escapeHtml(artifact.filename)}</code></div>`;
|
|
1120
|
+
break;
|
|
1121
|
+
default:
|
|
1122
|
+
content = `<div class="data-preview"><code>${escapeHtml(artifact.filename)}</code></div>`;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Find manifest entry for this artifact
|
|
1126
|
+
const manifestEntry = state.manifest?.artifacts?.find(e => e.filename === artifact.filename);
|
|
1127
|
+
const title = manifestEntry?.title || artifact.filename;
|
|
1128
|
+
const generatorBadge = artifact.generator
|
|
1129
|
+
? `<span class="generator-badge">${escapeHtml(artifact.generator)}</span>`
|
|
1130
|
+
: '';
|
|
1131
|
+
const sourceBadge = manifestEntry?.source === 'default'
|
|
1132
|
+
? '<span class="source-badge">Auto</span>'
|
|
1133
|
+
: '';
|
|
1134
|
+
const hiddenBadge = isVisual && isHidden
|
|
1135
|
+
? '<span class="layout-badge disabled">Hidden</span>'
|
|
1136
|
+
: '';
|
|
1137
|
+
const orderBadge = isVisual && Number.isFinite(order)
|
|
1138
|
+
? `<span class="layout-badge">#${order}</span>`
|
|
1139
|
+
: '';
|
|
1140
|
+
const layoutActions = isVisual
|
|
1141
|
+
? `
|
|
1142
|
+
<button class="action-btn move-up-btn" title="Move up">↑</button>
|
|
1143
|
+
<button class="action-btn move-down-btn" title="Move down">↓</button>
|
|
1144
|
+
<button class="action-btn toggle-enabled-btn" title="${isHidden ? 'Show chart' : 'Hide chart'}">
|
|
1145
|
+
${isHidden ? 'Show' : 'Hide'}
|
|
1146
|
+
</button>
|
|
1147
|
+
`
|
|
1148
|
+
: '';
|
|
1149
|
+
const contentHeightStyle = isVisual && chartState?.height
|
|
1150
|
+
? `style="height: ${chartState.height}px; min-height: ${chartState.height}px; overflow: auto;"`
|
|
1151
|
+
: '';
|
|
1152
|
+
const resizeHandle = isVisual
|
|
1153
|
+
? `<div class="resize-handle" data-filename="${escapeHtml(artifact.filename)}" title="Drag to resize height" role="separator"></div>`
|
|
1154
|
+
: '';
|
|
1155
|
+
|
|
1156
|
+
return `
|
|
1157
|
+
<div class="artifact-card ${isVisual && isHidden ? 'layout-disabled' : ''}${isExpanded ? ' expanded' : ''}" data-path="${escapeHtml(artifact.localPath)}" data-type="${artifact.type}" data-filename="${escapeHtml(artifact.filename)}" data-modified-at="${escapeHtml(String(artifact.modifiedAt || ''))}">
|
|
1158
|
+
<div class="artifact-content" ${contentHeightStyle}>
|
|
1159
|
+
${content}
|
|
1160
|
+
</div>
|
|
1161
|
+
<div class="artifact-meta">
|
|
1162
|
+
<span class="artifact-name" title="${escapeHtml(artifact.filename)}">${escapeHtml(title)}</span>
|
|
1163
|
+
${sourceBadge}
|
|
1164
|
+
${hiddenBadge}
|
|
1165
|
+
${orderBadge}
|
|
1166
|
+
${generatorBadge}
|
|
1167
|
+
<span class="artifact-time">${timeAgo}</span>
|
|
1168
|
+
<span class="artifact-size">${sizeFormatted}</span>
|
|
1169
|
+
</div>
|
|
1170
|
+
<div class="artifact-actions">
|
|
1171
|
+
<button class="action-btn open-btn" title="Open in editor">Open</button>
|
|
1172
|
+
<button class="action-btn delete-btn" title="Delete artifact">Delete</button>
|
|
1173
|
+
${layoutActions}
|
|
1174
|
+
</div>
|
|
1175
|
+
${resizeHandle}
|
|
1176
|
+
</div>
|
|
1177
|
+
`;
|
|
1178
|
+
}).join('');
|
|
1179
|
+
|
|
1180
|
+
container.innerHTML = html;
|
|
1181
|
+
/** @type {Array<object>} */
|
|
1182
|
+
const htmlArtifactsNeedingFetch = [];
|
|
1183
|
+
/** @type {Array<object>} */
|
|
1184
|
+
const markdownArtifactsNeedingFetch = [];
|
|
1185
|
+
container.querySelectorAll('.artifact-card').forEach((cardEl) => {
|
|
1186
|
+
const card = /** @type {HTMLElement} */ (cardEl);
|
|
1187
|
+
const path = card.dataset.path || '';
|
|
1188
|
+
const modifiedAt = card.dataset.modifiedAt || '';
|
|
1189
|
+
const htmlHost = /** @type {HTMLElement | null} */ (card.querySelector('.html-preview'));
|
|
1190
|
+
if (htmlHost && path && modifiedAt) {
|
|
1191
|
+
const reusable = forceFreshHtml ? undefined : reusableHtmlPreviews.get(`${path}::${modifiedAt}`);
|
|
1192
|
+
if (reusable) {
|
|
1193
|
+
htmlHost.classList.add('ready');
|
|
1194
|
+
htmlHost.innerHTML = '';
|
|
1195
|
+
htmlHost.appendChild(reusable);
|
|
1196
|
+
} else {
|
|
1197
|
+
const htmlArtifact = orderedArtifacts.find((item) => item.localPath === path && item.format === 'html');
|
|
1198
|
+
if (htmlArtifact) {
|
|
1199
|
+
htmlArtifactsNeedingFetch.push(htmlArtifact);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const markdownHost = /** @type {HTMLElement | null} */ (card.querySelector('.markdown-preview'));
|
|
1205
|
+
if (markdownHost && path) {
|
|
1206
|
+
const markdownArtifact = orderedArtifacts.find((item) => item.localPath === path && item.format === 'md');
|
|
1207
|
+
if (markdownArtifact) {
|
|
1208
|
+
markdownArtifactsNeedingFetch.push(markdownArtifact);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
requestHtmlPreviewContents(htmlArtifactsNeedingFetch);
|
|
1213
|
+
requestMarkdownPreviewContents(markdownArtifactsNeedingFetch);
|
|
1214
|
+
|
|
1215
|
+
// Attach event handlers
|
|
1216
|
+
container.querySelectorAll('.artifact-card').forEach(cardEl => {
|
|
1217
|
+
const card = /** @type {HTMLElement} */ (cardEl);
|
|
1218
|
+
const path = card.dataset.path;
|
|
1219
|
+
const filename = card.dataset.filename;
|
|
1220
|
+
const isChart = card.dataset.type === 'chart';
|
|
1221
|
+
const isVisual = card.dataset.type === 'chart' || card.dataset.type === 'image';
|
|
1222
|
+
|
|
1223
|
+
card.querySelector('.open-btn')?.addEventListener('click', (e) => {
|
|
1224
|
+
e.stopPropagation();
|
|
1225
|
+
panelApi.postMessage({ type: 'openArtifact', path });
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
card.querySelector('.delete-btn')?.addEventListener('click', (e) => {
|
|
1229
|
+
e.stopPropagation();
|
|
1230
|
+
panelApi.postMessage({ type: 'deleteArtifact', path });
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
if (isChart && filename) {
|
|
1234
|
+
card.querySelector('.move-up-btn')?.addEventListener('click', (e) => {
|
|
1235
|
+
e.stopPropagation();
|
|
1236
|
+
moveChartInPanelState(filename, 'up', interactiveChartKeys);
|
|
1237
|
+
renderGallery(state.artifacts);
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
card.querySelector('.move-down-btn')?.addEventListener('click', (e) => {
|
|
1241
|
+
e.stopPropagation();
|
|
1242
|
+
moveChartInPanelState(filename, 'down', interactiveChartKeys);
|
|
1243
|
+
renderGallery(state.artifacts);
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
card.querySelector('.toggle-enabled-btn')?.addEventListener('click', (e) => {
|
|
1247
|
+
e.stopPropagation();
|
|
1248
|
+
toggleChartHiddenInPanelState(filename);
|
|
1249
|
+
renderGallery(state.artifacts);
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Click on card to expand/collapse
|
|
1254
|
+
card.addEventListener('click', (event) => {
|
|
1255
|
+
const target = /** @type {HTMLElement | null} */ (event.target instanceof HTMLElement ? event.target : null);
|
|
1256
|
+
if (target?.closest('button,.resize-handle')) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const nextExpanded = !card.classList.contains('expanded');
|
|
1260
|
+
card.classList.toggle('expanded', nextExpanded);
|
|
1261
|
+
if (isVisual && filename) {
|
|
1262
|
+
setChartExpanded(filename, nextExpanded);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
if (filename) {
|
|
1267
|
+
attachResizeHandle(card, filename);
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
// Markdown previews are loaded via host message passing (artifactContent)
|
|
1272
|
+
// to avoid fetch restrictions for local report files.
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Build a compact signature for artifact catalog identity.
|
|
1277
|
+
* Used to avoid expensive no-op rerenders on duplicate updates.
|
|
1278
|
+
* @param {Array<object>} artifacts
|
|
1279
|
+
* @returns {string}
|
|
1280
|
+
*/
|
|
1281
|
+
function getArtifactCatalogSignature(artifacts) {
|
|
1282
|
+
return artifacts
|
|
1283
|
+
.map((artifact) => [
|
|
1284
|
+
String(artifact.localPath || ''),
|
|
1285
|
+
String(artifact.modifiedAt || ''),
|
|
1286
|
+
String(artifact.size || ''),
|
|
1287
|
+
String(artifact.format || ''),
|
|
1288
|
+
String(artifact.type || ''),
|
|
1289
|
+
].join('|'))
|
|
1290
|
+
.sort()
|
|
1291
|
+
.join('\n');
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/** Signature ignoring export files — chart HTML identity for preserveFleetIframe fast path. */
|
|
1295
|
+
function getArtifactCatalogSignatureExcludingExports(artifacts) {
|
|
1296
|
+
return getArtifactCatalogSignature(artifacts.filter((artifact) => artifact.type !== 'export'));
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Add nonce to inline script tags so they can run in srcdoc.
|
|
1301
|
+
* @param {string} html
|
|
1302
|
+
* @param {string} nonce
|
|
1303
|
+
* @returns {string}
|
|
1304
|
+
*/
|
|
1305
|
+
function injectNonceIntoInlineScripts(html, nonce) {
|
|
1306
|
+
if (!nonce) {
|
|
1307
|
+
return html;
|
|
1308
|
+
}
|
|
1309
|
+
return html.replace(/<script(?![^>]*\bnonce=)([^>]*)>/gi, `<script nonce="${nonce}"$1>`);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* Inject a keyboard shortcut bridge for embedded iframe widgets so common
|
|
1314
|
+
* shortcuts (Ctrl/Cmd+A/C/X/V) work reliably in webview sandboxes.
|
|
1315
|
+
* @param {string} html
|
|
1316
|
+
* @param {string} nonce
|
|
1317
|
+
* @returns {string}
|
|
1318
|
+
*/
|
|
1319
|
+
function injectIframeKeyboardShortcutBridge(html, nonce) {
|
|
1320
|
+
const bridge = `<script nonce="${nonce}">(function(){
|
|
1321
|
+
try {
|
|
1322
|
+
var pending = new Map();
|
|
1323
|
+
function requestClipboardText() {
|
|
1324
|
+
return new Promise(function(resolve){
|
|
1325
|
+
var requestId = 'clip-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
|
|
1326
|
+
var timer = setTimeout(function(){ pending.delete(requestId); resolve(''); }, 1500);
|
|
1327
|
+
pending.set(requestId, { resolve: resolve, timer: timer });
|
|
1328
|
+
window.parent.postMessage({ source: 'agentide-wow-widget', type: 'requestClipboardText', requestId: requestId }, '*');
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
window.addEventListener('message', function(event){
|
|
1332
|
+
var payload = event.data;
|
|
1333
|
+
if (!payload || payload.source !== 'agentide-panel' || payload.type !== 'clipboardText') return;
|
|
1334
|
+
var entry = pending.get(payload.requestId);
|
|
1335
|
+
if (!entry) return;
|
|
1336
|
+
clearTimeout(entry.timer);
|
|
1337
|
+
pending.delete(payload.requestId);
|
|
1338
|
+
entry.resolve(typeof payload.text === 'string' ? payload.text : '');
|
|
1339
|
+
});
|
|
1340
|
+
function getEditable(target) {
|
|
1341
|
+
if (!target) return null;
|
|
1342
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) return target;
|
|
1343
|
+
if (target instanceof HTMLElement && target.isContentEditable) return target;
|
|
1344
|
+
if (target instanceof HTMLElement) {
|
|
1345
|
+
var parentEditable = target.closest('[contenteditable=""],[contenteditable="true"]');
|
|
1346
|
+
return parentEditable instanceof HTMLElement ? parentEditable : null;
|
|
1347
|
+
}
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
function writeClipboardText(text) {
|
|
1351
|
+
var value = String(text || '');
|
|
1352
|
+
if (!value) return;
|
|
1353
|
+
try {
|
|
1354
|
+
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
1355
|
+
void navigator.clipboard.writeText(value).catch(function(){
|
|
1356
|
+
window.parent.postMessage({ source: 'agentide-wow-widget', type: 'writeClipboardText', text: value }, '*');
|
|
1357
|
+
});
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
} catch (_err) {
|
|
1361
|
+
// fall back to host bridge
|
|
1362
|
+
}
|
|
1363
|
+
window.parent.postMessage({ source: 'agentide-wow-widget', type: 'writeClipboardText', text: value }, '*');
|
|
1364
|
+
}
|
|
1365
|
+
function selectAll(editable) {
|
|
1366
|
+
if (editable instanceof HTMLInputElement || editable instanceof HTMLTextAreaElement) {
|
|
1367
|
+
editable.focus();
|
|
1368
|
+
editable.selectionStart = 0;
|
|
1369
|
+
editable.selectionEnd = editable.value.length;
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (editable instanceof HTMLElement) {
|
|
1373
|
+
var range = document.createRange();
|
|
1374
|
+
range.selectNodeContents(editable);
|
|
1375
|
+
var selection = window.getSelection();
|
|
1376
|
+
if (!selection) return;
|
|
1377
|
+
selection.removeAllRanges();
|
|
1378
|
+
selection.addRange(range);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function getSelectedText(editable) {
|
|
1382
|
+
if (editable instanceof HTMLInputElement || editable instanceof HTMLTextAreaElement) {
|
|
1383
|
+
var start = Number.isFinite(editable.selectionStart) ? editable.selectionStart : 0;
|
|
1384
|
+
var end = Number.isFinite(editable.selectionEnd) ? editable.selectionEnd : 0;
|
|
1385
|
+
if (end <= start) return '';
|
|
1386
|
+
return editable.value.slice(start, end);
|
|
1387
|
+
}
|
|
1388
|
+
var selection = window.getSelection();
|
|
1389
|
+
return selection ? String(selection.toString() || '') : '';
|
|
1390
|
+
}
|
|
1391
|
+
function replaceSelection(editable, text) {
|
|
1392
|
+
if (editable instanceof HTMLInputElement || editable instanceof HTMLTextAreaElement) {
|
|
1393
|
+
var start = Number.isFinite(editable.selectionStart) ? editable.selectionStart : editable.value.length;
|
|
1394
|
+
var end = Number.isFinite(editable.selectionEnd) ? editable.selectionEnd : editable.value.length;
|
|
1395
|
+
var before = editable.value.slice(0, start);
|
|
1396
|
+
var after = editable.value.slice(end);
|
|
1397
|
+
editable.value = before + text + after;
|
|
1398
|
+
var caret = start + text.length;
|
|
1399
|
+
editable.selectionStart = caret;
|
|
1400
|
+
editable.selectionEnd = caret;
|
|
1401
|
+
editable.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
if (!(editable instanceof HTMLElement)) return;
|
|
1405
|
+
var selection = window.getSelection();
|
|
1406
|
+
if (!selection || selection.rangeCount === 0) return;
|
|
1407
|
+
var range = selection.getRangeAt(0);
|
|
1408
|
+
range.deleteContents();
|
|
1409
|
+
var node = document.createTextNode(text);
|
|
1410
|
+
range.insertNode(node);
|
|
1411
|
+
range.setStartAfter(node);
|
|
1412
|
+
range.collapse(true);
|
|
1413
|
+
selection.removeAllRanges();
|
|
1414
|
+
selection.addRange(range);
|
|
1415
|
+
}
|
|
1416
|
+
document.addEventListener('keydown', function(event){
|
|
1417
|
+
if (event.defaultPrevented || event.altKey || !(event.metaKey || event.ctrlKey)) return;
|
|
1418
|
+
var key = String(event.key || '').toLowerCase();
|
|
1419
|
+
var editable = getEditable(document.activeElement) || getEditable(event.target);
|
|
1420
|
+
if (key === 'a') {
|
|
1421
|
+
if (!editable) return;
|
|
1422
|
+
event.preventDefault();
|
|
1423
|
+
selectAll(editable);
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
if (key === 'c') {
|
|
1427
|
+
var selected = getSelectedText(editable || null);
|
|
1428
|
+
if (!selected) return;
|
|
1429
|
+
event.preventDefault();
|
|
1430
|
+
writeClipboardText(selected);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
if (key === 'x') {
|
|
1434
|
+
if (!editable) return;
|
|
1435
|
+
var selectedForCut = getSelectedText(editable);
|
|
1436
|
+
if (!selectedForCut) return;
|
|
1437
|
+
event.preventDefault();
|
|
1438
|
+
writeClipboardText(selectedForCut);
|
|
1439
|
+
replaceSelection(editable, '');
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
if (key === 'v') {
|
|
1443
|
+
if (!editable) return;
|
|
1444
|
+
event.preventDefault();
|
|
1445
|
+
void requestClipboardText().then(function(text){
|
|
1446
|
+
if (!text) return;
|
|
1447
|
+
replaceSelection(editable, text);
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
}, true);
|
|
1451
|
+
document.addEventListener('copy', function(event){
|
|
1452
|
+
if (event.defaultPrevented) return;
|
|
1453
|
+
var editable = getEditable(document.activeElement) || getEditable(event.target);
|
|
1454
|
+
var selected = getSelectedText(editable || null);
|
|
1455
|
+
if (!selected) return;
|
|
1456
|
+
if (event.clipboardData) {
|
|
1457
|
+
event.clipboardData.setData('text/plain', selected);
|
|
1458
|
+
event.preventDefault();
|
|
1459
|
+
}
|
|
1460
|
+
writeClipboardText(selected);
|
|
1461
|
+
}, true);
|
|
1462
|
+
document.addEventListener('cut', function(event){
|
|
1463
|
+
if (event.defaultPrevented) return;
|
|
1464
|
+
var editable = getEditable(document.activeElement) || getEditable(event.target);
|
|
1465
|
+
if (!editable) return;
|
|
1466
|
+
var selected = getSelectedText(editable);
|
|
1467
|
+
if (!selected) return;
|
|
1468
|
+
if (event.clipboardData) {
|
|
1469
|
+
event.clipboardData.setData('text/plain', selected);
|
|
1470
|
+
event.preventDefault();
|
|
1471
|
+
}
|
|
1472
|
+
writeClipboardText(selected);
|
|
1473
|
+
replaceSelection(editable, '');
|
|
1474
|
+
}, true);
|
|
1475
|
+
document.addEventListener('paste', function(event){
|
|
1476
|
+
if (event.defaultPrevented) return;
|
|
1477
|
+
var editable = getEditable(document.activeElement) || getEditable(event.target);
|
|
1478
|
+
if (!editable) return;
|
|
1479
|
+
if (event.clipboardData && event.clipboardData.getData('text')) return;
|
|
1480
|
+
event.preventDefault();
|
|
1481
|
+
void requestClipboardText().then(function(text){
|
|
1482
|
+
if (!text) return;
|
|
1483
|
+
replaceSelection(editable, text);
|
|
1484
|
+
});
|
|
1485
|
+
}, true);
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
// no-op
|
|
1488
|
+
}
|
|
1489
|
+
})();</script>`;
|
|
1490
|
+
if (/<\/body>/i.test(html)) {
|
|
1491
|
+
return html.replace(/<\/body>/i, `${bridge}</body>`);
|
|
1492
|
+
}
|
|
1493
|
+
return `${html}${bridge}`;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* Ask host to read HTML artifact contents by local path.
|
|
1498
|
+
* This avoids webview URL fetch/DNS issues for nested iframe previews.
|
|
1499
|
+
* @param {Array<object>} artifacts
|
|
1500
|
+
*/
|
|
1501
|
+
function requestHtmlPreviewContents(artifacts) {
|
|
1502
|
+
for (const artifact of artifacts) {
|
|
1503
|
+
if (artifact.format !== 'html' || !artifact.localPath) {
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
panelApi.postMessage({
|
|
1507
|
+
type: 'requestArtifactContent',
|
|
1508
|
+
path: artifact.localPath,
|
|
1509
|
+
format: 'html',
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Ask host to read markdown artifact contents by local path.
|
|
1516
|
+
* @param {Array<object>} artifacts
|
|
1517
|
+
*/
|
|
1518
|
+
function requestMarkdownPreviewContents(artifacts) {
|
|
1519
|
+
for (const artifact of artifacts) {
|
|
1520
|
+
if (artifact.format !== 'md' || !artifact.localPath) {
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
panelApi.postMessage({
|
|
1524
|
+
type: 'requestArtifactContent',
|
|
1525
|
+
path: artifact.localPath,
|
|
1526
|
+
format: 'md',
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* Render HTML preview for one artifact path from host-provided content.
|
|
1533
|
+
* @param {string} path
|
|
1534
|
+
* @param {string | undefined} content
|
|
1535
|
+
* @param {string | undefined} error
|
|
1536
|
+
*/
|
|
1537
|
+
function renderHtmlPreview(path, content, error) {
|
|
1538
|
+
const card = Array.from(document.querySelectorAll('.artifact-card')).find((cardEl) => {
|
|
1539
|
+
const element = /** @type {HTMLElement} */ (cardEl);
|
|
1540
|
+
return element.dataset.path === path;
|
|
1541
|
+
});
|
|
1542
|
+
const host = /** @type {HTMLElement | null} */ (card?.querySelector('.html-preview'));
|
|
1543
|
+
if (!host) {
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
if (error || !content) {
|
|
1548
|
+
const reason = error || 'No preview content available.';
|
|
1549
|
+
host.innerHTML = `<div class="html-preview-fallback">Preview unavailable (${escapeHtml(reason)}). Use <strong>Open</strong> to inspect the artifact.</div>`;
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
const scriptNonce = document.querySelector('script[nonce]')?.getAttribute('nonce') || '';
|
|
1554
|
+
const iframe = document.createElement('iframe');
|
|
1555
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
|
|
1556
|
+
iframe.setAttribute('allow', 'clipboard-read; clipboard-write');
|
|
1557
|
+
iframe.setAttribute('loading', 'lazy');
|
|
1558
|
+
iframe.tabIndex = 0;
|
|
1559
|
+
const contentWithNonce = injectNonceIntoInlineScripts(content, scriptNonce);
|
|
1560
|
+
iframe.srcdoc = injectIframeKeyboardShortcutBridge(contentWithNonce, scriptNonce);
|
|
1561
|
+
iframe.addEventListener('load', () => {
|
|
1562
|
+
host.classList.add('ready');
|
|
1563
|
+
const parentCard = host.closest('.artifact-card');
|
|
1564
|
+
if (parentCard instanceof HTMLElement && parentCard.classList.contains('fleet-dashboard-full')) {
|
|
1565
|
+
autoFitFleetCardHeight(parentCard);
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
host.innerHTML = '';
|
|
1569
|
+
host.appendChild(iframe);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// -----------------------------------------------------------------------------
|
|
1573
|
+
// Quick Prompts & Ask AI
|
|
1574
|
+
// -----------------------------------------------------------------------------
|
|
1575
|
+
|
|
1576
|
+
function initQuickPrompts() {
|
|
1577
|
+
// Quick prompt buttons in empty state
|
|
1578
|
+
document.querySelectorAll('.quick-prompt').forEach(buttonEl => {
|
|
1579
|
+
const btn = /** @type {HTMLElement} */ (buttonEl);
|
|
1580
|
+
btn.addEventListener('click', () => {
|
|
1581
|
+
const prompt = btn.dataset.prompt;
|
|
1582
|
+
if (prompt) {
|
|
1583
|
+
panelApi.postMessage({ type: 'openChatWithPrompt', prompt });
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
// Main "Ask AI" button
|
|
1589
|
+
document.getElementById('ask-ai-btn')?.addEventListener('click', () => {
|
|
1590
|
+
panelApi.postMessage({ type: 'openChat' });
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
// Refresh button
|
|
1594
|
+
document.getElementById('refresh-charts-btn')?.addEventListener('click', () => {
|
|
1595
|
+
refreshCurrentView();
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
// Remove legacy Expand/Shrink buttons if a user-customized panel.html still contains them.
|
|
1599
|
+
document.getElementById('expand-sidebar-btn')?.remove();
|
|
1600
|
+
document.getElementById('shrink-sidebar-btn')?.remove();
|
|
1601
|
+
|
|
1602
|
+
document.getElementById('summary-toggle-btn')?.addEventListener('click', (e) => {
|
|
1603
|
+
e.stopPropagation();
|
|
1604
|
+
toggleSummarySection();
|
|
1605
|
+
});
|
|
1606
|
+
applySummarySectionVisibility();
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// -----------------------------------------------------------------------------
|
|
1610
|
+
// Filters
|
|
1611
|
+
// -----------------------------------------------------------------------------
|
|
1612
|
+
|
|
1613
|
+
function applyFiltersToControls() {
|
|
1614
|
+
const agentSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('agent-filter-select'));
|
|
1615
|
+
if (agentSelect) {
|
|
1616
|
+
agentSelect.innerHTML = '<option value="">All agents</option>';
|
|
1617
|
+
for (const agentId of state.agentIds) {
|
|
1618
|
+
const option = document.createElement('option');
|
|
1619
|
+
option.value = agentId;
|
|
1620
|
+
option.textContent = agentId;
|
|
1621
|
+
agentSelect.appendChild(option);
|
|
1622
|
+
}
|
|
1623
|
+
agentSelect.value = state.agentId || '';
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
function postFilterUpdate() {
|
|
1628
|
+
panelApi.postMessage({
|
|
1629
|
+
type: 'updateFilters',
|
|
1630
|
+
agentId: state.agentId || '',
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function initFilters() {
|
|
1635
|
+
const agentSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('agent-filter-select'));
|
|
1636
|
+
agentSelect?.addEventListener('change', () => {
|
|
1637
|
+
state.agentId = agentSelect.value || '';
|
|
1638
|
+
postFilterUpdate();
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// -----------------------------------------------------------------------------
|
|
1643
|
+
// State Actions
|
|
1644
|
+
// -----------------------------------------------------------------------------
|
|
1645
|
+
|
|
1646
|
+
function initStateActions() {
|
|
1647
|
+
// Cancel generation
|
|
1648
|
+
document.getElementById('cancel-generate-btn')?.addEventListener('click', () => {
|
|
1649
|
+
panelApi.postMessage({ type: 'cancelGeneration' });
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
// Retry generation
|
|
1653
|
+
document.getElementById('retry-generate-btn')?.addEventListener('click', () => {
|
|
1654
|
+
panelApi.postMessage({ type: 'retryGeneration' });
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// -----------------------------------------------------------------------------
|
|
1659
|
+
// Toolbar Menu
|
|
1660
|
+
// -----------------------------------------------------------------------------
|
|
1661
|
+
|
|
1662
|
+
function initToolbarMenu() {
|
|
1663
|
+
const menuBtn = document.getElementById('toolbar-menu-btn');
|
|
1664
|
+
const dropdown = document.getElementById('toolbar-menu-dropdown');
|
|
1665
|
+
|
|
1666
|
+
if (!menuBtn || !dropdown) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/**
|
|
1671
|
+
* Toggle dropdown visibility.
|
|
1672
|
+
* @param {boolean} [forceOpen]
|
|
1673
|
+
*/
|
|
1674
|
+
function toggleMenu(forceOpen) {
|
|
1675
|
+
const isOpen = forceOpen ?? !dropdown.classList.contains('open');
|
|
1676
|
+
dropdown.classList.toggle('open', isOpen);
|
|
1677
|
+
dropdown.setAttribute('aria-hidden', String(!isOpen));
|
|
1678
|
+
menuBtn.setAttribute('aria-expanded', String(isOpen));
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// Toggle on button click
|
|
1682
|
+
menuBtn.addEventListener('click', (e) => {
|
|
1683
|
+
e.stopPropagation();
|
|
1684
|
+
toggleMenu();
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
// Close when clicking outside
|
|
1688
|
+
document.addEventListener('click', (e) => {
|
|
1689
|
+
if (!dropdown.contains(/** @type {Node} */(e.target)) && e.target !== menuBtn) {
|
|
1690
|
+
toggleMenu(false);
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
// Close on Escape
|
|
1695
|
+
document.addEventListener('keydown', (e) => {
|
|
1696
|
+
if (e.key === 'Escape' && dropdown.classList.contains('open')) {
|
|
1697
|
+
toggleMenu(false);
|
|
1698
|
+
menuBtn.focus();
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
// Handle menu item clicks
|
|
1703
|
+
dropdown.querySelectorAll('.dropdown-item[data-action]').forEach((itemEl) => {
|
|
1704
|
+
const item = /** @type {HTMLElement} */ (itemEl);
|
|
1705
|
+
item.addEventListener('click', () => {
|
|
1706
|
+
const action = item.dataset.action;
|
|
1707
|
+
toggleMenu(false);
|
|
1708
|
+
const activeView = getActiveView();
|
|
1709
|
+
|
|
1710
|
+
switch (action) {
|
|
1711
|
+
case 'new-view':
|
|
1712
|
+
promptCreateView();
|
|
1713
|
+
break;
|
|
1714
|
+
|
|
1715
|
+
case 'duplicate-view':
|
|
1716
|
+
if (activeView?.id) {
|
|
1717
|
+
panelApi.postMessage({ type: 'duplicateView', viewId: activeView.id });
|
|
1718
|
+
}
|
|
1719
|
+
break;
|
|
1720
|
+
|
|
1721
|
+
case 'rename-view': {
|
|
1722
|
+
if (!activeView || activeView.system) {
|
|
1723
|
+
break;
|
|
1724
|
+
}
|
|
1725
|
+
panelApi.postMessage({ type: 'requestRenameView', viewId: activeView.id });
|
|
1726
|
+
break;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
case 'delete-view': {
|
|
1730
|
+
if (!activeView || activeView.system) {
|
|
1731
|
+
break;
|
|
1732
|
+
}
|
|
1733
|
+
panelApi.postMessage({ type: 'requestDeleteView', viewId: activeView.id });
|
|
1734
|
+
break;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
case 'edit-view-scripts':
|
|
1738
|
+
panelApi.postMessage({ type: 'editViewScripts', viewId: activeView?.id });
|
|
1739
|
+
break;
|
|
1740
|
+
|
|
1741
|
+
case 'share-scripts-only':
|
|
1742
|
+
onShareScriptsOnlyClick();
|
|
1743
|
+
break;
|
|
1744
|
+
|
|
1745
|
+
case 'refresh-all-views':
|
|
1746
|
+
panelApi.postMessage({ type: 'refreshAllViews' });
|
|
1747
|
+
break;
|
|
1748
|
+
|
|
1749
|
+
case 'reset-home':
|
|
1750
|
+
if (confirm('Reset all charts and scripts in your home analytics folder? This will delete generated charts and user scripts, then regenerate defaults.')) {
|
|
1751
|
+
panelApi.postMessage({ type: 'resetChartsHome' });
|
|
1752
|
+
}
|
|
1753
|
+
break;
|
|
1754
|
+
|
|
1755
|
+
case 'reset-workspace':
|
|
1756
|
+
if (confirm('Reset all charts and scripts in this workspace? This will delete generated charts and user scripts for the current workspace, then regenerate defaults.')) {
|
|
1757
|
+
panelApi.postMessage({ type: 'resetChartsWorkspace' });
|
|
1758
|
+
}
|
|
1759
|
+
break;
|
|
1760
|
+
|
|
1761
|
+
case 'reenable-charts':
|
|
1762
|
+
panelApi.postMessage({ type: 'reenableAllCharts' });
|
|
1763
|
+
break;
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
/**
|
|
1770
|
+
* Push a view export snapshot into embedded dashboard iframes (no iframe reload).
|
|
1771
|
+
* Only when the active view has preserveFleetIframe (host already gates pushes).
|
|
1772
|
+
* @param {{ payload?: object, meta?: object }} message
|
|
1773
|
+
*/
|
|
1774
|
+
function postViewExportSnapshotToDashboardIframes(message) {
|
|
1775
|
+
const payload = message.payload;
|
|
1776
|
+
const meta = message.meta;
|
|
1777
|
+
document.querySelectorAll('.html-preview iframe').forEach((frameEl) => {
|
|
1778
|
+
const iframe = /** @type {HTMLIFrameElement} */ (frameEl);
|
|
1779
|
+
try {
|
|
1780
|
+
iframe.contentWindow?.postMessage({ type: VIEW_EXPORT_SNAPSHOT, payload, meta }, '*');
|
|
1781
|
+
// Legacy widgets (e.g. live-fleet-2.html) still listen for liveFleetSnapshot.
|
|
1782
|
+
iframe.contentWindow?.postMessage({ type: LEGACY_VIEW_EXPORT_SNAPSHOT, payload, meta }, '*');
|
|
1783
|
+
} catch {
|
|
1784
|
+
// Cross-origin or unloaded iframe — ignore.
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
/**
|
|
1790
|
+
* Bridge postMessage events from embedded HTML widgets.
|
|
1791
|
+
* Allows widgets to trigger host actions (e.g. copy session id).
|
|
1792
|
+
*/
|
|
1793
|
+
function initEmbeddedWidgetBridge() {
|
|
1794
|
+
window.addEventListener('message', (event) => {
|
|
1795
|
+
const data = event.data;
|
|
1796
|
+
if (!data || typeof data !== 'object') {
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
if (data.source !== 'agentide-wow-widget') {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
if (data.type === 'copySessionId' && typeof data.sessionId === 'string') {
|
|
1803
|
+
const sessionId = data.sessionId.trim();
|
|
1804
|
+
if (sessionId) {
|
|
1805
|
+
panelApi.postMessage({ type: 'copySessionId', sessionId });
|
|
1806
|
+
}
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (data.type === 'requestClipboardText' && typeof data.requestId === 'string') {
|
|
1810
|
+
const requestId = data.requestId.trim();
|
|
1811
|
+
if (requestId) {
|
|
1812
|
+
panelApi.postMessage({ type: 'requestClipboardText', requestId });
|
|
1813
|
+
}
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
if (data.type === 'writeClipboardText' && typeof data.text === 'string') {
|
|
1817
|
+
panelApi.postMessage({ type: 'writeClipboardText', text: data.text });
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
if (data.type === 'openArtifact' && typeof data.path === 'string') {
|
|
1821
|
+
const artifactPath = data.path.trim();
|
|
1822
|
+
if (artifactPath) {
|
|
1823
|
+
panelApi.postMessage({ type: 'openArtifact', path: artifactPath });
|
|
1824
|
+
}
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (data.type === 'openChatWithPrompt' && typeof data.prompt === 'string') {
|
|
1828
|
+
const prompt = data.prompt.trim();
|
|
1829
|
+
if (prompt) {
|
|
1830
|
+
panelApi.postMessage({
|
|
1831
|
+
type: 'openChatWithPrompt',
|
|
1832
|
+
prompt: prompt.slice(0, 12_000),
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if (data.type === 'disconnectFleetChannel' && typeof data.sessionId === 'string') {
|
|
1838
|
+
const sessionId = data.sessionId.trim();
|
|
1839
|
+
if (sessionId) {
|
|
1840
|
+
panelApi.postMessage({
|
|
1841
|
+
type: 'disconnectFleetChannel',
|
|
1842
|
+
sessionId,
|
|
1843
|
+
channelType: typeof data.channelType === 'string' ? data.channelType : '',
|
|
1844
|
+
channelTarget: typeof data.channelTarget === 'string' ? data.channelTarget : '',
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
if (data.type === 'updateFleetCardTitle' && typeof data.sessionId === 'string' && typeof data.cardTitle === 'string') {
|
|
1850
|
+
const sessionId = data.sessionId.trim();
|
|
1851
|
+
const sourceId = typeof data.sourceId === 'string' ? data.sourceId.trim() : 'local';
|
|
1852
|
+
if (sessionId) {
|
|
1853
|
+
panelApi.postMessage({
|
|
1854
|
+
type: 'updateFleetCardTitle',
|
|
1855
|
+
sourceId,
|
|
1856
|
+
sessionId,
|
|
1857
|
+
cardTitle: data.cardTitle,
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
if (data.type === 'openSpotlightForSession' && typeof data.sessionId === 'string') {
|
|
1863
|
+
const sessionId = data.sessionId.trim();
|
|
1864
|
+
if (sessionId) {
|
|
1865
|
+
panelApi.postMessage({
|
|
1866
|
+
type: 'openSpotlightForSession',
|
|
1867
|
+
sessionId,
|
|
1868
|
+
channelType: typeof data.channelType === 'string' ? data.channelType : '',
|
|
1869
|
+
channelTarget: typeof data.channelTarget === 'string' ? data.channelTarget : '',
|
|
1870
|
+
model: typeof data.model === 'string' ? data.model : '',
|
|
1871
|
+
engine: typeof data.engine === 'string' ? data.engine : '',
|
|
1872
|
+
agentId: typeof data.agentId === 'string' ? data.agentId : '',
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (data.type === 'refreshView') {
|
|
1878
|
+
panelApi.postMessage({ type: 'refreshView' });
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
if (data.type === 'refreshFleetWidget') {
|
|
1882
|
+
panelApi.postMessage({ type: 'refreshFleetWidget' });
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
if (data.type === 'refreshWidget') {
|
|
1886
|
+
const script = typeof data.script === 'string' ? data.script.trim() : '';
|
|
1887
|
+
const scripts = Array.isArray(data.scripts)
|
|
1888
|
+
? data.scripts.filter((s) => typeof s === 'string').map((s) => s.trim()).filter(Boolean)
|
|
1889
|
+
: [];
|
|
1890
|
+
if (!script && scripts.length === 0) {
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
panelApi.postMessage({
|
|
1894
|
+
type: 'refreshWidget',
|
|
1895
|
+
script: script || undefined,
|
|
1896
|
+
scripts: scripts.length > 0 ? scripts : undefined,
|
|
1897
|
+
});
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
if (data.type === REQUEST_VIEW_EXPORT_SNAPSHOT || data.type === LEGACY_REQUEST_LIVE_FLEET_SNAPSHOT) {
|
|
1901
|
+
panelApi.postMessage({ type: REQUEST_VIEW_EXPORT_SNAPSHOT });
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
if (data.type === 'openChat') {
|
|
1905
|
+
panelApi.postMessage({ type: 'openChat' });
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
// Fallback: forward openSpotlightForSession when message is from a frame that didn't set
|
|
1910
|
+
// source (e.g. fleet iframe in some contexts) so card click always opens chat
|
|
1911
|
+
window.addEventListener('message', (event) => {
|
|
1912
|
+
const data = event.data;
|
|
1913
|
+
if (!data || typeof data !== 'object' || data.type !== 'openSpotlightForSession') {
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
if (data.source === 'agentide-wow-widget') {
|
|
1917
|
+
return; // already handled above
|
|
1918
|
+
}
|
|
1919
|
+
if (typeof data.sessionId !== 'string') {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
const sessionId = data.sessionId.trim();
|
|
1923
|
+
if (!sessionId) {
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
panelApi.postMessage({
|
|
1927
|
+
type: 'openSpotlightForSession',
|
|
1928
|
+
sessionId,
|
|
1929
|
+
channelType: typeof data.channelType === 'string' ? data.channelType : '',
|
|
1930
|
+
channelTarget: typeof data.channelTarget === 'string' ? data.channelTarget : '',
|
|
1931
|
+
model: typeof data.model === 'string' ? data.model : '',
|
|
1932
|
+
engine: typeof data.engine === 'string' ? data.engine : '',
|
|
1933
|
+
agentId: typeof data.agentId === 'string' ? data.agentId : '',
|
|
1934
|
+
});
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/**
|
|
1939
|
+
* Show generating state
|
|
1940
|
+
*/
|
|
1941
|
+
function showGeneratingState() {
|
|
1942
|
+
showGalleryState('generating');
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
/**
|
|
1946
|
+
* Show failed state with error message
|
|
1947
|
+
* @param {string} errorMessage
|
|
1948
|
+
*/
|
|
1949
|
+
function showFailedState(errorMessage) {
|
|
1950
|
+
const errorEl = document.getElementById('error-message');
|
|
1951
|
+
if (errorEl) {
|
|
1952
|
+
errorEl.textContent = errorMessage;
|
|
1953
|
+
}
|
|
1954
|
+
showGalleryState('failed');
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
/**
|
|
1958
|
+
* Show refresh hint
|
|
1959
|
+
*/
|
|
1960
|
+
function showRefreshHint() {
|
|
1961
|
+
const hint = document.getElementById('refresh-hint');
|
|
1962
|
+
if (hint) {
|
|
1963
|
+
hint.style.display = 'flex';
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
/**
|
|
1968
|
+
* Hide refresh hint
|
|
1969
|
+
*/
|
|
1970
|
+
function hideRefreshHint() {
|
|
1971
|
+
const hint = document.getElementById('refresh-hint');
|
|
1972
|
+
if (hint) {
|
|
1973
|
+
hint.style.display = 'none';
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
/**
|
|
1978
|
+
* Show/hide per-chart refresh indicator.
|
|
1979
|
+
* @param {boolean} isRefreshing
|
|
1980
|
+
*/
|
|
1981
|
+
function setPerChartRefreshing(isRefreshing) {
|
|
1982
|
+
document.querySelectorAll('.artifact-card[data-type="chart"]').forEach(card => {
|
|
1983
|
+
card.classList.toggle('chart-refreshing', isRefreshing);
|
|
1984
|
+
let indicator = card.querySelector('.artifact-refresh-indicator');
|
|
1985
|
+
|
|
1986
|
+
if (isRefreshing) {
|
|
1987
|
+
if (!indicator) {
|
|
1988
|
+
indicator = document.createElement('div');
|
|
1989
|
+
indicator.className = 'artifact-refresh-indicator';
|
|
1990
|
+
indicator.textContent = 'Refreshing';
|
|
1991
|
+
card.appendChild(indicator);
|
|
1992
|
+
}
|
|
1993
|
+
} else if (indicator) {
|
|
1994
|
+
indicator.remove();
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
/**
|
|
2000
|
+
* Toggle refresh button busy state.
|
|
2001
|
+
* @param {boolean} isRefreshing
|
|
2002
|
+
*/
|
|
2003
|
+
function setRefreshButtonState(isRefreshing) {
|
|
2004
|
+
const btn = document.getElementById('refresh-charts-btn');
|
|
2005
|
+
if (!btn) {
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
btn.classList.toggle('refreshing', isRefreshing);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// -----------------------------------------------------------------------------
|
|
2012
|
+
// Message Handling
|
|
2013
|
+
// -----------------------------------------------------------------------------
|
|
2014
|
+
|
|
2015
|
+
panelApi.onMessage(msg => {
|
|
2016
|
+
switch (msg.type) {
|
|
2017
|
+
case 'artifactsUpdated': {
|
|
2018
|
+
const nextSignature = getArtifactCatalogSignature(msg.artifacts);
|
|
2019
|
+
const nextActiveViewId = typeof msg.activeViewId === 'string' ? msg.activeViewId : state.activeViewId;
|
|
2020
|
+
const nextFleetRefreshVersion = typeof msg.fleetRefreshVersion === 'number'
|
|
2021
|
+
? Math.max(0, Math.floor(msg.fleetRefreshVersion))
|
|
2022
|
+
: state.fleetRefreshVersion;
|
|
2023
|
+
const nextGalleryIgnore = Array.isArray(msg.galleryIgnorePatterns)
|
|
2024
|
+
? msg.galleryIgnorePatterns.filter((p) => typeof p === 'string').map((p) => String(p).trim()).filter(Boolean)
|
|
2025
|
+
: [];
|
|
2026
|
+
const isFleetView = nextActiveViewId === 'fleet';
|
|
2027
|
+
const exportOnlyCatalogChange =
|
|
2028
|
+
activeViewPreservesFleetIframe()
|
|
2029
|
+
&& nextSignature !== state.lastArtifactCatalogSignature
|
|
2030
|
+
&& getArtifactCatalogSignatureExcludingExports(msg.artifacts)
|
|
2031
|
+
=== getArtifactCatalogSignatureExcludingExports(state.artifacts);
|
|
2032
|
+
// preserveFleetIframe views (default for fleet-dashboard layout) keep the iframe mounted
|
|
2033
|
+
// and rely on viewExportSnapshot postMessages for updates — the widget's DOM-diff
|
|
2034
|
+
// renderer applies them in-place. Non-preserve flows still reload when version bumps.
|
|
2035
|
+
const hasFleetVersionUpdate =
|
|
2036
|
+
isFleetView
|
|
2037
|
+
&& nextFleetRefreshVersion > state.fleetRefreshVersion
|
|
2038
|
+
&& !activeViewPreservesFleetIframe();
|
|
2039
|
+
// Skip only when signature unchanged and not refreshing, unless Fleet explicitly signaled fresher data.
|
|
2040
|
+
if (nextSignature === state.lastArtifactCatalogSignature && !state.isRefreshing && !hasFleetVersionUpdate) {
|
|
2041
|
+
break;
|
|
2042
|
+
}
|
|
2043
|
+
state.lastArtifactCatalogSignature = nextSignature;
|
|
2044
|
+
state.artifacts = msg.artifacts;
|
|
2045
|
+
state.galleryIgnorePatterns = nextGalleryIgnore;
|
|
2046
|
+
// View selection is owned by viewsLoaded; do not let artifact batches revert the dropdown.
|
|
2047
|
+
if (
|
|
2048
|
+
nextActiveViewId === state.activeViewId
|
|
2049
|
+
|| !state.views.some((view) => view.id === state.activeViewId)
|
|
2050
|
+
) {
|
|
2051
|
+
state.activeViewId = nextActiveViewId;
|
|
2052
|
+
}
|
|
2053
|
+
state.fleetRefreshVersion = nextFleetRefreshVersion;
|
|
2054
|
+
applyFleetAutoRefresh();
|
|
2055
|
+
if (exportOnlyCatalogChange) {
|
|
2056
|
+
// Export JSON changed; host already pushes viewExportSnapshot — avoid gallery/iframe rebuild.
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2059
|
+
// Fleet JSON can change without agent-fleet.html mtime changing if the host
|
|
2060
|
+
// did not re-run the widget script; bumping fleetRefreshVersion means data is
|
|
2061
|
+
// fresher — force a preview reload so the iframe is not reused with stale HTML.
|
|
2062
|
+
renderGallery(state.artifacts, { forceFreshHtml: hasFleetVersionUpdate });
|
|
2063
|
+
if (state.isRefreshing) {
|
|
2064
|
+
setPerChartRefreshing(true);
|
|
2065
|
+
}
|
|
2066
|
+
break;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
case 'artifactDeleted': {
|
|
2070
|
+
// Remove from gallery with animation
|
|
2071
|
+
const card = document.querySelector(`[data-path="${msg.path}"]`);
|
|
2072
|
+
if (card) {
|
|
2073
|
+
card.classList.add('deleting');
|
|
2074
|
+
setTimeout(() => card.remove(), 300);
|
|
2075
|
+
|
|
2076
|
+
// Update state
|
|
2077
|
+
state.artifacts = state.artifacts.filter(a => a.localPath !== msg.path);
|
|
2078
|
+
|
|
2079
|
+
// Check if gallery is now empty
|
|
2080
|
+
if (state.artifacts.length === 0) {
|
|
2081
|
+
showGalleryState('empty');
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
break;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
case 'summaryCards':
|
|
2088
|
+
state.summaryCards = msg.cards;
|
|
2089
|
+
renderSummaryCards(msg.cards);
|
|
2090
|
+
break;
|
|
2091
|
+
|
|
2092
|
+
case 'manifestUpdated':
|
|
2093
|
+
state.manifest = msg.manifest;
|
|
2094
|
+
// Re-render gallery with new manifest data
|
|
2095
|
+
if (state.artifacts.length > 0) {
|
|
2096
|
+
renderGallery(state.artifacts);
|
|
2097
|
+
}
|
|
2098
|
+
break;
|
|
2099
|
+
|
|
2100
|
+
case VIEW_EXPORT_SNAPSHOT:
|
|
2101
|
+
case LEGACY_VIEW_EXPORT_SNAPSHOT:
|
|
2102
|
+
postViewExportSnapshotToDashboardIframes(msg);
|
|
2103
|
+
break;
|
|
2104
|
+
|
|
2105
|
+
case 'artifactContent':
|
|
2106
|
+
if (msg.path && msg.format === 'html') {
|
|
2107
|
+
renderHtmlPreview(msg.path, msg.content, msg.error);
|
|
2108
|
+
}
|
|
2109
|
+
if (msg.path && msg.format === 'md') {
|
|
2110
|
+
const card = Array.from(document.querySelectorAll('.artifact-card')).find((cardEl) => {
|
|
2111
|
+
const element = /** @type {HTMLElement} */ (cardEl);
|
|
2112
|
+
return element.dataset.path === msg.path;
|
|
2113
|
+
});
|
|
2114
|
+
const host = /** @type {HTMLElement | null} */ (card?.querySelector('.markdown-preview'));
|
|
2115
|
+
if (host) {
|
|
2116
|
+
if (msg.error || !msg.content) {
|
|
2117
|
+
host.innerHTML = '<em>Failed to load preview</em>';
|
|
2118
|
+
} else {
|
|
2119
|
+
host.innerHTML = renderMarkdown(msg.content);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
break;
|
|
2124
|
+
|
|
2125
|
+
case 'clipboardText':
|
|
2126
|
+
document.querySelectorAll('.html-preview iframe').forEach((frame) => {
|
|
2127
|
+
const iframe = /** @type {HTMLIFrameElement} */ (frame);
|
|
2128
|
+
iframe.contentWindow?.postMessage({
|
|
2129
|
+
source: 'agentide-panel',
|
|
2130
|
+
type: 'clipboardText',
|
|
2131
|
+
requestId: msg.requestId,
|
|
2132
|
+
text: typeof msg.text === 'string' ? msg.text : '',
|
|
2133
|
+
}, '*');
|
|
2134
|
+
});
|
|
2135
|
+
break;
|
|
2136
|
+
|
|
2137
|
+
case 'generatingStarted':
|
|
2138
|
+
setRefreshButtonState(true);
|
|
2139
|
+
state.isRefreshing = false;
|
|
2140
|
+
setPerChartRefreshing(false);
|
|
2141
|
+
showGeneratingState();
|
|
2142
|
+
break;
|
|
2143
|
+
|
|
2144
|
+
case 'generatingCancelled':
|
|
2145
|
+
setRefreshButtonState(false);
|
|
2146
|
+
state.isRefreshing = false;
|
|
2147
|
+
setPerChartRefreshing(false);
|
|
2148
|
+
// Go back to empty or content state
|
|
2149
|
+
if (state.artifacts.length > 0) {
|
|
2150
|
+
renderGallery(state.artifacts);
|
|
2151
|
+
} else {
|
|
2152
|
+
showGalleryState('empty');
|
|
2153
|
+
}
|
|
2154
|
+
break;
|
|
2155
|
+
|
|
2156
|
+
case 'generatingFailed':
|
|
2157
|
+
setRefreshButtonState(false);
|
|
2158
|
+
state.isRefreshing = false;
|
|
2159
|
+
setPerChartRefreshing(false);
|
|
2160
|
+
showFailedState(msg.error);
|
|
2161
|
+
break;
|
|
2162
|
+
|
|
2163
|
+
case 'refreshStarted':
|
|
2164
|
+
if (!msg.viewId || msg.viewId === state.activeViewId) {
|
|
2165
|
+
setRefreshButtonState(true);
|
|
2166
|
+
state.isRefreshing = true;
|
|
2167
|
+
setPerChartRefreshing(true);
|
|
2168
|
+
}
|
|
2169
|
+
break;
|
|
2170
|
+
|
|
2171
|
+
case 'refreshFinished':
|
|
2172
|
+
if (!msg.viewId || msg.viewId === state.activeViewId) {
|
|
2173
|
+
setRefreshButtonState(false);
|
|
2174
|
+
state.isRefreshing = false;
|
|
2175
|
+
setPerChartRefreshing(false);
|
|
2176
|
+
if (activeViewPreservesFleetIframe()) {
|
|
2177
|
+
document.querySelectorAll('.html-preview iframe').forEach((frameEl) => {
|
|
2178
|
+
const iframe = /** @type {HTMLIFrameElement} */ (frameEl);
|
|
2179
|
+
try {
|
|
2180
|
+
iframe.contentWindow?.postMessage({ type: 'fleetRefreshComplete' }, '*');
|
|
2181
|
+
} catch {
|
|
2182
|
+
// Cross-origin or unloaded iframe — ignore.
|
|
2183
|
+
}
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
break;
|
|
2188
|
+
|
|
2189
|
+
case 'viewsLoaded':
|
|
2190
|
+
if (msg.surfaceMode === 'team' || msg.surfaceMode === 'spotlight') {
|
|
2191
|
+
applySurfaceMode(msg.surfaceMode);
|
|
2192
|
+
}
|
|
2193
|
+
state.views = Array.isArray(msg.views) ? msg.views : [];
|
|
2194
|
+
state.activeViewId = typeof msg.activeViewId === 'string' ? msg.activeViewId : state.activeViewId;
|
|
2195
|
+
applyViewOptions();
|
|
2196
|
+
applyFleetAutoRefresh();
|
|
2197
|
+
break;
|
|
2198
|
+
|
|
2199
|
+
case 'refreshNoData':
|
|
2200
|
+
if (!msg.viewId || msg.viewId === state.activeViewId) {
|
|
2201
|
+
setRefreshButtonState(false);
|
|
2202
|
+
state.isRefreshing = false;
|
|
2203
|
+
setPerChartRefreshing(false);
|
|
2204
|
+
}
|
|
2205
|
+
break;
|
|
2206
|
+
|
|
2207
|
+
case 'refreshFailed':
|
|
2208
|
+
if (!msg.viewId || msg.viewId === state.activeViewId) {
|
|
2209
|
+
setRefreshButtonState(false);
|
|
2210
|
+
state.isRefreshing = false;
|
|
2211
|
+
setPerChartRefreshing(false);
|
|
2212
|
+
if (state.artifacts.length === 0) {
|
|
2213
|
+
showFailedState(msg.error);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
break;
|
|
2217
|
+
|
|
2218
|
+
case 'filtersUpdated':
|
|
2219
|
+
state.scope = msg.scope === 'workspace' ? 'workspace' : 'global';
|
|
2220
|
+
state.projectId = msg.projectId || '';
|
|
2221
|
+
state.agentId = msg.agentId || '';
|
|
2222
|
+
state.agentIds = Array.isArray(msg.agentIds) ? msg.agentIds : [];
|
|
2223
|
+
// Note: Don't override activeViewId from filtersUpdated. This prevents race conditions
|
|
2224
|
+
// where filtersUpdated (sent last) overwrites the correct activeViewId set by viewsLoaded.
|
|
2225
|
+
// We still call applyViewOptions() to keep the dropdown UI in sync with the views list.
|
|
2226
|
+
applyViewOptions();
|
|
2227
|
+
applyFleetAutoRefresh();
|
|
2228
|
+
applyFiltersToControls();
|
|
2229
|
+
break;
|
|
2230
|
+
|
|
2231
|
+
case 'panelStateLoaded':
|
|
2232
|
+
if (msg.panelId && msg.panelId !== PANEL_ID) {
|
|
2233
|
+
break;
|
|
2234
|
+
}
|
|
2235
|
+
if (msg.surfaceMode === 'team' || msg.surfaceMode === 'spotlight') {
|
|
2236
|
+
applySurfaceMode(msg.surfaceMode);
|
|
2237
|
+
}
|
|
2238
|
+
state.panelState = normalizePanelState(msg.state);
|
|
2239
|
+
state.panelStateLoaded = true;
|
|
2240
|
+
applySummarySectionVisibility();
|
|
2241
|
+
if (typeof msg.fleetAutoRefreshIntervalSeconds === 'number') {
|
|
2242
|
+
state.fleetAutoRefreshIntervalSeconds = Math.max(0, msg.fleetAutoRefreshIntervalSeconds);
|
|
2243
|
+
}
|
|
2244
|
+
applyFleetAutoRefresh();
|
|
2245
|
+
if (ensurePanelStateForArtifacts(state.artifacts)) {
|
|
2246
|
+
savePanelState();
|
|
2247
|
+
}
|
|
2248
|
+
if (state.artifacts.length > 0) {
|
|
2249
|
+
renderGallery(state.artifacts);
|
|
2250
|
+
}
|
|
2251
|
+
break;
|
|
2252
|
+
|
|
2253
|
+
case 'disabledChartsUpdated':
|
|
2254
|
+
state.disabledCharts = new Set(
|
|
2255
|
+
(Array.isArray(msg.charts) ? msg.charts : [])
|
|
2256
|
+
.map((name) => String(name || '').trim().toLowerCase())
|
|
2257
|
+
.filter(Boolean),
|
|
2258
|
+
);
|
|
2259
|
+
renderGallery(state.artifacts);
|
|
2260
|
+
break;
|
|
2261
|
+
|
|
2262
|
+
case 'dataStale':
|
|
2263
|
+
showRefreshHint();
|
|
2264
|
+
break;
|
|
2265
|
+
|
|
2266
|
+
case 'dataFresh':
|
|
2267
|
+
hideRefreshHint();
|
|
2268
|
+
break;
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2271
|
+
|
|
2272
|
+
// -----------------------------------------------------------------------------
|
|
2273
|
+
// Initialization
|
|
2274
|
+
// -----------------------------------------------------------------------------
|
|
2275
|
+
|
|
2276
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2277
|
+
if (document.body.classList.contains('spotlight-surface-team')) {
|
|
2278
|
+
applySurfaceMode('team');
|
|
2279
|
+
}
|
|
2280
|
+
ensureShareToolbarButton();
|
|
2281
|
+
initQuickPrompts();
|
|
2282
|
+
initStateActions();
|
|
2283
|
+
initEmbeddedWidgetBridge();
|
|
2284
|
+
initFilters();
|
|
2285
|
+
initViews();
|
|
2286
|
+
initToolbarMenu();
|
|
2287
|
+
renderSummaryPlaceholders();
|
|
2288
|
+
|
|
2289
|
+
// Request initial data
|
|
2290
|
+
panelApi.postMessage({ type: 'requestViews' });
|
|
2291
|
+
panelApi.postMessage({ type: 'requestPanelState', panelId: PANEL_ID });
|
|
2292
|
+
panelApi.postMessage({ type: 'requestArtifacts' });
|
|
2293
|
+
panelApi.postMessage({ type: 'requestSummary' });
|
|
2294
|
+
panelApi.postMessage({ type: 'requestFilters' });
|
|
2295
|
+
});
|