@apprun/shiqu 0.6.0 → 0.6.5
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/README.md +7 -1
- package/dist/client/assets/{es-BsDtWQCI.js → es-B0vSJwvP.js} +1 -1
- package/dist/client/assets/index-B0URXv2Q.css +1 -0
- package/dist/client/assets/index-y4BxCnhg.js +17 -0
- package/dist/client/assets/reader-pdf-AMLMoode.js +4 -0
- package/dist/client/assets/reader-png-UKUNKgFB.js +1 -0
- package/dist/client/assets/reader-zip-CYrTAeJC.js +16 -0
- package/dist/client/index.html +2 -2
- package/dist/server/cli.js +163 -93
- package/drizzle/0011_publication_defaults.sql +5 -0
- package/package.json +1 -1
- package/dist/client/assets/index-BXhJYqND.js +0 -15
- package/dist/client/assets/index-CTBPjFtm.css +0 -1
- package/dist/client/assets/reader-pdf-DrQyHCbD.js +0 -4
- package/dist/client/assets/reader-png-Btyp3Ok6.js +0 -1
- package/dist/client/assets/reader-zip-BSWjbJfH.js +0 -5
package/dist/server/cli.js
CHANGED
|
@@ -130,12 +130,13 @@ __export(schema_exports, {
|
|
|
130
130
|
productionDemand: () => productionDemand,
|
|
131
131
|
productionJobs: () => productionJobs,
|
|
132
132
|
progress: () => progress,
|
|
133
|
+
readerPublicationDefaults: () => readerPublicationDefaults,
|
|
133
134
|
readerSessions: () => readerSessions,
|
|
134
135
|
stories: () => stories
|
|
135
136
|
});
|
|
136
137
|
import { sql } from "drizzle-orm";
|
|
137
138
|
import { sqliteTable, text, integer, real, uniqueIndex, index, primaryKey } from "drizzle-orm/sqlite-core";
|
|
138
|
-
var books, stories, jobs, events, progress, libraryDeletions, readerSessions, productionJobs, productionDemand, bookImageReferences;
|
|
139
|
+
var books, stories, jobs, events, progress, libraryDeletions, readerSessions, productionJobs, productionDemand, bookImageReferences, readerPublicationDefaults;
|
|
139
140
|
var init_schema = __esm({
|
|
140
141
|
"db/schema.ts"() {
|
|
141
142
|
"use strict";
|
|
@@ -176,6 +177,7 @@ var init_schema = __esm({
|
|
|
176
177
|
runtimeId: text("runtime_id").notNull()
|
|
177
178
|
}, (t) => [uniqueIndex("production_viewer").on(t.sessionId, t.ownerId, t.viewerId), index("production_live_demand").on(t.runtimeId, t.expiresAt)]);
|
|
178
179
|
bookImageReferences = sqliteTable("book_image_references", { ownerId: text("owner_id").notNull(), bookKey: text("book_key").notNull(), storyId: text("story_id").notNull(), page: integer("page").notNull(), token: text("token").notNull(), assetKey: text("asset_key").notNull(), updatedAt: text("updated_at").notNull() }, (t) => [primaryKey({ columns: [t.ownerId, t.bookKey] })]);
|
|
180
|
+
readerPublicationDefaults = sqliteTable("reader_publication_defaults", { ownerId: text("owner_id").primaryKey(), publication: text("publication").notNull() });
|
|
179
181
|
}
|
|
180
182
|
});
|
|
181
183
|
|
|
@@ -185,7 +187,7 @@ var acceptanceCriteria, acceptancePromptSchema, acceptanceInput, acceptanceDirec
|
|
|
185
187
|
var init_reader_acceptance = __esm({
|
|
186
188
|
"lib/reader-acceptance.ts"() {
|
|
187
189
|
"use strict";
|
|
188
|
-
acceptanceCriteria = (text3 = "") => text3.split(/\r?\n/).map((
|
|
190
|
+
acceptanceCriteria = (text3 = "") => text3.split(/\r?\n/).map((line2) => line2.trim()).filter(Boolean);
|
|
189
191
|
acceptancePromptSchema = z4.string().max(1200).refine((text3) => acceptanceCriteria(text3).length <= 20, "\u9A8C\u6536\u8981\u6C42\u6700\u591A20\u6761\uFF0C\u6BCF\u884C\u4E00\u6761\u3002");
|
|
190
192
|
acceptanceInput = (selection) => selection.acceptancePrompt?.trim() ? { acceptancePrompt: selection.acceptancePrompt, acceptanceCriteria: acceptanceCriteria(selection.acceptancePrompt) } : {};
|
|
191
193
|
acceptanceDirection = "\u672C\u7BC7\u9A8C\u6536\u8981\u6C42\u7531 acceptanceCriteria \u6309\u884C\u7F16\u53F7\u3002\u5236\u4F5C\u4E0E\u4FEE\u8BA2\u65F6\u9010\u6761\u843D\u5B9E\uFF0C\u4ECD\u987B\u670D\u4ECE\u5DF2\u6838\u539F\u6587\u4E8B\u5B9E\u3001\u9009\u5B9A\u6545\u4E8B\u3001\u753B\u98CE\u3001\u603B\u5E45\u6570\u7B56\u7565\u548C\u8F93\u51FA\u683C\u5F0F\u3002\u5236\u4F5C\u8981\u6C42\u4E0E\u9A8C\u6536\u8981\u6C42\u51B2\u7A81\u3001\u8981\u6C42\u65E0\u539F\u6587\u4F9D\u636E\u7684\u4E8B\u4EF6\u6216\u8D85\u51FA\u6587\u5B57\u5206\u955C\u53EF\u5224\u65AD\u8303\u56F4\u65F6\uFF0C\u5BA1\u6838\u5FC5\u987B\u660E\u786E\u6307\u51FA\uFF0C\u4E0D\u64C5\u81EA\u53D6\u820D\u6216\u4F2A\u9020\u4E8B\u5B9E\u3002";
|
|
@@ -705,6 +707,7 @@ function view(row) {
|
|
|
705
707
|
const storyboardBatch = doc.storyboard && !p && !doc.draft ? { first: doc.storyboard.pages.length + 1, last: Math.min(doc.storyboard.pages.length + readerStoryboardBatchSize, doc.storyboard.outline.beats.length) } : void 0;
|
|
706
708
|
const stage = intakePhase === "discovering" ? "\u6B63\u5728\u63D0\u53D6\u5019\u9009\u6545\u4E8B" : intakePhase === "choosing" ? "\u8BF7\u9009\u62E9\u6545\u4E8B" : intakePhase === "confirming" ? "\u8BF7\u786E\u8BA4\u5236\u4F5C\u5185\u5BB9" : waiting ? "\u8BF7\u9009\u62E9\u753B\u5E45\u6570" : step?.kind === "recommend" ? "\u6B63\u5728\u9605\u8BFB\u539F\u6587\uFF0C\u63A8\u8350\u753B\u5E45\u6570" : step?.kind === "plan" ? storyboardBatch ? `\u6B63\u5728\u7F16\u6392\u7B2C ${storyboardBatch.first}\u2013${storyboardBatch.last} \u5E45\u5206\u955C` : selection.length === "auto" || selection.length > readerStoryboardBatchSize ? "\u6B63\u5728\u7F16\u5236\u5206\u955C\u76EE\u5F55" : "\u6B63\u5728\u7F16\u6392\u6545\u4E8B" : step?.kind === "reflection" ? "\u6B63\u5728\u62DF\u5B9A\u6807\u9898\u4E0E\u7ED3\u8BED" : step?.kind === "source" ? "\u6B63\u5728\u4FDD\u5B58\u6545\u4E8B\u539F\u6587" : step?.kind === "review" ? doc.visualReview?.status === "blocked" ? "\u5206\u955C\u5BA1\u7247\u5F85\u4FEE\u8BA2" : doc.visualReview?.repairs ? "\u6B63\u5728\u590D\u6838\u5168\u7BC7\u5206\u955C" : "\u6B63\u5728\u5BA1\u6838\u5168\u7BC7\u5206\u955C" : step?.kind === "repair" ? step.key.endsWith(":0") ? "\u6B63\u5728\u4FEE\u8BA2\u6545\u4E8B\u7ED3\u8BED" : "\u6B63\u5728\u4FEE\u8BA2\u5206\u955C\u753B\u9762" : step?.kind === "audio" ? `\u6B63\u5728\u914D\u4E0A\u7B2C ${step.page + 1} \u5E45\u7684\u58F0\u97F3` : step ? `\u6B63\u5728\u7ED8\u5236\u7B2C ${step.page + 1} \u5E45` : "\u6545\u4E8B\u5DF2\u5B8C\u6574\u5C55\u5F00";
|
|
707
709
|
const story = p ? { id: row.id, title: selection.displayTitle || p.title, summary: p.summary, bookId: selection.bookId, theme: selection.theme, status: "draft", pages: p.pages.map((v, i) => ({ ...v, image: doc.images[i] || "", audio: doc.audio[i] || "" })), reflection: p.reflection, sourceText: doc.original?.chapterContext?.text || doc.original?.text || "", sourceUrl: doc.original?.url || "", original: doc.original, sourceReference: doc.original?.chapterContext?.title || doc.original?.title || p.sourceReference, adaptationNote: (doc.original ? "AI \u5373\u65F6\u6539\u7F16\uFF0C\u975E\u539F\u6587\u5F15\u8FF0\uFF1B\u539F\u6587\u8282\u9009\u4E0E\u6539\u7F16\u5185\u5BB9\u5206\u5F00\u4FDD\u5B58\u3002" : "AI \u5373\u65F6\u6539\u7F16\uFF0C\u975E\u539F\u6587\u5F15\u8FF0\uFF1B\u539F\u5178\u7EBF\u7D22\u7531\u6A21\u578B\u63D0\u4F9B\uFF0C\u5C1A\u672A\u9010\u6761\u6838\u9A8C\u3002") + p.adaptationNote, checks: { source: !!doc.original, plot: false, visual: false }, revision: doc.revision?.number || 1, createdAt: row.created_at, updatedAt: row.updated_at, generated: true, voiceRequested: selection.voice, artStyleLabel: getArtStyle(selection.artStyle).label, productionPrompt: selection.productionPrompt } : null;
|
|
710
|
+
if (story && doc.publication) story.publication = doc.publication;
|
|
708
711
|
if (story) story.generationModels = (doc.usage || []).map(({ step: step2, model: model2 }) => ({ step: step2, model: model2 }));
|
|
709
712
|
const frameCountConfirmed = !!p || !!doc.draft || !!doc.storyboard || !!doc.frameCountConfirmed || !selection.narrativeDetail && typeof selection.length === "number";
|
|
710
713
|
const generationProgress = { sourceReady: !!doc.original, frameCountConfirmed, frameCount: p?.pages.length ?? doc.draft?.pages.length ?? doc.storyboard?.outline.beats.length ?? (frameCountConfirmed && typeof selection.length === "number" ? selection.length : null), ...doc.storyboard || doc.draft ? { storyboardFrames: doc.draft?.pages.length ?? doc.storyboard.pages.length, storyboardBatch } : {}, ...doc.visualReview ? { reviewStatus: doc.visualReview.status, reviewRound: doc.visualReview.round, repairCompleted: doc.visualReview.repaired.length, repairTotal: doc.visualReview.issues.length, reviewAudits: doc.visualReview.reports.length, repairBatches: doc.visualReview.history?.length, repairPasses: doc.visualReview.repairs, reviewStopReason: doc.visualReview.stopReason, reviewOverridden: !!doc.visualReview.overridden } : {}, phase: intakePhase === "choosing" ? "story-choice" : intakePhase === "confirming" ? "production-confirmation" : waiting ? "frame-count" : step?.kind || "completed", startedAt: null, attempt: row.step_attempts || 0 };
|
|
@@ -755,7 +758,7 @@ var init_reader_model = __esm({
|
|
|
755
758
|
});
|
|
756
759
|
|
|
757
760
|
// lib/reader-repository.ts
|
|
758
|
-
var visible, familyRoot, familyNumber, supersededSql, latestVersionMessage, persistenceError, workerDocument, ReaderRepository;
|
|
761
|
+
var visible, familyRoot, familyNumber, supersededSql, latestVersionMessage, persistenceError, checkpoint, workerDocument, ReaderRepository;
|
|
759
762
|
var init_reader_repository = __esm({
|
|
760
763
|
"lib/reader-repository.ts"() {
|
|
761
764
|
"use strict";
|
|
@@ -766,7 +769,8 @@ var init_reader_repository = __esm({
|
|
|
766
769
|
supersededSql = (alias = "reader_sessions") => `EXISTS(SELECT 1 FROM reader_sessions newer WHERE newer.user_id=${alias}.user_id AND ${familyRoot("newer")}=${familyRoot(alias)} AND ${familyNumber("newer")}>${familyNumber(alias)})`;
|
|
767
770
|
latestVersionMessage = "\u8FD9\u662F\u65E7\u7248\u672C\uFF0C\u8BF7\u6253\u5F00\u4E66\u67B6\u4E2D\u7684\u6700\u65B0\u7248\u672C\u7EE7\u7EED\u751F\u6210\u3002";
|
|
768
771
|
persistenceError = (error) => error?.code === "ERR_SQLITE_ERROR";
|
|
769
|
-
|
|
772
|
+
checkpoint = "json_patch(?,CASE WHEN json_type(document,'$.publication')='object' THEN json_object('publication',json_extract(document,'$.publication')) ELSE '{}' END)";
|
|
773
|
+
workerDocument = `CASE WHEN json_type(document,'$.intake')='object' THEN json_set(${checkpoint},'$.intake',json(json_set(json_extract(document,'$.intake'),'$.phase',json_extract(?,'$.intake.phase')))) ELSE ${checkpoint} END`;
|
|
770
774
|
ReaderRepository = class {
|
|
771
775
|
db;
|
|
772
776
|
constructor(db) {
|
|
@@ -787,6 +791,25 @@ var init_reader_repository = __esm({
|
|
|
787
791
|
if (!result.meta.changes) throw new ReaderError(409, "\u5206\u955C\u4FDD\u5B58\u540E\u624D\u80FD\u4FEE\u6539\u6545\u4E8B\u540D\u3002");
|
|
788
792
|
return this.get(id, uid);
|
|
789
793
|
}
|
|
794
|
+
// Compare only publication metadata; independent image/progress saves must not conflict.
|
|
795
|
+
async savePublication(id, uid, publication, expected) {
|
|
796
|
+
await this.get(id, uid);
|
|
797
|
+
const result = await this.db.prepare(`UPDATE reader_sessions SET document=json_set(document,'$.publication',json(?)),updated_at=?
|
|
798
|
+
WHERE id=? AND user_id=? AND json_type(document,'$.plan')='object' AND ${visible} AND NOT ${supersededSql()}
|
|
799
|
+
AND json_extract(document,'$.publication') IS json_extract(?,'$')`).bind(JSON.stringify(publication), (/* @__PURE__ */ new Date()).toISOString(), id, uid, JSON.stringify(expected)).run();
|
|
800
|
+
if (!result.meta.changes) throw new ReaderError(409, "\u7F72\u540D\u8BF4\u660E\u6216\u6545\u4E8B\u7248\u672C\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u540E\u518D\u4FDD\u5B58\u3002");
|
|
801
|
+
return this.get(id, uid);
|
|
802
|
+
}
|
|
803
|
+
async publicationDefaults(uid) {
|
|
804
|
+
const row = await this.db.prepare("SELECT publication FROM reader_publication_defaults WHERE owner_id=?").bind(uid).first();
|
|
805
|
+
return row ? JSON.parse(row.publication) : null;
|
|
806
|
+
}
|
|
807
|
+
async savePublicationDefaults(uid, publication, expected) {
|
|
808
|
+
const value = JSON.stringify(publication);
|
|
809
|
+
const result = expected === null ? await this.db.prepare("INSERT INTO reader_publication_defaults(owner_id,publication) VALUES(?,?) ON CONFLICT(owner_id) DO NOTHING").bind(uid, value).run() : await this.db.prepare("UPDATE reader_publication_defaults SET publication=? WHERE owner_id=? AND json_extract(publication,'$') IS json_extract(?,'$')").bind(value, uid, JSON.stringify(expected)).run();
|
|
810
|
+
if (!result.meta.changes) throw new ReaderError(409, "\u9ED8\u8BA4\u8BF4\u660E\u5DF2\u7531\u53E6\u4E00\u6B21\u64CD\u4F5C\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u540E\u518D\u4FDD\u5B58\u3002");
|
|
811
|
+
return publication;
|
|
812
|
+
}
|
|
790
813
|
// Count every newer submission, including deleted ones, so deletion never promotes history.
|
|
791
814
|
async list(uid) {
|
|
792
815
|
return (await this.db.prepare(`SELECT * FROM reader_sessions WHERE user_id = ? AND ${visible}
|
|
@@ -853,9 +876,9 @@ var init_reader_repository = __esm({
|
|
|
853
876
|
if (!result.meta.changes) throw new ReaderError(409, "\u6545\u4E8B\u6B63\u5728\u66F4\u65B0\uFF0C\u8BF7\u7A0D\u540E\u518D\u4FDD\u5B58\u539F\u6587\u3002");
|
|
854
877
|
return this.get(row.id, row.user_id);
|
|
855
878
|
}
|
|
856
|
-
async create(id, uid, selection) {
|
|
857
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
858
|
-
await this.db.prepare(`INSERT INTO reader_sessions(id,user_id,selection,document,generation_mode,state,step_attempts,lock_until,page,saved,created_at,updated_at) VALUES(
|
|
879
|
+
async create(id, uid, selection, document = emptyDocument()) {
|
|
880
|
+
const now = (/* @__PURE__ */ new Date()).toISOString(), saved = JSON.stringify(document);
|
|
881
|
+
await this.db.prepare(`INSERT INTO reader_sessions(id,user_id,selection,document,generation_mode,state,step_attempts,lock_until,page,saved,created_at,updated_at) VALUES(?,?,?,COALESCE((SELECT json_set(?,'$.publication',json(publication)) FROM reader_publication_defaults WHERE owner_id=?),?),'background','idle',0,0,0,0,?,?) ON CONFLICT(id) DO NOTHING`).bind(id, uid, JSON.stringify(selection), saved, uid, saved, now, now).run();
|
|
859
882
|
return this.get(id, uid);
|
|
860
883
|
}
|
|
861
884
|
async confirmFrameCount(id, uid, count) {
|
|
@@ -1376,8 +1399,8 @@ function sectionNumber(value) {
|
|
|
1376
1399
|
return String(value);
|
|
1377
1400
|
}
|
|
1378
1401
|
function ruleLines(body2, bullets) {
|
|
1379
|
-
return body2.trim().split(/\n/).map((
|
|
1380
|
-
const text3 =
|
|
1402
|
+
return body2.trim().split(/\n/).map((line2) => {
|
|
1403
|
+
const text3 = line2.trim();
|
|
1381
1404
|
if (!text3) return "";
|
|
1382
1405
|
const sentences = text3.split(/(?<=[。!?])(?=[^”’」』】)"])/u).filter(Boolean);
|
|
1383
1406
|
if (sentences.length < 2) return text3;
|
|
@@ -2867,11 +2890,11 @@ function createCodexRunner(variables, options = {}) {
|
|
|
2867
2890
|
child.stdin.on("error", () => fail(new CodexError("PROCESS_EXIT")));
|
|
2868
2891
|
let text3 = "", image = null, threadId = "", actualModel = "", actualEffort, usage = null;
|
|
2869
2892
|
lines = createInterface3({ input: child.stdout });
|
|
2870
|
-
lines.on("line", (
|
|
2893
|
+
lines.on("line", (line2) => {
|
|
2871
2894
|
if (closed) return;
|
|
2872
2895
|
let message;
|
|
2873
2896
|
try {
|
|
2874
|
-
message = JSON.parse(
|
|
2897
|
+
message = JSON.parse(line2);
|
|
2875
2898
|
} catch {
|
|
2876
2899
|
fail(new CodexError("INVALID_PROTOCOL"));
|
|
2877
2900
|
return;
|
|
@@ -3161,6 +3184,34 @@ var init_generation = __esm({
|
|
|
3161
3184
|
}
|
|
3162
3185
|
});
|
|
3163
3186
|
|
|
3187
|
+
// lib/story-publication.ts
|
|
3188
|
+
import { z as z18 } from "zod";
|
|
3189
|
+
var line, watermarkFontSize, publicationSchema;
|
|
3190
|
+
var init_story_publication = __esm({
|
|
3191
|
+
"lib/story-publication.ts"() {
|
|
3192
|
+
"use strict";
|
|
3193
|
+
line = (max) => z18.string().trim().max(max).refine((value) => !/[\r\n\t]/.test(value), "\u8BF7\u5199\u5728\u4E00\u884C\u5185\u3002");
|
|
3194
|
+
watermarkFontSize = { min: 12, max: 48, default: 24 };
|
|
3195
|
+
publicationSchema = z18.object({
|
|
3196
|
+
author: line(80),
|
|
3197
|
+
workUrl: line(1e3).refine((value) => {
|
|
3198
|
+
if (!value) return true;
|
|
3199
|
+
try {
|
|
3200
|
+
const url = new URL(value);
|
|
3201
|
+
return ["https:", "http:"].includes(url.protocol) && !url.username && !url.password;
|
|
3202
|
+
} catch {
|
|
3203
|
+
return false;
|
|
3204
|
+
}
|
|
3205
|
+
}, "\u4F5C\u54C1\u94FE\u63A5\u8BF7\u4F7F\u7528\u4E0D\u542B\u8D26\u53F7\u5BC6\u7801\u7684 http/https \u5730\u5740\u3002"),
|
|
3206
|
+
contact: line(200),
|
|
3207
|
+
sourceNote: z18.string().trim().max(1200),
|
|
3208
|
+
reuse: z18.string().trim().max(1200),
|
|
3209
|
+
// Optional for historical snapshots: parsing an old CAS expectation must not add new fields.
|
|
3210
|
+
watermarkFontSize: z18.number().int("\u6C34\u5370\u5B57\u53F7\u8BF7\u4F7F\u7528\u6574\u6570\u3002").min(watermarkFontSize.min, "\u6C34\u5370\u5B57\u53F7\u4E0D\u80FD\u5C0F\u4E8E 12 px\u3002").max(watermarkFontSize.max, "\u6C34\u5370\u5B57\u53F7\u4E0D\u80FD\u5927\u4E8E 48 px\u3002").optional()
|
|
3211
|
+
}).strict();
|
|
3212
|
+
}
|
|
3213
|
+
});
|
|
3214
|
+
|
|
3164
3215
|
// server/book-image-reference.ts
|
|
3165
3216
|
import { createHash as createHash2, randomUUID as randomUUID4 } from "node:crypto";
|
|
3166
3217
|
function bookKey(id, custom) {
|
|
@@ -3271,7 +3322,7 @@ var init_book_image_reference = __esm({
|
|
|
3271
3322
|
});
|
|
3272
3323
|
|
|
3273
3324
|
// lib/reader-revision.ts
|
|
3274
|
-
import { z as
|
|
3325
|
+
import { z as z19 } from "zod";
|
|
3275
3326
|
function revisionImpact(base2, changes) {
|
|
3276
3327
|
const selection = { ...base2.selection, productionPrompt: changes.productionPrompt ?? base2.selection.productionPrompt, artStyle: changes.artStyle ?? base2.selection.artStyle };
|
|
3277
3328
|
const requirements = selection.productionPrompt !== base2.selection.productionPrompt;
|
|
@@ -3313,7 +3364,7 @@ var init_reader_revision = __esm({
|
|
|
3313
3364
|
init_reader_limits();
|
|
3314
3365
|
init_reader_frame_recommendation();
|
|
3315
3366
|
init_reader_revision_images();
|
|
3316
|
-
revisionChangesSchema =
|
|
3367
|
+
revisionChangesSchema = z19.object({ productionPrompt: z19.string().max(1200).optional(), acceptancePrompt: acceptancePromptSchema.optional(), artStyle: z19.enum(artStyleIds).optional(), length: z19.union([z19.number().int().min(readerMinFrames).max(readerMaxFrames), z19.literal("auto")]).optional(), narrativeDetail: narrativeDetailSchema.optional(), plan: planSchema.optional() }).strict();
|
|
3317
3368
|
revisionFrameCount = (base2) => base2.plan?.pages.length ?? (typeof base2.selection.length === "number" ? base2.selection.length : base2.frameRecommendation?.count ?? 8);
|
|
3318
3369
|
preserveRevisionScript = (base2, changes) => !base2.plan && base2.savedFrameCount === revisionFrameCount(base2) && (changes.productionPrompt ?? base2.selection.productionPrompt) === base2.selection.productionPrompt && (changes.narrativeDetail ?? base2.selection.narrativeDetail ?? "balanced") === (base2.selection.narrativeDetail || "balanced") && (changes.length === void 0 || changes.length === base2.selection.length || changes.length === revisionFrameCount(base2));
|
|
3319
3370
|
}
|
|
@@ -3672,7 +3723,7 @@ async function createIntake(repo, id, uid, input, books2) {
|
|
|
3672
3723
|
const choice = input.source ? await directBookChoice(books2, input) : null;
|
|
3673
3724
|
const selection = selectionSchema.parse({ ...input, storyTitle: choice?.entry.title, source: choice?.entry.source, discoverySources: scope?.discoverySources, length: "auto", narrativeDetail: input.narrativeDetail || "balanced" });
|
|
3674
3725
|
if (selection.bookId === "other" && !selection.customBook?.trim()) throw new ReaderError(400, "\u8BF7\u5148\u586B\u5199\u4E66\u540D\u6216 URL\u3002");
|
|
3675
|
-
const creationHash = hash(selection),
|
|
3726
|
+
const creationHash = hash(selection), doc = { ...emptyDocument(), intake: { phase: "discovering", generation: 0, creationHash, batches: [], briefRequired: true, search: { round: 1, state: "searching", cycleId: id } } };
|
|
3676
3727
|
if (choice) {
|
|
3677
3728
|
doc.original = choice.original;
|
|
3678
3729
|
doc.intake.phase = "preparing";
|
|
@@ -3680,8 +3731,7 @@ async function createIntake(repo, id, uid, input, books2) {
|
|
|
3680
3731
|
doc.intake.selected = { batch: 0, index: 0 };
|
|
3681
3732
|
doc.intake.batches = [{ stories: [{ title: choice.entry.title, description: choice.entry.excerpt, source: choice.entry.source, sourceLabel: choice.original.title.slice(0, 150) }], note: "" }];
|
|
3682
3733
|
}
|
|
3683
|
-
|
|
3684
|
-
const row = await repo.get(id, uid);
|
|
3734
|
+
const row = await repo.create(id, uid, selection, doc);
|
|
3685
3735
|
if (JSON.parse(row.document).intake?.creationHash !== creationHash) throw new ReaderError(409, "\u6B64\u521B\u5EFA\u7F16\u53F7\u5DF2\u7528\u4E8E\u5176\u4ED6\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u521B\u5EFA\u3002");
|
|
3686
3736
|
return row;
|
|
3687
3737
|
}
|
|
@@ -3836,7 +3886,7 @@ async function forkRevision(repo, media2, ownerId, parentId, requestId, token2,
|
|
|
3836
3886
|
}
|
|
3837
3887
|
}
|
|
3838
3888
|
const base2 = { ...original, images };
|
|
3839
|
-
const document = { plan, original: doc.original, frameRecommendation: doc.frameRecommendation, frameCountConfirmed: true, visualReview: newReview(), images: [], audio: [], usage: (doc.usage || []).map((item) => ({ ...item, originId: item.originId || parentId })), revision: { parentId, rootId, number: epoch + 1, baseToken: token2, requestHash, base: base2, ...impact.replan ? {} : { previewedRedraw: impact.redraw }, ...lostPages.length ? { lostPages } : {} } };
|
|
3889
|
+
const document = { publication: doc.publication, plan, original: doc.original, frameRecommendation: doc.frameRecommendation, frameCountConfirmed: true, visualReview: newReview(), images: [], audio: [], usage: (doc.usage || []).map((item) => ({ ...item, originId: item.originId || parentId })), revision: { parentId, rootId, number: epoch + 1, baseToken: token2, requestHash, base: base2, ...impact.replan ? {} : { previewedRedraw: impact.redraw }, ...lostPages.length ? { lostPages } : {} } };
|
|
3840
3890
|
if (plan) document.images = reusableRevisionImages(base2, selection, plan);
|
|
3841
3891
|
if (impact.preserveScript) {
|
|
3842
3892
|
document.storyboard = doc.storyboard;
|
|
@@ -3845,11 +3895,11 @@ async function forkRevision(repo, media2, ownerId, parentId, requestId, token2,
|
|
|
3845
3895
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3846
3896
|
const inserted = await repo.db.prepare(`INSERT INTO reader_sessions(id,user_id,selection,document,generation_mode,generation_control,state,step_attempts,lock_until,page,saved,last_read_at,created_at,updated_at)
|
|
3847
3897
|
SELECT ?,?,?,?,'background',?,'idle',0,0,MIN(p.page,?),p.saved,p.last_read_at,?,? FROM reader_sessions p
|
|
3848
|
-
WHERE p.id=? AND p.user_id=? AND json_extract(p.document,'$.plan') IS json_extract(?,'$.plan') AND p.selection=?
|
|
3898
|
+
WHERE p.id=? AND p.user_id=? AND json_extract(p.document,'$.plan') IS json_extract(?,'$.plan') AND p.selection=? AND json_extract(p.document,'$.publication') IS json_extract(?,'$.publication')
|
|
3849
3899
|
AND (json_extract(p.document,'$.plan') IS NOT NULL OR p.generation_control IN ('paused','stopped') OR ${supersededSql("p")})
|
|
3850
3900
|
AND NOT EXISTS(SELECT 1 FROM library_deletions WHERE story_id=p.id AND user_id=p.user_id)
|
|
3851
3901
|
AND (${familyEpoch.replace(" AS epoch", "")})=?
|
|
3852
|
-
ON CONFLICT(id) DO NOTHING`).bind(requestId, ownerId, JSON.stringify(selection), JSON.stringify(document), paused ? "paused" : "active", impact.total - 1, now, now, parentId, ownerId, row.document, row.selection, ownerId, rootId, rootId, epoch).run();
|
|
3902
|
+
ON CONFLICT(id) DO NOTHING`).bind(requestId, ownerId, JSON.stringify(selection), JSON.stringify(document), paused ? "paused" : "active", impact.total - 1, now, now, parentId, ownerId, row.document, row.selection, row.document, ownerId, rootId, rootId, epoch).run();
|
|
3853
3903
|
if (!inserted.meta.changes) {
|
|
3854
3904
|
const raced = await replay();
|
|
3855
3905
|
if (raced) return raced;
|
|
@@ -3874,7 +3924,7 @@ var init_reader_revisions = __esm({
|
|
|
3874
3924
|
});
|
|
3875
3925
|
|
|
3876
3926
|
// lib/reader-api.ts
|
|
3877
|
-
import { z as
|
|
3927
|
+
import { z as z20 } from "zod";
|
|
3878
3928
|
function shelfView(session) {
|
|
3879
3929
|
if (!session.story) return session;
|
|
3880
3930
|
const story = { ...session.story };
|
|
@@ -3908,31 +3958,39 @@ function createReaderApi(services) {
|
|
|
3908
3958
|
}
|
|
3909
3959
|
if (!services.database) throw new ReaderError(503, "\u4E66\u67B6\u6682\u65F6\u4E0D\u53EF\u7528");
|
|
3910
3960
|
const repo = new ReaderRepository(services.database);
|
|
3961
|
+
if (path15.join("/") === "publication-defaults") {
|
|
3962
|
+
if (req.method === "GET") return json({ publication: await repo.publicationDefaults(uid) });
|
|
3963
|
+
if (req.method === "POST") {
|
|
3964
|
+
const parsed = z20.object({ publication: publicationSchema, expected: publicationSchema.nullable() }).strict().safeParse(await body(req, 32768));
|
|
3965
|
+
if (!parsed.success) throw new ReaderError(400, parsed.error.issues[0].message);
|
|
3966
|
+
return json({ publication: await repo.savePublicationDefaults(uid, parsed.data.publication, parsed.data.expected) });
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3911
3969
|
if (path15.join("/") === "deletions" && req.method === "GET") return json(await repo.deletions(uid));
|
|
3912
3970
|
if (path15[0] === "library" && path15.length === 2 && req.method === "DELETE") {
|
|
3913
|
-
const id =
|
|
3971
|
+
const id = z20.string().min(1).max(100).parse(path15[1]);
|
|
3914
3972
|
return json(await repo.remove(id, uid));
|
|
3915
3973
|
}
|
|
3916
3974
|
if (path15[0] === "library" && path15.length === 3 && path15[2] === "restore" && req.method === "POST") {
|
|
3917
|
-
const id =
|
|
3975
|
+
const id = z20.string().min(1).max(100).parse(path15[1]);
|
|
3918
3976
|
return json(await repo.restore(id, uid));
|
|
3919
3977
|
}
|
|
3920
3978
|
if (path15[0] === "library" && path15.length === 3 && path15[2] === "reference") {
|
|
3921
|
-
const id =
|
|
3979
|
+
const id = z20.string().min(1).max(100).parse(path15[1]), references = new BookImageReferences(services.database, services.media, uid);
|
|
3922
3980
|
if (req.method === "GET") return json(await references.get(id));
|
|
3923
3981
|
if (req.method === "POST") {
|
|
3924
|
-
const data =
|
|
3982
|
+
const data = z20.object({ page: z20.number().int().min(0).max(readerMaxFrames - 1), starred: z20.boolean(), token: uuid.optional() }).strict().parse(await body(req));
|
|
3925
3983
|
return json(await references.set(id, data.page, data.starred, data.token));
|
|
3926
3984
|
}
|
|
3927
3985
|
}
|
|
3928
3986
|
if (path15[0] === "library" && path15.length === 3 && path15[2] === "favorite" && req.method === "POST") {
|
|
3929
|
-
const id =
|
|
3930
|
-
const b =
|
|
3987
|
+
const id = z20.string().min(1).max(100).parse(path15[1]);
|
|
3988
|
+
const b = z20.object({ saved: z20.boolean() }).strict().parse(await body(req));
|
|
3931
3989
|
return json(await repo.favorite(id, uid, b.saved));
|
|
3932
3990
|
}
|
|
3933
3991
|
if (path15[0] === "books" && path15.length === 3 && path15[2] === "contents" && req.method === "GET") {
|
|
3934
3992
|
if (!services.localBooks) throw new ReaderError(503, "\u672C\u5730\u4E66\u5E93\u6682\u4E0D\u53EF\u7528");
|
|
3935
|
-
const query =
|
|
3993
|
+
const query = z20.string().max(200).parse(url.searchParams.get("q") || "");
|
|
3936
3994
|
return json(await bookContents(services.localBooks, selectionSchema.parse({ bookId: path15[1], theme: "\u76EE\u5F55" }), query));
|
|
3937
3995
|
}
|
|
3938
3996
|
if (path15.join("/") === "suggestions" && req.method === "POST") {
|
|
@@ -3981,7 +4039,7 @@ function createReaderApi(services) {
|
|
|
3981
4039
|
if (req.method === "GET") return json(await Promise.all((await repo.list(uid)).filter(readableStory).map(async (row) => shelfView(services.production && row.state !== "completed" ? await services.production.snapshot(uid, row.id) : view(row)))));
|
|
3982
4040
|
if (req.method === "POST") {
|
|
3983
4041
|
await requireProvider();
|
|
3984
|
-
const b =
|
|
4042
|
+
const b = z20.object({ requestId: uuid, selection: selectionSchema.omit({ displayTitle: true }), intake: z20.boolean().optional() }).parse(await body(req, 32768));
|
|
3985
4043
|
if (b.selection.discoverySources && b.intake !== true) throw new ReaderError(400, "\u7AE0\u8282\u63D0\u53D6\u5FC5\u987B\u901A\u8FC7\u9009\u9898\u6D41\u7A0B\u521B\u5EFA\u3002");
|
|
3986
4044
|
return json(view(b.intake ? await createIntake(repo, b.requestId, uid, b.selection, services.localBooks) : await repo.create(b.requestId, uid, b.selection)), 201);
|
|
3987
4045
|
}
|
|
@@ -3991,10 +4049,21 @@ function createReaderApi(services) {
|
|
|
3991
4049
|
if (path15[2] === "prompts" && path15.length === 3 && req.method === "GET") {
|
|
3992
4050
|
const row = await repo.get(id, uid), doc = JSON.parse(row.document), selection = selectionSchema.parse(JSON.parse(row.selection));
|
|
3993
4051
|
const preferences = { productionPrompt: selection.productionPrompt, ...acceptanceInput(selection), artDirection: getArtStyle(selection.artStyle).prompt, narrativeDetail: selection.narrativeDetail || "balanced", exactPageCount: selection.length };
|
|
3994
|
-
|
|
4052
|
+
const history = {
|
|
4053
|
+
productionPrompt: selection.productionPrompt,
|
|
4054
|
+
acceptancePrompt: selection.acceptancePrompt || "",
|
|
4055
|
+
records: (doc.usage || []).flatMap((item, index2) => item.promptRecord ? [{ step: item.step, model: item.model, originId: item.originId || id, index: index2, record: item.promptRecord }] : []),
|
|
4056
|
+
missingRecords: (doc.usage || []).flatMap((item, index2) => item.promptRecord ? [] : [{ step: item.step, model: item.model, originId: item.originId || id, index: index2 }])
|
|
4057
|
+
};
|
|
4058
|
+
return json({ ...history, criteria: acceptanceCriteria(selection.acceptancePrompt), results: doc.visualReview?.reports.at(-1)?.acceptanceResults || [], overridden: !!doc.visualReview?.overridden, previews: { production: promptRecord(draftInstructions(!!doc.original?.localSource) + "\n\n## \u63D0\u7EB2\u9636\u6BB5\n" + outlineDirection + "\n\n## \u5206\u6279\u7EC6\u5316\u9636\u6BB5\n" + batchDirection, preferences), acceptance: promptRecord(reviewSystemPrompt + "\n\n" + acceptanceDirection + "\n\n## \u5B9A\u5411\u4FEE\u8BA2\n" + repairSystemPrompt, preferences) } });
|
|
4059
|
+
}
|
|
4060
|
+
if (path15[2] === "publication" && path15.length === 3 && req.method === "POST") {
|
|
4061
|
+
const parsed = z20.object({ publication: publicationSchema, expected: publicationSchema.nullable() }).strict().safeParse(await body(req, 32768));
|
|
4062
|
+
if (!parsed.success) throw new ReaderError(400, parsed.error.issues[0].message);
|
|
4063
|
+
return json(view(await repo.savePublication(id, uid, parsed.data.publication, parsed.data.expected)));
|
|
3995
4064
|
}
|
|
3996
4065
|
if (path15[2] === "title" && path15.length === 3 && req.method === "POST") {
|
|
3997
|
-
const parsed =
|
|
4066
|
+
const parsed = z20.object({ title: displayTitleSchema }).strict().safeParse(await body(req));
|
|
3998
4067
|
if (!parsed.success) throw new ReaderError(400, parsed.error.issues[0].message);
|
|
3999
4068
|
return json(view(await repo.rename(id, uid, parsed.data.title)));
|
|
4000
4069
|
}
|
|
@@ -4010,7 +4079,7 @@ function createReaderApi(services) {
|
|
|
4010
4079
|
if (req.method === "POST") {
|
|
4011
4080
|
await requireProvider();
|
|
4012
4081
|
if (!services.production) throw new ReaderError(503, "\u5236\u4F5C\u961F\u5217\u6682\u4E0D\u53EF\u7528");
|
|
4013
|
-
const b =
|
|
4082
|
+
const b = z20.object({ requestId: uuid, token: z20.string().regex(/^[a-f0-9]{64}$/), changes: revisionChangesSchema, paused: z20.boolean().default(false) }).strict().parse(await body(req, 262144));
|
|
4014
4083
|
const row = await forkRevision(repo, services.media, uid, id, b.requestId, b.token, b.changes, b.paused);
|
|
4015
4084
|
const doc = JSON.parse(row.document);
|
|
4016
4085
|
if (!b.paused && !doc.revision?.handoff) services.production.wake();
|
|
@@ -4024,7 +4093,7 @@ function createReaderApi(services) {
|
|
|
4024
4093
|
}
|
|
4025
4094
|
if (path15[2] === "production-control" && path15.length === 3 && req.method === "POST") {
|
|
4026
4095
|
if (!services.production) throw new ReaderError(503, "\u5236\u4F5C\u961F\u5217\u6682\u4E0D\u53EF\u7528");
|
|
4027
|
-
const b =
|
|
4096
|
+
const b = z20.object({ action: z20.enum(["background", "reader", "pause", "stop", "resume"]) }).strict().parse(await body(req));
|
|
4028
4097
|
if (b.action === "background" || b.action === "resume") await requireProvider();
|
|
4029
4098
|
return json(await services.production.control(uid, id, b.action));
|
|
4030
4099
|
}
|
|
@@ -4032,11 +4101,11 @@ function createReaderApi(services) {
|
|
|
4032
4101
|
if (!services.production) throw new ReaderError(503, "\u5236\u4F5C\u961F\u5217\u6682\u4E0D\u53EF\u7528");
|
|
4033
4102
|
if (req.method === "POST") {
|
|
4034
4103
|
await requireProvider();
|
|
4035
|
-
const b =
|
|
4104
|
+
const b = z20.object({ viewerId: uuid, page: z20.number().int().min(0).max(readerMaxFrames - 1), retry: z20.boolean().default(false) }).strict().parse(await body(req));
|
|
4036
4105
|
return json(await services.production.request(uid, id, b.viewerId, b.page, b.retry), 202);
|
|
4037
4106
|
}
|
|
4038
4107
|
if (req.method === "DELETE") {
|
|
4039
|
-
const b =
|
|
4108
|
+
const b = z20.object({ viewerId: uuid }).strict().parse(await body(req));
|
|
4040
4109
|
await services.production.leave(uid, id, b.viewerId);
|
|
4041
4110
|
return json({ ok: true });
|
|
4042
4111
|
}
|
|
@@ -4056,23 +4125,23 @@ function createReaderApi(services) {
|
|
|
4056
4125
|
}
|
|
4057
4126
|
if (path15[2] === "events" && req.method === "POST") {
|
|
4058
4127
|
const row = await repo.get(id, uid);
|
|
4059
|
-
const b =
|
|
4128
|
+
const b = z20.object({ id: z20.string().max(200), visitor: uuid, session: uuid, storyId: uuid, kind: z20.enum(["start", "page", "complete", "next", "save", "audio", "feedback_positive", "feedback_negative"]), page: z20.number().int().min(0).max(readerMaxFrames - 1).nullable(), mode: z20.enum(["manual", "audio"]), cohort: z20.enum(["internal", "pilot"]) }).parse(await body(req));
|
|
4060
4129
|
const current = view(row);
|
|
4061
4130
|
if (b.storyId !== id || b.page !== null && !current.story?.pages[b.page]?.image) throw new ReaderError(400, "\u8FD9\u5E45\u753B\u9762\u8FD8\u6CA1\u6709\u5C55\u5F00");
|
|
4062
4131
|
await services.db.insert(events).values({ ...b, createdAt: (/* @__PURE__ */ new Date()).toISOString() }).onConflictDoNothing();
|
|
4063
4132
|
return json({ ok: true });
|
|
4064
4133
|
}
|
|
4065
4134
|
if (path15[2] === "progress" && req.method === "POST") {
|
|
4066
|
-
const b =
|
|
4135
|
+
const b = z20.object({ page: z20.number().int().min(0).max(readerMaxFrames - 1), saved: z20.boolean() }).parse(await body(req));
|
|
4067
4136
|
return json(view(await repo.progress(id, uid, b.page, b.saved)));
|
|
4068
4137
|
}
|
|
4069
4138
|
if (path15[2] === "frame-count" && path15.length === 3 && req.method === "POST") {
|
|
4070
|
-
const b =
|
|
4139
|
+
const b = z20.object({ count: z20.union([z20.number().int().min(1).max(readerMaxFrames), z20.literal("auto")]) }).strict().parse(await body(req));
|
|
4071
4140
|
return json(view(await repo.confirmFrameCount(id, uid, b.count)));
|
|
4072
4141
|
}
|
|
4073
4142
|
if (path15[2] === "advance" && req.method === "POST") {
|
|
4074
4143
|
await requireProvider();
|
|
4075
|
-
const b =
|
|
4144
|
+
const b = z20.object({ throughPage: z20.number().int().min(0).max(readerMaxFrames - 1), retry: z20.boolean().default(false) }).parse(await body(req));
|
|
4076
4145
|
const result = services.production && await services.production.managed(uid, id) ? await services.production.advance(uid, id, b.throughPage, b.retry) : await advanceStory(repo, withBookReference(provider.generator(), new BookImageReferences(services.database, services.media, uid)), uid, id, b.throughPage, b.retry);
|
|
4077
4146
|
return json(result, result.state === "running" ? 202 : 200);
|
|
4078
4147
|
}
|
|
@@ -4100,7 +4169,7 @@ function createReaderApi(services) {
|
|
|
4100
4169
|
throw new ReaderError(404, "\u627E\u4E0D\u5230\u8FD9\u4E2A\u6545\u4E8B\u5165\u53E3");
|
|
4101
4170
|
} catch (error) {
|
|
4102
4171
|
if (error instanceof ReaderError) return json({ error: error.message }, error.status);
|
|
4103
|
-
if (error instanceof
|
|
4172
|
+
if (error instanceof z20.ZodError) return json({ error: "\u8BF7\u9009\u62E9\u6709\u6548\u7684\u4E66\u7C4D\u3001\u4E3B\u9898\u4E0E\u9875\u6570" }, 400);
|
|
4104
4173
|
return json({ error: "\u6545\u4E8B\u6682\u65F6\u65E0\u6CD5\u6253\u5F00\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" }, 500);
|
|
4105
4174
|
}
|
|
4106
4175
|
};
|
|
@@ -4109,6 +4178,7 @@ var json, uuid;
|
|
|
4109
4178
|
var init_reader_api = __esm({
|
|
4110
4179
|
"lib/reader-api.ts"() {
|
|
4111
4180
|
"use strict";
|
|
4181
|
+
init_story_publication();
|
|
4112
4182
|
init_reader_generation_contract();
|
|
4113
4183
|
init_reader_storyboard();
|
|
4114
4184
|
init_reader_review();
|
|
@@ -4130,12 +4200,12 @@ var init_reader_api = __esm({
|
|
|
4130
4200
|
init_reader_intake2();
|
|
4131
4201
|
init_reader_revisions();
|
|
4132
4202
|
json = (data, status = 200) => Response.json(data, { status, headers: { "Cache-Control": "private, no-store" } });
|
|
4133
|
-
uuid =
|
|
4203
|
+
uuid = z20.string().uuid();
|
|
4134
4204
|
}
|
|
4135
4205
|
});
|
|
4136
4206
|
|
|
4137
4207
|
// lib/validation.ts
|
|
4138
|
-
import { z as
|
|
4208
|
+
import { z as z21 } from "zod";
|
|
4139
4209
|
function publishErrors(s) {
|
|
4140
4210
|
const e = [];
|
|
4141
4211
|
if (s.pages.length < 4) e.push("\u81F3\u5C11\u9700\u89814\u9875\u5206\u955C");
|
|
@@ -4149,9 +4219,9 @@ var init_validation = __esm({
|
|
|
4149
4219
|
"lib/validation.ts"() {
|
|
4150
4220
|
"use strict";
|
|
4151
4221
|
init_reader_limits();
|
|
4152
|
-
media =
|
|
4153
|
-
pageSchema =
|
|
4154
|
-
storySchema =
|
|
4222
|
+
media = z21.string().max(300).refine((s) => s === "" || /^\/(art|api\/media)\/[a-zA-Z0-9._-]+$/.test(s), "\u5A92\u4F53\u5FC5\u987B\u662F\u5DF2\u4E0A\u4F20\u7684\u672C\u7AD9\u6587\u4EF6");
|
|
4223
|
+
pageSchema = z21.object({ caption: z21.string().max(400), prompt: z21.string().max(4e3), image: media, audio: media });
|
|
4224
|
+
storySchema = z21.object({ title: z21.string().trim().min(1).max(80), summary: z21.string().max(1200), bookId: z21.string().max(100), theme: z21.string().max(60), pages: z21.array(pageSchema).max(readerMaxFrames), reflection: z21.string().max(300), sourceText: z21.string().max(3e4), sourceUrl: z21.string().max(1e3).refine((s) => !s || /^https:\/\//.test(s), "\u6765\u6E90\u94FE\u63A5\u9700\u4E3AHTTPS"), adaptationNote: z21.string().max(2e3), checks: z21.object({ source: z21.boolean(), plot: z21.boolean(), visual: z21.boolean() }), revision: z21.number().int().min(1) });
|
|
4155
4225
|
}
|
|
4156
4226
|
});
|
|
4157
4227
|
|
|
@@ -4184,7 +4254,7 @@ var init_studio_prompts = __esm({
|
|
|
4184
4254
|
|
|
4185
4255
|
// lib/server.ts
|
|
4186
4256
|
import { eq, and, desc, sql as sql2 } from "drizzle-orm";
|
|
4187
|
-
import { z as
|
|
4257
|
+
import { z as z22 } from "zod";
|
|
4188
4258
|
function createApi(services) {
|
|
4189
4259
|
const readerApi = createReaderApi(services);
|
|
4190
4260
|
const vars = () => services.variables;
|
|
@@ -4253,7 +4323,7 @@ function createApi(services) {
|
|
|
4253
4323
|
}
|
|
4254
4324
|
async function generate(req) {
|
|
4255
4325
|
need(vars().SHIQU_AI_PROVIDER !== "readonly", "\u5F53\u524D\u4E3A\u53EA\u8BFB\u6A21\u5F0F\uFF0C\u8BF7\u5148\u914D\u7F6E\u751F\u6210\u670D\u52A1\u3002", 403);
|
|
4256
|
-
const b =
|
|
4326
|
+
const b = z22.object({ storyId: z22.string(), kind: z22.enum(["text", "image", "audio"]), page: z22.number().int().min(0).max(15).optional(), requestId: z22.string().uuid() }).parse(await body2(req));
|
|
4257
4327
|
const p = provider(b.kind);
|
|
4258
4328
|
const db = getDb();
|
|
4259
4329
|
const [existing] = await db.select().from(jobs).where(eq(jobs.id, b.requestId));
|
|
@@ -4282,7 +4352,7 @@ function createApi(services) {
|
|
|
4282
4352
|
} catch {
|
|
4283
4353
|
throw new ApiError(502, "\u6A21\u578B\u8FD4\u56DE\u7684JSON\u65E0\u6CD5\u89E3\u6790\uFF0C\u5DF2\u4FDD\u7559\u539F\u7A3F");
|
|
4284
4354
|
}
|
|
4285
|
-
const generated =
|
|
4355
|
+
const generated = z22.object({ title: z22.string().min(1).max(80), summary: z22.string().max(1200), reflection: z22.string().max(300), adaptationNote: z22.string().max(2e3), pages: z22.array(z22.object({ caption: z22.string().min(1).max(400), prompt: z22.string().min(1).max(4e3) })).min(4).max(16) }).parse(parsed);
|
|
4286
4356
|
result = { ...generated, pages: generated.pages.map((p2) => ({ ...p2, image: "", audio: "" })), checks: { source: false, plot: false, visual: false } };
|
|
4287
4357
|
} else if (b.kind === "image") {
|
|
4288
4358
|
const i = b.page;
|
|
@@ -4345,7 +4415,7 @@ function createApi(services) {
|
|
|
4345
4415
|
if (route === "admin/status") return json2({ providers: providerStatus(), database: !!services.database, storage: !!services.media, privateLab: true });
|
|
4346
4416
|
if (route === "books" && method === "GET") return json2(await db.select().from(books));
|
|
4347
4417
|
if (route === "admin/books" && method === "POST") {
|
|
4348
|
-
const b =
|
|
4418
|
+
const b = z22.object({ title: z22.string().min(1).max(100), author: z22.string().max(100), era: z22.string().max(50), sourceUrl: z22.string().max(1e3).refine((s) => !s || /^https:\/\//.test(s), "\u6765\u6E90\u94FE\u63A5\u9700\u4E3AHTTPS"), excerpt: z22.string().max(3e4), note: z22.string().max(1e3) }).parse(await body2(req));
|
|
4349
4419
|
const row = { ...b, id: crypto.randomUUID() };
|
|
4350
4420
|
await db.insert(books).values(row);
|
|
4351
4421
|
return json2(row);
|
|
@@ -4357,7 +4427,7 @@ function createApi(services) {
|
|
|
4357
4427
|
}
|
|
4358
4428
|
if (route === "admin/stories" && method === "GET") return json2((await db.select().from(stories).orderBy(desc(stories.updatedAt))).map(parse));
|
|
4359
4429
|
if (route === "admin/stories" && method === "POST") {
|
|
4360
|
-
const b =
|
|
4430
|
+
const b = z22.object({ title: z22.string().min(1).max(80), bookId: z22.string(), theme: z22.string().max(60), sourceText: z22.string().max(3e4), sourceUrl: z22.string().max(1e3).refine((s2) => !s2 || /^https:\/\//.test(s2), "\u6765\u6E90\u94FE\u63A5\u9700\u4E3AHTTPS") }).parse(await body2(req));
|
|
4361
4431
|
const s = { ...b, id: crypto.randomUUID(), summary: "", pages: [], reflection: "", adaptationNote: "", status: "draft", checks: { source: false, plot: false, visual: false }, revision: 1, createdAt: now(), updatedAt: now() };
|
|
4362
4432
|
await db.insert(stories).values(serialize(s));
|
|
4363
4433
|
return json2(s, 201);
|
|
@@ -4382,7 +4452,7 @@ function createApi(services) {
|
|
|
4382
4452
|
return json2(next);
|
|
4383
4453
|
}
|
|
4384
4454
|
if (method === "POST") {
|
|
4385
|
-
const b =
|
|
4455
|
+
const b = z22.object({ action: z22.enum(["review", "publish", "unpublish"]), revision: z22.number() }).parse(await body2(req));
|
|
4386
4456
|
need(b.revision === s.revision, "\u5185\u5BB9\u5DF2\u53D8\u66F4\uFF0C\u8BF7\u5237\u65B0", 409);
|
|
4387
4457
|
if (b.action === "publish") {
|
|
4388
4458
|
const errors = publishErrors(s);
|
|
@@ -4422,7 +4492,7 @@ function createApi(services) {
|
|
|
4422
4492
|
if (route === "me/progress") {
|
|
4423
4493
|
const uid = services.ownerId, hidden = new Set((await new ReaderRepository(services.database).deletions(uid)).map((d) => d.storyId));
|
|
4424
4494
|
if (method === "GET") return json2((await db.select().from(progress).where(eq(progress.userId, uid)).orderBy(desc(progress.updatedAt))).filter((p) => !hidden.has(p.storyId)));
|
|
4425
|
-
const b =
|
|
4495
|
+
const b = z22.object({ storyId: z22.string(), page: z22.number().int().min(0).max(readerMaxFrames - 1), saved: z22.boolean() }).parse(await body2(req));
|
|
4426
4496
|
need(!hidden.has(b.storyId), "\u8FD9\u7BC7\u6545\u4E8B\u5DF2\u5220\u9664", 404);
|
|
4427
4497
|
const s = await story(b.storyId);
|
|
4428
4498
|
need(s.status === "published" && b.page < s.pages.length, "\u65E0\u6548\u8FDB\u5EA6");
|
|
@@ -4431,7 +4501,7 @@ function createApi(services) {
|
|
|
4431
4501
|
return json2({ ok: true });
|
|
4432
4502
|
}
|
|
4433
4503
|
if (route === "events" && method === "POST") {
|
|
4434
|
-
const b =
|
|
4504
|
+
const b = z22.object({ id: z22.string().max(200), visitor: z22.string().uuid(), session: z22.string().uuid(), storyId: z22.string().max(100), kind: z22.enum(["start", "page", "complete", "next", "save", "audio", "feedback_positive", "feedback_negative"]), page: z22.number().int().min(0).max(readerMaxFrames - 1).nullable(), mode: z22.enum(["manual", "audio"]), cohort: z22.enum(["internal", "pilot"]) }).parse(await body2(req));
|
|
4435
4505
|
const s = await story(b.storyId);
|
|
4436
4506
|
need(s.status === "published", "\u65E0\u6548\u6545\u4E8B");
|
|
4437
4507
|
if (b.page !== null) need(b.page < s.pages.length, "\u65E0\u6548\u9875\u7801");
|
|
@@ -4470,7 +4540,7 @@ function createApi(services) {
|
|
|
4470
4540
|
}
|
|
4471
4541
|
throw new ApiError(404, "\u63A5\u53E3\u4E0D\u5B58\u5728");
|
|
4472
4542
|
} catch (e) {
|
|
4473
|
-
if (e instanceof
|
|
4543
|
+
if (e instanceof z22.ZodError) return json2({ error: e.errors.map((x) => x.path.join(".") + ": " + x.message).join("\uFF1B") }, 400);
|
|
4474
4544
|
if (e instanceof ApiError) return json2({ error: e.message }, e.status);
|
|
4475
4545
|
console.error(e instanceof Error ? e.message : "server error");
|
|
4476
4546
|
return json2({ error: "\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u6570\u636E\u672A\u786E\u8BA4\u4FDD\u5B58\u3002" }, 500);
|
|
@@ -4635,9 +4705,9 @@ function chapterNumber(value) {
|
|
|
4635
4705
|
}
|
|
4636
4706
|
function localLines(text3) {
|
|
4637
4707
|
const result = [];
|
|
4638
|
-
for (const
|
|
4708
|
+
for (const line2 of text3.split("\n").map((p) => p.trim()).filter(Boolean)) {
|
|
4639
4709
|
let part = "";
|
|
4640
|
-
for (const token2 of
|
|
4710
|
+
for (const token2 of line2.match(/[^。!?!?;;]+[。!?!?;;]?|[。!?!?;;]/gu) || [line2]) {
|
|
4641
4711
|
if (part.length + token2.length > 2e3 && part) {
|
|
4642
4712
|
result.push(part);
|
|
4643
4713
|
part = "";
|
|
@@ -4905,28 +4975,28 @@ function parseShidianOriginal(number, html, item) {
|
|
|
4905
4975
|
const lines = JSON.parse(paragraph.content).lines;
|
|
4906
4976
|
if (!Array.isArray(lines) || !lines.length) throw Error("Missing paragraph lines");
|
|
4907
4977
|
let body2 = "", small = false;
|
|
4908
|
-
for (const
|
|
4909
|
-
if (![1, 2, 3].includes(
|
|
4910
|
-
if (
|
|
4911
|
-
if (
|
|
4978
|
+
for (const line2 of lines) {
|
|
4979
|
+
if (![1, 2, 3].includes(line2.lineType) || typeof line2.content !== "string" || line2.marginNoteIdList?.length) throw Error("Unknown source line");
|
|
4980
|
+
if (line2.lineType === 3 && (line2.content || line2.charPic)) throw Error("Unknown page marker");
|
|
4981
|
+
if (line2.lineType === 2 && !small) {
|
|
4912
4982
|
body2 += "\u3014\u7248\u5F0F\uFF1A\u5C0F\u5B57\u3015";
|
|
4913
4983
|
small = true;
|
|
4914
|
-
} else if (
|
|
4984
|
+
} else if (line2.lineType !== 2 && small) {
|
|
4915
4985
|
body2 += "\u3014\u5C0F\u5B57\u6B62\u3015";
|
|
4916
4986
|
small = false;
|
|
4917
4987
|
}
|
|
4918
|
-
if (
|
|
4919
|
-
const pic =
|
|
4920
|
-
if (!/^\d+$/.test(pic.charPicId) || typeof pic.unicode !== "string" || pic.unicode.trim() ||
|
|
4988
|
+
if (line2.charPic) {
|
|
4989
|
+
const pic = line2.charPic;
|
|
4990
|
+
if (!/^\d+$/.test(pic.charPicId) || typeof pic.unicode !== "string" || pic.unicode.trim() || line2.content || typeof pic.url !== "string" || new URL(pic.url).protocol !== "https:") throw Error("Unknown glyph image");
|
|
4921
4991
|
const url2 = new URL(pic.url);
|
|
4922
4992
|
if (!url2.hostname.endsWith(".byteimg.com") || glyphSources.has(pic.charPicId) && glyphSources.get(pic.charPicId) !== url2.pathname) throw Error("Inconsistent glyph source");
|
|
4923
4993
|
glyphSources.set(pic.charPicId, url2.pathname);
|
|
4924
4994
|
glyphIds.push(pic.charPicId);
|
|
4925
4995
|
body2 += `\u25A1\u3014\u539F\u4EF6\u5B57\u5F62\u56FE\uFF1A${pic.charPicId}\u3015`;
|
|
4926
|
-
} else body2 +=
|
|
4996
|
+
} else body2 += line2.content;
|
|
4927
4997
|
}
|
|
4928
4998
|
if (small) body2 += "\u3014\u5C0F\u5B57\u6B62\u3015";
|
|
4929
|
-
const sourceText2 = lines.map((
|
|
4999
|
+
const sourceText2 = lines.map((line2) => line2.content).join("").replace(/\s/g, "");
|
|
4930
5000
|
const openingHeading = number === 1 ? index2 > 0 && index2 < 50 : index2 < (number === 27 ? 4 : [4, 28].includes(number) ? 2 : 3);
|
|
4931
5001
|
const volumeOrAttribution = sourceText2.length < 100 && /^(?:[酉西]?陽[雜雑維]俎(?:[續讀]集)?卷|唐臨淄[段叚]成式柯古撰)/.test(sourceText2);
|
|
4932
5002
|
if (body2.trim()) blocks.push({ text: body2, narrative: !openingHeading && !volumeOrAttribution && /[。!?!?,;:]/.test(body2) });
|
|
@@ -4999,10 +5069,10 @@ function parseKanripoChapter(id, number, raw, url) {
|
|
|
4999
5069
|
if (!spec?.repo || number < 1 || number > spec.count || !raw.match(new RegExp(`^#\\+PROPERTY: ID ${spec.repo}\\s*$`, "m")) || !raw.match(new RegExp(`^#\\+PROPERTY: BASEEDITION ${spec.edition}\\s*$`, "m"))) throw Error("Kanripo edition mismatch");
|
|
5000
5070
|
let title, body2;
|
|
5001
5071
|
if (spec.edition === "WYG") {
|
|
5002
|
-
const lines = raw.replace(/\r\n/g, "\n").split("\n").filter((
|
|
5072
|
+
const lines = raw.replace(/\r\n/g, "\n").split("\n").filter((line2) => !line2.startsWith("#")).map((line2) => line2.replace(/<pb:[^>]+>/g, "").replace(/¶/g, ""));
|
|
5003
5073
|
const expected = `${id === "\u641C\u795E\u8BB0" ? "\u641C\u795E\u8A18" : "\u535A\u7269\u5FD7"}[\u5377\u5DFB]${chinese(number)}`;
|
|
5004
|
-
const heads = lines.flatMap((
|
|
5005
|
-
if (heads.length !== 2 || lines.slice(heads[1] + 1).some((
|
|
5074
|
+
const heads = lines.flatMap((line2, index2) => new RegExp(`^\\s*${expected}(?:\\s.*)?$`).test(line2) ? [index2] : []);
|
|
5075
|
+
if (heads.length !== 2 || lines.slice(heads[1] + 1).some((line2) => line2.trim())) throw Error("Kanripo volume boundary mismatch");
|
|
5006
5076
|
title = `\u5377${chinese(number)}`;
|
|
5007
5077
|
body2 = lines.slice(heads[0] + 1, heads[1]).join("\n");
|
|
5008
5078
|
} else {
|
|
@@ -5010,7 +5080,7 @@ function parseKanripoChapter(id, number, raw, url) {
|
|
|
5010
5080
|
if (heads.length !== 1 || Number(heads[0][1]) !== number) throw Error("Kanripo chapter sequence mismatch");
|
|
5011
5081
|
title = heads[0][2].trim().replace(/第[一二三四五六七八九十百]+$/, "");
|
|
5012
5082
|
body2 = raw.slice(heads[0].index + heads[0][0].length).replace(/^#.*$/gm, "").replace(/<pb:[^>]+>/g, "").replace(/¶/g, "");
|
|
5013
|
-
body2 = body2.split(/\n\s*\n/).map((part) => part.split("\n").map((
|
|
5083
|
+
body2 = body2.split(/\n\s*\n/).map((part) => part.split("\n").map((line2) => line2.trim()).join("")).filter(Boolean).join("\n");
|
|
5014
5084
|
}
|
|
5015
5085
|
if (/[<>#¶]/.test(body2) || !body2.trim()) throw Error("Malformed Kanripo narrative");
|
|
5016
5086
|
const sectionId = `${spec.prefix}-${String(number).padStart(id === "zhuangzi" ? 2 : 3, "0")}`;
|
|
@@ -5161,16 +5231,16 @@ var init_local_book_erke = __esm({
|
|
|
5161
5231
|
function parseWygVolume(raw, number, url) {
|
|
5162
5232
|
const juan = raw.match(/^#\+PROPERTY: JUAN [卷巻]([^\r\n]+)$/m);
|
|
5163
5233
|
if (!/^#\+PROPERTY: ID KR3l0118\s*$/m.test(raw) || !/^#\+PROPERTY: BASEEDITION WYG\s*$/m.test(raw) || !juan || chapterNumber(juan[1]) !== number) throw Error("WYG volume metadata mismatch");
|
|
5164
|
-
const lines = raw.replace(/\r\n/g, "\n").split("\n").filter((
|
|
5165
|
-
const titles = lines.flatMap((
|
|
5166
|
-
const match =
|
|
5234
|
+
const lines = raw.replace(/\r\n/g, "\n").split("\n").filter((line2) => !line2.startsWith("#") && !/^<pb:KR3l0118_WYG_\d+-\d+[ab]>¶$/.test(line2)).map((line2) => line2.replace(/¶$/, ""));
|
|
5235
|
+
const titles = lines.flatMap((line2, index2) => {
|
|
5236
|
+
const match = line2.match(/^\u3000[太大]平廣記[卷巻]([〇零一二三四五六七八九十百千]+)(?:\u3000.*)?$/);
|
|
5167
5237
|
return match ? [{ index: index2, number: chapterNumber(match[1]) }] : [];
|
|
5168
5238
|
});
|
|
5169
5239
|
if (titles.length !== 2 || titles.some((title) => title.number !== number)) throw Error("WYG opening/closing volume mismatch");
|
|
5170
|
-
const first = lines.findIndex((
|
|
5171
|
-
if (first <= titles[0].index || first >= last || lines.slice(last + 1).some((
|
|
5240
|
+
const first = lines.findIndex((line2) => /^\u3000{4}[^\s]/u.test(line2)), last = titles[1].index;
|
|
5241
|
+
if (first <= titles[0].index || first >= last || lines.slice(last + 1).some((line2) => line2.trim())) throw Error("WYG body boundary mismatch");
|
|
5172
5242
|
const sectionId = `taiping-${String(number).padStart(3, "0")}`;
|
|
5173
|
-
const units = lines.slice(first, last).filter((
|
|
5243
|
+
const units = lines.slice(first, last).filter((line2) => line2.trim()).map((line2, index2) => ({ id: `${sectionId}-${String(index2 + 1).padStart(3, "0")}`, text: line2.trim(), narrative: !/^\u3000{2,}/u.test(line2) && [...line2.matchAll(new RegExp("\\p{Script=Han}", "gu"))].length >= 12 }));
|
|
5174
5244
|
if (!units.some((unit) => unit.narrative) || units.some((unit) => /[¶<>#]/u.test(unit.text))) throw Error("WYG narrative missing or malformed");
|
|
5175
5245
|
return { id: sectionId, title: juan[0].replace(/^.*JUAN /, "") + "\uFF08\u6587\u6E0A\u9601\u672C\uFF09", units, url };
|
|
5176
5246
|
}
|
|
@@ -5921,7 +5991,7 @@ var init_reader_discovery_v1 = __esm({
|
|
|
5921
5991
|
|
|
5922
5992
|
// server/story-discovery-cache.ts
|
|
5923
5993
|
import { createHash as createHash8 } from "node:crypto";
|
|
5924
|
-
import { z as
|
|
5994
|
+
import { z as z23 } from "zod";
|
|
5925
5995
|
function discoveryHash(value) {
|
|
5926
5996
|
return createHash8("sha256").update(JSON.stringify(value)).digest("hex");
|
|
5927
5997
|
}
|
|
@@ -5950,7 +6020,7 @@ var init_story_discovery_cache = __esm({
|
|
|
5950
6020
|
init_reader_model();
|
|
5951
6021
|
flightsByStore = /* @__PURE__ */ new WeakMap();
|
|
5952
6022
|
writesByStore = /* @__PURE__ */ new WeakMap();
|
|
5953
|
-
envelope =
|
|
6023
|
+
envelope = z23.object({ version: z23.literal(1), key: z23.string(), value: z23.unknown() });
|
|
5954
6024
|
StoryDiscoveryCache = class {
|
|
5955
6025
|
flights = /* @__PURE__ */ new Map();
|
|
5956
6026
|
updates = /* @__PURE__ */ new Map();
|
|
@@ -6109,7 +6179,7 @@ var init_story_discovery_legacy = __esm({
|
|
|
6109
6179
|
});
|
|
6110
6180
|
|
|
6111
6181
|
// server/story-discovery-metrics.ts
|
|
6112
|
-
import { z as
|
|
6182
|
+
import { z as z24 } from "zod";
|
|
6113
6183
|
async function settleMeasurements(cache) {
|
|
6114
6184
|
while (measurementBusy(cache)) await Promise.allSettled([...pendingMeasurements.get(cache.identity)]);
|
|
6115
6185
|
}
|
|
@@ -6166,14 +6236,14 @@ var init_story_discovery_metrics = __esm({
|
|
|
6166
6236
|
"server/story-discovery-metrics.ts"() {
|
|
6167
6237
|
"use strict";
|
|
6168
6238
|
init_story_discovery_cache();
|
|
6169
|
-
schema =
|
|
6239
|
+
schema = z24.object({ started: z24.number(), cold: z24.boolean(), firstStoredMs: z24.number().nullable(), firstMatchMs: z24.number().nullable(), calls: z24.number(), isolationCalls: z24.number(), inputTokens: z24.number(), outputTokens: z24.number(), unknownUsage: z24.number(), status: z24.enum(["searching", "matched", "incomplete", "none", "failed", "cancelled"]), ids: z24.array(z24.string()).max(5e4) });
|
|
6170
6240
|
pendingMeasurements = /* @__PURE__ */ new WeakMap();
|
|
6171
6241
|
measurementBusy = (cache) => !!pendingMeasurements.get(cache.identity)?.size;
|
|
6172
6242
|
}
|
|
6173
6243
|
});
|
|
6174
6244
|
|
|
6175
6245
|
// server/story-reading-inputs.ts
|
|
6176
|
-
import { z as
|
|
6246
|
+
import { z as z25 } from "zod";
|
|
6177
6247
|
function render(rows) {
|
|
6178
6248
|
let previous = "";
|
|
6179
6249
|
return rows.map(({ section: section2, unit }) => {
|
|
@@ -6269,15 +6339,15 @@ var init_story_reading_inputs = __esm({
|
|
|
6269
6339
|
init_reader_local_contract();
|
|
6270
6340
|
init_story_discovery_cache();
|
|
6271
6341
|
init_story_token_budget();
|
|
6272
|
-
blockSchema =
|
|
6273
|
-
manifestSchema =
|
|
6342
|
+
blockSchema = z25.object({ ranges: z25.array(localRangeSchema).min(1), tokens: z25.number().int().min(1) });
|
|
6343
|
+
manifestSchema = z25.object({ mode: z25.enum(["whole", "chapters"]), blocks: z25.array(blockSchema).min(1) });
|
|
6274
6344
|
defaultReadingLimits = { whole: wholeInputTokens, chapter: chapterInputTokens };
|
|
6275
6345
|
}
|
|
6276
6346
|
});
|
|
6277
6347
|
|
|
6278
6348
|
// server/story-theme-matching.ts
|
|
6279
6349
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
6280
|
-
import { z as
|
|
6350
|
+
import { z as z26 } from "zod";
|
|
6281
6351
|
async function settleMatching(cache) {
|
|
6282
6352
|
while (matchingBusy(cache)) await Promise.all([...new Set(coordinators.get(cache.identity).flights.values())].map((flight) => flight.promise));
|
|
6283
6353
|
}
|
|
@@ -6318,7 +6388,7 @@ async function matchLibraryStories(provider, cache, namespace, theme, stories2,
|
|
|
6318
6388
|
const fits = (input2) => readingTokens(themeMatchPrompt, providerName) + readingTokens(JSON.stringify(themeMatchPayload(theme, input2)), providerName) + readingTokens(JSON.stringify(themeMatchJson), providerName) <= budget;
|
|
6319
6389
|
const input = (entry) => themeStorySchema.parse({ id: entry.id, title: entry.story.title, text: entry.text });
|
|
6320
6390
|
for (const legacy of options.legacyNamespaces || []) {
|
|
6321
|
-
const old = await cache.read(legacyThemeDecisionKey(legacy, theme, { model: identity.model, reasoningEffort: identity.reasoningEffort }),
|
|
6391
|
+
const old = await cache.read(legacyThemeDecisionKey(legacy, theme, { model: identity.model, reasoningEffort: identity.reasoningEffort }), z26.object({ decisions: z26.record(z26.string(), themeDecisionSchema) }));
|
|
6322
6392
|
if (old) await cache.update(key, savedSchema, empty, (current) => {
|
|
6323
6393
|
for (const entry of entries) {
|
|
6324
6394
|
const decision = old.decisions[legacyThemeStoryId(entry.story)];
|
|
@@ -6541,9 +6611,9 @@ var init_story_theme_matching = __esm({
|
|
|
6541
6611
|
init_story_discovery_cache();
|
|
6542
6612
|
init_story_token_budget();
|
|
6543
6613
|
init_reader_discovery_v1();
|
|
6544
|
-
unknownSchema =
|
|
6545
|
-
savedSchema =
|
|
6546
|
-
cycleSchema =
|
|
6614
|
+
unknownSchema = z26.object({ reason: z26.enum(["failed", "budget"]), cycle: z26.string(), budget: z26.number() });
|
|
6615
|
+
savedSchema = z26.object({ decisions: z26.record(z26.string(), themeDecisionSchema), fingerprints: z26.record(z26.string(), z26.string()), unknown: z26.record(z26.string(), unknownSchema) });
|
|
6616
|
+
cycleSchema = z26.object({ calls: z26.record(z26.string(), z26.boolean()), retried: z26.record(z26.string(), z26.boolean()), retryAllowed: z26.boolean() });
|
|
6547
6617
|
empty = () => ({ decisions: {}, fingerprints: {}, unknown: {} });
|
|
6548
6618
|
themeStoryId = (story) => discoveryHash([story.title.normalize("NFKC").trim(), localSourceKey(story.source)]);
|
|
6549
6619
|
legacyThemeStoryId = (story) => discoveryHash([story.title, localSourceKey(story.source)]);
|
|
@@ -6573,7 +6643,7 @@ var init_story_theme_matching = __esm({
|
|
|
6573
6643
|
});
|
|
6574
6644
|
|
|
6575
6645
|
// server/local-story-discovery.ts
|
|
6576
|
-
import { z as
|
|
6646
|
+
import { z as z27 } from "zod";
|
|
6577
6647
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
6578
6648
|
function rejectedStory(code, raw) {
|
|
6579
6649
|
const value = raw, source2 = localSourceSchema.safeParse(value?.source);
|
|
@@ -6834,11 +6904,11 @@ var init_local_story_discovery = __esm({
|
|
|
6834
6904
|
init_story_token_budget();
|
|
6835
6905
|
init_local_source_ranges();
|
|
6836
6906
|
init_story_theme_matching();
|
|
6837
|
-
storySchema2 = suggestionBatchSchema.shape.stories.element.omit({ storyBrief: true, matchReason: true }).extend({ source: localSourceSchema, continuedFrom:
|
|
6838
|
-
pendingSchema2 =
|
|
6839
|
-
rejectionSchema =
|
|
6840
|
-
stateSchema =
|
|
6841
|
-
stateEnvelope = stateSchema.extend({ stories:
|
|
6907
|
+
storySchema2 = suggestionBatchSchema.shape.stories.element.omit({ storyBrief: true, matchReason: true }).extend({ source: localSourceSchema, continuedFrom: z27.array(localSourceSchema).min(1).max(16).optional() });
|
|
6908
|
+
pendingSchema2 = z27.object({ id: z27.string().min(1).max(64), title: z27.string().min(1).max(100), source: localSourceSchema });
|
|
6909
|
+
rejectionSchema = z27.object({ code: z27.enum(["duplicate", "no-new-evidence", "invalid-source", "no-progress", "pending-resolution"]), title: z27.string().max(100).optional(), source: localSourceSchema.optional() });
|
|
6910
|
+
stateSchema = z27.object({ stories: z27.array(storySchema2).max(5e4), cursor: z27.number().int().min(0), readThrough: z27.number().int().min(0), pendingEvents: z27.array(pendingSchema2).max(16), exhausted: z27.boolean(), rejected: z27.boolean(), lastRejection: rejectionSchema.optional(), overlapDiagnostics: z27.array(z27.object({ source: localSourceSchema, other: localSourceSchema, ratio: z27.number().min(0).max(1) })).max(32).optional() });
|
|
6911
|
+
stateEnvelope = stateSchema.extend({ stories: z27.array(z27.unknown()).max(5e4), pendingEvents: z27.array(z27.unknown()).max(16) });
|
|
6842
6912
|
failed = () => new ReaderError(502, "\u672C\u6BB5\u9009\u9898\u672A\u80FD\u5B8C\u6574\u6838\u5BF9\uFF0C\u5DF2\u4FDD\u7559\u8FDB\u5EA6\uFF0C\u8BF7\u91CD\u8BD5\u6216\u6362\u4E2A\u4E3B\u9898\u3002");
|
|
6843
6913
|
rejectionMessage = (reason) => ({
|
|
6844
6914
|
duplicate: "\u672C\u6B21\u63D0\u53D6\u53EA\u8FD4\u56DE\u4E86\u5DF2\u4FDD\u5B58\u7684\u6545\u4E8B\uFF0C\u5DF2\u4FDD\u7559\u8FDB\u5EA6\uFF0C\u8BF7\u91CD\u8BD5\u67E5\u627E\u3002",
|