diff --git a/components/book/settings/quillsense/QuillSenseSetting.tsx b/components/book/settings/quillsense/QuillSenseSetting.tsx index 2412813..607bcf0 100644 --- a/components/book/settings/quillsense/QuillSenseSetting.tsx +++ b/components/book/settings/quillsense/QuillSenseSetting.tsx @@ -66,13 +66,13 @@ const QuillSenseSetting = forwardRef(function QuillSenseSetting(props, ref) { lang ); if (updateResult) { - successMessage(t('quillsenseSetting.saveSuccess')); + successMessage(t('quillSenseSetting.successSave')); // Mettre a jour le contexte du livre if (setBook && book) { setBook({...book, quillsenseEnabled: quillsenseEnabled}); } } else { - errorMessage(t('quillsenseSetting.saveError')); + errorMessage(t('quillSenseSetting.errorSave')); } } catch (e: unknown) { if (e instanceof Error) { @@ -94,7 +94,7 @@ const QuillSenseSetting = forwardRef(function QuillSenseSetting(props, ref) {

- {t('quillsenseSetting.enableDescription')} + {t('quillSenseSetting.enableDescription')}

): void => setAdvancedPrompt(e.target.value)} - placeholder={t('quillsenseSetting.advancedPromptPlaceholder')} + placeholder={t('quillSenseSetting.advancedPromptPlaceholder')} /> } />

- {t('quillsenseSetting.advancedPromptDescription')} + {t('quillSenseSetting.advancedPromptHint')}

