hazo_files 1.4.2 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -4
- package/dist/index.d.mts +117 -4
- package/dist/index.d.ts +117 -4
- package/dist/index.js +158 -11
- package/dist/index.mjs +155 -11
- package/dist/server/index.d.mts +122 -4
- package/dist/server/index.d.ts +122 -4
- package/dist/server/index.js +162 -13
- package/dist/server/index.mjs +159 -13
- package/package.json +2 -6
package/dist/server/index.js
CHANGED
|
@@ -46,6 +46,7 @@ __export(index_exports, {
|
|
|
46
46
|
GoogleDriveModule: () => GoogleDriveModule,
|
|
47
47
|
HAZO_FILES_DEFAULT_TABLE_NAME: () => HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
48
48
|
HAZO_FILES_MIGRATION_V2: () => HAZO_FILES_MIGRATION_V2,
|
|
49
|
+
HAZO_FILES_MIGRATION_V3: () => HAZO_FILES_MIGRATION_V3,
|
|
49
50
|
HAZO_FILES_NAMING_DEFAULT_TABLE_NAME: () => HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
|
|
50
51
|
HAZO_FILES_NAMING_TABLE_SCHEMA: () => HAZO_FILES_NAMING_TABLE_SCHEMA,
|
|
51
52
|
HAZO_FILES_TABLE_SCHEMA: () => HAZO_FILES_TABLE_SCHEMA,
|
|
@@ -117,6 +118,7 @@ __export(index_exports, {
|
|
|
117
118
|
getFileMetadataValues: () => getFileMetadataValues,
|
|
118
119
|
getMergedData: () => getMergedData,
|
|
119
120
|
getMigrationForTable: () => getMigrationForTable,
|
|
121
|
+
getMigrationV3ForTable: () => getMigrationV3ForTable,
|
|
120
122
|
getMimeType: () => getMimeType,
|
|
121
123
|
getNameWithoutExtension: () => getNameWithoutExtension,
|
|
122
124
|
getNamingSchemaForTable: () => getNamingSchemaForTable,
|
|
@@ -149,6 +151,7 @@ __export(index_exports, {
|
|
|
149
151
|
loadConfig: () => loadConfig,
|
|
150
152
|
loadConfigAsync: () => loadConfigAsync,
|
|
151
153
|
migrateToV2: () => migrateToV2,
|
|
154
|
+
migrateToV3: () => migrateToV3,
|
|
152
155
|
normalizePath: () => normalizePath,
|
|
153
156
|
parseConfig: () => parseConfig,
|
|
154
157
|
parseFileData: () => parseFileData,
|
|
@@ -2397,6 +2400,7 @@ var FileMetadataService = class {
|
|
|
2397
2400
|
if (input.scope_id !== void 0) record.scope_id = input.scope_id;
|
|
2398
2401
|
if (input.uploaded_by !== void 0) record.uploaded_by = input.uploaded_by;
|
|
2399
2402
|
if (input.original_filename !== void 0) record.original_filename = input.original_filename;
|
|
2403
|
+
if (input.content_tag !== void 0) record.content_tag = input.content_tag;
|
|
2400
2404
|
const results = await this.crud.insert(record);
|
|
2401
2405
|
this.logger?.debug?.("Recorded file upload", { path: input.file_path });
|
|
2402
2406
|
return results[0] || null;
|
|
@@ -4231,10 +4235,11 @@ function generatePreviewName(pattern, userVariables, options = {}) {
|
|
|
4231
4235
|
|
|
4232
4236
|
// src/services/upload-extract-service.ts
|
|
4233
4237
|
var UploadExtractService = class {
|
|
4234
|
-
constructor(fileManager, namingService, extractionService) {
|
|
4238
|
+
constructor(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
4235
4239
|
this.fileManager = fileManager;
|
|
4236
4240
|
this.namingService = namingService;
|
|
4237
4241
|
this.extractionService = extractionService;
|
|
4242
|
+
this.defaultContentTagConfig = defaultContentTagConfig;
|
|
4238
4243
|
}
|
|
4239
4244
|
/**
|
|
4240
4245
|
* Upload a file with optional extraction and naming convention
|
|
@@ -4311,11 +4316,12 @@ var UploadExtractService = class {
|
|
|
4311
4316
|
metadata.extraction_id = extractionData.id;
|
|
4312
4317
|
metadata.extraction_source = extractionData.source;
|
|
4313
4318
|
}
|
|
4319
|
+
const effectiveContentTagConfig = options.contentTagConfig ?? this.defaultContentTagConfig;
|
|
4320
|
+
const needsContentTagging = effectiveContentTagConfig?.content_tag_set_by_llm && this.extractionService && this.fileManager.isTrackingActive();
|
|
4314
4321
|
const uploadResult = await this.fileManager.uploadFile(source, fullPath, {
|
|
4315
4322
|
...options,
|
|
4316
4323
|
metadata,
|
|
4317
|
-
awaitRecording: !!extractionData
|
|
4318
|
-
// Await recording when extraction needs to be added
|
|
4324
|
+
awaitRecording: !!extractionData || !!needsContentTagging
|
|
4319
4325
|
});
|
|
4320
4326
|
if (!uploadResult.success) {
|
|
4321
4327
|
return {
|
|
@@ -4339,13 +4345,23 @@ var UploadExtractService = class {
|
|
|
4339
4345
|
);
|
|
4340
4346
|
}
|
|
4341
4347
|
}
|
|
4348
|
+
let contentTag;
|
|
4349
|
+
if (needsContentTagging && effectiveContentTagConfig) {
|
|
4350
|
+
contentTag = await this.performContentTagging(
|
|
4351
|
+
source,
|
|
4352
|
+
mimeType,
|
|
4353
|
+
effectiveContentTagConfig,
|
|
4354
|
+
fullPath
|
|
4355
|
+
);
|
|
4356
|
+
}
|
|
4342
4357
|
return {
|
|
4343
4358
|
success: true,
|
|
4344
4359
|
file: uploadResult.data,
|
|
4345
4360
|
extraction: extractionData,
|
|
4346
4361
|
generatedPath: fullPath,
|
|
4347
4362
|
generatedFolderPath: generatedFolderPath || void 0,
|
|
4348
|
-
originalFileName
|
|
4363
|
+
originalFileName,
|
|
4364
|
+
contentTag
|
|
4349
4365
|
};
|
|
4350
4366
|
} catch (error) {
|
|
4351
4367
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4443,6 +4459,76 @@ var UploadExtractService = class {
|
|
|
4443
4459
|
folderPath: folderPath || void 0
|
|
4444
4460
|
};
|
|
4445
4461
|
}
|
|
4462
|
+
/**
|
|
4463
|
+
* Perform content tagging via LLM extraction.
|
|
4464
|
+
* Calls the LLM with the configured prompt, extracts the specified field,
|
|
4465
|
+
* and writes it to the content_tag column.
|
|
4466
|
+
*/
|
|
4467
|
+
async performContentTagging(buffer, mimeType, config, filePath) {
|
|
4468
|
+
try {
|
|
4469
|
+
if (!this.extractionService) return void 0;
|
|
4470
|
+
const result = await this.extractionService.extract(buffer, mimeType, {
|
|
4471
|
+
promptArea: config.content_tag_prompt_area,
|
|
4472
|
+
promptKey: config.content_tag_prompt_key,
|
|
4473
|
+
promptVariables: config.content_tag_prompt_variables
|
|
4474
|
+
});
|
|
4475
|
+
if (!result.success || !result.data) return void 0;
|
|
4476
|
+
const tagValue = result.data[config.content_tag_prompt_return_fieldname];
|
|
4477
|
+
if (typeof tagValue !== "string" || !tagValue) return void 0;
|
|
4478
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
4479
|
+
if (metadataService) {
|
|
4480
|
+
const storageType = this.fileManager.getProvider() || "local";
|
|
4481
|
+
const record = await metadataService.findByPath(filePath, storageType);
|
|
4482
|
+
if (record) {
|
|
4483
|
+
await metadataService.updateFields(record.id, { content_tag: tagValue });
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
return tagValue;
|
|
4487
|
+
} catch {
|
|
4488
|
+
return void 0;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
/**
|
|
4492
|
+
* Manually tag a file's content via LLM.
|
|
4493
|
+
* Works with existing DB records, resolving the file path internally.
|
|
4494
|
+
*
|
|
4495
|
+
* @param fileId - Database record ID of the file
|
|
4496
|
+
* @param config - Content tag config (falls back to default if not provided)
|
|
4497
|
+
* @returns OperationResult with the tag value
|
|
4498
|
+
*/
|
|
4499
|
+
async tagFileContent(fileId, config) {
|
|
4500
|
+
const effectiveConfig = config ?? this.defaultContentTagConfig;
|
|
4501
|
+
if (!effectiveConfig || !effectiveConfig.content_tag_set_by_llm) {
|
|
4502
|
+
return { success: false, error: "Content tagging is not configured or disabled" };
|
|
4503
|
+
}
|
|
4504
|
+
if (!this.extractionService) {
|
|
4505
|
+
return { success: false, error: "Extraction service not available" };
|
|
4506
|
+
}
|
|
4507
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
4508
|
+
if (!metadataService) {
|
|
4509
|
+
return { success: false, error: "Metadata service not available (tracking not enabled)" };
|
|
4510
|
+
}
|
|
4511
|
+
const record = await metadataService.findById(fileId);
|
|
4512
|
+
if (!record) {
|
|
4513
|
+
return { success: false, error: `File record not found: ${fileId}` };
|
|
4514
|
+
}
|
|
4515
|
+
const downloadResult = await this.fileManager.downloadFile(record.file_path);
|
|
4516
|
+
if (!downloadResult.success || !downloadResult.data) {
|
|
4517
|
+
return { success: false, error: `Failed to download file: ${downloadResult.error}` };
|
|
4518
|
+
}
|
|
4519
|
+
const buffer = Buffer.isBuffer(downloadResult.data) ? downloadResult.data : Buffer.from(downloadResult.data);
|
|
4520
|
+
const mimeType = getMimeType(record.filename);
|
|
4521
|
+
const tagValue = await this.performContentTagging(
|
|
4522
|
+
buffer,
|
|
4523
|
+
mimeType,
|
|
4524
|
+
effectiveConfig,
|
|
4525
|
+
record.file_path
|
|
4526
|
+
);
|
|
4527
|
+
if (!tagValue) {
|
|
4528
|
+
return { success: false, error: "Content tagging did not produce a result" };
|
|
4529
|
+
}
|
|
4530
|
+
return { success: true, data: tagValue };
|
|
4531
|
+
}
|
|
4446
4532
|
/**
|
|
4447
4533
|
* Get the file manager
|
|
4448
4534
|
*/
|
|
@@ -4462,8 +4548,8 @@ var UploadExtractService = class {
|
|
|
4462
4548
|
return this.extractionService;
|
|
4463
4549
|
}
|
|
4464
4550
|
};
|
|
4465
|
-
function createUploadExtractService(fileManager, namingService, extractionService) {
|
|
4466
|
-
return new UploadExtractService(fileManager, namingService, extractionService);
|
|
4551
|
+
function createUploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
4552
|
+
return new UploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig);
|
|
4467
4553
|
}
|
|
4468
4554
|
|
|
4469
4555
|
// src/server/factory.ts
|
|
@@ -4479,7 +4565,8 @@ async function createHazoFilesServer(options = {}) {
|
|
|
4479
4565
|
metadataTableName = "hazo_files",
|
|
4480
4566
|
namingTableName = "hazo_files_naming",
|
|
4481
4567
|
enableTracking = !!crudService,
|
|
4482
|
-
trackDownloads = true
|
|
4568
|
+
trackDownloads = true,
|
|
4569
|
+
defaultContentTagConfig
|
|
4483
4570
|
} = options;
|
|
4484
4571
|
const fileManagerOptions = {
|
|
4485
4572
|
config,
|
|
@@ -4508,7 +4595,8 @@ async function createHazoFilesServer(options = {}) {
|
|
|
4508
4595
|
const uploadExtractService = new UploadExtractService(
|
|
4509
4596
|
fileManager,
|
|
4510
4597
|
namingService || void 0,
|
|
4511
|
-
extractionService || void 0
|
|
4598
|
+
extractionService || void 0,
|
|
4599
|
+
defaultContentTagConfig
|
|
4512
4600
|
);
|
|
4513
4601
|
return {
|
|
4514
4602
|
fileManager,
|
|
@@ -4554,7 +4642,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4554
4642
|
uploaded_by TEXT,
|
|
4555
4643
|
storage_verified_at TEXT,
|
|
4556
4644
|
deleted_at TEXT,
|
|
4557
|
-
original_filename TEXT
|
|
4645
|
+
original_filename TEXT,
|
|
4646
|
+
content_tag TEXT
|
|
4558
4647
|
)`,
|
|
4559
4648
|
indexes: [
|
|
4560
4649
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4564,7 +4653,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4564
4653
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4565
4654
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4566
4655
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4567
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
4656
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
4657
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4568
4658
|
]
|
|
4569
4659
|
},
|
|
4570
4660
|
postgres: {
|
|
@@ -4587,7 +4677,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4587
4677
|
uploaded_by UUID,
|
|
4588
4678
|
storage_verified_at TIMESTAMP WITH TIME ZONE,
|
|
4589
4679
|
deleted_at TIMESTAMP WITH TIME ZONE,
|
|
4590
|
-
original_filename TEXT
|
|
4680
|
+
original_filename TEXT,
|
|
4681
|
+
content_tag TEXT
|
|
4591
4682
|
)`,
|
|
4592
4683
|
indexes: [
|
|
4593
4684
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4597,7 +4688,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4597
4688
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4598
4689
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4599
4690
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4600
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
4691
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
4692
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4601
4693
|
]
|
|
4602
4694
|
},
|
|
4603
4695
|
columns: [
|
|
@@ -4619,7 +4711,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4619
4711
|
"uploaded_by",
|
|
4620
4712
|
"storage_verified_at",
|
|
4621
4713
|
"deleted_at",
|
|
4622
|
-
"original_filename"
|
|
4714
|
+
"original_filename",
|
|
4715
|
+
"content_tag"
|
|
4623
4716
|
]
|
|
4624
4717
|
};
|
|
4625
4718
|
function getSchemaForTable(tableName, dbType) {
|
|
@@ -4760,6 +4853,45 @@ function getNamingSchemaForTable(tableName, dbType) {
|
|
|
4760
4853
|
)
|
|
4761
4854
|
};
|
|
4762
4855
|
}
|
|
4856
|
+
var HAZO_FILES_MIGRATION_V3 = {
|
|
4857
|
+
tableName: HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
4858
|
+
sqlite: {
|
|
4859
|
+
alterStatements: [
|
|
4860
|
+
"ALTER TABLE hazo_files ADD COLUMN content_tag TEXT"
|
|
4861
|
+
],
|
|
4862
|
+
indexes: [
|
|
4863
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4864
|
+
],
|
|
4865
|
+
backfill: ""
|
|
4866
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
4867
|
+
},
|
|
4868
|
+
postgres: {
|
|
4869
|
+
alterStatements: [
|
|
4870
|
+
"ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS content_tag TEXT"
|
|
4871
|
+
],
|
|
4872
|
+
indexes: [
|
|
4873
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4874
|
+
],
|
|
4875
|
+
backfill: ""
|
|
4876
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
4877
|
+
},
|
|
4878
|
+
newColumns: [
|
|
4879
|
+
"content_tag"
|
|
4880
|
+
]
|
|
4881
|
+
};
|
|
4882
|
+
function getMigrationV3ForTable(tableName, dbType) {
|
|
4883
|
+
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
4884
|
+
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
4885
|
+
return {
|
|
4886
|
+
alterStatements: migration.alterStatements.map(
|
|
4887
|
+
(stmt) => stmt.replace(new RegExp(defaultName, "g"), tableName)
|
|
4888
|
+
),
|
|
4889
|
+
indexes: migration.indexes.map(
|
|
4890
|
+
(idx) => idx.replace(new RegExp(defaultName, "g"), tableName)
|
|
4891
|
+
),
|
|
4892
|
+
backfill: migration.backfill
|
|
4893
|
+
};
|
|
4894
|
+
}
|
|
4763
4895
|
|
|
4764
4896
|
// src/migrations/add-reference-tracking.ts
|
|
4765
4897
|
async function migrateToV2(executor, dbType, tableName) {
|
|
@@ -4779,6 +4911,20 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4779
4911
|
await executor.run(migration.backfill);
|
|
4780
4912
|
}
|
|
4781
4913
|
|
|
4914
|
+
// src/migrations/add-content-tag.ts
|
|
4915
|
+
async function migrateToV3(executor, dbType, tableName) {
|
|
4916
|
+
const migration = tableName ? getMigrationV3ForTable(tableName, dbType) : HAZO_FILES_MIGRATION_V3[dbType];
|
|
4917
|
+
for (const stmt of migration.alterStatements) {
|
|
4918
|
+
try {
|
|
4919
|
+
await executor.run(stmt);
|
|
4920
|
+
} catch {
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
for (const idx of migration.indexes) {
|
|
4924
|
+
await executor.run(idx);
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4782
4928
|
// src/server/index.ts
|
|
4783
4929
|
try {
|
|
4784
4930
|
require("server-only");
|
|
@@ -4802,6 +4948,7 @@ try {
|
|
|
4802
4948
|
GoogleDriveModule,
|
|
4803
4949
|
HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
4804
4950
|
HAZO_FILES_MIGRATION_V2,
|
|
4951
|
+
HAZO_FILES_MIGRATION_V3,
|
|
4805
4952
|
HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
|
|
4806
4953
|
HAZO_FILES_NAMING_TABLE_SCHEMA,
|
|
4807
4954
|
HAZO_FILES_TABLE_SCHEMA,
|
|
@@ -4873,6 +5020,7 @@ try {
|
|
|
4873
5020
|
getFileMetadataValues,
|
|
4874
5021
|
getMergedData,
|
|
4875
5022
|
getMigrationForTable,
|
|
5023
|
+
getMigrationV3ForTable,
|
|
4876
5024
|
getMimeType,
|
|
4877
5025
|
getNameWithoutExtension,
|
|
4878
5026
|
getNamingSchemaForTable,
|
|
@@ -4905,6 +5053,7 @@ try {
|
|
|
4905
5053
|
loadConfig,
|
|
4906
5054
|
loadConfigAsync,
|
|
4907
5055
|
migrateToV2,
|
|
5056
|
+
migrateToV3,
|
|
4908
5057
|
normalizePath,
|
|
4909
5058
|
parseConfig,
|
|
4910
5059
|
parseFileData,
|
package/dist/server/index.mjs
CHANGED
|
@@ -2226,6 +2226,7 @@ var FileMetadataService = class {
|
|
|
2226
2226
|
if (input.scope_id !== void 0) record.scope_id = input.scope_id;
|
|
2227
2227
|
if (input.uploaded_by !== void 0) record.uploaded_by = input.uploaded_by;
|
|
2228
2228
|
if (input.original_filename !== void 0) record.original_filename = input.original_filename;
|
|
2229
|
+
if (input.content_tag !== void 0) record.content_tag = input.content_tag;
|
|
2229
2230
|
const results = await this.crud.insert(record);
|
|
2230
2231
|
this.logger?.debug?.("Recorded file upload", { path: input.file_path });
|
|
2231
2232
|
return results[0] || null;
|
|
@@ -4060,10 +4061,11 @@ function generatePreviewName(pattern, userVariables, options = {}) {
|
|
|
4060
4061
|
|
|
4061
4062
|
// src/services/upload-extract-service.ts
|
|
4062
4063
|
var UploadExtractService = class {
|
|
4063
|
-
constructor(fileManager, namingService, extractionService) {
|
|
4064
|
+
constructor(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
4064
4065
|
this.fileManager = fileManager;
|
|
4065
4066
|
this.namingService = namingService;
|
|
4066
4067
|
this.extractionService = extractionService;
|
|
4068
|
+
this.defaultContentTagConfig = defaultContentTagConfig;
|
|
4067
4069
|
}
|
|
4068
4070
|
/**
|
|
4069
4071
|
* Upload a file with optional extraction and naming convention
|
|
@@ -4140,11 +4142,12 @@ var UploadExtractService = class {
|
|
|
4140
4142
|
metadata.extraction_id = extractionData.id;
|
|
4141
4143
|
metadata.extraction_source = extractionData.source;
|
|
4142
4144
|
}
|
|
4145
|
+
const effectiveContentTagConfig = options.contentTagConfig ?? this.defaultContentTagConfig;
|
|
4146
|
+
const needsContentTagging = effectiveContentTagConfig?.content_tag_set_by_llm && this.extractionService && this.fileManager.isTrackingActive();
|
|
4143
4147
|
const uploadResult = await this.fileManager.uploadFile(source, fullPath, {
|
|
4144
4148
|
...options,
|
|
4145
4149
|
metadata,
|
|
4146
|
-
awaitRecording: !!extractionData
|
|
4147
|
-
// Await recording when extraction needs to be added
|
|
4150
|
+
awaitRecording: !!extractionData || !!needsContentTagging
|
|
4148
4151
|
});
|
|
4149
4152
|
if (!uploadResult.success) {
|
|
4150
4153
|
return {
|
|
@@ -4168,13 +4171,23 @@ var UploadExtractService = class {
|
|
|
4168
4171
|
);
|
|
4169
4172
|
}
|
|
4170
4173
|
}
|
|
4174
|
+
let contentTag;
|
|
4175
|
+
if (needsContentTagging && effectiveContentTagConfig) {
|
|
4176
|
+
contentTag = await this.performContentTagging(
|
|
4177
|
+
source,
|
|
4178
|
+
mimeType,
|
|
4179
|
+
effectiveContentTagConfig,
|
|
4180
|
+
fullPath
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4171
4183
|
return {
|
|
4172
4184
|
success: true,
|
|
4173
4185
|
file: uploadResult.data,
|
|
4174
4186
|
extraction: extractionData,
|
|
4175
4187
|
generatedPath: fullPath,
|
|
4176
4188
|
generatedFolderPath: generatedFolderPath || void 0,
|
|
4177
|
-
originalFileName
|
|
4189
|
+
originalFileName,
|
|
4190
|
+
contentTag
|
|
4178
4191
|
};
|
|
4179
4192
|
} catch (error) {
|
|
4180
4193
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4272,6 +4285,76 @@ var UploadExtractService = class {
|
|
|
4272
4285
|
folderPath: folderPath || void 0
|
|
4273
4286
|
};
|
|
4274
4287
|
}
|
|
4288
|
+
/**
|
|
4289
|
+
* Perform content tagging via LLM extraction.
|
|
4290
|
+
* Calls the LLM with the configured prompt, extracts the specified field,
|
|
4291
|
+
* and writes it to the content_tag column.
|
|
4292
|
+
*/
|
|
4293
|
+
async performContentTagging(buffer, mimeType, config, filePath) {
|
|
4294
|
+
try {
|
|
4295
|
+
if (!this.extractionService) return void 0;
|
|
4296
|
+
const result = await this.extractionService.extract(buffer, mimeType, {
|
|
4297
|
+
promptArea: config.content_tag_prompt_area,
|
|
4298
|
+
promptKey: config.content_tag_prompt_key,
|
|
4299
|
+
promptVariables: config.content_tag_prompt_variables
|
|
4300
|
+
});
|
|
4301
|
+
if (!result.success || !result.data) return void 0;
|
|
4302
|
+
const tagValue = result.data[config.content_tag_prompt_return_fieldname];
|
|
4303
|
+
if (typeof tagValue !== "string" || !tagValue) return void 0;
|
|
4304
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
4305
|
+
if (metadataService) {
|
|
4306
|
+
const storageType = this.fileManager.getProvider() || "local";
|
|
4307
|
+
const record = await metadataService.findByPath(filePath, storageType);
|
|
4308
|
+
if (record) {
|
|
4309
|
+
await metadataService.updateFields(record.id, { content_tag: tagValue });
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
return tagValue;
|
|
4313
|
+
} catch {
|
|
4314
|
+
return void 0;
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
/**
|
|
4318
|
+
* Manually tag a file's content via LLM.
|
|
4319
|
+
* Works with existing DB records, resolving the file path internally.
|
|
4320
|
+
*
|
|
4321
|
+
* @param fileId - Database record ID of the file
|
|
4322
|
+
* @param config - Content tag config (falls back to default if not provided)
|
|
4323
|
+
* @returns OperationResult with the tag value
|
|
4324
|
+
*/
|
|
4325
|
+
async tagFileContent(fileId, config) {
|
|
4326
|
+
const effectiveConfig = config ?? this.defaultContentTagConfig;
|
|
4327
|
+
if (!effectiveConfig || !effectiveConfig.content_tag_set_by_llm) {
|
|
4328
|
+
return { success: false, error: "Content tagging is not configured or disabled" };
|
|
4329
|
+
}
|
|
4330
|
+
if (!this.extractionService) {
|
|
4331
|
+
return { success: false, error: "Extraction service not available" };
|
|
4332
|
+
}
|
|
4333
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
4334
|
+
if (!metadataService) {
|
|
4335
|
+
return { success: false, error: "Metadata service not available (tracking not enabled)" };
|
|
4336
|
+
}
|
|
4337
|
+
const record = await metadataService.findById(fileId);
|
|
4338
|
+
if (!record) {
|
|
4339
|
+
return { success: false, error: `File record not found: ${fileId}` };
|
|
4340
|
+
}
|
|
4341
|
+
const downloadResult = await this.fileManager.downloadFile(record.file_path);
|
|
4342
|
+
if (!downloadResult.success || !downloadResult.data) {
|
|
4343
|
+
return { success: false, error: `Failed to download file: ${downloadResult.error}` };
|
|
4344
|
+
}
|
|
4345
|
+
const buffer = Buffer.isBuffer(downloadResult.data) ? downloadResult.data : Buffer.from(downloadResult.data);
|
|
4346
|
+
const mimeType = getMimeType(record.filename);
|
|
4347
|
+
const tagValue = await this.performContentTagging(
|
|
4348
|
+
buffer,
|
|
4349
|
+
mimeType,
|
|
4350
|
+
effectiveConfig,
|
|
4351
|
+
record.file_path
|
|
4352
|
+
);
|
|
4353
|
+
if (!tagValue) {
|
|
4354
|
+
return { success: false, error: "Content tagging did not produce a result" };
|
|
4355
|
+
}
|
|
4356
|
+
return { success: true, data: tagValue };
|
|
4357
|
+
}
|
|
4275
4358
|
/**
|
|
4276
4359
|
* Get the file manager
|
|
4277
4360
|
*/
|
|
@@ -4291,8 +4374,8 @@ var UploadExtractService = class {
|
|
|
4291
4374
|
return this.extractionService;
|
|
4292
4375
|
}
|
|
4293
4376
|
};
|
|
4294
|
-
function createUploadExtractService(fileManager, namingService, extractionService) {
|
|
4295
|
-
return new UploadExtractService(fileManager, namingService, extractionService);
|
|
4377
|
+
function createUploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
4378
|
+
return new UploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig);
|
|
4296
4379
|
}
|
|
4297
4380
|
|
|
4298
4381
|
// src/server/factory.ts
|
|
@@ -4308,7 +4391,8 @@ async function createHazoFilesServer(options = {}) {
|
|
|
4308
4391
|
metadataTableName = "hazo_files",
|
|
4309
4392
|
namingTableName = "hazo_files_naming",
|
|
4310
4393
|
enableTracking = !!crudService,
|
|
4311
|
-
trackDownloads = true
|
|
4394
|
+
trackDownloads = true,
|
|
4395
|
+
defaultContentTagConfig
|
|
4312
4396
|
} = options;
|
|
4313
4397
|
const fileManagerOptions = {
|
|
4314
4398
|
config,
|
|
@@ -4337,7 +4421,8 @@ async function createHazoFilesServer(options = {}) {
|
|
|
4337
4421
|
const uploadExtractService = new UploadExtractService(
|
|
4338
4422
|
fileManager,
|
|
4339
4423
|
namingService || void 0,
|
|
4340
|
-
extractionService || void 0
|
|
4424
|
+
extractionService || void 0,
|
|
4425
|
+
defaultContentTagConfig
|
|
4341
4426
|
);
|
|
4342
4427
|
return {
|
|
4343
4428
|
fileManager,
|
|
@@ -4383,7 +4468,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4383
4468
|
uploaded_by TEXT,
|
|
4384
4469
|
storage_verified_at TEXT,
|
|
4385
4470
|
deleted_at TEXT,
|
|
4386
|
-
original_filename TEXT
|
|
4471
|
+
original_filename TEXT,
|
|
4472
|
+
content_tag TEXT
|
|
4387
4473
|
)`,
|
|
4388
4474
|
indexes: [
|
|
4389
4475
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4393,7 +4479,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4393
4479
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4394
4480
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4395
4481
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4396
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
4482
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
4483
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4397
4484
|
]
|
|
4398
4485
|
},
|
|
4399
4486
|
postgres: {
|
|
@@ -4416,7 +4503,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4416
4503
|
uploaded_by UUID,
|
|
4417
4504
|
storage_verified_at TIMESTAMP WITH TIME ZONE,
|
|
4418
4505
|
deleted_at TIMESTAMP WITH TIME ZONE,
|
|
4419
|
-
original_filename TEXT
|
|
4506
|
+
original_filename TEXT,
|
|
4507
|
+
content_tag TEXT
|
|
4420
4508
|
)`,
|
|
4421
4509
|
indexes: [
|
|
4422
4510
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4426,7 +4514,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4426
4514
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4427
4515
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4428
4516
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4429
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
4517
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
4518
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4430
4519
|
]
|
|
4431
4520
|
},
|
|
4432
4521
|
columns: [
|
|
@@ -4448,7 +4537,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4448
4537
|
"uploaded_by",
|
|
4449
4538
|
"storage_verified_at",
|
|
4450
4539
|
"deleted_at",
|
|
4451
|
-
"original_filename"
|
|
4540
|
+
"original_filename",
|
|
4541
|
+
"content_tag"
|
|
4452
4542
|
]
|
|
4453
4543
|
};
|
|
4454
4544
|
function getSchemaForTable(tableName, dbType) {
|
|
@@ -4589,6 +4679,45 @@ function getNamingSchemaForTable(tableName, dbType) {
|
|
|
4589
4679
|
)
|
|
4590
4680
|
};
|
|
4591
4681
|
}
|
|
4682
|
+
var HAZO_FILES_MIGRATION_V3 = {
|
|
4683
|
+
tableName: HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
4684
|
+
sqlite: {
|
|
4685
|
+
alterStatements: [
|
|
4686
|
+
"ALTER TABLE hazo_files ADD COLUMN content_tag TEXT"
|
|
4687
|
+
],
|
|
4688
|
+
indexes: [
|
|
4689
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4690
|
+
],
|
|
4691
|
+
backfill: ""
|
|
4692
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
4693
|
+
},
|
|
4694
|
+
postgres: {
|
|
4695
|
+
alterStatements: [
|
|
4696
|
+
"ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS content_tag TEXT"
|
|
4697
|
+
],
|
|
4698
|
+
indexes: [
|
|
4699
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4700
|
+
],
|
|
4701
|
+
backfill: ""
|
|
4702
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
4703
|
+
},
|
|
4704
|
+
newColumns: [
|
|
4705
|
+
"content_tag"
|
|
4706
|
+
]
|
|
4707
|
+
};
|
|
4708
|
+
function getMigrationV3ForTable(tableName, dbType) {
|
|
4709
|
+
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
4710
|
+
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
4711
|
+
return {
|
|
4712
|
+
alterStatements: migration.alterStatements.map(
|
|
4713
|
+
(stmt) => stmt.replace(new RegExp(defaultName, "g"), tableName)
|
|
4714
|
+
),
|
|
4715
|
+
indexes: migration.indexes.map(
|
|
4716
|
+
(idx) => idx.replace(new RegExp(defaultName, "g"), tableName)
|
|
4717
|
+
),
|
|
4718
|
+
backfill: migration.backfill
|
|
4719
|
+
};
|
|
4720
|
+
}
|
|
4592
4721
|
|
|
4593
4722
|
// src/migrations/add-reference-tracking.ts
|
|
4594
4723
|
async function migrateToV2(executor, dbType, tableName) {
|
|
@@ -4608,6 +4737,20 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4608
4737
|
await executor.run(migration.backfill);
|
|
4609
4738
|
}
|
|
4610
4739
|
|
|
4740
|
+
// src/migrations/add-content-tag.ts
|
|
4741
|
+
async function migrateToV3(executor, dbType, tableName) {
|
|
4742
|
+
const migration = tableName ? getMigrationV3ForTable(tableName, dbType) : HAZO_FILES_MIGRATION_V3[dbType];
|
|
4743
|
+
for (const stmt of migration.alterStatements) {
|
|
4744
|
+
try {
|
|
4745
|
+
await executor.run(stmt);
|
|
4746
|
+
} catch {
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
for (const idx of migration.indexes) {
|
|
4750
|
+
await executor.run(idx);
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4611
4754
|
// src/server/index.ts
|
|
4612
4755
|
try {
|
|
4613
4756
|
__require("server-only");
|
|
@@ -4630,6 +4773,7 @@ export {
|
|
|
4630
4773
|
GoogleDriveModule,
|
|
4631
4774
|
HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
4632
4775
|
HAZO_FILES_MIGRATION_V2,
|
|
4776
|
+
HAZO_FILES_MIGRATION_V3,
|
|
4633
4777
|
HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
|
|
4634
4778
|
HAZO_FILES_NAMING_TABLE_SCHEMA,
|
|
4635
4779
|
HAZO_FILES_TABLE_SCHEMA,
|
|
@@ -4701,6 +4845,7 @@ export {
|
|
|
4701
4845
|
getFileMetadataValues,
|
|
4702
4846
|
getMergedData,
|
|
4703
4847
|
getMigrationForTable,
|
|
4848
|
+
getMigrationV3ForTable,
|
|
4704
4849
|
getMimeType,
|
|
4705
4850
|
getNameWithoutExtension,
|
|
4706
4851
|
getNamingSchemaForTable,
|
|
@@ -4733,6 +4878,7 @@ export {
|
|
|
4733
4878
|
loadConfig,
|
|
4734
4879
|
loadConfigAsync,
|
|
4735
4880
|
migrateToV2,
|
|
4881
|
+
migrateToV3,
|
|
4736
4882
|
normalizePath,
|
|
4737
4883
|
parseConfig,
|
|
4738
4884
|
parseFileData,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_files",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "File management including integration to cloud files",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -84,9 +84,8 @@
|
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
86
|
"@dnd-kit/core": "^6.0.0",
|
|
87
|
-
"@dnd-kit/sortable": "
|
|
87
|
+
"@dnd-kit/sortable": ">=8.0.0",
|
|
88
88
|
"@dnd-kit/utilities": "^3.0.0",
|
|
89
|
-
"hazo_config": ">=2.0.0",
|
|
90
89
|
"hazo_connect": ">=2.0.0",
|
|
91
90
|
"hazo_llm_api": ">=1.0.0",
|
|
92
91
|
"hazo_logs": ">=1.0.0",
|
|
@@ -107,9 +106,6 @@
|
|
|
107
106
|
"hazo_connect": {
|
|
108
107
|
"optional": true
|
|
109
108
|
},
|
|
110
|
-
"hazo_config": {
|
|
111
|
-
"optional": true
|
|
112
|
-
},
|
|
113
109
|
"hazo_logs": {
|
|
114
110
|
"optional": true
|
|
115
111
|
},
|