cardbuddy 0.11.0 → 0.12.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 (313) hide show
  1. package/README.md +46 -5
  2. package/dist/cli.js +51 -1
  3. package/dist/config.js +51 -16
  4. package/dist/core.js +2 -2
  5. package/dist/init.js +425 -0
  6. package/dist/preview-editor/assets/index-CHuGGIR3.js +276 -0
  7. package/dist/preview-editor/assets/{index-B4VZgNSK.css → index-CQScMhyg.css} +1 -1
  8. package/dist/preview-editor/index.html +2 -2
  9. package/dist/standalone.js +8 -0
  10. package/dist/vendored/mcp/client.mjs +537 -0
  11. package/dist/vendored/mcp/tools.mjs +268 -0
  12. package/dist/vendored/preview/assets/default-image.png +0 -0
  13. package/dist/{server-core → vendored/server/core}/ai-chat.js +82 -38
  14. package/dist/{server-core → vendored/server/core}/browser.js +1 -1
  15. package/dist/{server-core → vendored/server/core}/cardSave.js +8 -3
  16. package/dist/{server-core → vendored/server/core}/cardSync.js +6 -14
  17. package/dist/{server-core → vendored/server/core}/cardValidate.js +11 -8
  18. package/dist/{server-core → vendored/server/core}/index.js +5 -3
  19. package/dist/vendored/server/core/skillPaths.js +124 -0
  20. package/dist/{server-core → vendored/server/core}/sliceUpload.js +5 -8
  21. package/dist/vendored/skills/README.md +93 -0
  22. package/dist/vendored/skills/card-doctor/SKILL.md +52 -0
  23. package/dist/vendored/skills/card-skill-trainer/SKILL.md +312 -0
  24. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r038n.json +43 -0
  25. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r039n.json +47 -0
  26. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r050n.json +48 -0
  27. package/dist/vendored/skills/card-skill-trainer/scripts/build_dataset.py +472 -0
  28. package/dist/vendored/skills/card-skill-trainer/scripts/build_sample.mjs +162 -0
  29. package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +241 -0
  30. package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +525 -0
  31. package/dist/vendored/skills/card-skill-trainer/scripts/round_diff.mjs +133 -0
  32. package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +1043 -0
  33. package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +195 -0
  34. package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +122 -0
  35. package/dist/vendored/skills/dsl-card-ai/SKILL.md +191 -0
  36. package/dist/vendored/skills/dsl-card-generator/SKILL.md +480 -0
  37. package/dist/vendored/skills/dsl-card-generator/config/render-contract.json +11 -0
  38. package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +138 -0
  39. package/dist/vendored/skills/dsl-card-generator/references/card_index.json +1597 -0
  40. package/dist/vendored/skills/dsl-card-generator/references/card_templates.json +384 -0
  41. package/dist/vendored/skills/dsl-card-generator/references/card_to_summary.md +56 -0
  42. package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +76 -0
  43. package/dist/vendored/skills/dsl-card-generator/references/component_specs.md +393 -0
  44. package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +208 -0
  45. package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +287 -0
  46. package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +121 -0
  47. package/dist/vendored/skills/dsl-card-generator/references/field_name_dict.json +34 -0
  48. package/dist/vendored/skills/dsl-card-generator/references/field_naming.md +101 -0
  49. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +928 -0
  50. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/README.md +69 -0
  51. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/examples_index.json +2967 -0
  52. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/patterns.json +2835 -0
  53. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2.json +648 -0
  54. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2_schema.md +295 -0
  55. package/dist/vendored/skills/dsl-card-generator/references/image_diff.md +48 -0
  56. package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +129 -0
  57. package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +212 -0
  58. package/dist/vendored/skills/dsl-card-generator/references/message-center-pattern.md +149 -0
  59. package/dist/vendored/skills/dsl-card-generator/references/platform_jsapi.md +196 -0
  60. package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +54 -0
  61. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +197 -0
  62. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/absolute-badge.json +84 -0
  63. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/accordion-group-toggle.json +18 -0
  64. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertCard-pair.json +12 -0
  65. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertDialog-call.json +12 -0
  66. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-countdown.json +15 -0
  67. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-donut.json +15 -0
  68. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-gauge-score.json +11 -0
  69. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-radar.json +10 -0
  70. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-title-head.json +13 -0
  71. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/globalSet.json +12 -0
  72. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/label-col-modes.json +21 -0
  73. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/list-expand-collapse.json +17 -0
  74. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/money-format.json +11 -0
  75. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/opacity-switch.json +130 -0
  76. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/progress-bar.json +25 -0
  77. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/tab-billMode.json +15 -0
  78. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +21 -0
  79. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +797 -0
  80. package/dist/vendored/skills/dsl-card-generator/references/rules.md +235 -0
  81. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +177 -0
  82. package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +609 -0
  83. package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +131 -0
  84. package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +118 -0
  85. package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +153 -0
  86. package/dist/vendored/skills/dsl-card-generator/scripts/apply_opacity_pattern.mjs +343 -0
  87. package/dist/vendored/skills/dsl-card-generator/scripts/build_card_index.mjs +108 -0
  88. package/dist/vendored/skills/dsl-card-generator/scripts/build_card_templates.mjs +190 -0
  89. package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +257 -0
  90. package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +79 -0
  91. package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +98 -0
  92. package/dist/vendored/skills/dsl-card-generator/scripts/describe_card.mjs +265 -0
  93. package/dist/vendored/skills/dsl-card-generator/scripts/diff_bands.mjs +257 -0
  94. package/dist/vendored/skills/dsl-card-generator/scripts/diff_card.mjs +316 -0
  95. package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +873 -0
  96. package/dist/vendored/skills/dsl-card-generator/scripts/extract_handcrafted.py +1199 -0
  97. package/dist/vendored/skills/dsl-card-generator/scripts/extract_subtree.mjs +191 -0
  98. package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +389 -0
  99. package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +160 -0
  100. package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +700 -0
  101. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_image.mjs +56 -0
  102. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_sample.mjs +328 -0
  103. package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +79 -0
  104. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +103 -0
  105. package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +575 -0
  106. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +81 -0
  107. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +147 -0
  108. package/dist/vendored/skills/dsl-card-generator/scripts/lint_autofix.mjs +363 -0
  109. package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +1399 -0
  110. package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +249 -0
  111. package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +208 -0
  112. package/dist/vendored/skills/dsl-card-generator/scripts/render_baseline.mjs +231 -0
  113. package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +801 -0
  114. package/dist/vendored/skills/dsl-card-generator/scripts/render_diff.mjs +239 -0
  115. package/dist/vendored/skills/dsl-card-generator/scripts/resolve_card_template.mjs +73 -0
  116. package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +447 -0
  117. package/dist/vendored/skills/dsl-card-generator/scripts/scaffold_card.mjs +81 -0
  118. package/dist/vendored/skills/dsl-card-generator/scripts/slices_normalize.mjs +316 -0
  119. package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +462 -0
  120. package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +7735 -0
  121. package/dist/vendored/skills/dsl-card-generator/scripts/spec_tree.mjs +355 -0
  122. package/dist/vendored/skills/dsl-card-generator/scripts/validate_all_schemas.py +71 -0
  123. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/default-image-wzZYQ86K.png +0 -0
  124. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-C9k-07y7.css +1 -0
  125. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-oFRM4f4V.js +55 -0
  126. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/svg-symbols-B2exBrAr.svg +118 -0
  127. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/component-meta.json +2714 -0
  128. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/directive-meta.json +40 -0
  129. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/style-meta.json +1086 -0
  130. package/dist/vendored/{standalone.html → skills/dsl-card-generator/vendor/preview/standalone.html} +2 -1
  131. package/dist/vendored/skills/finmall-card-sync/SKILL.md +262 -0
  132. package/dist/vendored/skills/finmall-session-navigator/SKILL.md +169 -0
  133. package/dist/vendored/skills/finmall-session-navigator/scripts/finmall_login.mjs +882 -0
  134. package/dist/vendored/skills/finmall-session-navigator/scripts/package-lock.json +59 -0
  135. package/dist/vendored/skills/finmall-session-navigator/scripts/package.json +10 -0
  136. package/dist/vendored/skills/finmall-session-navigator/scripts/session-manager.mjs +96 -0
  137. package/dist/vendored/skills/shared/README.md +55 -0
  138. package/dist/vendored/skills/shared/auth-model.md +79 -0
  139. package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +329 -0
  140. package/dist/vendored/skills/sketch-spec-to-card/references/boundary.json +14 -0
  141. package/dist/vendored/skills/sketch-spec-to-card/references/card_refs.json +13 -0
  142. package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +103 -0
  143. package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +797 -0
  144. package/dist/vendored/skills/sketch-spec-to-card/scripts/card_refs.py +94 -0
  145. package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +706 -0
  146. package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +1781 -0
  147. package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +318 -0
  148. package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +1118 -0
  149. package/dist/vendored/templates/helloCard/data.json +10 -0
  150. package/dist/vendored/templates/helloCard/schema.json +238 -0
  151. package/dist/vendored/tools/link-skills.mjs +485 -0
  152. package/package.json +2 -1
  153. package/dist/preview-editor/assets/index-jl4YJzKv.js +0 -180
  154. package/dist/vendored/App.vue +0 -2858
  155. package/dist/vendored/ansi.js +0 -185
  156. package/dist/vendored/assets/apple-touch-icon.png +0 -0
  157. package/dist/vendored/assets/default-image.png +0 -0
  158. package/dist/vendored/assets/favicon-16.png +0 -0
  159. package/dist/vendored/assets/favicon-32.png +0 -0
  160. package/dist/vendored/assets/site-icon.png +0 -0
  161. package/dist/vendored/assets/svgIcons/icons.svg +0 -87
  162. package/dist/vendored/assets/svgIcons/index.js +0 -39
  163. package/dist/vendored/components/AiSettings.vue +0 -392
  164. package/dist/vendored/components/AlertCardInstance.vue +0 -58
  165. package/dist/vendored/components/AlertCardLayer.vue +0 -59
  166. package/dist/vendored/components/ApiCallPanel.vue +0 -127
  167. package/dist/vendored/components/ApiPayloadTree.vue +0 -201
  168. package/dist/vendored/components/AppToast.vue +0 -37
  169. package/dist/vendored/components/CardGallery.vue +0 -291
  170. package/dist/vendored/components/CardPreview.vue +0 -72
  171. package/dist/vendored/components/CardSelect.vue +0 -206
  172. package/dist/vendored/components/ChatComposer.vue +0 -413
  173. package/dist/vendored/components/ChatMessage.vue +0 -383
  174. package/dist/vendored/components/ChatPanel.vue +0 -937
  175. package/dist/vendored/components/ComponentPalette.vue +0 -301
  176. package/dist/vendored/components/ConfirmDialog.vue +0 -54
  177. package/dist/vendored/components/DatePickerOverlay.vue +0 -345
  178. package/dist/vendored/components/DebugPanel.vue +0 -1734
  179. package/dist/vendored/components/DeviceOverlay.vue +0 -70
  180. package/dist/vendored/components/Dropdown.vue +0 -193
  181. package/dist/vendored/components/EventEditor.vue +0 -273
  182. package/dist/vendored/components/HoverGuides.vue +0 -122
  183. package/dist/vendored/components/JsonTree.vue +0 -547
  184. package/dist/vendored/components/LogValue.vue +0 -52
  185. package/dist/vendored/components/MarkdownBody.vue +0 -64
  186. package/dist/vendored/components/NewCardDialog.vue +0 -132
  187. package/dist/vendored/components/NodeInspector.vue +0 -295
  188. package/dist/vendored/components/NumberStepper.vue +0 -125
  189. package/dist/vendored/components/PickerOverlay.vue +0 -131
  190. package/dist/vendored/components/PropControl.vue +0 -245
  191. package/dist/vendored/components/PropTree.vue +0 -128
  192. package/dist/vendored/components/RemotePreview.vue +0 -173
  193. package/dist/vendored/components/SelectionOverlay.vue +0 -105
  194. package/dist/vendored/components/StructureTree.vue +0 -1002
  195. package/dist/vendored/components/Switch.vue +0 -38
  196. package/dist/vendored/components/setters/SetBgPosition.vue +0 -64
  197. package/dist/vendored/components/setters/SetBgSize.vue +0 -90
  198. package/dist/vendored/components/setters/SetCode.vue +0 -198
  199. package/dist/vendored/components/setters/SetColor.vue +0 -244
  200. package/dist/vendored/components/setters/SetCustomSelect.vue +0 -58
  201. package/dist/vendored/components/setters/SetExpr.vue +0 -107
  202. package/dist/vendored/components/setters/SetGradient.vue +0 -125
  203. package/dist/vendored/components/setters/SetGrid9.vue +0 -68
  204. package/dist/vendored/components/setters/SetIconSelect.vue +0 -67
  205. package/dist/vendored/components/setters/SetIfForm.vue +0 -61
  206. package/dist/vendored/components/setters/SetImage.vue +0 -43
  207. package/dist/vendored/components/setters/SetList.vue +0 -139
  208. package/dist/vendored/components/setters/SetNumber.vue +0 -118
  209. package/dist/vendored/components/setters/SetNumberStyle.vue +0 -113
  210. package/dist/vendored/components/setters/SetPadding.vue +0 -77
  211. package/dist/vendored/components/setters/SetSegment.vue +0 -160
  212. package/dist/vendored/components/setters/SetSelect.vue +0 -51
  213. package/dist/vendored/components/setters/SetSwitch.vue +0 -49
  214. package/dist/vendored/components/setters/SetText.vue +0 -52
  215. package/dist/vendored/components/setters/props.js +0 -23
  216. package/dist/vendored/fidelity/baseline-messageOverview.json +0 -87
  217. package/dist/vendored/fidelity/calibration.json +0 -29
  218. package/dist/vendored/fidelity/fidelity-messageOverview-after.html +0 -114
  219. package/dist/vendored/fidelity/fidelity-messageOverview-after.json +0 -272
  220. package/dist/vendored/fidelity/fidelity-messageOverview.html +0 -185
  221. package/dist/vendored/fidelity/fidelity-messageOverview.json +0 -336
  222. package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +0 -262
  223. package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +0 -502
  224. package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +0 -201
  225. package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +0 -417
  226. package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +0 -125
  227. package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +0 -338
  228. package/dist/vendored/index.html +0 -16
  229. package/dist/vendored/main.js +0 -37
  230. package/dist/vendored/runtime/ai-api.js +0 -165
  231. package/dist/vendored/runtime/alert-card.js +0 -125
  232. package/dist/vendored/runtime/app-toast.js +0 -33
  233. package/dist/vendored/runtime/clipboard-node.js +0 -78
  234. package/dist/vendored/runtime/clipboard.js +0 -29
  235. package/dist/vendored/runtime/event-model.js +0 -300
  236. package/dist/vendored/runtime/hoverGuide.js +0 -31
  237. package/dist/vendored/runtime/jsapi-mock.js +0 -256
  238. package/dist/vendored/runtime/markdown.js +0 -140
  239. package/dist/vendored/runtime/node-model.js +0 -217
  240. package/dist/vendored/skills/.manifest.json +0 -1101
  241. package/dist/vendored/standalone-main.js +0 -36
  242. package/dist/vendored/styles/ai-chat.css +0 -1850
  243. package/dist/vendored/styles/app.css +0 -3648
  244. package/dist/vendored/styles/card-gallery.css +0 -285
  245. package/dist/vendored/styles/card-select.css +0 -288
  246. package/dist/vendored/styles/components.css +0 -383
  247. package/dist/vendored/styles/dropdown.css +0 -324
  248. package/dist/vendored/styles/json-tree.css +0 -308
  249. package/dist/vendored/styles/node-inspector.css +0 -800
  250. package/dist/vendored/styles/setters.css +0 -1050
  251. package/dist/vendored/styles/standalone.css +0 -27
  252. package/dist/vendored/styles/structure-tree.css +0 -460
  253. /package/dist/vendored/{api.js → preview/api.js} +0 -0
  254. /package/dist/vendored/{assets → preview/assets}/svg-symbols.svg +0 -0
  255. /package/dist/vendored/{components → preview/components}/AbsoluteLayout.vue +0 -0
  256. /package/dist/vendored/{components → preview/components}/Button.vue +0 -0
  257. /package/dist/vendored/{components → preview/components}/Canvas.vue +0 -0
  258. /package/dist/vendored/{components → preview/components}/CheckGroup.vue +0 -0
  259. /package/dist/vendored/{components → preview/components}/Column.vue +0 -0
  260. /package/dist/vendored/{components → preview/components}/CustomPlaceholder.vue +0 -0
  261. /package/dist/vendored/{components → preview/components}/Divider.vue +0 -0
  262. /package/dist/vendored/{components → preview/components}/GridLayout.vue +0 -0
  263. /package/dist/vendored/{components → preview/components}/Icon.vue +0 -0
  264. /package/dist/vendored/{components → preview/components}/Image.vue +0 -0
  265. /package/dist/vendored/{components → preview/components}/Input.vue +0 -0
  266. /package/dist/vendored/{components → preview/components}/ListLayout.vue +0 -0
  267. /package/dist/vendored/{components → preview/components}/Map.vue +0 -0
  268. /package/dist/vendored/{components → preview/components}/MultiColumn.vue +0 -0
  269. /package/dist/vendored/{components → preview/components}/MultiText.vue +0 -0
  270. /package/dist/vendored/{components → preview/components}/ProgressSlider.vue +0 -0
  271. /package/dist/vendored/{components → preview/components}/RadioButton.vue +0 -0
  272. /package/dist/vendored/{components → preview/components}/RenderTree.vue +0 -0
  273. /package/dist/vendored/{components → preview/components}/Root.vue +0 -0
  274. /package/dist/vendored/{components → preview/components}/Row.vue +0 -0
  275. /package/dist/vendored/{components → preview/components}/SvgCode.vue +0 -0
  276. /package/dist/vendored/{components → preview/components}/SvgSymbols.vue +0 -0
  277. /package/dist/vendored/{components → preview/components}/Text.vue +0 -0
  278. /package/dist/vendored/{components → preview/components}/Video.vue +0 -0
  279. /package/dist/vendored/{components → preview/components}/ViewContainer.vue +0 -0
  280. /package/dist/vendored/{components → preview/components}/base.js +0 -0
  281. /package/dist/vendored/{components → preview/components}/icons.js +0 -0
  282. /package/dist/vendored/{components → preview/components}/index.js +0 -0
  283. /package/dist/vendored/{meta → preview/meta}/component-meta.json +0 -0
  284. /package/dist/vendored/{meta → preview/meta}/directive-meta.json +0 -0
  285. /package/dist/vendored/{meta → preview/meta}/style-meta.json +0 -0
  286. /package/dist/vendored/{runtime → preview/runtime}/StyleParser.js +0 -0
  287. /package/dist/vendored/{runtime → preview/runtime}/adapter.js +0 -0
  288. /package/dist/vendored/{runtime → preview/runtime}/assets.js +0 -0
  289. /package/dist/vendored/{runtime → preview/runtime}/canvas-context.js +0 -0
  290. /package/dist/vendored/{runtime → preview/runtime}/card-runtime.js +0 -0
  291. /package/dist/vendored/{runtime → preview/runtime}/evaluate.js +0 -0
  292. /package/dist/vendored/{runtime → preview/runtime}/property.js +0 -0
  293. /package/dist/vendored/{runtime → preview/runtime}/selectionOverlay.js +0 -0
  294. /package/dist/vendored/{runtime → preview/runtime}/toPreview.js +0 -0
  295. /package/dist/{server-core → vendored/server/core}/assetProxy.js +0 -0
  296. /package/dist/{server-core → vendored/server/core}/bbox.js +0 -0
  297. /package/dist/{server-core → vendored/server/core}/buildDataFromSchema.js +0 -0
  298. /package/dist/{server-core → vendored/server/core}/cardFiles.js +0 -0
  299. /package/dist/{server-core → vendored/server/core}/cardHistory.js +0 -0
  300. /package/dist/{server-core → vendored/server/core}/cardImage.js +0 -0
  301. /package/dist/{server-core → vendored/server/core}/cardImportMeta.js +0 -0
  302. /package/dist/{server-core → vendored/server/core}/cardListCache.js +0 -0
  303. /package/dist/{server-core → vendored/server/core}/cardOpenFolder.js +0 -0
  304. /package/dist/{server-core → vendored/server/core}/cardSidecar.js +0 -0
  305. /package/dist/{server-core → vendored/server/core}/codeBridge.js +0 -0
  306. /package/dist/{server-core → vendored/server/core}/logStream.js +0 -0
  307. /package/dist/{server-core → vendored/server/core}/logger.js +0 -0
  308. /package/dist/{server-core → vendored/server/core}/nodeModels.js +0 -0
  309. /package/dist/{server-core → vendored/server/core}/renderStatus.js +0 -0
  310. /package/dist/{server-core → vendored/server/core}/screenshot.js +0 -0
  311. /package/dist/{server-core → vendored/server/core}/sliceStatic.js +0 -0
  312. /package/dist/vendored/{meta → skills/dsl-card-generator/vendor/preview/meta}/action-meta.json +0 -0
  313. /package/dist/{server-core/_vendor → vendored/tools}/finmall-env.mjs +0 -0
