feat(platform): 开源发布 - 初始化 Open AI Canvas

This commit is contained in:
ddCat committed 2026-07-22 14:29:10 +08:00
commit aea35896eb
365 files changed
+66410

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
import type { JSONContent } from "@tiptap/core";
import type { CanvasDocumentChapter, CanvasRichDocument } from "@/types/canvas";
export function emptyDocument(): JSONContent {
return { type: "doc", content: [{ type: "paragraph" }] };
}
export function textToDocument(text: string): JSONContent {
const content = text.split(/\n{2,}/).map((paragraph) => {
const value = paragraph.trim();
if (!value) return { type: "paragraph" };
const markdownHeading = /^(#{1,3})\s+(.+)$/.exec(value);
if (markdownHeading) return { type: "heading", attrs: { level: markdownHeading[1].length }, content: [{ type: "text", text: markdownHeading[2] }] };
return { type: "paragraph", content: [{ type: "text", text: value }] };
});
return { type: "doc", content: content.length ? content : [{ type: "paragraph" }] };
}
export function createDocumentChapter(title: string, plainText = "", order = 0): CanvasDocumentChapter {
return {
id: `chapter-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
title,
order,
json: textToDocument(plainText) as unknown as Record<string, unknown>,
plainText: plainText.trim(),
characterCount: Array.from(plainText.trim()).length,
storyboardStatus: "idle",
updatedAt: new Date().toISOString(),
};
}
export function normalizeDocumentChapters(document: CanvasRichDocument | undefined, fallbackText = "") {
if (document?.chapters?.length) {
return document.chapters
.map((chapter, index) => ({ ...chapter, title: chapter.title.trim() || `第 ${index + 1} 章`, order: index }))
.sort((left, right) => left.order - right.order);
}
const chapter = createDocumentChapter("第 1 章", document?.plainText || fallbackText, 0);
if (document?.json) chapter.json = document.json;
return [chapter];
}
export function splitTextIntoChapters(text: string) {
const normalized = text.replace(/\r\n?/g, "\n").trim();
if (!normalized) return [createDocumentChapter("第 1 章")];
const lines = normalized.split("\n");
const headingPattern = /^\s*(第[零〇一二两三四五六七八九十百千万\d]+[章节卷回部篇]|chapter\s+\d+\b|序章|楔子|引子|尾声|后记)(?:[\s::.-]+.*)?\s*$/i;
const sections: Array<{ title: string; lines: string[] }> = [];
let current: { title: string; lines: string[] } | null = null;
const preface: string[] = [];
for (const line of lines) {
if (headingPattern.test(line.trim())) {
if (current) sections.push(current);
current = { title: line.trim(), lines: [] };
continue;
}
if (current) current.lines.push(line);
else preface.push(line);
}
if (current) sections.push(current);
if (!sections.length) return [createDocumentChapter("第 1 章", normalized, 0)];
if (preface.some((line) => line.trim())) sections.unshift({ title: "序章", lines: preface });
return sections.map((section, index) => createDocumentChapter(section.title, section.lines.join("\n").trim(), index));
}
export function buildRichDocument(
previous: CanvasRichDocument | undefined,
chapters: CanvasDocumentChapter[],
activeChapterId: string,
sourceFileName?: string,
): CanvasRichDocument {
const ordered = chapters.map((chapter, index) => ({ ...chapter, order: index }));
const active = ordered.find((chapter) => chapter.id === activeChapterId) || ordered[0];
const plainText = ordered.map((chapter) => `${chapter.title}\n\n${chapter.plainText}`.trim()).filter(Boolean).join("\n\n");
return {
kind: previous?.kind || "novel",
format: "tiptap-json",
json: active?.json || (emptyDocument() as unknown as Record<string, unknown>),
plainText,
characterCount: ordered.reduce((total, chapter) => total + chapter.characterCount, 0),
chapters: ordered,
activeChapterId: active?.id,
sourceFileName,
updatedAt: new Date().toISOString(),
};
}