aiex-cli 0.0.6-beta.4 → 0.0.7-beta.1
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 +1 -0
- package/dist/cli.mjs +14050 -14044
- package/dist/{completions-Bh0DOngr.mjs → completions-DPALvX54.mjs} +1 -1
- package/dist/core/schema-sqlite/migrate-helper.mjs +1 -1
- package/dist/{doctor-collector-CrUoGloP.mjs → generate-drizzle-schema-D0o_j12G.mjs} +709 -709
- package/dist/index.d.mts +58 -58
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/core/schema-sqlite/migration-name.ts +1 -14
|
@@ -9,83 +9,9 @@ import { readFile, writeFile } from "jsonfile";
|
|
|
9
9
|
import Conf from "conf";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
-
//#region src/core/doctor.ts
|
|
13
|
-
function buildDoctorDiagnostics(input) {
|
|
14
|
-
return {
|
|
15
|
-
cli: {
|
|
16
|
-
name: input.pkg.name,
|
|
17
|
-
version: input.pkg.version,
|
|
18
|
-
executable: input.executable
|
|
19
|
-
},
|
|
20
|
-
runtime: {
|
|
21
|
-
node: input.node,
|
|
22
|
-
platform: input.platform,
|
|
23
|
-
arch: input.arch,
|
|
24
|
-
shell: input.shell,
|
|
25
|
-
packageManager: input.packageManager
|
|
26
|
-
},
|
|
27
|
-
system: {
|
|
28
|
-
os: `${input.osType} ${input.osRelease}`,
|
|
29
|
-
cwd: input.cwd
|
|
30
|
-
},
|
|
31
|
-
imageOcr: { ...input.imageOcr },
|
|
32
|
-
config: {
|
|
33
|
-
path: input.configPath,
|
|
34
|
-
keys: [...input.configStoreKeys].sort()
|
|
35
|
-
},
|
|
36
|
-
project: { ...input.project }
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
function formatDoctorDiagnosticsJson(d) {
|
|
40
|
-
return `${JSON.stringify(d, null, 2)}\n`;
|
|
41
|
-
}
|
|
42
|
-
function doctorDiagnosticsTableRows(d) {
|
|
43
|
-
const rows = [
|
|
44
|
-
["node", d.runtime.node],
|
|
45
|
-
["platform", d.runtime.platform],
|
|
46
|
-
["arch", d.runtime.arch],
|
|
47
|
-
["shell", d.runtime.shell],
|
|
48
|
-
["packageManager", d.runtime.packageManager],
|
|
49
|
-
["os", d.system.os],
|
|
50
|
-
["cwd", d.system.cwd],
|
|
51
|
-
["config", d.config.path],
|
|
52
|
-
["configKeys", d.config.keys.join(", ")]
|
|
53
|
-
];
|
|
54
|
-
const p = d.project;
|
|
55
|
-
rows.push(["aiexDir", p.aiexDir]);
|
|
56
|
-
rows.push(["dirExists", String(p.dirExists)]);
|
|
57
|
-
rows.push(["schemaFiles", `${p.schemaCount} (${p.schemaFiles.join(", ") || "none"})`]);
|
|
58
|
-
rows.push(["aiConfig", String(p.aiConfig)]);
|
|
59
|
-
rows.push(["aiApiKeySet", String(p.aiApiKeySet)]);
|
|
60
|
-
rows.push(["aiModels", p.aiModelCount ? p.aiModels.join(", ") : "none"]);
|
|
61
|
-
rows.push(["aiVisionModels", String(p.aiVisionModelCount)]);
|
|
62
|
-
rows.push(["aiStructuredOutputModels", String(p.aiStructuredOutputModelCount)]);
|
|
63
|
-
rows.push(["aiProvider", p.aiProvider ?? "none"]);
|
|
64
|
-
rows.push(["aiConnectionOk", p.aiConnectionOk === null ? "not tested" : String(p.aiConnectionOk)]);
|
|
65
|
-
rows.push(["pdfConverter", p.pdfConverter ?? "none"]);
|
|
66
|
-
rows.push(["pdfConverterOk", p.pdfConverterOk === null ? "not tested" : String(p.pdfConverterOk)]);
|
|
67
|
-
if (p.pdfConverterError) rows.push(["pdfConverterError", p.pdfConverterError]);
|
|
68
|
-
rows.push(["imageOcrPlatform", String(d.imageOcr.platformSupported)]);
|
|
69
|
-
rows.push(["imageOcrDependency", String(d.imageOcr.dependencyLoaded)]);
|
|
70
|
-
rows.push(["imageOcrOk", d.imageOcr.ocrOk === null ? "not tested" : String(d.imageOcr.ocrOk)]);
|
|
71
|
-
if (d.imageOcr.imagePath) rows.push(["imageOcrImage", d.imageOcr.imagePath]);
|
|
72
|
-
if (d.imageOcr.recognizedText) rows.push(["imageOcrText", d.imageOcr.recognizedText]);
|
|
73
|
-
if (typeof d.imageOcr.confidence === "number") rows.push(["imageOcrConfidence", `${(d.imageOcr.confidence * 100).toFixed(1)}%`]);
|
|
74
|
-
if (d.imageOcr.error) rows.push(["imageOcrError", d.imageOcr.error]);
|
|
75
|
-
rows.push(["hasDatabase", String(p.hasDatabase)]);
|
|
76
|
-
rows.push(["databaseTablesOk", p.databaseTablesOk === null ? "not tested" : String(p.databaseTablesOk)]);
|
|
77
|
-
if (p.missingDatabaseTables.length) rows.push(["missingDatabaseTables", p.missingDatabaseTables.join(", ")]);
|
|
78
|
-
rows.push(["migrations", String(p.migrationCount)]);
|
|
79
|
-
rows.push(["schemaValid", `${p.schemaValidCount}/${p.schemaCount}`]);
|
|
80
|
-
for (const invalid of p.invalidSchemas) rows.push(["invalidSchema", `${invalid.file}: ${invalid.error}`]);
|
|
81
|
-
for (const err of p.errors) rows.push(["error", err]);
|
|
82
|
-
return rows;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
//#endregion
|
|
86
12
|
//#region package.json
|
|
87
13
|
var name = "aiex-cli";
|
|
88
|
-
var version = "0.0.
|
|
14
|
+
var version = "0.0.7-beta.1";
|
|
89
15
|
var description = "JSON Schema → SQLite with AI-powered data extraction";
|
|
90
16
|
var package_default = {
|
|
91
17
|
name,
|
|
@@ -219,110 +145,7 @@ function seedConfig(config = createConfig()) {
|
|
|
219
145
|
}
|
|
220
146
|
|
|
221
147
|
//#endregion
|
|
222
|
-
//#region src/
|
|
223
|
-
const ModelCapabilitiesSchema = z.object({
|
|
224
|
-
vision: z.boolean(),
|
|
225
|
-
structuredOutput: z.boolean(),
|
|
226
|
-
maxTokens: z.number().int().positive().optional(),
|
|
227
|
-
maxOutputTokens: z.number().int().positive().optional()
|
|
228
|
-
});
|
|
229
|
-
const AIModelConfigSchema = z.object({
|
|
230
|
-
name: z.string().min(1),
|
|
231
|
-
capabilities: ModelCapabilitiesSchema
|
|
232
|
-
});
|
|
233
|
-
const AIProviderConfigSchema = z.object({
|
|
234
|
-
baseURL: z.string().min(1),
|
|
235
|
-
apiKey: z.string(),
|
|
236
|
-
models: z.array(AIModelConfigSchema).min(1),
|
|
237
|
-
timeout: z.number().int().positive().default(300).optional()
|
|
238
|
-
});
|
|
239
|
-
const PromptConfigSchema = z.object({
|
|
240
|
-
systemTemplate: z.string().min(1),
|
|
241
|
-
userTemplate: z.string().min(1)
|
|
242
|
-
});
|
|
243
|
-
const ExtractionConfigSchema = z.object({ outputDir: z.string().min(1) });
|
|
244
|
-
const ImageOcrFallbackSchema = z.preprocess((value) => [
|
|
245
|
-
"auto",
|
|
246
|
-
"off",
|
|
247
|
-
"local"
|
|
248
|
-
].includes(String(value)) ? "localAuto" : value, z.literal("localAuto").default("localAuto").optional());
|
|
249
|
-
const ImageOcrConfigSchema = z.object({
|
|
250
|
-
ocrFallback: ImageOcrFallbackSchema,
|
|
251
|
-
ocrLanguages: z.string().min(1).optional(),
|
|
252
|
-
ocrMinConfidence: z.number().min(0).max(1).optional()
|
|
253
|
-
});
|
|
254
|
-
const ExternalPdfConverterConfigSchema = z.object({
|
|
255
|
-
command: z.string().min(1),
|
|
256
|
-
args: z.array(z.string()).min(1).refine((args) => args.some((arg) => arg.includes("{input}")), { message: "args must contain {input} template variable" }),
|
|
257
|
-
outputFile: z.string().min(1).optional(),
|
|
258
|
-
timeout: z.number().int().positive().default(600).optional(),
|
|
259
|
-
fallbackToUnpdf: z.boolean().optional()
|
|
260
|
-
});
|
|
261
|
-
const MineruApiPdfConverterConfigSchema = z.object({
|
|
262
|
-
token: z.string(),
|
|
263
|
-
baseURL: z.string().url().optional(),
|
|
264
|
-
modelVersion: z.string().optional(),
|
|
265
|
-
isOcr: z.boolean().optional(),
|
|
266
|
-
enableFormula: z.boolean().optional(),
|
|
267
|
-
enableTable: z.boolean().optional()
|
|
268
|
-
});
|
|
269
|
-
const PdfConfigSchema = z.preprocess((value) => {
|
|
270
|
-
if (!value || typeof value !== "object") return value;
|
|
271
|
-
const config = { ...value };
|
|
272
|
-
if (config.converter === "markitdown" && config.markitdown) {
|
|
273
|
-
config.converter = "external";
|
|
274
|
-
config.external = config.markitdown;
|
|
275
|
-
} else if (config.converter === "marker" && config.marker) {
|
|
276
|
-
config.converter = "external";
|
|
277
|
-
config.external = config.marker;
|
|
278
|
-
} else if (config.converter === "markitdown" || config.converter === "marker") config.converter = "unpdf";
|
|
279
|
-
delete config.markitdown;
|
|
280
|
-
delete config.marker;
|
|
281
|
-
return config;
|
|
282
|
-
}, z.object({
|
|
283
|
-
converter: z.enum([
|
|
284
|
-
"unpdf",
|
|
285
|
-
"mineru",
|
|
286
|
-
"mineru_api",
|
|
287
|
-
"external"
|
|
288
|
-
]),
|
|
289
|
-
mineru: ExternalPdfConverterConfigSchema.optional(),
|
|
290
|
-
mineruApi: MineruApiPdfConverterConfigSchema.optional(),
|
|
291
|
-
external: ExternalPdfConverterConfigSchema.optional()
|
|
292
|
-
}));
|
|
293
|
-
const LangfuseConfigSchema = z.object({
|
|
294
|
-
publicKey: z.string(),
|
|
295
|
-
secretKey: z.string(),
|
|
296
|
-
host: z.string().optional()
|
|
297
|
-
});
|
|
298
|
-
const NotionSchemaConfigSchema = z.object({
|
|
299
|
-
databaseId: z.string(),
|
|
300
|
-
titleProperty: z.string().optional(),
|
|
301
|
-
fieldMap: z.record(z.string()).optional()
|
|
302
|
-
});
|
|
303
|
-
const NotionConfigSchema = z.object({
|
|
304
|
-
enabled: z.boolean(),
|
|
305
|
-
token: z.string(),
|
|
306
|
-
schemas: z.record(NotionSchemaConfigSchema).default({})
|
|
307
|
-
});
|
|
308
|
-
const WebhookConfigSchema = z.object({
|
|
309
|
-
enabled: z.boolean(),
|
|
310
|
-
url: z.string(),
|
|
311
|
-
secret: z.string().optional()
|
|
312
|
-
});
|
|
313
|
-
const AIConfigSchema = z.object({
|
|
314
|
-
provider: AIProviderConfigSchema,
|
|
315
|
-
prompt: PromptConfigSchema,
|
|
316
|
-
extraction: ExtractionConfigSchema,
|
|
317
|
-
image: ImageOcrConfigSchema.optional(),
|
|
318
|
-
pdf: PdfConfigSchema.optional(),
|
|
319
|
-
langfuse: LangfuseConfigSchema.optional(),
|
|
320
|
-
notion: NotionConfigSchema.optional(),
|
|
321
|
-
webhook: WebhookConfigSchema.optional()
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
//#endregion
|
|
325
|
-
//#region src/core/ai-extraction/types.ts
|
|
148
|
+
//#region src/domain/ai/types.ts
|
|
326
149
|
const PLACEHOLDER_SCHEMA = "{schema}";
|
|
327
150
|
const PLACEHOLDER_TEXT = "{text}";
|
|
328
151
|
const DEFAULT_MODELS = [{
|
|
@@ -395,63 +218,507 @@ const DEFAULT_AI_CONFIG = {
|
|
|
395
218
|
};
|
|
396
219
|
|
|
397
220
|
//#endregion
|
|
398
|
-
//#region src/
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
221
|
+
//#region src/domain/doctor/diagnostics.ts
|
|
222
|
+
function buildDoctorDiagnostics(input) {
|
|
223
|
+
return {
|
|
224
|
+
cli: {
|
|
225
|
+
name: input.pkg.name,
|
|
226
|
+
version: input.pkg.version,
|
|
227
|
+
executable: input.executable
|
|
228
|
+
},
|
|
229
|
+
runtime: {
|
|
230
|
+
node: input.node,
|
|
231
|
+
platform: input.platform,
|
|
232
|
+
arch: input.arch,
|
|
233
|
+
shell: input.shell,
|
|
234
|
+
packageManager: input.packageManager
|
|
235
|
+
},
|
|
236
|
+
system: {
|
|
237
|
+
os: `${input.osType} ${input.osRelease}`,
|
|
238
|
+
cwd: input.cwd
|
|
239
|
+
},
|
|
240
|
+
imageOcr: { ...input.imageOcr },
|
|
241
|
+
config: {
|
|
242
|
+
path: input.configPath,
|
|
243
|
+
keys: [...input.configStoreKeys].sort()
|
|
244
|
+
},
|
|
245
|
+
project: { ...input.project }
|
|
246
|
+
};
|
|
421
247
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const gitignorePath = path.join(projectRoot, GITIGNORE_FILE);
|
|
425
|
-
try {
|
|
426
|
-
const content = await fs.readFile(gitignorePath, "utf-8");
|
|
427
|
-
if (content.split("\n").some((line) => line.trim() === fileName || line.includes(".aiex/"))) return;
|
|
428
|
-
const newContent = content.endsWith("\n") ? `${content}${fileName}\n` : `${content}\n${fileName}\n`;
|
|
429
|
-
await fs.writeFile(gitignorePath, newContent);
|
|
430
|
-
} catch {
|
|
431
|
-
await fs.writeFile(gitignorePath, `${fileName}\n`);
|
|
432
|
-
}
|
|
248
|
+
function formatDoctorDiagnosticsJson(d) {
|
|
249
|
+
return `${JSON.stringify(d, null, 2)}\n`;
|
|
433
250
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
251
|
+
function doctorDiagnosticsTableRows(d) {
|
|
252
|
+
const rows = [
|
|
253
|
+
["node", d.runtime.node],
|
|
254
|
+
["platform", d.runtime.platform],
|
|
255
|
+
["arch", d.runtime.arch],
|
|
256
|
+
["shell", d.runtime.shell],
|
|
257
|
+
["packageManager", d.runtime.packageManager],
|
|
258
|
+
["os", d.system.os],
|
|
259
|
+
["cwd", d.system.cwd],
|
|
260
|
+
["config", d.config.path],
|
|
261
|
+
["configKeys", d.config.keys.join(", ")]
|
|
262
|
+
];
|
|
263
|
+
const p = d.project;
|
|
264
|
+
rows.push(["aiexDir", p.aiexDir]);
|
|
265
|
+
rows.push(["dirExists", String(p.dirExists)]);
|
|
266
|
+
rows.push(["schemaFiles", `${p.schemaCount} (${p.schemaFiles.join(", ") || "none"})`]);
|
|
267
|
+
rows.push(["aiConfig", String(p.aiConfig)]);
|
|
268
|
+
rows.push(["aiApiKeySet", String(p.aiApiKeySet)]);
|
|
269
|
+
rows.push(["aiModels", p.aiModelCount ? p.aiModels.join(", ") : "none"]);
|
|
270
|
+
rows.push(["aiVisionModels", String(p.aiVisionModelCount)]);
|
|
271
|
+
rows.push(["aiStructuredOutputModels", String(p.aiStructuredOutputModelCount)]);
|
|
272
|
+
rows.push(["aiProvider", p.aiProvider ?? "none"]);
|
|
273
|
+
rows.push(["aiConnectionOk", p.aiConnectionOk === null ? "not tested" : String(p.aiConnectionOk)]);
|
|
274
|
+
rows.push(["pdfConverter", p.pdfConverter ?? "none"]);
|
|
275
|
+
rows.push(["pdfConverterOk", p.pdfConverterOk === null ? "not tested" : String(p.pdfConverterOk)]);
|
|
276
|
+
if (p.pdfConverterError) rows.push(["pdfConverterError", p.pdfConverterError]);
|
|
277
|
+
rows.push(["imageOcrPlatform", String(d.imageOcr.platformSupported)]);
|
|
278
|
+
rows.push(["imageOcrDependency", String(d.imageOcr.dependencyLoaded)]);
|
|
279
|
+
rows.push(["imageOcrOk", d.imageOcr.ocrOk === null ? "not tested" : String(d.imageOcr.ocrOk)]);
|
|
280
|
+
if (d.imageOcr.imagePath) rows.push(["imageOcrImage", d.imageOcr.imagePath]);
|
|
281
|
+
if (d.imageOcr.recognizedText) rows.push(["imageOcrText", d.imageOcr.recognizedText]);
|
|
282
|
+
if (typeof d.imageOcr.confidence === "number") rows.push(["imageOcrConfidence", `${(d.imageOcr.confidence * 100).toFixed(1)}%`]);
|
|
283
|
+
if (d.imageOcr.error) rows.push(["imageOcrError", d.imageOcr.error]);
|
|
284
|
+
rows.push(["hasDatabase", String(p.hasDatabase)]);
|
|
285
|
+
rows.push(["databaseTablesOk", p.databaseTablesOk === null ? "not tested" : String(p.databaseTablesOk)]);
|
|
286
|
+
if (p.missingDatabaseTables.length) rows.push(["missingDatabaseTables", p.missingDatabaseTables.join(", ")]);
|
|
287
|
+
rows.push(["migrations", String(p.migrationCount)]);
|
|
288
|
+
rows.push(["schemaValid", `${p.schemaValidCount}/${p.schemaCount}`]);
|
|
289
|
+
for (const invalid of p.invalidSchemas) rows.push(["invalidSchema", `${invalid.file}: ${invalid.error}`]);
|
|
290
|
+
for (const err of p.errors) rows.push(["error", err]);
|
|
291
|
+
return rows;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
//#endregion
|
|
295
|
+
//#region src/domain/schema/parser.ts
|
|
296
|
+
function toSnakeCase(str) {
|
|
297
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
298
|
+
}
|
|
299
|
+
function mapPropertyToColumn(name$1, property, isRequired) {
|
|
300
|
+
const snakeName = toSnakeCase(name$1);
|
|
301
|
+
let drizzleType;
|
|
302
|
+
const isPrimary = property.primary ?? false;
|
|
303
|
+
const isAutoIncrement = property.autoIncrement ?? false;
|
|
304
|
+
switch (property.type) {
|
|
305
|
+
case "string": {
|
|
306
|
+
const format = property.format;
|
|
307
|
+
if (format === "date-time" || property.drizzle?.mode === "timestamp") drizzleType = `integer({ mode: 'timestamp' })`;
|
|
308
|
+
else if (format === "json" || property.drizzle?.mode === "json") drizzleType = `text({ mode: 'json' })`;
|
|
309
|
+
else drizzleType = "text()";
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
case "integer": {
|
|
313
|
+
const mode = property.drizzle?.mode;
|
|
314
|
+
if (mode === "boolean") drizzleType = `integer({ mode: 'boolean' })`;
|
|
315
|
+
else if (mode === "timestamp" || mode === "timestamp_ms") drizzleType = `integer({ mode: '${mode}' })`;
|
|
316
|
+
else if (mode === "bigint") drizzleType = `integer({ mode: 'bigint' })`;
|
|
317
|
+
else drizzleType = "integer()";
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case "number":
|
|
321
|
+
drizzleType = "real()";
|
|
322
|
+
break;
|
|
323
|
+
case "boolean":
|
|
324
|
+
drizzleType = `integer({ mode: 'boolean' })`;
|
|
325
|
+
break;
|
|
326
|
+
case "object":
|
|
327
|
+
case "array":
|
|
328
|
+
drizzleType = `text({ mode: 'json' })`;
|
|
329
|
+
break;
|
|
330
|
+
case "null":
|
|
331
|
+
drizzleType = "text()";
|
|
332
|
+
break;
|
|
333
|
+
default: drizzleType = "text()";
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
name: snakeName,
|
|
337
|
+
drizzleType,
|
|
338
|
+
isPrimary,
|
|
339
|
+
isAutoIncrement,
|
|
340
|
+
isNullable: !isRequired && !isPrimary,
|
|
341
|
+
isUnique: property.unique ?? false,
|
|
342
|
+
defaultValue: property.default !== void 0 ? JSON.stringify(property.default) : void 0,
|
|
343
|
+
isForeignKey: property.foreignKey !== void 0,
|
|
344
|
+
foreignKeyRef: property.foreignKey ?? void 0
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function parseObjectToTable(schema, _warnings) {
|
|
348
|
+
const tableName = schema.table.name;
|
|
349
|
+
const columns = [];
|
|
350
|
+
const requiredFields = new Set(schema.required ?? []);
|
|
351
|
+
const autoColumns = /* @__PURE__ */ new Set();
|
|
352
|
+
if (schema.table.timestamps) {
|
|
353
|
+
autoColumns.add("created_at");
|
|
354
|
+
autoColumns.add("updated_at");
|
|
355
|
+
}
|
|
356
|
+
if (schema.table.softDelete) autoColumns.add("deleted_at");
|
|
357
|
+
for (const [propName, prop] of Object.entries(schema.properties)) {
|
|
358
|
+
if (prop.nested?.enabled && (prop.type === "object" || prop.type === "array")) continue;
|
|
359
|
+
if (prop.type === "array" && prop.items?.nested?.enabled) continue;
|
|
360
|
+
const snakeName = toSnakeCase(propName);
|
|
361
|
+
if (autoColumns.has(snakeName)) continue;
|
|
362
|
+
const column = mapPropertyToColumn(propName, prop, requiredFields.has(propName));
|
|
363
|
+
columns.push(column);
|
|
364
|
+
}
|
|
365
|
+
if (schema.table.timestamps) {
|
|
366
|
+
columns.push({
|
|
367
|
+
name: "created_at",
|
|
368
|
+
drizzleType: `integer({ mode: 'timestamp' })`,
|
|
369
|
+
isPrimary: false,
|
|
370
|
+
isAutoIncrement: false,
|
|
371
|
+
isNullable: false,
|
|
372
|
+
isUnique: false,
|
|
373
|
+
defaultValue: void 0
|
|
374
|
+
});
|
|
375
|
+
columns.push({
|
|
376
|
+
name: "updated_at",
|
|
377
|
+
drizzleType: `integer({ mode: 'timestamp' })`,
|
|
378
|
+
isPrimary: false,
|
|
379
|
+
isAutoIncrement: false,
|
|
380
|
+
isNullable: false,
|
|
381
|
+
isUnique: false,
|
|
382
|
+
defaultValue: void 0
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
if (schema.table.softDelete) columns.push({
|
|
386
|
+
name: "deleted_at",
|
|
387
|
+
drizzleType: `integer({ mode: 'timestamp' })`,
|
|
388
|
+
isPrimary: false,
|
|
389
|
+
isAutoIncrement: false,
|
|
390
|
+
isNullable: true,
|
|
391
|
+
isUnique: false,
|
|
392
|
+
defaultValue: void 0
|
|
393
|
+
});
|
|
394
|
+
return {
|
|
395
|
+
name: tableName,
|
|
396
|
+
columns
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function parseNestedObject(propName, property, parentTableName, warnings) {
|
|
400
|
+
const nestedTableName = `${parentTableName}_${toSnakeCase(propName)}`;
|
|
401
|
+
const columns = [];
|
|
402
|
+
const relationType = property.nested?.relation === "has-many" ? "has-many" : "has-one";
|
|
403
|
+
columns.push({
|
|
404
|
+
name: "id",
|
|
405
|
+
drizzleType: "integer()",
|
|
406
|
+
isPrimary: true,
|
|
407
|
+
isAutoIncrement: true,
|
|
408
|
+
isNullable: false,
|
|
409
|
+
isUnique: false,
|
|
410
|
+
defaultValue: void 0
|
|
411
|
+
});
|
|
412
|
+
columns.push({
|
|
413
|
+
name: `${parentTableName}_id`,
|
|
414
|
+
drizzleType: "integer()",
|
|
415
|
+
isPrimary: false,
|
|
416
|
+
isAutoIncrement: false,
|
|
417
|
+
isNullable: false,
|
|
418
|
+
isUnique: false,
|
|
419
|
+
defaultValue: void 0,
|
|
420
|
+
isForeignKey: true,
|
|
421
|
+
foreignKeyRef: {
|
|
422
|
+
table: parentTableName,
|
|
423
|
+
column: "id"
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
if (property.type === "object" && property.properties) for (const [childName, childProp] of Object.entries(property.properties)) {
|
|
427
|
+
if (childProp.nested?.enabled) {
|
|
428
|
+
warnings.push(`Nested property "${childName}" inside "${nestedTableName}" is skipped — only one level of nesting is supported. Remove nested.enabled or use drizzle.mode: 'json' instead.`);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const column = mapPropertyToColumn(childName, childProp, false);
|
|
432
|
+
columns.push(column);
|
|
433
|
+
}
|
|
434
|
+
const relation = {
|
|
435
|
+
fromTable: nestedTableName,
|
|
436
|
+
fromColumn: `${parentTableName}_id`,
|
|
437
|
+
toTable: parentTableName,
|
|
438
|
+
toColumn: "id",
|
|
439
|
+
name: parentTableName
|
|
440
|
+
};
|
|
441
|
+
const reverseRelation = {
|
|
442
|
+
type: relationType,
|
|
443
|
+
fromTable: parentTableName,
|
|
444
|
+
toTable: nestedTableName,
|
|
445
|
+
name: toSnakeCase(propName)
|
|
446
|
+
};
|
|
447
|
+
return {
|
|
448
|
+
table: {
|
|
449
|
+
name: nestedTableName,
|
|
450
|
+
columns
|
|
451
|
+
},
|
|
452
|
+
relation,
|
|
453
|
+
reverseRelation
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function parseJsonSchema(schema) {
|
|
457
|
+
const tables = [];
|
|
458
|
+
const relations = [];
|
|
459
|
+
const reverseRelations = [];
|
|
460
|
+
const warnings = [];
|
|
461
|
+
const mainTable = parseObjectToTable(schema, warnings);
|
|
462
|
+
tables.push(mainTable);
|
|
463
|
+
for (const [propName, prop] of Object.entries(schema.properties)) if (prop.type === "object" && prop.nested?.enabled) {
|
|
464
|
+
const nested = parseNestedObject(propName, prop, mainTable.name, warnings);
|
|
465
|
+
tables.push(nested.table);
|
|
466
|
+
relations.push(nested.relation);
|
|
467
|
+
reverseRelations.push(nested.reverseRelation);
|
|
468
|
+
} else if (prop.type === "array" && prop.items?.nested?.enabled && prop.items?.type === "object" && prop.items.properties) {
|
|
469
|
+
const nested = parseNestedObject(propName, prop.items, mainTable.name, warnings);
|
|
470
|
+
tables.push(nested.table);
|
|
471
|
+
relations.push(nested.relation);
|
|
472
|
+
reverseRelations.push(nested.reverseRelation);
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
tables,
|
|
476
|
+
relations,
|
|
477
|
+
reverseRelations,
|
|
478
|
+
warnings
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/domain/schema/schemas.ts
|
|
484
|
+
const DrizzleModeSchema = z.enum([
|
|
485
|
+
"json",
|
|
486
|
+
"timestamp",
|
|
487
|
+
"timestamp_ms",
|
|
488
|
+
"boolean",
|
|
489
|
+
"bigint"
|
|
490
|
+
]);
|
|
491
|
+
const DrizzleExtensionSchema = z.object({
|
|
492
|
+
mode: DrizzleModeSchema.optional(),
|
|
493
|
+
customType: z.string().optional()
|
|
494
|
+
}).optional();
|
|
495
|
+
const NestedConfigSchema = z.object({
|
|
496
|
+
enabled: z.literal(true),
|
|
497
|
+
relation: z.enum(["has-one", "has-many"])
|
|
498
|
+
});
|
|
499
|
+
const ForeignKeyRefSchema = z.object({
|
|
500
|
+
table: z.string().min(1),
|
|
501
|
+
column: z.string().min(1)
|
|
502
|
+
});
|
|
503
|
+
const JsonSchemaPropertySchema = z.lazy(() => z.object({
|
|
504
|
+
description: z.string().optional(),
|
|
505
|
+
type: z.enum([
|
|
506
|
+
"string",
|
|
507
|
+
"integer",
|
|
508
|
+
"number",
|
|
509
|
+
"boolean",
|
|
510
|
+
"object",
|
|
511
|
+
"array",
|
|
512
|
+
"null"
|
|
513
|
+
]),
|
|
514
|
+
format: z.string().optional(),
|
|
515
|
+
primary: z.boolean().optional(),
|
|
516
|
+
autoIncrement: z.boolean().optional(),
|
|
517
|
+
unique: z.boolean().optional(),
|
|
518
|
+
default: z.unknown().optional(),
|
|
519
|
+
maxLength: z.number().int().positive().optional(),
|
|
520
|
+
minLength: z.number().int().nonnegative().optional(),
|
|
521
|
+
minimum: z.number().optional(),
|
|
522
|
+
maximum: z.number().optional(),
|
|
523
|
+
drizzle: DrizzleExtensionSchema,
|
|
524
|
+
nested: NestedConfigSchema.optional(),
|
|
525
|
+
foreignKey: ForeignKeyRefSchema.optional(),
|
|
526
|
+
properties: z.record(z.string(), JsonSchemaPropertySchema).optional(),
|
|
527
|
+
items: JsonSchemaPropertySchema.optional(),
|
|
528
|
+
required: z.array(z.string()).optional()
|
|
529
|
+
}));
|
|
530
|
+
const TableConfigSchema = z.object({
|
|
531
|
+
name: z.string().min(1).regex(/^[a-z][a-z0-9_]*$/, "Table name must be snake_case (lowercase letters, digits, underscores)"),
|
|
532
|
+
timestamps: z.boolean().optional(),
|
|
533
|
+
softDelete: z.boolean().optional()
|
|
534
|
+
});
|
|
535
|
+
const ExamplePairSchema = z.object({
|
|
536
|
+
text: z.string().min(1),
|
|
537
|
+
output: z.record(z.string(), z.unknown())
|
|
538
|
+
});
|
|
539
|
+
const JsonSchemaDefinitionSchema = z.object({
|
|
540
|
+
$schema: z.string().optional(),
|
|
541
|
+
title: z.string().min(1),
|
|
542
|
+
description: z.string().optional(),
|
|
543
|
+
type: z.literal("object"),
|
|
544
|
+
table: TableConfigSchema,
|
|
545
|
+
properties: z.record(z.string(), JsonSchemaPropertySchema),
|
|
546
|
+
required: z.array(z.string()).optional(),
|
|
547
|
+
examples: z.array(ExamplePairSchema).optional()
|
|
548
|
+
}).refine((schema) => Object.keys(schema.properties).length >= 1, {
|
|
549
|
+
message: "At least one property is required",
|
|
550
|
+
path: ["properties"]
|
|
551
|
+
}).refine((schema) => !schema.required || schema.required.every((r) => r in schema.properties), {
|
|
552
|
+
message: "All required fields must be defined in properties",
|
|
553
|
+
path: ["required"]
|
|
554
|
+
}).refine((schema) => {
|
|
555
|
+
return !Object.values(schema.properties).some((p) => p.primary) || Object.values(schema.properties).filter((p) => p.primary).length === 1;
|
|
556
|
+
}, {
|
|
557
|
+
message: "Only one primary key is allowed per table",
|
|
558
|
+
path: ["properties"]
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/domain/ai/schemas.ts
|
|
563
|
+
const ModelCapabilitiesSchema = z.object({
|
|
564
|
+
vision: z.boolean(),
|
|
565
|
+
structuredOutput: z.boolean(),
|
|
566
|
+
maxTokens: z.number().int().positive().optional(),
|
|
567
|
+
maxOutputTokens: z.number().int().positive().optional()
|
|
568
|
+
});
|
|
569
|
+
const AIModelConfigSchema = z.object({
|
|
570
|
+
name: z.string().min(1),
|
|
571
|
+
capabilities: ModelCapabilitiesSchema
|
|
572
|
+
});
|
|
573
|
+
const AIProviderConfigSchema = z.object({
|
|
574
|
+
baseURL: z.string().min(1),
|
|
575
|
+
apiKey: z.string(),
|
|
576
|
+
models: z.array(AIModelConfigSchema).min(1),
|
|
577
|
+
timeout: z.number().int().positive().default(300).optional()
|
|
578
|
+
});
|
|
579
|
+
const PromptConfigSchema = z.object({
|
|
580
|
+
systemTemplate: z.string().min(1),
|
|
581
|
+
userTemplate: z.string().min(1)
|
|
582
|
+
});
|
|
583
|
+
const ExtractionConfigSchema = z.object({ outputDir: z.string().min(1) });
|
|
584
|
+
const ImageOcrFallbackSchema = z.preprocess((value) => [
|
|
585
|
+
"auto",
|
|
586
|
+
"off",
|
|
587
|
+
"local"
|
|
588
|
+
].includes(String(value)) ? "localAuto" : value, z.literal("localAuto").default("localAuto").optional());
|
|
589
|
+
const ImageOcrConfigSchema = z.object({
|
|
590
|
+
ocrFallback: ImageOcrFallbackSchema,
|
|
591
|
+
ocrLanguages: z.string().min(1).optional(),
|
|
592
|
+
ocrMinConfidence: z.number().min(0).max(1).optional()
|
|
593
|
+
});
|
|
594
|
+
const ExternalPdfConverterConfigSchema = z.object({
|
|
595
|
+
command: z.string().min(1),
|
|
596
|
+
args: z.array(z.string()).min(1).refine((args) => args.some((arg) => arg.includes("{input}")), { message: "args must contain {input} template variable" }),
|
|
597
|
+
outputFile: z.string().min(1).optional(),
|
|
598
|
+
timeout: z.number().int().positive().default(600).optional(),
|
|
599
|
+
fallbackToUnpdf: z.boolean().optional()
|
|
600
|
+
});
|
|
601
|
+
const MineruApiPdfConverterConfigSchema = z.object({
|
|
602
|
+
token: z.string(),
|
|
603
|
+
baseURL: z.string().url().optional(),
|
|
604
|
+
modelVersion: z.string().optional(),
|
|
605
|
+
isOcr: z.boolean().optional(),
|
|
606
|
+
enableFormula: z.boolean().optional(),
|
|
607
|
+
enableTable: z.boolean().optional()
|
|
608
|
+
});
|
|
609
|
+
const PdfConfigSchema = z.preprocess((value) => {
|
|
610
|
+
if (!value || typeof value !== "object") return value;
|
|
611
|
+
const config = { ...value };
|
|
612
|
+
if (config.converter === "markitdown" && config.markitdown) {
|
|
613
|
+
config.converter = "external";
|
|
614
|
+
config.external = config.markitdown;
|
|
615
|
+
} else if (config.converter === "marker" && config.marker) {
|
|
616
|
+
config.converter = "external";
|
|
617
|
+
config.external = config.marker;
|
|
618
|
+
} else if (config.converter === "markitdown" || config.converter === "marker") config.converter = "unpdf";
|
|
619
|
+
delete config.markitdown;
|
|
620
|
+
delete config.marker;
|
|
621
|
+
return config;
|
|
622
|
+
}, z.object({
|
|
623
|
+
converter: z.enum([
|
|
624
|
+
"unpdf",
|
|
625
|
+
"mineru",
|
|
626
|
+
"mineru_api",
|
|
627
|
+
"external"
|
|
628
|
+
]),
|
|
629
|
+
mineru: ExternalPdfConverterConfigSchema.optional(),
|
|
630
|
+
mineruApi: MineruApiPdfConverterConfigSchema.optional(),
|
|
631
|
+
external: ExternalPdfConverterConfigSchema.optional()
|
|
632
|
+
}));
|
|
633
|
+
const LangfuseConfigSchema = z.object({
|
|
634
|
+
publicKey: z.string(),
|
|
635
|
+
secretKey: z.string(),
|
|
636
|
+
host: z.string().optional()
|
|
637
|
+
});
|
|
638
|
+
const NotionSchemaConfigSchema = z.object({
|
|
639
|
+
databaseId: z.string(),
|
|
640
|
+
titleProperty: z.string().optional(),
|
|
641
|
+
fieldMap: z.record(z.string()).optional()
|
|
642
|
+
});
|
|
643
|
+
const NotionConfigSchema = z.object({
|
|
644
|
+
enabled: z.boolean(),
|
|
645
|
+
token: z.string(),
|
|
646
|
+
schemas: z.record(NotionSchemaConfigSchema).default({})
|
|
647
|
+
});
|
|
648
|
+
const WebhookConfigSchema = z.object({
|
|
649
|
+
enabled: z.boolean(),
|
|
650
|
+
url: z.string(),
|
|
651
|
+
secret: z.string().optional()
|
|
652
|
+
});
|
|
653
|
+
const AIConfigSchema = z.object({
|
|
654
|
+
provider: AIProviderConfigSchema,
|
|
655
|
+
prompt: PromptConfigSchema,
|
|
656
|
+
extraction: ExtractionConfigSchema,
|
|
657
|
+
image: ImageOcrConfigSchema.optional(),
|
|
658
|
+
pdf: PdfConfigSchema.optional(),
|
|
659
|
+
langfuse: LangfuseConfigSchema.optional(),
|
|
660
|
+
notion: NotionConfigSchema.optional(),
|
|
661
|
+
webhook: WebhookConfigSchema.optional()
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
//#endregion
|
|
665
|
+
//#region src/infrastructure/ai/ai-config-store.ts
|
|
666
|
+
const CONFIG_FILE_NAME = "ai-config.json";
|
|
667
|
+
const GITIGNORE_FILE = ".gitignore";
|
|
668
|
+
async function readAIConfig(aiexDir) {
|
|
669
|
+
const configPath = path.join(aiexDir, CONFIG_FILE_NAME);
|
|
670
|
+
try {
|
|
671
|
+
const parsed = await readFile(configPath);
|
|
672
|
+
return AIConfigSchema.parse(parsed);
|
|
673
|
+
} catch {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async function writeAIConfig(aiexDir, config) {
|
|
678
|
+
const configPath = path.join(aiexDir, CONFIG_FILE_NAME);
|
|
679
|
+
await fs.mkdir(aiexDir, { recursive: true });
|
|
680
|
+
await writeFile(configPath, config, {
|
|
681
|
+
spaces: 2,
|
|
682
|
+
EOL: "\n"
|
|
683
|
+
});
|
|
684
|
+
await addToGitignore(aiexDir, CONFIG_FILE_NAME);
|
|
685
|
+
}
|
|
686
|
+
function getDefaultAIConfig() {
|
|
687
|
+
return structuredClone(DEFAULT_AI_CONFIG);
|
|
688
|
+
}
|
|
689
|
+
async function addToGitignore(aiexDir, fileName) {
|
|
690
|
+
const projectRoot = path.dirname(aiexDir);
|
|
691
|
+
const gitignorePath = path.join(projectRoot, GITIGNORE_FILE);
|
|
692
|
+
try {
|
|
693
|
+
const content = await fs.readFile(gitignorePath, "utf-8");
|
|
694
|
+
if (content.split("\n").some((line) => line.trim() === fileName || line.includes(".aiex/"))) return;
|
|
695
|
+
const newContent = content.endsWith("\n") ? `${content}${fileName}\n` : `${content}\n${fileName}\n`;
|
|
696
|
+
await fs.writeFile(gitignorePath, newContent);
|
|
697
|
+
} catch {
|
|
698
|
+
await fs.writeFile(gitignorePath, `${fileName}\n`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
//#endregion
|
|
703
|
+
//#region src/locales/en.ts
|
|
704
|
+
const en = {
|
|
705
|
+
common: {
|
|
706
|
+
done: "Done!",
|
|
707
|
+
failed: "Failed!",
|
|
708
|
+
cancelled: "Cancelled",
|
|
709
|
+
save: "Save",
|
|
710
|
+
cancel: "Cancel",
|
|
711
|
+
delete: "Delete",
|
|
712
|
+
close: "Close",
|
|
713
|
+
loading: "Loading...",
|
|
714
|
+
unknownError: "Unknown error"
|
|
715
|
+
},
|
|
716
|
+
cli: { description: "JSON Schema → SQLite with AI-powered data extraction" },
|
|
717
|
+
command: {
|
|
718
|
+
web: {
|
|
719
|
+
description: "Start visual JSON Schema editor",
|
|
720
|
+
args: { port: "Port to listen on" },
|
|
721
|
+
starting: "Starting web server...",
|
|
455
722
|
serverRunning: "Server running at {{url}}",
|
|
456
723
|
schemaDir: "Schema directory: {{path}}",
|
|
457
724
|
pressCtrlC: "Press Ctrl+C to stop",
|
|
@@ -901,499 +1168,169 @@ Extraction requirements:
|
|
|
901
1168
|
deleteExample: "Delete Example",
|
|
902
1169
|
inputPlaceholder: "Paste raw unstructured text example here...",
|
|
903
1170
|
outputPlaceholder: "{ ... }",
|
|
904
|
-
mustStartLowercase: "Must start with lowercase letter",
|
|
905
|
-
onlyLowercaseAllow: "Only lowercase letters, digits, and underscores allowed",
|
|
906
|
-
lessThan: "-"
|
|
907
|
-
},
|
|
908
|
-
extraction: { status: {
|
|
909
|
-
succeeded: "succeeded",
|
|
910
|
-
failed: "failed",
|
|
911
|
-
stale: "stale",
|
|
912
|
-
running: "running"
|
|
913
|
-
} }
|
|
914
|
-
};
|
|
915
|
-
|
|
916
|
-
//#endregion
|
|
917
|
-
//#region src/locales/i18n.ts
|
|
918
|
-
let tFn = null;
|
|
919
|
-
let i18nInstance = null;
|
|
920
|
-
let initPromise = null;
|
|
921
|
-
function detectLocale() {
|
|
922
|
-
if ((process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || "").startsWith("zh")) return "zh-CN";
|
|
923
|
-
return "en";
|
|
924
|
-
}
|
|
925
|
-
function resolveFallback(key, options) {
|
|
926
|
-
let value = en;
|
|
927
|
-
const parts = key.split(".");
|
|
928
|
-
for (const part of parts) if (value && typeof value === "object" && part in value) value = value[part];
|
|
929
|
-
else return key;
|
|
930
|
-
if (typeof value !== "string") return key;
|
|
931
|
-
if (options) return Object.entries(options).reduce((str, [k, v]) => str.replace(`{{${k}}}`, String(v)), value);
|
|
932
|
-
return value;
|
|
933
|
-
}
|
|
934
|
-
async function initI18n(lng) {
|
|
935
|
-
if (i18nInstance) return;
|
|
936
|
-
if (initPromise) return initPromise;
|
|
937
|
-
initPromise = (async () => {
|
|
938
|
-
const { createInstance } = await import("i18next");
|
|
939
|
-
const instance = createInstance();
|
|
940
|
-
i18nInstance = instance;
|
|
941
|
-
const locale = lng ?? detectLocale();
|
|
942
|
-
await instance.init({
|
|
943
|
-
lng: locale,
|
|
944
|
-
fallbackLng: "en",
|
|
945
|
-
resources: {
|
|
946
|
-
"en": { translation: en },
|
|
947
|
-
"zh-CN": { translation: await import("./zh-CN-wEUNhuHM.mjs").then((m) => m.zhCN) }
|
|
948
|
-
},
|
|
949
|
-
interpolation: { escapeValue: false },
|
|
950
|
-
returnNull: false
|
|
951
|
-
});
|
|
952
|
-
tFn = instance.t.bind(instance);
|
|
953
|
-
})();
|
|
954
|
-
return initPromise;
|
|
955
|
-
}
|
|
956
|
-
function t(key, options) {
|
|
957
|
-
if (tFn) return tFn(key, options);
|
|
958
|
-
return resolveFallback(key, options);
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
//#endregion
|
|
962
|
-
//#region src/core/image-ocr/index.ts
|
|
963
|
-
const DEFAULT_OCR_LANGUAGES = "en-US, zh-Hans";
|
|
964
|
-
const SELF_CHECK_EXPECTED_TEXT = "AIEX";
|
|
965
|
-
const defaultRuntime = {
|
|
966
|
-
platform: process.platform,
|
|
967
|
-
async loadLocalOcr() {
|
|
968
|
-
return await import("@napi-rs/system-ocr");
|
|
969
|
-
}
|
|
970
|
-
};
|
|
971
|
-
function imageOcrMode(config) {
|
|
972
|
-
return config?.ocrFallback ?? "localAuto";
|
|
973
|
-
}
|
|
974
|
-
function hasVisionModel(aiConfig, modelOverride) {
|
|
975
|
-
if (modelOverride) return modelOverride.capabilities.vision;
|
|
976
|
-
return aiConfig?.provider.models.some((model) => model.capabilities.vision) ?? true;
|
|
977
|
-
}
|
|
978
|
-
function shouldUseImageOcrFallback(aiConfig, modelOverride, runtime = defaultRuntime) {
|
|
979
|
-
if (hasVisionModel(aiConfig, modelOverride)) return false;
|
|
980
|
-
if (imageOcrMode(aiConfig?.image) === "localAuto") return isLocalOcrPlatform(runtime.platform);
|
|
981
|
-
return isLocalOcrPlatform(runtime.platform);
|
|
982
|
-
}
|
|
983
|
-
function isLocalOcrPlatform(platform) {
|
|
984
|
-
return platform === "darwin" || platform === "win32";
|
|
985
|
-
}
|
|
986
|
-
function parseOcrLanguages(languages) {
|
|
987
|
-
return (languages ?? DEFAULT_OCR_LANGUAGES).split(",").map((language) => language.trim()).filter(Boolean);
|
|
988
|
-
}
|
|
989
|
-
async function recognizeImageText(imagePath, config, runtime = defaultRuntime) {
|
|
990
|
-
if (!isLocalOcrPlatform(runtime.platform)) throw new Error(t("errors.ocr.platformUnsupported", { platform: runtime.platform }));
|
|
991
|
-
let localOcr;
|
|
992
|
-
try {
|
|
993
|
-
localOcr = await runtime.loadLocalOcr();
|
|
994
|
-
} catch (error) {
|
|
995
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
996
|
-
throw new Error(t("errors.ocr.unavailable", { error: message }));
|
|
997
|
-
}
|
|
998
|
-
const result = await localOcr.recognize(imagePath, localOcr.OcrAccuracy.Accurate, parseOcrLanguages(config?.ocrLanguages));
|
|
999
|
-
const text = result.text.trim();
|
|
1000
|
-
if (!text) throw new Error(t("errors.ocr.noText"));
|
|
1001
|
-
const confidence = result.confidence;
|
|
1002
|
-
const minConfidence = config?.ocrMinConfidence ?? 0;
|
|
1003
|
-
if (confidence < minConfidence) throw new Error(t("errors.ocr.lowConfidence", {
|
|
1004
|
-
confidence: (confidence * 100).toFixed(1),
|
|
1005
|
-
min: (minConfidence * 100).toFixed(1)
|
|
1006
|
-
}));
|
|
1007
|
-
return {
|
|
1008
|
-
text,
|
|
1009
|
-
confidence
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
function normalizeOcrText(text) {
|
|
1013
|
-
return text.replace(/\s+/g, "").trim().toUpperCase();
|
|
1014
|
-
}
|
|
1015
|
-
async function checkImageOcrAvailability(imagePath, runtime = defaultRuntime) {
|
|
1016
|
-
if (!isLocalOcrPlatform(runtime.platform)) return {
|
|
1017
|
-
platformSupported: false,
|
|
1018
|
-
dependencyLoaded: false,
|
|
1019
|
-
ocrOk: null,
|
|
1020
|
-
imagePath,
|
|
1021
|
-
error: t("errors.ocr.platformUnsupported", { platform: runtime.platform })
|
|
1022
|
-
};
|
|
1023
|
-
let localOcr;
|
|
1024
|
-
try {
|
|
1025
|
-
localOcr = await runtime.loadLocalOcr();
|
|
1026
|
-
} catch (error) {
|
|
1027
|
-
return {
|
|
1028
|
-
platformSupported: true,
|
|
1029
|
-
dependencyLoaded: false,
|
|
1030
|
-
ocrOk: null,
|
|
1031
|
-
imagePath,
|
|
1032
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
if (!imagePath) return {
|
|
1036
|
-
platformSupported: true,
|
|
1037
|
-
dependencyLoaded: true,
|
|
1038
|
-
ocrOk: null,
|
|
1039
|
-
error: "No OCR self-check image was found."
|
|
1040
|
-
};
|
|
1041
|
-
try {
|
|
1042
|
-
const result = await localOcr.recognize(imagePath, localOcr.OcrAccuracy.Accurate, ["en-US"]);
|
|
1043
|
-
const recognizedText = result.text.trim();
|
|
1044
|
-
const ocrOk = normalizeOcrText(recognizedText).includes(SELF_CHECK_EXPECTED_TEXT);
|
|
1045
|
-
return {
|
|
1046
|
-
platformSupported: true,
|
|
1047
|
-
dependencyLoaded: true,
|
|
1048
|
-
ocrOk,
|
|
1049
|
-
imagePath,
|
|
1050
|
-
recognizedText,
|
|
1051
|
-
confidence: result.confidence,
|
|
1052
|
-
error: ocrOk ? void 0 : `Expected OCR text "${SELF_CHECK_EXPECTED_TEXT}" was not recognized.`
|
|
1053
|
-
};
|
|
1054
|
-
} catch (error) {
|
|
1055
|
-
return {
|
|
1056
|
-
platformSupported: true,
|
|
1057
|
-
dependencyLoaded: true,
|
|
1058
|
-
ocrOk: false,
|
|
1059
|
-
imagePath,
|
|
1060
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1171
|
+
mustStartLowercase: "Must start with lowercase letter",
|
|
1172
|
+
onlyLowercaseAllow: "Only lowercase letters, digits, and underscores allowed",
|
|
1173
|
+
lessThan: "-"
|
|
1174
|
+
},
|
|
1175
|
+
extraction: { status: {
|
|
1176
|
+
succeeded: "succeeded",
|
|
1177
|
+
failed: "failed",
|
|
1178
|
+
stale: "stale",
|
|
1179
|
+
running: "running"
|
|
1180
|
+
} }
|
|
1181
|
+
};
|
|
1064
1182
|
|
|
1065
1183
|
//#endregion
|
|
1066
|
-
//#region src/
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
if (
|
|
1072
|
-
|
|
1073
|
-
if (column.defaultValue !== void 0) def += `.default(${column.defaultValue})`;
|
|
1074
|
-
if (column.isForeignKey && column.foreignKeyRef) def += `.references(() => ${column.foreignKeyRef.table}.${column.foreignKeyRef.column})`;
|
|
1075
|
-
return def;
|
|
1184
|
+
//#region src/locales/i18n.ts
|
|
1185
|
+
let tFn = null;
|
|
1186
|
+
let i18nInstance = null;
|
|
1187
|
+
let initPromise = null;
|
|
1188
|
+
function detectLocale() {
|
|
1189
|
+
if ((process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || "").startsWith("zh")) return "zh-CN";
|
|
1190
|
+
return "en";
|
|
1076
1191
|
}
|
|
1077
|
-
function
|
|
1078
|
-
|
|
1079
|
-
|
|
1192
|
+
function resolveFallback(key, options) {
|
|
1193
|
+
let value = en;
|
|
1194
|
+
const parts = key.split(".");
|
|
1195
|
+
for (const part of parts) if (value && typeof value === "object" && part in value) value = value[part];
|
|
1196
|
+
else return key;
|
|
1197
|
+
if (typeof value !== "string") return key;
|
|
1198
|
+
if (options) return Object.entries(options).reduce((str, [k, v]) => str.replace(`{{${k}}}`, String(v)), value);
|
|
1199
|
+
return value;
|
|
1080
1200
|
}
|
|
1081
|
-
function
|
|
1082
|
-
if (
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
const imports = [];
|
|
1103
|
-
if (needsOne) imports.push("one");
|
|
1104
|
-
if (needsMany) imports.push("many");
|
|
1105
|
-
const importStr = imports.join(", ");
|
|
1106
|
-
const relDefs = [];
|
|
1107
|
-
for (const rel of childRels) relDefs.push(` ${rel.name}: one(${rel.toTable}, {\n fields: [${rel.fromTable}.${rel.fromColumn}],\n references: [${rel.toTable}.${rel.toColumn}],\n })`);
|
|
1108
|
-
for (const rel of parentRels) if (rel.type === "has-many") relDefs.push(` ${rel.name}: many(${rel.toTable})`);
|
|
1109
|
-
else relDefs.push(` ${rel.name}: one(${rel.toTable})`);
|
|
1110
|
-
definitions.push(`export const ${tableName}Relations = relations(${tableName}, ({ ${importStr} }) => ({\n${relDefs.join(",\n")}\n}))`);
|
|
1111
|
-
}
|
|
1112
|
-
return definitions.join("\n\n");
|
|
1201
|
+
async function initI18n(lng) {
|
|
1202
|
+
if (i18nInstance) return;
|
|
1203
|
+
if (initPromise) return initPromise;
|
|
1204
|
+
initPromise = (async () => {
|
|
1205
|
+
const { createInstance } = await import("i18next");
|
|
1206
|
+
const instance = createInstance();
|
|
1207
|
+
i18nInstance = instance;
|
|
1208
|
+
const locale = lng ?? detectLocale();
|
|
1209
|
+
await instance.init({
|
|
1210
|
+
lng: locale,
|
|
1211
|
+
fallbackLng: "en",
|
|
1212
|
+
resources: {
|
|
1213
|
+
"en": { translation: en },
|
|
1214
|
+
"zh-CN": { translation: await import("./zh-CN-wEUNhuHM.mjs").then((m) => m.zhCN) }
|
|
1215
|
+
},
|
|
1216
|
+
interpolation: { escapeValue: false },
|
|
1217
|
+
returnNull: false
|
|
1218
|
+
});
|
|
1219
|
+
tFn = instance.t.bind(instance);
|
|
1220
|
+
})();
|
|
1221
|
+
return initPromise;
|
|
1113
1222
|
}
|
|
1114
|
-
function
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const relationDefs = generateRelationDefinitions(result.relations, result.reverseRelations);
|
|
1118
|
-
const parts = [
|
|
1119
|
-
imports,
|
|
1120
|
-
"",
|
|
1121
|
-
tableDefs
|
|
1122
|
-
];
|
|
1123
|
-
if (relationDefs) parts.push("", relationDefs);
|
|
1124
|
-
parts.push("");
|
|
1125
|
-
return parts.join("\n");
|
|
1223
|
+
function t(key, options) {
|
|
1224
|
+
if (tFn) return tFn(key, options);
|
|
1225
|
+
return resolveFallback(key, options);
|
|
1126
1226
|
}
|
|
1127
1227
|
|
|
1128
1228
|
//#endregion
|
|
1129
|
-
//#region src/
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
const isPrimary = property.primary ?? false;
|
|
1137
|
-
const isAutoIncrement = property.autoIncrement ?? false;
|
|
1138
|
-
switch (property.type) {
|
|
1139
|
-
case "string": {
|
|
1140
|
-
const format = property.format;
|
|
1141
|
-
if (format === "date-time" || property.drizzle?.mode === "timestamp") drizzleType = `integer({ mode: 'timestamp' })`;
|
|
1142
|
-
else if (format === "json" || property.drizzle?.mode === "json") drizzleType = `text({ mode: 'json' })`;
|
|
1143
|
-
else drizzleType = "text()";
|
|
1144
|
-
break;
|
|
1145
|
-
}
|
|
1146
|
-
case "integer": {
|
|
1147
|
-
const mode = property.drizzle?.mode;
|
|
1148
|
-
if (mode === "boolean") drizzleType = `integer({ mode: 'boolean' })`;
|
|
1149
|
-
else if (mode === "timestamp" || mode === "timestamp_ms") drizzleType = `integer({ mode: '${mode}' })`;
|
|
1150
|
-
else if (mode === "bigint") drizzleType = `integer({ mode: 'bigint' })`;
|
|
1151
|
-
else drizzleType = "integer()";
|
|
1152
|
-
break;
|
|
1153
|
-
}
|
|
1154
|
-
case "number":
|
|
1155
|
-
drizzleType = "real()";
|
|
1156
|
-
break;
|
|
1157
|
-
case "boolean":
|
|
1158
|
-
drizzleType = `integer({ mode: 'boolean' })`;
|
|
1159
|
-
break;
|
|
1160
|
-
case "object":
|
|
1161
|
-
case "array":
|
|
1162
|
-
drizzleType = `text({ mode: 'json' })`;
|
|
1163
|
-
break;
|
|
1164
|
-
case "null":
|
|
1165
|
-
drizzleType = "text()";
|
|
1166
|
-
break;
|
|
1167
|
-
default: drizzleType = "text()";
|
|
1229
|
+
//#region src/infrastructure/ocr/system-ocr.ts
|
|
1230
|
+
const DEFAULT_OCR_LANGUAGES = "en-US, zh-Hans";
|
|
1231
|
+
const SELF_CHECK_EXPECTED_TEXT = "AIEX";
|
|
1232
|
+
const defaultRuntime = {
|
|
1233
|
+
platform: process.platform,
|
|
1234
|
+
async loadLocalOcr() {
|
|
1235
|
+
return await import("@napi-rs/system-ocr");
|
|
1168
1236
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
isPrimary,
|
|
1173
|
-
isAutoIncrement,
|
|
1174
|
-
isNullable: !isRequired && !isPrimary,
|
|
1175
|
-
isUnique: property.unique ?? false,
|
|
1176
|
-
defaultValue: property.default !== void 0 ? JSON.stringify(property.default) : void 0,
|
|
1177
|
-
isForeignKey: property.foreignKey !== void 0,
|
|
1178
|
-
foreignKeyRef: property.foreignKey ?? void 0
|
|
1179
|
-
};
|
|
1237
|
+
};
|
|
1238
|
+
function imageOcrMode(config) {
|
|
1239
|
+
return config?.ocrFallback ?? "localAuto";
|
|
1180
1240
|
}
|
|
1181
|
-
function
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const requiredFields = new Set(schema.required ?? []);
|
|
1185
|
-
const autoColumns = /* @__PURE__ */ new Set();
|
|
1186
|
-
if (schema.table.timestamps) {
|
|
1187
|
-
autoColumns.add("created_at");
|
|
1188
|
-
autoColumns.add("updated_at");
|
|
1189
|
-
}
|
|
1190
|
-
if (schema.table.softDelete) autoColumns.add("deleted_at");
|
|
1191
|
-
for (const [propName, prop] of Object.entries(schema.properties)) {
|
|
1192
|
-
if (prop.nested?.enabled && (prop.type === "object" || prop.type === "array")) continue;
|
|
1193
|
-
if (prop.type === "array" && prop.items?.nested?.enabled) continue;
|
|
1194
|
-
const snakeName = toSnakeCase(propName);
|
|
1195
|
-
if (autoColumns.has(snakeName)) continue;
|
|
1196
|
-
const column = mapPropertyToColumn(propName, prop, requiredFields.has(propName));
|
|
1197
|
-
columns.push(column);
|
|
1198
|
-
}
|
|
1199
|
-
if (schema.table.timestamps) {
|
|
1200
|
-
columns.push({
|
|
1201
|
-
name: "created_at",
|
|
1202
|
-
drizzleType: `integer({ mode: 'timestamp' })`,
|
|
1203
|
-
isPrimary: false,
|
|
1204
|
-
isAutoIncrement: false,
|
|
1205
|
-
isNullable: false,
|
|
1206
|
-
isUnique: false,
|
|
1207
|
-
defaultValue: void 0
|
|
1208
|
-
});
|
|
1209
|
-
columns.push({
|
|
1210
|
-
name: "updated_at",
|
|
1211
|
-
drizzleType: `integer({ mode: 'timestamp' })`,
|
|
1212
|
-
isPrimary: false,
|
|
1213
|
-
isAutoIncrement: false,
|
|
1214
|
-
isNullable: false,
|
|
1215
|
-
isUnique: false,
|
|
1216
|
-
defaultValue: void 0
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
if (schema.table.softDelete) columns.push({
|
|
1220
|
-
name: "deleted_at",
|
|
1221
|
-
drizzleType: `integer({ mode: 'timestamp' })`,
|
|
1222
|
-
isPrimary: false,
|
|
1223
|
-
isAutoIncrement: false,
|
|
1224
|
-
isNullable: true,
|
|
1225
|
-
isUnique: false,
|
|
1226
|
-
defaultValue: void 0
|
|
1227
|
-
});
|
|
1228
|
-
return {
|
|
1229
|
-
name: tableName,
|
|
1230
|
-
columns
|
|
1231
|
-
};
|
|
1241
|
+
function hasVisionModel(aiConfig, modelOverride) {
|
|
1242
|
+
if (modelOverride) return modelOverride.capabilities.vision;
|
|
1243
|
+
return aiConfig?.provider.models.some((model) => model.capabilities.vision) ?? true;
|
|
1232
1244
|
}
|
|
1233
|
-
function
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
});
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
isUnique: false,
|
|
1253
|
-
defaultValue: void 0,
|
|
1254
|
-
isForeignKey: true,
|
|
1255
|
-
foreignKeyRef: {
|
|
1256
|
-
table: parentTableName,
|
|
1257
|
-
column: "id"
|
|
1258
|
-
}
|
|
1259
|
-
});
|
|
1260
|
-
if (property.type === "object" && property.properties) for (const [childName, childProp] of Object.entries(property.properties)) {
|
|
1261
|
-
if (childProp.nested?.enabled) {
|
|
1262
|
-
warnings.push(`Nested property "${childName}" inside "${nestedTableName}" is skipped — only one level of nesting is supported. Remove nested.enabled or use drizzle.mode: 'json' instead.`);
|
|
1263
|
-
continue;
|
|
1264
|
-
}
|
|
1265
|
-
const column = mapPropertyToColumn(childName, childProp, false);
|
|
1266
|
-
columns.push(column);
|
|
1245
|
+
function shouldUseImageOcrFallback(aiConfig, modelOverride, runtime = defaultRuntime) {
|
|
1246
|
+
if (hasVisionModel(aiConfig, modelOverride)) return false;
|
|
1247
|
+
if (imageOcrMode(aiConfig?.image) === "localAuto") return isLocalOcrPlatform(runtime.platform);
|
|
1248
|
+
return isLocalOcrPlatform(runtime.platform);
|
|
1249
|
+
}
|
|
1250
|
+
function isLocalOcrPlatform(platform) {
|
|
1251
|
+
return platform === "darwin" || platform === "win32";
|
|
1252
|
+
}
|
|
1253
|
+
function parseOcrLanguages(languages) {
|
|
1254
|
+
return (languages ?? DEFAULT_OCR_LANGUAGES).split(",").map((language) => language.trim()).filter(Boolean);
|
|
1255
|
+
}
|
|
1256
|
+
async function recognizeImageText(imagePath, config, runtime = defaultRuntime) {
|
|
1257
|
+
if (!isLocalOcrPlatform(runtime.platform)) throw new Error(t("errors.ocr.platformUnsupported", { platform: runtime.platform }));
|
|
1258
|
+
let localOcr;
|
|
1259
|
+
try {
|
|
1260
|
+
localOcr = await runtime.loadLocalOcr();
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1263
|
+
throw new Error(t("errors.ocr.unavailable", { error: message }));
|
|
1267
1264
|
}
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
fromTable: parentTableName,
|
|
1278
|
-
toTable: nestedTableName,
|
|
1279
|
-
name: toSnakeCase(propName)
|
|
1280
|
-
};
|
|
1265
|
+
const result = await localOcr.recognize(imagePath, localOcr.OcrAccuracy.Accurate, parseOcrLanguages(config?.ocrLanguages));
|
|
1266
|
+
const text = result.text.trim();
|
|
1267
|
+
if (!text) throw new Error(t("errors.ocr.noText"));
|
|
1268
|
+
const confidence = result.confidence;
|
|
1269
|
+
const minConfidence = config?.ocrMinConfidence ?? 0;
|
|
1270
|
+
if (confidence < minConfidence) throw new Error(t("errors.ocr.lowConfidence", {
|
|
1271
|
+
confidence: (confidence * 100).toFixed(1),
|
|
1272
|
+
min: (minConfidence * 100).toFixed(1)
|
|
1273
|
+
}));
|
|
1281
1274
|
return {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
columns
|
|
1285
|
-
},
|
|
1286
|
-
relation,
|
|
1287
|
-
reverseRelation
|
|
1275
|
+
text,
|
|
1276
|
+
confidence
|
|
1288
1277
|
};
|
|
1289
1278
|
}
|
|
1290
|
-
function
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1279
|
+
function normalizeOcrText(text) {
|
|
1280
|
+
return text.replace(/\s+/g, "").trim().toUpperCase();
|
|
1281
|
+
}
|
|
1282
|
+
async function checkImageOcrAvailability(imagePath, runtime = defaultRuntime) {
|
|
1283
|
+
if (!isLocalOcrPlatform(runtime.platform)) return {
|
|
1284
|
+
platformSupported: false,
|
|
1285
|
+
dependencyLoaded: false,
|
|
1286
|
+
ocrOk: null,
|
|
1287
|
+
imagePath,
|
|
1288
|
+
error: t("errors.ocr.platformUnsupported", { platform: runtime.platform })
|
|
1289
|
+
};
|
|
1290
|
+
let localOcr;
|
|
1291
|
+
try {
|
|
1292
|
+
localOcr = await runtime.loadLocalOcr();
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
return {
|
|
1295
|
+
platformSupported: true,
|
|
1296
|
+
dependencyLoaded: false,
|
|
1297
|
+
ocrOk: null,
|
|
1298
|
+
imagePath,
|
|
1299
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1300
|
+
};
|
|
1307
1301
|
}
|
|
1308
|
-
return {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1302
|
+
if (!imagePath) return {
|
|
1303
|
+
platformSupported: true,
|
|
1304
|
+
dependencyLoaded: true,
|
|
1305
|
+
ocrOk: null,
|
|
1306
|
+
error: "No OCR self-check image was found."
|
|
1313
1307
|
};
|
|
1308
|
+
try {
|
|
1309
|
+
const result = await localOcr.recognize(imagePath, localOcr.OcrAccuracy.Accurate, ["en-US"]);
|
|
1310
|
+
const recognizedText = result.text.trim();
|
|
1311
|
+
const ocrOk = normalizeOcrText(recognizedText).includes(SELF_CHECK_EXPECTED_TEXT);
|
|
1312
|
+
return {
|
|
1313
|
+
platformSupported: true,
|
|
1314
|
+
dependencyLoaded: true,
|
|
1315
|
+
ocrOk,
|
|
1316
|
+
imagePath,
|
|
1317
|
+
recognizedText,
|
|
1318
|
+
confidence: result.confidence,
|
|
1319
|
+
error: ocrOk ? void 0 : `Expected OCR text "${SELF_CHECK_EXPECTED_TEXT}" was not recognized.`
|
|
1320
|
+
};
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
return {
|
|
1323
|
+
platformSupported: true,
|
|
1324
|
+
dependencyLoaded: true,
|
|
1325
|
+
ocrOk: false,
|
|
1326
|
+
imagePath,
|
|
1327
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1314
1330
|
}
|
|
1315
1331
|
|
|
1316
1332
|
//#endregion
|
|
1317
|
-
//#region src/
|
|
1318
|
-
const DrizzleModeSchema = z.enum([
|
|
1319
|
-
"json",
|
|
1320
|
-
"timestamp",
|
|
1321
|
-
"timestamp_ms",
|
|
1322
|
-
"boolean",
|
|
1323
|
-
"bigint"
|
|
1324
|
-
]);
|
|
1325
|
-
const DrizzleExtensionSchema = z.object({
|
|
1326
|
-
mode: DrizzleModeSchema.optional(),
|
|
1327
|
-
customType: z.string().optional()
|
|
1328
|
-
}).optional();
|
|
1329
|
-
const NestedConfigSchema = z.object({
|
|
1330
|
-
enabled: z.literal(true),
|
|
1331
|
-
relation: z.enum(["has-one", "has-many"])
|
|
1332
|
-
});
|
|
1333
|
-
const ForeignKeyRefSchema = z.object({
|
|
1334
|
-
table: z.string().min(1),
|
|
1335
|
-
column: z.string().min(1)
|
|
1336
|
-
});
|
|
1337
|
-
const JsonSchemaPropertySchema = z.lazy(() => z.object({
|
|
1338
|
-
description: z.string().optional(),
|
|
1339
|
-
type: z.enum([
|
|
1340
|
-
"string",
|
|
1341
|
-
"integer",
|
|
1342
|
-
"number",
|
|
1343
|
-
"boolean",
|
|
1344
|
-
"object",
|
|
1345
|
-
"array",
|
|
1346
|
-
"null"
|
|
1347
|
-
]),
|
|
1348
|
-
format: z.string().optional(),
|
|
1349
|
-
primary: z.boolean().optional(),
|
|
1350
|
-
autoIncrement: z.boolean().optional(),
|
|
1351
|
-
unique: z.boolean().optional(),
|
|
1352
|
-
default: z.unknown().optional(),
|
|
1353
|
-
maxLength: z.number().int().positive().optional(),
|
|
1354
|
-
minLength: z.number().int().nonnegative().optional(),
|
|
1355
|
-
minimum: z.number().optional(),
|
|
1356
|
-
maximum: z.number().optional(),
|
|
1357
|
-
drizzle: DrizzleExtensionSchema,
|
|
1358
|
-
nested: NestedConfigSchema.optional(),
|
|
1359
|
-
foreignKey: ForeignKeyRefSchema.optional(),
|
|
1360
|
-
properties: z.record(z.string(), JsonSchemaPropertySchema).optional(),
|
|
1361
|
-
items: JsonSchemaPropertySchema.optional(),
|
|
1362
|
-
required: z.array(z.string()).optional()
|
|
1363
|
-
}));
|
|
1364
|
-
const TableConfigSchema = z.object({
|
|
1365
|
-
name: z.string().min(1).regex(/^[a-z][a-z0-9_]*$/, "Table name must be snake_case (lowercase letters, digits, underscores)"),
|
|
1366
|
-
timestamps: z.boolean().optional(),
|
|
1367
|
-
softDelete: z.boolean().optional()
|
|
1368
|
-
});
|
|
1369
|
-
const ExamplePairSchema = z.object({
|
|
1370
|
-
text: z.string().min(1),
|
|
1371
|
-
output: z.record(z.string(), z.unknown())
|
|
1372
|
-
});
|
|
1373
|
-
const JsonSchemaDefinitionSchema = z.object({
|
|
1374
|
-
$schema: z.string().optional(),
|
|
1375
|
-
title: z.string().min(1),
|
|
1376
|
-
description: z.string().optional(),
|
|
1377
|
-
type: z.literal("object"),
|
|
1378
|
-
table: TableConfigSchema,
|
|
1379
|
-
properties: z.record(z.string(), JsonSchemaPropertySchema),
|
|
1380
|
-
required: z.array(z.string()).optional(),
|
|
1381
|
-
examples: z.array(ExamplePairSchema).optional()
|
|
1382
|
-
}).refine((schema) => Object.keys(schema.properties).length >= 1, {
|
|
1383
|
-
message: "At least one property is required",
|
|
1384
|
-
path: ["properties"]
|
|
1385
|
-
}).refine((schema) => !schema.required || schema.required.every((r) => r in schema.properties), {
|
|
1386
|
-
message: "All required fields must be defined in properties",
|
|
1387
|
-
path: ["required"]
|
|
1388
|
-
}).refine((schema) => {
|
|
1389
|
-
return !Object.values(schema.properties).some((p) => p.primary) || Object.values(schema.properties).filter((p) => p.primary).length === 1;
|
|
1390
|
-
}, {
|
|
1391
|
-
message: "Only one primary key is allowed per table",
|
|
1392
|
-
path: ["properties"]
|
|
1393
|
-
});
|
|
1394
|
-
|
|
1395
|
-
//#endregion
|
|
1396
|
-
//#region src/core/schema-sqlite/migrator.ts
|
|
1333
|
+
//#region src/infrastructure/schema/migration-config.ts
|
|
1397
1334
|
function createMigrationConfig(cwd) {
|
|
1398
1335
|
return {
|
|
1399
1336
|
schemaPath: `${cwd}/.aiex/schema`,
|
|
@@ -1416,7 +1353,7 @@ function generateDrizzleConfig() {
|
|
|
1416
1353
|
}
|
|
1417
1354
|
|
|
1418
1355
|
//#endregion
|
|
1419
|
-
//#region src/
|
|
1356
|
+
//#region src/application/doctor/collect-diagnostics.ts
|
|
1420
1357
|
const V1_SUFFIX_RE = /\/v1\/?$/;
|
|
1421
1358
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
1422
1359
|
async function checkConnection(baseURL) {
|
|
@@ -1593,4 +1530,67 @@ async function collectDoctorDiagnostics(options = {}) {
|
|
|
1593
1530
|
}
|
|
1594
1531
|
|
|
1595
1532
|
//#endregion
|
|
1596
|
-
|
|
1533
|
+
//#region src/infrastructure/schema/generate-drizzle-schema.ts
|
|
1534
|
+
function generateColumnDefinition(column) {
|
|
1535
|
+
if (column.isPrimary && column.isAutoIncrement) return ` ${column.name}: integer().primaryKey({ autoIncrement: true })`;
|
|
1536
|
+
let def = ` ${column.name}: ${column.drizzleType}`;
|
|
1537
|
+
if (column.isPrimary) def += ".primaryKey()";
|
|
1538
|
+
if (!column.isNullable && !column.isPrimary) def += ".notNull()";
|
|
1539
|
+
if (column.isUnique && !column.isPrimary) def += ".unique()";
|
|
1540
|
+
if (column.defaultValue !== void 0) def += `.default(${column.defaultValue})`;
|
|
1541
|
+
if (column.isForeignKey && column.foreignKeyRef) def += `.references(() => ${column.foreignKeyRef.table}.${column.foreignKeyRef.column})`;
|
|
1542
|
+
return def;
|
|
1543
|
+
}
|
|
1544
|
+
function generateTableDefinition(table) {
|
|
1545
|
+
const columns = table.columns.map(generateColumnDefinition);
|
|
1546
|
+
return `export const ${table.name} = sqliteTable('${table.name}', {\n${columns.join(",\n")}\n})`;
|
|
1547
|
+
}
|
|
1548
|
+
function generateRelationDefinitions(relations, reverseRelations) {
|
|
1549
|
+
if (relations.length === 0 && reverseRelations.length === 0) return "";
|
|
1550
|
+
const definitions = [];
|
|
1551
|
+
const childByTable = /* @__PURE__ */ new Map();
|
|
1552
|
+
for (const rel of relations) {
|
|
1553
|
+
const list = childByTable.get(rel.fromTable) ?? [];
|
|
1554
|
+
list.push(rel);
|
|
1555
|
+
childByTable.set(rel.fromTable, list);
|
|
1556
|
+
}
|
|
1557
|
+
const parentByTable = /* @__PURE__ */ new Map();
|
|
1558
|
+
for (const rel of reverseRelations) {
|
|
1559
|
+
const list = parentByTable.get(rel.fromTable) ?? [];
|
|
1560
|
+
list.push(rel);
|
|
1561
|
+
parentByTable.set(rel.fromTable, list);
|
|
1562
|
+
}
|
|
1563
|
+
const allTableNames = new Set([...childByTable.keys(), ...parentByTable.keys()]);
|
|
1564
|
+
for (const tableName of allTableNames) {
|
|
1565
|
+
const childRels = childByTable.get(tableName) ?? [];
|
|
1566
|
+
const parentRels = parentByTable.get(tableName) ?? [];
|
|
1567
|
+
const needsOne = childRels.length > 0 || parentRels.some((r) => r.type === "has-one");
|
|
1568
|
+
const needsMany = parentRels.some((r) => r.type === "has-many");
|
|
1569
|
+
const imports = [];
|
|
1570
|
+
if (needsOne) imports.push("one");
|
|
1571
|
+
if (needsMany) imports.push("many");
|
|
1572
|
+
const importStr = imports.join(", ");
|
|
1573
|
+
const relDefs = [];
|
|
1574
|
+
for (const rel of childRels) relDefs.push(` ${rel.name}: one(${rel.toTable}, {\n fields: [${rel.fromTable}.${rel.fromColumn}],\n references: [${rel.toTable}.${rel.toColumn}],\n })`);
|
|
1575
|
+
for (const rel of parentRels) if (rel.type === "has-many") relDefs.push(` ${rel.name}: many(${rel.toTable})`);
|
|
1576
|
+
else relDefs.push(` ${rel.name}: one(${rel.toTable})`);
|
|
1577
|
+
definitions.push(`export const ${tableName}Relations = relations(${tableName}, ({ ${importStr} }) => ({\n${relDefs.join(",\n")}\n}))`);
|
|
1578
|
+
}
|
|
1579
|
+
return definitions.join("\n\n");
|
|
1580
|
+
}
|
|
1581
|
+
function generateDrizzleSchema(result) {
|
|
1582
|
+
const imports = `import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core'\nimport { relations } from 'drizzle-orm'`;
|
|
1583
|
+
const tableDefs = result.tables.map(generateTableDefinition).join("\n\n");
|
|
1584
|
+
const relationDefs = generateRelationDefinitions(result.relations, result.reverseRelations);
|
|
1585
|
+
const parts = [
|
|
1586
|
+
imports,
|
|
1587
|
+
"",
|
|
1588
|
+
tableDefs
|
|
1589
|
+
];
|
|
1590
|
+
if (relationDefs) parts.push("", relationDefs);
|
|
1591
|
+
parts.push("");
|
|
1592
|
+
return parts.join("\n");
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
//#endregion
|
|
1596
|
+
export { PLACEHOLDER_TEXT as C, name as D, description as E, package_default as O, PLACEHOLDER_SCHEMA as S, seedConfig as T, doctorDiagnosticsTableRows as _, recognizeImageText as a, DEFAULT_MINERU_CONFIG as b, t as c, writeAIConfig as d, AIConfigSchema as f, buildDoctorDiagnostics as g, toSnakeCase as h, generateDrizzleConfig as i, version as k, getDefaultAIConfig as l, parseJsonSchema as m, collectDoctorDiagnostics as n, shouldUseImageOcrFallback as o, JsonSchemaDefinitionSchema as p, createMigrationConfig as r, initI18n as s, generateDrizzleSchema as t, readAIConfig as u, formatDoctorDiagnosticsJson as v, createConfig as w, DEFAULT_PROMPT_CONFIG as x, DEFAULT_MINERU_API_CONFIG as y };
|