@@ -1,937 +0,0 @@
1
- <template>
2
- <aside
3
- class="aichat"
4
- :class="{ 'is-open': visible }"
5
- :style="{ width: panelWidth + 'px' }"
6
- role="complementary"
7
- aria-label="AI 开发助手"
8
- @keydown.esc="onEscape"
9
- >
10
- <!-- 左缘拖拽把手:调宽(双击恢复默认 33%) -->
11
- <div
12
- class="aichat__grip"
13
- :class="{ 'is-dragging': resizing }"
14
- title="拖拽调整宽度(双击恢复默认)"
15
- @pointerdown.prevent="startResize"
16
- @dblclick.prevent="resetWidth"
17
- ></div>
18
-
19
- <!-- 头部:标题 + 当前卡片 / 模型 + 清空 / 设置 / 关闭 -->
20
- <header class="aichat__head">
21
- <span class="aichat__logo">
22
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-sparkles" /></svg>
23
- </span>
24
- <span class="aichat__titles">
25
- <span class="aichat__title">AI 开发助手</span>
26
- <span class="aichat__sub">
27
- <template v-if="cardId">{{ cardName || cardId }}</template>
28
- <template v-else>未选择卡片</template>
29
- <template v-if="config.model"> · {{ config.model }}</template>
30
- </span>
31
- </span>
32
- <span class="aichat__acts">
33
- <button type="button" class="aichat__iconbtn" title="清空本卡片对话记录" :disabled="!messages.length || busy" @click="askClear">
34
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-trash" /></svg>
35
- </button>
36
- <button type="button" class="aichat__iconbtn" title="模型设置" @click="openSettings">
37
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-settings" /></svg>
38
- </button>
39
- <button type="button" class="aichat__iconbtn" title="关闭(Esc)" @click="$emit('close')">
40
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-x" /></svg>
41
- </button>
42
- </span>
43
- </header>
44
-
45
- <!-- 未配置模型:常驻提示条,一键打开设置 -->
46
- <div v-if="!configReady" class="aichat__banner">
47
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-alert-triangle" /></svg>
48
- <span>尚未配置模型,无法对话。</span>
49
- <button type="button" class="aichat__banner-btn" @click="openSettings">去设置</button>
50
- </div>
51
-
52
- <!-- 消息区:滚动容器与悬浮「回到底部」共处一个定位上下文,按钮只贴消息区底缘,
53
- 输入区拖高 / 反馈条出现都不会把它盖住或悬空 -->
54
- <div class="aichat__scroll">
55
- <div ref="listEl" class="aichat__list" @scroll="onScroll">
56
- <div v-if="loadingHistory" class="aichat__loading">正在加载对话记录…</div>
57
-
58
- <div v-else-if="!messages.length" class="aichat__empty">
59
- <span class="aichat__empty-logo">
60
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-sparkles" /></svg>
61
- </span>
62
- <p class="aichat__empty-title">让 AI 直接改这张卡</p>
63
- <p class="aichat__empty-desc">
64
- 描述需求即可,AI 会调用卡片工具读改校验并截图自检;也可以把设计稿标注目录、参考图拖进来,只传本机路径不上传。
65
- </p>
66
- <div class="aichat__suggests">
67
- <button v-for="s in suggestions" :key="s.text" type="button" class="aichat__suggest" @click="useSuggestion(s)">
68
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use :href="s.icon" /></svg>
69
- <span>{{ s.label }}</span>
70
- </button>
71
- </div>
72
- </div>
73
-
74
- <template v-else>
75
- <ChatMessage
76
- v-for="(msg, idx) in messages"
77
- :key="msg.id"
78
- :msg="msg"
79
- :streaming="busy && idx === messages.length - 1"
80
- :can-regenerate="msg.role === 'assistant' && idx === messages.length - 1"
81
- @regenerate="regenerate"
82
- @approval="onApproval"
83
- @continue="onContinue"
84
- />
85
- <div v-if="busy" class="aichat__live">生成中… 可随时终止</div>
86
- </template>
87
- </div>
88
-
89
- <!-- 回到底部:脱离底部吸附时出现 -->
90
- <button v-show="!pinned && messages.length" type="button" class="aichat__jump" title="回到底部" @click="scrollToBottom(true)">
91
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-arrow-down" /></svg>
92
- </button>
93
- </div>
94
-
95
- <!-- 操作反馈(附件解析失败 / 历史保存失败等) -->
96
- <div v-if="notice" class="aichat__notice" :class="`aichat__notice--${notice.type}`">
97
- <span>{{ notice.text }}</span>
98
- <button type="button" class="aichat__notice-x" title="关闭" @click="notice = null">
99
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-x" /></svg>
100
- </button>
101
- </div>
102
-
103
- <ChatComposer
104
- ref="composerEl"
105
- :busy="busy"
106
- :model="config.model"
107
- :attachments="draftAttachments"
108
- :placeholder="configReady ? '描述你要改的卡片,Enter 发送 / Shift+Enter 换行' : '请先在设置中配置模型…'"
109
- :context="contextUsage"
110
- @send="onSend"
111
- @stop="stop"
112
- @pick="onPick"
113
- @add-path="onAddPath"
114
- @remove-attachment="removeAttachment"
115
- @drop-files="onDropFiles"
116
- @paste-image="onPasteImage"
117
- @escape="onEscape"
118
- />
119
-
120
- <!-- 模型设置(面板内浮层,不额外抬 z-index) -->
121
- <AiSettings v-if="settingsOpen" :config="config" @close="settingsOpen = false" @saved="onConfigSaved" />
122
-
123
- <ConfirmDialog
124
- :visible="confirmClear"
125
- title="清空对话记录"
126
- :message="`将删除卡片「${cardName || cardId || '当前卡片'}」的全部对话记录,此操作不可撤销。`"
127
- confirm-text="清空"
128
- severity="danger"
129
- @confirm="doClear"
130
- @cancel="confirmClear = false"
131
- />
132
- </aside>
133
- </template>
134
-
135
- <script>
136
- import ChatMessage from './ChatMessage.vue'
137
- import ChatComposer from './ChatComposer.vue'
138
- import AiSettings from './AiSettings.vue'
139
- import ConfirmDialog from './ConfirmDialog.vue'
140
- import {
141
- fetchAiConfig,
142
- loadChatHistory,
143
- saveChatHistory,
144
- clearChatHistory,
145
- approveToolCall,
146
- pickLocalPaths,
147
- statLocalPath,
148
- locateDroppedFile,
149
- stashPastedImage,
150
- streamChat,
151
- } from '../runtime/ai-api.js'
152
-
153
- const WIDTH_KEY = 'cardbuddy:ai-panel-width'
154
- /** 宽度约束:不小于 320px、不大于视口 50%,默认 33% */
155
- const MIN_WIDTH = 320
156
- const MAX_RATIO = 0.5
157
- const DEFAULT_RATIO = 0.33
158
-
159
- let _seq = 0
160
- function uid(prefix = 'm') {
161
- _seq += 1
162
- return `${prefix}${Date.now().toString(36)}-${_seq}`
163
- }
164
-
165
- /** 建议问法:点一下即填入输入框(不自动发送,留给用户改写) */
166
- const SUGGESTIONS = [
167
- { label: '解释这张卡的结构与数据来源', icon: '#icon-search', text: '读取当前卡片 schema,用中文解释它的结构、关键绑定与数据来源,指出可能的问题。' },
168
- { label: '校验并修复当前卡片', icon: '#icon-check', text: '对当前卡片执行校验,列出全部错误并按规则修复,修完再校验一次确认清零。' },
169
- { label: '截图自检还原度', icon: '#icon-image', text: '给当前卡片截图,检查布局、字号、间距与配色是否协调,指出需要调整的地方。' },
170
- { label: '按标注目录建卡', icon: '#icon-folder-plus', text: '我会挂载一份 Sketch MeaXure 标注导出目录,请按标注生成卡片 schema 并自检。' },
171
- ]
172
-
173
- export default {
174
- name: 'ChatPanel',
175
- components: { ChatMessage, ChatComposer, AiSettings, ConfirmDialog },
176
- props: {
177
- visible: { type: Boolean, default: false },
178
- /** 当前卡片 id:对话记录按它分档保存 */
179
- cardId: { type: String, default: '' },
180
- /** 当前卡片名(头部展示) */
181
- cardName: { type: String, default: '' },
182
- },
183
- emits: ['close'],
184
- data() {
185
- return {
186
- messages: [],
187
- draftAttachments: [],
188
- busy: false,
189
- loadingHistory: false,
190
- settingsOpen: false,
191
- confirmClear: false,
192
- notice: null,
193
- config: { baseUrl: '', model: '', hasKey: false, apiKeyMasked: '' },
194
- widthPx: 0,
195
- winW: typeof window !== 'undefined' ? window.innerWidth : 1280,
196
- resizing: false,
197
- pinned: true,
198
- /** 上下文占用(后端每轮 context-usage 回传):{ used, total, window },输入框圆环的数据源 */
199
- contextUsage: null,
200
- suggestions: SUGGESTIONS,
201
- _abort: null,
202
- _saveTimer: 0,
203
- _loadedCard: '',
204
- }
205
- },
206
- computed: {
207
- configReady() {
208
- return Boolean(this.config.baseUrl && this.config.model && this.config.hasKey)
209
- },
210
- /** 面板宽度:夹取到 [320, 视口 50%],未设置时取视口 33% */
211
- panelWidth() {
212
- const vw = this.winW || 1280
213
- const max = Math.max(MIN_WIDTH, Math.floor(vw * MAX_RATIO))
214
- const fallback = Math.round(vw * DEFAULT_RATIO)
215
- const w = this.widthPx || fallback
216
- return Math.max(MIN_WIDTH, Math.min(max, w))
217
- },
218
- },
219
- watch: {
220
- visible(val) {
221
- if (val) {
222
- this.refreshConfig()
223
- this.ensureHistory()
224
- this.$nextTick(() => {
225
- this.scrollToBottom(true)
226
- this.$refs.composerEl?.focus()
227
- })
228
- } else if (this.busy) {
229
- // 关面板不打断生成:继续跑完并落盘,重开可见结果
230
- this.flushHistory()
231
- }
232
- },
233
- cardId(newId, oldId) {
234
- // 切卡:先把「旧卡」未落盘的消息按旧卡 ID 落盘(此刻 this.cardId 已是新卡,
235
- // 直接 flush 会把旧卡消息写进新卡的历史文件),再清空视图加载新卡记录
236
- if (this._saveTimer) {
237
- clearTimeout(this._saveTimer)
238
- this._saveTimer = 0
239
- }
240
- const pending = this._dirtyFor === oldId ? this.serialize() : null
241
- this._dirtyFor = null
242
- this.draftAttachments = []
243
- this.messages = []
244
- if (pending && oldId) this.flushHistory(oldId, pending)
245
- this.ensureHistory()
246
- },
247
- },
248
- mounted() {
249
- this.loadWidth()
250
- window.addEventListener('resize', this.onWinResize)
251
- this.refreshConfig()
252
- this.ensureHistory()
253
- },
254
- beforeUnmount() {
255
- window.removeEventListener('resize', this.onWinResize)
256
- this.stopTickers()
257
- this.flushHistory()
258
- if (this._abort) this._abort.abort()
259
- },
260
- methods: {
261
- // ---------- 宽度 ----------
262
- onWinResize() {
263
- this.winW = window.innerWidth
264
- },
265
- loadWidth() {
266
- try {
267
- const v = localStorage.getItem(WIDTH_KEY)
268
- if (v != null) {
269
- const px = parseInt(v, 10)
270
- if (!Number.isNaN(px) && px >= MIN_WIDTH) this.widthPx = px
271
- }
272
- } catch {
273
- /* localStorage 不可用时用默认比例 */
274
- }
275
- },
276
- saveWidth() {
277
- try {
278
- if (this.widthPx) localStorage.setItem(WIDTH_KEY, String(Math.round(this.panelWidth)))
279
- else localStorage.removeItem(WIDTH_KEY)
280
- } catch {
281
- /* 忽略 */
282
- }
283
- },
284
- startResize(ev) {
285
- const el = ev && ev.currentTarget
286
- if (!el) return
287
- this.resizing = true
288
- document.body.style.cursor = 'col-resize'
289
- document.body.style.userSelect = 'none'
290
- const pointerId = ev.pointerId
291
- try {
292
- el.setPointerCapture(pointerId)
293
- } catch {
294
- /* 不支持捕获时退化为元素级监听 */
295
- }
296
- const move = (e) => {
297
- const max = Math.max(MIN_WIDTH, Math.floor(window.innerWidth * MAX_RATIO))
298
- this.widthPx = Math.max(MIN_WIDTH, Math.min(max, Math.round(window.innerWidth - e.clientX)))
299
- }
300
- const finish = () => {
301
- el.removeEventListener('pointermove', move)
302
- el.removeEventListener('pointerup', finish)
303
- el.removeEventListener('pointercancel', finish)
304
- window.removeEventListener('blur', finish)
305
- try {
306
- if (el.hasPointerCapture?.(pointerId)) el.releasePointerCapture(pointerId)
307
- } catch {
308
- /* 已释放 */
309
- }
310
- this.resizing = false
311
- document.body.style.cursor = ''
312
- document.body.style.userSelect = ''
313
- this.saveWidth()
314
- }
315
- el.addEventListener('pointermove', move)
316
- el.addEventListener('pointerup', finish)
317
- el.addEventListener('pointercancel', finish)
318
- window.addEventListener('blur', finish) // 失焦兜底,避免拖拽态卡死
319
- },
320
- resetWidth() {
321
- this.widthPx = 0
322
- this.saveWidth()
323
- },
324
-
325
- // ---------- 配置 ----------
326
- async refreshConfig() {
327
- try {
328
- const cfg = await fetchAiConfig()
329
- // 兼容旧版后端:无 hasKey 字段、直接回明文 apiKey,按「有 key」归一,避免误报「尚未配置」
330
- this.config = { ...cfg, hasKey: Boolean(cfg.hasKey || cfg.apiKey) }
331
- } catch {
332
- this.config = { baseUrl: '', model: '', hasKey: false, apiKeyMasked: '' }
333
- }
334
- },
335
- onConfigSaved(cfg) {
336
- if (cfg) this.config = cfg
337
- this.settingsOpen = false
338
- this.refreshConfig()
339
- },
340
-
341
- // ---------- 历史 ----------
342
- async ensureHistory() {
343
- const cardId = this.cardId
344
- if (!cardId || this._loadedCard === cardId) return
345
- this._loadedCard = cardId
346
- this.loadingHistory = true
347
- try {
348
- const data = await loadChatHistory(cardId)
349
- // 期间可能已切卡:只有仍是同一张卡才落地
350
- if (this.cardId !== cardId) return
351
- const loaded = (data.messages || []).map((m) => this.revive(m))
352
- // 历史是异步落地的,而面板一打开就能发消息:若这期间用户已经发过(甚至正在生成),
353
- // 整体替换会把这轮对话从视图里抹掉(请求还在跑,界面却像凭空消失)。
354
- // 故按 id 去重后把「已加载的历史」拼在当前会话消息之前。
355
- const seen = new Set(loaded.map((m) => m.id))
356
- const live = this.messages.filter((m) => !seen.has(m.id))
357
- this.messages = [...loaded, ...live]
358
- this.contextUsage = null // 换卡后旧卡的占用数字没有意义,等新一轮回传
359
- this._dirtyFor = null // 视图与落盘一致,切卡时不必再回写
360
- this.$nextTick(() => this.scrollToBottom(true))
361
- } catch {
362
- if (this.cardId === cardId) this.messages = []
363
- this._dirtyFor = null
364
- } finally {
365
- if (this.cardId === cardId) this.loadingHistory = false
366
- }
367
- },
368
- /** 落盘前剥离易失字段(流式态 / 计时器 / base64 截图) */
369
- serialize() {
370
- return this.messages.map((m) => {
371
- const out = {
372
- id: m.id,
373
- role: m.role,
374
- content: m.content || '',
375
- ts: m.ts,
376
- }
377
- if (m.role === 'assistant') {
378
- out.reasoning = m.reasoning || ''
379
- out.reasoningMs = m.reasoningMs || 0
380
- out.error = m.error || ''
381
- out.status = m.status === 'streaming' ? 'aborted' : m.status || 'done'
382
- // 收尾原因一并落盘(死循环熔断 / 墙钟 / 用户自设硬顶)
383
- if (m.budget?.message) out.budget = { reason: m.budget.reason || '', message: m.budget.message }
384
- if (m.toolCalls?.length) {
385
- out.toolCalls = m.toolCalls.map((tc) => ({
386
- toolCallId: tc.toolCallId,
387
- toolName: tc.toolName,
388
- args: tc.args || {},
389
- result: tc.result,
390
- error: tc.error || '',
391
- ms: tc.ms || 0,
392
- // 审批 / 免确认痕迹一并落盘:重开面板后仍看得见「当时跑了什么命令、是谁批的」
393
- command: tc.command || '',
394
- approvalNote: tc.approvalNote || '',
395
- autoApproved: Boolean(tc.autoApproved),
396
- }))
397
- }
398
- } else if (m.attachments?.length) {
399
- out.attachments = m.attachments.map((a) => ({
400
- path: a.path,
401
- name: a.name,
402
- isDir: Boolean(a.isDir),
403
- isImage: Boolean(a.isImage),
404
- size: a.size || 0,
405
- entryCount: a.entryCount || 0,
406
- exists: a.exists !== false,
407
- }))
408
- }
409
- return out
410
- })
411
- },
412
- /** 读回时补默认值,保证旧记录也能渲染 */
413
- revive(m) {
414
- return {
415
- id: m.id || uid(),
416
- role: m.role === 'user' ? 'user' : 'assistant',
417
- content: m.content || '',
418
- reasoning: m.reasoning || '',
419
- reasoningMs: m.reasoningMs || 0,
420
- toolCalls: Array.isArray(m.toolCalls)
421
- ? m.toolCalls.map((tc) =>
422
- // 被终止的轮次可能留下「参数 / 结果没等到」的悬空调用:读回时钉成终止态,
423
- // 否则前端会永远转圈显示「参数生成中…」
424
- (m.status === 'aborted' || m.status === 'streaming') && tc && !tc.error && tc.result == null
425
- ? { ...tc, error: '已终止:该调用未拿到结果(轮次被中断)' }
426
- : tc,
427
- )
428
- : [],
429
- attachments: Array.isArray(m.attachments) ? m.attachments : [],
430
- error: m.error || '',
431
- status: m.status || 'done',
432
- // 收尾原因(死循环 / 墙钟 / 步数硬顶):重开面板仍要看得见这轮为什么停
433
- budget: m.budget?.message ? { reason: m.budget.reason || '', message: m.budget.message } : undefined,
434
- ts: m.ts || 0,
435
- }
436
- },
437
- persist() {
438
- if (!this.cardId) return
439
- this._dirtyFor = this.cardId
440
- if (this._saveTimer) clearTimeout(this._saveTimer)
441
- this._saveTimer = setTimeout(() => this.flushHistory(), 500)
442
- },
443
- async flushHistory(cardId = this.cardId, snapshot = null) {
444
- if (this._saveTimer) {
445
- clearTimeout(this._saveTimer)
446
- this._saveTimer = 0
447
- }
448
- if (!cardId) return
449
- try {
450
- await saveChatHistory(cardId, snapshot || this.serialize())
451
- if (this._dirtyFor === cardId) this._dirtyFor = null
452
- } catch (e) {
453
- this.notify('warn', `对话记录保存失败:${e.message || e}`)
454
- }
455
- },
456
- askClear() {
457
- if (!this.messages.length) return
458
- this.confirmClear = true
459
- },
460
- async doClear() {
461
- this.confirmClear = false
462
- const cardId = this.cardId
463
- this.messages = []
464
- this.contextUsage = null
465
- this._loadedCard = cardId
466
- this._dirtyFor = null
467
- if (this._saveTimer) {
468
- clearTimeout(this._saveTimer)
469
- this._saveTimer = 0
470
- }
471
- try {
472
- await clearChatHistory(cardId)
473
- this.notify('ok', '已清空本卡片的对话记录')
474
- } catch (e) {
475
- this.notify('warn', `清空失败:${e.message || e}`)
476
- }
477
- },
478
-
479
- // ---------- 附件 ----------
480
- async onPick(kind) {
481
- try {
482
- const res = await pickLocalPaths(kind)
483
- if (res.cancelled) return
484
- for (const item of res.items || []) this.pushAttachment(item)
485
- if (!res.items?.length) this.notify('warn', '未选到可用文件')
486
- } catch (e) {
487
- this.notify('warn', e.message || '无法调起系统选择器,可改用拖拽或粘贴路径')
488
- }
489
- },
490
- async onAddPath(rawPath) {
491
- try {
492
- const info = await statLocalPath(rawPath)
493
- this.pushAttachment(info)
494
- } catch (e) {
495
- this.notify('warn', `路径不可用:${rawPath}(${e.message || e})`)
496
- }
497
- },
498
- async onPasteImage(dataUrl) {
499
- try {
500
- const info = await stashPastedImage(dataUrl)
501
- this.pushAttachment(info, '粘贴的截图已存到本机 .cache/ai-attachments/')
502
- } catch (e) {
503
- this.notify('warn', `截图保存失败:${e.message || e}`)
504
- }
505
- },
506
- /**
507
- * 拖入文件 / 文件夹:浏览器不给绝对路径,先按「名字 + 大小」请后端反查;
508
- * 文件夹则递归收集一层清单交给 AI(仍只传路径,不上传内容)。
509
- */
510
- async onDropFiles(dataTransfer) {
511
- const items = Array.from(dataTransfer.items || [])
512
- const entries = items.map((it) => (it.webkitGetAsEntry ? it.webkitGetAsEntry() : null)).filter(Boolean)
513
- const files = Array.from(dataTransfer.files || [])
514
- const jobs = []
515
-
516
- if (entries.length) {
517
- for (const entry of entries) {
518
- jobs.push(this.resolveEntry(entry))
519
- }
520
- } else {
521
- for (const file of files) {
522
- jobs.push(this.resolveFile(file))
523
- }
524
- }
525
- if (!jobs.length) {
526
- this.notify('warn', '没有识别到可挂载的文件')
527
- return
528
- }
529
- const results = await Promise.all(jobs)
530
- const missed = results.filter((r) => !r).length
531
- if (missed) {
532
- this.notify('warn', `${missed} 项未能定位本机路径,可改用「粘贴路径」添加`)
533
- }
534
- },
535
- async resolveEntry(entry) {
536
- if (entry.isDirectory) {
537
- const file = await this.firstFileInDir(entry)
538
- return this.resolveByName(entry.name, entry.fullPath || entry.name, file?.size ?? -1)
539
- }
540
- const file = await this.entryToFile(entry)
541
- if (file) return this.resolveFile(file)
542
- return this.resolveByName(entry.name, entry.fullPath || entry.name, -1)
543
- },
544
- async resolveFile(file) {
545
- return this.resolveByName(file.name, file.webkitRelativePath || file.name, file.size)
546
- },
547
- async resolveByName(name, relPath, size) {
548
- const chip = { path: '', name, isDir: false, isImage: false, pending: true }
549
- this.draftAttachments = [...this.draftAttachments, chip]
550
- try {
551
- const info = await locateDroppedFile({ name, relPath, size })
552
- this.draftAttachments = this.draftAttachments.filter((a) => a !== chip)
553
- this.pushAttachment(info)
554
- return info
555
- } catch {
556
- this.draftAttachments = this.draftAttachments.filter((a) => a !== chip)
557
- return null
558
- }
559
- },
560
- entryToFile(entry) {
561
- return new Promise((resolve) => {
562
- try {
563
- entry.file((f) => resolve(f), () => resolve(null))
564
- } catch {
565
- resolve(null)
566
- }
567
- })
568
- },
569
- /** 文件夹拖入:取第一个文件的大小做匹配特征(目录本身没有 size) */
570
- firstFileInDir(entry) {
571
- return new Promise((resolve) => {
572
- try {
573
- const reader = entry.createReader()
574
- reader.readEntries(async (list) => {
575
- for (const ent of list || []) {
576
- if (!ent.isDirectory) {
577
- const f = await this.entryToFile(ent)
578
- if (f) return resolve(f)
579
- }
580
- }
581
- resolve(null)
582
- }, () => resolve(null))
583
- } catch {
584
- resolve(null)
585
- }
586
- })
587
- },
588
- pushAttachment(info, note) {
589
- if (!info?.path) return
590
- if (this.draftAttachments.some((a) => a.path === info.path)) return
591
- this.draftAttachments = [...this.draftAttachments, { ...info, note }]
592
- if (note) this.notify('ok', note)
593
- },
594
- removeAttachment(pathToRemove) {
595
- this.draftAttachments = this.draftAttachments.filter((a) => a.path !== pathToRemove || a.pending)
596
- },
597
-
598
- // ---------- 对话 ----------
599
- useSuggestion(s) {
600
- this.$refs.composerEl?.setText(s.text)
601
- },
602
- async onSend({ text, attachments }) {
603
- if (this.busy) return
604
- if (!(await this.ensureConfig())) return
605
- this.notice = null
606
- this.messages.push({
607
- id: uid('u'),
608
- role: 'user',
609
- content: text,
610
- attachments: attachments.map((a) => ({ ...a })),
611
- ts: Date.now(),
612
- })
613
- this.draftAttachments = []
614
- this.pinned = true
615
- await this.runTurn()
616
- },
617
- /** 打开设置面板前刷新一次:窗口探测缓存等「服务端认知」可能已被对话或探测按钮更新 */
618
- openSettings() {
619
- this.settingsOpen = true
620
- this.refreshConfig()
621
- },
622
- async ensureConfig() {
623
- await this.refreshConfig()
624
- if (!this.configReady) {
625
- this.settingsOpen = true
626
- this.notify('warn', '请先配置 Base URL / API Key / 模型名')
627
- return false
628
- }
629
- return true
630
- },
631
- /** 熔断收尾后一键续跑:等价于用户手打「继续」,上下文(含工具结果)都还在 */
632
- onContinue() {
633
- if (this.busy) return
634
- this.onSend({ text: '继续', attachments: [] })
635
- },
636
- /** 重新生成:丢掉最后一条助手回答,按同样的上下文再跑一轮 */
637
- async regenerate(msg) {
638
- if (this.busy) return
639
- const idx = this.messages.findIndex((m) => m.id === msg.id)
640
- if (idx > 0) this.messages = this.messages.slice(0, idx)
641
- await this.runTurn()
642
- },
643
- async runTurn() {
644
- this.messages.push({
645
- id: uid('a'),
646
- role: 'assistant',
647
- content: '',
648
- reasoning: '',
649
- reasoningMs: 0,
650
- toolCalls: [],
651
- error: '',
652
- status: 'streaming',
653
- ts: Date.now(),
654
- })
655
- // 关键:改的必须是「reactive 数组里的代理对象」。若沿用 push 前的原始对象,
656
- // 写入不会触发任何依赖 —— 流式期间界面会一直停在「正在思考」,直到回合结束
657
- // 父组件因 busy 变化重渲染,才把累积的全文一次性刷出来(看着像卡死后吐一大坨)。
658
- const asst = this.messages[this.messages.length - 1]
659
- const asstId = asst.id
660
- this.busy = true
661
- this.pinned = true
662
- const ctl = new AbortController()
663
- this._abort = ctl
664
- const thinkStartedAt = Date.now()
665
- const freezeThinking = () => {
666
- if (asst.reasoning && !asst.reasoningMs) asst.reasoningMs = Date.now() - thinkStartedAt
667
- }
668
-
669
- try {
670
- await streamChat({
671
- messages: this.toWire(this.messages.filter((m) => m.id !== asstId)),
672
- cardContext: this.cardId ? { id: this.cardId } : undefined,
673
- attachments: this.lastAttachments(),
674
- signal: ctl.signal,
675
- onEvent: (evt) => this.onEvent(evt, asst, freezeThinking),
676
- })
677
- if (asst.status === 'streaming') asst.status = 'done'
678
- } catch (e) {
679
- const aborted = ctl.signal.aborted || /abort/i.test(String(e?.message || e))
680
- freezeThinking()
681
- if (aborted) {
682
- asst.status = 'aborted'
683
- } else {
684
- asst.error = String(e?.message || e)
685
- asst.status = 'error'
686
- }
687
- } finally {
688
- freezeThinking()
689
- if (asst.status === 'streaming') asst.status = 'done'
690
- this.busy = false
691
- this._abort = null
692
- this.persist()
693
- this.$nextTick(() => this.scrollToBottom())
694
- }
695
- },
696
- onEvent(evt, asst, freezeThinking) {
697
- switch (evt.type) {
698
- case 'reasoning-delta':
699
- asst.reasoning += evt.text || ''
700
- break
701
- case 'text-delta':
702
- freezeThinking()
703
- asst.content += evt.text || ''
704
- break
705
- // 工具名先到、参数还在流式拼装:先占一行「调用中」,参数齐了由 tool-call 补齐
706
- case 'tool-call-start':
707
- freezeThinking()
708
- if (!this.findToolRow(asst, evt)) {
709
- asst.toolCalls.push({
710
- toolCallId: evt.toolCallId || uid('tc'),
711
- toolName: evt.toolName,
712
- args: {},
713
- pending: true,
714
- result: undefined,
715
- error: '',
716
- startedAt: Date.now(),
717
- ms: 0,
718
- })
719
- }
720
- break
721
- case 'tool-call': {
722
- freezeThinking()
723
- const row = this.findToolRow(asst, evt)
724
- if (row) {
725
- if (evt.toolCallId) row.toolCallId = evt.toolCallId
726
- if (evt.toolName) row.toolName = evt.toolName
727
- row.args = evt.args || {}
728
- row.pending = false
729
- } else {
730
- asst.toolCalls.push({
731
- toolCallId: evt.toolCallId || uid('tc'),
732
- toolName: evt.toolName,
733
- args: evt.args || {},
734
- result: undefined,
735
- error: '',
736
- startedAt: Date.now(),
737
- ms: 0,
738
- })
739
- }
740
- break
741
- }
742
- case 'tool-result': {
743
- const tc = asst.toolCalls.find((t) => t.toolCallId === evt.toolCallId || (!evt.toolCallId && t.toolName === evt.toolName && t.result === undefined))
744
- if (tc) {
745
- tc.result = evt.result ?? ''
746
- tc.truncated = Boolean(evt.truncated)
747
- if (evt.error) tc.error = String(evt.error)
748
- if (evt.image) tc.image = evt.image
749
- tc.ms = Date.now() - (tc.startedAt || Date.now())
750
- }
751
- break
752
- }
753
- case 'approval-request': {
754
- // skill_exec / shell_exec:先占行再等用户点确认,后端流在此期间挂起
755
- const row = this.findToolRow(asst, evt) || asst.toolCalls[asst.toolCalls.length - 1]
756
- if (row && row.toolName === evt.toolName && row.result === undefined) {
757
- row.approvalId = evt.approvalId
758
- // 只补 command(命令原文),不覆盖真实参数:evt.args 是审批用的 {command} 精简体
759
- if (evt.command) row.command = evt.command
760
- row.needsApproval = true
761
- } else {
762
- asst.toolCalls.push({
763
- toolCallId: evt.toolCallId || uid('tc'),
764
- toolName: evt.toolName,
765
- args: evt.args || {},
766
- command: evt.command || '',
767
- approvalId: evt.approvalId,
768
- needsApproval: true,
769
- result: undefined,
770
- error: '',
771
- startedAt: Date.now(),
772
- ms: 0,
773
- })
774
- }
775
- break
776
- }
777
- case 'approval-decided': {
778
- const row = (asst.toolCalls || []).find((t) => t.approvalId === evt.approvalId)
779
- if (row) {
780
- row.needsApproval = false
781
- row.approvalNote = evt.ok
782
- ? evt.scope === 'turn'
783
- ? '已允许,本回合后续同类执行不再询问…'
784
- : '已允许,执行中…'
785
- : '已拒绝执行'
786
- }
787
- break
788
- }
789
- case 'approval-auto': {
790
- // 免确认策略命中:后端没弹审批就直接执行了,这里把命令原文与说明补到对应工具行,
791
- // 用户照样看得见「跑了什么」——免确认不等于无痕迹(后端另写 .cache/ai-audit.log)
792
- const row =
793
- this.findToolRow(asst, evt) ||
794
- [...(asst.toolCalls || [])].reverse().find((t) => t.toolName === evt.toolName && t.result === undefined)
795
- if (row) {
796
- if (evt.command) row.command = evt.command
797
- row.autoApproved = true
798
- row.approvalNote = '按设置免确认执行'
799
- }
800
- break
801
- }
802
- case 'notice':
803
- this.notify('warn', evt.message || '')
804
- break
805
- case 'context-usage':
806
- // 输入框圆环:本轮实际送进网关的上下文 token / 预算(有网关窗口认知时环按窗口算)
807
- this.contextUsage = { used: Number(evt.used) || 0, total: Number(evt.total) || 0, window: Number(evt.window) || 0 }
808
- break
809
- case 'turn-ended':
810
- // 熔断 / 硬顶收尾:横幅 6 秒就没了,这里落进消息本体,重开面板仍看得见「这轮为什么停」
811
- asst.budget = { reason: String(evt.reason || ''), message: String(evt.message || '') }
812
- break
813
- case 'error':
814
- asst.error = asst.error ? `${asst.error}\n${evt.message}` : String(evt.message || '')
815
- asst.status = 'error'
816
- break
817
- case 'aborted':
818
- asst.status = 'aborted'
819
- break
820
- case 'done':
821
- break
822
- default:
823
- break
824
- }
825
- this.$nextTick(() => this.scrollToBottom())
826
- },
827
- /** 审批回落:把用户的允许(一次 / 本回合)或拒绝交给挂起中的后端流 */
828
- async onApproval({ tc, ok, scope = 'once' }) {
829
- if (!tc?.approvalId) return
830
- tc.needsApproval = false
831
- tc.approvalNote = ok ? (scope === 'turn' ? '已允许,本回合后续同类执行不再询问…' : '已允许,执行中…') : '已拒绝执行'
832
- try {
833
- await approveToolCall({ approvalId: tc.approvalId, ok, scope })
834
- } catch (e) {
835
- this.notify('warn', `审批提交失败:${e.message}`)
836
- }
837
- },
838
- /**
839
- * 工具行定位:优先按 toolCallId,其次认「同名且仍在等参数」的占位行。
840
- * 占位行的 id 是本地生成的,参数齐了会被真实 id 覆盖,故两条路径都能对上。
841
- */
842
- findToolRow(asst, evt) {
843
- const list = asst.toolCalls || []
844
- if (evt.toolCallId) {
845
- const byId = list.find((t) => t.toolCallId === evt.toolCallId)
846
- if (byId) return byId
847
- }
848
- return list.find((t) => t.pending && t.toolName === evt.toolName) || null
849
- },
850
- stop() {
851
- if (!this._abort) return
852
- this._abort.abort()
853
- this.notify('ok', '已终止本次生成')
854
- },
855
- onEscape() {
856
- if (this.settingsOpen) {
857
- this.settingsOpen = false
858
- return
859
- }
860
- if (this.busy) {
861
- this.stop()
862
- return
863
- }
864
- this.$emit('close')
865
- },
866
- /** 最近一条用户消息挂载的附件(后端会写进 system prompt 的「本地文件」段) */
867
- lastAttachments() {
868
- for (let i = this.messages.length - 1; i >= 0; i--) {
869
- const m = this.messages[i]
870
- if (m.role === 'user') return (m.attachments || []).map((a) => ({ path: a.path, name: a.name, isDir: a.isDir, isImage: a.isImage }))
871
- }
872
- return []
873
- },
874
- /**
875
- * 组装上行 messages:用户消息把附件路径写成清单(路径即上下文,不上传文件),
876
- * 助手消息保留 toolCalls 供后端还原 tool_calls / tool 结果,续聊上下文才合法。
877
- */
878
- toWire(list) {
879
- return list.map((m) => {
880
- if (m.role === 'user') {
881
- const atts = m.attachments || []
882
- let content = m.content || ''
883
- if (atts.length) {
884
- const lines = atts.map((a) => `- ${a.isDir ? '文件夹' : a.isImage ? '图片' : '文件'}:${a.path}`)
885
- content = `${content}${content ? '\n\n' : ''}[已挂载本地文件(仅本机路径,未上传;需要内容请用工具读取)]\n${lines.join('\n')}`
886
- }
887
- return { role: 'user', content }
888
- }
889
- // 被中止的轮次:只回传「真正执行过」的工具调用(有结果或报错),
890
- // 参数还没流完就被中止的调用从未执行,回传会骗模型说它拿到了空结果。
891
- const calls = (m.toolCalls || [])
892
- .filter((tc) => tc.result !== undefined || tc.error)
893
- .map((tc) => ({
894
- toolCallId: tc.toolCallId,
895
- toolName: tc.toolName,
896
- args: tc.args || {},
897
- result: typeof tc.result === 'string' ? tc.result.slice(0, 4000) : tc.result,
898
- error: tc.error || '',
899
- }))
900
- // 中止标记只进上下文、不进界面:让模型知道上一轮被打断、做到哪了,
901
- // 用户说「继续」时能接着干而不是从零重来(空 assistant 消息对模型等于没有信息)。
902
- let content = m.content || ''
903
- if (m.status === 'aborted') {
904
- content = content
905
- ? `${content}\n\n[本轮生成被用户手动中止,以上为已完成部分;未完成的步骤没有执行,请从中断处继续。]`
906
- : '[上一轮被用户手动中止于思考/工具阶段,没有产出可用内容;请重新规划并继续完成用户的目标。]'
907
- }
908
- return { role: 'assistant', content, toolCalls: calls }
909
- })
910
- },
911
-
912
- // ---------- 滚动 ----------
913
- onScroll() {
914
- const el = this.$refs.listEl
915
- if (!el) return
916
- this.pinned = el.scrollHeight - el.scrollTop - el.clientHeight < 48
917
- },
918
- scrollToBottom(force = false) {
919
- const el = this.$refs.listEl
920
- if (!el) return
921
- if (!force && !this.pinned) return
922
- el.scrollTop = el.scrollHeight
923
- },
924
- stopTickers() {
925
- if (this._saveTimer) clearTimeout(this._saveTimer)
926
- this._saveTimer = 0
927
- },
928
- notify(type, text) {
929
- this.notice = { type, text }
930
- if (this._noticeTimer) clearTimeout(this._noticeTimer)
931
- this._noticeTimer = setTimeout(() => {
932
- this.notice = null
933
- }, 6000)
934
- },
935
- },
936
- }
937
- </script>