import StarterKit from '@tiptap/starter-kit' import TextAlign from '@tiptap/extension-text-align' import ChapterRepo, { ActChapterQuery, ChapterQueryResult, ChapterContentQueryResult, LastChapterResult, CompanionContentQueryResult, ChapterStoryQueryResult, ContentQueryResult } from "@/electron/database/repositories/chapter.repository"; import System from "../System"; import {getUserEncryptionKey} from "../keyManager"; import { generateHTML } from "@tiptap/react"; export interface ChapterContent { version: number; content: string; wordsCount: number; } export interface ChapterContentData extends ChapterContent { title: string; chapterOrder: number; } export interface ChapterProps { chapterId: string; title: string; chapterOrder: number; chapterContent?: ChapterContent } export interface ActChapter { chapterInfoId: number; chapterId: string; title: string; chapterOrder: number; actId: number; incidentId: string | null; plotPointId: string | null; summary: string; goal: string; } export interface CompanionContent { version: number; content: string; wordsCount: number; } export interface ActStory { actId: number; summary: string; chapterSummary: string; chapterGoal: string; incidents: IncidentStory[]; plotPoints: PlotPointStory[]; } export interface IncidentStory { incidentTitle: string; incidentSummary: string; chapterSummary: string; chapterGoal: string; } export interface PlotPointStory { plotTitle: string; plotSummary: string; chapterSummary: string; chapterGoal: string; } export default class Chapter { public static getAllChaptersFromABook(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterProps[] { const chapters: ChapterQueryResult[] = ChapterRepo.fetchAllChapterFromABook(userId, bookId, lang); let returnChapters: ChapterProps[] = []; const userKey: string = getUserEncryptionKey(userId); for (const chapter of chapters) { const title: string = System.decryptDataWithUserKey(chapter.title, userKey); returnChapters.push({ chapterId: chapter.chapter_id, title: title, chapterOrder: chapter.chapter_order }); } return returnChapters; } public static getAllChapterFromActs(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ActChapter[] { const query: ActChapterQuery[] = ChapterRepo.fetchAllChapterForActs(userId, bookId, lang); let chapters: ActChapter[] = []; let tempChapter: { id: string, title: string }[] = [] const userKey: string = getUserEncryptionKey(userId); if (query.length > 0) { for (const chapter of query) { let decryptTitle: string = ''; const newTitleId: number = tempChapter.findIndex((temp: { id: string, title: string }) => temp.id === chapter.chapter_id); if (newTitleId > -1) { decryptTitle = tempChapter[newTitleId]?.title ?? '' } else { decryptTitle = System.decryptDataWithUserKey(chapter.title, userKey); tempChapter.push({id: chapter.chapter_id, title: decryptTitle}); } chapters.push({ chapterId: chapter.chapter_id, title: decryptTitle, actId: chapter.act_id, chapterInfoId: chapter.chapter_info_id, chapterOrder: chapter.chapter_order, goal: chapter.goal ? System.decryptDataWithUserKey(chapter.goal, userKey) : '', summary: chapter.summary ? System.decryptDataWithUserKey(chapter.summary, userKey) : '', incidentId: chapter.incident_id, plotPointId: chapter.plot_point_id }) } return chapters; } else { return [] } } public static getWholeChapter(userId: string, chapterId: string, version: number, bookId?: string, lang: 'fr' | 'en' = 'fr'): ChapterProps { const chapter: ChapterContentQueryResult = ChapterRepo.fetchWholeChapter(userId, chapterId, version, lang); const userKey: string = getUserEncryptionKey(userId); if (bookId) { ChapterRepo.updateLastChapterRecord(userId, bookId, chapterId, version, lang); } return { chapterId: chapter.chapter_id, title: System.decryptDataWithUserKey(chapter.title, userKey), chapterOrder: chapter.chapter_order, chapterContent: { content: chapter.content ? System.decryptDataWithUserKey(chapter.content, userKey) : '', version: version, wordsCount: chapter.words_count } }; } public static saveChapterContent(userId: string, chapterId: string, version: number, content: JSON, wordsCount: number, currentTime: number, lang: 'fr' | 'en' = 'fr'): boolean { const userKey: string = getUserEncryptionKey(userId); const encryptContent: string = System.encryptDataWithUserKey(JSON.stringify(content), userKey); /*if (version === 2){ const QS = new AI(); const prompt:string = System.htmlToText(Chapter.tipTapToHtml(content)); const response:string = await QS.request(prompt,'summary-chapter'); console.log(response); }*/ return ChapterRepo.updateChapterContent(userId, chapterId, version, encryptContent, wordsCount, lang); } public static getLastChapter(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterProps | null { const lastChapter: LastChapterResult | null = ChapterRepo.fetchLastChapter(userId, bookId, lang); if (lastChapter) { return Chapter.getWholeChapter(userId, lastChapter.chapter_id, lastChapter.version, bookId, lang); } const chapter: ChapterContentQueryResult[] = ChapterRepo.fetchLastChapterContent(userId, bookId, lang); if (chapter.length === 0) { return null } const chapterData: ChapterContentQueryResult = chapter[0]; const userKey: string = getUserEncryptionKey(userId); return { chapterId: chapterData.chapter_id, title: chapterData.title ? System.decryptDataWithUserKey(chapterData.title, userKey) : '', chapterOrder: chapterData.chapter_order, chapterContent: { content: chapterData.content ? System.decryptDataWithUserKey(chapterData.content, userKey) : '', version: chapterData.version, wordsCount: chapterData.words_count } }; } public static addChapter(userId: string, bookId: string, title: string, wordsCount: number, chapterOrder: number, lang: 'fr' | 'en' = 'fr'): string { const hashedTitle: string = System.hashElement(title); const userKey: string = getUserEncryptionKey(userId); const encryptedTitle: string = System.encryptDataWithUserKey(title, userKey); if (ChapterRepo.checkNameDuplication(userId, bookId, hashedTitle, lang)) { throw new Error(lang === 'fr' ? `Ce nom de chapitre existe déjà.` : `This chapter name already exists.`); } const chapterId: string = System.createUniqueId(); return ChapterRepo.insertChapter(chapterId, userId, bookId, encryptedTitle, hashedTitle, wordsCount, chapterOrder, lang); } public static removeChapter(userId: string, chapterId: string, lang: 'fr' | 'en' = 'fr'): boolean { return ChapterRepo.deleteChapter(userId, chapterId, lang); } public static addChapterInformation(userId: string, chapterId: string, actId: number, bookId: string, plotId: string | null, incidentId: string | null, lang: 'fr' | 'en' = 'fr'): string { const chapterInfoId: string = System.createUniqueId(); return ChapterRepo.insertChapterInformation(chapterInfoId, userId, chapterId, actId, bookId, plotId, incidentId, lang); } public static updateChapter(userId: string, chapterId: string, title: string, chapterOrder: number, lang: 'fr' | 'en' = 'fr'): boolean { const hashedTitle: string = System.hashElement(title); const userKey: string = getUserEncryptionKey(userId); const encryptedTitle: string = System.encryptDataWithUserKey(title, userKey); return ChapterRepo.updateChapter(userId, chapterId, encryptedTitle, hashedTitle, chapterOrder, lang); } static updateChapterInfos(chapters: ActChapter[], userId: string, actId: number, bookId: string, incidentId: string | null, plotId: string | null, lang: 'fr' | 'en' = 'fr') { const userKey: string = getUserEncryptionKey(userId); for (const chapter of chapters) { const summary: string = chapter.summary ? System.encryptDataWithUserKey(chapter.summary, userKey) : ''; const goal: string = chapter.goal ? System.encryptDataWithUserKey(chapter.goal, userKey) : ''; const chapterId: string = chapter.chapterId; ChapterRepo.updateChapterInfos(userId, chapterId, actId, bookId, incidentId, plotId, summary, goal, lang); } } static getCompanionContent(userId: string, chapterId: string, version: number, lang: 'fr' | 'en' = 'fr'): CompanionContent { const versionNum: number = version - 1; const chapterResponse: CompanionContentQueryResult[] = ChapterRepo.fetchCompanionContent(userId, chapterId, versionNum, lang); if (chapterResponse.length === 0) { return { version: version, content: '', wordsCount: 0 }; } const chapter: CompanionContentQueryResult = chapterResponse[0]; const userKey: string = getUserEncryptionKey(userId); return { version: chapter.version, content: chapter.content ? System.decryptDataWithUserKey(chapter.content, userKey) : '', wordsCount: chapter.words_count }; } static getChapterStory(userId: string, chapterId: string, lang: 'fr' | 'en' = 'fr'): ActStory[] { const stories: ChapterStoryQueryResult[] = ChapterRepo.fetchChapterStory(userId, chapterId, lang); const actStories: Record = {}; const userKey: string = getUserEncryptionKey(userId); for (const story of stories) { const actId: number = story.act_id; if (!actStories[actId]) { actStories[actId] = { actId: actId, summary: story.summary ? System.decryptDataWithUserKey(story.summary, userKey) : '', chapterSummary: story.chapter_summary ? System.decryptDataWithUserKey(story.chapter_summary, userKey) : '', chapterGoal: story.chapter_goal ? System.decryptDataWithUserKey(story.chapter_goal, userKey) : '', incidents: [], plotPoints: [] }; } if (story.incident_id) { const incidentTitle = story.incident_title ? System.decryptDataWithUserKey(story.incident_title, userKey) : ''; const incidentSummary = story.incident_summary ? System.decryptDataWithUserKey(story.incident_summary, userKey) : ''; const incidentExists = actStories[actId].incidents.some( (incident) => incident.incidentTitle === incidentTitle && incident.incidentSummary === incidentSummary ); if (!incidentExists) { actStories[actId].incidents.push({ incidentTitle: incidentTitle, incidentSummary: incidentSummary, chapterSummary: story.chapter_summary ? System.decryptDataWithUserKey(story.chapter_summary, userKey) : '', chapterGoal: story.chapter_goal ? System.decryptDataWithUserKey(story.chapter_goal, userKey) : '' }); } } if (story.plot_point_id) { const plotTitle = story.plot_title ? System.decryptDataWithUserKey(story.plot_title, userKey) : ''; const plotSummary = story.plot_summary ? System.decryptDataWithUserKey(story.plot_summary, userKey) : ''; const plotPointExists = actStories[actId].plotPoints.some( (plotPoint) => plotPoint.plotTitle === plotTitle && plotPoint.plotSummary === plotSummary ); if (!plotPointExists) { actStories[actId].plotPoints.push({ plotTitle: plotTitle, plotSummary: plotSummary, chapterSummary: story.chapter_summary ? System.decryptDataWithUserKey(story.chapter_summary, userKey) : '', chapterGoal: story.chapter_goal ? System.decryptDataWithUserKey(story.chapter_goal, userKey) : '' }); } } } return Object.values(actStories); } static getChapterContentByVersion(userId: string, chapterid: string, version: number, lang: 'fr' | 'en' = 'fr'): string { const chapter: ContentQueryResult = ChapterRepo.fetchChapterContentByVersion(userId, chapterid, version, lang); const userKey: string = getUserEncryptionKey(userId); return chapter.content ? System.decryptDataWithUserKey(chapter.content, userKey) : ''; } static removeChapterInformation(userId: string, chapterInfoId: string, lang: 'fr' | 'en' = 'fr') { return ChapterRepo.deleteChapterInformation(userId, chapterInfoId, lang); } static tipTapToHtml(tipTapContent: JSON): string { const fixNode = (node: Record): Record => { if (!node) return node; if (node.type === 'text' && (!node.text || node.text === '')) { node.text = '\u00A0'; } if (Array.isArray(node.content) && node.content.length) { node.content = node.content.map(fixNode); } return node; }; return generateHTML(fixNode(tipTapContent as unknown as Record), [ StarterKit, TextAlign.configure({ types: ['heading', 'paragraph'], }), ]); } }