import {ChangeEvent, useContext, useState} from 'react'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import { faBookOpen, faFileImport, faFloppyDisk, faGear, faGhost, faHashtag, faMagicWandSparkles, faPalette, faTags, faX } from '@fortawesome/free-solid-svg-icons'; import {SessionContext} from "@/context/SessionContext"; import {AlertContext} from "@/context/AlertContext"; import {EditorContext} from "@/context/EditorContext"; import System from "@/lib/models/System"; import {BookContext} from "@/context/BookContext"; import {ChapterContext} from "@/context/ChapterContext"; import QSTextGeneratedPreview from "@/components/QSTextGeneratedPreview"; import TextInput from "@/components/form/TextInput"; import InputField from "@/components/form/InputField"; import RadioBox from "@/components/form/RadioBox"; import TexteAreaInput from "@/components/form/TexteAreaInput"; import SubmitButtonWLoading from "@/components/form/SubmitButtonWLoading"; import NumberInput from "@/components/form/NumberInput"; import PanelHeader from "@/components/PanelHeader"; import GhostWriterTags from "@/components/ghostwriter/GhostWriterTags"; import Chapter, {TiptapNode} from "@/lib/models/Chapter"; import GhostWriterSettings from "@/components/ghostwriter/GhostWriterSettings"; import {useTranslations} from "next-intl"; import QuillSense, {AIGeneratedText} from "@/lib/models/QuillSense"; import {AIUsageContext, AIUsageContextProps} from "@/context/AIUsageContext"; import {LangContext} from "@/context/LangContext"; import {configs} from "@/lib/configs"; export default function GhostWriter() { const t = useTranslations(); const {lang} = useContext(LangContext) const {session} = useContext(SessionContext); const {errorMessage, successMessage, infoMessage} = useContext(AlertContext); const {editor} = useContext(EditorContext); const {book} = useContext(BookContext); const {chapter} = useContext(ChapterContext); const {setTotalPrice, setTotalCredits} = useContext(AIUsageContext); const [minWords, setMinWords] = useState(500); const [maxWords, setMaxWords] = useState(1000); const [toneAtmosphere, setToneAtmosphere] = useState(''); const [directive, setDirective] = useState(''); const [type, setType] = useState(0); const [isGenerating, setIsGenerating] = useState(false); const [textGenerated, setTextGenerated] = useState(''); const [isTextGenerated, setIsTextGenerated] = useState(false); const [advanceSettings, setAdvanceSettings] = useState(false); const [advancedPrompt, setAdvancedPrompt] = useState(''); const [showTags, setShowTags] = useState(false); const [taguedCharacters, setTaguedCharacters] = useState([]); const [taguedLocations, setTaguedLocations] = useState([]); const [taguedObjects, setTaguedObjects] = useState([]); const [taguedWorldElements, setTaguedWorldElements] = useState([]); const [abortController, setAbortController] = useState | null>(null); const isGPTEnabled: boolean = QuillSense.isOpenAIEnabled(session); const isSubTierTree: boolean = QuillSense.getSubLevel(session) === 3; const hasAccess: boolean = isGPTEnabled || isSubTierTree; async function showAdvanceSetting(): Promise { if (advanceSettings) { await handleSaveAdvancedSettings(); setAdvanceSettings(false); } else { setAdvanceSettings(true); } } async function handleSaveAdvancedSettings(): Promise { try { if (advancedPrompt.trim() === '') { errorMessage(t('ghostWriter.promptEmpty')); return; } const response: boolean = await System.authPostToServer(`quillsense/ghostwriter/advanced-settings`, { bookId: book?.bookId, advancedPrompt: advancedPrompt }, session.accessToken, lang); if (!response) { errorMessage(t('ghostWriter.errorSaveAdvanced')); return; } successMessage(t('ghostWriter.successSaveAdvanced')); setAdvanceSettings(false); } catch (e: unknown) { if (e instanceof Error) { errorMessage(t('ghostWriter.errorSave')); } else { errorMessage(t('ghostWriter.errorUnknown')); } } } async function handleStopGeneration(): Promise { if (abortController) { await abortController.cancel(); setAbortController(null); infoMessage(t("ghostWriter.abortSuccess")); } } async function handleGenerateGhostWriter(): Promise { setIsGenerating(true); setIsTextGenerated(false); setTextGenerated(''); try { let content: string = ''; if (editor?.getText()) { try { content = editor?.getText(); } catch (e: unknown) { if (e instanceof Error) { errorMessage(t('ghostWriter.errorRetrieveContent')); } else { errorMessage(t('ghostWriter.errorUnknownRetrieveContent')); } } } const response: Response = await fetch(`${configs.apiUrl}quillsense/ghostwriter/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.accessToken}`, }, body: JSON.stringify({ bookId: book?.bookId, minWords: minWords, maxWords: maxWords, toneAtmosphere: toneAtmosphere, directive: directive, positionType: type, content: content, tags: { characters: taguedCharacters, locations: taguedLocations, objects: taguedObjects, worldElements: taguedWorldElements, }, }), }); if (!response.ok) { const error: { message?: string } = await response.json(); errorMessage(error.message || t('ghostWriter.errorGenerate')); setIsGenerating(false); return; } const reader: ReadableStreamDefaultReader | undefined = response.body?.getReader(); const decoder: TextDecoder = new TextDecoder(); let accumulatedText: string = ''; if (!reader) { errorMessage(t('ghostWriter.errorGenerate')); setIsGenerating(false); return; } setAbortController(reader); while (true) { try { const {done, value}: ReadableStreamReadResult = await reader.read(); if (done) break; const chunk: string = decoder.decode(value, {stream: true}); const lines: string[] = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const dataStr: string = line.slice(6); const data: { content?: string; totalCost?: number; totalPrice?: number; useYourKey?: boolean; aborted?: boolean; } = JSON.parse(dataStr); // Si c'est le message final avec les totaux if ('totalCost' in data && 'useYourKey' in data && 'totalPrice' in data) { console.log(data) if (data.useYourKey) { setTotalPrice((prev: number): number => prev + data.totalPrice!); } else { setTotalCredits(data.totalPrice!); } } else if ('content' in data && data.content && data.content !== 'starting') { accumulatedText += data.content; setTextGenerated(accumulatedText); } } catch (e: unknown) { console.error('Error parsing SSE data:', e); } } } } catch (e: unknown) { break; } } setIsGenerating(false); setIsTextGenerated(true); setAbortController(null); } catch (e: unknown) { setIsGenerating(false); setAbortController(null); if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t('ghostWriter.errorUnknown')); } } } async function importPrompt(): Promise { try { const response: TiptapNode = await System.authGetQueryToServer( `chapter/content`, session.accessToken, lang, { chapterid: chapter?.chapterId, version: 1 }, ) if (!response) { errorMessage(t('ghostWriter.noContentFound')); return; } const content: string = System.htmlToText(Chapter.convertTiptapToHTML(response)); setDirective(content); } catch (e: unknown) { if (e instanceof Error) { errorMessage(e.message); } else { errorMessage(t('ghostWriter.errorUnknownImport')); } } } function insertText(): void { if (editor && textGenerated) { editor.commands.focus('end'); if (editor.getText().length > 0) { editor.commands.insertContent('\n\n'); } editor.commands.insertContent(System.textContentToHtml(textGenerated)); setIsTextGenerated(false); } } if (!hasAccess) { return (

{t("ghostWriter.title")}

{t("ghostWriter.subscriptionRequired")}

); } return (
{ showTags ? ( ) : !showTags && !advanceSettings ? (

{t("ghostWriter.length")}

} /> } />
} />
) => setToneAtmosphere(e.target.value)} placeholder={t("ghostWriter.tonePlaceholder")} /> } />
) => setDirective(e.target.value)} placeholder={t("ghostWriter.directivePlaceholder")} /> } />
) : advanceSettings && ( ) }
{ advanceSettings && ( ) }
{(isTextGenerated || isGenerating) && ( setIsTextGenerated(false)} onRefresh={(): Promise => handleGenerateGhostWriter()} value={textGenerated} onInsert={insertText} isGenerating={isGenerating} onStop={handleStopGeneration} /> )}
); }