@tstdl/base 0.93.62 → 0.93.66
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/ai/genkit/helpers.d.ts +10 -0
- package/ai/genkit/helpers.js +14 -0
- package/ai/genkit/index.d.ts +2 -0
- package/ai/genkit/index.js +2 -0
- package/ai/genkit/module.d.ts +35 -0
- package/ai/genkit/module.js +56 -0
- package/ai/index.d.ts +1 -0
- package/ai/index.js +1 -0
- package/ai/prompts/format.d.ts +15 -0
- package/ai/prompts/format.js +17 -0
- package/ai/prompts/index.d.ts +3 -0
- package/ai/prompts/index.js +3 -0
- package/ai/prompts/instructions-formatter.d.ts +25 -0
- package/ai/prompts/instructions-formatter.js +166 -0
- package/ai/prompts/instructions.d.ts +3 -0
- package/ai/prompts/instructions.js +8 -0
- package/document-management/server/services/document-file.service.d.ts +2 -0
- package/document-management/server/services/document-file.service.js +10 -9
- package/document-management/server/services/document-management-ai.service.d.ts +1 -0
- package/document-management/server/services/document-management-ai.service.js +267 -133
- package/document-management/server/services/document.service.js +1 -2
- package/examples/document-management/main.js +6 -0
- package/json-path/json-path.js +1 -1
- package/orm/server/repository.js +4 -6
- package/package.json +10 -6
- package/pdf/utils.js +1 -1
- package/schema/converters/zod-converter.d.ts +1 -1
- package/schema/converters/zod-converter.js +2 -13
- package/schema/converters/zod-v3-converter.d.ts +3 -3
- package/utils/date-time.d.ts +6 -0
- package/utils/date-time.js +36 -1
- package/utils/file-reader.d.ts +0 -1
- package/utils/file-reader.js +4 -7
- package/utils/object/object.d.ts +4 -2
- package/utils/object/object.js +30 -21
- package/utils/stream/from-promise.js +2 -2
|
@@ -58,9 +58,11 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
58
58
|
});
|
|
59
59
|
var _a;
|
|
60
60
|
var DocumentManagementAiService_1;
|
|
61
|
+
import { readFile } from 'node:fs/promises';
|
|
61
62
|
import { and, isNull as drizzleIsNull, eq, inArray } from 'drizzle-orm';
|
|
62
63
|
import { P, match } from 'ts-pattern';
|
|
63
|
-
import {
|
|
64
|
+
import { convertToGenkitSchema, genkitGenerationOptions, injectGenkit, injectModel } from '../../../ai/genkit/index.js';
|
|
65
|
+
import { formatData, formatInstructions, orderedList } from '../../../ai/prompts/index.js';
|
|
64
66
|
import { TemporaryFile } from '../../../file/server/index.js';
|
|
65
67
|
import { inject } from '../../../injector/inject.js';
|
|
66
68
|
import { Logger } from '../../../logger/logger.js';
|
|
@@ -68,7 +70,7 @@ import { arrayAgg } from '../../../orm/index.js';
|
|
|
68
70
|
import { injectRepository } from '../../../orm/server/index.js';
|
|
69
71
|
import { array, boolean, enumeration, integer, nullable, number, object, string } from '../../../schema/index.js';
|
|
70
72
|
import { distinct } from '../../../utils/array/index.js';
|
|
71
|
-
import {
|
|
73
|
+
import { numericDateToDateTime, tryDateObjectToNumericDate } from '../../../utils/date-time.js';
|
|
72
74
|
import { fromEntries, objectEntries } from '../../../utils/object/object.js';
|
|
73
75
|
import { assertDefined, assertDefinedPass, assertNotNull, isNotNull, isNull, isUndefined } from '../../../utils/type-guards.js';
|
|
74
76
|
import { Document, DocumentProperty, DocumentRequestState, DocumentTypeProperty } from '../../models/index.js';
|
|
@@ -79,78 +81,227 @@ import { DocumentFileService } from './document-file.service.js';
|
|
|
79
81
|
import { DocumentPropertyService } from './document-property.service.js';
|
|
80
82
|
import { DocumentTagService } from './document-tag.service.js';
|
|
81
83
|
import { DocumentManagementSingleton } from './singleton.js';
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
84
|
+
// --- Prompts ---
|
|
85
|
+
const ocrSystemPrompt = `
|
|
86
|
+
You are an expert OCR and Document Digitization engine.
|
|
87
|
+
|
|
88
|
+
${formatInstructions({
|
|
89
|
+
'Primary Objective': 'Convert the provided document into semantically structured, clean Markdown.',
|
|
90
|
+
'Critical Constraints': orderedList([
|
|
91
|
+
'Output ONLY the Markdown content. Do not include introductory text, conversational filler, or code block fences (```).',
|
|
92
|
+
'Do not describe the visual appearance (e.g., "This looks like an invoice"). Transcribe the content only.',
|
|
93
|
+
]),
|
|
94
|
+
'Formatting Rules': orderedList({
|
|
95
|
+
'Headings': 'Use # for the main document title (once). Use ##, ### for sections based on logical hierarchy.',
|
|
96
|
+
'Text Content': 'Transcribe text verbatim. Do not correct spelling or grammar, summarize, or rewrite.',
|
|
97
|
+
'Tables': 'Strictly use Markdown table syntax. Align columns logically based on the visual grid.',
|
|
98
|
+
'Lists': 'Detect bullet points and numbered lists and format them as Markdown lists.',
|
|
99
|
+
'Emphasis': 'Use **bold** and _italics_ only where visually distinct in the source.',
|
|
100
|
+
'Columns': 'Read multi-column text as a single continuous flow.',
|
|
101
|
+
}),
|
|
102
|
+
'Complex Elements': {
|
|
103
|
+
'Images/Visuals': 'Replace non-textual diagrams with `> [Visual: Brief description of the image/chart]`.',
|
|
104
|
+
'Signatures': 'Mark distinct signatures as `> [Signature: {Name if legible/Context}]`.',
|
|
105
|
+
'Forms': 'Represent checkboxes as `[ ]` (unchecked) or `[x]` (checked). Format label/value pairs on separate lines or as a definition list if applicable.',
|
|
106
|
+
'Math': 'Transcribe equations using LaTeX syntax enclosed in `$...$` for inline or `$$...$$` for block equations.',
|
|
107
|
+
},
|
|
108
|
+
'Page Handling': [
|
|
109
|
+
'Metadata: Start every page with `<!-- Page {n} Start -->` and end with `<!-- Page {n} End -->` on separate lines.',
|
|
110
|
+
'Artifacts: Exclude running headers, footers, and page numbers unless they contain unique data not found elsewhere.',
|
|
111
|
+
],
|
|
112
|
+
'Error Handling': [
|
|
113
|
+
'Mark illegible text as `[Illegible]`.',
|
|
114
|
+
'Mark cut-off text as `[Cut off]`.',
|
|
115
|
+
],
|
|
116
|
+
})}
|
|
117
|
+
`.trim();
|
|
118
|
+
const ocrUserPrompt = 'Transcribe the attached document into Markdown following the system instructions.';
|
|
119
|
+
const classifySystemPrompt = `
|
|
120
|
+
You are a Document Taxonomy Specialist.
|
|
121
|
+
|
|
122
|
+
${formatInstructions({
|
|
123
|
+
'Task': `Analyze the visual layout and text content of the document to categorize it into exactly one of the provided hierarchical types.`,
|
|
124
|
+
'Input Context': 'You will be provided with a list of valid category labels (e.g., "Finance -> Invoice").',
|
|
125
|
+
'Analysis Strategy': orderedList([
|
|
126
|
+
'Scan the header and title for explicit document type names (e.g., "Invoice", "Contract", "Bill of Lading").',
|
|
127
|
+
'Analyze the layout structure (e.g., columns often imply Invoices/Receipts; dense paragraphs imply Contracts/Letters).',
|
|
128
|
+
'Identify key entities (e.g., "Total Due" implies financial; "Signed by" implies legal).',
|
|
129
|
+
]),
|
|
130
|
+
'Selection Logic': orderedList([
|
|
131
|
+
'Exact Match: If the document explicitly states its type, select the corresponding category.',
|
|
132
|
+
'Content Match: If implicit, match the intent.',
|
|
133
|
+
'Specificity: Always choose the most specific leaf-node category available.',
|
|
134
|
+
'Fallback: If ambiguous, choose the category that best describes the *primary* purpose of the document.',
|
|
135
|
+
]),
|
|
136
|
+
})}
|
|
137
|
+
`.trim();
|
|
138
|
+
const classifyUserPrompt = 'Determine the single most accurate document type from the provided list based on the document following the system instructions.';
|
|
139
|
+
const extractSystemPrompt = `
|
|
140
|
+
You are a Structured Data Extraction Analyst.
|
|
141
|
+
|
|
142
|
+
${formatInstructions({
|
|
143
|
+
'Task': 'Analyze the document and extract metadata into the defined JSON schema.',
|
|
144
|
+
'General Guidelines': orderedList({
|
|
145
|
+
'Language': 'Ensure all generated text (titles, summaries) matches the primary language of the document.',
|
|
146
|
+
'Null Handling': 'If a specific field or property is not present in the document, return null. Do not guess or hallucinate values.',
|
|
147
|
+
}),
|
|
148
|
+
'Field Specific Instructions': {
|
|
149
|
+
'Title': 'Create a concise, searchable filename-style title (e.g., "Invoice - Oct 2023").',
|
|
150
|
+
'Subtitle': 'Extract context usually found below the header (e.g., Project Name, Reference Number).',
|
|
151
|
+
'Summary': 'Write a 2-3 sentence executive summary. Mention the what type of information can be found in the document and its purpose.',
|
|
152
|
+
'Tags': 'Generate 3-5 keywords for categorization. Only use important information missing in title, subtitle and properties. Prioritize reusing of existing tags where possible.',
|
|
153
|
+
'Date': 'Identify the *creation* date of the document. If multiple dates exist, prioritize the primary date (like invoice or letter Date)',
|
|
154
|
+
},
|
|
155
|
+
'Property Extraction': [
|
|
156
|
+
'You will be given a list of specific dynamic properties to look for.',
|
|
157
|
+
'Extract values *exactly* as they appear for strings.',
|
|
158
|
+
'Normalize numbers and dates to standard formats.',
|
|
159
|
+
'If a property is ambiguous, favor the value most prominent in the document layout.',
|
|
160
|
+
'If a property is missing, set its value to null.',
|
|
161
|
+
],
|
|
162
|
+
})}`.trim();
|
|
163
|
+
const extractUserPrompt = 'Analyze the document and extract metadata and specific properties defined in the output schema following the system instructions.';
|
|
164
|
+
const assignCollectionSystemPrompt = `
|
|
165
|
+
You are a Digital Filing Assistant.
|
|
166
|
+
|
|
167
|
+
${formatInstructions({
|
|
168
|
+
'Task': `Assign the document to relevant collections based on its metadata and content.`,
|
|
169
|
+
'Input': 'Document Metadata and a list of Available Collections.',
|
|
170
|
+
'Matching Logic': orderedList([
|
|
171
|
+
'Direct Key-Match: Look for exact keyword matches between the collection name and the document metadata.',
|
|
172
|
+
'Semantic Fit: Determine if the document functionally belongs to a group.',
|
|
173
|
+
'Project Association: If the document references a specific project code or name found in a collection name, assign it there.',
|
|
174
|
+
]),
|
|
175
|
+
'Output': 'Return an array of matching collection IDs. If no collection is a strong fit, return an empty array.',
|
|
176
|
+
})}
|
|
177
|
+
`.trim();
|
|
178
|
+
const assignCollectionUserPrompt = 'Select the most appropriate collections for this document from the provided list following the system instructions.';
|
|
179
|
+
const assignRequestSystemPrompt = `
|
|
180
|
+
You are a Workflow Routing Agent.
|
|
181
|
+
|
|
182
|
+
${formatInstructions({
|
|
183
|
+
'Task': 'Match the provided document to an existing Open Document Request.',
|
|
184
|
+
'Input': 'Document Metadata and a list of Open Requests.',
|
|
185
|
+
'Matching Rules': orderedList({
|
|
186
|
+
'Hard Constraints': 'If a Request has a "Comment" or specific property requirement, the document MUST fulfill it strictly (e.g., "Need bill from July" must match date).',
|
|
187
|
+
'Ambiguity': 'If multiple requests match, select the one with the most specific constraints that are satisfied.',
|
|
188
|
+
'Negative Match': 'If the document satisfies the metadata but violates a comment constraint, it is unsuitable.',
|
|
189
|
+
}),
|
|
190
|
+
'Output': 'The ID of the matching request, or null if no request matches.',
|
|
191
|
+
})}
|
|
192
|
+
`.trim();
|
|
193
|
+
const assignRequestUserPrompt = 'Evaluate the document against the list of open requests and find the best match following the system instructions.';
|
|
85
194
|
let DocumentManagementAiService = DocumentManagementAiService_1 = class DocumentManagementAiService {
|
|
195
|
+
#genkit = injectGenkit();
|
|
196
|
+
#ocrModel = injectModel('gemini-2.5-flash-lite').withConfig({ temperature: 0.25, topP: 0.75, topK: 8 });
|
|
197
|
+
#classifyModel = injectModel('gemini-2.5-flash').withConfig({ temperature: 0.25, topP: 0.75, topK: 8 });
|
|
198
|
+
#extractModel = injectModel('gemini-2.5-flash').withConfig({ temperature: 0.25, topP: 0.75, topK: 8 });
|
|
199
|
+
#assignModel = injectModel('gemini-2.5-flash').withConfig({ temperature: 0.25, topP: 0.75, topK: 8 });
|
|
86
200
|
#documentCollectionService = inject(DocumentCollectionService);
|
|
87
201
|
#documentTagService = inject(DocumentTagService);
|
|
88
202
|
#documentCategoryTypeService = inject(DocumentCategoryTypeService);
|
|
89
203
|
#documentFileService = inject(DocumentFileService);
|
|
90
204
|
#documentPropertyService = inject(DocumentPropertyService);
|
|
91
|
-
#aiService = inject(AiService);
|
|
92
205
|
#documentPropertyRepository = injectRepository(DocumentProperty);
|
|
93
206
|
#documentRepository = injectRepository(Document);
|
|
94
207
|
#documentTypePropertyRepository = injectRepository(DocumentTypeProperty);
|
|
95
208
|
#logger = inject(Logger, DocumentManagementAiService_1.name);
|
|
96
|
-
async
|
|
209
|
+
async extractDocumentContent(tenantId, documentId) {
|
|
97
210
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
211
|
+
try {
|
|
212
|
+
const document = await this.#documentRepository.loadByQuery({ tenantId, id: documentId });
|
|
213
|
+
const fileContentStream = this.#documentFileService.getContentStream(document);
|
|
214
|
+
const tmpFile = __addDisposableResource(env_1, await TemporaryFile.from(fileContentStream), true);
|
|
215
|
+
const buffer = await readFile(tmpFile.path);
|
|
216
|
+
const base64Data = buffer.toString('base64');
|
|
217
|
+
const dataUrl = `data:${document.mimeType};base64,${base64Data}`;
|
|
218
|
+
this.#logger.trace(`Extracting content from document ${document.id}`);
|
|
219
|
+
const result = await this.#genkit.generate({
|
|
220
|
+
model: this.#ocrModel,
|
|
221
|
+
output: { schema: convertToGenkitSchema(object({ content: string() })) },
|
|
222
|
+
system: ocrSystemPrompt,
|
|
223
|
+
prompt: [
|
|
224
|
+
{ media: { url: dataUrl } },
|
|
225
|
+
{ text: ocrUserPrompt },
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
if (isNull(result.output)) {
|
|
229
|
+
throw new Error(`AI returned null output for document "${document.id}".`);
|
|
230
|
+
}
|
|
231
|
+
const markdownBlockStripped = result.output.content.trim().replaceAll(/^```\w*\s*|```$/gi, '').trim();
|
|
232
|
+
return markdownBlockStripped;
|
|
233
|
+
}
|
|
234
|
+
catch (e_1) {
|
|
235
|
+
env_1.error = e_1;
|
|
236
|
+
env_1.hasError = true;
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
const result_1 = __disposeResources(env_1);
|
|
240
|
+
if (result_1)
|
|
241
|
+
await result_1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async classifyDocumentType(tenantId, documentId) {
|
|
245
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
98
246
|
try {
|
|
99
247
|
const document = await this.#documentRepository.loadByQuery({ tenantId, id: documentId });
|
|
100
248
|
if (isNotNull(document.typeId)) {
|
|
101
249
|
return document.typeId;
|
|
102
250
|
}
|
|
103
251
|
const fileContentStream = this.#documentFileService.getContentStream(document);
|
|
104
|
-
const tmpFile = __addDisposableResource(
|
|
105
|
-
const
|
|
252
|
+
const tmpFile = __addDisposableResource(env_2, await TemporaryFile.from(fileContentStream), true);
|
|
253
|
+
const buffer = await readFile(tmpFile.path);
|
|
254
|
+
const base64Data = buffer.toString('base64');
|
|
255
|
+
const dataUrl = `data:${document.mimeType};base64,${base64Data}`;
|
|
106
256
|
const categories = await this.#documentCategoryTypeService.loadCategoryViews(tenantId);
|
|
107
257
|
const typeLabelEntries = getDescriptiveTypeLabels(categories);
|
|
108
258
|
const typeLabels = typeLabelEntries.map(({ label }) => label);
|
|
109
259
|
this.#logger.trace(`Classifying document ${document.id}`);
|
|
110
|
-
const
|
|
111
|
-
model:
|
|
112
|
-
|
|
260
|
+
const result = await this.#genkit.generate(genkitGenerationOptions({
|
|
261
|
+
model: this.#classifyModel,
|
|
262
|
+
config: {
|
|
113
263
|
maxOutputTokens: 128,
|
|
114
|
-
|
|
115
|
-
topP: 0.75,
|
|
116
|
-
topK: 4,
|
|
117
|
-
thinkingBudget: 0,
|
|
264
|
+
thinkingConfig: { thinkingBudget: 0 },
|
|
118
265
|
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
],
|
|
129
|
-
},
|
|
266
|
+
output: {
|
|
267
|
+
schema: object({
|
|
268
|
+
documentType: enumeration(typeLabels),
|
|
269
|
+
}),
|
|
270
|
+
},
|
|
271
|
+
system: classifySystemPrompt,
|
|
272
|
+
prompt: [
|
|
273
|
+
{ media: { url: dataUrl } },
|
|
274
|
+
{ text: classifyUserPrompt },
|
|
130
275
|
],
|
|
131
|
-
});
|
|
132
|
-
|
|
276
|
+
}));
|
|
277
|
+
if (isNull(result.output)) {
|
|
278
|
+
throw new Error(`AI returned null output for document classification "${document.id}".`);
|
|
279
|
+
}
|
|
280
|
+
const output = result.output;
|
|
281
|
+
const typeId = typeLabelEntries.find((entry) => entry.label == output.documentType)?.id;
|
|
133
282
|
assertDefined(typeId, `Could not classify document ${document.id}`);
|
|
134
283
|
return typeId;
|
|
135
284
|
}
|
|
136
|
-
catch (
|
|
137
|
-
|
|
138
|
-
|
|
285
|
+
catch (e_2) {
|
|
286
|
+
env_2.error = e_2;
|
|
287
|
+
env_2.hasError = true;
|
|
139
288
|
}
|
|
140
289
|
finally {
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
await
|
|
290
|
+
const result_2 = __disposeResources(env_2);
|
|
291
|
+
if (result_2)
|
|
292
|
+
await result_2;
|
|
144
293
|
}
|
|
145
294
|
}
|
|
146
295
|
async extractDocumentInformation(tenantId, documentId) {
|
|
147
|
-
const
|
|
296
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
148
297
|
try {
|
|
149
298
|
const document = await this.#documentRepository.loadByQuery({ tenantId, id: documentId });
|
|
150
299
|
const existingTags = await this.#documentTagService.loadTags(tenantId);
|
|
151
300
|
const fileContentStream = this.#documentFileService.getContentStream(document);
|
|
152
|
-
const tmpFile = __addDisposableResource(
|
|
153
|
-
const
|
|
301
|
+
const tmpFile = __addDisposableResource(env_3, await TemporaryFile.from(fileContentStream), true);
|
|
302
|
+
const buffer = await readFile(tmpFile.path);
|
|
303
|
+
const base64Data = buffer.toString('base64');
|
|
304
|
+
const dataUrl = `data:${document.mimeType};base64,${base64Data}`;
|
|
154
305
|
if (isNull(document.typeId)) {
|
|
155
306
|
throw new Error(`Document ${document.id} has no type`);
|
|
156
307
|
}
|
|
@@ -177,39 +328,31 @@ let DocumentManagementAiService = DocumentManagementAiService_1 = class Document
|
|
|
177
328
|
? {}
|
|
178
329
|
: { documentProperties: object(fromEntries(propertiesSchemaEntries)) }),
|
|
179
330
|
});
|
|
180
|
-
const
|
|
331
|
+
const tagLabels = existingTags.map((tag) => tag.label);
|
|
181
332
|
this.#logger.trace(`Extracting document ${document.id}`);
|
|
182
|
-
const
|
|
183
|
-
model:
|
|
184
|
-
|
|
333
|
+
const result = await this.#genkit.generate(genkitGenerationOptions({
|
|
334
|
+
model: this.#extractModel,
|
|
335
|
+
output: { schema: generationSchema },
|
|
336
|
+
config: {
|
|
185
337
|
maxOutputTokens: 2048,
|
|
186
|
-
|
|
187
|
-
topP: 0.5,
|
|
188
|
-
topK: 16,
|
|
189
|
-
thinkingBudget: 0,
|
|
338
|
+
thinkingConfig: { thinkingBudget: 0 },
|
|
190
339
|
},
|
|
191
|
-
|
|
192
|
-
|
|
340
|
+
system: extractSystemPrompt,
|
|
341
|
+
prompt: [
|
|
342
|
+
{ media: { url: dataUrl } },
|
|
193
343
|
{
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
{ file: filePart.file },
|
|
197
|
-
{
|
|
198
|
-
text: `<context>
|
|
199
|
-
${JSON.stringify(context, null, 2)}
|
|
200
|
-
</context>
|
|
201
|
-
Extrahiere den Inhalt des Dokuments in das angegebenen JSON Schema.
|
|
344
|
+
text: `
|
|
345
|
+
${formatData({ existingTags: tagLabels })}
|
|
202
346
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
Erstelle bis zu 5 Tags. Verwende vorhandene Tags, wenn sie passen. Erstelle neue Tags, wenn es keine passenden gibt.
|
|
206
|
-
Vermeide es, den Titel oder Untertitel als Tag zu verwenden.
|
|
207
|
-
Antworte auf deutsch.`,
|
|
208
|
-
},
|
|
209
|
-
],
|
|
347
|
+
${extractUserPrompt}
|
|
348
|
+
`.trim(),
|
|
210
349
|
},
|
|
211
350
|
],
|
|
212
|
-
});
|
|
351
|
+
}));
|
|
352
|
+
if (isNull(result.output)) {
|
|
353
|
+
throw new Error(`AI returned null output for document extraction "${document.id}".`);
|
|
354
|
+
}
|
|
355
|
+
const extraction = result.output;
|
|
213
356
|
const filteredDocumentTags = extraction.documentTags.filter((tag) => (tag != extraction.documentTitle) && (tag != extraction.documentSubtitle));
|
|
214
357
|
const date = isNotNull(extraction.documentDate) ? tryAiOutputDateObjectToNumericDate(extraction.documentDate) : null;
|
|
215
358
|
const parsedProperties = isUndefined(extraction.documentProperties)
|
|
@@ -238,14 +381,14 @@ Antworte auf deutsch.`,
|
|
|
238
381
|
properties: parsedProperties,
|
|
239
382
|
};
|
|
240
383
|
}
|
|
241
|
-
catch (
|
|
242
|
-
|
|
243
|
-
|
|
384
|
+
catch (e_3) {
|
|
385
|
+
env_3.error = e_3;
|
|
386
|
+
env_3.hasError = true;
|
|
244
387
|
}
|
|
245
388
|
finally {
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
248
|
-
await
|
|
389
|
+
const result_3 = __disposeResources(env_3);
|
|
390
|
+
if (result_3)
|
|
391
|
+
await result_3;
|
|
249
392
|
}
|
|
250
393
|
}
|
|
251
394
|
async findSuitableCollectionsForDocument(document, collectionIds) {
|
|
@@ -261,41 +404,36 @@ Antworte auf deutsch.`,
|
|
|
261
404
|
}));
|
|
262
405
|
const documentTagLabels = documentTags.map((tag) => tag.label);
|
|
263
406
|
const propertyEntries = documentProperties.map((property) => [property.label, property.value]);
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
tags: (documentTagLabels.length > 0) ? documentTagLabels : undefined,
|
|
271
|
-
properties: fromEntries(propertyEntries),
|
|
272
|
-
},
|
|
273
|
-
collections,
|
|
407
|
+
const documentData = {
|
|
408
|
+
title: document.title ?? null,
|
|
409
|
+
subtitle: document.subtitle ?? null,
|
|
410
|
+
date: isNotNull(document.date) ? numericDateToDateTime(document.date).toISODate() : null,
|
|
411
|
+
tags: (documentTagLabels.length > 0) ? documentTagLabels : null,
|
|
412
|
+
summary: document.summary ?? null,
|
|
274
413
|
};
|
|
275
|
-
const result = await this.#
|
|
276
|
-
model:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
414
|
+
const result = await this.#genkit.generate(genkitGenerationOptions({
|
|
415
|
+
model: this.#assignModel,
|
|
416
|
+
output: { schema: object({ collectionIds: array(string()) }) },
|
|
417
|
+
config: {
|
|
418
|
+
maxOutputTokens: 512,
|
|
419
|
+
thinkingConfig: {
|
|
420
|
+
thinkingBudget: 0,
|
|
421
|
+
},
|
|
283
422
|
},
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
${JSON.stringify(context, null, 2)}
|
|
291
|
-
</context>
|
|
423
|
+
system: assignCollectionSystemPrompt,
|
|
424
|
+
prompt: [{
|
|
425
|
+
text: `
|
|
426
|
+
${formatData({ document: documentData, documentProperties: fromEntries(propertyEntries) })}
|
|
427
|
+
|
|
428
|
+
${formatData({ collections })}
|
|
292
429
|
|
|
293
|
-
|
|
294
|
-
},
|
|
295
|
-
],
|
|
430
|
+
${assignCollectionUserPrompt}`,
|
|
296
431
|
}],
|
|
297
|
-
});
|
|
298
|
-
|
|
432
|
+
}));
|
|
433
|
+
if (isNull(result.output)) {
|
|
434
|
+
throw new Error(`AI returned null output for collection assignment "${document.id}".`);
|
|
435
|
+
}
|
|
436
|
+
return result.output.collectionIds;
|
|
299
437
|
}
|
|
300
438
|
async findSuitableRequestForDocument(document, collectionIds) {
|
|
301
439
|
const session = this.#documentPropertyRepository.session;
|
|
@@ -322,44 +460,40 @@ Ordne das Dokument unter "document" einer oder mehreren passenden Collection unt
|
|
|
322
460
|
const requests = openRequestsWithoutDocument.map((request) => ({
|
|
323
461
|
id: request.id,
|
|
324
462
|
collections: request.collectionIds.map((collectionId) => assertDefinedPass(collectionNamesMap[collectionId]).name),
|
|
325
|
-
comment: request.comment ??
|
|
463
|
+
comment: request.comment ?? null,
|
|
326
464
|
}));
|
|
327
465
|
const propertyEntries = documentProperties.map((property) => [property.label, property.value]);
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
tags: (documentTagLabels.length > 0) ? documentTagLabels : undefined,
|
|
335
|
-
properties: fromEntries(propertyEntries),
|
|
336
|
-
},
|
|
337
|
-
requests,
|
|
466
|
+
const documentData = {
|
|
467
|
+
title: document.title ?? null,
|
|
468
|
+
subtitle: document.subtitle ?? null,
|
|
469
|
+
date: isNotNull(document.date) ? numericDateToDateTime(document.date).toISODate() : null,
|
|
470
|
+
tags: (documentTagLabels.length > 0) ? documentTagLabels : null,
|
|
471
|
+
summary: document.summary ?? null,
|
|
338
472
|
};
|
|
339
|
-
const result = await this.#
|
|
340
|
-
model:
|
|
341
|
-
|
|
342
|
-
maxOutputTokens:
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
thinkingBudget: 0,
|
|
473
|
+
const result = await this.#genkit.generate(genkitGenerationOptions({
|
|
474
|
+
model: this.#assignModel,
|
|
475
|
+
config: {
|
|
476
|
+
maxOutputTokens: 128,
|
|
477
|
+
thinkingConfig: {
|
|
478
|
+
thinkingBudget: 0,
|
|
479
|
+
},
|
|
347
480
|
},
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
${
|
|
355
|
-
</context>
|
|
481
|
+
output: { schema: object({ requestId: nullable(string()) }) },
|
|
482
|
+
system: assignRequestSystemPrompt,
|
|
483
|
+
prompt: [{
|
|
484
|
+
text: `
|
|
485
|
+
${formatData({ document: documentData, documentProperties: fromEntries(propertyEntries) })}
|
|
486
|
+
|
|
487
|
+
${formatData({ requests })}
|
|
356
488
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
],
|
|
489
|
+
${assignRequestUserPrompt}
|
|
490
|
+
`.trim(),
|
|
360
491
|
}],
|
|
361
|
-
});
|
|
362
|
-
|
|
492
|
+
}));
|
|
493
|
+
if (isNull(result.output)) {
|
|
494
|
+
throw new Error(`AI returned null output for request assignment "${document.id}".`);
|
|
495
|
+
}
|
|
496
|
+
return result.output.requestId;
|
|
363
497
|
}
|
|
364
498
|
};
|
|
365
499
|
DocumentManagementAiService = DocumentManagementAiService_1 = __decorate([
|
|
@@ -17,7 +17,7 @@ import { objectKeys } from '../../../utils/object/object.js';
|
|
|
17
17
|
import { readableStreamFromPromise } from '../../../utils/stream/from-promise.js';
|
|
18
18
|
import { tryIgnoreLogAsync } from '../../../utils/try-ignore.js';
|
|
19
19
|
import { isDefined, isNotReadableStream, isNotUint8Array, isUndefined } from '../../../utils/type-guards.js';
|
|
20
|
-
import { Document, DocumentApproval, DocumentAssignmentScope, DocumentAssignmentTask,
|
|
20
|
+
import { Document, DocumentApproval, DocumentAssignmentScope, DocumentAssignmentTask, DocumentWorkflowStep } from '../../models/index.js';
|
|
21
21
|
import { DocumentCollectionService } from './document-collection.service.js';
|
|
22
22
|
import { DocumentFileService } from './document-file.service.js';
|
|
23
23
|
import { DocumentManagementObservationService } from './document-management-observation.service.js';
|
|
@@ -33,7 +33,6 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
|
|
|
33
33
|
#workflowService = injectTransactional(DocumentWorkflowService);
|
|
34
34
|
#documentPropertyService = injectTransactional(DocumentPropertyService);
|
|
35
35
|
#collectionService = injectTransactional(DocumentCollectionService);
|
|
36
|
-
#documentTypeRepository = injectRepository(DocumentType);
|
|
37
36
|
#documentAssignmentTaskRepository = injectRepository(DocumentAssignmentTask);
|
|
38
37
|
#documentAssignmentScopeRepository = injectRepository(DocumentAssignmentScope);
|
|
39
38
|
#observationService = inject(DocumentManagementObservationService);
|
|
@@ -5,6 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import '../../polyfills.js';
|
|
8
|
+
import { configureGenkit } from '../../ai/genkit/module.js';
|
|
8
9
|
import { configureAiService } from '../../ai/index.js';
|
|
9
10
|
import { MockApiRequestTokenProvider } from '../../api/server/api-request-token.provider.js';
|
|
10
11
|
import { configureApiServer } from '../../api/server/module.js';
|
|
@@ -88,6 +89,11 @@ async function bootstrap() {
|
|
|
88
89
|
configurePostgresQueue();
|
|
89
90
|
configureLocalMessageBus();
|
|
90
91
|
configureDefaultSignalsImplementation();
|
|
92
|
+
configureGenkit({
|
|
93
|
+
gemini: {
|
|
94
|
+
apiKey: config.ai.apiKey,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
91
97
|
configureOrm({
|
|
92
98
|
connection: {
|
|
93
99
|
host: config.database.host,
|
package/json-path/json-path.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isIterable } from '../utils/iterable-helpers/is-iterable.js';
|
|
2
2
|
import { assertDefinedPass, isArray, isDefined, isString, isSymbol, isUndefined } from '../utils/type-guards.js';
|
|
3
3
|
const numberPattern = /^\d+$/u;
|
|
4
|
-
const parsePattern = /(?:(?:^|\.)(?<dot>[^.[]+))|(?<root>^\$)|\[(?:(?:'(?<bracket>.+?)')|(?<index>\d+)|(?:Symbol\((?<symbol>.*)\)))\]|(?<error>.+?)/ug;
|
|
4
|
+
const parsePattern = /(?:(?:^|\.)(?<dot>[^.[]+))|(?<root>^\$)|\[(?:(?:['"](?<bracket>.+?)['"])|(?<index>\d+)|(?:Symbol\((?<symbol>.*)\)))\]|(?<error>.+?)/ug;
|
|
5
5
|
export class JsonPath {
|
|
6
6
|
_options;
|
|
7
7
|
_path;
|
package/orm/server/repository.js
CHANGED
|
@@ -11,12 +11,11 @@ import { NotFoundError } from '../../errors/not-found.error.js';
|
|
|
11
11
|
import { Singleton } from '../../injector/decorators.js';
|
|
12
12
|
import { inject, injectArgument } from '../../injector/inject.js';
|
|
13
13
|
import { afterResolve, resolveArgumentType } from '../../injector/interfaces.js';
|
|
14
|
-
import { Schema } from '../../schema/schema.js';
|
|
15
14
|
import { distinct, toArray } from '../../utils/array/array.js';
|
|
16
15
|
import { mapAsync } from '../../utils/async-iterable-helpers/map.js';
|
|
17
16
|
import { toArrayAsync } from '../../utils/async-iterable-helpers/to-array.js';
|
|
18
17
|
import { importSymmetricKey } from '../../utils/cryptography.js';
|
|
19
|
-
import {
|
|
18
|
+
import { assignDeep, fromEntries, objectEntries } from '../../utils/object/object.js';
|
|
20
19
|
import { toSnakeCase } from '../../utils/string/index.js';
|
|
21
20
|
import { cancelableTimeout } from '../../utils/timing.js';
|
|
22
21
|
import { tryIgnoreAsync } from '../../utils/try-ignore.js';
|
|
@@ -1073,14 +1072,13 @@ let EntityRepository = class EntityRepository extends Transactional {
|
|
|
1073
1072
|
return await toArrayAsync(mapAsync(columns, async (column) => await this._mapToEntity(column, transformContext)));
|
|
1074
1073
|
}
|
|
1075
1074
|
async _mapToEntity(columns, transformContext) {
|
|
1076
|
-
const
|
|
1075
|
+
const entity = new this.type();
|
|
1077
1076
|
for (const def of this.#columnDefinitions) {
|
|
1078
1077
|
const rawValue = columns[def.name];
|
|
1079
1078
|
const transformed = await def.fromDatabase(rawValue, transformContext);
|
|
1080
|
-
|
|
1079
|
+
assignDeep(entity, def.objectPath, transformed);
|
|
1081
1080
|
}
|
|
1082
|
-
|
|
1083
|
-
return Schema.parse(this.type, obj);
|
|
1081
|
+
return entity;
|
|
1084
1082
|
}
|
|
1085
1083
|
async _mapManyToColumns(objects, transformContext) {
|
|
1086
1084
|
return await toArrayAsync(mapAsync(objects, async (obj) => await this._mapToColumns(obj, transformContext)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.66",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"./supports": "./supports.js",
|
|
38
38
|
"./tokens": "./tokens.js",
|
|
39
39
|
"./ai": "./ai/index.js",
|
|
40
|
+
"./ai/genkit": "./ai/genkit/index.js",
|
|
40
41
|
"./api": "./api/index.js",
|
|
41
42
|
"./api/client": "./api/client/index.js",
|
|
42
43
|
"./api/server": "./api/server/index.js",
|
|
@@ -138,18 +139,21 @@
|
|
|
138
139
|
"type-fest": "^5.3"
|
|
139
140
|
},
|
|
140
141
|
"peerDependencies": {
|
|
142
|
+
"@genkit-ai/google-genai": "^1.25",
|
|
141
143
|
"@google-cloud/storage": "^7.18",
|
|
142
|
-
"@google/genai": "^1.
|
|
144
|
+
"@google/genai": "^1.32",
|
|
145
|
+
"@toon-format/toon": "^2.1.0",
|
|
143
146
|
"@tstdl/angular": "^0.93",
|
|
144
147
|
"@zxcvbn-ts/core": "^3.0",
|
|
145
148
|
"@zxcvbn-ts/language-common": "^3.0",
|
|
146
149
|
"@zxcvbn-ts/language-de": "^3.0",
|
|
147
150
|
"@zxcvbn-ts/language-en": "^3.0",
|
|
148
|
-
"drizzle-orm": "^0.
|
|
151
|
+
"drizzle-orm": "^0.45",
|
|
149
152
|
"file-type": "^21.1",
|
|
153
|
+
"genkit": "^1.25",
|
|
150
154
|
"handlebars": "^4.7",
|
|
151
155
|
"minio": "^8.0",
|
|
152
|
-
"mjml": "^4.
|
|
156
|
+
"mjml": "^4.18",
|
|
153
157
|
"nodemailer": "^7.0",
|
|
154
158
|
"pg": "^8.16",
|
|
155
159
|
"playwright": "^1.57",
|
|
@@ -158,7 +162,7 @@
|
|
|
158
162
|
"sharp": "^0.34",
|
|
159
163
|
"undici": "^7.16",
|
|
160
164
|
"urlpattern-polyfill": "^10.1",
|
|
161
|
-
"zod": "^
|
|
165
|
+
"zod": "^3.25"
|
|
162
166
|
},
|
|
163
167
|
"peerDependenciesMeta": {
|
|
164
168
|
"@tstdl/angular": {
|
|
@@ -182,7 +186,7 @@
|
|
|
182
186
|
"typedoc-plugin-markdown": "4.9",
|
|
183
187
|
"typedoc-plugin-missing-exports": "4.1",
|
|
184
188
|
"typescript": "5.9",
|
|
185
|
-
"typescript-eslint": "8.
|
|
189
|
+
"typescript-eslint": "8.49"
|
|
186
190
|
},
|
|
187
191
|
"overrides": {
|
|
188
192
|
"drizzle-kit": {
|