- 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.
289 lines
13 KiB
TypeScript
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;
|