glotfile 0.7.0 → 0.7.2
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/server/cli.js +182 -92
- package/dist/server/server.js +161 -79
- package/dist/ui/assets/index-D04CSFY5.js +2124 -0
- package/dist/ui/assets/index-DNjcY2ek.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-BO3TJzQp.css +0 -1
- package/dist/ui/assets/index-CvnmUNjM.js +0 -2124
package/dist/server/cli.js
CHANGED
|
@@ -2216,6 +2216,73 @@ var init_batch = __esm({
|
|
|
2216
2216
|
}
|
|
2217
2217
|
});
|
|
2218
2218
|
|
|
2219
|
+
// src/server/ai/pricing.ts
|
|
2220
|
+
function addUsage(into, add) {
|
|
2221
|
+
into.inputTokens += add.inputTokens;
|
|
2222
|
+
into.outputTokens += add.outputTokens;
|
|
2223
|
+
into.cacheCreationInputTokens += add.cacheCreationInputTokens;
|
|
2224
|
+
into.cacheReadInputTokens += add.cacheReadInputTokens;
|
|
2225
|
+
}
|
|
2226
|
+
function usageCostUsd(usage, ai, multiplier = 1) {
|
|
2227
|
+
if (!usage) return void 0;
|
|
2228
|
+
const pricing = resolvePricing(ai);
|
|
2229
|
+
return pricing ? estimateUsageCostUsd(usage, pricing, multiplier) : void 0;
|
|
2230
|
+
}
|
|
2231
|
+
function estimateUsageCostUsd(usage, pricing, multiplier = 1) {
|
|
2232
|
+
const inputCost = (usage.inputTokens + usage.cacheCreationInputTokens * CACHE_WRITE_MULTIPLIER + usage.cacheReadInputTokens * CACHE_READ_MULTIPLIER) * pricing.inputPerMTok;
|
|
2233
|
+
return (inputCost + usage.outputTokens * pricing.outputPerMTok) / 1e6 * multiplier;
|
|
2234
|
+
}
|
|
2235
|
+
function bareModelId(model) {
|
|
2236
|
+
let id = model.trim().toLowerCase();
|
|
2237
|
+
const slash = id.lastIndexOf("/");
|
|
2238
|
+
if (slash !== -1) id = id.slice(slash + 1);
|
|
2239
|
+
const anth = id.lastIndexOf("anthropic.");
|
|
2240
|
+
if (anth !== -1) id = id.slice(anth + "anthropic.".length);
|
|
2241
|
+
return id;
|
|
2242
|
+
}
|
|
2243
|
+
function resolvePricing(ai) {
|
|
2244
|
+
if (ai.inputPricePerMTok !== void 0 && ai.outputPricePerMTok !== void 0) {
|
|
2245
|
+
return { source: "profile", inputPerMTok: ai.inputPricePerMTok, outputPerMTok: ai.outputPricePerMTok };
|
|
2246
|
+
}
|
|
2247
|
+
if (FREE_PROVIDERS.has(ai.provider)) return { source: "builtin", inputPerMTok: 0, outputPerMTok: 0 };
|
|
2248
|
+
const id = bareModelId(ai.model);
|
|
2249
|
+
let best;
|
|
2250
|
+
for (const row of PRICE_TABLE) {
|
|
2251
|
+
if (id.startsWith(row[0]) && (!best || row[0].length > best[0].length)) best = row;
|
|
2252
|
+
}
|
|
2253
|
+
return best ? { source: "builtin", inputPerMTok: best[1], outputPerMTok: best[2] } : null;
|
|
2254
|
+
}
|
|
2255
|
+
var BATCH_PRICE_MULTIPLIER, CACHE_WRITE_MULTIPLIER, CACHE_READ_MULTIPLIER, PRICE_TABLE, FREE_PROVIDERS;
|
|
2256
|
+
var init_pricing = __esm({
|
|
2257
|
+
"src/server/ai/pricing.ts"() {
|
|
2258
|
+
"use strict";
|
|
2259
|
+
BATCH_PRICE_MULTIPLIER = 0.5;
|
|
2260
|
+
CACHE_WRITE_MULTIPLIER = 1.25;
|
|
2261
|
+
CACHE_READ_MULTIPLIER = 0.1;
|
|
2262
|
+
PRICE_TABLE = [
|
|
2263
|
+
["claude-fable-5", 10, 50],
|
|
2264
|
+
["claude-mythos-5", 10, 50],
|
|
2265
|
+
// Deprecated Opus 4.1 / 4.0 cost 3x the 4.5+ generation — they must outrank
|
|
2266
|
+
// the shorter "claude-opus-4" prefix (4-2025 covers the dated Opus 4 full IDs).
|
|
2267
|
+
["claude-opus-4-1", 15, 75],
|
|
2268
|
+
["claude-opus-4-0", 15, 75],
|
|
2269
|
+
["claude-opus-4-2025", 15, 75],
|
|
2270
|
+
["claude-opus-4", 5, 25],
|
|
2271
|
+
["claude-sonnet-4", 3, 15],
|
|
2272
|
+
["claude-haiku-4", 1, 5],
|
|
2273
|
+
["claude-3-5-haiku", 0.8, 4],
|
|
2274
|
+
["gpt-5.5-pro", 30, 180],
|
|
2275
|
+
["gpt-5.5", 5, 30],
|
|
2276
|
+
["gpt-5.4-pro", 30, 180],
|
|
2277
|
+
["gpt-5.4-mini", 0.75, 4.5],
|
|
2278
|
+
["gpt-5.4-nano", 0.2, 1.25],
|
|
2279
|
+
["gpt-5.4", 2.5, 15],
|
|
2280
|
+
["gpt-5.3-codex", 1.75, 14]
|
|
2281
|
+
];
|
|
2282
|
+
FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "claude-code"]);
|
|
2283
|
+
}
|
|
2284
|
+
});
|
|
2285
|
+
|
|
2219
2286
|
// src/server/ai/anthropic.ts
|
|
2220
2287
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2221
2288
|
var AnthropicProvider;
|
|
@@ -2224,6 +2291,7 @@ var init_anthropic = __esm({
|
|
|
2224
2291
|
"use strict";
|
|
2225
2292
|
init_provider();
|
|
2226
2293
|
init_batch();
|
|
2294
|
+
init_pricing();
|
|
2227
2295
|
AnthropicProvider = class {
|
|
2228
2296
|
constructor(config, client) {
|
|
2229
2297
|
this.config = config;
|
|
@@ -2238,9 +2306,25 @@ var init_anthropic = __esm({
|
|
|
2238
2306
|
}
|
|
2239
2307
|
config;
|
|
2240
2308
|
client;
|
|
2309
|
+
usage = { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
2241
2310
|
supportsVision() {
|
|
2242
2311
|
return true;
|
|
2243
2312
|
}
|
|
2313
|
+
recordUsage(usage) {
|
|
2314
|
+
if (!usage) return;
|
|
2315
|
+
addUsage(this.usage, {
|
|
2316
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
2317
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
2318
|
+
cacheCreationInputTokens: usage.cache_creation_input_tokens ?? 0,
|
|
2319
|
+
cacheReadInputTokens: usage.cache_read_input_tokens ?? 0
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
takeUsage() {
|
|
2323
|
+
const taken = this.usage;
|
|
2324
|
+
this.usage = { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
2325
|
+
const any = taken.inputTokens || taken.outputTokens || taken.cacheCreationInputTokens || taken.cacheReadInputTokens;
|
|
2326
|
+
return any ? taken : void 0;
|
|
2327
|
+
}
|
|
2244
2328
|
translate(reqs, onBatchComplete, signal, onMalformedReply) {
|
|
2245
2329
|
return runBatched(reqs, this.config.batchSize, (batch, sig) => this.callBatch(batch, sig), onBatchComplete, signal, onMalformedReply);
|
|
2246
2330
|
}
|
|
@@ -2273,6 +2357,7 @@ var init_anthropic = __esm({
|
|
|
2273
2357
|
output_config: { format: { type: "json_schema", schema: req.schema } },
|
|
2274
2358
|
messages: [{ role: "user", content }]
|
|
2275
2359
|
});
|
|
2360
|
+
this.recordUsage(res.usage);
|
|
2276
2361
|
const text = res.content.find((b) => b.type === "text")?.text ?? "{}";
|
|
2277
2362
|
try {
|
|
2278
2363
|
return JSON.parse(text);
|
|
@@ -2314,6 +2399,7 @@ var init_anthropic = __esm({
|
|
|
2314
2399
|
out.set(entry.custom_id, { type: "failed", error: entry.result.error?.message ?? entry.result.type });
|
|
2315
2400
|
continue;
|
|
2316
2401
|
}
|
|
2402
|
+
this.recordUsage(entry.result.message?.usage);
|
|
2317
2403
|
const text = entry.result.message?.content.find((b) => b.type === "text")?.text ?? "";
|
|
2318
2404
|
try {
|
|
2319
2405
|
out.set(entry.custom_id, { type: "items", items: parseReplyItems(text) });
|
|
@@ -2336,6 +2422,7 @@ var init_anthropic = __esm({
|
|
|
2336
2422
|
output_config: { format: { type: "json_schema", schema: BATCH_SCHEMA } },
|
|
2337
2423
|
messages: [{ role: "user", content }]
|
|
2338
2424
|
}, { signal });
|
|
2425
|
+
this.recordUsage(res.usage);
|
|
2339
2426
|
const text = res.content.find((b) => b.type === "text")?.text ?? "";
|
|
2340
2427
|
return parseReplyItems(text);
|
|
2341
2428
|
}
|
|
@@ -3133,6 +3220,30 @@ var init_pending_batch = __esm({
|
|
|
3133
3220
|
}
|
|
3134
3221
|
});
|
|
3135
3222
|
|
|
3223
|
+
// src/server/log.ts
|
|
3224
|
+
import { appendFileSync, readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
3225
|
+
import { resolve as resolve5 } from "path";
|
|
3226
|
+
function logPath(projectRoot) {
|
|
3227
|
+
return resolve5(projectRoot, ".glotfile", "log.jsonl");
|
|
3228
|
+
}
|
|
3229
|
+
function appendLog(projectRoot, entry) {
|
|
3230
|
+
ensureGlotfileDir(projectRoot);
|
|
3231
|
+
appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
|
|
3232
|
+
}
|
|
3233
|
+
function readLog(projectRoot, limit = 100) {
|
|
3234
|
+
const path = logPath(projectRoot);
|
|
3235
|
+
if (!existsSync7(path)) return [];
|
|
3236
|
+
const lines = readFileSync7(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
3237
|
+
const entries = lines.map((l) => JSON.parse(l));
|
|
3238
|
+
return entries.reverse().slice(0, limit);
|
|
3239
|
+
}
|
|
3240
|
+
var init_log = __esm({
|
|
3241
|
+
"src/server/log.ts"() {
|
|
3242
|
+
"use strict";
|
|
3243
|
+
init_glotfile_dir();
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
3246
|
+
|
|
3136
3247
|
// src/server/ai/batch-run.ts
|
|
3137
3248
|
function buildBatchJobs(reqs, batchSize) {
|
|
3138
3249
|
const byLocale = /* @__PURE__ */ new Map();
|
|
@@ -3147,7 +3258,8 @@ function buildBatchJobs(reqs, batchSize) {
|
|
|
3147
3258
|
const jobs = [];
|
|
3148
3259
|
for (const [locale, group] of byLocale) {
|
|
3149
3260
|
chunk(group, Math.max(1, batchSize)).forEach((batch, i) => {
|
|
3150
|
-
|
|
3261
|
+
const safeLocale = locale.replace(/[^a-zA-Z0-9-]/g, "-").slice(0, 56);
|
|
3262
|
+
jobs.push({ customId: `${safeLocale}_${i}`, locale, requests: batch });
|
|
3151
3263
|
});
|
|
3152
3264
|
}
|
|
3153
3265
|
return jobs;
|
|
@@ -3179,7 +3291,9 @@ async function submitBatchTranslation(state, provider, reqs, batchSize, model, p
|
|
|
3179
3291
|
return pending;
|
|
3180
3292
|
}
|
|
3181
3293
|
async function applyBatchResults(load, persist, provider, pending, projectRoot, ai) {
|
|
3294
|
+
provider.takeUsage?.();
|
|
3182
3295
|
const outcomes = await provider.translationBatchResults(pending.batchId);
|
|
3296
|
+
const batchUsage = provider.takeUsage?.();
|
|
3183
3297
|
const fresh = load();
|
|
3184
3298
|
const isStale = (r) => {
|
|
3185
3299
|
const entry = fresh.keys[r.key];
|
|
@@ -3188,13 +3302,19 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3188
3302
|
const applied = [];
|
|
3189
3303
|
const results = [];
|
|
3190
3304
|
const retryReqs = [];
|
|
3191
|
-
|
|
3305
|
+
const stale = [];
|
|
3306
|
+
const jobFailures = [];
|
|
3192
3307
|
for (const job of pending.jobs) {
|
|
3193
3308
|
const outcome = outcomes.get(job.customId);
|
|
3194
3309
|
const itemsById = outcome?.type === "items" ? new Map(outcome.items.map((i) => [i.id, i])) : null;
|
|
3310
|
+
if (!itemsById) {
|
|
3311
|
+
if (!outcome) jobFailures.push({ customId: job.customId, locale: job.locale, type: "missing" });
|
|
3312
|
+
else if (outcome.type === "malformed") jobFailures.push({ customId: job.customId, locale: job.locale, type: "malformed", raw: outcome.raw });
|
|
3313
|
+
else if (outcome.type === "failed") jobFailures.push({ customId: job.customId, locale: job.locale, type: "failed", error: outcome.error });
|
|
3314
|
+
}
|
|
3195
3315
|
for (const stored of job.requests) {
|
|
3196
3316
|
if (isStale(stored)) {
|
|
3197
|
-
|
|
3317
|
+
stale.push({ key: stored.key, locale: stored.targetLocale });
|
|
3198
3318
|
continue;
|
|
3199
3319
|
}
|
|
3200
3320
|
const { sourceHash: _hash, ...req } = stored;
|
|
@@ -3213,7 +3333,20 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3213
3333
|
const retryResults = await runLocaleParallel(
|
|
3214
3334
|
retryReqs,
|
|
3215
3335
|
provider,
|
|
3216
|
-
{
|
|
3336
|
+
{
|
|
3337
|
+
// Record the raw reply so an unparseable retry response is diagnosable
|
|
3338
|
+
// from the activity log instead of vanishing into per-item errors.
|
|
3339
|
+
onMalformedReply: (raw, batchSize, locale) => {
|
|
3340
|
+
appendLog(projectRoot, {
|
|
3341
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3342
|
+
kind: "translate",
|
|
3343
|
+
summary: `Malformed model reply (${locale}, batch of ${batchSize})`,
|
|
3344
|
+
model: pending.model,
|
|
3345
|
+
locale,
|
|
3346
|
+
raw
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3349
|
+
},
|
|
3217
3350
|
ai.concurrency,
|
|
3218
3351
|
void 0,
|
|
3219
3352
|
ai.batchSize
|
|
@@ -3221,10 +3354,34 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3221
3354
|
applied.push(...retryReqs);
|
|
3222
3355
|
results.push(...retryResults);
|
|
3223
3356
|
}
|
|
3357
|
+
const retryUsage = provider.takeUsage?.();
|
|
3358
|
+
const pricing = resolvePricing({ ...ai, model: pending.model });
|
|
3359
|
+
let estimatedCostUsd;
|
|
3360
|
+
if (pricing && (batchUsage || retryUsage)) {
|
|
3361
|
+
estimatedCostUsd = (batchUsage ? estimateUsageCostUsd(batchUsage, pricing, BATCH_PRICE_MULTIPLIER) : 0) + (retryUsage ? estimateUsageCostUsd(retryUsage, pricing) : 0);
|
|
3362
|
+
}
|
|
3363
|
+
let usage;
|
|
3364
|
+
if (batchUsage || retryUsage) {
|
|
3365
|
+
usage = batchUsage ?? { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
3366
|
+
if (retryUsage) addUsage(usage, retryUsage);
|
|
3367
|
+
}
|
|
3224
3368
|
const { written, errors } = applyResults(fresh, applied, results);
|
|
3225
3369
|
persist(fresh);
|
|
3226
3370
|
clearPendingBatch(projectRoot);
|
|
3227
|
-
|
|
3371
|
+
const costSuffix = estimatedCostUsd !== void 0 ? ` (~$${estimatedCostUsd.toFixed(2)})` : "";
|
|
3372
|
+
appendLog(projectRoot, {
|
|
3373
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3374
|
+
kind: "translate",
|
|
3375
|
+
summary: `Applied batch ${pending.batchId}: wrote ${written}, ${errors.length} error(s), ${retryReqs.length} retried, ${stale.length} stale${costSuffix}`,
|
|
3376
|
+
model: pending.model,
|
|
3377
|
+
items: applied.map((r) => ({ id: r.id, key: r.key, source: r.source, targetLocale: r.targetLocale })),
|
|
3378
|
+
results,
|
|
3379
|
+
jobFailures: jobFailures.length ? jobFailures : void 0,
|
|
3380
|
+
stale: stale.length ? stale : void 0,
|
|
3381
|
+
usage,
|
|
3382
|
+
estimatedCostUsd
|
|
3383
|
+
});
|
|
3384
|
+
return { written, errors, staleSkipped: stale.length, retried: retryReqs.length, screenshotsSkipped };
|
|
3228
3385
|
}
|
|
3229
3386
|
var init_batch_run = __esm({
|
|
3230
3387
|
"src/server/ai/batch-run.ts"() {
|
|
@@ -3233,55 +3390,8 @@ var init_batch_run = __esm({
|
|
|
3233
3390
|
init_batch();
|
|
3234
3391
|
init_run();
|
|
3235
3392
|
init_pending_batch();
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
// src/server/ai/pricing.ts
|
|
3240
|
-
function bareModelId(model) {
|
|
3241
|
-
let id = model.trim().toLowerCase();
|
|
3242
|
-
const slash = id.lastIndexOf("/");
|
|
3243
|
-
if (slash !== -1) id = id.slice(slash + 1);
|
|
3244
|
-
const anth = id.lastIndexOf("anthropic.");
|
|
3245
|
-
if (anth !== -1) id = id.slice(anth + "anthropic.".length);
|
|
3246
|
-
return id;
|
|
3247
|
-
}
|
|
3248
|
-
function resolvePricing(ai) {
|
|
3249
|
-
if (ai.inputPricePerMTok !== void 0 && ai.outputPricePerMTok !== void 0) {
|
|
3250
|
-
return { source: "profile", inputPerMTok: ai.inputPricePerMTok, outputPerMTok: ai.outputPricePerMTok };
|
|
3251
|
-
}
|
|
3252
|
-
if (FREE_PROVIDERS.has(ai.provider)) return { source: "builtin", inputPerMTok: 0, outputPerMTok: 0 };
|
|
3253
|
-
const id = bareModelId(ai.model);
|
|
3254
|
-
let best;
|
|
3255
|
-
for (const row of PRICE_TABLE) {
|
|
3256
|
-
if (id.startsWith(row[0]) && (!best || row[0].length > best[0].length)) best = row;
|
|
3257
|
-
}
|
|
3258
|
-
return best ? { source: "builtin", inputPerMTok: best[1], outputPerMTok: best[2] } : null;
|
|
3259
|
-
}
|
|
3260
|
-
var PRICE_TABLE, FREE_PROVIDERS;
|
|
3261
|
-
var init_pricing = __esm({
|
|
3262
|
-
"src/server/ai/pricing.ts"() {
|
|
3263
|
-
"use strict";
|
|
3264
|
-
PRICE_TABLE = [
|
|
3265
|
-
["claude-fable-5", 10, 50],
|
|
3266
|
-
["claude-mythos-5", 10, 50],
|
|
3267
|
-
// Deprecated Opus 4.1 / 4.0 cost 3x the 4.5+ generation — they must outrank
|
|
3268
|
-
// the shorter "claude-opus-4" prefix (4-2025 covers the dated Opus 4 full IDs).
|
|
3269
|
-
["claude-opus-4-1", 15, 75],
|
|
3270
|
-
["claude-opus-4-0", 15, 75],
|
|
3271
|
-
["claude-opus-4-2025", 15, 75],
|
|
3272
|
-
["claude-opus-4", 5, 25],
|
|
3273
|
-
["claude-sonnet-4", 3, 15],
|
|
3274
|
-
["claude-haiku-4", 1, 5],
|
|
3275
|
-
["claude-3-5-haiku", 0.8, 4],
|
|
3276
|
-
["gpt-5.5-pro", 30, 180],
|
|
3277
|
-
["gpt-5.5", 5, 30],
|
|
3278
|
-
["gpt-5.4-pro", 30, 180],
|
|
3279
|
-
["gpt-5.4-mini", 0.75, 4.5],
|
|
3280
|
-
["gpt-5.4-nano", 0.2, 1.25],
|
|
3281
|
-
["gpt-5.4", 2.5, 15],
|
|
3282
|
-
["gpt-5.3-codex", 1.75, 14]
|
|
3283
|
-
];
|
|
3284
|
-
FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "claude-code"]);
|
|
3393
|
+
init_log();
|
|
3394
|
+
init_pricing();
|
|
3285
3395
|
}
|
|
3286
3396
|
});
|
|
3287
3397
|
|
|
@@ -3348,30 +3458,6 @@ var init_estimate = __esm({
|
|
|
3348
3458
|
}
|
|
3349
3459
|
});
|
|
3350
3460
|
|
|
3351
|
-
// src/server/log.ts
|
|
3352
|
-
import { appendFileSync, readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
3353
|
-
import { resolve as resolve5 } from "path";
|
|
3354
|
-
function logPath(projectRoot) {
|
|
3355
|
-
return resolve5(projectRoot, ".glotfile", "log.jsonl");
|
|
3356
|
-
}
|
|
3357
|
-
function appendLog(projectRoot, entry) {
|
|
3358
|
-
ensureGlotfileDir(projectRoot);
|
|
3359
|
-
appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
|
|
3360
|
-
}
|
|
3361
|
-
function readLog(projectRoot, limit = 100) {
|
|
3362
|
-
const path = logPath(projectRoot);
|
|
3363
|
-
if (!existsSync7(path)) return [];
|
|
3364
|
-
const lines = readFileSync7(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
3365
|
-
const entries = lines.map((l) => JSON.parse(l));
|
|
3366
|
-
return entries.reverse().slice(0, limit);
|
|
3367
|
-
}
|
|
3368
|
-
var init_log = __esm({
|
|
3369
|
-
"src/server/log.ts"() {
|
|
3370
|
-
"use strict";
|
|
3371
|
-
init_glotfile_dir();
|
|
3372
|
-
}
|
|
3373
|
-
});
|
|
3374
|
-
|
|
3375
3461
|
// src/server/scan.ts
|
|
3376
3462
|
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
3377
3463
|
import { resolve as resolve6 } from "path";
|
|
@@ -6835,6 +6921,7 @@ function createApi(deps) {
|
|
|
6835
6921
|
persist(fresh);
|
|
6836
6922
|
totalWritten += written;
|
|
6837
6923
|
allErrors.push(...errors);
|
|
6924
|
+
const usage = provider.takeUsage?.();
|
|
6838
6925
|
appendLog(projectRoot, {
|
|
6839
6926
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6840
6927
|
kind: "translate",
|
|
@@ -6845,7 +6932,9 @@ function createApi(deps) {
|
|
|
6845
6932
|
const req = reqById.get(r.id);
|
|
6846
6933
|
return { id: r.id, key: req?.key ?? "", source: req?.source ?? "", targetLocale: req?.targetLocale, context: req?.context, glossary: req?.glossary, screenshot: req ? fresh.keys[req.key]?.screenshot : void 0 };
|
|
6847
6934
|
}),
|
|
6848
|
-
results: batchResults
|
|
6935
|
+
results: batchResults,
|
|
6936
|
+
usage,
|
|
6937
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg)
|
|
6849
6938
|
});
|
|
6850
6939
|
const ld = (localeDone.get(locale) ?? 0) + batchResults.length;
|
|
6851
6940
|
localeDone.set(locale, ld);
|
|
@@ -6917,11 +7006,14 @@ function createApi(deps) {
|
|
|
6917
7006
|
}, aiCfg.concurrency, void 0, aiCfg.batchSize);
|
|
6918
7007
|
const latest = load();
|
|
6919
7008
|
({ written, errors } = applyResults(latest, toTranslate, results, void 0, force));
|
|
7009
|
+
const usage = provider.takeUsage?.();
|
|
6920
7010
|
const entry = {
|
|
6921
7011
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6922
7012
|
kind: "translate",
|
|
6923
7013
|
summary: `Translated ${toTranslate.length} item(s)`,
|
|
6924
7014
|
model: aiCfg.model,
|
|
7015
|
+
usage,
|
|
7016
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg),
|
|
6925
7017
|
system: buildSystemPrompt(toTranslate.some((r) => r.plural !== void 0)),
|
|
6926
7018
|
// Log the screenshot PATH only — never the image bytes.
|
|
6927
7019
|
items: toTranslate.map((r) => ({
|
|
@@ -7019,17 +7111,7 @@ function createApi(deps) {
|
|
|
7019
7111
|
if (!supportsBatchTranslate(provider)) {
|
|
7020
7112
|
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
7021
7113
|
}
|
|
7022
|
-
const outcome = await applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
7023
|
-
batchSize: aiCfg.batchSize,
|
|
7024
|
-
concurrency: aiCfg.concurrency
|
|
7025
|
-
});
|
|
7026
|
-
appendLog(projectRoot, {
|
|
7027
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7028
|
-
kind: "translate",
|
|
7029
|
-
summary: `Applied batch ${pending.batchId}: wrote ${outcome.written}, ${outcome.retried} retried, ${outcome.staleSkipped} stale`,
|
|
7030
|
-
model: aiCfg.model,
|
|
7031
|
-
results: []
|
|
7032
|
-
});
|
|
7114
|
+
const outcome = await applyBatchResults(load, persist, provider, pending, projectRoot, aiCfg);
|
|
7033
7115
|
console.log(`[batch] applied ${pending.batchId} \u2014 wrote ${outcome.written}, ${outcome.errors.length} error(s)`);
|
|
7034
7116
|
return c.json(outcome);
|
|
7035
7117
|
}));
|
|
@@ -7180,6 +7262,7 @@ function createApi(deps) {
|
|
|
7180
7262
|
const batch = raw;
|
|
7181
7263
|
const fresh = load();
|
|
7182
7264
|
const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [], void 0, body.force === true);
|
|
7265
|
+
const usage = provider.takeUsage?.();
|
|
7183
7266
|
appendLog(projectRoot, {
|
|
7184
7267
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7185
7268
|
kind: "context",
|
|
@@ -7187,7 +7270,9 @@ function createApi(deps) {
|
|
|
7187
7270
|
model: aiCfg.model,
|
|
7188
7271
|
system,
|
|
7189
7272
|
items: chunk2.map((t) => ({ id: t.id, key: t.key, source: t.source })),
|
|
7190
|
-
results: (batch.items ?? []).map((r) => ({ id: r.id, value: r.context, error: r.error }))
|
|
7273
|
+
results: (batch.items ?? []).map((r) => ({ id: r.id, value: r.context, error: r.error })),
|
|
7274
|
+
usage,
|
|
7275
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg)
|
|
7191
7276
|
});
|
|
7192
7277
|
persist(fresh);
|
|
7193
7278
|
totalWritten += written;
|
|
@@ -7228,6 +7313,7 @@ var init_api = __esm({
|
|
|
7228
7313
|
init_batch_run();
|
|
7229
7314
|
init_pending_batch();
|
|
7230
7315
|
init_estimate();
|
|
7316
|
+
init_pricing();
|
|
7231
7317
|
init_log();
|
|
7232
7318
|
init_schema();
|
|
7233
7319
|
init_run3();
|
|
@@ -7390,6 +7476,7 @@ init_provider();
|
|
|
7390
7476
|
init_batch_run();
|
|
7391
7477
|
init_pending_batch();
|
|
7392
7478
|
init_estimate();
|
|
7479
|
+
init_pricing();
|
|
7393
7480
|
init_log();
|
|
7394
7481
|
init_scan();
|
|
7395
7482
|
init_scanner();
|
|
@@ -7712,11 +7799,14 @@ async function runTranslate(args) {
|
|
|
7712
7799
|
if (!batchCallbackFired) {
|
|
7713
7800
|
({ written, errors } = applyResults(state, toTranslate, results));
|
|
7714
7801
|
}
|
|
7802
|
+
const usage = provider.takeUsage?.();
|
|
7715
7803
|
appendLog(projectRoot, {
|
|
7716
7804
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7717
7805
|
kind: "translate",
|
|
7718
7806
|
summary: `Translated ${toTranslate.length} item(s)`,
|
|
7719
7807
|
model: ai.model,
|
|
7808
|
+
usage,
|
|
7809
|
+
estimatedCostUsd: usageCostUsd(usage, ai),
|
|
7720
7810
|
system: buildSystemPrompt(toTranslate.some((r) => r.plural !== void 0)),
|
|
7721
7811
|
items: toTranslate.map((r) => ({
|
|
7722
7812
|
id: r.id,
|
|
@@ -7751,7 +7841,7 @@ async function applyPending(args, provider, pending, ai) {
|
|
|
7751
7841
|
provider,
|
|
7752
7842
|
pending,
|
|
7753
7843
|
projectRoot,
|
|
7754
|
-
|
|
7844
|
+
ai
|
|
7755
7845
|
);
|
|
7756
7846
|
reportApply(outcome);
|
|
7757
7847
|
}
|