@ecsplico/editor 3.6.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/dist/adapters/direct-ai.d.ts +4 -4
- package/dist/adapters/direct-ai.js +2 -2
- package/dist/adapters/direct-ai.js.map +1 -1
- package/dist/adapters/document-state.d.ts +1 -1
- package/dist/adapters/document-state.js.map +1 -1
- package/dist/adapters/forge.d.ts +2 -2
- package/dist/adapters/forge.js.map +1 -1
- package/dist/adapters/nitro.d.ts +11 -3
- package/dist/adapters/nitro.js +3 -1
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +6 -6
- package/dist/adapters/shared.js.map +1 -1
- package/dist/config/index.d.ts +14 -14
- package/dist/config/index.js +8 -8
- package/dist/config/index.js.map +1 -1
- package/dist/core/backend-types.d.ts +3 -3
- package/dist/core/backend-types.js.map +1 -1
- package/dist/core/backend.d.ts +4 -4
- package/dist/core/backend.js.map +1 -1
- package/dist/core/block-convert.d.ts +1 -1
- package/dist/core/block-convert.js +5 -5
- package/dist/core/block-convert.js.map +1 -1
- package/dist/core/block-transfer.d.ts +8 -4
- package/dist/core/block-transfer.js +23 -14
- package/dist/core/block-transfer.js.map +1 -1
- package/dist/core/capabilities.d.ts +113 -0
- package/dist/core/capabilities.js +155 -0
- package/dist/core/capabilities.js.map +1 -0
- package/dist/core/command-types.d.ts +1 -1
- package/dist/core/context.js.map +1 -1
- package/dist/core/convert.d.ts +5 -5
- package/dist/core/convert.js +5 -5
- package/dist/core/convert.js.map +1 -1
- package/dist/core/core-feature.js +1 -1
- package/dist/core/define-editor.d.ts +2 -2
- package/dist/core/define-editor.js +34 -6
- package/dist/core/define-editor.js.map +1 -1
- package/dist/core/derivation.d.ts +119 -0
- package/dist/core/derivation.js +187 -0
- package/dist/core/derivation.js.map +1 -0
- package/dist/core/derive.d.ts +61 -0
- package/dist/core/derive.js +69 -0
- package/dist/core/derive.js.map +1 -0
- package/dist/core/extension-points.d.ts +16 -0
- package/dist/core/feature-context.d.ts +5 -1
- package/dist/core/feature-context.js +10 -3
- package/dist/core/feature-context.js.map +1 -1
- package/dist/core/feature.d.ts +41 -19
- package/dist/core/feature.js.map +1 -1
- package/dist/core/fences.js +1 -1
- package/dist/core/fences.js.map +1 -1
- package/dist/core/index.d.ts +6 -2
- package/dist/core/index.js +5 -1
- package/dist/core/pipeline.d.ts +14 -5
- package/dist/core/pipeline.js +19 -7
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/properties.d.ts +22 -0
- package/dist/core/properties.js +17 -0
- package/dist/core/properties.js.map +1 -0
- package/dist/core/schema/block-ids.js +5 -5
- package/dist/core/schema/block-ids.js.map +1 -1
- package/dist/core/schema/index.d.ts +2 -1
- package/dist/core/schema/index.js +2 -1
- package/dist/core/schema/index.js.map +1 -1
- package/dist/core/schema/opaque-fence.d.ts +4 -4
- package/dist/core/schema/opaque-fence.js +7 -7
- package/dist/core/schema/opaque-fence.js.map +1 -1
- package/dist/core/schema/persisted-names.d.ts +58 -0
- package/dist/core/schema/persisted-names.js +96 -0
- package/dist/core/schema/persisted-names.js.map +1 -0
- package/dist/core/toolbar-items.js +1 -1
- package/dist/core/toolbar-items.js.map +1 -1
- package/dist/core/util.js +5 -1
- package/dist/core/util.js.map +1 -1
- package/dist/features/ai/agent-ops.js +1 -1
- package/dist/features/ai/ai-review.js +1 -1
- package/dist/features/ai/assistant.d.ts +4 -4
- package/dist/features/ai/assistant.js +1 -1
- package/dist/features/ai/assistant.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.js +7 -7
- package/dist/features/ai/components/ModelQuickSwitch.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.vue.d.ts +4 -4
- package/dist/features/ai/components/SettingsAiTab.js +8 -8
- package/dist/features/ai/components/SettingsAiTab.js.map +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/document-chat-model.d.ts +2 -2
- package/dist/features/ai/document-chat-model.js +5 -5
- package/dist/features/ai/document-chat-model.js.map +1 -1
- package/dist/features/ai/document-chat.js +2 -2
- package/dist/features/ai/document-chat.js.map +1 -1
- package/dist/features/ai/feature.d.ts +2 -2
- package/dist/features/ai/feature.js +2 -2
- package/dist/features/ai/feature.js.map +1 -1
- package/dist/features/ai/ghost-autocomplete.d.ts +4 -4
- package/dist/features/ai/ghost-autocomplete.js +1 -1
- package/dist/features/ai/ghost-autocomplete.js.map +1 -1
- package/dist/features/ai/index.d.ts +3 -3
- package/dist/features/ai/index.js +2 -2
- package/dist/features/ai/model-profiles.d.ts +9 -9
- package/dist/features/ai/model-profiles.js +13 -13
- package/dist/features/ai/model-profiles.js.map +1 -1
- package/dist/features/ai/protocol/agent-ops.d.ts +2 -2
- package/dist/features/ai/protocol/agent-ops.js +1 -1
- package/dist/features/ai/protocol/agent-ops.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.d.ts +7 -7
- package/dist/features/ai/protocol/block-lines.js +10 -10
- package/dist/features/ai/protocol/block-lines.js.map +1 -1
- package/dist/features/ai/protocol/chat.d.ts +5 -5
- package/dist/features/ai/protocol/chat.js +12 -12
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/generate.d.ts +1 -1
- package/dist/features/ai/protocol/generate.js +1 -1
- package/dist/features/ai/protocol/generate.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -3
- package/dist/features/ai/protocol/index.js +2 -2
- package/dist/features/ai/protocol/models.d.ts +3 -3
- package/dist/features/ai/protocol/models.js +3 -3
- package/dist/features/ai/protocol/models.js.map +1 -1
- package/dist/features/ai/protocol/schemas.d.ts +1 -1
- package/dist/features/ai/protocol/schemas.js +1 -1
- package/dist/features/ai/protocol/schemas.js.map +1 -1
- package/dist/features/ai/protocol/tasks.d.ts +5 -5
- package/dist/features/ai/protocol/tasks.js +12 -12
- package/dist/features/ai/protocol/tasks.js.map +1 -1
- package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/react/ModelQuickSwitch.d.ts +4 -4
- package/dist/features/ai/react/ModelQuickSwitch.js +6 -6
- package/dist/features/ai/react/ModelQuickSwitch.js.map +1 -1
- package/dist/features/ai/react/SettingsAiTab.d.ts +1 -1
- package/dist/features/ai/react/SettingsAiTab.js +8 -8
- package/dist/features/ai/react/SettingsAiTab.js.map +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.d.ts +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/react/use-ai-assistant.js +1 -1
- package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/react/use-model-profiles.d.ts +5 -5
- package/dist/features/ai/react/use-model-profiles.js +2 -2
- package/dist/features/ai/react/use-model-profiles.js.map +1 -1
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-ai-assistant.js +1 -1
- package/dist/features/ai/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/use-model-profiles.d.ts +4 -4
- package/dist/features/ai/use-model-profiles.js +2 -2
- package/dist/features/ai/use-model-profiles.js.map +1 -1
- package/dist/features/block-ui/block-actions.d.ts +8 -4
- package/dist/features/block-ui/block-actions.js +38 -22
- package/dist/features/block-ui/block-actions.js.map +1 -1
- package/dist/features/block-ui/block-target.d.ts +29 -0
- package/dist/features/block-ui/block-target.js +63 -0
- package/dist/features/block-ui/block-target.js.map +1 -0
- package/dist/features/block-ui/components/BlockHandles.js +3 -3
- package/dist/features/block-ui/components/BlockHandles.js.map +1 -1
- package/dist/features/block-ui/components/BlockUi.js +11 -23
- package/dist/features/block-ui/components/BlockUi.js.map +1 -1
- package/dist/features/block-ui/feature.d.ts +3 -2
- package/dist/features/block-ui/feature.js +3 -2
- package/dist/features/block-ui/feature.js.map +1 -1
- package/dist/features/block-ui/handle-geometry.js +12 -23
- package/dist/features/block-ui/handle-geometry.js.map +1 -1
- package/dist/features/block-ui/index.d.ts +2 -1
- package/dist/features/block-ui/index.js +2 -1
- package/dist/features/block-ui/react/BlockHandles.d.ts +1 -1
- package/dist/features/block-ui/react/BlockHandles.js +4 -4
- package/dist/features/block-ui/react/BlockHandles.js.map +1 -1
- package/dist/features/block-ui/react/BlockUi.js +13 -25
- package/dist/features/block-ui/react/BlockUi.js.map +1 -1
- package/dist/features/comments/model.js +1 -1
- package/dist/features/composites/accepted-children.d.ts +5 -4
- package/dist/features/composites/accepted-children.js +26 -26
- package/dist/features/composites/accepted-children.js.map +1 -1
- package/dist/features/composites/ai-brief.d.ts +4 -4
- package/dist/features/composites/ai-brief.js +14 -12
- package/dist/features/composites/ai-brief.js.map +1 -1
- package/dist/features/composites/anchors.d.ts +1 -1
- package/dist/features/composites/anchors.js.map +1 -1
- package/dist/features/composites/baskets.d.ts +1 -1
- package/dist/features/composites/baskets.js.map +1 -1
- package/dist/features/composites/bibliography-command.d.ts +1 -1
- package/dist/features/composites/bibliography-form.js +1 -1
- package/dist/features/composites/bibliography-form.js.map +1 -1
- package/dist/features/composites/bibliography.js.map +1 -1
- package/dist/features/composites/block-types-json.d.ts +38 -0
- package/dist/features/composites/block-types-json.js +122 -0
- package/dist/features/composites/block-types-json.js.map +1 -0
- package/dist/features/composites/block-types.d.ts +119 -0
- package/dist/features/composites/block-types.js +343 -0
- package/dist/features/composites/block-types.js.map +1 -0
- package/dist/features/composites/capabilities.js +109 -0
- package/dist/features/composites/capabilities.js.map +1 -0
- package/dist/features/composites/components/BibliographyEditor.js +5 -5
- package/dist/features/composites/components/BibliographyEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeBasketMenu.js +2 -2
- package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -1
- package/dist/features/composites/components/CompositeChrome.js +18 -8
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeDetails.js +11 -10
- package/dist/features/composites/components/CompositeDetails.js.map +1 -1
- package/dist/features/composites/components/CompositeDetails.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeFieldEditor.js +7 -7
- package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeFieldEditor.vue.d.ts +5 -5
- package/dist/features/composites/components/CompositeInspector.js +136 -113
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/components/CompositeInspector.vue.d.ts +3 -3
- package/dist/features/composites/components/CompositeStatusMenu.js +2 -2
- package/dist/features/composites/components/CompositeStatusMenu.js.map +1 -1
- package/dist/features/composites/components/CompositeSummaryEditor.js +1 -1
- package/dist/features/composites/components/CompositeSummaryEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeSummaryEditor.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeTable.js +2 -2
- package/dist/features/composites/components/CompositeTable.js.map +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.vue.d.ts +1 -1
- package/dist/features/composites/components/CompositeView.js +19 -17
- package/dist/features/composites/components/CompositeView.js.map +1 -1
- package/dist/features/composites/components/DerivationBadge.js +15 -6
- package/dist/features/composites/components/DerivationBadge.js.map +1 -1
- package/dist/features/composites/components/FocusChip.js +1 -1
- package/dist/features/composites/components/FocusChip.js.map +1 -1
- package/dist/features/composites/components/FocusChip.vue.d.ts +1 -1
- package/dist/features/composites/components/ReferenceFieldSelect.js +3 -3
- package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -1
- package/dist/features/composites/components/ReferenceView.js +1 -1
- package/dist/features/composites/components/ReferenceView.js.map +1 -1
- package/dist/features/composites/components/SettingsEditingTab.js +18 -18
- package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
- package/dist/features/composites/components/SettingsEditingTab.vue.d.ts +3 -3
- package/dist/features/composites/convert.d.ts +4 -4
- package/dist/features/composites/convert.js +27 -28
- package/dist/features/composites/convert.js.map +1 -1
- package/dist/features/composites/derived-chrome.js +34 -28
- package/dist/features/composites/derived-chrome.js.map +1 -1
- package/dist/features/composites/editing-settings-keys.js +6 -6
- package/dist/features/composites/editing-settings-keys.js.map +1 -1
- package/dist/features/composites/feature.d.ts +10 -10
- package/dist/features/composites/feature.js +58 -46
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/fences.d.ts +3 -3
- package/dist/features/composites/fences.js +18 -13
- package/dist/features/composites/fences.js.map +1 -1
- package/dist/features/composites/focus.d.ts +10 -10
- package/dist/features/composites/focus.js +11 -11
- package/dist/features/composites/focus.js.map +1 -1
- package/dist/features/composites/heading-composites.d.ts +5 -5
- package/dist/features/composites/heading-composites.js +13 -16
- package/dist/features/composites/heading-composites.js.map +1 -1
- package/dist/features/composites/index.d.ts +20 -18
- package/dist/features/composites/index.js +9 -7
- package/dist/features/composites/inspector-model.d.ts +45 -22
- package/dist/features/composites/inspector-model.js +53 -14
- package/dist/features/composites/inspector-model.js.map +1 -1
- package/dist/features/composites/legacy.d.ts +5 -4
- package/dist/features/composites/legacy.js +16 -23
- package/dist/features/composites/legacy.js.map +1 -1
- package/dist/features/composites/markdown-export.js +3 -3
- package/dist/features/composites/markdown-export.js.map +1 -1
- package/dist/features/composites/merge.d.ts +8 -8
- package/dist/features/composites/merge.js +23 -22
- package/dist/features/composites/merge.js.map +1 -1
- package/dist/features/composites/meta-display.js +5 -5
- package/dist/features/composites/meta-display.js.map +1 -1
- package/dist/features/composites/meta-keys.d.ts +5 -5
- package/dist/features/composites/meta-keys.js +8 -8
- package/dist/features/composites/meta-keys.js.map +1 -1
- package/dist/features/composites/node-views.d.ts +3 -3
- package/dist/features/composites/node-views.js +2 -2
- package/dist/features/composites/node-views.js.map +1 -1
- package/dist/features/composites/nodes.d.ts +13 -13
- package/dist/features/composites/nodes.js +100 -77
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/outline.d.ts +15 -12
- package/dist/features/composites/outline.js +24 -21
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/properties.d.ts +51 -0
- package/dist/features/composites/properties.js +89 -0
- package/dist/features/composites/properties.js.map +1 -0
- package/dist/features/composites/react/BibliographyEditor.js +5 -5
- package/dist/features/composites/react/BibliographyEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeBasketMenu.js +2 -2
- package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -1
- package/dist/features/composites/react/CompositeChrome.d.ts +1 -1
- package/dist/features/composites/react/CompositeChrome.js +15 -6
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.d.ts +1 -1
- package/dist/features/composites/react/CompositeDetails.js +11 -10
- package/dist/features/composites/react/CompositeDetails.js.map +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.d.ts +5 -5
- package/dist/features/composites/react/CompositeFieldEditor.js +6 -6
- package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeInspector.d.ts +3 -3
- package/dist/features/composites/react/CompositeInspector.js +142 -128
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.js +2 -2
- package/dist/features/composites/react/CompositeStatusMenu.js.map +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.d.ts +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeTable.d.ts +1 -1
- package/dist/features/composites/react/CompositeTable.js +3 -3
- package/dist/features/composites/react/CompositeTable.js.map +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.d.ts +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.js +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeView.js +17 -15
- package/dist/features/composites/react/CompositeView.js.map +1 -1
- package/dist/features/composites/react/DerivationBadge.js +13 -4
- package/dist/features/composites/react/DerivationBadge.js.map +1 -1
- package/dist/features/composites/react/FocusChip.d.ts +1 -1
- package/dist/features/composites/react/FocusChip.js +1 -1
- package/dist/features/composites/react/FocusChip.js.map +1 -1
- package/dist/features/composites/react/ReferenceFieldSelect.js +3 -3
- package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -1
- package/dist/features/composites/react/ReferenceView.d.ts +1 -1
- package/dist/features/composites/react/ReferenceView.js +1 -1
- package/dist/features/composites/react/ReferenceView.js.map +1 -1
- package/dist/features/composites/react/SettingsEditingTab.d.ts +4 -4
- package/dist/features/composites/react/SettingsEditingTab.js +20 -20
- package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
- package/dist/features/composites/react.js +1 -1
- package/dist/features/composites/react.js.map +1 -1
- package/dist/features/composites/reference-ai.js +1 -1
- package/dist/features/composites/reference-ai.js.map +1 -1
- package/dist/features/composites/reference-label.d.ts +3 -3
- package/dist/features/composites/reference-label.js +6 -6
- package/dist/features/composites/reference-label.js.map +1 -1
- package/dist/features/composites/reference-suggestion.d.ts +1 -1
- package/dist/features/composites/reference-suggestion.js +3 -3
- package/dist/features/composites/reference-suggestion.js.map +1 -1
- package/dist/features/composites/reference-text.d.ts +2 -2
- package/dist/features/composites/reference-text.js +4 -4
- package/dist/features/composites/reference-text.js.map +1 -1
- package/dist/features/composites/reference.d.ts +3 -3
- package/dist/features/composites/reference.js +1 -1
- package/dist/features/composites/reference.js.map +1 -1
- package/dist/features/composites/reveal-source.d.ts +3 -3
- package/dist/features/composites/reveal-source.js +6 -6
- package/dist/features/composites/reveal-source.js.map +1 -1
- package/dist/features/composites/rows-model.d.ts +2 -2
- package/dist/features/composites/rows-model.js +4 -3
- package/dist/features/composites/rows-model.js.map +1 -1
- package/dist/features/composites/rows.d.ts +16 -28
- package/dist/features/composites/rows.js +20 -54
- package/dist/features/composites/rows.js.map +1 -1
- package/dist/features/composites/schema.d.ts +12 -11
- package/dist/features/composites/schema.js +10 -9
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/source.d.ts +20 -0
- package/dist/features/composites/source.js +40 -0
- package/dist/features/composites/source.js.map +1 -0
- package/dist/features/composites/split.d.ts +11 -11
- package/dist/features/composites/split.js +20 -20
- package/dist/features/composites/split.js.map +1 -1
- package/dist/features/composites/structure-mode.js +3 -3
- package/dist/features/composites/structure-mode.js.map +1 -1
- package/dist/features/composites/table-model.js +2 -2
- package/dist/features/composites/table-model.js.map +1 -1
- package/dist/features/composites/use-composite-inspector.js +3 -1
- package/dist/features/composites/use-composite-inspector.js.map +1 -1
- package/dist/features/composites/use-composite-rows.d.ts +2 -2
- package/dist/features/composites/use-composite-rows.js.map +1 -1
- package/dist/features/composites/vue.js +1 -1
- package/dist/features/composites/vue.js.map +1 -1
- package/dist/features/image-studio/components/ImageStudioDialog.js +1 -1
- package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -1
- package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +1 -1
- package/dist/features/image-studio/components/ImageStudioView.js.map +1 -1
- package/dist/features/image-studio/feature.d.ts +1 -1
- package/dist/features/image-studio/feature.js +1 -1
- package/dist/features/image-studio/feature.js.map +1 -1
- package/dist/features/image-studio/node-core.d.ts +3 -3
- package/dist/features/image-studio/node-core.js +1 -1
- package/dist/features/image-studio/node-core.js.map +1 -1
- package/dist/features/image-studio/node-store.d.ts +1 -1
- package/dist/features/image-studio/node-store.js +1 -1
- package/dist/features/image-studio/node-store.js.map +1 -1
- package/dist/features/image-studio/react/ImageStudioDialog.d.ts +1 -1
- package/dist/features/image-studio/react/ImageStudioDialog.js +1 -1
- package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -1
- package/dist/features/image-studio/react/ImageStudioView.js +1 -1
- package/dist/features/image-studio/react/ImageStudioView.js.map +1 -1
- package/dist/features/image-studio/react/store.d.ts +3 -3
- package/dist/features/image-studio/react/store.js.map +1 -1
- package/dist/features/image-studio/react.d.ts +1 -1
- package/dist/features/image-studio/react.js +1 -1
- package/dist/features/image-studio/react.js.map +1 -1
- package/dist/features/image-studio/schema.d.ts +1 -1
- package/dist/features/image-studio/schema.js +1 -1
- package/dist/features/image-studio/schema.js.map +1 -1
- package/dist/features/image-studio/state.d.ts +3 -3
- package/dist/features/image-studio/state.js +2 -2
- package/dist/features/image-studio/state.js.map +1 -1
- package/dist/features/image-studio/store.d.ts +3 -3
- package/dist/features/image-studio/vue.d.ts +1 -1
- package/dist/features/image-studio/vue.js +1 -1
- package/dist/features/image-studio/vue.js.map +1 -1
- package/dist/features/media/index.js +1 -1
- package/dist/features/media/react/AudioRecorderModal.js +1 -1
- package/dist/features/media/react.js +1 -1
- package/dist/features/mentions/tag.js +2 -1
- package/dist/features/mentions/tag.js.map +1 -1
- package/dist/features/themes/components/SettingsAppearanceTab.js +1 -1
- package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/feature.d.ts +1 -1
- package/dist/features/themes/feature.js.map +1 -1
- package/dist/features/themes/react/SettingsAppearanceTab.d.ts +1 -1
- package/dist/features/themes/react/SettingsAppearanceTab.js +1 -1
- package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/theme-runtime.js +1 -1
- package/dist/features/themes/theme-runtime.js.map +1 -1
- package/dist/features/transcript/TranscriptSegmentView.js +1 -1
- package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/react/TranscriptSegmentView.js +1 -1
- package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/segment-node.js.map +1 -1
- package/dist/features/transfer/feature.js +1 -1
- package/dist/features/transfer/feature.js.map +1 -1
- package/dist/features/transfer/transforms.d.ts +4 -4
- package/dist/features/transfer/transforms.js +12 -12
- package/dist/features/transfer/transforms.js.map +1 -1
- package/dist/i18n/de/ai.js +4 -4
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/authors.js +1 -1
- package/dist/i18n/de/authors.js.map +1 -1
- package/dist/i18n/de/comments.js +1 -1
- package/dist/i18n/de/comments.js.map +1 -1
- package/dist/i18n/de/composites.js +62 -59
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/image-studio.js +2 -2
- package/dist/i18n/de/image-studio.js.map +1 -1
- package/dist/i18n/de/integrations.js +20 -20
- package/dist/i18n/de/integrations.js.map +1 -1
- package/dist/i18n/de/markdown.js +1 -1
- package/dist/i18n/de/markdown.js.map +1 -1
- package/dist/i18n/de/palette.js +1 -1
- package/dist/i18n/de/palette.js.map +1 -1
- package/dist/i18n/de/themes.js +3 -3
- package/dist/i18n/de/themes.js.map +1 -1
- package/dist/i18n/de/transfer.js +2 -2
- package/dist/i18n/de/transfer.js.map +1 -1
- package/dist/i18n/de/tree.js +6 -6
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +101 -98
- package/dist/i18n/en/ai.js +2 -2
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/composites.js +64 -61
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/integrations.js +9 -9
- package/dist/i18n/en/integrations.js.map +1 -1
- package/dist/i18n/en/markdown.js +1 -1
- package/dist/i18n/en/markdown.js.map +1 -1
- package/dist/i18n/en/palette.js +1 -1
- package/dist/i18n/en/palette.js.map +1 -1
- package/dist/i18n/en/themes.js +4 -4
- package/dist/i18n/en/themes.js.map +1 -1
- package/dist/i18n/en/tree.js +5 -5
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/integrations/composite-ai/bibliography-fetch.d.ts +6 -6
- package/dist/integrations/composite-ai/bibliography-fetch.js +19 -19
- package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.d.ts +4 -4
- package/dist/integrations/composite-ai/composite-fill.js +5 -5
- package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
- package/dist/integrations/composite-ai/composite-summary.d.ts +2 -2
- package/dist/integrations/composite-ai/composite-summary.js +5 -5
- package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
- package/dist/integrations/composite-ai/derive.d.ts +17 -17
- package/dist/integrations/composite-ai/derive.js +27 -26
- package/dist/integrations/composite-ai/derive.js.map +1 -1
- package/dist/integrations/composite-ai/index.d.ts +14 -15
- package/dist/integrations/composite-ai/index.js +98 -77
- package/dist/integrations/composite-ai/index.js.map +1 -1
- package/dist/integrations/composite-ai/outline-sanitize.d.ts +1 -1
- package/dist/integrations/composite-ai/outline-sanitize.js +1 -1
- package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -1
- package/dist/integrations/composite-ai/rendition.d.ts +9 -9
- package/dist/integrations/composite-ai/rendition.js +27 -27
- package/dist/integrations/composite-ai/rendition.js.map +1 -1
- package/dist/integrations/composite-ai/split.d.ts +8 -8
- package/dist/integrations/composite-ai/split.js +12 -12
- package/dist/integrations/composite-ai/split.js.map +1 -1
- package/dist/integrations/composite-ai/transfer-transforms.d.ts +2 -2
- package/dist/integrations/composite-ai/transfer-transforms.js +12 -12
- package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -1
- package/dist/integrations/composite-media/index.d.ts +4 -3
- package/dist/integrations/composite-media/index.js +20 -14
- package/dist/integrations/composite-media/index.js.map +1 -1
- package/dist/markdown/document.js +1 -1
- package/dist/markdown/document.js.map +1 -1
- package/dist/nuxt/module.d.ts +30 -24
- package/dist/nuxt/module.js.map +1 -1
- package/dist/nuxt/runtime/server/plugins/editor-server.js +3 -2
- package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
- package/dist/presets/compose.d.ts +6 -6
- package/dist/presets/compose.js +8 -5
- package/dist/presets/compose.js.map +1 -1
- package/dist/react/AdvancedEditor.d.ts +2 -2
- package/dist/react/AdvancedEditor.js +1 -1
- package/dist/react/AdvancedEditor.js.map +1 -1
- package/dist/react/compose.d.ts +1 -1
- package/dist/react/compose.js +2 -2
- package/dist/react/compose.js.map +1 -1
- package/dist/react/composed-surface.d.ts +5 -5
- package/dist/react/composed-surface.js +2 -2
- package/dist/react/composed-surface.js.map +1 -1
- package/dist/server/ai.d.ts +7 -7
- package/dist/server/ai.js +1 -1
- package/dist/server/ai.js.map +1 -1
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/collab-state.d.ts +1 -1
- package/dist/server/collab-state.js.map +1 -1
- package/dist/server/collab.d.ts +4 -4
- package/dist/server/collab.js +22 -17
- package/dist/server/collab.js.map +1 -1
- package/dist/server/handlers/ai.d.ts +7 -4
- package/dist/server/handlers/ai.js +13 -17
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +4 -0
- package/dist/server/handlers/context.js.map +1 -1
- package/dist/server/handlers/editor.d.ts +33 -32
- package/dist/server/handlers/editor.js +13 -8
- package/dist/server/handlers/editor.js.map +1 -1
- package/dist/server/handlers/images.d.ts +1 -1
- package/dist/server/handlers/images.js +1 -1
- package/dist/server/handlers/images.js.map +1 -1
- package/dist/server/host.d.ts +4 -0
- package/dist/server/host.js +10 -2
- package/dist/server/host.js.map +1 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/ports.d.ts +8 -8
- package/dist/server/ports.js.map +1 -1
- package/dist/settings/index.d.ts +20 -19
- package/dist/settings/index.js +25 -24
- package/dist/settings/index.js.map +1 -1
- package/dist/settings/migrate.d.ts +18 -0
- package/dist/settings/migrate.js +46 -0
- package/dist/settings/migrate.js.map +1 -0
- package/dist/settings/values.d.ts +12 -12
- package/dist/settings/values.js +11 -11
- package/dist/settings/values.js.map +1 -1
- package/dist/styles/composites.css +10 -10
- package/dist/tree/DocumentTree.js +12 -5
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +5 -5
- package/dist/tree/drop-rules.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +14 -5
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-host.js +6 -6
- package/dist/tree/tree-host.js.map +1 -1
- package/dist/tree/tree-items.js +34 -28
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +16 -16
- package/dist/tree/tree-model.js.map +1 -1
- package/dist/tree/tree-rows.js +2 -2
- package/dist/tree/tree-rows.js.map +1 -1
- package/dist/tree/tree-view-model.d.ts +2 -2
- package/dist/tree/tree-view-model.js.map +1 -1
- package/dist/vue/AdvancedEditor.js +2 -2
- package/dist/vue/AdvancedEditor.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
- package/dist/vue/compose.d.ts +1 -1
- package/dist/vue/compose.js +2 -2
- package/dist/vue/compose.js.map +1 -1
- package/package.json +1 -1
- package/dist/features/composites/derivation.d.ts +0 -62
- package/dist/features/composites/derivation.js +0 -102
- package/dist/features/composites/derivation.js.map +0 -1
- package/dist/features/composites/kinds.d.ts +0 -89
- package/dist/features/composites/kinds.js +0 -285
- package/dist/features/composites/kinds.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ImageStudioStore } from "../store.js";
|
|
2
2
|
//#region src/features/image-studio/components/ImageStudioDialog.vue.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* The Bild-Studio: description and
|
|
4
|
+
* The Bild-Studio: description and prompt blocks on the left, references and previous generations in the middle,
|
|
5
5
|
* the block's image and the chat on the right. The dialog applies the reducers of `../state` to `store.state` and
|
|
6
6
|
* hands every result to `store.update`; the store only generates, uploads and persists (see `ImageStudioStore`).
|
|
7
7
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageStudioView.js","names":["selected","node","editor","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementVNode","_createElementBlock","_createVNode","_toDisplayString","_createCommentVNode","ImageStudioDialog"],"sources":["../../../../src/features/image-studio/components/ImageStudioView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'\nimport { computed, onBeforeUnmount, ref, shallowRef, toRaw } from 'vue'\nimport { editorEntryOf } from '../../../core'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { useNotifier } from '../../../vue/use-notifier'\nimport { SCRATCH_DOCUMENT_ID } from '../../media/assets'\nimport { createImageStudioNodeStore, type ImageStudioNodeStore } from '../node-store'\nimport { normalizeImageStudioState } from '../state'\nimport ImageStudioDialog from './ImageStudioDialog.vue'\n\nconst props = defineProps(nodeViewProps)\n\nconst { Button: UButton, Icon: UIcon } = useUiKit()\nconst { t } = useEditorI18n()\nconst notify = useNotifier(() => props.editor)\n\nconst studio = computed(() => normalizeImageStudioState(props.node.attrs.studio))\nconst description = computed(() => studio.value.description.trim())\n\n// The store (and with it the
|
|
1
|
+
{"version":3,"file":"ImageStudioView.js","names":["selected","node","editor","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementVNode","_createElementBlock","_createVNode","_toDisplayString","_createCommentVNode","ImageStudioDialog"],"sources":["../../../../src/features/image-studio/components/ImageStudioView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'\nimport { computed, onBeforeUnmount, ref, shallowRef, toRaw } from 'vue'\nimport { editorEntryOf } from '../../../core'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { useNotifier } from '../../../vue/use-notifier'\nimport { SCRATCH_DOCUMENT_ID } from '../../media/assets'\nimport { createImageStudioNodeStore, type ImageStudioNodeStore } from '../node-store'\nimport { normalizeImageStudioState } from '../state'\nimport ImageStudioDialog from './ImageStudioDialog.vue'\n\nconst props = defineProps(nodeViewProps)\n\nconst { Button: UButton, Icon: UIcon } = useUiKit()\nconst { t } = useEditorI18n()\nconst notify = useNotifier(() => props.editor)\n\nconst studio = computed(() => normalizeImageStudioState(props.node.attrs.studio))\nconst description = computed(() => studio.value.description.trim())\n\n// The store (and with it the prompt blocks list) is built when the studio opens first and lives as long as the view.\nconst open = ref(false)\nconst store = shallowRef<ImageStudioNodeStore>()\n\n/** Opens the studio dialog; a read-only editor opens none, a backend without image generation explains itself. */\nfunction openStudio() {\n if (!props.editor.isEditable) return\n const ctx = editorEntryOf(toRaw(props.editor))?.ctx\n const backend = ctx?.backend.images\n if (!ctx || !backend) {\n notify({ id: 'image-studio.unavailable', title: t('imageStudio.action.notConfigured'), kind: 'warning' })\n return\n }\n store.value ??= createImageStudioNodeStore({\n editor: toRaw(props.editor),\n getPos: props.getPos,\n backend,\n documentId: ctx.documentId ?? SCRATCH_DOCUMENT_ID,\n })\n open.value = true\n}\n\nonBeforeUnmount(() => store.value?.dispose())\n</script>\n\n<template>\n <NodeViewWrapper\n as=\"figure\"\n data-image-studio-block\n :class=\"['my-3 flex flex-col gap-2 rounded-md', { 'outline-2 outline-offset-2 outline-[var(--sk-primary)]': selected }]\"\n @dblclick=\"openStudio\"\n @sk-image-studio-open=\"openStudio\"\n >\n <div class=\"flex flex-col gap-2\" contenteditable=\"false\">\n <img\n v-if=\"node.attrs.src\"\n :src=\"node.attrs.src\"\n :alt=\"node.attrs.alt || description\"\n class=\"m-0 max-w-full rounded-md\"\n >\n <div\n v-else\n class=\"flex min-h-32 flex-col items-center justify-center gap-2 rounded-md border border-dashed border-[var(--sk-border)] p-4 text-center text-sm\"\n >\n <UIcon name=\"i-lucide-image-plus\" class=\"size-6 shrink-0 text-[var(--sk-fg-dimmed)]\" />\n <span v-if=\"description\" class=\"whitespace-pre-wrap text-[var(--sk-muted)]\">{{ description }}</span>\n <span v-else class=\"text-[var(--sk-fg-dimmed)]\">{{ t('imageStudio.node.noDescription') }}</span>\n </div>\n <figcaption v-if=\"node.attrs.src && description\" class=\"whitespace-pre-wrap px-1 text-sm text-[var(--sk-muted)]\">\n {{ description }}\n </figcaption>\n <div v-if=\"editor.isEditable\" class=\"flex\">\n <UButton\n :label=\"t('imageStudio.node.open')\"\n icon=\"i-lucide-wand-sparkles\"\n size=\"xs\"\n color=\"neutral\"\n variant=\"soft\"\n @click=\"openStudio\"\n />\n </div>\n </div>\n <ImageStudioDialog\n v-if=\"store\"\n v-model:open=\"open\"\n :store=\"store\"\n :title=\"t('imageStudio.node.dialogTitle')\"\n />\n </NodeViewWrapper>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYA,MAAM,QAAQ;EAEd,MAAM,EAAE,QAAQ,SAAS,MAAM,UAAU,SAAS;EAClD,MAAM,EAAE,MAAM,cAAc;EAC5B,MAAM,SAAS,kBAAkB,MAAM,MAAM;EAE7C,MAAM,SAAS,eAAe,0BAA0B,MAAM,KAAK,MAAM,MAAM,CAAC;EAChF,MAAM,cAAc,eAAe,OAAO,MAAM,YAAY,KAAK,CAAC;EAGlE,MAAM,OAAO,IAAI,KAAK;EACtB,MAAM,QAAQ,WAAiC;;EAG/C,SAAS,aAAa;GACpB,IAAI,CAAC,MAAM,OAAO,YAAY;GAC9B,MAAM,MAAM,cAAc,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE;GAChD,MAAM,UAAU,KAAK,QAAQ;GAC7B,IAAI,CAAC,OAAO,CAAC,SAAS;IACpB,OAAO;KAAE,IAAI;KAA4B,OAAO,EAAE,kCAAkC;KAAG,MAAM;IAAU,CAAC;IACxG;GACF;GACA,MAAM,UAAU,2BAA2B;IACzC,QAAQ,MAAM,MAAM,MAAM;IAC1B,QAAQ,MAAM;IACd;IACA,YAAY,IAAI,cAAA;GAClB,CAAC;GACD,KAAK,QAAQ;EACf;EAEA,sBAAsB,MAAM,OAAO,QAAQ,CAAC;;GAI1C,OAAAG,UAAA,GAAAC,YA0CkBC,MAAA,eAAA,GAAA;IAzChB,IAAG;IACH,2BAAA;IACC,OAAKC,eAAA,CAAA,uCAAA,EAAA,0DAAsGN,KAAAA,SAAQ,CAAA,CAAA;IACnH,YAAU;IACV,qBAAsB;;IAEvB,SAAAO,cA4BM,CA5BNC,mBA4BM,OA5BN,YA4BM;KA1BIP,KAAAA,KAAK,MAAM,OADnBE,UAAA,GAAAM,mBAKC,OAAA;;MAHE,KAAKR,KAAAA,KAAK,MAAM;MAChB,KAAKA,KAAAA,KAAK,MAAM,OAAO,YAAA;MACxB,OAAM;KAER,GAAA,MAAA,GAAA,UAAA,MAAAE,UAAA,GAAAM,mBAOM,OAPN,YAOM,CAHJC,YAAuFL,MAAA,KAAA,GAAA;MAAhF,MAAK;MAAsB,OAAM;KAC5B,CAAA,GAAA,YAAA,SAAZF,UAAA,GAAAM,mBAAoG,QAApG,YAAoGE,gBAArB,YAAA,KAAW,GAAA,CAAA,MAC1FR,UAAA,GAAAM,mBAAgG,QAAhG,YAAgGE,gBAA7CN,MAAA,CAAA,CAAC,CAAA,gCAAA,CAAA,GAAA,CAAA,EAAA,CAAA;KAEpCJ,KAAAA,KAAK,MAAM,OAAO,YAAA,SAApCE,UAAA,GAAAM,mBAEa,cAFb,YAEaE,gBADR,YAAA,KAAW,GAAA,CAAA,KAAAC,mBAAA,QAAA,IAAA;KAELV,KAAAA,OAAO,cAAlBC,UAAA,GAAAM,mBASM,OATN,YASM,CARJC,YAOEL,MAAA,OAAA,GAAA;MANC,OAAOA,MAAA,CAAA,CAAC,CAAA,uBAAA;MACT,MAAK;MACL,MAAK;MACL,OAAM;MACN,SAAQ;MACP,SAAO;;IAKN,CAAA,GAAA,MAAA,SADRF,UAAA,GAAAC,YAKES,aAAA;;KAHQ,MAAM,KAAA;KAAA,iBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,KAAI,QAAA;KACjB,OAAO,MAAA;KACP,OAAOR,MAAA,CAAA,CAAC,CAAA,8BAAA"}
|
|
@@ -13,7 +13,7 @@ export interface ImageStudioUi {
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const IMAGE_STUDIO_OPEN = "sk-image-studio-open";
|
|
15
15
|
/**
|
|
16
|
-
* The Bild-Studio block (`imageStudio` node: a picture with its description,
|
|
16
|
+
* The Bild-Studio block (`imageStudio` node: a picture with its description, prompt blocks, references, previous
|
|
17
17
|
* generations and chat log in one `studio` attribute), the `/` entry `bild-studio` that inserts an empty one, offered
|
|
18
18
|
* where the caret has `insert.imageStudio`, and the block action `image-studio.open` («Im Bild-Studio öffnen») that asks
|
|
19
19
|
* the block's view to open the studio dialog. The node view and the dialog come from the framework binding (`ui`).
|
|
@@ -28,7 +28,7 @@ const blockActions = (ctx) => [{
|
|
|
28
28
|
}
|
|
29
29
|
}];
|
|
30
30
|
/**
|
|
31
|
-
* The Bild-Studio block (`imageStudio` node: a picture with its description,
|
|
31
|
+
* The Bild-Studio block (`imageStudio` node: a picture with its description, prompt blocks, references, previous
|
|
32
32
|
* generations and chat log in one `studio` attribute), the `/` entry `bild-studio` that inserts an empty one, offered
|
|
33
33
|
* where the caret has `insert.imageStudio`, and the block action `image-studio.open` («Im Bild-Studio öffnen») that asks
|
|
34
34
|
* the block's view to open the studio dialog. The node view and the dialog come from the framework binding (`ui`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/image-studio/feature.ts"],"sourcesContent":["import type { NodeViewRenderer } from '@tiptap/core'\nimport { type BlockAction, command, type CommandDef, type EditorFeature, type FeatureContext } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport { ImageStudioNode, imageStudioSchemaExtensions } from './schema'\n\n/** What a framework binding supplies to the Bild-Studio feature. */\nexport interface ImageStudioUi {\n /** The block's view of the framework; without one the node renders as the plain `<img>` of its HTML form. */\n nodeView?: NodeViewRenderer\n}\n\n/**\n * The event the `image-studio.open` block action fires on the block's node view element; the view answers it by opening\n * the studio dialog. A node view that does not listen (a headless editor) ignores it.\n */\nexport const IMAGE_STUDIO_OPEN = 'sk-image-studio-open'\n\nconst commands = (ctx: FeatureContext): CommandDef<never>[] => [\n command({\n id: 'imageStudio',\n slash: { id: 'bild-studio' },\n creates: { type: 'imageStudio' },\n icon: 'i-lucide-image-plus',\n label: ctx.t('imageStudio.slash.insert'),\n ability: ABILITY.imageStudio,\n command: c => c.insertImageStudio(),\n }),\n]\n\nconst blockActions = (ctx: FeatureContext): BlockAction[] => [{\n id: 'image-studio.open',\n label: ctx.t('imageStudio.action.open'),\n icon: 'i-lucide-wand-sparkles',\n isVisible: block => block.node.type.name === 'imageStudio',\n placement: 'both',\n run: (block) => {\n const dom = block.editor.view.nodeDOM(block.pos)\n if (dom instanceof HTMLElement) dom.dispatchEvent(new CustomEvent(IMAGE_STUDIO_OPEN))\n },\n}]\n\n/**\n * The Bild-Studio block (`imageStudio` node: a picture with its description,
|
|
1
|
+
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/image-studio/feature.ts"],"sourcesContent":["import type { NodeViewRenderer } from '@tiptap/core'\nimport { type BlockAction, command, type CommandDef, type EditorFeature, type FeatureContext } from '../../core'\nimport { ABILITY } from '../../core/schema'\nimport { ImageStudioNode, imageStudioSchemaExtensions } from './schema'\n\n/** What a framework binding supplies to the Bild-Studio feature. */\nexport interface ImageStudioUi {\n /** The block's view of the framework; without one the node renders as the plain `<img>` of its HTML form. */\n nodeView?: NodeViewRenderer\n}\n\n/**\n * The event the `image-studio.open` block action fires on the block's node view element; the view answers it by opening\n * the studio dialog. A node view that does not listen (a headless editor) ignores it.\n */\nexport const IMAGE_STUDIO_OPEN = 'sk-image-studio-open'\n\nconst commands = (ctx: FeatureContext): CommandDef<never>[] => [\n command({\n id: 'imageStudio',\n slash: { id: 'bild-studio' },\n creates: { type: 'imageStudio' },\n icon: 'i-lucide-image-plus',\n label: ctx.t('imageStudio.slash.insert'),\n ability: ABILITY.imageStudio,\n command: c => c.insertImageStudio(),\n }),\n]\n\nconst blockActions = (ctx: FeatureContext): BlockAction[] => [{\n id: 'image-studio.open',\n label: ctx.t('imageStudio.action.open'),\n icon: 'i-lucide-wand-sparkles',\n isVisible: block => block.node.type.name === 'imageStudio',\n placement: 'both',\n run: (block) => {\n const dom = block.editor.view.nodeDOM(block.pos)\n if (dom instanceof HTMLElement) dom.dispatchEvent(new CustomEvent(IMAGE_STUDIO_OPEN))\n },\n}]\n\n/**\n * The Bild-Studio block (`imageStudio` node: a picture with its description, prompt blocks, references, previous\n * generations and chat log in one `studio` attribute), the `/` entry `bild-studio` that inserts an empty one, offered\n * where the caret has `insert.imageStudio`, and the block action `image-studio.open` («Im Bild-Studio öffnen») that asks\n * the block's view to open the studio dialog. The node view and the dialog come from the framework binding (`ui`).\n * Works without the `images` backend capability: the view then explains that generation is not set up instead of opening.\n */\nexport function imageStudioFeatureCore(ui: ImageStudioUi = {}): EditorFeature<never> {\n const { nodeView } = ui\n return {\n id: 'image-studio',\n dimension: 'content',\n schemaExtensions: imageStudioSchemaExtensions,\n extensions: () => [nodeView\n ? ImageStudioNode.extend({\n addNodeView(): NodeViewRenderer {\n return nodeView\n },\n })\n : ImageStudioNode],\n commands,\n blockActions,\n }\n}\n"],"mappings":";;;;;;;;AAeA,MAAa,oBAAoB;AAEjC,MAAM,YAAY,QAA6C,CAC7D,QAAQ;CACN,IAAI;CACJ,OAAO,EAAE,IAAI,cAAc;CAC3B,SAAS,EAAE,MAAM,cAAc;CAC/B,MAAM;CACN,OAAO,IAAI,EAAE,0BAA0B;CACvC,SAAS,QAAQ;CACjB,UAAS,MAAK,EAAE,kBAAkB;AACpC,CAAC,CACH;AAEA,MAAM,gBAAgB,QAAuC,CAAC;CAC5D,IAAI;CACJ,OAAO,IAAI,EAAE,yBAAyB;CACtC,MAAM;CACN,YAAW,UAAS,MAAM,KAAK,KAAK,SAAS;CAC7C,WAAW;CACX,MAAM,UAAU;EACd,MAAM,MAAM,MAAM,OAAO,KAAK,QAAQ,MAAM,GAAG;EAC/C,IAAI,eAAe,aAAa,IAAI,cAAc,IAAI,YAAY,iBAAiB,CAAC;CACtF;AACF,CAAC;;;;;;;;AASD,SAAgB,uBAAuB,KAAoB,CAAC,GAAyB;CACnF,MAAM,EAAE,aAAa;CACrB,OAAO;EACL,IAAI;EACJ,WAAW;EACX,kBAAkB;EAClB,kBAAkB,CAAC,WACf,gBAAgB,OAAO,EACrB,cAAgC;GAC9B,OAAO;EACT,EACF,CAAC,IACD,eAAe;EACnB;EACA;CACF;AACF"}
|
|
@@ -16,9 +16,9 @@ export interface ImageStudioNodeCoreOptions {
|
|
|
16
16
|
/** What a view renders from a node's studio: the observable part of an `ImageStudioNodeCore`. */
|
|
17
17
|
export interface ImageStudioNodeSnapshot {
|
|
18
18
|
readonly state: ImageStudioState;
|
|
19
|
-
/** The managed list of
|
|
19
|
+
/** The managed list of prompt blocks, as the backend answered; empty until then. */
|
|
20
20
|
readonly promptBlocks: readonly ImageStudioPromptBlock[];
|
|
21
|
-
/** The
|
|
21
|
+
/** The prompt blocks could not be loaded: the message. */
|
|
22
22
|
readonly problem: string | undefined;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
@@ -38,7 +38,7 @@ export interface ImageStudioNodeCore extends ReadonlyStore<ImageStudioNodeSnapsh
|
|
|
38
38
|
* The core of the store of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every
|
|
39
39
|
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
40
40
|
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
41
|
-
* `generate`, `upload` and the
|
|
41
|
+
* `generate`, `upload` and the prompt blocks go to `backend.images`. Listeners run synchronously. Call `dispose`
|
|
42
42
|
* when the view goes.
|
|
43
43
|
*/
|
|
44
44
|
export declare function createImageStudioNodeCore(options: ImageStudioNodeCoreOptions): ImageStudioNodeCore;
|
|
@@ -5,7 +5,7 @@ const NO_IMAGES = "@ecsplico/editor: the backend has no images capability";
|
|
|
5
5
|
* The core of the store of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every
|
|
6
6
|
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
7
7
|
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
8
|
-
* `generate`, `upload` and the
|
|
8
|
+
* `generate`, `upload` and the prompt blocks go to `backend.images`. Listeners run synchronously. Call `dispose`
|
|
9
9
|
* when the view goes.
|
|
10
10
|
*/
|
|
11
11
|
function createImageStudioNodeCore(options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-core.js","names":[],"sources":["../../../src/features/image-studio/node-core.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { EditorBackend } from '../../core/backend'\nimport type { ReadonlyStore } from '../../core/store'\nimport { EMPTY_IMAGE_STUDIO_STATE, type ImageStudioImage, type ImageStudioPromptBlock, type ImageStudioState, normalizeImageStudioState } from './state'\n\nexport interface ImageStudioNodeCoreOptions {\n editor: Editor\n /** Position of the `imageStudio` node (a node view's `getPos`); `undefined` once the node is gone. */\n getPos: () => number | undefined\n /** The backend's image port; `undefined` (backend without the `images` capability) makes `generate` and `upload` reject. */\n backend: EditorBackend['images']\n /** Document whose package stores the uploaded references. */\n documentId: string\n}\n\n/** What a view renders from a node's studio: the observable part of an `ImageStudioNodeCore`. */\nexport interface ImageStudioNodeSnapshot {\n readonly state: ImageStudioState\n /** The managed list of
|
|
1
|
+
{"version":3,"file":"node-core.js","names":[],"sources":["../../../src/features/image-studio/node-core.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { EditorBackend } from '../../core/backend'\nimport type { ReadonlyStore } from '../../core/store'\nimport { EMPTY_IMAGE_STUDIO_STATE, type ImageStudioImage, type ImageStudioPromptBlock, type ImageStudioState, normalizeImageStudioState } from './state'\n\nexport interface ImageStudioNodeCoreOptions {\n editor: Editor\n /** Position of the `imageStudio` node (a node view's `getPos`); `undefined` once the node is gone. */\n getPos: () => number | undefined\n /** The backend's image port; `undefined` (backend without the `images` capability) makes `generate` and `upload` reject. */\n backend: EditorBackend['images']\n /** Document whose package stores the uploaded references. */\n documentId: string\n}\n\n/** What a view renders from a node's studio: the observable part of an `ImageStudioNodeCore`. */\nexport interface ImageStudioNodeSnapshot {\n readonly state: ImageStudioState\n /** The managed list of prompt blocks, as the backend answered; empty until then. */\n readonly promptBlocks: readonly ImageStudioPromptBlock[]\n /** The prompt blocks could not be loaded: the message. */\n readonly problem: string | undefined\n}\n\n/**\n * The framework-free store of one `imageStudio` node (`createImageStudioNodeCore`): an observable `ImageStudioNodeSnapshot`\n * (`get`/`subscribe`, see `ReadonlyStore`; every change replaces the snapshot) and the actions the dialog needs.\n * The Vue and React bindings wrap it into the store shape of their dialog.\n */\nexport interface ImageStudioNodeCore extends ReadonlyStore<ImageStudioNodeSnapshot> {\n /** Writes `next` into the node's `studio` attribute as one transaction (`src` follows the selected image). */\n update: (next: ImageStudioState) => void\n generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>\n upload: (file: File) => Promise<ImageStudioImage>\n /** Stops following the editor and ignores the answers still outstanding; call when the node view unmounts. */\n dispose: () => void\n}\n\n// What a store without the backend's `images` port rejects with (the node view does not open one without the port).\nconst NO_IMAGES = '@ecsplico/editor: the backend has no images capability'\n\n/**\n * The core of the store of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every\n * editor transaction, so undo, collaboration and other writers show), `update` writes it back through\n * `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and\n * `generate`, `upload` and the prompt blocks go to `backend.images`. Listeners run synchronously. Call `dispose`\n * when the view goes.\n */\nexport function createImageStudioNodeCore(options: ImageStudioNodeCoreOptions): ImageStudioNodeCore {\n const { editor, getPos, backend, documentId } = options\n let snapshot: ImageStudioNodeSnapshot = { state: EMPTY_IMAGE_STUDIO_STATE, promptBlocks: [], problem: undefined }\n const listeners = new Set<(value: ImageStudioNodeSnapshot) => void>()\n let disposed = false\n // The attribute value `state` was normalized from: a transaction that left it alone keeps `state` as it is.\n let source: unknown\n\n function publish(patch: Partial<ImageStudioNodeSnapshot>): void {\n snapshot = { ...snapshot, ...patch }\n // A copy: listeners may (un)subscribe while being notified.\n for (const listener of [...listeners]) listener(snapshot)\n }\n\n function sync(): void {\n const pos = getPos()\n const node = pos === undefined || editor.isDestroyed ? null : editor.state.doc.nodeAt(pos)\n if (node?.type.name !== 'imageStudio' || node.attrs.studio === source) return\n source = node.attrs.studio\n publish({ state: normalizeImageStudioState(source) })\n }\n\n editor.on('transaction', sync)\n sync()\n\n if (backend) {\n backend.promptBlocks().then(\n (blocks) => { if (!disposed) publish({ promptBlocks: blocks }) },\n (error: unknown) => { if (!disposed) publish({ problem: error instanceof Error ? error.message : String(error) }) },\n )\n }\n\n return {\n get: () => snapshot,\n subscribe(listener) {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n },\n update(next) {\n const pos = getPos()\n if (pos === undefined || editor.isDestroyed) return\n editor.commands.setImageStudioState(pos, next)\n },\n generate(instruction, signal) {\n if (!backend) return Promise.reject(new Error(NO_IMAGES))\n const { description, promptBlockIds, references, generations } = snapshot.state\n const referenceUrls = [...references, ...generations].filter(image => image.active).map(image => image.url)\n return backend.generate({ description, instruction, promptBlockIds: [...promptBlockIds], referenceUrls }, signal)\n },\n upload(file): Promise<ImageStudioImage> {\n return backend ? backend.upload(documentId, file) : Promise.reject(new Error(NO_IMAGES))\n },\n dispose() {\n disposed = true\n listeners.clear()\n editor.off('transaction', sync)\n },\n }\n}\n"],"mappings":";;AAuCA,MAAM,YAAY;;;;;;;;AASlB,SAAgB,0BAA0B,SAA0D;CAClG,MAAM,EAAE,QAAQ,QAAQ,SAAS,eAAe;CAChD,IAAI,WAAoC;EAAE,OAAO;EAA0B,cAAc,CAAC;EAAG,SAAS,KAAA;CAAU;CAChH,MAAM,4BAAY,IAAI,IAA8C;CACpE,IAAI,WAAW;CAEf,IAAI;CAEJ,SAAS,QAAQ,OAA+C;EAC9D,WAAW;GAAE,GAAG;GAAU,GAAG;EAAM;EAEnC,KAAK,MAAM,YAAY,CAAC,GAAG,SAAS,GAAG,SAAS,QAAQ;CAC1D;CAEA,SAAS,OAAa;EACpB,MAAM,MAAM,OAAO;EACnB,MAAM,OAAO,QAAQ,KAAA,KAAa,OAAO,cAAc,OAAO,OAAO,MAAM,IAAI,OAAO,GAAG;EACzF,IAAI,MAAM,KAAK,SAAS,iBAAiB,KAAK,MAAM,WAAW,QAAQ;EACvE,SAAS,KAAK,MAAM;EACpB,QAAQ,EAAE,OAAO,0BAA0B,MAAM,EAAE,CAAC;CACtD;CAEA,OAAO,GAAG,eAAe,IAAI;CAC7B,KAAK;CAEL,IAAI,SACF,QAAQ,aAAa,CAAC,CAAC,MACpB,WAAW;EAAE,IAAI,CAAC,UAAU,QAAQ,EAAE,cAAc,OAAO,CAAC;CAAE,IAC9D,UAAmB;EAAE,IAAI,CAAC,UAAU,QAAQ,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;CAAE,CACpH;CAGF,OAAO;EACL,WAAW;EACX,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa;IAAE,UAAU,OAAO,QAAQ;GAAE;EAC5C;EACA,OAAO,MAAM;GACX,MAAM,MAAM,OAAO;GACnB,IAAI,QAAQ,KAAA,KAAa,OAAO,aAAa;GAC7C,OAAO,SAAS,oBAAoB,KAAK,IAAI;EAC/C;EACA,SAAS,aAAa,QAAQ;GAC5B,IAAI,CAAC,SAAS,OAAO,QAAQ,uBAAO,IAAI,MAAM,SAAS,CAAC;GACxD,MAAM,EAAE,aAAa,gBAAgB,YAAY,gBAAgB,SAAS;GAC1E,MAAM,gBAAgB,CAAC,GAAG,YAAY,GAAG,WAAW,CAAC,CAAC,QAAO,UAAS,MAAM,MAAM,CAAC,CAAC,KAAI,UAAS,MAAM,GAAG;GAC1G,OAAO,QAAQ,SAAS;IAAE;IAAa;IAAa,gBAAgB,CAAC,GAAG,cAAc;IAAG;GAAc,GAAG,MAAM;EAClH;EACA,OAAO,MAAiC;GACtC,OAAO,UAAU,QAAQ,OAAO,YAAY,IAAI,IAAI,QAAQ,uBAAO,IAAI,MAAM,SAAS,CAAC;EACzF;EACA,UAAU;GACR,WAAW;GACX,UAAU,MAAM;GAChB,OAAO,IAAI,eAAe,IAAI;EAChC;CACF;AACF"}
|
|
@@ -11,7 +11,7 @@ export interface ImageStudioNodeStore extends ImageStudioStore {
|
|
|
11
11
|
* The `ImageStudioStore` of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every
|
|
12
12
|
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
13
13
|
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
14
|
-
* `generate`, `upload` and the
|
|
14
|
+
* `generate`, `upload` and the prompt blocks go to `backend.images`. Call `dispose` when the view goes.
|
|
15
15
|
* The Vue wrapper of `createImageStudioNodeCore`.
|
|
16
16
|
*/
|
|
17
17
|
export declare function createImageStudioNodeStore(options: ImageStudioNodeStoreOptions): ImageStudioNodeStore;
|
|
@@ -5,7 +5,7 @@ import { shallowRef } from "vue";
|
|
|
5
5
|
* The `ImageStudioStore` of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every
|
|
6
6
|
* editor transaction, so undo, collaboration and other writers show), `update` writes it back through
|
|
7
7
|
* `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and
|
|
8
|
-
* `generate`, `upload` and the
|
|
8
|
+
* `generate`, `upload` and the prompt blocks go to `backend.images`. Call `dispose` when the view goes.
|
|
9
9
|
* The Vue wrapper of `createImageStudioNodeCore`.
|
|
10
10
|
*/
|
|
11
11
|
function createImageStudioNodeStore(options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-store.js","names":[],"sources":["../../../src/features/image-studio/node-store.ts"],"sourcesContent":["import { shallowRef } from 'vue'\nimport { createImageStudioNodeCore, type ImageStudioNodeCoreOptions } from './node-core'\nimport type { ImageStudioPromptBlock, ImageStudioState } from './state'\nimport type { ImageStudioStore } from './store'\n\nexport type ImageStudioNodeStoreOptions = ImageStudioNodeCoreOptions\n\n/** A store over one node, with the function that detaches it from the editor. */\nexport interface ImageStudioNodeStore extends ImageStudioStore {\n /** Stops following the editor and ignores the answers still outstanding; call when the node view unmounts. */\n dispose: () => void\n}\n\n/**\n * The `ImageStudioStore` of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every\n * editor transaction, so undo, collaboration and other writers show), `update` writes it back through\n * `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and\n * `generate`, `upload` and the
|
|
1
|
+
{"version":3,"file":"node-store.js","names":[],"sources":["../../../src/features/image-studio/node-store.ts"],"sourcesContent":["import { shallowRef } from 'vue'\nimport { createImageStudioNodeCore, type ImageStudioNodeCoreOptions } from './node-core'\nimport type { ImageStudioPromptBlock, ImageStudioState } from './state'\nimport type { ImageStudioStore } from './store'\n\nexport type ImageStudioNodeStoreOptions = ImageStudioNodeCoreOptions\n\n/** A store over one node, with the function that detaches it from the editor. */\nexport interface ImageStudioNodeStore extends ImageStudioStore {\n /** Stops following the editor and ignores the answers still outstanding; call when the node view unmounts. */\n dispose: () => void\n}\n\n/**\n * The `ImageStudioStore` of one `imageStudio` node: `state` follows the node's `studio` attribute (re-read on every\n * editor transaction, so undo, collaboration and other writers show), `update` writes it back through\n * `setImageStudioState` as one transaction (one undo step; the node's `src` follows the selected image), and\n * `generate`, `upload` and the prompt blocks go to `backend.images`. Call `dispose` when the view goes.\n * The Vue wrapper of `createImageStudioNodeCore`.\n */\nexport function createImageStudioNodeStore(options: ImageStudioNodeStoreOptions): ImageStudioNodeStore {\n const core = createImageStudioNodeCore(options)\n const initial = core.get()\n const state = shallowRef<ImageStudioState>(initial.state)\n const promptBlocks = shallowRef<readonly ImageStudioPromptBlock[]>(initial.promptBlocks)\n const problem = shallowRef<string | undefined>(initial.problem)\n core.subscribe((snapshot) => {\n state.value = snapshot.state\n promptBlocks.value = snapshot.promptBlocks\n problem.value = snapshot.problem\n })\n return {\n state,\n promptBlocks,\n problem,\n update: core.update,\n generate: core.generate,\n upload: core.upload,\n dispose: core.dispose,\n }\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAgB,2BAA2B,SAA4D;CACrG,MAAM,OAAO,0BAA0B,OAAO;CAC9C,MAAM,UAAU,KAAK,IAAI;CACzB,MAAM,QAAQ,WAA6B,QAAQ,KAAK;CACxD,MAAM,eAAe,WAA8C,QAAQ,YAAY;CACvF,MAAM,UAAU,WAA+B,QAAQ,OAAO;CAC9D,KAAK,WAAW,aAAa;EAC3B,MAAM,QAAQ,SAAS;EACvB,aAAa,QAAQ,SAAS;EAC9B,QAAQ,QAAQ,SAAS;CAC3B,CAAC;CACD,OAAO;EACL;EACA;EACA;EACA,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,QAAQ,KAAK;EACb,SAAS,KAAK;CAChB;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ImageStudioReactStore } from "./store.js";
|
|
2
2
|
//#region src/features/image-studio/react/ImageStudioDialog.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* The Bild-Studio: description and
|
|
4
|
+
* The Bild-Studio: description and prompt blocks on the left, references and previous generations in the middle,
|
|
5
5
|
* the block's image and the chat on the right. The dialog applies the reducers of `../state` to `store.state` and
|
|
6
6
|
* hands every result to `store.update`; the store only generates, uploads and persists (see `ImageStudioReactStore`).
|
|
7
7
|
*
|
|
@@ -15,7 +15,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
15
15
|
//#region src/features/image-studio/react/ImageStudioDialog.tsx
|
|
16
16
|
const COMMIT_DELAY_MS = 500;
|
|
17
17
|
/**
|
|
18
|
-
* The Bild-Studio: description and
|
|
18
|
+
* The Bild-Studio: description and prompt blocks on the left, references and previous generations in the middle,
|
|
19
19
|
* the block's image and the chat on the right. The dialog applies the reducers of `../state` to `store.state` and
|
|
20
20
|
* hands every result to `store.update`; the store only generates, uploads and persists (see `ImageStudioReactStore`).
|
|
21
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageStudioDialog.js","names":[],"sources":["../../../../src/features/image-studio/react/ImageStudioDialog.tsx"],"sourcesContent":["import { type ChangeEvent, type DragEvent, useEffect, useId, useRef, useState } from 'react'\nimport { fetchErrorMessage } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { Alert, AlertTitle } from '../../../react/ui/alert'\nimport { Button } from '../../../react/ui/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../react/ui/dialog'\nimport { Switch } from '../../../react/ui/switch'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport {\n addGeneration,\n addReference,\n type ImageStudioState,\n type ImageStudioTurn,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from '../state'\nimport { ImageStudioChat } from './ImageStudioChat'\nimport { ImageStudioTile } from './ImageStudioTile'\nimport type { ImageStudioReactStore } from './store'\n\nconst COMMIT_DELAY_MS = 500\n\n/**\n * The Bild-Studio: description and Prompt-Bausteine on the left, references and previous generations in the middle,\n * the block's image and the chat on the right. The dialog applies the reducers of `../state` to `store.state` and\n * hands every result to `store.update`; the store only generates, uploads and persists (see `ImageStudioReactStore`).\n *\n * The description is committed 500 ms after the last keystroke and at once on blur, on „Erzeugen“ and on close, so the\n * host never builds a prompt from a stale text. Closing (or unmounting) aborts a running generation; an aborted\n * generation adds no turn, a failed one adds a turn with the problem.\n */\nexport function ImageStudioDialog({ open, onOpenChange, store, title }: {\n open: boolean\n onOpenChange: (open: boolean) => void\n store: ImageStudioReactStore\n title?: string\n}) {\n const { t } = useEditorI18n()\n const uid = useId()\n const { state, promptBlocks, problem } = store\n const images = [...state.generations, ...state.references]\n const picture = selectedImage(state)\n\n // The latest store and the latest state: async continuations (an upload, a generation) must build on the newest\n // state, also when a render has not caught up with the `update` just made. A new `store.state` replaces it.\n const storeRef = useRef(store)\n storeRef.current = store\n const latest = useRef(state)\n const seen = useRef(state)\n if (seen.current !== state) {\n seen.current = state\n latest.current = state\n }\n const apply = (next: ImageStudioState) => {\n latest.current = next\n storeRef.current.update(next)\n }\n\n // --- Description --------------------------------------------------------------------------------------------------\n\n const [draft, setDraft] = useState(state.description)\n const draftRef = useRef(draft)\n /** The description as last committed or received: the host's echo of our own commit must not overwrite a newer draft. */\n const known = useRef(state.description)\n const commitTimer = useRef<ReturnType<typeof setTimeout>>(undefined)\n\n function commitDescription() {\n clearTimeout(commitTimer.current)\n commitTimer.current = undefined\n if (draftRef.current === latest.current.description) return\n known.current = draftRef.current\n apply(setDescription(latest.current, draftRef.current))\n }\n\n function onDescriptionChange(event: ChangeEvent<HTMLTextAreaElement>) {\n const text = event.target.value\n draftRef.current = text\n setDraft(text)\n if (text === latest.current.description) return\n clearTimeout(commitTimer.current)\n commitTimer.current = setTimeout(commitDescription, COMMIT_DELAY_MS)\n }\n\n // A change of the host (undo, another tab) shows unless the user has typed something that is not committed yet.\n useEffect(() => {\n if (state.description === known.current) return\n known.current = state.description\n if (commitTimer.current === undefined) {\n draftRef.current = state.description\n setDraft(state.description)\n }\n }, [state.description])\n\n // --- References: upload and drop ----------------------------------------------------------------------------------\n\n const fileInput = useRef<HTMLInputElement>(null)\n const [uploading, setUploading] = useState(0)\n const [uploadProblem, setUploadProblem] = useState<string>()\n const [dragging, setDragging] = useState(false)\n\n async function upload(files: readonly File[]) {\n const pictures = files.filter(file => file.type.startsWith('image/'))\n if (!pictures.length) return\n setUploadProblem(undefined)\n setUploading(count => count + pictures.length)\n await Promise.all(pictures.map(async (file) => {\n try {\n const image = await storeRef.current.upload(file)\n apply(addReference(latest.current, image))\n }\n catch (error) {\n setUploadProblem(t('imageStudio.dialog.references.failed', { message: fetchErrorMessage(error) }))\n }\n finally {\n setUploading(count => count - 1)\n }\n }))\n }\n\n function onFilesChosen(event: ChangeEvent<HTMLInputElement>) {\n const input = event.target\n const files = [...(input.files ?? [])]\n input.value = '' // the same file can be chosen again\n void upload(files)\n }\n\n function onDrop(event: DragEvent) {\n event.preventDefault()\n setDragging(false)\n void upload([...event.dataTransfer.files])\n }\n\n // --- Generation ---------------------------------------------------------------------------------------------------\n\n const [running, setRunning] = useState(false)\n const controller = useRef<AbortController>(undefined)\n\n function abortGeneration() {\n controller.current?.abort()\n controller.current = undefined\n setRunning(false)\n }\n\n async function generate(instruction: string) {\n if (controller.current) return\n commitDescription()\n const own = new AbortController()\n controller.current = own\n setRunning(true)\n const turn: ImageStudioTurn = { id: crypto.randomUUID(), instruction, imageId: null, createdAt: new Date().toISOString() }\n try {\n const image = await storeRef.current.generate(instruction, own.signal)\n if (own.signal.aborted) return\n apply(addGeneration(latest.current, image, turn))\n }\n catch (error) {\n if (own.signal.aborted) return\n apply({ ...latest.current, turns: [...latest.current.turns, { ...turn, problem: fetchErrorMessage(error) }] })\n }\n finally {\n if (controller.current === own) {\n controller.current = undefined\n setRunning(false)\n }\n }\n }\n\n // --- Closing ------------------------------------------------------------------------------------------------------\n\n function close() {\n commitDescription()\n abortGeneration()\n onOpenChange(false)\n }\n\n // The host closes the dialog itself, or the component goes: commit what was typed, stop what runs.\n useEffect(() => {\n if (open) return\n commitDescription()\n abortGeneration()\n }, [open])\n useEffect(() => () => {\n commitDescription()\n abortGeneration()\n }, [])\n\n const heading = 'text-sm font-medium text-foreground'\n\n return (\n <Dialog open={open} onOpenChange={(value) => { if (!value) close() }}>\n <DialogContent className=\"max-h-[calc(100vh-2rem)] overflow-y-auto sm:max-w-[min(72rem,calc(100vw-2rem))]\" aria-describedby={undefined}>\n <DialogHeader>\n <DialogTitle>{title ?? t('imageStudio.dialog.title')}</DialogTitle>\n </DialogHeader>\n <div className=\"flex flex-col gap-3\" data-image-studio-dialog>\n {problem && (\n <Alert variant=\"destructive\">\n <AlertTitle>{problem}</AlertTitle>\n </Alert>\n )}\n <div className=\"grid gap-4 lg:h-[68vh] lg:grid-cols-[17rem_minmax(0,1fr)_minmax(0,22rem)]\">\n {/* Left: what the picture shows. */}\n <section className=\"flex min-h-0 flex-col gap-3 lg:overflow-y-auto\" aria-labelledby={`${uid}-description`}>\n <h3 id={`${uid}-description`} className={heading}>{t('imageStudio.dialog.description.heading')}</h3>\n <Textarea\n value={draft}\n rows={6}\n placeholder={t('imageStudio.dialog.description.placeholder')}\n aria-label={t('imageStudio.dialog.description.heading')}\n onChange={onDescriptionChange}\n onBlur={commitDescription}\n />\n <h3 id={`${uid}-blocks`} className={cn(heading, 'mt-2')}>{t('imageStudio.dialog.blocks.heading')}</h3>\n {promptBlocks.length\n ? (\n <ul className=\"flex flex-col gap-3\" aria-labelledby={`${uid}-blocks`}>\n {promptBlocks.map(block => (\n <li key={block.id} className=\"flex items-start gap-2\">\n <Switch\n id={`${uid}-block-${block.id}`}\n checked={state.promptBlockIds.includes(block.id)}\n aria-labelledby={`${uid}-block-${block.id}-label`}\n aria-describedby={`${uid}-block-${block.id}-text`}\n onCheckedChange={() => apply(togglePromptBlock(latest.current, block.id))}\n />\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <label id={`${uid}-block-${block.id}-label`} htmlFor={`${uid}-block-${block.id}`} className=\"text-sm font-medium text-foreground\">\n {block.label}\n </label>\n <p id={`${uid}-block-${block.id}-text`} className=\"whitespace-pre-wrap break-words text-xs text-muted-foreground\">\n {block.text}\n </p>\n </div>\n </li>\n ))}\n </ul>\n )\n : <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.blocks.empty')}</p>}\n </section>\n\n {/* Middle: references and previous generations. */}\n <div className=\"flex min-h-0 flex-col gap-4 lg:overflow-y-auto\">\n <section\n className=\"flex flex-col gap-2 rounded-md border border-dashed border-transparent p-1 data-[dragging=true]:border-primary\"\n data-dragging={dragging}\n aria-labelledby={`${uid}-references`}\n data-image-studio-drop\n onDragEnter={(event) => { event.preventDefault(); setDragging(true) }}\n onDragOver={(event) => { event.preventDefault(); setDragging(true) }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <h3 id={`${uid}-references`} className={heading}>{t('imageStudio.dialog.references.heading')}</h3>\n <Button size=\"xs\" variant=\"outline\" disabled={uploading > 0} onClick={() => fileInput.current?.click()}>\n <Icon name={uploading > 0 ? 'i-lucide-loader-circle' : 'i-lucide-upload'} className={cn(uploading > 0 && 'animate-spin')} />\n {t('imageStudio.dialog.references.upload')}\n </Button>\n <input ref={fileInput} type=\"file\" accept=\"image/*\" multiple hidden data-image-studio-file onChange={onFilesChosen} />\n </div>\n {uploading > 0 && <p className=\"text-xs text-muted-foreground\" role=\"status\">{t('imageStudio.dialog.references.uploading')}</p>}\n {uploadProblem && (\n <Alert variant=\"destructive\">\n <AlertTitle>{uploadProblem}</AlertTitle>\n </Alert>\n )}\n {state.references.length\n ? (\n <ul className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {state.references.map(image => (\n <ImageStudioTile\n key={image.id}\n image={image}\n selected={state.selected === image.id}\n onToggle={() => apply(toggleImage(latest.current, image.id))}\n onSelect={() => apply(select(latest.current, image.id))}\n onRemove={() => apply(removeImage(latest.current, image.id))}\n />\n ))}\n </ul>\n )\n : <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.references.drop')}</p>}\n </section>\n\n <section className=\"flex flex-col gap-2 p-1\" aria-labelledby={`${uid}-generations`}>\n <h3 id={`${uid}-generations`} className={heading}>{t('imageStudio.dialog.generations.heading')}</h3>\n {state.generations.length\n ? (\n <ul className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {state.generations.map(image => (\n <ImageStudioTile\n key={image.id}\n image={image}\n selected={state.selected === image.id}\n onToggle={() => apply(toggleImage(latest.current, image.id))}\n onSelect={() => apply(select(latest.current, image.id))}\n onRemove={() => apply(removeImage(latest.current, image.id))}\n />\n ))}\n </ul>\n )\n : <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.generations.empty')}</p>}\n </section>\n </div>\n\n {/* Right: the block's image and the chat. */}\n <div className=\"flex min-h-0 flex-col gap-3\">\n <section className=\"flex flex-col gap-2\" aria-labelledby={`${uid}-preview`}>\n <h3 id={`${uid}-preview`} className={heading}>{t('imageStudio.dialog.preview.heading')}</h3>\n <div className=\"flex aspect-square items-center justify-center overflow-hidden rounded-md border border-border bg-muted\">\n {picture\n ? (\n <img\n src={picture.url}\n alt={picture.title || t('imageStudio.dialog.tile.untitled')}\n className=\"size-full object-contain\"\n data-image-studio-preview\n />\n )\n : <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.preview.empty')}</p>}\n </div>\n </section>\n <ImageStudioChat\n turns={state.turns}\n images={images}\n running={running}\n emptyAllowed={draft.trim() !== ''}\n onSend={instruction => void generate(instruction)}\n />\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ImageStudioDialog.js","names":[],"sources":["../../../../src/features/image-studio/react/ImageStudioDialog.tsx"],"sourcesContent":["import { type ChangeEvent, type DragEvent, useEffect, useId, useRef, useState } from 'react'\nimport { fetchErrorMessage } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { Alert, AlertTitle } from '../../../react/ui/alert'\nimport { Button } from '../../../react/ui/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../react/ui/dialog'\nimport { Switch } from '../../../react/ui/switch'\nimport { Textarea } from '../../../react/ui/textarea'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport {\n addGeneration,\n addReference,\n type ImageStudioState,\n type ImageStudioTurn,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from '../state'\nimport { ImageStudioChat } from './ImageStudioChat'\nimport { ImageStudioTile } from './ImageStudioTile'\nimport type { ImageStudioReactStore } from './store'\n\nconst COMMIT_DELAY_MS = 500\n\n/**\n * The Bild-Studio: description and prompt blocks on the left, references and previous generations in the middle,\n * the block's image and the chat on the right. The dialog applies the reducers of `../state` to `store.state` and\n * hands every result to `store.update`; the store only generates, uploads and persists (see `ImageStudioReactStore`).\n *\n * The description is committed 500 ms after the last keystroke and at once on blur, on „Erzeugen“ and on close, so the\n * host never builds a prompt from a stale text. Closing (or unmounting) aborts a running generation; an aborted\n * generation adds no turn, a failed one adds a turn with the problem.\n */\nexport function ImageStudioDialog({ open, onOpenChange, store, title }: {\n open: boolean\n onOpenChange: (open: boolean) => void\n store: ImageStudioReactStore\n title?: string\n}) {\n const { t } = useEditorI18n()\n const uid = useId()\n const { state, promptBlocks, problem } = store\n const images = [...state.generations, ...state.references]\n const picture = selectedImage(state)\n\n // The latest store and the latest state: async continuations (an upload, a generation) must build on the newest\n // state, also when a render has not caught up with the `update` just made. A new `store.state` replaces it.\n const storeRef = useRef(store)\n storeRef.current = store\n const latest = useRef(state)\n const seen = useRef(state)\n if (seen.current !== state) {\n seen.current = state\n latest.current = state\n }\n const apply = (next: ImageStudioState) => {\n latest.current = next\n storeRef.current.update(next)\n }\n\n // --- Description --------------------------------------------------------------------------------------------------\n\n const [draft, setDraft] = useState(state.description)\n const draftRef = useRef(draft)\n /** The description as last committed or received: the host's echo of our own commit must not overwrite a newer draft. */\n const known = useRef(state.description)\n const commitTimer = useRef<ReturnType<typeof setTimeout>>(undefined)\n\n function commitDescription() {\n clearTimeout(commitTimer.current)\n commitTimer.current = undefined\n if (draftRef.current === latest.current.description) return\n known.current = draftRef.current\n apply(setDescription(latest.current, draftRef.current))\n }\n\n function onDescriptionChange(event: ChangeEvent<HTMLTextAreaElement>) {\n const text = event.target.value\n draftRef.current = text\n setDraft(text)\n if (text === latest.current.description) return\n clearTimeout(commitTimer.current)\n commitTimer.current = setTimeout(commitDescription, COMMIT_DELAY_MS)\n }\n\n // A change of the host (undo, another tab) shows unless the user has typed something that is not committed yet.\n useEffect(() => {\n if (state.description === known.current) return\n known.current = state.description\n if (commitTimer.current === undefined) {\n draftRef.current = state.description\n setDraft(state.description)\n }\n }, [state.description])\n\n // --- References: upload and drop ----------------------------------------------------------------------------------\n\n const fileInput = useRef<HTMLInputElement>(null)\n const [uploading, setUploading] = useState(0)\n const [uploadProblem, setUploadProblem] = useState<string>()\n const [dragging, setDragging] = useState(false)\n\n async function upload(files: readonly File[]) {\n const pictures = files.filter(file => file.type.startsWith('image/'))\n if (!pictures.length) return\n setUploadProblem(undefined)\n setUploading(count => count + pictures.length)\n await Promise.all(pictures.map(async (file) => {\n try {\n const image = await storeRef.current.upload(file)\n apply(addReference(latest.current, image))\n }\n catch (error) {\n setUploadProblem(t('imageStudio.dialog.references.failed', { message: fetchErrorMessage(error) }))\n }\n finally {\n setUploading(count => count - 1)\n }\n }))\n }\n\n function onFilesChosen(event: ChangeEvent<HTMLInputElement>) {\n const input = event.target\n const files = [...(input.files ?? [])]\n input.value = '' // the same file can be chosen again\n void upload(files)\n }\n\n function onDrop(event: DragEvent) {\n event.preventDefault()\n setDragging(false)\n void upload([...event.dataTransfer.files])\n }\n\n // --- Generation ---------------------------------------------------------------------------------------------------\n\n const [running, setRunning] = useState(false)\n const controller = useRef<AbortController>(undefined)\n\n function abortGeneration() {\n controller.current?.abort()\n controller.current = undefined\n setRunning(false)\n }\n\n async function generate(instruction: string) {\n if (controller.current) return\n commitDescription()\n const own = new AbortController()\n controller.current = own\n setRunning(true)\n const turn: ImageStudioTurn = { id: crypto.randomUUID(), instruction, imageId: null, createdAt: new Date().toISOString() }\n try {\n const image = await storeRef.current.generate(instruction, own.signal)\n if (own.signal.aborted) return\n apply(addGeneration(latest.current, image, turn))\n }\n catch (error) {\n if (own.signal.aborted) return\n apply({ ...latest.current, turns: [...latest.current.turns, { ...turn, problem: fetchErrorMessage(error) }] })\n }\n finally {\n if (controller.current === own) {\n controller.current = undefined\n setRunning(false)\n }\n }\n }\n\n // --- Closing ------------------------------------------------------------------------------------------------------\n\n function close() {\n commitDescription()\n abortGeneration()\n onOpenChange(false)\n }\n\n // The host closes the dialog itself, or the component goes: commit what was typed, stop what runs.\n useEffect(() => {\n if (open) return\n commitDescription()\n abortGeneration()\n }, [open])\n useEffect(() => () => {\n commitDescription()\n abortGeneration()\n }, [])\n\n const heading = 'text-sm font-medium text-foreground'\n\n return (\n <Dialog open={open} onOpenChange={(value) => { if (!value) close() }}>\n <DialogContent className=\"max-h-[calc(100vh-2rem)] overflow-y-auto sm:max-w-[min(72rem,calc(100vw-2rem))]\" aria-describedby={undefined}>\n <DialogHeader>\n <DialogTitle>{title ?? t('imageStudio.dialog.title')}</DialogTitle>\n </DialogHeader>\n <div className=\"flex flex-col gap-3\" data-image-studio-dialog>\n {problem && (\n <Alert variant=\"destructive\">\n <AlertTitle>{problem}</AlertTitle>\n </Alert>\n )}\n <div className=\"grid gap-4 lg:h-[68vh] lg:grid-cols-[17rem_minmax(0,1fr)_minmax(0,22rem)]\">\n {/* Left: what the picture shows. */}\n <section className=\"flex min-h-0 flex-col gap-3 lg:overflow-y-auto\" aria-labelledby={`${uid}-description`}>\n <h3 id={`${uid}-description`} className={heading}>{t('imageStudio.dialog.description.heading')}</h3>\n <Textarea\n value={draft}\n rows={6}\n placeholder={t('imageStudio.dialog.description.placeholder')}\n aria-label={t('imageStudio.dialog.description.heading')}\n onChange={onDescriptionChange}\n onBlur={commitDescription}\n />\n <h3 id={`${uid}-blocks`} className={cn(heading, 'mt-2')}>{t('imageStudio.dialog.blocks.heading')}</h3>\n {promptBlocks.length\n ? (\n <ul className=\"flex flex-col gap-3\" aria-labelledby={`${uid}-blocks`}>\n {promptBlocks.map(block => (\n <li key={block.id} className=\"flex items-start gap-2\">\n <Switch\n id={`${uid}-block-${block.id}`}\n checked={state.promptBlockIds.includes(block.id)}\n aria-labelledby={`${uid}-block-${block.id}-label`}\n aria-describedby={`${uid}-block-${block.id}-text`}\n onCheckedChange={() => apply(togglePromptBlock(latest.current, block.id))}\n />\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <label id={`${uid}-block-${block.id}-label`} htmlFor={`${uid}-block-${block.id}`} className=\"text-sm font-medium text-foreground\">\n {block.label}\n </label>\n <p id={`${uid}-block-${block.id}-text`} className=\"whitespace-pre-wrap break-words text-xs text-muted-foreground\">\n {block.text}\n </p>\n </div>\n </li>\n ))}\n </ul>\n )\n : <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.blocks.empty')}</p>}\n </section>\n\n {/* Middle: references and previous generations. */}\n <div className=\"flex min-h-0 flex-col gap-4 lg:overflow-y-auto\">\n <section\n className=\"flex flex-col gap-2 rounded-md border border-dashed border-transparent p-1 data-[dragging=true]:border-primary\"\n data-dragging={dragging}\n aria-labelledby={`${uid}-references`}\n data-image-studio-drop\n onDragEnter={(event) => { event.preventDefault(); setDragging(true) }}\n onDragOver={(event) => { event.preventDefault(); setDragging(true) }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <h3 id={`${uid}-references`} className={heading}>{t('imageStudio.dialog.references.heading')}</h3>\n <Button size=\"xs\" variant=\"outline\" disabled={uploading > 0} onClick={() => fileInput.current?.click()}>\n <Icon name={uploading > 0 ? 'i-lucide-loader-circle' : 'i-lucide-upload'} className={cn(uploading > 0 && 'animate-spin')} />\n {t('imageStudio.dialog.references.upload')}\n </Button>\n <input ref={fileInput} type=\"file\" accept=\"image/*\" multiple hidden data-image-studio-file onChange={onFilesChosen} />\n </div>\n {uploading > 0 && <p className=\"text-xs text-muted-foreground\" role=\"status\">{t('imageStudio.dialog.references.uploading')}</p>}\n {uploadProblem && (\n <Alert variant=\"destructive\">\n <AlertTitle>{uploadProblem}</AlertTitle>\n </Alert>\n )}\n {state.references.length\n ? (\n <ul className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {state.references.map(image => (\n <ImageStudioTile\n key={image.id}\n image={image}\n selected={state.selected === image.id}\n onToggle={() => apply(toggleImage(latest.current, image.id))}\n onSelect={() => apply(select(latest.current, image.id))}\n onRemove={() => apply(removeImage(latest.current, image.id))}\n />\n ))}\n </ul>\n )\n : <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.references.drop')}</p>}\n </section>\n\n <section className=\"flex flex-col gap-2 p-1\" aria-labelledby={`${uid}-generations`}>\n <h3 id={`${uid}-generations`} className={heading}>{t('imageStudio.dialog.generations.heading')}</h3>\n {state.generations.length\n ? (\n <ul className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {state.generations.map(image => (\n <ImageStudioTile\n key={image.id}\n image={image}\n selected={state.selected === image.id}\n onToggle={() => apply(toggleImage(latest.current, image.id))}\n onSelect={() => apply(select(latest.current, image.id))}\n onRemove={() => apply(removeImage(latest.current, image.id))}\n />\n ))}\n </ul>\n )\n : <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.generations.empty')}</p>}\n </section>\n </div>\n\n {/* Right: the block's image and the chat. */}\n <div className=\"flex min-h-0 flex-col gap-3\">\n <section className=\"flex flex-col gap-2\" aria-labelledby={`${uid}-preview`}>\n <h3 id={`${uid}-preview`} className={heading}>{t('imageStudio.dialog.preview.heading')}</h3>\n <div className=\"flex aspect-square items-center justify-center overflow-hidden rounded-md border border-border bg-muted\">\n {picture\n ? (\n <img\n src={picture.url}\n alt={picture.title || t('imageStudio.dialog.tile.untitled')}\n className=\"size-full object-contain\"\n data-image-studio-preview\n />\n )\n : <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.preview.empty')}</p>}\n </div>\n </section>\n <ImageStudioChat\n turns={state.turns}\n images={images}\n running={running}\n emptyAllowed={draft.trim() !== ''}\n onSend={instruction => void generate(instruction)}\n />\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
13
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
14
|
import { NodeViewWrapper } from "@tiptap/react";
|
|
15
15
|
//#region src/features/image-studio/react/ImageStudioView.tsx
|
|
16
|
-
/** The studio of one block: its store (and with it the
|
|
16
|
+
/** The studio of one block: its store (and with it the prompt blocks list) exists from the first opening on. */
|
|
17
17
|
function NodeStudio({ editor, getPos, backend, documentId, open, onOpenChange }) {
|
|
18
18
|
const { t } = useEditorI18n();
|
|
19
19
|
const store = useImageStudioNodeStore({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageStudioView.js","names":[],"sources":["../../../../src/features/image-studio/react/ImageStudioView.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport { useEffect, useRef, useState } from 'react'\nimport { editorEntryOf } from '../../../core'\nimport type { EditorBackend } from '../../../core/backend'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { useNotifier } from '../../../react/use-notifier'\nimport { SCRATCH_DOCUMENT_ID } from '../../media/assets'\nimport { IMAGE_STUDIO_OPEN } from '../feature'\nimport { normalizeImageStudioState } from '../state'\nimport { ImageStudioDialog } from './ImageStudioDialog'\nimport { useImageStudioNodeStore } from './store'\n\n/** The studio of one block: its store (and with it the
|
|
1
|
+
{"version":3,"file":"ImageStudioView.js","names":[],"sources":["../../../../src/features/image-studio/react/ImageStudioView.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport { useEffect, useRef, useState } from 'react'\nimport { editorEntryOf } from '../../../core'\nimport type { EditorBackend } from '../../../core/backend'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { useNotifier } from '../../../react/use-notifier'\nimport { SCRATCH_DOCUMENT_ID } from '../../media/assets'\nimport { IMAGE_STUDIO_OPEN } from '../feature'\nimport { normalizeImageStudioState } from '../state'\nimport { ImageStudioDialog } from './ImageStudioDialog'\nimport { useImageStudioNodeStore } from './store'\n\n/** The studio of one block: its store (and with it the prompt blocks list) exists from the first opening on. */\nfunction NodeStudio({ editor, getPos, backend, documentId, open, onOpenChange }: {\n editor: Editor\n getPos: NodeViewProps['getPos']\n backend: NonNullable<EditorBackend['images']>\n documentId: string\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const { t } = useEditorI18n()\n const store = useImageStudioNodeStore({ editor, getPos, backend, documentId })\n return store\n ? <ImageStudioDialog open={open} onOpenChange={onOpenChange} store={store} title={t('imageStudio.node.dialogTitle')} />\n : null\n}\n\n/**\n * The `imageStudio` block: the selected picture (or the description as a placeholder), the description as caption and the\n * „Bild-Studio öffnen“ button. The studio dialog opens from that button, on double-click and through the block action\n * `image-studio.open` (the `IMAGE_STUDIO_OPEN` event on the block's node view element); a read-only editor opens none,\n * a backend without image generation explains itself.\n */\nexport function ImageStudioView({ node, selected, editor, getPos }: NodeViewProps) {\n const { t } = useEditorI18n()\n const notify = useNotifier(editor)\n const studio = normalizeImageStudioState(node.attrs.studio)\n const description = studio.description.trim()\n\n const [target, setTarget] = useState<{ backend: NonNullable<EditorBackend['images']>, documentId: string }>()\n const [open, setOpen] = useState(false)\n\n function openStudio() {\n if (!editor.isEditable) return\n const ctx = editorEntryOf(editor)?.ctx\n const backend = ctx?.backend.images\n if (!ctx || !backend) {\n notify({ id: 'image-studio.unavailable', title: t('imageStudio.action.notConfigured'), kind: 'warning' })\n return\n }\n setTarget(previous => previous?.backend === backend ? previous : { backend, documentId: ctx.documentId ?? SCRATCH_DOCUMENT_ID })\n setOpen(true)\n }\n\n // The block action dispatches `IMAGE_STUDIO_OPEN` on the node view's element, the parent of the wrapper rendered here.\n const figure = useRef<HTMLElement>(null)\n const openRef = useRef(openStudio)\n openRef.current = openStudio\n useEffect(() => {\n const dom = figure.current?.parentElement\n if (!dom) return\n const onOpen = () => openRef.current()\n dom.addEventListener(IMAGE_STUDIO_OPEN, onOpen)\n return () => dom.removeEventListener(IMAGE_STUDIO_OPEN, onOpen)\n }, [])\n\n return (\n <NodeViewWrapper\n ref={figure}\n as=\"figure\"\n className={cn('my-3 flex flex-col gap-2 rounded-md', selected && 'outline-2 outline-offset-2 outline-primary')}\n data-image-studio-block\n onDoubleClick={openStudio}\n >\n <div className=\"flex flex-col gap-2\" contentEditable={false}>\n {node.attrs.src\n ? <img src={node.attrs.src} alt={node.attrs.alt || description} className=\"m-0 max-w-full rounded-md\" />\n : (\n <div className=\"flex min-h-32 flex-col items-center justify-center gap-2 rounded-md border border-dashed border-border p-4 text-center text-sm\">\n <Icon name=\"i-lucide-image-plus\" className=\"size-6 text-muted-foreground/60\" />\n {description\n ? <span className=\"whitespace-pre-wrap text-muted-foreground\">{description}</span>\n : <span className=\"text-muted-foreground/60\">{t('imageStudio.node.noDescription')}</span>}\n </div>\n )}\n {node.attrs.src && description && (\n <figcaption className=\"whitespace-pre-wrap px-1 text-sm text-muted-foreground\">{description}</figcaption>\n )}\n {editor.isEditable && (\n <div className=\"flex\">\n <Button size=\"xs\" variant=\"secondary\" onClick={openStudio}>\n <Icon name=\"i-lucide-wand-sparkles\" />\n {t('imageStudio.node.open')}\n </Button>\n </div>\n )}\n </div>\n {target && (\n <NodeStudio\n editor={editor}\n getPos={getPos}\n backend={target.backend}\n documentId={target.documentId}\n open={open}\n onOpenChange={setOpen}\n />\n )}\n </NodeViewWrapper>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,SAAS,WAAW,EAAE,QAAQ,QAAQ,SAAS,YAAY,MAAM,gBAO9D;CACD,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,QAAQ,wBAAwB;EAAE;EAAQ;EAAQ;EAAS;CAAW,CAAC;CAC7E,OAAO,QACH,oBAAC,mBAAD;EAAyB;EAAoB;EAAqB;EAAO,OAAO,EAAE,8BAA8B;CAAI,CAAA,IACpH;AACN;;;;;;;AAQA,SAAgB,gBAAgB,EAAE,MAAM,UAAU,QAAQ,UAAyB;CACjF,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,SAAS,YAAY,MAAM;CAEjC,MAAM,cADS,0BAA0B,KAAK,MAAM,MAC3B,CAAC,CAAC,YAAY,KAAK;CAE5C,MAAM,CAAC,QAAQ,aAAa,SAAgF;CAC5G,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,SAAS,aAAa;EACpB,IAAI,CAAC,OAAO,YAAY;EACxB,MAAM,MAAM,cAAc,MAAM,CAAC,EAAE;EACnC,MAAM,UAAU,KAAK,QAAQ;EAC7B,IAAI,CAAC,OAAO,CAAC,SAAS;GACpB,OAAO;IAAE,IAAI;IAA4B,OAAO,EAAE,kCAAkC;IAAG,MAAM;GAAU,CAAC;GACxG;EACF;EACA,WAAU,aAAY,UAAU,YAAY,UAAU,WAAW;GAAE;GAAS,YAAY,IAAI,cAAA;EAAkC,CAAC;EAC/H,QAAQ,IAAI;CACd;CAGA,MAAM,SAAS,OAAoB,IAAI;CACvC,MAAM,UAAU,OAAO,UAAU;CACjC,QAAQ,UAAU;CAClB,gBAAgB;EACd,MAAM,MAAM,OAAO,SAAS;EAC5B,IAAI,CAAC,KAAK;EACV,MAAM,eAAe,QAAQ,QAAQ;EACrC,IAAI,iBAAiB,mBAAmB,MAAM;EAC9C,aAAa,IAAI,oBAAoB,mBAAmB,MAAM;CAChE,GAAG,CAAC,CAAC;CAEL,OACE,qBAAC,iBAAD;EACE,KAAK;EACL,IAAG;EACH,WAAW,GAAG,uCAAuC,YAAY,4CAA4C;EAC7G,2BAAA;EACA,eAAe;EALjB,UAAA,CAOE,qBAAC,OAAD;GAAK,WAAU;GAAsB,iBAAiB;GAAtD,UAAA;IACG,KAAK,MAAM,MACR,oBAAC,OAAD;KAAK,KAAK,KAAK,MAAM;KAAK,KAAK,KAAK,MAAM,OAAO;KAAa,WAAU;IAA6B,CAAA,IAEnG,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,MAAD;MAAM,MAAK;MAAsB,WAAU;KAAmC,CAAA,GAC7E,cACG,oBAAC,QAAD;MAAM,WAAU;MAA6C,UAAA;KAAkB,CAAA,IAC/E,oBAAC,QAAD;MAAM,WAAU;MAA4B,UAAA,EAAE,gCAAgC;KAAQ,CAAA,CACvF;;IAEV,KAAK,MAAM,OAAO,eACjB,oBAAC,cAAD;KAAY,WAAU;KAA0D,UAAA;IAAwB,CAAA;IAEzG,OAAO,cACN,oBAAC,OAAD;KAAK,WAAU;KACb,UAAA,qBAAC,QAAD;MAAQ,MAAK;MAAK,SAAQ;MAAY,SAAS;MAA/C,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,yBAA0B,CAAA,GACpC,EAAE,uBAAuB,CACpB;;IACL,CAAA;GAEJ;EACJ,CAAA,GAAA,UACC,oBAAC,YAAD;GACU;GACA;GACR,SAAS,OAAO;GAChB,YAAY,OAAO;GACb;GACN,cAAc;EACf,CAAA,CAEY;;AAErB"}
|
|
@@ -9,16 +9,16 @@ import { ImageStudioNodeCoreOptions } from "../node-core.js";
|
|
|
9
9
|
* The dialog applies the reducers of `../state` and hands the result to `update`; the store only persists it. The
|
|
10
10
|
* store's methods never touch `state` themselves:
|
|
11
11
|
* - `update` must lead to a new `state` (the dialog keeps working on its own latest value until `state` changes).
|
|
12
|
-
* - `generate` produces one image from the block's `state.description`, the active
|
|
12
|
+
* - `generate` produces one image from the block's `state.description`, the active prompt blocks and the active
|
|
13
13
|
* images (the host builds the prompt). When it resolves, the dialog calls `update(addGeneration(state, image, turn))`;
|
|
14
14
|
* when it rejects (and the signal was not aborted) the dialog appends a turn with `imageId: null` and the message as
|
|
15
15
|
* `problem`. An aborted generation adds nothing.
|
|
16
16
|
* - `upload` stores the file and returns the image; the dialog then calls `update(addReference(state, image))`.
|
|
17
|
-
* - `problem` is a problem of the store itself (the
|
|
17
|
+
* - `problem` is a problem of the store itself (the prompt blocks could not be loaded, say); the dialog shows it as a notice.
|
|
18
18
|
*/
|
|
19
19
|
export interface ImageStudioReactStore {
|
|
20
20
|
readonly state: ImageStudioState;
|
|
21
|
-
/** The managed list of
|
|
21
|
+
/** The managed list of prompt blocks, loaded by the host. */
|
|
22
22
|
readonly promptBlocks: readonly ImageStudioPromptBlock[];
|
|
23
23
|
/** Called with the state after a reducer; the host persists it. */
|
|
24
24
|
update: (next: ImageStudioState) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","names":[],"sources":["../../../../src/features/image-studio/react/store.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'\nimport { createImageStudioNodeCore, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions } from '../node-core'\nimport type { ImageStudioImage, ImageStudioPromptBlock, ImageStudioState } from '../state'\n\n/**\n * What the React Bild-Studio dialog drives; a host supplies it. The same contract as the Vue `ImageStudioStore`, with\n * plain values where Vue has refs: the host re-renders the dialog with a new `state` after `update`.\n *\n * The dialog applies the reducers of `../state` and hands the result to `update`; the store only persists it. The\n * store's methods never touch `state` themselves:\n * - `update` must lead to a new `state` (the dialog keeps working on its own latest value until `state` changes).\n * - `generate` produces one image from the block's `state.description`, the active
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../../../../src/features/image-studio/react/store.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'\nimport { createImageStudioNodeCore, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions } from '../node-core'\nimport type { ImageStudioImage, ImageStudioPromptBlock, ImageStudioState } from '../state'\n\n/**\n * What the React Bild-Studio dialog drives; a host supplies it. The same contract as the Vue `ImageStudioStore`, with\n * plain values where Vue has refs: the host re-renders the dialog with a new `state` after `update`.\n *\n * The dialog applies the reducers of `../state` and hands the result to `update`; the store only persists it. The\n * store's methods never touch `state` themselves:\n * - `update` must lead to a new `state` (the dialog keeps working on its own latest value until `state` changes).\n * - `generate` produces one image from the block's `state.description`, the active prompt blocks and the active\n * images (the host builds the prompt). When it resolves, the dialog calls `update(addGeneration(state, image, turn))`;\n * when it rejects (and the signal was not aborted) the dialog appends a turn with `imageId: null` and the message as\n * `problem`. An aborted generation adds nothing.\n * - `upload` stores the file and returns the image; the dialog then calls `update(addReference(state, image))`.\n * - `problem` is a problem of the store itself (the prompt blocks could not be loaded, say); the dialog shows it as a notice.\n */\nexport interface ImageStudioReactStore {\n readonly state: ImageStudioState\n /** The managed list of prompt blocks, loaded by the host. */\n readonly promptBlocks: readonly ImageStudioPromptBlock[]\n /** Called with the state after a reducer; the host persists it. */\n update: (next: ImageStudioState) => void\n generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>\n upload: (file: File) => Promise<ImageStudioImage>\n readonly problem: string | undefined\n}\n\nconst noUnsubscribe = () => {}\n\n/**\n * The `ImageStudioReactStore` of one `imageStudio` node (the React wrapper of `createImageStudioNodeCore`): `state`\n * follows the node's `studio` attribute, `update` writes it back as one transaction, the rest goes to\n * `backend.images`. The core lives for as long as the component and the `editor`, `backend` and `documentId` stay the\n * same; the first render returns `undefined` (the core is built in an effect), so render the dialog once there is a store.\n */\nexport function useImageStudioNodeStore(options: ImageStudioNodeCoreOptions): ImageStudioReactStore | undefined {\n const { editor, backend, documentId } = options\n const getPos = useRef(options.getPos)\n getPos.current = options.getPos\n const [core, setCore] = useState<ImageStudioNodeCore>()\n useEffect(() => {\n const own = createImageStudioNodeCore({ editor, backend, documentId, getPos: () => getPos.current() })\n setCore(own)\n return () => {\n own.dispose()\n setCore(undefined)\n }\n }, [editor, backend, documentId])\n const subscribe = useCallback((listener: () => void) => core ? core.subscribe(listener) : noUnsubscribe, [core])\n const getSnapshot = useCallback(() => core?.get(), [core])\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n return useMemo(\n () => core && snapshot ? { ...snapshot, update: core.update, generate: core.generate, upload: core.upload } : undefined,\n [core, snapshot],\n )\n}\n"],"mappings":";;;AA6BA,MAAM,sBAAsB,CAAC;;;;;;;AAQ7B,SAAgB,wBAAwB,SAAwE;CAC9G,MAAM,EAAE,QAAQ,SAAS,eAAe;CACxC,MAAM,SAAS,OAAO,QAAQ,MAAM;CACpC,OAAO,UAAU,QAAQ;CACzB,MAAM,CAAC,MAAM,WAAW,SAA8B;CACtD,gBAAgB;EACd,MAAM,MAAM,0BAA0B;GAAE;GAAQ;GAAS;GAAY,cAAc,OAAO,QAAQ;EAAE,CAAC;EACrG,QAAQ,GAAG;EACX,aAAa;GACX,IAAI,QAAQ;GACZ,QAAQ,KAAA,CAAS;EACnB;CACF,GAAG;EAAC;EAAQ;EAAS;CAAU,CAAC;CAChC,MAAM,YAAY,aAAa,aAAyB,OAAO,KAAK,UAAU,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC;CAC/G,MAAM,cAAc,kBAAkB,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC;CACzD,MAAM,WAAW,qBAAqB,WAAW,aAAa,WAAW;CACzE,OAAO,cACC,QAAQ,WAAW;EAAE,GAAG;EAAU,QAAQ,KAAK;EAAQ,UAAU,KAAK;EAAU,QAAQ,KAAK;CAAO,IAAI,KAAA,GAC9G,CAAC,MAAM,QAAQ,CACjB;AACF"}
|
|
@@ -9,7 +9,7 @@ import { ImageStudioDialog } from "./react/ImageStudioDialog.js";
|
|
|
9
9
|
/**
|
|
10
10
|
* The React Bild-Studio feature (shadcn): the `imageStudio` block with its React view (picture or description
|
|
11
11
|
* placeholder, opens the studio dialog on double-click and through the block action `image-studio.open`), the `/` entry
|
|
12
|
-
* `bild-studio` and the action. Generation, upload and the
|
|
12
|
+
* `bild-studio` and the action. Generation, upload and the prompt blocks need a backend with the `images`
|
|
13
13
|
* capability; without it the view says that generation is not set up.
|
|
14
14
|
*/
|
|
15
15
|
export declare function imageStudioFeature(): ReactFeature;
|
|
@@ -9,7 +9,7 @@ import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
|
9
9
|
/**
|
|
10
10
|
* The React Bild-Studio feature (shadcn): the `imageStudio` block with its React view (picture or description
|
|
11
11
|
* placeholder, opens the studio dialog on double-click and through the block action `image-studio.open`), the `/` entry
|
|
12
|
-
* `bild-studio` and the action. Generation, upload and the
|
|
12
|
+
* `bild-studio` and the action. Generation, upload and the prompt blocks need a backend with the `images`
|
|
13
13
|
* capability; without it the view says that generation is not set up.
|
|
14
14
|
*/
|
|
15
15
|
function imageStudioFeature() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","names":[],"sources":["../../../src/features/image-studio/react.ts"],"sourcesContent":["import { ReactNodeViewRenderer } from '@tiptap/react'\nimport type { ReactFeature } from '../../react'\nimport { imageStudioFeatureCore } from './feature'\nimport { ImageStudioView } from './react/ImageStudioView'\n\nexport { ImageStudioDialog } from './react/ImageStudioDialog'\nexport { type ImageStudioReactStore, useImageStudioNodeStore } from './react/store'\nexport { createImageStudioNodeCore, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions, type ImageStudioNodeSnapshot } from './node-core'\nexport {\n addGeneration,\n addReference,\n EMPTY_IMAGE_STUDIO_STATE,\n type ImageStudioImage,\n type ImageStudioPromptBlock,\n type ImageStudioState,\n type ImageStudioTurn,\n isEmptyImageStudioState,\n normalizeImageStudioState,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from './state'\n\n/**\n * The React Bild-Studio feature (shadcn): the `imageStudio` block with its React view (picture or description\n * placeholder, opens the studio dialog on double-click and through the block action `image-studio.open`), the `/` entry\n * `bild-studio` and the action. Generation, upload and the
|
|
1
|
+
{"version":3,"file":"react.js","names":[],"sources":["../../../src/features/image-studio/react.ts"],"sourcesContent":["import { ReactNodeViewRenderer } from '@tiptap/react'\nimport type { ReactFeature } from '../../react'\nimport { imageStudioFeatureCore } from './feature'\nimport { ImageStudioView } from './react/ImageStudioView'\n\nexport { ImageStudioDialog } from './react/ImageStudioDialog'\nexport { type ImageStudioReactStore, useImageStudioNodeStore } from './react/store'\nexport { createImageStudioNodeCore, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions, type ImageStudioNodeSnapshot } from './node-core'\nexport {\n addGeneration,\n addReference,\n EMPTY_IMAGE_STUDIO_STATE,\n type ImageStudioImage,\n type ImageStudioPromptBlock,\n type ImageStudioState,\n type ImageStudioTurn,\n isEmptyImageStudioState,\n normalizeImageStudioState,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from './state'\n\n/**\n * The React Bild-Studio feature (shadcn): the `imageStudio` block with its React view (picture or description\n * placeholder, opens the studio dialog on double-click and through the block action `image-studio.open`), the `/` entry\n * `bild-studio` and the action. Generation, upload and the prompt blocks need a backend with the `images`\n * capability; without it the view says that generation is not set up.\n */\nexport function imageStudioFeature(): ReactFeature {\n return imageStudioFeatureCore({ nodeView: ReactNodeViewRenderer(ImageStudioView) })\n}\n"],"mappings":";;;;;;;;;;;;;;AAgCA,SAAgB,qBAAmC;CACjD,OAAO,uBAAuB,EAAE,UAAU,sBAAsB,eAAe,EAAE,CAAC;AACpF"}
|
|
@@ -19,7 +19,7 @@ declare module '@tiptap/core' {
|
|
|
19
19
|
/** The Bild-Studio node of the document schema. */
|
|
20
20
|
export declare const imageStudioSchemaExtensions: () => AnyExtension[];
|
|
21
21
|
/**
|
|
22
|
-
* The Bild-Studio block: a picture whose description,
|
|
22
|
+
* The Bild-Studio block: a picture whose description, prompt blocks, references, previous generations and chat live in
|
|
23
23
|
* the `studio` attribute (`ImageStudioState`); `src` mirrors the selected image. HTML: `<img data-image-studio src alt
|
|
24
24
|
* data-studio="{json}">`. Markdown: `[Bild-Studio: alt](src) <!-- {"studio":{…}} -->` on its own line, the comment left
|
|
25
25
|
* out while the studio is empty. The label is the literal `Bild-Studio` in every UI language (it is what parsing looks for).
|
|
@@ -16,7 +16,7 @@ function parseStudioJson(json) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* The Bild-Studio block: a picture whose description,
|
|
19
|
+
* The Bild-Studio block: a picture whose description, prompt blocks, references, previous generations and chat live in
|
|
20
20
|
* the `studio` attribute (`ImageStudioState`); `src` mirrors the selected image. HTML: `<img data-image-studio src alt
|
|
21
21
|
* data-studio="{json}">`. Markdown: `[Bild-Studio: alt](src) <!-- {"studio":{…}} -->` on its own line, the comment left
|
|
22
22
|
* out while the studio is empty. The label is the literal `Bild-Studio` in every UI language (it is what parsing looks for).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/image-studio/schema.ts"],"sourcesContent":["import { type AnyExtension, mergeAttributes, Node } from '@tiptap/core'\nimport { TEXT_ABILITIES } from '../../core/schema'\nimport { attrComment, mediaTokenizer } from '../media/schema'\nimport { EMPTY_IMAGE_STUDIO_STATE, type ImageStudioState, isEmptyImageStudioState, normalizeImageStudioState, selectedImage } from './state'\n\n// Vue-free and server-importable (`@ecsplico/editor/image-studio/schema`): the stored document may contain the node, so\n// the server schema has it. The client adds the node view and the dialog.\n\n/** What `insertImageStudio` takes: the description, chat and images live in `studio`, `src` follows its selected image. */\nexport interface ImageStudioAttrs {\n alt?: string\n studio?: ImageStudioState\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n imageStudio: {\n /** Inserts a Bild-Studio block at the selection. */\n insertImageStudio: (attrs?: ImageStudioAttrs) => ReturnType\n /** Replaces the studio state of the block at `pos` and mirrors the selected image into its `src`. */\n setImageStudioState: (pos: number, state: ImageStudioState) => ReturnType\n }\n }\n}\n\n/** The Bild-Studio node of the document schema. */\nexport const imageStudioSchemaExtensions = (): AnyExtension[] => [ImageStudioNode]\n\n/** The `src` a block shows for `studio`: the url of its selected image, `null` without one. */\nconst srcOf = (studio: ImageStudioState): string | null => selectedImage(studio)?.url ?? null\n\nfunction parseStudioJson(json: string | null): unknown {\n if (!json) return undefined\n try {\n return JSON.parse(json)\n }\n catch {\n return undefined\n }\n}\n\n/**\n * The Bild-Studio block: a picture whose description,
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/image-studio/schema.ts"],"sourcesContent":["import { type AnyExtension, mergeAttributes, Node } from '@tiptap/core'\nimport { TEXT_ABILITIES } from '../../core/schema'\nimport { attrComment, mediaTokenizer } from '../media/schema'\nimport { EMPTY_IMAGE_STUDIO_STATE, type ImageStudioState, isEmptyImageStudioState, normalizeImageStudioState, selectedImage } from './state'\n\n// Vue-free and server-importable (`@ecsplico/editor/image-studio/schema`): the stored document may contain the node, so\n// the server schema has it. The client adds the node view and the dialog.\n\n/** What `insertImageStudio` takes: the description, chat and images live in `studio`, `src` follows its selected image. */\nexport interface ImageStudioAttrs {\n alt?: string\n studio?: ImageStudioState\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n imageStudio: {\n /** Inserts a Bild-Studio block at the selection. */\n insertImageStudio: (attrs?: ImageStudioAttrs) => ReturnType\n /** Replaces the studio state of the block at `pos` and mirrors the selected image into its `src`. */\n setImageStudioState: (pos: number, state: ImageStudioState) => ReturnType\n }\n }\n}\n\n/** The Bild-Studio node of the document schema. */\nexport const imageStudioSchemaExtensions = (): AnyExtension[] => [ImageStudioNode]\n\n/** The `src` a block shows for `studio`: the url of its selected image, `null` without one. */\nconst srcOf = (studio: ImageStudioState): string | null => selectedImage(studio)?.url ?? null\n\nfunction parseStudioJson(json: string | null): unknown {\n if (!json) return undefined\n try {\n return JSON.parse(json)\n }\n catch {\n return undefined\n }\n}\n\n/**\n * The Bild-Studio block: a picture whose description, prompt blocks, references, previous generations and chat live in\n * the `studio` attribute (`ImageStudioState`); `src` mirrors the selected image. HTML: `<img data-image-studio src alt\n * data-studio=\"{json}\">`. Markdown: `[Bild-Studio: alt](src) <!-- {\"studio\":{…}} -->` on its own line, the comment left\n * out while the studio is empty. The label is the literal `Bild-Studio` in every UI language (it is what parsing looks for).\n */\nexport const ImageStudioNode = Node.create({\n name: 'imageStudio',\n group: 'block',\n atom: true,\n draggable: true,\n editorBlock: true,\n // A picture block holds no text: nothing text-related applies inside it.\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => ({\n src: { default: null },\n alt: { default: '' },\n studio: {\n default: EMPTY_IMAGE_STUDIO_STATE,\n parseHTML: el => normalizeImageStudioState(parseStudioJson(el.getAttribute('data-studio'))),\n renderHTML: attrs => ({ 'data-studio': JSON.stringify(attrs.studio) }),\n },\n }),\n // Before the stock image rule, which also matches an `<img src>`.\n parseHTML: () => [{ tag: 'img[data-image-studio]', priority: 100 }],\n renderHTML: ({ HTMLAttributes }) => ['img', mergeAttributes(HTMLAttributes, { 'data-image-studio': '' })],\n markdownTokenizer: mediaTokenizer('imageStudio', 'Bild-Studio'),\n parseMarkdown: (token, helpers) => helpers.createNode('imageStudio', {\n src: token.href || null,\n alt: token.title ?? '',\n studio: normalizeImageStudioState(token.attrs?.studio),\n }),\n renderMarkdown: (node) => {\n const alt = typeof node.attrs?.alt === 'string' ? node.attrs.alt : ''\n const studio = normalizeImageStudioState(node.attrs?.studio)\n const label = alt ? `Bild-Studio: ${alt.replace(/[[\\]\\\\]/g, '\\\\$&')}` : 'Bild-Studio'\n const src = typeof node.attrs?.src === 'string' ? node.attrs.src : ''\n return `[${label}](${src})${attrComment({ studio: isEmptyImageStudioState(studio) ? undefined : studio })}`\n },\n addCommands() {\n return {\n insertImageStudio: attrs => ({ commands }) => {\n const studio = normalizeImageStudioState(attrs?.studio)\n return commands.insertContent({ type: this.name, attrs: { src: srcOf(studio), alt: attrs?.alt ?? '', studio } })\n },\n setImageStudioState: (pos, state) => ({ tr, dispatch }) => {\n const node = tr.doc.nodeAt(pos)\n if (node?.type.name !== this.name) return false\n const studio = normalizeImageStudioState(state)\n if (dispatch) tr.setNodeMarkup(pos, undefined, { ...node.attrs, studio, src: srcOf(studio) })\n return true\n },\n }\n },\n})\n"],"mappings":";;;;;;AA0BA,MAAa,oCAAoD,CAAC,eAAe;;AAGjF,MAAM,SAAS,WAA4C,cAAc,MAAM,CAAC,EAAE,OAAO;AAEzF,SAAS,gBAAgB,MAA8B;CACrD,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QACM;EACJ;CACF;AACF;;;;;;;AAQA,MAAa,kBAAkB,KAAK,OAAO;CACzC,MAAM;CACN,OAAO;CACP,MAAM;CACN,WAAW;CACX,aAAa;CAEb,WAAW,EAAE,SAAS,eAAe;CACrC,sBAAsB;EACpB,KAAK,EAAE,SAAS,KAAK;EACrB,KAAK,EAAE,SAAS,GAAG;EACnB,QAAQ;GACN,SAAS;GACT,YAAW,OAAM,0BAA0B,gBAAgB,GAAG,aAAa,aAAa,CAAC,CAAC;GAC1F,aAAY,WAAU,EAAE,eAAe,KAAK,UAAU,MAAM,MAAM,EAAE;EACtE;CACF;CAEA,iBAAiB,CAAC;EAAE,KAAK;EAA0B,UAAU;CAAI,CAAC;CAClE,aAAa,EAAE,qBAAqB,CAAC,OAAO,gBAAgB,gBAAgB,EAAE,qBAAqB,GAAG,CAAC,CAAC;CACxG,mBAAmB,eAAe,eAAe,aAAa;CAC9D,gBAAgB,OAAO,YAAY,QAAQ,WAAW,eAAe;EACnE,KAAK,MAAM,QAAQ;EACnB,KAAK,MAAM,SAAS;EACpB,QAAQ,0BAA0B,MAAM,OAAO,MAAM;CACvD,CAAC;CACD,iBAAiB,SAAS;EACxB,MAAM,MAAM,OAAO,KAAK,OAAO,QAAQ,WAAW,KAAK,MAAM,MAAM;EACnE,MAAM,SAAS,0BAA0B,KAAK,OAAO,MAAM;EAG3D,OAAO,IAFO,MAAM,gBAAgB,IAAI,QAAQ,YAAY,MAAM,MAAM,cAEvD,IADL,OAAO,KAAK,OAAO,QAAQ,WAAW,KAAK,MAAM,MAAM,GAC1C,GAAG,YAAY,EAAE,QAAQ,wBAAwB,MAAM,IAAI,KAAA,IAAY,OAAO,CAAC;CAC1G;CACA,cAAc;EACZ,OAAO;GACL,oBAAmB,WAAU,EAAE,eAAe;IAC5C,MAAM,SAAS,0BAA0B,OAAO,MAAM;IACtD,OAAO,SAAS,cAAc;KAAE,MAAM,KAAK;KAAM,OAAO;MAAE,KAAK,MAAM,MAAM;MAAG,KAAK,OAAO,OAAO;MAAI;KAAO;IAAE,CAAC;GACjH;GACA,sBAAsB,KAAK,WAAW,EAAE,IAAI,eAAe;IACzD,MAAM,OAAO,GAAG,IAAI,OAAO,GAAG;IAC9B,IAAI,MAAM,KAAK,SAAS,KAAK,MAAM,OAAO;IAC1C,MAAM,SAAS,0BAA0B,KAAK;IAC9C,IAAI,UAAU,GAAG,cAAc,KAAK,KAAA,GAAW;KAAE,GAAG,KAAK;KAAO;KAAQ,KAAK,MAAM,MAAM;IAAE,CAAC;IAC5F,OAAO;GACT;EACF;CACF;AACF,CAAC"}
|
|
@@ -11,7 +11,7 @@ export interface ImageStudioTurn {
|
|
|
11
11
|
export interface ImageStudioState {
|
|
12
12
|
/** The text describing the image; '' shows the placeholder. */
|
|
13
13
|
description: string;
|
|
14
|
-
/** Active
|
|
14
|
+
/** Active prompt blocks, by host id. */
|
|
15
15
|
promptBlockIds: string[];
|
|
16
16
|
/** Uploaded or picked references. */
|
|
17
17
|
references: ImageStudioImage[];
|
|
@@ -27,7 +27,7 @@ export declare const EMPTY_IMAGE_STUDIO_STATE: ImageStudioState;
|
|
|
27
27
|
/** The image that is the block's image (a generation or a reference), if the selection still points at one. */
|
|
28
28
|
export declare function selectedImage(state: ImageStudioState): ImageStudioImage | undefined;
|
|
29
29
|
export declare function setDescription(state: ImageStudioState, description: string): ImageStudioState;
|
|
30
|
-
/** Adds the
|
|
30
|
+
/** Adds the prompt block `id` to the active ones, or removes it when it is active. */
|
|
31
31
|
export declare function togglePromptBlock(state: ImageStudioState, id: string): ImageStudioState;
|
|
32
32
|
/** Flips `active` of the reference or generation `id`; an unknown id changes nothing. */
|
|
33
33
|
export declare function toggleImage(state: ImageStudioState, id: string): ImageStudioState;
|
|
@@ -45,7 +45,7 @@ export declare function addGeneration(state: ImageStudioState, image: ImageStudi
|
|
|
45
45
|
* state comes back unchanged (as a copy).
|
|
46
46
|
*/
|
|
47
47
|
export declare function normalizeImageStudioState(value: unknown): ImageStudioState;
|
|
48
|
-
/** Whether `state` holds nothing: no description,
|
|
48
|
+
/** Whether `state` holds nothing: no description, prompt block, image, selection or chat turn. */
|
|
49
49
|
export declare function isEmptyImageStudioState(state: ImageStudioState): boolean;
|
|
50
50
|
//#endregion
|
|
51
51
|
export type { ImageStudioImage, ImageStudioPromptBlock };
|
|
@@ -29,7 +29,7 @@ function setDescription(state, description) {
|
|
|
29
29
|
description
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
/** Adds the
|
|
32
|
+
/** Adds the prompt block `id` to the active ones, or removes it when it is active. */
|
|
33
33
|
function togglePromptBlock(state, id) {
|
|
34
34
|
const active = state.promptBlockIds.includes(id);
|
|
35
35
|
return {
|
|
@@ -137,7 +137,7 @@ function normalizeImageStudioState(value) {
|
|
|
137
137
|
selected: selected !== null && hasImage(state, selected) ? selected : null
|
|
138
138
|
};
|
|
139
139
|
}
|
|
140
|
-
/** Whether `state` holds nothing: no description,
|
|
140
|
+
/** Whether `state` holds nothing: no description, prompt block, image, selection or chat turn. */
|
|
141
141
|
function isEmptyImageStudioState(state) {
|
|
142
142
|
return state.description === "" && state.promptBlockIds.length === 0 && state.references.length === 0 && state.generations.length === 0 && state.selected === null && state.turns.length === 0;
|
|
143
143
|
}
|