text-img-editor 0.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/README.md +88 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +180 -0
- package/dist/index.js +545 -0
- package/dist/style.css +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Text & Image Editor
|
|
2
|
+
|
|
3
|
+
Block-based editor for creating LLM prompts with text, annotated images, and variables. Features Slate.js rich text with @mentions and #variables.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
npm install
|
|
10
|
+
|
|
11
|
+
# Start upload server (for image uploads)
|
|
12
|
+
cd server && npm install && npm start
|
|
13
|
+
|
|
14
|
+
# Run development server (new terminal)
|
|
15
|
+
npm run dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Open http://localhost:5173
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
### Rich Text Editor (Slate.js)
|
|
23
|
+
- **@mentions** - Reference bounding boxes on images (`@BOX_NAME`)
|
|
24
|
+
- **#variables** - Insert string or boolean variables (`#VAR_NAME`)
|
|
25
|
+
- Click on tokens to see details (box preview / variable info)
|
|
26
|
+
|
|
27
|
+
### Image Blocks
|
|
28
|
+
- Drag & drop images into the document
|
|
29
|
+
- Draw bounding boxes with mouse
|
|
30
|
+
- Name boxes in SCREAMING_SNAKE_CASE for @mention references
|
|
31
|
+
- Add descriptions to boxes
|
|
32
|
+
|
|
33
|
+
### LLM-Ready Output
|
|
34
|
+
Generates OpenAI Vision API compatible format:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"content": [
|
|
39
|
+
{ "type": "text", "text": "Analyze [box_id - abc123]" },
|
|
40
|
+
{ "type": "image", "image": "https://..." },
|
|
41
|
+
{ "type": "text", "text": "{\"image_id\":\"...\",\"image_size\":[1920,1080],\"boxes\":[...]}" }
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
1. **Text**: Click to type, use `@` for box mentions, `#` for variables
|
|
49
|
+
2. **Images**: Drag image files into the editor
|
|
50
|
+
3. **Boxes**: Click and drag on images to draw bounding boxes
|
|
51
|
+
4. **Box Names**: Click a box to set its SCREAMING_SNAKE_CASE name
|
|
52
|
+
5. **Mentions**: Type `@` to reference named boxes in text
|
|
53
|
+
6. **Variables**: Type `#` to insert or create variables
|
|
54
|
+
|
|
55
|
+
## Token Format
|
|
56
|
+
|
|
57
|
+
| Token | Example | Output |
|
|
58
|
+
|-------|---------|--------|
|
|
59
|
+
| @mention | `@MACHINE` | `[box_id - xyz123]` |
|
|
60
|
+
| #variable (string) | `#USER_NAME` | Replaced with value |
|
|
61
|
+
| #variable (boolean) | `#IS_ACTIVE` | `[CONDITION: if "..." then ... else ...]` |
|
|
62
|
+
|
|
63
|
+
## Box Metadata Format
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"image_id": "abc123",
|
|
68
|
+
"image_size": [1920, 1080],
|
|
69
|
+
"bbox_format": "xyxy",
|
|
70
|
+
"bbox_units": "pixels",
|
|
71
|
+
"boxes": [
|
|
72
|
+
{
|
|
73
|
+
"box_id": "xyz789",
|
|
74
|
+
"label": "MACHINE",
|
|
75
|
+
"semantic": "CNC machine",
|
|
76
|
+
"bbox": [100, 200, 500, 600]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Tech Stack
|
|
83
|
+
|
|
84
|
+
- React 18
|
|
85
|
+
- TypeScript
|
|
86
|
+
- Vite
|
|
87
|
+
- Slate.js (rich text)
|
|
88
|
+
- Express (upload server)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D=require("react/jsx-runtime"),i=require("react");function S(){return Math.random().toString(36).substring(2,9)}const V=new Map;function W(e){const s=[];for(const n of V.values()){const o=new RegExp(n.parsePattern.source,"g");let t;for(;(t=o.exec(e))!==null;){const a=n.parseMatch(t);a.start=t.index,a.end=t.index+t[0].length,s.push(a)}}return s.sort((n,o)=>n.start-o.start)}function _(e,s){const n=W(e);for(let o=n.length-1;o>=0;o--){const t=n[o];if(t.type==="mention"){const a=`[box_id - ${t.data.boxId}]`;e=e.substring(0,t.start)+a+e.substring(t.end)}else if(t.type==="variable"){const a=s[t.data.name];if(a)if(a.type==="string")e=e.substring(0,t.start)+a.value+e.substring(t.end);else{const r=`[CONDITION: if "${a.condition.if}" then ${a.condition.then} else ${a.condition.else}]`;e=e.substring(0,t.start)+r+e.substring(t.end)}}}return e}function q(e,s){const n=[];for(const o of e)if(o.type==="text"){const a=_(o.content,s);a.trim()&&n.push({type:"text",text:a})}else if(o.type==="image"){const t=o;if(n.push({type:"image",image:t.src}),t.width&&t.height){const a=t.width,r=t.height,p={image_id:t.id,image_size:[a,r],bbox_format:"[x, y, width, height]",bbox_units:"pixels",boxes:t.boxes.map(c=>({box_id:c.id,label:c.name||"",semantic:c.description||"",bbox_format:"[x, y, width, height]",bbox:[Math.round(c.x*a),Math.round(c.y*r),Math.round(c.width*a),Math.round(c.height*r)]}))};n.push({type:"text",text:JSON.stringify(p,null,2)})}}return{content:n}}const A={name:"json-v1",version:"1.0",serialize(e,s={},n){const o=e.map(a=>a.type==="text"?{type:"text",content:a.content}:{type:"image",src:a.src,width:a.width,height:a.height,boxes:a.boxes.map(p=>({id:p.id,name:p.name||"",x:p.x,y:p.y,width:p.width,height:p.height,description:p.description}))}),t=q(e,s);return{version:1,blocks:o,variables:Object.keys(s).length>0?s:void 0,metadata:n??{updatedAt:new Date().toISOString()},llm_value:t}},deserialize(e){return{blocks:e.blocks.map(n=>n.type==="text"?{id:S(),type:"text",content:n.content}:{id:S(),type:"image",src:n.src,width:n.width,height:n.height,boxes:n.boxes.map(t=>({id:t.id,name:t.name||"",x:t.x,y:t.y,width:t.width,height:t.height,description:t.description}))}),variables:e.variables||{}}},validate(e){const s=[];if(!e||typeof e!="object")return{valid:!1,errors:[{path:"",message:"Data must be an object"}]};const n=e;return n.version!==1&&s.push({path:"version",message:"Version must be 1"}),Array.isArray(n.blocks)?(n.blocks.forEach((o,t)=>{if(!o||typeof o!="object"){s.push({path:`blocks[${t}]`,message:"Block must be an object"});return}const a=o;a.type==="text"?typeof a.content!="string"&&s.push({path:`blocks[${t}].content`,message:"Content must be a string"}):a.type==="image"?(typeof a.src!="string"&&s.push({path:`blocks[${t}].src`,message:"Src must be a string"}),Array.isArray(a.boxes)||s.push({path:`blocks[${t}].boxes`,message:"Boxes must be an array"})):s.push({path:`blocks[${t}].type`,message:'Type must be "text" or "image"'})}),n.variables!==void 0&&(typeof n.variables!="object"||n.variables===null)&&s.push({path:"variables",message:"Variables must be an object"}),{valid:s.length===0,errors:s}):(s.push({path:"blocks",message:"Blocks must be an array"}),{valid:!1,errors:s})}},J=e=>e.startsWith("data:image/");function H({initialData:e,onChange:s}={}){const o=(()=>{if(e){const f=A.validate(e);if(f.valid)return A.deserialize(e);console.error("Invalid initialData:",f.errors)}return{blocks:[],variables:{}}})(),[t,a]=i.useState(o.blocks),[r,p]=i.useState(o.variables),c=i.useRef(!0),d=i.useRef(s);d.current=s,i.useEffect(()=>{if(c.current){c.current=!1;return}if(!t.some(u=>u.type==="image"&&J(u.src))&&d.current){const u=A.serialize(t,r);d.current(u)}},[t,r]);const m=i.useCallback(f=>{const u={id:S(),type:"text",content:""};a(l=>f!==void 0?[...l.slice(0,f),u,...l.slice(f)]:[...l,u])},[]),v=i.useCallback((f,u)=>{const l=S(),b={id:l,type:"image",src:f,boxes:[]};return a(k=>u!==void 0?[...k.slice(0,u),b,...k.slice(u)]:[...k,b]),l},[]),B=i.useCallback((f,u)=>{a(l=>l.map(b=>b.id===f?{...b,...u}:b))},[]),w=i.useCallback(f=>{a(u=>u.filter(l=>l.id!==f))},[]),x=i.useCallback((f,u)=>{p(l=>({...l,[f]:u}))},[]);return{blocks:t,variables:r,addTextBlock:m,addImageBlock:v,updateBlock:B,deleteBlock:w,addVariable:x}}const F=e=>e.startsWith("data:image/");function G({onUploadImage:e,onUpdateBlock:s}){const[n,o]=i.useState(new Map),t=i.useCallback((c,d)=>F(d)&&!n.has(c),[n]),a=i.useCallback((c,d)=>{!e||!F(d)||(o(m=>{const v=new Map(m);return v.delete(c),v}),e(d).then(m=>{const v=new Image;v.onload=()=>{s(c,{src:m})},v.onerror=()=>{o(B=>new Map(B).set(c,"Failed to load image"))},v.src=m}).catch(m=>{console.error("Failed to upload image:",m);const v=m instanceof Error?m.message:"Upload failed";o(B=>new Map(B).set(c,v))}))},[e,s]),r=i.useCallback((c,d)=>{F(d)&&a(c,d)},[a]),p=i.useCallback(c=>c.some(d=>d.type==="image"&&F(d.src)),[]);return{uploadErrors:n,isUploading:t,uploadImageInBackground:a,retryUpload:r,hasBase64Images:p}}function K({blocksCount:e,focusedBlockIndex:s,onAddImageBlock:n,onAddTextBlock:o,onSetFocusedBlockIndex:t}){const[a,r]=i.useState(!1),[p,c]=i.useState(null),d=i.useRef(0),m=i.useCallback(l=>{l.preventDefault(),d.current++,l.dataTransfer.types.includes("Files")&&r(!0)},[]),v=i.useCallback(l=>{l.preventDefault(),l.dataTransfer.dropEffect="copy"},[]),B=i.useCallback(l=>{l.preventDefault(),d.current--,d.current===0&&(r(!1),c(null))},[]),w=i.useCallback((l,b)=>{const k=new FileReader;k.onload=y=>{var M;const E=(M=y.target)==null?void 0:M.result;n(E,b),setTimeout(()=>{o(b+1),t(b+1)},50)},k.readAsDataURL(l)},[n,o,t]),x=i.useCallback(l=>{if(l.defaultPrevented)return;l.preventDefault(),d.current=0,r(!1),c(null);const k=Array.from(l.dataTransfer.files).find(y=>y.type.startsWith("image/"));k&&w(k,e)},[e,w]),f=i.useCallback((l,b)=>{l.preventDefault(),l.stopPropagation(),d.current=0,r(!1),c(null);const y=Array.from(l.dataTransfer.files).find(E=>E.type.startsWith("image/"));y&&w(y,b)},[w]),u=i.useCallback(l=>{const k=Array.from(l.clipboardData.items).find(y=>y.type.startsWith("image/"));if(k){l.preventDefault();const y=k.getAsFile();if(y){const E=s+1;w(y,E)}}},[s,w]);return{isDraggingFile:a,activeDropZone:p,setActiveDropZone:c,handleDragEnter:m,handleDragOver:v,handleDragLeave:B,handleEditorDrop:x,handleDropOnZone:f,handlePaste:u}}function Q({isFirst:e,visible:s,isActive:n,onDragEnter:o,onDragLeave:t,onDrop:a}){return D.jsx("div",{className:`drop-zone ${s?"drop-zone--visible":""} ${n?"drop-zone--active":""} ${e?"drop-zone--first":""}`,onDragEnter:o,onDragLeave:t,onDragOver:r=>r.preventDefault(),onDrop:a,children:D.jsx("div",{className:"drop-zone-line"})})}const L=i.memo(Q,(e,s)=>e.visible===s.visible&&e.isActive===s.isActive&&e.isFirst===s.isFirst),X=new Map;function Y(e){return X.get(e)}function ee({block:e,blockIndex:s,focusedBlockIndex:n,allBlocks:o,variables:t,onUpdate:a,onDelete:r,onVariableCreate:p,isUploading:c,getUploadError:d,onRetryUpload:m,onFocus:v}){const B=Y(e.type);if(!B)return null;const w=B.Component,x=i.useMemo(()=>({blockIndex:s,focusedBlockIndex:n,allBlocks:o,variables:t,onVariableCreate:p,isUploading:c,getUploadError:d,onRetryUpload:m}),[s,n,o,t,p,c,d,m]),f=i.useMemo(()=>B.getProps(e,x),[B,e,x]);return D.jsx("div",{className:"block-wrapper",draggable:!1,onFocus:v,onDragStart:u=>u.preventDefault(),children:D.jsx(w,{block:e,onUpdate:a,onDelete:r,...f})})}function te(e,s){if(e.block!==s.block)return!1;const n=e.blockIndex===e.focusedBlockIndex,o=s.blockIndex===s.focusedBlockIndex;if(n!==o||s.block.type==="text"&&(e.variables!==s.variables||e.allBlocks!==s.allBlocks))return!1;if(s.block.type==="image"){const t=e.isUploading(e.block.id,e.block.src),a=s.isUploading(s.block.id,s.block.src);if(t!==a)return!1;const r=e.getUploadError(e.block.id),p=s.getUploadError(s.block.id);if(r!==p)return!1}return!0}const se=i.memo(ee,te);function ae({initialData:e,onChange:s,onUploadImage:n}){const[o,t]=i.useState(0),a=i.useRef(null),{blocks:r,variables:p,addTextBlock:c,addImageBlock:d,updateBlock:m,deleteBlock:v,addVariable:B}=H({initialData:e,onChange:s}),{uploadErrors:w,isUploading:x,uploadImageInBackground:f,retryUpload:u}=G({onUploadImage:n,onUpdateBlock:m}),l=i.useCallback((g,h)=>{const C=d(g,h);f(C,g)},[d,f]),{isDraggingFile:b,activeDropZone:k,setActiveDropZone:y,handleDragEnter:E,handleDragOver:M,handleDragLeave:$,handleEditorDrop:N,handleDropOnZone:U,handlePaste:O}=K({blocksCount:r.length,focusedBlockIndex:o,onAddImageBlock:l,onAddTextBlock:c,onSetFocusedBlockIndex:t}),T=i.useCallback((g,h)=>{B(g,h)},[B]),z=i.useCallback(g=>{const h=r.find(C=>C.id===g);h&&h.type==="image"&&u(g,h.src)},[r,u]),P=i.useCallback(g=>w.get(g),[w]),Z=i.useMemo(()=>{const g=new Map;return r.forEach((h,C)=>{g.set(h.id,{onUpdate:j=>m(h.id,j),onDelete:()=>v(h.id),onFocus:()=>t(C)})}),g},[r,m,v]),R=i.useCallback(g=>{k===g&&y(null)},[k,y]),I=i.useMemo(()=>{const g=new Map;for(let h=0;h<=r.length;h++){const C=h;g.set(C,{onDragEnter:()=>y(C),onDragLeave:()=>R(C),onDrop:j=>U(j,C)})}return g},[r.length,y,R,U]);return r.length===0?D.jsxs("div",{className:"editor editor--empty",onDragEnter:E,onDragOver:M,onDragLeave:$,onDrop:g=>U(g,0),children:[D.jsx("div",{className:"editor-placeholder",onClick:()=>c(),children:D.jsx("span",{children:"Start typing or drag an image here..."})}),b&&D.jsxs("div",{className:"editor-drop-overlay",children:[D.jsx("div",{className:"drop-icon",children:"📷"}),D.jsx("p",{children:"Drop image to add"})]})]}):D.jsx("div",{ref:a,className:`editor ${b?"editor--dragging":""}`,onDragEnter:E,onDragOver:M,onDragLeave:$,onDrop:N,onPaste:O,children:D.jsxs("div",{className:"editor-document",children:[r.map((g,h)=>{const C=Z.get(g.id),j=I.get(h);return!C||!j?null:D.jsxs("div",{className:"block-with-dropzone",children:[D.jsx(L,{index:h,isFirst:h===0,visible:b,isActive:k===h,onDragEnter:j.onDragEnter,onDragLeave:j.onDragLeave,onDrop:j.onDrop}),D.jsx(se,{block:g,blockIndex:h,focusedBlockIndex:o,allBlocks:r,variables:p,onUpdate:C.onUpdate,onDelete:C.onDelete,onVariableCreate:T,isUploading:x,getUploadError:P,onRetryUpload:z,onFocus:C.onFocus})]},g.id)}),I.get(r.length)&&D.jsx(L,{index:r.length,visible:b,isActive:k===r.length,onDragEnter:I.get(r.length).onDragEnter,onDragLeave:I.get(r.length).onDragLeave,onDrop:I.get(r.length).onDrop})]})})}exports.Editor=ae;exports.JsonV1Adapter=A;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
export declare type Block = TextBlock | ImageBlock;
|
|
4
|
+
|
|
5
|
+
declare type BlockV1 = TextBlockV1 | ImageBlockV1;
|
|
6
|
+
|
|
7
|
+
declare interface BooleanVariable {
|
|
8
|
+
type: 'boolean';
|
|
9
|
+
condition: {
|
|
10
|
+
if: string;
|
|
11
|
+
then: boolean;
|
|
12
|
+
else: boolean;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export declare interface BoundingBox {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare interface BoxV1 {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
description: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare interface DocumentMetadata {
|
|
37
|
+
createdAt?: string;
|
|
38
|
+
updatedAt?: string;
|
|
39
|
+
title?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export declare interface DocumentV1 {
|
|
43
|
+
version: 1;
|
|
44
|
+
blocks: BlockV1[];
|
|
45
|
+
variables?: VariablesMap;
|
|
46
|
+
metadata?: DocumentMetadata;
|
|
47
|
+
llm_value?: LLMValue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export declare function Editor({ initialData, onChange, onUploadImage }: EditorProps_2): JSX_2.Element;
|
|
51
|
+
|
|
52
|
+
export declare interface EditorProps {
|
|
53
|
+
initialData?: DocumentV1 | null;
|
|
54
|
+
onChange?: (data: DocumentV1) => void;
|
|
55
|
+
onUploadImage?: (base64: string) => Promise<string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
declare interface EditorProps_2 {
|
|
59
|
+
initialData?: DocumentV1 | null;
|
|
60
|
+
onChange?: (data: DocumentV1) => void;
|
|
61
|
+
onUploadImage?: (base64: string) => Promise<string>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export declare interface ImageBlock {
|
|
65
|
+
id: string;
|
|
66
|
+
type: 'image';
|
|
67
|
+
src: string;
|
|
68
|
+
width?: number;
|
|
69
|
+
height?: number;
|
|
70
|
+
boxes: BoundingBox[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare interface ImageBlockV1 {
|
|
74
|
+
type: 'image';
|
|
75
|
+
src: string;
|
|
76
|
+
width?: number;
|
|
77
|
+
height?: number;
|
|
78
|
+
boxes: BoxV1[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* JSON V1 Serializer Adapter
|
|
83
|
+
*
|
|
84
|
+
* Converts between internal Block[] format and external DocumentV1 format.
|
|
85
|
+
*
|
|
86
|
+
* Key differences:
|
|
87
|
+
* - External format has version and metadata
|
|
88
|
+
* - External TextBlock/ImageBlock don't have id (internal only)
|
|
89
|
+
* - External BoxV1 keeps id (for referencing in text)
|
|
90
|
+
*/
|
|
91
|
+
export declare const JsonV1Adapter: SerializerAdapter<DocumentV1>;
|
|
92
|
+
|
|
93
|
+
export declare interface LLMBox {
|
|
94
|
+
box_id: string;
|
|
95
|
+
label: string;
|
|
96
|
+
semantic: string;
|
|
97
|
+
bbox: [number, number, number, number];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export declare interface LLMBoxMetadata {
|
|
101
|
+
image_id: string;
|
|
102
|
+
image_size: [number, number];
|
|
103
|
+
bbox_format: '[x, y, width, height]';
|
|
104
|
+
bbox_units: 'pixels';
|
|
105
|
+
boxes: LLMBox[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export declare type LLMContentItem = LLMTextContent | LLMImageContent;
|
|
109
|
+
|
|
110
|
+
declare interface LLMImageContent {
|
|
111
|
+
type: 'image';
|
|
112
|
+
image: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
declare interface LLMTextContent {
|
|
116
|
+
type: 'text';
|
|
117
|
+
text: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export declare interface LLMValue {
|
|
121
|
+
content: LLMContentItem[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
declare interface SerializerAdapter<T = unknown> {
|
|
125
|
+
/** Adapter name for identification */
|
|
126
|
+
name: string;
|
|
127
|
+
/** Version of this adapter */
|
|
128
|
+
version: string;
|
|
129
|
+
/** Convert internal blocks to external format */
|
|
130
|
+
serialize(blocks: Block[], variables?: VariablesMap, metadata?: DocumentMetadata): T;
|
|
131
|
+
/** Convert external format to internal blocks */
|
|
132
|
+
deserialize(data: T): {
|
|
133
|
+
blocks: Block[];
|
|
134
|
+
variables: VariablesMap;
|
|
135
|
+
};
|
|
136
|
+
/** Validate external data before deserializing */
|
|
137
|
+
validate(data: unknown): ValidationResult;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
declare interface StringVariable {
|
|
141
|
+
type: 'string';
|
|
142
|
+
value: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export declare interface TextBlock {
|
|
146
|
+
id: string;
|
|
147
|
+
type: 'text';
|
|
148
|
+
content: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
declare interface TextBlockV1 {
|
|
152
|
+
type: 'text';
|
|
153
|
+
content: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
declare interface ValidationError {
|
|
157
|
+
path: string;
|
|
158
|
+
message: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
declare interface ValidationResult {
|
|
162
|
+
valid: boolean;
|
|
163
|
+
errors: ValidationError[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export declare type Variable = StringVariable | BooleanVariable;
|
|
167
|
+
|
|
168
|
+
export declare type VariablesMap = Record<string, Variable>;
|
|
169
|
+
|
|
170
|
+
export { }
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
declare module 'slate' {
|
|
174
|
+
interface CustomTypes {
|
|
175
|
+
Editor: BaseEditor & ReactEditor & HistoryEditor;
|
|
176
|
+
Element: CustomElement;
|
|
177
|
+
Text: CustomText;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import { jsx as E, jsxs as x } from "react/jsx-runtime";
|
|
2
|
+
import { useState as A, useRef as N, useEffect as q, useCallback as h, memo as Z, useMemo as z } from "react";
|
|
3
|
+
function C() {
|
|
4
|
+
return Math.random().toString(36).substring(2, 9);
|
|
5
|
+
}
|
|
6
|
+
const G = /* @__PURE__ */ new Map();
|
|
7
|
+
function K(e) {
|
|
8
|
+
const n = [];
|
|
9
|
+
for (const r of G.values()) {
|
|
10
|
+
const o = new RegExp(r.parsePattern.source, "g");
|
|
11
|
+
let t;
|
|
12
|
+
for (; (t = o.exec(e)) !== null; ) {
|
|
13
|
+
const s = r.parseMatch(t);
|
|
14
|
+
s.start = t.index, s.end = t.index + t[0].length, n.push(s);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return n.sort((r, o) => r.start - o.start);
|
|
18
|
+
}
|
|
19
|
+
function Q(e, n) {
|
|
20
|
+
const r = K(e);
|
|
21
|
+
for (let o = r.length - 1; o >= 0; o--) {
|
|
22
|
+
const t = r[o];
|
|
23
|
+
if (t.type === "mention") {
|
|
24
|
+
const s = `[box_id - ${t.data.boxId}]`;
|
|
25
|
+
e = e.substring(0, t.start) + s + e.substring(t.end);
|
|
26
|
+
} else if (t.type === "variable") {
|
|
27
|
+
const s = n[t.data.name];
|
|
28
|
+
if (s)
|
|
29
|
+
if (s.type === "string")
|
|
30
|
+
e = e.substring(0, t.start) + s.value + e.substring(t.end);
|
|
31
|
+
else {
|
|
32
|
+
const a = `[CONDITION: if "${s.condition.if}" then ${s.condition.then} else ${s.condition.else}]`;
|
|
33
|
+
e = e.substring(0, t.start) + a + e.substring(t.end);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return e;
|
|
38
|
+
}
|
|
39
|
+
function X(e, n) {
|
|
40
|
+
const r = [];
|
|
41
|
+
for (const o of e)
|
|
42
|
+
if (o.type === "text") {
|
|
43
|
+
const s = Q(o.content, n);
|
|
44
|
+
s.trim() && r.push({
|
|
45
|
+
type: "text",
|
|
46
|
+
text: s
|
|
47
|
+
});
|
|
48
|
+
} else if (o.type === "image") {
|
|
49
|
+
const t = o;
|
|
50
|
+
if (r.push({
|
|
51
|
+
type: "image",
|
|
52
|
+
image: t.src
|
|
53
|
+
}), t.width && t.height) {
|
|
54
|
+
const s = t.width, a = t.height, u = {
|
|
55
|
+
image_id: t.id,
|
|
56
|
+
image_size: [s, a],
|
|
57
|
+
bbox_format: "[x, y, width, height]",
|
|
58
|
+
bbox_units: "pixels",
|
|
59
|
+
boxes: t.boxes.map((c) => ({
|
|
60
|
+
box_id: c.id,
|
|
61
|
+
label: c.name || "",
|
|
62
|
+
semantic: c.description || "",
|
|
63
|
+
bbox_format: "[x, y, width, height]",
|
|
64
|
+
// Convert normalized (0-1) to pixels [x, y, width, height]
|
|
65
|
+
bbox: [
|
|
66
|
+
Math.round(c.x * s),
|
|
67
|
+
Math.round(c.y * a),
|
|
68
|
+
Math.round(c.width * s),
|
|
69
|
+
Math.round(c.height * a)
|
|
70
|
+
]
|
|
71
|
+
}))
|
|
72
|
+
};
|
|
73
|
+
r.push({
|
|
74
|
+
type: "text",
|
|
75
|
+
text: JSON.stringify(u, null, 2)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { content: r };
|
|
80
|
+
}
|
|
81
|
+
const T = {
|
|
82
|
+
name: "json-v1",
|
|
83
|
+
version: "1.0",
|
|
84
|
+
serialize(e, n = {}, r) {
|
|
85
|
+
const o = e.map((s) => s.type === "text" ? {
|
|
86
|
+
type: "text",
|
|
87
|
+
content: s.content
|
|
88
|
+
} : {
|
|
89
|
+
type: "image",
|
|
90
|
+
src: s.src,
|
|
91
|
+
width: s.width,
|
|
92
|
+
height: s.height,
|
|
93
|
+
boxes: s.boxes.map((u) => ({
|
|
94
|
+
id: u.id,
|
|
95
|
+
name: u.name || "",
|
|
96
|
+
x: u.x,
|
|
97
|
+
y: u.y,
|
|
98
|
+
width: u.width,
|
|
99
|
+
height: u.height,
|
|
100
|
+
description: u.description
|
|
101
|
+
}))
|
|
102
|
+
}), t = X(e, n);
|
|
103
|
+
return {
|
|
104
|
+
version: 1,
|
|
105
|
+
blocks: o,
|
|
106
|
+
variables: Object.keys(n).length > 0 ? n : void 0,
|
|
107
|
+
metadata: r ?? {
|
|
108
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
109
|
+
},
|
|
110
|
+
llm_value: t
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
deserialize(e) {
|
|
114
|
+
return {
|
|
115
|
+
blocks: e.blocks.map((r) => r.type === "text" ? {
|
|
116
|
+
id: C(),
|
|
117
|
+
type: "text",
|
|
118
|
+
content: r.content
|
|
119
|
+
} : {
|
|
120
|
+
id: C(),
|
|
121
|
+
type: "image",
|
|
122
|
+
src: r.src,
|
|
123
|
+
width: r.width,
|
|
124
|
+
height: r.height,
|
|
125
|
+
boxes: r.boxes.map((t) => ({
|
|
126
|
+
id: t.id,
|
|
127
|
+
name: t.name || "",
|
|
128
|
+
x: t.x,
|
|
129
|
+
y: t.y,
|
|
130
|
+
width: t.width,
|
|
131
|
+
height: t.height,
|
|
132
|
+
description: t.description
|
|
133
|
+
}))
|
|
134
|
+
}),
|
|
135
|
+
variables: e.variables || {}
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
validate(e) {
|
|
139
|
+
const n = [];
|
|
140
|
+
if (!e || typeof e != "object")
|
|
141
|
+
return { valid: !1, errors: [{ path: "", message: "Data must be an object" }] };
|
|
142
|
+
const r = e;
|
|
143
|
+
return r.version !== 1 && n.push({ path: "version", message: "Version must be 1" }), Array.isArray(r.blocks) ? (r.blocks.forEach((o, t) => {
|
|
144
|
+
if (!o || typeof o != "object") {
|
|
145
|
+
n.push({ path: `blocks[${t}]`, message: "Block must be an object" });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const s = o;
|
|
149
|
+
s.type === "text" ? typeof s.content != "string" && n.push({ path: `blocks[${t}].content`, message: "Content must be a string" }) : s.type === "image" ? (typeof s.src != "string" && n.push({ path: `blocks[${t}].src`, message: "Src must be a string" }), Array.isArray(s.boxes) || n.push({ path: `blocks[${t}].boxes`, message: "Boxes must be an array" })) : n.push({ path: `blocks[${t}].type`, message: 'Type must be "text" or "image"' });
|
|
150
|
+
}), r.variables !== void 0 && (typeof r.variables != "object" || r.variables === null) && n.push({ path: "variables", message: "Variables must be an object" }), { valid: n.length === 0, errors: n }) : (n.push({ path: "blocks", message: "Blocks must be an array" }), { valid: !1, errors: n });
|
|
151
|
+
}
|
|
152
|
+
}, Y = (e) => e.startsWith("data:image/");
|
|
153
|
+
function ee({
|
|
154
|
+
initialData: e,
|
|
155
|
+
onChange: n
|
|
156
|
+
} = {}) {
|
|
157
|
+
const o = (() => {
|
|
158
|
+
if (e) {
|
|
159
|
+
const p = T.validate(e);
|
|
160
|
+
if (p.valid)
|
|
161
|
+
return T.deserialize(e);
|
|
162
|
+
console.error("Invalid initialData:", p.errors);
|
|
163
|
+
}
|
|
164
|
+
return { blocks: [], variables: {} };
|
|
165
|
+
})(), [t, s] = A(o.blocks), [a, u] = A(o.variables), c = N(!0), l = N(n);
|
|
166
|
+
l.current = n, q(() => {
|
|
167
|
+
if (c.current) {
|
|
168
|
+
c.current = !1;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!t.some(
|
|
172
|
+
(d) => d.type === "image" && Y(d.src)
|
|
173
|
+
) && l.current) {
|
|
174
|
+
const d = T.serialize(t, a);
|
|
175
|
+
l.current(d);
|
|
176
|
+
}
|
|
177
|
+
}, [t, a]);
|
|
178
|
+
const m = h((p) => {
|
|
179
|
+
const d = {
|
|
180
|
+
id: C(),
|
|
181
|
+
type: "text",
|
|
182
|
+
content: ""
|
|
183
|
+
};
|
|
184
|
+
s(
|
|
185
|
+
(i) => p !== void 0 ? [...i.slice(0, p), d, ...i.slice(p)] : [...i, d]
|
|
186
|
+
);
|
|
187
|
+
}, []), D = h((p, d) => {
|
|
188
|
+
const i = C(), v = {
|
|
189
|
+
id: i,
|
|
190
|
+
type: "image",
|
|
191
|
+
src: p,
|
|
192
|
+
boxes: []
|
|
193
|
+
};
|
|
194
|
+
return s(
|
|
195
|
+
(b) => d !== void 0 ? [...b.slice(0, d), v, ...b.slice(d)] : [...b, v]
|
|
196
|
+
), i;
|
|
197
|
+
}, []), k = h((p, d) => {
|
|
198
|
+
s(
|
|
199
|
+
(i) => i.map(
|
|
200
|
+
(v) => v.id === p ? { ...v, ...d } : v
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
}, []), w = h((p) => {
|
|
204
|
+
s((d) => d.filter((i) => i.id !== p));
|
|
205
|
+
}, []), I = h((p, d) => {
|
|
206
|
+
u((i) => ({
|
|
207
|
+
...i,
|
|
208
|
+
[p]: d
|
|
209
|
+
}));
|
|
210
|
+
}, []);
|
|
211
|
+
return {
|
|
212
|
+
blocks: t,
|
|
213
|
+
variables: a,
|
|
214
|
+
addTextBlock: m,
|
|
215
|
+
addImageBlock: D,
|
|
216
|
+
updateBlock: k,
|
|
217
|
+
deleteBlock: w,
|
|
218
|
+
addVariable: I
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const L = (e) => e.startsWith("data:image/");
|
|
222
|
+
function te({
|
|
223
|
+
onUploadImage: e,
|
|
224
|
+
onUpdateBlock: n
|
|
225
|
+
}) {
|
|
226
|
+
const [r, o] = A(/* @__PURE__ */ new Map()), t = h((c, l) => L(l) && !r.has(c), [r]), s = h((c, l) => {
|
|
227
|
+
!e || !L(l) || (o((m) => {
|
|
228
|
+
const D = new Map(m);
|
|
229
|
+
return D.delete(c), D;
|
|
230
|
+
}), e(l).then((m) => {
|
|
231
|
+
const D = new Image();
|
|
232
|
+
D.onload = () => {
|
|
233
|
+
n(c, { src: m });
|
|
234
|
+
}, D.onerror = () => {
|
|
235
|
+
o((k) => new Map(k).set(c, "Failed to load image"));
|
|
236
|
+
}, D.src = m;
|
|
237
|
+
}).catch((m) => {
|
|
238
|
+
console.error("Failed to upload image:", m);
|
|
239
|
+
const D = m instanceof Error ? m.message : "Upload failed";
|
|
240
|
+
o((k) => new Map(k).set(c, D));
|
|
241
|
+
}));
|
|
242
|
+
}, [e, n]), a = h((c, l) => {
|
|
243
|
+
L(l) && s(c, l);
|
|
244
|
+
}, [s]), u = h((c) => c.some(
|
|
245
|
+
(l) => l.type === "image" && L(l.src)
|
|
246
|
+
), []);
|
|
247
|
+
return {
|
|
248
|
+
uploadErrors: r,
|
|
249
|
+
isUploading: t,
|
|
250
|
+
uploadImageInBackground: s,
|
|
251
|
+
retryUpload: a,
|
|
252
|
+
hasBase64Images: u
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function ne({
|
|
256
|
+
blocksCount: e,
|
|
257
|
+
focusedBlockIndex: n,
|
|
258
|
+
onAddImageBlock: r,
|
|
259
|
+
onAddTextBlock: o,
|
|
260
|
+
onSetFocusedBlockIndex: t
|
|
261
|
+
}) {
|
|
262
|
+
const [s, a] = A(!1), [u, c] = A(null), l = N(0), m = h((i) => {
|
|
263
|
+
i.preventDefault(), l.current++, i.dataTransfer.types.includes("Files") && a(!0);
|
|
264
|
+
}, []), D = h((i) => {
|
|
265
|
+
i.preventDefault(), i.dataTransfer.dropEffect = "copy";
|
|
266
|
+
}, []), k = h((i) => {
|
|
267
|
+
i.preventDefault(), l.current--, l.current === 0 && (a(!1), c(null));
|
|
268
|
+
}, []), w = h((i, v) => {
|
|
269
|
+
const b = new FileReader();
|
|
270
|
+
b.onload = (y) => {
|
|
271
|
+
var U;
|
|
272
|
+
const F = (U = y.target) == null ? void 0 : U.result;
|
|
273
|
+
r(F, v), setTimeout(() => {
|
|
274
|
+
o(v + 1), t(v + 1);
|
|
275
|
+
}, 50);
|
|
276
|
+
}, b.readAsDataURL(i);
|
|
277
|
+
}, [r, o, t]), I = h((i) => {
|
|
278
|
+
if (i.defaultPrevented) return;
|
|
279
|
+
i.preventDefault(), l.current = 0, a(!1), c(null);
|
|
280
|
+
const b = Array.from(i.dataTransfer.files).find((y) => y.type.startsWith("image/"));
|
|
281
|
+
b && w(b, e);
|
|
282
|
+
}, [e, w]), p = h((i, v) => {
|
|
283
|
+
i.preventDefault(), i.stopPropagation(), l.current = 0, a(!1), c(null);
|
|
284
|
+
const y = Array.from(i.dataTransfer.files).find((F) => F.type.startsWith("image/"));
|
|
285
|
+
y && w(y, v);
|
|
286
|
+
}, [w]), d = h((i) => {
|
|
287
|
+
const b = Array.from(i.clipboardData.items).find((y) => y.type.startsWith("image/"));
|
|
288
|
+
if (b) {
|
|
289
|
+
i.preventDefault();
|
|
290
|
+
const y = b.getAsFile();
|
|
291
|
+
if (y) {
|
|
292
|
+
const F = n + 1;
|
|
293
|
+
w(y, F);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}, [n, w]);
|
|
297
|
+
return {
|
|
298
|
+
isDraggingFile: s,
|
|
299
|
+
activeDropZone: u,
|
|
300
|
+
setActiveDropZone: c,
|
|
301
|
+
handleDragEnter: m,
|
|
302
|
+
handleDragOver: D,
|
|
303
|
+
handleDragLeave: k,
|
|
304
|
+
handleEditorDrop: I,
|
|
305
|
+
handleDropOnZone: p,
|
|
306
|
+
handlePaste: d
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function se({
|
|
310
|
+
isFirst: e,
|
|
311
|
+
visible: n,
|
|
312
|
+
isActive: r,
|
|
313
|
+
onDragEnter: o,
|
|
314
|
+
onDragLeave: t,
|
|
315
|
+
onDrop: s
|
|
316
|
+
}) {
|
|
317
|
+
return /* @__PURE__ */ E(
|
|
318
|
+
"div",
|
|
319
|
+
{
|
|
320
|
+
className: `drop-zone ${n ? "drop-zone--visible" : ""} ${r ? "drop-zone--active" : ""} ${e ? "drop-zone--first" : ""}`,
|
|
321
|
+
onDragEnter: o,
|
|
322
|
+
onDragLeave: t,
|
|
323
|
+
onDragOver: (a) => a.preventDefault(),
|
|
324
|
+
onDrop: s,
|
|
325
|
+
children: /* @__PURE__ */ E("div", { className: "drop-zone-line" })
|
|
326
|
+
}
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const S = Z(se, (e, n) => e.visible === n.visible && e.isActive === n.isActive && e.isFirst === n.isFirst), re = /* @__PURE__ */ new Map();
|
|
330
|
+
function ae(e) {
|
|
331
|
+
return re.get(e);
|
|
332
|
+
}
|
|
333
|
+
function oe({
|
|
334
|
+
block: e,
|
|
335
|
+
blockIndex: n,
|
|
336
|
+
focusedBlockIndex: r,
|
|
337
|
+
allBlocks: o,
|
|
338
|
+
variables: t,
|
|
339
|
+
onUpdate: s,
|
|
340
|
+
onDelete: a,
|
|
341
|
+
onVariableCreate: u,
|
|
342
|
+
isUploading: c,
|
|
343
|
+
getUploadError: l,
|
|
344
|
+
onRetryUpload: m,
|
|
345
|
+
onFocus: D
|
|
346
|
+
}) {
|
|
347
|
+
const k = ae(e.type);
|
|
348
|
+
if (!k) return null;
|
|
349
|
+
const w = k.Component, I = z(() => ({
|
|
350
|
+
blockIndex: n,
|
|
351
|
+
focusedBlockIndex: r,
|
|
352
|
+
allBlocks: o,
|
|
353
|
+
variables: t,
|
|
354
|
+
onVariableCreate: u,
|
|
355
|
+
isUploading: c,
|
|
356
|
+
getUploadError: l,
|
|
357
|
+
onRetryUpload: m
|
|
358
|
+
}), [n, r, o, t, u, c, l, m]), p = z(
|
|
359
|
+
() => k.getProps(e, I),
|
|
360
|
+
[k, e, I]
|
|
361
|
+
);
|
|
362
|
+
return /* @__PURE__ */ E(
|
|
363
|
+
"div",
|
|
364
|
+
{
|
|
365
|
+
className: "block-wrapper",
|
|
366
|
+
draggable: !1,
|
|
367
|
+
onFocus: D,
|
|
368
|
+
onDragStart: (d) => d.preventDefault(),
|
|
369
|
+
children: /* @__PURE__ */ E(
|
|
370
|
+
w,
|
|
371
|
+
{
|
|
372
|
+
block: e,
|
|
373
|
+
onUpdate: s,
|
|
374
|
+
onDelete: a,
|
|
375
|
+
...p
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
function ie(e, n) {
|
|
382
|
+
if (e.block !== n.block) return !1;
|
|
383
|
+
const r = e.blockIndex === e.focusedBlockIndex, o = n.blockIndex === n.focusedBlockIndex;
|
|
384
|
+
if (r !== o || n.block.type === "text" && (e.variables !== n.variables || e.allBlocks !== n.allBlocks))
|
|
385
|
+
return !1;
|
|
386
|
+
if (n.block.type === "image") {
|
|
387
|
+
const t = e.isUploading(e.block.id, e.block.src), s = n.isUploading(n.block.id, n.block.src);
|
|
388
|
+
if (t !== s) return !1;
|
|
389
|
+
const a = e.getUploadError(e.block.id), u = n.getUploadError(n.block.id);
|
|
390
|
+
if (a !== u) return !1;
|
|
391
|
+
}
|
|
392
|
+
return !0;
|
|
393
|
+
}
|
|
394
|
+
const ce = Z(oe, ie);
|
|
395
|
+
function ge({
|
|
396
|
+
initialData: e,
|
|
397
|
+
onChange: n,
|
|
398
|
+
onUploadImage: r
|
|
399
|
+
}) {
|
|
400
|
+
const [o, t] = A(0), s = N(null), {
|
|
401
|
+
blocks: a,
|
|
402
|
+
variables: u,
|
|
403
|
+
addTextBlock: c,
|
|
404
|
+
addImageBlock: l,
|
|
405
|
+
updateBlock: m,
|
|
406
|
+
deleteBlock: D,
|
|
407
|
+
addVariable: k
|
|
408
|
+
} = ee({
|
|
409
|
+
initialData: e,
|
|
410
|
+
onChange: n
|
|
411
|
+
}), {
|
|
412
|
+
uploadErrors: w,
|
|
413
|
+
isUploading: I,
|
|
414
|
+
uploadImageInBackground: p,
|
|
415
|
+
retryUpload: d
|
|
416
|
+
} = te({
|
|
417
|
+
onUploadImage: r,
|
|
418
|
+
onUpdateBlock: m
|
|
419
|
+
}), i = h((g, f) => {
|
|
420
|
+
const B = l(g, f);
|
|
421
|
+
p(B, g);
|
|
422
|
+
}, [l, p]), {
|
|
423
|
+
isDraggingFile: v,
|
|
424
|
+
activeDropZone: b,
|
|
425
|
+
setActiveDropZone: y,
|
|
426
|
+
handleDragEnter: F,
|
|
427
|
+
handleDragOver: U,
|
|
428
|
+
handleDragLeave: j,
|
|
429
|
+
handleEditorDrop: R,
|
|
430
|
+
handleDropOnZone: O,
|
|
431
|
+
handlePaste: V
|
|
432
|
+
} = ne({
|
|
433
|
+
blocksCount: a.length,
|
|
434
|
+
focusedBlockIndex: o,
|
|
435
|
+
onAddImageBlock: i,
|
|
436
|
+
onAddTextBlock: c,
|
|
437
|
+
onSetFocusedBlockIndex: t
|
|
438
|
+
}), W = h((g, f) => {
|
|
439
|
+
k(g, f);
|
|
440
|
+
}, [k]), _ = h((g) => {
|
|
441
|
+
const f = a.find((B) => B.id === g);
|
|
442
|
+
f && f.type === "image" && d(g, f.src);
|
|
443
|
+
}, [a, d]), H = h((g) => w.get(g), [w]), J = z(() => {
|
|
444
|
+
const g = /* @__PURE__ */ new Map();
|
|
445
|
+
return a.forEach((f, B) => {
|
|
446
|
+
g.set(f.id, {
|
|
447
|
+
onUpdate: (M) => m(f.id, M),
|
|
448
|
+
onDelete: () => D(f.id),
|
|
449
|
+
onFocus: () => t(B)
|
|
450
|
+
});
|
|
451
|
+
}), g;
|
|
452
|
+
}, [a, m, D]), P = h((g) => {
|
|
453
|
+
b === g && y(null);
|
|
454
|
+
}, [b, y]), $ = z(() => {
|
|
455
|
+
const g = /* @__PURE__ */ new Map();
|
|
456
|
+
for (let f = 0; f <= a.length; f++) {
|
|
457
|
+
const B = f;
|
|
458
|
+
g.set(B, {
|
|
459
|
+
onDragEnter: () => y(B),
|
|
460
|
+
onDragLeave: () => P(B),
|
|
461
|
+
onDrop: (M) => O(M, B)
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return g;
|
|
465
|
+
}, [a.length, y, P, O]);
|
|
466
|
+
return a.length === 0 ? /* @__PURE__ */ x(
|
|
467
|
+
"div",
|
|
468
|
+
{
|
|
469
|
+
className: "editor editor--empty",
|
|
470
|
+
onDragEnter: F,
|
|
471
|
+
onDragOver: U,
|
|
472
|
+
onDragLeave: j,
|
|
473
|
+
onDrop: (g) => O(g, 0),
|
|
474
|
+
children: [
|
|
475
|
+
/* @__PURE__ */ E("div", { className: "editor-placeholder", onClick: () => c(), children: /* @__PURE__ */ E("span", { children: "Start typing or drag an image here..." }) }),
|
|
476
|
+
v && /* @__PURE__ */ x("div", { className: "editor-drop-overlay", children: [
|
|
477
|
+
/* @__PURE__ */ E("div", { className: "drop-icon", children: "📷" }),
|
|
478
|
+
/* @__PURE__ */ E("p", { children: "Drop image to add" })
|
|
479
|
+
] })
|
|
480
|
+
]
|
|
481
|
+
}
|
|
482
|
+
) : /* @__PURE__ */ E(
|
|
483
|
+
"div",
|
|
484
|
+
{
|
|
485
|
+
ref: s,
|
|
486
|
+
className: `editor ${v ? "editor--dragging" : ""}`,
|
|
487
|
+
onDragEnter: F,
|
|
488
|
+
onDragOver: U,
|
|
489
|
+
onDragLeave: j,
|
|
490
|
+
onDrop: R,
|
|
491
|
+
onPaste: V,
|
|
492
|
+
children: /* @__PURE__ */ x("div", { className: "editor-document", children: [
|
|
493
|
+
a.map((g, f) => {
|
|
494
|
+
const B = J.get(g.id), M = $.get(f);
|
|
495
|
+
return !B || !M ? null : /* @__PURE__ */ x("div", { className: "block-with-dropzone", children: [
|
|
496
|
+
/* @__PURE__ */ E(
|
|
497
|
+
S,
|
|
498
|
+
{
|
|
499
|
+
index: f,
|
|
500
|
+
isFirst: f === 0,
|
|
501
|
+
visible: v,
|
|
502
|
+
isActive: b === f,
|
|
503
|
+
onDragEnter: M.onDragEnter,
|
|
504
|
+
onDragLeave: M.onDragLeave,
|
|
505
|
+
onDrop: M.onDrop
|
|
506
|
+
}
|
|
507
|
+
),
|
|
508
|
+
/* @__PURE__ */ E(
|
|
509
|
+
ce,
|
|
510
|
+
{
|
|
511
|
+
block: g,
|
|
512
|
+
blockIndex: f,
|
|
513
|
+
focusedBlockIndex: o,
|
|
514
|
+
allBlocks: a,
|
|
515
|
+
variables: u,
|
|
516
|
+
onUpdate: B.onUpdate,
|
|
517
|
+
onDelete: B.onDelete,
|
|
518
|
+
onVariableCreate: W,
|
|
519
|
+
isUploading: I,
|
|
520
|
+
getUploadError: H,
|
|
521
|
+
onRetryUpload: _,
|
|
522
|
+
onFocus: B.onFocus
|
|
523
|
+
}
|
|
524
|
+
)
|
|
525
|
+
] }, g.id);
|
|
526
|
+
}),
|
|
527
|
+
$.get(a.length) && /* @__PURE__ */ E(
|
|
528
|
+
S,
|
|
529
|
+
{
|
|
530
|
+
index: a.length,
|
|
531
|
+
visible: v,
|
|
532
|
+
isActive: b === a.length,
|
|
533
|
+
onDragEnter: $.get(a.length).onDragEnter,
|
|
534
|
+
onDragLeave: $.get(a.length).onDragLeave,
|
|
535
|
+
onDrop: $.get(a.length).onDrop
|
|
536
|
+
}
|
|
537
|
+
)
|
|
538
|
+
] })
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
export {
|
|
543
|
+
ge as Editor,
|
|
544
|
+
T as JsonV1Adapter
|
|
545
|
+
};
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.editor{background:#fff;border:1px solid #e0e0e0;border-radius:8px;min-height:500px;position:relative;padding:40px 60px;box-shadow:0 1px 3px #00000014}.editor--dragging{border-color:#2196f3;background:linear-gradient(to bottom,#e3f2fd,#fff 100px)}.editor--empty{display:flex;align-items:flex-start;justify-content:flex-start}.editor-placeholder{color:#9e9e9e;font-size:16px;cursor:text;padding:4px 0;width:100%}.editor-placeholder:hover{color:#757575}.editor-document{display:flex;flex-direction:column;padding-bottom:48px;position:relative}.block-wrapper{position:relative;-webkit-user-drag:none;user-drag:none}.block-with-dropzone{position:relative}.drop-zone{position:absolute;left:0;right:0;top:-12px;height:24px;z-index:50;display:flex;align-items:center;justify-content:center;cursor:copy;opacity:0;pointer-events:none}.drop-zone--visible{opacity:1;pointer-events:auto}.drop-zone--first{top:0}.drop-zone-line{width:100%;height:4px;background:#90caf9;border-radius:2px;pointer-events:none}.drop-zone--active .drop-zone-line{background:#2196f3}.editor-document>.drop-zone{position:absolute;bottom:8px;top:auto;height:32px}.editor-drop-overlay{position:absolute;top:0;right:0;bottom:0;left:0;background:#2196f3f2;display:flex;flex-direction:column;align-items:center;justify-content:center;border-radius:8px;z-index:100;pointer-events:none}.drop-icon{font-size:64px;margin-bottom:16px}.editor-drop-overlay p{font-size:18px;font-weight:500;color:#fff}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "text-img-editor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./styles.css": "./dist/style.css"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": [
|
|
20
|
+
"**/*.css"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"dev": "vite",
|
|
24
|
+
"build": "tsc && vite build",
|
|
25
|
+
"build:lib": "vite build",
|
|
26
|
+
"preview": "vite preview",
|
|
27
|
+
"prepublishOnly": "npm run build:lib"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"react": "^18.0.0",
|
|
31
|
+
"react-dom": "^18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"slate": "^0.120.0",
|
|
35
|
+
"slate-history": "^0.113.1",
|
|
36
|
+
"slate-react": "^0.120.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"@types/react": "^18.2.0",
|
|
41
|
+
"@types/react-dom": "^18.2.0",
|
|
42
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
43
|
+
"react": "^18.2.0",
|
|
44
|
+
"react-dom": "^18.2.0",
|
|
45
|
+
"typescript": "^5.3.0",
|
|
46
|
+
"vite": "^5.0.0",
|
|
47
|
+
"vite-plugin-dts": "^3.9.0"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/yourorg/text-img-editor"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"react",
|
|
55
|
+
"editor",
|
|
56
|
+
"llm",
|
|
57
|
+
"bounding-box",
|
|
58
|
+
"image-annotation"
|
|
59
|
+
],
|
|
60
|
+
"license": "MIT"
|
|
61
|
+
}
|