Multi lingua, hotfixes and design

This commit is contained in:
Ichitux
2026-04-05 03:56:31 +02:00
parent 594b50b77f
commit 6cd1bf305d
29 changed files with 3157 additions and 562 deletions

File diff suppressed because one or more lines are too long

1
client/dist/assets/index-CZFZrYeL.css vendored Normal file

File diff suppressed because one or more lines are too long

453
client/dist/assets/index-D3_QgWye.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Pacifico&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-9IOnhCkb.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DS0xE7Ko.css">
<script type="module" crossorigin src="/assets/index-D3_QgWye.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CZFZrYeL.css">
</head>
<body>
<div id="root"></div>

View File

@@ -1,100 +1,145 @@
{
"hash": "deb88d1e",
"hash": "e20034a6",
"configHash": "e64eb32e",
"lockfileHash": "fe4eb8a2",
"browserHash": "c9f0ad5b",
"lockfileHash": "b223a39a",
"browserHash": "4e2530e7",
"optimized": {
"react": {
"src": "../../../../node_modules/react/index.js",
"file": "react.js",
"fileHash": "80d72dea",
"fileHash": "bc47155f",
"needsInterop": true
},
"react-dom": {
"src": "../../../../node_modules/react-dom/index.js",
"file": "react-dom.js",
"fileHash": "3f8ff889",
"fileHash": "f3537af4",
"needsInterop": true
},
"react/jsx-dev-runtime": {
"src": "../../../../node_modules/react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "05119472",
"fileHash": "dd4d0058",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../../../node_modules/react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "e0024c91",
"fileHash": "49f980be",
"needsInterop": true
},
"@tanstack/react-query": {
"src": "../../../../node_modules/@tanstack/react-query/build/modern/index.js",
"file": "@tanstack_react-query.js",
"fileHash": "4d070117",
"fileHash": "cb583fa8",
"needsInterop": false
},
"axios": {
"src": "../../../../node_modules/axios/index.js",
"file": "axios.js",
"fileHash": "a4419c2e",
"fileHash": "b1a573d1",
"needsInterop": false
},
"lucide-react": {
"src": "../../../../node_modules/lucide-react/dist/esm/lucide-react.js",
"file": "lucide-react.js",
"fileHash": "92a1456b",
"fileHash": "14fb8e72",
"needsInterop": false
},
"react-dom/client": {
"src": "../../../../node_modules/react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "86bf4335",
"fileHash": "5d2ebd48",
"needsInterop": true
},
"react-konva": {
"src": "../../../../node_modules/react-konva/es/ReactKonva.js",
"file": "react-konva.js",
"fileHash": "56221f90",
"fileHash": "a0e0f67f",
"needsInterop": false
},
"react-router-dom": {
"src": "../../../../node_modules/react-router-dom/dist/index.js",
"file": "react-router-dom.js",
"fileHash": "457f2587",
"fileHash": "fec8af05",
"needsInterop": false
},
"zustand": {
"src": "../../../../node_modules/zustand/esm/index.mjs",
"file": "zustand.js",
"fileHash": "848a6c38",
"fileHash": "35ad42af",
"needsInterop": false
},
"i18next": {
"src": "../../../../node_modules/i18next/dist/esm/i18next.js",
"file": "i18next.js",
"fileHash": "3d2d9026",
"fileHash": "9acaef61",
"needsInterop": false
},
"react-i18next": {
"src": "../../../../node_modules/react-i18next/dist/es/index.js",
"file": "react-i18next.js",
"fileHash": "78d9a217",
"fileHash": "b0e3e0fb",
"needsInterop": false
},
"i18next-browser-languagedetector": {
"src": "../../../../node_modules/i18next-browser-languagedetector/dist/esm/i18nextBrowserLanguageDetector.js",
"file": "i18next-browser-languagedetector.js",
"fileHash": "09be5c13",
"fileHash": "4cc94160",
"needsInterop": false
},
"@tiptap/react": {
"src": "../../../../node_modules/@tiptap/react/dist/index.js",
"file": "@tiptap_react.js",
"fileHash": "b3a13a61",
"needsInterop": false
},
"@tiptap/starter-kit": {
"src": "../../../../node_modules/@tiptap/starter-kit/dist/index.js",
"file": "@tiptap_starter-kit.js",
"fileHash": "a44103b6",
"needsInterop": false
},
"@tiptap/extension-underline": {
"src": "../../../../node_modules/@tiptap/extension-underline/dist/index.js",
"file": "@tiptap_extension-underline.js",
"fileHash": "1e7a61a1",
"needsInterop": false
},
"@tiptap/extension-text-align": {
"src": "../../../../node_modules/@tiptap/extension-text-align/dist/index.js",
"file": "@tiptap_extension-text-align.js",
"fileHash": "cd5adcf1",
"needsInterop": false
},
"@tiptap/extension-placeholder": {
"src": "../../../../node_modules/@tiptap/extension-placeholder/dist/index.js",
"file": "@tiptap_extension-placeholder.js",
"fileHash": "8fd72abc",
"needsInterop": false
}
},
"chunks": {
"chunk-DRQXKEUS": {
"file": "chunk-DRQXKEUS.js"
},
"chunk-JYSI5OBP": {
"file": "chunk-JYSI5OBP.js"
},
"chunk-6FQSNAE3": {
"file": "chunk-6FQSNAE3.js"
},
"chunk-CITXGXPZ": {
"file": "chunk-CITXGXPZ.js"
},
"chunk-DRQXKEUS": {
"file": "chunk-DRQXKEUS.js"
"chunk-KMJOTAPQ": {
"file": "chunk-KMJOTAPQ.js"
},
"chunk-N2AN2IIV": {
"file": "chunk-N2AN2IIV.js"
},
"chunk-DBGWLXJ5": {
"file": "chunk-DBGWLXJ5.js"
},
"chunk-TJ6I66L4": {
"file": "chunk-TJ6I66L4.js"
@@ -102,9 +147,6 @@
"chunk-RMSUKPN2": {
"file": "chunk-RMSUKPN2.js"
},
"chunk-JYSI5OBP": {
"file": "chunk-JYSI5OBP.js"
},
"chunk-7URR3GLA": {
"file": "chunk-7URR3GLA.js"
},

View File

@@ -1,9 +1,9 @@
import {
require_shim
} from "./chunk-CITXGXPZ.js";
import {
keysFromSelector
} from "./chunk-DRQXKEUS.js";
import {
require_shim
} from "./chunk-CITXGXPZ.js";
import {
require_react
} from "./chunk-7URR3GLA.js";

View File

@@ -1,93 +1,14 @@
import {
require_shim
} from "./chunk-CITXGXPZ.js";
require_with_selector
} from "./chunk-6FQSNAE3.js";
import "./chunk-CITXGXPZ.js";
import {
require_react
} from "./chunk-7URR3GLA.js";
import {
__commonJS,
__toESM
} from "./chunk-4MBMRILA.js";
// ../node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js
var require_with_selector_development = __commonJS({
"../node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js"(exports) {
"use strict";
(function() {
function is(x, y) {
return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
}
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
var React = require_react(), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is, useSyncExternalStore = shim.useSyncExternalStore, useRef = React.useRef, useEffect = React.useEffect, useMemo = React.useMemo, useDebugValue2 = React.useDebugValue;
exports.useSyncExternalStoreWithSelector = function(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
var instRef = useRef(null);
if (null === instRef.current) {
var inst = { hasValue: false, value: null };
instRef.current = inst;
} else inst = instRef.current;
instRef = useMemo(
function() {
function memoizedSelector(nextSnapshot) {
if (!hasMemo) {
hasMemo = true;
memoizedSnapshot = nextSnapshot;
nextSnapshot = selector(nextSnapshot);
if (void 0 !== isEqual && inst.hasValue) {
var currentSelection = inst.value;
if (isEqual(currentSelection, nextSnapshot))
return memoizedSelection = currentSelection;
}
return memoizedSelection = nextSnapshot;
}
currentSelection = memoizedSelection;
if (objectIs(memoizedSnapshot, nextSnapshot))
return currentSelection;
var nextSelection = selector(nextSnapshot);
if (void 0 !== isEqual && isEqual(currentSelection, nextSelection))
return memoizedSnapshot = nextSnapshot, currentSelection;
memoizedSnapshot = nextSnapshot;
return memoizedSelection = nextSelection;
}
var hasMemo = false, memoizedSnapshot, memoizedSelection, maybeGetServerSnapshot = void 0 === getServerSnapshot ? null : getServerSnapshot;
return [
function() {
return memoizedSelector(getSnapshot());
},
null === maybeGetServerSnapshot ? void 0 : function() {
return memoizedSelector(maybeGetServerSnapshot());
}
];
},
[getSnapshot, getServerSnapshot, selector, isEqual]
);
var value = useSyncExternalStore(subscribe, instRef[0], instRef[1]);
useEffect(
function() {
inst.hasValue = true;
inst.value = value;
},
[value]
);
useDebugValue2(value);
return value;
};
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
})();
}
});
// ../node_modules/use-sync-external-store/shim/with-selector.js
var require_with_selector = __commonJS({
"../node_modules/use-sync-external-store/shim/with-selector.js"(exports, module) {
"use strict";
if (false) {
module.exports = null;
} else {
module.exports = require_with_selector_development();
}
}
});
// ../node_modules/zustand/esm/vanilla.mjs
var createStoreImpl = (createState) => {
let state;
@@ -170,17 +91,4 @@ export {
react as default,
useStore
};
/*! Bundled license information:
use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.development.js:
(**
* @license React
* use-sync-external-store-shim/with-selector.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=zustand.js.map

File diff suppressed because one or more lines are too long

View File

@@ -14,6 +14,12 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-query": "^5.17.0",
"@tiptap/extension-placeholder": "^3.22.2",
"@tiptap/extension-text-align": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@tiptap/react": "^3.22.2",
"@tiptap/starter-kit": "^3.22.2",
"axios": "^1.6.5",
"clsx": "^2.1.0",
"i18next": "^26.0.3",

View File

@@ -1,4 +1,4 @@
import { Routes, Route, Navigate } from 'react-router-dom';
import { Routes, Route } from 'react-router-dom';
import { BookOpen, Image, Settings } from 'lucide-react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
@@ -7,6 +7,7 @@ import AuthModal from './components/auth/AuthModal';
import CoverDesigner from './components/cover-designer/CoverDesigner';
import BookGenerator from './components/book-generator/BookGenerator';
import SettingsPage from './components/SettingsPage';
import OnboardingPage from './components/OnboardingPage';
function App() {
const { t } = useTranslation();
@@ -23,12 +24,12 @@ function App() {
<header className="bg-white border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center gap-2">
<a href="/" className="flex items-center gap-2 no-underline">
<div className="w-8 h-8 bg-gradient-to-br from-primary-500 to-accent-500 rounded-lg flex items-center justify-center">
<BookOpen className="w-5 h-5 text-white" />
</div>
<h1 className="text-xl font-bold text-gray-900">CreaBook</h1>
</div>
</a>
{/* Navigation */}
<nav className="flex items-center gap-1">
@@ -84,7 +85,7 @@ function App() {
{/* Main Content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Routes>
<Route path="/" element={<Navigate to="/covers" replace />} />
<Route path="/" element={<OnboardingPage />} />
<Route path="/covers" element={<CoverDesigner />} />
<Route path="/books" element={<BookGenerator />} />
<Route path="/settings" element={<SettingsPage />} />

View File

@@ -0,0 +1,124 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Globe, BookOpen, Wand2, Edit3 } from 'lucide-react';
import { booksApi } from '../services/api';
import { useQuery } from '@tanstack/react-query';
export default function OnboardingPage() {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const [idea, setIdea] = useState('');
const [genre, setGenre] = useState('fiction');
const { data: genresData } = useQuery({
queryKey: ['genres'],
queryFn: async () => {
const response = await booksApi.getGenres();
return response.data.genres;
},
});
const handleLanguageChange = (lang: string) => {
i18n.changeLanguage(lang);
};
const startJourney = (useAI: boolean) => {
navigate('/books', { state: { autoBook: { idea, genre, useAI } } });
};
return (
<div className="max-w-3xl mx-auto py-12 px-6">
<div className="text-center mb-12">
<h1 className="text-4xl font-extrabold text-gray-900 mb-4">{t('onboarding.title')}</h1>
<p className="text-lg text-gray-600">{t('onboarding.subtitle')}</p>
</div>
<div className="space-y-12">
{/* Step 1: Language */}
<section className="card">
<h2 className="text-xl font-bold flex items-center gap-2 mb-4">
<Globe className="text-primary-500 w-6 h-6" />
{t('onboarding.step1')}
</h2>
<div className="flex gap-4">
<button
onClick={() => handleLanguageChange('en')}
className={`flex-1 py-3 px-4 rounded-lg font-medium transition-all ${
i18n.language === 'en'
? 'bg-primary-500 text-white shadow-md'
: 'bg-white text-gray-700 border border-gray-200 hover:bg-gray-50'
}`}
>
English
</button>
<button
onClick={() => handleLanguageChange('es')}
className={`flex-1 py-3 px-4 rounded-lg font-medium transition-all ${
i18n.language === 'es'
? 'bg-primary-500 text-white shadow-md'
: 'bg-white text-gray-700 border border-gray-200 hover:bg-gray-50'
}`}
>
Español
</button>
</div>
</section>
{/* Step 2: Book Idea */}
<section className="card">
<h2 className="text-xl font-bold flex items-center gap-2 mb-4">
<BookOpen className="text-primary-500 w-6 h-6" />
{t('onboarding.step2')}
</h2>
<div className="space-y-4">
<div>
<label className="label">{t('onboarding.genreLabel')}</label>
<select
value={genre}
onChange={(e) => setGenre(e.target.value)}
className="input bg-white"
>
{genresData?.map((g: any) => (
<option key={g.id} value={g.id}>
{String(t(`genres.${g.id}.name`, g.name))}
</option>
))}
</select>
</div>
<div>
<textarea
rows={4}
value={idea}
onChange={(e) => setIdea(e.target.value)}
placeholder={t('onboarding.ideaPlaceholder')}
className="input text-lg resize-none"
/>
</div>
</div>
</section>
{/* Step 3: Progression */}
<section className="text-center pt-4">
<h2 className="text-xl font-bold mb-6">{t('onboarding.step3')}</h2>
<div className="grid sm:grid-cols-2 gap-4">
<button
onClick={() => startJourney(false)}
className="flex flex-col items-center justify-center p-6 bg-white border-2 border-gray-200 hover:border-gray-400 rounded-xl transition-all"
>
<Edit3 className="w-10 h-10 text-gray-500 mb-3" />
<span className="font-semibold text-gray-800 text-lg">{t('onboarding.btnFree')}</span>
</button>
<button
onClick={() => startJourney(true)}
className="flex flex-col items-center justify-center p-6 bg-primary-50 border-2 border-primary-500 hover:bg-primary-100 rounded-xl transition-all transform hover:scale-[1.02]"
>
<Wand2 className="w-10 h-10 text-primary-600 mb-3" />
<span className="font-semibold text-primary-800 text-lg">{t('onboarding.btnAI')}</span>
</button>
</div>
</section>
</div>
</div>
);
}

View File

@@ -0,0 +1,219 @@
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import Placeholder from '@tiptap/extension-placeholder';
import {
Bold, Italic, Underline as UnderlineIcon, Strikethrough,
List, ListOrdered, AlignLeft, AlignCenter, AlignRight,
Undo2, Redo2, Heading1, Heading2, Heading3, Quote, Minus,
} from 'lucide-react';
import { useEffect } from 'react';
interface RichTextEditorProps {
content: string;
onChange: (html: string) => void;
placeholder?: string;
}
function ToolbarButton({
onClick,
active,
children,
title,
}: {
onClick: () => void;
active?: boolean;
children: React.ReactNode;
title: string;
}) {
return (
<button
type="button"
onClick={onClick}
title={title}
className={`p-1.5 rounded transition-colors ${
active
? 'bg-primary-100 text-primary-700'
: 'text-gray-500 hover:bg-gray-100 hover:text-gray-700'
}`}
>
{children}
</button>
);
}
export default function RichTextEditor({ content, onChange, placeholder }: RichTextEditorProps) {
const editor = useEditor({
extensions: [
StarterKit.configure({
heading: { levels: [1, 2, 3] },
}),
Underline,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
Placeholder.configure({
placeholder: placeholder || 'Start writing...',
}),
],
content,
onUpdate: ({ editor }) => {
onChange(editor.getHTML());
},
editorProps: {
attributes: {
class:
'prose prose-lg max-w-none min-h-[500px] focus:outline-none p-6 font-serif leading-relaxed text-center',
},
},
onCreate: ({ editor }) => {
// Set default text alignment to center for completely new/empty content
if (!content || content.trim() === '' || content.trim() === '<p></p>') {
editor.chain().focus().setTextAlign('center').run();
}
},
});
// Sync external content changes (e.g. AI-generated content)
useEffect(() => {
if (editor && content !== editor.getHTML()) {
editor.commands.setContent(content);
}
}, [content]);
if (!editor) return null;
return (
<div className="border border-gray-200 rounded-lg overflow-hidden">
{/* Toolbar */}
<div className="flex flex-wrap items-center gap-0.5 px-3 py-2 bg-gray-50 border-b border-gray-200">
{/* Undo / Redo */}
<ToolbarButton onClick={() => editor.chain().focus().undo().run()} title="Undo">
<Undo2 className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton onClick={() => editor.chain().focus().redo().run()} title="Redo">
<Redo2 className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Headings */}
<ToolbarButton
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
active={editor.isActive('heading', { level: 1 })}
title="Heading 1"
>
<Heading1 className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
active={editor.isActive('heading', { level: 2 })}
title="Heading 2"
>
<Heading2 className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
active={editor.isActive('heading', { level: 3 })}
title="Heading 3"
>
<Heading3 className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Inline formatting */}
<ToolbarButton
onClick={() => editor.chain().focus().toggleBold().run()}
active={editor.isActive('bold')}
title="Bold"
>
<Bold className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleItalic().run()}
active={editor.isActive('italic')}
title="Italic"
>
<Italic className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleUnderline().run()}
active={editor.isActive('underline')}
title="Underline"
>
<UnderlineIcon className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleStrike().run()}
active={editor.isActive('strike')}
title="Strikethrough"
>
<Strikethrough className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Lists */}
<ToolbarButton
onClick={() => editor.chain().focus().toggleBulletList().run()}
active={editor.isActive('bulletList')}
title="Bullet List"
>
<List className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleOrderedList().run()}
active={editor.isActive('orderedList')}
title="Ordered List"
>
<ListOrdered className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Alignment */}
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('left').run()}
active={editor.isActive({ textAlign: 'left' })}
title="Align Left"
>
<AlignLeft className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('center').run()}
active={editor.isActive({ textAlign: 'center' })}
title="Align Center"
>
<AlignCenter className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('right').run()}
active={editor.isActive({ textAlign: 'right' })}
title="Align Right"
>
<AlignRight className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-gray-300 mx-1" />
{/* Block elements */}
<ToolbarButton
onClick={() => editor.chain().focus().toggleBlockquote().run()}
active={editor.isActive('blockquote')}
title="Blockquote"
>
<Quote className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().setHorizontalRule().run()}
title="Horizontal Rule"
>
<Minus className="w-4 h-4" />
</ToolbarButton>
</div>
{/* Editor content */}
<EditorContent editor={editor} />
</div>
);
}