diff --git a/electron/database/models/Act.ts b/electron/database/models/Act.ts index dd9cdb6..07fb590 100644 --- a/electron/database/models/Act.ts +++ b/electron/database/models/Act.ts @@ -174,10 +174,8 @@ export default class Act { for (const chapter of mainChapters) { const chapterId: string = chapter.chapterId; const chapterTitle: string = chapter.title; - const hashedChapterTitle: string = System.hashElement(chapterTitle); - const encryptedChapterTitle: string = System.encryptDataWithUserKey(chapterTitle, userEncryptionKey); const chapterOrder: number = chapter.chapterOrder; - ChapterRepo.updateChapter(userId, chapterId, encryptedChapterTitle, hashedChapterTitle, chapterOrder, System.timeStampInSeconds(), lang); + Chapter.updateChapter(userId, chapterId, chapterTitle, chapterOrder, lang); } return true; diff --git a/electron/database/models/GuideLine.ts b/electron/database/models/GuideLine.ts index 9fe01c2..84eb4a1 100644 --- a/electron/database/models/GuideLine.ts +++ b/electron/database/models/GuideLine.ts @@ -120,6 +120,7 @@ export default class GuideLine { encryptedPacing, encryptedKeyMessages, encryptedIntendedAudience, + System.timeStampInSeconds(), lang ); } @@ -210,6 +211,7 @@ export default class GuideLine { verbTense, language, encryptedThemes, + System.timeStampInSeconds(), lang ); } diff --git a/electron/database/models/Sync.ts b/electron/database/models/Sync.ts index fc0f0e2..88625dc 100644 --- a/electron/database/models/Sync.ts +++ b/electron/database/models/Sync.ts @@ -303,6 +303,41 @@ export default class Sync { } } + if (syncCompareData.guideLine) { + const guidelineResults: BookGuideLineTable[] = await GuidelineRepo.fetchBookGuideLineTable(userId, syncCompareData.id, lang); + if (guidelineResults.length > 0) { + const guidelineRecord: BookGuideLineTable = guidelineResults[0]; + decryptedGuideLines.push({ + ...guidelineRecord, + tone: guidelineRecord.tone ? System.decryptDataWithUserKey(guidelineRecord.tone, userEncryptionKey) : null, + atmosphere: guidelineRecord.atmosphere ? System.decryptDataWithUserKey(guidelineRecord.atmosphere, userEncryptionKey) : null, + writing_style: guidelineRecord.writing_style ? System.decryptDataWithUserKey(guidelineRecord.writing_style, userEncryptionKey) : null, + themes: guidelineRecord.themes ? System.decryptDataWithUserKey(guidelineRecord.themes, userEncryptionKey) : null, + symbolism: guidelineRecord.symbolism ? System.decryptDataWithUserKey(guidelineRecord.symbolism, userEncryptionKey) : null, + motifs: guidelineRecord.motifs ? System.decryptDataWithUserKey(guidelineRecord.motifs, userEncryptionKey) : null, + narrative_voice: guidelineRecord.narrative_voice ? System.decryptDataWithUserKey(guidelineRecord.narrative_voice, userEncryptionKey) : null, + pacing: guidelineRecord.pacing ? System.decryptDataWithUserKey(guidelineRecord.pacing, userEncryptionKey) : null, + intended_audience: guidelineRecord.intended_audience ? System.decryptDataWithUserKey(guidelineRecord.intended_audience, userEncryptionKey) : null, + key_messages: guidelineRecord.key_messages ? System.decryptDataWithUserKey(guidelineRecord.key_messages, userEncryptionKey) : null + }); + } + } + + if (syncCompareData.aiGuideLine) { + const aiGuidelineResults: BookAIGuideLineTable[] = await GuidelineRepo.fetchBookAIGuideLine(userId, syncCompareData.id, lang); + if (aiGuidelineResults.length > 0) { + const aiGuidelineRecord: BookAIGuideLineTable = aiGuidelineResults[0]; + decryptedAIGuideLines.push({ + ...aiGuidelineRecord, + global_resume: aiGuidelineRecord.global_resume ? System.decryptDataWithUserKey(aiGuidelineRecord.global_resume, userEncryptionKey) : null, + themes: aiGuidelineRecord.themes ? System.decryptDataWithUserKey(aiGuidelineRecord.themes, userEncryptionKey) : null, + tone: aiGuidelineRecord.tone ? System.decryptDataWithUserKey(aiGuidelineRecord.tone, userEncryptionKey) : null, + atmosphere: aiGuidelineRecord.atmosphere ? System.decryptDataWithUserKey(aiGuidelineRecord.atmosphere, userEncryptionKey) : null, + current_resume: aiGuidelineRecord.current_resume ? System.decryptDataWithUserKey(aiGuidelineRecord.current_resume, userEncryptionKey) : null + }); + } + } + const bookResults: EritBooksTable[] = await BookRepo.fetchCompleteBookById(syncCompareData.id, lang); if (bookResults.length > 0) { const bookRecord: EritBooksTable = bookResults[0]; @@ -361,6 +396,8 @@ export default class Sync { const serverWorlds: BookWorldTable[] = completeBook.worlds; const serverWorldElements: BookWorldElementsTable[] = completeBook.worldElements; const serverIssues: BookIssuesTable[] = completeBook.issues; + const serverGuideLines: BookGuideLineTable[] = completeBook.guideLine; + const serverAIGuideLines: BookAIGuideLineTable[] = completeBook.aiGuideLine; const bookId: string = completeBook.eritBooks.length > 0 ? completeBook.eritBooks[0].book_id : ''; @@ -638,6 +675,55 @@ export default class Sync { } } + if (serverGuideLines && serverGuideLines.length > 0) { + for (const serverGuideLine of serverGuideLines) { + const guideLineExists: boolean = GuidelineRepo.guideLineExist(userId, bookId, lang); + const encryptedTone: string | null = serverGuideLine.tone ? System.encryptDataWithUserKey(serverGuideLine.tone, userEncryptionKey) : null; + const encryptedAtmosphere: string | null = serverGuideLine.atmosphere ? System.encryptDataWithUserKey(serverGuideLine.atmosphere, userEncryptionKey) : null; + const encryptedWritingStyle: string | null = serverGuideLine.writing_style ? System.encryptDataWithUserKey(serverGuideLine.writing_style, userEncryptionKey) : null; + const encryptedThemes: string | null = serverGuideLine.themes ? System.encryptDataWithUserKey(serverGuideLine.themes, userEncryptionKey) : null; + const encryptedSymbolism: string | null = serverGuideLine.symbolism ? System.encryptDataWithUserKey(serverGuideLine.symbolism, userEncryptionKey) : null; + const encryptedMotifs: string | null = serverGuideLine.motifs ? System.encryptDataWithUserKey(serverGuideLine.motifs, userEncryptionKey) : null; + const encryptedNarrativeVoice: string | null = serverGuideLine.narrative_voice ? System.encryptDataWithUserKey(serverGuideLine.narrative_voice, userEncryptionKey) : null; + const encryptedPacing: string | null = serverGuideLine.pacing ? System.encryptDataWithUserKey(serverGuideLine.pacing, userEncryptionKey) : null; + const encryptedKeyMessages: string | null = serverGuideLine.key_messages ? System.encryptDataWithUserKey(serverGuideLine.key_messages, userEncryptionKey) : null; + const encryptedIntendedAudience: string | null = serverGuideLine.intended_audience ? System.encryptDataWithUserKey(serverGuideLine.intended_audience, userEncryptionKey) : null; + if (guideLineExists) { + const updateSuccessful: boolean = GuidelineRepo.updateGuideLine(userId, bookId, encryptedTone, encryptedAtmosphere, encryptedWritingStyle, encryptedThemes, encryptedSymbolism, encryptedMotifs, encryptedNarrativeVoice, encryptedPacing, encryptedKeyMessages, encryptedIntendedAudience, serverGuideLine.last_update, lang); + if (!updateSuccessful) { + return false; + } + } else { + const insertSuccessful: boolean = GuidelineRepo.insertSyncGuideLine(userId, bookId, encryptedTone, encryptedAtmosphere, encryptedWritingStyle, encryptedThemes, encryptedSymbolism, encryptedMotifs, encryptedNarrativeVoice, encryptedPacing, encryptedIntendedAudience, encryptedKeyMessages, serverGuideLine.last_update, lang); + if (!insertSuccessful) { + return false; + } + } + } + } + + if (serverAIGuideLines && serverAIGuideLines.length > 0) { + for (const serverAIGuideLine of serverAIGuideLines) { + const aiGuideLineExists: boolean = GuidelineRepo.aiGuideLineExist(userId, bookId, lang); + const encryptedGlobalResume: string | null = serverAIGuideLine.global_resume ? System.encryptDataWithUserKey(serverAIGuideLine.global_resume, userEncryptionKey) : null; + const encryptedThemes: string | null = serverAIGuideLine.themes ? System.encryptDataWithUserKey(serverAIGuideLine.themes, userEncryptionKey) : null; + const encryptedTone: string | null = serverAIGuideLine.tone ? System.encryptDataWithUserKey(serverAIGuideLine.tone, userEncryptionKey) : null; + const encryptedAtmosphere: string | null = serverAIGuideLine.atmosphere ? System.encryptDataWithUserKey(serverAIGuideLine.atmosphere, userEncryptionKey) : null; + const encryptedCurrentResume: string | null = serverAIGuideLine.current_resume ? System.encryptDataWithUserKey(serverAIGuideLine.current_resume, userEncryptionKey) : null; + if (aiGuideLineExists) { + const updateSuccessful: boolean = GuidelineRepo.insertAIGuideLine(userId, bookId, serverAIGuideLine.narrative_type, serverAIGuideLine.dialogue_type, encryptedGlobalResume, encryptedAtmosphere, serverAIGuideLine.verbe_tense, serverAIGuideLine.langue, encryptedThemes, serverAIGuideLine.last_update, lang); + if (!updateSuccessful) { + return false; + } + } else { + const insertSuccessful: boolean = GuidelineRepo.insertSyncAIGuideLine(userId, bookId, encryptedGlobalResume, encryptedThemes, serverAIGuideLine.verbe_tense, serverAIGuideLine.narrative_type, serverAIGuideLine.langue, serverAIGuideLine.dialogue_type, encryptedTone, encryptedAtmosphere, encryptedCurrentResume, serverAIGuideLine.last_update, lang); + if (!insertSuccessful) { + return false; + } + } + } + } + return true; } @@ -703,8 +789,8 @@ export default class Sync { PlotPointRepository.fetchSyncedPlotPoints(userId, lang), IssueRepository.fetchSyncedIssues(userId, lang), ActRepository.fetchSyncedActSummaries(userId, lang), - GuidelineRepo.fetchSyncedAIGuideLine(userId, lang), - GuidelineRepo.fetchSyncedGuideLine(userId, lang) + GuidelineRepo.fetchSyncedGuideLine(userId, lang), + GuidelineRepo.fetchSyncedAIGuideLine(userId, lang) ]); return allBooks.map((bookRecord: SyncedBookResult): SyncedBook => { diff --git a/electron/database/repositories/guideline.repository.ts b/electron/database/repositories/guideline.repository.ts index 35bf516..cb8694b 100644 --- a/electron/database/repositories/guideline.repository.ts +++ b/electron/database/repositories/guideline.repository.ts @@ -117,24 +117,30 @@ export default class GuidelineRepo { * @returns True if the operation was successful * @throws Error if the guideline cannot be updated or inserted */ - static updateGuideLine( - userId: string, - bookId: string, - encryptedTone: string, - encryptedAtmosphere: string, - encryptedWritingStyle: string, - encryptedThemes: string, - encryptedSymbolism: string, - encryptedMotifs: string, - encryptedNarrativeVoice: string, - encryptedPacing: string, - encryptedKeyMessages: string, - encryptedIntendedAudience: string, - lang: 'fr' | 'en' - ): boolean { + /** + * Updates or inserts a guideline for a specific book. + * If the guideline exists, it updates it; otherwise, it inserts a new one. + * @param userId - The user identifier + * @param bookId - The book identifier + * @param encryptedTone - The encrypted tone value + * @param encryptedAtmosphere - The encrypted atmosphere value + * @param encryptedWritingStyle - The encrypted writing style value + * @param encryptedThemes - The encrypted themes value + * @param encryptedSymbolism - The encrypted symbolism value + * @param encryptedMotifs - The encrypted motifs value + * @param encryptedNarrativeVoice - The encrypted narrative voice value + * @param encryptedPacing - The encrypted pacing value + * @param encryptedKeyMessages - The encrypted key messages value + * @param encryptedIntendedAudience - The encrypted intended audience value + * @param lastUpdate - The timestamp of the last update + * @param lang - The language for error messages ('fr' or 'en') + * @returns True if the operation was successful + * @throws Error if the guideline cannot be updated or inserted + */ + static updateGuideLine(userId: string, bookId: string, encryptedTone: string | null, encryptedAtmosphere: string | null, encryptedWritingStyle: string | null, encryptedThemes: string | null, encryptedSymbolism: string | null, encryptedMotifs: string | null, encryptedNarrativeVoice: string | null, encryptedPacing: string | null, encryptedKeyMessages: string | null, encryptedIntendedAudience: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); - const updateQuery: string = 'UPDATE book_guide_line SET tone=?, atmosphere=?, writing_style=?, themes=?, symbolism=?, motifs=?, narrative_voice=?, pacing=?, key_messages=?, last_update=? WHERE user_id=? AND book_id=?'; + const updateQuery: string = 'UPDATE book_guide_line SET tone=?, atmosphere=?, writing_style=?, themes=?, symbolism=?, motifs=?, narrative_voice=?, pacing=?, intended_audience=?, key_messages=?, last_update=? WHERE user_id=? AND book_id=?'; const updateParams: SQLiteValue[] = [ encryptedTone, encryptedAtmosphere, @@ -144,8 +150,9 @@ export default class GuidelineRepo { encryptedMotifs, encryptedNarrativeVoice, encryptedPacing, + encryptedIntendedAudience, encryptedKeyMessages, - System.timeStampInSeconds(), + lastUpdate, userId, bookId ]; @@ -168,7 +175,7 @@ export default class GuidelineRepo { encryptedPacing, encryptedIntendedAudience, encryptedKeyMessages, - System.timeStampInSeconds() + lastUpdate ]; const insertResult: RunResult = db.run(insertQuery, insertParams); return insertResult.changes > 0; @@ -196,34 +203,24 @@ export default class GuidelineRepo { * @param verbTense - The verb tense identifier * @param language - The language identifier * @param encryptedThemes - The encrypted themes value + * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the operation was successful * @throws Error if the AI guideline cannot be inserted or updated */ - static insertAIGuideLine( - userId: string, - bookId: string, - narrativeType: number, - dialogueType: number, - encryptedPlotSummary: string, - encryptedToneAtmosphere: string, - verbTense: number, - language: number, - encryptedThemes: string, - lang: 'fr' | 'en' - ): boolean { + static insertAIGuideLine(userId: string, bookId: string, narrativeType: number | null, dialogueType: number | null, encryptedPlotSummary: string | null, encryptedToneAtmosphere: string | null, verbTense: number | null, language: number | null, encryptedThemes: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const updateQuery: string = 'UPDATE book_ai_guide_line SET narrative_type=?, dialogue_type=?, global_resume=?, atmosphere=?, verbe_tense=?, langue=?, themes=?, last_update=? WHERE user_id=? AND book_id=?'; const updateParams: SQLiteValue[] = [ - narrativeType ? narrativeType : null, - dialogueType ? dialogueType : null, + narrativeType, + dialogueType, encryptedPlotSummary, encryptedToneAtmosphere, - verbTense ? verbTense : null, - language ? language : null, + verbTense, + language, encryptedThemes, - System.timeStampInSeconds(), + lastUpdate, userId, bookId ]; @@ -238,14 +235,14 @@ export default class GuidelineRepo { bookId, encryptedPlotSummary, encryptedThemes, - verbTense ? verbTense : null, - narrativeType ? narrativeType : null, - language ? language : null, - dialogueType ? dialogueType : null, + verbTense, + narrativeType, + language, + dialogueType, encryptedToneAtmosphere, encryptedToneAtmosphere, encryptedPlotSummary, - System.timeStampInSeconds() + lastUpdate ]; const insertResult: RunResult = db.run(insertQuery, insertParams); return insertResult.changes > 0; @@ -391,6 +388,54 @@ export default class GuidelineRepo { } } + /** + * Checks if a guideline exists for a specific book. + * @param userId - The unique identifier of the user. + * @param bookId - The unique identifier of the book. + * @param lang - The language for error messages ('fr' or 'en'). + * @returns True if the guideline exists, false otherwise. + */ + static guideLineExist(userId: string, bookId: string, lang: 'fr' | 'en'): boolean { + try { + const db: Database = System.getDb(); + const query: string = 'SELECT 1 FROM book_guide_line WHERE user_id=? AND book_id=?'; + const params: SQLiteValue[] = [userId, bookId]; + const result: Record | undefined = db.get(query, params) as Record | undefined; + return result !== undefined; + } catch (error: unknown) { + if (error instanceof Error) { + console.error(`DB Error: ${error.message}`); + throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence de la ligne directrice.` : `Unable to check guideline existence.`); + } else { + throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); + } + } + } + + /** + * Checks if an AI guideline exists for a specific book. + * @param userId - The unique identifier of the user. + * @param bookId - The unique identifier of the book. + * @param lang - The language for error messages ('fr' or 'en'). + * @returns True if the AI guideline exists, false otherwise. + */ + static aiGuideLineExist(userId: string, bookId: string, lang: 'fr' | 'en'): boolean { + try { + const db: Database = System.getDb(); + const query: string = 'SELECT 1 FROM book_ai_guide_line WHERE user_id=? AND book_id=?'; + const params: SQLiteValue[] = [userId, bookId]; + const result: Record | undefined = db.get(query, params) as Record | undefined; + return result !== undefined; + } catch (error: unknown) { + if (error instanceof Error) { + console.error(`DB Error: ${error.message}`); + throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence de la ligne directrice IA.` : `Unable to check AI guideline existence.`); + } else { + throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); + } + } + } + /** * Inserts a synced AI guideline for a specific book. * @param userId - The user identifier