Files
natreex 64ed90d993 Remove unused components and models for improved maintainability
- Deleted redundant components (`AddActionButton`, `AlertBox`, `AlertStack`, `BackButton`, `CancelButton`, and `CollapsableArea`) and related files.
- Removed unused models (`Book`, `BookSerie`, `BookTables`, `Character`, and `Chapter`) to reduce codebase clutter.
- Updated project structure and references to reflect these removals.
2026-03-22 22:37:31 -04:00

289 lines
13 KiB
TypeScript

'use client'
import React, {ChangeEvent, forwardRef, useContext, useEffect, useImperativeHandle, useState} from "react";
import {BookContext, BookContextProps} from "@/context/BookContext";
import {SessionContext, SessionContextProps} from "@/context/SessionContext";
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
import {LangContext, LangContextProps} from "@/context/LangContext";
import {apiGet, apiPost, apiPut} from "@/lib/api/client";
import {QuillSenseSettingsProps} from "@/lib/types/quillsense";
import {GuideLineAI} from "@/lib/types/book";
import {useTranslations} from '@/lib/i18n';
import TextAreaInput from "@/components/form/TextAreaInput";
import TextInput from "@/components/form/TextInput";
import SelectBox from "@/components/form/SelectBox";
import InputField from "@/components/form/InputField";
import PulseLoader from '@/components/ui/PulseLoader';
import {SettingRef} from "@/lib/types/settings";
import {
advancedDialogueTypes,
advancedNarrativePersons,
beginnerDialogueTypes,
beginnerNarrativePersons,
intermediateDialogueTypes,
intermediateNarrativePersons,
langues,
verbalTime
} from "@/lib/constants/story";
type QuillSenseTab = 'ghostwriter' | 'guideline';
interface QuillSenseSettingProps {
toolEnabled?: boolean;
}
const QuillSenseSetting = forwardRef<SettingRef, QuillSenseSettingProps>(function QuillSenseSetting({toolEnabled}: QuillSenseSettingProps, ref: React.ForwardedRef<SettingRef>): React.JSX.Element | null {
const t = useTranslations();
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
const bookId: string = book?.bookId ?? '';
const userToken: string = session?.accessToken ?? '';
const authorLevel: string = session.user?.writingLevel?.toString() ?? '1';
const [activeTab, setActiveTab] = useState<QuillSenseTab>('ghostwriter');
const [isLoading, setIsLoading] = useState<boolean>(true);
// GhostWriter state
const [advancedPrompt, setAdvancedPrompt] = useState<string>('');
// Guideline AI state
const [plotSummary, setPlotSummary] = useState<string>('');
const [narrativeType, setNarrativeType] = useState<string>('');
const [verbTense, setVerbTense] = useState<string>('');
const [dialogueType, setDialogueType] = useState<string>('');
const [toneAtmosphere, setToneAtmosphere] = useState<string>('');
const [language, setLanguage] = useState<string>('');
const [themes, setThemes] = useState<string>('');
useImperativeHandle(ref, (): SettingRef => ({
handleSave: activeTab === 'ghostwriter' ? handleSaveGhostWriter : handleSaveGuideline
}));
useEffect((): void => {
if (book?.bookId) {
fetchQuillSenseSettings();
}
}, [book?.bookId]);
useEffect((): void => {
if (activeTab === 'guideline' && !isLoading) {
fetchAIGuideline();
}
}, [activeTab]);
async function fetchQuillSenseSettings(): Promise<void> {
try {
setIsLoading(true);
const settings: QuillSenseSettingsProps = await apiGet<QuillSenseSettingsProps>(
'book/quillsense-settings',
session.accessToken,
lang,
{bookId: book?.bookId}
);
setAdvancedPrompt(settings.advancedPrompt ?? '');
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('quillsenseSetting.unknownError'));
}
} finally {
setIsLoading(false);
}
}
async function fetchAIGuideline(): Promise<void> {
try {
const response: GuideLineAI = await apiGet<GuideLineAI>(`book/ai/guideline`, userToken, lang, {id: bookId});
if (response) {
setPlotSummary(response.globalResume || '');
setVerbTense(response.verbeTense?.toString() || '');
setNarrativeType(response.narrativeType?.toString() || '');
setDialogueType(response.dialogueType?.toString() || '');
setToneAtmosphere(response.atmosphere || '');
setLanguage(response.langue?.toString() || '');
setThemes(response.themes || '');
}
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t("guideLineSetting.errorUnknown"));
}
}
}
async function handleSaveGhostWriter(): Promise<void> {
try {
const updateResult: boolean = await apiPut<boolean>(
'book/quillsense-settings',
{
bookId: book?.bookId,
advancedPrompt: advancedPrompt
},
session.accessToken,
lang
);
if (updateResult) {
successMessage(t('quillsenseSetting.saveSuccess'));
} else {
errorMessage(t('quillsenseSetting.saveError'));
}
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('quillsenseSetting.unknownError'));
}
}
}
async function handleSaveGuideline(): Promise<void> {
try {
const response: boolean = await apiPost<boolean>(
'quillsense/book/guide-line',
{
bookId: bookId,
plotSummary: plotSummary,
verbTense: verbTense,
narrativeType: narrativeType,
dialogueType: dialogueType,
toneAtmosphere: toneAtmosphere,
language: language,
themes: themes,
},
userToken,
lang,
);
if (response) {
successMessage(t("guideLineSetting.saveSuccess"));
} else {
errorMessage(t("guideLineSetting.saveError"));
}
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t("guideLineSetting.errorUnknown"));
}
}
}
if (isLoading) {
return <PulseLoader/>;
}
if (!toolEnabled) {
return null;
}
return (
<div className="space-y-6">
<div className="flex gap-4 border-b border-secondary">
<button
className={`pb-2 text-sm font-medium transition-colors duration-150 ${
activeTab === 'ghostwriter'
? 'border-b-2 border-primary text-text-primary'
: 'text-muted hover:text-text-primary'
}`}
onClick={(): void => setActiveTab('ghostwriter')}
>
GhostWriter
</button>
<button
className={`pb-2 text-sm font-medium transition-colors duration-150 ${
activeTab === 'guideline'
? 'border-b-2 border-primary text-text-primary'
: 'text-muted hover:text-text-primary'
}`}
onClick={(): void => setActiveTab('guideline')}
>
{t("bookSetting.guideLine")}
</button>
</div>
{activeTab === 'ghostwriter' && (
<div className="space-y-4">
<InputField
fieldName={t('quillsenseSetting.advancedPrompt')}
input={
<TextAreaInput
value={advancedPrompt}
setValue={(e: ChangeEvent<HTMLTextAreaElement>): void => setAdvancedPrompt(e.target.value)}
placeholder={t('quillsenseSetting.advancedPromptPlaceholder')}
/>
}
/>
<p className="text-muted text-xs">
{t('quillsenseSetting.advancedPromptDescription')}
</p>
</div>
)}
{activeTab === 'guideline' && (
<div className="space-y-4">
<InputField fieldName={t("guideLineSetting.plotSummary")} input={
<TextAreaInput
value={plotSummary}
setValue={(e: ChangeEvent<HTMLTextAreaElement>): void => setPlotSummary(e.target.value)}
placeholder={t("guideLineSetting.plotSummaryPlaceholder")}
/>
}/>
<InputField fieldName={t("guideLineSetting.toneAtmosphere")} input={
<TextInput
value={toneAtmosphere}
setValue={(e: ChangeEvent<HTMLInputElement>): void => setToneAtmosphere(e.target.value)}
placeholder={t("guideLineSetting.toneAtmospherePlaceholder")}
/>
}/>
<InputField fieldName={t("guideLineSetting.themes")} input={
<TextInput
value={themes}
setValue={(e: ChangeEvent<HTMLInputElement>): void => setThemes(e.target.value)}
placeholder={t("guideLineSetting.themesPlaceholderQuill")}
/>
}/>
<InputField fieldName={t("guideLineSetting.verbTense")} input={
<SelectBox
defaultValue={verbTense}
onChangeCallBack={(event: ChangeEvent<HTMLSelectElement>): void => setVerbTense(event.target.value)}
data={verbalTime}
placeholder={t("guideLineSetting.verbTensePlaceholder")}
/>
}/>
<InputField fieldName={t("guideLineSetting.narrativeType")} input={
<SelectBox defaultValue={narrativeType} data={
authorLevel === '1'
? beginnerNarrativePersons
: authorLevel === '2'
? intermediateNarrativePersons
: advancedNarrativePersons
} onChangeCallBack={(event: ChangeEvent<HTMLSelectElement>): void => {
setNarrativeType(event.target.value)
}} placeholder={t("guideLineSetting.narrativeTypePlaceholder")}/>
}/>
<InputField fieldName={t("guideLineSetting.dialogueType")} input={
<SelectBox defaultValue={dialogueType} data={authorLevel === '1'
? beginnerDialogueTypes
: authorLevel === '2'
? intermediateDialogueTypes
: advancedDialogueTypes}
onChangeCallBack={(event: ChangeEvent<HTMLSelectElement>) => {
setDialogueType(event.target.value)
}} placeholder={t("guideLineSetting.dialogueTypePlaceholder")}/>
}/>
<InputField fieldName={t("guideLineSetting.language")} input={
<SelectBox defaultValue={language} data={langues}
onChangeCallBack={(event: ChangeEvent<HTMLSelectElement>) => {
setLanguage(event.target.value)
}} placeholder={t("guideLineSetting.languagePlaceholder")}/>
}/>
</div>
)}
</div>
);
});
export default QuillSenseSetting;