Add Content, Model, and Story models with text processing and AI model configuration utilities
- Implement `Content` model for converting Tiptap raw data into HTML and plain text. - Add `Model` for storing and managing AI model configurations with pricing and metadata. - Introduce `Story` model to handle verbal styles and linguistic properties for diverse narrative structures. - Update `book.repository.ts` to refine `updateBookBasicInformation` and `insertNewPlotPoint` methods, removing unused parameters and optimizing queries.
This commit is contained in:
327
electron/database/models/Chapter.ts
Normal file
327
electron/database/models/Chapter.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
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, meta: string, 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, meta, 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<number, ActStory> = {};
|
||||
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<string, unknown>): Record<string, unknown> => {
|
||||
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<string, unknown>), [
|
||||
StarterKit,
|
||||
TextAlign.configure({
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user