@larkup/tool-doc-editor 0.2.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/LICENSE +176 -0
- package/dist/editor.d.ts +22 -0
- package/dist/editor.js +696 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/parsers.d.ts +15 -0
- package/dist/parsers.js +384 -0
- package/dist/sandbox-scripts.d.ts +19 -0
- package/dist/sandbox-scripts.js +359 -0
- package/dist/types.d.ts +158 -0
- package/dist/types.js +4 -0
- package/package.json +68 -0
- package/src/editor.ts +840 -0
- package/src/index.ts +53 -0
- package/src/parsers.ts +439 -0
- package/src/sandbox-scripts.ts +380 -0
- package/src/types.ts +170 -0
- package/tool.manifest.json +22 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python code templates for sandbox-based document editing.
|
|
3
|
+
*
|
|
4
|
+
* These scripts run inside the Docker sandbox with:
|
|
5
|
+
* - pypdf: PDF form field filling
|
|
6
|
+
* - python-docx: Word document editing
|
|
7
|
+
* - python-pptx: PowerPoint editing
|
|
8
|
+
* - Pillow: Image processing
|
|
9
|
+
*
|
|
10
|
+
* All scripts expect input files in /sandbox/input/ and output to /sandbox/output/
|
|
11
|
+
*/
|
|
12
|
+
/* ------------------------------------------------------------------ */
|
|
13
|
+
/* PDF — Fill form fields using pypdf */
|
|
14
|
+
/* ------------------------------------------------------------------ */
|
|
15
|
+
export function generateFillPDFScript(edits) {
|
|
16
|
+
const editsJson = JSON.stringify(edits);
|
|
17
|
+
return `
|
|
18
|
+
import json
|
|
19
|
+
import sys
|
|
20
|
+
from pypdf import PdfReader, PdfWriter
|
|
21
|
+
|
|
22
|
+
# Load the PDF
|
|
23
|
+
reader = PdfReader("/sandbox/input/document.pdf")
|
|
24
|
+
writer = PdfWriter()
|
|
25
|
+
|
|
26
|
+
# Copy all pages
|
|
27
|
+
for page in reader.pages:
|
|
28
|
+
writer.add_page(page)
|
|
29
|
+
|
|
30
|
+
# Copy existing form fields
|
|
31
|
+
if reader.is_form:
|
|
32
|
+
writer.clone_reader_document_root(reader)
|
|
33
|
+
|
|
34
|
+
# Parse edits
|
|
35
|
+
edits = json.loads('''${editsJson}''')
|
|
36
|
+
|
|
37
|
+
# Build field value map (field_name -> value)
|
|
38
|
+
field_map = {}
|
|
39
|
+
for edit in edits:
|
|
40
|
+
field_map[edit["fieldId"]] = edit["value"]
|
|
41
|
+
|
|
42
|
+
# Apply form field fills
|
|
43
|
+
if reader.is_form:
|
|
44
|
+
fields = reader.get_fields()
|
|
45
|
+
update_dict = {}
|
|
46
|
+
for field_name, field_obj in (fields or {}).items():
|
|
47
|
+
for edit_id, value in field_map.items():
|
|
48
|
+
# Match by name or by detected ID pattern
|
|
49
|
+
if field_name == edit_id or edit_id.endswith(field_name):
|
|
50
|
+
update_dict[field_name] = value
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
if update_dict:
|
|
54
|
+
writer.update_page_form_field_values(writer.pages[0], update_dict)
|
|
55
|
+
|
|
56
|
+
# Save
|
|
57
|
+
with open("/sandbox/output/document.pdf", "wb") as f:
|
|
58
|
+
writer.write(f)
|
|
59
|
+
|
|
60
|
+
print(f"SUCCESS: Updated {len(field_map)} fields")
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
/* ------------------------------------------------------------------ */
|
|
64
|
+
/* PDF — Extract full text using pypdf */
|
|
65
|
+
/* ------------------------------------------------------------------ */
|
|
66
|
+
export function generateExtractPDFTextScript() {
|
|
67
|
+
return `
|
|
68
|
+
import json
|
|
69
|
+
from pypdf import PdfReader
|
|
70
|
+
|
|
71
|
+
reader = PdfReader("/sandbox/input/document.pdf")
|
|
72
|
+
pages = []
|
|
73
|
+
fields = []
|
|
74
|
+
|
|
75
|
+
for i, page in enumerate(reader.pages):
|
|
76
|
+
text = page.extract_text() or ""
|
|
77
|
+
pages.append({
|
|
78
|
+
"index": i,
|
|
79
|
+
"text": text,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
# Smart signature detection
|
|
83
|
+
lines = text.split('\\n')
|
|
84
|
+
for line in lines:
|
|
85
|
+
lower_line = line.lower()
|
|
86
|
+
if "signature:" in lower_line or "sign here" in lower_line or "date:" in lower_line or "_______" in lower_line:
|
|
87
|
+
if "signature" in lower_line or "sign" in lower_line:
|
|
88
|
+
fields.append({
|
|
89
|
+
"name": "detected_signature_" + str(i),
|
|
90
|
+
"type": "signature_placeholder",
|
|
91
|
+
"value": "",
|
|
92
|
+
"pageIndex": i,
|
|
93
|
+
"context": line.strip()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
# Extract form fields
|
|
97
|
+
if reader.is_form:
|
|
98
|
+
form_fields = reader.get_fields() or {}
|
|
99
|
+
for name, field in form_fields.items():
|
|
100
|
+
field_type = str(field.get("/FT", ""))
|
|
101
|
+
value = str(field.get("/V", ""))
|
|
102
|
+
fields.append({
|
|
103
|
+
"name": name,
|
|
104
|
+
"type": field_type,
|
|
105
|
+
"value": value,
|
|
106
|
+
"pageIndex": 0 # We might not know the exact page from get_fields easily, default to 0
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
result = {
|
|
110
|
+
"pages": pages,
|
|
111
|
+
"fields": fields,
|
|
112
|
+
"totalPages": len(reader.pages),
|
|
113
|
+
"metadata": dict(reader.metadata or {}),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
print("__RESULT__:" + json.dumps(result, default=str))
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
/* ------------------------------------------------------------------ */
|
|
120
|
+
/* DOCX — Edit content using python-docx */
|
|
121
|
+
/* ------------------------------------------------------------------ */
|
|
122
|
+
export function generateEditDOCXScript(edits) {
|
|
123
|
+
const editsJson = JSON.stringify(edits);
|
|
124
|
+
return `
|
|
125
|
+
import json
|
|
126
|
+
from docx import Document
|
|
127
|
+
|
|
128
|
+
doc = Document("/sandbox/input/document.docx")
|
|
129
|
+
edits = json.loads('''${editsJson}''')
|
|
130
|
+
|
|
131
|
+
updated = 0
|
|
132
|
+
for edit in edits:
|
|
133
|
+
edit_type = edit.get("type", "replace_text")
|
|
134
|
+
|
|
135
|
+
if edit_type == "replace_text":
|
|
136
|
+
search = edit.get("search", "")
|
|
137
|
+
replacement = edit.get("text", "")
|
|
138
|
+
|
|
139
|
+
for para in doc.paragraphs:
|
|
140
|
+
if search in para.text:
|
|
141
|
+
# Preserve formatting by replacing in runs
|
|
142
|
+
full_text = ""
|
|
143
|
+
for run in para.runs:
|
|
144
|
+
full_text += run.text
|
|
145
|
+
|
|
146
|
+
if search in full_text:
|
|
147
|
+
# Simple replacement — replace in runs
|
|
148
|
+
remaining = search
|
|
149
|
+
for run in para.runs:
|
|
150
|
+
if remaining and remaining in run.text:
|
|
151
|
+
run.text = run.text.replace(remaining, replacement, 1)
|
|
152
|
+
remaining = ""
|
|
153
|
+
updated += 1
|
|
154
|
+
break
|
|
155
|
+
elif remaining and run.text and remaining.startswith(run.text):
|
|
156
|
+
remaining = remaining[len(run.text):]
|
|
157
|
+
run.text = ""
|
|
158
|
+
|
|
159
|
+
# Also check tables
|
|
160
|
+
for table in doc.tables:
|
|
161
|
+
for row in table.rows:
|
|
162
|
+
for cell in row.cells:
|
|
163
|
+
for para in cell.paragraphs:
|
|
164
|
+
if search in para.text:
|
|
165
|
+
for run in para.runs:
|
|
166
|
+
if search in run.text:
|
|
167
|
+
run.text = run.text.replace(search, replacement, 1)
|
|
168
|
+
updated += 1
|
|
169
|
+
|
|
170
|
+
elif edit_type == "insert_text":
|
|
171
|
+
text = edit.get("text", "")
|
|
172
|
+
position = edit.get("position", len(doc.paragraphs))
|
|
173
|
+
if position < len(doc.paragraphs):
|
|
174
|
+
para = doc.paragraphs[position]
|
|
175
|
+
para.insert_paragraph_before(text)
|
|
176
|
+
else:
|
|
177
|
+
doc.add_paragraph(text)
|
|
178
|
+
updated += 1
|
|
179
|
+
|
|
180
|
+
doc.save("/sandbox/output/document.docx")
|
|
181
|
+
print(f"SUCCESS: Applied {updated} edits")
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
/* ------------------------------------------------------------------ */
|
|
185
|
+
/* DOCX — Fill form fields (content controls / placeholder text) */
|
|
186
|
+
/* ------------------------------------------------------------------ */
|
|
187
|
+
export function generateFillDOCXScript(edits) {
|
|
188
|
+
const editsJson = JSON.stringify(edits);
|
|
189
|
+
return `
|
|
190
|
+
import json
|
|
191
|
+
from docx import Document
|
|
192
|
+
|
|
193
|
+
doc = Document("/sandbox/input/document.docx")
|
|
194
|
+
edits = json.loads('''${editsJson}''')
|
|
195
|
+
|
|
196
|
+
# Build lookup
|
|
197
|
+
field_map = {edit["fieldId"]: edit["value"] for edit in edits}
|
|
198
|
+
|
|
199
|
+
updated = 0
|
|
200
|
+
|
|
201
|
+
# Strategy 1: Look for common form patterns like "Label: ___" or "Label: "
|
|
202
|
+
for para in doc.paragraphs:
|
|
203
|
+
for field_id, value in field_map.items():
|
|
204
|
+
# The field_id contains the label name from detection
|
|
205
|
+
label = field_id.replace("detected_", "").strip()
|
|
206
|
+
|
|
207
|
+
# Check if paragraph contains "Label:" pattern
|
|
208
|
+
text = para.text
|
|
209
|
+
for run in para.runs:
|
|
210
|
+
if "___" in run.text or (run.text.strip().endswith(":") and not value):
|
|
211
|
+
# Replace underscores with value
|
|
212
|
+
run.text = run.text.replace("___", value).rstrip("_")
|
|
213
|
+
updated += 1
|
|
214
|
+
|
|
215
|
+
# Strategy 2: Check tables (common in forms)
|
|
216
|
+
for table in doc.tables:
|
|
217
|
+
for row in table.rows:
|
|
218
|
+
cells = row.cells
|
|
219
|
+
for i, cell in enumerate(cells):
|
|
220
|
+
cell_text = cell.text.strip().rstrip(":")
|
|
221
|
+
# If this cell's text matches a field name, fill the next cell
|
|
222
|
+
for field_id, value in field_map.items():
|
|
223
|
+
if cell_text.lower() == field_id.lower() and i + 1 < len(cells):
|
|
224
|
+
next_cell = cells[i + 1]
|
|
225
|
+
for para in next_cell.paragraphs:
|
|
226
|
+
if para.runs:
|
|
227
|
+
para.runs[0].text = value
|
|
228
|
+
else:
|
|
229
|
+
para.text = value
|
|
230
|
+
updated += 1
|
|
231
|
+
|
|
232
|
+
doc.save("/sandbox/output/document.docx")
|
|
233
|
+
print(f"SUCCESS: Filled {updated} fields")
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
/* ------------------------------------------------------------------ */
|
|
237
|
+
/* PPTX — Extract text from slides */
|
|
238
|
+
/* ------------------------------------------------------------------ */
|
|
239
|
+
export function generateExtractPPTXScript() {
|
|
240
|
+
return `
|
|
241
|
+
import json
|
|
242
|
+
from pptx import Presentation
|
|
243
|
+
from pptx.util import Inches, Pt
|
|
244
|
+
|
|
245
|
+
prs = Presentation("/sandbox/input/document.pptx")
|
|
246
|
+
slides = []
|
|
247
|
+
|
|
248
|
+
for i, slide in enumerate(prs.slides):
|
|
249
|
+
texts = []
|
|
250
|
+
fields = []
|
|
251
|
+
|
|
252
|
+
for shape in slide.shapes:
|
|
253
|
+
if shape.has_text_frame:
|
|
254
|
+
for para in shape.text_frame.paragraphs:
|
|
255
|
+
text = para.text.strip()
|
|
256
|
+
if text:
|
|
257
|
+
texts.append(text)
|
|
258
|
+
|
|
259
|
+
# Detect form-like patterns
|
|
260
|
+
if ":" in text and text.endswith(":"):
|
|
261
|
+
fields.append({
|
|
262
|
+
"name": text.rstrip(":").strip(),
|
|
263
|
+
"type": "text",
|
|
264
|
+
"value": "",
|
|
265
|
+
"slideIndex": i,
|
|
266
|
+
})
|
|
267
|
+
elif "___" in text:
|
|
268
|
+
label = text.split("___")[0].strip().rstrip(":")
|
|
269
|
+
if label:
|
|
270
|
+
fields.append({
|
|
271
|
+
"name": label,
|
|
272
|
+
"type": "text",
|
|
273
|
+
"value": "",
|
|
274
|
+
"slideIndex": i,
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
slides.append({
|
|
278
|
+
"index": i,
|
|
279
|
+
"text": "\\n".join(texts),
|
|
280
|
+
"fields": fields,
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
result = {
|
|
284
|
+
"slides": slides,
|
|
285
|
+
"totalSlides": len(prs.slides),
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
print("__RESULT__:" + json.dumps(result, default=str))
|
|
289
|
+
`;
|
|
290
|
+
}
|
|
291
|
+
/* ------------------------------------------------------------------ */
|
|
292
|
+
/* PPTX — Edit slide text */
|
|
293
|
+
/* ------------------------------------------------------------------ */
|
|
294
|
+
export function generateEditPPTXScript(edits) {
|
|
295
|
+
const editsJson = JSON.stringify(edits);
|
|
296
|
+
return `
|
|
297
|
+
import json
|
|
298
|
+
from pptx import Presentation
|
|
299
|
+
|
|
300
|
+
prs = Presentation("/sandbox/input/document.pptx")
|
|
301
|
+
edits = json.loads('''${editsJson}''')
|
|
302
|
+
|
|
303
|
+
updated = 0
|
|
304
|
+
for edit in edits:
|
|
305
|
+
slide_index = edit.get("pageIndex", 0)
|
|
306
|
+
if slide_index >= len(prs.slides):
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
slide = prs.slides[slide_index]
|
|
310
|
+
edit_type = edit.get("type", "replace_text")
|
|
311
|
+
|
|
312
|
+
if edit_type == "replace_text":
|
|
313
|
+
search = edit.get("search", "")
|
|
314
|
+
replacement = edit.get("text", "")
|
|
315
|
+
|
|
316
|
+
for shape in slide.shapes:
|
|
317
|
+
if shape.has_text_frame:
|
|
318
|
+
for para in shape.text_frame.paragraphs:
|
|
319
|
+
for run in para.runs:
|
|
320
|
+
if search in run.text:
|
|
321
|
+
run.text = run.text.replace(search, replacement)
|
|
322
|
+
updated += 1
|
|
323
|
+
|
|
324
|
+
prs.save("/sandbox/output/document.pptx")
|
|
325
|
+
print(f"SUCCESS: Applied {updated} edits")
|
|
326
|
+
`;
|
|
327
|
+
}
|
|
328
|
+
/* ------------------------------------------------------------------ */
|
|
329
|
+
/* PPTX — Fill form fields in slides */
|
|
330
|
+
/* ------------------------------------------------------------------ */
|
|
331
|
+
export function generateFillPPTXScript(edits) {
|
|
332
|
+
const editsJson = JSON.stringify(edits);
|
|
333
|
+
return `
|
|
334
|
+
import json
|
|
335
|
+
from pptx import Presentation
|
|
336
|
+
|
|
337
|
+
prs = Presentation("/sandbox/input/document.pptx")
|
|
338
|
+
edits = json.loads('''${editsJson}''')
|
|
339
|
+
|
|
340
|
+
field_map = {edit["fieldId"]: edit["value"] for edit in edits}
|
|
341
|
+
updated = 0
|
|
342
|
+
|
|
343
|
+
for slide in prs.slides:
|
|
344
|
+
for shape in slide.shapes:
|
|
345
|
+
if shape.has_text_frame:
|
|
346
|
+
for para in shape.text_frame.paragraphs:
|
|
347
|
+
full_text = para.text
|
|
348
|
+
for field_id, value in field_map.items():
|
|
349
|
+
# Replace "___" blanks after field labels
|
|
350
|
+
if "___" in full_text:
|
|
351
|
+
for run in para.runs:
|
|
352
|
+
if "___" in run.text:
|
|
353
|
+
run.text = run.text.replace("___", value, 1)
|
|
354
|
+
updated += 1
|
|
355
|
+
|
|
356
|
+
prs.save("/sandbox/output/document.pptx")
|
|
357
|
+
print(f"SUCCESS: Filled {updated} fields")
|
|
358
|
+
`;
|
|
359
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/** Supported document types */
|
|
2
|
+
export type DocumentType = 'pdf' | 'docx' | 'pptx' | 'txt' | 'xlsx';
|
|
3
|
+
/** A single editable field detected in a document */
|
|
4
|
+
export interface DocumentField {
|
|
5
|
+
/** Unique field identifier within the document */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Human-readable field name / label */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Field type */
|
|
10
|
+
type: 'text' | 'textarea' | 'checkbox' | 'radio' | 'date' | 'number' | 'select' | 'signature_placeholder';
|
|
11
|
+
/** Current value (empty if unfilled) */
|
|
12
|
+
value: string;
|
|
13
|
+
/** Page/slide index (0-based) */
|
|
14
|
+
pageIndex: number;
|
|
15
|
+
/** Whether the field is required (if detectable) */
|
|
16
|
+
required?: boolean;
|
|
17
|
+
/** Placeholder or tooltip text */
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
/** Options for select/radio fields */
|
|
20
|
+
options?: string[];
|
|
21
|
+
/** Bounding box for overlay positioning (PDF) — percentages of page */
|
|
22
|
+
bbox?: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
};
|
|
28
|
+
/** Context around the field (for smart detection) */
|
|
29
|
+
context?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A single page or slide of a parsed document */
|
|
32
|
+
export interface DocumentPage {
|
|
33
|
+
/** 0-based page/slide index */
|
|
34
|
+
index: number;
|
|
35
|
+
/** Extracted text content */
|
|
36
|
+
text: string;
|
|
37
|
+
/** HTML rendering (for DOCX via mammoth) */
|
|
38
|
+
html?: string;
|
|
39
|
+
/** Fields on this page */
|
|
40
|
+
fields: DocumentField[];
|
|
41
|
+
/** Page dimensions in points (PDF) */
|
|
42
|
+
dimensions?: {
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Result of parsing a document */
|
|
48
|
+
export interface ParsedDocument {
|
|
49
|
+
/** Session ID for tracking edits */
|
|
50
|
+
sessionId: string;
|
|
51
|
+
/** Original filename */
|
|
52
|
+
fileName: string;
|
|
53
|
+
/** Detected document type */
|
|
54
|
+
type: DocumentType;
|
|
55
|
+
/** MIME type */
|
|
56
|
+
mimeType: string;
|
|
57
|
+
/** File size in bytes */
|
|
58
|
+
fileSize: number;
|
|
59
|
+
/** Pages/slides */
|
|
60
|
+
pages: DocumentPage[];
|
|
61
|
+
/** All fields across all pages (flat) */
|
|
62
|
+
fields: DocumentField[];
|
|
63
|
+
/** Full raw text extraction */
|
|
64
|
+
rawText: string;
|
|
65
|
+
/** Document metadata (author, title, dates, etc.) */
|
|
66
|
+
metadata: Record<string, string>;
|
|
67
|
+
/** Total page/slide count */
|
|
68
|
+
totalPages: number;
|
|
69
|
+
}
|
|
70
|
+
/** An edit operation to apply to a document */
|
|
71
|
+
export interface FieldEdit {
|
|
72
|
+
/** Field ID to update */
|
|
73
|
+
fieldId: string;
|
|
74
|
+
/** New value */
|
|
75
|
+
value: string;
|
|
76
|
+
/** Type of edit */
|
|
77
|
+
type: 'fill' | 'replace' | 'append' | 'clear';
|
|
78
|
+
}
|
|
79
|
+
/** A content-level edit (not field-based) */
|
|
80
|
+
export interface ContentEdit {
|
|
81
|
+
/** Page/slide index */
|
|
82
|
+
pageIndex: number;
|
|
83
|
+
/** Type of content edit */
|
|
84
|
+
type: 'replace_text' | 'insert_text' | 'delete_text';
|
|
85
|
+
/** Search text (for replace) */
|
|
86
|
+
search?: string;
|
|
87
|
+
/** Replacement or insertion text */
|
|
88
|
+
text: string;
|
|
89
|
+
/** Position for insert (paragraph index) */
|
|
90
|
+
position?: number;
|
|
91
|
+
}
|
|
92
|
+
/** Signature details for applying to a document */
|
|
93
|
+
export interface SignatureData {
|
|
94
|
+
/** The mode of the signature */
|
|
95
|
+
mode: 'type' | 'draw' | 'upload';
|
|
96
|
+
/** Base64 image of the signature (for draw/upload) */
|
|
97
|
+
imagePayload?: string;
|
|
98
|
+
/** Typed text for the signature */
|
|
99
|
+
text?: string;
|
|
100
|
+
/** Font family for typed text */
|
|
101
|
+
font?: string;
|
|
102
|
+
/** Color (hex) */
|
|
103
|
+
color?: string;
|
|
104
|
+
/** Optional date to include */
|
|
105
|
+
date?: string;
|
|
106
|
+
/** Optional extra text */
|
|
107
|
+
extraText?: string;
|
|
108
|
+
/** Page index (0-based) where the signature should be placed */
|
|
109
|
+
pageIndex: number;
|
|
110
|
+
/** Approximate X coordinate (if known) */
|
|
111
|
+
x?: number;
|
|
112
|
+
/** Approximate Y coordinate (if known) */
|
|
113
|
+
y?: number;
|
|
114
|
+
/** Signature scale multiplier */
|
|
115
|
+
scale?: number;
|
|
116
|
+
/** Optional base64 to use instead of current session base64 (for undo/replace) */
|
|
117
|
+
base64Override?: string;
|
|
118
|
+
/** The text context detected near the signature line (e.g. "Datum, Unterschrift des Empfängers") */
|
|
119
|
+
detectedContext?: string;
|
|
120
|
+
/** User-provided note about placement (e.g. "bottom right corner") */
|
|
121
|
+
placementNote?: string;
|
|
122
|
+
}
|
|
123
|
+
/** Active editing session */
|
|
124
|
+
export interface EditorSession {
|
|
125
|
+
/** Unique session ID */
|
|
126
|
+
id: string;
|
|
127
|
+
/** Original filename */
|
|
128
|
+
fileName: string;
|
|
129
|
+
/** Document type */
|
|
130
|
+
type: DocumentType;
|
|
131
|
+
/** MIME type */
|
|
132
|
+
mimeType: string;
|
|
133
|
+
/** Original file as base64 */
|
|
134
|
+
originalFileBase64: string;
|
|
135
|
+
/** Current (modified) file as base64 */
|
|
136
|
+
currentFileBase64: string;
|
|
137
|
+
/** Parsed document state */
|
|
138
|
+
parsed: ParsedDocument;
|
|
139
|
+
/** Applied edits history */
|
|
140
|
+
edits: FieldEdit[];
|
|
141
|
+
/** Created timestamp */
|
|
142
|
+
createdAt: string;
|
|
143
|
+
/** Last modified timestamp */
|
|
144
|
+
updatedAt: string;
|
|
145
|
+
}
|
|
146
|
+
/** Result of applying edits */
|
|
147
|
+
export interface EditResult {
|
|
148
|
+
/** Whether the edit succeeded */
|
|
149
|
+
success: boolean;
|
|
150
|
+
/** Updated parsed document */
|
|
151
|
+
parsed: ParsedDocument;
|
|
152
|
+
/** Updated file as base64 */
|
|
153
|
+
fileBase64: string;
|
|
154
|
+
/** Error message if failed */
|
|
155
|
+
error?: string;
|
|
156
|
+
/** Fields that were updated */
|
|
157
|
+
updatedFields: string[];
|
|
158
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@larkup/tool-doc-editor",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"description": "Larkup marketplace tool: AI-powered form filling and document editing.",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./types": {
|
|
18
|
+
"types": "./dist/types.d.ts",
|
|
19
|
+
"import": "./dist/types.js",
|
|
20
|
+
"default": "./dist/types.js"
|
|
21
|
+
},
|
|
22
|
+
"./parsers": {
|
|
23
|
+
"types": "./dist/parsers.d.ts",
|
|
24
|
+
"import": "./dist/parsers.js",
|
|
25
|
+
"default": "./dist/parsers.js"
|
|
26
|
+
},
|
|
27
|
+
"./editor": {
|
|
28
|
+
"types": "./dist/editor.d.ts",
|
|
29
|
+
"import": "./dist/editor.js",
|
|
30
|
+
"default": "./dist/editor.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src",
|
|
36
|
+
"tool.manifest.json",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"jszip": "^3.10.1",
|
|
41
|
+
"mammoth": "^1.12.0",
|
|
42
|
+
"pdf-lib": "^1.17.1",
|
|
43
|
+
"@larkup/sandbox": "0.1.1",
|
|
44
|
+
"@larkup/core": "0.2.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@larkup/marketplace": ">=0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@larkup/marketplace": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"typescript": "5.7.3",
|
|
56
|
+
"@larkup/marketplace": "0.1.1"
|
|
57
|
+
},
|
|
58
|
+
"license": "Apache-2.0",
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "https://github.com/Larkup-AI/larkup-rag",
|
|
62
|
+
"directory": "packages/tools/doc-editor"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsc --outDir dist",
|
|
66
|
+
"type-check": "tsc --noEmit"
|
|
67
|
+
}
|
|
68
|
+
}
|