@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.
Files changed (297) hide show
  1. package/README.md +69 -36
  2. package/dist/bin/cli.js +2 -0
  3. package/dist/design-bridge/bridge.js +2 -0
  4. package/dist/figma-bridge-plugin/bridge-relay.js +2 -0
  5. package/dist/figma-bridge-plugin/code.js +1 -0
  6. package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package-lock.json +0 -3
  7. package/dist/figma-bridge-plugin/ui.html +4970 -0
  8. package/dist/figma-intelligence-layer/dist/index.js +2 -0
  9. package/dist/scripts/clean-existing-chunks.js +2 -0
  10. package/dist/scripts/connect-ai-tool.js +2 -0
  11. package/dist/scripts/convert-hub-pdfs.js +2 -0
  12. package/dist/scripts/figma-mcp-status.js +2 -0
  13. package/dist/scripts/register-codex-mcp.js +2 -0
  14. package/dist/scripts/test-copilot-chat.js +2 -0
  15. package/package.json +11 -8
  16. package/bin/cli.js +0 -859
  17. package/design-bridge/bridge.js +0 -196
  18. package/design-bridge/lib/assets.js +0 -367
  19. package/design-bridge/lib/prompt.js +0 -85
  20. package/design-bridge/lib/server.js +0 -66
  21. package/design-bridge/lib/stitch.js +0 -37
  22. package/design-bridge/lib/tokens.js +0 -82
  23. package/design-bridge/package-lock.json +0 -579
  24. package/figma-bridge-plugin/README.md +0 -97
  25. package/figma-bridge-plugin/anthropic-chat-runner.js +0 -192
  26. package/figma-bridge-plugin/bridge-relay.js +0 -2505
  27. package/figma-bridge-plugin/chat-runner.js +0 -485
  28. package/figma-bridge-plugin/code.js +0 -1534
  29. package/figma-bridge-plugin/codex-runner.js +0 -505
  30. package/figma-bridge-plugin/component-schemas.js +0 -110
  31. package/figma-bridge-plugin/content-context.js +0 -869
  32. package/figma-bridge-plugin/create-button.js +0 -216
  33. package/figma-bridge-plugin/gemini-cli-runner.js +0 -291
  34. package/figma-bridge-plugin/gemini-runner.js +0 -187
  35. package/figma-bridge-plugin/html-to-figma.js +0 -927
  36. package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
  37. package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +0 -159
  38. package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +0 -162
  39. package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +0 -148
  40. package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +0 -314
  41. package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +0 -175
  42. package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +0 -180
  43. package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +0 -165
  44. package/figma-bridge-plugin/perplexity-runner.js +0 -188
  45. package/figma-bridge-plugin/references/SKILL.md +0 -178
  46. package/figma-bridge-plugin/references/anatomy-spec.md +0 -159
  47. package/figma-bridge-plugin/references/api-spec.md +0 -162
  48. package/figma-bridge-plugin/references/color-spec.md +0 -148
  49. package/figma-bridge-plugin/references/full-spec-template.md +0 -314
  50. package/figma-bridge-plugin/references/property-spec.md +0 -175
  51. package/figma-bridge-plugin/references/screen-reader-spec.md +0 -180
  52. package/figma-bridge-plugin/references/structure-spec.md +0 -165
  53. package/figma-bridge-plugin/shared-prompt-config.js +0 -645
  54. package/figma-bridge-plugin/spec-helpers/build-table.js +0 -269
  55. package/figma-bridge-plugin/spec-helpers/classify-elements.js +0 -189
  56. package/figma-bridge-plugin/spec-helpers/index.js +0 -35
  57. package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +0 -49
  58. package/figma-bridge-plugin/spec-helpers/position-markers.js +0 -158
  59. package/figma-bridge-plugin/stitch-auth.js +0 -322
  60. package/figma-bridge-plugin/stitch-runner.js +0 -1427
  61. package/figma-bridge-plugin/token-resolver.js +0 -107
  62. package/figma-bridge-plugin/ui.html +0 -4542
  63. package/figma-intelligence-layer/.env.example +0 -39
  64. package/figma-intelligence-layer/docs/local-image-generation.md +0 -60
  65. package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +0 -101
  66. package/figma-intelligence-layer/jest.config.js +0 -14
  67. package/figma-intelligence-layer/mcp-config.json +0 -19
  68. package/figma-intelligence-layer/package-lock.json +0 -5892
  69. package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +0 -67
  70. package/figma-intelligence-layer/scripts/start-comfyui.sh +0 -33
  71. package/figma-intelligence-layer/src/index.ts +0 -2233
  72. package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +0 -404
  73. package/figma-intelligence-layer/src/shared/cache.ts +0 -187
  74. package/figma-intelligence-layer/src/shared/color-operations.ts +0 -533
  75. package/figma-intelligence-layer/src/shared/color-utils.ts +0 -138
  76. package/figma-intelligence-layer/src/shared/component-script-builder.ts +0 -413
  77. package/figma-intelligence-layer/src/shared/component-templates.ts +0 -2767
  78. package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +0 -694
  79. package/figma-intelligence-layer/src/shared/decision-log.ts +0 -128
  80. package/figma-intelligence-layer/src/shared/design-system-context.ts +0 -568
  81. package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +0 -131
  82. package/figma-intelligence-layer/src/shared/design-system-matcher.ts +0 -184
  83. package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +0 -196
  84. package/figma-intelligence-layer/src/shared/design-system-tokens.ts +0 -295
  85. package/figma-intelligence-layer/src/shared/dtcg-validator.ts +0 -530
  86. package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +0 -671
  87. package/figma-intelligence-layer/src/shared/figma-bridge.ts +0 -1418
  88. package/figma-intelligence-layer/src/shared/font-config.ts +0 -126
  89. package/figma-intelligence-layer/src/shared/icon-catalog.ts +0 -360
  90. package/figma-intelligence-layer/src/shared/icon-fetch.ts +0 -80
  91. package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +0 -162
  92. package/figma-intelligence-layer/src/shared/response-compression.ts +0 -440
  93. package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +0 -324
  94. package/figma-intelligence-layer/src/shared/token-binder.ts +0 -505
  95. package/figma-intelligence-layer/src/shared/token-math.ts +0 -427
  96. package/figma-intelligence-layer/src/shared/token-naming.ts +0 -468
  97. package/figma-intelligence-layer/src/shared/token-utils.ts +0 -420
  98. package/figma-intelligence-layer/src/shared/types.ts +0 -346
  99. package/figma-intelligence-layer/src/shared/typography-presets.ts +0 -94
  100. package/figma-intelligence-layer/src/shared/unsplash.ts +0 -165
  101. package/figma-intelligence-layer/src/shared/vision-client.ts +0 -607
  102. package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +0 -334
  103. package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +0 -446
  104. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +0 -782
  105. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +0 -496
  106. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +0 -230
  107. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +0 -66
  108. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +0 -810
  109. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +0 -1191
  110. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +0 -1346
  111. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +0 -148
  112. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +0 -499
  113. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +0 -910
  114. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +0 -989
  115. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +0 -1160
  116. package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +0 -424
  117. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +0 -38
  118. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +0 -111
  119. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +0 -114
  120. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +0 -103
  121. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +0 -1060
  122. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +0 -18
  123. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +0 -39
  124. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +0 -58
  125. package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +0 -298
  126. package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +0 -197
  127. package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +0 -494
  128. package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +0 -356
  129. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +0 -123
  130. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +0 -663
  131. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +0 -56
  132. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +0 -614
  133. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +0 -113
  134. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +0 -178
  135. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +0 -470
  136. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +0 -429
  137. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +0 -226
  138. package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +0 -535
  139. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +0 -660
  140. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +0 -209
  141. package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +0 -540
  142. package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +0 -391
  143. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +0 -2019
  144. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +0 -131
  145. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +0 -381
  146. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +0 -565
  147. package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +0 -764
  148. package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +0 -535
  149. package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +0 -84
  150. package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +0 -401
  151. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +0 -68
  152. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +0 -78
  153. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +0 -93
  154. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +0 -596
  155. package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +0 -462
  156. package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +0 -1470
  157. package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +0 -829
  158. package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +0 -702
  159. package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +0 -483
  160. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +0 -501
  161. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +0 -106
  162. package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +0 -676
  163. package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +0 -560
  164. package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +0 -1043
  165. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +0 -620
  166. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +0 -331
  167. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +0 -77
  168. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +0 -54
  169. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +0 -287
  170. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +0 -71
  171. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +0 -43
  172. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +0 -71
  173. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +0 -221
  174. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +0 -166
  175. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +0 -232
  176. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +0 -234
  177. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +0 -270
  178. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +0 -249
  179. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +0 -231
  180. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +0 -293
  181. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +0 -240
  182. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +0 -243
  183. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +0 -307
  184. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +0 -143
  185. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +0 -227
  186. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +0 -233
  187. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +0 -282
  188. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +0 -276
  189. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +0 -223
  190. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +0 -255
  191. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +0 -289
  192. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +0 -261
  193. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +0 -290
  194. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +0 -265
  195. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +0 -238
  196. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +0 -255
  197. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +0 -128
  198. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +0 -286
  199. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +0 -255
  200. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +0 -330
  201. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +0 -247
  202. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +0 -250
  203. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +0 -247
  204. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +0 -144
  205. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +0 -264
  206. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +0 -251
  207. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +0 -261
  208. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +0 -248
  209. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +0 -270
  210. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +0 -251
  211. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +0 -142
  212. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +0 -282
  213. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +0 -250
  214. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +0 -258
  215. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +0 -265
  216. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +0 -319
  217. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +0 -256
  218. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +0 -232
  219. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +0 -239
  220. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +0 -252
  221. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +0 -270
  222. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +0 -244
  223. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +0 -143
  224. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +0 -243
  225. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +0 -259
  226. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +0 -293
  227. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +0 -144
  228. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +0 -289
  229. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +0 -267
  230. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +0 -232
  231. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +0 -257
  232. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +0 -319
  233. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +0 -121
  234. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +0 -430
  235. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +0 -312
  236. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +0 -129
  237. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +0 -78
  238. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +0 -2333
  239. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +0 -100
  240. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +0 -32
  241. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +0 -59
  242. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +0 -18
  243. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +0 -53
  244. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +0 -19
  245. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +0 -91
  246. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +0 -71
  247. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +0 -19
  248. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +0 -110
  249. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +0 -19
  250. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +0 -67
  251. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +0 -58
  252. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +0 -79
  253. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +0 -50
  254. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +0 -33
  255. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +0 -55
  256. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +0 -73
  257. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +0 -81
  258. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +0 -409
  259. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +0 -198
  260. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +0 -701
  261. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +0 -88
  262. package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +0 -135
  263. package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +0 -491
  264. package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +0 -416
  265. package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +0 -722
  266. package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +0 -449
  267. package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +0 -393
  268. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +0 -406
  269. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +0 -292
  270. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +0 -24
  271. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +0 -172
  272. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +0 -409
  273. package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +0 -594
  274. package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +0 -710
  275. package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +0 -458
  276. package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +0 -134
  277. package/figma-intelligence-layer/tests/apg-doc.test.ts +0 -101
  278. package/figma-intelligence-layer/tests/design-system-context.test.ts +0 -152
  279. package/figma-intelligence-layer/tests/design-system-matcher.test.ts +0 -144
  280. package/figma-intelligence-layer/tests/figma-bridge.test.ts +0 -83
  281. package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +0 -56
  282. package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +0 -69
  283. package/figma-intelligence-layer/tests/smoke.test.ts +0 -174
  284. package/figma-intelligence-layer/tests/spec-generator.test.ts +0 -127
  285. package/figma-intelligence-layer/tests/token-migrate.test.ts +0 -21
  286. package/figma-intelligence-layer/tests/token-naming.test.ts +0 -30
  287. package/figma-intelligence-layer/tsconfig.json +0 -19
  288. package/scripts/clean-existing-chunks.js +0 -179
  289. package/scripts/connect-ai-tool.js +0 -490
  290. package/scripts/convert-hub-pdfs.js +0 -425
  291. package/scripts/figma-mcp-status.js +0 -349
  292. package/scripts/register-codex-mcp.js +0 -96
  293. /package/{design-bridge → dist/design-bridge}/.env.example +0 -0
  294. /package/{design-bridge → dist/design-bridge}/package.json +0 -0
  295. /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/manifest.json +0 -0
  296. /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package.json +0 -0
  297. /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 &mdash; 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 &middot; Subscription required</div>
