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.
- package/README.md +46 -5
- package/dist/cli.js +51 -1
- package/dist/config.js +51 -16
- package/dist/core.js +2 -2
- package/dist/init.js +425 -0
- package/dist/preview-editor/assets/index-CHuGGIR3.js +276 -0
- package/dist/preview-editor/assets/{index-B4VZgNSK.css → index-CQScMhyg.css} +1 -1
- package/dist/preview-editor/index.html +2 -2
- package/dist/standalone.js +8 -0
- package/dist/vendored/mcp/client.mjs +537 -0
- package/dist/vendored/mcp/tools.mjs +268 -0
- package/dist/vendored/preview/assets/default-image.png +0 -0
- package/dist/{server-core → vendored/server/core}/ai-chat.js +82 -38
- package/dist/{server-core → vendored/server/core}/browser.js +1 -1
- package/dist/{server-core → vendored/server/core}/cardSave.js +8 -3
- package/dist/{server-core → vendored/server/core}/cardSync.js +6 -14
- package/dist/{server-core → vendored/server/core}/cardValidate.js +11 -8
- package/dist/{server-core → vendored/server/core}/index.js +5 -3
- package/dist/vendored/server/core/skillPaths.js +124 -0
- package/dist/{server-core → vendored/server/core}/sliceUpload.js +5 -8
- package/dist/vendored/skills/README.md +93 -0
- package/dist/vendored/skills/card-doctor/SKILL.md +52 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +312 -0
- package/dist/vendored/skills/card-skill-trainer/baselines/v3-r038n.json +43 -0
- package/dist/vendored/skills/card-skill-trainer/baselines/v3-r039n.json +47 -0
- package/dist/vendored/skills/card-skill-trainer/baselines/v3-r050n.json +48 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/build_dataset.py +472 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/build_sample.mjs +162 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +241 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +525 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/round_diff.mjs +133 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +1043 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +195 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +122 -0
- package/dist/vendored/skills/dsl-card-ai/SKILL.md +191 -0
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +480 -0
- package/dist/vendored/skills/dsl-card-generator/config/render-contract.json +11 -0
- package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +138 -0
- package/dist/vendored/skills/dsl-card-generator/references/card_index.json +1597 -0
- package/dist/vendored/skills/dsl-card-generator/references/card_templates.json +384 -0
- package/dist/vendored/skills/dsl-card-generator/references/card_to_summary.md +56 -0
- package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +76 -0
- package/dist/vendored/skills/dsl-card-generator/references/component_specs.md +393 -0
- package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +208 -0
- package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +287 -0
- package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +121 -0
- package/dist/vendored/skills/dsl-card-generator/references/field_name_dict.json +34 -0
- package/dist/vendored/skills/dsl-card-generator/references/field_naming.md +101 -0
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +928 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/README.md +69 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/examples_index.json +2967 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/patterns.json +2835 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2.json +648 -0
- package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2_schema.md +295 -0
- package/dist/vendored/skills/dsl-card-generator/references/image_diff.md +48 -0
- package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +129 -0
- package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +212 -0
- package/dist/vendored/skills/dsl-card-generator/references/message-center-pattern.md +149 -0
- package/dist/vendored/skills/dsl-card-generator/references/platform_jsapi.md +196 -0
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +54 -0
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +197 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/absolute-badge.json +84 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/accordion-group-toggle.json +18 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertCard-pair.json +12 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertDialog-call.json +12 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-countdown.json +15 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-donut.json +15 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-gauge-score.json +11 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-radar.json +10 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-title-head.json +13 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/globalSet.json +12 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/label-col-modes.json +21 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/list-expand-collapse.json +17 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/money-format.json +11 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/opacity-switch.json +130 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/progress-bar.json +25 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/tab-billMode.json +15 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +21 -0
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +797 -0
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +235 -0
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +177 -0
- package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +609 -0
- package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +131 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +118 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +153 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/apply_opacity_pattern.mjs +343 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/build_card_index.mjs +108 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/build_card_templates.mjs +190 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +257 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +79 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +98 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/describe_card.mjs +265 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/diff_bands.mjs +257 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/diff_card.mjs +316 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +873 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/extract_handcrafted.py +1199 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/extract_subtree.mjs +191 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +389 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +160 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +700 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_image.mjs +56 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_sample.mjs +328 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +79 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +103 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +575 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +81 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +147 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lint_autofix.mjs +363 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +1399 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +249 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +208 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/render_baseline.mjs +231 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +801 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/render_diff.mjs +239 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/resolve_card_template.mjs +73 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +447 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/scaffold_card.mjs +81 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/slices_normalize.mjs +316 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +462 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +7735 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_tree.mjs +355 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_all_schemas.py +71 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/default-image-wzZYQ86K.png +0 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-C9k-07y7.css +1 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-oFRM4f4V.js +55 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/svg-symbols-B2exBrAr.svg +118 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/component-meta.json +2714 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/directive-meta.json +40 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/style-meta.json +1086 -0
- package/dist/vendored/{standalone.html → skills/dsl-card-generator/vendor/preview/standalone.html} +2 -1
- package/dist/vendored/skills/finmall-card-sync/SKILL.md +262 -0
- package/dist/vendored/skills/finmall-session-navigator/SKILL.md +169 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/finmall_login.mjs +882 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/package-lock.json +59 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/package.json +10 -0
- package/dist/vendored/skills/finmall-session-navigator/scripts/session-manager.mjs +96 -0
- package/dist/vendored/skills/shared/README.md +55 -0
- package/dist/vendored/skills/shared/auth-model.md +79 -0
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +329 -0
- package/dist/vendored/skills/sketch-spec-to-card/references/boundary.json +14 -0
- package/dist/vendored/skills/sketch-spec-to-card/references/card_refs.json +13 -0
- package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +103 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +797 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/card_refs.py +94 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +706 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +1781 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +318 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +1118 -0
- package/dist/vendored/templates/helloCard/data.json +10 -0
- package/dist/vendored/templates/helloCard/schema.json +238 -0
- package/dist/vendored/tools/link-skills.mjs +485 -0
- package/package.json +2 -1
- package/dist/preview-editor/assets/index-jl4YJzKv.js +0 -180
- package/dist/vendored/App.vue +0 -2858
- package/dist/vendored/ansi.js +0 -185
- package/dist/vendored/assets/apple-touch-icon.png +0 -0
- package/dist/vendored/assets/default-image.png +0 -0
- package/dist/vendored/assets/favicon-16.png +0 -0
- package/dist/vendored/assets/favicon-32.png +0 -0
- package/dist/vendored/assets/site-icon.png +0 -0
- package/dist/vendored/assets/svgIcons/icons.svg +0 -87
- package/dist/vendored/assets/svgIcons/index.js +0 -39
- package/dist/vendored/components/AiSettings.vue +0 -392
- package/dist/vendored/components/AlertCardInstance.vue +0 -58
- package/dist/vendored/components/AlertCardLayer.vue +0 -59
- package/dist/vendored/components/ApiCallPanel.vue +0 -127
- package/dist/vendored/components/ApiPayloadTree.vue +0 -201
- package/dist/vendored/components/AppToast.vue +0 -37
- package/dist/vendored/components/CardGallery.vue +0 -291
- package/dist/vendored/components/CardPreview.vue +0 -72
- package/dist/vendored/components/CardSelect.vue +0 -206
- package/dist/vendored/components/ChatComposer.vue +0 -413
- package/dist/vendored/components/ChatMessage.vue +0 -383
- package/dist/vendored/components/ChatPanel.vue +0 -937
- package/dist/vendored/components/ComponentPalette.vue +0 -301
- package/dist/vendored/components/ConfirmDialog.vue +0 -54
- package/dist/vendored/components/DatePickerOverlay.vue +0 -345
- package/dist/vendored/components/DebugPanel.vue +0 -1734
- package/dist/vendored/components/DeviceOverlay.vue +0 -70
- package/dist/vendored/components/Dropdown.vue +0 -193
- package/dist/vendored/components/EventEditor.vue +0 -273
- package/dist/vendored/components/HoverGuides.vue +0 -122
- package/dist/vendored/components/JsonTree.vue +0 -547
- package/dist/vendored/components/LogValue.vue +0 -52
- package/dist/vendored/components/MarkdownBody.vue +0 -64
- package/dist/vendored/components/NewCardDialog.vue +0 -132
- package/dist/vendored/components/NodeInspector.vue +0 -295
- package/dist/vendored/components/NumberStepper.vue +0 -125
- package/dist/vendored/components/PickerOverlay.vue +0 -131
- package/dist/vendored/components/PropControl.vue +0 -245
- package/dist/vendored/components/PropTree.vue +0 -128
- package/dist/vendored/components/RemotePreview.vue +0 -173
- package/dist/vendored/components/SelectionOverlay.vue +0 -105
- package/dist/vendored/components/StructureTree.vue +0 -1002
- package/dist/vendored/components/Switch.vue +0 -38
- package/dist/vendored/components/setters/SetBgPosition.vue +0 -64
- package/dist/vendored/components/setters/SetBgSize.vue +0 -90
- package/dist/vendored/components/setters/SetCode.vue +0 -198
- package/dist/vendored/components/setters/SetColor.vue +0 -244
- package/dist/vendored/components/setters/SetCustomSelect.vue +0 -58
- package/dist/vendored/components/setters/SetExpr.vue +0 -107
- package/dist/vendored/components/setters/SetGradient.vue +0 -125
- package/dist/vendored/components/setters/SetGrid9.vue +0 -68
- package/dist/vendored/components/setters/SetIconSelect.vue +0 -67
- package/dist/vendored/components/setters/SetIfForm.vue +0 -61
- package/dist/vendored/components/setters/SetImage.vue +0 -43
- package/dist/vendored/components/setters/SetList.vue +0 -139
- package/dist/vendored/components/setters/SetNumber.vue +0 -118
- package/dist/vendored/components/setters/SetNumberStyle.vue +0 -113
- package/dist/vendored/components/setters/SetPadding.vue +0 -77
- package/dist/vendored/components/setters/SetSegment.vue +0 -160
- package/dist/vendored/components/setters/SetSelect.vue +0 -51
- package/dist/vendored/components/setters/SetSwitch.vue +0 -49
- package/dist/vendored/components/setters/SetText.vue +0 -52
- package/dist/vendored/components/setters/props.js +0 -23
- package/dist/vendored/fidelity/baseline-messageOverview.json +0 -87
- package/dist/vendored/fidelity/calibration.json +0 -29
- package/dist/vendored/fidelity/fidelity-messageOverview-after.html +0 -114
- package/dist/vendored/fidelity/fidelity-messageOverview-after.json +0 -272
- package/dist/vendored/fidelity/fidelity-messageOverview.html +0 -185
- package/dist/vendored/fidelity/fidelity-messageOverview.json +0 -336
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +0 -262
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +0 -502
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +0 -201
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +0 -417
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +0 -125
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +0 -338
- package/dist/vendored/index.html +0 -16
- package/dist/vendored/main.js +0 -37
- package/dist/vendored/runtime/ai-api.js +0 -165
- package/dist/vendored/runtime/alert-card.js +0 -125
- package/dist/vendored/runtime/app-toast.js +0 -33
- package/dist/vendored/runtime/clipboard-node.js +0 -78
- package/dist/vendored/runtime/clipboard.js +0 -29
- package/dist/vendored/runtime/event-model.js +0 -300
- package/dist/vendored/runtime/hoverGuide.js +0 -31
- package/dist/vendored/runtime/jsapi-mock.js +0 -256
- package/dist/vendored/runtime/markdown.js +0 -140
- package/dist/vendored/runtime/node-model.js +0 -217
- package/dist/vendored/skills/.manifest.json +0 -1101
- package/dist/vendored/standalone-main.js +0 -36
- package/dist/vendored/styles/ai-chat.css +0 -1850
- package/dist/vendored/styles/app.css +0 -3648
- package/dist/vendored/styles/card-gallery.css +0 -285
- package/dist/vendored/styles/card-select.css +0 -288
- package/dist/vendored/styles/components.css +0 -383
- package/dist/vendored/styles/dropdown.css +0 -324
- package/dist/vendored/styles/json-tree.css +0 -308
- package/dist/vendored/styles/node-inspector.css +0 -800
- package/dist/vendored/styles/setters.css +0 -1050
- package/dist/vendored/styles/standalone.css +0 -27
- package/dist/vendored/styles/structure-tree.css +0 -460
- /package/dist/vendored/{api.js → preview/api.js} +0 -0
- /package/dist/vendored/{assets → preview/assets}/svg-symbols.svg +0 -0
- /package/dist/vendored/{components → preview/components}/AbsoluteLayout.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Button.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Canvas.vue +0 -0
- /package/dist/vendored/{components → preview/components}/CheckGroup.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Column.vue +0 -0
- /package/dist/vendored/{components → preview/components}/CustomPlaceholder.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Divider.vue +0 -0
- /package/dist/vendored/{components → preview/components}/GridLayout.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Icon.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Image.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Input.vue +0 -0
- /package/dist/vendored/{components → preview/components}/ListLayout.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Map.vue +0 -0
- /package/dist/vendored/{components → preview/components}/MultiColumn.vue +0 -0
- /package/dist/vendored/{components → preview/components}/MultiText.vue +0 -0
- /package/dist/vendored/{components → preview/components}/ProgressSlider.vue +0 -0
- /package/dist/vendored/{components → preview/components}/RadioButton.vue +0 -0
- /package/dist/vendored/{components → preview/components}/RenderTree.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Root.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Row.vue +0 -0
- /package/dist/vendored/{components → preview/components}/SvgCode.vue +0 -0
- /package/dist/vendored/{components → preview/components}/SvgSymbols.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Text.vue +0 -0
- /package/dist/vendored/{components → preview/components}/Video.vue +0 -0
- /package/dist/vendored/{components → preview/components}/ViewContainer.vue +0 -0
- /package/dist/vendored/{components → preview/components}/base.js +0 -0
- /package/dist/vendored/{components → preview/components}/icons.js +0 -0
- /package/dist/vendored/{components → preview/components}/index.js +0 -0
- /package/dist/vendored/{meta → preview/meta}/component-meta.json +0 -0
- /package/dist/vendored/{meta → preview/meta}/directive-meta.json +0 -0
- /package/dist/vendored/{meta → preview/meta}/style-meta.json +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/StyleParser.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/adapter.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/assets.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/canvas-context.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/card-runtime.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/evaluate.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/property.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/selectionOverlay.js +0 -0
- /package/dist/vendored/{runtime → preview/runtime}/toPreview.js +0 -0
- /package/dist/{server-core → vendored/server/core}/assetProxy.js +0 -0
- /package/dist/{server-core → vendored/server/core}/bbox.js +0 -0
- /package/dist/{server-core → vendored/server/core}/buildDataFromSchema.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardFiles.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardHistory.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardImage.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardImportMeta.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardListCache.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardOpenFolder.js +0 -0
- /package/dist/{server-core → vendored/server/core}/cardSidecar.js +0 -0
- /package/dist/{server-core → vendored/server/core}/codeBridge.js +0 -0
- /package/dist/{server-core → vendored/server/core}/logStream.js +0 -0
- /package/dist/{server-core → vendored/server/core}/logger.js +0 -0
- /package/dist/{server-core → vendored/server/core}/nodeModels.js +0 -0
- /package/dist/{server-core → vendored/server/core}/renderStatus.js +0 -0
- /package/dist/{server-core → vendored/server/core}/screenshot.js +0 -0
- /package/dist/{server-core → vendored/server/core}/sliceStatic.js +0 -0
- /package/dist/vendored/{meta → skills/dsl-card-generator/vendor/preview/meta}/action-meta.json +0 -0
- /package/dist/{server-core/_vendor → vendored/tools}/finmall-env.mjs +0 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
cluster_states.py — sketch-spec-to-card 卡片维度聚类与产物聚合(2026-08-14 重写)。
|
|
5
|
+
|
|
6
|
+
把 parse_meaxure.py(视觉驱动)产出的 UI/<画板>/spec.json 按「卡片维度」聚类为
|
|
7
|
+
逻辑卡片,并聚合多状态产物:
|
|
8
|
+
|
|
9
|
+
输入:UI/<画板名>/spec.json (每画板含 cards/elements/styleSummary/artboardType)
|
|
10
|
+
输出:UI/cards/<cardKey>/ 按卡片维度聚合的产物
|
|
11
|
+
card.json 卡片元信息:状态集合 / 交互 / 样式 / 列表 / 维度
|
|
12
|
+
states/<stateKey>.png 各状态的代表性聚焦截图(从对应画板 card-*.png 软链/拷贝)
|
|
13
|
+
states/<stateKey>/spec.json 该状态的视觉要素标注(复制自画板 spec.json)
|
|
14
|
+
UI/clusters.json 全局聚类 + 每卡状态模型 + 交互维度
|
|
15
|
+
UI/interactions/index.yaml 跨卡片交互图(弹框/子卡父子关系)
|
|
16
|
+
|
|
17
|
+
设计原则(HANDOFF Part 1 判定原则):
|
|
18
|
+
- 抽象通用化:状态键用 state-overview/state-empty/state-selected 等抽象名,不写业务词。
|
|
19
|
+
- 子卡独立成卡:subcard 独立一个 cardKey,仅通过 parentKey 与父卡建立交互关系。
|
|
20
|
+
- 多状态聚焦截图在同一卡片下:一张卡片可对应多个画板(多状态),各自一张聚焦图。
|
|
21
|
+
|
|
22
|
+
用法:
|
|
23
|
+
python cluster_states.py --out <UI目录>
|
|
24
|
+
python cluster_states.py --out <UI目录> --auto-refs # 从 src/cards 动态探测参考卡
|
|
25
|
+
|
|
26
|
+
设计原则(强化):维度判定 100% 基于视觉结构(指纹 diff / Jaccard 相似度),
|
|
27
|
+
不依赖任何业务文案、枚举值列表、领域关键词。换业务域零调整即可生效。
|
|
28
|
+
残留的语法功能词(否定前缀/登录态/结果尾缀)仅用于状态键命名归类,不参与维度判定。
|
|
29
|
+
"""
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import re
|
|
34
|
+
import shutil
|
|
35
|
+
import sys
|
|
36
|
+
from collections import defaultdict
|
|
37
|
+
|
|
38
|
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
39
|
+
sys.path.insert(0, SCRIPT_DIR)
|
|
40
|
+
from card_refs import load_card_refs
|
|
41
|
+
|
|
42
|
+
ALERTDIALOG_REF_CARDS = []
|
|
43
|
+
ALERTCARD_REF_CARDS = []
|
|
44
|
+
|
|
45
|
+
# ----------------------------------------------------------------------------
|
|
46
|
+
# 1. 维度识别:纯结构(fingerprint diff),无业务词集
|
|
47
|
+
# ----------------------------------------------------------------------------
|
|
48
|
+
# 设计原则:维度判定依据 = 同卡片多状态间的视觉结构差异(文本/块/颜色集合的增减),
|
|
49
|
+
# 绝不依赖任何业务文案、枚举值列表、领域关键词。换业务域零调整即可生效。
|
|
50
|
+
# 残留的语法功能词(否定前缀/登录态等)仅用于状态键命名,不参与维度判定。
|
|
51
|
+
|
|
52
|
+
# 语法功能标记(跨业务通用,非领域词):用于把结构检测出的状态归入抽象状态键。
|
|
53
|
+
# 仅匹配画板名 qualifier token 的「语法角色」,不匹配任何业务实体名。
|
|
54
|
+
_NEG_PREFIX = re.compile(r"^(无|未|空|没)(?=[\u4e00-\u9fff])") # 否定前缀:无/未/空/没+汉字
|
|
55
|
+
_EMPTY_HINT = re.compile(r"^(空|无.{0,4}|未登录|未绑定|暂无|没有)") # 空态语法(保守)
|
|
56
|
+
_RESULT_HINT = re.compile(r"(成功|失败|完成|结果|处理中|已.{0,2})$") # 结果态语法尾缀
|
|
57
|
+
_SELECTED_HINT = re.compile(r"(选中|已选|激活|active|selected)", re.I)
|
|
58
|
+
_AUTH_HINT = re.compile(r"(登录|授权|登入)", re.I)
|
|
59
|
+
# 过渡态:画板名 qualifier 指向另一卡(跨卡跳转),由结构判定,不用业务名正则
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _resolve_src_cards(explicit):
|
|
63
|
+
if explicit:
|
|
64
|
+
return explicit
|
|
65
|
+
repo_root = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
|
66
|
+
cand = os.path.join(repo_root, "src", "cards")
|
|
67
|
+
return cand if os.path.isdir(cand) else None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ----------------------------------------------------------------------------
|
|
71
|
+
# 2. 画板名解析(抽象、通用)
|
|
72
|
+
# ----------------------------------------------------------------------------
|
|
73
|
+
def normalize_name(canvas_name):
|
|
74
|
+
return re.sub(r"^\d+", "", canvas_name or "").strip()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def card_key(canvas_name):
|
|
78
|
+
return normalize_name(canvas_name).split("_", 1)[0]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def qualifiers(canvas_name):
|
|
82
|
+
nm = normalize_name(canvas_name)
|
|
83
|
+
if "_" not in nm:
|
|
84
|
+
return []
|
|
85
|
+
return [q for q in nm.split("_", 1)[1].split("_") if q]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def slugify_dim(name, idx):
|
|
89
|
+
"""维度绑定变量名:纯抽象 dim{idx},不映射任何业务词。"""
|
|
90
|
+
return f"dim{idx}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def abstract_state_key(qs, fp=None):
|
|
94
|
+
"""从 qualifier token 的语法角色推导抽象状态键(纯语法,无业务词)。
|
|
95
|
+
判定顺序:空态语法 → 结果态语法尾缀 → 选中态 → 登录态 → 默认 overview。
|
|
96
|
+
fp(指纹)可选,用于辅助空态判定(内容元素极少 → 强化空态判定)。
|
|
97
|
+
"""
|
|
98
|
+
joined = "_".join(qs)
|
|
99
|
+
# 内容稀疏度(辅助):无 fp 时保守不判
|
|
100
|
+
sparse = False
|
|
101
|
+
if fp:
|
|
102
|
+
sparse = (len(fp.get("texts", [])) + len(fp.get("blocks", []))) <= 3
|
|
103
|
+
if _EMPTY_HINT.search(joined) or sparse:
|
|
104
|
+
return "state-empty"
|
|
105
|
+
if _RESULT_HINT.search(joined):
|
|
106
|
+
return "state-result"
|
|
107
|
+
if _SELECTED_HINT.search(joined):
|
|
108
|
+
return "state-selected"
|
|
109
|
+
if _AUTH_HINT.search(joined):
|
|
110
|
+
return "state-auth"
|
|
111
|
+
return "state-overview"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ----------------------------------------------------------------------------
|
|
115
|
+
# 3. 指纹(基于新 spec 结构:elements/cards/styleSummary)
|
|
116
|
+
# ----------------------------------------------------------------------------
|
|
117
|
+
def fingerprint(spec):
|
|
118
|
+
"""卡片主体指纹:文本集合 + 视觉块集合 + 颜色集合(用于状态间 diff)。"""
|
|
119
|
+
texts, blocks, colors = [], [], []
|
|
120
|
+
for el in spec.get("elements", []):
|
|
121
|
+
nm = (el.get("name") or "").split("/")[-1].strip()
|
|
122
|
+
if el.get("type") == "text" and el.get("text"):
|
|
123
|
+
texts.append(str(el["text"]).strip())
|
|
124
|
+
elif el.get("type") in ("group", "shape", "symbol") and nm:
|
|
125
|
+
blocks.append(nm)
|
|
126
|
+
# 颜色
|
|
127
|
+
st = el.get("style") or {}
|
|
128
|
+
bkg = st.get("background") or {}
|
|
129
|
+
if bkg.get("color"):
|
|
130
|
+
colors.append(bkg["color"])
|
|
131
|
+
ts = el.get("textStyle") or {}
|
|
132
|
+
if ts.get("color"):
|
|
133
|
+
colors.append(ts["color"])
|
|
134
|
+
ss = spec.get("styleSummary") or {}
|
|
135
|
+
colors = sorted(set(colors)) or sorted(set(ss.get("colors", [])))
|
|
136
|
+
return {
|
|
137
|
+
"texts": sorted(set(texts)),
|
|
138
|
+
"blocks": sorted(set(blocks)),
|
|
139
|
+
"colors": colors,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def jaccard(a, b):
|
|
144
|
+
if not a and not b:
|
|
145
|
+
return 1.0
|
|
146
|
+
return len(a & b) / len(a | b)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def diff_fingerprint(base, other):
|
|
150
|
+
bt, bb = set(base["texts"]), set(base["blocks"])
|
|
151
|
+
ot, ob = set(other["texts"]), set(other["blocks"])
|
|
152
|
+
return {
|
|
153
|
+
"addedTexts": sorted(ot - bt),
|
|
154
|
+
"removedTexts": sorted(bt - ot),
|
|
155
|
+
"addedBlocks": sorted(ob - bb),
|
|
156
|
+
"removedBlocks": sorted(bb - ob),
|
|
157
|
+
"textJaccard": round(jaccard(bt, ot), 3),
|
|
158
|
+
"blockJaccard": round(jaccard(bb, ob), 3),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ----------------------------------------------------------------------------
|
|
163
|
+
# 4. 维度提取(tab / conditional / form / sequential)—— 纯结构,无业务词
|
|
164
|
+
# ----------------------------------------------------------------------------
|
|
165
|
+
def _fp_sim(a, b):
|
|
166
|
+
"""两状态指纹平均相似度(文本 Jaccard + 块 Jaccard)/2。"""
|
|
167
|
+
jt = jaccard(set(a["fp"]["texts"]), set(b["fp"]["texts"]))
|
|
168
|
+
jb = jaccard(set(a["fp"]["blocks"]), set(b["fp"]["blocks"]))
|
|
169
|
+
return (jt + jb) / 2
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def is_empty_or_error(qs, fp=None):
|
|
173
|
+
"""空/错误态判定:qualifier 含否定语法前缀 或 指纹内容极稀疏。"""
|
|
174
|
+
joined = "_".join(qs)
|
|
175
|
+
if _EMPTY_HINT.search(joined):
|
|
176
|
+
return True
|
|
177
|
+
if fp and (len(fp.get("texts", [])) + len(fp.get("blocks", []))) <= 3:
|
|
178
|
+
return True
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _is_result_token(q):
|
|
183
|
+
"""结果态语法尾缀(成功/失败/完成/已…),跨业务通用语法,非领域词。"""
|
|
184
|
+
return bool(_RESULT_HINT.search(q))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def detect_positional_tabs(cluster_states):
|
|
188
|
+
"""统一结构化维度检测(纯结构,无业务词)。
|
|
189
|
+
|
|
190
|
+
核心信号:**互斥值对跨多个独立 qualifier 分组反复共现 = 真维度**。
|
|
191
|
+
一个真正的 Tab/枚举维度(如 已出账/未出账)会在卡片的不同状态下反复出现
|
|
192
|
+
(无论其他 qualifier 是什么),因此同一值对会在多个分组里被观察到。
|
|
193
|
+
而局部状态变体(如 已逾期/已还最低)只在单一狭窄分组出现 → 不升维。
|
|
194
|
+
|
|
195
|
+
两阶段:
|
|
196
|
+
阶段A「局部配对」:对每个 qualifier 位置,按其余位置分组,组内 ≥2 个不同值
|
|
197
|
+
→ 记录所有 (值A, 值B) 配对及其出现组。
|
|
198
|
+
阶段B「跨组聚合」:同一值对在 ≥2 个独立组出现 → 真维度(Tab/Conditional/Sequential)。
|
|
199
|
+
单组出现的局部变化不升维(避免把状态变体误判为维度)。
|
|
200
|
+
|
|
201
|
+
分类(基于值的语法角色,非领域词):
|
|
202
|
+
- 含否定前缀(无/未/空) → conditional
|
|
203
|
+
- 全部带结果尾缀(成功/失败/已…) → sequential
|
|
204
|
+
- 其余 → tab
|
|
205
|
+
"""
|
|
206
|
+
# 阶段A:局部配对收集
|
|
207
|
+
local_groups = defaultdict(list)
|
|
208
|
+
for st in cluster_states:
|
|
209
|
+
qs = st["qualifiers"]
|
|
210
|
+
for i in range(len(qs)):
|
|
211
|
+
key = tuple(qs[:i] + qs[i + 1:])
|
|
212
|
+
local_groups[(i, key)].append(st)
|
|
213
|
+
|
|
214
|
+
# pair_groups: {(pos, valA, valB): set(其他qualifier元组)} — 记录每对值在多少独立组出现
|
|
215
|
+
pair_groups = defaultdict(set)
|
|
216
|
+
for (i, other_key), members in local_groups.items():
|
|
217
|
+
vals = sorted({m["qualifiers"][i] for m in members})
|
|
218
|
+
if len(vals) < 2:
|
|
219
|
+
continue
|
|
220
|
+
for a in range(len(vals)):
|
|
221
|
+
for b in range(a + 1, len(vals)):
|
|
222
|
+
pair_groups[(i, vals[a], vals[b])].add(other_key)
|
|
223
|
+
|
|
224
|
+
# 阶段B:跨组聚合 — 值对在 ≥2 独立组出现才升维
|
|
225
|
+
# 先按值对集合归并(同一维度可能有多对值,如三值枚举 A/B/C 有 3 对)
|
|
226
|
+
# 用并查集合并同位置的连通值对
|
|
227
|
+
pairs_by_pos = defaultdict(list) # pos -> [(valA, valB, group_count)]
|
|
228
|
+
for (i, va, vb), groups in pair_groups.items():
|
|
229
|
+
if len(groups) >= 2: # ≥2 独立组才是真维度
|
|
230
|
+
pairs_by_pos[i].append((va, vb, len(groups)))
|
|
231
|
+
# 并查集合并同位置连通值
|
|
232
|
+
out = []
|
|
233
|
+
_DIM_VAL_MAXLEN = 6 # 维度值最长 6 字符(tab 标签/短枚举),过长的是状态描述非维度
|
|
234
|
+
for i, pairs in sorted(pairs_by_pos.items()):
|
|
235
|
+
if not pairs:
|
|
236
|
+
continue
|
|
237
|
+
# 过滤过长的值(状态描述如"没有已出账单也没有未出账单"不是维度标签)
|
|
238
|
+
pairs = [(va, vb, gc) for va, vb, gc in pairs
|
|
239
|
+
if len(va) <= _DIM_VAL_MAXLEN and len(vb) <= _DIM_VAL_MAXLEN]
|
|
240
|
+
if not pairs:
|
|
241
|
+
continue
|
|
242
|
+
# 并查集
|
|
243
|
+
parent = {}
|
|
244
|
+
def find(x):
|
|
245
|
+
parent.setdefault(x, x)
|
|
246
|
+
while parent[x] != x:
|
|
247
|
+
parent[x] = parent[parent[x]]
|
|
248
|
+
x = parent[x]
|
|
249
|
+
return x
|
|
250
|
+
def union(a, b):
|
|
251
|
+
ra, rb = find(a), find(b)
|
|
252
|
+
if ra != rb:
|
|
253
|
+
parent[ra] = rb
|
|
254
|
+
for va, vb, _ in pairs:
|
|
255
|
+
union(va, vb)
|
|
256
|
+
# 按根分组
|
|
257
|
+
clusters = defaultdict(set)
|
|
258
|
+
for va, vb, _ in pairs:
|
|
259
|
+
clusters[find(va)].add(va)
|
|
260
|
+
clusters[find(vb)].add(vb)
|
|
261
|
+
for root, vals in clusters.items():
|
|
262
|
+
uniq = sorted(vals)
|
|
263
|
+
if len(uniq) < 2:
|
|
264
|
+
continue
|
|
265
|
+
# 分类:语法角色
|
|
266
|
+
# conditional 须有「纯净否定值」:整值=无/未/空/没 或 否定前缀+≤2字(无X/未X)
|
|
267
|
+
# 避免把 未出账/已出账(枚举值恰好含"未"字)误判为条件显隐
|
|
268
|
+
_PURE_NEG = re.compile(r"^(无|未|空|没|没有)$")
|
|
269
|
+
_NEG_SHORT = re.compile(r"^(无|未|空|没)(.{0,2})$")
|
|
270
|
+
has_pure_neg = any(_PURE_NEG.match(v) or _NEG_SHORT.match(v) for v in uniq)
|
|
271
|
+
# 若否定值与肯定值仅前缀不同(无X↔有X),则是条件;否则否定只是巧合 → tab
|
|
272
|
+
is_conditional = False
|
|
273
|
+
if has_pure_neg:
|
|
274
|
+
negs = [v for v in uniq if (_PURE_NEG.match(v) or _NEG_SHORT.match(v))]
|
|
275
|
+
poss = [v for v in uniq if not (_PURE_NEG.match(v) or _NEG_SHORT.match(v))]
|
|
276
|
+
# 条件显隐特征:存在 neg 与 pos 去掉前缀后词干相同(无交易↔有交易)
|
|
277
|
+
_STRIP_NEG = re.compile(r"^(无|未|空|没|没有)")
|
|
278
|
+
stems_match = any(
|
|
279
|
+
_STRIP_NEG.sub("", n) and _STRIP_NEG.sub("", n) in [re.sub(r"^(有|已)", "", p) for p in poss]
|
|
280
|
+
for n in negs
|
|
281
|
+
)
|
|
282
|
+
# 若所有否定值去掉前缀后能与某肯定值词干对上 → conditional
|
|
283
|
+
# 否则(如 未出账/已出账,"未出账"去"未"="出账"≠"已出账"去"已"="出账" → 其实对得上)
|
|
284
|
+
# 更精确:neg 去 neg 前缀 == pos 去 pos 前缀(有/已/正)
|
|
285
|
+
_STRIP_POS = re.compile(r"^(有|已)")
|
|
286
|
+
stems_match = any(
|
|
287
|
+
(_STRIP_NEG.sub("", n) or n) == (_STRIP_POS.sub("", p) or p)
|
|
288
|
+
for n in negs for p in poss
|
|
289
|
+
)
|
|
290
|
+
is_conditional = stems_match
|
|
291
|
+
all_result = all(_is_result_token(v) for v in uniq)
|
|
292
|
+
# 该簇的总独立组数
|
|
293
|
+
gc = sum(len(pair_groups[(i, va, vb)]) for va in uniq for vb in uniq if va < vb)
|
|
294
|
+
if all_result:
|
|
295
|
+
out.append({
|
|
296
|
+
"type": "sequential", "dim": f"phase{i}", "values": uniq,
|
|
297
|
+
"phases": uniq, "boundVar": slugify_dim(f"seq{i}", len(out)),
|
|
298
|
+
"tokens": uniq, "positional": True, "position": i, "groupCount": gc,
|
|
299
|
+
"note": "结果/状态态,由上游流程驱动;基础态 + 结果块 v-show 绑定 status 变量",
|
|
300
|
+
})
|
|
301
|
+
elif is_conditional:
|
|
302
|
+
negs = [v for v in uniq if (_PURE_NEG.match(v) or _NEG_SHORT.match(v))]
|
|
303
|
+
poss = [v for v in uniq if not (_PURE_NEG.match(v) or _NEG_SHORT.match(v))]
|
|
304
|
+
neg = negs[0] if negs else uniq[0]
|
|
305
|
+
pos = poss[0] if poss else uniq[-1]
|
|
306
|
+
out.append({
|
|
307
|
+
"type": "conditional", "dim": f"empty{i}", "kind": "presence",
|
|
308
|
+
"negative": neg, "positive": pos, "values": uniq,
|
|
309
|
+
"boundVar": slugify_dim(f"cond{i}", len(out)),
|
|
310
|
+
"showWhen": pos, "hideWhen": neg,
|
|
311
|
+
"tokens": uniq, "positional": True, "position": i, "groupCount": gc,
|
|
312
|
+
})
|
|
313
|
+
else:
|
|
314
|
+
out.append({
|
|
315
|
+
"type": "tab", "dim": f"tab{i}", "values": uniq,
|
|
316
|
+
"default": uniq[0], "boundVar": slugify_dim(f"tab{i}", len(out)),
|
|
317
|
+
"tokens": uniq, "positional": True, "position": i, "groupCount": gc,
|
|
318
|
+
})
|
|
319
|
+
return out
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def detect_forms(cluster_states):
|
|
323
|
+
"""表单态检测(纯结构):状态指纹相对基础态仅新增少量文本输入块(blockJaccard 高、
|
|
324
|
+
textJaccard 中等、新增 blocks 含输入/选择控件类型名)。
|
|
325
|
+
控件类型名取通用 UI 语义(input/select/选择/输入),非业务词。
|
|
326
|
+
"""
|
|
327
|
+
out, seen = [], set()
|
|
328
|
+
_FORM_CONTROL = re.compile(r"(input|select|输入|选择|填写|修改)", re.I)
|
|
329
|
+
for st in cluster_states:
|
|
330
|
+
df = st.get("diffVsBase") or {}
|
|
331
|
+
added_blocks = df.get("addedBlocks", [])
|
|
332
|
+
if not added_blocks:
|
|
333
|
+
continue
|
|
334
|
+
# 新增块里有输入/选择控件类型 → 表单态
|
|
335
|
+
if any(_FORM_CONTROL.search(b) for b in added_blocks):
|
|
336
|
+
token = "_".join(st["qualifiers"]) or st["name"]
|
|
337
|
+
if token in seen:
|
|
338
|
+
continue
|
|
339
|
+
field = added_blocks[0].split("/")[-1].strip()
|
|
340
|
+
out.append({
|
|
341
|
+
"type": "form", "token": token, "field": field,
|
|
342
|
+
"control": "input", "boundVar": slugify_dim(f"form{len(out)}", len(out)),
|
|
343
|
+
})
|
|
344
|
+
seen.add(token)
|
|
345
|
+
return out
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# 兼容旧调用名(build_cluster 仍引用)
|
|
349
|
+
def detect_enums(cluster_states):
|
|
350
|
+
return [d for d in detect_positional_tabs(cluster_states) if d["type"] == "tab"]
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def detect_pairs(cluster_states):
|
|
354
|
+
return [d for d in detect_positional_tabs(cluster_states) if d["type"] == "conditional"]
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def detect_sequential(cluster_states):
|
|
358
|
+
seqs = [d for d in detect_positional_tabs(cluster_states) if d["type"] == "sequential"]
|
|
359
|
+
# 保持旧输出结构(phases + 单变量)
|
|
360
|
+
return [{"type": "sequential", "phases": s["values"], "boundVar": s["boundVar"],
|
|
361
|
+
"note": s.get("note", "")} for s in seqs]
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
# ----------------------------------------------------------------------------
|
|
365
|
+
# 5. 主流程:聚类 + 产物聚合
|
|
366
|
+
# ----------------------------------------------------------------------------
|
|
367
|
+
def load_specs(out_dir):
|
|
368
|
+
specs = []
|
|
369
|
+
for name in sorted(os.listdir(out_dir)):
|
|
370
|
+
sd = os.path.join(out_dir, name)
|
|
371
|
+
sj = os.path.join(sd, "spec.json")
|
|
372
|
+
if not os.path.isfile(sj):
|
|
373
|
+
continue
|
|
374
|
+
try:
|
|
375
|
+
spec = json.load(open(sj, encoding="utf-8"))
|
|
376
|
+
except Exception as e:
|
|
377
|
+
print(f"[WARN] 读取失败 {sj}: {e}", file=sys.stderr)
|
|
378
|
+
continue
|
|
379
|
+
specs.append((name, spec))
|
|
380
|
+
return specs
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def build_cluster(card_key_name, items):
|
|
384
|
+
"""items: [(canvas_name, spec)]。聚合为一张逻辑卡片的多状态信息。"""
|
|
385
|
+
states = []
|
|
386
|
+
# 结构性过渡态判定:若某状态的某 qualifier 恰好是其他卡片的 cardKey,则它是过渡态
|
|
387
|
+
all_card_keys = {card_key(cn) for cn, _ in items}
|
|
388
|
+
for cn, spec in items:
|
|
389
|
+
qs = qualifiers(cn)
|
|
390
|
+
fp = fingerprint(spec)
|
|
391
|
+
# 过渡态:qualifier 里出现了别的 cardKey(跨卡跳转)
|
|
392
|
+
trans_to = next((q for q in qs if q in all_card_keys and q != card_key_name), None)
|
|
393
|
+
is_trans = trans_to is not None
|
|
394
|
+
states.append({
|
|
395
|
+
"name": cn,
|
|
396
|
+
"stateKey": abstract_state_key(qs, fp),
|
|
397
|
+
"qualifiers": qs,
|
|
398
|
+
"isTransition": is_trans,
|
|
399
|
+
"transitionTo": trans_to,
|
|
400
|
+
"fp": fp,
|
|
401
|
+
"artboardType": spec.get("artboardType", "card"),
|
|
402
|
+
"cardCount": len(spec.get("cards", [])),
|
|
403
|
+
"interaction": spec.get("interaction", ""),
|
|
404
|
+
"jsapiInteractions": spec.get("jsapiInteractions", []),
|
|
405
|
+
"flexLayouts": [c.get("flexLayout") for c in spec.get("cards", []) if c.get("flexLayout")],
|
|
406
|
+
})
|
|
407
|
+
# 推荐基础态:排除过渡态与空/错误态后,指纹最丰富
|
|
408
|
+
def richness(s):
|
|
409
|
+
return len(s["fp"]["texts"]) + len(s["fp"]["blocks"])
|
|
410
|
+
candidates = [s for s in states if not s["isTransition"] and not is_empty_or_error(s["qualifiers"], s["fp"])]
|
|
411
|
+
if not candidates:
|
|
412
|
+
candidates = [s for s in states if not s["isTransition"]]
|
|
413
|
+
if not candidates:
|
|
414
|
+
candidates = states
|
|
415
|
+
base = max(candidates, key=richness)
|
|
416
|
+
for st in states:
|
|
417
|
+
st["diffVsBase"] = diff_fingerprint(base["fp"], st["fp"])
|
|
418
|
+
# 维度提取(纯结构,统一由 detect_positional_tabs 产出 tab/conditional/sequential)
|
|
419
|
+
dims_all = detect_positional_tabs(states)
|
|
420
|
+
tabs = [d for d in dims_all if d["type"] == "tab"]
|
|
421
|
+
conds = [d for d in dims_all if d["type"] == "conditional"]
|
|
422
|
+
seqs = [{"type": "sequential", "phases": d["values"], "boundVar": d["boundVar"],
|
|
423
|
+
"note": d.get("note", "")} for d in dims_all if d["type"] == "sequential"]
|
|
424
|
+
forms = detect_forms(states)
|
|
425
|
+
seen_var = set()
|
|
426
|
+
tabs_dedup = []
|
|
427
|
+
for t in tabs:
|
|
428
|
+
if t["boundVar"] in seen_var:
|
|
429
|
+
continue
|
|
430
|
+
seen_var.add(t["boundVar"])
|
|
431
|
+
tabs_dedup.append(t)
|
|
432
|
+
# 合并所有状态的样式汇总(颜色去重)
|
|
433
|
+
all_colors = sorted({c for s in states for c in s["fp"]["colors"]})
|
|
434
|
+
return {
|
|
435
|
+
"cardKey": card_key_name,
|
|
436
|
+
"canvasCount": len(states),
|
|
437
|
+
"stateNames": [s["name"] for s in states],
|
|
438
|
+
"stateKeys": sorted({s["stateKey"] for s in states}),
|
|
439
|
+
"recommendedBase": base["name"],
|
|
440
|
+
"states": [{k: v for k, v in s.items() if k != "fp"} | {"fingerprint": s["fp"]} for s in states],
|
|
441
|
+
"dimensions": {
|
|
442
|
+
"tabs": tabs_dedup,
|
|
443
|
+
"conditionals": conds,
|
|
444
|
+
"forms": forms,
|
|
445
|
+
"sequential": seqs,
|
|
446
|
+
},
|
|
447
|
+
"colors": all_colors,
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def aggregate_card_products(out_dir, card):
|
|
452
|
+
"""把一张逻辑卡片的多状态产物聚合到 cards/<cardKey>/。"""
|
|
453
|
+
cdir = os.path.join(out_dir, "cards", card["cardKey"])
|
|
454
|
+
states_dir = os.path.join(cdir, "states")
|
|
455
|
+
os.makedirs(states_dir, exist_ok=True)
|
|
456
|
+
aggregated = {"copiedStates": []}
|
|
457
|
+
# 每个状态:复制代表性聚焦截图 + spec.json 到 states/<stateKey>_<idx>/
|
|
458
|
+
seen_state_keys = defaultdict(int)
|
|
459
|
+
for st in card["states"]:
|
|
460
|
+
cn = st["name"]
|
|
461
|
+
src_canvas_dir = os.path.join(out_dir, cn)
|
|
462
|
+
if not os.path.isdir(src_canvas_dir):
|
|
463
|
+
continue
|
|
464
|
+
sk = st["stateKey"]
|
|
465
|
+
seen_state_keys[sk] += 1
|
|
466
|
+
state_name = f"{sk}_{seen_state_keys[sk]}" if seen_state_keys[sk] > 1 else sk
|
|
467
|
+
dst_state_dir = os.path.join(states_dir, state_name)
|
|
468
|
+
os.makedirs(dst_state_dir, exist_ok=True)
|
|
469
|
+
# 复制该画板的聚焦截图(card-*.png)到 states/<state>/
|
|
470
|
+
n_img = 0
|
|
471
|
+
for fn in sorted(os.listdir(src_canvas_dir)):
|
|
472
|
+
if re.match(r"card-\d+\.png$", fn):
|
|
473
|
+
shutil.copy2(os.path.join(src_canvas_dir, fn), os.path.join(dst_state_dir, fn))
|
|
474
|
+
n_img += 1
|
|
475
|
+
# 复制整图(参考)
|
|
476
|
+
if os.path.isfile(os.path.join(src_canvas_dir, "artboard.png")):
|
|
477
|
+
shutil.copy2(os.path.join(src_canvas_dir, "artboard.png"),
|
|
478
|
+
os.path.join(dst_state_dir, "artboard.png"))
|
|
479
|
+
# 复制 spec.json
|
|
480
|
+
src_spec = os.path.join(src_canvas_dir, "spec.json")
|
|
481
|
+
if os.path.isfile(src_spec):
|
|
482
|
+
shutil.copy2(src_spec, os.path.join(dst_state_dir, "spec.json"))
|
|
483
|
+
aggregated["copiedStates"].append({
|
|
484
|
+
"stateName": state_name, "canvas": cn,
|
|
485
|
+
"stateKey": sk, "images": n_img,
|
|
486
|
+
})
|
|
487
|
+
# 写 card.json
|
|
488
|
+
# 聚合所有状态的 JSAPI 交互控件与 Flex 布局(去重)
|
|
489
|
+
jsapi_all = []
|
|
490
|
+
seen_js = set()
|
|
491
|
+
flex_all = []
|
|
492
|
+
for st in card["states"]:
|
|
493
|
+
for j in st.get("jsapiInteractions", []):
|
|
494
|
+
k = j.get("control")
|
|
495
|
+
if k and k not in seen_js:
|
|
496
|
+
jsapi_all.append(j)
|
|
497
|
+
seen_js.add(k)
|
|
498
|
+
for fl in st.get("flexLayouts", []):
|
|
499
|
+
if fl and fl not in flex_all:
|
|
500
|
+
flex_all.append(fl)
|
|
501
|
+
card_json = {
|
|
502
|
+
"cardKey": card["cardKey"],
|
|
503
|
+
"pageId": card.get("pageId"),
|
|
504
|
+
"cardKeyDisplay": card.get("cardKeyDisplay"),
|
|
505
|
+
"isDialog": card.get("isDialog", False),
|
|
506
|
+
"parentKey": card.get("parentKey"),
|
|
507
|
+
"canvasCount": card["canvasCount"],
|
|
508
|
+
"recommendedBase": card["recommendedBase"],
|
|
509
|
+
"stateKeys": card["stateKeys"],
|
|
510
|
+
"stateNames": card["stateNames"],
|
|
511
|
+
"dimensions": card["dimensions"],
|
|
512
|
+
"colors": card["colors"],
|
|
513
|
+
"jsapiInteractions": jsapi_all,
|
|
514
|
+
"flexLayouts": flex_all,
|
|
515
|
+
"states": aggregated["copiedStates"],
|
|
516
|
+
}
|
|
517
|
+
with open(os.path.join(cdir, "card.json"), "w", encoding="utf-8") as f:
|
|
518
|
+
json.dump(card_json, f, ensure_ascii=False, indent=2)
|
|
519
|
+
return aggregated["copiedStates"]
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def write_interactions_index(out_dir, cards, overlays):
|
|
523
|
+
"""跨卡片交互图:弹框/子卡父子关系 + 维度切换。"""
|
|
524
|
+
lines = ["# 跨卡片交互图(由 cluster_states.py 生成)", ""]
|
|
525
|
+
if overlays:
|
|
526
|
+
lines.append("## 弹框 / 提示(overlay_dialog → alertDialog JSAPI,不建卡)")
|
|
527
|
+
for o in overlays:
|
|
528
|
+
lines.append(f"- `{o['name']}`:父卡候选 `{o['parentKey']}`;"
|
|
529
|
+
f"参考 `{', '.join(o.get('refs', []))}`")
|
|
530
|
+
lines.append("")
|
|
531
|
+
subcards = [c for c in cards if c.get("isDialog")]
|
|
532
|
+
if subcards:
|
|
533
|
+
lines.append("## 子卡(subcard → alertCard 父子卡片,独立 Dialog 卡)")
|
|
534
|
+
for c in subcards:
|
|
535
|
+
lines.append(f"- `{c['cardKey']}`:父卡 `{c.get('parentKey')}`;"
|
|
536
|
+
f"打开 `alertCard({{cardCode}})` / 关闭 `closeAlertCard`")
|
|
537
|
+
lines.append("")
|
|
538
|
+
lines.append("## 维度切换(多形态卡片)")
|
|
539
|
+
for c in cards:
|
|
540
|
+
d = c["dimensions"]
|
|
541
|
+
dims = []
|
|
542
|
+
for t in d["tabs"]:
|
|
543
|
+
dims.append(f"Tab:{t['dim']}={t['values']}")
|
|
544
|
+
for cond in d["conditionals"]:
|
|
545
|
+
dims.append(f"Cond:{cond['kind']}({cond['negative']}↔{cond['positive']})")
|
|
546
|
+
for fm in d["forms"]:
|
|
547
|
+
dims.append(f"Form:{fm['field']}({fm['control']})")
|
|
548
|
+
for sq in d["sequential"]:
|
|
549
|
+
dims.append(f"Seq:{sq['phases']}")
|
|
550
|
+
if dims:
|
|
551
|
+
lines.append(f"- `{c['cardKey']}`:{'; '.join(dims)}")
|
|
552
|
+
lines.append("")
|
|
553
|
+
# JSAPI 外部交互控件聚合
|
|
554
|
+
jsapi_cards = [c for c in cards if any(s.get("jsapiInteractions") for s in c["states"])]
|
|
555
|
+
if jsapi_cards:
|
|
556
|
+
lines.append("## JSAPI 外部交互控件(密码/验证码/数字键盘/toast/二次确认)")
|
|
557
|
+
for c in jsapi_cards:
|
|
558
|
+
seen_ctl = set()
|
|
559
|
+
for st in c["states"]:
|
|
560
|
+
for j in st.get("jsapiInteractions", []):
|
|
561
|
+
ctl = j.get("control")
|
|
562
|
+
if ctl and ctl not in seen_ctl:
|
|
563
|
+
seen_ctl.add(ctl)
|
|
564
|
+
lines.append(f"- `{c['cardKey']}` ← `{ctl}`:{j.get('note', '')}")
|
|
565
|
+
lines.append("")
|
|
566
|
+
lines.append("")
|
|
567
|
+
idx_dir = os.path.join(out_dir, "interactions")
|
|
568
|
+
os.makedirs(idx_dir, exist_ok=True)
|
|
569
|
+
with open(os.path.join(idx_dir, "index.yaml"), "w", encoding="utf-8") as f:
|
|
570
|
+
f.write("\n".join(lines))
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def augment_prompt(out_dir, card):
|
|
574
|
+
"""给每画板 prompt.md 追加 ## 5. 多形态归属。"""
|
|
575
|
+
dims = card["dimensions"]
|
|
576
|
+
lines = ["", "## 5. 多形态归属(由 cluster_states.py 生成)", ""]
|
|
577
|
+
lines.append(f"- **所属逻辑卡片**:`{card['cardKey']}`(共 {card['canvasCount']} 个形态)")
|
|
578
|
+
lines.append(f"- **抽象状态键**:{card['stateKeys']}")
|
|
579
|
+
lines.append(f"- **推荐基础态**:`{card['recommendedBase']}`")
|
|
580
|
+
lines.append("")
|
|
581
|
+
lines.append("### 检测到的切换维度")
|
|
582
|
+
if dims["tabs"]:
|
|
583
|
+
for t in dims["tabs"]:
|
|
584
|
+
lines.append(f"- **Tab 维度** `{t['dim']}`:取值 {t['values']},默认 `{t['default']}`,绑定变量 `{t['boundVar']}`")
|
|
585
|
+
if dims["conditionals"]:
|
|
586
|
+
for c in dims["conditionals"]:
|
|
587
|
+
lines.append(f"- **条件显隐** `{c['kind']}`:`{c['negative']}` ↔ `{c['positive']}`,变量 `{c['boundVar']}`")
|
|
588
|
+
if dims["forms"]:
|
|
589
|
+
for fm in dims["forms"]:
|
|
590
|
+
lines.append(f"- **动态表单** `{fm['token']}`:控件 `{fm['control']}`,字段 `{fm['field']}`,变量 `{fm['boundVar']}`")
|
|
591
|
+
if dims["sequential"]:
|
|
592
|
+
for s in dims["sequential"]:
|
|
593
|
+
lines.append(f"- **时序/结果态** 阶段 {s['phases']},变量 `{s['boundVar']}`")
|
|
594
|
+
if not (dims["tabs"] or dims["conditionals"] or dims["forms"] or dims["sequential"]):
|
|
595
|
+
lines.append("- (未检测到明确切换维度,按单态生成)")
|
|
596
|
+
lines.append("")
|
|
597
|
+
lines.append("> 多形态建卡:以推荐基础态建基础 schema,Tab 用 TabBar + `v-show` 切内容;"
|
|
598
|
+
"条件显隐对差异块加 `v-show`;表单插 Input/Select 绑变量。")
|
|
599
|
+
if card.get("isDialog"):
|
|
600
|
+
lines.append("")
|
|
601
|
+
lines.append("### 本卡为 alertCard 子卡(Dialog 卡)")
|
|
602
|
+
lines.append("- 关闭按钮(×) → `ynet.callJSAPI('closeAlertCard', { cardCode: '<pageId>' })`")
|
|
603
|
+
parent = card.get("parentKey") or "对应触发卡"
|
|
604
|
+
lines.append(f"- 父卡(`{parent}`)打开:`ynet.callJSAPI('alertCard', {{ cardCode: '<pageId>', data: {{...}}, gravity: 'bottom' }})`")
|
|
605
|
+
if ALERTCARD_REF_CARDS:
|
|
606
|
+
lines.append(f"- 参考存量:{', '.join(ALERTCARD_REF_CARDS)}")
|
|
607
|
+
block = "\n".join(lines)
|
|
608
|
+
for st in card["states"]:
|
|
609
|
+
cn = st["name"]
|
|
610
|
+
pm = os.path.join(out_dir, cn, "prompt.md")
|
|
611
|
+
if not os.path.isfile(pm):
|
|
612
|
+
continue
|
|
613
|
+
txt = open(pm, encoding="utf-8").read()
|
|
614
|
+
txt = re.sub(r"\n## 5\. 多形态归属.*?(?=\n## |\Z)", "", txt, flags=re.S)
|
|
615
|
+
txt = txt.rstrip() + "\n" + block + "\n"
|
|
616
|
+
open(pm, "w", encoding="utf-8").write(txt)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def main():
|
|
620
|
+
ap = argparse.ArgumentParser(description="卡片维度聚类 + 产物聚合(视觉驱动 spec 结构)")
|
|
621
|
+
ap.add_argument("--out", required=True, help="UI 目录(含各画板子目录与 spec.json)")
|
|
622
|
+
ap.add_argument("--card-refs", default=None)
|
|
623
|
+
ap.add_argument("--auto-refs", action="store_true")
|
|
624
|
+
ap.add_argument("--src-cards", default=None)
|
|
625
|
+
args = ap.parse_args()
|
|
626
|
+
|
|
627
|
+
global ALERTDIALOG_REF_CARDS, ALERTCARD_REF_CARDS
|
|
628
|
+
refs = load_card_refs(args.card_refs, args.auto_refs, _resolve_src_cards(args.src_cards))
|
|
629
|
+
ALERTDIALOG_REF_CARDS = refs["alertDialog"]
|
|
630
|
+
ALERTCARD_REF_CARDS = refs["alertCard"]
|
|
631
|
+
if args.auto_refs:
|
|
632
|
+
print(f"[INFO] 自动探测参考卡:alertDialog×{len(ALERTDIALOG_REF_CARDS)} / alertCard×{len(ALERTCARD_REF_CARDS)}")
|
|
633
|
+
|
|
634
|
+
out_dir = os.path.abspath(args.out)
|
|
635
|
+
if not os.path.isdir(out_dir):
|
|
636
|
+
print(f"[ERR] 目录不存在: {out_dir}", file=sys.stderr)
|
|
637
|
+
sys.exit(1)
|
|
638
|
+
|
|
639
|
+
specs = load_specs(out_dir)
|
|
640
|
+
if not specs:
|
|
641
|
+
print(f"[ERR] {out_dir} 下未找到任何 spec.json(请先运行 parse_meaxure.py --build)", file=sys.stderr)
|
|
642
|
+
sys.exit(1)
|
|
643
|
+
|
|
644
|
+
# 聚类分流:普通卡按 card_key;subcard 独立;overlay_dialog 进 overlays
|
|
645
|
+
clusters_map = defaultdict(list)
|
|
646
|
+
overlays = []
|
|
647
|
+
for cn, spec in specs:
|
|
648
|
+
at = spec.get("artboardType", "card")
|
|
649
|
+
if at == "overlay_dialog":
|
|
650
|
+
parts = normalize_name(cn).split("_", 1)
|
|
651
|
+
parent_key = parts[0] if len(parts) > 1 else None
|
|
652
|
+
overlays.append({
|
|
653
|
+
"name": cn, "artboardType": "overlay_dialog",
|
|
654
|
+
"interaction": spec.get("interaction", ""),
|
|
655
|
+
"parentKey": parent_key, "refs": list(ALERTDIALOG_REF_CARDS),
|
|
656
|
+
})
|
|
657
|
+
continue
|
|
658
|
+
if at == "subcard":
|
|
659
|
+
key = normalize_name(cn)
|
|
660
|
+
else:
|
|
661
|
+
key = card_key(cn)
|
|
662
|
+
clusters_map[key].append((cn, spec))
|
|
663
|
+
|
|
664
|
+
cards = []
|
|
665
|
+
for key in sorted(clusters_map):
|
|
666
|
+
items = clusters_map[key]
|
|
667
|
+
card = build_cluster(key, items)
|
|
668
|
+
# 英文 pageId(稳定序号,供下游当 id 用,解决 cardKey 中文无法当 pageId)+ 中文 cardKeyDisplay(展示用)
|
|
669
|
+
card["pageId"] = f"card-{len(cards) + 1}"
|
|
670
|
+
card["cardKeyDisplay"] = key
|
|
671
|
+
is_dialog = any(s.get("artboardType") == "subcard" for _, s in items)
|
|
672
|
+
card["isDialog"] = is_dialog
|
|
673
|
+
if is_dialog:
|
|
674
|
+
pk = key.split("_", 1)
|
|
675
|
+
card["parentKey"] = pk[0] if len(pk) > 1 else None
|
|
676
|
+
aggregate_card_products(out_dir, card)
|
|
677
|
+
augment_prompt(out_dir, card)
|
|
678
|
+
cards.append(card)
|
|
679
|
+
|
|
680
|
+
draft_name = os.path.basename(out_dir.rstrip("/\\"))
|
|
681
|
+
result = {
|
|
682
|
+
"draft": draft_name,
|
|
683
|
+
"totalCanvases": len(specs),
|
|
684
|
+
"cardCount": len(cards),
|
|
685
|
+
"cards": cards,
|
|
686
|
+
"overlays": overlays,
|
|
687
|
+
}
|
|
688
|
+
with open(os.path.join(out_dir, "clusters.json"), "w", encoding="utf-8") as f:
|
|
689
|
+
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
690
|
+
write_interactions_index(out_dir, cards, overlays)
|
|
691
|
+
|
|
692
|
+
print(f"[OK] 草稿「{draft_name}」共 {len(specs)} 画板 → {len(cards)} 张逻辑卡片"
|
|
693
|
+
+ (f" + {len(overlays)} 弹框(overlay_dialog)" if overlays else ""))
|
|
694
|
+
for c in cards:
|
|
695
|
+
d = c["dimensions"]
|
|
696
|
+
ntab = len(d["tabs"]); ncond = len(d["conditionals"]); nform = len(d["forms"]); nseq = len(d["sequential"])
|
|
697
|
+
tag = " [Dialog子卡]" if c.get("isDialog") else ""
|
|
698
|
+
print(f" - {c['cardKey']}{tag}: {c['canvasCount']}态 keys={c['stateKeys']} | "
|
|
699
|
+
f"tab×{ntab} cond×{ncond} form×{nform} seq×{nseq} | base={c['recommendedBase']}")
|
|
700
|
+
for o in overlays:
|
|
701
|
+
print(f" - [overlay_dialog] {o['name']} (父卡候选: {o['parentKey']})")
|
|
702
|
+
print(f"[OK] 已写出 {out_dir}/clusters.json + cards/<cardKey>/ + interactions/index.yaml")
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
if __name__ == "__main__":
|
|
706
|
+
main()
|