@tstdl/base 0.92.35 → 0.92.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/ai-file.service.d.ts +1 -0
- package/ai/ai-file.service.js +63 -33
- package/ai/ai.service.d.ts +1 -0
- package/package.json +2 -1
package/ai/ai-file.service.d.ts
CHANGED
package/ai/ai-file.service.js
CHANGED
|
@@ -60,6 +60,7 @@ import '../polyfills.js';
|
|
|
60
60
|
import { stat, unlink, writeFile } from 'node:fs/promises';
|
|
61
61
|
import { tmpdir } from 'node:os';
|
|
62
62
|
import { join } from 'node:path';
|
|
63
|
+
import { Storage } from '@google-cloud/storage';
|
|
63
64
|
import { FileState, GoogleAIFileManager } from '@google/generative-ai/server';
|
|
64
65
|
import { AsyncEnumerable } from '../enumerable/async-enumerable.js';
|
|
65
66
|
import { DetailsError } from '../errors/details.error.js';
|
|
@@ -70,14 +71,16 @@ import { createArray } from '../utils/array/array.js';
|
|
|
70
71
|
import { formatBytes } from '../utils/format.js';
|
|
71
72
|
import { timeout } from '../utils/timing.js';
|
|
72
73
|
import { tryIgnoreAsync } from '../utils/try-ignore.js';
|
|
73
|
-
import { isBlob } from '../utils/type-guards.js';
|
|
74
|
+
import { assertDefinedPass, isBlob, isDefined, isUndefined } from '../utils/type-guards.js';
|
|
74
75
|
import { millisecondsPerSecond } from '../utils/units.js';
|
|
75
76
|
let AiFileService = class AiFileService {
|
|
76
77
|
#options = injectArgument(this);
|
|
77
|
-
#fileManager = new GoogleAIFileManager(this.#options.apiKey);
|
|
78
|
+
#fileManager = isUndefined(this.#options.vertex) ? new GoogleAIFileManager(this.#options.apiKey) : undefined;
|
|
79
|
+
#storage = isDefined(this.#options.vertex) ? new Storage({ projectId: this.#options.vertex.project, apiKey: this.#options.apiKey }) : undefined;
|
|
78
80
|
#fileMap = new Map();
|
|
79
81
|
#fileUriMap = new Map();
|
|
80
82
|
#logger = inject(Logger, 'AiFileService');
|
|
83
|
+
#bucket;
|
|
81
84
|
async processFile(fileInput) {
|
|
82
85
|
const file = await this.getFile(fileInput);
|
|
83
86
|
this.#fileMap.set(file.id, file);
|
|
@@ -100,27 +103,17 @@ let AiFileService = class AiFileService {
|
|
|
100
103
|
}
|
|
101
104
|
async getFile(fileInput) {
|
|
102
105
|
const id = crypto.randomUUID();
|
|
103
|
-
const
|
|
106
|
+
const file = await this.uploadFile(fileInput, id);
|
|
104
107
|
this.#logger.verbose(`Processing file "${id}"...`);
|
|
105
|
-
|
|
106
|
-
return
|
|
107
|
-
id,
|
|
108
|
-
name: response.name,
|
|
109
|
-
uri: response.uri,
|
|
110
|
-
mimeType: response.mimeType
|
|
111
|
-
};
|
|
108
|
+
await this.waitForFileActive(file);
|
|
109
|
+
return file;
|
|
112
110
|
}
|
|
113
|
-
async getFiles(
|
|
114
|
-
const ids = createArray(
|
|
115
|
-
const
|
|
116
|
-
this.#logger.verbose(`Processing ${
|
|
117
|
-
|
|
118
|
-
return
|
|
119
|
-
id: ids[index],
|
|
120
|
-
name: response.name,
|
|
121
|
-
uri: response.uri,
|
|
122
|
-
mimeType: response.mimeType
|
|
123
|
-
}));
|
|
111
|
+
async getFiles(fileInputs) {
|
|
112
|
+
const ids = createArray(fileInputs.length, () => crypto.randomUUID());
|
|
113
|
+
const files = await AsyncEnumerable.from(fileInputs).parallelMap(5, true, async (file, index) => this.uploadFile(file, ids[index])).toArray();
|
|
114
|
+
this.#logger.verbose(`Processing ${fileInputs.length} files...`);
|
|
115
|
+
await this.waitForFilesActive(files);
|
|
116
|
+
return files;
|
|
124
117
|
}
|
|
125
118
|
async uploadFile(fileInput, id) {
|
|
126
119
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
@@ -135,8 +128,23 @@ let AiFileService = class AiFileService {
|
|
|
135
128
|
}
|
|
136
129
|
const fileSize = isBlob(fileInput) ? fileInput.size : (await stat(path)).size;
|
|
137
130
|
this.#logger.verbose(`Uploading file "${id}" (${formatBytes(fileSize)})...`);
|
|
131
|
+
if (isDefined(this.#storage)) {
|
|
132
|
+
const bucket = await this.getBucket();
|
|
133
|
+
const [file] = await bucket.upload(path, { destination: id, contentType: mimeType });
|
|
134
|
+
return {
|
|
135
|
+
id,
|
|
136
|
+
name: id,
|
|
137
|
+
uri: file.cloudStorageURI.toString(),
|
|
138
|
+
mimeType
|
|
139
|
+
};
|
|
140
|
+
}
|
|
138
141
|
const response = await this.#fileManager.uploadFile(path, { mimeType });
|
|
139
|
-
return
|
|
142
|
+
return {
|
|
143
|
+
id,
|
|
144
|
+
name: response.file.name,
|
|
145
|
+
uri: response.file.uri,
|
|
146
|
+
mimeType: response.file.mimeType
|
|
147
|
+
};
|
|
140
148
|
}
|
|
141
149
|
catch (e_1) {
|
|
142
150
|
env_1.error = e_1;
|
|
@@ -148,24 +156,46 @@ let AiFileService = class AiFileService {
|
|
|
148
156
|
await result_1;
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
|
-
async
|
|
152
|
-
|
|
153
|
-
|
|
159
|
+
async getBucket() {
|
|
160
|
+
if (isUndefined(this.#options.vertex)) {
|
|
161
|
+
throw new Error('Not using Vertex');
|
|
162
|
+
}
|
|
163
|
+
if (isDefined(this.#bucket)) {
|
|
164
|
+
return this.#bucket;
|
|
165
|
+
}
|
|
166
|
+
const bucketName = assertDefinedPass(this.#options.vertex.bucket, 'Bucket not specified');
|
|
167
|
+
const [exists] = await this.#storage.bucket(bucketName).exists();
|
|
168
|
+
if (!exists) {
|
|
169
|
+
const [bucket] = await this.#storage.createBucket(bucketName, {
|
|
170
|
+
location: this.#options.vertex.location,
|
|
171
|
+
lifecycle: {
|
|
172
|
+
rule: [{
|
|
173
|
+
action: { type: 'Delete' },
|
|
174
|
+
condition: { age: 1 }
|
|
175
|
+
}]
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
this.#bucket = bucket;
|
|
179
|
+
}
|
|
180
|
+
return this.#bucket;
|
|
181
|
+
}
|
|
182
|
+
async waitForFileActive(file) {
|
|
183
|
+
if (isUndefined(this.#fileManager)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let state = await this.#fileManager.getFile(file.name);
|
|
187
|
+
while (state.state == FileState.PROCESSING) {
|
|
154
188
|
await timeout(millisecondsPerSecond);
|
|
155
|
-
|
|
189
|
+
state = await this.#fileManager.getFile(file.name);
|
|
156
190
|
}
|
|
157
|
-
if (
|
|
158
|
-
throw new DetailsError(
|
|
191
|
+
if (state.state == FileState.FAILED) {
|
|
192
|
+
throw new DetailsError(state.error?.message ?? `Failed to process file ${state.name}`, state.error?.details);
|
|
159
193
|
}
|
|
160
|
-
return file;
|
|
161
194
|
}
|
|
162
195
|
async waitForFilesActive(files) {
|
|
163
|
-
const responses = [];
|
|
164
196
|
for (const file of files) {
|
|
165
|
-
|
|
166
|
-
responses.push(respones);
|
|
197
|
+
await this.waitForFileActive(file);
|
|
167
198
|
}
|
|
168
|
-
return responses;
|
|
169
199
|
}
|
|
170
200
|
};
|
|
171
201
|
AiFileService = __decorate([
|
package/ai/ai.service.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.37",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
"./utils/string": "./utils/string/index.js"
|
|
119
119
|
},
|
|
120
120
|
"dependencies": {
|
|
121
|
+
"@google-cloud/storage": "^7.15.0",
|
|
121
122
|
"@google-cloud/vertexai": "^1.9.2",
|
|
122
123
|
"disposablestack": "1.1",
|
|
123
124
|
"luxon": "^3.5",
|