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.
This commit is contained in:
@@ -1,28 +1,28 @@
|
||||
import {ChangeEvent, RefObject, useContext, useEffect, useRef, useState} from 'react';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import React, {ChangeEvent, useContext, useEffect, useRef, useState} from 'react';
|
||||
import {
|
||||
BarChart2,
|
||||
BookMarked,
|
||||
BookOpen,
|
||||
ChevronRight,
|
||||
Clock,
|
||||
CloudSun,
|
||||
FileText,
|
||||
GraduationCap,
|
||||
Languages,
|
||||
Loader2,
|
||||
LucideIcon,
|
||||
MessageSquare,
|
||||
Music,
|
||||
Pencil,
|
||||
RotateCw,
|
||||
Square,
|
||||
User,
|
||||
UserPen,
|
||||
Wand2,
|
||||
X
|
||||
} from 'lucide-react';
|
||||
import {writingLevel} from "@/lib/constants/user";
|
||||
import {
|
||||
faBookBookmark,
|
||||
faBookOpen,
|
||||
faChartSimple,
|
||||
faChevronRight,
|
||||
faClock,
|
||||
faCloudSun,
|
||||
faComments,
|
||||
faFileLines,
|
||||
faGraduationCap,
|
||||
faLanguage,
|
||||
faMagicWandSparkles,
|
||||
faMusic,
|
||||
faPencilAlt,
|
||||
faRotateRight,
|
||||
faSpinner,
|
||||
faStop,
|
||||
faUserAstronaut,
|
||||
faUserEdit,
|
||||
faX
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {writingLevel} from "@/lib/models/User";
|
||||
import Story, {
|
||||
advancedDialogueTypes,
|
||||
advancedNarrativePersons,
|
||||
advancedPredefinedType,
|
||||
@@ -34,23 +34,27 @@ import Story, {
|
||||
intermediatePredefinedType,
|
||||
langues,
|
||||
verbalTime
|
||||
} from '@/lib/models/Story';
|
||||
} from '@/lib/constants/story';
|
||||
import {presetStoryType} from '@/lib/utils/story';
|
||||
import SelectBox from "@/components/form/SelectBox";
|
||||
import TextInput from "@/components/form/TextInput";
|
||||
import TexteAreaInput from "@/components/form/TexteAreaInput";
|
||||
import {SessionContext} from "@/context/SessionContext";
|
||||
import System from "@/lib/models/System";
|
||||
import {AlertContext} from "@/context/AlertContext";
|
||||
import TextAreaInput from "@/components/form/TextAreaInput";
|
||||
import {SessionContext, SessionContextProps} from "@/context/SessionContext";
|
||||
import {apiPost} from '@/lib/api/client';
|
||||
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
|
||||
import {configs} from "@/lib/configs";
|
||||
import InputField from "@/components/form/InputField";
|
||||
import NumberInput from "@/components/form/NumberInput";
|
||||
import Button from "@/components/ui/Button";
|
||||
import IconButton from "@/components/ui/IconButton";
|
||||
import PulseLoader from "@/components/ui/PulseLoader";
|
||||
import {Editor as TipEditor, EditorContent, useEditor} from "@tiptap/react";
|
||||
import Editor from "@/lib/models/Editor";
|
||||
import {convertToHtml} from "@/lib/utils/editor";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import TextAlign from "@tiptap/extension-text-align";
|
||||
import QuillSense from "@/lib/models/QuillSense";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {getSubLevel, isAnthropicEnabled} from "@/lib/utils/quillsense";
|
||||
import {useTranslations} from '@/lib/i18n';
|
||||
import {LangContext, LangContextProps} from "@/context/LangContext";
|
||||
import {AIUsageContext, AIUsageContextProps} from "@/context/AIUsageContext";
|
||||
import AdvancedGenerationOptions from "@/components/form/AdvancedGenerationOptions";
|
||||
@@ -59,33 +63,35 @@ interface ShortStoryGeneratorProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface TabItem {
|
||||
id: number;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
}
|
||||
|
||||
export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps) {
|
||||
const {session} = useContext(SessionContext);
|
||||
const {errorMessage, infoMessage} = useContext(AlertContext);
|
||||
const {lang} = useContext<LangContextProps>(LangContext)
|
||||
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
|
||||
const {errorMessage, infoMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext)
|
||||
const t = useTranslations();
|
||||
const {setTotalPrice, setTotalCredits} = useContext<AIUsageContextProps>(AIUsageContext)
|
||||
const {setTotalPrice, setTotalCredits}: AIUsageContextProps = useContext<AIUsageContextProps>(AIUsageContext)
|
||||
|
||||
const [tone, setTone] = useState<string>('');
|
||||
const [atmosphere, setAtmosphere] = useState<string>('');
|
||||
const [verbTense, setVerbTense] = useState<string>('0');
|
||||
const [person, setPerson] = useState<string>('0');
|
||||
const [characters, setCharacters] = useState<string>('');
|
||||
const [language, setLanguage] = useState<string>(
|
||||
session.user?.writingLang.toString() ?? '0',
|
||||
);
|
||||
const [language, setLanguage] = useState<string>(session.user?.writingLang?.toString() ?? '0');
|
||||
const [dialogueType, setDialogueType] = useState<string>('0');
|
||||
const [wordsCount, setWordsCount] = useState<number>(500)
|
||||
const [directives, setDirectives] = useState<string>('');
|
||||
const [authorLevel, setAuthorLevel] = useState<string>(
|
||||
session.user?.writingLevel.toString() ?? '0',
|
||||
);
|
||||
const [authorLevel, setAuthorLevel] = useState<string>(session.user?.writingLevel?.toString() ?? '0');
|
||||
const [presetType, setPresetType] = useState<string>('0');
|
||||
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
const [progress, setProgress] = useState<number>(25);
|
||||
const modalRef: RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null);
|
||||
const [isGenerating, setIsGenerating] = useState<boolean>(false);
|
||||
const progressRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [generatedText, setGeneratedText] = useState<string>('');
|
||||
const [generatedStoryTitle, setGeneratedStoryTitle] = useState<string>('');
|
||||
@@ -98,10 +104,16 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
const [useExplicit, setUseExplicit] = useState<boolean>(false);
|
||||
const [useSmart, setUseSmart] = useState<boolean>(false);
|
||||
|
||||
const isAnthropicEnabled: boolean = QuillSense.isAnthropicEnabled(session);
|
||||
const isSubTierTwo: boolean = QuillSense.getSubLevel(session) >= 2;
|
||||
const hasAccess: boolean = isAnthropicEnabled || isSubTierTwo;
|
||||
|
||||
useEffect((): void => {
|
||||
if (progressRef.current) {
|
||||
progressRef.current.style.width = `${progress}%`;
|
||||
}
|
||||
}, [progress]);
|
||||
|
||||
const anthropicEnabled: boolean = isAnthropicEnabled(session);
|
||||
const isSubTierTwo: boolean = getSubLevel(session) >= 2;
|
||||
const hasAccess: boolean = anthropicEnabled || isSubTierTwo;
|
||||
|
||||
const editor: TipEditor | null = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
@@ -122,7 +134,7 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
}, []);
|
||||
|
||||
useEffect((): void => {
|
||||
Story.presetStoryType(
|
||||
presetStoryType(
|
||||
presetType,
|
||||
setTone,
|
||||
setAtmosphere,
|
||||
@@ -140,7 +152,7 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
|
||||
useEffect((): void => {
|
||||
if (editor)
|
||||
editor.commands.setContent(Editor.convertToHtml(generatedText))
|
||||
editor.commands.setContent(convertToHtml(generatedText))
|
||||
getWordCount();
|
||||
}, [editor, generatedText]);
|
||||
|
||||
@@ -151,7 +163,7 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
infoMessage(t("shortStoryGenerator.result.abortSuccess"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleGeneration(): Promise<void> {
|
||||
setIsGenerating(true);
|
||||
setGeneratedText('');
|
||||
@@ -219,12 +231,13 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
totalPrice?: number;
|
||||
totalCost?: number;
|
||||
} = JSON.parse(line.slice(6));
|
||||
|
||||
|
||||
if (data.content && data.content !== 'starting') {
|
||||
accumulatedText += data.content;
|
||||
setGeneratedText(accumulatedText);
|
||||
}
|
||||
|
||||
// Le message final du endpoint avec title, totalPrice, useYourKey, totalCost
|
||||
if (data.title && data.useYourKey !== undefined && data.totalPrice !== undefined) {
|
||||
setGeneratedStoryTitle(data.title);
|
||||
if (data.useYourKey) {
|
||||
@@ -242,7 +255,7 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setIsGenerating(false);
|
||||
setHasGenerated(true);
|
||||
setAbortController(null);
|
||||
@@ -284,10 +297,10 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
}
|
||||
|
||||
async function handleSave(): Promise<void> {
|
||||
let content: string = '';
|
||||
if (editor) content = editor?.state?.doc.toJSON();
|
||||
let content: Record<string, unknown> | string = '';
|
||||
if (editor) content = editor.state.doc.toJSON();
|
||||
try {
|
||||
const bookId: string = await System.authPostToServer<string>(
|
||||
const bookId: string = await apiPost<string>(
|
||||
`quillsense/generate/add`,
|
||||
{
|
||||
title: generatedStoryTitle,
|
||||
@@ -324,106 +337,102 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
if (!hasAccess) {
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 flex items-center justify-center bg-darkest-background/80 z-50 backdrop-blur-sm">
|
||||
className="fixed inset-0 flex items-center justify-center p-4 bg-darkest-background/60 z-50 backdrop-blur-md animate-fadeIn">
|
||||
<div
|
||||
className="bg-dark-background text-text-primary rounded-lg border border-secondary shadow-xl w-full max-w-md p-6">
|
||||
className="relative bg-tertiary text-text-primary rounded-xl overflow-hidden w-full max-w-md p-6">
|
||||
<h2 className="flex items-center font-['ADLaM_Display'] text-xl text-text-primary mb-4">
|
||||
<FontAwesomeIcon icon={faMagicWandSparkles} className="mr-3 w-5 h-5"/>
|
||||
<Wand2 className="mr-3 w-5 h-5" strokeWidth={1.75}/>
|
||||
{t("shortStoryGenerator.accessDenied.title")}
|
||||
</h2>
|
||||
<p className="text-text-secondary mb-6">
|
||||
{t("shortStoryGenerator.accessDenied.message")}
|
||||
</p>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-full bg-primary text-text-primary px-4 py-2 rounded-lg hover:bg-primary-dark transition-colors"
|
||||
>
|
||||
<Button variant="primary" onClick={onClose} fullWidth>
|
||||
{t("shortStoryGenerator.accessDenied.close")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-overlay z-40 backdrop-blur-sm">
|
||||
<div ref={modalRef}
|
||||
className="bg-tertiary/90 backdrop-blur-sm text-text-primary rounded-2xl border border-secondary/50 shadow-2xl w-full max-w-4xl max-h-[90vh] flex flex-col">
|
||||
<div
|
||||
className="fixed inset-0 flex items-center justify-center p-4 bg-darkest-background/60 z-40 backdrop-blur-md animate-fadeIn">
|
||||
<div
|
||||
className="relative bg-tertiary text-text-primary rounded-xl max-h-[90vh] overflow-hidden flex flex-col w-full max-w-4xl">
|
||||
|
||||
<div className="flex justify-between items-center bg-primary px-6 py-4 rounded-t-2xl shadow-md">
|
||||
<div className="flex justify-between items-center px-6 py-4">
|
||||
<h2 className="font-['ADLaM_Display'] text-xl text-text-primary flex items-center">
|
||||
<FontAwesomeIcon icon={faMagicWandSparkles} className="mr-3 w-5 h-5"/>
|
||||
<Wand2 className="mr-3 w-5 h-5" strokeWidth={1.75}/>
|
||||
{t("shortStoryGenerator.title")}
|
||||
</h2>
|
||||
<button
|
||||
className="text-text-primary hover:bg-primary-dark p-2 rounded-xl transition-all duration-200 hover:scale-110"
|
||||
onClick={onClose}
|
||||
disabled={isGenerating}
|
||||
>
|
||||
<FontAwesomeIcon icon={faX} className="w-5 h-5"/>
|
||||
</button>
|
||||
<IconButton icon={X} variant="light" onClick={onClose} disabled={isGenerating}/>
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 border-b border-secondary/50">
|
||||
<div className="w-full bg-secondary/50 rounded-full h-2.5 shadow-inner">
|
||||
<div
|
||||
className="bg-primary h-2.5 rounded-full transition-all duration-300 shadow-sm"
|
||||
style={{width: `${progress}%`}}
|
||||
/>
|
||||
<div className="flex-1 min-h-0 bg-darkest-background rounded-xl mx-2 flex flex-col overflow-hidden">
|
||||
<div className="px-4 pt-4 pb-2">
|
||||
<div className="w-full bg-secondary rounded-full h-2">
|
||||
<div
|
||||
ref={progressRef}
|
||||
className="bg-primary h-2 rounded-full transition-all duration-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex border-b border-secondary/50">
|
||||
{[
|
||||
{id: 1, label: t("shortStoryGenerator.tabs.basics"), icon: faBookOpen},
|
||||
{id: 2, label: t("shortStoryGenerator.tabs.structure"), icon: faUserEdit},
|
||||
{id: 3, label: t("shortStoryGenerator.tabs.atmosphere"), icon: faCloudSun},
|
||||
...(hasGenerated || isGenerating ? [{
|
||||
id: 4,
|
||||
label: t("shortStoryGenerator.tabs.result"),
|
||||
icon: faFileLines
|
||||
}] : [])
|
||||
].map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
disabled={isGenerating}
|
||||
className={`flex items-center px-6 py-3 font-medium transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'text-primary border-b-2 border-primary bg-primary/5'
|
||||
: 'text-text-secondary hover:text-text-primary'
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={tab.icon} className="mr-2 w-4 h-4"/>
|
||||
{tab.label}
|
||||
{tab.id === 4 && isGenerating && !generatedText && (
|
||||
<FontAwesomeIcon icon={faSpinner} className="ml-2 animate-spin w-4 h-4"/>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
|
||||
<div className="flex border-b border-secondary">
|
||||
{([
|
||||
{id: 1, label: t("shortStoryGenerator.tabs.basics"), icon: BookOpen},
|
||||
{id: 2, label: t("shortStoryGenerator.tabs.structure"), icon: UserPen},
|
||||
{id: 3, label: t("shortStoryGenerator.tabs.atmosphere"), icon: CloudSun},
|
||||
...(hasGenerated || isGenerating ? [{
|
||||
id: 4,
|
||||
label: t("shortStoryGenerator.tabs.result"),
|
||||
icon: FileText
|
||||
}] : [])
|
||||
] satisfies TabItem[]).map((tab: TabItem): React.JSX.Element => {
|
||||
const TabIcon: LucideIcon = tab.icon;
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={(): void => setActiveTab(tab.id)}
|
||||
disabled={isGenerating}
|
||||
className={`flex items-center px-6 py-3 font-medium transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'text-primary border-b-2 border-primary bg-primary/5'
|
||||
: 'text-text-secondary hover:text-text-primary'
|
||||
}`}
|
||||
>
|
||||
<TabIcon className="mr-2 w-4 h-4" strokeWidth={1.75}/>
|
||||
{tab.label}
|
||||
{tab.id === 4 && isGenerating && !generatedText && (
|
||||
<Loader2 className="ml-2 animate-spin w-4 h-4" strokeWidth={1.75}/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0 overflow-auto custom-scrollbar">
|
||||
{activeTab === 1 && (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="p-5 space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<InputField
|
||||
icon={faGraduationCap}
|
||||
icon={GraduationCap}
|
||||
fieldName={t("shortStoryGenerator.fields.complexity")}
|
||||
input={
|
||||
<SelectBox
|
||||
onChangeCallBack={(e) => setAuthorLevel(e.target.value)}
|
||||
onChangeCallBack={(e: ChangeEvent<HTMLSelectElement>): void => setAuthorLevel(e.target.value)}
|
||||
data={writingLevel}
|
||||
defaultValue={authorLevel}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<InputField
|
||||
icon={faBookOpen}
|
||||
icon={BookOpen}
|
||||
fieldName={t("shortStoryGenerator.fields.preset")}
|
||||
input={
|
||||
<SelectBox
|
||||
onChangeCallBack={(e) => setPresetType(e.target.value)}
|
||||
onChangeCallBack={(e: ChangeEvent<HTMLSelectElement>): void => setPresetType(e.target.value)}
|
||||
data={
|
||||
authorLevel === '1'
|
||||
? beginnerPredefinedType
|
||||
@@ -436,18 +445,18 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
}
|
||||
/>
|
||||
<InputField
|
||||
icon={faLanguage}
|
||||
icon={Languages}
|
||||
fieldName={t("shortStoryGenerator.fields.language")}
|
||||
input={
|
||||
<SelectBox
|
||||
onChangeCallBack={(e) => setLanguage(e.target.value)}
|
||||
onChangeCallBack={(e: ChangeEvent<HTMLSelectElement>): void => setLanguage(e.target.value)}
|
||||
data={langues}
|
||||
defaultValue={language}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<InputField
|
||||
icon={faChartSimple}
|
||||
icon={BarChart2}
|
||||
fieldName={t("shortStoryGenerator.fields.wordCount")}
|
||||
input={
|
||||
<NumberInput
|
||||
@@ -460,27 +469,27 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{activeTab === 2 && (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="p-5 space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<InputField
|
||||
icon={faClock}
|
||||
icon={Clock}
|
||||
fieldName={t("shortStoryGenerator.fields.tense")}
|
||||
input={
|
||||
<SelectBox
|
||||
onChangeCallBack={(e) => setVerbTense(e.target.value)}
|
||||
onChangeCallBack={(e: ChangeEvent<HTMLSelectElement>): void => setVerbTense(e.target.value)}
|
||||
data={verbalTime}
|
||||
defaultValue={verbTense}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<InputField
|
||||
icon={faUserEdit}
|
||||
icon={UserPen}
|
||||
fieldName={t("shortStoryGenerator.fields.narrative")}
|
||||
input={
|
||||
<SelectBox
|
||||
onChangeCallBack={(e) => setPerson(e.target.value)}
|
||||
onChangeCallBack={(e: ChangeEvent<HTMLSelectElement>): void => setPerson(e.target.value)}
|
||||
data={
|
||||
authorLevel === '1'
|
||||
? beginnerNarrativePersons
|
||||
@@ -493,13 +502,13 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<InputField
|
||||
icon={faComments}
|
||||
icon={MessageSquare}
|
||||
fieldName={t("shortStoryGenerator.fields.dialogue")}
|
||||
input={
|
||||
<SelectBox
|
||||
onChangeCallBack={(e) => setDialogueType(e.target.value)}
|
||||
onChangeCallBack={(e: ChangeEvent<HTMLSelectElement>): void => setDialogueType(e.target.value)}
|
||||
data={
|
||||
authorLevel === '1'
|
||||
? beginnerDialogueTypes
|
||||
@@ -511,12 +520,12 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
<InputField
|
||||
icon={faPencilAlt}
|
||||
icon={Pencil}
|
||||
fieldName={t("shortStoryGenerator.fields.directives")}
|
||||
input={
|
||||
<TexteAreaInput
|
||||
<TextAreaInput
|
||||
value={directives}
|
||||
setValue={(e: ChangeEvent<HTMLTextAreaElement>) => setDirectives(e.target.value)}
|
||||
placeholder={t("shortStoryGenerator.placeholders.directives")}
|
||||
@@ -525,39 +534,35 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{activeTab === 3 && (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<InputField
|
||||
icon={faMusic}
|
||||
fieldName={t("shortStoryGenerator.fields.tone")}
|
||||
input={
|
||||
<TextInput
|
||||
value={tone}
|
||||
setValue={(e: ChangeEvent<HTMLInputElement>) => setTone(e.target.value)}
|
||||
placeholder={t("shortStoryGenerator.placeholders.tone")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<InputField
|
||||
icon={faCloudSun}
|
||||
fieldName={t("shortStoryGenerator.fields.atmosphere")}
|
||||
input={
|
||||
<TextInput
|
||||
value={atmosphere}
|
||||
setValue={(e: ChangeEvent<HTMLInputElement>) => setAtmosphere(e.target.value)}
|
||||
placeholder={t("shortStoryGenerator.placeholders.atmosphere")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-5 space-y-6">
|
||||
<InputField
|
||||
icon={Music}
|
||||
fieldName={t("shortStoryGenerator.fields.tone")}
|
||||
input={
|
||||
<TextInput
|
||||
value={tone}
|
||||
setValue={(e: ChangeEvent<HTMLInputElement>) => setTone(e.target.value)}
|
||||
placeholder={t("shortStoryGenerator.placeholders.tone")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
icon={faUserAstronaut}
|
||||
icon={CloudSun}
|
||||
fieldName={t("shortStoryGenerator.fields.atmosphere")}
|
||||
input={
|
||||
<TextInput
|
||||
value={atmosphere}
|
||||
setValue={(e: ChangeEvent<HTMLInputElement>) => setAtmosphere(e.target.value)}
|
||||
placeholder={t("shortStoryGenerator.placeholders.atmosphere")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
icon={User}
|
||||
fieldName={t("shortStoryGenerator.fields.character")}
|
||||
input={
|
||||
<TextInput
|
||||
@@ -576,120 +581,75 @@ export default function ShortStoryGenerator({onClose}: ShortStoryGeneratorProps)
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{activeTab === 4 && (
|
||||
<div className="p-6">
|
||||
<div className="p-5">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="font-semibold text-lg">
|
||||
{generatedStoryTitle || t("shortStoryGenerator.result.title")}
|
||||
</h3>
|
||||
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{isGenerating ? (
|
||||
<button
|
||||
onClick={handleStopGeneration}
|
||||
className="p-2 rounded-xl bg-red-500 hover:bg-red-600 transition-all duration-200 hover:scale-110 shadow-md"
|
||||
title={t("shortStoryGenerator.actions.stop")}
|
||||
>
|
||||
<FontAwesomeIcon icon={faStop} className="w-4 h-4"/>
|
||||
</button>
|
||||
<IconButton icon={Square} variant="danger" onClick={handleStopGeneration}
|
||||
tooltip={t("shortStoryGenerator.actions.stop")}/>
|
||||
) : generatedText && (
|
||||
<>
|
||||
<button
|
||||
onClick={handleGeneration}
|
||||
className="p-2 rounded-xl bg-secondary/50 hover:bg-secondary transition-all duration-200 hover:scale-110 shadow-sm border border-secondary/50"
|
||||
title={t("shortStoryGenerator.actions.regenerate")}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRotateRight} className="w-4 h-4"/>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="p-2 rounded-xl bg-primary hover:bg-primary-dark transition-all duration-200 hover:scale-110 shadow-md"
|
||||
title={t("shortStoryGenerator.actions.save")}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBookBookmark} className="w-4 h-4"/>
|
||||
</button>
|
||||
<IconButton icon={RotateCw} variant="muted" onClick={handleGeneration}
|
||||
tooltip={t("shortStoryGenerator.actions.regenerate")}/>
|
||||
<IconButton icon={BookMarked} variant="primary" onClick={handleSave}
|
||||
tooltip={t("shortStoryGenerator.actions.save")}/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{isGenerating && !generatedText ? (
|
||||
<div className="flex flex-col items-center justify-center py-20">
|
||||
<FontAwesomeIcon icon={faSpinner}
|
||||
className="animate-spin text-primary mb-4 w-8 h-8"/>
|
||||
<p className="text-text-secondary">{t("shortStoryGenerator.result.generating")}</p>
|
||||
</div>
|
||||
<PulseLoader text={t("shortStoryGenerator.result.generating")} size="lg"/>
|
||||
) : (
|
||||
<div
|
||||
className="bg-darkest-background rounded-lg p-6 overflow-auto max-h-96 fade-in-text">
|
||||
className="rounded-lg p-6 overflow-auto max-h-96 fade-in-text">
|
||||
<EditorContent editor={editor} className="prose prose-invert max-w-none"/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{generatedText && (
|
||||
<div className="flex justify-between items-center mt-4 pt-4 border-t border-secondary">
|
||||
<div className="flex items-center text-sm text-text-secondary">
|
||||
<FontAwesomeIcon icon={faChartSimple} className="mr-2 w-4 h-4"/>
|
||||
<BarChart2 className="mr-2 w-4 h-4" strokeWidth={1.75}/>
|
||||
{totalWordsCount} {t("shortStoryGenerator.result.words")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex justify-between items-center p-6 border-t border-secondary/50 bg-secondary/30 backdrop-blur-sm shadow-inner">
|
||||
<button
|
||||
onClick={() => setActiveTab(Math.max(1, activeTab - 1))}
|
||||
className={`px-4 py-2 rounded-xl transition-all duration-200 flex items-center ${
|
||||
activeTab > 1 && !isGenerating
|
||||
? 'text-text-secondary hover:text-text-primary hover:bg-secondary hover:scale-105 shadow-sm'
|
||||
: 'text-muted cursor-not-allowed'
|
||||
}`}
|
||||
disabled={activeTab === 1 || isGenerating}
|
||||
>
|
||||
<FontAwesomeIcon icon={faChevronRight} className="mr-2 rotate-180 w-4 h-4"/>
|
||||
<div className="flex justify-between items-center px-6 py-4">
|
||||
<Button variant="ghost" onClick={(): void => setActiveTab(Math.max(1, activeTab - 1))}
|
||||
disabled={activeTab === 1 || isGenerating}>
|
||||
<ChevronRight className="rotate-180 w-4 h-4" strokeWidth={1.75}/>
|
||||
{t("shortStoryGenerator.navigation.previous")}
|
||||
</button>
|
||||
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-6 py-2.5 rounded-xl bg-secondary/50 text-text-primary hover:bg-secondary transition-all duration-200 hover:scale-105 shadow-sm border border-secondary/50 font-medium"
|
||||
disabled={isGenerating}
|
||||
>
|
||||
<Button variant="secondary" onClick={onClose} disabled={isGenerating}>
|
||||
{activeTab === 4 && hasGenerated ? t("shortStoryGenerator.navigation.close") : t("shortStoryGenerator.navigation.cancel")}
|
||||
</button>
|
||||
|
||||
</Button>
|
||||
|
||||
{activeTab < 3 ? (
|
||||
<button
|
||||
onClick={() => setActiveTab(activeTab + 1)}
|
||||
disabled={isGenerating}
|
||||
className="px-6 py-2.5 rounded-xl bg-primary text-text-primary hover:bg-primary-dark transition-all duration-200 hover:scale-105 flex items-center disabled:opacity-50 shadow-md hover:shadow-lg font-medium"
|
||||
>
|
||||
<Button variant="primary" onClick={(): void => setActiveTab(activeTab + 1)}
|
||||
disabled={isGenerating}>
|
||||
{t("shortStoryGenerator.navigation.next")}
|
||||
<FontAwesomeIcon icon={faChevronRight} className="ml-2 w-4 h-4"/>
|
||||
</button>
|
||||
<ChevronRight className="w-4 h-4" strokeWidth={1.75}/>
|
||||
</Button>
|
||||
) : activeTab === 3 && (
|
||||
<button
|
||||
onClick={handleGeneration}
|
||||
disabled={isGenerating}
|
||||
className="px-6 py-2.5 rounded-xl bg-primary text-text-primary hover:bg-primary-dark transition-all duration-200 hover:scale-105 flex items-center disabled:opacity-50 shadow-md hover:shadow-lg font-medium"
|
||||
>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faSpinner} className="animate-spin mr-2 w-4 h-4"/>
|
||||
{t("shortStoryGenerator.actions.generating")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faMagicWandSparkles} className="mr-2 w-4 h-4"/>
|
||||
{t("shortStoryGenerator.actions.generate")}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<Button variant="primary" icon={Wand2} onClick={handleGeneration}
|
||||
disabled={isGenerating} isLoading={isGenerating}
|
||||
loadingText={t("shortStoryGenerator.actions.generating")}>
|
||||
{t("shortStoryGenerator.actions.generate")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user