@series-inc/stowkit-cli 0.6.36 → 0.6.38
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/ai-tagger.js +1 -1
- package/dist/firestore.d.ts +1 -0
- package/dist/firestore.js +3 -0
- package/dist/server.js +96 -0
- package/package.json +2 -2
package/dist/ai-tagger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai';
|
|
2
2
|
import { AssetType } from './core/types.js';
|
|
3
|
-
const MODEL = 'gemini-
|
|
3
|
+
const MODEL = 'gemini-3.1-flash-lite-preview';
|
|
4
4
|
const ASSET_TYPE_LABELS = {
|
|
5
5
|
[AssetType.Texture2D]: 'texture',
|
|
6
6
|
[AssetType.StaticMesh]: '3D mesh',
|
package/dist/firestore.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface FirestoreClient extends FirestoreReader {
|
|
|
29
29
|
deletePackage(name: string): Promise<void>;
|
|
30
30
|
setVersion(packageName: string, version: string, data: FirestoreVersionDoc): Promise<void>;
|
|
31
31
|
}
|
|
32
|
+
export declare function clearFirestoreCache(): void;
|
|
32
33
|
/**
|
|
33
34
|
* Create a read-only Firestore client using the public API key.
|
|
34
35
|
* No service account or credentials needed — safe for CLI store commands,
|
package/dist/firestore.js
CHANGED
|
@@ -73,6 +73,9 @@ function objectToFields(data) {
|
|
|
73
73
|
// ─── In-memory cache ─────────────────────────────────────────────────────────
|
|
74
74
|
const CACHE_TTL_MS = 60_000; // 1 minute
|
|
75
75
|
const cache = new Map();
|
|
76
|
+
export function clearFirestoreCache() {
|
|
77
|
+
cache.clear();
|
|
78
|
+
}
|
|
76
79
|
function cacheGet(key) {
|
|
77
80
|
const entry = cache.get(key);
|
|
78
81
|
if (!entry)
|
package/dist/server.js
CHANGED
|
@@ -1789,6 +1789,10 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1789
1789
|
// GET /api/asset-store/registry — fetch the public registry
|
|
1790
1790
|
if (pathname === '/api/asset-store/registry' && req.method === 'GET') {
|
|
1791
1791
|
try {
|
|
1792
|
+
if (url.searchParams.get('refresh')) {
|
|
1793
|
+
const { clearFirestoreCache } = await import('./firestore.js');
|
|
1794
|
+
clearFirestoreCache();
|
|
1795
|
+
}
|
|
1792
1796
|
const { createFirestoreReader } = await import('./firestore.js');
|
|
1793
1797
|
const reader = createFirestoreReader();
|
|
1794
1798
|
const allPackages = await reader.listPackages();
|
|
@@ -1846,6 +1850,10 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1846
1850
|
// GET /api/asset-store/packages — list all packages
|
|
1847
1851
|
if (pathname === '/api/asset-store/packages' && req.method === 'GET') {
|
|
1848
1852
|
try {
|
|
1853
|
+
if (url.searchParams.get('refresh')) {
|
|
1854
|
+
const { clearFirestoreCache } = await import('./firestore.js');
|
|
1855
|
+
clearFirestoreCache();
|
|
1856
|
+
}
|
|
1849
1857
|
const bucketParam = url.searchParams.get('bucket') ?? undefined;
|
|
1850
1858
|
const { createFirestoreReader } = await import('./firestore.js');
|
|
1851
1859
|
const { listStorePackages } = await import('./store.js');
|
|
@@ -2196,6 +2204,94 @@ async function handleRequest(req, res, staticApps) {
|
|
|
2196
2204
|
}
|
|
2197
2205
|
return;
|
|
2198
2206
|
}
|
|
2207
|
+
// POST /api/ai/tag-batch — AI-tag specific assets by ID, concurrently
|
|
2208
|
+
if (pathname === '/api/ai/tag-batch' && req.method === 'POST') {
|
|
2209
|
+
if (!projectConfig) {
|
|
2210
|
+
json(res, { error: 'No project open' }, 400);
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
const apiKey = projectConfig.config.ai?.geminiApiKey;
|
|
2214
|
+
if (!apiKey) {
|
|
2215
|
+
json(res, { error: 'Gemini API key not configured' }, 400);
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
try {
|
|
2219
|
+
const body = JSON.parse(await readBody(req));
|
|
2220
|
+
const ids = body.ids;
|
|
2221
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
2222
|
+
json(res, { error: 'ids must be a non-empty array' }, 400);
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
const { tagAsset: aiTagAsset } = await import('./ai-tagger.js');
|
|
2226
|
+
const { readManifest, readThumbnailFile } = await import('./app/thumbnail-cache.js');
|
|
2227
|
+
const manifest = await readManifest(projectConfig.srcArtDir);
|
|
2228
|
+
const eligible = assets.filter(a => ids.includes(a.id) &&
|
|
2229
|
+
a.status === 'ready' &&
|
|
2230
|
+
!a.settings.excluded &&
|
|
2231
|
+
(!a.settings.tags || a.settings.tags.length === 0) &&
|
|
2232
|
+
a.type !== AssetType.MaterialSchema &&
|
|
2233
|
+
!a.parentId);
|
|
2234
|
+
let tagged = 0;
|
|
2235
|
+
let skipped = ids.length - eligible.length;
|
|
2236
|
+
const errors = [];
|
|
2237
|
+
const CONCURRENCY = 20;
|
|
2238
|
+
// Process in batches of CONCURRENCY
|
|
2239
|
+
for (let i = 0; i < eligible.length; i += CONCURRENCY) {
|
|
2240
|
+
const batch = eligible.slice(i, i + CONCURRENCY);
|
|
2241
|
+
const results = await Promise.allSettled(batch.map(async (asset) => {
|
|
2242
|
+
let inputData;
|
|
2243
|
+
let mimeType;
|
|
2244
|
+
if (asset.type === AssetType.Audio) {
|
|
2245
|
+
const ext = asset.fileName.split('.').pop()?.toLowerCase() ?? 'wav';
|
|
2246
|
+
const mimeMap = {
|
|
2247
|
+
wav: 'audio/wav', mp3: 'audio/mpeg', ogg: 'audio/ogg',
|
|
2248
|
+
flac: 'audio/flac', aac: 'audio/aac', m4a: 'audio/mp4',
|
|
2249
|
+
};
|
|
2250
|
+
mimeType = mimeMap[ext] ?? 'audio/wav';
|
|
2251
|
+
const raw = await readFile(projectConfig.srcArtDir, asset.id);
|
|
2252
|
+
if (!raw)
|
|
2253
|
+
throw new Error('Source file not found');
|
|
2254
|
+
inputData = Buffer.from(raw);
|
|
2255
|
+
}
|
|
2256
|
+
else {
|
|
2257
|
+
const entry = manifest[asset.stringId];
|
|
2258
|
+
if (!entry)
|
|
2259
|
+
throw new Error('No thumbnail cached');
|
|
2260
|
+
const thumbData = await readThumbnailFile(projectConfig.srcArtDir, asset.stringId, entry.format);
|
|
2261
|
+
if (!thumbData)
|
|
2262
|
+
throw new Error('Thumbnail file missing');
|
|
2263
|
+
inputData = thumbData;
|
|
2264
|
+
const formatMime = {
|
|
2265
|
+
png: 'image/png', webp: 'image/webp', webm: 'video/webm',
|
|
2266
|
+
};
|
|
2267
|
+
mimeType = formatMime[entry.format] ?? 'image/webp';
|
|
2268
|
+
}
|
|
2269
|
+
const newTags = await Promise.race([
|
|
2270
|
+
aiTagAsset(apiKey, asset.type, asset.fileName, inputData, mimeType),
|
|
2271
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Gemini request timed out (30s)')), 30_000)),
|
|
2272
|
+
]);
|
|
2273
|
+
asset.settings = { ...asset.settings, tags: newTags };
|
|
2274
|
+
if (!asset.parentId) {
|
|
2275
|
+
const meta = assetSettingsToStowmeta(asset);
|
|
2276
|
+
await writeStowmeta(projectConfig.srcArtDir, asset.id, meta);
|
|
2277
|
+
}
|
|
2278
|
+
broadcast({ type: 'asset-update', id: asset.id, updates: { settings: asset.settings } });
|
|
2279
|
+
return asset.id;
|
|
2280
|
+
}));
|
|
2281
|
+
for (const r of results) {
|
|
2282
|
+
if (r.status === 'fulfilled')
|
|
2283
|
+
tagged++;
|
|
2284
|
+
else
|
|
2285
|
+
errors.push({ id: 'unknown', error: r.reason?.message ?? String(r.reason) });
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
json(res, { tagged, skipped, errors });
|
|
2289
|
+
}
|
|
2290
|
+
catch (err) {
|
|
2291
|
+
json(res, { error: err.message }, 500);
|
|
2292
|
+
}
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2199
2295
|
// GET /api/thumbnails/cached — return which thumbnails are still valid in the cache
|
|
2200
2296
|
if (pathname === '/api/thumbnails/cached' && req.method === 'GET') {
|
|
2201
2297
|
if (!projectConfig) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/stowkit-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"stowkit": "./dist/cli.js"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@google/genai": "^1.46.0",
|
|
22
22
|
"@series-inc/stowkit-editor": "^0.1.8",
|
|
23
|
-
"@series-inc/stowkit-packer-gui": "^0.1.
|
|
23
|
+
"@series-inc/stowkit-packer-gui": "^0.1.25",
|
|
24
24
|
"@strangeape/ffmpeg-audio-wasm": "^0.1.0",
|
|
25
25
|
"draco3d": "^1.5.7",
|
|
26
26
|
"fbx-parser": "^2.1.3",
|