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/server.js
CHANGED
|
@@ -3082,6 +3082,67 @@ async function runBatched(reqs, batchSize, callBatch, onBatchComplete, signal, o
|
|
|
3082
3082
|
return results;
|
|
3083
3083
|
}
|
|
3084
3084
|
|
|
3085
|
+
// src/server/ai/pricing.ts
|
|
3086
|
+
function addUsage(into, add) {
|
|
3087
|
+
into.inputTokens += add.inputTokens;
|
|
3088
|
+
into.outputTokens += add.outputTokens;
|
|
3089
|
+
into.cacheCreationInputTokens += add.cacheCreationInputTokens;
|
|
3090
|
+
into.cacheReadInputTokens += add.cacheReadInputTokens;
|
|
3091
|
+
}
|
|
3092
|
+
var BATCH_PRICE_MULTIPLIER = 0.5;
|
|
3093
|
+
var CACHE_WRITE_MULTIPLIER = 1.25;
|
|
3094
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
3095
|
+
function usageCostUsd(usage, ai, multiplier = 1) {
|
|
3096
|
+
if (!usage) return void 0;
|
|
3097
|
+
const pricing = resolvePricing(ai);
|
|
3098
|
+
return pricing ? estimateUsageCostUsd(usage, pricing, multiplier) : void 0;
|
|
3099
|
+
}
|
|
3100
|
+
function estimateUsageCostUsd(usage, pricing, multiplier = 1) {
|
|
3101
|
+
const inputCost = (usage.inputTokens + usage.cacheCreationInputTokens * CACHE_WRITE_MULTIPLIER + usage.cacheReadInputTokens * CACHE_READ_MULTIPLIER) * pricing.inputPerMTok;
|
|
3102
|
+
return (inputCost + usage.outputTokens * pricing.outputPerMTok) / 1e6 * multiplier;
|
|
3103
|
+
}
|
|
3104
|
+
var PRICE_TABLE = [
|
|
3105
|
+
["claude-fable-5", 10, 50],
|
|
3106
|
+
["claude-mythos-5", 10, 50],
|
|
3107
|
+
// Deprecated Opus 4.1 / 4.0 cost 3x the 4.5+ generation — they must outrank
|
|
3108
|
+
// the shorter "claude-opus-4" prefix (4-2025 covers the dated Opus 4 full IDs).
|
|
3109
|
+
["claude-opus-4-1", 15, 75],
|
|
3110
|
+
["claude-opus-4-0", 15, 75],
|
|
3111
|
+
["claude-opus-4-2025", 15, 75],
|
|
3112
|
+
["claude-opus-4", 5, 25],
|
|
3113
|
+
["claude-sonnet-4", 3, 15],
|
|
3114
|
+
["claude-haiku-4", 1, 5],
|
|
3115
|
+
["claude-3-5-haiku", 0.8, 4],
|
|
3116
|
+
["gpt-5.5-pro", 30, 180],
|
|
3117
|
+
["gpt-5.5", 5, 30],
|
|
3118
|
+
["gpt-5.4-pro", 30, 180],
|
|
3119
|
+
["gpt-5.4-mini", 0.75, 4.5],
|
|
3120
|
+
["gpt-5.4-nano", 0.2, 1.25],
|
|
3121
|
+
["gpt-5.4", 2.5, 15],
|
|
3122
|
+
["gpt-5.3-codex", 1.75, 14]
|
|
3123
|
+
];
|
|
3124
|
+
var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "claude-code"]);
|
|
3125
|
+
function bareModelId(model) {
|
|
3126
|
+
let id = model.trim().toLowerCase();
|
|
3127
|
+
const slash = id.lastIndexOf("/");
|
|
3128
|
+
if (slash !== -1) id = id.slice(slash + 1);
|
|
3129
|
+
const anth = id.lastIndexOf("anthropic.");
|
|
3130
|
+
if (anth !== -1) id = id.slice(anth + "anthropic.".length);
|
|
3131
|
+
return id;
|
|
3132
|
+
}
|
|
3133
|
+
function resolvePricing(ai) {
|
|
3134
|
+
if (ai.inputPricePerMTok !== void 0 && ai.outputPricePerMTok !== void 0) {
|
|
3135
|
+
return { source: "profile", inputPerMTok: ai.inputPricePerMTok, outputPerMTok: ai.outputPricePerMTok };
|
|
3136
|
+
}
|
|
3137
|
+
if (FREE_PROVIDERS.has(ai.provider)) return { source: "builtin", inputPerMTok: 0, outputPerMTok: 0 };
|
|
3138
|
+
const id = bareModelId(ai.model);
|
|
3139
|
+
let best;
|
|
3140
|
+
for (const row of PRICE_TABLE) {
|
|
3141
|
+
if (id.startsWith(row[0]) && (!best || row[0].length > best[0].length)) best = row;
|
|
3142
|
+
}
|
|
3143
|
+
return best ? { source: "builtin", inputPerMTok: best[1], outputPerMTok: best[2] } : null;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3085
3146
|
// src/server/ai/anthropic.ts
|
|
3086
3147
|
var AnthropicProvider = class {
|
|
3087
3148
|
constructor(config, client) {
|
|
@@ -3097,9 +3158,25 @@ var AnthropicProvider = class {
|
|
|
3097
3158
|
}
|
|
3098
3159
|
config;
|
|
3099
3160
|
client;
|
|
3161
|
+
usage = { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
3100
3162
|
supportsVision() {
|
|
3101
3163
|
return true;
|
|
3102
3164
|
}
|
|
3165
|
+
recordUsage(usage) {
|
|
3166
|
+
if (!usage) return;
|
|
3167
|
+
addUsage(this.usage, {
|
|
3168
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
3169
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
3170
|
+
cacheCreationInputTokens: usage.cache_creation_input_tokens ?? 0,
|
|
3171
|
+
cacheReadInputTokens: usage.cache_read_input_tokens ?? 0
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
takeUsage() {
|
|
3175
|
+
const taken = this.usage;
|
|
3176
|
+
this.usage = { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
3177
|
+
const any = taken.inputTokens || taken.outputTokens || taken.cacheCreationInputTokens || taken.cacheReadInputTokens;
|
|
3178
|
+
return any ? taken : void 0;
|
|
3179
|
+
}
|
|
3103
3180
|
translate(reqs, onBatchComplete, signal, onMalformedReply) {
|
|
3104
3181
|
return runBatched(reqs, this.config.batchSize, (batch, sig) => this.callBatch(batch, sig), onBatchComplete, signal, onMalformedReply);
|
|
3105
3182
|
}
|
|
@@ -3132,6 +3209,7 @@ var AnthropicProvider = class {
|
|
|
3132
3209
|
output_config: { format: { type: "json_schema", schema: req.schema } },
|
|
3133
3210
|
messages: [{ role: "user", content }]
|
|
3134
3211
|
});
|
|
3212
|
+
this.recordUsage(res.usage);
|
|
3135
3213
|
const text = res.content.find((b) => b.type === "text")?.text ?? "{}";
|
|
3136
3214
|
try {
|
|
3137
3215
|
return JSON.parse(text);
|
|
@@ -3173,6 +3251,7 @@ var AnthropicProvider = class {
|
|
|
3173
3251
|
out.set(entry.custom_id, { type: "failed", error: entry.result.error?.message ?? entry.result.type });
|
|
3174
3252
|
continue;
|
|
3175
3253
|
}
|
|
3254
|
+
this.recordUsage(entry.result.message?.usage);
|
|
3176
3255
|
const text = entry.result.message?.content.find((b) => b.type === "text")?.text ?? "";
|
|
3177
3256
|
try {
|
|
3178
3257
|
out.set(entry.custom_id, { type: "items", items: parseReplyItems(text) });
|
|
@@ -3195,6 +3274,7 @@ var AnthropicProvider = class {
|
|
|
3195
3274
|
output_config: { format: { type: "json_schema", schema: BATCH_SCHEMA } },
|
|
3196
3275
|
messages: [{ role: "user", content }]
|
|
3197
3276
|
}, { signal });
|
|
3277
|
+
this.recordUsage(res.usage);
|
|
3198
3278
|
const text = res.content.find((b) => b.type === "text")?.text ?? "";
|
|
3199
3279
|
return parseReplyItems(text);
|
|
3200
3280
|
}
|
|
@@ -3760,6 +3840,24 @@ function clearPendingBatch(projectRoot) {
|
|
|
3760
3840
|
rmSync4(pendingBatchPath(projectRoot), { force: true });
|
|
3761
3841
|
}
|
|
3762
3842
|
|
|
3843
|
+
// src/server/log.ts
|
|
3844
|
+
import { appendFileSync, readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
3845
|
+
import { resolve as resolve6 } from "path";
|
|
3846
|
+
function logPath(projectRoot) {
|
|
3847
|
+
return resolve6(projectRoot, ".glotfile", "log.jsonl");
|
|
3848
|
+
}
|
|
3849
|
+
function appendLog(projectRoot, entry) {
|
|
3850
|
+
ensureGlotfileDir(projectRoot);
|
|
3851
|
+
appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
|
|
3852
|
+
}
|
|
3853
|
+
function readLog(projectRoot, limit = 100) {
|
|
3854
|
+
const path = logPath(projectRoot);
|
|
3855
|
+
if (!existsSync9(path)) return [];
|
|
3856
|
+
const lines = readFileSync9(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
3857
|
+
const entries = lines.map((l) => JSON.parse(l));
|
|
3858
|
+
return entries.reverse().slice(0, limit);
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3763
3861
|
// src/server/ai/batch-run.ts
|
|
3764
3862
|
function buildBatchJobs(reqs, batchSize) {
|
|
3765
3863
|
const byLocale = /* @__PURE__ */ new Map();
|
|
@@ -3774,7 +3872,8 @@ function buildBatchJobs(reqs, batchSize) {
|
|
|
3774
3872
|
const jobs = [];
|
|
3775
3873
|
for (const [locale, group] of byLocale) {
|
|
3776
3874
|
chunk(group, Math.max(1, batchSize)).forEach((batch, i) => {
|
|
3777
|
-
|
|
3875
|
+
const safeLocale = locale.replace(/[^a-zA-Z0-9-]/g, "-").slice(0, 56);
|
|
3876
|
+
jobs.push({ customId: `${safeLocale}_${i}`, locale, requests: batch });
|
|
3778
3877
|
});
|
|
3779
3878
|
}
|
|
3780
3879
|
return jobs;
|
|
@@ -3806,7 +3905,9 @@ async function submitBatchTranslation(state, provider, reqs, batchSize, model, p
|
|
|
3806
3905
|
return pending;
|
|
3807
3906
|
}
|
|
3808
3907
|
async function applyBatchResults(load, persist, provider, pending, projectRoot, ai) {
|
|
3908
|
+
provider.takeUsage?.();
|
|
3809
3909
|
const outcomes = await provider.translationBatchResults(pending.batchId);
|
|
3910
|
+
const batchUsage = provider.takeUsage?.();
|
|
3810
3911
|
const fresh = load();
|
|
3811
3912
|
const isStale = (r) => {
|
|
3812
3913
|
const entry = fresh.keys[r.key];
|
|
@@ -3815,13 +3916,19 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3815
3916
|
const applied = [];
|
|
3816
3917
|
const results = [];
|
|
3817
3918
|
const retryReqs = [];
|
|
3818
|
-
|
|
3919
|
+
const stale = [];
|
|
3920
|
+
const jobFailures = [];
|
|
3819
3921
|
for (const job of pending.jobs) {
|
|
3820
3922
|
const outcome = outcomes.get(job.customId);
|
|
3821
3923
|
const itemsById = outcome?.type === "items" ? new Map(outcome.items.map((i) => [i.id, i])) : null;
|
|
3924
|
+
if (!itemsById) {
|
|
3925
|
+
if (!outcome) jobFailures.push({ customId: job.customId, locale: job.locale, type: "missing" });
|
|
3926
|
+
else if (outcome.type === "malformed") jobFailures.push({ customId: job.customId, locale: job.locale, type: "malformed", raw: outcome.raw });
|
|
3927
|
+
else if (outcome.type === "failed") jobFailures.push({ customId: job.customId, locale: job.locale, type: "failed", error: outcome.error });
|
|
3928
|
+
}
|
|
3822
3929
|
for (const stored of job.requests) {
|
|
3823
3930
|
if (isStale(stored)) {
|
|
3824
|
-
|
|
3931
|
+
stale.push({ key: stored.key, locale: stored.targetLocale });
|
|
3825
3932
|
continue;
|
|
3826
3933
|
}
|
|
3827
3934
|
const { sourceHash: _hash, ...req } = stored;
|
|
@@ -3840,7 +3947,20 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3840
3947
|
const retryResults = await runLocaleParallel(
|
|
3841
3948
|
retryReqs,
|
|
3842
3949
|
provider,
|
|
3843
|
-
{
|
|
3950
|
+
{
|
|
3951
|
+
// Record the raw reply so an unparseable retry response is diagnosable
|
|
3952
|
+
// from the activity log instead of vanishing into per-item errors.
|
|
3953
|
+
onMalformedReply: (raw, batchSize, locale) => {
|
|
3954
|
+
appendLog(projectRoot, {
|
|
3955
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3956
|
+
kind: "translate",
|
|
3957
|
+
summary: `Malformed model reply (${locale}, batch of ${batchSize})`,
|
|
3958
|
+
model: pending.model,
|
|
3959
|
+
locale,
|
|
3960
|
+
raw
|
|
3961
|
+
});
|
|
3962
|
+
}
|
|
3963
|
+
},
|
|
3844
3964
|
ai.concurrency,
|
|
3845
3965
|
void 0,
|
|
3846
3966
|
ai.batchSize
|
|
@@ -3848,53 +3968,34 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3848
3968
|
applied.push(...retryReqs);
|
|
3849
3969
|
results.push(...retryResults);
|
|
3850
3970
|
}
|
|
3971
|
+
const retryUsage = provider.takeUsage?.();
|
|
3972
|
+
const pricing = resolvePricing({ ...ai, model: pending.model });
|
|
3973
|
+
let estimatedCostUsd;
|
|
3974
|
+
if (pricing && (batchUsage || retryUsage)) {
|
|
3975
|
+
estimatedCostUsd = (batchUsage ? estimateUsageCostUsd(batchUsage, pricing, BATCH_PRICE_MULTIPLIER) : 0) + (retryUsage ? estimateUsageCostUsd(retryUsage, pricing) : 0);
|
|
3976
|
+
}
|
|
3977
|
+
let usage;
|
|
3978
|
+
if (batchUsage || retryUsage) {
|
|
3979
|
+
usage = batchUsage ?? { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
3980
|
+
if (retryUsage) addUsage(usage, retryUsage);
|
|
3981
|
+
}
|
|
3851
3982
|
const { written, errors } = applyResults(fresh, applied, results);
|
|
3852
3983
|
persist(fresh);
|
|
3853
3984
|
clearPendingBatch(projectRoot);
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
["claude-haiku-4", 1, 5],
|
|
3869
|
-
["claude-3-5-haiku", 0.8, 4],
|
|
3870
|
-
["gpt-5.5-pro", 30, 180],
|
|
3871
|
-
["gpt-5.5", 5, 30],
|
|
3872
|
-
["gpt-5.4-pro", 30, 180],
|
|
3873
|
-
["gpt-5.4-mini", 0.75, 4.5],
|
|
3874
|
-
["gpt-5.4-nano", 0.2, 1.25],
|
|
3875
|
-
["gpt-5.4", 2.5, 15],
|
|
3876
|
-
["gpt-5.3-codex", 1.75, 14]
|
|
3877
|
-
];
|
|
3878
|
-
var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "claude-code"]);
|
|
3879
|
-
function bareModelId(model) {
|
|
3880
|
-
let id = model.trim().toLowerCase();
|
|
3881
|
-
const slash = id.lastIndexOf("/");
|
|
3882
|
-
if (slash !== -1) id = id.slice(slash + 1);
|
|
3883
|
-
const anth = id.lastIndexOf("anthropic.");
|
|
3884
|
-
if (anth !== -1) id = id.slice(anth + "anthropic.".length);
|
|
3885
|
-
return id;
|
|
3886
|
-
}
|
|
3887
|
-
function resolvePricing(ai) {
|
|
3888
|
-
if (ai.inputPricePerMTok !== void 0 && ai.outputPricePerMTok !== void 0) {
|
|
3889
|
-
return { source: "profile", inputPerMTok: ai.inputPricePerMTok, outputPerMTok: ai.outputPricePerMTok };
|
|
3890
|
-
}
|
|
3891
|
-
if (FREE_PROVIDERS.has(ai.provider)) return { source: "builtin", inputPerMTok: 0, outputPerMTok: 0 };
|
|
3892
|
-
const id = bareModelId(ai.model);
|
|
3893
|
-
let best;
|
|
3894
|
-
for (const row of PRICE_TABLE) {
|
|
3895
|
-
if (id.startsWith(row[0]) && (!best || row[0].length > best[0].length)) best = row;
|
|
3896
|
-
}
|
|
3897
|
-
return best ? { source: "builtin", inputPerMTok: best[1], outputPerMTok: best[2] } : null;
|
|
3985
|
+
const costSuffix = estimatedCostUsd !== void 0 ? ` (~$${estimatedCostUsd.toFixed(2)})` : "";
|
|
3986
|
+
appendLog(projectRoot, {
|
|
3987
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3988
|
+
kind: "translate",
|
|
3989
|
+
summary: `Applied batch ${pending.batchId}: wrote ${written}, ${errors.length} error(s), ${retryReqs.length} retried, ${stale.length} stale${costSuffix}`,
|
|
3990
|
+
model: pending.model,
|
|
3991
|
+
items: applied.map((r) => ({ id: r.id, key: r.key, source: r.source, targetLocale: r.targetLocale })),
|
|
3992
|
+
results,
|
|
3993
|
+
jobFailures: jobFailures.length ? jobFailures : void 0,
|
|
3994
|
+
stale: stale.length ? stale : void 0,
|
|
3995
|
+
usage,
|
|
3996
|
+
estimatedCostUsd
|
|
3997
|
+
});
|
|
3998
|
+
return { written, errors, staleSkipped: stale.length, retried: retryReqs.length, screenshotsSkipped };
|
|
3898
3999
|
}
|
|
3899
4000
|
|
|
3900
4001
|
// src/server/ai/estimate.ts
|
|
@@ -3950,24 +4051,6 @@ function estimateTranslation(state, ai, opts) {
|
|
|
3950
4051
|
};
|
|
3951
4052
|
}
|
|
3952
4053
|
|
|
3953
|
-
// src/server/log.ts
|
|
3954
|
-
import { appendFileSync, readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
3955
|
-
import { resolve as resolve6 } from "path";
|
|
3956
|
-
function logPath(projectRoot) {
|
|
3957
|
-
return resolve6(projectRoot, ".glotfile", "log.jsonl");
|
|
3958
|
-
}
|
|
3959
|
-
function appendLog(projectRoot, entry) {
|
|
3960
|
-
ensureGlotfileDir(projectRoot);
|
|
3961
|
-
appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
|
|
3962
|
-
}
|
|
3963
|
-
function readLog(projectRoot, limit = 100) {
|
|
3964
|
-
const path = logPath(projectRoot);
|
|
3965
|
-
if (!existsSync9(path)) return [];
|
|
3966
|
-
const lines = readFileSync9(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
3967
|
-
const entries = lines.map((l) => JSON.parse(l));
|
|
3968
|
-
return entries.reverse().slice(0, limit);
|
|
3969
|
-
}
|
|
3970
|
-
|
|
3971
4054
|
// src/server/import/run.ts
|
|
3972
4055
|
import { relative as relative3 } from "path";
|
|
3973
4056
|
|
|
@@ -6292,6 +6375,7 @@ function createApi(deps) {
|
|
|
6292
6375
|
persist(fresh);
|
|
6293
6376
|
totalWritten += written;
|
|
6294
6377
|
allErrors.push(...errors);
|
|
6378
|
+
const usage = provider.takeUsage?.();
|
|
6295
6379
|
appendLog(projectRoot, {
|
|
6296
6380
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6297
6381
|
kind: "translate",
|
|
@@ -6302,7 +6386,9 @@ function createApi(deps) {
|
|
|
6302
6386
|
const req = reqById.get(r.id);
|
|
6303
6387
|
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 };
|
|
6304
6388
|
}),
|
|
6305
|
-
results: batchResults
|
|
6389
|
+
results: batchResults,
|
|
6390
|
+
usage,
|
|
6391
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg)
|
|
6306
6392
|
});
|
|
6307
6393
|
const ld = (localeDone.get(locale) ?? 0) + batchResults.length;
|
|
6308
6394
|
localeDone.set(locale, ld);
|
|
@@ -6374,11 +6460,14 @@ function createApi(deps) {
|
|
|
6374
6460
|
}, aiCfg.concurrency, void 0, aiCfg.batchSize);
|
|
6375
6461
|
const latest = load();
|
|
6376
6462
|
({ written, errors } = applyResults(latest, toTranslate, results, void 0, force));
|
|
6463
|
+
const usage = provider.takeUsage?.();
|
|
6377
6464
|
const entry = {
|
|
6378
6465
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6379
6466
|
kind: "translate",
|
|
6380
6467
|
summary: `Translated ${toTranslate.length} item(s)`,
|
|
6381
6468
|
model: aiCfg.model,
|
|
6469
|
+
usage,
|
|
6470
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg),
|
|
6382
6471
|
system: buildSystemPrompt(toTranslate.some((r) => r.plural !== void 0)),
|
|
6383
6472
|
// Log the screenshot PATH only — never the image bytes.
|
|
6384
6473
|
items: toTranslate.map((r) => ({
|
|
@@ -6476,17 +6565,7 @@ function createApi(deps) {
|
|
|
6476
6565
|
if (!supportsBatchTranslate(provider)) {
|
|
6477
6566
|
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
6478
6567
|
}
|
|
6479
|
-
const outcome = await applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
6480
|
-
batchSize: aiCfg.batchSize,
|
|
6481
|
-
concurrency: aiCfg.concurrency
|
|
6482
|
-
});
|
|
6483
|
-
appendLog(projectRoot, {
|
|
6484
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6485
|
-
kind: "translate",
|
|
6486
|
-
summary: `Applied batch ${pending.batchId}: wrote ${outcome.written}, ${outcome.retried} retried, ${outcome.staleSkipped} stale`,
|
|
6487
|
-
model: aiCfg.model,
|
|
6488
|
-
results: []
|
|
6489
|
-
});
|
|
6568
|
+
const outcome = await applyBatchResults(load, persist, provider, pending, projectRoot, aiCfg);
|
|
6490
6569
|
console.log(`[batch] applied ${pending.batchId} \u2014 wrote ${outcome.written}, ${outcome.errors.length} error(s)`);
|
|
6491
6570
|
return c.json(outcome);
|
|
6492
6571
|
}));
|
|
@@ -6637,6 +6716,7 @@ function createApi(deps) {
|
|
|
6637
6716
|
const batch = raw;
|
|
6638
6717
|
const fresh = load();
|
|
6639
6718
|
const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [], void 0, body.force === true);
|
|
6719
|
+
const usage = provider.takeUsage?.();
|
|
6640
6720
|
appendLog(projectRoot, {
|
|
6641
6721
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6642
6722
|
kind: "context",
|
|
@@ -6644,7 +6724,9 @@ function createApi(deps) {
|
|
|
6644
6724
|
model: aiCfg.model,
|
|
6645
6725
|
system,
|
|
6646
6726
|
items: chunk2.map((t) => ({ id: t.id, key: t.key, source: t.source })),
|
|
6647
|
-
results: (batch.items ?? []).map((r) => ({ id: r.id, value: r.context, error: r.error }))
|
|
6727
|
+
results: (batch.items ?? []).map((r) => ({ id: r.id, value: r.context, error: r.error })),
|
|
6728
|
+
usage,
|
|
6729
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg)
|
|
6648
6730
|
});
|
|
6649
6731
|
persist(fresh);
|
|
6650
6732
|
totalWritten += written;
|