@exulu/backend 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs
CHANGED
|
@@ -11452,6 +11452,52 @@ var usersSchema = {
|
|
|
11452
11452
|
// triggered by the key with project_id_ for LiteLLM cost attribution.
|
|
11453
11453
|
name: "project",
|
|
11454
11454
|
type: "uuid"
|
|
11455
|
+
},
|
|
11456
|
+
// ── Lead capture (see lead-capture spec §5) ─────────────────────────────
|
|
11457
|
+
// Consent must be *provable* under Art. 7(1) GDPR: who agreed, when, and
|
|
11458
|
+
// to which wording. Hence a timestamp per consent and a version string
|
|
11459
|
+
// pointing at the exact text that was shown.
|
|
11460
|
+
{
|
|
11461
|
+
// Current state of the optional marketing consent.
|
|
11462
|
+
name: "marketing_consent",
|
|
11463
|
+
type: "boolean",
|
|
11464
|
+
default: false
|
|
11465
|
+
},
|
|
11466
|
+
{
|
|
11467
|
+
// When marketing consent was GIVEN.
|
|
11468
|
+
name: "marketing_consent_at",
|
|
11469
|
+
type: "date"
|
|
11470
|
+
},
|
|
11471
|
+
{
|
|
11472
|
+
// When marketing consent was WITHDRAWN. Deliberately a separate column:
|
|
11473
|
+
// overwriting marketing_consent_at on withdrawal would destroy the grant
|
|
11474
|
+
// time, and with it the ability to prove the order of events if someone
|
|
11475
|
+
// later claims they were mailed after withdrawing.
|
|
11476
|
+
name: "marketing_consent_withdrawn_at",
|
|
11477
|
+
type: "date"
|
|
11478
|
+
},
|
|
11479
|
+
{
|
|
11480
|
+
// When the mandatory storage/processing consent was given.
|
|
11481
|
+
name: "processing_consent_at",
|
|
11482
|
+
type: "date"
|
|
11483
|
+
},
|
|
11484
|
+
{
|
|
11485
|
+
// When the user asked for erasure (Art. 17). The row survives so the
|
|
11486
|
+
// one-month deadline stays queryable even if the notification mail fails.
|
|
11487
|
+
name: "erasure_requested_at",
|
|
11488
|
+
type: "date"
|
|
11489
|
+
},
|
|
11490
|
+
{
|
|
11491
|
+
// Where the lead came from, e.g. "eu-ai-act-bot".
|
|
11492
|
+
name: "signup_source",
|
|
11493
|
+
type: "text"
|
|
11494
|
+
},
|
|
11495
|
+
{
|
|
11496
|
+
// Version of the consent wording that was actually displayed, e.g.
|
|
11497
|
+
// "2026-08-18". The wording itself lives in the frontend repo under
|
|
11498
|
+
// version control; this column points at it.
|
|
11499
|
+
name: "consent_version",
|
|
11500
|
+
type: "text"
|
|
11455
11501
|
}
|
|
11456
11502
|
]
|
|
11457
11503
|
};
|
|
@@ -11760,6 +11806,32 @@ var addCoreFields = (schema) => {
|
|
|
11760
11806
|
}
|
|
11761
11807
|
return schema;
|
|
11762
11808
|
};
|
|
11809
|
+
var otpSendAttemptsSchema = {
|
|
11810
|
+
type: "otp_send_attempts",
|
|
11811
|
+
name: {
|
|
11812
|
+
plural: "otp_send_attempts",
|
|
11813
|
+
singular: "otp_send_attempt"
|
|
11814
|
+
},
|
|
11815
|
+
fields: [
|
|
11816
|
+
{
|
|
11817
|
+
name: "key",
|
|
11818
|
+
type: "text",
|
|
11819
|
+
index: true,
|
|
11820
|
+
unique: true,
|
|
11821
|
+
required: true
|
|
11822
|
+
},
|
|
11823
|
+
{
|
|
11824
|
+
name: "count",
|
|
11825
|
+
type: "number",
|
|
11826
|
+
required: true
|
|
11827
|
+
},
|
|
11828
|
+
{
|
|
11829
|
+
name: "window_start",
|
|
11830
|
+
type: "date",
|
|
11831
|
+
required: true
|
|
11832
|
+
}
|
|
11833
|
+
]
|
|
11834
|
+
};
|
|
11763
11835
|
var coreSchemas = {
|
|
11764
11836
|
get: () => {
|
|
11765
11837
|
const license = checkLicense();
|
|
@@ -11780,7 +11852,8 @@ var coreSchemas = {
|
|
|
11780
11852
|
contextPresetsSchema: () => addCoreFields(contextPresetsSchema),
|
|
11781
11853
|
sharedArtifactsSchema: () => addCoreFields(sharedArtifactsSchema),
|
|
11782
11854
|
transcriptionJobsSchema: () => addCoreFields(transcriptionJobsSchema),
|
|
11783
|
-
imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema)
|
|
11855
|
+
imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema),
|
|
11856
|
+
otpSendAttemptsSchema: () => addCoreFields(otpSendAttemptsSchema)
|
|
11784
11857
|
};
|
|
11785
11858
|
if (license["agent-feedback"]) {
|
|
11786
11859
|
schemas.feedbackSchema = () => addCoreFields(feedbackSchema);
|
|
@@ -31560,7 +31633,8 @@ var {
|
|
|
31560
31633
|
promptFavoritesSchema: promptFavoritesSchema3,
|
|
31561
31634
|
transcriptionJobsSchema: transcriptionJobsSchema3,
|
|
31562
31635
|
imageGenerationsSchema: imageGenerationsSchema2,
|
|
31563
|
-
sharedArtifactsSchema: sharedArtifactsSchema2
|
|
31636
|
+
sharedArtifactsSchema: sharedArtifactsSchema2,
|
|
31637
|
+
otpSendAttemptsSchema: otpSendAttemptsSchema2
|
|
31564
31638
|
} = coreSchemas.get();
|
|
31565
31639
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
31566
31640
|
for (const field of fields) {
|
|
@@ -31631,7 +31705,8 @@ var up = async function(knex) {
|
|
|
31631
31705
|
variablesSchema3(),
|
|
31632
31706
|
skillsSchema3(),
|
|
31633
31707
|
workflowTemplatesSchema3(),
|
|
31634
|
-
workflowTriggersSchema2()
|
|
31708
|
+
workflowTriggersSchema2(),
|
|
31709
|
+
otpSendAttemptsSchema2()
|
|
31635
31710
|
];
|
|
31636
31711
|
const createTable = async (schema) => {
|
|
31637
31712
|
if (!await knex.schema.hasTable(schema.name.plural)) {
|
|
@@ -33398,7 +33473,16 @@ ${setupResult.output || ""}`);
|
|
|
33398
33473
|
args: [paths.source, chunksDir, "--chunk-size", String(maxPagesPerChunk), "--max-size-mb", "25"],
|
|
33399
33474
|
timeout: 5 * 60 * 1e3
|
|
33400
33475
|
});
|
|
33401
|
-
|
|
33476
|
+
let pdfChunks;
|
|
33477
|
+
try {
|
|
33478
|
+
pdfChunks = JSON.parse(splitResult.stdout);
|
|
33479
|
+
} catch (err) {
|
|
33480
|
+
throw new Error(
|
|
33481
|
+
`[EXULU] split_pdf.py returned invalid JSON on stdout: ${err.message}
|
|
33482
|
+
stdout: ${splitResult.stdout.slice(0, 500)}
|
|
33483
|
+
stderr: ${splitResult.stderr.slice(-1e3)}`
|
|
33484
|
+
);
|
|
33485
|
+
}
|
|
33402
33486
|
console.log(`[EXULU] PDF split into ${pdfChunks.length} chunk(s) for OCR (max ${maxPagesPerChunk} pages each)`);
|
|
33403
33487
|
const chunkLimit = (0, import_p_limit.default)(3);
|
|
33404
33488
|
const chunkResults = await Promise.all(
|
package/dist/index.js
CHANGED
|
@@ -2568,6 +2568,52 @@ var usersSchema = {
|
|
|
2568
2568
|
// triggered by the key with project_id_ for LiteLLM cost attribution.
|
|
2569
2569
|
name: "project",
|
|
2570
2570
|
type: "uuid"
|
|
2571
|
+
},
|
|
2572
|
+
// ── Lead capture (see lead-capture spec §5) ─────────────────────────────
|
|
2573
|
+
// Consent must be *provable* under Art. 7(1) GDPR: who agreed, when, and
|
|
2574
|
+
// to which wording. Hence a timestamp per consent and a version string
|
|
2575
|
+
// pointing at the exact text that was shown.
|
|
2576
|
+
{
|
|
2577
|
+
// Current state of the optional marketing consent.
|
|
2578
|
+
name: "marketing_consent",
|
|
2579
|
+
type: "boolean",
|
|
2580
|
+
default: false
|
|
2581
|
+
},
|
|
2582
|
+
{
|
|
2583
|
+
// When marketing consent was GIVEN.
|
|
2584
|
+
name: "marketing_consent_at",
|
|
2585
|
+
type: "date"
|
|
2586
|
+
},
|
|
2587
|
+
{
|
|
2588
|
+
// When marketing consent was WITHDRAWN. Deliberately a separate column:
|
|
2589
|
+
// overwriting marketing_consent_at on withdrawal would destroy the grant
|
|
2590
|
+
// time, and with it the ability to prove the order of events if someone
|
|
2591
|
+
// later claims they were mailed after withdrawing.
|
|
2592
|
+
name: "marketing_consent_withdrawn_at",
|
|
2593
|
+
type: "date"
|
|
2594
|
+
},
|
|
2595
|
+
{
|
|
2596
|
+
// When the mandatory storage/processing consent was given.
|
|
2597
|
+
name: "processing_consent_at",
|
|
2598
|
+
type: "date"
|
|
2599
|
+
},
|
|
2600
|
+
{
|
|
2601
|
+
// When the user asked for erasure (Art. 17). The row survives so the
|
|
2602
|
+
// one-month deadline stays queryable even if the notification mail fails.
|
|
2603
|
+
name: "erasure_requested_at",
|
|
2604
|
+
type: "date"
|
|
2605
|
+
},
|
|
2606
|
+
{
|
|
2607
|
+
// Where the lead came from, e.g. "eu-ai-act-bot".
|
|
2608
|
+
name: "signup_source",
|
|
2609
|
+
type: "text"
|
|
2610
|
+
},
|
|
2611
|
+
{
|
|
2612
|
+
// Version of the consent wording that was actually displayed, e.g.
|
|
2613
|
+
// "2026-08-18". The wording itself lives in the frontend repo under
|
|
2614
|
+
// version control; this column points at it.
|
|
2615
|
+
name: "consent_version",
|
|
2616
|
+
type: "text"
|
|
2571
2617
|
}
|
|
2572
2618
|
]
|
|
2573
2619
|
};
|
|
@@ -2876,6 +2922,32 @@ var addCoreFields = (schema) => {
|
|
|
2876
2922
|
}
|
|
2877
2923
|
return schema;
|
|
2878
2924
|
};
|
|
2925
|
+
var otpSendAttemptsSchema = {
|
|
2926
|
+
type: "otp_send_attempts",
|
|
2927
|
+
name: {
|
|
2928
|
+
plural: "otp_send_attempts",
|
|
2929
|
+
singular: "otp_send_attempt"
|
|
2930
|
+
},
|
|
2931
|
+
fields: [
|
|
2932
|
+
{
|
|
2933
|
+
name: "key",
|
|
2934
|
+
type: "text",
|
|
2935
|
+
index: true,
|
|
2936
|
+
unique: true,
|
|
2937
|
+
required: true
|
|
2938
|
+
},
|
|
2939
|
+
{
|
|
2940
|
+
name: "count",
|
|
2941
|
+
type: "number",
|
|
2942
|
+
required: true
|
|
2943
|
+
},
|
|
2944
|
+
{
|
|
2945
|
+
name: "window_start",
|
|
2946
|
+
type: "date",
|
|
2947
|
+
required: true
|
|
2948
|
+
}
|
|
2949
|
+
]
|
|
2950
|
+
};
|
|
2879
2951
|
var coreSchemas = {
|
|
2880
2952
|
get: () => {
|
|
2881
2953
|
const license = checkLicense();
|
|
@@ -2896,7 +2968,8 @@ var coreSchemas = {
|
|
|
2896
2968
|
contextPresetsSchema: () => addCoreFields(contextPresetsSchema),
|
|
2897
2969
|
sharedArtifactsSchema: () => addCoreFields(sharedArtifactsSchema),
|
|
2898
2970
|
transcriptionJobsSchema: () => addCoreFields(transcriptionJobsSchema),
|
|
2899
|
-
imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema)
|
|
2971
|
+
imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema),
|
|
2972
|
+
otpSendAttemptsSchema: () => addCoreFields(otpSendAttemptsSchema)
|
|
2900
2973
|
};
|
|
2901
2974
|
if (license["agent-feedback"]) {
|
|
2902
2975
|
schemas.feedbackSchema = () => addCoreFields(feedbackSchema);
|
|
@@ -22174,7 +22247,8 @@ var {
|
|
|
22174
22247
|
promptFavoritesSchema: promptFavoritesSchema3,
|
|
22175
22248
|
transcriptionJobsSchema: transcriptionJobsSchema3,
|
|
22176
22249
|
imageGenerationsSchema: imageGenerationsSchema2,
|
|
22177
|
-
sharedArtifactsSchema: sharedArtifactsSchema2
|
|
22250
|
+
sharedArtifactsSchema: sharedArtifactsSchema2,
|
|
22251
|
+
otpSendAttemptsSchema: otpSendAttemptsSchema2
|
|
22178
22252
|
} = coreSchemas.get();
|
|
22179
22253
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
22180
22254
|
for (const field of fields) {
|
|
@@ -22245,7 +22319,8 @@ var up = async function(knex) {
|
|
|
22245
22319
|
variablesSchema3(),
|
|
22246
22320
|
skillsSchema3(),
|
|
22247
22321
|
workflowTemplatesSchema3(),
|
|
22248
|
-
workflowTriggersSchema2()
|
|
22322
|
+
workflowTriggersSchema2(),
|
|
22323
|
+
otpSendAttemptsSchema2()
|
|
22249
22324
|
];
|
|
22250
22325
|
const createTable = async (schema) => {
|
|
22251
22326
|
if (!await knex.schema.hasTable(schema.name.plural)) {
|
|
@@ -23992,7 +24067,16 @@ ${setupResult.output || ""}`);
|
|
|
23992
24067
|
args: [paths.source, chunksDir, "--chunk-size", String(maxPagesPerChunk), "--max-size-mb", "25"],
|
|
23993
24068
|
timeout: 5 * 60 * 1e3
|
|
23994
24069
|
});
|
|
23995
|
-
|
|
24070
|
+
let pdfChunks;
|
|
24071
|
+
try {
|
|
24072
|
+
pdfChunks = JSON.parse(splitResult.stdout);
|
|
24073
|
+
} catch (err) {
|
|
24074
|
+
throw new Error(
|
|
24075
|
+
`[EXULU] split_pdf.py returned invalid JSON on stdout: ${err.message}
|
|
24076
|
+
stdout: ${splitResult.stdout.slice(0, 500)}
|
|
24077
|
+
stderr: ${splitResult.stderr.slice(-1e3)}`
|
|
24078
|
+
);
|
|
24079
|
+
}
|
|
23996
24080
|
console.log(`[EXULU] PDF split into ${pdfChunks.length} chunk(s) for OCR (max ${maxPagesPerChunk} pages each)`);
|
|
23997
24081
|
const chunkLimit = pLimit(3);
|
|
23998
24082
|
const chunkResults = await Promise.all(
|
|
@@ -837,8 +837,20 @@ async function processPdf(
|
|
|
837
837
|
timeout: 5 * 60 * 1000,
|
|
838
838
|
});
|
|
839
839
|
|
|
840
|
-
|
|
841
|
-
|
|
840
|
+
// split_pdf.py contracts to put nothing but the JSON payload on stdout.
|
|
841
|
+
// If something slips in anyway, the bare SyntaxError only quotes the first
|
|
842
|
+
// few characters ("Unexpected token 'w'") and names neither the script nor
|
|
843
|
+
// the offending output — so re-throw with the actual streams attached.
|
|
844
|
+
let pdfChunks: Array<{ path: string; start_page: number; end_page: number }>;
|
|
845
|
+
try {
|
|
846
|
+
pdfChunks = JSON.parse(splitResult.stdout);
|
|
847
|
+
} catch (err) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
`[EXULU] split_pdf.py returned invalid JSON on stdout: ${(err as Error).message}\n` +
|
|
850
|
+
`stdout: ${splitResult.stdout.slice(0, 500)}\n` +
|
|
851
|
+
`stderr: ${splitResult.stderr.slice(-1000)}`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
842
854
|
|
|
843
855
|
console.log(`[EXULU] PDF split into ${pdfChunks.length} chunk(s) for OCR (max ${maxPagesPerChunk} pages each)`);
|
|
844
856
|
|
|
@@ -20,7 +20,24 @@ import os
|
|
|
20
20
|
import json
|
|
21
21
|
import argparse
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
# stdout is this script's result channel and the caller does JSON.parse() on it,
|
|
24
|
+
# so nothing else may write there. Our own prints all pass file=sys.stderr, but
|
|
25
|
+
# dependencies do not honour that: PyMuPDF sends its messages to sys.stdout by
|
|
26
|
+
# default, and importing the legacy `fitz` alias emits
|
|
27
|
+
# "warning: The `fitz` API is deprecated ..." — which lands ahead of the payload
|
|
28
|
+
# and fails the caller with "Unexpected token 'w'". PyMuPDF arrives unpinned as
|
|
29
|
+
# a docling transitive dependency, so a routine rebuild is enough to introduce a
|
|
30
|
+
# banner like that. Point sys.stdout at stderr before importing anything and
|
|
31
|
+
# keep a private handle for the result, so any library that prints — now or
|
|
32
|
+
# after a future dependency bump — is shunted to the log channel instead of
|
|
33
|
+
# corrupting the payload.
|
|
34
|
+
_stdout = sys.stdout
|
|
35
|
+
sys.stdout = sys.stderr
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import pymupdf as fitz # PyMuPDF >= 1.24.3, where the module was renamed
|
|
39
|
+
except ImportError: # older releases only ship the legacy `fitz` module
|
|
40
|
+
import fitz
|
|
24
41
|
|
|
25
42
|
|
|
26
43
|
def _write_chunk(
|
|
@@ -145,7 +162,7 @@ if __name__ == "__main__":
|
|
|
145
162
|
|
|
146
163
|
try:
|
|
147
164
|
chunks = split_pdf(args.input_pdf, args.output_dir, args.chunk_size, max_size_bytes)
|
|
148
|
-
print(json.dumps(chunks))
|
|
165
|
+
print(json.dumps(chunks), file=_stdout)
|
|
149
166
|
except Exception as e:
|
|
150
167
|
print(f"[split_pdf] ERROR: {e}", file=sys.stderr)
|
|
151
168
|
sys.exit(1)
|
|
@@ -24,17 +24,26 @@ pyannote.audio>=3.3.0
|
|
|
24
24
|
# Belt-and-suspenders: keep huggingface_hub on the 0.x line so pyannote 3.x's
|
|
25
25
|
# `use_auth_token=` calls keep working (1.x removed that kwarg → diarization off).
|
|
26
26
|
huggingface_hub<1.0
|
|
27
|
-
fastapi
|
|
28
27
|
uvicorn
|
|
29
28
|
python-multipart
|
|
30
29
|
requests
|
|
31
30
|
# LiteLLM proxy — only used when EXULU_USE_LITELLM=true. Always installed so
|
|
32
31
|
# the dep is ready when the env var is flipped. Pinned to a tested version;
|
|
33
32
|
# upgrade deliberately.
|
|
34
|
-
litellm[proxy]==1.
|
|
33
|
+
litellm[proxy]==1.97.0
|
|
34
|
+
# FastAPI ceiling — NOT cosmetic. litellm 1.97.0 declares fastapi>=0.136.3,<1.0
|
|
35
|
+
# but imports the private `get_flat_dependant` from fastapi.dependencies.utils
|
|
36
|
+
# (proxy/management_endpoints/management_v1/common.py:7), which FastAPI deleted
|
|
37
|
+
# in 0.140.7. Any resolve that picks 0.140.7+ yields a proxy that cannot start:
|
|
38
|
+
# "ImportError: cannot import name 'get_flat_dependant'". 0.140.6 is the last
|
|
39
|
+
# version that still has it. NOTE: fastapi is also a first-party dependency of
|
|
40
|
+
# ee/python/transcription/server.py (not just a litellm transitive). Once
|
|
41
|
+
# litellm ships its own ceiling, replace this constrained pin with a bare
|
|
42
|
+
# `fastapi` line — do not delete it entirely.
|
|
43
|
+
fastapi>=0.136.3,<0.140.7 # to confirm this pin is still needed: grep -r get_flat_dependant $(pip show litellm | awk '/Location/{print $2}')/litellm/proxy/
|
|
35
44
|
# Prisma Python client — required by LiteLLM proxy whenever a `database_url`
|
|
36
45
|
# is set in config.litellm.yaml (used for user/budget tracking, key
|
|
37
|
-
# management, etc.). NOT included in litellm[proxy] in 1.
|
|
46
|
+
# management, etc.). NOT included in litellm[proxy] in 1.97.0, hence the
|
|
38
47
|
# separate pin. setup.sh runs `prisma generate` against LiteLLM's bundled
|
|
39
48
|
# schema after pip install so the Python client module is materialized.
|
|
40
49
|
prisma==0.15.0
|