View File

@@ -1,18 +1,21 @@
import { useState } from 'react';
import { Wand2, ChevronLeft, ChevronRight, Sparkles } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useBookStore } from '../../stores/bookStore';
import { booksApi } from '../../services/api';
import RichTextEditor from '../RichTextEditor';
export default function BookEditor() {
const { activeBook, updateChapterContent, setCurrentChapter } = useBookStore();
const [isGenerating, setIsGenerating] = useState(false);
const [showAIHelp, setShowAIHelp] = useState(false);
const { t } = useTranslation();
if (!activeBook || !activeBook.outline) {
return (
<div className="card text-center py-12">
<p className="text-gray-500">
Generate an outline first to start writing your book.
{t('bookEditor.noOutline')}
</p>
</div>
);
@@ -51,6 +54,7 @@ export default function BookEditor() {
previousChapter?.content
);
console.log('Chapter generation response:', response.data);
updateChapterContent(activeBook.currentChapter, response.data.content);
} catch (error) {
console.error('Failed to generate chapter:', error);
@@ -69,7 +73,7 @@ export default function BookEditor() {
{/* Chapter Navigation */}
<div className="space-y-4">
<div className="card">
<h3 className="font-semibold text-gray-900 mb-4">Chapters</h3>
<h3 className="font-semibold text-gray-900 mb-4">{t('bookEditor.chapters')}</h3>
<div className="space-y-2">
{activeBook.outline.chapters.map((chapter) => (
<button
@@ -88,7 +92,7 @@ export default function BookEditor() {
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{chapter.title}</p>
{chapter.content && (
<span className="text-xs text-green-600"> Written</span>
<span className="text-xs text-green-600"> {t('bookEditor.written')}</span>
)}
</div>
</div>
@@ -99,7 +103,7 @@ export default function BookEditor() {
{/* Chapter Info */}
<div className="card">
<h4 className="font-medium text-gray-900 mb-2">Chapter Summary</h4>
<h4 className="font-medium text-gray-900 mb-2">{t('bookEditor.chapterSummary')}</h4>
<p className="text-sm text-gray-600">{currentChapter?.summary}</p>
</div>
</div>
@@ -122,7 +126,7 @@ export default function BookEditor() {
{currentChapter?.title}
</h2>
<p className="text-sm text-gray-500">
Chapter {activeBook.currentChapter} of {activeBook.outline.chapters.length}
{t('bookEditor.chapterOf', { current: activeBook.currentChapter, total: activeBook.outline.chapters.length })}
</p>
</div>
<button
@@ -140,7 +144,7 @@ export default function BookEditor() {
className="btn-secondary flex items-center gap-2"
>
<Sparkles className="w-4 h-4" />
AI Assist
{t('bookEditor.aiAssist')}
</button>
<button
onClick={handleGenerateChapter}
@@ -148,7 +152,7 @@ export default function BookEditor() {
className="btn-primary flex items-center gap-2"
>
<Wand2 className="w-4 h-4" />
{isGenerating ? 'Generating...' : 'Generate Chapter'}
{isGenerating ? t('bookEditor.generating') : t('bookEditor.generateChapter')}
</button>
</div>
</div>
@@ -156,22 +160,22 @@ export default function BookEditor() {
{/* AI Help Panel */}
{showAIHelp && (
<div className="mb-6 bg-gradient-to-r from-accent-50 to-primary-50 rounded-lg p-4 border border-accent-200">
<h4 className="font-medium text-gray-900 mb-3">AI Writing Assistant</h4>
<h4 className="font-medium text-gray-900 mb-3">{t('bookEditor.aiWritingAssistant')}</h4>
<div className="flex flex-wrap gap-2">
<button
onClick={handleExpandText}
className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors"
>
Expand this section
{t('bookEditor.expandSection')}
</button>
<button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors">
📝 Improve prose
📝 {t('bookEditor.improveProse')}
</button>
<button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors">
💡 Add description
💡 {t('bookEditor.addDescription')}
</button>
<button className="text-sm px-3 py-1.5 bg-white rounded-lg border border-gray-200 hover:border-accent-300 transition-colors">
🔄 Rewrite paragraph
🔄 {t('bookEditor.rewriteParagraph')}
</button>
</div>
</div>
@@ -179,24 +183,13 @@ export default function BookEditor() {
{/* Editor */}
<div className="min-h-[500px]">
{currentChapter?.content ? (
<textarea
value={currentChapter.content}
onChange={(e) =>
updateChapterContent(activeBook.currentChapter, e.target.value)
<RichTextEditor
content={currentChapter?.content || ''}
onChange={(html) =>
updateChapterContent(activeBook.currentChapter, html)
}
className="w-full h-[600px] p-6 font-serif text-lg leading-relaxed border-0 focus:ring-0 resize-y outline-none"
placeholder="Start writing or use AI to generate this chapter..."
placeholder={t('bookEditor.editorPlaceholder')}
/>
) : (
<div className="h-[500px] flex items-center justify-center text-gray-400">
<div className="text-center">
<Wand2 className="w-12 h-12 mx-auto mb-3 opacity-50" />
<p>Click "Generate Chapter" to create content with AI</p>
<p className="text-sm mt-1">Or start writing manually</p>
</div>
</div>
)}
</div>
{/* Word Count */}
@@ -205,10 +198,10 @@ export default function BookEditor() {
{currentChapter?.content
? currentChapter.content.split(/\s+/).length
: 0}{' '}
words
{t('bookEditor.words')}
</span>
<span>
Last updated:{' '}
{t('bookEditor.lastUpdated')}{' '}
{new Date(activeBook.updatedAt).toLocaleDateString()}
</span>
</div>

View File

@@ -1,5 +1,7 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { BookOpen, Wand2, Edit3, Users, FileText } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import GenreSelector from './GenreSelector';
import BookOutlineGenerator from './BookOutlineGenerator';
import BookEditor from './BookEditor';
@@ -10,6 +12,31 @@ import { useQuery } from '@tanstack/react-query';
export default function BookGenerator() {
const [activeTab, setActiveTab] = useState<'genre' | 'outline' | 'editor' | 'characters'>('genre');
const { activeBook, setActiveBook, setOutline } = useBookStore();
const location = useLocation();
const { t } = useTranslation();
// Auto-initialize from onboarding page state
useEffect(() => {
const state = location.state as { autoBook?: { idea: string; genre: string; useAI: boolean } } | null;
if (state?.autoBook) {
const { idea, genre, useAI } = state.autoBook;
const newBook: Book = {
id: `book-${Date.now()}`,
title: idea.substring(0, 50) || 'Untitled',
genre,
idea,
outline: null,
characters: [],
currentChapter: 1,
createdAt: new Date(),
updatedAt: new Date(),
};
setActiveBook(newBook);
setActiveTab(useAI ? 'outline' : 'editor');
// Clear the state so refreshing doesn't re-trigger
window.history.replaceState({}, document.title);
}
}, []);
const { data: genresData } = useQuery({
queryKey: ['genres'],
@@ -46,9 +73,9 @@ export default function BookGenerator() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Book Generator</h2>
<h2 className="text-2xl font-bold text-gray-900">{t('bookGenerator.title')}</h2>
<p className="text-gray-500 mt-1">
Generate book ideas and write with AI assistance
{t('bookGenerator.subtitle')}
</p>
</div>
@@ -64,7 +91,7 @@ export default function BookGenerator() {
disabled={!activeBook && activeTab !== 'genre'}
>
<BookOpen className="w-4 h-4" />
<span className="hidden sm:inline">Genre</span>
<span className="hidden sm:inline">{t('bookGenerator.tabGenre')}</span>
</button>
<button
onClick={() => setActiveTab('outline')}
@@ -76,7 +103,7 @@ export default function BookGenerator() {
}`}
>
<FileText className="w-4 h-4" />
<span className="hidden sm:inline">Outline</span>
<span className="hidden sm:inline">{t('bookGenerator.tabOutline')}</span>
</button>
<button
onClick={() => setActiveTab('editor')}
@@ -88,7 +115,7 @@ export default function BookGenerator() {
}`}
>
<Edit3 className="w-4 h-4" />
<span className="hidden sm:inline">Write</span>
<span className="hidden sm:inline">{t('bookGenerator.tabWrite')}</span>
</button>
<button
onClick={() => setActiveTab('characters')}
@@ -100,7 +127,7 @@ export default function BookGenerator() {
}`}
>
<Users className="w-4 h-4" />
<span className="hidden sm:inline">Characters</span>
<span className="hidden sm:inline">{t('bookGenerator.tabCharacters')}</span>
</button>
</div>
</div>
@@ -133,6 +160,7 @@ export default function BookGenerator() {
function CharacterGenerator() {
const { activeBook, addCharacter } = useBookStore();
const [isGenerating, setIsGenerating] = useState(false);
const { t } = useTranslation();
const handleGenerateCharacters = async () => {
if (!activeBook) return;
@@ -166,7 +194,7 @@ function CharacterGenerator() {
<div className="card">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold text-gray-900">
Character Development
{t('characters.title')}
</h3>
<button
onClick={handleGenerateCharacters}
@@ -174,7 +202,7 @@ function CharacterGenerator() {
className="btn-primary flex items-center gap-2"
>
<Wand2 className="w-4 h-4" />
{isGenerating ? 'Generating...' : 'Generate Characters'}
{isGenerating ? t('characters.generating') : t('characters.generate')}
</button>
</div>
@@ -182,7 +210,7 @@ function CharacterGenerator() {
<div className="text-center py-8">
<Users className="w-12 h-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500">
No characters yet. Click "Generate Characters" to create AI-suggested characters.
{t('characters.empty')}
</p>
</div>
) : (
@@ -206,11 +234,11 @@ function CharacterGenerator() {
</div>
<div className="space-y-2 text-sm">
<div>
<span className="text-gray-500">Traits:</span>
<span className="text-gray-500">{t('characters.traits')}:</span>
<p className="text-gray-700">{character.traits?.join(', ') || 'N/A'}</p>
</div>
<div>
<span className="text-gray-500">Motivation:</span>
<span className="text-gray-500">{t('characters.motivation')}:</span>
<p className="text-gray-700">{character.motivation || 'N/A'}</p>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import { useState } from 'react';
import { Wand2, CheckCircle, Loader2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Book, useBookStore } from '../../stores/bookStore';
import { booksApi } from '../../services/api';
@@ -12,6 +13,7 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
const { activeBook } = useBookStore();
const [isGenerating, setIsGenerating] = useState(false);
const [error, setError] = useState<string>('');
const { t } = useTranslation();
const outline = activeBook?.outline;
@@ -22,9 +24,12 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
try {
const response = await booksApi.generateOutline(book.genre, book.idea, book.title);
const generatedOutline = response.data.outline;
console.log('Generated outline:', generatedOutline);
console.log('Outline chapters:', generatedOutline?.chapters);
onComplete(generatedOutline);
} catch (err: any) {
setError(err.response?.data?.error || 'Failed to generate outline');
console.error('Outline generation error:', err);
setError(err.response?.data?.error || t('bookOutline.failedToGenerate'));
} finally {
setIsGenerating(false);
}
@@ -36,8 +41,8 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
<div className="card bg-gradient-to-r from-primary-50 to-accent-50">
<div className="flex items-start justify-between">
<div>
<h3 className="font-semibold text-gray-900">{book.title || 'Untitled Book'}</h3>
<p className="text-sm text-gray-600 mt-1 capitalize">Genre: {book.genre}</p>
<h3 className="font-semibold text-gray-900">{book.title || t('bookOutline.untitledBook')}</h3>
<p className="text-sm text-gray-600 mt-1 capitalize">{t('bookOutline.genre')}: {book.genre}</p>
<p className="text-sm text-gray-500 mt-2 line-clamp-2">{book.idea}</p>
</div>
{!outline && (
@@ -49,12 +54,12 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
{isGenerating ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Generating...
{t('bookOutline.generatingOutline')}
</>
) : (
<>
<Wand2 className="w-4 h-4" />
Generate Outline
{t('bookOutline.generateOutline')}
</>
)}
</button>
@@ -72,22 +77,37 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
{/* Outline Display */}
{outline && (
<div className="card">
{outline.error ? (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<h4 className="text-red-800 font-medium mb-2">Generation Error</h4>
<p className="text-red-700 text-sm">{outline.error}</p>
{outline.raw && (
<details className="mt-2">
<summary className="text-red-600 cursor-pointer">Raw AI Response</summary>
<pre className="text-xs text-gray-600 mt-2 whitespace-pre-wrap bg-gray-100 p-2 rounded">
{outline.raw}
</pre>
</details>
)}
</div>
) : (
<>
<div className="flex items-center gap-2 mb-6">
<CheckCircle className="w-5 h-5 text-green-500" />
<h3 className="text-lg font-semibold text-gray-900">
Generated Outline: {outline.title || book.title}
{t('bookOutline.generatedOutline')}: {outline.title || book.title}
</h3>
</div>
{outline.logline && (
<div className="mb-6 pb-6 border-b border-gray-200">
<h4 className="text-sm font-medium text-gray-500 mb-2">Logline</h4>
<h4 className="text-sm font-medium text-gray-500 mb-2">{t('bookOutline.logline')}</h4>
<p className="text-gray-900 italic">{outline.logline}</p>
</div>
)}
<div>
<h4 className="text-sm font-medium text-gray-500 mb-4">Chapter Outline</h4>
<h4 className="text-sm font-medium text-gray-500 mb-4">{t('bookOutline.chapterOutline')}</h4>
<div className="space-y-3">
{outline.chapters?.map((chapter: any, index: number) => (
<div
@@ -100,10 +120,10 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
</span>
<div className="flex-1">
<h5 className="font-medium text-gray-900">
{chapter.title || `Chapter ${chapter.number || index + 1}`}
{chapter.title || `${t('bookOutline.chapter')} ${chapter.number || index + 1}`}
</h5>
<p className="text-sm text-gray-600 mt-1">
{chapter.summary || 'No summary available'}
{chapter.summary || t('bookOutline.noSummary')}
</p>
</div>
</div>
@@ -114,9 +134,11 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
<div className="mt-6 flex items-center justify-end gap-3">
<p className="text-sm text-gray-500">
Ready to start writing! Navigate to the Write tab to begin.
{t('bookOutline.readyToWrite')}
</p>
</div>
</>
)}
</div>
)}
@@ -124,10 +146,9 @@ export default function BookOutlineGenerator({ book, onComplete }: BookOutlineGe
{!outline && !isGenerating && (
<div className="card text-center py-12">
<Wand2 className="w-12 h-12 text-gray-300 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900">Ready to Generate</h3>
<h3 className="text-lg font-medium text-gray-900">{t('bookOutline.readyToGenerate')}</h3>
<p className="text-gray-500 mt-2 max-w-md mx-auto">
Click "Generate Outline" to create a detailed chapter outline based on your genre and book idea.
The AI will create a structure following genre-specific patterns.
{t('bookOutline.readyToGenerateDesc')}
</p>
</div>
)}

View File

@@ -1,4 +1,5 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { BookOpen, Sparkles } from 'lucide-react';
interface Genre {
@@ -14,6 +15,7 @@ interface GenreSelectorProps {
}
export default function GenreSelector({ genres, onSelect }: GenreSelectorProps) {
const { t } = useTranslation();
const [selectedGenre, setSelectedGenre] = useState<string>('');
const [title, setTitle] = useState('');
const [idea, setIdea] = useState('');
@@ -32,7 +34,7 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
<div className="card">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<BookOpen className="w-5 h-5" />
Select Your Genre
{t('genreSelector.selectGenre')}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
@@ -47,9 +49,11 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
}`}
>
<span className="text-2xl mb-2 block">{genre.icon}</span>
<h4 className="font-medium text-gray-900">{genre.name}</h4>
<h4 className="font-medium text-gray-900">
{t(`genres.${genre.id}.name`, genre.name)}
</h4>
<p className="text-xs text-gray-500 mt-1 line-clamp-2">
{genre.description}
{t(`genres.${genre.id}.description`, genre.description)}
</p>
</button>
))}
@@ -62,29 +66,29 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
<div className="card sticky top-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<Sparkles className="w-5 h-5" />
Book Details
{t('genreSelector.bookDetails')}
</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="label">Book Title (optional)</label>
<label className="label">{t('genreSelector.bookTitle')}</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="My Amazing Book"
placeholder={t('genreSelector.bookTitlePlaceholder')}
className="input"
/>
</div>
<div>
<label className="label">
Core Idea <span className="text-red-500">*</span>
{t('genreSelector.coreIdea')} <span className="text-red-500">*</span>
</label>
<textarea
value={idea}
onChange={(e) => setIdea(e.target.value)}
placeholder="Describe your book idea... What's the story about? Who are the main characters? What conflict drives the narrative?"
placeholder={t('genreSelector.coreIdeaPlaceholder')}
className="input min-h-[150px] resize-y"
required
/>
@@ -92,7 +96,7 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
<div className="bg-amber-50 border border-amber-200 rounded-lg p-3">
<p className="text-xs text-amber-700">
<strong>Tip:</strong> The more details you provide, the better the AI can generate your outline and content.
<strong>Tip:</strong> {t('genreSelector.tip')}
</p>
</div>
@@ -101,7 +105,7 @@ export default function GenreSelector({ genres, onSelect }: GenreSelectorProps)
disabled={!selectedGenre || !idea.trim()}
className="btn-primary w-full py-3"
>
Generate Outline
{t('genreSelector.generateOutline')}
</button>
</form>
</div>

View File

@@ -1,5 +1,6 @@
import { useState } from 'react';
import { Image, Wand2, Type } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import CoverGallery from './CoverGallery';
import CoverEditor from './CoverEditor';
import { useCoverStore, Cover } from '../../stores/coverStore';
@@ -10,6 +11,8 @@ export default function CoverDesigner() {
const [activeTab, setActiveTab] = useState<'gallery' | 'editor' | 'ai'>('gallery');
const { activeCover, setActiveCover } = useCoverStore();
const queryClient = useQueryClient();
const { t: tMain } = useTranslation();
const { t } = useTranslation();
const { data: coversData } = useQuery({
queryKey: ['covers'],
@@ -37,8 +40,8 @@ export default function CoverDesigner() {
const handleSelectCover = (coverUrl: string) => {
const newCover: Cover = {
id: `cover-${Date.now()}`,
title: 'New Book',
author: 'Author Name',
title: tMain('coverDesigner.newBookTitle'),
author: tMain('coverDesigner.authorName'),
backgroundImage: coverUrl,
backgroundColor: '#ffffff',
width: 1600,
@@ -55,9 +58,9 @@ export default function CoverDesigner() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Cover Designer</h2>
<h2 className="text-2xl font-bold text-gray-900">{tMain('coverDesigner.title')}</h2>
<p className="text-gray-500 mt-1">
Design stunning book covers with AI or manual editing
{tMain('coverDesigner.subtitle')}
</p>
</div>
@@ -72,7 +75,7 @@ export default function CoverDesigner() {
}`}
>
<Image className="w-4 h-4" />
Gallery
{tMain('coverDesigner.tabGallery')}
</button>
<button
onClick={() => setActiveTab('editor')}
@@ -83,7 +86,7 @@ export default function CoverDesigner() {
}`}
>
<Type className="w-4 h-4" />
Editor
{tMain('coverDesigner.tabEditor')}
</button>
<button
onClick={() => setActiveTab('ai')}
@@ -94,7 +97,7 @@ export default function CoverDesigner() {
}`}
>
<Wand2 className="w-4 h-4" />
AI Generate
{tMain('coverDesigner.tabAIGenerate')}
</button>
</div>
</div>
@@ -131,36 +134,37 @@ function AIGenerator({
}) {
const [prompt, setPrompt] = useState('');
const [selectedStyle, setSelectedStyle] = useState('modern');
const { t } = useTranslation();
const styles = [
{ id: 'modern', name: 'Modern', icon: '✨' },
{ id: 'minimal', name: 'Minimalist', icon: '🎯' },
{ id: 'vintage', name: 'Vintage', icon: '📜' },
{ id: 'bold', name: 'Bold', icon: '🔥' },
{ id: 'elegant', name: 'Elegant', icon: '💎' },
{ id: 'playful', name: 'Playful', icon: '🎨' },
{ id: 'modern', name: t('coverDesigner.aiGenerator.modern'), icon: '✨' },
{ id: 'minimalist', name: t('coverDesigner.aiGenerator.minimalist'), icon: '🎯' },
{ id: 'vintage', name: t('coverDesigner.aiGenerator.vintage'), icon: '📜' },
{ id: 'bold', name: t('coverDesigner.aiGenerator.bold'), icon: '🔥' },
{ id: 'elegant', name: t('coverDesigner.aiGenerator.elegant'), icon: '💎' },
{ id: 'playful', name: t('coverDesigner.aiGenerator.playful'), icon: '🎨' },
];
const genrePresets = [
{
genre: 'Mystery',
prompt: 'A mysterious book cover with dark shadows, silhouette of a detective, foggy street scene, noir atmosphere',
genre: t('coverDesigner.aiGenerator.mystery'),
prompt: t('coverDesigner.aiGenerator.mysteryPrompt'),
},
{
genre: 'Romance',
prompt: 'A romantic book cover with soft pastel colors, couple silhouette at sunset, dreamy and emotional',
genre: t('coverDesigner.aiGenerator.romance'),
prompt: t('coverDesigner.aiGenerator.romancePrompt'),
},
{
genre: 'Fantasy',
prompt: 'An epic fantasy book cover with magical elements, dragon, castle in the background, mystical glowing effects',
genre: t('coverDesigner.aiGenerator.fantasy'),
prompt: t('coverDesigner.aiGenerator.fantasyPrompt'),
},
{
genre: 'Sci-Fi',
prompt: 'A futuristic sci-fi book cover with spaceships, neon lights, cyberpunk city, high-tech atmosphere',
genre: t('coverDesigner.aiGenerator.scifi'),
prompt: t('coverDesigner.aiGenerator.scifiPrompt'),
},
{
genre: 'Self-Help',
prompt: 'A clean self-help book cover with inspiring imagery, mountain peak or sunrise, professional and motivating',
genre: t('coverDesigner.aiGenerator.selfhelp'),
prompt: t('coverDesigner.aiGenerator.selfhelpPrompt'),
},
];
@@ -179,12 +183,12 @@ function AIGenerator({
<div className="card space-y-6">
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">
AI Cover Generator
{t('coverDesigner.aiGenerator.title')}
</h3>
{/* Style Selection */}
<div className="mb-6">
<label className="label">Select Style</label>
<label className="label">{t('coverDesigner.aiGenerator.selectStyle')}</label>
<div className="grid grid-cols-3 sm:grid-cols-6 gap-2 mt-2">
{styles.map((style) => (
<button
@@ -205,18 +209,18 @@ function AIGenerator({
{/* Prompt Input */}
<div className="mb-6">
<label className="label">Describe Your Cover</label>
<label className="label">{t('coverDesigner.aiGenerator.describeCover')}</label>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Describe the book cover you want to generate... e.g., 'A mysterious forest with glowing eyes in the darkness'"
placeholder={t('coverDesigner.aiGenerator.coverPlaceholder')}
className="input min-h-[120px] resize-y"
/>
</div>
{/* Genre Presets */}
<div className="mb-6">
<label className="label">Quick Presets by Genre</label>
<label className="label">{t('coverDesigner.aiGenerator.quickPresets')}</label>
<div className="grid gap-2 mt-2">
{genrePresets.map((preset) => (
<button
@@ -238,7 +242,7 @@ function AIGenerator({
className="btn-primary w-full py-3 flex items-center justify-center gap-2"
>
<Wand2 className="w-5 h-5" />
{isGenerating ? 'Generating...' : 'Generate Cover'}
{isGenerating ? t('coverDesigner.aiGenerator.generating') : t('coverDesigner.aiGenerator.generateCover')}
</button>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import { Stage, Layer, Rect, Text, Transformer, Image as KonvaImage } from 'reac
import type Konva from 'konva';
import { useCoverStore } from '../../stores/coverStore';
import { Type, Download } from 'lucide-react';
import { useTranslation } from 'react-i18next';
const BackgroundImage = ({ url, width, height }: { url: string; width: number; height: number }) => {
const [image, setImage] = useState<HTMLImageElement | null>(null);
@@ -21,13 +22,13 @@ const BackgroundImage = ({ url, width, height }: { url: string; width: number; h
};
const FONTS = [
{ name: 'Roboto', label: 'Modern (Roboto)' },
{ name: 'Montserrat', label: 'Minimalist (Montserrat)' },
{ name: 'Playfair Display', label: 'Elegant (Playfair Display)' },
{ name: 'Bebas Neue', label: 'Bold (Bebas Neue)' },
{ name: 'Pacifico', label: 'Playful (Pacifico)' },
{ name: 'Georgia', label: 'Classic (Georgia)' },
{ name: 'Arial', label: 'Standard (Arial)' },
{ name: 'Roboto', labelKey: 'coverDesigner.modernFont' },
{ name: 'Montserrat', labelKey: 'coverDesigner.minimalistFont' },
{ name: 'Playfair Display', labelKey: 'coverDesigner.elegantFont' },
{ name: 'Bebas Neue', labelKey: 'coverDesigner.boldFont' },
{ name: 'Pacifico', labelKey: 'coverDesigner.playfulFont' },
{ name: 'Georgia', labelKey: 'coverDesigner.classicFont' },
{ name: 'Arial', labelKey: 'coverDesigner.standardFont' },
];
export default function CoverEditor() {
@@ -37,11 +38,12 @@ export default function CoverEditor() {
const [textTool, setTextTool] = useState<'title' | 'author'>('title');
const [newText, setNewText] = useState('');
const [selectedFont, setSelectedFont] = useState('Roboto');
const { t } = useTranslation();
if (!activeCover) {
return (
<div className="card text-center py-12">
<p className="text-gray-500">Select a cover from the gallery to edit</p>
<p className="text-gray-500">{t('coverDesigner.selectCover')}</p>
</div>
);
}
@@ -86,7 +88,7 @@ export default function CoverEditor() {
<div className="lg:col-span-3 card p-4 bg-gray-100">
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold text-gray-900">
Editing: {activeCover.title}
{t('coverDesigner.editing')}: {activeCover.title}
</h3>
<div className="flex items-center gap-2">
<button
@@ -94,7 +96,7 @@ export default function CoverEditor() {
className="btn-primary flex items-center gap-2"
>
<Download className="w-4 h-4" />
Export
{t('coverDesigner.export')}
</button>
</div>
</div>
@@ -185,12 +187,12 @@ export default function CoverEditor() {
<div className="card">
<h4 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
<Type className="w-4 h-4" />
Add Text
{t('coverDesigner.addText')}
</h4>
<div className="space-y-3">
<div>
<label className="label">Text Type</label>
<label className="label">{t('coverDesigner.textType')}</label>
<div className="flex gap-2">
<button
onClick={() => setTextTool('title')}
@@ -200,7 +202,7 @@ export default function CoverEditor() {
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
Title
{t('coverDesigner.titleText')}
</button>
<button
onClick={() => setTextTool('author')}
@@ -210,13 +212,13 @@ export default function CoverEditor() {
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
Author
{t('coverDesigner.authorText')}
</button>
</div>
</div>
<div>
<label className="label">Font</label>
<label className="label">{t('coverDesigner.font')}</label>
<select
value={selectedFont}
onChange={(e) => setSelectedFont(e.target.value)}
@@ -224,19 +226,19 @@ export default function CoverEditor() {
>
{FONTS.map((font) => (
<option key={font.name} value={font.name}>
{font.label}
{t(font.labelKey)}
</option>
))}
</select>
</div>
<div>
<label className="label">Text Content</label>
<label className="label">{t('coverDesigner.textContent')}</label>
<input
type="text"
value={newText}
onChange={(e) => setNewText(e.target.value)}
placeholder={textTool === 'title' ? 'Book Title' : 'Author Name'}
placeholder={textTool === 'title' ? t('coverDesigner.bookTitlePlaceholder') : t('coverDesigner.authorNamePlaceholder')}
className="input"
onKeyPress={(e) => e.key === 'Enter' && handleAddText()}
/>
@@ -247,7 +249,7 @@ export default function CoverEditor() {
disabled={!newText.trim()}
className="btn-primary w-full"
>
Add Text Layer
{t('coverDesigner.addTextLayer')}
</button>
</div>
</div>
@@ -269,7 +271,7 @@ export default function CoverEditor() {
>
{FONTS.map((font) => (
<option key={font.name} value={font.name}>
{font.label}
{t(font.labelKey)}
</option>
))}
</select>

View File

@@ -1,4 +1,5 @@
import { Image, Eye, Trash2, Download } from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface CoverData {
id: string;
@@ -14,13 +15,15 @@ interface CoverGalleryProps {
}
export default function CoverGallery({ covers, onSelect, onDelete }: CoverGalleryProps) {
const { t } = useTranslation();
if (covers.length === 0) {
return (
<div className="card text-center py-12">
<Image className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900">No covers yet</h3>
<h3 className="text-lg font-medium text-gray-900">{t('coverDesigner.noCovers')}</h3>
<p className="text-gray-500 mt-2">
Upload a cover image or generate one with AI to get started
{t('coverDesigner.noCoversDesc')}
</p>
</div>
);
@@ -43,7 +46,7 @@ export default function CoverGallery({ covers, onSelect, onDelete }: CoverGaller
<button
onClick={() => onSelect(cover.url)}
className="p-2 bg-white rounded-lg hover:bg-gray-100 transition-colors"
title="Edit"
title={t('coverDesigner.edit')}
>
<Eye className="w-5 h-5 text-gray-700" />
</button>
@@ -55,14 +58,14 @@ export default function CoverGallery({ covers, onSelect, onDelete }: CoverGaller
link.click();
}}
className="p-2 bg-white rounded-lg hover:bg-gray-100 transition-colors"
title="Download"
title={t('coverDesigner.download')}
>
<Download className="w-5 h-5 text-gray-700" />
</button>
<button
onClick={() => onDelete(cover.id)}
className="p-2 bg-red-500 rounded-lg hover:bg-red-600 transition-colors"
title="Delete"
title={t('coverDesigner.delete')}
>
<Trash2 className="w-5 h-5 text-white" />
</button>

View File

@@ -35,3 +35,71 @@ body {
@apply block text-sm font-medium text-gray-700 mb-1;
}
}
/* TipTap WYSIWYG Editor Styles */
.tiptap {
min-height: 500px;
}
.tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}
/* Book chapter formatting */
.tiptap p {
text-indent: 2rem;
margin-bottom: 1.5rem;
line-height: 1.8;
}
.tiptap p:first-child {
text-indent: 0;
}
.tiptap h1, .tiptap h2, .tiptap h3 {
text-align: center;
text-indent: 0;
margin-top: 2rem;
margin-bottom: 1rem;
}
.tiptap blockquote {
text-align: left;
text-indent: 0;
margin-left: 2rem;
margin-right: 2rem;
}
.tiptap ul, .tiptap ol {
text-align: left;
text-indent: 0;
margin-left: 2rem;
}
.tiptap h1 { font-size: 2em; font-weight: 700; margin: 0.67em 0; }
.tiptap h2 { font-size: 1.5em; font-weight: 600; margin: 0.75em 0; }
.tiptap h3 { font-size: 1.25em; font-weight: 600; margin: 0.83em 0; }
.tiptap ul { list-style: disc; padding-left: 1.5em; margin: 0.5em 0; }
.tiptap ol { list-style: decimal; padding-left: 1.5em; margin: 0.5em 0; }
.tiptap li { margin: 0.25em 0; }
.tiptap blockquote {
border-left: 3px solid #e5e7eb;
padding-left: 1em;
margin: 1em 0;
color: #6b7280;
font-style: italic;
}
.tiptap hr {
border: none;
border-top: 2px solid #e5e7eb;
margin: 1.5em 0;
}
.tiptap p { margin: 0.5em 0; }

View File

@@ -24,5 +24,148 @@
"languageSelect": "Select your preferred language",
"english": "English",
"spanish": "Español"
},
"onboarding": {
"title": "Welcome to CreaBook",
"subtitle": "Let's personalize your creative journey.",
"step1": "1. Select your language",
"step2": "2. What kind of book do you want to create?",
"ideaPlaceholder": "E.g., A sci-fi thriller about a detective on Mars...",
"genreLabel": "Genre (optional)",
"step3": "3. How do you want to start?",
"btnFree": "Move Freely (Blank Canvas)",
"btnAI": "Use AI to Generate Outline"
},
"genres": {
"fiction": { "name": "Fiction", "description": "General literary fiction with focus on character development and narrative" },
"mystery": { "name": "Mystery", "description": "Puzzle-driven narratives with clues, suspects, and a satisfying reveal" },
"romance": { "name": "Romance", "description": "Love-centered stories with emotional intimacy and satisfying relationship resolution" },
"scifi": { "name": "Science Fiction", "description": "Speculative fiction exploring technology, space, time, and their impact on humanity" },
"fantasy": { "name": "Fantasy", "description": "Magical worlds with supernatural elements, quests, and epic stakes" },
"horror": { "name": "Horror", "description": "Fear-driven narratives designed to unsettle, frighten, and provoke dread" },
"thriller": { "name": "Thriller", "description": "High-stakes, fast-paced narratives with constant tension and danger" },
"children": { "name": "Children's Book", "description": "Age-appropriate stories with clear morals, simple language, and engaging characters" },
"nonfiction": { "name": "Non-Fiction", "description": "Factual, informative content organized around a central topic or argument" },
"selfhelp": { "name": "Self-Help", "description": "Practical guidance for personal improvement and growth" },
"business": { "name": "Business", "description": "Professional insights, strategies, and case studies for business success" },
"memoir": { "name": "Memoir", "description": "Personal life stories focused on transformation and universal themes" }
},
"genreSelector": {
"selectGenre": "Select Your Genre",
"bookDetails": "Book Details",
"bookTitle": "Book Title (optional)",
"bookTitlePlaceholder": "My Amazing Book",
"coreIdea": "Core Idea",
"coreIdeaPlaceholder": "Describe your book idea... What's the story about? Who are the main characters? What conflict drives the narrative?",
"tip": "The more details you provide, the better the AI can generate your outline and content.",
"generateOutline": "Generate Outline"
},
"bookGenerator": {
"title": "Book Generator",
"subtitle": "Generate book ideas and write with AI assistance",
"tabGenre": "Genre",
"tabOutline": "Outline",
"tabWrite": "Write",
"tabCharacters": "Characters"
},
"bookEditor": {
"chapters": "Chapters",
"written": "Written",
"chapterSummary": "Chapter Summary",
"chapterOf": "Chapter {{current}} of {{total}}",
"aiAssist": "AI Assist",
"generating": "Generating...",
"generateChapter": "Generate Chapter",
"aiWritingAssistant": "AI Writing Assistant",
"expandSection": "Expand this section",
"improveProse": "Improve prose",
"addDescription": "Add description",
"rewriteParagraph": "Rewrite paragraph",
"words": "words",
"lastUpdated": "Last updated",
"noOutline": "Generate an outline first to start writing your book.",
"editorPlaceholder": "Start writing or use AI to generate this chapter...",
"written": "Written"
},
"bookOutline": {
"untitledBook": "Untitled Book",
"genre": "Genre",
"generatingOutline": "Generating...",
"generateOutline": "Generate Outline",
"generatedOutline": "Generated Outline",
"logline": "Logline",
"chapterOutline": "Chapter Outline",
"chapter": "Chapter",
"noSummary": "No summary available",
"readyToWrite": "Ready to start writing! Navigate to the Write tab to begin.",
"readyToGenerate": "Ready to Generate",
"readyToGenerateDesc": "Click \"Generate Outline\" to create a detailed chapter outline based on your genre and book idea. The AI will create a structure following genre-specific patterns.",
"failedToGenerate": "Failed to generate outline"
},
"characters": {
"title": "Character Development",
"generating": "Generating...",
"generate": "Generate Characters",
"empty": "No characters yet. Click \"Generate Characters\" to create AI-suggested characters.",
"traits": "Traits",
"motivation": "Motivation"
},
"coverDesigner": {
"title": "Cover Designer",
"subtitle": "Design stunning book covers with AI or manual editing",
"tabGallery": "Gallery",
"tabEditor": "Editor",
"tabAIGenerate": "AI Generate",
"newBookTitle": "New Book",
"authorName": "Author Name",
"noCovers": "No covers yet",
"noCoversDesc": "Upload a cover image or generate one with AI to get started",
"edit": "Edit",
"delete": "Delete",
"download": "Download",
"selectCover": "Select a cover from the gallery to edit",
"editing": "Editing",
"export": "Export",
"addText": "Add Text",
"textType": "Text Type",
"titleText": "Title",
"authorText": "Author",
"font": "Font",
"modernFont": "Modern (Roboto)",
"minimalistFont": "Minimalist (Montserrat)",
"elegantFont": "Elegant (Playfair Display)",
"boldFont": "Bold (Bebas Neue)",
"playfulFont": "Playful (Pacifico)",
"classicFont": "Classic (Georgia)",
"standardFont": "Standard (Arial)",
"textContent": "Text Content",
"bookTitlePlaceholder": "Book Title",
"authorNamePlaceholder": "Author Name",
"addTextLayer": "Add Text Layer",
"aiGenerator": {
"title": "AI Cover Generator",
"selectStyle": "Select Style",
"modern": "Modern",
"minimalist": "Minimalist",
"vintage": "Vintage",
"bold": "Bold",
"elegant": "Elegant",
"playful": "Playful",
"describeCover": "Describe Your Cover",
"coverPlaceholder": "Describe the book cover you want to generate... e.g., 'A mysterious forest with glowing eyes in the darkness'",
"quickPresets": "Quick Presets by Genre",
"mystery": "Mystery",
"mysteryPrompt": "A mysterious book cover with dark shadows, silhouette of a detective, foggy street scene, noir atmosphere",
"romance": "Romance",
"romancePrompt": "A romantic book cover with soft pastel colors, couple silhouette at sunset, dreamy and emotional",
"fantasy": "Fantasy",
"fantasyPrompt": "An epic fantasy book cover with magical elements, dragon, castle in the background, mystical glowing effects",
"scifi": "Sci-Fi",
"scifiPrompt": "A futuristic sci-fi book cover with spaceships, neon lights, cyberpunk city, high-tech atmosphere",
"selfhelp": "Self-Help",
"selfhelpPrompt": "A clean self-help book cover with inspiring imagery, mountain peak or sunrise, professional and motivating",
"generating": "Generating...",
"generateCover": "Generate Cover"
}
}
}

View File

@@ -24,5 +24,148 @@
"languageSelect": "Selecciona tu idioma preferido",
"english": "Inglés",
"spanish": "Español"
},
"onboarding": {
"title": "Bienvenido a CreaBook",
"subtitle": "Vamos a personalizar tu viaje creativo.",
"step1": "1. Selecciona tu idioma",
"step2": "2. ¿Qué tipo de libro quieres crear?",
"ideaPlaceholder": "Ej. Un thriller de ciencia ficción sobre un detective en Marte...",
"genreLabel": "Género (opcional)",
"step3": "3. ¿Cómo quieres empezar?",
"btnFree": "Moverse libremente (Lienzo en blanco)",
"btnAI": "Usar IA para generar tu esquema"
},
"genres": {
"fiction": { "name": "Ficción", "description": "Ficción literaria general centrada en el desarrollo de personajes y la narrativa" },
"mystery": { "name": "Misterio", "description": "Narrativas basadas en enigmas con pistas, sospechosos y una revelación satisfactoria" },
"romance": { "name": "Romance", "description": "Historias centradas en el amor con intimidad emocional y una resolución satisfactoria" },
"scifi": { "name": "Ciencia Ficción", "description": "Ficción especulativa que explora la tecnología, el espacio, el tiempo y su impacto en la humanidad" },
"fantasy": { "name": "Fantasía", "description": "Mundos mágicos con elementos sobrenaturales, misiones y grandes desafíos" },
"horror": { "name": "Terror", "description": "Narrativas de miedo diseñadas para inquietar, asustar y provocar pavor" },
"thriller": { "name": "Thriller", "description": "Narrativas de alto riesgo y ritmo rápido con tensión y peligro constantes" },
"children": { "name": "Libro Infantil", "description": "Historias apropiadas para niños con moralejas claras, lenguaje sencillo y personajes atractivos" },
"nonfiction": { "name": "No Ficción", "description": "Contenido informativo y factual organizado en torno a un tema o argumento central" },
"selfhelp": { "name": "Autoayuda", "description": "Guía práctica para la mejora y el crecimiento personal" },
"business": { "name": "Negocios", "description": "Perspectivas profesionales, estrategias y casos de estudio para el éxito empresarial" },
"memoir": { "name": "Memorias", "description": "Historias de vida personales centradas en la transformación y temas universales" }
},
"genreSelector": {
"selectGenre": "Selecciona tu Género",
"bookDetails": "Detalles del Libro",
"bookTitle": "Título del Libro (opcional)",
"bookTitlePlaceholder": "Mi Libro Increíble",
"coreIdea": "Idea Principal",
"coreIdeaPlaceholder": "Describe tu idea para el libro... ¿De qué trata la historia? ¿Quiénes son los personajes principales? ¿Qué conflicto impulsa la narrativa?",
"tip": "Cuantos más detalles proporciones, mejor podrá la IA generar tu esquema y contenido.",
"generateOutline": "Generar Esquema"
},
"bookGenerator": {
"title": "Generador de Libros",
"subtitle": "Genera ideas de libros y escribe con asistencia de IA",
"tabGenre": "Género",
"tabOutline": "Esquema",
"tabWrite": "Escribir",
"tabCharacters": "Personajes"
},
"bookEditor": {
"chapters": "Capítulos",
"written": "Escrito",
"chapterSummary": "Resumen del Capítulo",
"chapterOf": "Capítulo {{current}} de {{total}}",
"aiAssist": "Asistente IA",
"generating": "Generando...",
"generateChapter": "Generar Capítulo",
"aiWritingAssistant": "Asistente de Escritura IA",
"expandSection": "Expandir esta sección",
"improveProse": "Mejorar prosa",
"addDescription": "Añadir descripción",
"rewriteParagraph": "Reescribir párrafo",
"words": "palabras",
"lastUpdated": "Última actualización",
"noOutline": "Genera un esquema primero para empezar a escribir tu libro.",
"editorPlaceholder": "Empieza a escribir o usa la IA para generar este capítulo...",
"written": "Escrito"
},
"bookOutline": {
"untitledBook": "Libro sin título",
"genre": "Género",
"generatingOutline": "Generando...",
"generateOutline": "Generar Esquema",
"generatedOutline": "Esquema Generado",
"logline": "Sinopsis",
"chapterOutline": "Esquema de Capítulos",
"chapter": "Capítulo",
"noSummary": "Sin resumen disponible",
"readyToWrite": "¡Listo para escribir! Navega a la pestaña Escribir para comenzar.",
"readyToGenerate": "Listo para Generar",
"readyToGenerateDesc": "Haz clic en \"Generar Esquema\" para crear un esquema detallado de capítulos basado en tu género e idea. La IA creará una estructura siguiendo patrones específicos del género.",
"failedToGenerate": "Error al generar el esquema"
},
"characters": {
"title": "Desarrollo de Personajes",
"generating": "Generando...",
"generate": "Generar Personajes",
"empty": "Aún no hay personajes. Haz clic en \"Generar Personajes\" para crear personajes sugeridos por la IA.",
"traits": "Rasgos",
"motivation": "Motivación"
},
"coverDesigner": {
"title": "Diseñador de Portadas",
"subtitle": "Diseña portadas de libros impresionantes con IA o edición manual",
"tabGallery": "Galería",
"tabEditor": "Editor",
"tabAIGenerate": "Generar con IA",
"newBookTitle": "Nuevo Libro",
"authorName": "Nombre del Autor",
"noCovers": "Aún no hay portadas",
"noCoversDesc": "Sube una imagen de portada o genera una con IA para comenzar",
"edit": "Editar",
"delete": "Eliminar",
"download": "Descargar",
"selectCover": "Selecciona una portada de la galería para editar",
"editing": "Editando",
"export": "Exportar",
"addText": "Añadir Texto",
"textType": "Tipo de Texto",
"titleText": "Título",
"authorText": "Autor",
"font": "Fuente",
"modernFont": "Moderno (Roboto)",
"minimalistFont": "Minimalista (Montserrat)",
"elegantFont": "Elegante (Playfair Display)",
"boldFont": "Audaz (Bebas Neue)",
"playfulFont": "Juguetón (Pacifico)",
"classicFont": "Clásico (Georgia)",
"standardFont": "Estándar (Arial)",
"textContent": "Contenido del Texto",
"bookTitlePlaceholder": "Título del Libro",
"authorNamePlaceholder": "Nombre del Autor",
"addTextLayer": "Añadir Capa de Texto",
"aiGenerator": {
"title": "Generador de Portadas con IA",
"selectStyle": "Seleccionar Estilo",
"modern": "Moderno",
"minimalist": "Minimalista",
"vintage": "Antiguo",
"bold": "Audaz",
"elegant": "Elegante",
"playful": "Juguetón",
"describeCover": "Describe tu Portada",
"coverPlaceholder": "Describe la portada del libro que quieres generar... ej. 'Un bosque misterioso con ojos brillantes en la oscuridad'",
"quickPresets": "Preajustes Rápidos por Género",
"mystery": "Misterio",
"mysteryPrompt": "Una portada de libro misteriosa con sombras oscuras, silueta de un detective, escena de calle brumosa, atmósfera noir",
"romance": "Romance",
"romancePrompt": "Una portada de libro romántica con colores pastel suaves, silueta de pareja al atardecer, soñadora y emocional",
"fantasy": "Fantasía",
"fantasyPrompt": "Una portada de libro de fantasía épica con elementos mágicos, dragón, castillo al fondo, efectos luminosos místicos",
"scifi": "Ciencia Ficción",
"scifiPrompt": "Una portada de libro de ciencia ficción futurista con naves espaciales, luces de neón, ciudad cyberpunk, atmósfera high-tech",
"selfhelp": "Autoayuda",
"selfhelpPrompt": "Una portada de libro de autoayuda limpia con imágenes inspiradoras, cima de montaña o amanecer, profesional y motivadora",
"generating": "Generando...",
"generateCover": "Generar Portada"
}
}
}

View File

@@ -13,6 +13,9 @@ export interface BookOutline {
genre: string;
logline: string;
chapters: Chapter[];
// Error handling properties
error?: string;
raw?: string;
}
export interface Character {

812
node_modules/.package-lock.json generated vendored
View File

@@ -13,6 +13,12 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-query": "^5.17.0",
"@tiptap/extension-placeholder": "^3.22.2",
"@tiptap/extension-text-align": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@tiptap/react": "^3.22.2",
"@tiptap/starter-kit": "^3.22.2",
"axios": "^1.6.5",
"clsx": "^2.1.0",
"i18next": "^26.0.3",
@@ -1544,6 +1550,12 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT"
},
"node_modules/@remirror/core-constants": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
"license": "MIT"
},
"node_modules/@remix-run/router": {
"version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
@@ -1609,6 +1621,466 @@
"react": "^18 || ^19"
}
},
"node_modules/@tiptap/core": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.2.tgz",
"integrity": "sha512-atq35NkpeEphH6vNYJ0pTLLBA73FAbvTV9Ovd3AaTC5s99/KF5Q86zVJXvml8xPRcMGM6dLp+eSSd06oTscMSA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.22.2.tgz",
"integrity": "sha512-iTdlmGFcgxi4LKaOW2Rc9/yD83qTXgRm5BN3vCHWy5+TbEnReYxYqU5qKsbtTbKy30sO8TJTdAXTZ29uomShQQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bold": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.2.tgz",
"integrity": "sha512-bqsPJyKcT/RWse4e16U2EKhraR8a2+98TUuk1amG3yCyFJZStoO/j+pN0IqZdZZjr3WtxFyvwWp7Kc59UN+jUA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bubble-menu": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.2.tgz",
"integrity": "sha512-5hbyDOSkJwA2uh0v9Mm0Dd9bb9inx6tHBEDSH2tCB9Rm23poz3yOreB7SNX8xDMe5L0/PQesfWC14RitcmhKPg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.2.tgz",
"integrity": "sha512-llrTJnA72RGcWLLO+ro0QN4sjHynhaCerhpV+GZE/ATd8BqV/ekQFdBLJrvC/09My2XQfCwLsyCh92NPXUdELA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-code": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.2.tgz",
"integrity": "sha512-iYFY+yzfYA9MKt7nupyW/PzqL9XC2D0mC8l1z2Y10i0/fGL8NbqIYjhNUAyXGqH3QWcI+DirI66842y2OadPOg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.2.tgz",
"integrity": "sha512-PEwFlDyvtKF19WCrOFg77qJV9WqhvjCY4ZoXlHP9Hx0KTcOA8W39mtw8d4NWU5pLRK94yHKF1DVVL8UUkEOnww==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-document": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.2.tgz",
"integrity": "sha512-yPw9pQeVC4QDh86TuyKCZxxM4g0NAw7mEtGnAo6EpxaBQr1wyBr9yFpys+QTsQpRTmyTf1VHp4iTTLuWHMljIw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.22.2.tgz",
"integrity": "sha512-sDv3fv4LtX0X4nqwh9Gn3C/aZXT+C2JlK7tJovPOpaYP/a6hr03Sn35X5moAfgMCSiWFygEvlTriqwmCsJuxog==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-floating-menu": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.22.2.tgz",
"integrity": "sha512-r0ZTeh9rNtj9Api+G0YyaB+tAKPDn7aYWg+qSrmAC5EyUPee6Zjn3zlw0q4renCeQflvNRK20xHM8zokC41jOA==",
"license": "MIT",
"optional": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@floating-ui/dom": "^1.0.0",
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.2.tgz",
"integrity": "sha512-rR2OLrl/k2kj7xehaZHq0Y7T+1wy2DOTabir9LsTrktTFEcklrh9qY1KC6rEBkwMKaWrmignR1l39kS6RlKFNw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.22.2.tgz",
"integrity": "sha512-ChsoqF4XRp6EWatTRlXL4LMFh/ggwRVCyt09brSfjJV5knFaXlECSa5/+rKLMLMULaj6dVlJqoAD15exgu2HHA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-heading": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.2.tgz",
"integrity": "sha512-QPHLef+ikAyf7RVc4EdGeKxH4OEGb3ueCEwJ41RcYPtZ1BX9ueei7FC936guTdL1U7w3vQ65qfy86HznzkYgvw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.2.tgz",
"integrity": "sha512-Oz8KN5KJAWV1mFNE9UIWXdMD6xa5zPf/0yLsT8V4sgaRm+VsdFKllN58BY9qCZf/kIZbaOez5KkaoeAcm0MAZg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-italic": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.2.tgz",
"integrity": "sha512-fmtQu2HDnV3sOZPdz0+1lOLI7UtrIhusohJj2UwOLQxG8qqhLwbvWx2OQTlfblgY0z+CjLRr6ANbNDxOTIblfg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-link": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.2.tgz",
"integrity": "sha512-TXfSoKmng5pecvQUZqdsx6ICeob5V5hhYOj2vCEtjfcjWsyCndqFIl1w+Nt/yI5ehrFNOVPyj3ZvcELuuAW6pw==",
"license": "MIT",
"dependencies": {
"linkifyjs": "^4.3.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.2.tgz",
"integrity": "sha512-Vq9xScgkA2A3Zj9dQ4WUBKK7u7UCzeSFRz9FcKTQVZHRPbZoqFGnlRUVngqsE7JXrCOthXQ1dXxgk40nAsBFRw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.22.2.tgz",
"integrity": "sha512-Mk+iiLIFh8Pfuarr6mWfTO7QJbd2ZQd0nGNhNWXlGAO7DJCb4BP9nj4bEIJ17SbcykGRjsi4WMqY50z4MHXqKQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list-keymap": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.22.2.tgz",
"integrity": "sha512-TozU9V2vldMUPpTXnfLCO33EO06jLxn7uEJTMBnN4iX/dLV3cBVCbE4kHyDKS0sLd7joUeekS06vYP9uQb1hFw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.2.tgz",
"integrity": "sha512-K7qxoBKmsVkAd3kW64ZRCUPFrDcNGpXRDUBx9YgAO/bTfsfxtH2oil+igsUWGXPczpP4yoHPKjTfhpBpLjGl6Q==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.2.tgz",
"integrity": "sha512-EHZZzxVhvzEPDPWtRBF1YKhB+WCUjd1C2NhjHfL3Dl71PBqM3ZWA6qN7NDGPyNyGGWauui/NR/4X+5AfPqlHyA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-placeholder": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.22.2.tgz",
"integrity": "sha512-xYw733CmSeG7MyYBDdV5NFiwlBdXXzw4Mvjb2t4QRXagkDbHeNY/LtKTcrtcMNfO4Jx0mwivGQZUIEC8oAfvxg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-strike": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.2.tgz",
"integrity": "sha512-YFC3elKU1L8PiGbcB6tqd/7vWPF5IbydJz0POJpHzSjstX+VfT8VsvS7ubxVuSIWQ11kGkH3mzX6LX8JHsHZxg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-text": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.2.tgz",
"integrity": "sha512-J1w7JwijfSD7ah0WfiwZ/DVWCIGT9x369RM4RJc57i44mIBElj7tl1dh+N5KPGOXKUup4gr7sSJAE38lgeaDMg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-text-align": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.22.2.tgz",
"integrity": "sha512-pgqyXzVHo4WmDhK26rDwhK2lxQwnjl/9DP816C2k3To/fZRK1eW7q0pSAYteHWmKkaYAxwj/0UvCU0nXKlPujw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-underline": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.2.tgz",
"integrity": "sha512-BaV6WOowxdkGTLWiU7DdZ3Twh633O4RGqwUM5dDas5LvaqL8AMWGTO8Wg9yAaaKXzd9MtKI1ZCqS/+MtzusgkQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extensions": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.2.tgz",
"integrity": "sha512-s7MZmm2Xdq+8feIXgY3v7gVpQ5ClqBZi20KheouS7KSbBlrY4fu2irYR1EGc6r1UUVaHMxEa+cx5knhx+mIPUw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/pm": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.2.tgz",
"integrity": "sha512-G2ENwIazoSKkAnN5MN5yN91TIZNFm6TxB74kPf3Empr2k9W51Hkcier70jHGpArhgcEaL4BVreuU1PRDRwCeGw==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.3.0",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.6.2",
"prosemirror-dropcursor": "^1.8.1",
"prosemirror-gapcursor": "^1.3.2",
"prosemirror-history": "^1.4.1",
"prosemirror-inputrules": "^1.4.0",
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.1",
"prosemirror-menu": "^1.2.4",
"prosemirror-model": "^1.24.1",
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.5.0",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.4",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.38.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@tiptap/react": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.22.2.tgz",
"integrity": "sha512-tyGKG69e/MkpoD/JTpVPz0XydEHxh1MSAYnLb3gRvyvBDv2r/veLea+cApkmjQaCfkKC/CWwTFXBYlOB0caSBA==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"fast-equals": "^5.3.3",
"use-sync-external-store": "^1.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"optionalDependencies": {
"@tiptap/extension-bubble-menu": "^3.22.2",
"@tiptap/extension-floating-menu": "^3.22.2"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tiptap/starter-kit": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.22.2.tgz",
"integrity": "sha512-+CCKX8tOQ/ZPb2k/z6em4AQCFYAcdd8+0TOzPWiuLxRyCHRPBBVhnPsXOKgKwE4OO3E8BsezquuYRYRwsyzCqg==",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/extension-blockquote": "^3.22.2",
"@tiptap/extension-bold": "^3.22.2",
"@tiptap/extension-bullet-list": "^3.22.2",
"@tiptap/extension-code": "^3.22.2",
"@tiptap/extension-code-block": "^3.22.2",
"@tiptap/extension-document": "^3.22.2",
"@tiptap/extension-dropcursor": "^3.22.2",
"@tiptap/extension-gapcursor": "^3.22.2",
"@tiptap/extension-hard-break": "^3.22.2",
"@tiptap/extension-heading": "^3.22.2",
"@tiptap/extension-horizontal-rule": "^3.22.2",
"@tiptap/extension-italic": "^3.22.2",
"@tiptap/extension-link": "^3.22.2",
"@tiptap/extension-list": "^3.22.2",
"@tiptap/extension-list-item": "^3.22.2",
"@tiptap/extension-list-keymap": "^3.22.2",
"@tiptap/extension-ordered-list": "^3.22.2",
"@tiptap/extension-paragraph": "^3.22.2",
"@tiptap/extension-strike": "^3.22.2",
"@tiptap/extension-text": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/extensions": "^3.22.2",
"@tiptap/pm": "^3.22.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1741,6 +2213,28 @@
"@types/node": "*"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@@ -1807,7 +2301,6 @@
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
@@ -1855,6 +2348,12 @@
"@types/node": "*"
}
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -1974,6 +2473,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/aria-hidden": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
@@ -2662,6 +3167,12 @@
"resolved": "server",
"link": true
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cross-fetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
@@ -2938,6 +3449,18 @@
"node": ">= 0.8"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
@@ -3053,6 +3576,18 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -3195,6 +3730,15 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-equals": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
"integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -4037,6 +4581,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/linkifyjs": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
"integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
"license": "MIT"
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@@ -4138,6 +4697,23 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -4147,6 +4723,12 @@
"node": ">= 0.4"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -4505,6 +5087,12 @@
"node": ">= 0.8"
}
},
"node_modules/orderedmap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
"license": "MIT"
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -4836,6 +5424,201 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/prosemirror-changeset": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz",
"integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==",
"license": "MIT",
"dependencies": {
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-collab": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.10.2"
}
},
"node_modules/prosemirror-dropcursor": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"node_modules/prosemirror-gapcursor": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"node_modules/prosemirror-history": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.31.0",
"rope-sequence": "^1.3.0"
}
},
"node_modules/prosemirror-inputrules": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
"integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-keymap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"node_modules/prosemirror-markdown": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
"integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
"license": "MIT",
"dependencies": {
"@types/markdown-it": "^14.0.0",
"markdown-it": "^14.0.0",
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-menu": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz",
"integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==",
"license": "MIT",
"dependencies": {
"crelt": "^1.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-model": {
"version": "1.25.4",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
"license": "MIT",
"dependencies": {
"orderedmap": "^2.0.0"
}
},
"node_modules/prosemirror-schema-basic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
"integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-schema-list": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.7.3"
}
},
"node_modules/prosemirror-state": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.27.0"
}
},
"node_modules/prosemirror-tables": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.2.3",
"prosemirror-model": "^1.25.4",
"prosemirror-state": "^1.4.4",
"prosemirror-transform": "^1.10.5",
"prosemirror-view": "^1.41.4"
}
},
"node_modules/prosemirror-trailing-node": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
"license": "MIT",
"dependencies": {
"@remirror/core-constants": "3.0.0",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"prosemirror-model": "^1.22.1",
"prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.33.8"
}
},
"node_modules/prosemirror-transform": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz",
"integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.21.0"
}
},
"node_modules/prosemirror-view": {
"version": "1.41.8",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
"integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -4858,6 +5641,15 @@
"node": ">=10"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/pure-rand": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
@@ -5331,6 +6123,12 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rope-sequence": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
"license": "MIT"
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6034,6 +6832,12 @@
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -6307,6 +7111,12 @@
"node": ">=0.10.0"
}
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",

812
package-lock.json generated
View File

@@ -24,6 +24,12 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-query": "^5.17.0",
"@tiptap/extension-placeholder": "^3.22.2",
"@tiptap/extension-text-align": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@tiptap/react": "^3.22.2",
"@tiptap/starter-kit": "^3.22.2",
"axios": "^1.6.5",
"clsx": "^2.1.0",
"i18next": "^26.0.3",
@@ -2434,6 +2440,12 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT"
},
"node_modules/@remirror/core-constants": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
"license": "MIT"
},
"node_modules/@remix-run/router": {
"version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
@@ -2871,6 +2883,466 @@
"react": "^18 || ^19"
}
},
"node_modules/@tiptap/core": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.2.tgz",
"integrity": "sha512-atq35NkpeEphH6vNYJ0pTLLBA73FAbvTV9Ovd3AaTC5s99/KF5Q86zVJXvml8xPRcMGM6dLp+eSSd06oTscMSA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.22.2.tgz",
"integrity": "sha512-iTdlmGFcgxi4LKaOW2Rc9/yD83qTXgRm5BN3vCHWy5+TbEnReYxYqU5qKsbtTbKy30sO8TJTdAXTZ29uomShQQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bold": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.2.tgz",
"integrity": "sha512-bqsPJyKcT/RWse4e16U2EKhraR8a2+98TUuk1amG3yCyFJZStoO/j+pN0IqZdZZjr3WtxFyvwWp7Kc59UN+jUA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bubble-menu": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.2.tgz",
"integrity": "sha512-5hbyDOSkJwA2uh0v9Mm0Dd9bb9inx6tHBEDSH2tCB9Rm23poz3yOreB7SNX8xDMe5L0/PQesfWC14RitcmhKPg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.2.tgz",
"integrity": "sha512-llrTJnA72RGcWLLO+ro0QN4sjHynhaCerhpV+GZE/ATd8BqV/ekQFdBLJrvC/09My2XQfCwLsyCh92NPXUdELA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-code": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.2.tgz",
"integrity": "sha512-iYFY+yzfYA9MKt7nupyW/PzqL9XC2D0mC8l1z2Y10i0/fGL8NbqIYjhNUAyXGqH3QWcI+DirI66842y2OadPOg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.2.tgz",
"integrity": "sha512-PEwFlDyvtKF19WCrOFg77qJV9WqhvjCY4ZoXlHP9Hx0KTcOA8W39mtw8d4NWU5pLRK94yHKF1DVVL8UUkEOnww==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-document": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.2.tgz",
"integrity": "sha512-yPw9pQeVC4QDh86TuyKCZxxM4g0NAw7mEtGnAo6EpxaBQr1wyBr9yFpys+QTsQpRTmyTf1VHp4iTTLuWHMljIw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.22.2.tgz",
"integrity": "sha512-sDv3fv4LtX0X4nqwh9Gn3C/aZXT+C2JlK7tJovPOpaYP/a6hr03Sn35X5moAfgMCSiWFygEvlTriqwmCsJuxog==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-floating-menu": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.22.2.tgz",
"integrity": "sha512-r0ZTeh9rNtj9Api+G0YyaB+tAKPDn7aYWg+qSrmAC5EyUPee6Zjn3zlw0q4renCeQflvNRK20xHM8zokC41jOA==",
"license": "MIT",
"optional": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@floating-ui/dom": "^1.0.0",
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.2.tgz",
"integrity": "sha512-rR2OLrl/k2kj7xehaZHq0Y7T+1wy2DOTabir9LsTrktTFEcklrh9qY1KC6rEBkwMKaWrmignR1l39kS6RlKFNw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.22.2.tgz",
"integrity": "sha512-ChsoqF4XRp6EWatTRlXL4LMFh/ggwRVCyt09brSfjJV5knFaXlECSa5/+rKLMLMULaj6dVlJqoAD15exgu2HHA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-heading": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.2.tgz",
"integrity": "sha512-QPHLef+ikAyf7RVc4EdGeKxH4OEGb3ueCEwJ41RcYPtZ1BX9ueei7FC936guTdL1U7w3vQ65qfy86HznzkYgvw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.2.tgz",
"integrity": "sha512-Oz8KN5KJAWV1mFNE9UIWXdMD6xa5zPf/0yLsT8V4sgaRm+VsdFKllN58BY9qCZf/kIZbaOez5KkaoeAcm0MAZg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-italic": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.2.tgz",
"integrity": "sha512-fmtQu2HDnV3sOZPdz0+1lOLI7UtrIhusohJj2UwOLQxG8qqhLwbvWx2OQTlfblgY0z+CjLRr6ANbNDxOTIblfg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-link": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.2.tgz",
"integrity": "sha512-TXfSoKmng5pecvQUZqdsx6ICeob5V5hhYOj2vCEtjfcjWsyCndqFIl1w+Nt/yI5ehrFNOVPyj3ZvcELuuAW6pw==",
"license": "MIT",
"dependencies": {
"linkifyjs": "^4.3.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.2.tgz",
"integrity": "sha512-Vq9xScgkA2A3Zj9dQ4WUBKK7u7UCzeSFRz9FcKTQVZHRPbZoqFGnlRUVngqsE7JXrCOthXQ1dXxgk40nAsBFRw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.22.2.tgz",
"integrity": "sha512-Mk+iiLIFh8Pfuarr6mWfTO7QJbd2ZQd0nGNhNWXlGAO7DJCb4BP9nj4bEIJ17SbcykGRjsi4WMqY50z4MHXqKQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-list-keymap": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.22.2.tgz",
"integrity": "sha512-TozU9V2vldMUPpTXnfLCO33EO06jLxn7uEJTMBnN4iX/dLV3cBVCbE4kHyDKS0sLd7joUeekS06vYP9uQb1hFw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.2.tgz",
"integrity": "sha512-K7qxoBKmsVkAd3kW64ZRCUPFrDcNGpXRDUBx9YgAO/bTfsfxtH2oil+igsUWGXPczpP4yoHPKjTfhpBpLjGl6Q==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.22.2"
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.2.tgz",
"integrity": "sha512-EHZZzxVhvzEPDPWtRBF1YKhB+WCUjd1C2NhjHfL3Dl71PBqM3ZWA6qN7NDGPyNyGGWauui/NR/4X+5AfPqlHyA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-placeholder": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.22.2.tgz",
"integrity": "sha512-xYw733CmSeG7MyYBDdV5NFiwlBdXXzw4Mvjb2t4QRXagkDbHeNY/LtKTcrtcMNfO4Jx0mwivGQZUIEC8oAfvxg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.22.2"
}
},
"node_modules/@tiptap/extension-strike": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.2.tgz",
"integrity": "sha512-YFC3elKU1L8PiGbcB6tqd/7vWPF5IbydJz0POJpHzSjstX+VfT8VsvS7ubxVuSIWQ11kGkH3mzX6LX8JHsHZxg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-text": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.2.tgz",
"integrity": "sha512-J1w7JwijfSD7ah0WfiwZ/DVWCIGT9x369RM4RJc57i44mIBElj7tl1dh+N5KPGOXKUup4gr7sSJAE38lgeaDMg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-text-align": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.22.2.tgz",
"integrity": "sha512-pgqyXzVHo4WmDhK26rDwhK2lxQwnjl/9DP816C2k3To/fZRK1eW7q0pSAYteHWmKkaYAxwj/0UvCU0nXKlPujw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extension-underline": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.2.tgz",
"integrity": "sha512-BaV6WOowxdkGTLWiU7DdZ3Twh633O4RGqwUM5dDas5LvaqL8AMWGTO8Wg9yAaaKXzd9MtKI1ZCqS/+MtzusgkQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2"
}
},
"node_modules/@tiptap/extensions": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.2.tgz",
"integrity": "sha512-s7MZmm2Xdq+8feIXgY3v7gVpQ5ClqBZi20KheouS7KSbBlrY4fu2irYR1EGc6r1UUVaHMxEa+cx5knhx+mIPUw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2"
}
},
"node_modules/@tiptap/pm": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.2.tgz",
"integrity": "sha512-G2ENwIazoSKkAnN5MN5yN91TIZNFm6TxB74kPf3Empr2k9W51Hkcier70jHGpArhgcEaL4BVreuU1PRDRwCeGw==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.3.0",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.6.2",
"prosemirror-dropcursor": "^1.8.1",
"prosemirror-gapcursor": "^1.3.2",
"prosemirror-history": "^1.4.1",
"prosemirror-inputrules": "^1.4.0",
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.1",
"prosemirror-menu": "^1.2.4",
"prosemirror-model": "^1.24.1",
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.5.0",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.4",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.38.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@tiptap/react": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.22.2.tgz",
"integrity": "sha512-tyGKG69e/MkpoD/JTpVPz0XydEHxh1MSAYnLb3gRvyvBDv2r/veLea+cApkmjQaCfkKC/CWwTFXBYlOB0caSBA==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"fast-equals": "^5.3.3",
"use-sync-external-store": "^1.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"optionalDependencies": {
"@tiptap/extension-bubble-menu": "^3.22.2",
"@tiptap/extension-floating-menu": "^3.22.2"
},
"peerDependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/pm": "^3.22.2",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tiptap/starter-kit": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.22.2.tgz",
"integrity": "sha512-+CCKX8tOQ/ZPb2k/z6em4AQCFYAcdd8+0TOzPWiuLxRyCHRPBBVhnPsXOKgKwE4OO3E8BsezquuYRYRwsyzCqg==",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^3.22.2",
"@tiptap/extension-blockquote": "^3.22.2",
"@tiptap/extension-bold": "^3.22.2",
"@tiptap/extension-bullet-list": "^3.22.2",
"@tiptap/extension-code": "^3.22.2",
"@tiptap/extension-code-block": "^3.22.2",
"@tiptap/extension-document": "^3.22.2",
"@tiptap/extension-dropcursor": "^3.22.2",
"@tiptap/extension-gapcursor": "^3.22.2",
"@tiptap/extension-hard-break": "^3.22.2",
"@tiptap/extension-heading": "^3.22.2",
"@tiptap/extension-horizontal-rule": "^3.22.2",
"@tiptap/extension-italic": "^3.22.2",
"@tiptap/extension-link": "^3.22.2",
"@tiptap/extension-list": "^3.22.2",
"@tiptap/extension-list-item": "^3.22.2",
"@tiptap/extension-list-keymap": "^3.22.2",
"@tiptap/extension-ordered-list": "^3.22.2",
"@tiptap/extension-paragraph": "^3.22.2",
"@tiptap/extension-strike": "^3.22.2",
"@tiptap/extension-text": "^3.22.2",
"@tiptap/extension-underline": "^3.22.2",
"@tiptap/extensions": "^3.22.2",
"@tiptap/pm": "^3.22.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -3003,6 +3475,28 @@
"@types/node": "*"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@@ -3069,7 +3563,6 @@
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
@@ -3117,6 +3610,12 @@
"@types/node": "*"
}
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -3236,6 +3735,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/aria-hidden": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
@@ -3924,6 +4429,12 @@
"resolved": "server",
"link": true
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cross-fetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
@@ -4200,6 +4711,18 @@
"node": ">= 0.8"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
@@ -4315,6 +4838,18 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -4457,6 +4992,15 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-equals": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
"integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -5314,6 +5858,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/linkifyjs": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
"integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
"license": "MIT"
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@@ -5415,6 +5974,23 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5424,6 +6000,12 @@
"node": ">= 0.4"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -5784,6 +6366,12 @@
"node": ">= 0.8"
}
},
"node_modules/orderedmap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
"license": "MIT"
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -6115,6 +6703,201 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/prosemirror-changeset": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz",
"integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==",
"license": "MIT",
"dependencies": {
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-collab": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.10.2"
}
},
"node_modules/prosemirror-dropcursor": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"node_modules/prosemirror-gapcursor": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"node_modules/prosemirror-history": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.31.0",
"rope-sequence": "^1.3.0"
}
},
"node_modules/prosemirror-inputrules": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
"integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-keymap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"node_modules/prosemirror-markdown": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
"integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
"license": "MIT",
"dependencies": {
"@types/markdown-it": "^14.0.0",
"markdown-it": "^14.0.0",
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-menu": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz",
"integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==",
"license": "MIT",
"dependencies": {
"crelt": "^1.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-model": {
"version": "1.25.4",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
"license": "MIT",
"dependencies": {
"orderedmap": "^2.0.0"
}
},
"node_modules/prosemirror-schema-basic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
"integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-schema-list": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.7.3"
}
},
"node_modules/prosemirror-state": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.27.0"
}
},
"node_modules/prosemirror-tables": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.2.3",
"prosemirror-model": "^1.25.4",
"prosemirror-state": "^1.4.4",
"prosemirror-transform": "^1.10.5",
"prosemirror-view": "^1.41.4"
}
},
"node_modules/prosemirror-trailing-node": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
"license": "MIT",
"dependencies": {
"@remirror/core-constants": "3.0.0",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"prosemirror-model": "^1.22.1",
"prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.33.8"
}
},
"node_modules/prosemirror-transform": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz",
"integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.21.0"
}
},
"node_modules/prosemirror-view": {
"version": "1.41.8",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
"integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -6137,6 +6920,15 @@
"node": ">=10"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/pure-rand": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
@@ -6610,6 +7402,12 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rope-sequence": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
"license": "MIT"
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -7313,6 +8111,12 @@
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -7960,6 +8764,12 @@
"node": ">=0.10.0"
}
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",

View File

@@ -46,16 +46,22 @@ Core Idea: ${idea}
Generate a detailed chapter outline following the structure: ${template.structure.join(' → ')}
IMPORTANT: The response MUST be written in ${targetLang}. All text values (title, logline, chapter summaries, etc) must be translated to ${targetLang}. Keep the exact JSON KEYS in English.
CRITICAL INSTRUCTIONS:
- You MUST respond with VALID JSON only
- Do NOT include any text before or after the JSON
- Do NOT wrap the JSON in markdown code blocks
- All text content (titles, summaries, etc.) MUST be in ${targetLang}
- JSON keys MUST remain in English
- Ensure the JSON is properly formatted and parseable
Return the response in JSON format:
Required JSON format:
{
"title": "Book Title",
"title": "Book Title in ${targetLang}",
"genre": "${genre}",
"logline": "One sentence summary",
"logline": "One sentence summary in ${targetLang}",
"chapters": [
{"number": 1, "title": "Chapter Title", "summary": "Brief description"},
...
{"number": 1, "title": "Chapter Title in ${targetLang}", "summary": "Brief description in ${targetLang}"},
{"number": 2, "title": "Chapter Title in ${targetLang}", "summary": "Brief description in ${targetLang}"}
]
}`;
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
@@ -74,22 +80,29 @@ Return the response in JSON format:
const content = response.data.choices[0].message.content;
console.log('=== Outline AI Response ===');
console.log('Raw content:', content);
// Try to parse JSON from response
console.log('Target language:', targetLang);
// Try to parse JSON from response - improved parsing for multilingual content
let outline;
try {
// First try to find JSON object
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
outline = JSON.parse(jsonMatch[0]);
// Clean the JSON string to handle potential encoding issues
let jsonString = jsonMatch[0];
// Remove any markdown formatting that might interfere
jsonString = jsonString.replace(/```json\s*/g, '').replace(/```\s*$/g, '');
outline = JSON.parse(jsonString);
console.log('Parsed outline:', outline);
}
catch (parseError) {
console.error('JSON parse error:', parseError);
outline = { raw: content, error: 'Failed to parse JSON' };
}
}
else {
console.error('No JSON object found in response');
outline = { raw: content, error: 'No JSON found' };
outline = { raw: content, error: 'No JSON found in AI response' };
}
}
catch (parseError) {
console.error('JSON parse error:', parseError);
console.error('Failed content:', content);
outline = { raw: content, error: 'Failed to parse JSON from AI response' };
}
res.json({ outline });
}
@@ -117,8 +130,11 @@ Summary: ${chapterSummary}
Tone: ${template.defaults.tone}
POV: ${template.defaults.pov}
IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}.
`;
CRITICAL: Write the ENTIRE chapter content in ${targetLang} only. Do NOT include any English text.
Write a complete, engaging chapter that matches the summary above.
The chapter should be between 1500-3000 words.
IMPORTANT: Respond with the chapter content directly, without any introduction, conclusion, or formatting markers. Just the narrative text in ${targetLang}.`;
if (previousContent) {
prompt += `\n\nPrevious content for context:\n${previousContent.substring(0, 2000)}...`;
}
@@ -136,6 +152,11 @@ IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}.
}
});
const content = response.data.choices[0].message.content;
console.log('=== Chapter AI Response ===');
console.log('Target language:', targetLang);
console.log('Chapter title:', chapterTitle);
console.log('Content length:', content.length);
console.log('Content preview:', content.substring(0, 200) + '...');
res.json({
content,
chapterTitle

File diff suppressed because one or more lines are too long

View File

@@ -49,16 +49,22 @@ Core Idea: ${idea}
Generate a detailed chapter outline following the structure: ${template.structure.join(' → ')}
IMPORTANT: The response MUST be written in ${targetLang}. All text values (title, logline, chapter summaries, etc) must be translated to ${targetLang}. Keep the exact JSON KEYS in English.
CRITICAL INSTRUCTIONS:
- You MUST respond with VALID JSON only
- Do NOT include any text before or after the JSON
- Do NOT wrap the JSON in markdown code blocks
- All text content (titles, summaries, etc.) MUST be in ${targetLang}
- JSON keys MUST remain in English
- Ensure the JSON is properly formatted and parseable
Return the response in JSON format:
Required JSON format:
{
"title": "Book Title",
"title": "Book Title in ${targetLang}",
"genre": "${genre}",
"logline": "One sentence summary",
"logline": "One sentence summary in ${targetLang}",
"chapters": [
{"number": 1, "title": "Chapter Title", "summary": "Brief description"},
...
{"number": 1, "title": "Chapter Title in ${targetLang}", "summary": "Brief description in ${targetLang}"},
{"number": 2, "title": "Chapter Title in ${targetLang}", "summary": "Brief description in ${targetLang}"}
]
}`;
@@ -80,22 +86,28 @@ Return the response in JSON format:
console.log('=== Outline AI Response ===');
console.log('Raw content:', content);
console.log('Target language:', targetLang);
// Try to parse JSON from response
// Try to parse JSON from response - improved parsing for multilingual content
let outline;
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
outline = JSON.parse(jsonMatch[0]);
// First try to find JSON object
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
// Clean the JSON string to handle potential encoding issues
let jsonString = jsonMatch[0];
// Remove any markdown formatting that might interfere
jsonString = jsonString.replace(/```json\s*/g, '').replace(/```\s*$/g, '');
outline = JSON.parse(jsonString);
console.log('Parsed outline:', outline);
} catch (parseError) {
console.error('JSON parse error:', parseError);
outline = { raw: content, error: 'Failed to parse JSON' };
}
} else {
console.error('No JSON object found in response');
outline = { raw: content, error: 'No JSON found' };
outline = { raw: content, error: 'No JSON found in AI response' };
}
} catch (parseError) {
console.error('JSON parse error:', parseError);
console.error('Failed content:', content);
outline = { raw: content, error: 'Failed to parse JSON from AI response' };
}
res.json({ outline });
@@ -127,8 +139,11 @@ Summary: ${chapterSummary}
Tone: ${template.defaults.tone}
POV: ${template.defaults.pov}
IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}.
`;
CRITICAL: Write the ENTIRE chapter content in ${targetLang} only. Do NOT include any English text.
Write a complete, engaging chapter that matches the summary above.
The chapter should be between 1500-3000 words.
IMPORTANT: Respond with the chapter content directly, without any introduction, conclusion, or formatting markers. Just the narrative text in ${targetLang}.`;
if (previousContent) {
prompt += `\n\nPrevious content for context:\n${previousContent.substring(0, 2000)}...`;
@@ -150,6 +165,12 @@ IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}.
const content = response.data.choices[0].message.content;
console.log('=== Chapter AI Response ===');
console.log('Target language:', targetLang);
console.log('Chapter title:', chapterTitle);
console.log('Content length:', content.length);
console.log('Content preview:', content.substring(0, 200) + '...');
res.json({
content,
chapterTitle