@ecsplico/editor 4.0.0 → 4.1.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 +8 -0
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/image-studio/components/ImageStudioDialog.js +149 -86
- package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -1
- package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +4 -0
- package/dist/features/image-studio/components/ImageStudioLibraryPanel.js +186 -0
- package/dist/features/image-studio/components/ImageStudioLibraryPanel.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioLibraryTile.js +134 -0
- package/dist/features/image-studio/components/ImageStudioLibraryTile.js.map +1 -0
- package/dist/features/image-studio/index.d.ts +3 -2
- package/dist/features/image-studio/index.js +2 -2
- package/dist/features/image-studio/library.d.ts +35 -0
- package/dist/features/image-studio/react/ImageStudioDialog.js +45 -4
- package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -1
- package/dist/features/image-studio/react/ImageStudioLibraryPanel.js +141 -0
- package/dist/features/image-studio/react/ImageStudioLibraryPanel.js.map +1 -0
- package/dist/features/image-studio/react/ImageStudioLibraryTile.js +105 -0
- package/dist/features/image-studio/react/ImageStudioLibraryTile.js.map +1 -0
- package/dist/features/image-studio/react/store.d.ts +5 -0
- package/dist/features/image-studio/react/store.js.map +1 -1
- package/dist/features/image-studio/react.d.ts +3 -2
- package/dist/features/image-studio/react.js +2 -2
- package/dist/features/image-studio/react.js.map +1 -1
- package/dist/features/image-studio/state.d.ts +5 -0
- package/dist/features/image-studio/state.js +9 -1
- package/dist/features/image-studio/state.js.map +1 -1
- package/dist/features/image-studio/store.d.ts +5 -0
- package/dist/features/image-studio/vue.d.ts +3 -2
- package/dist/features/image-studio/vue.js +2 -2
- 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/i18n/de/image-studio.js +9 -0
- package/dist/i18n/de/image-studio.js.map +1 -1
- package/dist/i18n/de.d.ts +9 -0
- package/dist/i18n/en/image-studio.js +9 -0
- package/dist/i18n/en/image-studio.js.map +1 -1
- package/dist/integrations/composite-ai/transfer-transforms.js +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
- package/dist/nuxt/module.d.ts +18 -0
- package/dist/presets/compose.js +1 -1
- package/dist/react/compose.js +1 -1
- package/dist/server/handlers/ai.d.ts +2 -2
- package/dist/tree/tree-host.js +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
- package/dist/vue/SimpleEditor.vue.d.ts +4 -4
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/compose.js +1 -1
- package/package.json +1 -1
|
@@ -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 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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"}
|
|
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 type { ImageStudioLibraryItem } from '../library'\nimport {\n addGeneration,\n addReference,\n adoptImage,\n type ImageStudioImage,\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 { ImageStudioLibraryPanel } from './ImageStudioLibraryPanel'\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 // --- Gallery ------------------------------------------------------------------------------------------------------\n\n const { library } = store\n const [tab, setTab] = useState<'block' | 'library'>('block')\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 setTab('block')\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 /** A gallery item as the state holds it: without the host's `subtitle`, not active. */\n const pick = ({ id, url, title }: ImageStudioLibraryItem): ImageStudioImage => ({ id, url, title, active: false })\n\n return (\n <Dialog open={open} onOpenChange={(value) => { if (!value) close() }}>\n <DialogContent\n className=\"max-h-[calc(100vh-2rem)] overflow-y-auto sm:max-w-[min(72rem,calc(100vw-2rem))]\"\n aria-describedby={undefined}\n onEscapeKeyDown={(event) => {\n // Escape in the rename input of a gallery tile cancels the rename, it does not close the dialog.\n if (event.target instanceof Element && event.target.closest('[data-image-studio-library-rename]')) event.preventDefault()\n }}\n >\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 {library && (\n <div className=\"flex gap-1\" role=\"tablist\" data-image-studio-tabs>\n {(['block', 'library'] as const).map(name => (\n <Button\n key={name}\n size=\"sm\"\n variant={tab === name ? 'default' : 'ghost'}\n role=\"tab\"\n aria-selected={tab === name}\n data-image-studio-tab={name}\n onClick={() => setTab(name)}\n >\n {t(name === 'block' ? 'imageStudio.dialog.tabs.block' : 'imageStudio.dialog.tabs.library')}\n </Button>\n ))}\n </div>\n )}\n {library && tab === 'library'\n ? (\n <ImageStudioLibraryPanel\n library={library}\n state={state}\n onUse={item => apply(adoptImage(latest.current, pick(item)))}\n onReference={item => apply(addReference(latest.current, pick(item)))}\n onRemoved={(id) => {\n const current = latest.current\n if (current.references.some(image => image.id === id) || current.generations.some(image => image.id === id)) apply(removeImage(current, id))\n }}\n />\n )\n : (\n <>\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 </>\n )}\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 : store.placeholderUrl\n ? <img src={store.placeholderUrl} alt=\"\" className=\"size-full object-contain opacity-60\" data-image-studio-placeholder />\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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"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { fetchErrorMessage } from "../../../core/fetch-error.js";
|
|
2
|
+
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
3
|
+
import { Input } from "../../../react/ui/input.js";
|
|
4
|
+
import { Alert, AlertTitle } from "../../../react/ui/alert.js";
|
|
5
|
+
import { ImageStudioLibraryTile } from "./ImageStudioLibraryTile.js";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/features/image-studio/react/ImageStudioLibraryPanel.tsx
|
|
9
|
+
const SEARCH_DELAY_MS = 300;
|
|
10
|
+
/**
|
|
11
|
+
* The gallery tab of the Bild-Studio: search, one select per facet of the host and the host's images as tiles. Lists on
|
|
12
|
+
* mount, on every facet change and 300 ms after the last keystroke of the search; a newer request aborts the one still
|
|
13
|
+
* running. Rename and remove go through the library; a rejection shows as a notice and leaves the tile as it was.
|
|
14
|
+
* Picking is the dialog's business: the panel only reports `onUse`, `onReference` and `onRemoved`.
|
|
15
|
+
*/
|
|
16
|
+
function ImageStudioLibraryPanel({ library, state, onUse, onReference, onRemoved }) {
|
|
17
|
+
const { t } = useEditorI18n();
|
|
18
|
+
const [facets] = useState(() => library.facets());
|
|
19
|
+
const [search, setSearch] = useState("");
|
|
20
|
+
const [chosen, setChosen] = useState(() => Object.fromEntries(facets.map((facet) => [facet.id, facet.options[0]?.value ?? ""])));
|
|
21
|
+
const [items, setItems] = useState([]);
|
|
22
|
+
const [busy, setBusy] = useState(false);
|
|
23
|
+
const [problem, setProblem] = useState();
|
|
24
|
+
const libraryRef = useRef(library);
|
|
25
|
+
libraryRef.current = library;
|
|
26
|
+
const controller = useRef(void 0);
|
|
27
|
+
async function load(query) {
|
|
28
|
+
controller.current?.abort();
|
|
29
|
+
const own = new AbortController();
|
|
30
|
+
controller.current = own;
|
|
31
|
+
setBusy(true);
|
|
32
|
+
setProblem(void 0);
|
|
33
|
+
try {
|
|
34
|
+
const found = await libraryRef.current.list(query, own.signal);
|
|
35
|
+
if (own.signal.aborted) return;
|
|
36
|
+
setItems(found);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (own.signal.aborted) return;
|
|
39
|
+
setProblem(t("imageStudio.dialog.library.failed", { message: fetchErrorMessage(error) }));
|
|
40
|
+
} finally {
|
|
41
|
+
if (controller.current === own) setBusy(false);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const listedSearch = useRef(search);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const query = {
|
|
47
|
+
search,
|
|
48
|
+
facets: chosen
|
|
49
|
+
};
|
|
50
|
+
if (listedSearch.current === search) {
|
|
51
|
+
load(query);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
listedSearch.current = search;
|
|
55
|
+
const timer = setTimeout(() => void load(query), SEARCH_DELAY_MS);
|
|
56
|
+
return () => clearTimeout(timer);
|
|
57
|
+
}, [search, chosen]);
|
|
58
|
+
useEffect(() => () => controller.current?.abort(), []);
|
|
59
|
+
async function rename(item, title) {
|
|
60
|
+
if (!library.rename) return;
|
|
61
|
+
setProblem(void 0);
|
|
62
|
+
try {
|
|
63
|
+
const renamed = await library.rename(item.id, title);
|
|
64
|
+
setItems((current) => current.map((entry) => entry.id === item.id ? renamed : entry));
|
|
65
|
+
} catch (error) {
|
|
66
|
+
setProblem(fetchErrorMessage(error));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function remove(item) {
|
|
70
|
+
if (!library.remove) return;
|
|
71
|
+
setProblem(void 0);
|
|
72
|
+
try {
|
|
73
|
+
await library.remove(item.id);
|
|
74
|
+
setItems((current) => current.filter((entry) => entry.id !== item.id));
|
|
75
|
+
onRemoved(item.id);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
setProblem(fetchErrorMessage(error));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
81
|
+
className: "flex flex-col gap-2 p-1",
|
|
82
|
+
"data-image-studio-library": true,
|
|
83
|
+
children: [
|
|
84
|
+
/* @__PURE__ */ jsxs("div", {
|
|
85
|
+
className: "flex flex-wrap items-center gap-2",
|
|
86
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
87
|
+
type: "search",
|
|
88
|
+
value: search,
|
|
89
|
+
className: "min-w-32 flex-1",
|
|
90
|
+
"aria-label": t("imageStudio.dialog.library.search"),
|
|
91
|
+
"data-image-studio-library-search": true,
|
|
92
|
+
onChange: (event) => setSearch(event.target.value)
|
|
93
|
+
}), facets.map((facet) => /* @__PURE__ */ jsx("select", {
|
|
94
|
+
value: chosen[facet.id],
|
|
95
|
+
className: "h-8 max-w-full min-w-0 rounded-lg border border-input bg-transparent px-2 text-sm outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30",
|
|
96
|
+
"aria-label": facet.label,
|
|
97
|
+
"data-image-studio-facet": facet.id,
|
|
98
|
+
onChange: (event) => setChosen((current) => ({
|
|
99
|
+
...current,
|
|
100
|
+
[facet.id]: event.target.value
|
|
101
|
+
})),
|
|
102
|
+
children: facet.options.map((option) => /* @__PURE__ */ jsx("option", {
|
|
103
|
+
value: option.value,
|
|
104
|
+
children: option.label
|
|
105
|
+
}, option.value))
|
|
106
|
+
}, facet.id))]
|
|
107
|
+
}),
|
|
108
|
+
problem && /* @__PURE__ */ jsx(Alert, {
|
|
109
|
+
variant: "destructive",
|
|
110
|
+
children: /* @__PURE__ */ jsx(AlertTitle, { children: problem })
|
|
111
|
+
}),
|
|
112
|
+
busy && /* @__PURE__ */ jsx("p", {
|
|
113
|
+
className: "text-sm text-muted-foreground",
|
|
114
|
+
role: "status",
|
|
115
|
+
children: t("imageStudio.dialog.library.loading")
|
|
116
|
+
}),
|
|
117
|
+
!busy && !problem && !items.length && /* @__PURE__ */ jsx("p", {
|
|
118
|
+
className: "text-sm text-muted-foreground",
|
|
119
|
+
children: t("imageStudio.dialog.library.empty")
|
|
120
|
+
}),
|
|
121
|
+
items.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
122
|
+
className: "grid grid-cols-2 gap-2 sm:grid-cols-3",
|
|
123
|
+
children: items.map((item) => /* @__PURE__ */ jsx(ImageStudioLibraryTile, {
|
|
124
|
+
item,
|
|
125
|
+
selected: state.selected === item.id,
|
|
126
|
+
held: state.references.some((image) => image.id === item.id) || state.generations.some((image) => image.id === item.id),
|
|
127
|
+
renamable: Boolean(library.rename),
|
|
128
|
+
removable: Boolean(library.remove),
|
|
129
|
+
onUse: () => onUse(item),
|
|
130
|
+
onReference: () => onReference(item),
|
|
131
|
+
onRename: (title) => void rename(item, title),
|
|
132
|
+
onRemove: () => void remove(item)
|
|
133
|
+
}, item.id))
|
|
134
|
+
})
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { ImageStudioLibraryPanel };
|
|
140
|
+
|
|
141
|
+
//# sourceMappingURL=ImageStudioLibraryPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageStudioLibraryPanel.js","names":[],"sources":["../../../../src/features/image-studio/react/ImageStudioLibraryPanel.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react'\nimport { fetchErrorMessage } from '../../../core'\nimport { Alert, AlertTitle } from '../../../react/ui/alert'\nimport { Input } from '../../../react/ui/input'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { ImageStudioLibrary, ImageStudioLibraryItem, ImageStudioLibraryQuery } from '../library'\nimport type { ImageStudioState } from '../state'\nimport { ImageStudioLibraryTile } from './ImageStudioLibraryTile'\n\nconst SEARCH_DELAY_MS = 300\n\n/**\n * The gallery tab of the Bild-Studio: search, one select per facet of the host and the host's images as tiles. Lists on\n * mount, on every facet change and 300 ms after the last keystroke of the search; a newer request aborts the one still\n * running. Rename and remove go through the library; a rejection shows as a notice and leaves the tile as it was.\n * Picking is the dialog's business: the panel only reports `onUse`, `onReference` and `onRemoved`.\n */\nexport function ImageStudioLibraryPanel({ library, state, onUse, onReference, onRemoved }: {\n library: ImageStudioLibrary\n state: ImageStudioState\n onUse: (item: ImageStudioLibraryItem) => void\n onReference: (item: ImageStudioLibraryItem) => void\n onRemoved: (id: string) => void\n}) {\n const { t } = useEditorI18n()\n // Read once per opening of the tab: the panel is mounted by the tab.\n const [facets] = useState(() => library.facets())\n const [search, setSearch] = useState('')\n const [chosen, setChosen] = useState<Readonly<Record<string, string>>>(\n () => Object.fromEntries(facets.map(facet => [facet.id, facet.options[0]?.value ?? ''])),\n )\n const [items, setItems] = useState<readonly ImageStudioLibraryItem[]>([])\n const [busy, setBusy] = useState(false)\n const [problem, setProblem] = useState<string>()\n\n const libraryRef = useRef(library)\n libraryRef.current = library\n const controller = useRef<AbortController>(undefined)\n\n async function load(query: ImageStudioLibraryQuery) {\n controller.current?.abort()\n const own = new AbortController()\n controller.current = own\n setBusy(true)\n setProblem(undefined)\n try {\n const found = await libraryRef.current.list(query, own.signal)\n if (own.signal.aborted) return\n setItems(found)\n }\n catch (error) {\n if (own.signal.aborted) return\n setProblem(t('imageStudio.dialog.library.failed', { message: fetchErrorMessage(error) }))\n }\n finally {\n if (controller.current === own) setBusy(false)\n }\n }\n\n // The first run and a facet change list at once, a changed search text after the delay.\n const listedSearch = useRef(search)\n useEffect(() => {\n const query = { search, facets: chosen }\n if (listedSearch.current === search) {\n void load(query)\n return\n }\n listedSearch.current = search\n const timer = setTimeout(() => void load(query), SEARCH_DELAY_MS)\n return () => clearTimeout(timer)\n }, [search, chosen])\n useEffect(() => () => controller.current?.abort(), [])\n\n async function rename(item: ImageStudioLibraryItem, title: string) {\n if (!library.rename) return\n setProblem(undefined)\n try {\n const renamed = await library.rename(item.id, title)\n setItems(current => current.map(entry => entry.id === item.id ? renamed : entry))\n }\n catch (error) {\n setProblem(fetchErrorMessage(error))\n }\n }\n\n async function remove(item: ImageStudioLibraryItem) {\n if (!library.remove) return\n setProblem(undefined)\n try {\n await library.remove(item.id)\n setItems(current => current.filter(entry => entry.id !== item.id))\n onRemoved(item.id)\n }\n catch (error) {\n setProblem(fetchErrorMessage(error))\n }\n }\n\n return (\n <div className=\"flex flex-col gap-2 p-1\" data-image-studio-library>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Input\n type=\"search\"\n value={search}\n className=\"min-w-32 flex-1\"\n aria-label={t('imageStudio.dialog.library.search')}\n data-image-studio-library-search\n onChange={event => setSearch(event.target.value)}\n />\n {facets.map(facet => (\n <select\n key={facet.id}\n value={chosen[facet.id]}\n className=\"h-8 max-w-full min-w-0 rounded-lg border border-input bg-transparent px-2 text-sm outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30\"\n aria-label={facet.label}\n data-image-studio-facet={facet.id}\n onChange={event => setChosen(current => ({ ...current, [facet.id]: event.target.value }))}\n >\n {facet.options.map(option => <option key={option.value} value={option.value}>{option.label}</option>)}\n </select>\n ))}\n </div>\n {problem && (\n <Alert variant=\"destructive\">\n <AlertTitle>{problem}</AlertTitle>\n </Alert>\n )}\n {busy && <p className=\"text-sm text-muted-foreground\" role=\"status\">{t('imageStudio.dialog.library.loading')}</p>}\n {!busy && !problem && !items.length && <p className=\"text-sm text-muted-foreground\">{t('imageStudio.dialog.library.empty')}</p>}\n {items.length > 0 && (\n <ul className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {items.map(item => (\n <ImageStudioLibraryTile\n key={item.id}\n item={item}\n selected={state.selected === item.id}\n held={state.references.some(image => image.id === item.id) || state.generations.some(image => image.id === item.id)}\n renamable={Boolean(library.rename)}\n removable={Boolean(library.remove)}\n onUse={() => onUse(item)}\n onReference={() => onReference(item)}\n onRename={title => void rename(item, title)}\n onRemove={() => void remove(item)}\n />\n ))}\n </ul>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
2
|
+
import { Icon } from "../../../react/Icon.js";
|
|
3
|
+
import { Button } from "../../../react/ui/button.js";
|
|
4
|
+
import { Input } from "../../../react/ui/input.js";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/features/image-studio/react/ImageStudioLibraryTile.tsx
|
|
8
|
+
/**
|
|
9
|
+
* One image of the gallery tab. „Als Bild verwenden“ makes it the block's image, „Als Referenz“ adds it to the block's
|
|
10
|
+
* references (disabled when the block holds it already); the pencil swaps the title for an input (Enter renames, Escape
|
|
11
|
+
* or leaving the input cancels), the trash removes it from the gallery. Rename and remove show only when the host
|
|
12
|
+
* offers them (`renamable`, `removable`).
|
|
13
|
+
*/
|
|
14
|
+
function ImageStudioLibraryTile({ item, selected, held, renamable, removable, onUse, onReference, onRename, onRemove }) {
|
|
15
|
+
const { t } = useEditorI18n();
|
|
16
|
+
const title = item.title || t("imageStudio.dialog.tile.untitled");
|
|
17
|
+
const [draft, setDraft] = useState();
|
|
18
|
+
function commit() {
|
|
19
|
+
if (draft === void 0) return;
|
|
20
|
+
const next = draft.trim();
|
|
21
|
+
setDraft(void 0);
|
|
22
|
+
if (next && next !== item.title) onRename(next);
|
|
23
|
+
}
|
|
24
|
+
function onKeyDown(event) {
|
|
25
|
+
if (event.key === "Enter") {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
commit();
|
|
28
|
+
} else if (event.key === "Escape") setDraft(void 0);
|
|
29
|
+
}
|
|
30
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
31
|
+
className: "flex flex-col gap-1.5 rounded-md border border-border p-1.5 data-[selected=true]:border-primary data-[selected=true]:ring-2 data-[selected=true]:ring-primary",
|
|
32
|
+
"data-selected": selected,
|
|
33
|
+
"data-image-studio-library-tile": true,
|
|
34
|
+
children: [
|
|
35
|
+
/* @__PURE__ */ jsx("div", {
|
|
36
|
+
className: "overflow-hidden rounded bg-muted",
|
|
37
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
38
|
+
src: item.url,
|
|
39
|
+
alt: title,
|
|
40
|
+
className: "aspect-square w-full object-cover",
|
|
41
|
+
draggable: false
|
|
42
|
+
})
|
|
43
|
+
}),
|
|
44
|
+
/* @__PURE__ */ jsxs("div", {
|
|
45
|
+
className: "flex min-w-0 flex-col gap-0.5",
|
|
46
|
+
children: [draft === void 0 ? /* @__PURE__ */ jsx("span", {
|
|
47
|
+
className: "truncate text-sm font-medium text-foreground",
|
|
48
|
+
title,
|
|
49
|
+
children: title
|
|
50
|
+
}) : /* @__PURE__ */ jsx(Input, {
|
|
51
|
+
value: draft,
|
|
52
|
+
autoFocus: true,
|
|
53
|
+
className: "h-6 px-1.5 text-xs md:text-xs",
|
|
54
|
+
"aria-label": t("imageStudio.dialog.library.rename"),
|
|
55
|
+
"data-image-studio-library-rename": true,
|
|
56
|
+
onChange: (event) => setDraft(event.target.value),
|
|
57
|
+
onKeyDown,
|
|
58
|
+
onBlur: () => setDraft(void 0)
|
|
59
|
+
}), item.subtitle && /* @__PURE__ */ jsx("span", {
|
|
60
|
+
className: "truncate text-xs text-muted-foreground",
|
|
61
|
+
title: item.subtitle,
|
|
62
|
+
children: item.subtitle
|
|
63
|
+
})]
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ jsxs("div", {
|
|
66
|
+
className: "flex flex-wrap items-center justify-between gap-1",
|
|
67
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
68
|
+
className: "flex flex-wrap gap-1",
|
|
69
|
+
children: [/* @__PURE__ */ jsxs(Button, {
|
|
70
|
+
size: "xs",
|
|
71
|
+
variant: selected ? "default" : "outline",
|
|
72
|
+
"aria-pressed": selected,
|
|
73
|
+
onClick: onUse,
|
|
74
|
+
children: [selected && /* @__PURE__ */ jsx(Icon, { name: "i-lucide-check" }), t("imageStudio.dialog.tile.use")]
|
|
75
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
76
|
+
size: "xs",
|
|
77
|
+
variant: "outline",
|
|
78
|
+
disabled: held,
|
|
79
|
+
onClick: onReference,
|
|
80
|
+
children: t("imageStudio.dialog.library.reference")
|
|
81
|
+
})]
|
|
82
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
83
|
+
className: "flex gap-0.5",
|
|
84
|
+
children: [renamable && /* @__PURE__ */ jsx(Button, {
|
|
85
|
+
size: "icon-xs",
|
|
86
|
+
variant: "ghost",
|
|
87
|
+
"aria-label": t("imageStudio.dialog.library.rename"),
|
|
88
|
+
onClick: () => setDraft(item.title),
|
|
89
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "i-lucide-pencil" })
|
|
90
|
+
}), removable && /* @__PURE__ */ jsx(Button, {
|
|
91
|
+
size: "icon-xs",
|
|
92
|
+
variant: "ghost",
|
|
93
|
+
"aria-label": t("imageStudio.dialog.library.remove"),
|
|
94
|
+
onClick: onRemove,
|
|
95
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "i-lucide-trash-2" })
|
|
96
|
+
})]
|
|
97
|
+
})]
|
|
98
|
+
})
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { ImageStudioLibraryTile };
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=ImageStudioLibraryTile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageStudioLibraryTile.js","names":[],"sources":["../../../../src/features/image-studio/react/ImageStudioLibraryTile.tsx"],"sourcesContent":["import { type KeyboardEvent, useState } from 'react'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { Input } from '../../../react/ui/input'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport type { ImageStudioLibraryItem } from '../library'\n\n/**\n * One image of the gallery tab. „Als Bild verwenden“ makes it the block's image, „Als Referenz“ adds it to the block's\n * references (disabled when the block holds it already); the pencil swaps the title for an input (Enter renames, Escape\n * or leaving the input cancels), the trash removes it from the gallery. Rename and remove show only when the host\n * offers them (`renamable`, `removable`).\n */\nexport function ImageStudioLibraryTile({ item, selected, held, renamable, removable, onUse, onReference, onRename, onRemove }: {\n item: ImageStudioLibraryItem\n selected: boolean\n held: boolean\n renamable: boolean\n removable: boolean\n onUse: () => void\n onReference: () => void\n onRename: (title: string) => void\n onRemove: () => void\n}) {\n const { t } = useEditorI18n()\n const title = item.title || t('imageStudio.dialog.tile.untitled')\n const [draft, setDraft] = useState<string>()\n\n function commit() {\n if (draft === undefined) return\n const next = draft.trim()\n setDraft(undefined)\n if (next && next !== item.title) onRename(next)\n }\n\n function onKeyDown(event: KeyboardEvent<HTMLInputElement>) {\n if (event.key === 'Enter') {\n event.preventDefault()\n commit()\n }\n else if (event.key === 'Escape') {\n setDraft(undefined) // the dialog ignores this Escape (see `onEscapeKeyDown` there)\n }\n }\n\n return (\n <li\n className=\"flex flex-col gap-1.5 rounded-md border border-border p-1.5 data-[selected=true]:border-primary data-[selected=true]:ring-2 data-[selected=true]:ring-primary\"\n data-selected={selected}\n data-image-studio-library-tile\n >\n <div className=\"overflow-hidden rounded bg-muted\">\n <img src={item.url} alt={title} className=\"aspect-square w-full object-cover\" draggable={false} />\n </div>\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n {draft === undefined\n ? <span className=\"truncate text-sm font-medium text-foreground\" title={title}>{title}</span>\n : (\n <Input\n value={draft}\n autoFocus\n className=\"h-6 px-1.5 text-xs md:text-xs\"\n aria-label={t('imageStudio.dialog.library.rename')}\n data-image-studio-library-rename\n onChange={event => setDraft(event.target.value)}\n onKeyDown={onKeyDown}\n onBlur={() => setDraft(undefined)}\n />\n )}\n {item.subtitle && <span className=\"truncate text-xs text-muted-foreground\" title={item.subtitle}>{item.subtitle}</span>}\n </div>\n <div className=\"flex flex-wrap items-center justify-between gap-1\">\n <div className=\"flex flex-wrap gap-1\">\n <Button size=\"xs\" variant={selected ? 'default' : 'outline'} aria-pressed={selected} onClick={onUse}>\n {selected && <Icon name=\"i-lucide-check\" />}\n {t('imageStudio.dialog.tile.use')}\n </Button>\n <Button size=\"xs\" variant=\"outline\" disabled={held} onClick={onReference}>\n {t('imageStudio.dialog.library.reference')}\n </Button>\n </div>\n <div className=\"flex gap-0.5\">\n {renamable && (\n <Button size=\"icon-xs\" variant=\"ghost\" aria-label={t('imageStudio.dialog.library.rename')} onClick={() => setDraft(item.title)}>\n <Icon name=\"i-lucide-pencil\" />\n </Button>\n )}\n {removable && (\n <Button size=\"icon-xs\" variant=\"ghost\" aria-label={t('imageStudio.dialog.library.remove')} onClick={onRemove}>\n <Icon name=\"i-lucide-trash-2\" />\n </Button>\n )}\n </div>\n </div>\n </li>\n )\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,uBAAuB,EAAE,MAAM,UAAU,MAAM,WAAW,WAAW,OAAO,aAAa,UAAU,YAUhH;CACD,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,QAAQ,KAAK,SAAS,EAAE,kCAAkC;CAChE,MAAM,CAAC,OAAO,YAAY,SAAiB;CAE3C,SAAS,SAAS;EAChB,IAAI,UAAU,KAAA,GAAW;EACzB,MAAM,OAAO,MAAM,KAAK;EACxB,SAAS,KAAA,CAAS;EAClB,IAAI,QAAQ,SAAS,KAAK,OAAO,SAAS,IAAI;CAChD;CAEA,SAAS,UAAU,OAAwC;EACzD,IAAI,MAAM,QAAQ,SAAS;GACzB,MAAM,eAAe;GACrB,OAAO;EACT,OACK,IAAI,MAAM,QAAQ,UACrB,SAAS,KAAA,CAAS;CAEtB;CAEA,OACE,qBAAC,MAAD;EACE,WAAU;EACV,iBAAe;EACf,kCAAA;EAHF,UAAA;GAKE,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,OAAD;KAAK,KAAK,KAAK;KAAK,KAAK;KAAO,WAAU;KAAoC,WAAW;IAAQ,CAAA;GAC9F,CAAA;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACG,UAAU,KAAA,IACP,oBAAC,QAAD;KAAM,WAAU;KAAsD;KAAQ,UAAA;IAAY,CAAA,IAExF,oBAAC,OAAD;KACE,OAAO;KACP,WAAA;KACA,WAAU;KACV,cAAY,EAAE,mCAAmC;KACjD,oCAAA;KACA,WAAU,UAAS,SAAS,MAAM,OAAO,KAAK;KACnC;KACX,cAAc,SAAS,KAAA,CAAS;IACjC,CAAA,GAEN,KAAK,YAAY,oBAAC,QAAD;KAAM,WAAU;KAAyC,OAAO,KAAK;KAAW,UAAA,KAAK;IAAe,CAAA,CACnH;;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,qBAAC,QAAD;MAAQ,MAAK;MAAK,SAAS,WAAW,YAAY;MAAW,gBAAc;MAAU,SAAS;MAA9F,UAAA,CACG,YAAY,oBAAC,MAAD,EAAM,MAAK,iBAAkB,CAAA,GACzC,EAAE,6BAA6B,CAC1B;KACR,CAAA,GAAA,oBAAC,QAAD;MAAQ,MAAK;MAAK,SAAQ;MAAU,UAAU;MAAM,SAAS;MAC1D,UAAA,EAAE,sCAAsC;KACnC,CAAA,CACL;IACL,CAAA,GAAA,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACG,aACC,oBAAC,QAAD;MAAQ,MAAK;MAAU,SAAQ;MAAQ,cAAY,EAAE,mCAAmC;MAAG,eAAe,SAAS,KAAK,KAAK;MAC3H,UAAA,oBAAC,MAAD,EAAM,MAAK,kBAAmB,CAAA;KACxB,CAAA,GAET,aACC,oBAAC,QAAD;MAAQ,MAAK;MAAU,SAAQ;MAAQ,cAAY,EAAE,mCAAmC;MAAG,SAAS;MAClG,UAAA,oBAAC,MAAD,EAAM,MAAK,mBAAoB,CAAA;KACzB,CAAA,CAEP;IACF,CAAA,CAAA;;EACH;;AAER"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ImageStudioImage, ImageStudioPromptBlock } from "../../../core/backend-types.js";
|
|
2
2
|
import { ImageStudioState } from "../state.js";
|
|
3
|
+
import { ImageStudioLibrary } from "../library.js";
|
|
3
4
|
import { ImageStudioNodeCoreOptions } from "../node-core.js";
|
|
4
5
|
//#region src/features/image-studio/react/store.d.ts
|
|
5
6
|
/**
|
|
@@ -25,6 +26,10 @@ export interface ImageStudioReactStore {
|
|
|
25
26
|
generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>;
|
|
26
27
|
upload: (file: File) => Promise<ImageStudioImage>;
|
|
27
28
|
readonly problem: string | undefined;
|
|
29
|
+
/** The host's gallery; the dialog shows a «Galerie» tab only when it is set. */
|
|
30
|
+
readonly library?: ImageStudioLibrary;
|
|
31
|
+
/** The URL of the image shown in the preview while no image is selected. */
|
|
32
|
+
readonly placeholderUrl?: string;
|
|
28
33
|
}
|
|
29
34
|
/**
|
|
30
35
|
* The `ImageStudioReactStore` of one `imageStudio` node (the React wrapper of `createImageStudioNodeCore`): `state`
|
|
@@ -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 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":";;;
|
|
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 { ImageStudioLibrary } from '../library'\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 /** The host's gallery; the dialog shows a «Galerie» tab only when it is set. */\n readonly library?: ImageStudioLibrary\n /** The URL of the image shown in the preview while no image is selected. */\n readonly placeholderUrl?: string\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":";;;AAkCA,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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ImageStudioImage, ImageStudioPromptBlock } from "../../core/backend-types.js";
|
|
2
2
|
import { ReactFeature } from "../../react/EditorSurface.js";
|
|
3
3
|
import "../../react/index.js";
|
|
4
|
-
import { EMPTY_IMAGE_STUDIO_STATE, ImageStudioState, ImageStudioTurn, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
4
|
+
import { EMPTY_IMAGE_STUDIO_STATE, ImageStudioState, ImageStudioTurn, addGeneration, addReference, adoptImage, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
5
|
+
import { ImageStudioLibrary, ImageStudioLibraryFacet, ImageStudioLibraryItem, ImageStudioLibraryQuery } from "./library.js";
|
|
5
6
|
import { ImageStudioNodeCore, ImageStudioNodeCoreOptions, ImageStudioNodeSnapshot, createImageStudioNodeCore } from "./node-core.js";
|
|
6
7
|
import { ImageStudioReactStore, useImageStudioNodeStore } from "./react/store.js";
|
|
7
8
|
import { ImageStudioDialog } from "./react/ImageStudioDialog.js";
|
|
@@ -14,5 +15,5 @@ import { ImageStudioDialog } from "./react/ImageStudioDialog.js";
|
|
|
14
15
|
*/
|
|
15
16
|
export declare function imageStudioFeature(): ReactFeature;
|
|
16
17
|
//#endregion
|
|
17
|
-
export { EMPTY_IMAGE_STUDIO_STATE, ImageStudioDialog, type ImageStudioImage, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions, type ImageStudioNodeSnapshot, type ImageStudioPromptBlock, type ImageStudioReactStore, type ImageStudioState, type ImageStudioTurn, addGeneration, addReference, createImageStudioNodeCore, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock, useImageStudioNodeStore };
|
|
18
|
+
export { EMPTY_IMAGE_STUDIO_STATE, ImageStudioDialog, type ImageStudioImage, type ImageStudioLibrary, type ImageStudioLibraryFacet, type ImageStudioLibraryItem, type ImageStudioLibraryQuery, type ImageStudioNodeCore, type ImageStudioNodeCoreOptions, type ImageStudioNodeSnapshot, type ImageStudioPromptBlock, type ImageStudioReactStore, type ImageStudioState, type ImageStudioTurn, addGeneration, addReference, adoptImage, createImageStudioNodeCore, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock, useImageStudioNodeStore };
|
|
18
19
|
//# sourceMappingURL=react.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
1
|
+
import { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, adoptImage, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
2
2
|
import { imageStudioFeatureCore } from "./feature.js";
|
|
3
3
|
import { ImageStudioDialog } from "./react/ImageStudioDialog.js";
|
|
4
4
|
import { createImageStudioNodeCore } from "./node-core.js";
|
|
@@ -16,6 +16,6 @@ function imageStudioFeature() {
|
|
|
16
16
|
return imageStudioFeatureCore({ nodeView: ReactNodeViewRenderer(ImageStudioView) });
|
|
17
17
|
}
|
|
18
18
|
//#endregion
|
|
19
|
-
export { EMPTY_IMAGE_STUDIO_STATE, ImageStudioDialog, addGeneration, addReference, createImageStudioNodeCore, imageStudioFeature, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock, useImageStudioNodeStore };
|
|
19
|
+
export { EMPTY_IMAGE_STUDIO_STATE, ImageStudioDialog, addGeneration, addReference, adoptImage, createImageStudioNodeCore, imageStudioFeature, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock, useImageStudioNodeStore };
|
|
20
20
|
|
|
21
21
|
//# sourceMappingURL=react.js.map
|
|
@@ -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 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":";;;;;;;;;;;;;;
|
|
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 adoptImage,\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'\nexport type { ImageStudioLibrary, ImageStudioLibraryFacet, ImageStudioLibraryItem, ImageStudioLibraryQuery } from './library'\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":";;;;;;;;;;;;;;AAkCA,SAAgB,qBAAmC;CACjD,OAAO,uBAAuB,EAAE,UAAU,sBAAsB,eAAe,EAAE,CAAC;AACpF"}
|
|
@@ -37,6 +37,11 @@ export declare function select(state: ImageStudioState, id: string | null): Imag
|
|
|
37
37
|
export declare function removeImage(state: ImageStudioState, id: string): ImageStudioState;
|
|
38
38
|
/** Appends a reference; one with the same id is replaced where it stands. */
|
|
39
39
|
export declare function addReference(state: ImageStudioState, image: ImageStudioImage): ImageStudioState;
|
|
40
|
+
/**
|
|
41
|
+
* Makes `image` the block's image: unchanged when it already is; an image the block holds (reference or generation) is
|
|
42
|
+
* only selected; any other is added as a reference first.
|
|
43
|
+
*/
|
|
44
|
+
export declare function adoptImage(state: ImageStudioState, image: ImageStudioImage): ImageStudioState;
|
|
40
45
|
/** A chat generate means "this is the picture now": appends the image and its turn (pointing at the image) and selects it. */
|
|
41
46
|
export declare function addGeneration(state: ImageStudioState, image: ImageStudioImage, turn: ImageStudioTurn): ImageStudioState;
|
|
42
47
|
/**
|
|
@@ -69,6 +69,14 @@ function addReference(state, image) {
|
|
|
69
69
|
references: known ? state.references.map((other) => other.id === image.id ? { ...image } : other) : [...state.references, { ...image }]
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Makes `image` the block's image: unchanged when it already is; an image the block holds (reference or generation) is
|
|
74
|
+
* only selected; any other is added as a reference first.
|
|
75
|
+
*/
|
|
76
|
+
function adoptImage(state, image) {
|
|
77
|
+
if (state.selected === image.id) return state;
|
|
78
|
+
return select(hasImage(state, image.id) ? state : addReference(state, image), image.id);
|
|
79
|
+
}
|
|
72
80
|
/** A chat generate means "this is the picture now": appends the image and its turn (pointing at the image) and selects it. */
|
|
73
81
|
function addGeneration(state, image, turn) {
|
|
74
82
|
return {
|
|
@@ -142,6 +150,6 @@ function isEmptyImageStudioState(state) {
|
|
|
142
150
|
return state.description === "" && state.promptBlockIds.length === 0 && state.references.length === 0 && state.generations.length === 0 && state.selected === null && state.turns.length === 0;
|
|
143
151
|
}
|
|
144
152
|
//#endregion
|
|
145
|
-
export { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
153
|
+
export { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, adoptImage, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
146
154
|
|
|
147
155
|
//# sourceMappingURL=state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","names":[],"sources":["../../../src/features/image-studio/state.ts"],"sourcesContent":["/**\n * The Bild-Studio's state: what a block holds about its picture. Host-agnostic\n * JSON; image ids are opaque to the library (a host uses its own asset ids).\n */\n\nimport { z } from 'zod'\nimport type { ImageStudioImage, ImageStudioPromptBlock } from '../../core/backend-types'\n\n// `ImageStudioImage` and `ImageStudioPromptBlock` are wire types of `EditorBackend.images`, so they live in\n// `core/backend-types.ts` (core never imports a feature); this module is their feature-side entry.\nexport type { ImageStudioImage, ImageStudioPromptBlock }\n\n/** One chat turn: the instruction and the generation it produced (or the problem). */\nexport interface ImageStudioTurn {\n id: string\n instruction: string\n imageId: string | null\n createdAt: string\n problem?: string\n}\n\nexport interface ImageStudioState {\n /** The text describing the image; '' shows the placeholder. */\n description: string\n /** Active prompt blocks, by host id. */\n promptBlockIds: string[]\n /** Uploaded or picked references. */\n references: ImageStudioImage[]\n /** Previous generations; an active one goes into the next generation as a reference. */\n generations: ImageStudioImage[]\n /** Id of the generation (or reference) that is the block's image. */\n selected: string | null\n /** The chat log: instruction → generation. */\n turns: ImageStudioTurn[]\n}\n\n/** The state of a block that has no description, no images and no chat yet. */\nexport const EMPTY_IMAGE_STUDIO_STATE: ImageStudioState = {\n description: '',\n promptBlockIds: [],\n references: [],\n generations: [],\n selected: null,\n turns: [],\n}\n\n// Reducers: pure, each returns a new state and never touches its argument. The host owns the files behind the images.\n\nconst hasImage = (state: ImageStudioState, id: string): boolean =>\n state.references.some(image => image.id === id) || state.generations.some(image => image.id === id)\n\nconst toggled = (images: readonly ImageStudioImage[], id: string): ImageStudioImage[] =>\n images.map(image => ({ ...image, active: image.id === id ? !image.active : image.active }))\n\n/** The image that is the block's image (a generation or a reference), if the selection still points at one. */\nexport function selectedImage(state: ImageStudioState): ImageStudioImage | undefined {\n if (state.selected === null) return undefined\n return state.generations.find(image => image.id === state.selected) ?? state.references.find(image => image.id === state.selected)\n}\n\nexport function setDescription(state: ImageStudioState, description: string): ImageStudioState {\n return { ...state, description }\n}\n\n/** Adds the prompt block `id` to the active ones, or removes it when it is active. */\nexport function togglePromptBlock(state: ImageStudioState, id: string): ImageStudioState {\n const active = state.promptBlockIds.includes(id)\n return { ...state, promptBlockIds: active ? state.promptBlockIds.filter(other => other !== id) : [...state.promptBlockIds, id] }\n}\n\n/** Flips `active` of the reference or generation `id`; an unknown id changes nothing. */\nexport function toggleImage(state: ImageStudioState, id: string): ImageStudioState {\n return { ...state, references: toggled(state.references, id), generations: toggled(state.generations, id) }\n}\n\n/** Makes the reference or generation `id` the block's image; `null` clears the choice. An unknown id changes nothing. */\nexport function select(state: ImageStudioState, id: string | null): ImageStudioState {\n return { ...state, selected: id === null || hasImage(state, id) ? id : state.selected }\n}\n\n/** Drops the image `id` from the block (references and generations); the selection goes with it, the chat log keeps its turns. */\nexport function removeImage(state: ImageStudioState, id: string): ImageStudioState {\n return {\n ...state,\n references: state.references.filter(image => image.id !== id),\n generations: state.generations.filter(image => image.id !== id),\n selected: state.selected === id ? null : state.selected,\n }\n}\n\n/** Appends a reference; one with the same id is replaced where it stands. */\nexport function addReference(state: ImageStudioState, image: ImageStudioImage): ImageStudioState {\n const known = state.references.some(other => other.id === image.id)\n return { ...state, references: known ? state.references.map(other => other.id === image.id ? { ...image } : other) : [...state.references, { ...image }] }\n}\n\n/** A chat generate means \"this is the picture now\": appends the image and its turn (pointing at the image) and selects it. */\nexport function addGeneration(state: ImageStudioState, image: ImageStudioImage, turn: ImageStudioTurn): ImageStudioState {\n return {\n ...state,\n generations: [...state.generations, { ...image }],\n turns: [...state.turns, { ...turn, imageId: image.id }],\n selected: image.id,\n }\n}\n\n// Parsing: stored attributes and JSON from other tools are untrusted, a missing or mistyped field falls back to its empty value.\n// Fields are read one by one from a record: a zod object would reject an entry for a missing key, where this one defaults it.\nconst Fields = z.record(z.string(), z.unknown())\n\nconst textOf = (value: unknown): string => typeof value === 'string' ? value : ''\nconst idOf = (value: unknown): string | null => typeof value === 'string' && value !== '' ? value : null\n\n/** The entries of the array `value` that `parse` accepts (it answers `null` for a refused one); a non-array has none. */\nfunction entriesOf<T>(value: unknown, parse: (entry: unknown) => T | null): T[] {\n return Array.isArray(value) ? value.flatMap((entry: unknown) => parse(entry) ?? []) : []\n}\n\nfunction imageOf(entry: unknown): ImageStudioImage | null {\n const fields = Fields.safeParse(entry)\n const id = fields.success ? idOf(fields.data.id) : null\n if (!fields.success || id === null) return null\n return { id, url: textOf(fields.data.url), title: textOf(fields.data.title), active: fields.data.active === true }\n}\n\nfunction turnOf(entry: unknown): ImageStudioTurn | null {\n const fields = Fields.safeParse(entry)\n const id = fields.success ? idOf(fields.data.id) : null\n if (!fields.success || id === null) return null\n const turn: ImageStudioTurn = { id, instruction: textOf(fields.data.instruction), imageId: idOf(fields.data.imageId), createdAt: textOf(fields.data.createdAt) }\n const problem = idOf(fields.data.problem)\n return problem === null ? turn : { ...turn, problem }\n}\n\n/**\n * The state `value` stands for, for parsed attributes and stored JSON: a missing field, a wrong type or an entry without\n * an id falls back to the empty value of that field, and a `selected` that points at no image becomes `null`. A valid\n * state comes back unchanged (as a copy).\n */\nexport function normalizeImageStudioState(value: unknown): ImageStudioState {\n const fields = Fields.safeParse(value)\n const data = fields.success ? fields.data : {}\n const state: ImageStudioState = {\n description: textOf(data.description),\n promptBlockIds: entriesOf(data.promptBlockIds, idOf),\n references: entriesOf(data.references, imageOf),\n generations: entriesOf(data.generations, imageOf),\n selected: null,\n turns: entriesOf(data.turns, turnOf),\n }\n const selected = idOf(data.selected)\n return { ...state, selected: selected !== null && hasImage(state, selected) ? selected : null }\n}\n\n/** Whether `state` holds nothing: no description, prompt block, image, selection or chat turn. */\nexport function isEmptyImageStudioState(state: ImageStudioState): boolean {\n return state.description === '' && state.promptBlockIds.length === 0 && state.references.length === 0 && state.generations.length === 0\n && state.selected === null && state.turns.length === 0\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"state.js","names":[],"sources":["../../../src/features/image-studio/state.ts"],"sourcesContent":["/**\n * The Bild-Studio's state: what a block holds about its picture. Host-agnostic\n * JSON; image ids are opaque to the library (a host uses its own asset ids).\n */\n\nimport { z } from 'zod'\nimport type { ImageStudioImage, ImageStudioPromptBlock } from '../../core/backend-types'\n\n// `ImageStudioImage` and `ImageStudioPromptBlock` are wire types of `EditorBackend.images`, so they live in\n// `core/backend-types.ts` (core never imports a feature); this module is their feature-side entry.\nexport type { ImageStudioImage, ImageStudioPromptBlock }\n\n/** One chat turn: the instruction and the generation it produced (or the problem). */\nexport interface ImageStudioTurn {\n id: string\n instruction: string\n imageId: string | null\n createdAt: string\n problem?: string\n}\n\nexport interface ImageStudioState {\n /** The text describing the image; '' shows the placeholder. */\n description: string\n /** Active prompt blocks, by host id. */\n promptBlockIds: string[]\n /** Uploaded or picked references. */\n references: ImageStudioImage[]\n /** Previous generations; an active one goes into the next generation as a reference. */\n generations: ImageStudioImage[]\n /** Id of the generation (or reference) that is the block's image. */\n selected: string | null\n /** The chat log: instruction → generation. */\n turns: ImageStudioTurn[]\n}\n\n/** The state of a block that has no description, no images and no chat yet. */\nexport const EMPTY_IMAGE_STUDIO_STATE: ImageStudioState = {\n description: '',\n promptBlockIds: [],\n references: [],\n generations: [],\n selected: null,\n turns: [],\n}\n\n// Reducers: pure, each returns a new state and never touches its argument. The host owns the files behind the images.\n\nconst hasImage = (state: ImageStudioState, id: string): boolean =>\n state.references.some(image => image.id === id) || state.generations.some(image => image.id === id)\n\nconst toggled = (images: readonly ImageStudioImage[], id: string): ImageStudioImage[] =>\n images.map(image => ({ ...image, active: image.id === id ? !image.active : image.active }))\n\n/** The image that is the block's image (a generation or a reference), if the selection still points at one. */\nexport function selectedImage(state: ImageStudioState): ImageStudioImage | undefined {\n if (state.selected === null) return undefined\n return state.generations.find(image => image.id === state.selected) ?? state.references.find(image => image.id === state.selected)\n}\n\nexport function setDescription(state: ImageStudioState, description: string): ImageStudioState {\n return { ...state, description }\n}\n\n/** Adds the prompt block `id` to the active ones, or removes it when it is active. */\nexport function togglePromptBlock(state: ImageStudioState, id: string): ImageStudioState {\n const active = state.promptBlockIds.includes(id)\n return { ...state, promptBlockIds: active ? state.promptBlockIds.filter(other => other !== id) : [...state.promptBlockIds, id] }\n}\n\n/** Flips `active` of the reference or generation `id`; an unknown id changes nothing. */\nexport function toggleImage(state: ImageStudioState, id: string): ImageStudioState {\n return { ...state, references: toggled(state.references, id), generations: toggled(state.generations, id) }\n}\n\n/** Makes the reference or generation `id` the block's image; `null` clears the choice. An unknown id changes nothing. */\nexport function select(state: ImageStudioState, id: string | null): ImageStudioState {\n return { ...state, selected: id === null || hasImage(state, id) ? id : state.selected }\n}\n\n/** Drops the image `id` from the block (references and generations); the selection goes with it, the chat log keeps its turns. */\nexport function removeImage(state: ImageStudioState, id: string): ImageStudioState {\n return {\n ...state,\n references: state.references.filter(image => image.id !== id),\n generations: state.generations.filter(image => image.id !== id),\n selected: state.selected === id ? null : state.selected,\n }\n}\n\n/** Appends a reference; one with the same id is replaced where it stands. */\nexport function addReference(state: ImageStudioState, image: ImageStudioImage): ImageStudioState {\n const known = state.references.some(other => other.id === image.id)\n return { ...state, references: known ? state.references.map(other => other.id === image.id ? { ...image } : other) : [...state.references, { ...image }] }\n}\n\n/**\n * Makes `image` the block's image: unchanged when it already is; an image the block holds (reference or generation) is\n * only selected; any other is added as a reference first.\n */\nexport function adoptImage(state: ImageStudioState, image: ImageStudioImage): ImageStudioState {\n if (state.selected === image.id) return state\n return select(hasImage(state, image.id) ? state : addReference(state, image), image.id)\n}\n\n/** A chat generate means \"this is the picture now\": appends the image and its turn (pointing at the image) and selects it. */\nexport function addGeneration(state: ImageStudioState, image: ImageStudioImage, turn: ImageStudioTurn): ImageStudioState {\n return {\n ...state,\n generations: [...state.generations, { ...image }],\n turns: [...state.turns, { ...turn, imageId: image.id }],\n selected: image.id,\n }\n}\n\n// Parsing: stored attributes and JSON from other tools are untrusted, a missing or mistyped field falls back to its empty value.\n// Fields are read one by one from a record: a zod object would reject an entry for a missing key, where this one defaults it.\nconst Fields = z.record(z.string(), z.unknown())\n\nconst textOf = (value: unknown): string => typeof value === 'string' ? value : ''\nconst idOf = (value: unknown): string | null => typeof value === 'string' && value !== '' ? value : null\n\n/** The entries of the array `value` that `parse` accepts (it answers `null` for a refused one); a non-array has none. */\nfunction entriesOf<T>(value: unknown, parse: (entry: unknown) => T | null): T[] {\n return Array.isArray(value) ? value.flatMap((entry: unknown) => parse(entry) ?? []) : []\n}\n\nfunction imageOf(entry: unknown): ImageStudioImage | null {\n const fields = Fields.safeParse(entry)\n const id = fields.success ? idOf(fields.data.id) : null\n if (!fields.success || id === null) return null\n return { id, url: textOf(fields.data.url), title: textOf(fields.data.title), active: fields.data.active === true }\n}\n\nfunction turnOf(entry: unknown): ImageStudioTurn | null {\n const fields = Fields.safeParse(entry)\n const id = fields.success ? idOf(fields.data.id) : null\n if (!fields.success || id === null) return null\n const turn: ImageStudioTurn = { id, instruction: textOf(fields.data.instruction), imageId: idOf(fields.data.imageId), createdAt: textOf(fields.data.createdAt) }\n const problem = idOf(fields.data.problem)\n return problem === null ? turn : { ...turn, problem }\n}\n\n/**\n * The state `value` stands for, for parsed attributes and stored JSON: a missing field, a wrong type or an entry without\n * an id falls back to the empty value of that field, and a `selected` that points at no image becomes `null`. A valid\n * state comes back unchanged (as a copy).\n */\nexport function normalizeImageStudioState(value: unknown): ImageStudioState {\n const fields = Fields.safeParse(value)\n const data = fields.success ? fields.data : {}\n const state: ImageStudioState = {\n description: textOf(data.description),\n promptBlockIds: entriesOf(data.promptBlockIds, idOf),\n references: entriesOf(data.references, imageOf),\n generations: entriesOf(data.generations, imageOf),\n selected: null,\n turns: entriesOf(data.turns, turnOf),\n }\n const selected = idOf(data.selected)\n return { ...state, selected: selected !== null && hasImage(state, selected) ? selected : null }\n}\n\n/** Whether `state` holds nothing: no description, prompt block, image, selection or chat turn. */\nexport function isEmptyImageStudioState(state: ImageStudioState): boolean {\n return state.description === '' && state.promptBlockIds.length === 0 && state.references.length === 0 && state.generations.length === 0\n && state.selected === null && state.turns.length === 0\n}\n\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ImageStudioImage, ImageStudioPromptBlock } from "../../core/backend-types.js";
|
|
2
2
|
import { ImageStudioState } from "./state.js";
|
|
3
|
+
import { ImageStudioLibrary } from "./library.js";
|
|
3
4
|
import { Ref } from "vue";
|
|
4
5
|
//#region src/features/image-studio/store.d.ts
|
|
5
6
|
/**
|
|
@@ -24,6 +25,10 @@ export interface ImageStudioStore {
|
|
|
24
25
|
generate: (instruction: string, signal: AbortSignal) => Promise<ImageStudioImage>;
|
|
25
26
|
upload: (file: File) => Promise<ImageStudioImage>;
|
|
26
27
|
readonly problem: Ref<string | undefined>;
|
|
28
|
+
/** The host's gallery; the dialog shows a «Galerie» tab only when it is set. */
|
|
29
|
+
readonly library?: ImageStudioLibrary;
|
|
30
|
+
/** The URL of the image shown in the preview while no image is selected. */
|
|
31
|
+
readonly placeholderUrl?: string;
|
|
27
32
|
}
|
|
28
33
|
//#endregion
|
|
29
34
|
//# sourceMappingURL=store.d.ts.map
|