Add QuillSense support with settings and integration
- Introduced new "QuillSense" feature for AI-assisted book creation. - Added QuillSense settings panel with advanced options and disable/enable functionality. - Updated GhostWriter and TextEditor components to respect QuillSense settings. - Extended models and repositories to track and manage QuillSense state per book. - Localized new strings for English and French.
This commit is contained in:
@@ -77,6 +77,7 @@ export default function ScribeControllerBar() {
|
||||
publicationDate: response.desiredReleaseDate,
|
||||
desiredWordCount: response.desiredWordCount,
|
||||
totalWordCount: response.desiredWordCount,
|
||||
quillsenseEnabled: response.quillsenseEnabled,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
@@ -141,7 +142,7 @@ export default function ScribeControllerBar() {
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
{
|
||||
hasAccess &&
|
||||
hasAccess && book?.quillsenseEnabled !== false &&
|
||||
<CreditCounter isCredit={isSubTierTwo}/>
|
||||
}
|
||||
<div
|
||||
|
||||
@@ -266,6 +266,7 @@ export default function BookList() {
|
||||
totalWordCount: 0,
|
||||
localBook: localBookOnly,
|
||||
coverImage: bookResponse?.coverImage ? 'data:image/jpeg;base64,' + bookResponse.coverImage : '',
|
||||
quillsenseEnabled: bookResponse?.quillsenseEnabled,
|
||||
});
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {RefObject, useRef} from "react";
|
||||
import PanelHeader from "@/components/PanelHeader";
|
||||
import LocationComponent from "@/components/book/settings/locations/LocationComponent";
|
||||
import CharacterComponent from "@/components/book/settings/characters/CharacterComponent";
|
||||
import QuillSenseSetting from "@/components/book/settings/quillsense/QuillSenseSetting";
|
||||
import {useTranslations} from "next-intl"; // Ajouté pour la traduction
|
||||
|
||||
export default function BookSettingOption(
|
||||
@@ -35,7 +36,10 @@ export default function BookSettingOption(
|
||||
const characterRef: RefObject<{ handleSave: () => Promise<void> } | null> = useRef<{
|
||||
handleSave: () => Promise<void>
|
||||
}>(null);
|
||||
|
||||
const quillSenseRef: RefObject<{ handleSave: () => Promise<void> } | null> = useRef<{
|
||||
handleSave: () => Promise<void>
|
||||
}>(null);
|
||||
|
||||
function renderTitle(): string {
|
||||
switch (setting) {
|
||||
case 'basic-information':
|
||||
@@ -54,6 +58,8 @@ export default function BookSettingOption(
|
||||
return t("bookSettingOption.objectsList");
|
||||
case 'goals':
|
||||
return t("bookSettingOption.bookGoals");
|
||||
case 'quillsense':
|
||||
return t("bookSettingOption.quillsense");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -79,6 +85,9 @@ export default function BookSettingOption(
|
||||
case 'characters':
|
||||
characterRef.current?.handleSave();
|
||||
break;
|
||||
case 'quillsense':
|
||||
quillSenseRef.current?.handleSave();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -109,6 +118,8 @@ export default function BookSettingOption(
|
||||
<LocationComponent ref={locationRef}/>
|
||||
) : setting === 'characters' ? (
|
||||
<CharacterComponent ref={characterRef}/>
|
||||
) : setting === 'quillsense' ? (
|
||||
<QuillSenseSetting ref={quillSenseRef}/>
|
||||
) : <div
|
||||
className="text-text-secondary py-4 text-center">{t("bookSettingOption.notAvailable")}</div>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
// Removed Next.js Link import for Electron
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faBook, faGlobe, faListAlt, faMapMarkedAlt, faPencilAlt, faUser} from "@fortawesome/free-solid-svg-icons";
|
||||
import {faBook, faFeather, faGlobe, faListAlt, faMapMarkedAlt, faPencilAlt, faUser} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Dispatch, SetStateAction} from "react";
|
||||
import {IconDefinition} from "@fortawesome/fontawesome-svg-core";
|
||||
import {useTranslations} from "next-intl";
|
||||
@@ -53,6 +53,11 @@ export default function BookSettingSidebar(
|
||||
name: 'bookSetting.characters',
|
||||
icon: faUser
|
||||
},
|
||||
{
|
||||
id: 'quillsense',
|
||||
name: 'bookSetting.quillsense',
|
||||
icon: faFeather
|
||||
},
|
||||
// {
|
||||
// id: 'objects',
|
||||
// name: t('bookSetting.objects'),
|
||||
|
||||
@@ -483,7 +483,7 @@ export default function TextEditor() {
|
||||
onClick={handleShowUserSettings}
|
||||
icon={faCog}
|
||||
/>
|
||||
{chapter?.chapterContent.version === 2 && !isCurrentlyOffline() && !book?.localBook && (
|
||||
{chapter?.chapterContent.version === 2 && !isCurrentlyOffline() && !book?.localBook && book?.quillsenseEnabled !== false && (
|
||||
<CollapsableButton
|
||||
showCollapsable={showGhostWriter}
|
||||
text={t("textEditor.ghostWriter")}
|
||||
|
||||
@@ -260,19 +260,25 @@ export default function GhostWriter() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAccess) {
|
||||
if (!hasAccess || !book?.quillsenseEnabled) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div
|
||||
className="bg-tertiary/90 backdrop-blur-sm p-10 rounded-2xl shadow-2xl text-center border border-secondary/50 max-w-md">
|
||||
<h2 className="text-2xl font-['ADLaM_Display'] text-text-primary mb-4">{t("ghostWriter.title")}</h2>
|
||||
<p className="text-muted mb-6 text-lg leading-relaxed">{t("ghostWriter.subscriptionRequired")}</p>
|
||||
<button
|
||||
onClick={(): string => window.location.href = '/pricing'}
|
||||
className="px-6 py-3 bg-primary text-text-primary rounded-xl hover:bg-primary-dark transition-all duration-200 hover:scale-105 shadow-md hover:shadow-lg font-semibold"
|
||||
>
|
||||
{t("ghostWriter.subscribe")}
|
||||
</button>
|
||||
<p className="text-muted mb-6 text-lg leading-relaxed">
|
||||
{!book?.quillsenseEnabled
|
||||
? t("ghostWriter.quillsenseDisabled")
|
||||
: t("ghostWriter.subscriptionRequired")}
|
||||
</p>
|
||||
{hasAccess && !book?.quillsenseEnabled ? null : (
|
||||
<button
|
||||
onClick={(): string => window.location.href = '/pricing'}
|
||||
className="px-6 py-3 bg-primary text-text-primary rounded-xl hover:bg-primary-dark transition-all duration-200 hover:scale-105 shadow-md hover:shadow-lg font-semibold"
|
||||
>
|
||||
{t("ghostWriter.subscribe")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -171,7 +171,16 @@ export default function ComposerRightBar() {
|
||||
<div className="bg-tertiary border-l border-secondary/50 p-3 flex flex-col space-y-3 shadow-xl">
|
||||
{book ? editorComponents
|
||||
.filter((component: PanelComponent):boolean => {
|
||||
return !((isCurrentlyOffline() || book?.localBook) && component.id === 1);
|
||||
// Filter QuillSense if offline, local book, or quillsenseEnabled is false
|
||||
if (component.id === 1) {
|
||||
if (isCurrentlyOffline() || book?.localBook) {
|
||||
return false;
|
||||
}
|
||||
if (book?.quillsenseEnabled === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((component: PanelComponent) => (
|
||||
<button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Database, QueryResult, RunResult, SQLiteValue } from "node-sqlite3-wasm";
|
||||
import {Database, QueryResult, RunResult, SQLiteValue} from "node-sqlite3-wasm";
|
||||
import System from "../System.js";
|
||||
|
||||
export interface ChapterContentQueryResult extends Record<string, SQLiteValue> {
|
||||
@@ -244,8 +244,7 @@ export default class ChapterContentRepository {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT content_id, chapter_id, author_id, version, content, words_count, time_on_it, last_update FROM book_chapter_content WHERE author_id=? AND chapter_id=?';
|
||||
const params: SQLiteValue[] = [userId, chapterId];
|
||||
const bookChapterContents: BookChapterContentTable[] = db.all(query, params) as BookChapterContentTable[];
|
||||
return bookChapterContents;
|
||||
return db.all(query, params) as BookChapterContentTable[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
@@ -267,8 +266,7 @@ export default class ChapterContentRepository {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT content_id, chapter_id, last_update FROM book_chapter_content WHERE author_id = ?';
|
||||
const params: SQLiteValue[] = [userId];
|
||||
const syncedChapterContents: SyncedChapterContentResult[] = db.all(query, params) as SyncedChapterContentResult[];
|
||||
return syncedChapterContents;
|
||||
return db.all(query, params) as SyncedChapterContentResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
@@ -296,10 +294,7 @@ export default class ChapterContentRepository {
|
||||
static insertSyncChapterContent(contentId: string, chapterId: string, authorId: string, version: number, content: string | null, wordsCount: number, timeOnIt: number, lastUpdate: number, lang: 'fr' | 'en'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = `
|
||||
INSERT INTO book_chapter_content (content_id, chapter_id, author_id, version, content, words_count, time_on_it, last_update)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const query: string = `INSERT INTO book_chapter_content (content_id, chapter_id, author_id, version, content, words_count, time_on_it, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
const params: SQLiteValue[] = [contentId, chapterId, authorId, version, content, wordsCount, timeOnIt, lastUpdate];
|
||||
const insertResult: RunResult = db.run(query, params);
|
||||
return insertResult.changes > 0;
|
||||
@@ -324,8 +319,7 @@ export default class ChapterContentRepository {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT content_id, chapter_id, author_id, version, content, words_count, time_on_it, last_update FROM book_chapter_content WHERE content_id = ?';
|
||||
const params: SQLiteValue[] = [contentId];
|
||||
const completeChapterContent: BookChapterContentTable[] = db.all(query, params) as BookChapterContentTable[];
|
||||
return completeChapterContent;
|
||||
return db.all(query, params) as BookChapterContentTable[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu de chapitre complet.` : `Unable to retrieve complete chapter content.`);
|
||||
|
||||
@@ -450,6 +450,7 @@
|
||||
"characters": "Characters",
|
||||
"objectsList": "Objects list",
|
||||
"bookGoals": "Book goals",
|
||||
"quillsense": "QuillSense Settings",
|
||||
"save": "Save",
|
||||
"notAvailable": "Option not available"
|
||||
},
|
||||
@@ -489,6 +490,7 @@
|
||||
"title": "Ghost Writer",
|
||||
"description": "Turn your ideas into captivating prose",
|
||||
"subscriptionRequired": "You must be subscribed to Quill Sense to use Ghost Writer.",
|
||||
"quillsenseDisabled": "QuillSense is disabled for this book. You can enable it in the book settings.",
|
||||
"subscribe": "Subscribe",
|
||||
"length": "Text length",
|
||||
"minimum": "Minimum",
|
||||
@@ -768,7 +770,8 @@
|
||||
"locations": "Locations",
|
||||
"characters": "Characters",
|
||||
"objects": "Objects",
|
||||
"goals": "Goals"
|
||||
"goals": "Goals",
|
||||
"quillsense": "QuillSense"
|
||||
},
|
||||
"basicInformationSetting": {
|
||||
"error": {
|
||||
@@ -829,7 +832,8 @@
|
||||
"common": {
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"unknownError": "An unknown error occurred"
|
||||
"unknownError": "An unknown error occurred",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"editor": {
|
||||
"error": {
|
||||
@@ -993,5 +997,22 @@
|
||||
"deleteLocalToo": "Also delete local version",
|
||||
"deleteLocalWarning": "Warning: This action will delete the book from the server AND your device. This action is irreversible.",
|
||||
"errorUnknown": "An unknown error occurred while deleting the book."
|
||||
},
|
||||
"quillSenseSetting": {
|
||||
"title": "QuillSense Settings",
|
||||
"description": "Manage AI features for this book.",
|
||||
"enableSection": "Enable/Disable QuillSense",
|
||||
"enableLabel": "QuillSense enabled",
|
||||
"enableDescription": "When enabled, AI features like Ghost Writer and QuillSense will be available for this book.",
|
||||
"advancedSection": "Advanced Settings",
|
||||
"advancedPromptLabel": "Advanced Prompt",
|
||||
"advancedPromptPlaceholder": "Enter custom instructions for the AI...",
|
||||
"advancedPromptHint": "These instructions will be included in every text generation for this book.",
|
||||
"disabledWarning": "QuillSense is disabled. AI features will not be available for this book.",
|
||||
"errorFetch": "Error fetching QuillSense settings.",
|
||||
"errorSave": "Error saving settings.",
|
||||
"errorUnknown": "An unknown error occurred.",
|
||||
"successSave": "QuillSense settings saved successfully.",
|
||||
"noBookSelected": "No book selected."
|
||||
}
|
||||
}
|
||||
@@ -450,6 +450,7 @@
|
||||
"characters": "Les personnages",
|
||||
"objectsList": "Liste des objets",
|
||||
"bookGoals": "Objectifs du livre",
|
||||
"quillsense": "Parametres QuillSense",
|
||||
"save": "Sauvegarder",
|
||||
"notAvailable": "Option non disponible"
|
||||
},
|
||||
@@ -489,6 +490,7 @@
|
||||
"title": "Écrivain Fantôme",
|
||||
"description": "Transformez vos idées en prose captivante",
|
||||
"subscriptionRequired": "Vous devez être abonné à Quill Sense pour utiliser Ghost Writer.",
|
||||
"quillsenseDisabled": "QuillSense est désactivé pour ce livre. Vous pouvez l'activer dans les paramètres du livre.",
|
||||
"subscribe": "S'abonner",
|
||||
"length": "Longueur du texte",
|
||||
"minimum": "Minimum",
|
||||
@@ -769,7 +771,8 @@
|
||||
"locations": "Emplacements",
|
||||
"characters": "Personnages",
|
||||
"objects": "Objets",
|
||||
"goals": "Buts"
|
||||
"goals": "Buts",
|
||||
"quillsense": "QuillSense"
|
||||
},
|
||||
"basicInformationSetting": {
|
||||
"error": {
|
||||
@@ -830,7 +833,8 @@
|
||||
"common": {
|
||||
"cancel": "Annuler",
|
||||
"confirm": "Confirmer",
|
||||
"unknownError": "Une erreur inconnue est survenue"
|
||||
"unknownError": "Une erreur inconnue est survenue",
|
||||
"loading": "Chargement..."
|
||||
},
|
||||
"editor": {
|
||||
"error": {
|
||||
@@ -994,5 +998,22 @@
|
||||
"deleteLocalToo": "Supprimer également la version locale",
|
||||
"deleteLocalWarning": "Attention : Cette action supprimera le livre du serveur ET de votre appareil. Cette action est irréversible.",
|
||||
"errorUnknown": "Une erreur inconnue est survenue lors de la suppression du livre."
|
||||
},
|
||||
"quillSenseSetting": {
|
||||
"title": "Paramètres QuillSense",
|
||||
"description": "Gérez les fonctionnalités d'intelligence artificielle pour ce livre.",
|
||||
"enableSection": "Activer/Désactiver QuillSense",
|
||||
"enableLabel": "QuillSense activé",
|
||||
"enableDescription": "Lorsque activé, les fonctionnalités d'IA comme Ghost Writer et QuillSense seront disponibles pour ce livre.",
|
||||
"advancedSection": "Paramètres avancés",
|
||||
"advancedPromptLabel": "Invite avancée",
|
||||
"advancedPromptPlaceholder": "Entrez des instructions personnalisées pour l'IA...",
|
||||
"advancedPromptHint": "Ces instructions seront incluses dans chaque génération de texte pour ce livre.",
|
||||
"disabledWarning": "QuillSense est désactivé. Les fonctionnalités d'IA ne seront pas disponibles pour ce livre.",
|
||||
"errorFetch": "Erreur lors de la récupération des paramètres QuillSense.",
|
||||
"errorSave": "Erreur lors de la sauvegarde des paramètres.",
|
||||
"errorUnknown": "Une erreur inconnue est survenue.",
|
||||
"successSave": "Paramètres QuillSense sauvegardés avec succès.",
|
||||
"noBookSelected": "Aucun livre sélectionné."
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,7 @@ export interface BookProps {
|
||||
coverImage?: string;
|
||||
localBook?: boolean;
|
||||
chapters?: ChapterProps[];
|
||||
quillsenseEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface BookListProps {
|
||||
@@ -87,6 +88,7 @@ export interface BookListProps {
|
||||
coverImage?: string;
|
||||
bookMeta?: string;
|
||||
itIsLocal?: boolean;
|
||||
quillsenseEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface GuideLine {
|
||||
|
||||
@@ -194,4 +194,11 @@ export interface LocationSubElementTable {
|
||||
original_name: string;
|
||||
sub_elem_description: string | null;
|
||||
last_update: number;
|
||||
}
|
||||
|
||||
export interface GhostWriterSettingsTable {
|
||||
book_id: string;
|
||||
user_id: string;
|
||||
advanced_prompt: string | null;
|
||||
quillsense_enabled: number;
|
||||
}
|
||||
Reference in New Issue
Block a user