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:
natreex
2026-01-13 19:52:31 -05:00
parent 8bad6159cf
commit 306262caba
12 changed files with 106 additions and 28 deletions

View File

@@ -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

View File

@@ -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) {

View File

@@ -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>
}

View File

@@ -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'),

View File

@@ -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")}

View File

@@ -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>
);

View File

@@ -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