@musnows/scriverse 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.js +7 -7
- package/dist/ai.js.map +1 -1
- package/dist/app.js +71 -3
- package/dist/app.js.map +1 -1
- package/dist/database.js +95 -0
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +641 -12
- package/dist/public/index.html +79 -4
- package/dist/public/relationship-graph.js +80 -22
- package/dist/public/styles.css +85 -2
- package/dist/store.js +425 -30
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +11 -1
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/app.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
+
import JSZip from "jszip";
|
|
2
3
|
import multer from "multer";
|
|
3
4
|
import mammoth from "mammoth";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -6,6 +7,7 @@ import { dirname, extname, join } from "node:path";
|
|
|
6
7
|
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
7
8
|
import { rm } from "node:fs/promises";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
10
|
+
import { pipeline } from "node:stream/promises";
|
|
9
11
|
import { z, ZodError } from "zod";
|
|
10
12
|
import { AttachmentStorage } from "./attachment-storage.js";
|
|
11
13
|
import { AiManager } from "./ai.js";
|
|
@@ -993,6 +995,12 @@ export function createRuntime(options) {
|
|
|
993
995
|
const input = parse(z.object({ volumeId: identifier, title: nonEmpty.max(300), content: z.string().max(2_000_000).optional(), chapterType: chapterTypeSchema.optional() }), request.body);
|
|
994
996
|
data(response, store.createChapter(request.params.workId, input), 201);
|
|
995
997
|
});
|
|
998
|
+
app.get("/api/works/:workId/deleted-chapters", (request, response) => {
|
|
999
|
+
const pagination = parsePagination(request.query);
|
|
1000
|
+
data(response, pagination
|
|
1001
|
+
? store.listDeletedChaptersPage(request.params.workId, pagination)
|
|
1002
|
+
: store.listDeletedChapters(request.params.workId));
|
|
1003
|
+
});
|
|
996
1004
|
app.get("/api/chapters/:chapterId", (request, response) => data(response, store.getChapter(request.params.chapterId)));
|
|
997
1005
|
app.patch("/api/chapters/:chapterId", (request, response) => {
|
|
998
1006
|
const input = parse(z.object({ title: nonEmpty.max(300).optional(), content: z.string().max(2_000_000).optional(), excludedFromAnalysis: z.boolean().optional(), chapterType: chapterTypeSchema.optional(), source: z.enum(["manual", "auto"]).optional(), changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
@@ -1013,6 +1021,26 @@ export function createRuntime(options) {
|
|
|
1013
1021
|
const pagination = parsePagination(request.query);
|
|
1014
1022
|
data(response, pagination ? store.listChapterInsightsPage(request.params.chapterId, pagination) : store.listChapterInsights(request.params.chapterId));
|
|
1015
1023
|
});
|
|
1024
|
+
app.get("/api/chapters/:chapterId/annotations", (request, response) => data(response, store.listChapterAnnotations(request.params.chapterId)));
|
|
1025
|
+
app.post("/api/chapters/:chapterId/annotations", (request, response) => {
|
|
1026
|
+
const input = parse(z.object({
|
|
1027
|
+
kind: z.enum(["note", "todo"]),
|
|
1028
|
+
startLine: z.number().int().positive(),
|
|
1029
|
+
endLine: z.number().int().positive(),
|
|
1030
|
+
note: z.string().trim().min(1).max(2000)
|
|
1031
|
+
}).strict().refine((value) => value.endLine >= value.startLine, { message: "结束行不能早于开始行", path: ["endLine"] }), request.body);
|
|
1032
|
+
data(response, store.createChapterAnnotation(request.params.chapterId, input), 201);
|
|
1033
|
+
});
|
|
1034
|
+
app.patch("/api/chapter-annotations/:annotationId", (request, response) => {
|
|
1035
|
+
const input = parse(z.object({ note: z.string().trim().min(1).max(2000).optional(), status: z.enum(["open", "resolved"]).optional(), expectedVersionNo: expectedVersionNoSchema }).strict().refine((value) => value.note !== undefined || value.status !== undefined, { message: "至少需要修改一项" }), request.body);
|
|
1036
|
+
const { expectedVersionNo, ...update } = input;
|
|
1037
|
+
data(response, store.updateChapterAnnotation(request.params.annotationId, update, expectedVersionNo));
|
|
1038
|
+
});
|
|
1039
|
+
app.delete("/api/chapter-annotations/:annotationId", (request, response) => {
|
|
1040
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
1041
|
+
store.deleteChapterAnnotation(request.params.annotationId, input.expectedVersionNo);
|
|
1042
|
+
noContent(response);
|
|
1043
|
+
});
|
|
1016
1044
|
app.post("/api/chapters/:chapterId/restore", (request, response) => {
|
|
1017
1045
|
const input = parse(z.object({ versionNo: z.number().int().positive(), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
1018
1046
|
const chapter = store.restoreChapter(request.params.chapterId, input.versionNo, input.expectedVersionNo);
|
|
@@ -1023,6 +1051,17 @@ export function createRuntime(options) {
|
|
|
1023
1051
|
const { expectedVersionNo, ...moveInput } = input;
|
|
1024
1052
|
data(response, store.moveChapter(request.params.chapterId, moveInput, expectedVersionNo));
|
|
1025
1053
|
});
|
|
1054
|
+
app.post("/api/works/:workId/chapters/batch", (request, response) => {
|
|
1055
|
+
const selectedChapters = z.array(z.object({ id: identifier, expectedVersionNo: z.number().int().positive() }).strict()).min(1).max(200);
|
|
1056
|
+
const action = z.discriminatedUnion("type", [
|
|
1057
|
+
z.object({ type: z.literal("move"), volumeId: identifier }).strict(),
|
|
1058
|
+
z.object({ type: z.literal("setType"), chapterType: chapterTypeSchema }).strict(),
|
|
1059
|
+
z.object({ type: z.literal("setAnalysisExclusion"), excludedFromAnalysis: z.boolean() }).strict(),
|
|
1060
|
+
z.object({ type: z.literal("delete") }).strict()
|
|
1061
|
+
]);
|
|
1062
|
+
const input = parse(z.object({ chapters: selectedChapters, action }).strict(), request.body);
|
|
1063
|
+
data(response, store.batchManageChapters(request.params.workId, input.chapters, input.action));
|
|
1064
|
+
});
|
|
1026
1065
|
app.get("/api/works/:workId/outlines", (request, response) => {
|
|
1027
1066
|
const pagination = parsePagination(request.query);
|
|
1028
1067
|
data(response, pagination ? store.listChapterOutlinesPage(request.params.workId, pagination) : store.listChapterOutlines(request.params.workId));
|
|
@@ -1818,21 +1857,44 @@ export function createRuntime(options) {
|
|
|
1818
1857
|
const query = parse(z.string().trim().min(1).max(500), request.query.q);
|
|
1819
1858
|
data(response, store.search(request.params.workId, query));
|
|
1820
1859
|
});
|
|
1821
|
-
app.get("/api/works/:workId/export", (request, response) => {
|
|
1860
|
+
app.get("/api/works/:workId/export", async (request, response) => {
|
|
1822
1861
|
const format = parse(z.enum(["json", "txt", "markdown"]), request.query.format ?? "json");
|
|
1823
1862
|
if (format === "json") {
|
|
1824
1863
|
response.setHeader("Content-Disposition", `attachment; filename=novel-${request.params.workId}.json`);
|
|
1825
1864
|
data(response, store.exportWork(request.params.workId));
|
|
1826
1865
|
return;
|
|
1827
1866
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1867
|
+
if (format === "markdown") {
|
|
1868
|
+
const exportName = `novel-${request.params.workId}`;
|
|
1869
|
+
const archive = new JSZip();
|
|
1870
|
+
archive.file(`${exportName}.md`, store.exportText(request.params.workId, format));
|
|
1871
|
+
response.type("application/zip");
|
|
1872
|
+
response.setHeader("Content-Disposition", `attachment; filename=${exportName}.zip`);
|
|
1873
|
+
await pipeline(archive.generateNodeStream({
|
|
1874
|
+
type: "nodebuffer",
|
|
1875
|
+
streamFiles: true,
|
|
1876
|
+
compression: "DEFLATE",
|
|
1877
|
+
compressionOptions: { level: 6 }
|
|
1878
|
+
}), response);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
response.type("text/plain");
|
|
1882
|
+
response.setHeader("Content-Disposition", `attachment; filename=novel-${request.params.workId}.txt`);
|
|
1830
1883
|
response.send(store.exportText(request.params.workId, format));
|
|
1831
1884
|
});
|
|
1832
1885
|
app.get("/api/works/:workId/audit-logs", (request, response) => {
|
|
1833
1886
|
const pagination = parsePagination(request.query);
|
|
1834
1887
|
data(response, pagination ? store.listAuditLogsPage(request.params.workId, pagination) : store.listAuditLogs(request.params.workId));
|
|
1835
1888
|
});
|
|
1889
|
+
app.get("/api/works/:workId/writing-progress", (request, response) => data(response, store.getWritingProgress(request.params.workId)));
|
|
1890
|
+
app.put("/api/works/:workId/writing-goal", (request, response) => {
|
|
1891
|
+
const input = parse(z.object({
|
|
1892
|
+
dailyGoal: z.number().int().min(0).max(1_000_000),
|
|
1893
|
+
targetTotal: z.number().int().min(0).max(100_000_000),
|
|
1894
|
+
deadline: z.string().date().nullable()
|
|
1895
|
+
}).strict(), request.body);
|
|
1896
|
+
data(response, store.updateWritingGoal(request.params.workId, input));
|
|
1897
|
+
});
|
|
1836
1898
|
if (options.serveUi ?? true) {
|
|
1837
1899
|
const publicPath = options.publicPath ?? join(process.cwd(), "src", "public");
|
|
1838
1900
|
// index.html 按登录态动态下发:未登录时注入 login-route 类,首帧直接渲染登录页;
|
|
@@ -1883,6 +1945,12 @@ export function createRuntime(options) {
|
|
|
1883
1945
|
app.use((_request, _response, next) => next(new AppError(404, "ROUTE_NOT_FOUND", "请求的接口不存在")));
|
|
1884
1946
|
app.use((error, request, response, _next) => {
|
|
1885
1947
|
const commonFields = { method: request.method, path: sanitizeRequestPath(request.path), error: sanitizeError(error) };
|
|
1948
|
+
if (response.headersSent || response.destroyed) {
|
|
1949
|
+
logger.warn("http.request.response_stream_failed", commonFields);
|
|
1950
|
+
if (!response.destroyed)
|
|
1951
|
+
response.destroy(error instanceof Error ? error : undefined);
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1886
1954
|
if (error instanceof ZodError) {
|
|
1887
1955
|
logger.warn("http.request.validation_failed", { ...commonFields, issuePaths: error.issues.map((issue) => issue.path.join(".")) });
|
|
1888
1956
|
response.status(400).json({
|