videobook 0.2.5 → 0.3.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/CHANGELOG.md +96 -0
- package/README.md +6 -2
- package/dist/archive-b2.js +1 -1
- package/dist/asset-media-projection.js +161 -0
- package/dist/cli/args.js +42 -3
- package/dist/cli/config-command.js +17 -1
- package/dist/cli/doctor-command.js +15 -0
- package/dist/cli/har-command.js +86 -0
- package/dist/cli/main.js +13 -1
- package/dist/command-reference.js +3 -1
- package/dist/content-store.js +2 -1
- package/dist/diagnostics.js +2 -2
- package/dist/env-secrets.js +1 -0
- package/dist/generation-har.js +106 -0
- package/dist/generation-reference-mentions.js +19 -0
- package/dist/goal-plan-v3.js +11 -1
- package/dist/goal-plan.js +1 -1
- package/dist/goal-store.js +2 -2
- package/dist/har-capture.js +175 -0
- package/dist/har-export.js +238 -0
- package/dist/har-index.js +98 -0
- package/dist/har-retention.js +73 -0
- package/dist/har-settings.js +27 -0
- package/dist/jobs/_alibaba-upload.js +2 -4
- package/dist/jobs/_fal-upload.js +2 -4
- package/dist/jobs/_image-edit-adapter.js +3 -1
- package/dist/jobs/_kie-upload.js +2 -4
- package/dist/jobs/assess-media-probes.js +94 -0
- package/dist/jobs/generate-media.js +10 -1
- package/dist/jobs/image-subjects.js +3 -1
- package/dist/jobs/kie-paygate.js +10 -3
- package/dist/jobs/register-all.js +2 -0
- package/dist/jobs/run-notebook.js +20 -2
- package/dist/jobs/xai-paygate.js +5 -2
- package/dist/library-asset-projection.js +141 -0
- package/dist/lightning-defaults.js +4 -4
- package/dist/media/adapters/kie.js +2 -1
- package/dist/media/catalog.js +8 -2
- package/dist/media/cost.js +1 -1
- package/dist/media/descriptor.js +7 -4
- package/dist/media/frontier-gaps.js +0 -1
- package/dist/media/har-endpoint.js +25 -0
- package/dist/media/json-schema.js +3 -0
- package/dist/media/preferred-defaults.js +161 -0
- package/dist/media/prompt-dialects.js +9 -1
- package/dist/media-library.js +158 -153
- package/dist/notebook-sticky-settings.js +60 -0
- package/dist/package-version.js +18 -0
- package/dist/posts/dropbox-auth.js +1 -1
- package/dist/posts/post-metrics.js +2 -1
- package/dist/provider-usage.js +1 -18
- package/dist/queue.js +12 -1
- package/dist/serve.js +2 -0
- package/dist/tools/api-status.js +1 -1
- package/dist/tools/generate-media.js +3 -0
- package/dist/tools/generation-payload.js +17 -2
- package/dist/tools/get-generation-har.js +43 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/list-cell-generations.js +19 -0
- package/dist/tools/reconcile-project-work.js +52 -193
- package/dist/tools/set-har-capture.js +30 -0
- package/dist/tools/videobook-domain.js +16 -1
- package/dist/upload.js +2 -1
- package/dist/usage-providers.js +48 -10
- package/dist/workspace-action-groups.js +2 -2
- package/dist/x/account.js +1 -1
- package/dist/x/authenticated-handle.js +1 -1
- package/dist/x/oauth-flow.js +1 -1
- package/dist/x/post-to-x.js +2 -1
- package/dist/x/x-tokens.js +2 -1
- package/package.json +15 -14
- package/ui/assets/{alive-captions-duTKsyll.js → alive-captions-CnLK22ED.js} +1 -1
- package/ui/assets/confirm-bodies-BaalBxp1.js +1 -0
- package/ui/assets/{fullscreen-COrmiHPn.js → fullscreen-DTJQJ6dX.js} +1 -1
- package/ui/assets/{goal-ui-D5Xsm2EY.js → goal-ui-CN-kZTEO.js} +2 -2
- package/ui/assets/index-CTPstLYq.css +1 -0
- package/ui/assets/index-Dp81yChE.js +37 -0
- package/ui/assets/inspector-jeyma7HL.js +1 -0
- package/ui/assets/{jobs-ui-DdP2Re41.js → jobs-ui-D6S3LJ4j.js} +1 -1
- package/ui/assets/{music-video-B709JZqa.js → music-video-CDqr0X6G.js} +1 -1
- package/ui/assets/ui-BBM3YCY6.css +1 -0
- package/ui/assets/ui-Cqs1-YNg.js +2 -0
- package/ui/assets/vendor-BWf-WCsY.js +19 -0
- package/ui/assets/{vendor-flow-CKiuzaeM.js → vendor-flow-DPvi8ynA.js} +1 -1
- package/ui/assets/vendor-icons-D3KrGMJY.js +1 -0
- package/ui/assets/vendor-mcp-BSR9acpG.js +60 -0
- package/ui/assets/videobook-b9VODNOA.js +21 -0
- package/ui/index.html +14 -14
- package/ui/assets/confirm-bodies--2sc9DbH.js +0 -1
- package/ui/assets/index-DEKl00St.js +0 -37
- package/ui/assets/index-DWfR79lA.css +0 -1
- package/ui/assets/inspector-EBp3IhvH.js +0 -1
- package/ui/assets/ui-Oirr9VOf.css +0 -1
- package/ui/assets/ui-jRPWsNUw.js +0 -2
- package/ui/assets/vendor-BFupLz37.js +0 -19
- package/ui/assets/vendor-icons-CGqhCIXV.js +0 -1
- package/ui/assets/vendor-mcp-DQEeVZH0.js +0 -67
- package/ui/assets/videobook-0DOLlhG2.js +0 -20
package/dist/media-library.js
CHANGED
|
@@ -7,9 +7,9 @@ import { markDescendantsStale } from "./notebook-action-catalog.js";
|
|
|
7
7
|
import { mutateNotebook } from "./notebook-mutations.js";
|
|
8
8
|
import { searchReferenceStore } from "./search-reference-store.js";
|
|
9
9
|
import { comparableHitScore } from "./search-scoring.js";
|
|
10
|
-
import {
|
|
10
|
+
import { projectLibraryAsset, readLibraryCandidates, } from "./library-asset-projection.js";
|
|
11
11
|
import { assetRecency, readAssetUsage, recordAssetUse } from "./asset-usage.js";
|
|
12
|
-
import {
|
|
12
|
+
import { tagFacetCounts, transferAssetTags, } from "./asset-tags.js";
|
|
13
13
|
import { addStatusCounts, countBrowseStatuses, emptyStatusCounts, matchesBrowseStatus, pageLibraryAssets, } from "./library-browse.js";
|
|
14
14
|
function isMediaCellType(value) {
|
|
15
15
|
return value === "audio" || value === "image" || value === "video";
|
|
@@ -101,9 +101,12 @@ export async function setNotebookMediaSlot(engine, input) {
|
|
|
101
101
|
revision: result.value.hash,
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
-
function
|
|
105
|
-
return
|
|
106
|
-
|
|
104
|
+
function libraryArtifactKinds(requested) {
|
|
105
|
+
return [
|
|
106
|
+
...(requested.has("audio") ? ["audio"] : []),
|
|
107
|
+
...(requested.has("image") ? ["image"] : []),
|
|
108
|
+
...(requested.has("video") ? ["video", "final"] : []),
|
|
109
|
+
];
|
|
107
110
|
}
|
|
108
111
|
function searchRank(asset, query) {
|
|
109
112
|
if (!query)
|
|
@@ -143,175 +146,177 @@ function compareRecentlyUsed(left, right) {
|
|
|
143
146
|
? created
|
|
144
147
|
: left.artifact_id.localeCompare(right.artifact_id);
|
|
145
148
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
...(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
})
|
|
179
|
-
const statusCounts = countBrowseStatuses(eligible);
|
|
180
|
-
const readyAssets = eligible.filter((asset) => asset.status === "ready");
|
|
181
|
-
const assets = searching
|
|
182
|
-
? readyAssets
|
|
183
|
-
: eligible.filter((asset) => matchesBrowseStatus(asset.status, browseStatus));
|
|
184
|
-
let hits = [];
|
|
185
|
-
let searchError;
|
|
186
|
-
let preparationJob;
|
|
187
|
-
if (searching) {
|
|
188
|
-
registerLocalClipSearchProvider(engine);
|
|
189
|
-
const searchQuery = {
|
|
190
|
-
...(query ? { text: query } : {}),
|
|
191
|
-
modalities: query
|
|
192
|
-
? ["visual", "metadata"]
|
|
193
|
-
: ["visual"],
|
|
194
|
-
artifactKinds,
|
|
195
|
-
sourceArtifactIds: readyAssets.map((asset) => asset.artifact_id),
|
|
196
|
-
limit: 100,
|
|
197
|
-
};
|
|
198
|
-
const result = prepared
|
|
199
|
-
? await engine.temporalSearch.queryPrepared(searchQuery, prepared, input.referenceRange ? { range: input.referenceRange } : {})
|
|
200
|
-
: await engine.temporalSearch.query(searchQuery);
|
|
201
|
-
if (result.ok) {
|
|
202
|
-
hits = result.value.hits;
|
|
203
|
-
}
|
|
204
|
-
else if (result.error.code === "NOT_READY" && result.error.details?.requiresIndexPreparation) {
|
|
205
|
-
preparationJob = await enqueueTemporalSearchPreparation(summary.bookId);
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
searchError = result.error.message;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
assets,
|
|
213
|
-
statusCounts,
|
|
214
|
-
hits,
|
|
215
|
-
searchError,
|
|
216
|
-
preparationJob,
|
|
217
|
-
unindexed: missingVisualIndexes(engine, readyAssets).length,
|
|
218
|
-
};
|
|
149
|
+
function compareNewest(left, right) {
|
|
150
|
+
const created = Date.parse(right.created_at) - Date.parse(left.created_at);
|
|
151
|
+
return created !== 0
|
|
152
|
+
? created
|
|
153
|
+
: left.artifact_id.localeCompare(right.artifact_id);
|
|
154
|
+
}
|
|
155
|
+
function entryKey(entry) {
|
|
156
|
+
return `${entry.owning_book_id}:${entry.artifact_id}`;
|
|
157
|
+
}
|
|
158
|
+
async function querySemanticHits(engine, plan, readyEntries) {
|
|
159
|
+
registerLocalClipSearchProvider(engine);
|
|
160
|
+
const searchQuery = {
|
|
161
|
+
...(plan.query ? { text: plan.query } : {}),
|
|
162
|
+
modalities: plan.query
|
|
163
|
+
? ["visual", "metadata"]
|
|
164
|
+
: ["visual"],
|
|
165
|
+
artifactKinds: plan.searchKinds,
|
|
166
|
+
sourceArtifactIds: readyEntries.map((entry) => entry.artifact_id),
|
|
167
|
+
limit: 100,
|
|
168
|
+
};
|
|
169
|
+
const result = plan.prepared
|
|
170
|
+
? await engine.temporalSearch.queryPrepared(searchQuery, plan.prepared, plan.referenceRange ? { range: plan.referenceRange } : {})
|
|
171
|
+
: await engine.temporalSearch.query(searchQuery);
|
|
172
|
+
return result.ok ? { ok: true, value: result.value.hits } : result;
|
|
173
|
+
}
|
|
174
|
+
async function searchBook(books, summary, plan) {
|
|
175
|
+
const book = books.get(summary.bookId);
|
|
176
|
+
if (!book.ok)
|
|
177
|
+
throw new Error(book.error.message);
|
|
178
|
+
const engine = book.value.engine;
|
|
179
|
+
const candidates = await engine.withLocalMedia(() => readLibraryCandidates(engine, {
|
|
180
|
+
kinds: plan.candidateKinds,
|
|
181
|
+
...(plan.tags ? { tags: plan.tags } : {}),
|
|
219
182
|
}));
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
? created
|
|
257
|
-
: left.artifact_id.localeCompare(right.artifact_id);
|
|
258
|
-
})
|
|
259
|
-
.map((asset, index) => [
|
|
260
|
-
`${asset.owning_book_id}:${asset.artifact_id}`,
|
|
261
|
-
index + 1,
|
|
262
|
-
]));
|
|
263
|
-
semanticHits.sort((left, right) => comparableHitScore(right.hit) - comparableHitScore(left.hit)
|
|
183
|
+
const usage = readAssetUsage(engine);
|
|
184
|
+
const eligible = candidates.map((candidate) => ({
|
|
185
|
+
...candidate,
|
|
186
|
+
owning_book_id: summary.bookId,
|
|
187
|
+
owning_book_name: summary.name,
|
|
188
|
+
last_used_at: lastUsedAt(usage.get(candidate.artifact_id)),
|
|
189
|
+
}));
|
|
190
|
+
const readyEntries = eligible.filter((entry) => entry.status === "ready");
|
|
191
|
+
const result = {
|
|
192
|
+
entries: plan.searching
|
|
193
|
+
? readyEntries
|
|
194
|
+
: eligible.filter((entry) => matchesBrowseStatus(entry.status, plan.browseStatus)),
|
|
195
|
+
statusCounts: countBrowseStatuses(eligible),
|
|
196
|
+
hits: [],
|
|
197
|
+
unindexed: plan.indexCoverage ? missingVisualIndexes(engine, readyEntries).length : 0,
|
|
198
|
+
};
|
|
199
|
+
if (!plan.searching)
|
|
200
|
+
return result;
|
|
201
|
+
const hits = await querySemanticHits(engine, plan, readyEntries);
|
|
202
|
+
if (hits.ok)
|
|
203
|
+
return { ...result, hits: hits.value };
|
|
204
|
+
if (hits.error.code === "NOT_READY" && hits.error.details?.requiresIndexPreparation) {
|
|
205
|
+
return { ...result, preparationJob: await enqueueTemporalSearchPreparation(summary.bookId) };
|
|
206
|
+
}
|
|
207
|
+
return { ...result, searchError: hits.error.message };
|
|
208
|
+
}
|
|
209
|
+
function rankEntries(entries, semanticHits, plan) {
|
|
210
|
+
if (!plan.searching) {
|
|
211
|
+
return [...entries].sort(compareRecentlyUsed).map((entry) => ({ entry, hit: undefined }));
|
|
212
|
+
}
|
|
213
|
+
const metadataRank = new Map(entries
|
|
214
|
+
.filter((entry) => !plan.prepared && matchesQuery(entry, plan.query))
|
|
215
|
+
.sort((left, right) => searchRank(left, plan.query) - searchRank(right, plan.query)
|
|
216
|
+
|| compareNewest(left, right))
|
|
217
|
+
.map((entry, index) => [entryKey(entry), index + 1]));
|
|
218
|
+
const sortedHits = [...semanticHits].sort((left, right) => comparableHitScore(right.hit) - comparableHitScore(left.hit)
|
|
264
219
|
|| left.key.localeCompare(right.key));
|
|
265
220
|
const semanticRank = new Map();
|
|
266
221
|
const semanticMatch = new Map();
|
|
267
|
-
for (const item of
|
|
222
|
+
for (const item of sortedHits) {
|
|
268
223
|
if (semanticRank.has(item.key))
|
|
269
224
|
continue;
|
|
270
225
|
semanticRank.set(item.key, semanticRank.size + 1);
|
|
271
226
|
semanticMatch.set(item.key, item.hit);
|
|
272
227
|
}
|
|
273
|
-
|
|
274
|
-
.flatMap((
|
|
275
|
-
const key =
|
|
228
|
+
return entries
|
|
229
|
+
.flatMap((entry) => {
|
|
230
|
+
const key = entryKey(entry);
|
|
276
231
|
const metadata = metadataRank.get(key);
|
|
277
232
|
const semantic = semanticRank.get(key);
|
|
278
|
-
if (
|
|
233
|
+
if (metadata === undefined && semantic === undefined)
|
|
279
234
|
return [];
|
|
280
|
-
}
|
|
281
|
-
const hit = semanticMatch.get(key);
|
|
282
235
|
return [{
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
...asset,
|
|
286
|
-
search_match: {
|
|
287
|
-
score: comparableHitScore(hit),
|
|
288
|
-
location: hit.location,
|
|
289
|
-
},
|
|
290
|
-
}
|
|
291
|
-
: asset,
|
|
236
|
+
entry,
|
|
237
|
+
hit: semanticMatch.get(key),
|
|
292
238
|
score: (metadata === undefined ? 0 : 1 / (60 + metadata))
|
|
293
239
|
+ (semantic === undefined ? 0 : 1 / (60 + semantic)),
|
|
294
240
|
}];
|
|
295
241
|
})
|
|
296
|
-
.sort((left, right) =>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
242
|
+
.sort((left, right) => right.score - left.score || compareNewest(left.entry, right.entry))
|
|
243
|
+
.map(({ entry, hit }) => ({ entry, hit }));
|
|
244
|
+
}
|
|
245
|
+
async function projectLibraryEntry(books, entry, hit) {
|
|
246
|
+
const book = books.get(entry.owning_book_id);
|
|
247
|
+
if (!book.ok)
|
|
248
|
+
throw new Error(book.error.message);
|
|
249
|
+
const engine = book.value.engine;
|
|
250
|
+
const row = await engine.withLocalMedia(() => projectLibraryAsset(engine, entry.owning_book_id, entry));
|
|
251
|
+
return {
|
|
252
|
+
...row,
|
|
253
|
+
owning_book_id: entry.owning_book_id,
|
|
254
|
+
owning_book_name: entry.owning_book_name,
|
|
255
|
+
last_used_at: entry.last_used_at,
|
|
256
|
+
...(hit
|
|
257
|
+
? { search_match: { score: comparableHitScore(hit), location: hit.location } }
|
|
258
|
+
: {}),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function librarySearchPlan(input) {
|
|
262
|
+
const query = input.query?.trim().toLowerCase() ?? "";
|
|
263
|
+
const prepared = input.referenceId
|
|
264
|
+
? searchReferenceStore.get(input.referenceId).reference
|
|
265
|
+
: undefined;
|
|
266
|
+
const searching = Boolean(query || prepared);
|
|
267
|
+
const searchKinds = libraryArtifactKinds(new Set(input.kinds));
|
|
268
|
+
return {
|
|
269
|
+
query,
|
|
270
|
+
prepared,
|
|
271
|
+
referenceRange: input.referenceRange,
|
|
272
|
+
searching,
|
|
273
|
+
browseStatus: searching ? "ready" : (input.browseStatus ?? "ready"),
|
|
274
|
+
candidateKinds: new Set(searchKinds),
|
|
275
|
+
searchKinds,
|
|
276
|
+
tags: input.tags,
|
|
277
|
+
indexCoverage: input.indexCoverage ?? searching,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
export async function searchLibraryAssets(books, input) {
|
|
281
|
+
await books.ready();
|
|
282
|
+
const plan = librarySearchPlan(input);
|
|
283
|
+
const summaries = books.list({ sort: "newest" });
|
|
284
|
+
const results = await Promise.allSettled(summaries.map((summary) => searchBook(books, summary, plan)));
|
|
285
|
+
const errors = [];
|
|
286
|
+
const entries = [];
|
|
287
|
+
let statusCounts = emptyStatusCounts();
|
|
288
|
+
const semanticHits = [];
|
|
289
|
+
results.forEach((result, index) => {
|
|
290
|
+
const bookId = summaries[index]?.bookId ?? "unknown";
|
|
291
|
+
if (result.status === "rejected") {
|
|
292
|
+
errors.push({
|
|
293
|
+
book_id: bookId,
|
|
294
|
+
message: result.reason instanceof Error
|
|
295
|
+
? result.reason.message
|
|
296
|
+
: String(result.reason),
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
entries.push(...result.value.entries);
|
|
301
|
+
statusCounts = addStatusCounts(statusCounts, result.value.statusCounts);
|
|
302
|
+
semanticHits.push(...result.value.hits.map((hit) => ({
|
|
303
|
+
key: `${bookId}:${hit.artifactId}`,
|
|
304
|
+
hit,
|
|
305
|
+
})));
|
|
306
|
+
if (result.value.searchError) {
|
|
307
|
+
errors.push({ book_id: bookId, message: result.value.searchError });
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
const ranked = rankEntries(entries, semanticHits, plan);
|
|
311
|
+
const paged = pageLibraryAssets(ranked.map(({ entry }) => entry), {
|
|
309
312
|
offset: input.offset ?? 0,
|
|
310
313
|
limit: input.limit ?? 50,
|
|
311
314
|
});
|
|
315
|
+
const offset = input.offset ?? 0;
|
|
316
|
+
const pageHits = ranked.slice(offset, offset + paged.pageAssets.length);
|
|
312
317
|
return {
|
|
313
|
-
assets:
|
|
314
|
-
tag_facets: tagFacetCounts(
|
|
318
|
+
assets: await Promise.all(pageHits.map(({ entry, hit }) => projectLibraryEntry(books, entry, hit))),
|
|
319
|
+
tag_facets: tagFacetCounts(paged.matching),
|
|
315
320
|
unindexed_visual_assets: results.reduce((count, result) => count + (result.status === "fulfilled" ? result.value.unindexed : 0), 0),
|
|
316
321
|
preparationJobs: results.flatMap((result) => result.status === "fulfilled" && result.value.preparationJob
|
|
317
322
|
? [result.value.preparationJob]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { tryResolveModel } from "./media/descriptor.js";
|
|
2
|
+
import { parseStickySettings, stickySettingGroup, stickySettingsFor, stickySettingsFrom, takesDuration, } from "./media/preferred-defaults.js";
|
|
3
|
+
import { controlsForModel } from "./notebook-generation-controls.js";
|
|
4
|
+
function settingsKey(notebookId) {
|
|
5
|
+
return `notebook-generation-settings:${notebookId}`;
|
|
6
|
+
}
|
|
7
|
+
function generationSettingsOf(cell) {
|
|
8
|
+
const settings = cell?.inputs?.generationSettings;
|
|
9
|
+
return settings && typeof settings === "object" && !Array.isArray(settings)
|
|
10
|
+
? settings
|
|
11
|
+
: {};
|
|
12
|
+
}
|
|
13
|
+
function changedSettings(previous, next) {
|
|
14
|
+
return Object.fromEntries(Object.entries(next).filter(([name, value]) => previous[name] !== value));
|
|
15
|
+
}
|
|
16
|
+
export function readNotebookStickySettings(engine, notebookId) {
|
|
17
|
+
const stored = engine.settings.get(settingsKey(notebookId));
|
|
18
|
+
return stored ? parseStickySettings(stored) : {};
|
|
19
|
+
}
|
|
20
|
+
export function rememberNotebookStickySettings(engine, notebookId, next, previous) {
|
|
21
|
+
const changed = stickySettingsFrom(changedSettings(generationSettingsOf(previous), generationSettingsOf(next)));
|
|
22
|
+
if (Object.keys(changed).length === 0)
|
|
23
|
+
return;
|
|
24
|
+
const written = engine.settings.set(settingsKey(notebookId), {
|
|
25
|
+
...readNotebookStickySettings(engine, notebookId),
|
|
26
|
+
...changed,
|
|
27
|
+
});
|
|
28
|
+
if (!written.ok)
|
|
29
|
+
throw new Error(written.error.message);
|
|
30
|
+
}
|
|
31
|
+
function stickyControlsFor(model) {
|
|
32
|
+
const resolved = tryResolveModel(model);
|
|
33
|
+
if (!resolved)
|
|
34
|
+
return [];
|
|
35
|
+
const timed = takesDuration(resolved.entry);
|
|
36
|
+
return controlsForModel(resolved).flatMap((control) => {
|
|
37
|
+
const group = stickySettingGroup(control.name);
|
|
38
|
+
if (!group || (group === "duration" && !timed))
|
|
39
|
+
return [];
|
|
40
|
+
return [{
|
|
41
|
+
name: control.name,
|
|
42
|
+
valueType: control.valueType,
|
|
43
|
+
options: control.options ?? [],
|
|
44
|
+
...(control.minimum === undefined ? {} : { minimum: control.minimum }),
|
|
45
|
+
...(control.maximum === undefined ? {} : { maximum: control.maximum }),
|
|
46
|
+
}];
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function seedNotebookStickySettings(cell, sticky) {
|
|
50
|
+
if (!cell.model || Object.keys(sticky).length === 0)
|
|
51
|
+
return cell;
|
|
52
|
+
const explicit = generationSettingsOf(cell);
|
|
53
|
+
const seeded = stickySettingsFor(stickyControlsFor(cell.model).filter((control) => explicit[control.name] === undefined), sticky);
|
|
54
|
+
if (Object.keys(seeded).length === 0)
|
|
55
|
+
return cell;
|
|
56
|
+
return {
|
|
57
|
+
...cell,
|
|
58
|
+
inputs: { ...(cell.inputs ?? {}), generationSettings: { ...seeded, ...explicit } },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export function installedPackageVersion(name) {
|
|
5
|
+
let dir = dirname(fileURLToPath(import.meta.resolve(name)));
|
|
6
|
+
while (dir !== dirname(dir)) {
|
|
7
|
+
try {
|
|
8
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
9
|
+
if (manifest.name === name && manifest.version)
|
|
10
|
+
return manifest.version;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
void 0;
|
|
14
|
+
}
|
|
15
|
+
dir = dirname(dir);
|
|
16
|
+
}
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
|
-
import { createDropbox } from "
|
|
3
|
+
import { createDropbox } from "../usage-providers.js";
|
|
4
4
|
import { appState } from "../app-state.js";
|
|
5
5
|
const KEY = "dropbox.oauth.tokens";
|
|
6
6
|
const REDIRECT = "http://127.0.0.1:8766/callback";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createX
|
|
1
|
+
import { createX } from "../usage-providers.js";
|
|
2
|
+
import { XError } from "@apicity/x";
|
|
2
3
|
import { v7 as uuidv7 } from "uuid";
|
|
3
4
|
import { getValidAccessToken } from "../x/x-tokens.js";
|
|
4
5
|
import { listBookEngines, queue } from "../queue.js";
|
package/dist/provider-usage.js
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
2
|
import { createCost, PRICING, PRICING_AS_OF } from "@apicity/cost";
|
|
6
3
|
import { modelEntry } from "./media/catalog.js";
|
|
7
4
|
import { estimateFalRequest } from "./media/cost.js";
|
|
8
5
|
import { currentBookContext } from "./book-context.js";
|
|
9
|
-
|
|
10
|
-
let dir = dirname(fileURLToPath(import.meta.resolve(name)));
|
|
11
|
-
while (dir !== dirname(dir)) {
|
|
12
|
-
try {
|
|
13
|
-
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
14
|
-
if (manifest.name === name && manifest.version)
|
|
15
|
-
return manifest.version;
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
void 0;
|
|
19
|
-
}
|
|
20
|
-
dir = dirname(dir);
|
|
21
|
-
}
|
|
22
|
-
return "unknown";
|
|
23
|
-
}
|
|
6
|
+
import { installedPackageVersion } from "./package-version.js";
|
|
24
7
|
const COST_PACKAGE_VERSION = installedPackageVersion("@apicity/cost");
|
|
25
8
|
const incomplete = new WeakSet();
|
|
26
9
|
const cost = createCost();
|
package/dist/queue.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sweepHarCaptures } from "./har-retention.js";
|
|
1
2
|
import { appendDiagnostic, registerDiagnosticName } from "./diagnostics.js";
|
|
2
3
|
import { createEngine, } from "videobook-engine";
|
|
3
4
|
import { activeBookEngine, withBookContext } from "./book-context.js";
|
|
@@ -54,11 +55,13 @@ export function isProviderPollDriverActive(bookId) {
|
|
|
54
55
|
const info = runnerByBook.get(bookId);
|
|
55
56
|
return info?.providerPollTimer != null;
|
|
56
57
|
}
|
|
58
|
+
let harRetentionTimer = null;
|
|
57
59
|
async function tickProviderPoll(info) {
|
|
58
60
|
if (info.providerPollInFlight || info.pollAbort.signal.aborted)
|
|
59
61
|
return;
|
|
60
62
|
info.providerPollInFlight = true;
|
|
61
63
|
try {
|
|
64
|
+
await sweepHarCaptures();
|
|
62
65
|
if (providerPollWorkHook) {
|
|
63
66
|
await providerPollWorkHook(info.pollAbort.signal);
|
|
64
67
|
}
|
|
@@ -258,7 +261,10 @@ function makeWrappedResolver(bookId, engine) {
|
|
|
258
261
|
const handler = handlers.get(type);
|
|
259
262
|
if (!handler)
|
|
260
263
|
return null;
|
|
261
|
-
return async (job, queueSignal) => withBookContext({ bookId, engine, operation: type, ...(job.artifactId ? { artifactId: job.artifactId } : {})
|
|
264
|
+
return async (job, queueSignal) => withBookContext({ bookId, engine, operation: type, ...(job.artifactId ? { artifactId: job.artifactId } : {}),
|
|
265
|
+
...(typeof job.payload.runId === "string" ? { runId: job.payload.runId } : {}),
|
|
266
|
+
...(typeof job.payload.generationId === "string" ? { attemptId: job.payload.generationId } : {}),
|
|
267
|
+
...(typeof job.payload.model === "string" ? { model: job.payload.model } : {}) }, () => runWithJobDeadline(type, async (signal) => {
|
|
262
268
|
const jobSignal = queueSignal
|
|
263
269
|
? AbortSignal.any([signal, queueSignal])
|
|
264
270
|
: signal;
|
|
@@ -579,6 +585,8 @@ export const queue = {
|
|
|
579
585
|
};
|
|
580
586
|
},
|
|
581
587
|
async start() {
|
|
588
|
+
await sweepHarCaptures(true);
|
|
589
|
+
harRetentionTimer ??= setInterval(() => { void sweepHarCaptures(); }, 60_000).unref();
|
|
582
590
|
await ensureJobHandlersLoaded();
|
|
583
591
|
await reconcileQueueOnStartup();
|
|
584
592
|
},
|
|
@@ -586,6 +594,9 @@ export const queue = {
|
|
|
586
594
|
await ensureRunner(bookId);
|
|
587
595
|
},
|
|
588
596
|
async stop() {
|
|
597
|
+
if (harRetentionTimer)
|
|
598
|
+
clearInterval(harRetentionTimer);
|
|
599
|
+
harRetentionTimer = null;
|
|
589
600
|
if (stoppingQueue)
|
|
590
601
|
return stoppingQueue;
|
|
591
602
|
const pending = stopQueueRunners();
|
package/dist/serve.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { flushHarCaptures } from "./har-capture.js";
|
|
1
2
|
import { once } from "node:events";
|
|
2
3
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
4
|
import { stopAceServer } from "./acestep/process.js";
|
|
@@ -22,6 +23,7 @@ async function shutdownServices(services) {
|
|
|
22
23
|
await services.voiceSessions.close();
|
|
23
24
|
await services.mcpHandler.close();
|
|
24
25
|
await queue.stop();
|
|
26
|
+
await flushHarCaptures();
|
|
25
27
|
await stopAceServer();
|
|
26
28
|
await bookManager.close();
|
|
27
29
|
}
|
package/dist/tools/api-status.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { currentBookContext } from "../book-context.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { queue } from "../queue.js";
|
|
3
4
|
import { generationLabel } from "../artifact-label.js";
|
|
@@ -92,6 +93,8 @@ async function enqueueGeneration(engine, model, args, refs) {
|
|
|
92
93
|
const jobId = `generate-${model.media}-${artifactId}-${Date.now()}`;
|
|
93
94
|
const job = {
|
|
94
95
|
type: "generate_media",
|
|
96
|
+
runId: currentBookContext()?.runId,
|
|
97
|
+
generationId: currentBookContext()?.attemptId,
|
|
95
98
|
model: model.id,
|
|
96
99
|
bookId: String(args.book_id),
|
|
97
100
|
assetId: artifactId,
|
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { captureKeys, generationHarStatus } from "../generation-har.js";
|
|
3
|
+
import { readHarSettings } from "../har-settings.js";
|
|
4
|
+
import { logger } from "../logger.js";
|
|
2
5
|
import { readProviderTaskStatus } from "../provider-task-status.js";
|
|
6
|
+
async function inspectionHar(engine, assetId) {
|
|
7
|
+
try {
|
|
8
|
+
const { mode } = await readHarSettings();
|
|
9
|
+
return await generationHarStatus(await captureKeys(engine, [assetId]), mode);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
logger.warn("har-capture-failed", { error });
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
3
16
|
export function generationPayloadDef(engine) {
|
|
4
17
|
return {
|
|
5
18
|
name: "inspect_generation_request",
|
|
6
|
-
description: "Read the persisted provider request payload, task details, status-page information,
|
|
19
|
+
description: "Read the persisted provider request payload, task details, status-page information, failure info and HAR capture status for a single asset. Used by the generating-cell inspector to show what was sent to the provider.",
|
|
7
20
|
schema: {
|
|
8
21
|
book_id: z.string().describe("Book id"),
|
|
9
22
|
asset_id: z.string().describe("Artifact id"),
|
|
10
23
|
},
|
|
11
24
|
handler: async (args) => {
|
|
12
25
|
const { book_id: _book_id, asset_id } = args;
|
|
13
|
-
const [payloadRes, taskRes, errorRes, providerTask] = await Promise.all([
|
|
26
|
+
const [payloadRes, taskRes, errorRes, providerTask, har] = await Promise.all([
|
|
14
27
|
engine.metadata.artifacts.read(asset_id, "generation-payload"),
|
|
15
28
|
engine.jobs.pending.read(asset_id),
|
|
16
29
|
engine.jobs.failures.read(asset_id),
|
|
17
30
|
readProviderTaskStatus(engine, asset_id),
|
|
31
|
+
inspectionHar(engine, asset_id),
|
|
18
32
|
]);
|
|
19
33
|
const task = taskRes.ok ? taskRes.value : null;
|
|
20
34
|
const errorRecord = errorRes.ok ? errorRes.value : null;
|
|
@@ -32,6 +46,7 @@ export function generationPayloadDef(engine) {
|
|
|
32
46
|
error,
|
|
33
47
|
taskType: task ? task.taskType : null,
|
|
34
48
|
providerTask,
|
|
49
|
+
har,
|
|
35
50
|
task: task
|
|
36
51
|
? {
|
|
37
52
|
taskId: task.taskId,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { generationCaptureKeys, generationHar } from "../generation-har.js";
|
|
3
|
+
import { readHarSettings } from "../har-settings.js";
|
|
4
|
+
function errorResult(message) {
|
|
5
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
6
|
+
}
|
|
7
|
+
export function getGenerationHarDef(engine) {
|
|
8
|
+
return {
|
|
9
|
+
name: "get_generation_har",
|
|
10
|
+
description: "Return the scrubbed provider HAR capture of one generation attempt, by attempt id or asset id. Several captured calls are combined into one HAR with a page per call. Needs provider capture (set_har_capture).",
|
|
11
|
+
schema: {
|
|
12
|
+
book_id: z.string().describe("Book id"),
|
|
13
|
+
generation_id: z.string().min(1).describe("Generation attempt id or its asset id"),
|
|
14
|
+
},
|
|
15
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
16
|
+
handler: async (args) => {
|
|
17
|
+
const { generation_id } = args;
|
|
18
|
+
try {
|
|
19
|
+
const result = await generationHar(await generationCaptureKeys(engine, generation_id), generation_id);
|
|
20
|
+
if (!result) {
|
|
21
|
+
const { mode } = await readHarSettings();
|
|
22
|
+
return errorResult(mode === "off"
|
|
23
|
+
? "No HAR capture for this generation. Provider capture is off; turn it on in Settings > Diagnostics or with set_har_capture."
|
|
24
|
+
: "No HAR capture for this generation.");
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
content: [{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: JSON.stringify({
|
|
30
|
+
generationId: generation_id,
|
|
31
|
+
filename: `videobook-generation-${generation_id.replace(/[^A-Za-z0-9._-]+/gu, "-")}.har`,
|
|
32
|
+
captures: result.captures,
|
|
33
|
+
har: result.har,
|
|
34
|
+
}),
|
|
35
|
+
}],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return errorResult(error instanceof Error ? error.message : String(error));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|