1543
- </div>
1544
- <div class="provider-check">&#10003;</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 &middot; Subscription required</div>
1553
- </div>
1554
- <div class="provider-check">&#10003;</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 &middot; Subscription or API key</div>
1563
- </div>
1564
- <div class="provider-check">&#10003;</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 &middot; API key required</div>
1579
- </div>
1580
- <div class="provider-check">&#10003;</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 &middot; Access token required</div>
1594
- </div>
1595
- <div class="provider-check">&#10003;</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 &middot; Cursor &middot; Antigravity &middot; More</div>
1610
- </div>
1611
- <div class="provider-check">&#10003;</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&hellip;"
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 &amp; 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">&times;</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 &mdash; 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 = '&#10003; MCP bridge active &middot; <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
- ? '&#10003; OpenAI Codex subscription active' +
3461
- (openaiEmail ? ' &middot; <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
- ? '&#10003; Gemini subscription active &middot; Google One AI Premium'
3468
- : loginApiKey
3469
- ? '&#10003; Gemini connected &middot; API key'
3470
- : '<span style="color:rgba(218,119,86,0.75)">Gemini not configured &mdash; <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 = '&#10003; Claude subscription active' +
3474
- (claudeEmail ? ' &middot; <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, "&amp;")
3536
- .replace(/</g, "&lt;")
3537
- .replace(/>/g, "&gt;")
3538
- .replace(/"/g, "&quot;");
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> &middot; ' + escapeHtml(status),
3783
- msg.timestamp
3784
- );
3785
- return;
3786
- }
3787
- appendActivityLine(
3788
- "phase",
3789
- '<strong>' + escapeHtml(agent) + '</strong> &middot; ' + 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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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>