@sarjallab09/figma-intelligence 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -36
- package/dist/bin/cli.js +2 -0
- package/dist/design-bridge/bridge.js +2 -0
- package/dist/figma-bridge-plugin/bridge-relay.js +2 -0
- package/dist/figma-bridge-plugin/code.js +1 -0
- package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package-lock.json +0 -3
- package/dist/figma-bridge-plugin/ui.html +4970 -0
- package/dist/figma-intelligence-layer/dist/index.js +2 -0
- package/dist/scripts/clean-existing-chunks.js +2 -0
- package/dist/scripts/connect-ai-tool.js +2 -0
- package/dist/scripts/convert-hub-pdfs.js +2 -0
- package/dist/scripts/figma-mcp-status.js +2 -0
- package/dist/scripts/register-codex-mcp.js +2 -0
- package/dist/scripts/test-copilot-chat.js +2 -0
- package/package.json +11 -8
- package/bin/cli.js +0 -859
- package/design-bridge/bridge.js +0 -196
- package/design-bridge/lib/assets.js +0 -367
- package/design-bridge/lib/prompt.js +0 -85
- package/design-bridge/lib/server.js +0 -66
- package/design-bridge/lib/stitch.js +0 -37
- package/design-bridge/lib/tokens.js +0 -82
- package/design-bridge/package-lock.json +0 -579
- package/figma-bridge-plugin/README.md +0 -97
- package/figma-bridge-plugin/anthropic-chat-runner.js +0 -192
- package/figma-bridge-plugin/bridge-relay.js +0 -2505
- package/figma-bridge-plugin/chat-runner.js +0 -485
- package/figma-bridge-plugin/code.js +0 -1534
- package/figma-bridge-plugin/codex-runner.js +0 -505
- package/figma-bridge-plugin/component-schemas.js +0 -110
- package/figma-bridge-plugin/content-context.js +0 -869
- package/figma-bridge-plugin/create-button.js +0 -216
- package/figma-bridge-plugin/gemini-cli-runner.js +0 -291
- package/figma-bridge-plugin/gemini-runner.js +0 -187
- package/figma-bridge-plugin/html-to-figma.js +0 -927
- package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +0 -159
- package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +0 -162
- package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +0 -148
- package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +0 -314
- package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +0 -175
- package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +0 -180
- package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +0 -165
- package/figma-bridge-plugin/perplexity-runner.js +0 -188
- package/figma-bridge-plugin/references/SKILL.md +0 -178
- package/figma-bridge-plugin/references/anatomy-spec.md +0 -159
- package/figma-bridge-plugin/references/api-spec.md +0 -162
- package/figma-bridge-plugin/references/color-spec.md +0 -148
- package/figma-bridge-plugin/references/full-spec-template.md +0 -314
- package/figma-bridge-plugin/references/property-spec.md +0 -175
- package/figma-bridge-plugin/references/screen-reader-spec.md +0 -180
- package/figma-bridge-plugin/references/structure-spec.md +0 -165
- package/figma-bridge-plugin/shared-prompt-config.js +0 -645
- package/figma-bridge-plugin/spec-helpers/build-table.js +0 -269
- package/figma-bridge-plugin/spec-helpers/classify-elements.js +0 -189
- package/figma-bridge-plugin/spec-helpers/index.js +0 -35
- package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +0 -49
- package/figma-bridge-plugin/spec-helpers/position-markers.js +0 -158
- package/figma-bridge-plugin/stitch-auth.js +0 -322
- package/figma-bridge-plugin/stitch-runner.js +0 -1427
- package/figma-bridge-plugin/token-resolver.js +0 -107
- package/figma-bridge-plugin/ui.html +0 -4542
- package/figma-intelligence-layer/.env.example +0 -39
- package/figma-intelligence-layer/docs/local-image-generation.md +0 -60
- package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +0 -101
- package/figma-intelligence-layer/jest.config.js +0 -14
- package/figma-intelligence-layer/mcp-config.json +0 -19
- package/figma-intelligence-layer/package-lock.json +0 -5892
- package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +0 -67
- package/figma-intelligence-layer/scripts/start-comfyui.sh +0 -33
- package/figma-intelligence-layer/src/index.ts +0 -2233
- package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +0 -404
- package/figma-intelligence-layer/src/shared/cache.ts +0 -187
- package/figma-intelligence-layer/src/shared/color-operations.ts +0 -533
- package/figma-intelligence-layer/src/shared/color-utils.ts +0 -138
- package/figma-intelligence-layer/src/shared/component-script-builder.ts +0 -413
- package/figma-intelligence-layer/src/shared/component-templates.ts +0 -2767
- package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +0 -694
- package/figma-intelligence-layer/src/shared/decision-log.ts +0 -128
- package/figma-intelligence-layer/src/shared/design-system-context.ts +0 -568
- package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +0 -131
- package/figma-intelligence-layer/src/shared/design-system-matcher.ts +0 -184
- package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +0 -196
- package/figma-intelligence-layer/src/shared/design-system-tokens.ts +0 -295
- package/figma-intelligence-layer/src/shared/dtcg-validator.ts +0 -530
- package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +0 -671
- package/figma-intelligence-layer/src/shared/figma-bridge.ts +0 -1418
- package/figma-intelligence-layer/src/shared/font-config.ts +0 -126
- package/figma-intelligence-layer/src/shared/icon-catalog.ts +0 -360
- package/figma-intelligence-layer/src/shared/icon-fetch.ts +0 -80
- package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +0 -162
- package/figma-intelligence-layer/src/shared/response-compression.ts +0 -440
- package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +0 -324
- package/figma-intelligence-layer/src/shared/token-binder.ts +0 -505
- package/figma-intelligence-layer/src/shared/token-math.ts +0 -427
- package/figma-intelligence-layer/src/shared/token-naming.ts +0 -468
- package/figma-intelligence-layer/src/shared/token-utils.ts +0 -420
- package/figma-intelligence-layer/src/shared/types.ts +0 -346
- package/figma-intelligence-layer/src/shared/typography-presets.ts +0 -94
- package/figma-intelligence-layer/src/shared/unsplash.ts +0 -165
- package/figma-intelligence-layer/src/shared/vision-client.ts +0 -607
- package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +0 -334
- package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +0 -446
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +0 -782
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +0 -496
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +0 -230
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +0 -66
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +0 -810
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +0 -1191
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +0 -1346
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +0 -148
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +0 -499
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +0 -910
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +0 -989
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +0 -1160
- package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +0 -424
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +0 -38
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +0 -111
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +0 -114
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +0 -103
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +0 -1060
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +0 -18
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +0 -39
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +0 -58
- package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +0 -298
- package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +0 -197
- package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +0 -494
- package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +0 -356
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +0 -123
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +0 -663
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +0 -56
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +0 -614
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +0 -113
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +0 -178
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +0 -470
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +0 -429
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +0 -226
- package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +0 -535
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +0 -660
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +0 -209
- package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +0 -540
- package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +0 -391
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +0 -2019
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +0 -131
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +0 -381
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +0 -565
- package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +0 -764
- package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +0 -535
- package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +0 -84
- package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +0 -401
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +0 -68
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +0 -78
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +0 -93
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +0 -596
- package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +0 -462
- package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +0 -1470
- package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +0 -829
- package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +0 -702
- package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +0 -483
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +0 -501
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +0 -106
- package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +0 -676
- package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +0 -560
- package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +0 -1043
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +0 -620
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +0 -331
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +0 -77
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +0 -54
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +0 -287
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +0 -43
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +0 -221
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +0 -166
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +0 -234
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +0 -249
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +0 -231
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +0 -293
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +0 -240
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +0 -243
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +0 -307
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +0 -143
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +0 -227
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +0 -233
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +0 -282
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +0 -276
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +0 -223
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +0 -289
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +0 -261
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +0 -290
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +0 -265
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +0 -238
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +0 -128
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +0 -286
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +0 -330
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +0 -247
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +0 -250
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +0 -247
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +0 -144
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +0 -264
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +0 -251
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +0 -261
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +0 -248
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +0 -251
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +0 -142
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +0 -282
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +0 -250
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +0 -258
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +0 -265
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +0 -319
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +0 -256
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +0 -239
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +0 -252
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +0 -244
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +0 -143
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +0 -243
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +0 -259
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +0 -293
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +0 -144
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +0 -289
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +0 -267
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +0 -257
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +0 -319
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +0 -121
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +0 -430
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +0 -312
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +0 -129
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +0 -78
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +0 -2333
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +0 -100
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +0 -32
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +0 -59
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +0 -18
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +0 -53
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +0 -91
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +0 -110
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +0 -67
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +0 -58
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +0 -79
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +0 -50
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +0 -33
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +0 -55
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +0 -73
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +0 -81
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +0 -409
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +0 -198
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +0 -701
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +0 -88
- package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +0 -135
- package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +0 -491
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +0 -416
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +0 -722
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +0 -449
- package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +0 -393
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +0 -406
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +0 -292
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +0 -24
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +0 -172
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +0 -409
- package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +0 -594
- package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +0 -710
- package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +0 -458
- package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +0 -134
- package/figma-intelligence-layer/tests/apg-doc.test.ts +0 -101
- package/figma-intelligence-layer/tests/design-system-context.test.ts +0 -152
- package/figma-intelligence-layer/tests/design-system-matcher.test.ts +0 -144
- package/figma-intelligence-layer/tests/figma-bridge.test.ts +0 -83
- package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +0 -56
- package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +0 -69
- package/figma-intelligence-layer/tests/smoke.test.ts +0 -174
- package/figma-intelligence-layer/tests/spec-generator.test.ts +0 -127
- package/figma-intelligence-layer/tests/token-migrate.test.ts +0 -21
- package/figma-intelligence-layer/tests/token-naming.test.ts +0 -30
- package/figma-intelligence-layer/tsconfig.json +0 -19
- package/scripts/clean-existing-chunks.js +0 -179
- package/scripts/connect-ai-tool.js +0 -490
- package/scripts/convert-hub-pdfs.js +0 -425
- package/scripts/figma-mcp-status.js +0 -349
- package/scripts/register-codex-mcp.js +0 -96
- /package/{design-bridge → dist/design-bridge}/.env.example +0 -0
- /package/{design-bridge → dist/design-bridge}/package.json +0 -0
- /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/manifest.json +0 -0
- /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package.json +0 -0
- /package/{figma-intelligence-layer → dist/figma-intelligence-layer}/package.json +0 -0
|
@@ -1,4542 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
6
|
-
<link href="https://fonts.googleapis.com/css2?family=Tiempos+Text:ital,wght@0,400;1,400&display=swap" rel="stylesheet">
|
|
7
|
-
<style>
|
|
8
|
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
-
|
|
10
|
-
:root {
|
|
11
|
-
/* Claude.ai dark theme */
|
|
12
|
-
--bg: #2b2a27;
|
|
13
|
-
--bg-header: #242422;
|
|
14
|
-
--bg-input: #3a3936;
|
|
15
|
-
--bg-user-msg: #1e1d1b;
|
|
16
|
-
--border-header: rgba(255,255,255,0.06);
|
|
17
|
-
--text-primary: #e8e6e1;
|
|
18
|
-
--text-secondary:#9b9890;
|
|
19
|
-
--text-muted: #6b6966;
|
|
20
|
-
--text-ghost: #46443f;
|
|
21
|
-
--accent: #da7756;
|
|
22
|
-
--accent-hover: #c86644;
|
|
23
|
-
--font-serif: 'Tiempos Text', 'Georgia', 'Times New Roman', serif;
|
|
24
|
-
--font-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, sans-serif;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
html, body {
|
|
28
|
-
width: 100%; height: 100%;
|
|
29
|
-
background: var(--bg);
|
|
30
|
-
font-family: var(--font-sans);
|
|
31
|
-
color: var(--text-primary);
|
|
32
|
-
font-size: 14px;
|
|
33
|
-
overflow: hidden;
|
|
34
|
-
-webkit-font-smoothing: antialiased;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.app {
|
|
38
|
-
position: relative;
|
|
39
|
-
display: flex;
|
|
40
|
-
flex-direction: column;
|
|
41
|
-
height: 100%;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/* ── Header ─────────────────────────────────────────────── */
|
|
45
|
-
.header {
|
|
46
|
-
height: 38px;
|
|
47
|
-
padding: 0 12px;
|
|
48
|
-
background: var(--bg-header);
|
|
49
|
-
border-bottom: 1px solid var(--border-header);
|
|
50
|
-
flex-shrink: 0;
|
|
51
|
-
display: flex;
|
|
52
|
-
align-items: center;
|
|
53
|
-
justify-content: space-between;
|
|
54
|
-
}
|
|
55
|
-
.header-left {
|
|
56
|
-
display: flex;
|
|
57
|
-
align-items: center;
|
|
58
|
-
gap: 5px;
|
|
59
|
-
}
|
|
60
|
-
.header-title {
|
|
61
|
-
font-size: 12.5px;
|
|
62
|
-
font-weight: 400;
|
|
63
|
-
color: var(--text-primary);
|
|
64
|
-
letter-spacing: -0.01em;
|
|
65
|
-
}
|
|
66
|
-
.header-chevron { color: var(--text-muted); line-height: 1; }
|
|
67
|
-
.header-controls {
|
|
68
|
-
display: flex;
|
|
69
|
-
align-items: center;
|
|
70
|
-
gap: 6px;
|
|
71
|
-
}
|
|
72
|
-
.icon-btn {
|
|
73
|
-
width: 26px; height: 26px;
|
|
74
|
-
display: flex; align-items: center; justify-content: center;
|
|
75
|
-
border: none; background: transparent;
|
|
76
|
-
color: var(--text-muted);
|
|
77
|
-
border-radius: 6px;
|
|
78
|
-
cursor: pointer;
|
|
79
|
-
transition: background 0.15s, color 0.15s;
|
|
80
|
-
padding: 0;
|
|
81
|
-
}
|
|
82
|
-
.icon-btn:hover {
|
|
83
|
-
background: rgba(255,255,255,0.08);
|
|
84
|
-
color: var(--text-primary);
|
|
85
|
-
}
|
|
86
|
-
.conn-pill {
|
|
87
|
-
display: flex;
|
|
88
|
-
align-items: center;
|
|
89
|
-
gap: 4px;
|
|
90
|
-
font-size: 10.5px;
|
|
91
|
-
color: var(--text-muted);
|
|
92
|
-
padding: 3px 8px;
|
|
93
|
-
border-radius: 999px;
|
|
94
|
-
background: rgba(255,255,255,0.04);
|
|
95
|
-
border: 1px solid rgba(255,255,255,0.06);
|
|
96
|
-
}
|
|
97
|
-
.conn-dot {
|
|
98
|
-
width: 5px; height: 5px;
|
|
99
|
-
border-radius: 50%;
|
|
100
|
-
background: var(--text-ghost);
|
|
101
|
-
flex-shrink: 0;
|
|
102
|
-
transition: background 0.3s;
|
|
103
|
-
}
|
|
104
|
-
.conn-dot.online { background: #4d8f5f; }
|
|
105
|
-
.conn-dot.offline { background: #8f4d4d; }
|
|
106
|
-
|
|
107
|
-
/* ── Mode tabs (Chat / Code) ─────────────────────────────── */
|
|
108
|
-
.mode-tabs {
|
|
109
|
-
display: flex;
|
|
110
|
-
gap: 2px;
|
|
111
|
-
padding: 4px 12px;
|
|
112
|
-
background: var(--bg-header);
|
|
113
|
-
border-bottom: 1px solid var(--border-header);
|
|
114
|
-
flex-shrink: 0;
|
|
115
|
-
}
|
|
116
|
-
.mode-tab {
|
|
117
|
-
flex: 1;
|
|
118
|
-
display: flex;
|
|
119
|
-
align-items: center;
|
|
120
|
-
justify-content: center;
|
|
121
|
-
gap: 5px;
|
|
122
|
-
padding: 5px 0;
|
|
123
|
-
font-size: 11.5px;
|
|
124
|
-
font-weight: 500;
|
|
125
|
-
color: var(--text-muted);
|
|
126
|
-
background: transparent;
|
|
127
|
-
border: none;
|
|
128
|
-
border-radius: 7px;
|
|
129
|
-
cursor: pointer;
|
|
130
|
-
transition: background 0.13s, color 0.13s;
|
|
131
|
-
font-family: var(--font-sans);
|
|
132
|
-
}
|
|
133
|
-
.mode-tab:hover { background: rgba(255,255,255,0.05); color: var(--text-secondary); }
|
|
134
|
-
.mode-tab.active {
|
|
135
|
-
background: rgba(255,255,255,0.08);
|
|
136
|
-
color: var(--text-primary);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/* ── Auth strip ──────────────────────────────────────────── */
|
|
140
|
-
.auth-strip {
|
|
141
|
-
padding: 4px 12px;
|
|
142
|
-
font-size: 10.5px;
|
|
143
|
-
color: var(--text-muted);
|
|
144
|
-
background: var(--bg-header);
|
|
145
|
-
border-bottom: 1px solid var(--border-header);
|
|
146
|
-
flex-shrink: 0;
|
|
147
|
-
}
|
|
148
|
-
.auth-strip.ok { color: #4d8f5f; }
|
|
149
|
-
.auth-email { color: #4d8f5f; }
|
|
150
|
-
|
|
151
|
-
/* ── Thread ──────────────────────────────────────────────── */
|
|
152
|
-
.thread {
|
|
153
|
-
flex: 1;
|
|
154
|
-
overflow-y: auto;
|
|
155
|
-
padding: 20px 0 10px;
|
|
156
|
-
display: flex;
|
|
157
|
-
flex-direction: column;
|
|
158
|
-
}
|
|
159
|
-
.thread::-webkit-scrollbar { width: 3px; }
|
|
160
|
-
.thread::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.07); border-radius: 3px; }
|
|
161
|
-
|
|
162
|
-
/* ── Home state ──────────────────────────────────────────── */
|
|
163
|
-
.home-state {
|
|
164
|
-
padding: 8px 20px 16px;
|
|
165
|
-
animation: fadein 0.35s ease;
|
|
166
|
-
}
|
|
167
|
-
@keyframes fadein { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
|
|
168
|
-
|
|
169
|
-
.home-heading {
|
|
170
|
-
display: flex;
|
|
171
|
-
align-items: center;
|
|
172
|
-
gap: 9px;
|
|
173
|
-
margin-bottom: 18px;
|
|
174
|
-
}
|
|
175
|
-
.home-heading-text {
|
|
176
|
-
font-family: var(--font-serif);
|
|
177
|
-
font-size: 20px;
|
|
178
|
-
font-weight: 400;
|
|
179
|
-
color: var(--text-primary);
|
|
180
|
-
letter-spacing: -0.025em;
|
|
181
|
-
line-height: 1.25;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.chips {
|
|
185
|
-
display: flex;
|
|
186
|
-
flex-wrap: wrap;
|
|
187
|
-
gap: 5px;
|
|
188
|
-
}
|
|
189
|
-
.chip {
|
|
190
|
-
display: inline-flex;
|
|
191
|
-
align-items: center;
|
|
192
|
-
gap: 5px;
|
|
193
|
-
padding: 5px 11px;
|
|
194
|
-
border-radius: 999px;
|
|
195
|
-
border: 1px solid rgba(255,255,255,0.09);
|
|
196
|
-
background: rgba(255,255,255,0.03);
|
|
197
|
-
font-size: 11px;
|
|
198
|
-
color: var(--text-secondary);
|
|
199
|
-
cursor: pointer;
|
|
200
|
-
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
|
201
|
-
white-space: nowrap;
|
|
202
|
-
}
|
|
203
|
-
.chip:hover {
|
|
204
|
-
background: rgba(255,255,255,0.07);
|
|
205
|
-
border-color: rgba(255,255,255,0.14);
|
|
206
|
-
color: var(--text-primary);
|
|
207
|
-
}
|
|
208
|
-
.chip svg { flex-shrink: 0; opacity: 0.6; }
|
|
209
|
-
|
|
210
|
-
/* ── Message rows ────────────────────────────────────────── */
|
|
211
|
-
.msg-row {
|
|
212
|
-
padding: 0 20px;
|
|
213
|
-
animation: fadein 0.2s ease;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/* User — right-aligned dark pill */
|
|
217
|
-
.user-wrap {
|
|
218
|
-
display: flex;
|
|
219
|
-
justify-content: flex-end;
|
|
220
|
-
margin-bottom: 22px;
|
|
221
|
-
}
|
|
222
|
-
.user-bubble {
|
|
223
|
-
background: var(--bg-user-msg);
|
|
224
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
225
|
-
border-radius: 18px;
|
|
226
|
-
border-bottom-right-radius: 6px;
|
|
227
|
-
padding: 9px 14px;
|
|
228
|
-
font-size: 13px;
|
|
229
|
-
line-height: 1.55;
|
|
230
|
-
color: var(--text-primary);
|
|
231
|
-
max-width: 88%;
|
|
232
|
-
word-break: break-word;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/* Claude — plain flowing text, serif, no bubble */
|
|
236
|
-
.claude-wrap {
|
|
237
|
-
margin-bottom: 6px;
|
|
238
|
-
}
|
|
239
|
-
.claude-text {
|
|
240
|
-
font-family: var(--font-serif);
|
|
241
|
-
font-size: 13.5px;
|
|
242
|
-
line-height: 1.72;
|
|
243
|
-
color: var(--text-primary);
|
|
244
|
-
word-break: break-word;
|
|
245
|
-
white-space: pre-wrap;
|
|
246
|
-
}
|
|
247
|
-
.claude-text.rendered {
|
|
248
|
-
white-space: normal;
|
|
249
|
-
}
|
|
250
|
-
.claude-text.rendered p {
|
|
251
|
-
margin: 0 0 8px 0;
|
|
252
|
-
}
|
|
253
|
-
.claude-text.rendered p:last-child {
|
|
254
|
-
margin-bottom: 0;
|
|
255
|
-
}
|
|
256
|
-
.claude-text.rendered strong {
|
|
257
|
-
font-weight: 600;
|
|
258
|
-
color: #fff;
|
|
259
|
-
}
|
|
260
|
-
.claude-text.rendered em {
|
|
261
|
-
font-style: italic;
|
|
262
|
-
color: rgba(255,255,255,0.85);
|
|
263
|
-
}
|
|
264
|
-
.claude-text.rendered code {
|
|
265
|
-
font-family: var(--font-mono, 'SF Mono', 'Fira Code', monospace);
|
|
266
|
-
font-size: 12px;
|
|
267
|
-
background: rgba(255,255,255,0.06);
|
|
268
|
-
border: 1px solid rgba(255,255,255,0.08);
|
|
269
|
-
border-radius: 4px;
|
|
270
|
-
padding: 1px 5px;
|
|
271
|
-
color: rgba(218,119,86,0.9);
|
|
272
|
-
}
|
|
273
|
-
.claude-text.rendered pre {
|
|
274
|
-
background: rgba(0,0,0,0.25);
|
|
275
|
-
border: 1px solid rgba(255,255,255,0.08);
|
|
276
|
-
border-radius: 8px;
|
|
277
|
-
padding: 10px 12px;
|
|
278
|
-
overflow-x: auto;
|
|
279
|
-
margin: 8px 0;
|
|
280
|
-
position: relative;
|
|
281
|
-
}
|
|
282
|
-
.claude-text.rendered pre code {
|
|
283
|
-
background: none;
|
|
284
|
-
border: none;
|
|
285
|
-
padding: 0;
|
|
286
|
-
font-size: 11.5px;
|
|
287
|
-
color: var(--text-primary);
|
|
288
|
-
}
|
|
289
|
-
.code-block-actions {
|
|
290
|
-
position: absolute;
|
|
291
|
-
top: 6px;
|
|
292
|
-
right: 6px;
|
|
293
|
-
display: flex;
|
|
294
|
-
gap: 4px;
|
|
295
|
-
opacity: 0;
|
|
296
|
-
transition: opacity 0.15s;
|
|
297
|
-
}
|
|
298
|
-
.claude-text.rendered pre:hover .code-block-actions {
|
|
299
|
-
opacity: 1;
|
|
300
|
-
}
|
|
301
|
-
.code-block-actions button {
|
|
302
|
-
background: rgba(255,255,255,0.1);
|
|
303
|
-
border: 1px solid rgba(255,255,255,0.15);
|
|
304
|
-
border-radius: 4px;
|
|
305
|
-
color: var(--text-muted);
|
|
306
|
-
font-size: 10px;
|
|
307
|
-
padding: 3px 8px;
|
|
308
|
-
cursor: pointer;
|
|
309
|
-
font-family: inherit;
|
|
310
|
-
}
|
|
311
|
-
.code-block-actions button:hover {
|
|
312
|
-
background: rgba(255,255,255,0.2);
|
|
313
|
-
color: var(--text-primary);
|
|
314
|
-
}
|
|
315
|
-
.code-block-actions button.copied {
|
|
316
|
-
background: rgba(74,222,128,0.2);
|
|
317
|
-
border-color: rgba(74,222,128,0.3);
|
|
318
|
-
color: #4ade80;
|
|
319
|
-
}
|
|
320
|
-
.claude-text.rendered ul, .claude-text.rendered ol {
|
|
321
|
-
margin: 6px 0;
|
|
322
|
-
padding-left: 20px;
|
|
323
|
-
}
|
|
324
|
-
.claude-text.rendered li {
|
|
325
|
-
margin-bottom: 4px;
|
|
326
|
-
}
|
|
327
|
-
.claude-text.rendered h1, .claude-text.rendered h2, .claude-text.rendered h3 {
|
|
328
|
-
font-weight: 600;
|
|
329
|
-
color: #fff;
|
|
330
|
-
margin: 12px 0 6px 0;
|
|
331
|
-
}
|
|
332
|
-
.claude-text.rendered h1 { font-size: 16px; }
|
|
333
|
-
.claude-text.rendered h2 { font-size: 14.5px; }
|
|
334
|
-
.claude-text.rendered h3 { font-size: 13.5px; }
|
|
335
|
-
.claude-text.rendered blockquote {
|
|
336
|
-
border-left: 3px solid rgba(218,119,86,0.4);
|
|
337
|
-
margin: 8px 0;
|
|
338
|
-
padding: 4px 12px;
|
|
339
|
-
color: rgba(255,255,255,0.7);
|
|
340
|
-
}
|
|
341
|
-
.claude-text.rendered hr {
|
|
342
|
-
border: none;
|
|
343
|
-
border-top: 1px solid rgba(255,255,255,0.1);
|
|
344
|
-
margin: 10px 0;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/* Tool pills */
|
|
348
|
-
.tool-steps {
|
|
349
|
-
display: flex;
|
|
350
|
-
flex-direction: column;
|
|
351
|
-
gap: 4px;
|
|
352
|
-
margin: 6px 0 4px;
|
|
353
|
-
}
|
|
354
|
-
.activity-stream {
|
|
355
|
-
display: flex;
|
|
356
|
-
flex-direction: column;
|
|
357
|
-
gap: 4px;
|
|
358
|
-
margin: 0 0 8px;
|
|
359
|
-
}
|
|
360
|
-
.activity-line {
|
|
361
|
-
display: flex;
|
|
362
|
-
align-items: center;
|
|
363
|
-
gap: 8px;
|
|
364
|
-
font-size: 10.5px;
|
|
365
|
-
line-height: 1.35;
|
|
366
|
-
color: var(--text-muted);
|
|
367
|
-
font-family: var(--font-sans);
|
|
368
|
-
}
|
|
369
|
-
.activity-line .activity-time {
|
|
370
|
-
flex: 0 0 48px;
|
|
371
|
-
color: rgba(255,255,255,0.32);
|
|
372
|
-
font-variant-numeric: tabular-nums;
|
|
373
|
-
}
|
|
374
|
-
.activity-line .activity-dot {
|
|
375
|
-
width: 5px;
|
|
376
|
-
height: 5px;
|
|
377
|
-
border-radius: 50%;
|
|
378
|
-
background: rgba(218,119,86,0.72);
|
|
379
|
-
flex-shrink: 0;
|
|
380
|
-
margin-top: 1px;
|
|
381
|
-
}
|
|
382
|
-
.activity-line .activity-body {
|
|
383
|
-
color: rgba(255,255,255,0.72);
|
|
384
|
-
word-break: break-word;
|
|
385
|
-
}
|
|
386
|
-
.activity-line.tool .activity-body strong {
|
|
387
|
-
color: rgba(218,119,86,0.95);
|
|
388
|
-
font-weight: 600;
|
|
389
|
-
}
|
|
390
|
-
.activity-line.phase .activity-body strong {
|
|
391
|
-
color: rgba(160,132,232,0.95);
|
|
392
|
-
font-weight: 600;
|
|
393
|
-
}
|
|
394
|
-
.activity-line.phase .activity-dot {
|
|
395
|
-
background: rgba(160,132,232,0.72);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/* Tools-used summary shown after response completes */
|
|
399
|
-
.tools-used-summary {
|
|
400
|
-
display: flex;
|
|
401
|
-
flex-wrap: wrap;
|
|
402
|
-
gap: 4px;
|
|
403
|
-
margin: 8px 0 2px;
|
|
404
|
-
}
|
|
405
|
-
.tools-used-tag {
|
|
406
|
-
display: inline-flex;
|
|
407
|
-
align-items: center;
|
|
408
|
-
gap: 4px;
|
|
409
|
-
font-size: 10px;
|
|
410
|
-
padding: 2px 7px;
|
|
411
|
-
border-radius: 999px;
|
|
412
|
-
background: rgba(77,143,95,0.08);
|
|
413
|
-
border: 1px solid rgba(77,143,95,0.22);
|
|
414
|
-
color: rgba(77,143,95,0.9);
|
|
415
|
-
font-family: var(--font-sans);
|
|
416
|
-
}
|
|
417
|
-
.tools-used-label {
|
|
418
|
-
font-size: 10px;
|
|
419
|
-
color: var(--text-muted);
|
|
420
|
-
margin-bottom: 4px;
|
|
421
|
-
font-family: var(--font-sans);
|
|
422
|
-
}
|
|
423
|
-
.citations-section { margin-top: 10px; padding-top: 8px; border-top: 1px solid rgba(255,255,255,0.06); }
|
|
424
|
-
.citations-label { font-size: 10.5px; color: var(--text-muted); margin-bottom: 4px; font-family: var(--font-sans); }
|
|
425
|
-
.citation-link {
|
|
426
|
-
display: block;
|
|
427
|
-
font-size: 11px;
|
|
428
|
-
color: var(--accent);
|
|
429
|
-
text-decoration: none;
|
|
430
|
-
padding: 2px 0;
|
|
431
|
-
overflow: hidden;
|
|
432
|
-
text-overflow: ellipsis;
|
|
433
|
-
white-space: nowrap;
|
|
434
|
-
}
|
|
435
|
-
.citation-link:hover { text-decoration: underline; }
|
|
436
|
-
.tool-pill {
|
|
437
|
-
display: inline-flex;
|
|
438
|
-
align-items: center;
|
|
439
|
-
gap: 6px;
|
|
440
|
-
font-size: 10.5px;
|
|
441
|
-
padding: 3px 9px;
|
|
442
|
-
border-radius: 5px;
|
|
443
|
-
background: rgba(255,255,255,0.03);
|
|
444
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
445
|
-
color: var(--text-muted);
|
|
446
|
-
width: fit-content;
|
|
447
|
-
font-family: var(--font-sans);
|
|
448
|
-
}
|
|
449
|
-
.tool-pill.running { border-color:rgba(218,119,86,.3); color:rgba(218,119,86,.9); background:rgba(218,119,86,.05); }
|
|
450
|
-
.tool-pill.done { border-color:rgba(77,143,95,.3); color:rgba(77,143,95,.9); background:rgba(77,143,95,.05); }
|
|
451
|
-
.tool-pill.error { border-color:rgba(143,77,77,.3); color:rgba(220,120,120,.9);background:rgba(143,77,77,.05); }
|
|
452
|
-
.tool-spinner {
|
|
453
|
-
width: 8px; height: 8px;
|
|
454
|
-
border: 1.5px solid rgba(218,119,86,0.2);
|
|
455
|
-
border-top-color: var(--accent);
|
|
456
|
-
border-radius: 50%;
|
|
457
|
-
animation: spin 0.75s linear infinite;
|
|
458
|
-
}
|
|
459
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
460
|
-
|
|
461
|
-
/* Error */
|
|
462
|
-
.error-msg {
|
|
463
|
-
padding: 9px 13px;
|
|
464
|
-
background: rgba(160,60,60,0.1);
|
|
465
|
-
border: 1px solid rgba(160,60,60,0.18);
|
|
466
|
-
border-radius: 10px;
|
|
467
|
-
color: #c87878;
|
|
468
|
-
font-size: 12.5px;
|
|
469
|
-
line-height: 1.55;
|
|
470
|
-
max-width: 90%;
|
|
471
|
-
margin-bottom: 16px;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/* ── Thinking — Claude-style rotating words ─────────────── */
|
|
475
|
-
.thinking-row {
|
|
476
|
-
padding: 4px 0 14px;
|
|
477
|
-
display: flex;
|
|
478
|
-
align-items: center;
|
|
479
|
-
gap: 8px;
|
|
480
|
-
}
|
|
481
|
-
.thinking-sparkle {
|
|
482
|
-
flex-shrink: 0;
|
|
483
|
-
animation: sparkle-pulse 2s ease-in-out infinite;
|
|
484
|
-
}
|
|
485
|
-
@keyframes sparkle-pulse {
|
|
486
|
-
0%, 100% { opacity: 0.6; transform: scale(1); }
|
|
487
|
-
50% { opacity: 1; transform: scale(1.15); }
|
|
488
|
-
}
|
|
489
|
-
.thinking-words {
|
|
490
|
-
font-family: var(--font-sans);
|
|
491
|
-
font-size: 13px;
|
|
492
|
-
color: rgba(218,119,86,0.85);
|
|
493
|
-
position: relative;
|
|
494
|
-
height: 18px;
|
|
495
|
-
min-width: 120px;
|
|
496
|
-
overflow: hidden;
|
|
497
|
-
}
|
|
498
|
-
.thinking-word {
|
|
499
|
-
display: block;
|
|
500
|
-
position: absolute;
|
|
501
|
-
left: 0; top: 0;
|
|
502
|
-
width: 100%;
|
|
503
|
-
line-height: 18px;
|
|
504
|
-
opacity: 0;
|
|
505
|
-
transform: translateY(10px);
|
|
506
|
-
transition: opacity 0.4s ease, transform 0.4s ease;
|
|
507
|
-
white-space: nowrap;
|
|
508
|
-
}
|
|
509
|
-
.thinking-word.active {
|
|
510
|
-
opacity: 1;
|
|
511
|
-
transform: translateY(0);
|
|
512
|
-
}
|
|
513
|
-
.thinking-word.exit {
|
|
514
|
-
opacity: 0;
|
|
515
|
-
transform: translateY(-10px);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/* ── Input ───────────────────────────────────────────────── */
|
|
519
|
-
.input-area {
|
|
520
|
-
flex-shrink: 0;
|
|
521
|
-
padding: 6px 12px 10px;
|
|
522
|
-
}
|
|
523
|
-
.input-box {
|
|
524
|
-
background: var(--bg-input);
|
|
525
|
-
border-radius: 16px;
|
|
526
|
-
}
|
|
527
|
-
.input-main {
|
|
528
|
-
padding: 11px 14px 5px;
|
|
529
|
-
}
|
|
530
|
-
textarea {
|
|
531
|
-
width: 100%;
|
|
532
|
-
background: transparent;
|
|
533
|
-
border: none;
|
|
534
|
-
outline: none;
|
|
535
|
-
color: var(--text-primary);
|
|
536
|
-
font-family: var(--font-sans);
|
|
537
|
-
font-size: 13px;
|
|
538
|
-
line-height: 1.55;
|
|
539
|
-
resize: none;
|
|
540
|
-
min-height: 22px;
|
|
541
|
-
max-height: 120px;
|
|
542
|
-
overflow-y: auto;
|
|
543
|
-
display: block;
|
|
544
|
-
}
|
|
545
|
-
textarea::placeholder { color: var(--text-muted); }
|
|
546
|
-
textarea::-webkit-scrollbar { width: 3px; }
|
|
547
|
-
textarea::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); }
|
|
548
|
-
|
|
549
|
-
.input-toolbar {
|
|
550
|
-
padding: 5px 8px 8px;
|
|
551
|
-
display: flex;
|
|
552
|
-
align-items: center;
|
|
553
|
-
justify-content: space-between;
|
|
554
|
-
}
|
|
555
|
-
.toolbar-left { display: flex; align-items: center; }
|
|
556
|
-
.toolbar-right { display: flex; align-items: center; gap: 5px; }
|
|
557
|
-
|
|
558
|
-
.tb-btn {
|
|
559
|
-
width: 26px; height: 26px;
|
|
560
|
-
border-radius: 6px;
|
|
561
|
-
background: none;
|
|
562
|
-
border: none;
|
|
563
|
-
cursor: pointer;
|
|
564
|
-
display: flex; align-items: center; justify-content: center;
|
|
565
|
-
color: var(--text-muted);
|
|
566
|
-
transition: background 0.13s, color 0.13s;
|
|
567
|
-
}
|
|
568
|
-
.tb-btn:hover:not(:disabled) { background: rgba(255,255,255,0.07); color: var(--text-secondary); }
|
|
569
|
-
.tb-btn:disabled { opacity: 0.25; cursor: default; }
|
|
570
|
-
|
|
571
|
-
.model-badge {
|
|
572
|
-
display: flex; align-items: center; gap: 3px;
|
|
573
|
-
font-size: 11px;
|
|
574
|
-
color: var(--text-muted);
|
|
575
|
-
padding: 3px 7px;
|
|
576
|
-
border-radius: 6px;
|
|
577
|
-
cursor: pointer;
|
|
578
|
-
user-select: none;
|
|
579
|
-
position: relative;
|
|
580
|
-
transition: background 0.13s, color 0.13s;
|
|
581
|
-
}
|
|
582
|
-
.model-badge:hover { background: rgba(255,255,255,0.07); color: var(--text-secondary); }
|
|
583
|
-
|
|
584
|
-
/* UX Researcher toggle */
|
|
585
|
-
.ux-researcher-toggle {
|
|
586
|
-
display: flex; align-items: center; gap: 4px;
|
|
587
|
-
font-size: 10px;
|
|
588
|
-
color: var(--text-muted);
|
|
589
|
-
padding: 2px 6px;
|
|
590
|
-
border-radius: 5px;
|
|
591
|
-
cursor: pointer;
|
|
592
|
-
user-select: none;
|
|
593
|
-
transition: background 0.13s, color 0.13s;
|
|
594
|
-
}
|
|
595
|
-
.ux-researcher-toggle:hover { background: rgba(255,255,255,0.07); color: var(--text-secondary); }
|
|
596
|
-
.ux-researcher-toggle.active {
|
|
597
|
-
background: rgba(168, 130, 255, 0.15);
|
|
598
|
-
color: #c4a1ff;
|
|
599
|
-
}
|
|
600
|
-
.ux-researcher-toggle.active:hover {
|
|
601
|
-
background: rgba(168, 130, 255, 0.22);
|
|
602
|
-
}
|
|
603
|
-
.ux-researcher-dot {
|
|
604
|
-
width: 6px; height: 6px; border-radius: 50%;
|
|
605
|
-
background: var(--text-muted);
|
|
606
|
-
transition: background 0.13s;
|
|
607
|
-
}
|
|
608
|
-
.ux-researcher-toggle.active .ux-researcher-dot {
|
|
609
|
-
background: #c4a1ff;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/* Model dropdown */
|
|
613
|
-
.model-dropdown {
|
|
614
|
-
position: absolute;
|
|
615
|
-
bottom: calc(100% + 6px);
|
|
616
|
-
left: 50%;
|
|
617
|
-
transform: translateX(-50%);
|
|
618
|
-
background: #353432;
|
|
619
|
-
border: 1px solid rgba(255,255,255,0.1);
|
|
620
|
-
border-radius: 10px;
|
|
621
|
-
padding: 4px;
|
|
622
|
-
min-width: 160px;
|
|
623
|
-
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
|
624
|
-
z-index: 100;
|
|
625
|
-
animation: dropIn 0.12s ease;
|
|
626
|
-
}
|
|
627
|
-
@keyframes dropIn { from { opacity:0; transform:translateX(-50%) translateY(4px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }
|
|
628
|
-
.model-dropdown-item {
|
|
629
|
-
display: flex;
|
|
630
|
-
align-items: center;
|
|
631
|
-
justify-content: space-between;
|
|
632
|
-
padding: 7px 10px;
|
|
633
|
-
border-radius: 7px;
|
|
634
|
-
font-size: 12px;
|
|
635
|
-
color: var(--text-secondary);
|
|
636
|
-
cursor: pointer;
|
|
637
|
-
transition: background 0.1s, color 0.1s;
|
|
638
|
-
}
|
|
639
|
-
.model-dropdown-item:hover { background: rgba(255,255,255,0.07); color: var(--text-primary); }
|
|
640
|
-
.model-dropdown-item.active { color: var(--text-primary); }
|
|
641
|
-
.model-dropdown-item .check { color: var(--accent); font-size: 13px; opacity: 0; }
|
|
642
|
-
.model-dropdown-item.active .check { opacity: 1; }
|
|
643
|
-
.model-dropdown-item .model-desc {
|
|
644
|
-
font-size: 10px;
|
|
645
|
-
color: var(--text-muted);
|
|
646
|
-
margin-left: 6px;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/* Attached files strip */
|
|
650
|
-
.attached-files {
|
|
651
|
-
display: flex;
|
|
652
|
-
flex-wrap: wrap;
|
|
653
|
-
gap: 4px;
|
|
654
|
-
padding: 0 14px 6px;
|
|
655
|
-
}
|
|
656
|
-
.file-chip {
|
|
657
|
-
display: inline-flex;
|
|
658
|
-
align-items: center;
|
|
659
|
-
gap: 4px;
|
|
660
|
-
padding: 3px 8px;
|
|
661
|
-
border-radius: 6px;
|
|
662
|
-
background: rgba(255,255,255,0.05);
|
|
663
|
-
border: 1px solid rgba(255,255,255,0.08);
|
|
664
|
-
font-size: 10.5px;
|
|
665
|
-
color: var(--text-secondary);
|
|
666
|
-
max-width: 180px;
|
|
667
|
-
animation: fadein 0.15s ease;
|
|
668
|
-
}
|
|
669
|
-
.file-chip span {
|
|
670
|
-
overflow: hidden;
|
|
671
|
-
text-overflow: ellipsis;
|
|
672
|
-
white-space: nowrap;
|
|
673
|
-
}
|
|
674
|
-
.file-chip-x {
|
|
675
|
-
cursor: pointer;
|
|
676
|
-
color: var(--text-muted);
|
|
677
|
-
font-size: 12px;
|
|
678
|
-
line-height: 1;
|
|
679
|
-
padding: 0 1px;
|
|
680
|
-
border-radius: 3px;
|
|
681
|
-
transition: color 0.1s, background 0.1s;
|
|
682
|
-
flex-shrink: 0;
|
|
683
|
-
}
|
|
684
|
-
.file-chip-x:hover { color: var(--text-primary); background: rgba(255,255,255,0.08); }
|
|
685
|
-
|
|
686
|
-
/* Send button — orange arrow-up */
|
|
687
|
-
.send-btn {
|
|
688
|
-
width: 28px; height: 28px;
|
|
689
|
-
border-radius: 8px;
|
|
690
|
-
background: var(--accent);
|
|
691
|
-
border: none;
|
|
692
|
-
cursor: pointer;
|
|
693
|
-
display: flex; align-items: center; justify-content: center;
|
|
694
|
-
color: #fff;
|
|
695
|
-
transition: background 0.13s, transform 0.08s, opacity 0.15s;
|
|
696
|
-
flex-shrink: 0;
|
|
697
|
-
}
|
|
698
|
-
.send-btn:hover:not(:disabled) { background: var(--accent-hover); }
|
|
699
|
-
.send-btn:active:not(:disabled) { transform: scale(0.93); }
|
|
700
|
-
.send-btn:disabled {
|
|
701
|
-
background: rgba(255,255,255,0.06);
|
|
702
|
-
color: var(--text-ghost);
|
|
703
|
-
cursor: default;
|
|
704
|
-
}
|
|
705
|
-
.stop-btn {
|
|
706
|
-
width: 28px; height: 28px;
|
|
707
|
-
border-radius: 8px;
|
|
708
|
-
background: rgba(255,255,255,0.06);
|
|
709
|
-
border: 1px solid rgba(255,255,255,0.09);
|
|
710
|
-
cursor: pointer;
|
|
711
|
-
display: flex; align-items: center; justify-content: center;
|
|
712
|
-
color: var(--text-secondary);
|
|
713
|
-
transition: background 0.13s;
|
|
714
|
-
}
|
|
715
|
-
.stop-btn:hover { background: rgba(255,255,255,0.1); }
|
|
716
|
-
|
|
717
|
-
.input-footer {
|
|
718
|
-
font-size: 9.5px;
|
|
719
|
-
color: var(--text-ghost);
|
|
720
|
-
text-align: center;
|
|
721
|
-
margin-top: 5px;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
@media (prefers-reduced-motion: reduce) {
|
|
725
|
-
.asterisk-spin, .tool-spinner { animation: none !important; }
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/* ── Setup Guide (shown when server not running) ──────────── */
|
|
729
|
-
.setup-guide {
|
|
730
|
-
position: absolute;
|
|
731
|
-
inset: 0;
|
|
732
|
-
background: var(--bg);
|
|
733
|
-
display: flex;
|
|
734
|
-
flex-direction: column;
|
|
735
|
-
align-items: center;
|
|
736
|
-
justify-content: center;
|
|
737
|
-
padding: 32px 24px;
|
|
738
|
-
z-index: 250;
|
|
739
|
-
animation: fadein 0.3s ease;
|
|
740
|
-
text-align: center;
|
|
741
|
-
}
|
|
742
|
-
.setup-guide.hidden { display: none; }
|
|
743
|
-
.setup-guide-icon { margin-bottom: 16px; opacity: 0.6; }
|
|
744
|
-
.setup-guide-title {
|
|
745
|
-
font-family: var(--font-serif);
|
|
746
|
-
font-size: 18px;
|
|
747
|
-
font-weight: 400;
|
|
748
|
-
color: var(--text-primary);
|
|
749
|
-
margin-bottom: 8px;
|
|
750
|
-
}
|
|
751
|
-
.setup-guide-subtitle {
|
|
752
|
-
font-size: 12px;
|
|
753
|
-
color: var(--text-secondary);
|
|
754
|
-
margin-bottom: 24px;
|
|
755
|
-
line-height: 1.5;
|
|
756
|
-
}
|
|
757
|
-
.setup-guide-steps {
|
|
758
|
-
text-align: left;
|
|
759
|
-
width: 100%;
|
|
760
|
-
max-width: 300px;
|
|
761
|
-
margin-bottom: 24px;
|
|
762
|
-
}
|
|
763
|
-
.setup-guide-step {
|
|
764
|
-
display: flex;
|
|
765
|
-
align-items: flex-start;
|
|
766
|
-
gap: 10px;
|
|
767
|
-
margin-bottom: 14px;
|
|
768
|
-
font-size: 12px;
|
|
769
|
-
color: var(--text-secondary);
|
|
770
|
-
line-height: 1.5;
|
|
771
|
-
}
|
|
772
|
-
.setup-guide-step-num {
|
|
773
|
-
flex-shrink: 0;
|
|
774
|
-
width: 20px;
|
|
775
|
-
height: 20px;
|
|
776
|
-
border-radius: 50%;
|
|
777
|
-
background: rgba(218,119,86,0.15);
|
|
778
|
-
color: var(--accent);
|
|
779
|
-
font-size: 11px;
|
|
780
|
-
font-weight: 600;
|
|
781
|
-
display: flex;
|
|
782
|
-
align-items: center;
|
|
783
|
-
justify-content: center;
|
|
784
|
-
}
|
|
785
|
-
.setup-guide-cmd {
|
|
786
|
-
display: block;
|
|
787
|
-
background: var(--bg-input);
|
|
788
|
-
border: 1px solid rgba(255,255,255,0.08);
|
|
789
|
-
border-radius: 6px;
|
|
790
|
-
padding: 10px 14px;
|
|
791
|
-
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
|
792
|
-
font-size: 12px;
|
|
793
|
-
color: var(--accent);
|
|
794
|
-
width: 100%;
|
|
795
|
-
max-width: 300px;
|
|
796
|
-
text-align: center;
|
|
797
|
-
cursor: pointer;
|
|
798
|
-
transition: border-color 0.15s;
|
|
799
|
-
position: relative;
|
|
800
|
-
margin-bottom: 8px;
|
|
801
|
-
}
|
|
802
|
-
.setup-guide-cmd:hover { border-color: var(--accent); }
|
|
803
|
-
.setup-guide-cmd::after {
|
|
804
|
-
content: 'Click to copy';
|
|
805
|
-
position: absolute;
|
|
806
|
-
right: 10px;
|
|
807
|
-
top: 50%;
|
|
808
|
-
transform: translateY(-50%);
|
|
809
|
-
font-size: 9px;
|
|
810
|
-
color: var(--text-muted);
|
|
811
|
-
font-family: var(--font-sans);
|
|
812
|
-
}
|
|
813
|
-
.setup-guide-cmd.copied::after {
|
|
814
|
-
content: 'Copied!';
|
|
815
|
-
color: var(--accent);
|
|
816
|
-
}
|
|
817
|
-
.setup-guide-connecting {
|
|
818
|
-
display: flex;
|
|
819
|
-
align-items: center;
|
|
820
|
-
gap: 8px;
|
|
821
|
-
font-size: 11px;
|
|
822
|
-
color: var(--text-muted);
|
|
823
|
-
margin-top: 8px;
|
|
824
|
-
}
|
|
825
|
-
.setup-guide-connecting .dot-pulse {
|
|
826
|
-
width: 6px; height: 6px;
|
|
827
|
-
border-radius: 50%;
|
|
828
|
-
background: var(--accent);
|
|
829
|
-
animation: pulse-dot 1.5s ease infinite;
|
|
830
|
-
}
|
|
831
|
-
@keyframes pulse-dot {
|
|
832
|
-
0%, 100% { opacity: 0.3; }
|
|
833
|
-
50% { opacity: 1; }
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/* ── Login / Provider Screen ─────────────────────────────── */
|
|
837
|
-
.login-screen {
|
|
838
|
-
position: absolute;
|
|
839
|
-
inset: 0;
|
|
840
|
-
background: var(--bg);
|
|
841
|
-
display: flex;
|
|
842
|
-
flex-direction: column;
|
|
843
|
-
align-items: center;
|
|
844
|
-
justify-content: flex-start;
|
|
845
|
-
padding: 28px 20px 20px;
|
|
846
|
-
z-index: 200;
|
|
847
|
-
overflow-y: auto;
|
|
848
|
-
animation: fadein 0.3s ease;
|
|
849
|
-
}
|
|
850
|
-
.login-screen.hiding {
|
|
851
|
-
animation: fadeout 0.25s ease forwards;
|
|
852
|
-
pointer-events: none;
|
|
853
|
-
}
|
|
854
|
-
@keyframes fadeout {
|
|
855
|
-
from { opacity: 1; transform: translateY(0); }
|
|
856
|
-
to { opacity: 0; transform: translateY(-8px); }
|
|
857
|
-
}
|
|
858
|
-
.login-logo { margin-bottom: 10px; }
|
|
859
|
-
.login-title {
|
|
860
|
-
font-family: var(--font-serif);
|
|
861
|
-
font-size: 20px;
|
|
862
|
-
font-weight: 400;
|
|
863
|
-
color: var(--text-primary);
|
|
864
|
-
letter-spacing: -0.025em;
|
|
865
|
-
margin-bottom: 3px;
|
|
866
|
-
text-align: center;
|
|
867
|
-
}
|
|
868
|
-
.login-subtitle {
|
|
869
|
-
font-size: 11.5px;
|
|
870
|
-
color: var(--text-muted);
|
|
871
|
-
margin-bottom: 20px;
|
|
872
|
-
text-align: center;
|
|
873
|
-
}
|
|
874
|
-
.provider-list {
|
|
875
|
-
display: flex;
|
|
876
|
-
flex-direction: column;
|
|
877
|
-
gap: 6px;
|
|
878
|
-
width: 100%;
|
|
879
|
-
max-width: 264px;
|
|
880
|
-
}
|
|
881
|
-
.provider-card {
|
|
882
|
-
display: flex;
|
|
883
|
-
align-items: center;
|
|
884
|
-
gap: 10px;
|
|
885
|
-
padding: 10px 12px;
|
|
886
|
-
border-radius: 11px;
|
|
887
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
888
|
-
background: rgba(255,255,255,0.02);
|
|
889
|
-
cursor: pointer;
|
|
890
|
-
transition: border-color 0.15s, background 0.15s;
|
|
891
|
-
user-select: none;
|
|
892
|
-
}
|
|
893
|
-
.provider-card:hover {
|
|
894
|
-
background: rgba(255,255,255,0.05);
|
|
895
|
-
border-color: rgba(255,255,255,0.12);
|
|
896
|
-
}
|
|
897
|
-
.provider-card.selected {
|
|
898
|
-
border-color: var(--accent);
|
|
899
|
-
background: rgba(218,119,86,0.06);
|
|
900
|
-
}
|
|
901
|
-
.provider-card.selected .provider-name { color: var(--text-primary); }
|
|
902
|
-
.provider-icon {
|
|
903
|
-
width: 28px; height: 28px;
|
|
904
|
-
border-radius: 7px;
|
|
905
|
-
display: flex; align-items: center; justify-content: center;
|
|
906
|
-
flex-shrink: 0;
|
|
907
|
-
}
|
|
908
|
-
.provider-icon svg {
|
|
909
|
-
width: 16px;
|
|
910
|
-
height: 16px;
|
|
911
|
-
display: block;
|
|
912
|
-
}
|
|
913
|
-
.pc-claude { background: rgba(218,119,86,0.14); }
|
|
914
|
-
.pc-openai { background: rgba(25,195,125,0.12); }
|
|
915
|
-
.pc-gemini { background: rgba(66,133,244,0.12); }
|
|
916
|
-
.pc-bridge { background: rgba(160,132,232,0.12); }
|
|
917
|
-
.pc-perplexity { background: rgba(32,191,187,0.12); }
|
|
918
|
-
.pc-stitch { background: rgba(66,133,244,0.12); }
|
|
919
|
-
.provider-info { flex: 1; min-width: 0; }
|
|
920
|
-
.provider-name {
|
|
921
|
-
font-size: 12.5px;
|
|
922
|
-
font-weight: 500;
|
|
923
|
-
color: var(--text-secondary);
|
|
924
|
-
margin-bottom: 1px;
|
|
925
|
-
}
|
|
926
|
-
.provider-desc {
|
|
927
|
-
font-size: 10.5px;
|
|
928
|
-
color: var(--text-muted);
|
|
929
|
-
}
|
|
930
|
-
.provider-check {
|
|
931
|
-
font-size: 13px;
|
|
932
|
-
color: var(--accent);
|
|
933
|
-
opacity: 0;
|
|
934
|
-
transition: opacity 0.15s;
|
|
935
|
-
flex-shrink: 0;
|
|
936
|
-
}
|
|
937
|
-
.provider-card.selected .provider-check { opacity: 1; }
|
|
938
|
-
|
|
939
|
-
/* Expandable area below the provider list */
|
|
940
|
-
.login-expand {
|
|
941
|
-
width: 100%;
|
|
942
|
-
max-width: 264px;
|
|
943
|
-
margin-top: 7px;
|
|
944
|
-
}
|
|
945
|
-
.login-api-input {
|
|
946
|
-
width: 100%;
|
|
947
|
-
background: var(--bg-input);
|
|
948
|
-
border: 1px solid rgba(255,255,255,0.08);
|
|
949
|
-
border-radius: 9px;
|
|
950
|
-
padding: 9px 12px;
|
|
951
|
-
color: var(--text-primary);
|
|
952
|
-
font-size: 12px;
|
|
953
|
-
font-family: var(--font-sans);
|
|
954
|
-
outline: none;
|
|
955
|
-
transition: border-color 0.15s;
|
|
956
|
-
}
|
|
957
|
-
.login-api-input:focus { border-color: rgba(255,255,255,0.18); }
|
|
958
|
-
.login-api-input::placeholder { color: var(--text-ghost); }
|
|
959
|
-
.login-api-link {
|
|
960
|
-
display: block;
|
|
961
|
-
margin-top: 5px;
|
|
962
|
-
font-size: 10px;
|
|
963
|
-
color: var(--text-muted);
|
|
964
|
-
text-align: right;
|
|
965
|
-
text-decoration: none;
|
|
966
|
-
}
|
|
967
|
-
.login-bridge-info {
|
|
968
|
-
background: rgba(160,132,232,0.06);
|
|
969
|
-
border: 1px solid rgba(160,132,232,0.14);
|
|
970
|
-
border-radius: 9px;
|
|
971
|
-
padding: 10px 12px;
|
|
972
|
-
font-size: 11px;
|
|
973
|
-
color: var(--text-muted);
|
|
974
|
-
line-height: 1.6;
|
|
975
|
-
}
|
|
976
|
-
.login-bridge-info code {
|
|
977
|
-
font-size: 10.5px;
|
|
978
|
-
color: rgba(160,132,232,0.9);
|
|
979
|
-
background: rgba(160,132,232,0.1);
|
|
980
|
-
padding: 1px 5px;
|
|
981
|
-
border-radius: 3px;
|
|
982
|
-
}
|
|
983
|
-
.login-bridge-tools {
|
|
984
|
-
display: flex;
|
|
985
|
-
flex-wrap: wrap;
|
|
986
|
-
gap: 4px;
|
|
987
|
-
margin-top: 8px;
|
|
988
|
-
}
|
|
989
|
-
.bridge-tool-tag {
|
|
990
|
-
font-size: 10px;
|
|
991
|
-
padding: 2px 7px;
|
|
992
|
-
border-radius: 999px;
|
|
993
|
-
background: rgba(160,132,232,0.08);
|
|
994
|
-
border: 1px solid rgba(160,132,232,0.15);
|
|
995
|
-
color: rgba(160,132,232,0.8);
|
|
996
|
-
}
|
|
997
|
-
.login-continue {
|
|
998
|
-
margin-top: 14px;
|
|
999
|
-
width: 100%;
|
|
1000
|
-
max-width: 264px;
|
|
1001
|
-
padding: 10px;
|
|
1002
|
-
border-radius: 10px;
|
|
1003
|
-
background: var(--accent);
|
|
1004
|
-
color: #fff;
|
|
1005
|
-
border: none;
|
|
1006
|
-
font-size: 12.5px;
|
|
1007
|
-
font-weight: 500;
|
|
1008
|
-
cursor: pointer;
|
|
1009
|
-
transition: background 0.13s, opacity 0.15s;
|
|
1010
|
-
font-family: var(--font-sans);
|
|
1011
|
-
}
|
|
1012
|
-
.login-continue:hover:not(:disabled) { background: var(--accent-hover); }
|
|
1013
|
-
.login-continue:disabled { opacity: 0.35; cursor: default; }
|
|
1014
|
-
.login-hint {
|
|
1015
|
-
margin-top: 8px;
|
|
1016
|
-
font-size: 10px;
|
|
1017
|
-
color: var(--text-ghost);
|
|
1018
|
-
text-align: center;
|
|
1019
|
-
max-width: 240px;
|
|
1020
|
-
min-height: 14px;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
/* Provider badge in header */
|
|
1024
|
-
.provider-badge {
|
|
1025
|
-
display: none;
|
|
1026
|
-
align-items: center;
|
|
1027
|
-
gap: 4px;
|
|
1028
|
-
font-size: 10px;
|
|
1029
|
-
color: var(--text-muted);
|
|
1030
|
-
padding: 2px 7px;
|
|
1031
|
-
border-radius: 5px;
|
|
1032
|
-
cursor: pointer;
|
|
1033
|
-
transition: background 0.13s, color 0.13s;
|
|
1034
|
-
}
|
|
1035
|
-
.provider-badge:hover {
|
|
1036
|
-
background: rgba(255,255,255,0.06);
|
|
1037
|
-
color: var(--text-secondary);
|
|
1038
|
-
}
|
|
1039
|
-
.prov-dot {
|
|
1040
|
-
width: 5px; height: 5px;
|
|
1041
|
-
border-radius: 50%;
|
|
1042
|
-
flex-shrink: 0;
|
|
1043
|
-
}
|
|
1044
|
-
.prov-dot.claude { background: var(--accent); }
|
|
1045
|
-
.prov-dot.openai { background: #19c37d; }
|
|
1046
|
-
.prov-dot.gemini { background: #4285f4; }
|
|
1047
|
-
.prov-dot.bridge { background: #a084e8; }
|
|
1048
|
-
.prov-dot.perplexity { background: #20bfbb; }
|
|
1049
|
-
.prov-dot.stitch { background: #4285F4; }
|
|
1050
|
-
|
|
1051
|
-
/* Bridge mode home state */
|
|
1052
|
-
.bridge-home {
|
|
1053
|
-
padding: 24px 20px;
|
|
1054
|
-
display: flex;
|
|
1055
|
-
flex-direction: column;
|
|
1056
|
-
align-items: center;
|
|
1057
|
-
gap: 8px;
|
|
1058
|
-
animation: fadein 0.35s ease;
|
|
1059
|
-
}
|
|
1060
|
-
.bridge-home-icon {
|
|
1061
|
-
width: 40px; height: 40px;
|
|
1062
|
-
border-radius: 12px;
|
|
1063
|
-
background: rgba(160,132,232,0.1);
|
|
1064
|
-
border: 1px solid rgba(160,132,232,0.18);
|
|
1065
|
-
display: flex; align-items: center; justify-content: center;
|
|
1066
|
-
margin-bottom: 4px;
|
|
1067
|
-
}
|
|
1068
|
-
.bridge-home-title {
|
|
1069
|
-
font-family: var(--font-serif);
|
|
1070
|
-
font-size: 15px;
|
|
1071
|
-
color: var(--text-primary);
|
|
1072
|
-
letter-spacing: -0.02em;
|
|
1073
|
-
}
|
|
1074
|
-
.bridge-home-desc {
|
|
1075
|
-
font-size: 11.5px;
|
|
1076
|
-
color: var(--text-muted);
|
|
1077
|
-
text-align: center;
|
|
1078
|
-
line-height: 1.6;
|
|
1079
|
-
max-width: 220px;
|
|
1080
|
-
}
|
|
1081
|
-
.bridge-home-url {
|
|
1082
|
-
font-size: 11px;
|
|
1083
|
-
color: rgba(160,132,232,0.85);
|
|
1084
|
-
background: rgba(160,132,232,0.08);
|
|
1085
|
-
border: 1px solid rgba(160,132,232,0.14);
|
|
1086
|
-
border-radius: 7px;
|
|
1087
|
-
padding: 5px 10px;
|
|
1088
|
-
margin-top: 2px;
|
|
1089
|
-
}
|
|
1090
|
-
.resize-handle {
|
|
1091
|
-
position: absolute;
|
|
1092
|
-
left: 0;
|
|
1093
|
-
right: 0;
|
|
1094
|
-
height: 8px;
|
|
1095
|
-
z-index: 12;
|
|
1096
|
-
cursor: ns-resize;
|
|
1097
|
-
touch-action: none;
|
|
1098
|
-
}
|
|
1099
|
-
.resize-handle::after {
|
|
1100
|
-
content: "";
|
|
1101
|
-
position: absolute;
|
|
1102
|
-
left: 12px;
|
|
1103
|
-
right: 12px;
|
|
1104
|
-
height: 2px;
|
|
1105
|
-
border-radius: 999px;
|
|
1106
|
-
background: rgba(255,255,255,0.16);
|
|
1107
|
-
opacity: 0;
|
|
1108
|
-
transition: opacity 0.14s ease, background 0.14s ease;
|
|
1109
|
-
pointer-events: none;
|
|
1110
|
-
}
|
|
1111
|
-
.resize-handle:hover::after,
|
|
1112
|
-
.resize-handle.is-active::after {
|
|
1113
|
-
opacity: 1;
|
|
1114
|
-
background: rgba(255,255,255,0.32);
|
|
1115
|
-
}
|
|
1116
|
-
.resize-handle--top {
|
|
1117
|
-
top: 0;
|
|
1118
|
-
}
|
|
1119
|
-
.resize-handle--top::after {
|
|
1120
|
-
top: 1px;
|
|
1121
|
-
}
|
|
1122
|
-
.resize-handle--bottom {
|
|
1123
|
-
bottom: 0;
|
|
1124
|
-
}
|
|
1125
|
-
.resize-handle--bottom::after {
|
|
1126
|
-
bottom: 1px;
|
|
1127
|
-
}
|
|
1128
|
-
body.plugin-resizing,
|
|
1129
|
-
body.plugin-resizing * {
|
|
1130
|
-
cursor: ns-resize !important;
|
|
1131
|
-
user-select: none !important;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
/* ── Design System Picker ────────────────────────────────────── */
|
|
1135
|
-
.sg-overlay {
|
|
1136
|
-
position: fixed;
|
|
1137
|
-
inset: 0;
|
|
1138
|
-
background: rgba(0,0,0,0.6);
|
|
1139
|
-
z-index: 500;
|
|
1140
|
-
display: flex;
|
|
1141
|
-
align-items: flex-end;
|
|
1142
|
-
animation: fadein 0.15s ease;
|
|
1143
|
-
}
|
|
1144
|
-
.sg-overlay.hiding {
|
|
1145
|
-
animation: fadeout 0.2s ease forwards;
|
|
1146
|
-
pointer-events: none;
|
|
1147
|
-
}
|
|
1148
|
-
.sg-modal {
|
|
1149
|
-
width: 100%;
|
|
1150
|
-
background: #2b2a27;
|
|
1151
|
-
border-top: 1px solid rgba(255,255,255,0.1);
|
|
1152
|
-
border-radius: 16px 16px 0 0;
|
|
1153
|
-
padding: 0 0 16px;
|
|
1154
|
-
max-height: 85vh;
|
|
1155
|
-
overflow-y: auto;
|
|
1156
|
-
animation: slideUp 0.22s cubic-bezier(0.32,0.72,0,1);
|
|
1157
|
-
}
|
|
1158
|
-
@keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
|
|
1159
|
-
.sg-header {
|
|
1160
|
-
display: flex;
|
|
1161
|
-
align-items: center;
|
|
1162
|
-
justify-content: space-between;
|
|
1163
|
-
padding: 14px 16px 10px;
|
|
1164
|
-
position: sticky;
|
|
1165
|
-
top: 0;
|
|
1166
|
-
background: #2b2a27;
|
|
1167
|
-
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
1168
|
-
z-index: 1;
|
|
1169
|
-
}
|
|
1170
|
-
.sg-title {
|
|
1171
|
-
font-size: 13px;
|
|
1172
|
-
font-weight: 500;
|
|
1173
|
-
color: var(--text-primary);
|
|
1174
|
-
letter-spacing: -0.01em;
|
|
1175
|
-
}
|
|
1176
|
-
.sg-close {
|
|
1177
|
-
width: 24px; height: 24px;
|
|
1178
|
-
border-radius: 6px;
|
|
1179
|
-
background: none;
|
|
1180
|
-
border: none;
|
|
1181
|
-
cursor: pointer;
|
|
1182
|
-
display: flex; align-items: center; justify-content: center;
|
|
1183
|
-
color: var(--text-muted);
|
|
1184
|
-
transition: background 0.12s, color 0.12s;
|
|
1185
|
-
}
|
|
1186
|
-
.sg-close:hover { background: rgba(255,255,255,0.07); color: var(--text-secondary); }
|
|
1187
|
-
.ds-filters {
|
|
1188
|
-
display: flex;
|
|
1189
|
-
gap: 4px;
|
|
1190
|
-
padding: 8px 12px 4px;
|
|
1191
|
-
flex-wrap: wrap;
|
|
1192
|
-
position: sticky;
|
|
1193
|
-
top: 46px;
|
|
1194
|
-
background: #2b2a27;
|
|
1195
|
-
z-index: 1;
|
|
1196
|
-
}
|
|
1197
|
-
.ds-filter-btn {
|
|
1198
|
-
font-size: 10px;
|
|
1199
|
-
padding: 3px 8px;
|
|
1200
|
-
border-radius: 999px;
|
|
1201
|
-
border: 1px solid rgba(255,255,255,0.12);
|
|
1202
|
-
background: transparent;
|
|
1203
|
-
color: var(--text-muted);
|
|
1204
|
-
cursor: pointer;
|
|
1205
|
-
transition: all 0.12s;
|
|
1206
|
-
}
|
|
1207
|
-
.ds-filter-btn:hover { border-color: rgba(255,255,255,0.25); color: var(--text-secondary); }
|
|
1208
|
-
.ds-filter-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
1209
|
-
.sg-grid {
|
|
1210
|
-
display: grid;
|
|
1211
|
-
grid-template-columns: 1fr 1fr;
|
|
1212
|
-
gap: 8px;
|
|
1213
|
-
padding: 8px 12px 0;
|
|
1214
|
-
}
|
|
1215
|
-
.sg-card {
|
|
1216
|
-
border-radius: 10px;
|
|
1217
|
-
overflow: hidden;
|
|
1218
|
-
cursor: pointer;
|
|
1219
|
-
border: 2px solid transparent;
|
|
1220
|
-
transition: border-color 0.15s, transform 0.1s;
|
|
1221
|
-
position: relative;
|
|
1222
|
-
background: rgba(255,255,255,0.03);
|
|
1223
|
-
}
|
|
1224
|
-
.sg-card:hover { transform: scale(1.02); }
|
|
1225
|
-
.sg-card.active { border-color: var(--accent); }
|
|
1226
|
-
.ds-swatches {
|
|
1227
|
-
display: flex;
|
|
1228
|
-
gap: 4px;
|
|
1229
|
-
padding: 12px 10px 8px;
|
|
1230
|
-
}
|
|
1231
|
-
.ds-swatch {
|
|
1232
|
-
width: 16px; height: 16px;
|
|
1233
|
-
border-radius: 50%;
|
|
1234
|
-
border: 1px solid rgba(255,255,255,0.1);
|
|
1235
|
-
flex-shrink: 0;
|
|
1236
|
-
}
|
|
1237
|
-
.sg-card-label {
|
|
1238
|
-
font-size: 11px;
|
|
1239
|
-
font-weight: 500;
|
|
1240
|
-
color: var(--text-secondary);
|
|
1241
|
-
padding: 0 10px 2px;
|
|
1242
|
-
white-space: nowrap;
|
|
1243
|
-
overflow: hidden;
|
|
1244
|
-
text-overflow: ellipsis;
|
|
1245
|
-
}
|
|
1246
|
-
.ds-org {
|
|
1247
|
-
font-size: 9.5px;
|
|
1248
|
-
color: var(--text-muted);
|
|
1249
|
-
padding: 0 10px 4px;
|
|
1250
|
-
white-space: nowrap;
|
|
1251
|
-
overflow: hidden;
|
|
1252
|
-
text-overflow: ellipsis;
|
|
1253
|
-
}
|
|
1254
|
-
.ds-category-badge {
|
|
1255
|
-
display: inline-block;
|
|
1256
|
-
font-size: 8.5px;
|
|
1257
|
-
padding: 1px 5px;
|
|
1258
|
-
border-radius: 3px;
|
|
1259
|
-
background: rgba(255,255,255,0.06);
|
|
1260
|
-
color: var(--text-muted);
|
|
1261
|
-
margin: 0 10px 8px;
|
|
1262
|
-
}
|
|
1263
|
-
.sg-card.active .sg-card-label { color: var(--accent); }
|
|
1264
|
-
.sg-active-check {
|
|
1265
|
-
position: absolute;
|
|
1266
|
-
top: 6px; right: 6px;
|
|
1267
|
-
width: 18px; height: 18px;
|
|
1268
|
-
border-radius: 50%;
|
|
1269
|
-
background: var(--accent);
|
|
1270
|
-
display: flex; align-items: center; justify-content: center;
|
|
1271
|
-
opacity: 0;
|
|
1272
|
-
transition: opacity 0.15s;
|
|
1273
|
-
}
|
|
1274
|
-
.sg-card.active .sg-active-check { opacity: 1; }
|
|
1275
|
-
|
|
1276
|
-
/* Design system chip in input */
|
|
1277
|
-
.style-guide-chip {
|
|
1278
|
-
display: inline-flex;
|
|
1279
|
-
align-items: center;
|
|
1280
|
-
gap: 5px;
|
|
1281
|
-
padding: 3px 8px 3px 7px;
|
|
1282
|
-
border-radius: 999px;
|
|
1283
|
-
background: rgba(218,119,86,0.12);
|
|
1284
|
-
border: 1px solid rgba(218,119,86,0.28);
|
|
1285
|
-
font-size: 10.5px;
|
|
1286
|
-
color: var(--accent);
|
|
1287
|
-
margin: 6px 14px 0;
|
|
1288
|
-
cursor: default;
|
|
1289
|
-
animation: fadein 0.15s ease;
|
|
1290
|
-
}
|
|
1291
|
-
.style-guide-chip-x {
|
|
1292
|
-
cursor: pointer;
|
|
1293
|
-
color: rgba(218,119,86,0.6);
|
|
1294
|
-
font-size: 12px;
|
|
1295
|
-
line-height: 1;
|
|
1296
|
-
padding: 0 1px;
|
|
1297
|
-
border-radius: 3px;
|
|
1298
|
-
transition: color 0.1s;
|
|
1299
|
-
}
|
|
1300
|
-
.style-guide-chip-x:hover { color: var(--accent); }
|
|
1301
|
-
.sg-btn-active { color: var(--accent) !important; }
|
|
1302
|
-
|
|
1303
|
-
/* ── Knowledge Sources Panel ──────────────────────────────── */
|
|
1304
|
-
.ks-panel {
|
|
1305
|
-
display: none;
|
|
1306
|
-
position: absolute;
|
|
1307
|
-
bottom: calc(100% + 4px);
|
|
1308
|
-
left: 0;
|
|
1309
|
-
right: 0;
|
|
1310
|
-
background: var(--bg-header);
|
|
1311
|
-
border: 1px solid var(--border-header);
|
|
1312
|
-
border-radius: 12px;
|
|
1313
|
-
padding: 10px;
|
|
1314
|
-
z-index: 200;
|
|
1315
|
-
max-height: 240px;
|
|
1316
|
-
overflow-y: auto;
|
|
1317
|
-
animation: fadein 0.15s ease;
|
|
1318
|
-
}
|
|
1319
|
-
.ks-panel.open { display: block; }
|
|
1320
|
-
.ks-panel-title {
|
|
1321
|
-
font-size: 11px;
|
|
1322
|
-
font-weight: 500;
|
|
1323
|
-
color: var(--text-secondary);
|
|
1324
|
-
margin-bottom: 8px;
|
|
1325
|
-
display: flex;
|
|
1326
|
-
align-items: center;
|
|
1327
|
-
gap: 5px;
|
|
1328
|
-
}
|
|
1329
|
-
.ks-input-row {
|
|
1330
|
-
display: flex;
|
|
1331
|
-
gap: 6px;
|
|
1332
|
-
margin-bottom: 8px;
|
|
1333
|
-
}
|
|
1334
|
-
.ks-input {
|
|
1335
|
-
flex: 1;
|
|
1336
|
-
background: var(--bg-input);
|
|
1337
|
-
border: 1px solid rgba(255,255,255,0.08);
|
|
1338
|
-
border-radius: 8px;
|
|
1339
|
-
padding: 6px 10px;
|
|
1340
|
-
font-size: 12px;
|
|
1341
|
-
color: var(--text-primary);
|
|
1342
|
-
font-family: var(--font-sans);
|
|
1343
|
-
outline: none;
|
|
1344
|
-
}
|
|
1345
|
-
.ks-input:focus { border-color: rgba(218,119,86,0.4); }
|
|
1346
|
-
.ks-input::placeholder { color: var(--text-ghost); }
|
|
1347
|
-
.ks-add-btn {
|
|
1348
|
-
background: var(--accent);
|
|
1349
|
-
color: #fff;
|
|
1350
|
-
border: none;
|
|
1351
|
-
border-radius: 8px;
|
|
1352
|
-
padding: 6px 12px;
|
|
1353
|
-
font-size: 11px;
|
|
1354
|
-
font-weight: 500;
|
|
1355
|
-
cursor: pointer;
|
|
1356
|
-
white-space: nowrap;
|
|
1357
|
-
}
|
|
1358
|
-
.ks-add-btn:hover { background: var(--accent-hover); }
|
|
1359
|
-
.ks-add-btn:disabled { opacity: 0.4; cursor: default; }
|
|
1360
|
-
.ks-card {
|
|
1361
|
-
display: flex;
|
|
1362
|
-
align-items: center;
|
|
1363
|
-
justify-content: space-between;
|
|
1364
|
-
padding: 6px 8px;
|
|
1365
|
-
background: rgba(255,255,255,0.03);
|
|
1366
|
-
border-radius: 8px;
|
|
1367
|
-
margin-bottom: 4px;
|
|
1368
|
-
}
|
|
1369
|
-
.ks-card-info {
|
|
1370
|
-
display: flex;
|
|
1371
|
-
flex-direction: column;
|
|
1372
|
-
gap: 1px;
|
|
1373
|
-
min-width: 0;
|
|
1374
|
-
}
|
|
1375
|
-
.ks-card-title {
|
|
1376
|
-
font-size: 12px;
|
|
1377
|
-
color: var(--text-primary);
|
|
1378
|
-
white-space: nowrap;
|
|
1379
|
-
overflow: hidden;
|
|
1380
|
-
text-overflow: ellipsis;
|
|
1381
|
-
}
|
|
1382
|
-
.ks-card-meta {
|
|
1383
|
-
font-size: 10px;
|
|
1384
|
-
color: var(--text-muted);
|
|
1385
|
-
}
|
|
1386
|
-
.ks-card-remove {
|
|
1387
|
-
background: transparent;
|
|
1388
|
-
border: none;
|
|
1389
|
-
color: var(--text-ghost);
|
|
1390
|
-
cursor: pointer;
|
|
1391
|
-
font-size: 14px;
|
|
1392
|
-
padding: 2px 4px;
|
|
1393
|
-
border-radius: 4px;
|
|
1394
|
-
line-height: 1;
|
|
1395
|
-
}
|
|
1396
|
-
.ks-card-remove:hover { color: var(--accent); background: rgba(255,255,255,0.06); }
|
|
1397
|
-
.ks-card-preview {
|
|
1398
|
-
font-size: 10px;
|
|
1399
|
-
color: var(--text-ghost);
|
|
1400
|
-
line-height: 1.4;
|
|
1401
|
-
margin-top: 3px;
|
|
1402
|
-
max-height: 0;
|
|
1403
|
-
overflow: hidden;
|
|
1404
|
-
transition: max-height 0.2s ease;
|
|
1405
|
-
}
|
|
1406
|
-
.ks-card.expanded .ks-card-preview {
|
|
1407
|
-
max-height: 120px;
|
|
1408
|
-
overflow-y: auto;
|
|
1409
|
-
}
|
|
1410
|
-
.ks-card-toggle {
|
|
1411
|
-
font-size: 9px;
|
|
1412
|
-
color: var(--text-ghost);
|
|
1413
|
-
cursor: pointer;
|
|
1414
|
-
margin-left: 4px;
|
|
1415
|
-
}
|
|
1416
|
-
.ks-card-toggle:hover { color: var(--text-secondary); }
|
|
1417
|
-
.ks-card-chars {
|
|
1418
|
-
font-size: 10px;
|
|
1419
|
-
color: var(--text-ghost);
|
|
1420
|
-
margin-left: 4px;
|
|
1421
|
-
}
|
|
1422
|
-
.ks-empty {
|
|
1423
|
-
font-size: 11px;
|
|
1424
|
-
color: var(--text-ghost);
|
|
1425
|
-
text-align: center;
|
|
1426
|
-
padding: 4px 0;
|
|
1427
|
-
}
|
|
1428
|
-
.ks-error {
|
|
1429
|
-
font-size: 11px;
|
|
1430
|
-
color: #e55;
|
|
1431
|
-
padding: 4px 0;
|
|
1432
|
-
}
|
|
1433
|
-
.ks-success {
|
|
1434
|
-
font-size: 11px;
|
|
1435
|
-
color: #4a2;
|
|
1436
|
-
padding: 4px 0;
|
|
1437
|
-
animation: fadein 0.15s ease;
|
|
1438
|
-
}
|
|
1439
|
-
.ks-badge {
|
|
1440
|
-
position: absolute;
|
|
1441
|
-
top: -3px;
|
|
1442
|
-
right: -3px;
|
|
1443
|
-
width: 14px;
|
|
1444
|
-
height: 14px;
|
|
1445
|
-
border-radius: 50%;
|
|
1446
|
-
background: var(--accent);
|
|
1447
|
-
color: #fff;
|
|
1448
|
-
font-size: 9px;
|
|
1449
|
-
display: flex;
|
|
1450
|
-
align-items: center;
|
|
1451
|
-
justify-content: center;
|
|
1452
|
-
font-weight: 600;
|
|
1453
|
-
}
|
|
1454
|
-
.ks-btn-wrap { position: relative; }
|
|
1455
|
-
.ks-hub-item {
|
|
1456
|
-
display: flex;
|
|
1457
|
-
align-items: center;
|
|
1458
|
-
gap: 8px;
|
|
1459
|
-
padding: 7px 8px;
|
|
1460
|
-
border-radius: 8px;
|
|
1461
|
-
cursor: pointer;
|
|
1462
|
-
transition: background 0.1s;
|
|
1463
|
-
margin-bottom: 2px;
|
|
1464
|
-
}
|
|
1465
|
-
.ks-hub-item:hover { background: rgba(255,255,255,0.06); }
|
|
1466
|
-
.ks-hub-item.loading { opacity: 0.5; pointer-events: none; }
|
|
1467
|
-
.ks-hub-icon { font-size: 18px; flex-shrink: 0; }
|
|
1468
|
-
.ks-hub-info { min-width: 0; flex: 1; }
|
|
1469
|
-
.ks-hub-name { font-size: 12px; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1470
|
-
.ks-hub-meta { font-size: 10px; color: var(--text-ghost); }
|
|
1471
|
-
.ks-hub-loaded {
|
|
1472
|
-
font-size: 9px;
|
|
1473
|
-
background: rgba(74,170,34,0.15);
|
|
1474
|
-
color: #4a2;
|
|
1475
|
-
padding: 1px 6px;
|
|
1476
|
-
border-radius: 4px;
|
|
1477
|
-
flex-shrink: 0;
|
|
1478
|
-
}
|
|
1479
|
-
</style>
|
|
1480
|
-
</head>
|
|
1481
|
-
<body>
|
|
1482
|
-
<div class="app">
|
|
1483
|
-
<div class="resize-handle resize-handle--top" id="resize-handle-top" title="Drag to resize plugin height"></div>
|
|
1484
|
-
|
|
1485
|
-
<!-- Login / Provider Selection Screen -->
|
|
1486
|
-
<!-- Setup Guide (shown when local server not running) -->
|
|
1487
|
-
<div class="setup-guide hidden" id="setup-guide">
|
|
1488
|
-
<div class="setup-guide-icon">
|
|
1489
|
-
<svg width="40" height="40" viewBox="0 0 28 28" fill="none">
|
|
1490
|
-
<path d="M14 3V11M14 17V25M3 14H11M17 14H25M5.757 5.757L11.314 11.314M16.686 16.686L22.243 22.243M5.757 22.243L11.314 16.686M16.686 11.314L22.243 5.757" stroke="#da7756" stroke-width="2" stroke-linecap="round"/>
|
|
1491
|
-
</svg>
|
|
1492
|
-
</div>
|
|
1493
|
-
<div class="setup-guide-title">Figma Intelligence</div>
|
|
1494
|
-
<div class="setup-guide-subtitle">
|
|
1495
|
-
88 AI-powered design tools.<br>
|
|
1496
|
-
Connect via your Claude, OpenAI, or Gemini subscription.
|
|
1497
|
-
</div>
|
|
1498
|
-
|
|
1499
|
-
<div class="setup-guide-steps">
|
|
1500
|
-
<div class="setup-guide-step">
|
|
1501
|
-
<div class="setup-guide-step-num">1</div>
|
|
1502
|
-
<div>Open your terminal and run the command below</div>
|
|
1503
|
-
</div>
|
|
1504
|
-
</div>
|
|
1505
|
-
|
|
1506
|
-
<div class="setup-guide-cmd" id="setup-cmd" onclick="copySetupCmd()">
|
|
1507
|
-
npx @sarjallab09/figma-intelligence setup
|
|
1508
|
-
</div>
|
|
1509
|
-
|
|
1510
|
-
<div class="setup-guide-steps" style="margin-top:16px">
|
|
1511
|
-
<div class="setup-guide-step">
|
|
1512
|
-
<div class="setup-guide-step-num">2</div>
|
|
1513
|
-
<div>Follow the prompts to log in to your AI provider</div>
|
|
1514
|
-
</div>
|
|
1515
|
-
<div class="setup-guide-step">
|
|
1516
|
-
<div class="setup-guide-step-num">3</div>
|
|
1517
|
-
<div>Once you see "Ready" in the terminal, come back here — the plugin will auto-connect</div>
|
|
1518
|
-
</div>
|
|
1519
|
-
</div>
|
|
1520
|
-
|
|
1521
|
-
<div class="setup-guide-connecting" id="setup-connecting">
|
|
1522
|
-
<div class="dot-pulse"></div>
|
|
1523
|
-
<span>Waiting for local server...</span>
|
|
1524
|
-
</div>
|
|
1525
|
-
</div>
|
|
1526
|
-
|
|
1527
|
-
<div class="login-screen" id="login-screen" style="display:none">
|
|
1528
|
-
<div class="login-logo">
|
|
1529
|
-
<svg width="26" height="26" viewBox="0 0 28 28" fill="none">
|
|
1530
|
-
<path d="M14 3V11M14 17V25M3 14H11M17 14H25M5.757 5.757L11.314 11.314M16.686 16.686L22.243 22.243M5.757 22.243L11.314 16.686M16.686 11.314L22.243 5.757" stroke="#da7756" stroke-width="2.5" stroke-linecap="round"/>
|
|
1531
|
-
</svg>
|
|
1532
|
-
</div>
|
|
1533
|
-
<div class="login-title">Figma Intelligence</div>
|
|
1534
|
-
<div class="login-subtitle">Choose your AI assistant</div>
|
|
1535
|
-
|
|
1536
|
-
<div class="provider-list">
|
|
1537
|
-
<!-- Claude -->
|
|
1538
|
-
<div class="provider-card" id="pc-claude" onclick="selectLoginProvider('claude')">
|
|
1539
|
-
<div class="provider-icon pc-claude" id="claude-logo-slot"></div>
|
|
1540
|
-
<div class="provider-info">
|
|
1541
|
-
<div class="provider-name">Claude</div>
|
|
1542
|
-
<div class="provider-desc" id="pd-claude">Anthropic · Subscription required</div>
|
|
1543
|
-
</div>
|
|
1544
|
-
<div class="provider-check">✓</div>
|
|
1545
|
-
</div>
|
|
1546
|
-
|
|
1547
|
-
<!-- OpenAI -->
|
|
1548
|
-
<div class="provider-card" id="pc-openai" onclick="selectLoginProvider('openai')">
|
|
1549
|
-
<div class="provider-icon pc-openai" id="codex-logo-slot"></div>
|
|
1550
|
-
<div class="provider-info">
|
|
1551
|
-
<div class="provider-name">OpenAI</div>
|
|
1552
|
-
<div class="provider-desc" id="pd-openai">OpenAI · Subscription required</div>
|
|
1553
|
-
</div>
|
|
1554
|
-
<div class="provider-check">✓</div>
|
|
1555
|
-
</div>
|
|
1556
|
-
|
|
1557
|
-
<!-- Gemini -->
|
|
1558
|
-
<div class="provider-card" id="pc-gemini" onclick="selectLoginProvider('gemini')">
|
|
1559
|
-
<div class="provider-icon pc-gemini" id="gemini-logo-slot"></div>
|
|
1560
|
-
<div class="provider-info">
|
|
1561
|
-
<div class="provider-name">Gemini</div>
|
|
1562
|
-
<div class="provider-desc" id="pd-gemini">Google AI · Subscription or API key</div>
|
|
1563
|
-
</div>
|
|
1564
|
-
<div class="provider-check">✓</div>
|
|
1565
|
-
</div>
|
|
1566
|
-
|
|
1567
|
-
<!-- Perplexity (Research) -->
|
|
1568
|
-
<div class="provider-card" id="pc-perplexity" onclick="selectLoginProvider('perplexity')">
|
|
1569
|
-
<div class="provider-icon pc-perplexity" id="perplexity-logo-slot">
|
|
1570
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
|
1571
|
-
<circle cx="12" cy="12" r="9" stroke="#20bfbb" stroke-width="1.5"/>
|
|
1572
|
-
<path d="M12 7v5l3.5 3.5" stroke="#20bfbb" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1573
|
-
<circle cx="12" cy="12" r="1.5" fill="#20bfbb"/>
|
|
1574
|
-
</svg>
|
|
1575
|
-
</div>
|
|
1576
|
-
<div class="provider-info">
|
|
1577
|
-
<div class="provider-name">Perplexity</div>
|
|
1578
|
-
<div class="provider-desc" id="pd-perplexity">Research AI · API key required</div>
|
|
1579
|
-
</div>
|
|
1580
|
-
<div class="provider-check">✓</div>
|
|
1581
|
-
</div>
|
|
1582
|
-
|
|
1583
|
-
<!-- Google Stitch -->
|
|
1584
|
-
<div class="provider-card" id="pc-stitch" onclick="selectLoginProvider('stitch')">
|
|
1585
|
-
<div class="provider-icon pc-stitch">
|
|
1586
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
|
1587
|
-
<rect x="3" y="3" width="18" height="18" rx="3" stroke="#4285F4" stroke-width="1.5"/>
|
|
1588
|
-
<path d="M7 8h10M7 12h6M7 16h8" stroke="#4285F4" stroke-width="1.5" stroke-linecap="round"/>
|
|
1589
|
-
</svg>
|
|
1590
|
-
</div>
|
|
1591
|
-
<div class="provider-info">
|
|
1592
|
-
<div class="provider-name">Stitch</div>
|
|
1593
|
-
<div class="provider-desc" id="pd-stitch">Google AI · Access token required</div>
|
|
1594
|
-
</div>
|
|
1595
|
-
<div class="provider-check">✓</div>
|
|
1596
|
-
</div>
|
|
1597
|
-
|
|
1598
|
-
<!-- External Tool / Bridge -->
|
|
1599
|
-
<div class="provider-card" id="pc-bridge" onclick="selectLoginProvider('bridge')">
|
|
1600
|
-
<div class="provider-icon pc-bridge">
|
|
1601
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
|
1602
|
-
<path d="M4 12h4M16 12h4M8 12a4 4 0 008 0M8 12a4 4 0 018 0" stroke="#a084e8" stroke-width="1.5" stroke-linecap="round"/>
|
|
1603
|
-
<circle cx="4" cy="12" r="1.5" fill="#a084e8"/>
|
|
1604
|
-
<circle cx="20" cy="12" r="1.5" fill="#a084e8"/>
|
|
1605
|
-
</svg>
|
|
1606
|
-
</div>
|
|
1607
|
-
<div class="provider-info">
|
|
1608
|
-
<div class="provider-name">External Tool</div>
|
|
1609
|
-
<div class="provider-desc">VS Code · Cursor · Antigravity · More</div>
|
|
1610
|
-
</div>
|
|
1611
|
-
<div class="provider-check">✓</div>
|
|
1612
|
-
</div>
|
|
1613
|
-
</div>
|
|
1614
|
-
|
|
1615
|
-
<!-- API key input (OpenAI / Gemini) -->
|
|
1616
|
-
<div class="login-expand" id="login-api-expand" style="display:none">
|
|
1617
|
-
<input
|
|
1618
|
-
class="login-api-input"
|
|
1619
|
-
type="password"
|
|
1620
|
-
id="login-api-key"
|
|
1621
|
-
placeholder="Paste your API key…"
|
|
1622
|
-
oninput="updateLoginContinue()"
|
|
1623
|
-
/>
|
|
1624
|
-
<span class="login-api-link" id="login-api-link"></span>
|
|
1625
|
-
</div>
|
|
1626
|
-
|
|
1627
|
-
<!-- Stitch Google Sign-In -->
|
|
1628
|
-
<div class="login-expand" id="login-stitch-auth" style="display:none">
|
|
1629
|
-
<button
|
|
1630
|
-
id="stitch-google-signin-btn"
|
|
1631
|
-
onclick="startStitchGoogleAuth()"
|
|
1632
|
-
style="width:100%;padding:12px 16px;border:none;border-radius:8px;background:#4285F4;color:#fff;font-size:14px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:8px"
|
|
1633
|
-
>
|
|
1634
|
-
<svg width="18" height="18" viewBox="0 0 48 48"><path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/><path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/><path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/><path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"/></svg>
|
|
1635
|
-
Sign in with Google
|
|
1636
|
-
</button>
|
|
1637
|
-
<div id="stitch-auth-status" style="text-align:center;font-size:12px;color:#888;min-height:18px"></div>
|
|
1638
|
-
</div>
|
|
1639
|
-
|
|
1640
|
-
<!-- Project name (Stitch, optional) -->
|
|
1641
|
-
<div class="login-expand" id="login-project-expand" style="display:none">
|
|
1642
|
-
<input
|
|
1643
|
-
class="login-api-input"
|
|
1644
|
-
type="text"
|
|
1645
|
-
id="login-project-id"
|
|
1646
|
-
placeholder="Project name (optional)"
|
|
1647
|
-
/>
|
|
1648
|
-
</div>
|
|
1649
|
-
|
|
1650
|
-
<!-- Bridge mode info -->
|
|
1651
|
-
<div class="login-expand" id="login-bridge-expand" style="display:none">
|
|
1652
|
-
<div class="login-bridge-info">
|
|
1653
|
-
Your AI tool connects to Figma via MCP at<br>
|
|
1654
|
-
<code>ws://localhost:9001</code><br><br>
|
|
1655
|
-
Point any MCP-compatible tool here and it can read & modify your Figma designs directly.
|
|
1656
|
-
<div class="login-bridge-tools">
|
|
1657
|
-
<span class="bridge-tool-tag">VS Code Copilot</span>
|
|
1658
|
-
<span class="bridge-tool-tag">Cursor</span>
|
|
1659
|
-
<span class="bridge-tool-tag">Antigravity</span>
|
|
1660
|
-
<span class="bridge-tool-tag">Claude Desktop</span>
|
|
1661
|
-
<span class="bridge-tool-tag">Any MCP client</span>
|
|
1662
|
-
</div>
|
|
1663
|
-
</div>
|
|
1664
|
-
</div>
|
|
1665
|
-
|
|
1666
|
-
<button class="login-continue" id="login-continue-btn" disabled onclick="continueWithProvider()">
|
|
1667
|
-
Continue
|
|
1668
|
-
</button>
|
|
1669
|
-
<div class="login-hint" id="login-hint">Select an AI provider to get started</div>
|
|
1670
|
-
</div>
|
|
1671
|
-
|
|
1672
|
-
<!-- Header -->
|
|
1673
|
-
<div class="header">
|
|
1674
|
-
<div class="header-left">
|
|
1675
|
-
<svg width="14" height="14" viewBox="0 0 28 28" fill="none">
|
|
1676
|
-
<path d="M14 3V11M14 17V25M3 14H11M17 14H25M5.757 5.757L11.314 11.314M16.686 16.686L22.243 22.243M5.757 22.243L11.314 16.686M16.686 11.314L22.243 5.757" stroke="#da7756" stroke-width="2.8" stroke-linecap="round"/>
|
|
1677
|
-
</svg>
|
|
1678
|
-
<span class="header-title" id="header-title">Figma Intelligence</span>
|
|
1679
|
-
<div class="provider-badge" id="provider-badge" onclick="showLoginScreen()"></div>
|
|
1680
|
-
</div>
|
|
1681
|
-
<div class="header-controls">
|
|
1682
|
-
<button class="icon-btn" id="new-chat-btn" title="New Chat" onclick="startNewConversation()">
|
|
1683
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1684
|
-
<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
|
1685
|
-
</svg>
|
|
1686
|
-
</button>
|
|
1687
|
-
<div class="conn-pill">
|
|
1688
|
-
<div class="conn-dot" id="conn-dot"></div>
|
|
1689
|
-
<span id="conn-text">Connecting…</span>
|
|
1690
|
-
</div>
|
|
1691
|
-
</div>
|
|
1692
|
-
</div>
|
|
1693
|
-
|
|
1694
|
-
<!-- Auth strip -->
|
|
1695
|
-
<div class="auth-strip" id="auth-strip">
|
|
1696
|
-
<span id="auth-text">Checking provider subscriptions…</span>
|
|
1697
|
-
</div>
|
|
1698
|
-
|
|
1699
|
-
<!-- VS Code connection status (shown in dual mode) -->
|
|
1700
|
-
<div class="auth-strip" id="vscode-status" style="display:none"></div>
|
|
1701
|
-
|
|
1702
|
-
<!-- Mode tabs (Chat / Code / Design+Code) -->
|
|
1703
|
-
<div class="mode-tabs" id="mode-tabs">
|
|
1704
|
-
<button class="mode-tab" id="mode-tab-chat" onclick="switchMode('chat')">
|
|
1705
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
1706
|
-
Chat
|
|
1707
|
-
</button>
|
|
1708
|
-
<button class="mode-tab active" id="mode-tab-code" onclick="switchMode('code')">
|
|
1709
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
1710
|
-
Code
|
|
1711
|
-
</button>
|
|
1712
|
-
<button class="mode-tab" id="mode-tab-dual" onclick="switchMode('dual')">
|
|
1713
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
|
1714
|
-
Design + Code
|
|
1715
|
-
</button>
|
|
1716
|
-
</div>
|
|
1717
|
-
|
|
1718
|
-
<!-- Thread -->
|
|
1719
|
-
<div class="thread" id="thread">
|
|
1720
|
-
<!-- Bridge mode home (shown instead of chat home when provider = bridge) -->
|
|
1721
|
-
<div class="bridge-home" id="bridge-home" style="display:none">
|
|
1722
|
-
<div class="bridge-home-icon">
|
|
1723
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
|
1724
|
-
<path d="M4 12h4M16 12h4M8 12a4 4 0 008 0M8 12a4 4 0 018 0" stroke="#a084e8" stroke-width="1.5" stroke-linecap="round"/>
|
|
1725
|
-
<circle cx="4" cy="12" r="1.5" fill="#a084e8"/>
|
|
1726
|
-
<circle cx="20" cy="12" r="1.5" fill="#a084e8"/>
|
|
1727
|
-
</svg>
|
|
1728
|
-
</div>
|
|
1729
|
-
<div class="bridge-home-title">Bridge Mode Active</div>
|
|
1730
|
-
<div class="bridge-home-desc">
|
|
1731
|
-
Your AI tool connects to this Figma plugin at<br>
|
|
1732
|
-
<span class="bridge-home-url">ws://localhost:9001</span>
|
|
1733
|
-
</div>
|
|
1734
|
-
<div class="bridge-home-desc" style="margin-top:6px">
|
|
1735
|
-
Use VS Code Copilot, Cursor, Antigravity, or any<br>MCP-compatible tool to control your Figma designs.
|
|
1736
|
-
</div>
|
|
1737
|
-
</div>
|
|
1738
|
-
<!-- Code mode home -->
|
|
1739
|
-
<div class="home-state" id="home-state">
|
|
1740
|
-
<div class="home-heading">
|
|
1741
|
-
<svg width="22" height="22" viewBox="0 0 28 28" fill="none">
|
|
1742
|
-
<path d="M14 3V11M14 17V25M3 14H11M17 14H25M5.757 5.757L11.314 11.314M16.686 16.686L22.243 22.243M5.757 22.243L11.314 16.686M16.686 11.314L22.243 5.757" stroke="#da7756" stroke-width="2.5" stroke-linecap="round"/>
|
|
1743
|
-
</svg>
|
|
1744
|
-
<div class="home-heading-text">How can I help you design?</div>
|
|
1745
|
-
</div>
|
|
1746
|
-
<div class="chips">
|
|
1747
|
-
<div class="chip" onclick="useChip(this)">
|
|
1748
|
-
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><rect x="1" y="1" width="10" height="10" rx="2" stroke="currentColor" stroke-width="1.2"/><path d="M3 4h6M3 6.5h3.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
|
1749
|
-
Create a login screen
|
|
1750
|
-
</div>
|
|
1751
|
-
<div class="chip" onclick="useChip(this)">
|
|
1752
|
-
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><rect x="1.5" y="2.5" width="9" height="7" rx="1.5" stroke="currentColor" stroke-width="1.2"/><path d="M3.5 5h5M3.5 7h2.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
|
1753
|
-
Design a button component
|
|
1754
|
-
</div>
|
|
1755
|
-
<div class="chip" onclick="useChip(this)">
|
|
1756
|
-
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M1 9L4 5.5L6.5 7.5L9 4L11 6" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
1757
|
-
Generate a dashboard layout
|
|
1758
|
-
</div>
|
|
1759
|
-
<div class="chip" onclick="useChip(this)">
|
|
1760
|
-
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.2"/><circle cx="6" cy="6" r="1.5" fill="currentColor"/></svg>
|
|
1761
|
-
Set up a color system
|
|
1762
|
-
</div>
|
|
1763
|
-
</div>
|
|
1764
|
-
</div>
|
|
1765
|
-
<!-- Chat mode home (hidden, captured as template) -->
|
|
1766
|
-
<div class="home-state" id="chat-home" style="display:none">
|
|
1767
|
-
<div class="home-heading">
|
|
1768
|
-
<svg width="22" height="22" viewBox="0 0 28 28" fill="none">
|
|
1769
|
-
<path d="M14 3V11M14 17V25M3 14H11M17 14H25M5.757 5.757L11.314 11.314M16.686 16.686L22.243 22.243M5.757 22.243L11.314 16.686M16.686 11.314L22.243 5.757" stroke="#da7756" stroke-width="2.5" stroke-linecap="round"/>
|
|
1770
|
-
</svg>
|
|
1771
|
-
<div class="home-heading-text">What would you like to know?</div>
|
|
1772
|
-
</div>
|
|
1773
|
-
<div class="chips">
|
|
1774
|
-
<div class="chip" onclick="useChip(this)">
|
|
1775
|
-
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 4.5C4.5 3.7 5.2 3 6 3s1.5.7 1.5 1.5c0 .8-.6 1.1-1 1.3-.2.1-.5.3-.5.7" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="6" cy="8.5" r=".6" fill="currentColor"/></svg>
|
|
1776
|
-
What are design tokens?
|
|
1777
|
-
</div>
|
|
1778
|
-
<div class="chip" onclick="useChip(this)">
|
|
1779
|
-
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 4.5C4.5 3.7 5.2 3 6 3s1.5.7 1.5 1.5c0 .8-.6 1.1-1 1.3-.2.1-.5.3-.5.7" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="6" cy="8.5" r=".6" fill="currentColor"/></svg>
|
|
1780
|
-
How should I structure my variables?
|
|
1781
|
-
</div>
|
|
1782
|
-
<div class="chip" onclick="useChip(this)">
|
|
1783
|
-
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 4.5C4.5 3.7 5.2 3 6 3s1.5.7 1.5 1.5c0 .8-.6 1.1-1 1.3-.2.1-.5.3-.5.7" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="6" cy="8.5" r=".6" fill="currentColor"/></svg>
|
|
1784
|
-
Best practices for auto layout
|
|
1785
|
-
</div>
|
|
1786
|
-
<div class="chip" onclick="useChip(this)">
|
|
1787
|
-
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 4.5C4.5 3.7 5.2 3 6 3s1.5.7 1.5 1.5c0 .8-.6 1.1-1 1.3-.2.1-.5.3-.5.7" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="6" cy="8.5" r=".6" fill="currentColor"/></svg>
|
|
1788
|
-
Explain component variants
|
|
1789
|
-
</div>
|
|
1790
|
-
</div>
|
|
1791
|
-
</div>
|
|
1792
|
-
</div>
|
|
1793
|
-
|
|
1794
|
-
<!-- Design System Modal -->
|
|
1795
|
-
<div class="sg-overlay" id="sg-overlay" style="display:none" onclick="onSgOverlayClick(event)">
|
|
1796
|
-
<div class="sg-modal" id="sg-modal">
|
|
1797
|
-
<div class="sg-header">
|
|
1798
|
-
<span class="sg-title">Pick a Design System</span>
|
|
1799
|
-
<button class="sg-close" onclick="closeStyleGuideModal()">
|
|
1800
|
-
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
|
1801
|
-
<path d="M1 1L11 11M11 1L1 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
1802
|
-
</svg>
|
|
1803
|
-
</button>
|
|
1804
|
-
</div>
|
|
1805
|
-
<div class="ds-filters" id="ds-filters"></div>
|
|
1806
|
-
<div class="sg-grid" id="sg-grid"></div>
|
|
1807
|
-
</div>
|
|
1808
|
-
</div>
|
|
1809
|
-
|
|
1810
|
-
<!-- Input area -->
|
|
1811
|
-
<div class="input-area" style="position:relative">
|
|
1812
|
-
<div id="style-guide-chip-container"></div>
|
|
1813
|
-
<!-- Knowledge Sources Panel -->
|
|
1814
|
-
<div class="ks-panel" id="ks-panel">
|
|
1815
|
-
<div class="ks-panel-title">
|
|
1816
|
-
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M3 2h8a2 2 0 012 2v8a2 2 0 01-2 2H3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><path d="M3 2v12" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
|
|
1817
|
-
Knowledge Sources
|
|
1818
|
-
</div>
|
|
1819
|
-
<!-- Tab switcher: 4 tabs -->
|
|
1820
|
-
<div style="display:flex;gap:0;margin-bottom:8px;border-radius:6px;overflow:hidden;border:1px solid rgba(255,255,255,0.08)">
|
|
1821
|
-
<button class="ks-tab" id="ks-tab-hub" onclick="switchKsTab('hub')" style="flex:1;padding:5px 8px;font-size:10.5px;border:none;cursor:pointer;background:rgba(255,255,255,0.08);color:var(--text-primary);font-family:var(--font-sans)">Hub</button>
|
|
1822
|
-
<button class="ks-tab" id="ks-tab-file" onclick="switchKsTab('file')" style="flex:1;padding:5px 8px;font-size:10.5px;border:none;cursor:pointer;background:transparent;color:var(--text-muted);font-family:var(--font-sans)">Upload</button>
|
|
1823
|
-
<button class="ks-tab" id="ks-tab-url" onclick="switchKsTab('url')" style="flex:1;padding:5px 8px;font-size:10.5px;border:none;cursor:pointer;background:transparent;color:var(--text-muted);font-family:var(--font-sans)">URL</button>
|
|
1824
|
-
<button class="ks-tab" id="ks-tab-text" onclick="switchKsTab('text')" style="flex:1;padding:5px 8px;font-size:10.5px;border:none;cursor:pointer;background:transparent;color:var(--text-muted);font-family:var(--font-sans)">Paste</button>
|
|
1825
|
-
<button class="ks-tab" id="ks-tab-web" onclick="switchKsTab('web')" style="flex:1;padding:5px 8px;font-size:10.5px;border:none;cursor:pointer;background:transparent;color:var(--text-muted);font-family:var(--font-sans)">Web</button>
|
|
1826
|
-
</div>
|
|
1827
|
-
<!-- Knowledge Hub -->
|
|
1828
|
-
<div id="ks-hub-section">
|
|
1829
|
-
<div style="font-size:10px;color:var(--text-ghost);margin-bottom:6px">Pre-loaded reference library. Click a file to activate it as context.</div>
|
|
1830
|
-
<div id="ks-hub-list" style="max-height:160px;overflow-y:auto">
|
|
1831
|
-
<div class="ks-empty" style="padding:8px 0">Loading hub...</div>
|
|
1832
|
-
</div>
|
|
1833
|
-
</div>
|
|
1834
|
-
<!-- File upload -->
|
|
1835
|
-
<div id="ks-file-section" style="display:none">
|
|
1836
|
-
<div class="ks-input-row">
|
|
1837
|
-
<button class="ks-add-btn" id="ks-file-btn" onclick="document.getElementById('ks-file-input').click()" style="width:100%;padding:12px;text-align:center">Choose PDF, DOCX, TXT, or MD file...</button>
|
|
1838
|
-
<input type="file" id="ks-file-input" accept=".pdf,.docx,.doc,.txt,.md,.csv,.json,.xml,.html" style="display:none" />
|
|
1839
|
-
</div>
|
|
1840
|
-
<div id="ks-file-name" style="display:none;font-size:11px;color:var(--text-secondary);margin-bottom:6px"></div>
|
|
1841
|
-
</div>
|
|
1842
|
-
<!-- URL input -->
|
|
1843
|
-
<div id="ks-url-section" style="display:none">
|
|
1844
|
-
<div class="ks-input-row">
|
|
1845
|
-
<input class="ks-input" id="ks-url-input" placeholder="Paste any web URL (article, docs, blog...)" />
|
|
1846
|
-
<button class="ks-add-btn" id="ks-url-btn" onclick="addKsUrl()">Add</button>
|
|
1847
|
-
</div>
|
|
1848
|
-
</div>
|
|
1849
|
-
<!-- Text paste input -->
|
|
1850
|
-
<div id="ks-text-section" style="display:none">
|
|
1851
|
-
<input class="ks-input" id="ks-paste-title" placeholder="Title (e.g. UX Research Notes)" style="margin-bottom:6px" />
|
|
1852
|
-
<textarea class="ks-input" id="ks-paste-content" placeholder="Paste your content here..." style="min-height:80px;max-height:140px;resize:vertical;margin-bottom:6px"></textarea>
|
|
1853
|
-
<button class="ks-add-btn" id="ks-paste-btn" onclick="addKsText()" style="width:100%">Add Content</button>
|
|
1854
|
-
</div>
|
|
1855
|
-
<!-- Web Reference Sites -->
|
|
1856
|
-
<div id="ks-web-section" style="display:none">
|
|
1857
|
-
<div style="font-size:10px;color:var(--text-ghost);margin-bottom:6px">Add authoritative design reference sites. Chat mode auto-searches these for answers.</div>
|
|
1858
|
-
<div class="ks-input-row">
|
|
1859
|
-
<input class="ks-input" id="ks-web-name" placeholder="Site name (e.g. Nielsen Norman Group)" style="margin-bottom:4px" />
|
|
1860
|
-
</div>
|
|
1861
|
-
<div class="ks-input-row">
|
|
1862
|
-
<input class="ks-input" id="ks-web-url" placeholder="Site URL (e.g. nngroup.com)" />
|
|
1863
|
-
<button class="ks-add-btn" id="ks-web-btn" onclick="addReferenceSite()">Add</button>
|
|
1864
|
-
</div>
|
|
1865
|
-
<div id="ks-web-list" style="margin-top:8px;max-height:140px;overflow-y:auto"></div>
|
|
1866
|
-
</div>
|
|
1867
|
-
<div id="ks-error" class="ks-error" style="display:none"></div>
|
|
1868
|
-
<div id="ks-success" class="ks-success" style="display:none"></div>
|
|
1869
|
-
<div id="ks-list"></div>
|
|
1870
|
-
</div>
|
|
1871
|
-
<div class="input-box">
|
|
1872
|
-
<div class="attached-files" id="attached-files" style="display:none"></div>
|
|
1873
|
-
<div class="input-main">
|
|
1874
|
-
<textarea
|
|
1875
|
-
id="input"
|
|
1876
|
-
placeholder="How can I help you today?"
|
|
1877
|
-
rows="1"
|
|
1878
|
-
disabled
|
|
1879
|
-
></textarea>
|
|
1880
|
-
</div>
|
|
1881
|
-
<div class="input-toolbar">
|
|
1882
|
-
<div class="toolbar-left">
|
|
1883
|
-
<button class="tb-btn" id="attach-btn" title="Attach file">
|
|
1884
|
-
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
|
1885
|
-
<path d="M13.5 7.5L7.5 13.5C5.843 15.157 3.157 15.157 1.5 13.5C-0.157 11.843 -0.157 9.157 1.5 7.5L7.5 1.5C8.605 0.395 10.395 0.395 11.5 1.5C12.605 2.605 12.605 4.395 11.5 5.5L5.5 11.5C4.948 12.052 4.052 12.052 3.5 11.5C2.948 10.948 2.948 10.052 3.5 9.5L9.5 3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
|
1886
|
-
</svg>
|
|
1887
|
-
</button>
|
|
1888
|
-
<button class="tb-btn" id="style-guide-btn" title="Pick a design system" onclick="openStyleGuideModal()">
|
|
1889
|
-
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
|
1890
|
-
<circle cx="5" cy="5" r="2" stroke="currentColor" stroke-width="1.3"/>
|
|
1891
|
-
<circle cx="11" cy="5" r="2" stroke="currentColor" stroke-width="1.3"/>
|
|
1892
|
-
<circle cx="5" cy="11" r="2" stroke="currentColor" stroke-width="1.3"/>
|
|
1893
|
-
<circle cx="11" cy="11" r="2" stroke="currentColor" stroke-width="1.3"/>
|
|
1894
|
-
</svg>
|
|
1895
|
-
</button>
|
|
1896
|
-
<div class="ks-btn-wrap">
|
|
1897
|
-
<button class="tb-btn" id="ks-btn" title="Knowledge Sources" onclick="toggleKsPanel()">
|
|
1898
|
-
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
|
1899
|
-
<path d="M3 2h8a2 2 0 012 2v8a2 2 0 01-2 2H3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
|
1900
|
-
<path d="M3 2v12" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
|
1901
|
-
<path d="M6 5h4M6 8h4M6 11h2" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/>
|
|
1902
|
-
</svg>
|
|
1903
|
-
</button>
|
|
1904
|
-
<span class="ks-badge" id="ks-badge" style="display:none">0</span>
|
|
1905
|
-
</div>
|
|
1906
|
-
<input type="file" id="file-input" multiple style="display:none" accept="image/*,.pdf,.txt,.json,.csv,.svg,.md,.html,.css,.js,.ts,.jsx,.tsx,.py,.go,.rs,.java,.rb,.yml,.yaml,.xml,.sql,.sh">
|
|
1907
|
-
</div>
|
|
1908
|
-
<div class="toolbar-right">
|
|
1909
|
-
<div class="ux-researcher-toggle" id="ux-researcher-toggle" onclick="toggleUxResearcher()" title="UX Researcher — fast Haiku-powered UX critique. Auto-attaches your current Figma selection." style="display:none">
|
|
1910
|
-
<span class="ux-researcher-dot"></span>
|
|
1911
|
-
<span>UXR</span>
|
|
1912
|
-
</div>
|
|
1913
|
-
<div class="model-badge" id="model-badge" onclick="toggleModelDropdown(event)">
|
|
1914
|
-
<span id="model-label">Sonnet</span>
|
|
1915
|
-
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
|
|
1916
|
-
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
|
1917
|
-
</svg>
|
|
1918
|
-
</div>
|
|
1919
|
-
<button class="tb-btn" disabled title="Voice">
|
|
1920
|
-
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
|
1921
|
-
<path d="M1 8h2M4 5v6M7 3v10M10 5v6M13 8h2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
|
1922
|
-
</svg>
|
|
1923
|
-
</button>
|
|
1924
|
-
<button class="send-btn" id="send-btn" disabled title="Send (Enter)">
|
|
1925
|
-
<svg width="13" height="13" viewBox="0 0 14 14" fill="none">
|
|
1926
|
-
<path d="M7 12V2M7 2L3 6M7 2L11 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1927
|
-
</svg>
|
|
1928
|
-
</button>
|
|
1929
|
-
</div>
|
|
1930
|
-
</div>
|
|
1931
|
-
</div>
|
|
1932
|
-
<div class="input-footer" id="input-footer">copyright ramsarjal</div>
|
|
1933
|
-
</div>
|
|
1934
|
-
|
|
1935
|
-
<div class="resize-handle resize-handle--bottom" id="resize-handle-bottom" title="Drag to resize plugin height"></div>
|
|
1936
|
-
</div>
|
|
1937
|
-
|
|
1938
|
-
<script>
|
|
1939
|
-
/* ── Helpers ──────────────────────────────────────────────────────────── */
|
|
1940
|
-
function uuid() {
|
|
1941
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
|
1942
|
-
var r = Math.random() * 16 | 0, v = c === "x" ? r : (r & 0x3 | 0x8);
|
|
1943
|
-
return v.toString(16);
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
var TOOL_LABELS = {
|
|
1948
|
-
// Vision / Audit
|
|
1949
|
-
figma_screen_cloner: "Clone screen",
|
|
1950
|
-
figma_visual_audit: "Visual audit",
|
|
1951
|
-
figma_a11y_audit: "Accessibility audit",
|
|
1952
|
-
figma_sketch_to_design: "Sketch → design",
|
|
1953
|
-
figma_design_from_ref: "Design from reference",
|
|
1954
|
-
// Accuracy / Layout
|
|
1955
|
-
figma_intent_translator: "Translate intent",
|
|
1956
|
-
figma_layout_intelligence: "Layout intelligence",
|
|
1957
|
-
figma_variant_expander: "Expand variants",
|
|
1958
|
-
figma_theme_generator: "Generate theme",
|
|
1959
|
-
figma_lint_rules: "Lint rules",
|
|
1960
|
-
figma_component_audit: "Component audit",
|
|
1961
|
-
figma_component_archaeologist: "Component archaeology",
|
|
1962
|
-
// Generation
|
|
1963
|
-
figma_page_architect: "Page architect",
|
|
1964
|
-
figma_generate_image_and_insert: "Generate image",
|
|
1965
|
-
figma_unsplash_search: "Image search",
|
|
1966
|
-
figma_url_to_frame: "URL → frame",
|
|
1967
|
-
figma_system_drift: "System drift",
|
|
1968
|
-
figma_prototype_map: "Prototype map",
|
|
1969
|
-
figma_animated_build: "Animated build",
|
|
1970
|
-
// Sync
|
|
1971
|
-
figma_animation_specifier: "Animation spec",
|
|
1972
|
-
figma_sync_from_code: "Sync from code",
|
|
1973
|
-
figma_webhook_listener: "Webhook listener",
|
|
1974
|
-
// Governance
|
|
1975
|
-
figma_design_system_scaffolder: "DS scaffolder",
|
|
1976
|
-
figma_design_system_primitives: "DS primitives",
|
|
1977
|
-
figma_design_system_variables: "DS variables",
|
|
1978
|
-
figma_token_naming_convention: "Token naming",
|
|
1979
|
-
figma_decision_log: "Decision log",
|
|
1980
|
-
figma_health_report: "Health report",
|
|
1981
|
-
figma_generate_spec: "Generate spec",
|
|
1982
|
-
figma_apg_doc: "APG doc",
|
|
1983
|
-
figma_token_migrate: "Token migrate",
|
|
1984
|
-
// Bridge / Execute
|
|
1985
|
-
figma_execute: "Execute code",
|
|
1986
|
-
figma_get_status: "Check status",
|
|
1987
|
-
figma_navigate: "Navigate",
|
|
1988
|
-
figma_get_selection: "Get selection",
|
|
1989
|
-
figma_take_screenshot: "Screenshot",
|
|
1990
|
-
figma_get_node: "Read node",
|
|
1991
|
-
figma_create_variable_collection: "Create var collection",
|
|
1992
|
-
figma_create_variable: "Create variable",
|
|
1993
|
-
figma_update_variable: "Update variable",
|
|
1994
|
-
figma_delete_variable: "Delete variable",
|
|
1995
|
-
figma_rename_variable: "Rename variable",
|
|
1996
|
-
figma_delete_variable_collection: "Delete var collection",
|
|
1997
|
-
figma_add_mode: "Add mode",
|
|
1998
|
-
figma_rename_mode: "Rename mode",
|
|
1999
|
-
figma_batch_create_variables: "Batch create variables",
|
|
2000
|
-
figma_batch_update_variables: "Batch update variables",
|
|
2001
|
-
figma_get_variables: "Get variables",
|
|
2002
|
-
figma_clone_node: "Clone node",
|
|
2003
|
-
figma_delete_node: "Delete node",
|
|
2004
|
-
figma_move_node: "Move node",
|
|
2005
|
-
figma_resize_node: "Resize node",
|
|
2006
|
-
figma_rename_node: "Rename node",
|
|
2007
|
-
figma_set_fills: "Set fills",
|
|
2008
|
-
figma_set_strokes: "Set strokes",
|
|
2009
|
-
figma_set_text: "Set text",
|
|
2010
|
-
figma_search_components: "Search components",
|
|
2011
|
-
figma_instantiate_component: "Use component",
|
|
2012
|
-
figma_set_description: "Set description",
|
|
2013
|
-
figma_get_styles: "Get styles",
|
|
2014
|
-
figma_create_child: "Create node",
|
|
2015
|
-
figma_create_frame: "Create frame",
|
|
2016
|
-
figma_get_pages: "Get pages",
|
|
2017
|
-
figma_create_page: "Create page",
|
|
2018
|
-
// Generic
|
|
2019
|
-
exec_command: "Run command",
|
|
2020
|
-
mcp_tool: "MCP tool",
|
|
2021
|
-
};
|
|
2022
|
-
|
|
2023
|
-
function toolLabel(name) {
|
|
2024
|
-
return TOOL_LABELS[name] || name.replace(/^figma_/, "").replace(/_/g, " ");
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
function useChip(el) {
|
|
2028
|
-
var t = el.textContent.trim();
|
|
2029
|
-
input.value = t;
|
|
2030
|
-
input.focus();
|
|
2031
|
-
autoResize();
|
|
2032
|
-
updateSend();
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
var PROVIDER_LOGOS = {
|
|
2036
|
-
claude: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"></path></svg>',
|
|
2037
|
-
gemini: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#gemini-fill-0)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#gemini-fill-1)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#gemini-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="gemini-fill-0" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="gemini-fill-1" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="gemini-fill-2" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>',
|
|
2038
|
-
openai: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19.503 0H4.496A4.496 4.496 0 000 4.496v15.007A4.496 4.496 0 004.496 24h15.007A4.496 4.496 0 0024 19.503V4.496A4.496 4.496 0 0019.503 0z" fill="#fff"></path><path d="M9.064 3.344a4.578 4.578 0 012.285-.312c1 .115 1.891.54 2.673 1.275.01.01.024.017.037.021a.09.09 0 00.043 0 4.55 4.55 0 013.046.275l.047.022.116.057a4.581 4.581 0 012.188 2.399c.209.51.313 1.041.315 1.595a4.24 4.24 0 01-.134 1.223.123.123 0 00.03.115c.594.607.988 1.33 1.183 2.17.289 1.425-.007 2.71-.887 3.854l-.136.166a4.548 4.548 0 01-2.201 1.388.123.123 0 00-.081.076c-.191.551-.383 1.023-.74 1.494-.9 1.187-2.222 1.846-3.711 1.838-1.187-.006-2.239-.44-3.157-1.302a.107.107 0 00-.105-.024c-.388.125-.78.143-1.204.138a4.441 4.441 0 01-1.945-.466 4.544 4.544 0 01-1.61-1.335c-.152-.202-.303-.392-.414-.617a5.81 5.81 0 01-.37-.961 4.582 4.582 0 01-.014-2.298.124.124 0 00.006-.056.085.085 0 00-.027-.048 4.467 4.467 0 01-1.034-1.651 3.896 3.896 0 01-.251-1.192 5.189 5.189 0 01.141-1.6c.337-1.112.982-1.985 1.933-2.618.212-.141.413-.251.601-.33.215-.089.43-.164.646-.227a.098.098 0 00.065-.066 4.51 4.51 0 01.829-1.615 4.535 4.535 0 011.837-1.388zm3.482 10.565a.637.637 0 000 1.272h3.636a.637.637 0 100-1.272h-3.636zM8.462 9.23a.637.637 0 00-1.106.631l1.272 2.224-1.266 2.136a.636.636 0 101.095.649l1.454-2.455a.636.636 0 00.005-.64L8.462 9.23z" fill="url(#codex-fill)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="codex-fill" x1="12" x2="12" y1="3" y2="21"><stop stop-color="#B1A7FF"></stop><stop offset=".5" stop-color="#7A9DFF"></stop><stop offset="1" stop-color="#3941FF"></stop></linearGradient></defs></svg>'
|
|
2039
|
-
};
|
|
2040
|
-
|
|
2041
|
-
var PLUGIN_SIZE_KEY = "figma-intelligence-plugin-height";
|
|
2042
|
-
var DEFAULT_PLUGIN_WIDTH = 320;
|
|
2043
|
-
var DEFAULT_PLUGIN_HEIGHT = 480;
|
|
2044
|
-
var MIN_PLUGIN_HEIGHT = 480;
|
|
2045
|
-
var MAX_PLUGIN_HEIGHT = 960;
|
|
2046
|
-
var pluginHeight = DEFAULT_PLUGIN_HEIGHT;
|
|
2047
|
-
var pluginResizeState = null;
|
|
2048
|
-
|
|
2049
|
-
function renderProviderLogos() {
|
|
2050
|
-
var claudeSlot = document.getElementById("claude-logo-slot");
|
|
2051
|
-
var codexSlot = document.getElementById("codex-logo-slot");
|
|
2052
|
-
var geminiSlot = document.getElementById("gemini-logo-slot");
|
|
2053
|
-
if (claudeSlot) claudeSlot.innerHTML = PROVIDER_LOGOS.claude;
|
|
2054
|
-
if (codexSlot) codexSlot.innerHTML = PROVIDER_LOGOS.openai;
|
|
2055
|
-
if (geminiSlot) geminiSlot.innerHTML = PROVIDER_LOGOS.gemini;
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
function clampPluginHeight(height) {
|
|
2059
|
-
return Math.max(MIN_PLUGIN_HEIGHT, Math.min(MAX_PLUGIN_HEIGHT, height));
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
function persistPluginHeight() {
|
|
2063
|
-
try { localStorage.setItem(PLUGIN_SIZE_KEY, String(pluginHeight)); } catch (e) {}
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
function requestPluginResize(height) {
|
|
2067
|
-
pluginHeight = clampPluginHeight(height);
|
|
2068
|
-
persistPluginHeight();
|
|
2069
|
-
parent.postMessage({
|
|
2070
|
-
pluginMessage: {
|
|
2071
|
-
type: "resize-ui",
|
|
2072
|
-
width: DEFAULT_PLUGIN_WIDTH,
|
|
2073
|
-
height: pluginHeight,
|
|
2074
|
-
},
|
|
2075
|
-
}, "*");
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
function initPluginSize() {
|
|
2079
|
-
var saved = null;
|
|
2080
|
-
try { saved = Number(localStorage.getItem(PLUGIN_SIZE_KEY)); } catch (e) {}
|
|
2081
|
-
if (saved && isFinite(saved)) {
|
|
2082
|
-
pluginHeight = clampPluginHeight(saved);
|
|
2083
|
-
}
|
|
2084
|
-
requestPluginResize(pluginHeight);
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
function beginPluginResize(edge, event) {
|
|
2088
|
-
if (!event) return;
|
|
2089
|
-
|
|
2090
|
-
pluginResizeState = {
|
|
2091
|
-
edge: edge,
|
|
2092
|
-
pointerId: event.pointerId,
|
|
2093
|
-
startY: event.clientY,
|
|
2094
|
-
startHeight: pluginHeight,
|
|
2095
|
-
handle: event.currentTarget,
|
|
2096
|
-
};
|
|
2097
|
-
|
|
2098
|
-
if (pluginResizeState.handle && pluginResizeState.handle.setPointerCapture) {
|
|
2099
|
-
pluginResizeState.handle.setPointerCapture(event.pointerId);
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
if (pluginResizeState.handle) {
|
|
2103
|
-
pluginResizeState.handle.classList.add("is-active");
|
|
2104
|
-
}
|
|
2105
|
-
document.body.classList.add("plugin-resizing");
|
|
2106
|
-
event.preventDefault();
|
|
2107
|
-
}
|
|
2108
|
-
|
|
2109
|
-
function updatePluginResize(event) {
|
|
2110
|
-
if (!pluginResizeState || event.pointerId !== pluginResizeState.pointerId) return;
|
|
2111
|
-
|
|
2112
|
-
var deltaY = event.clientY - pluginResizeState.startY;
|
|
2113
|
-
var nextHeight = pluginResizeState.edge === "top"
|
|
2114
|
-
? pluginResizeState.startHeight - deltaY
|
|
2115
|
-
: pluginResizeState.startHeight + deltaY;
|
|
2116
|
-
|
|
2117
|
-
requestPluginResize(nextHeight);
|
|
2118
|
-
event.preventDefault();
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
function endPluginResize(event) {
|
|
2122
|
-
if (!pluginResizeState) return;
|
|
2123
|
-
if (event && event.pointerId !== pluginResizeState.pointerId) return;
|
|
2124
|
-
|
|
2125
|
-
if (pluginResizeState.handle) {
|
|
2126
|
-
pluginResizeState.handle.classList.remove("is-active");
|
|
2127
|
-
if (
|
|
2128
|
-
event &&
|
|
2129
|
-
pluginResizeState.handle.releasePointerCapture &&
|
|
2130
|
-
pluginResizeState.handle.hasPointerCapture &&
|
|
2131
|
-
pluginResizeState.handle.hasPointerCapture(pluginResizeState.pointerId)
|
|
2132
|
-
) {
|
|
2133
|
-
pluginResizeState.handle.releasePointerCapture(pluginResizeState.pointerId);
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
pluginResizeState = null;
|
|
2138
|
-
document.body.classList.remove("plugin-resizing");
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
function initPluginResizeHandles() {
|
|
2142
|
-
["top", "bottom"].forEach(function(edge) {
|
|
2143
|
-
var handle = document.getElementById("resize-handle-" + edge);
|
|
2144
|
-
if (!handle) return;
|
|
2145
|
-
handle.addEventListener("pointerdown", function(event) {
|
|
2146
|
-
beginPluginResize(edge, event);
|
|
2147
|
-
});
|
|
2148
|
-
handle.addEventListener("pointermove", updatePluginResize);
|
|
2149
|
-
handle.addEventListener("pointerup", endPluginResize);
|
|
2150
|
-
handle.addEventListener("pointercancel", endPluginResize);
|
|
2151
|
-
});
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
/* ── Model switcher ───────────────────────────────────────────────────── */
|
|
2155
|
-
var PROVIDER_MODELS = {
|
|
2156
|
-
claude: [
|
|
2157
|
-
{ id: "claude-opus-4-6", label: "Opus 4", desc: "Most capable · default" },
|
|
2158
|
-
{ id: "claude-sonnet-4-6", label: "Sonnet 4", desc: "Faster · balanced" },
|
|
2159
|
-
{ id: "claude-haiku-4-5-20251001",label: "Haiku 4.5", desc: "Fastest · lightweight" },
|
|
2160
|
-
],
|
|
2161
|
-
openai: [
|
|
2162
|
-
{ id: "gpt-5.4", label: "GPT-5.4", desc: "Best quality" },
|
|
2163
|
-
{ id: "gpt-5-codex", label: "GPT-5-Codex", desc: "Best for coding-heavy tool use" },
|
|
2164
|
-
{ id: "gpt-5", label: "GPT-5", desc: "Stable general-purpose" },
|
|
2165
|
-
{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini", desc: "Faster" },
|
|
2166
|
-
],
|
|
2167
|
-
gemini: [
|
|
2168
|
-
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", desc: "Fast + capable (recommended)" },
|
|
2169
|
-
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro", desc: "Most capable (may hit rate limits)" },
|
|
2170
|
-
{ id: "gemini-2.0-flash", label: "Gemini 2.0 Flash", desc: "Balanced" },
|
|
2171
|
-
{ id: "gemini-1.5-flash-8b", label: "Gemini 1.5 Flash 8B", desc: "Fastest" },
|
|
2172
|
-
],
|
|
2173
|
-
bridge: [],
|
|
2174
|
-
stitch: [
|
|
2175
|
-
{ id: "experimental", label: "Experimental", desc: "Default generation mode" },
|
|
2176
|
-
],
|
|
2177
|
-
perplexity: [
|
|
2178
|
-
{ id: "sonar-pro", label: "Sonar Pro", desc: "Thorough research" },
|
|
2179
|
-
{ id: "sonar", label: "Sonar", desc: "Fast search answers" },
|
|
2180
|
-
{ id: "sonar-reasoning-pro", label: "Sonar Reasoning Pro", desc: "Best reasoning" },
|
|
2181
|
-
{ id: "sonar-reasoning", label: "Sonar Reasoning", desc: "Step-by-step analysis" },
|
|
2182
|
-
],
|
|
2183
|
-
};
|
|
2184
|
-
var selectedModelByProvider = {
|
|
2185
|
-
claude: "claude-opus-4-6",
|
|
2186
|
-
openai: "gpt-5.4",
|
|
2187
|
-
gemini: "gemini-2.5-flash",
|
|
2188
|
-
bridge: null,
|
|
2189
|
-
stitch: "experimental",
|
|
2190
|
-
perplexity: "sonar-pro",
|
|
2191
|
-
};
|
|
2192
|
-
var modelDropdownOpen = false;
|
|
2193
|
-
|
|
2194
|
-
function getActiveProvider() {
|
|
2195
|
-
return selectedLoginProvider || "claude";
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
function getModelsForProvider(provider) {
|
|
2199
|
-
return PROVIDER_MODELS[provider || getActiveProvider()] || PROVIDER_MODELS.claude;
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
function getSelectedModel(provider) {
|
|
2203
|
-
var activeProvider = provider || getActiveProvider();
|
|
2204
|
-
var models = getModelsForProvider(activeProvider);
|
|
2205
|
-
var selected = selectedModelByProvider[activeProvider];
|
|
2206
|
-
if (!models.length) return null;
|
|
2207
|
-
if (models.some(function(m) { return m.id === selected; })) return selected;
|
|
2208
|
-
selectedModelByProvider[activeProvider] = models[0].id;
|
|
2209
|
-
return models[0].id;
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
function syncModelBadge() {
|
|
2213
|
-
var badge = document.getElementById("model-badge");
|
|
2214
|
-
var label = document.getElementById("model-label");
|
|
2215
|
-
var provider = getActiveProvider();
|
|
2216
|
-
var models = getModelsForProvider(provider);
|
|
2217
|
-
if (!badge || !label) return;
|
|
2218
|
-
if (!models.length || provider === "bridge") {
|
|
2219
|
-
badge.style.display = "none";
|
|
2220
|
-
closeModelDropdown();
|
|
2221
|
-
return;
|
|
2222
|
-
}
|
|
2223
|
-
badge.style.display = "inline-flex";
|
|
2224
|
-
var selected = getSelectedModel(provider);
|
|
2225
|
-
var model = models.find(function(item) { return item.id === selected; });
|
|
2226
|
-
label.textContent = model ? model.label : selected;
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
function toggleModelDropdown(e) {
|
|
2230
|
-
e.stopPropagation();
|
|
2231
|
-
if (!getModelsForProvider(getActiveProvider()).length) return;
|
|
2232
|
-
if (modelDropdownOpen) { closeModelDropdown(); return; }
|
|
2233
|
-
modelDropdownOpen = true;
|
|
2234
|
-
var badge = document.getElementById("model-badge");
|
|
2235
|
-
var dd = document.createElement("div");
|
|
2236
|
-
dd.className = "model-dropdown";
|
|
2237
|
-
dd.id = "model-dropdown";
|
|
2238
|
-
dd.onclick = function(ev) { ev.stopPropagation(); };
|
|
2239
|
-
var provider = getActiveProvider();
|
|
2240
|
-
var models = getModelsForProvider(provider);
|
|
2241
|
-
var selectedModel = getSelectedModel(provider);
|
|
2242
|
-
models.forEach(function(m) {
|
|
2243
|
-
var item = document.createElement("div");
|
|
2244
|
-
item.className = "model-dropdown-item" + (m.id === selectedModel ? " active" : "");
|
|
2245
|
-
item.innerHTML =
|
|
2246
|
-
'<div>' + m.label + '<span class="model-desc">' + m.desc + '</span></div>' +
|
|
2247
|
-
'<span class="check">\u2713</span>';
|
|
2248
|
-
item.onclick = function() { selectModel(m.id); };
|
|
2249
|
-
dd.appendChild(item);
|
|
2250
|
-
});
|
|
2251
|
-
badge.appendChild(dd);
|
|
2252
|
-
setTimeout(function() { document.addEventListener("click", closeModelDropdown); }, 0);
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
function closeModelDropdown() {
|
|
2256
|
-
modelDropdownOpen = false;
|
|
2257
|
-
var dd = document.getElementById("model-dropdown");
|
|
2258
|
-
if (dd) dd.remove();
|
|
2259
|
-
document.removeEventListener("click", closeModelDropdown);
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
function selectModel(id) {
|
|
2263
|
-
selectedModelByProvider[getActiveProvider()] = id;
|
|
2264
|
-
syncModelBadge();
|
|
2265
|
-
closeModelDropdown();
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
/* ── Design System ──────────────────────────────────────────────────── */
|
|
2269
|
-
var activeDesignSystem = null;
|
|
2270
|
-
|
|
2271
|
-
var DESIGN_SYSTEMS_UI = [
|
|
2272
|
-
{ id: "mui", name: "Material UI", org: "Google", category: "Consumer", swatches: ["#1976D2","#9C27B0","#D32F2F","#2E7D32","#ED6C02"], font: "Roboto", radius: "4-16px" },
|
|
2273
|
-
{ id: "carbon", name: "IBM Carbon", org: "IBM", category: "Enterprise", swatches: ["#0F62FE","#DA1E28","#24A148","#F1C21B","#6929C4"], font: "IBM Plex Sans", radius: "0px" },
|
|
2274
|
-
{ id: "atlassian", name: "Atlassian DS", org: "Atlassian", category: "Enterprise", swatches: ["#0052CC","#DE350B","#00875A","#FF991F","#6554C0"], font: "Inter", radius: "3px" },
|
|
2275
|
-
{ id: "polaris", name: "Polaris", org: "Shopify", category: "E-commerce", swatches: ["#008060","#D72C0D","#FFC453","#5C6AC4","#47C1BF"], font: "Inter", radius: "8px" },
|
|
2276
|
-
{ id: "fluent", name: "Fluent UI", org: "Microsoft", category: "Enterprise", swatches: ["#0078D4","#D13438","#107C10","#FFB900","#8764B8"], font: "Segoe UI", radius: "4px" },
|
|
2277
|
-
{ id: "antd", name: "Ant Design", org: "Ant Group", category: "Enterprise", swatches: ["#1677FF","#FF4D4F","#52C41A","#FAAD14","#722ED1"], font: "system-ui", radius: "6px" },
|
|
2278
|
-
{ id: "shadcn", name: "shadcn/ui", org: "Community", category: "Developer", swatches: ["#18181B","#F4F4F5","#09090B","#A1A1AA","#E4E4E7"], font: "Inter / Geist", radius: "8px" },
|
|
2279
|
-
{ id: "primer", name: "Primer", org: "GitHub", category: "Developer", swatches: ["#0969DA","#CF222E","#1A7F37","#BF8700","#8250DF"], font: "system-ui", radius: "6px" },
|
|
2280
|
-
];
|
|
2281
|
-
var DS_CATEGORIES = ["All", "Enterprise", "Consumer", "E-commerce", "Developer"];
|
|
2282
|
-
var activeDsFilter = "All";
|
|
2283
|
-
|
|
2284
|
-
function openStyleGuideModal() {
|
|
2285
|
-
var overlay = document.getElementById("sg-overlay");
|
|
2286
|
-
activeDsFilter = "All";
|
|
2287
|
-
renderDsFilters();
|
|
2288
|
-
renderDsGrid();
|
|
2289
|
-
overlay.style.display = "flex";
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
function renderDsFilters() {
|
|
2293
|
-
var container = document.getElementById("ds-filters");
|
|
2294
|
-
container.innerHTML = "";
|
|
2295
|
-
DS_CATEGORIES.forEach(function(cat) {
|
|
2296
|
-
var btn = document.createElement("button");
|
|
2297
|
-
btn.className = "ds-filter-btn" + (activeDsFilter === cat ? " active" : "");
|
|
2298
|
-
btn.textContent = cat;
|
|
2299
|
-
btn.onclick = function() {
|
|
2300
|
-
activeDsFilter = cat;
|
|
2301
|
-
renderDsFilters();
|
|
2302
|
-
renderDsGrid();
|
|
2303
|
-
};
|
|
2304
|
-
container.appendChild(btn);
|
|
2305
|
-
});
|
|
2306
|
-
}
|
|
2307
|
-
|
|
2308
|
-
function renderDsGrid() {
|
|
2309
|
-
var grid = document.getElementById("sg-grid");
|
|
2310
|
-
grid.innerHTML = "";
|
|
2311
|
-
var filtered = activeDsFilter === "All"
|
|
2312
|
-
? DESIGN_SYSTEMS_UI
|
|
2313
|
-
: DESIGN_SYSTEMS_UI.filter(function(ds) { return ds.category === activeDsFilter; });
|
|
2314
|
-
|
|
2315
|
-
filtered.forEach(function(ds) {
|
|
2316
|
-
var card = document.createElement("div");
|
|
2317
|
-
card.className = "sg-card" + (activeDesignSystem && activeDesignSystem.id === ds.id ? " active" : "");
|
|
2318
|
-
card.onclick = function() { selectDesignSystem(ds); };
|
|
2319
|
-
|
|
2320
|
-
// Swatches
|
|
2321
|
-
var swatchRow = document.createElement("div");
|
|
2322
|
-
swatchRow.className = "ds-swatches";
|
|
2323
|
-
ds.swatches.forEach(function(color) {
|
|
2324
|
-
var dot = document.createElement("div");
|
|
2325
|
-
dot.className = "ds-swatch";
|
|
2326
|
-
dot.style.background = color;
|
|
2327
|
-
swatchRow.appendChild(dot);
|
|
2328
|
-
});
|
|
2329
|
-
card.appendChild(swatchRow);
|
|
2330
|
-
|
|
2331
|
-
// Check mark
|
|
2332
|
-
var check = document.createElement("div");
|
|
2333
|
-
check.className = "sg-active-check";
|
|
2334
|
-
check.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 5l2.5 2.5L8 3" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
2335
|
-
card.appendChild(check);
|
|
2336
|
-
|
|
2337
|
-
// Name
|
|
2338
|
-
var label = document.createElement("div");
|
|
2339
|
-
label.className = "sg-card-label";
|
|
2340
|
-
label.textContent = ds.name;
|
|
2341
|
-
card.appendChild(label);
|
|
2342
|
-
|
|
2343
|
-
// Org + font
|
|
2344
|
-
var org = document.createElement("div");
|
|
2345
|
-
org.className = "ds-org";
|
|
2346
|
-
org.textContent = ds.org + " \u00B7 " + ds.font;
|
|
2347
|
-
card.appendChild(org);
|
|
2348
|
-
|
|
2349
|
-
// Category badge
|
|
2350
|
-
var badge = document.createElement("span");
|
|
2351
|
-
badge.className = "ds-category-badge";
|
|
2352
|
-
badge.textContent = ds.category;
|
|
2353
|
-
card.appendChild(badge);
|
|
2354
|
-
|
|
2355
|
-
grid.appendChild(card);
|
|
2356
|
-
});
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
function closeStyleGuideModal() {
|
|
2360
|
-
var overlay = document.getElementById("sg-overlay");
|
|
2361
|
-
overlay.classList.add("hiding");
|
|
2362
|
-
setTimeout(function() {
|
|
2363
|
-
overlay.style.display = "none";
|
|
2364
|
-
overlay.classList.remove("hiding");
|
|
2365
|
-
}, 200);
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
function onSgOverlayClick(e) {
|
|
2369
|
-
if (e.target === document.getElementById("sg-overlay")) closeStyleGuideModal();
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
|
-
function selectDesignSystem(ds) {
|
|
2373
|
-
activeDesignSystem = ds;
|
|
2374
|
-
closeStyleGuideModal();
|
|
2375
|
-
renderDesignSystemChip();
|
|
2376
|
-
var btn = document.getElementById("style-guide-btn");
|
|
2377
|
-
if (btn) btn.classList.add("sg-btn-active");
|
|
2378
|
-
if (ws && ws.readyState === 1) {
|
|
2379
|
-
ws.send(JSON.stringify({ type: "set-design-system", designSystemId: ds.id }));
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
|
-
function clearDesignSystem() {
|
|
2384
|
-
activeDesignSystem = null;
|
|
2385
|
-
renderDesignSystemChip();
|
|
2386
|
-
var btn = document.getElementById("style-guide-btn");
|
|
2387
|
-
if (btn) btn.classList.remove("sg-btn-active");
|
|
2388
|
-
if (ws && ws.readyState === 1) {
|
|
2389
|
-
ws.send(JSON.stringify({ type: "set-design-system", designSystemId: null }));
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
function renderDesignSystemChip() {
|
|
2394
|
-
var container = document.getElementById("style-guide-chip-container");
|
|
2395
|
-
if (!activeDesignSystem) {
|
|
2396
|
-
container.innerHTML = "";
|
|
2397
|
-
return;
|
|
2398
|
-
}
|
|
2399
|
-
container.innerHTML = '<div class="style-guide-chip">' +
|
|
2400
|
-
'<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><circle cx="2.5" cy="2.5" r="1.5" fill="currentColor"/><circle cx="7.5" cy="2.5" r="1.5" fill="currentColor"/><circle cx="2.5" cy="7.5" r="1.5" fill="currentColor"/><circle cx="7.5" cy="7.5" r="1.5" fill="currentColor"/></svg>' +
|
|
2401
|
-
activeDesignSystem.name + ' <span style="opacity:0.6">' + activeDesignSystem.org + '</span>' +
|
|
2402
|
-
'<span class="style-guide-chip-x" onclick="clearDesignSystem()" title="Remove design system">\u00d7</span>' +
|
|
2403
|
-
'</div>';
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
/* ── File attachments ────────────────────────────────────────────────── */
|
|
2407
|
-
var attachedFiles = []; // { name, data (base64 or text), type, size }
|
|
2408
|
-
|
|
2409
|
-
function initFileAttach() {
|
|
2410
|
-
var attachBtn = document.getElementById("attach-btn");
|
|
2411
|
-
var fileInput = document.getElementById("file-input");
|
|
2412
|
-
attachBtn.onclick = function() { fileInput.click(); };
|
|
2413
|
-
fileInput.onchange = function() {
|
|
2414
|
-
var files = fileInput.files;
|
|
2415
|
-
for (var i = 0; i < files.length; i++) {
|
|
2416
|
-
readAttachedFile(files[i]);
|
|
2417
|
-
}
|
|
2418
|
-
fileInput.value = "";
|
|
2419
|
-
};
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
function readAttachedFile(file) {
|
|
2423
|
-
var isImage = file.type.startsWith("image/");
|
|
2424
|
-
var reader = new FileReader();
|
|
2425
|
-
reader.onload = function(e) {
|
|
2426
|
-
attachedFiles.push({
|
|
2427
|
-
name: file.name,
|
|
2428
|
-
data: e.target.result,
|
|
2429
|
-
type: file.type,
|
|
2430
|
-
size: file.size,
|
|
2431
|
-
isImage: isImage,
|
|
2432
|
-
});
|
|
2433
|
-
renderAttachedFiles();
|
|
2434
|
-
};
|
|
2435
|
-
if (isImage) {
|
|
2436
|
-
reader.readAsDataURL(file);
|
|
2437
|
-
} else {
|
|
2438
|
-
reader.readAsText(file);
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
function removeAttachedFile(index) {
|
|
2443
|
-
attachedFiles.splice(index, 1);
|
|
2444
|
-
renderAttachedFiles();
|
|
2445
|
-
}
|
|
2446
|
-
|
|
2447
|
-
function renderAttachedFiles() {
|
|
2448
|
-
var container = document.getElementById("attached-files");
|
|
2449
|
-
if (attachedFiles.length === 0) {
|
|
2450
|
-
container.style.display = "none";
|
|
2451
|
-
container.innerHTML = "";
|
|
2452
|
-
return;
|
|
2453
|
-
}
|
|
2454
|
-
container.style.display = "flex";
|
|
2455
|
-
container.innerHTML = "";
|
|
2456
|
-
attachedFiles.forEach(function(f, i) {
|
|
2457
|
-
var chip = document.createElement("div");
|
|
2458
|
-
chip.className = "file-chip";
|
|
2459
|
-
var icon = f.isImage ? "\ud83d\uddbc" : "\ud83d\udcc4";
|
|
2460
|
-
chip.innerHTML =
|
|
2461
|
-
'<span>' + icon + ' ' + escapeHtml(f.name) + '</span>' +
|
|
2462
|
-
'<span class="file-chip-x" onclick="removeAttachedFile(' + i + ')">\u00d7</span>';
|
|
2463
|
-
container.appendChild(chip);
|
|
2464
|
-
});
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
/* ── Knowledge Sources ───────────────────────────────────────────────── */
|
|
2468
|
-
var ksPanelOpen = false;
|
|
2469
|
-
var ksSources = []; // Array of { id, title, sourceCount, meta }
|
|
2470
|
-
var KS_STORAGE_KEY = "figma-intel-knowledge-sources";
|
|
2471
|
-
|
|
2472
|
-
function toggleKsPanel() {
|
|
2473
|
-
ksPanelOpen = !ksPanelOpen;
|
|
2474
|
-
var panel = document.getElementById("ks-panel");
|
|
2475
|
-
if (ksPanelOpen) {
|
|
2476
|
-
panel.classList.add("open");
|
|
2477
|
-
if (ws && ws.readyState === 1) {
|
|
2478
|
-
ws.send(JSON.stringify({ type: "list-content" }));
|
|
2479
|
-
// Auto-scan hub when opening on the hub tab
|
|
2480
|
-
if (ksActiveTab === "hub") {
|
|
2481
|
-
ws.send(JSON.stringify({ type: "hub-scan" }));
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
|
-
} else {
|
|
2485
|
-
panel.classList.remove("open");
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
|
|
2489
|
-
function closeKsPanel() {
|
|
2490
|
-
ksPanelOpen = false;
|
|
2491
|
-
var panel = document.getElementById("ks-panel");
|
|
2492
|
-
if (panel) panel.classList.remove("open");
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
var ksActiveTab = "hub";
|
|
2496
|
-
|
|
2497
|
-
function switchKsTab(tab) {
|
|
2498
|
-
ksActiveTab = tab;
|
|
2499
|
-
var tabs = ["hub", "file", "url", "text", "web"];
|
|
2500
|
-
tabs.forEach(function(t) {
|
|
2501
|
-
var tabEl = document.getElementById("ks-tab-" + t);
|
|
2502
|
-
var secEl = document.getElementById("ks-" + t + "-section");
|
|
2503
|
-
if (t === tab) {
|
|
2504
|
-
if (tabEl) { tabEl.style.background = "rgba(255,255,255,0.08)"; tabEl.style.color = "var(--text-primary)"; }
|
|
2505
|
-
if (secEl) secEl.style.display = "block";
|
|
2506
|
-
} else {
|
|
2507
|
-
if (tabEl) { tabEl.style.background = "transparent"; tabEl.style.color = "var(--text-muted)"; }
|
|
2508
|
-
if (secEl) secEl.style.display = "none";
|
|
2509
|
-
}
|
|
2510
|
-
});
|
|
2511
|
-
document.getElementById("ks-error").style.display = "none";
|
|
2512
|
-
// Auto-scan hub when switching to it
|
|
2513
|
-
if (tab === "hub" && ws && ws.readyState === 1) {
|
|
2514
|
-
ws.send(JSON.stringify({ type: "hub-scan" }));
|
|
2515
|
-
}
|
|
2516
|
-
// Auto-load reference sites when switching to web tab
|
|
2517
|
-
if (tab === "web" && ws && ws.readyState === 1) {
|
|
2518
|
-
ws.send(JSON.stringify({ type: "list-reference-sites" }));
|
|
2519
|
-
}
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
// ── Web Reference Sites ──
|
|
2523
|
-
var webReferenceSites = [];
|
|
2524
|
-
|
|
2525
|
-
function addReferenceSite() {
|
|
2526
|
-
var nameInput = document.getElementById("ks-web-name");
|
|
2527
|
-
var urlInput = document.getElementById("ks-web-url");
|
|
2528
|
-
var name = (nameInput.value || "").trim();
|
|
2529
|
-
var url = (urlInput.value || "").trim();
|
|
2530
|
-
if (!url) return;
|
|
2531
|
-
// Auto-generate name from domain if not provided
|
|
2532
|
-
if (!name) {
|
|
2533
|
-
try { name = new URL(url.startsWith("http") ? url : "https://" + url).hostname.replace(/^www\./, ""); } catch(e) { name = url; }
|
|
2534
|
-
}
|
|
2535
|
-
// Ensure URL has protocol
|
|
2536
|
-
var baseUrl = url.startsWith("http") ? url : "https://" + url;
|
|
2537
|
-
var searchDomain = url.replace(/^https?:\/\/(www\.)?/, "").split("/")[0];
|
|
2538
|
-
if (ws && ws.readyState === 1) {
|
|
2539
|
-
ws.send(JSON.stringify({ type: "add-reference-site", name: name, baseUrl: baseUrl, searchDomain: searchDomain }));
|
|
2540
|
-
}
|
|
2541
|
-
nameInput.value = "";
|
|
2542
|
-
urlInput.value = "";
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
function removeRefSite(id) {
|
|
2546
|
-
if (ws && ws.readyState === 1) {
|
|
2547
|
-
ws.send(JSON.stringify({ type: "remove-reference-site", id: id }));
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
|
|
2551
|
-
function renderWebRefList(sites) {
|
|
2552
|
-
webReferenceSites = sites || [];
|
|
2553
|
-
var container = document.getElementById("ks-web-list");
|
|
2554
|
-
if (!container) return;
|
|
2555
|
-
if (webReferenceSites.length === 0) {
|
|
2556
|
-
container.innerHTML = '<div style="font-size:10.5px;color:var(--text-ghost);padding:8px 0">No reference sites configured. Add a site above to enable web search in chat mode.</div>';
|
|
2557
|
-
return;
|
|
2558
|
-
}
|
|
2559
|
-
container.innerHTML = webReferenceSites.map(function(site) {
|
|
2560
|
-
return '<div style="display:flex;align-items:center;justify-content:space-between;padding:6px 8px;background:rgba(255,255,255,0.04);border-radius:6px;margin-bottom:4px">' +
|
|
2561
|
-
'<div style="flex:1;min-width:0">' +
|
|
2562
|
-
'<div style="font-size:11px;font-weight:500;color:var(--text-primary)">' + escapeHtml(site.name) + '</div>' +
|
|
2563
|
-
'<div style="font-size:10px;color:var(--text-ghost)">' + escapeHtml(site.searchDomain) + '</div>' +
|
|
2564
|
-
'</div>' +
|
|
2565
|
-
'<button onclick="removeRefSite(\'' + site.id + '\')" style="background:none;border:none;color:var(--text-ghost);cursor:pointer;padding:2px 4px;font-size:12px" title="Remove">×</button>' +
|
|
2566
|
-
'</div>';
|
|
2567
|
-
}).join("");
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
|
-
// ── Knowledge Hub ──
|
|
2571
|
-
var hubCatalog = [];
|
|
2572
|
-
var hubLoadedFiles = new Set(); // fileNames currently loaded as sources
|
|
2573
|
-
|
|
2574
|
-
function renderHubCatalog(files) {
|
|
2575
|
-
hubCatalog = files || [];
|
|
2576
|
-
var list = document.getElementById("ks-hub-list");
|
|
2577
|
-
if (!list) return;
|
|
2578
|
-
|
|
2579
|
-
if (hubCatalog.length === 0) {
|
|
2580
|
-
list.innerHTML = '<div class="ks-empty" style="padding:8px 0">' +
|
|
2581
|
-
'No files in the knowledge hub yet.<br>' +
|
|
2582
|
-
'<span style="font-size:9px;color:var(--text-ghost)">Add PDFs, DOCX, or TXT files to:<br>figma-bridge-plugin/knowledge-hub/</span>' +
|
|
2583
|
-
'</div>';
|
|
2584
|
-
return;
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
var html = "";
|
|
2588
|
-
hubCatalog.forEach(function(f) {
|
|
2589
|
-
var icon = "\ud83d\udcc4"; // 📄
|
|
2590
|
-
if (f.fileType === "pdf") icon = "\ud83d\udcc4";
|
|
2591
|
-
else if (f.fileType === "docx" || f.fileType === "doc") icon = "\ud83d\uddd2";
|
|
2592
|
-
else if (f.fileType === "md") icon = "\ud83d\udcdd";
|
|
2593
|
-
else if (f.fileType === "txt") icon = "\ud83d\udcc3";
|
|
2594
|
-
|
|
2595
|
-
var isLoaded = hubLoadedFiles.has(f.fileName);
|
|
2596
|
-
var sizeStr = f.sizeBytes >= 1048576 ? (f.sizeBytes / 1048576).toFixed(1) + " MB"
|
|
2597
|
-
: f.sizeBytes >= 1024 ? (f.sizeBytes / 1024).toFixed(0) + " KB"
|
|
2598
|
-
: f.sizeBytes + " B";
|
|
2599
|
-
var metaParts = [f.fileType.toUpperCase(), sizeStr];
|
|
2600
|
-
if (f.charCount) metaParts.push(formatCharCount(f.charCount));
|
|
2601
|
-
|
|
2602
|
-
html += '<div class="ks-hub-item' + (isLoaded ? "" : "") + '" onclick="loadHubFile(\'' + escapeHtml(f.fileName).replace(/'/g, "\\'") + '\')" id="hub-' + escapeHtml(f.fileName).replace(/[^a-zA-Z0-9]/g, "_") + '">' +
|
|
2603
|
-
'<span class="ks-hub-icon">' + icon + '</span>' +
|
|
2604
|
-
'<div class="ks-hub-info">' +
|
|
2605
|
-
'<div class="ks-hub-name">' + escapeHtml(f.title) + '</div>' +
|
|
2606
|
-
'<div class="ks-hub-meta">' + metaParts.join(" \u00b7 ") + '</div>' +
|
|
2607
|
-
'</div>' +
|
|
2608
|
-
(isLoaded ? '<span class="ks-hub-loaded">Active</span>' : '') +
|
|
2609
|
-
'</div>';
|
|
2610
|
-
});
|
|
2611
|
-
list.innerHTML = html;
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
function loadHubFile(fileName) {
|
|
2615
|
-
if (hubLoadedFiles.has(fileName)) return; // already loaded
|
|
2616
|
-
var itemId = "hub-" + fileName.replace(/[^a-zA-Z0-9]/g, "_");
|
|
2617
|
-
var el = document.getElementById(itemId);
|
|
2618
|
-
if (el) el.classList.add("loading");
|
|
2619
|
-
|
|
2620
|
-
if (ws && ws.readyState === 1) {
|
|
2621
|
-
ws.send(JSON.stringify({ type: "hub-load", fileName: fileName }));
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
|
-
|
|
2625
|
-
function updateHubLoadedState() {
|
|
2626
|
-
// Mark which hub files are currently active as knowledge sources
|
|
2627
|
-
hubLoadedFiles.clear();
|
|
2628
|
-
ksSources.forEach(function(s) {
|
|
2629
|
-
if (s.meta && s.meta.hubFile && s.meta.fileName) {
|
|
2630
|
-
hubLoadedFiles.add(s.meta.fileName);
|
|
2631
|
-
}
|
|
2632
|
-
});
|
|
2633
|
-
// Re-render if hub tab is active
|
|
2634
|
-
if (ksActiveTab === "hub" && hubCatalog.length > 0) {
|
|
2635
|
-
renderHubCatalog(hubCatalog);
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
// ── File Upload ──
|
|
2640
|
-
document.addEventListener("DOMContentLoaded", function() {
|
|
2641
|
-
var fileInput = document.getElementById("ks-file-input");
|
|
2642
|
-
if (fileInput) {
|
|
2643
|
-
fileInput.addEventListener("change", function() {
|
|
2644
|
-
var file = fileInput.files[0];
|
|
2645
|
-
if (!file) return;
|
|
2646
|
-
var errEl = document.getElementById("ks-error");
|
|
2647
|
-
errEl.style.display = "none";
|
|
2648
|
-
var nameEl = document.getElementById("ks-file-name");
|
|
2649
|
-
nameEl.textContent = "Reading: " + file.name + "...";
|
|
2650
|
-
nameEl.style.display = "block";
|
|
2651
|
-
var btn = document.getElementById("ks-file-btn");
|
|
2652
|
-
btn.disabled = true;
|
|
2653
|
-
btn.textContent = "Uploading...";
|
|
2654
|
-
|
|
2655
|
-
// Read as base64 DataURL (works for binary PDFs/DOCX)
|
|
2656
|
-
var reader = new FileReader();
|
|
2657
|
-
reader.onload = function(e) {
|
|
2658
|
-
if (ws && ws.readyState === 1) {
|
|
2659
|
-
nameEl.textContent = "\u23f3 Extracting text from: " + file.name + "...";
|
|
2660
|
-
btn.textContent = "Processing...";
|
|
2661
|
-
ws.send(JSON.stringify({
|
|
2662
|
-
type: "add-content-file",
|
|
2663
|
-
name: file.name,
|
|
2664
|
-
data: e.target.result, // base64 DataURL
|
|
2665
|
-
fileType: file.type,
|
|
2666
|
-
}));
|
|
2667
|
-
}
|
|
2668
|
-
// Fallback timeout in case relay never responds
|
|
2669
|
-
setTimeout(function() {
|
|
2670
|
-
if (btn.disabled) {
|
|
2671
|
-
btn.disabled = false;
|
|
2672
|
-
btn.textContent = "Choose PDF, DOCX, TXT, or MD file...";
|
|
2673
|
-
nameEl.style.display = "none";
|
|
2674
|
-
}
|
|
2675
|
-
}, 30000);
|
|
2676
|
-
};
|
|
2677
|
-
reader.onerror = function() {
|
|
2678
|
-
errEl.textContent = "Failed to read file";
|
|
2679
|
-
errEl.style.display = "block";
|
|
2680
|
-
btn.disabled = false;
|
|
2681
|
-
btn.textContent = "Choose PDF, DOCX, TXT, or MD file...";
|
|
2682
|
-
nameEl.style.display = "none";
|
|
2683
|
-
};
|
|
2684
|
-
reader.readAsDataURL(file);
|
|
2685
|
-
fileInput.value = "";
|
|
2686
|
-
});
|
|
2687
|
-
}
|
|
2688
|
-
});
|
|
2689
|
-
|
|
2690
|
-
// ── URL Add ──
|
|
2691
|
-
function addKsUrl() {
|
|
2692
|
-
var input = document.getElementById("ks-url-input");
|
|
2693
|
-
var url = (input.value || "").trim();
|
|
2694
|
-
if (!url) return;
|
|
2695
|
-
var errEl = document.getElementById("ks-error");
|
|
2696
|
-
errEl.style.display = "none";
|
|
2697
|
-
|
|
2698
|
-
if (!/^https?:\/\//i.test(url)) {
|
|
2699
|
-
errEl.textContent = "Enter a valid URL starting with http:// or https://";
|
|
2700
|
-
errEl.style.display = "block";
|
|
2701
|
-
return;
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
if (ws && ws.readyState === 1) {
|
|
2705
|
-
ws.send(JSON.stringify({ type: "add-content-url", url: url }));
|
|
2706
|
-
input.value = "";
|
|
2707
|
-
var btn = document.getElementById("ks-url-btn");
|
|
2708
|
-
btn.disabled = true;
|
|
2709
|
-
btn.textContent = "Fetching...";
|
|
2710
|
-
setTimeout(function() { btn.disabled = false; btn.textContent = "Add"; }, 15000);
|
|
2711
|
-
}
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
// ── Text Paste ──
|
|
2715
|
-
function addKsText() {
|
|
2716
|
-
var titleInput = document.getElementById("ks-paste-title");
|
|
2717
|
-
var contentInput = document.getElementById("ks-paste-content");
|
|
2718
|
-
var title = (titleInput.value || "").trim();
|
|
2719
|
-
var content = (contentInput.value || "").trim();
|
|
2720
|
-
var errEl = document.getElementById("ks-error");
|
|
2721
|
-
|
|
2722
|
-
if (!content) {
|
|
2723
|
-
errEl.textContent = "Paste some content to use as a knowledge source.";
|
|
2724
|
-
errEl.style.display = "block";
|
|
2725
|
-
return;
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
errEl.style.display = "none";
|
|
2729
|
-
if (ws && ws.readyState === 1) {
|
|
2730
|
-
ws.send(JSON.stringify({ type: "add-content-text", title: title || "Pasted Content", content: content }));
|
|
2731
|
-
titleInput.value = "";
|
|
2732
|
-
contentInput.value = "";
|
|
2733
|
-
var btn = document.getElementById("ks-paste-btn");
|
|
2734
|
-
btn.disabled = true;
|
|
2735
|
-
btn.textContent = "Adding...";
|
|
2736
|
-
setTimeout(function() { btn.disabled = false; btn.textContent = "Add Content"; }, 5000);
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
function removeKsSource(id) {
|
|
2741
|
-
if (ws && ws.readyState === 1) {
|
|
2742
|
-
ws.send(JSON.stringify({ type: "remove-content", sourceId: id }));
|
|
2743
|
-
}
|
|
2744
|
-
ksSources = ksSources.filter(function(s) { return s.id !== id; });
|
|
2745
|
-
renderKsList();
|
|
2746
|
-
updateKsBadge();
|
|
2747
|
-
updateHubLoadedState();
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
function ksTypeIcon(meta) {
|
|
2751
|
-
if (!meta) return "\ud83d\udcc4"; // 📄
|
|
2752
|
-
var t = meta.fileType || "";
|
|
2753
|
-
if (t === "pdf") return "\ud83d\udcc4"; // 📄
|
|
2754
|
-
if (t === "docx" || t === "doc") return "\ud83d\uddd2"; // 🗒
|
|
2755
|
-
if (t === "url") return "\ud83c\udf10"; // 🌐
|
|
2756
|
-
if (t === "text") return "\ud83d\udcdd"; // 📝
|
|
2757
|
-
return "\ud83d\udcc4"; // 📄
|
|
2758
|
-
}
|
|
2759
|
-
|
|
2760
|
-
function toggleKsPreview(id) {
|
|
2761
|
-
var card = document.getElementById("ksc-" + id);
|
|
2762
|
-
if (card) card.classList.toggle("expanded");
|
|
2763
|
-
}
|
|
2764
|
-
|
|
2765
|
-
function formatCharCount(n) {
|
|
2766
|
-
if (n >= 1000000) return (n / 1000000).toFixed(1) + "M chars";
|
|
2767
|
-
if (n >= 1000) return (n / 1000).toFixed(1) + "K chars";
|
|
2768
|
-
return n + " chars";
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
|
-
function renderKsList() {
|
|
2772
|
-
var list = document.getElementById("ks-list");
|
|
2773
|
-
if (!list) return;
|
|
2774
|
-
if (ksSources.length === 0) {
|
|
2775
|
-
list.innerHTML = '<div class="ks-empty">No sources added. Upload a file, paste a URL, or paste text.</div>';
|
|
2776
|
-
return;
|
|
2777
|
-
}
|
|
2778
|
-
var html = "";
|
|
2779
|
-
ksSources.forEach(function(s) {
|
|
2780
|
-
var icon = ksTypeIcon(s.meta);
|
|
2781
|
-
var metaParts = [];
|
|
2782
|
-
if (s.meta) {
|
|
2783
|
-
if (s.meta.fileType === "pdf") metaParts.push("PDF");
|
|
2784
|
-
else if (s.meta.fileType === "docx" || s.meta.fileType === "doc") metaParts.push("DOCX");
|
|
2785
|
-
else if (s.meta.fileType === "url") metaParts.push("Web page");
|
|
2786
|
-
else if (s.meta.fileType === "text") metaParts.push("Pasted text");
|
|
2787
|
-
else if (s.meta.fileType) metaParts.push(s.meta.fileType.toUpperCase());
|
|
2788
|
-
if (s.meta.pages) metaParts.push(s.meta.pages + " pages");
|
|
2789
|
-
}
|
|
2790
|
-
if (s.charCount) metaParts.push(formatCharCount(s.charCount));
|
|
2791
|
-
metaParts.push("Ready");
|
|
2792
|
-
var metaStr = metaParts.join(" \u00b7 ");
|
|
2793
|
-
|
|
2794
|
-
var previewHtml = "";
|
|
2795
|
-
if (s.preview) {
|
|
2796
|
-
previewHtml =
|
|
2797
|
-
'<span class="ks-card-toggle" onclick="event.stopPropagation();toggleKsPreview(\'' + s.id + '\')">\u25b6 preview</span>' +
|
|
2798
|
-
'<div class="ks-card-preview">' + escapeHtml(s.preview) + (s.charCount > 500 ? "..." : "") + '</div>';
|
|
2799
|
-
}
|
|
2800
|
-
|
|
2801
|
-
html += '<div class="ks-card" id="ksc-' + s.id + '" onclick="toggleKsPreview(\'' + s.id + '\')" style="cursor:pointer;flex-direction:column;align-items:stretch">' +
|
|
2802
|
-
'<div style="display:flex;align-items:center;justify-content:space-between">' +
|
|
2803
|
-
'<div style="display:flex;align-items:center;gap:6px;min-width:0">' +
|
|
2804
|
-
'<span style="font-size:16px;flex-shrink:0">' + icon + '</span>' +
|
|
2805
|
-
'<div class="ks-card-info">' +
|
|
2806
|
-
'<div class="ks-card-title">' + escapeHtml(s.title) + '</div>' +
|
|
2807
|
-
'<div class="ks-card-meta">' + metaStr + previewHtml + '</div>' +
|
|
2808
|
-
'</div>' +
|
|
2809
|
-
'</div>' +
|
|
2810
|
-
'<button class="ks-card-remove" onclick="event.stopPropagation();removeKsSource(\'' + s.id + '\')" title="Remove">\u00d7</button>' +
|
|
2811
|
-
'</div>' +
|
|
2812
|
-
'</div>';
|
|
2813
|
-
});
|
|
2814
|
-
list.innerHTML = html;
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
|
-
function updateKsBadge() {
|
|
2818
|
-
var badge = document.getElementById("ks-badge");
|
|
2819
|
-
var btn = document.getElementById("ks-btn");
|
|
2820
|
-
if (ksSources.length > 0) {
|
|
2821
|
-
badge.textContent = ksSources.length;
|
|
2822
|
-
badge.style.display = "flex";
|
|
2823
|
-
if (btn) btn.style.color = "var(--accent)";
|
|
2824
|
-
} else {
|
|
2825
|
-
badge.style.display = "none";
|
|
2826
|
-
if (btn) btn.style.color = "";
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
|
|
2830
|
-
function resetKsButtons() {
|
|
2831
|
-
var b1 = document.getElementById("ks-url-btn");
|
|
2832
|
-
if (b1) { b1.disabled = false; b1.textContent = "Add"; }
|
|
2833
|
-
var b2 = document.getElementById("ks-paste-btn");
|
|
2834
|
-
if (b2) { b2.disabled = false; b2.textContent = "Add Content"; }
|
|
2835
|
-
var b3 = document.getElementById("ks-file-btn");
|
|
2836
|
-
if (b3) { b3.disabled = false; b3.textContent = "Choose PDF, DOCX, TXT, or MD file..."; }
|
|
2837
|
-
var fn = document.getElementById("ks-file-name");
|
|
2838
|
-
if (fn) fn.style.display = "none";
|
|
2839
|
-
}
|
|
2840
|
-
|
|
2841
|
-
function showKsSuccess(text) {
|
|
2842
|
-
var el = document.getElementById("ks-success");
|
|
2843
|
-
if (el) {
|
|
2844
|
-
el.textContent = text;
|
|
2845
|
-
el.style.display = "block";
|
|
2846
|
-
setTimeout(function() { el.style.display = "none"; }, 4000);
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
|
-
function handleKsMessage(msg) {
|
|
2851
|
-
if (msg.type === "content-added") {
|
|
2852
|
-
var s = msg.source;
|
|
2853
|
-
var exists = ksSources.some(function(x) { return x.id === s.id; });
|
|
2854
|
-
if (!exists) ksSources.push(s);
|
|
2855
|
-
else ksSources = ksSources.map(function(x) { return x.id === s.id ? s : x; });
|
|
2856
|
-
renderKsList();
|
|
2857
|
-
updateKsBadge();
|
|
2858
|
-
updateHubLoadedState();
|
|
2859
|
-
resetKsButtons();
|
|
2860
|
-
var errEl = document.getElementById("ks-error");
|
|
2861
|
-
if (errEl) errEl.style.display = "none";
|
|
2862
|
-
// Show clear success feedback
|
|
2863
|
-
var metaStr = "";
|
|
2864
|
-
if (s.meta && s.meta.fileType === "pdf" && s.meta.pages) metaStr = " (" + s.meta.pages + " pages)";
|
|
2865
|
-
else if (s.meta && s.meta.fileType === "url") metaStr = " (web page)";
|
|
2866
|
-
showKsSuccess("\u2705 Added: \"" + s.title + "\"" + metaStr + " \u2014 AI can now answer from this source");
|
|
2867
|
-
}
|
|
2868
|
-
if (msg.type === "content-removed") {
|
|
2869
|
-
ksSources = ksSources.filter(function(x) { return x.id !== msg.sourceId; });
|
|
2870
|
-
renderKsList();
|
|
2871
|
-
updateKsBadge();
|
|
2872
|
-
}
|
|
2873
|
-
if (msg.type === "content-error") {
|
|
2874
|
-
var errEl = document.getElementById("ks-error");
|
|
2875
|
-
if (errEl) {
|
|
2876
|
-
errEl.textContent = msg.error || "Failed to process content";
|
|
2877
|
-
errEl.style.display = "block";
|
|
2878
|
-
}
|
|
2879
|
-
var sucEl = document.getElementById("ks-success");
|
|
2880
|
-
if (sucEl) sucEl.style.display = "none";
|
|
2881
|
-
resetKsButtons();
|
|
2882
|
-
}
|
|
2883
|
-
if (msg.type === "content-list") {
|
|
2884
|
-
ksSources = msg.sources || [];
|
|
2885
|
-
renderKsList();
|
|
2886
|
-
updateKsBadge();
|
|
2887
|
-
}
|
|
2888
|
-
}
|
|
2889
|
-
|
|
2890
|
-
// Enter key in URL input
|
|
2891
|
-
document.addEventListener("DOMContentLoaded", function() {
|
|
2892
|
-
var urlInput = document.getElementById("ks-url-input");
|
|
2893
|
-
if (urlInput) {
|
|
2894
|
-
urlInput.addEventListener("keydown", function(e) {
|
|
2895
|
-
if (e.key === "Enter") { e.preventDefault(); addKsUrl(); }
|
|
2896
|
-
});
|
|
2897
|
-
}
|
|
2898
|
-
});
|
|
2899
|
-
|
|
2900
|
-
// Close panel when clicking outside
|
|
2901
|
-
document.addEventListener("click", function(e) {
|
|
2902
|
-
if (!ksPanelOpen) return;
|
|
2903
|
-
var panel = document.getElementById("ks-panel");
|
|
2904
|
-
var btn = document.getElementById("ks-btn");
|
|
2905
|
-
var wrap = btn ? btn.parentElement : null;
|
|
2906
|
-
if (panel && !panel.contains(e.target) && wrap && !wrap.contains(e.target)) {
|
|
2907
|
-
closeKsPanel();
|
|
2908
|
-
}
|
|
2909
|
-
});
|
|
2910
|
-
|
|
2911
|
-
/* ── Provider / Login Screen ─────────────────────────────────────────── */
|
|
2912
|
-
var selectedLoginProvider = null;
|
|
2913
|
-
var loginApiKey = null;
|
|
2914
|
-
var loginDismissed = false;
|
|
2915
|
-
var geminiCliLoggedIn = false; // set from bridge-status message
|
|
2916
|
-
|
|
2917
|
-
var PROVIDER_META = {
|
|
2918
|
-
claude: { label: "Claude", dotClass: "claude", btnText: "Continue with Claude" },
|
|
2919
|
-
openai: { label: "OpenAI", dotClass: "openai", btnText: "Continue with OpenAI" },
|
|
2920
|
-
gemini: { label: "Gemini", dotClass: "gemini", btnText: "Continue with Gemini" },
|
|
2921
|
-
bridge: { label: "Bridge", dotClass: "bridge", btnText: "Activate Bridge Mode" },
|
|
2922
|
-
perplexity: { label: "Perplexity", dotClass: "perplexity", btnText: "Continue with Perplexity" },
|
|
2923
|
-
stitch: { label: "Stitch", dotClass: "stitch", btnText: "Continue with Stitch" },
|
|
2924
|
-
};
|
|
2925
|
-
|
|
2926
|
-
var PROVIDER_HINTS = {
|
|
2927
|
-
claude: 'Uses your Claude Code subscription. Setup prepares it up front so switching does not require restarting the relay.',
|
|
2928
|
-
openai: 'Uses the OpenAI account currently signed in to Codex. Setup also registers Codex for MCP so switching stays restart-free.',
|
|
2929
|
-
gemini: '',
|
|
2930
|
-
bridge: 'Your AI tool (VS Code Copilot, Cursor, etc.) controls Figma via MCP.',
|
|
2931
|
-
perplexity: 'Uses your Perplexity API key for research-focused conversations. Get a key at perplexity.ai/settings.',
|
|
2932
|
-
stitch: 'Uses Google Stitch to generate UI screens from prompts. Requires an OAuth2 access token — run: npx @anthropic-ai/stitch-auth or get one at stitch.withgoogle.com → Settings.',
|
|
2933
|
-
};
|
|
2934
|
-
|
|
2935
|
-
var PROVIDER_API_LINKS = {
|
|
2936
|
-
openai: "Get key at platform.openai.com",
|
|
2937
|
-
gemini: "Get key at aistudio.google.com",
|
|
2938
|
-
perplexity: "Get key at perplexity.ai/settings",
|
|
2939
|
-
stitch: "Get token at stitch.withgoogle.com",
|
|
2940
|
-
};
|
|
2941
|
-
|
|
2942
|
-
function geminiNeedsApiKey() {
|
|
2943
|
-
// API key is only required when the Gemini CLI is not authenticated
|
|
2944
|
-
return !geminiCliLoggedIn;
|
|
2945
|
-
}
|
|
2946
|
-
|
|
2947
|
-
function selectLoginProvider(id) {
|
|
2948
|
-
selectedLoginProvider = id;
|
|
2949
|
-
["claude","openai","gemini","perplexity","stitch","bridge"].forEach(function(p) {
|
|
2950
|
-
var card = document.getElementById("pc-" + p);
|
|
2951
|
-
if (card) card.className = "provider-card" + (p === id ? " selected" : "");
|
|
2952
|
-
});
|
|
2953
|
-
|
|
2954
|
-
var apiExpand = document.getElementById("login-api-expand");
|
|
2955
|
-
var bridgeExpand = document.getElementById("login-bridge-expand");
|
|
2956
|
-
var projectExpand = document.getElementById("login-project-expand");
|
|
2957
|
-
var stitchAuthDiv = document.getElementById("login-stitch-auth");
|
|
2958
|
-
var apiInput = document.getElementById("login-api-key");
|
|
2959
|
-
|
|
2960
|
-
// Hide stitch auth div by default (shown only for stitch)
|
|
2961
|
-
if (stitchAuthDiv) stitchAuthDiv.style.display = "none";
|
|
2962
|
-
var apiLink = document.getElementById("login-api-link");
|
|
2963
|
-
|
|
2964
|
-
if (id === "gemini" && geminiNeedsApiKey()) {
|
|
2965
|
-
apiExpand.style.display = "block";
|
|
2966
|
-
bridgeExpand.style.display = "none";
|
|
2967
|
-
if (projectExpand) projectExpand.style.display = "none";
|
|
2968
|
-
apiInput.placeholder = "AIza\u2026 (Google AI Studio key)";
|
|
2969
|
-
if (apiLink) apiLink.textContent = PROVIDER_API_LINKS[id] || "";
|
|
2970
|
-
} else if (id === "perplexity") {
|
|
2971
|
-
apiExpand.style.display = "block";
|
|
2972
|
-
bridgeExpand.style.display = "none";
|
|
2973
|
-
if (projectExpand) projectExpand.style.display = "none";
|
|
2974
|
-
apiInput.placeholder = "pplx-\u2026 (Perplexity API key)";
|
|
2975
|
-
if (apiLink) apiLink.textContent = PROVIDER_API_LINKS[id] || "";
|
|
2976
|
-
} else if (id === "stitch") {
|
|
2977
|
-
apiExpand.style.display = "none";
|
|
2978
|
-
bridgeExpand.style.display = "none";
|
|
2979
|
-
if (projectExpand) projectExpand.style.display = "block";
|
|
2980
|
-
// Show Google Sign-In button instead of API key field
|
|
2981
|
-
var stitchAuthDiv = document.getElementById("login-stitch-auth");
|
|
2982
|
-
if (stitchAuthDiv) stitchAuthDiv.style.display = "block";
|
|
2983
|
-
} else if (id === "bridge") {
|
|
2984
|
-
apiExpand.style.display = "none";
|
|
2985
|
-
bridgeExpand.style.display = "block";
|
|
2986
|
-
if (projectExpand) projectExpand.style.display = "none";
|
|
2987
|
-
} else {
|
|
2988
|
-
apiExpand.style.display = "none";
|
|
2989
|
-
bridgeExpand.style.display = "none";
|
|
2990
|
-
if (projectExpand) projectExpand.style.display = "none";
|
|
2991
|
-
}
|
|
2992
|
-
|
|
2993
|
-
var hint = document.getElementById("login-hint");
|
|
2994
|
-
if (hint) hint.textContent = PROVIDER_HINTS[id] || "";
|
|
2995
|
-
|
|
2996
|
-
updateLoginContinue();
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
function updateLoginContinue() {
|
|
3000
|
-
var btn = document.getElementById("login-continue-btn");
|
|
3001
|
-
if (!btn) return;
|
|
3002
|
-
if (!selectedLoginProvider) { btn.disabled = true; return; }
|
|
3003
|
-
|
|
3004
|
-
var needsKey = (selectedLoginProvider === "gemini" && geminiNeedsApiKey()) || selectedLoginProvider === "perplexity";
|
|
3005
|
-
var keyVal = needsKey ? (document.getElementById("login-api-key").value || "").trim() : "";
|
|
3006
|
-
// Stitch: enabled when Google OAuth is done
|
|
3007
|
-
if (selectedLoginProvider === "stitch") {
|
|
3008
|
-
btn.disabled = !stitchGoogleAuthed;
|
|
3009
|
-
} else {
|
|
3010
|
-
btn.disabled = needsKey && keyVal.length < 8;
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
|
-
var meta = PROVIDER_META[selectedLoginProvider];
|
|
3014
|
-
if (meta) btn.textContent = meta.btnText;
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
var loginProjectId = null;
|
|
3018
|
-
var stitchGoogleAuthed = false;
|
|
3019
|
-
|
|
3020
|
-
function startStitchGoogleAuth() {
|
|
3021
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
3022
|
-
var statusEl = document.getElementById("stitch-auth-status");
|
|
3023
|
-
var btn = document.getElementById("stitch-google-signin-btn");
|
|
3024
|
-
if (statusEl) statusEl.textContent = "Opening Google sign-in...";
|
|
3025
|
-
if (btn) { btn.disabled = true; btn.style.opacity = "0.6"; }
|
|
3026
|
-
ws.send(JSON.stringify({ type: "stitch-auth" }));
|
|
3027
|
-
}
|
|
3028
|
-
|
|
3029
|
-
function continueWithProvider() {
|
|
3030
|
-
if (!selectedLoginProvider) return;
|
|
3031
|
-
var needsKey = (selectedLoginProvider === "gemini" && geminiNeedsApiKey()) || selectedLoginProvider === "perplexity";
|
|
3032
|
-
// Stitch uses Google OAuth — no manual key needed
|
|
3033
|
-
loginApiKey = needsKey ? (document.getElementById("login-api-key").value || "").trim() : null;
|
|
3034
|
-
loginProjectId = selectedLoginProvider === "stitch" ? (document.getElementById("login-project-id").value || "").trim() : null;
|
|
3035
|
-
|
|
3036
|
-
try {
|
|
3037
|
-
localStorage.setItem("figma-intelligence-provider", selectedLoginProvider);
|
|
3038
|
-
// Persist a flag if an API key was provided, so we can skip login screen on reload
|
|
3039
|
-
if (loginApiKey) {
|
|
3040
|
-
localStorage.setItem("figma-intelligence-has-key", "1");
|
|
3041
|
-
}
|
|
3042
|
-
} catch(e) {}
|
|
3043
|
-
|
|
3044
|
-
hideLoginScreen();
|
|
3045
|
-
updateProviderBadge();
|
|
3046
|
-
updateModeTabsVisibility();
|
|
3047
|
-
// Force chat mode for Perplexity (research-only, no Code mode)
|
|
3048
|
-
if (selectedLoginProvider === "perplexity" && activeMode !== "chat") {
|
|
3049
|
-
switchMode("chat");
|
|
3050
|
-
}
|
|
3051
|
-
// Force code mode for Stitch (UI generation, not chat)
|
|
3052
|
-
if (selectedLoginProvider === "stitch" && activeMode === "chat") {
|
|
3053
|
-
switchMode("code");
|
|
3054
|
-
}
|
|
3055
|
-
// Set Perplexity-specific placeholder
|
|
3056
|
-
if (selectedLoginProvider === "perplexity" && input) {
|
|
3057
|
-
input.placeholder = "Research any topic...";
|
|
3058
|
-
}
|
|
3059
|
-
// Set Stitch-specific placeholder
|
|
3060
|
-
if (selectedLoginProvider === "stitch" && input) {
|
|
3061
|
-
input.placeholder = "Describe the UI screen to generate...";
|
|
3062
|
-
}
|
|
3063
|
-
renderThreadForProvider(selectedLoginProvider);
|
|
3064
|
-
updateStatus();
|
|
3065
|
-
syncResearcherToggle();
|
|
3066
|
-
|
|
3067
|
-
if (ws && ws.readyState === WebSocket.OPEN) sendProviderToRelay();
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
function sendProviderToRelay() {
|
|
3071
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
3072
|
-
ws.send(JSON.stringify({
|
|
3073
|
-
type: "set-provider",
|
|
3074
|
-
provider: selectedLoginProvider,
|
|
3075
|
-
apiKey: loginApiKey || null,
|
|
3076
|
-
projectId: loginProjectId || null,
|
|
3077
|
-
}));
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
function hideLoginScreen() {
|
|
3081
|
-
var screen = document.getElementById("login-screen");
|
|
3082
|
-
if (!screen) return;
|
|
3083
|
-
loginDismissed = true;
|
|
3084
|
-
screen.classList.add("hiding");
|
|
3085
|
-
setTimeout(function() { screen.style.display = "none"; }, 260);
|
|
3086
|
-
}
|
|
3087
|
-
|
|
3088
|
-
function showLoginScreen() {
|
|
3089
|
-
var screen = document.getElementById("login-screen");
|
|
3090
|
-
if (!screen) return;
|
|
3091
|
-
loginDismissed = false;
|
|
3092
|
-
screen.style.display = "flex";
|
|
3093
|
-
screen.classList.remove("hiding");
|
|
3094
|
-
if (selectedLoginProvider) selectLoginProvider(selectedLoginProvider);
|
|
3095
|
-
}
|
|
3096
|
-
|
|
3097
|
-
function updateProviderBadge() {
|
|
3098
|
-
var badge = document.getElementById("provider-badge");
|
|
3099
|
-
if (!badge || !selectedLoginProvider) return;
|
|
3100
|
-
var meta = PROVIDER_META[selectedLoginProvider];
|
|
3101
|
-
badge.style.display = "flex";
|
|
3102
|
-
badge.innerHTML =
|
|
3103
|
-
'<div class="prov-dot ' + meta.dotClass + '"></div>' +
|
|
3104
|
-
'<span>' + meta.label + '</span>';
|
|
3105
|
-
syncModelBadge();
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
function initLoginScreen() {
|
|
3109
|
-
var saved = null;
|
|
3110
|
-
var hasKey = false;
|
|
3111
|
-
try {
|
|
3112
|
-
saved = localStorage.getItem("figma-intelligence-provider");
|
|
3113
|
-
hasKey = localStorage.getItem("figma-intelligence-has-key") === "1";
|
|
3114
|
-
} catch(e) {}
|
|
3115
|
-
|
|
3116
|
-
if (saved && PROVIDER_META[saved]) {
|
|
3117
|
-
selectedLoginProvider = saved;
|
|
3118
|
-
selectLoginProvider(saved);
|
|
3119
|
-
// Skip login screen when:
|
|
3120
|
-
// - Subscription providers (Claude, OpenAI, Bridge)
|
|
3121
|
-
// - Gemini with CLI auth
|
|
3122
|
-
// - API-key providers (Stitch, Perplexity) with a previously stored key
|
|
3123
|
-
var isApiKeyProvider = saved === "stitch" || saved === "perplexity" || (saved === "gemini" && !geminiCliLoggedIn);
|
|
3124
|
-
if (saved === "claude" || saved === "openai" || saved === "bridge" || (saved === "gemini" && geminiCliLoggedIn) || (isApiKeyProvider && hasKey)) {
|
|
3125
|
-
hideLoginScreen();
|
|
3126
|
-
updateProviderBadge();
|
|
3127
|
-
if (saved === "stitch" && activeMode === "chat") switchMode("code");
|
|
3128
|
-
if (saved === "perplexity" && activeMode !== "chat") switchMode("chat");
|
|
3129
|
-
if (saved === "stitch" && input) input.placeholder = "Describe the UI screen to generate...";
|
|
3130
|
-
if (saved === "perplexity" && input) input.placeholder = "Research any topic...";
|
|
3131
|
-
} else {
|
|
3132
|
-
showLoginScreen();
|
|
3133
|
-
}
|
|
3134
|
-
} else {
|
|
3135
|
-
// First run — show login screen, default select Claude
|
|
3136
|
-
selectLoginProvider("claude");
|
|
3137
|
-
showLoginScreen();
|
|
3138
|
-
}
|
|
3139
|
-
syncModelBadge();
|
|
3140
|
-
updateModeTabsVisibility();
|
|
3141
|
-
renderThreadForProvider(selectedLoginProvider || "claude");
|
|
3142
|
-
|
|
3143
|
-
// Apply chat mode UI if restored
|
|
3144
|
-
if (activeMode === "chat") {
|
|
3145
|
-
var dsBtn = document.getElementById("style-guide-btn");
|
|
3146
|
-
if (dsBtn) dsBtn.style.display = "none";
|
|
3147
|
-
if (input) input.placeholder = "Ask me anything...";
|
|
3148
|
-
}
|
|
3149
|
-
}
|
|
3150
|
-
|
|
3151
|
-
function isBridgeMode() {
|
|
3152
|
-
return selectedLoginProvider === "bridge";
|
|
3153
|
-
}
|
|
3154
|
-
|
|
3155
|
-
/* ── Mode tabs (Chat / Code / Design+Code) ────────────────────────────── */
|
|
3156
|
-
var activeMode = "code";
|
|
3157
|
-
var MODE_STORAGE_KEY = "figma-intelligence-mode";
|
|
3158
|
-
try {
|
|
3159
|
-
var savedMode = localStorage.getItem(MODE_STORAGE_KEY);
|
|
3160
|
-
if (savedMode === "chat" || savedMode === "code" || savedMode === "dual") activeMode = savedMode;
|
|
3161
|
-
} catch(e) {}
|
|
3162
|
-
|
|
3163
|
-
// Apply initial tab state
|
|
3164
|
-
(function() {
|
|
3165
|
-
var codeTab = document.getElementById("mode-tab-code");
|
|
3166
|
-
var chatTab = document.getElementById("mode-tab-chat");
|
|
3167
|
-
var dualTab = document.getElementById("mode-tab-dual");
|
|
3168
|
-
if (codeTab) codeTab.className = "mode-tab" + (activeMode === "code" ? " active" : "");
|
|
3169
|
-
if (chatTab) chatTab.className = "mode-tab" + (activeMode === "chat" ? " active" : "");
|
|
3170
|
-
if (dualTab) dualTab.className = "mode-tab" + (activeMode === "dual" ? " active" : "");
|
|
3171
|
-
})();
|
|
3172
|
-
|
|
3173
|
-
window.switchMode = function(mode) {
|
|
3174
|
-
if (mode === activeMode) return;
|
|
3175
|
-
if (isStreaming) return; // prevent switching mid-stream
|
|
3176
|
-
|
|
3177
|
-
activeMode = mode;
|
|
3178
|
-
try { localStorage.setItem(MODE_STORAGE_KEY, mode); } catch(e) {}
|
|
3179
|
-
|
|
3180
|
-
// Update tab UI
|
|
3181
|
-
document.getElementById("mode-tab-code").className = "mode-tab" + (mode === "code" ? " active" : "");
|
|
3182
|
-
document.getElementById("mode-tab-chat").className = "mode-tab" + (mode === "chat" ? " active" : "");
|
|
3183
|
-
document.getElementById("mode-tab-dual").className = "mode-tab" + (mode === "dual" ? " active" : "");
|
|
3184
|
-
|
|
3185
|
-
// Swap home state template based on mode
|
|
3186
|
-
homeStateTemplate = mode === "chat" ? chatHomeTemplate : codeHomeTemplate;
|
|
3187
|
-
|
|
3188
|
-
// Re-render thread for current provider + new mode
|
|
3189
|
-
renderThreadForProvider(getActiveProvider());
|
|
3190
|
-
|
|
3191
|
-
// Update placeholder text
|
|
3192
|
-
if (input) {
|
|
3193
|
-
if (selectedLoginProvider === "perplexity") {
|
|
3194
|
-
input.placeholder = "Research any topic...";
|
|
3195
|
-
} else if (mode === "chat") {
|
|
3196
|
-
input.placeholder = "Ask me anything...";
|
|
3197
|
-
} else if (mode === "dual") {
|
|
3198
|
-
input.placeholder = "Describe a component — generates design + code...";
|
|
3199
|
-
} else {
|
|
3200
|
-
input.placeholder = "How can I help you today?";
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
|
|
3204
|
-
// Hide design-system picker in chat mode
|
|
3205
|
-
var dsBtn = document.getElementById("style-guide-btn");
|
|
3206
|
-
if (dsBtn) dsBtn.style.display = (mode === "chat") ? "none" : "";
|
|
3207
|
-
|
|
3208
|
-
// Show VS Code connection indicator for dual mode
|
|
3209
|
-
updateVscodeStatus();
|
|
3210
|
-
|
|
3211
|
-
// Sync researcher toggle visibility on mode change
|
|
3212
|
-
syncResearcherToggle();
|
|
3213
|
-
};
|
|
3214
|
-
|
|
3215
|
-
/* ── UX Researcher Mode ──────────────────────────────────────────────── */
|
|
3216
|
-
var isUxResearcherMode = false;
|
|
3217
|
-
var UX_RESEARCHER_KEY = "figma-intelligence:researcher-mode";
|
|
3218
|
-
try {
|
|
3219
|
-
if (localStorage.getItem(UX_RESEARCHER_KEY) === "1") isUxResearcherMode = true;
|
|
3220
|
-
} catch(e) {}
|
|
3221
|
-
|
|
3222
|
-
window.toggleUxResearcher = function() {
|
|
3223
|
-
isUxResearcherMode = !isUxResearcherMode;
|
|
3224
|
-
try { localStorage.setItem(UX_RESEARCHER_KEY, isUxResearcherMode ? "1" : "0"); } catch(e) {}
|
|
3225
|
-
syncResearcherToggle();
|
|
3226
|
-
};
|
|
3227
|
-
|
|
3228
|
-
function syncResearcherToggle() {
|
|
3229
|
-
var el = document.getElementById("ux-researcher-toggle");
|
|
3230
|
-
if (!el) return;
|
|
3231
|
-
|
|
3232
|
-
// Only show when provider is Claude and mode is Chat
|
|
3233
|
-
var show = (getActiveProvider() === "claude") && (activeMode === "chat");
|
|
3234
|
-
el.style.display = show ? "" : "none";
|
|
3235
|
-
|
|
3236
|
-
if (show) {
|
|
3237
|
-
el.className = "ux-researcher-toggle" + (isUxResearcherMode ? " active" : "");
|
|
3238
|
-
}
|
|
3239
|
-
|
|
3240
|
-
// Update input placeholder
|
|
3241
|
-
if (input) {
|
|
3242
|
-
if (show && isUxResearcherMode) {
|
|
3243
|
-
input.placeholder = "Ask the UX researcher\u2026";
|
|
3244
|
-
} else if (activeMode === "chat") {
|
|
3245
|
-
input.placeholder = "Ask me anything...";
|
|
3246
|
-
}
|
|
3247
|
-
}
|
|
3248
|
-
}
|
|
3249
|
-
|
|
3250
|
-
// Initial sync on load
|
|
3251
|
-
setTimeout(syncResearcherToggle, 0);
|
|
3252
|
-
|
|
3253
|
-
function isChatOnlyProvider() {
|
|
3254
|
-
return selectedLoginProvider === "bridge" || selectedLoginProvider === "perplexity";
|
|
3255
|
-
}
|
|
3256
|
-
|
|
3257
|
-
function isSingleModeProvider() {
|
|
3258
|
-
return isChatOnlyProvider() || selectedLoginProvider === "stitch";
|
|
3259
|
-
}
|
|
3260
|
-
|
|
3261
|
-
function updateModeTabsVisibility() {
|
|
3262
|
-
var modeTabs = document.getElementById("mode-tabs");
|
|
3263
|
-
if (modeTabs) modeTabs.style.display = isSingleModeProvider() ? "none" : "flex";
|
|
3264
|
-
// Hide dual tab for providers that don't support it
|
|
3265
|
-
var dualTab = document.getElementById("mode-tab-dual");
|
|
3266
|
-
if (dualTab) dualTab.style.display = isSingleModeProvider() ? "none" : "";
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
// VS Code connection status for dual mode
|
|
3270
|
-
var isVscodeConnected = false;
|
|
3271
|
-
var hasFastChat = false;
|
|
3272
|
-
|
|
3273
|
-
function updateFastChatIndicator() {
|
|
3274
|
-
var chatTab = document.getElementById("mode-tab-chat");
|
|
3275
|
-
if (!chatTab) return;
|
|
3276
|
-
var svg = chatTab.querySelector("svg");
|
|
3277
|
-
var isFast = hasFastChat || webReferenceSites.length > 0 || ksSources.length > 0;
|
|
3278
|
-
// Update text after the SVG
|
|
3279
|
-
var nodes = chatTab.childNodes;
|
|
3280
|
-
for (var ni = 0; ni < nodes.length; ni++) {
|
|
3281
|
-
if (nodes[ni].nodeType === 3 && nodes[ni].textContent.trim()) {
|
|
3282
|
-
nodes[ni].textContent = isFast ? "\n \u26a1 Chat\n " : "\n Chat\n ";
|
|
3283
|
-
break;
|
|
3284
|
-
}
|
|
3285
|
-
}
|
|
3286
|
-
}
|
|
3287
|
-
|
|
3288
|
-
function updateVscodeStatus() {
|
|
3289
|
-
var strip = document.getElementById("vscode-status");
|
|
3290
|
-
if (!strip) return;
|
|
3291
|
-
if (activeMode === "dual") {
|
|
3292
|
-
strip.style.display = "";
|
|
3293
|
-
strip.textContent = isVscodeConnected
|
|
3294
|
-
? "VS Code connected — code files will be written to workspace"
|
|
3295
|
-
: "VS Code extension not connected — code will appear in chat only";
|
|
3296
|
-
strip.className = "auth-strip" + (isVscodeConnected ? " ok" : "");
|
|
3297
|
-
} else {
|
|
3298
|
-
strip.style.display = "none";
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
|
-
/* ── State ────────────────────────────────────────────────────────────── */
|
|
3303
|
-
var ws = null;
|
|
3304
|
-
var reconnectTimer = null;
|
|
3305
|
-
var isPluginConnected = false;
|
|
3306
|
-
var isMcpConnected = false;
|
|
3307
|
-
var isClaudeLoggedIn = false;
|
|
3308
|
-
var claudeEmail = null;
|
|
3309
|
-
var isOpenAILoggedIn = false;
|
|
3310
|
-
var openaiEmail = null;
|
|
3311
|
-
var isStreaming = false;
|
|
3312
|
-
var currentRequestId = null;
|
|
3313
|
-
var currentRequestProvider = null;
|
|
3314
|
-
var currentAssistantRawText = "";
|
|
3315
|
-
var THREAD_STORAGE_KEY = "figma-intelligence-threads-v1";
|
|
3316
|
-
var MAX_CONVERSATION_MESSAGES = 12;
|
|
3317
|
-
var chatThreads = loadChatThreads();
|
|
3318
|
-
|
|
3319
|
-
// Current assistant message being built
|
|
3320
|
-
var currentMsgEl = null;
|
|
3321
|
-
var currentTextEl = null;
|
|
3322
|
-
var currentActivityEl = null;
|
|
3323
|
-
var currentToolStepsEl = null;
|
|
3324
|
-
var currentToolPills = {};
|
|
3325
|
-
var currentToolsUsed = []; // ordered list of tools called this turn
|
|
3326
|
-
var pendingCitations = null; // Perplexity source URLs
|
|
3327
|
-
|
|
3328
|
-
/* ── UI refs ──────────────────────────────────────────────────────────── */
|
|
3329
|
-
var thread = document.getElementById("thread");
|
|
3330
|
-
var homeState = document.getElementById("home-state");
|
|
3331
|
-
var input = document.getElementById("input");
|
|
3332
|
-
var sendBtn = document.getElementById("send-btn");
|
|
3333
|
-
var connDot = document.getElementById("conn-dot");
|
|
3334
|
-
var connText = document.getElementById("conn-text");
|
|
3335
|
-
var authStrip = document.getElementById("auth-strip");
|
|
3336
|
-
var authText = document.getElementById("auth-text");
|
|
3337
|
-
var footer = document.getElementById("input-footer");
|
|
3338
|
-
var codeHomeTemplate = document.getElementById("home-state").outerHTML;
|
|
3339
|
-
var chatHomeTemplate = document.getElementById("chat-home").outerHTML.replace('id="chat-home"', 'id="home-state"').replace('style="display:none"', '');
|
|
3340
|
-
var homeStateTemplate = activeMode === "chat" ? chatHomeTemplate : codeHomeTemplate;
|
|
3341
|
-
var bridgeHomeTemplate = document.getElementById("bridge-home").outerHTML;
|
|
3342
|
-
// Remove the hidden chat-home div from the DOM (it was only needed for template capture)
|
|
3343
|
-
var chatHomeEl = document.getElementById("chat-home");
|
|
3344
|
-
if (chatHomeEl) chatHomeEl.parentNode.removeChild(chatHomeEl);
|
|
3345
|
-
|
|
3346
|
-
/* ── Setup Guide ─────────────────────────────────────────────────────── */
|
|
3347
|
-
var setupGuide = document.getElementById("setup-guide");
|
|
3348
|
-
var setupGuideShown = false;
|
|
3349
|
-
var connectionAttempts = 0;
|
|
3350
|
-
var SETUP_GUIDE_THRESHOLD = 3; // Show after 3 failed connection attempts
|
|
3351
|
-
|
|
3352
|
-
function showSetupGuide() {
|
|
3353
|
-
if (setupGuideShown) return;
|
|
3354
|
-
setupGuideShown = true;
|
|
3355
|
-
setupGuide.classList.remove("hidden");
|
|
3356
|
-
}
|
|
3357
|
-
|
|
3358
|
-
function hideSetupGuide() {
|
|
3359
|
-
if (!setupGuideShown) return;
|
|
3360
|
-
setupGuideShown = false;
|
|
3361
|
-
setupGuide.classList.add("hidden");
|
|
3362
|
-
}
|
|
3363
|
-
|
|
3364
|
-
window.copySetupCmd = function() {
|
|
3365
|
-
var cmd = "npx @sarjallab09/figma-intelligence setup";
|
|
3366
|
-
if (navigator.clipboard) {
|
|
3367
|
-
navigator.clipboard.writeText(cmd);
|
|
3368
|
-
} else {
|
|
3369
|
-
var ta = document.createElement("textarea");
|
|
3370
|
-
ta.value = cmd;
|
|
3371
|
-
document.body.appendChild(ta);
|
|
3372
|
-
ta.select();
|
|
3373
|
-
document.execCommand("copy");
|
|
3374
|
-
document.body.removeChild(ta);
|
|
3375
|
-
}
|
|
3376
|
-
var el = document.getElementById("setup-cmd");
|
|
3377
|
-
el.classList.add("copied");
|
|
3378
|
-
setTimeout(function() { el.classList.remove("copied"); }, 2000);
|
|
3379
|
-
};
|
|
3380
|
-
|
|
3381
|
-
function loadChatThreads() {
|
|
3382
|
-
var defaults = {
|
|
3383
|
-
claude_code: [], claude_chat: [],
|
|
3384
|
-
openai_code: [], openai_chat: [],
|
|
3385
|
-
gemini_code: [], gemini_chat: [],
|
|
3386
|
-
perplexity_chat: [],
|
|
3387
|
-
bridge: []
|
|
3388
|
-
};
|
|
3389
|
-
try {
|
|
3390
|
-
var raw = localStorage.getItem(THREAD_STORAGE_KEY);
|
|
3391
|
-
if (!raw) return defaults;
|
|
3392
|
-
var parsed = JSON.parse(raw);
|
|
3393
|
-
|
|
3394
|
-
// Migrate old format: { claude: [], openai: [], gemini: [] } → { claude_code: [], ... }
|
|
3395
|
-
var oldProviders = ["claude", "openai", "gemini"];
|
|
3396
|
-
oldProviders.forEach(function(p) {
|
|
3397
|
-
if (Array.isArray(parsed[p]) && parsed[p].length > 0 && !parsed[p + "_code"]) {
|
|
3398
|
-
parsed[p + "_code"] = parsed[p];
|
|
3399
|
-
}
|
|
3400
|
-
delete parsed[p];
|
|
3401
|
-
});
|
|
3402
|
-
|
|
3403
|
-
Object.keys(defaults).forEach(function(key) {
|
|
3404
|
-
if (!Array.isArray(parsed[key])) parsed[key] = [];
|
|
3405
|
-
});
|
|
3406
|
-
return parsed;
|
|
3407
|
-
} catch (e) {
|
|
3408
|
-
return defaults;
|
|
3409
|
-
}
|
|
3410
|
-
}
|
|
3411
|
-
|
|
3412
|
-
function saveChatThreads() {
|
|
3413
|
-
try {
|
|
3414
|
-
localStorage.setItem(THREAD_STORAGE_KEY, JSON.stringify(chatThreads));
|
|
3415
|
-
} catch (e) {}
|
|
3416
|
-
}
|
|
3417
|
-
|
|
3418
|
-
function getThreadKey(provider) {
|
|
3419
|
-
var p = provider || getActiveProvider();
|
|
3420
|
-
if (p === "bridge") return "bridge";
|
|
3421
|
-
if (p === "perplexity") return "perplexity_chat";
|
|
3422
|
-
return p + "_" + activeMode;
|
|
3423
|
-
}
|
|
3424
|
-
|
|
3425
|
-
function getThreadEntries(provider) {
|
|
3426
|
-
var key = getThreadKey(provider);
|
|
3427
|
-
if (!Array.isArray(chatThreads[key])) chatThreads[key] = [];
|
|
3428
|
-
return chatThreads[key];
|
|
3429
|
-
}
|
|
3430
|
-
|
|
3431
|
-
function pushThreadEntry(provider, entry) {
|
|
3432
|
-
getThreadEntries(provider).push(entry);
|
|
3433
|
-
saveChatThreads();
|
|
3434
|
-
}
|
|
3435
|
-
|
|
3436
|
-
/* ── Connection status ────────────────────────────────────────────────── */
|
|
3437
|
-
function updateStatus() {
|
|
3438
|
-
var bridge = isBridgeMode();
|
|
3439
|
-
var prov = selectedLoginProvider || "claude";
|
|
3440
|
-
var online = isPluginConnected && isMcpConnected;
|
|
3441
|
-
|
|
3442
|
-
connDot.className = "conn-dot" + (isPluginConnected ? (online ? " online" : " offline") : "");
|
|
3443
|
-
|
|
3444
|
-
if (bridge) {
|
|
3445
|
-
connText.textContent = !isPluginConnected ? "Offline" : (isMcpConnected ? "Tool connected" : "Bridge active");
|
|
3446
|
-
} else {
|
|
3447
|
-
connText.textContent = !isPluginConnected ? "Offline" : (isMcpConnected ? "Connected" : "Relay only");
|
|
3448
|
-
}
|
|
3449
|
-
|
|
3450
|
-
// Auth strip
|
|
3451
|
-
if (!isPluginConnected) {
|
|
3452
|
-
authStrip.className = "auth-strip";
|
|
3453
|
-
authText.innerHTML = 'Server offline — run <code style="color:rgba(218,119,86,0.8);font-size:10px">npx @sarjallab09/figma-intelligence start</code> in terminal';
|
|
3454
|
-
} else if (bridge) {
|
|
3455
|
-
authStrip.className = "auth-strip ok";
|
|
3456
|
-
authText.innerHTML = '✓ MCP bridge active · <span style="color:rgba(160,132,232,0.8)">ws://localhost:9001</span>';
|
|
3457
|
-
} else if (prov === "openai") {
|
|
3458
|
-
authStrip.className = isOpenAILoggedIn ? "auth-strip ok" : "auth-strip";
|
|
3459
|
-
authText.innerHTML = isOpenAILoggedIn
|
|
3460
|
-
? '✓ OpenAI Codex subscription active' +
|
|
3461
|
-
(openaiEmail ? ' · <span class="auth-email">' + openaiEmail + '</span>' : '')
|
|
3462
|
-
: '<span style="color:rgba(218,119,86,0.75)">Run <code style="font-size:10px">codex login</code> to use your current Codex account. No relay restart needed after login.</span>';
|
|
3463
|
-
} else if (prov === "gemini") {
|
|
3464
|
-
var geminiReady = geminiCliLoggedIn || !!loginApiKey;
|
|
3465
|
-
authStrip.className = geminiReady ? "auth-strip ok" : "auth-strip";
|
|
3466
|
-
authText.innerHTML = geminiCliLoggedIn
|
|
3467
|
-
? '✓ Gemini subscription active · Google One AI Premium'
|
|
3468
|
-
: loginApiKey
|
|
3469
|
-
? '✓ Gemini connected · API key'
|
|
3470
|
-
: '<span style="color:rgba(218,119,86,0.75)">Gemini not configured — <span style="cursor:pointer;text-decoration:underline" onclick="showLoginScreen()">set up subscription or API key</span></span>';
|
|
3471
|
-
} else if (isClaudeLoggedIn) {
|
|
3472
|
-
authStrip.className = "auth-strip ok";
|
|
3473
|
-
authText.innerHTML = '✓ Claude subscription active' +
|
|
3474
|
-
(claudeEmail ? ' · <span class="auth-email">' + claudeEmail + '</span>' : '');
|
|
3475
|
-
} else {
|
|
3476
|
-
authStrip.className = "auth-strip";
|
|
3477
|
-
authText.innerHTML = '<span style="color:rgba(218,119,86,0.75)">Run <code style="font-size:10px">claude login</code> to authenticate. No relay restart needed after login.</span>';
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
|
-
// Show bridge home / normal home
|
|
3481
|
-
var bridgeHomeEl = document.getElementById("bridge-home");
|
|
3482
|
-
if (bridgeHomeEl) bridgeHomeEl.style.display = bridge ? "" : "none";
|
|
3483
|
-
if (homeState) homeState.style.display = bridge ? "none" : "";
|
|
3484
|
-
|
|
3485
|
-
// Input area visibility
|
|
3486
|
-
var inputAreaEl = document.querySelector(".input-area");
|
|
3487
|
-
if (inputAreaEl) inputAreaEl.style.display = bridge ? "none" : "";
|
|
3488
|
-
|
|
3489
|
-
// Can we chat?
|
|
3490
|
-
var canChat;
|
|
3491
|
-
if (!isPluginConnected || isStreaming || bridge) {
|
|
3492
|
-
canChat = false;
|
|
3493
|
-
} else if (prov === "openai") {
|
|
3494
|
-
canChat = isOpenAILoggedIn;
|
|
3495
|
-
} else if (prov === "gemini") {
|
|
3496
|
-
canChat = geminiCliLoggedIn || !!loginApiKey;
|
|
3497
|
-
} else {
|
|
3498
|
-
canChat = isClaudeLoggedIn;
|
|
3499
|
-
}
|
|
3500
|
-
|
|
3501
|
-
input.disabled = !canChat;
|
|
3502
|
-
updateSend();
|
|
3503
|
-
footer.textContent = "copyright ramsarjal";
|
|
3504
|
-
}
|
|
3505
|
-
|
|
3506
|
-
function updateSend() {
|
|
3507
|
-
var prov = selectedLoginProvider || "claude";
|
|
3508
|
-
var hasAuth;
|
|
3509
|
-
if (prov === "openai") {
|
|
3510
|
-
hasAuth = isOpenAILoggedIn;
|
|
3511
|
-
} else if (prov === "gemini") {
|
|
3512
|
-
hasAuth = geminiCliLoggedIn || !!loginApiKey;
|
|
3513
|
-
} else if (prov === "perplexity") {
|
|
3514
|
-
hasAuth = !!loginApiKey;
|
|
3515
|
-
} else {
|
|
3516
|
-
hasAuth = isClaudeLoggedIn;
|
|
3517
|
-
}
|
|
3518
|
-
sendBtn.disabled = !(isPluginConnected && hasAuth && !isStreaming && !isBridgeMode() && (input.value.trim() || attachedFiles.length > 0));
|
|
3519
|
-
}
|
|
3520
|
-
|
|
3521
|
-
/* ── Thread helpers ───────────────────────────────────────────────────── */
|
|
3522
|
-
function removeHome() {
|
|
3523
|
-
if (homeState && homeState.parentNode) {
|
|
3524
|
-
homeState.parentNode.removeChild(homeState);
|
|
3525
|
-
homeState = null;
|
|
3526
|
-
}
|
|
3527
|
-
}
|
|
3528
|
-
|
|
3529
|
-
function scrollBottom() {
|
|
3530
|
-
thread.scrollTop = thread.scrollHeight;
|
|
3531
|
-
}
|
|
3532
|
-
|
|
3533
|
-
function escapeHtml(s) {
|
|
3534
|
-
return String(s)
|
|
3535
|
-
.replace(/&/g, "&")
|
|
3536
|
-
.replace(/</g, "<")
|
|
3537
|
-
.replace(/>/g, ">")
|
|
3538
|
-
.replace(/"/g, """);
|
|
3539
|
-
}
|
|
3540
|
-
|
|
3541
|
-
function createUserMessageRow(text, fileNames) {
|
|
3542
|
-
var row = document.createElement("div");
|
|
3543
|
-
row.className = "msg-row";
|
|
3544
|
-
var html = '<div class="user-wrap"><div class="user-bubble">';
|
|
3545
|
-
if (fileNames && fileNames.length > 0) {
|
|
3546
|
-
html += '<div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:5px">';
|
|
3547
|
-
fileNames.forEach(function(name) {
|
|
3548
|
-
html += '<span style="display:inline-flex;align-items:center;gap:3px;padding:2px 6px;border-radius:4px;background:rgba(255,255,255,0.06);font-size:10px;color:var(--text-secondary)">\ud83d\udcce ' + escapeHtml(name) + '</span>';
|
|
3549
|
-
});
|
|
3550
|
-
html += '</div>';
|
|
3551
|
-
}
|
|
3552
|
-
html += escapeHtml(text) + '</div></div>';
|
|
3553
|
-
row.innerHTML = html;
|
|
3554
|
-
return row;
|
|
3555
|
-
}
|
|
3556
|
-
|
|
3557
|
-
function createAssistantMessageRow(text) {
|
|
3558
|
-
var row = document.createElement("div");
|
|
3559
|
-
row.className = "msg-row";
|
|
3560
|
-
var wrap = document.createElement("div");
|
|
3561
|
-
wrap.className = "claude-wrap";
|
|
3562
|
-
var body = document.createElement("div");
|
|
3563
|
-
body.className = "claude-text rendered";
|
|
3564
|
-
body.innerHTML = simpleMarkdown(text);
|
|
3565
|
-
wrap.appendChild(body);
|
|
3566
|
-
row.appendChild(wrap);
|
|
3567
|
-
return row;
|
|
3568
|
-
}
|
|
3569
|
-
|
|
3570
|
-
function createErrorRow(text) {
|
|
3571
|
-
var row = document.createElement("div");
|
|
3572
|
-
row.className = "msg-row";
|
|
3573
|
-
row.innerHTML = '<div class="error-msg">' + escapeHtml(text) + '</div>';
|
|
3574
|
-
return row;
|
|
3575
|
-
}
|
|
3576
|
-
|
|
3577
|
-
function renderThreadForProvider(provider) {
|
|
3578
|
-
var activeProvider = provider || getActiveProvider();
|
|
3579
|
-
var entries = getThreadEntries(activeProvider);
|
|
3580
|
-
thread.innerHTML = "";
|
|
3581
|
-
|
|
3582
|
-
if (activeProvider === "bridge") {
|
|
3583
|
-
thread.innerHTML = bridgeHomeTemplate;
|
|
3584
|
-
homeState = null;
|
|
3585
|
-
return;
|
|
3586
|
-
}
|
|
3587
|
-
|
|
3588
|
-
if (!entries.length) {
|
|
3589
|
-
thread.innerHTML = homeStateTemplate;
|
|
3590
|
-
homeState = document.getElementById("home-state");
|
|
3591
|
-
return;
|
|
3592
|
-
}
|
|
3593
|
-
|
|
3594
|
-
homeState = null;
|
|
3595
|
-
entries.forEach(function(entry) {
|
|
3596
|
-
if (entry.role === "user") {
|
|
3597
|
-
thread.appendChild(createUserMessageRow(entry.text || "", entry.fileNames || []));
|
|
3598
|
-
} else if (entry.role === "assistant") {
|
|
3599
|
-
thread.appendChild(createAssistantMessageRow(entry.text || ""));
|
|
3600
|
-
} else if (entry.role === "error") {
|
|
3601
|
-
thread.appendChild(createErrorRow(entry.text || "Something went wrong."));
|
|
3602
|
-
}
|
|
3603
|
-
var spacer = document.createElement("div");
|
|
3604
|
-
spacer.style.height = "14px";
|
|
3605
|
-
thread.appendChild(spacer);
|
|
3606
|
-
});
|
|
3607
|
-
scrollBottom();
|
|
3608
|
-
}
|
|
3609
|
-
|
|
3610
|
-
function buildConversationContext(provider) {
|
|
3611
|
-
return getThreadEntries(provider)
|
|
3612
|
-
.filter(function(entry) {
|
|
3613
|
-
return entry && (entry.role === "user" || entry.role === "assistant") && entry.text;
|
|
3614
|
-
})
|
|
3615
|
-
.slice(-MAX_CONVERSATION_MESSAGES)
|
|
3616
|
-
.map(function(entry) {
|
|
3617
|
-
return {
|
|
3618
|
-
role: entry.role,
|
|
3619
|
-
text: entry.text,
|
|
3620
|
-
fileNames: entry.fileNames || [],
|
|
3621
|
-
};
|
|
3622
|
-
});
|
|
3623
|
-
}
|
|
3624
|
-
|
|
3625
|
-
function appendUserMessage(text, fileNames) {
|
|
3626
|
-
removeHome();
|
|
3627
|
-
var row = createUserMessageRow(text, fileNames);
|
|
3628
|
-
thread.appendChild(row);
|
|
3629
|
-
scrollBottom();
|
|
3630
|
-
pushThreadEntry(getActiveProvider(), { role: "user", text: text, fileNames: fileNames || [] });
|
|
3631
|
-
}
|
|
3632
|
-
|
|
3633
|
-
function beginAssistantMessage() {
|
|
3634
|
-
removeHome();
|
|
3635
|
-
var row = document.createElement("div");
|
|
3636
|
-
row.className = "msg-row";
|
|
3637
|
-
currentMsgEl = document.createElement("div");
|
|
3638
|
-
currentMsgEl.className = "claude-wrap";
|
|
3639
|
-
|
|
3640
|
-
// Claude-style thinking indicator with rotating words
|
|
3641
|
-
var th = document.createElement("div");
|
|
3642
|
-
th.className = "thinking-row";
|
|
3643
|
-
th.id = "thinking";
|
|
3644
|
-
|
|
3645
|
-
var sparkleSvg = '<svg class="thinking-sparkle" width="18" height="18" viewBox="0 0 24 24" fill="none">' +
|
|
3646
|
-
'<path d="M12 2L13.09 8.26L18 4L14.74 9.91L21 10L14.74 12.09L18 18L13.09 13.74L12 20L10.91 13.74L6 18L9.26 12.09L3 10L9.26 9.91L6 4L10.91 8.26L12 2Z" fill="#da7756" opacity="0.85"/>' +
|
|
3647
|
-
'</svg>';
|
|
3648
|
-
|
|
3649
|
-
var thinkingPhrases = [
|
|
3650
|
-
"Thinking\u2026",
|
|
3651
|
-
"Analyzing\u2026",
|
|
3652
|
-
"Considering\u2026",
|
|
3653
|
-
"Processing\u2026",
|
|
3654
|
-
"Pondering\u2026",
|
|
3655
|
-
"Munching\u2026",
|
|
3656
|
-
"Reasoning\u2026",
|
|
3657
|
-
"Contemplating\u2026",
|
|
3658
|
-
"Reflecting\u2026",
|
|
3659
|
-
"Working\u2026",
|
|
3660
|
-
"Researching\u2026",
|
|
3661
|
-
"Composing\u2026",
|
|
3662
|
-
"Crafting\u2026",
|
|
3663
|
-
"Evaluating\u2026",
|
|
3664
|
-
"Exploring\u2026",
|
|
3665
|
-
"Imagining\u2026",
|
|
3666
|
-
"Synthesizing\u2026",
|
|
3667
|
-
"Interpreting\u2026",
|
|
3668
|
-
"Figuring out\u2026",
|
|
3669
|
-
"Brainstorming\u2026"
|
|
3670
|
-
];
|
|
3671
|
-
|
|
3672
|
-
var wordsHtml = '<div class="thinking-words">';
|
|
3673
|
-
for (var i = 0; i < thinkingPhrases.length; i++) {
|
|
3674
|
-
wordsHtml += '<span class="thinking-word' + (i === 0 ? ' active' : '') + '" data-idx="' + i + '">' + thinkingPhrases[i] + '</span>';
|
|
3675
|
-
}
|
|
3676
|
-
wordsHtml += '</div>';
|
|
3677
|
-
|
|
3678
|
-
th.innerHTML = sparkleSvg + wordsHtml;
|
|
3679
|
-
currentMsgEl.appendChild(th);
|
|
3680
|
-
|
|
3681
|
-
// Rotate words every 2.5 seconds
|
|
3682
|
-
var wordIdx = 0;
|
|
3683
|
-
var totalWords = thinkingPhrases.length;
|
|
3684
|
-
th._rotateInterval = setInterval(function() {
|
|
3685
|
-
var words = th.querySelectorAll(".thinking-word");
|
|
3686
|
-
if (!words.length || !document.contains(th)) {
|
|
3687
|
-
clearInterval(th._rotateInterval);
|
|
3688
|
-
return;
|
|
3689
|
-
}
|
|
3690
|
-
var current = words[wordIdx];
|
|
3691
|
-
current.classList.remove("active");
|
|
3692
|
-
current.classList.add("exit");
|
|
3693
|
-
wordIdx = (wordIdx + 1) % totalWords;
|
|
3694
|
-
var next = words[wordIdx];
|
|
3695
|
-
// Reset next position
|
|
3696
|
-
next.classList.remove("exit");
|
|
3697
|
-
next.classList.add("active");
|
|
3698
|
-
// Clean up exit class after transition
|
|
3699
|
-
setTimeout(function() { current.classList.remove("exit"); }, 400);
|
|
3700
|
-
}, 2500);
|
|
3701
|
-
|
|
3702
|
-
row.appendChild(currentMsgEl);
|
|
3703
|
-
thread.appendChild(row);
|
|
3704
|
-
scrollBottom();
|
|
3705
|
-
|
|
3706
|
-
currentTextEl = null;
|
|
3707
|
-
currentActivityEl = null;
|
|
3708
|
-
currentAssistantRawText = "";
|
|
3709
|
-
currentToolStepsEl = null;
|
|
3710
|
-
currentToolPills = {};
|
|
3711
|
-
}
|
|
3712
|
-
|
|
3713
|
-
function ensureText() {
|
|
3714
|
-
if (currentTextEl) return;
|
|
3715
|
-
var th = document.getElementById("thinking");
|
|
3716
|
-
if (th) {
|
|
3717
|
-
if (th._rotateInterval) clearInterval(th._rotateInterval);
|
|
3718
|
-
th.remove();
|
|
3719
|
-
}
|
|
3720
|
-
currentTextEl = document.createElement("div");
|
|
3721
|
-
currentTextEl.className = "claude-text";
|
|
3722
|
-
currentMsgEl.appendChild(currentTextEl);
|
|
3723
|
-
}
|
|
3724
|
-
|
|
3725
|
-
function appendTextDelta(delta) {
|
|
3726
|
-
ensureText();
|
|
3727
|
-
currentAssistantRawText += delta;
|
|
3728
|
-
var anchorEl = currentActivityEl || currentToolStepsEl;
|
|
3729
|
-
if (anchorEl) {
|
|
3730
|
-
currentTextEl.insertBefore(document.createTextNode(delta), anchorEl);
|
|
3731
|
-
} else {
|
|
3732
|
-
currentTextEl.appendChild(document.createTextNode(delta));
|
|
3733
|
-
}
|
|
3734
|
-
scrollBottom();
|
|
3735
|
-
}
|
|
3736
|
-
|
|
3737
|
-
function ensureActivityStream() {
|
|
3738
|
-
ensureText();
|
|
3739
|
-
if (currentActivityEl) return;
|
|
3740
|
-
currentActivityEl = document.createElement("div");
|
|
3741
|
-
currentActivityEl.className = "activity-stream";
|
|
3742
|
-
if (currentToolStepsEl) {
|
|
3743
|
-
currentTextEl.insertBefore(currentActivityEl, currentToolStepsEl);
|
|
3744
|
-
} else {
|
|
3745
|
-
currentTextEl.appendChild(currentActivityEl);
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
|
|
3749
|
-
function formatActivityTime(timestamp) {
|
|
3750
|
-
try {
|
|
3751
|
-
return new Date(timestamp || Date.now()).toLocaleTimeString([], {
|
|
3752
|
-
hour: "2-digit",
|
|
3753
|
-
minute: "2-digit",
|
|
3754
|
-
second: "2-digit"
|
|
3755
|
-
});
|
|
3756
|
-
} catch (e) {
|
|
3757
|
-
return "";
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
|
|
3761
|
-
function appendActivityLine(kind, html, timestamp) {
|
|
3762
|
-
ensureActivityStream();
|
|
3763
|
-
var line = document.createElement("div");
|
|
3764
|
-
line.className = "activity-line " + kind;
|
|
3765
|
-
line.innerHTML =
|
|
3766
|
-
'<span class="activity-time">' + escapeHtml(formatActivityTime(timestamp)) + '</span>' +
|
|
3767
|
-
'<span class="activity-dot"></span>' +
|
|
3768
|
-
'<span class="activity-body">' + html + '</span>';
|
|
3769
|
-
currentActivityEl.appendChild(line);
|
|
3770
|
-
scrollBottom();
|
|
3771
|
-
}
|
|
3772
|
-
|
|
3773
|
-
function appendAgentActivity(msg) {
|
|
3774
|
-
if (!isStreaming || !currentMsgEl) return;
|
|
3775
|
-
var agent = msg.agent || "Agent";
|
|
3776
|
-
var status = msg.status || msg.method || "Working";
|
|
3777
|
-
var method = msg.method || "";
|
|
3778
|
-
var isToolLike = method && method !== "plan" && method !== "review" && method !== "done";
|
|
3779
|
-
if (isToolLike) {
|
|
3780
|
-
appendActivityLine(
|
|
3781
|
-
"tool",
|
|
3782
|
-
'<strong>' + escapeHtml(method) + '</strong> · ' + escapeHtml(status),
|
|
3783
|
-
msg.timestamp
|
|
3784
|
-
);
|
|
3785
|
-
return;
|
|
3786
|
-
}
|
|
3787
|
-
appendActivityLine(
|
|
3788
|
-
"phase",
|
|
3789
|
-
'<strong>' + escapeHtml(agent) + '</strong> · ' + escapeHtml(status),
|
|
3790
|
-
msg.timestamp
|
|
3791
|
-
);
|
|
3792
|
-
}
|
|
3793
|
-
|
|
3794
|
-
function ensureToolSteps() {
|
|
3795
|
-
ensureText();
|
|
3796
|
-
if (currentToolStepsEl) return;
|
|
3797
|
-
currentToolStepsEl = document.createElement("div");
|
|
3798
|
-
currentToolStepsEl.className = "tool-steps";
|
|
3799
|
-
currentTextEl.appendChild(currentToolStepsEl);
|
|
3800
|
-
}
|
|
3801
|
-
|
|
3802
|
-
function addToolStart(toolName) {
|
|
3803
|
-
ensureToolSteps();
|
|
3804
|
-
var label = toolLabel(toolName);
|
|
3805
|
-
var pill = document.createElement("div");
|
|
3806
|
-
pill.className = "tool-pill running";
|
|
3807
|
-
pill.innerHTML =
|
|
3808
|
-
'<div class="tool-spinner"></div>' +
|
|
3809
|
-
'<span>' + escapeHtml(label) + '</span>';
|
|
3810
|
-
currentToolStepsEl.appendChild(pill);
|
|
3811
|
-
currentToolPills[toolName] = pill;
|
|
3812
|
-
// Track unique tools used this turn
|
|
3813
|
-
if (currentToolsUsed.indexOf(toolName) === -1) {
|
|
3814
|
-
currentToolsUsed.push(toolName);
|
|
3815
|
-
}
|
|
3816
|
-
scrollBottom();
|
|
3817
|
-
}
|
|
3818
|
-
|
|
3819
|
-
function resolveToolPill(toolName, isError) {
|
|
3820
|
-
var pill = currentToolPills[toolName];
|
|
3821
|
-
if (!pill) return;
|
|
3822
|
-
pill.className = "tool-pill " + (isError ? "error" : "done");
|
|
3823
|
-
pill.innerHTML =
|
|
3824
|
-
'<span>' + (isError ? "\u2715 " : "\u2713 ") + '</span>' +
|
|
3825
|
-
'<span>' + escapeHtml(toolLabel(toolName)) + '</span>';
|
|
3826
|
-
}
|
|
3827
|
-
|
|
3828
|
-
function simpleMarkdown(text) {
|
|
3829
|
-
// Escape HTML first
|
|
3830
|
-
var s = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3831
|
-
// Code blocks (``` ... ```)
|
|
3832
|
-
// Supports ```download:filename.md to add a download button
|
|
3833
|
-
var codeBlockId = 0;
|
|
3834
|
-
s = s.replace(/```(\S*)\n([\s\S]*?)```/g, function(m, lang, code) {
|
|
3835
|
-
var bid = 'codeblock-' + Date.now() + '-' + (codeBlockId++);
|
|
3836
|
-
var cleanCode = code.replace(/\n$/, '');
|
|
3837
|
-
var downloadFile = '';
|
|
3838
|
-
if (lang && lang.startsWith('download:')) {
|
|
3839
|
-
downloadFile = lang.slice(9);
|
|
3840
|
-
lang = '';
|
|
3841
|
-
}
|
|
3842
|
-
var actions = '<span class="code-block-actions">' +
|
|
3843
|
-
'<button class="copy-code-btn" data-block="' + bid + '">Copy</button>' +
|
|
3844
|
-
(downloadFile ? '<button class="download-code-btn" data-block="' + bid + '" data-filename="' + downloadFile + '">Download</button>' : '') +
|
|
3845
|
-
'</span>';
|
|
3846
|
-
return '<pre id="' + bid + '">' + actions + '<code>' + cleanCode + '</code></pre>';
|
|
3847
|
-
});
|
|
3848
|
-
// Inline code
|
|
3849
|
-
s = s.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
3850
|
-
// Bold
|
|
3851
|
-
s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
3852
|
-
// Italic
|
|
3853
|
-
s = s.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>');
|
|
3854
|
-
// Headings
|
|
3855
|
-
s = s.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
|
3856
|
-
s = s.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
|
3857
|
-
s = s.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
|
3858
|
-
// Horizontal rule
|
|
3859
|
-
s = s.replace(/^---$/gm, '<hr>');
|
|
3860
|
-
// Blockquote
|
|
3861
|
-
s = s.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');
|
|
3862
|
-
// Unordered lists
|
|
3863
|
-
s = s.replace(/^[*\-] (.+)$/gm, '<li>$1</li>');
|
|
3864
|
-
s = s.replace(/((?:<li>.*<\/li>\n?)+)/g, '<ul>$1</ul>');
|
|
3865
|
-
// Ordered lists
|
|
3866
|
-
s = s.replace(/^\d+\. (.+)$/gm, '<li>$1</li>');
|
|
3867
|
-
// Paragraphs: split by double newline
|
|
3868
|
-
s = s.split(/\n{2,}/).map(function(block) {
|
|
3869
|
-
block = block.trim();
|
|
3870
|
-
if (!block) return '';
|
|
3871
|
-
// Don't wrap blocks that are already block-level elements
|
|
3872
|
-
if (/^<(h[1-3]|pre|ul|ol|li|blockquote|hr)/.test(block)) return block;
|
|
3873
|
-
return '<p>' + block.replace(/\n/g, '<br>') + '</p>';
|
|
3874
|
-
}).join('\n');
|
|
3875
|
-
return s;
|
|
3876
|
-
}
|
|
3877
|
-
|
|
3878
|
-
function wireCodeBlockButtons(container) {
|
|
3879
|
-
// Copy buttons
|
|
3880
|
-
var copyBtns = container.querySelectorAll('.copy-code-btn');
|
|
3881
|
-
for (var i = 0; i < copyBtns.length; i++) {
|
|
3882
|
-
copyBtns[i].addEventListener('click', function(e) {
|
|
3883
|
-
var btn = e.currentTarget;
|
|
3884
|
-
var blockId = btn.getAttribute('data-block');
|
|
3885
|
-
var pre = document.getElementById(blockId);
|
|
3886
|
-
if (!pre) return;
|
|
3887
|
-
var code = pre.querySelector('code');
|
|
3888
|
-
var text = code ? code.textContent : pre.textContent;
|
|
3889
|
-
// Decode HTML entities back to raw text
|
|
3890
|
-
var tmp = document.createElement('textarea');
|
|
3891
|
-
tmp.innerHTML = text;
|
|
3892
|
-
text = tmp.value;
|
|
3893
|
-
// Copy to clipboard via textarea (Figma plugin sandbox)
|
|
3894
|
-
var ta = document.createElement('textarea');
|
|
3895
|
-
ta.value = text;
|
|
3896
|
-
ta.style.position = 'fixed';
|
|
3897
|
-
ta.style.left = '-9999px';
|
|
3898
|
-
document.body.appendChild(ta);
|
|
3899
|
-
ta.select();
|
|
3900
|
-
document.execCommand('copy');
|
|
3901
|
-
document.body.removeChild(ta);
|
|
3902
|
-
btn.textContent = 'Copied!';
|
|
3903
|
-
btn.classList.add('copied');
|
|
3904
|
-
setTimeout(function() {
|
|
3905
|
-
btn.textContent = 'Copy';
|
|
3906
|
-
btn.classList.remove('copied');
|
|
3907
|
-
}, 2000);
|
|
3908
|
-
});
|
|
3909
|
-
}
|
|
3910
|
-
// Download buttons
|
|
3911
|
-
var dlBtns = container.querySelectorAll('.download-code-btn');
|
|
3912
|
-
for (var j = 0; j < dlBtns.length; j++) {
|
|
3913
|
-
dlBtns[j].addEventListener('click', function(e) {
|
|
3914
|
-
var btn = e.currentTarget;
|
|
3915
|
-
var blockId = btn.getAttribute('data-block');
|
|
3916
|
-
var filename = btn.getAttribute('data-filename') || 'download.txt';
|
|
3917
|
-
var pre = document.getElementById(blockId);
|
|
3918
|
-
if (!pre) return;
|
|
3919
|
-
var code = pre.querySelector('code');
|
|
3920
|
-
var text = code ? code.textContent : pre.textContent;
|
|
3921
|
-
// Decode HTML entities
|
|
3922
|
-
var tmp = document.createElement('textarea');
|
|
3923
|
-
tmp.innerHTML = text;
|
|
3924
|
-
text = tmp.value;
|
|
3925
|
-
// Create download via data URI + anchor click
|
|
3926
|
-
var blob = new Blob([text], { type: 'text/plain' });
|
|
3927
|
-
var url = URL.createObjectURL(blob);
|
|
3928
|
-
var a = document.createElement('a');
|
|
3929
|
-
a.href = url;
|
|
3930
|
-
a.download = filename;
|
|
3931
|
-
document.body.appendChild(a);
|
|
3932
|
-
a.click();
|
|
3933
|
-
document.body.removeChild(a);
|
|
3934
|
-
URL.revokeObjectURL(url);
|
|
3935
|
-
btn.textContent = 'Downloaded!';
|
|
3936
|
-
btn.classList.add('copied');
|
|
3937
|
-
setTimeout(function() {
|
|
3938
|
-
btn.textContent = 'Download';
|
|
3939
|
-
btn.classList.remove('copied');
|
|
3940
|
-
}, 2000);
|
|
3941
|
-
});
|
|
3942
|
-
}
|
|
3943
|
-
}
|
|
3944
|
-
|
|
3945
|
-
function finalizeAssistantMessage() {
|
|
3946
|
-
if (!currentMsgEl) return;
|
|
3947
|
-
var th = document.getElementById("thinking");
|
|
3948
|
-
if (th) {
|
|
3949
|
-
if (th._rotateInterval) clearInterval(th._rotateInterval);
|
|
3950
|
-
th.remove();
|
|
3951
|
-
}
|
|
3952
|
-
|
|
3953
|
-
if (!currentTextEl) {
|
|
3954
|
-
ensureText();
|
|
3955
|
-
}
|
|
3956
|
-
|
|
3957
|
-
if (currentTextEl) {
|
|
3958
|
-
var rawText = (currentAssistantRawText || "").trim();
|
|
3959
|
-
var activityStream = currentTextEl.querySelector('.activity-stream');
|
|
3960
|
-
var toolSteps = currentTextEl.querySelector('.tool-steps');
|
|
3961
|
-
if (rawText) {
|
|
3962
|
-
currentTextEl.innerHTML = simpleMarkdown(rawText);
|
|
3963
|
-
currentTextEl.classList.add('rendered');
|
|
3964
|
-
if (activityStream) currentTextEl.appendChild(activityStream);
|
|
3965
|
-
if (toolSteps) currentTextEl.appendChild(toolSteps);
|
|
3966
|
-
if (currentRequestProvider) {
|
|
3967
|
-
pushThreadEntry(currentRequestProvider, { role: "assistant", text: rawText });
|
|
3968
|
-
}
|
|
3969
|
-
} else {
|
|
3970
|
-
currentTextEl.innerHTML = '<p style="color:var(--text-muted)">Completed.</p>';
|
|
3971
|
-
currentTextEl.classList.add('rendered');
|
|
3972
|
-
if (activityStream) currentTextEl.appendChild(activityStream);
|
|
3973
|
-
if (toolSteps) currentTextEl.appendChild(toolSteps);
|
|
3974
|
-
}
|
|
3975
|
-
// Wire up copy/download buttons on code blocks
|
|
3976
|
-
wireCodeBlockButtons(currentTextEl);
|
|
3977
|
-
}
|
|
3978
|
-
|
|
3979
|
-
// Mark any still-running pills as done
|
|
3980
|
-
for (var name in currentToolPills) {
|
|
3981
|
-
if (currentToolPills[name].classList.contains("running")) {
|
|
3982
|
-
resolveToolPill(name, false);
|
|
3983
|
-
}
|
|
3984
|
-
}
|
|
3985
|
-
|
|
3986
|
-
// Append tools-used summary if any tools were called
|
|
3987
|
-
if (currentToolsUsed.length > 0 && currentTextEl) {
|
|
3988
|
-
var summaryWrap = document.createElement("div");
|
|
3989
|
-
summaryWrap.style.marginTop = "10px";
|
|
3990
|
-
var summaryLabel = document.createElement("div");
|
|
3991
|
-
summaryLabel.className = "tools-used-label";
|
|
3992
|
-
summaryLabel.textContent = currentToolsUsed.length + " MCP tool" + (currentToolsUsed.length > 1 ? "s" : "") + " used:";
|
|
3993
|
-
summaryWrap.appendChild(summaryLabel);
|
|
3994
|
-
var summaryRow = document.createElement("div");
|
|
3995
|
-
summaryRow.className = "tools-used-summary";
|
|
3996
|
-
currentToolsUsed.forEach(function(t) {
|
|
3997
|
-
var tag = document.createElement("span");
|
|
3998
|
-
tag.className = "tools-used-tag";
|
|
3999
|
-
tag.innerHTML = '<svg width="8" height="8" viewBox="0 0 8 8" fill="none"><circle cx="4" cy="4" r="3" stroke="currentColor" stroke-width="1"/><path d="M2.5 4l1 1L5.5 2.5" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/></svg>' + escapeHtml(toolLabel(t));
|
|
4000
|
-
summaryRow.appendChild(tag);
|
|
4001
|
-
});
|
|
4002
|
-
summaryWrap.appendChild(summaryRow);
|
|
4003
|
-
currentTextEl.appendChild(summaryWrap);
|
|
4004
|
-
}
|
|
4005
|
-
|
|
4006
|
-
// Append Perplexity citations if present
|
|
4007
|
-
if (pendingCitations && pendingCitations.length > 0 && currentTextEl) {
|
|
4008
|
-
var citationWrap = document.createElement("div");
|
|
4009
|
-
citationWrap.className = "citations-section";
|
|
4010
|
-
var citationLabel = document.createElement("div");
|
|
4011
|
-
citationLabel.className = "citations-label";
|
|
4012
|
-
citationLabel.textContent = pendingCitations.length + " source" + (pendingCitations.length > 1 ? "s" : "");
|
|
4013
|
-
citationWrap.appendChild(citationLabel);
|
|
4014
|
-
pendingCitations.forEach(function(url, i) {
|
|
4015
|
-
var link = document.createElement("a");
|
|
4016
|
-
link.className = "citation-link";
|
|
4017
|
-
link.href = url;
|
|
4018
|
-
link.target = "_blank";
|
|
4019
|
-
link.rel = "noopener";
|
|
4020
|
-
try {
|
|
4021
|
-
link.textContent = "[" + (i + 1) + "] " + url.replace(/^https?:\/\/(www\.)?/, "").split("/")[0];
|
|
4022
|
-
} catch(e) {
|
|
4023
|
-
link.textContent = "[" + (i + 1) + "] " + url;
|
|
4024
|
-
}
|
|
4025
|
-
citationWrap.appendChild(link);
|
|
4026
|
-
});
|
|
4027
|
-
currentTextEl.appendChild(citationWrap);
|
|
4028
|
-
pendingCitations = null;
|
|
4029
|
-
}
|
|
4030
|
-
|
|
4031
|
-
// Spacer between turns
|
|
4032
|
-
var sp = document.createElement("div");
|
|
4033
|
-
sp.style.height = "14px";
|
|
4034
|
-
if (currentMsgEl.parentNode) currentMsgEl.parentNode.appendChild(sp);
|
|
4035
|
-
|
|
4036
|
-
currentMsgEl = null;
|
|
4037
|
-
currentTextEl = null;
|
|
4038
|
-
currentActivityEl = null;
|
|
4039
|
-
currentAssistantRawText = "";
|
|
4040
|
-
currentToolStepsEl = null;
|
|
4041
|
-
currentToolPills = {};
|
|
4042
|
-
currentToolsUsed = [];
|
|
4043
|
-
pendingCitations = null;
|
|
4044
|
-
scrollBottom();
|
|
4045
|
-
}
|
|
4046
|
-
|
|
4047
|
-
function showError(text) {
|
|
4048
|
-
var th = document.getElementById("thinking");
|
|
4049
|
-
if (th) th.remove();
|
|
4050
|
-
removeHome();
|
|
4051
|
-
if (currentMsgEl && currentMsgEl.parentNode) {
|
|
4052
|
-
currentMsgEl.parentNode.remove();
|
|
4053
|
-
}
|
|
4054
|
-
|
|
4055
|
-
var row = createErrorRow(text);
|
|
4056
|
-
thread.appendChild(row);
|
|
4057
|
-
scrollBottom();
|
|
4058
|
-
if (currentRequestProvider) {
|
|
4059
|
-
pushThreadEntry(currentRequestProvider, { role: "error", text: text });
|
|
4060
|
-
}
|
|
4061
|
-
|
|
4062
|
-
currentMsgEl = null;
|
|
4063
|
-
currentTextEl = null;
|
|
4064
|
-
currentActivityEl = null;
|
|
4065
|
-
currentAssistantRawText = "";
|
|
4066
|
-
currentToolStepsEl = null;
|
|
4067
|
-
currentToolPills = {};
|
|
4068
|
-
currentToolsUsed = [];
|
|
4069
|
-
}
|
|
4070
|
-
|
|
4071
|
-
/* ── Send message ─────────────────────────────────────────────────────── */
|
|
4072
|
-
function sendMessage() {
|
|
4073
|
-
var text = input.value.trim();
|
|
4074
|
-
if ((!text && attachedFiles.length === 0) || isStreaming || !ws || ws.readyState !== WebSocket.OPEN) return;
|
|
4075
|
-
|
|
4076
|
-
var activeProvider = getActiveProvider();
|
|
4077
|
-
var conversation = buildConversationContext(activeProvider);
|
|
4078
|
-
input.value = "";
|
|
4079
|
-
autoResize();
|
|
4080
|
-
|
|
4081
|
-
var fileNames = attachedFiles.map(function(f) { return f.name; });
|
|
4082
|
-
appendUserMessage(text, fileNames);
|
|
4083
|
-
|
|
4084
|
-
isStreaming = true;
|
|
4085
|
-
currentRequestId = uuid();
|
|
4086
|
-
currentRequestProvider = activeProvider;
|
|
4087
|
-
updateStatus();
|
|
4088
|
-
|
|
4089
|
-
// Switch to stop button
|
|
4090
|
-
sendBtn.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><rect x="1.5" y="1.5" width="7" height="7" rx="1.5"/></svg>';
|
|
4091
|
-
sendBtn.title = "Stop";
|
|
4092
|
-
sendBtn.disabled = false;
|
|
4093
|
-
sendBtn.onclick = stopStreaming;
|
|
4094
|
-
sendBtn.className = "stop-btn";
|
|
4095
|
-
|
|
4096
|
-
beginAssistantMessage();
|
|
4097
|
-
|
|
4098
|
-
var messageText = text;
|
|
4099
|
-
|
|
4100
|
-
// Intercept /knowledge command client-side — handle without AI
|
|
4101
|
-
if (/^\s*\/knowledge\b/i.test(messageText)) {
|
|
4102
|
-
var kQuery = messageText.replace(/^\s*\/knowledge\s*/i, "").trim();
|
|
4103
|
-
// Send to relay for hub processing (not as a chat message to AI)
|
|
4104
|
-
ws.send(JSON.stringify({ type: "chat", id: currentRequestId, message: messageText, mode: activeMode, model: getSelectedModel(activeProvider), conversation: [], researcherMode: isUxResearcherMode }));
|
|
4105
|
-
// Reset input color
|
|
4106
|
-
input.style.color = "";
|
|
4107
|
-
return;
|
|
4108
|
-
}
|
|
4109
|
-
|
|
4110
|
-
var payload = {
|
|
4111
|
-
type: "chat",
|
|
4112
|
-
id: currentRequestId,
|
|
4113
|
-
message: messageText,
|
|
4114
|
-
model: getSelectedModel(activeProvider),
|
|
4115
|
-
conversation: conversation,
|
|
4116
|
-
mode: activeMode,
|
|
4117
|
-
researcherMode: isUxResearcherMode,
|
|
4118
|
-
};
|
|
4119
|
-
|
|
4120
|
-
if (attachedFiles.length > 0) {
|
|
4121
|
-
payload.attachments = attachedFiles.map(function(f) {
|
|
4122
|
-
return { name: f.name, data: f.data, type: f.type, isImage: f.isImage };
|
|
4123
|
-
});
|
|
4124
|
-
attachedFiles = [];
|
|
4125
|
-
renderAttachedFiles();
|
|
4126
|
-
}
|
|
4127
|
-
|
|
4128
|
-
ws.send(JSON.stringify(payload));
|
|
4129
|
-
}
|
|
4130
|
-
|
|
4131
|
-
function stopStreaming() {
|
|
4132
|
-
if (!isStreaming || !currentRequestId) return;
|
|
4133
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
4134
|
-
ws.send(JSON.stringify({ type: "abort-chat", id: currentRequestId }));
|
|
4135
|
-
}
|
|
4136
|
-
finalizeAssistantMessage();
|
|
4137
|
-
endStreaming();
|
|
4138
|
-
}
|
|
4139
|
-
|
|
4140
|
-
function endStreaming() {
|
|
4141
|
-
isStreaming = false;
|
|
4142
|
-
currentRequestId = null;
|
|
4143
|
-
currentRequestProvider = null;
|
|
4144
|
-
resetSendBtn();
|
|
4145
|
-
updateStatus();
|
|
4146
|
-
}
|
|
4147
|
-
|
|
4148
|
-
/* ── New Conversation ─────────────────────────────────────────────────── */
|
|
4149
|
-
window.startNewConversation = function() {
|
|
4150
|
-
if (isStreaming) stopStreaming();
|
|
4151
|
-
|
|
4152
|
-
// Clear thread entries for the active provider + mode
|
|
4153
|
-
var key = getThreadKey(getActiveProvider());
|
|
4154
|
-
chatThreads[key] = [];
|
|
4155
|
-
saveChatThreads();
|
|
4156
|
-
|
|
4157
|
-
// Reset DOM to home state
|
|
4158
|
-
thread.innerHTML = homeStateTemplate;
|
|
4159
|
-
homeState = document.getElementById("home-state");
|
|
4160
|
-
|
|
4161
|
-
// Tell the relay to reset the session for this mode
|
|
4162
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
4163
|
-
ws.send(JSON.stringify({ type: "new-conversation", mode: activeMode }));
|
|
4164
|
-
}
|
|
4165
|
-
|
|
4166
|
-
// Focus the input
|
|
4167
|
-
input.value = "";
|
|
4168
|
-
input.focus();
|
|
4169
|
-
};
|
|
4170
|
-
|
|
4171
|
-
function resetSendBtn() {
|
|
4172
|
-
sendBtn.className = "send-btn";
|
|
4173
|
-
sendBtn.title = "Send (Enter)";
|
|
4174
|
-
sendBtn.onclick = sendMessage;
|
|
4175
|
-
sendBtn.innerHTML = '<svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M7 12V2M7 2L3 6M7 2L11 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
4176
|
-
updateSend();
|
|
4177
|
-
}
|
|
4178
|
-
|
|
4179
|
-
/* ── Input auto-resize ────────────────────────────────────────────────── */
|
|
4180
|
-
function autoResize() {
|
|
4181
|
-
input.style.height = "auto";
|
|
4182
|
-
input.style.height = Math.min(input.scrollHeight, 120) + "px";
|
|
4183
|
-
}
|
|
4184
|
-
|
|
4185
|
-
input.addEventListener("input", function() {
|
|
4186
|
-
autoResize();
|
|
4187
|
-
updateSend();
|
|
4188
|
-
// Highlight /knowledge command
|
|
4189
|
-
var val = (input.value || "").trim();
|
|
4190
|
-
if (/^\/knowledge\b/i.test(val)) {
|
|
4191
|
-
input.style.color = "#da7756";
|
|
4192
|
-
} else {
|
|
4193
|
-
input.style.color = "";
|
|
4194
|
-
}
|
|
4195
|
-
});
|
|
4196
|
-
|
|
4197
|
-
input.addEventListener("keydown", function(e) {
|
|
4198
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
4199
|
-
e.preventDefault();
|
|
4200
|
-
sendMessage();
|
|
4201
|
-
}
|
|
4202
|
-
});
|
|
4203
|
-
|
|
4204
|
-
sendBtn.onclick = sendMessage;
|
|
4205
|
-
|
|
4206
|
-
/* ── WebSocket ────────────────────────────────────────────────────────── */
|
|
4207
|
-
function connect() {
|
|
4208
|
-
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
|
|
4209
|
-
|
|
4210
|
-
ws = new WebSocket("ws://localhost:9001/plugin");
|
|
4211
|
-
|
|
4212
|
-
ws.onopen = function() {
|
|
4213
|
-
isPluginConnected = true;
|
|
4214
|
-
connectionAttempts = 0;
|
|
4215
|
-
hideSetupGuide();
|
|
4216
|
-
updateStatus();
|
|
4217
|
-
ws.send(JSON.stringify({ type: "plugin-hello", fileName: "Figma Intelligence" }));
|
|
4218
|
-
// Tell the relay which provider is active
|
|
4219
|
-
if (loginDismissed && selectedLoginProvider) {
|
|
4220
|
-
sendProviderToRelay();
|
|
4221
|
-
}
|
|
4222
|
-
// Knowledge sources are restored from bridge-status payload (no localStorage restore needed)
|
|
4223
|
-
};
|
|
4224
|
-
|
|
4225
|
-
ws.onclose = function() {
|
|
4226
|
-
isPluginConnected = false;
|
|
4227
|
-
isMcpConnected = false;
|
|
4228
|
-
isClaudeLoggedIn = false;
|
|
4229
|
-
isOpenAILoggedIn = false;
|
|
4230
|
-
ws = null;
|
|
4231
|
-
if (isStreaming) {
|
|
4232
|
-
finalizeAssistantMessage();
|
|
4233
|
-
endStreaming();
|
|
4234
|
-
}
|
|
4235
|
-
updateStatus();
|
|
4236
|
-
connectionAttempts++;
|
|
4237
|
-
if (connectionAttempts >= SETUP_GUIDE_THRESHOLD) {
|
|
4238
|
-
showSetupGuide();
|
|
4239
|
-
}
|
|
4240
|
-
if (reconnectTimer) return;
|
|
4241
|
-
// Retry every 3 seconds when setup guide is shown, 500ms for quick reconnects
|
|
4242
|
-
var delay = connectionAttempts >= SETUP_GUIDE_THRESHOLD ? 3000 : 500;
|
|
4243
|
-
reconnectTimer = setTimeout(function() {
|
|
4244
|
-
reconnectTimer = null;
|
|
4245
|
-
connect();
|
|
4246
|
-
}, delay);
|
|
4247
|
-
};
|
|
4248
|
-
|
|
4249
|
-
ws.onerror = function() {
|
|
4250
|
-
if (ws && ws.readyState !== WebSocket.OPEN) ws.close();
|
|
4251
|
-
};
|
|
4252
|
-
|
|
4253
|
-
ws.onmessage = function(event) {
|
|
4254
|
-
var msg;
|
|
4255
|
-
try { msg = JSON.parse(event.data); } catch { return; }
|
|
4256
|
-
|
|
4257
|
-
// ── Bridge status ────────────────────────────────────────────────
|
|
4258
|
-
if (msg.type === "bridge-status") {
|
|
4259
|
-
isMcpConnected = !!msg.mcpConnected;
|
|
4260
|
-
isClaudeLoggedIn = !!msg.claudeLoggedIn;
|
|
4261
|
-
claudeEmail = msg.claudeEmail || null;
|
|
4262
|
-
isOpenAILoggedIn = !!msg.openaiLoggedIn;
|
|
4263
|
-
openaiEmail = msg.openaiEmail || null;
|
|
4264
|
-
geminiCliLoggedIn = !!msg.geminiLoggedIn;
|
|
4265
|
-
var geminiEmail = msg.geminiEmail || null;
|
|
4266
|
-
if (msg.provider && PROVIDER_META[msg.provider]) {
|
|
4267
|
-
var providerChanged = msg.provider !== selectedLoginProvider;
|
|
4268
|
-
selectedLoginProvider = msg.provider;
|
|
4269
|
-
if (providerChanged) {
|
|
4270
|
-
updateProviderBadge();
|
|
4271
|
-
renderThreadForProvider(selectedLoginProvider);
|
|
4272
|
-
}
|
|
4273
|
-
// If relay already knows a subscription provider or has a stored API key,
|
|
4274
|
-
// skip the login screen. API-key providers (Perplexity, Stitch) also skip
|
|
4275
|
-
// when the relay confirms it has their key persisted.
|
|
4276
|
-
var canSkipLogin =
|
|
4277
|
-
selectedLoginProvider === "claude" ||
|
|
4278
|
-
selectedLoginProvider === "openai" ||
|
|
4279
|
-
selectedLoginProvider === "bridge" ||
|
|
4280
|
-
(selectedLoginProvider === "gemini" && geminiCliLoggedIn) ||
|
|
4281
|
-
((selectedLoginProvider === "stitch") && (msg.hasApiKey || msg.hasStitchOAuth)) ||
|
|
4282
|
-
(selectedLoginProvider === "perplexity" && msg.hasApiKey);
|
|
4283
|
-
if (!loginDismissed && canSkipLogin) {
|
|
4284
|
-
hideLoginScreen();
|
|
4285
|
-
updateProviderBadge();
|
|
4286
|
-
updateModeTabsVisibility();
|
|
4287
|
-
if (selectedLoginProvider === "stitch" && activeMode === "chat") switchMode("code");
|
|
4288
|
-
if (selectedLoginProvider === "perplexity" && activeMode !== "chat") switchMode("chat");
|
|
4289
|
-
if (selectedLoginProvider === "stitch" && input) input.placeholder = "Describe the UI screen to generate...";
|
|
4290
|
-
if (selectedLoginProvider === "perplexity" && input) input.placeholder = "Research any topic...";
|
|
4291
|
-
}
|
|
4292
|
-
}
|
|
4293
|
-
// Update Claude card description in login screen
|
|
4294
|
-
var pdClaude = document.getElementById("pd-claude");
|
|
4295
|
-
if (pdClaude) {
|
|
4296
|
-
pdClaude.textContent = isClaudeLoggedIn
|
|
4297
|
-
? ("Logged in" + (claudeEmail ? " \u00b7 " + claudeEmail : ""))
|
|
4298
|
-
: "Anthropic \u00b7 Subscription required";
|
|
4299
|
-
}
|
|
4300
|
-
// Update OpenAI card description in login screen
|
|
4301
|
-
var pdOpenAI = document.getElementById("pd-openai");
|
|
4302
|
-
if (pdOpenAI) {
|
|
4303
|
-
pdOpenAI.textContent = isOpenAILoggedIn
|
|
4304
|
-
? ("Codex logged in" + (openaiEmail ? " \u00b7 " + openaiEmail : ""))
|
|
4305
|
-
: "OpenAI Codex \u00b7 Uses your current codex login";
|
|
4306
|
-
}
|
|
4307
|
-
// Update Gemini card description in login screen
|
|
4308
|
-
var pdGemini = document.getElementById("pd-gemini");
|
|
4309
|
-
if (pdGemini) {
|
|
4310
|
-
pdGemini.textContent = geminiCliLoggedIn
|
|
4311
|
-
? ("Gemini CLI logged in" + (geminiEmail ? " \u00b7 " + geminiEmail : "") + " \u00b7 Subscription")
|
|
4312
|
-
: "Google AI \u00b7 Subscription or API key";
|
|
4313
|
-
}
|
|
4314
|
-
// If Gemini CLI just became authenticated, refresh the API key expand state
|
|
4315
|
-
if (selectedLoginProvider === "gemini") {
|
|
4316
|
-
selectLoginProvider("gemini");
|
|
4317
|
-
}
|
|
4318
|
-
// Restore design system state on reconnect
|
|
4319
|
-
if (msg.activeDesignSystemId) {
|
|
4320
|
-
var found = null;
|
|
4321
|
-
for (var di = 0; di < DESIGN_SYSTEMS_UI.length; di++) {
|
|
4322
|
-
if (DESIGN_SYSTEMS_UI[di].id === msg.activeDesignSystemId) { found = DESIGN_SYSTEMS_UI[di]; break; }
|
|
4323
|
-
}
|
|
4324
|
-
if (found && (!activeDesignSystem || activeDesignSystem.id !== found.id)) {
|
|
4325
|
-
activeDesignSystem = found;
|
|
4326
|
-
renderDesignSystemChip();
|
|
4327
|
-
var dsBtn = document.getElementById("style-guide-btn");
|
|
4328
|
-
if (dsBtn) dsBtn.classList.add("sg-btn-active");
|
|
4329
|
-
}
|
|
4330
|
-
} else if (activeDesignSystem && !msg.activeDesignSystemId) {
|
|
4331
|
-
activeDesignSystem = null;
|
|
4332
|
-
renderDesignSystemChip();
|
|
4333
|
-
var dsBtn2 = document.getElementById("style-guide-btn");
|
|
4334
|
-
if (dsBtn2) dsBtn2.classList.remove("sg-btn-active");
|
|
4335
|
-
}
|
|
4336
|
-
// Restore knowledge sources from bridge-status
|
|
4337
|
-
if (msg.knowledgeSources && msg.knowledgeSources.length > 0) {
|
|
4338
|
-
ksSources = msg.knowledgeSources;
|
|
4339
|
-
renderKsList();
|
|
4340
|
-
updateKsBadge();
|
|
4341
|
-
}
|
|
4342
|
-
|
|
4343
|
-
// Restore reference sites from bridge-status
|
|
4344
|
-
if (msg.referenceSites) {
|
|
4345
|
-
renderWebRefList(msg.referenceSites);
|
|
4346
|
-
}
|
|
4347
|
-
|
|
4348
|
-
// Track fast chat availability
|
|
4349
|
-
hasFastChat = !!(msg.hasAnthropicKey);
|
|
4350
|
-
updateFastChatIndicator();
|
|
4351
|
-
|
|
4352
|
-
updateStatus();
|
|
4353
|
-
return;
|
|
4354
|
-
}
|
|
4355
|
-
|
|
4356
|
-
// ── Knowledge source messages ────────────────────────────────────
|
|
4357
|
-
if (msg.type === "content-added" || msg.type === "content-removed" ||
|
|
4358
|
-
msg.type === "content-error" || msg.type === "content-list") {
|
|
4359
|
-
handleKsMessage(msg);
|
|
4360
|
-
return;
|
|
4361
|
-
}
|
|
4362
|
-
|
|
4363
|
-
// ── Knowledge Hub messages ──────────────────────────────────────
|
|
4364
|
-
if (msg.type === "hub-catalog" || msg.type === "hub-search-results") {
|
|
4365
|
-
renderHubCatalog(msg.files || []);
|
|
4366
|
-
// Auto-open the knowledge panel on hub tab
|
|
4367
|
-
if (!ksPanelOpen) {
|
|
4368
|
-
ksPanelOpen = true;
|
|
4369
|
-
var panel = document.getElementById("ks-panel");
|
|
4370
|
-
if (panel) panel.classList.add("open");
|
|
4371
|
-
switchKsTab("hub");
|
|
4372
|
-
}
|
|
4373
|
-
return;
|
|
4374
|
-
}
|
|
4375
|
-
if (msg.type === "hub-auto-loaded") {
|
|
4376
|
-
// Show a chat-like notification that a hub file was auto-loaded
|
|
4377
|
-
showKsSuccess("\ud83d\udcda Loaded \"" + msg.title + "\" from Knowledge Hub" +
|
|
4378
|
-
(msg.totalMatches > 1 ? " (" + (msg.totalMatches - 1) + " more match" + (msg.totalMatches > 2 ? "es" : "") + " available)" : ""));
|
|
4379
|
-
return;
|
|
4380
|
-
}
|
|
4381
|
-
|
|
4382
|
-
// ── Web Reference Site messages ─────────────────────────────────
|
|
4383
|
-
if (msg.type === "reference-site-added") {
|
|
4384
|
-
showKsSuccess("Added reference site: " + (msg.site?.name || ""));
|
|
4385
|
-
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: "list-reference-sites" }));
|
|
4386
|
-
return;
|
|
4387
|
-
}
|
|
4388
|
-
if (msg.type === "reference-site-removed") {
|
|
4389
|
-
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: "list-reference-sites" }));
|
|
4390
|
-
return;
|
|
4391
|
-
}
|
|
4392
|
-
if (msg.type === "reference-sites-list") {
|
|
4393
|
-
renderWebRefList(msg.sites || []);
|
|
4394
|
-
return;
|
|
4395
|
-
}
|
|
4396
|
-
|
|
4397
|
-
// ── Stitch Google OAuth status ──────────────────────────────────
|
|
4398
|
-
if (msg.type === "stitch-auth-status") {
|
|
4399
|
-
var statusEl = document.getElementById("stitch-auth-status");
|
|
4400
|
-
var btn = document.getElementById("stitch-google-signin-btn");
|
|
4401
|
-
if (msg.status === "success") {
|
|
4402
|
-
stitchGoogleAuthed = true;
|
|
4403
|
-
if (statusEl) { statusEl.style.color = "#4ade80"; statusEl.textContent = "Signed in" + (msg.email ? " as " + msg.email : ""); }
|
|
4404
|
-
if (btn) { btn.textContent = "Signed in with Google"; btn.style.background = "#166534"; btn.disabled = true; btn.style.opacity = "1"; }
|
|
4405
|
-
updateLoginContinue();
|
|
4406
|
-
} else if (msg.status === "error") {
|
|
4407
|
-
if (statusEl) { statusEl.style.color = "#f87171"; statusEl.textContent = msg.error || "Sign-in failed"; }
|
|
4408
|
-
if (btn) { btn.disabled = false; btn.style.opacity = "1"; }
|
|
4409
|
-
} else if (msg.status === "signing-in") {
|
|
4410
|
-
if (statusEl) { statusEl.style.color = "#888"; statusEl.textContent = "Waiting for Google sign-in..."; }
|
|
4411
|
-
}
|
|
4412
|
-
return;
|
|
4413
|
-
}
|
|
4414
|
-
|
|
4415
|
-
// ── Provider stored confirmation ─────────────────────────────────
|
|
4416
|
-
if (msg.type === "provider-stored") {
|
|
4417
|
-
if (msg.provider && PROVIDER_META[msg.provider] && msg.provider !== selectedLoginProvider) {
|
|
4418
|
-
selectedLoginProvider = msg.provider;
|
|
4419
|
-
updateProviderBadge();
|
|
4420
|
-
renderThreadForProvider(selectedLoginProvider);
|
|
4421
|
-
}
|
|
4422
|
-
updateStatus();
|
|
4423
|
-
return; // acknowledged
|
|
4424
|
-
}
|
|
4425
|
-
|
|
4426
|
-
// ── VS Code connection status ────────────────────────────────────
|
|
4427
|
-
if (msg.type === "vscode-connected") {
|
|
4428
|
-
isVscodeConnected = msg.connected;
|
|
4429
|
-
updateVscodeStatus();
|
|
4430
|
-
return;
|
|
4431
|
-
}
|
|
4432
|
-
|
|
4433
|
-
// ── MCP tool request (forward to plugin sandbox) ─────────────────
|
|
4434
|
-
if (msg.type === "bridge-request") {
|
|
4435
|
-
parent.postMessage({
|
|
4436
|
-
pluginMessage: {
|
|
4437
|
-
type: "bridge-request",
|
|
4438
|
-
id: msg.id,
|
|
4439
|
-
method: msg.method,
|
|
4440
|
-
params: msg.params || {},
|
|
4441
|
-
},
|
|
4442
|
-
}, "*");
|
|
4443
|
-
return;
|
|
4444
|
-
}
|
|
4445
|
-
|
|
4446
|
-
// ── Chat streaming events ────────────────────────────────────────
|
|
4447
|
-
if (msg.type === "phase_start") {
|
|
4448
|
-
if (isStreaming && currentMsgEl) {
|
|
4449
|
-
appendActivityLine("phase",
|
|
4450
|
-
'<strong>' + escapeHtml(msg.phase || "") + '</strong>',
|
|
4451
|
-
Date.now()
|
|
4452
|
-
);
|
|
4453
|
-
}
|
|
4454
|
-
return;
|
|
4455
|
-
}
|
|
4456
|
-
|
|
4457
|
-
if (msg.type === "text_delta") {
|
|
4458
|
-
appendTextDelta(msg.delta);
|
|
4459
|
-
return;
|
|
4460
|
-
}
|
|
4461
|
-
|
|
4462
|
-
if (msg.type === "tool_start") {
|
|
4463
|
-
addToolStart(msg.tool);
|
|
4464
|
-
return;
|
|
4465
|
-
}
|
|
4466
|
-
|
|
4467
|
-
if (msg.type === "tool_done") {
|
|
4468
|
-
resolveToolPill(msg.tool, !!msg.isError);
|
|
4469
|
-
return;
|
|
4470
|
-
}
|
|
4471
|
-
|
|
4472
|
-
if (msg.type === "citations") {
|
|
4473
|
-
if (msg.urls && msg.urls.length > 0) {
|
|
4474
|
-
pendingCitations = msg.urls;
|
|
4475
|
-
}
|
|
4476
|
-
return;
|
|
4477
|
-
}
|
|
4478
|
-
|
|
4479
|
-
if (msg.type === "done") {
|
|
4480
|
-
finalizeAssistantMessage();
|
|
4481
|
-
endStreaming();
|
|
4482
|
-
return;
|
|
4483
|
-
}
|
|
4484
|
-
|
|
4485
|
-
if (msg.type === "error") {
|
|
4486
|
-
showError(msg.error || "Something went wrong.");
|
|
4487
|
-
endStreaming();
|
|
4488
|
-
return;
|
|
4489
|
-
}
|
|
4490
|
-
|
|
4491
|
-
};
|
|
4492
|
-
}
|
|
4493
|
-
|
|
4494
|
-
/* ── Messages from Plugin Sandbox (code.js) ──────────────────────────── */
|
|
4495
|
-
window.onmessage = function(event) {
|
|
4496
|
-
var msg = event.data && event.data.pluginMessage;
|
|
4497
|
-
if (!msg) return;
|
|
4498
|
-
|
|
4499
|
-
// Forward bridge events to relay
|
|
4500
|
-
if (msg.type === "bridge-event") {
|
|
4501
|
-
if (ws && ws.readyState === WebSocket.OPEN && isMcpConnected) {
|
|
4502
|
-
ws.send(JSON.stringify({
|
|
4503
|
-
type: "bridge-event",
|
|
4504
|
-
eventType: msg.eventType,
|
|
4505
|
-
payload: msg.payload,
|
|
4506
|
-
timestamp: msg.timestamp || Date.now(),
|
|
4507
|
-
}));
|
|
4508
|
-
}
|
|
4509
|
-
return;
|
|
4510
|
-
}
|
|
4511
|
-
|
|
4512
|
-
if (msg.type === "agent-activity") {
|
|
4513
|
-
appendAgentActivity(msg);
|
|
4514
|
-
return;
|
|
4515
|
-
}
|
|
4516
|
-
|
|
4517
|
-
// Forward MCP tool responses to relay
|
|
4518
|
-
if (msg.type === "bridge-response") {
|
|
4519
|
-
if (msg.resultBytes) {
|
|
4520
|
-
var bytes = new Uint8Array(msg.resultBytes);
|
|
4521
|
-
var binary = "";
|
|
4522
|
-
for (var i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
4523
|
-
msg.result = "data:image/png;base64," + btoa(binary);
|
|
4524
|
-
delete msg.resultBytes;
|
|
4525
|
-
}
|
|
4526
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
4527
|
-
ws.send(JSON.stringify({ id: msg.id, result: msg.result, error: msg.error }));
|
|
4528
|
-
}
|
|
4529
|
-
}
|
|
4530
|
-
};
|
|
4531
|
-
|
|
4532
|
-
/* ── Init ─────────────────────────────────────────────────────────────── */
|
|
4533
|
-
initFileAttach();
|
|
4534
|
-
renderProviderLogos();
|
|
4535
|
-
initLoginScreen();
|
|
4536
|
-
initPluginSize();
|
|
4537
|
-
initPluginResizeHandles();
|
|
4538
|
-
updateStatus();
|
|
4539
|
-
connect();
|
|
4540
|
-
</script>
|
|
4541
|
-
</body>
|
|
4542
|
-
</html>
|