@kienmatu/inkos-studio 0.1.3 → 3.3.0
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/api/server.d.ts.map +1 -1
- package/dist/api/server.js +192 -10
- package/dist/api/server.js.map +1 -1
- package/dist/api/task-store.d.ts +1 -0
- package/dist/api/task-store.d.ts.map +1 -1
- package/dist/api/task-store.js +3 -0
- package/dist/api/task-store.js.map +1 -1
- package/dist/assets/{FilmWizard-BAeDX_rI.js → FilmWizard-DsSZPo5A.js} +1 -1
- package/dist/assets/{FlowView-C6n6r8pJ.js → FlowView-DtborsGc.js} +1 -1
- package/dist/assets/{_baseUniq-CHNUfn5h.js → _baseUniq-BEIj9tcE.js} +1 -1
- package/dist/assets/{arc-CdPV8k-0.js → arc-C7Rm_CAk.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-oodkcVSQ.js → architectureDiagram-Q4EWVU46-DlmfKm7b.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-CIbxhPHs.js → blockDiagram-DXYQGD6D-DwWcyB5e.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-CxIpgCiP.js → c4Diagram-AHTNJAMY-DJr7ig4J.js} +1 -1
- package/dist/assets/channel-D3RJhFng.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-Cf70tfVa.js → chunk-4BX2VUAB-BEbkhxPj.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-uFQAF256.js → chunk-4TB4RGXK-CKlayWkT.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-4u7WcTds.js → chunk-55IACEB6-Do_NAv20.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-DKvxdCCA.js → chunk-EDXVE4YY-CIaC2_XQ.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DA6v8xvJ.js → chunk-FMBD7UC4-DK2fFb-S.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-DwLlaWl9.js → chunk-OYMX7WX6-DOk8ZO7Q.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-CFgB8UWK.js → chunk-QZHKN3VN-CWQlxTIX.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-JgBXvZgf.js → chunk-YZCP3GAM-BKMKNDs_.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Ctnvqnpw.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-Ctnvqnpw.js +1 -0
- package/dist/assets/clone-BdOaHqQL.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-DKMKqdAL.js → cose-bilkent-S5V4N54A-CtsSt-S-.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-DuZ4zm4B.js → dagre-KV5264BT-zROnUgJY.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-CoA3HYsp.js → diagram-5BDNPKRD-Ct6aFDWr.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-DVnnKNaz.js → diagram-G4DWMVQ6-B2bwAps6.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-iB_Pzv_O.js → diagram-MMDJMWI5-CbwZreHT.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-CJ1VtRnc.js → diagram-TYMM5635-gKzWLxCO.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-SWAf-S26.js → erDiagram-SMLLAGMA-DipB25lI.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-bjSm1F6q.js → flowDiagram-DWJPFMVM-C9VuPnc4.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-B3eioyUQ.js → ganttDiagram-T4ZO3ILL-B2dTjD_8.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-DGaIKwEh.js → gitGraphDiagram-UUTBAWPF-BZXYA0Ec.js} +1 -1
- package/dist/assets/{graph-zC0IkLW4.js → graph-iDCrfqrI.js} +1 -1
- package/dist/assets/{highlighted-body-OFNGDK62-CkE4PgdB.js → highlighted-body-OFNGDK62-CP2BM2l8.js} +1 -1
- package/dist/assets/index-CTu6WYGu.css +1 -0
- package/dist/assets/{index-9mF0_S-1.js → index-PEL5ijWf.js} +298 -293
- package/dist/assets/{infoDiagram-42DDH7IO-z7BfWhD6.js → infoDiagram-42DDH7IO-Bd7gtY2E.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-DgkoM8pX.js → ishikawaDiagram-UXIWVN3A-HMp96hG2.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-7szsjnaK.js → journeyDiagram-VCZTEJTY-D36jcF3p.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-CN_iYP62.js → kanban-definition-6JOO6SKY-C4wSbzoj.js} +1 -1
- package/dist/assets/{layout-B1_IEbhE.js → layout-DqXRWJEx.js} +1 -1
- package/dist/assets/{linear-DgZKeCD_.js → linear-QlXzAe7A.js} +1 -1
- package/dist/assets/{min-BGmTIgk7.js → min-iaA6vGbj.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-CaZaCUVQ.js → mindmap-definition-QFDTVHPH-B-PCe4LP.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-Bai_9_pu.js → pieDiagram-DEJITSTG-DRSPzSKd.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-CgKE3-Ag.js → quadrantDiagram-34T5L4WZ-D-c8JbLY.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-CiP3d8R4.js → requirementDiagram-MS252O5E-CdOoLbvR.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-RIeASvdJ.js → sankeyDiagram-XADWPNL6-CQDxnTGB.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-Buf7D5PQ.js → sequenceDiagram-FGHM5R23-SZbPsgFb.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-5uYn-_xo.js → stateDiagram-FHFEXIEX-uFY2njzT.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-BJCQ3uMo.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-fcTegT1X.js → timeline-definition-GMOUNBTQ-n_oY30Kg.js} +1 -1
- package/dist/assets/{value-CCp47p1v.js → value-hJ3k-Vla.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-BUY_I73j.js → vennDiagram-DHZGUBPP-B9VAsWw3.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-CfGeUcvr.js → wardley-RL74JXVD-DPynk1O-.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-0A6kvF9i.js → wardleyDiagram-NUSXRM2D-BxHtrGEZ.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-C2L50m3n.js → xychartDiagram-5P7HB3ND-B3Hi8GO_.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/dist/assets/channel-BudrdgJ_.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CNYOE6Pk.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CNYOE6Pk.js +0 -1
- package/dist/assets/clone-CbLIuAlV.js +0 -1
- package/dist/assets/index-B9yC-RFq.css +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CiNPYrtS.js +0 -1
package/dist/api/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM5B,OAAO,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM5B,OAAO,EAuHL,KAAK,aAAa,EAMlB,KAAK,aAAa,EAOnB,MAAM,sBAAsB,CAAC;AAkB9B,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAItE;AAGD,wBAAgB,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAGtF;AAwgFD,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE;IAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAA;CAAO,8EAsjI7I;AAID,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,SAAO,EACX,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
|
package/dist/api/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import { streamSSE } from "hono/streaming";
|
|
|
4
4
|
import { serve } from "@hono/node-server";
|
|
5
5
|
import { gzipSync } from "node:zlib";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, listBookSessions, loadBookSession, appendManualSessionMessages, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, abortAgentSession, runAgentSession, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, guessServiceFromBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, listModelsForService, isApiKeyOptionalForEndpoint, getAllEndpoints, probeModelsFromUpstream, fetchWithProxy, chatCompletion, runWorkerAgent, buildExportArtifact, evaluateBookQuality, ConsolidatorAgent, DetectionConfigSchema, ResearchSearchConfigSchema, GLOBAL_ENV_PATH, COVER_PROVIDER_PRESETS, createPlayDB, PlayStore, buildPlayEntityImagePrompt, buildPlaySceneImagePrompt, generatePlayImage, readPlayImageManifest, readPlayImageSettings, writePlayImageSettings, Scheduler, coverSecretKey, normalizeCoverBaseUrl, resolveCoverProviderPreset, SessionKindSchema, normalizeActionSource as normalizeCoreActionSource, normalizeActionPayload as normalizeCoreActionPayload, normalizePlayMode as normalizeCorePlayMode, normalizeRequestedIntent as normalizeCoreRequestedIntent, normalizeSkillIdList as normalizeCoreSkillIdList, inferLanguage, toWritingLanguage, ingestMaterial, createSkillRegistry, loadAvailableAgentSkills, activatedSkillIds, mergeActivatedSkillGuidance, resolveProductionSkillActivations, parseAgentSkillDocument, getBuiltinPrompt, listBuiltinPromptPacks, listBuiltinPrompts, loadPromptPackPrompt, promptOverridePath, toPosixPath, createGenerateCoverTool, createInteractiveFilmCreationTool, createPlayStartTool, createScriptCreationTool, createShortFictionRunTool, createStoryboardCreationTool, createTranslationCreateTool, createFanficBookTool, createContinuationImportTool, createSpinoffBookTool, createImitationBookTool, createSubAgentTool, createDraftStructureTool, createConnectChoiceTool, createRemoveNodeTool, createLLMTranslationModel, deleteLatestChapter, executeEditTransaction, listChapterVersions, readChapterPlanDocument, readChapterUserBrief, readChapterVersion, saveChapterUserBrief, createTranslationProjectFromFile, loadTranslationChapter, loadTranslationManifest, runTranslationProject, writeTranslationExport, filmLLMDepsFromClient, applyGraphDelta, loadStoryGraph, reviewStoryGraph, exportInk, buildPlayableHtml, analyzeEmotionalArcs, analyzePathDistribution, generateNodeImage, defaultNodeImageDeps, } from "@kienmatu/inkos-core";
|
|
7
|
+
import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, listBookSessions, loadBookSession, appendManualSessionMessages, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, abortAgentSession, runAgentSession, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, guessServiceFromBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, listModelsForService, isApiKeyOptionalForEndpoint, getAllEndpoints, probeModelsFromUpstream, fetchWithProxy, chatCompletion, runWorkerAgent, buildExportArtifact, createProductionRunSnapshot, evaluateBookQuality, ConsolidatorAgent, DetectionConfigSchema, ResearchSearchConfigSchema, GLOBAL_ENV_PATH, COVER_PROVIDER_PRESETS, createPlayDB, PlayStore, buildPlayEntityImagePrompt, buildPlaySceneImagePrompt, generatePlayImage, readPlayImageManifest, readPlayImageSettings, writePlayImageSettings, Scheduler, coverSecretKey, normalizeCoverBaseUrl, resolveCoverProviderPreset, SessionKindSchema, normalizeActionSource as normalizeCoreActionSource, normalizeActionPayload as normalizeCoreActionPayload, normalizePlayMode as normalizeCorePlayMode, normalizeRequestedIntent as normalizeCoreRequestedIntent, normalizeSkillIdList as normalizeCoreSkillIdList, inferLanguage, toWritingLanguage, ingestMaterial, createSkillRegistry, loadAvailableAgentSkills, activatedSkillIds, mergeActivatedSkillGuidance, resolveProductionSkillActivations, parseAgentSkillDocument, getBuiltinPrompt, listBuiltinPromptPacks, listBuiltinPrompts, loadPromptPackPrompt, promptOverridePath, toPosixPath, createGenerateCoverTool, createInteractiveFilmCreationTool, createPlayStartTool, createScriptCreationTool, createShortFictionRunTool, createStoryboardCreationTool, createTranslationCreateTool, createFanficBookTool, createContinuationImportTool, createSpinoffBookTool, createImitationBookTool, createSubAgentTool, createDraftStructureTool, createConnectChoiceTool, createRemoveNodeTool, createLLMTranslationModel, deleteLatestChapter, executeEditTransaction, listChapterVersions, readChapterPlanDocument, readChapterUserBrief, readChapterVersion, saveChapterUserBrief, createTranslationProjectFromFile, loadTranslationChapter, loadTranslationManifest, runTranslationProject, writeTranslationExport, filmLLMDepsFromClient, applyGraphDelta, loadStoryGraph, reviewStoryGraph, exportInk, buildPlayableHtml, analyzeEmotionalArcs, analyzePathDistribution, generateNodeImage, defaultNodeImageDeps, writeProductionRunSnapshot, } from "@kienmatu/inkos-core";
|
|
8
8
|
import { isConfirmedProductionAction } from "../shared/confirmed-production.js";
|
|
9
9
|
import { summarizeToolResult } from "../shared/tool-result.js";
|
|
10
10
|
import { access, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -879,6 +879,7 @@ function toolResultText(result, lang = "vi") {
|
|
|
879
879
|
}
|
|
880
880
|
async function executeConfirmedProductionAction(args) {
|
|
881
881
|
const lang = args.language ?? "vi";
|
|
882
|
+
const uiLang = args.uiLanguage ?? lang;
|
|
882
883
|
const id = args.taskId;
|
|
883
884
|
const actionPayload = args.actionPayload;
|
|
884
885
|
const configuredSkills = await loadAvailableAgentSkills({ projectRoot: args.root });
|
|
@@ -950,7 +951,7 @@ async function executeConfirmedProductionAction(args) {
|
|
|
950
951
|
else if (args.requestedIntent === "generate_cover") {
|
|
951
952
|
const payload = actionPayload?.generateCover;
|
|
952
953
|
const title = requirePayloadText(payload?.title, pick(lang, "确认生成封面缺少标题,请重新生成确认卡。", "The cover generation confirmation is missing a title. Regenerate the confirmation card.", "Xác nhận tạo bìa sách thiếu tiêu đề. Vui lòng tạo lại thẻ xác nhận."));
|
|
953
|
-
tool = createGenerateCoverTool(args.root, { actionPayload });
|
|
954
|
+
tool = createGenerateCoverTool(args.root, { actionPayload, language: toWritingLanguage(lang) });
|
|
954
955
|
params = {
|
|
955
956
|
title,
|
|
956
957
|
...(payload?.intro ? { intro: payload.intro } : {}),
|
|
@@ -1204,10 +1205,10 @@ async function executeConfirmedProductionAction(args) {
|
|
|
1204
1205
|
id,
|
|
1205
1206
|
tool: tool.name,
|
|
1206
1207
|
agent,
|
|
1207
|
-
label: resolveToolLabel(tool.name, agent,
|
|
1208
|
+
label: resolveToolLabel(tool.name, agent, uiLang),
|
|
1208
1209
|
status: "running",
|
|
1209
1210
|
args: params,
|
|
1210
|
-
stages: agent ? pipelineStages(agent,
|
|
1211
|
+
stages: agent ? pipelineStages(agent, uiLang)?.map(label => ({ label, status: "pending" })) : undefined,
|
|
1211
1212
|
startedAt: Date.now(),
|
|
1212
1213
|
};
|
|
1213
1214
|
await args.onTaskChange(exec);
|
|
@@ -1266,6 +1267,124 @@ async function executeConfirmedProductionAction(args) {
|
|
|
1266
1267
|
throw new ConfirmedActionExecutionError(message, exec, error);
|
|
1267
1268
|
}
|
|
1268
1269
|
}
|
|
1270
|
+
function parseProductionObservation(value) {
|
|
1271
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1272
|
+
return null;
|
|
1273
|
+
const record = value;
|
|
1274
|
+
if (typeof record.metric !== "string"
|
|
1275
|
+
|| !["info", "warning", "blocking"].includes(String(record.severity))
|
|
1276
|
+
|| typeof record.repairable !== "boolean"
|
|
1277
|
+
|| (record.evidence !== undefined && typeof record.evidence !== "string")) {
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1280
|
+
return {
|
|
1281
|
+
metric: record.metric,
|
|
1282
|
+
expected: record.expected,
|
|
1283
|
+
actual: record.actual,
|
|
1284
|
+
severity: record.severity,
|
|
1285
|
+
...(typeof record.evidence === "string" ? { evidence: record.evidence } : {}),
|
|
1286
|
+
repairable: record.repairable,
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
function parseStudioShortRun(value, expectedStoryId) {
|
|
1290
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1291
|
+
return null;
|
|
1292
|
+
const record = value;
|
|
1293
|
+
const status = record.status;
|
|
1294
|
+
const artifacts = Array.isArray(record.artifacts) && record.artifacts.every((item) => typeof item === "string")
|
|
1295
|
+
? record.artifacts
|
|
1296
|
+
: null;
|
|
1297
|
+
const observations = Array.isArray(record.observations)
|
|
1298
|
+
? record.observations.map(parseProductionObservation)
|
|
1299
|
+
: null;
|
|
1300
|
+
if (record.version !== 1
|
|
1301
|
+
|| record.kind !== "short-fiction"
|
|
1302
|
+
|| record.id !== expectedStoryId
|
|
1303
|
+
|| (status !== "complete" && status !== "needs-review")
|
|
1304
|
+
|| typeof record.stage !== "string"
|
|
1305
|
+
|| !artifacts
|
|
1306
|
+
|| !observations
|
|
1307
|
+
|| observations.some((observation) => observation === null)
|
|
1308
|
+
|| typeof record.updatedAt !== "string"
|
|
1309
|
+
|| (record.model !== undefined && typeof record.model !== "string")
|
|
1310
|
+
|| (record.skillIds !== undefined && (!Array.isArray(record.skillIds) || !record.skillIds.every((item) => typeof item === "string")))
|
|
1311
|
+
|| (record.resumeCursor !== undefined && typeof record.resumeCursor !== "string")
|
|
1312
|
+
|| (record.error !== undefined && typeof record.error !== "string")) {
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
return {
|
|
1316
|
+
version: 1,
|
|
1317
|
+
kind: "short-fiction",
|
|
1318
|
+
id: expectedStoryId,
|
|
1319
|
+
status,
|
|
1320
|
+
stage: record.stage,
|
|
1321
|
+
artifacts,
|
|
1322
|
+
observations: observations,
|
|
1323
|
+
...(typeof record.model === "string" ? { model: record.model } : {}),
|
|
1324
|
+
...(Array.isArray(record.skillIds) ? { skillIds: record.skillIds } : {}),
|
|
1325
|
+
...(typeof record.resumeCursor === "string" ? { resumeCursor: record.resumeCursor } : {}),
|
|
1326
|
+
...(typeof record.error === "string" ? { error: record.error } : {}),
|
|
1327
|
+
updatedAt: record.updatedAt,
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function parseVisibleShortRunIndex(value, expectedStoryId) {
|
|
1331
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1332
|
+
return null;
|
|
1333
|
+
const record = value;
|
|
1334
|
+
const status = record.status;
|
|
1335
|
+
if (record.version !== 1
|
|
1336
|
+
|| record.kind !== "short-fiction"
|
|
1337
|
+
|| record.id !== expectedStoryId
|
|
1338
|
+
|| (status !== "complete" && status !== "needs-review")
|
|
1339
|
+
|| typeof record.updatedAt !== "string") {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
return { storyId: expectedStoryId, status, updatedAt: record.updatedAt };
|
|
1343
|
+
}
|
|
1344
|
+
function parseShortStoryTitle(value) {
|
|
1345
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1346
|
+
return null;
|
|
1347
|
+
const storyTitle = value.storyTitle;
|
|
1348
|
+
return typeof storyTitle === "string" && storyTitle.trim() ? storyTitle.trim() : null;
|
|
1349
|
+
}
|
|
1350
|
+
async function loadStudioShortList(root) {
|
|
1351
|
+
const shortsDir = join(root, "shorts");
|
|
1352
|
+
let storyIds = [];
|
|
1353
|
+
try {
|
|
1354
|
+
const entries = await readdir(shortsDir, { withFileTypes: true });
|
|
1355
|
+
storyIds = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1356
|
+
}
|
|
1357
|
+
catch (error) {
|
|
1358
|
+
if (error.code !== "ENOENT")
|
|
1359
|
+
throw error;
|
|
1360
|
+
}
|
|
1361
|
+
const shorts = [];
|
|
1362
|
+
for (const storyId of storyIds) {
|
|
1363
|
+
if (!isSafeBookId(storyId))
|
|
1364
|
+
continue;
|
|
1365
|
+
const shortDir = join(shortsDir, storyId);
|
|
1366
|
+
try {
|
|
1367
|
+
const status = JSON.parse(await readFile(join(shortDir, "status.json"), "utf-8"));
|
|
1368
|
+
const run = parseVisibleShortRunIndex(status, storyId);
|
|
1369
|
+
if (!run)
|
|
1370
|
+
continue;
|
|
1371
|
+
const story = JSON.parse(await readFile(join(shortDir, "final", "short-story.json"), "utf-8"));
|
|
1372
|
+
const title = parseShortStoryTitle(story) ?? storyId;
|
|
1373
|
+
await readFile(join(shortDir, "final", "full.md"), "utf-8");
|
|
1374
|
+
shorts.push({
|
|
1375
|
+
storyId,
|
|
1376
|
+
title,
|
|
1377
|
+
status: run.status,
|
|
1378
|
+
finalMarkdownPath: toPosixPath(join("shorts", storyId, "final", "full.md")),
|
|
1379
|
+
updatedAt: run.updatedAt,
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
catch {
|
|
1383
|
+
// A malformed or incomplete directory is not a completed short work.
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return shorts;
|
|
1387
|
+
}
|
|
1269
1388
|
const subscribers = new Set();
|
|
1270
1389
|
const bookCreateStatus = new Map();
|
|
1271
1390
|
// 内存缓存:service -> 模型列表 + 更新时间戳;避免每次 sidebar 挂载时都打真实 LLM /models
|
|
@@ -2010,6 +2129,7 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
2010
2129
|
const state = new StateManager(root);
|
|
2011
2130
|
let cachedConfig = initialConfig;
|
|
2012
2131
|
const activeConfirmedTasks = new Map();
|
|
2132
|
+
const confirmedTaskRevisions = new Map();
|
|
2013
2133
|
// 确认式生产任务的单任务名额(sessionId → taskId)。原来的检查是"await 读快照
|
|
2014
2134
|
// → 之后才 set controller"的 check-then-act:两个并发确认请求都能通过检查,
|
|
2015
2135
|
// 双任务同时启动、快照互相覆盖。这里在任何 await 之前同步占位,占位失败的
|
|
@@ -2033,8 +2153,11 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
2033
2153
|
const persistConfirmedTask = async (sessionId, requestedIntent, exec, sourceRequestId) => {
|
|
2034
2154
|
if (deletedSessionIds.has(sessionId))
|
|
2035
2155
|
return;
|
|
2156
|
+
const revision = (confirmedTaskRevisions.get(exec.id) ?? 0) + 1;
|
|
2157
|
+
confirmedTaskRevisions.set(exec.id, revision);
|
|
2036
2158
|
const snapshot = {
|
|
2037
2159
|
version: 1,
|
|
2160
|
+
revision,
|
|
2038
2161
|
sessionId,
|
|
2039
2162
|
...(sourceRequestId ? { sourceRequestId } : {}),
|
|
2040
2163
|
requestedIntent,
|
|
@@ -2045,7 +2168,10 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
2045
2168
|
...(exec.logs ? { logs: [...exec.logs] } : {}),
|
|
2046
2169
|
},
|
|
2047
2170
|
};
|
|
2171
|
+
// Publish the same durable snapshot that refresh recovery reads, so an
|
|
2172
|
+
// already-open task card sees milestone logs without requiring a reload.
|
|
2048
2173
|
await saveStudioTaskSnapshot(root, snapshot);
|
|
2174
|
+
broadcast("task:snapshot", snapshot);
|
|
2049
2175
|
};
|
|
2050
2176
|
const loadReconciledTaskSnapshot = async (sessionId) => {
|
|
2051
2177
|
const task = await loadStudioTaskSnapshot(root, sessionId);
|
|
@@ -2174,6 +2300,9 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
2174
2300
|
}
|
|
2175
2301
|
: sseSink;
|
|
2176
2302
|
const logger = createLogger({ tag: "studio", sinks: [scopedSseSink, consoleSink] });
|
|
2303
|
+
// Progress lines are read by the person watching Studio, so they follow the
|
|
2304
|
+
// UI language rather than the book's writing language.
|
|
2305
|
+
const logLanguage = await currentProjectLanguage();
|
|
2177
2306
|
return {
|
|
2178
2307
|
client: overrides?.client ?? createLLMClient(currentConfig.llm),
|
|
2179
2308
|
model: overrides?.model ?? currentConfig.llm.model,
|
|
@@ -2186,6 +2315,7 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
2186
2315
|
modelOverrides: currentConfig.modelOverrides,
|
|
2187
2316
|
notifyChannels: currentConfig.notify,
|
|
2188
2317
|
logger,
|
|
2318
|
+
logLanguage,
|
|
2189
2319
|
onContextCompression: (event) => {
|
|
2190
2320
|
broadcast("context:compression", {
|
|
2191
2321
|
...(overrides?.sessionIdForSSE ? { sessionId: overrides.sessionIdForSSE } : {}),
|
|
@@ -3189,13 +3319,29 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
3189
3319
|
id: `custom:${s.name ?? "Custom"}`,
|
|
3190
3320
|
baseUrl: s.baseUrl ?? "",
|
|
3191
3321
|
label: s.name ?? "Custom",
|
|
3322
|
+
configuredModels: s.models ?? [],
|
|
3192
3323
|
}))
|
|
3193
|
-
.filter((s) =>
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3324
|
+
.filter((s) => {
|
|
3325
|
+
if (!s.baseUrl)
|
|
3326
|
+
return false;
|
|
3327
|
+
if (secrets.services[s.id]?.apiKey)
|
|
3328
|
+
return true;
|
|
3329
|
+
// Local/self-hosted endpoints are usable without a key.
|
|
3330
|
+
return isApiKeyOptionalForEndpoint({
|
|
3331
|
+
provider: resolveServiceProviderFamily("custom") ?? "openai",
|
|
3332
|
+
baseUrl: s.baseUrl,
|
|
3333
|
+
});
|
|
3334
|
+
});
|
|
3335
|
+
const groups = await Promise.all(customs.map(async (s) => {
|
|
3336
|
+
const probed = filterTextChatModels(await probeModelsFromUpstream(s.baseUrl, secrets.services[s.id]?.apiKey ?? "", 10_000));
|
|
3337
|
+
// The probe swallows every failure (timeout, offline upstream, bad
|
|
3338
|
+
// gateway) and returns []. Without the configured ids as a fallback a
|
|
3339
|
+
// single slow reload empties the model picker, so the UI drops back to
|
|
3340
|
+
// "set up models" even though the service is configured.
|
|
3341
|
+
const models = mergeServiceModelIds(probed.map((model) => model.id), s.configuredModels)
|
|
3342
|
+
.map((id) => probed.find((model) => model.id.toLowerCase() === id.toLowerCase()) ?? { id, name: id });
|
|
3343
|
+
return { service: s.id, label: s.label, models };
|
|
3344
|
+
}));
|
|
3199
3345
|
return c.json({ groups });
|
|
3200
3346
|
});
|
|
3201
3347
|
app.get("/api/v1/services/:service/models", async (c) => {
|
|
@@ -4025,6 +4171,7 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
4025
4171
|
requestedSkills,
|
|
4026
4172
|
disabledSkills,
|
|
4027
4173
|
language: surfaceLanguage,
|
|
4174
|
+
uiLanguage: language,
|
|
4028
4175
|
taskId,
|
|
4029
4176
|
sourceRequestId,
|
|
4030
4177
|
signal: taskController.signal,
|
|
@@ -4108,6 +4255,7 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
4108
4255
|
}
|
|
4109
4256
|
finally {
|
|
4110
4257
|
activeConfirmedTasks.delete(taskId);
|
|
4258
|
+
confirmedTaskRevisions.delete(taskId);
|
|
4111
4259
|
reservedProductionSessions.delete(reservedSessionId);
|
|
4112
4260
|
}
|
|
4113
4261
|
}
|
|
@@ -5313,6 +5461,40 @@ export function createStudioServer(initialConfig, root, overrides = {}) {
|
|
|
5313
5461
|
films.sort((a, b) => a.title.localeCompare(b.title, "zh"));
|
|
5314
5462
|
return c.json({ films });
|
|
5315
5463
|
});
|
|
5464
|
+
app.get("/api/v1/shorts", async (c) => {
|
|
5465
|
+
const shorts = await loadStudioShortList(root);
|
|
5466
|
+
return c.json({ shorts });
|
|
5467
|
+
});
|
|
5468
|
+
app.post("/api/v1/shorts/:id/complete", async (c) => {
|
|
5469
|
+
const storyId = c.req.param("id");
|
|
5470
|
+
if (!isSafeBookId(storyId)) {
|
|
5471
|
+
return c.json({ error: "Invalid short id" }, 400);
|
|
5472
|
+
}
|
|
5473
|
+
const runPath = join("shorts", storyId, "status.json");
|
|
5474
|
+
let raw;
|
|
5475
|
+
try {
|
|
5476
|
+
raw = JSON.parse(await readFile(join(root, runPath), "utf-8"));
|
|
5477
|
+
}
|
|
5478
|
+
catch {
|
|
5479
|
+
return c.json({ error: "Short not found" }, 404);
|
|
5480
|
+
}
|
|
5481
|
+
const run = parseStudioShortRun(raw, storyId);
|
|
5482
|
+
if (!run) {
|
|
5483
|
+
return c.json({ error: "Short status is invalid" }, 409);
|
|
5484
|
+
}
|
|
5485
|
+
if (run.status === "needs-review") {
|
|
5486
|
+
await writeProductionRunSnapshot({
|
|
5487
|
+
rootDir: root,
|
|
5488
|
+
runPath,
|
|
5489
|
+
run: createProductionRunSnapshot({
|
|
5490
|
+
...run,
|
|
5491
|
+
status: "complete",
|
|
5492
|
+
updatedAt: new Date().toISOString(),
|
|
5493
|
+
}),
|
|
5494
|
+
});
|
|
5495
|
+
}
|
|
5496
|
+
return c.json({ ok: true, storyId, status: "complete" });
|
|
5497
|
+
});
|
|
5316
5498
|
app.get("/api/v1/translations", async (c) => {
|
|
5317
5499
|
const translationsDir = join(root, "translations");
|
|
5318
5500
|
let entries = [];
|