Remove unused components and models for improved maintainability
- Deleted redundant components (`AddActionButton`, `AlertBox`, `AlertStack`, `BackButton`, `CancelButton`, and `CollapsableArea`) and related files. - Removed unused models (`Book`, `BookSerie`, `BookTables`, `Character`, and `Chapter`) to reduce codebase clutter. - Updated project structure and references to reflect these removals.
This commit is contained in:
94
lib/api/client.ts
Normal file
94
lib/api/client.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import axios, {AxiosResponse, Method} from "axios";
|
||||
import {configs, isDesktop} from "@/lib/configs";
|
||||
|
||||
type ContentType = 'application/json' | 'multipart/form-data';
|
||||
|
||||
interface ApiRequestConfig {
|
||||
method: Method;
|
||||
url: string;
|
||||
auth: string;
|
||||
lang?: string;
|
||||
params?: Record<string, unknown>;
|
||||
data?: unknown;
|
||||
contentType?: ContentType;
|
||||
}
|
||||
|
||||
function handleApiError(error: unknown): never {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const serverMessage: string = error.response?.data?.message || error.response?.data || error.message;
|
||||
throw new Error(serverMessage);
|
||||
} else if (error instanceof Error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
|
||||
async function apiRequest<T>(config: ApiRequestConfig): Promise<T> {
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
'Authorization': `Bearer ${config.auth}`
|
||||
};
|
||||
|
||||
if (config.contentType) {
|
||||
headers['Content-Type'] = config.contentType;
|
||||
}
|
||||
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: config.method,
|
||||
headers,
|
||||
params: {
|
||||
lang: config.lang ?? 'fr',
|
||||
plateforme: isDesktop ? 'desktop' : 'web',
|
||||
...config.params
|
||||
},
|
||||
url: configs.apiUrl + config.url,
|
||||
data: config.data
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
handleApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiGet<T>(url: string, auth: string, lang: string = "fr", params: Record<string, unknown> = {}): Promise<T> {
|
||||
return apiRequest<T>({method: 'GET', url, auth, lang, params});
|
||||
}
|
||||
|
||||
export async function apiPost<T>(url: string, data: object, auth: string, lang: string = "fr"): Promise<T> {
|
||||
return apiRequest<T>({method: 'POST', url, auth, lang, data, contentType: 'application/json'});
|
||||
}
|
||||
|
||||
export async function apiPut<T>(url: string, data: object, auth: string, lang: string = "fr"): Promise<T> {
|
||||
return apiRequest<T>({method: 'PUT', url, auth, lang, data, contentType: 'application/json'});
|
||||
}
|
||||
|
||||
export async function apiPatch<T>(url: string, data: object, auth: string, lang: string = "fr"): Promise<T> {
|
||||
return apiRequest<T>({method: 'PATCH', url, auth, lang, data, contentType: 'application/json'});
|
||||
}
|
||||
|
||||
export async function apiDelete<T>(url: string, data: object, auth: string, lang: string = "fr"): Promise<T> {
|
||||
return apiRequest<T>({method: 'DELETE', url, auth, lang, data, contentType: 'application/json'});
|
||||
}
|
||||
|
||||
export async function apiPostPublic<T>(url: string, data: object, lang: string = "fr"): Promise<T> {
|
||||
try {
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
params: {lang, plateforme: isDesktop ? 'desktop' : 'web'},
|
||||
url: configs.apiUrl + url,
|
||||
data
|
||||
});
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
handleApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiUpload<T>(url: string, file: File, auth: string, lang: string = "fr"): Promise<T> {
|
||||
const formData: FormData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return apiRequest<T>({method: 'POST', url, auth, lang, data: formData, contentType: 'multipart/form-data'});
|
||||
}
|
||||
@@ -9,11 +9,12 @@ export interface Configs {
|
||||
}
|
||||
|
||||
const isProduction: boolean = false;
|
||||
export const isDesktop: boolean = true;
|
||||
|
||||
export const configs: Configs = {
|
||||
apiUrl: isProduction ? 'https://api.eritors.com/' : 'http://localhost:3001/',
|
||||
baseUrl: isProduction ? 'https://scribe.eritors.com/' : 'http://localhost:3000/',
|
||||
appName: 'ERitors Scribe',
|
||||
appDescription: 'ERitors Scribe est une application de prise de notes et d\'écriture collaborative.',
|
||||
appName: 'Eritors Scribe',
|
||||
appDescription: 'Eritors Scribe est une application de prise de notes et d\'écriture collaborative.',
|
||||
appVersion: packageJson.version,
|
||||
};
|
||||
9
lib/constants/book.ts
Normal file
9
lib/constants/book.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {SelectBoxProps} from "@/components/form/SelectBox";
|
||||
|
||||
export const bookTypes: SelectBoxProps[] = [
|
||||
{label: 'bookTypes.short', value: 'short'},
|
||||
{label: 'bookTypes.novelette', value: 'novelette'},
|
||||
{label: 'bookTypes.novella', value: 'long'},
|
||||
{label: 'bookTypes.chapbook', value: 'chapbook'},
|
||||
{label: 'bookTypes.novel', value: 'novel'},
|
||||
];
|
||||
9
lib/constants/chapter.ts
Normal file
9
lib/constants/chapter.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {SelectBoxProps} from "@/components/form/SelectBox";
|
||||
|
||||
export const chapterVersions: SelectBoxProps[] = [
|
||||
{value: '1', label: 'chapterVersions.prompt'},
|
||||
{value: '2', label: 'chapterVersions.draft'},
|
||||
{value: '3', label: 'chapterVersions.refine'},
|
||||
{value: '4', label: 'chapterVersions.review'},
|
||||
{value: '5', label: 'chapterVersions.final'},
|
||||
];
|
||||
153
lib/constants/character.ts
Normal file
153
lib/constants/character.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
Zap,
|
||||
Box,
|
||||
Brain,
|
||||
Target,
|
||||
AlertTriangle,
|
||||
Fingerprint,
|
||||
Flame,
|
||||
Ghost,
|
||||
Heart,
|
||||
HeartCrack,
|
||||
Users,
|
||||
Quote,
|
||||
Route,
|
||||
Ruler,
|
||||
Shield,
|
||||
UserX,
|
||||
Wrench
|
||||
} from 'lucide-react';
|
||||
import {SelectBoxProps} from "@/components/form/SelectBox";
|
||||
import {CharacterElement} from "@/lib/types/character";
|
||||
|
||||
export const characterCategories: SelectBoxProps[] = [
|
||||
{value: 'none', label: 'characterCategories.none'},
|
||||
{value: 'main', label: 'characterCategories.main'},
|
||||
{value: 'secondary', label: 'characterCategories.secondary'},
|
||||
{value: 'recurring', label: 'characterCategories.recurring'},
|
||||
];
|
||||
|
||||
export const characterStatus: SelectBoxProps[] = [
|
||||
{value: 'alive', label: 'characterStatus.alive'},
|
||||
{value: 'dead', label: 'characterStatus.dead'},
|
||||
{value: 'unknown', label: 'characterStatus.unknown'},
|
||||
];
|
||||
|
||||
export const basicCharacterElements: CharacterElement[] = [
|
||||
{
|
||||
title: 'Descriptions physiques',
|
||||
section: 'physical',
|
||||
placeholder: 'Nouvelle Description Physique',
|
||||
icon: Ruler,
|
||||
},
|
||||
{
|
||||
title: 'Descriptions psychologiques',
|
||||
section: 'psychological',
|
||||
placeholder: 'Nouvelle Description Psychologique',
|
||||
icon: Brain,
|
||||
},
|
||||
];
|
||||
|
||||
export const advancedCharacterElements: CharacterElement[] = [
|
||||
{
|
||||
title: 'Signes distinctifs',
|
||||
section: 'distinguishingMarks',
|
||||
placeholder: 'Nouveau signe distinctif',
|
||||
icon: Fingerprint,
|
||||
},
|
||||
{
|
||||
title: 'Arc du personnage',
|
||||
section: 'arc',
|
||||
placeholder: 'Nouvelle étape de l\'arc',
|
||||
icon: Route,
|
||||
},
|
||||
{
|
||||
title: 'Secrets',
|
||||
section: 'secrets',
|
||||
placeholder: 'Nouveau secret',
|
||||
icon: UserX,
|
||||
},
|
||||
{
|
||||
title: 'Peurs',
|
||||
section: 'fears',
|
||||
placeholder: 'Nouvelle peur',
|
||||
icon: Ghost,
|
||||
},
|
||||
{
|
||||
title: 'Défauts',
|
||||
section: 'flaws',
|
||||
placeholder: 'Nouveau défaut',
|
||||
icon: HeartCrack,
|
||||
},
|
||||
{
|
||||
title: 'Croyances',
|
||||
section: 'beliefs',
|
||||
placeholder: 'Nouvelle croyance',
|
||||
icon: Heart,
|
||||
},
|
||||
{
|
||||
title: 'Conflits internes',
|
||||
section: 'conflicts',
|
||||
placeholder: 'Nouveau conflit',
|
||||
icon: Zap,
|
||||
},
|
||||
{
|
||||
title: 'Citations',
|
||||
section: 'quotes',
|
||||
placeholder: 'Nouvelle citation',
|
||||
icon: Quote,
|
||||
},
|
||||
{
|
||||
title: 'Relations',
|
||||
section: 'relations',
|
||||
placeholder: 'Nouveau Nom de Relation',
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: 'Compétences',
|
||||
section: 'skills',
|
||||
placeholder: 'Nouvelle Compétence',
|
||||
icon: Wrench,
|
||||
},
|
||||
{
|
||||
title: 'Faiblesses',
|
||||
section: 'weaknesses',
|
||||
placeholder: 'Nouvelle Faiblesse',
|
||||
icon: AlertTriangle,
|
||||
},
|
||||
{
|
||||
title: 'Forces',
|
||||
section: 'strengths',
|
||||
placeholder: 'Nouvelle Force',
|
||||
icon: Shield,
|
||||
},
|
||||
{
|
||||
title: 'Objectifs',
|
||||
section: 'goals',
|
||||
placeholder: 'Nouvel Objectif',
|
||||
icon: Target,
|
||||
},
|
||||
{
|
||||
title: 'Motivations',
|
||||
section: 'motivations',
|
||||
placeholder: 'Nouvelle Motivation',
|
||||
icon: Flame,
|
||||
},
|
||||
{
|
||||
title: 'Objets importants',
|
||||
section: 'items',
|
||||
placeholder: 'Nouvel objet',
|
||||
icon: Box,
|
||||
},
|
||||
{
|
||||
title: 'Affiliations',
|
||||
section: 'affiliations',
|
||||
placeholder: 'Nouvelle affiliation',
|
||||
icon: Users,
|
||||
},
|
||||
];
|
||||
|
||||
export const characterElementCategory: CharacterElement[] = [
|
||||
...basicCharacterElements,
|
||||
...advancedCharacterElements,
|
||||
];
|
||||
35
lib/constants/spell.ts
Normal file
35
lib/constants/spell.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {SelectBoxProps} from "@/components/form/SelectBox";
|
||||
import {SpellEditState} from "@/lib/types/spell";
|
||||
|
||||
export const initialSpellState: SpellEditState = {
|
||||
id: null,
|
||||
name: '',
|
||||
description: '',
|
||||
appearance: '',
|
||||
tags: [],
|
||||
powerLevel: null,
|
||||
components: null,
|
||||
limitations: null,
|
||||
notes: null,
|
||||
seriesSpellId: null,
|
||||
};
|
||||
|
||||
export const spellPowerLevels: SelectBoxProps[] = [
|
||||
{value: 'none', label: 'spellPowerLevels.none'},
|
||||
{value: 'cantrip', label: 'spellPowerLevels.cantrip'},
|
||||
{value: 'novice', label: 'spellPowerLevels.novice'},
|
||||
{value: 'apprentice', label: 'spellPowerLevels.apprentice'},
|
||||
{value: 'journeyman', label: 'spellPowerLevels.journeyman'},
|
||||
{value: 'expert', label: 'spellPowerLevels.expert'},
|
||||
{value: 'master', label: 'spellPowerLevels.master'},
|
||||
{value: 'grandmaster', label: 'spellPowerLevels.grandmaster'},
|
||||
{value: 'legendary', label: 'spellPowerLevels.legendary'},
|
||||
{value: 'divine', label: 'spellPowerLevels.divine'},
|
||||
];
|
||||
|
||||
export const defaultTagColors: string[] = [
|
||||
'#51AE84', '#3A8B69', '#2196F3', '#1976D2',
|
||||
'#FFA726', '#FF9800', '#EF5350', '#E53935',
|
||||
'#AB47BC', '#9C27B0', '#26A69A', '#00897B',
|
||||
'#5C6BC0', '#3F51B5', '#EC407A', '#D81B60',
|
||||
];
|
||||
135
lib/constants/story.ts
Normal file
135
lib/constants/story.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import {RadioBoxValue} from "@/components/form/RadioBox";
|
||||
import {SelectBoxProps} from "@/components/form/SelectBox";
|
||||
|
||||
export const storyStates: RadioBoxValue[] = [
|
||||
{label: 'Suite', value: 0},
|
||||
{label: 'Début', value: 1},
|
||||
{label: 'Fin', value: 2},
|
||||
{label: 'Introduction', value: 3},
|
||||
{label: 'Final', value: 4},
|
||||
];
|
||||
|
||||
export const beginnerPredefinedType: SelectBoxProps[] = [
|
||||
{label: `Thème défini disponible.`, value: '0'},
|
||||
{label: `Féerique`, value: '3'},
|
||||
{label: 'Romance historique', value: '10'},
|
||||
{label: 'Conte de fées moderne', value: '13'},
|
||||
{label: 'Romance contemporaine', value: '17'},
|
||||
{label: 'Conte moral', value: '20'},
|
||||
];
|
||||
|
||||
export const intermediatePredefinedType: SelectBoxProps[] = [
|
||||
{label: `Thème défini disponible.`, value: '0'},
|
||||
{label: `Histoire d'horreur jeune adulte`, value: '1'},
|
||||
{label: 'Féerique', value: '3'},
|
||||
{label: 'Romance dramatique', value: '5'},
|
||||
{label: 'Fantastique sombre', value: '9'},
|
||||
{label: 'Romance historique', value: '10'},
|
||||
{label: 'Science-fiction utopique', value: '12'},
|
||||
{label: 'Conte de fées moderne', value: '13'},
|
||||
{label: 'Drame familial', value: '14'},
|
||||
{label: 'Romance contemporaine', value: '17'},
|
||||
{label: 'Science-fiction post-apocalyptique', value: '19'},
|
||||
{label: 'Conte moral', value: '20'},
|
||||
];
|
||||
|
||||
export const advancedPredefinedType: SelectBoxProps[] = [
|
||||
{label: `Thème défini disponible.`, value: '0'},
|
||||
{label: `Histoire d'horreur jeune adulte`, value: '1'},
|
||||
{label: `Horreur adulte`, value: '2'},
|
||||
{label: 'Féerique', value: '3'},
|
||||
{label: 'Science-fiction dystopique', value: '4'},
|
||||
{label: 'Romance dramatique', value: '5'},
|
||||
{label: 'Aventure épique', value: '6'},
|
||||
{label: 'Conte philosophique', value: '7'},
|
||||
{label: 'Thriller psychologique', value: '8'},
|
||||
{label: 'Fantastique sombre', value: '9'},
|
||||
{label: 'Romance historique', value: '10'},
|
||||
{label: 'Polar noir', value: '11'},
|
||||
{label: 'Science-fiction utopique', value: '12'},
|
||||
{label: 'Conte de fées moderne', value: '13'},
|
||||
{label: 'Drame familial', value: '14'},
|
||||
{label: 'Aventure maritime', value: '15'},
|
||||
{label: 'Fantaisie épique', value: '16'},
|
||||
{label: 'Romance contemporaine', value: '17'},
|
||||
{label: "Thriller d'espionnage", value: '18'},
|
||||
{label: 'Science-fiction post-apocalyptique', value: '19'},
|
||||
{label: 'Conte moral', value: '20'},
|
||||
];
|
||||
|
||||
export const beginnerNarrativePersons: SelectBoxProps[] = [
|
||||
{label: 'Sélectionner un type narrative.', value: '0'},
|
||||
{label: 'Première personne (Je acteur) - Implication émotionnelle', value: '1'},
|
||||
{label: 'Troisième omnisciente - Narration divine, savoir total', value: '3'},
|
||||
];
|
||||
|
||||
export const intermediateNarrativePersons: SelectBoxProps[] = [
|
||||
{label: 'Sélectionner un type narrative.', value: '0'},
|
||||
{label: 'Première personne (Je acteur) - Implication émotionnelle', value: '1'},
|
||||
{label: 'Première personne (Je témoin) - Observation extérieure', value: '2'},
|
||||
{label: 'Troisième omnisciente - Narration divine, savoir total', value: '3'},
|
||||
{label: 'Troisième limitée - Focus sur 1 personnage', value: '4'},
|
||||
];
|
||||
|
||||
export const advancedNarrativePersons: SelectBoxProps[] = [
|
||||
{label: 'Sélectionner un type narrative.', value: '0'},
|
||||
{label: 'Première personne (Je acteur) - Implication émotionnelle', value: '1'},
|
||||
{label: 'Première personne (Je témoin) - Observation extérieure', value: '2'},
|
||||
{label: 'Troisième omnisciente - Narration divine, savoir total', value: '3'},
|
||||
{label: 'Troisième limitée - Focus sur 1 personnage', value: '4'},
|
||||
{label: 'Deuxième personne (Tu) - Immersion/confrontation', value: '5'},
|
||||
{label: 'Nous collectif - Voix chorale, destin partagé', value: '6'},
|
||||
];
|
||||
|
||||
export const langues: SelectBoxProps[] = [
|
||||
{label: 'Sélectionner une langue.', value: '0'},
|
||||
{label: 'Français Canada', value: '1'},
|
||||
{label: 'Français France', value: '2'},
|
||||
{label: 'Français Québécois', value: '3'},
|
||||
{label: 'English Canada', value: '4'},
|
||||
];
|
||||
|
||||
export const beginnerDialogueTypes: SelectBoxProps[] = [
|
||||
{label: 'Sélectionner un type de dialogue.', value: '0'},
|
||||
{label: 'Dialogue direct - Paroles exactes des personnages', value: '1'},
|
||||
{label: 'Dialogue indirect - Paroles résumées par le narrateur', value: '2'},
|
||||
];
|
||||
|
||||
export const intermediateDialogueTypes: SelectBoxProps[] = [
|
||||
{label: 'Sélectionner un type de dialogue.', value: '0'},
|
||||
{label: 'Dialogue direct - Paroles exactes des personnages', value: '1'},
|
||||
{label: 'Dialogue indirect - Paroles résumées par le narrateur', value: '2'},
|
||||
{label: 'Dialogue mixte - Mélange de dialogue direct et indirect', value: '3'},
|
||||
];
|
||||
|
||||
export const advancedDialogueTypes: SelectBoxProps[] = [
|
||||
{label: 'Sélectionner un type de dialogue.', value: '0'},
|
||||
{label: 'Dialogue direct - Paroles exactes des personnages', value: '1'},
|
||||
{label: 'Dialogue indirect - Paroles résumées par le narrateur', value: '2'},
|
||||
{label: 'Dialogue mixte - Mélange de dialogue direct et indirect', value: '3'},
|
||||
{label: 'Monologue intérieur - Interaction avec soi-même', value: '4'},
|
||||
];
|
||||
|
||||
export const verbalTime: SelectBoxProps[] = [
|
||||
{label: 'Sélectionner un temps verbal.', value: '0'},
|
||||
{label: 'Passé Simple', value: '1'},
|
||||
{label: 'Passé Immédiat → Témoignages, récits autobiographiques', value: '2'},
|
||||
{label: 'Passé Profond → Flashbacks littéraires, tragédies', value: '3'},
|
||||
{label: 'Présent Brut → Urgence, immersion totale', value: '4'},
|
||||
{label: 'Présent Réflexif → Méditations philosophiques', value: '5'},
|
||||
{label: 'Futur Projeté → Prophéties, plans stratégiques', value: '6'},
|
||||
{label: 'Futur Catastrophe → Dystopies, récits post-apocalyptiques', value: '7'},
|
||||
{label: 'Imparfait Onirique → Rêves, souvenirs déformés', value: '8'},
|
||||
{label: 'Conditionnel Hypothétique → Uchronies, réalités alternatives', value: '9'},
|
||||
{label: 'Subjonctif Angoissé → Drames psychologiques, dilemmes', value: '10'},
|
||||
{label: 'Mélancolie Composée → Regrets, introspection nostalgique', value: '11'},
|
||||
{label: 'Urgence Narrative → Urgences', value: '12'},
|
||||
{label: 'Présent Émotionnel → Émotions intenses', value: '13'},
|
||||
{label: 'Présent Introspectif → Réflexions profondes', value: '14'},
|
||||
{label: 'Présent Historique → Histoires historiques', value: '15'},
|
||||
{label: 'Passé Réflexif → Récits introspectifs', value: '16'},
|
||||
{label: 'Futur Prophétique → Prophéties, visions apocalyptiques', value: '17'},
|
||||
{label: 'Conditionnel Visionnaire → Mondes parallèles', value: '18'},
|
||||
{label: 'Imparfait Poétique → Lyrisme, poésie narrative', value: '19'},
|
||||
{label: 'Second Person Narrative → Immersion totale', value: '20'},
|
||||
];
|
||||
8
lib/constants/user.ts
Normal file
8
lib/constants/user.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {SelectBoxProps} from "@/components/form/SelectBox";
|
||||
|
||||
export const writingLevel: SelectBoxProps[] = [
|
||||
{value: '0', label: 'Sélectionner un niveau d\'écriture'},
|
||||
{value: '1', label: 'Je suis débutant'},
|
||||
{value: '2', label: 'Je suis intermédiaire'},
|
||||
{value: '3', label: 'Je suis avancé'},
|
||||
];
|
||||
30
lib/constants/world.ts
Normal file
30
lib/constants/world.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
Crown,
|
||||
AlertTriangle,
|
||||
Flag,
|
||||
Gavel,
|
||||
Factory,
|
||||
Leaf,
|
||||
Mountain,
|
||||
Music,
|
||||
ArrowLeftRight,
|
||||
Snowflake,
|
||||
UserCog,
|
||||
Users
|
||||
} from 'lucide-react';
|
||||
import {ElementSection} from "@/lib/types/world";
|
||||
|
||||
export const elementSections: ElementSection[] = [
|
||||
{title: 'Lois', section: 'laws', icon: Gavel},
|
||||
{title: 'Biomes', section: 'biomes', icon: Mountain},
|
||||
{title: 'Problèmes', section: 'issues', icon: AlertTriangle},
|
||||
{title: 'Coutumes', section: 'customs', icon: ArrowLeftRight},
|
||||
{title: 'Royaumes', section: 'kingdoms', icon: Flag},
|
||||
{title: 'Climat', section: 'climate', icon: Snowflake},
|
||||
{title: 'Ressources', section: 'resources', icon: Factory},
|
||||
{title: 'Faune', section: 'wildlife', icon: Leaf},
|
||||
{title: 'Arts', section: 'arts', icon: Music},
|
||||
{title: 'Groupes ethniques', section: 'ethnicGroups', icon: Users},
|
||||
{title: 'Classes sociales', section: 'socialClasses', icon: UserCog},
|
||||
{title: 'Personnages importants', section: 'importantCharacters', icon: Crown},
|
||||
];
|
||||
30
lib/i18n.ts
Normal file
30
lib/i18n.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import i18n from 'i18next';
|
||||
import {initReactI18next, useTranslation} from 'react-i18next';
|
||||
import fr from '@/lib/locales/fr.json';
|
||||
import en from '@/lib/locales/en.json';
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
fr: {translation: fr},
|
||||
en: {translation: en},
|
||||
},
|
||||
lng: 'fr',
|
||||
fallbackLng: 'fr',
|
||||
interpolation: {escapeValue: false},
|
||||
});
|
||||
|
||||
export function useTranslations(namespace?: string) {
|
||||
const {t} = useTranslation();
|
||||
if (namespace) {
|
||||
return (key: string, params?: Record<string, unknown>) =>
|
||||
t(`${namespace}.${key}`, params as Record<string, string>);
|
||||
}
|
||||
return (key: string, params?: Record<string, unknown>) =>
|
||||
t(key, params as Record<string, string>);
|
||||
}
|
||||
|
||||
export function changeLanguage(lang: string) {
|
||||
i18n.changeLanguage(lang);
|
||||
}
|
||||
|
||||
export {i18n};
|
||||
1
lib/image.ts
Normal file
1
lib/image.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const Image = 'img';
|
||||
@@ -13,121 +13,6 @@
|
||||
"activate": "Activate",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"loginPage": {
|
||||
"title": "Login",
|
||||
"welcome": "Welcome to ERitors",
|
||||
"orSocial": "or continue with",
|
||||
"noAccount": "Don't have an account yet?",
|
||||
"createAccount": "Create one here",
|
||||
"backToLogin": "Back to login",
|
||||
"offlineWarning": {
|
||||
"title": "First sync required",
|
||||
"message": "An Internet connection is required for your first login to sync your data."
|
||||
}
|
||||
},
|
||||
"loginForm": {
|
||||
"error": {
|
||||
"emailRequired": "Your email must be filled in.",
|
||||
"passwordRequired": "Your password must be filled in.",
|
||||
"emailLength": "Your email must have at least 3 characters.",
|
||||
"emailInvalidChars": "Your email contains invalid characters.",
|
||||
"connection": "An error occurred during login.",
|
||||
"server": "A server error occurred during login.",
|
||||
"unknown": "An unknown error occurred during login."
|
||||
},
|
||||
"fields": {
|
||||
"email": {
|
||||
"label": "Email address",
|
||||
"placeholder": "your.email@example.com"
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"placeholder": "••••••••",
|
||||
"forgot": "Forgot password?"
|
||||
}
|
||||
},
|
||||
"loading": "Logging in...",
|
||||
"submit": "Log in"
|
||||
},
|
||||
"registerPage": {
|
||||
"title": "Create account",
|
||||
"subtitle": "Create a free account and start your adventure with us.",
|
||||
"progress": {
|
||||
"infos": "Information",
|
||||
"verif": "Verification"
|
||||
},
|
||||
"backToLogin": "Back to login"
|
||||
},
|
||||
"registerStepOne": {
|
||||
"fields": {
|
||||
"firstName": {
|
||||
"label": "First Name",
|
||||
"placeholder": "Your first name"
|
||||
},
|
||||
"lastName": {
|
||||
"label": "Last Name",
|
||||
"placeholder": "Your last name"
|
||||
},
|
||||
"username": {
|
||||
"label": "Username",
|
||||
"placeholder": "Choose a username",
|
||||
"note": "Username must be at least 3 characters"
|
||||
},
|
||||
"email": {
|
||||
"label": "Email Address",
|
||||
"placeholder": "your.email@example.com"
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"placeholder": "••••••••"
|
||||
},
|
||||
"repeatPassword": {
|
||||
"label": "Confirm Password",
|
||||
"placeholder": "••••••••"
|
||||
}
|
||||
},
|
||||
"next": "Next"
|
||||
},
|
||||
"resetPassword": {
|
||||
"title": "Forgot password",
|
||||
"subtitle": "Reset your password in a few simple steps",
|
||||
"progress": {
|
||||
"email": "Email",
|
||||
"verification": "Verification",
|
||||
"final": "Finalization"
|
||||
},
|
||||
"fields": {
|
||||
"email": {
|
||||
"label": "Email address",
|
||||
"placeholder": "your.email@example.com"
|
||||
},
|
||||
"code": {
|
||||
"label": "Verification code",
|
||||
"placeholder": "Enter the code received by email"
|
||||
},
|
||||
"newPassword": {
|
||||
"label": "New password",
|
||||
"placeholder": "••••••••"
|
||||
}
|
||||
},
|
||||
"verify": "Verify",
|
||||
"confirm": "Confirm",
|
||||
"changePassword": "Change password",
|
||||
"back": "Back",
|
||||
"success": "Your password has been successfully updated!",
|
||||
"goToLogin": "Go to login page",
|
||||
"backToLogin": "Back to login",
|
||||
"error": {
|
||||
"codeServer": "An error occurred while verifying the code on the server.",
|
||||
"codeUnknown": "An unknown error occurred while verifying the code.",
|
||||
"emailInvalid": "Your email is invalid. Please enter a valid email address.",
|
||||
"emailFormat": "Your input is not a valid email address.",
|
||||
"emailServer": "An error occurred while verifying the email on the server.",
|
||||
"emailUnknown": "An unknown error occurred while verifying the email.",
|
||||
"passwordServer": "An error occurred while changing the password on the server.",
|
||||
"passwordUnknown": "An unknown error occurred while changing the password."
|
||||
}
|
||||
},
|
||||
"controllerBar": {
|
||||
"bookNotFound": "No book found",
|
||||
"errorGettingBook": "Error while trying to get the book information",
|
||||
@@ -137,6 +22,10 @@
|
||||
"unknownBookError": "Unknown error while retrieving book",
|
||||
"unknownChapterError": "Unknown error while retrieving chapter"
|
||||
},
|
||||
"userMenu": {
|
||||
"settings": "Settings",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"bookList": {
|
||||
"library": "Library",
|
||||
"booksAreMirrors": "\"Books are the mirrors of the soul\"",
|
||||
@@ -161,18 +50,7 @@
|
||||
"bookCard": {
|
||||
"noCoverAlt": "No cover",
|
||||
"initialsSeparator": ".",
|
||||
"subtitlePlaceholder": "No subtitle",
|
||||
"synced": "Synced",
|
||||
"localOnly": "Local only",
|
||||
"serverOnly": "Server only",
|
||||
"toSyncFromServer": "Download from server",
|
||||
"toSyncToServer": "Upload to server",
|
||||
"sync": "Sync",
|
||||
"uploadError": "Error uploading book.",
|
||||
"downloadError": "Error downloading book.",
|
||||
"syncFromServerError": "Error syncing from server.",
|
||||
"syncToServerError": "Error syncing to server.",
|
||||
"refreshError": "Error refreshing books."
|
||||
"subtitlePlaceholder": "No subtitle"
|
||||
},
|
||||
"scribeTopBar": {
|
||||
"logoAlt": "Logo",
|
||||
@@ -210,11 +88,41 @@
|
||||
},
|
||||
"importBook": {
|
||||
"title": "Import a book",
|
||||
"description": "Import a book from a DOCX file.",
|
||||
"badge": "IMPORT"
|
||||
"description": "Import a DOCX file to create a book.",
|
||||
"badge": "DOCX"
|
||||
}
|
||||
}
|
||||
},
|
||||
"importBook": {
|
||||
"header": "Import a book",
|
||||
"pickFile": "Select a DOCX file",
|
||||
"parsing": "Parsing file...",
|
||||
"fields": {
|
||||
"type": "Book type",
|
||||
"title": "Title",
|
||||
"subTitle": "Subtitle",
|
||||
"summary": "Summary",
|
||||
"version": "Chapter version"
|
||||
},
|
||||
"chapters": {
|
||||
"title": "Chapters",
|
||||
"detected": "{count} chapter(s) detected",
|
||||
"selectAll": "Select all",
|
||||
"deselectAll": "Deselect all",
|
||||
"words": "words"
|
||||
},
|
||||
"submit": "Import",
|
||||
"importing": "Importing...",
|
||||
"success": "Book imported successfully.",
|
||||
"error": {
|
||||
"invalidFormat": "Invalid file format. Please select a DOCX file.",
|
||||
"parseFailed": "Failed to parse the file.",
|
||||
"titleRequired": "Title is required.",
|
||||
"typeRequired": "Book type is required.",
|
||||
"noChaptersSelected": "Please select at least one chapter.",
|
||||
"importFailed": "An error occurred while importing the book."
|
||||
}
|
||||
},
|
||||
"scribeChapterComponent": {
|
||||
"sheetHeading": "Sheet",
|
||||
"createSheet": "Create your sheet",
|
||||
@@ -255,7 +163,7 @@
|
||||
},
|
||||
"spells": {
|
||||
"title": "Spell Book",
|
||||
"description": "Create and manage spells, magic systems, and supernatural abilities.",
|
||||
"description": "Manage the spells and magic of your universe.",
|
||||
"badge": "SPELL"
|
||||
},
|
||||
"items": {
|
||||
@@ -303,7 +211,12 @@
|
||||
"exampleHeading": "Example",
|
||||
"literaryUsageHeading": "Literary usage",
|
||||
"description": "Search for a word to get its definition, usage examples, and literary tips.",
|
||||
"errorUnknown": "An unknown error occurred while searching for the word."
|
||||
"errorUnknown": "An unknown error occurred while searching for the word.",
|
||||
"errorNoResponse": "No response received from the server.",
|
||||
"locked": {
|
||||
"title": "Access required",
|
||||
"description": "A QuillSense basic subscription or an API key is required to enable the smart dictionary."
|
||||
}
|
||||
},
|
||||
"synonyms": {
|
||||
"heading": "Lexical search",
|
||||
@@ -319,7 +232,12 @@
|
||||
"emptyAntonymsTitle": "Antonym Search",
|
||||
"emptySynonymsDescription": "Enter a word to find synonyms suitable for different writing contexts.",
|
||||
"emptyAntonymsDescription": "Enter a word to find antonyms suitable for different writing contexts.",
|
||||
"errorUnknown": "An unknown error occurred while searching for the word."
|
||||
"errorUnknown": "An unknown error occurred while searching for the word.",
|
||||
"errorNoResponse": "No response received from the server.",
|
||||
"locked": {
|
||||
"title": "Access required",
|
||||
"description": "A QuillSense basic subscription or an API key is required to enable synonym search."
|
||||
}
|
||||
},
|
||||
"inspireMe": {
|
||||
"fieldName": "Find inspiration",
|
||||
@@ -332,7 +250,17 @@
|
||||
"emptyHeading": "Inspire me",
|
||||
"emptyDescription": "Search for ideas to enrich your writing. Enter a prompt and let AI inspire you with creative suggestions based on your current content.",
|
||||
"emptyPromptError": "Please enter a prompt to get inspired.",
|
||||
"errorUnknown": "An unknown error occurred while trying to fetch inspiration."
|
||||
"error": {
|
||||
"contentRetrieval": "Error retrieving content.",
|
||||
"contentRetrievalUnknown": "Unknown error retrieving content.",
|
||||
"noBook": "No book selected.",
|
||||
"noChapter": "No chapter selected.",
|
||||
"unknown": "An unknown error occurred during generation."
|
||||
},
|
||||
"locked": {
|
||||
"title": "Access required",
|
||||
"description": "A QuillSense basic subscription or an API key is required to enable the Inspire Me mode."
|
||||
}
|
||||
},
|
||||
"conjugator": {
|
||||
"locked": {
|
||||
@@ -467,82 +395,17 @@
|
||||
"errorCategoryRequired": "Character role is required.",
|
||||
"successAdd": "Character added successfully.",
|
||||
"successUpdate": "Character updated successfully.",
|
||||
"successDelete": "Character deleted successfully.",
|
||||
"errorAddCharacter": "Error adding character.",
|
||||
"errorUpdateCharacter": "Error updating character.",
|
||||
"errorDeleteCharacter": "Error deleting character.",
|
||||
"errorAddAttribute": "Error adding attribute.",
|
||||
"errorRemoveAttribute": "Error removing attribute.",
|
||||
"errorDeleteCharacter": "Error deleting character.",
|
||||
"successDelete": "Character deleted successfully.",
|
||||
"enableTool": "Enable characters",
|
||||
"enableToolDescription": "Enable character management for this book.",
|
||||
"toolEnabled": "Character management enabled.",
|
||||
"toolDisabled": "Character management disabled."
|
||||
},
|
||||
"characterDetail": {
|
||||
"back": "Back",
|
||||
"newCharacter": "New character",
|
||||
"exportToSeries": "Export to series",
|
||||
"deleteTitle": "Delete character",
|
||||
"deleteMessage": "You are about to permanently delete the character \"{name}\".",
|
||||
"basicInfo": "Basic information",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Enter a name",
|
||||
"lastName": "Last name",
|
||||
"lastNamePlaceholder": "Example: Smith",
|
||||
"nickname": "Nickname",
|
||||
"nicknamePlaceholder": "Alias or nickname",
|
||||
"role": "Role",
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "Ex: King, Captain, Doctor...",
|
||||
"gender": "Gender",
|
||||
"genderPlaceholder": "Ex: Male, Female, Non-binary",
|
||||
"age": "Age",
|
||||
"agePlaceholder": "Ex: 25",
|
||||
"yearsOld": "years old",
|
||||
"species": "Species",
|
||||
"speciesPlaceholder": "Ex: Human, Elf, Vampire",
|
||||
"nationality": "Nationality/Origin",
|
||||
"nationalityPlaceholder": "Ex: French, Elven",
|
||||
"status": "Status",
|
||||
"residence": "Place of residence",
|
||||
"residencePlaceholder": "Where the character lives",
|
||||
"speechPattern": "Speech pattern",
|
||||
"speechPatternPlaceholder": "Verbal tics, accent, vocabulary...",
|
||||
"catchphrase": "Catchphrase",
|
||||
"catchphrasePlaceholder": "Character's recurring quote",
|
||||
"notes": "Author notes",
|
||||
"notesPlaceholder": "Personal notes, reminders...",
|
||||
"colorLabel": "Associated color",
|
||||
"colorPlaceholder": "Ex: #51AE84 or green",
|
||||
"advancedMode": "Advanced mode",
|
||||
"showAdvanced": "Show",
|
||||
"hideAdvanced": "Hide",
|
||||
"identitySection": "Extended identity",
|
||||
"voiceSection": "Character voice",
|
||||
"authorSection": "Author notes",
|
||||
"historySection": "Background",
|
||||
"biography": "Biography",
|
||||
"biographyPlaceholder": "Character biography.",
|
||||
"history": "History",
|
||||
"historyPlaceholder": "Character history...",
|
||||
"roleFull": "Role",
|
||||
"roleFullPlaceholder": "Role of the character in the story",
|
||||
"fetchAttributesError": "Error fetching attributes."
|
||||
},
|
||||
"characterList": {
|
||||
"search": "Search for a character...",
|
||||
"add": "Add a character",
|
||||
"unknownImage": "?",
|
||||
"unknown": "Unknown",
|
||||
"noLastName": "No last name",
|
||||
"noTitle": "No title",
|
||||
"noRole": "No role",
|
||||
"noCharacters": "No characters",
|
||||
"noCharactersDescription": "Add your first character to get started."
|
||||
},
|
||||
"characterSectionElement": {
|
||||
"newItem": "New {item}"
|
||||
},
|
||||
"spellComponent": {
|
||||
"exportSuccess": "Spell exported to series successfully.",
|
||||
"enableTool": "Enable spell book",
|
||||
@@ -613,16 +476,77 @@
|
||||
},
|
||||
"spellPowerLevels": {
|
||||
"none": "None",
|
||||
"cantrip": "Cantrip",
|
||||
"novice": "Novice",
|
||||
"apprentice": "Apprentice",
|
||||
"journeyman": "Journeyman",
|
||||
"expert": "Expert",
|
||||
"master": "Master",
|
||||
"grandmaster": "Grandmaster",
|
||||
"minor": "Minor",
|
||||
"moderate": "Moderate",
|
||||
"major": "Major",
|
||||
"legendary": "Legendary",
|
||||
"divine": "Divine"
|
||||
},
|
||||
"characterDetail": {
|
||||
"back": "Back",
|
||||
"newCharacter": "New character",
|
||||
"exportToSeries": "Export to series",
|
||||
"deleteTitle": "Delete character",
|
||||
"deleteMessage": "You are about to permanently delete the character \"{name}\".",
|
||||
"basicInfo": "Basic information",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Enter a name",
|
||||
"lastName": "Last name",
|
||||
"lastNamePlaceholder": "Example: Smith",
|
||||
"nickname": "Nickname",
|
||||
"nicknamePlaceholder": "Alias or nickname",
|
||||
"role": "Role",
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "Ex: King, Captain, Doctor...",
|
||||
"gender": "Gender",
|
||||
"genderPlaceholder": "Ex: Male, Female, Non-binary",
|
||||
"age": "Age",
|
||||
"agePlaceholder": "Ex: 25",
|
||||
"yearsOld": "years old",
|
||||
"species": "Species",
|
||||
"speciesPlaceholder": "Ex: Human, Elf, Vampire",
|
||||
"nationality": "Nationality/Origin",
|
||||
"nationalityPlaceholder": "Ex: French, Elven",
|
||||
"status": "Status",
|
||||
"residence": "Place of residence",
|
||||
"residencePlaceholder": "Where the character lives",
|
||||
"speechPattern": "Speech pattern",
|
||||
"speechPatternPlaceholder": "Verbal tics, accent, vocabulary...",
|
||||
"catchphrase": "Catchphrase",
|
||||
"catchphrasePlaceholder": "Character's recurring quote",
|
||||
"notes": "Author notes",
|
||||
"notesPlaceholder": "Personal notes, reminders...",
|
||||
"colorLabel": "Associated color",
|
||||
"colorPlaceholder": "Ex: #51AE84 or green",
|
||||
"advancedMode": "Advanced mode",
|
||||
"showAdvanced": "Show",
|
||||
"hideAdvanced": "Hide",
|
||||
"identitySection": "Extended identity",
|
||||
"voiceSection": "Character voice",
|
||||
"authorSection": "Author notes",
|
||||
"historySection": "Background",
|
||||
"biography": "Biography",
|
||||
"biographyPlaceholder": "Character biography.",
|
||||
"history": "History",
|
||||
"historyPlaceholder": "Character history...",
|
||||
"roleFull": "Role",
|
||||
"roleFullPlaceholder": "Role of the character in the story",
|
||||
"fetchAttributesError": "Error fetching attributes."
|
||||
},
|
||||
"characterList": {
|
||||
"search": "Search for a character...",
|
||||
"add": "Add a character",
|
||||
"unknownImage": "?",
|
||||
"unknown": "Unknown",
|
||||
"noLastName": "No last name",
|
||||
"noTitle": "No title",
|
||||
"noRole": "No role",
|
||||
"noCharacters": "No characters",
|
||||
"noCharactersDescription": "Add your first character to get started."
|
||||
},
|
||||
"characterSectionElement": {
|
||||
"newItem": "New {item}"
|
||||
},
|
||||
"aboutEditors": {
|
||||
"title": "About Scribe",
|
||||
"version": "Version",
|
||||
@@ -642,29 +566,13 @@
|
||||
"yourLocations": "Your locations",
|
||||
"characters": "Characters",
|
||||
"spells": "Spell Book",
|
||||
"quillsense": "QuillSense Settings",
|
||||
"export": "Export your book",
|
||||
"objectsList": "Objects list",
|
||||
"bookGoals": "Book goals",
|
||||
"quillsense": "QuillSense Settings",
|
||||
"export": "Export Book",
|
||||
"save": "Save",
|
||||
"notAvailable": "Option not available"
|
||||
},
|
||||
"exportOption": {
|
||||
"title": "Export Your Book",
|
||||
"description": "Choose the format and chapters to export.",
|
||||
"format": "Format",
|
||||
"selectFormat": "Select a format",
|
||||
"chapters": "Chapters",
|
||||
"selectAll": "Select all",
|
||||
"deselectAll": "Deselect all",
|
||||
"version": "Version",
|
||||
"export": "Export",
|
||||
"exporting": "Exporting...",
|
||||
"noChapters": "No chapters available for export.",
|
||||
"success": "Book exported successfully!",
|
||||
"cancelled": "Export cancelled.",
|
||||
"error": "Error exporting the book.",
|
||||
"loadingChapters": "Loading chapters..."
|
||||
"notAvailable": "Option not available",
|
||||
"unknownError": "An unknown error occurred."
|
||||
},
|
||||
"noBookHome": {
|
||||
"title": "Your work is waiting for its first words",
|
||||
@@ -675,14 +583,7 @@
|
||||
"preferences": "Preferences",
|
||||
"ghostWriter": "Ghost Writer",
|
||||
"draftCompanion": "Draft Companion",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"close": "Close",
|
||||
"toolbar": {
|
||||
"1": "1",
|
||||
"2": "2",
|
||||
"3": "3"
|
||||
}
|
||||
"save": "Save"
|
||||
},
|
||||
"draftCompanion": {
|
||||
"noPreviousVersion": "No previous version of this chapter",
|
||||
@@ -696,13 +597,13 @@
|
||||
"words": "Words",
|
||||
"refine": "Refine",
|
||||
"refining": "Refining...",
|
||||
"abortSuccess": "Generation stopped. Token and cost totals will be available on next page refresh."
|
||||
"abortSuccess": "Generation stopped. Token and cost totals will be available on next page refresh.",
|
||||
"sseParsingError": "Error reading generation data"
|
||||
},
|
||||
"ghostWriter": {
|
||||
"title": "Ghost Writer",
|
||||
"description": "Turn your ideas into captivating prose",
|
||||
"subscriptionRequired": "You must be subscribed to Quill Sense to use Ghost Writer.",
|
||||
"quillsenseDisabled": "QuillSense is disabled for this book. You can enable it in the book settings.",
|
||||
"subscriptionRequired": "You must be subscribed to QuillSense Pro to use Ghost Writer.",
|
||||
"subscribe": "Subscribe",
|
||||
"length": "Text length",
|
||||
"minimum": "Minimum",
|
||||
@@ -719,6 +620,7 @@
|
||||
"cancel": "Cancel",
|
||||
"generate": "Generate",
|
||||
"generating": "Generating...",
|
||||
"stop": "Stop",
|
||||
"successGenerate": "Text generated successfully.",
|
||||
"successInsert": "Excerpt successfully inserted",
|
||||
"successImport": "Content imported successfully",
|
||||
@@ -740,6 +642,7 @@
|
||||
"unknownError": "Unknown error saving settings."
|
||||
},
|
||||
"tags": {
|
||||
"addTagPlaceholder": "Tags",
|
||||
"unknownError": "Unknown error managing tags."
|
||||
}
|
||||
},
|
||||
@@ -776,12 +679,16 @@
|
||||
"sépia": "Sepia"
|
||||
},
|
||||
"focusMode": "Focus mode (hide distractions)",
|
||||
"reset": "Reset preferences"
|
||||
"reset": "Reset preferences",
|
||||
"saveError": "Error saving preferences.",
|
||||
"unknownError": "An unknown error occurred while saving preferences."
|
||||
},
|
||||
"scribeFooterBar": {
|
||||
"sheet": "Sheet : ",
|
||||
"madeWith": "Scribe Editor made with",
|
||||
"words": "Words",
|
||||
"words": "words",
|
||||
"pages": "pages",
|
||||
"paragraphs": "paragraphs",
|
||||
"books": "Books"
|
||||
},
|
||||
"addNewBookForm": {
|
||||
@@ -1082,17 +989,7 @@
|
||||
"book": "book",
|
||||
"books": "books",
|
||||
"series": "Series",
|
||||
"settings": "Series settings",
|
||||
"synced": "Synced",
|
||||
"localOnly": "Local only",
|
||||
"serverOnly": "Server only",
|
||||
"toSyncFromServer": "Download from server",
|
||||
"toSyncToServer": "Upload to server",
|
||||
"uploadError": "Error uploading series.",
|
||||
"downloadError": "Error downloading series.",
|
||||
"syncFromServerError": "Error syncing from server.",
|
||||
"syncToServerError": "Error syncing to server.",
|
||||
"refreshError": "Error refreshing series."
|
||||
"settings": "Series settings"
|
||||
},
|
||||
"basicInformationSetting": {
|
||||
"error": {
|
||||
@@ -1120,6 +1017,12 @@
|
||||
"generateWithQuillSense": "Generate with QuillSense"
|
||||
}
|
||||
},
|
||||
"quillList": {
|
||||
"untitled": "Untitled",
|
||||
"error": {
|
||||
"unknown": "An unknown error occurred while loading conversations."
|
||||
}
|
||||
},
|
||||
"quillConversation": {
|
||||
"emptyMessageError": "Please enter a message before sending it.",
|
||||
"inputPlaceholder": "What's on your mind?",
|
||||
@@ -1160,23 +1063,25 @@
|
||||
"edit": "Edit",
|
||||
"exportToSeries": "Export to series",
|
||||
"save": "Save",
|
||||
"unknownError": "An unknown error occurred",
|
||||
"loading": "Loading..."
|
||||
"unknownError": "An unknown error occurred"
|
||||
},
|
||||
"syncField": {
|
||||
"uploadSuccess": "{count} element(s) updated successfully.",
|
||||
"uploadTooltip": "Push to series",
|
||||
"downloadTooltip": "Pull from series"
|
||||
},
|
||||
"seriesImport": {
|
||||
"importButton": "Import",
|
||||
"importFromSeries": "Import from series",
|
||||
"selectElement": "Select an element"
|
||||
"spellPowerLevels": {
|
||||
"none": "None",
|
||||
"cantrip": "Cantrip",
|
||||
"novice": "Novice",
|
||||
"apprentice": "Apprentice",
|
||||
"journeyman": "Journeyman",
|
||||
"expert": "Expert",
|
||||
"master": "Master",
|
||||
"grandmaster": "Grandmaster",
|
||||
"legendary": "Legendary",
|
||||
"divine": "Divine"
|
||||
},
|
||||
"editor": {
|
||||
"error": {
|
||||
"savedFailed": "Save failed",
|
||||
"unknownError": "An unknown error occurred"
|
||||
"unknownError": "An unknown error occurred",
|
||||
"parsingContent": "Error loading chapter content"
|
||||
},
|
||||
"success": {
|
||||
"saved": "Saved successfully"
|
||||
@@ -1217,18 +1122,22 @@
|
||||
"userNotFound": "User not found",
|
||||
"authenticationError": "Error during authentication",
|
||||
"termsAcceptError": "Error accepting terms of service",
|
||||
"lastChapterError": "Error retrieving last chapter",
|
||||
"localDataError": "Unable to load local data",
|
||||
"encryptionKeyError": "Encryption key not found",
|
||||
"offlineModeError": "Error initializing offline mode",
|
||||
"offlineInitError": "Error initializing offline mode",
|
||||
"syncError": "Error syncing data",
|
||||
"dbInitError": "Error initializing local database",
|
||||
"offlineError": "Error checking offline mode",
|
||||
"fetchBooksError": "Error fetching books",
|
||||
"fetchSeriesError": "Error fetching series"
|
||||
"lastChapterError": "Error retrieving last chapter"
|
||||
}
|
||||
},
|
||||
"quillsenseSetting": {
|
||||
"enableQuillsense": "Enable QuillSense",
|
||||
"enableDescription": "When disabled, all AI features will be hidden for this book.",
|
||||
"advancedPrompt": "Advanced prompt for Ghost Writer",
|
||||
"advancedPromptPlaceholder": "Enter custom instructions to guide the AI when generating text...",
|
||||
"advancedPromptDescription": "This prompt will be used as a priority directive when generating text with Ghost Writer.",
|
||||
"saveSuccess": "QuillSense settings saved successfully.",
|
||||
"saveError": "Error saving settings.",
|
||||
"unknownError": "An unknown error occurred.",
|
||||
"enable_characters": "Enable characters",
|
||||
"enable_worlds": "Enable worlds",
|
||||
"enable_locations": "Enable locations"
|
||||
},
|
||||
"shortStoryGenerator": {
|
||||
"title": "Short Story Generator",
|
||||
"tabs": {
|
||||
@@ -1285,123 +1194,221 @@
|
||||
"close": "Close"
|
||||
}
|
||||
},
|
||||
"userMenu": {
|
||||
"settings": "Settings",
|
||||
"logout": "Logout"
|
||||
"syncField": {
|
||||
"uploadSuccess": "{count} element(s) updated successfully.",
|
||||
"uploadTooltip": "Push to series",
|
||||
"downloadTooltip": "Pull from series"
|
||||
},
|
||||
"exportOption": {
|
||||
"formatLabel": "Export format",
|
||||
"chapters": "Chapters",
|
||||
"loadingChapters": "Loading chapters...",
|
||||
"noChaptersAvailable": "No chapters available for export.",
|
||||
"selectAll": "Select all",
|
||||
"deselectAll": "Deselect all",
|
||||
"exportButton": "Export",
|
||||
"exporting": "Exporting...",
|
||||
"noBookSelected": "No book selected.",
|
||||
"noChaptersSelected": "Please select at least one chapter.",
|
||||
"downloadSuccess": "Your {format} file has been downloaded successfully.",
|
||||
"downloadError": "Download failed.",
|
||||
"serverError": "Server error during export.",
|
||||
"unknownError": "An unknown error occurred."
|
||||
},
|
||||
"seriesImport": {
|
||||
"importButton": "Import",
|
||||
"importFromSeries": "Import from series",
|
||||
"selectElement": "Select an element"
|
||||
},
|
||||
"loginPage": {
|
||||
"title": "Login",
|
||||
"welcome": "Welcome to ERitors Scribe",
|
||||
"orSocial": "or sign in with",
|
||||
"offlineWarning": {
|
||||
"title": "Offline mode required",
|
||||
"message": "You are not connected to the Internet. To use the app offline, you must first sign in at least once online."
|
||||
}
|
||||
},
|
||||
"loginForm": {
|
||||
"submit": "Sign in",
|
||||
"fields": {
|
||||
"email": {
|
||||
"label": "Email address",
|
||||
"placeholder": "Enter your email address"
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"placeholder": "Enter your password",
|
||||
"forgot": "Forgot password?"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"emailRequired": "Please enter your email address.",
|
||||
"passwordRequired": "Please enter your password.",
|
||||
"emailLength": "Email address must be between 5 and 100 characters.",
|
||||
"emailInvalidChars": "Email address contains invalid characters.",
|
||||
"connection": "Connection error. Check your credentials.",
|
||||
"server": "Server error. Please try again later.",
|
||||
"unknown": "An unknown error occurred."
|
||||
}
|
||||
},
|
||||
"socialForm": {
|
||||
"error": {
|
||||
"connection": "Connection error with the provider."
|
||||
}
|
||||
},
|
||||
"registerPage": {
|
||||
"title": "Register",
|
||||
"subtitle": "Create your ERitors account",
|
||||
"backToLogin": "Back to login",
|
||||
"progress": {
|
||||
"infos": "Information",
|
||||
"verif": "Verification"
|
||||
}
|
||||
},
|
||||
"registerStepOne": {
|
||||
"next": "Continue",
|
||||
"fields": {
|
||||
"firstName": {
|
||||
"label": "First name",
|
||||
"placeholder": "Enter your first name"
|
||||
},
|
||||
"lastName": {
|
||||
"label": "Last name",
|
||||
"placeholder": "Enter your last name"
|
||||
},
|
||||
"username": {
|
||||
"label": "Username",
|
||||
"placeholder": "Choose a username",
|
||||
"note": "Username must be between 3 and 50 characters."
|
||||
},
|
||||
"email": {
|
||||
"label": "Email address",
|
||||
"placeholder": "Enter your email address"
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"placeholder": "Choose a password"
|
||||
},
|
||||
"repeatPassword": {
|
||||
"label": "Confirm password",
|
||||
"placeholder": "Confirm your password"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"requiredFields": "All fields are required.",
|
||||
"firstNameLength": "First name must be between 2 and 50 characters.",
|
||||
"lastNameLength": "Last name must be between 2 and 50 characters.",
|
||||
"usernameLength": "Username must be between 3 and 50 characters.",
|
||||
"invalidInput": "One or more fields contain invalid characters.",
|
||||
"passwordMismatch": "Passwords do not match.",
|
||||
"preRegister": "Error during pre-registration.",
|
||||
"unknown": "An unknown error occurred."
|
||||
},
|
||||
"success": {
|
||||
"preRegister": "A verification code has been sent to your email address."
|
||||
}
|
||||
},
|
||||
"registerStepTwo": {
|
||||
"verify": "Verify",
|
||||
"back": "Back",
|
||||
"confirmed": "Your account has been verified successfully!",
|
||||
"start": "Get started",
|
||||
"instructions": {
|
||||
"sent": "A verification code has been sent to your email address.",
|
||||
"checkInbox": "Check your inbox and spam folder."
|
||||
},
|
||||
"fields": {
|
||||
"code": {
|
||||
"label": "Verification code",
|
||||
"placeholder": "Enter the code received by email"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"codeIncorrect": "The verification code is incorrect.",
|
||||
"unknown": "An unknown error occurred."
|
||||
},
|
||||
"success": {
|
||||
"verified": "Your account has been verified successfully!"
|
||||
}
|
||||
},
|
||||
"resetPassword": {
|
||||
"title": "Reset Password",
|
||||
"subtitle": "Reset your password",
|
||||
"verify": "Verify email",
|
||||
"confirm": "Confirm code",
|
||||
"changePassword": "Change password",
|
||||
"back": "Back",
|
||||
"backToLogin": "Back to login",
|
||||
"success": "Your password has been reset successfully!",
|
||||
"goToLogin": "Sign in",
|
||||
"progress": {
|
||||
"email": "Email",
|
||||
"verification": "Verification",
|
||||
"final": "New password"
|
||||
},
|
||||
"fields": {
|
||||
"email": {
|
||||
"label": "Email address",
|
||||
"placeholder": "Enter your email address"
|
||||
},
|
||||
"code": {
|
||||
"label": "Verification code",
|
||||
"placeholder": "Enter the code received"
|
||||
},
|
||||
"newPassword": {
|
||||
"label": "New password",
|
||||
"placeholder": "Enter your new password"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"emailInvalid": "Please enter a valid email address.",
|
||||
"emailFormat": "The email address format is invalid.",
|
||||
"emailServer": "Server error while verifying email.",
|
||||
"emailUnknown": "An unknown error occurred.",
|
||||
"codeServer": "Server error while verifying code.",
|
||||
"codeUnknown": "An unknown error occurred.",
|
||||
"passwordServer": "Server error while changing password.",
|
||||
"passwordUnknown": "An unknown error occurred."
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
"mode": {
|
||||
"title": "Offline Mode",
|
||||
"backToOnline": "Back to online login"
|
||||
"title": "Offline mode",
|
||||
"backToOnline": "Back online"
|
||||
},
|
||||
"pin": {
|
||||
"errors": {
|
||||
"tooShort": "PIN must be at least 4 digits.",
|
||||
"tooLong": "PIN cannot exceed 8 digits.",
|
||||
"digitsOnly": "PIN must contain only digits.",
|
||||
"mismatch": "PINs do not match.",
|
||||
"setupFailed": "Error while setting up PIN."
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configure PIN",
|
||||
"titleFirstLogin": "Secure your offline access",
|
||||
"subtitle": "Protect your local data",
|
||||
"description": "This PIN will allow you to access your works even without an internet connection",
|
||||
"pinLabel": "PIN Code (4-8 digits)",
|
||||
"confirmPinLabel": "Confirm PIN",
|
||||
"title": "Set up offline PIN",
|
||||
"titleFirstLogin": "Set up a PIN for offline mode",
|
||||
"subtitle": "This PIN will allow you to access your data without an Internet connection.",
|
||||
"description": "Choose a 4 to 8 digit PIN to secure offline access to your data.",
|
||||
"pinLabel": "PIN code",
|
||||
"confirmPinLabel": "Confirm PIN code",
|
||||
"laterButton": "Later",
|
||||
"configureButton": "Configure PIN",
|
||||
"configuringButton": "Configuring...",
|
||||
"footer": "Your PIN is stored securely on your device"
|
||||
"configuringButton": "Setting up...",
|
||||
"configureButton": "Set up PIN",
|
||||
"footer": "You can change this PIN in settings."
|
||||
},
|
||||
"verify": {
|
||||
"title": "Offline Mode",
|
||||
"subtitle": "Enter your PIN to access your local works",
|
||||
"placeholder": "Enter your PIN",
|
||||
"enterPin": "Please enter your PIN",
|
||||
"incorrect": "Incorrect PIN",
|
||||
"tooManyAttempts": "Too many failed attempts. Please reconnect online.",
|
||||
"error": "Error verifying PIN",
|
||||
"title": "PIN Verification",
|
||||
"subtitle": "Enter your PIN to access your offline data.",
|
||||
"placeholder": "PIN code",
|
||||
"enterPin": "Please enter your PIN.",
|
||||
"incorrect": "Incorrect PIN.",
|
||||
"tooManyAttempts": "Too many attempts. Please sign in online.",
|
||||
"error": "Error while verifying PIN.",
|
||||
"cancelButton": "Cancel",
|
||||
"unlockButton": "Unlock",
|
||||
"verifyingButton": "Verifying...",
|
||||
"attemptsRemaining": "{{count}} attempt(s) remaining"
|
||||
},
|
||||
"errors": {
|
||||
"tooShort": "PIN must be at least 4 digits",
|
||||
"tooLong": "PIN cannot exceed 8 digits",
|
||||
"digitsOnly": "PIN must contain only digits",
|
||||
"mismatch": "PINs do not match",
|
||||
"setupFailed": "Error configuring PIN"
|
||||
"unlockButton": "Unlock"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deleteBook": {
|
||||
"title": "Delete book",
|
||||
"message": "You are about to permanently delete your book.",
|
||||
"confirm": "Delete",
|
||||
"cancel": "Cancel",
|
||||
"deleteLocalToo": "Also delete local version",
|
||||
"deleteLocalWarning": "Warning: This action will delete the book from the server AND your device. This action is irreversible.",
|
||||
"errorUnknown": "An unknown error occurred while deleting the book."
|
||||
},
|
||||
"quillSenseSetting": {
|
||||
"title": "QuillSense Settings",
|
||||
"description": "Manage AI features for this book.",
|
||||
"enableSection": "Enable/Disable QuillSense",
|
||||
"enableLabel": "QuillSense enabled",
|
||||
"enableDescription": "When enabled, AI features like Ghost Writer and QuillSense will be available for this book.",
|
||||
"advancedSection": "Advanced Settings",
|
||||
"advancedPromptLabel": "Advanced Prompt",
|
||||
"advancedPromptPlaceholder": "Enter custom instructions for the AI...",
|
||||
"advancedPromptHint": "These instructions will be included in every text generation for this book.",
|
||||
"disabledWarning": "QuillSense is disabled. AI features will not be available for this book.",
|
||||
"errorFetch": "Error fetching QuillSense settings.",
|
||||
"errorSave": "Error saving settings.",
|
||||
"errorUnknown": "An unknown error occurred.",
|
||||
"successSave": "QuillSense settings saved successfully.",
|
||||
"noBookSelected": "No book selected.",
|
||||
"enable_characters": "Enable character management for this book",
|
||||
"enable_worlds": "Enable world management for this book",
|
||||
"enable_locations": "Enable location management for this book"
|
||||
},
|
||||
"importBook": {
|
||||
"header": {
|
||||
"title": "Import a Book"
|
||||
},
|
||||
"pickFile": "Choose a DOCX file",
|
||||
"parsing": "Analyzing file...",
|
||||
"chaptersDetected": "{count} chapters detected",
|
||||
"noChaptersDetected": "No chapters detected in the file",
|
||||
"fields": {
|
||||
"title": {
|
||||
"label": "Book Title",
|
||||
"placeholder": "Enter the title"
|
||||
},
|
||||
"subTitle": {
|
||||
"label": "Subtitle",
|
||||
"placeholder": "Enter the subtitle"
|
||||
},
|
||||
"summary": {
|
||||
"label": "Summary",
|
||||
"placeholder": "Enter a summary"
|
||||
},
|
||||
"type": {
|
||||
"label": "Book Type"
|
||||
},
|
||||
"version": {
|
||||
"label": "Chapter Version"
|
||||
}
|
||||
},
|
||||
"chapters": {
|
||||
"title": "Chapters to import",
|
||||
"words": "{count} words",
|
||||
"selectAll": "Select all",
|
||||
"deselectAll": "Deselect all"
|
||||
},
|
||||
"submit": "Import",
|
||||
"importing": "Importing...",
|
||||
"success": "Book imported successfully",
|
||||
"error": {
|
||||
"titleRequired": "Book title is required",
|
||||
"typeRequired": "Book type is required",
|
||||
"noChaptersSelected": "Select at least one chapter",
|
||||
"parseFailed": "Error analyzing the file",
|
||||
"importFailed": "Error during import",
|
||||
"invalidFormat": "Invalid format. Only DOCX files are accepted"
|
||||
}
|
||||
}
|
||||
}
|
||||
1183
lib/locales/fr.json
1183
lib/locales/fr.json
File diff suppressed because it is too large
Load Diff
@@ -1,187 +0,0 @@
|
||||
import {Author} from './User';
|
||||
import {ActChapter, ChapterProps} from "@/lib/models/Chapter";
|
||||
import {SelectBoxProps} from "@/shared/interface";
|
||||
import {
|
||||
BookActSummariesTable,
|
||||
BookAIGuideLineTable,
|
||||
BookChapterContentTable, BookChapterInfosTable,
|
||||
BookChaptersTable, BookCharactersAttributesTable, BookCharactersTable, BookGuideLineTable, BookIncidentsTable,
|
||||
BookIssuesTable, BookLocationTable, BookPlotPointsTable, BookWorldElementsTable, BookWorldTable,
|
||||
EritBooksTable, LocationElementTable, LocationSubElementTable
|
||||
} from "@/lib/models/BookTables";
|
||||
import {
|
||||
SyncedActSummary, SyncedAIGuideLine,
|
||||
SyncedChapter,
|
||||
SyncedCharacter, SyncedGuideLine,
|
||||
SyncedIncident, SyncedIssue,
|
||||
SyncedLocation,
|
||||
SyncedPlotPoint,
|
||||
SyncedWorld
|
||||
} from "@/lib/models/SyncedBook";
|
||||
|
||||
export interface CompleteBook {
|
||||
eritBooks: EritBooksTable[];
|
||||
actSummaries: BookActSummariesTable[];
|
||||
aiGuideLine: BookAIGuideLineTable[];
|
||||
chapters: BookChaptersTable[];
|
||||
chapterContents: BookChapterContentTable[];
|
||||
chapterInfos: BookChapterInfosTable[];
|
||||
characters: BookCharactersTable[];
|
||||
characterAttributes: BookCharactersAttributesTable[];
|
||||
guideLine: BookGuideLineTable[];
|
||||
incidents: BookIncidentsTable[];
|
||||
issues: BookIssuesTable[];
|
||||
locations: BookLocationTable[];
|
||||
plotPoints: BookPlotPointsTable[];
|
||||
worlds: BookWorldTable[];
|
||||
worldElements: BookWorldElementsTable[];
|
||||
locationElements: LocationElementTable[];
|
||||
locationSubElements: LocationSubElementTable[];
|
||||
}
|
||||
|
||||
export interface SyncedBook {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
subTitle: string | null;
|
||||
lastUpdate: number;
|
||||
chapters: SyncedChapter[];
|
||||
characters: SyncedCharacter[];
|
||||
locations: SyncedLocation[];
|
||||
worlds: SyncedWorld[];
|
||||
incidents: SyncedIncident[];
|
||||
plotPoints: SyncedPlotPoint[];
|
||||
issues: SyncedIssue[];
|
||||
actSummaries: SyncedActSummary[];
|
||||
guideLine: SyncedGuideLine | null;
|
||||
aiGuideLine: SyncedAIGuideLine | null;
|
||||
}
|
||||
|
||||
export interface BookToolsSettings {
|
||||
characters: boolean;
|
||||
worlds: boolean;
|
||||
locations: boolean;
|
||||
spells: boolean;
|
||||
}
|
||||
|
||||
export interface BookProps {
|
||||
bookId: string;
|
||||
type: string;
|
||||
title: string;
|
||||
author?: Author;
|
||||
serie?: number;
|
||||
seriesId?: string | null;
|
||||
subTitle?: string;
|
||||
summary?: string;
|
||||
publicationDate?: string;
|
||||
desiredWordCount?: number;
|
||||
totalWordCount?: number;
|
||||
coverImage?: string;
|
||||
localBook?: boolean;
|
||||
chapters?: ChapterProps[];
|
||||
quillsenseEnabled?: boolean;
|
||||
tools?: BookToolsSettings;
|
||||
}
|
||||
|
||||
export interface GuideLine {
|
||||
tone: string;
|
||||
atmosphere: string;
|
||||
writingStyle: string;
|
||||
themes: string;
|
||||
symbolism: string;
|
||||
motifs: string;
|
||||
narrativeVoice: string;
|
||||
pacing: string;
|
||||
intendedAudience: string;
|
||||
keyMessages: string;
|
||||
}
|
||||
|
||||
export interface GuideLineAI {
|
||||
narrativeType: number|null;
|
||||
dialogueType: number|null;
|
||||
globalResume: string|null;
|
||||
atmosphere: string|null;
|
||||
verbeTense: number|null;
|
||||
langue: number|null;
|
||||
currentResume: string|null;
|
||||
themes: string|null;
|
||||
}
|
||||
|
||||
export interface PlotPoint {
|
||||
plotPointId: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
linkedIncidentId: string;
|
||||
chapters?: ActChapter[];
|
||||
}
|
||||
|
||||
export interface Incident {
|
||||
incidentId: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
chapters?: ActChapter[];
|
||||
}
|
||||
|
||||
export interface Issue {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface Act {
|
||||
id: number;
|
||||
summary: string | null;
|
||||
incidents?: Incident[];
|
||||
plotPoints?: PlotPoint[];
|
||||
chapters?: ActChapter[];
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
label: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export interface BookTags {
|
||||
characters: Tag[];
|
||||
locations: Tag[];
|
||||
objects: Tag[];
|
||||
worldElements: Tag[];
|
||||
}
|
||||
|
||||
export const bookTypes: SelectBoxProps[] = [
|
||||
{label: 'bookTypes.short', value: 'short'},
|
||||
{label: 'bookTypes.novelette', value: 'novelette'},
|
||||
{label: 'bookTypes.novella', value: 'long'},
|
||||
{label: 'bookTypes.chapbook', value: 'chapbook'},
|
||||
{label: 'bookTypes.novel', value: 'novel'},
|
||||
]
|
||||
|
||||
export default class Book {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
static booksToSelectBox(books: SyncedBook[]): SelectBoxProps[] {
|
||||
return books.map((book: SyncedBook): SelectBoxProps => {
|
||||
return {
|
||||
label: book.title,
|
||||
value: book.id,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static getBookTypeLabel(value: string): string {
|
||||
switch (value) {
|
||||
case 'short':
|
||||
return 'bookTypes.short';
|
||||
case 'novelette':
|
||||
return 'bookTypes.novelette';
|
||||
case 'long':
|
||||
return 'bookTypes.novella';
|
||||
case 'chapbook':
|
||||
return 'bookTypes.chapbook';
|
||||
case 'novel':
|
||||
return 'bookTypes.novel';
|
||||
default:
|
||||
return 'bookTypes.novel';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default class BookSerie{
|
||||
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
import {SelectBoxProps} from "@/shared/interface";
|
||||
|
||||
export interface ActChapter {
|
||||
chapterInfoId: string;
|
||||
chapterId: string;
|
||||
title: string;
|
||||
chapterOrder: number;
|
||||
actId: number;
|
||||
incidentId?: string;
|
||||
plotPointId?: string;
|
||||
summary: string;
|
||||
goal: string;
|
||||
}
|
||||
|
||||
export interface ChapterListProps {
|
||||
chapterId: string;
|
||||
title: string;
|
||||
summary?: string;
|
||||
chapterOrder?: number;
|
||||
goal?: string;
|
||||
}
|
||||
|
||||
export interface ChapterProps {
|
||||
chapterId: string;
|
||||
chapterOrder: number;
|
||||
title: string;
|
||||
chapterContent: ChapterContent;
|
||||
}
|
||||
|
||||
export interface ChapterContent {
|
||||
version: number;
|
||||
content: string;
|
||||
wordsCount: number;
|
||||
}
|
||||
|
||||
export interface ChapterVersion {
|
||||
value: number;
|
||||
label: 'Invite' | 'Brouillon' | 'Perfectionnement' | 'Révision' | 'Finale';
|
||||
}
|
||||
|
||||
export type TiptapNode = {
|
||||
type: string;
|
||||
content?: TiptapNode[];
|
||||
text?: string;
|
||||
attrs?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export type ExportFormat = 'epub' | 'pdf' | 'docx';
|
||||
|
||||
export interface ChapterExportInfo {
|
||||
chapterId: string;
|
||||
title: string;
|
||||
chapterOrder: number;
|
||||
availableVersions: number[];
|
||||
}
|
||||
|
||||
export interface ChapterExportSelection {
|
||||
chapterId: string;
|
||||
version: number;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export const chapterVersions: SelectBoxProps[] = [
|
||||
{value: '1', label: 'chapterVersions.prompt'},
|
||||
{value: '2', label: 'chapterVersions.draft'},
|
||||
{value: '3', label: 'chapterVersions.refine'},
|
||||
{value: '4', label: 'chapterVersions.review'},
|
||||
{value: '5', label: 'chapterVersions.final'},
|
||||
];
|
||||
|
||||
export default class Chapter {
|
||||
public static getPageCount(text: string): number {
|
||||
const charactersPerLine = 90;
|
||||
const linesPerPage = 40;
|
||||
|
||||
const lines: string[] = text.split('\n');
|
||||
let totalLines: number = 0;
|
||||
|
||||
lines.forEach((line: string) => {
|
||||
const lineLength: number = line.length;
|
||||
const estimatedLines: number = Math.ceil(lineLength / charactersPerLine);
|
||||
totalLines += estimatedLines;
|
||||
});
|
||||
|
||||
// Calcul du nombre de pages
|
||||
return Math.ceil(totalLines / linesPerPage);
|
||||
}
|
||||
static convertTiptapToHTML(node: TiptapNode): string {
|
||||
let html = '';
|
||||
|
||||
switch (node.type) {
|
||||
case 'doc':
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'paragraph':
|
||||
html += '<p>';
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</p>';
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
let textContent = node.text || '';
|
||||
|
||||
// Apply attributes like bold, italic, etc.
|
||||
if (node.attrs) {
|
||||
if (node.attrs.bold) {
|
||||
textContent = `<strong>${textContent}</strong>`;
|
||||
}
|
||||
if (node.attrs.italic) {
|
||||
textContent = `<em>${textContent}</em>`;
|
||||
}
|
||||
if (node.attrs.underline) {
|
||||
textContent = `<u>${textContent}</u>`;
|
||||
}
|
||||
if (node.attrs.strike) {
|
||||
textContent = `<s>${textContent}</s>`;
|
||||
}
|
||||
if (node.attrs.link) {
|
||||
textContent = `<a href="${node.attrs.link.href}">${textContent}</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += textContent;
|
||||
break;
|
||||
|
||||
case 'heading':
|
||||
const level = node.attrs?.level || 1;
|
||||
html += `<h${level}>`;
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += `</h${level}>`;
|
||||
break;
|
||||
|
||||
case 'bulletList':
|
||||
html += '<ul>';
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</ul>';
|
||||
break;
|
||||
|
||||
case 'orderedList':
|
||||
html += '<ol>';
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</ol>';
|
||||
break;
|
||||
|
||||
case 'listItem':
|
||||
html += '<li>';
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</li>';
|
||||
break;
|
||||
|
||||
case 'blockquote':
|
||||
html += '<blockquote>';
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</blockquote>';
|
||||
break;
|
||||
|
||||
case 'codeBlock':
|
||||
html += '<pre><code>';
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</code></pre>';
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Unhandled node type: ${node.type}`);
|
||||
if (node.content) {
|
||||
node.content.forEach(childNode => {
|
||||
html += this.convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
import {
|
||||
faBrain,
|
||||
faBullseye,
|
||||
faExclamationTriangle,
|
||||
faFire,
|
||||
faRuler,
|
||||
faShieldAlt,
|
||||
faUsers,
|
||||
faWrench,
|
||||
faRoute,
|
||||
faUserSecret,
|
||||
faGhost,
|
||||
faHeartBroken,
|
||||
faHandHoldingHeart,
|
||||
faBolt,
|
||||
faQuoteLeft,
|
||||
faFingerprint,
|
||||
faBox,
|
||||
faPeopleGroup,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {SelectBoxProps} from "@/shared/interface";
|
||||
|
||||
type CharacterCategory = 'main' | 'secondary' | 'recurring' | 'none';
|
||||
|
||||
export const characterCategories: SelectBoxProps[] = [
|
||||
{value: 'none', label: 'characterCategories.none'},
|
||||
{value: 'main', label: 'characterCategories.main'},
|
||||
{value: 'secondary', label: 'characterCategories.secondary'},
|
||||
{value: 'recurring', label: 'characterCategories.recurring'},
|
||||
];
|
||||
|
||||
export const characterStatus: SelectBoxProps[] = [
|
||||
{value: 'alive', label: 'characterStatus.alive'},
|
||||
{value: 'dead', label: 'characterStatus.dead'},
|
||||
{value: 'unknown', label: 'characterStatus.unknown'},
|
||||
];
|
||||
|
||||
export interface Relation {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
history: string;
|
||||
}
|
||||
|
||||
export interface Attribute {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CharacterAttribute {
|
||||
[key: string]: Array<Attribute>;
|
||||
}
|
||||
|
||||
export interface CharacterProps {
|
||||
id: string | null;
|
||||
name: string;
|
||||
lastName: string;
|
||||
nickname: string;
|
||||
age: number | null;
|
||||
gender: string;
|
||||
species: string;
|
||||
nationality: string;
|
||||
status: 'alive' | 'dead' | 'unknown';
|
||||
category: CharacterCategory;
|
||||
title: string;
|
||||
image: string;
|
||||
physical: Attribute[];
|
||||
psychological: Attribute[];
|
||||
relations: Attribute[];
|
||||
skills: Attribute[];
|
||||
weaknesses: Attribute[];
|
||||
strengths: Attribute[];
|
||||
goals: Attribute[];
|
||||
motivations: Attribute[];
|
||||
arc: Attribute[];
|
||||
secrets: Attribute[];
|
||||
fears: Attribute[];
|
||||
flaws: Attribute[];
|
||||
beliefs: Attribute[];
|
||||
conflicts: Attribute[];
|
||||
quotes: Attribute[];
|
||||
distinguishingMarks: Attribute[];
|
||||
items: Attribute[];
|
||||
affiliations: Attribute[];
|
||||
role: string;
|
||||
biography?: string;
|
||||
history?: string;
|
||||
speechPattern?: string;
|
||||
catchphrase?: string;
|
||||
residence?: string;
|
||||
notes?: string;
|
||||
color?: string;
|
||||
seriesCharacterId?: string | null;
|
||||
}
|
||||
|
||||
export interface CharacterListResponse {
|
||||
characters: CharacterProps[];
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface CharacterElement {
|
||||
title: string;
|
||||
section: keyof CharacterProps;
|
||||
placeholder: string;
|
||||
icon: any; // Replace `any` with an appropriate type if you have a specific icon type.
|
||||
}
|
||||
|
||||
// Attributs de base (toujours visibles)
|
||||
export const basicCharacterElements: CharacterElement[] = [
|
||||
{
|
||||
title: 'Descriptions physiques',
|
||||
section: 'physical',
|
||||
placeholder: 'Nouvelle Description Physique',
|
||||
icon: faRuler,
|
||||
},
|
||||
{
|
||||
title: 'Descriptions psychologiques',
|
||||
section: 'psychological',
|
||||
placeholder: 'Nouvelle Description Psychologique',
|
||||
icon: faBrain,
|
||||
},
|
||||
];
|
||||
|
||||
// Attributs avancés (visibles en mode avancé)
|
||||
export const advancedCharacterElements: CharacterElement[] = [
|
||||
{
|
||||
title: 'Signes distinctifs',
|
||||
section: 'distinguishingMarks',
|
||||
placeholder: 'Nouveau signe distinctif',
|
||||
icon: faFingerprint,
|
||||
},
|
||||
{
|
||||
title: 'Arc du personnage',
|
||||
section: 'arc',
|
||||
placeholder: 'Nouvelle étape de l\'arc',
|
||||
icon: faRoute,
|
||||
},
|
||||
{
|
||||
title: 'Secrets',
|
||||
section: 'secrets',
|
||||
placeholder: 'Nouveau secret',
|
||||
icon: faUserSecret,
|
||||
},
|
||||
{
|
||||
title: 'Peurs',
|
||||
section: 'fears',
|
||||
placeholder: 'Nouvelle peur',
|
||||
icon: faGhost,
|
||||
},
|
||||
{
|
||||
title: 'Défauts',
|
||||
section: 'flaws',
|
||||
placeholder: 'Nouveau défaut',
|
||||
icon: faHeartBroken,
|
||||
},
|
||||
{
|
||||
title: 'Croyances',
|
||||
section: 'beliefs',
|
||||
placeholder: 'Nouvelle croyance',
|
||||
icon: faHandHoldingHeart,
|
||||
},
|
||||
{
|
||||
title: 'Conflits internes',
|
||||
section: 'conflicts',
|
||||
placeholder: 'Nouveau conflit',
|
||||
icon: faBolt,
|
||||
},
|
||||
{
|
||||
title: 'Citations',
|
||||
section: 'quotes',
|
||||
placeholder: 'Nouvelle citation',
|
||||
icon: faQuoteLeft,
|
||||
},
|
||||
{
|
||||
title: 'Relations',
|
||||
section: 'relations',
|
||||
placeholder: 'Nouveau Nom de Relation',
|
||||
icon: faUsers,
|
||||
},
|
||||
{
|
||||
title: 'Compétences',
|
||||
section: 'skills',
|
||||
placeholder: 'Nouvelle Compétence',
|
||||
icon: faWrench,
|
||||
},
|
||||
{
|
||||
title: 'Faiblesses',
|
||||
section: 'weaknesses',
|
||||
placeholder: 'Nouvelle Faiblesse',
|
||||
icon: faExclamationTriangle,
|
||||
},
|
||||
{
|
||||
title: 'Forces',
|
||||
section: 'strengths',
|
||||
placeholder: 'Nouvelle Force',
|
||||
icon: faShieldAlt,
|
||||
},
|
||||
{
|
||||
title: 'Objectifs',
|
||||
section: 'goals',
|
||||
placeholder: 'Nouvel Objectif',
|
||||
icon: faBullseye,
|
||||
},
|
||||
{
|
||||
title: 'Motivations',
|
||||
section: 'motivations',
|
||||
placeholder: 'Nouvelle Motivation',
|
||||
icon: faFire,
|
||||
},
|
||||
{
|
||||
title: 'Objets importants',
|
||||
section: 'items',
|
||||
placeholder: 'Nouvel objet',
|
||||
icon: faBox,
|
||||
},
|
||||
{
|
||||
title: 'Affiliations',
|
||||
section: 'affiliations',
|
||||
placeholder: 'Nouvelle affiliation',
|
||||
icon: faPeopleGroup,
|
||||
},
|
||||
];
|
||||
|
||||
// Pour rétro-compatibilité, on garde characterElementCategory qui combine les deux
|
||||
export const characterElementCategory: CharacterElement[] = [
|
||||
...basicCharacterElements,
|
||||
...advancedCharacterElements,
|
||||
];
|
||||
@@ -1,20 +0,0 @@
|
||||
import {IconDefinition} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export interface PanelComponent {
|
||||
id: number,
|
||||
title: string,
|
||||
badge: string,
|
||||
description: string,
|
||||
icon: IconDefinition,
|
||||
action?: () => void,
|
||||
}
|
||||
|
||||
export default class Editor {
|
||||
public static convertToHtml(text: string): string {
|
||||
return text
|
||||
.split(/\n\s*\n/)
|
||||
.map((paragraph: string): string => `<p>${paragraph.trim()}</p>`)
|
||||
.join('');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
import User, {Subscription} from "@/lib/models/User";
|
||||
import {SessionProps} from "@/lib/models/Session";
|
||||
|
||||
export type MessageType = "user" | "model";
|
||||
export type QSView = 'list' | 'chat' | 'ghostwritter' | 'dictionary' | 'synonyms' | 'conjugator' | 'inspiration'
|
||||
export type ConversationType = 'dictionary' | 'synonyms' | 'conjugator' | 'chatbot' | 'inspire';
|
||||
|
||||
export interface Message {
|
||||
id: number;
|
||||
type: MessageType;
|
||||
message: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: string;
|
||||
title?: string;
|
||||
date?: string;
|
||||
type?: ConversationType;
|
||||
messages: Message[];
|
||||
status: number;
|
||||
totalPrice?: number
|
||||
useYourKey?: boolean;
|
||||
}
|
||||
|
||||
export interface AIGeneratedText {
|
||||
totalTokens: number;
|
||||
totalPrice: number;
|
||||
response: string;
|
||||
}
|
||||
|
||||
export interface AIResponseWithCredits<T> {
|
||||
useYourKey: boolean;
|
||||
totalPrice: number;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface AIDictionary extends AIResponseWithCredits<DictionaryAIResponse> {
|
||||
}
|
||||
|
||||
export interface AIGeneratedTextData {
|
||||
totalCost: number;
|
||||
response: string;
|
||||
}
|
||||
|
||||
export interface AIGeneratedText extends AIResponseWithCredits<AIGeneratedTextData> {
|
||||
}
|
||||
|
||||
export interface AIInspire extends AIResponseWithCredits<InspireAIResponse> {
|
||||
}
|
||||
|
||||
export interface AISynonyms extends AIResponseWithCredits<SynonymsAIResponse> {
|
||||
}
|
||||
|
||||
export interface AIVerbConjugation extends AIResponseWithCredits<unknown> {
|
||||
}
|
||||
|
||||
interface InspireAIResponse {
|
||||
ideas: {
|
||||
idea: string,
|
||||
reason: string;
|
||||
relatedTo: string;
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface DictionaryAIResponse {
|
||||
word: string;
|
||||
definition: string;
|
||||
example: string;
|
||||
literaryUsage: string
|
||||
}
|
||||
|
||||
export interface SynonymAI {
|
||||
word: string;
|
||||
context: string;
|
||||
}
|
||||
|
||||
export interface SynonymsAIResponse {
|
||||
words: SynonymAI[];
|
||||
}
|
||||
|
||||
export interface InspirationAIIdea {
|
||||
idea: string;
|
||||
reason: string;
|
||||
relatedTo: string;
|
||||
}
|
||||
|
||||
export interface ConversationProps {
|
||||
id: string;
|
||||
mode: string;
|
||||
title: string;
|
||||
startDate: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export default class QuillSense {
|
||||
static getSubLevel(session: SessionProps): number {
|
||||
let currentSub: Subscription | null = User.getCurrentSubscription(session?.user, 'quill-sense');
|
||||
if (!currentSub) {
|
||||
currentSub = User.getCurrentSubscription(session?.user, 'quill-trial');
|
||||
if (!currentSub) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
switch (currentSub?.subTier) {
|
||||
case 1:
|
||||
return 1;
|
||||
case 2:
|
||||
return 2;
|
||||
case 3:
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
static isBringYourKeys(session: SessionProps): boolean {
|
||||
if (!session?.user) return false;
|
||||
const currentSub: Subscription | null = User.getCurrentSubscription(session?.user, 'use-your-keys');
|
||||
return currentSub?.status || session.user.groupId <= 4;
|
||||
}
|
||||
|
||||
static isGeminiEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.gemini || false;
|
||||
}
|
||||
|
||||
static isAnthropicEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.anthropic || false;
|
||||
}
|
||||
|
||||
static isOpenAIEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.openai || false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface QuillSenseSettingsProps {
|
||||
quillsenseEnabled: boolean;
|
||||
advancedPrompt: string | null;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import {UserProps} from "@/lib/models/User";
|
||||
|
||||
export interface SessionProps {
|
||||
isConnected: boolean,
|
||||
accessToken: string;
|
||||
user: UserProps | null;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
valid: boolean,
|
||||
message?: string,
|
||||
token?: string,
|
||||
userid?: string
|
||||
}
|
||||
|
||||
export default class Session {
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import {SelectBoxProps} from "@/shared/interface";
|
||||
|
||||
// ==================== SPELL TAG INTERFACES ====================
|
||||
|
||||
export interface SpellTagProps {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string | null;
|
||||
}
|
||||
|
||||
// ==================== SPELL INTERFACES ====================
|
||||
|
||||
// Réponse de GET /spell/detail et POST /spell/add
|
||||
export interface SpellProps {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string[]; // IDs des tags
|
||||
powerLevel: string | null;
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
notes: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
// Pour POST /spell/add et PUT /spell/update
|
||||
export interface SpellPropsPost {
|
||||
id?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string[];
|
||||
powerLevel?: string | null;
|
||||
components?: string | null;
|
||||
limitations?: string | null;
|
||||
notes?: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
// Item dans la liste (GET /spell/list)
|
||||
export interface SpellListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
tags: SpellTagProps[]; // Tags résolus (pas les IDs)
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
// Réponse de GET /spell/list
|
||||
export interface SpellListResponse {
|
||||
enabled: boolean;
|
||||
spells: SpellListItem[];
|
||||
tags: SpellTagProps[];
|
||||
}
|
||||
|
||||
// État local pour l'édition (avec id nullable pour création)
|
||||
export interface SpellEditState {
|
||||
id: string | null;
|
||||
name: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string[];
|
||||
powerLevel: string | null;
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
notes: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
export const initialSpellState: SpellEditState = {
|
||||
id: null,
|
||||
name: '',
|
||||
description: '',
|
||||
appearance: '',
|
||||
tags: [],
|
||||
powerLevel: null,
|
||||
components: null,
|
||||
limitations: null,
|
||||
notes: null,
|
||||
seriesSpellId: null,
|
||||
};
|
||||
|
||||
export const spellPowerLevels: SelectBoxProps[] = [
|
||||
{value: 'none', label: 'spellPowerLevels.none'},
|
||||
{value: 'cantrip', label: 'spellPowerLevels.cantrip'},
|
||||
{value: 'novice', label: 'spellPowerLevels.novice'},
|
||||
{value: 'apprentice', label: 'spellPowerLevels.apprentice'},
|
||||
{value: 'journeyman', label: 'spellPowerLevels.journeyman'},
|
||||
{value: 'expert', label: 'spellPowerLevels.expert'},
|
||||
{value: 'master', label: 'spellPowerLevels.master'},
|
||||
{value: 'grandmaster', label: 'spellPowerLevels.grandmaster'},
|
||||
{value: 'legendary', label: 'spellPowerLevels.legendary'},
|
||||
{value: 'divine', label: 'spellPowerLevels.divine'},
|
||||
];
|
||||
|
||||
export const defaultTagColors: string[] = [
|
||||
'#51AE84',
|
||||
'#3A8B69',
|
||||
'#2196F3',
|
||||
'#1976D2',
|
||||
'#FFA726',
|
||||
'#FF9800',
|
||||
'#EF5350',
|
||||
'#E53935',
|
||||
'#AB47BC',
|
||||
'#9C27B0',
|
||||
'#26A69A',
|
||||
'#00897B',
|
||||
'#5C6BC0',
|
||||
'#3F51B5',
|
||||
'#EC407A',
|
||||
'#D81B60',
|
||||
];
|
||||
1116
lib/models/Story.ts
1116
lib/models/Story.ts
File diff suppressed because it is too large
Load Diff
@@ -1,274 +0,0 @@
|
||||
import axios, {AxiosResponse} from "axios";
|
||||
import {configs} from "@/lib/configs";
|
||||
|
||||
export default class System{
|
||||
static verifyInput(input: string): boolean {
|
||||
let pattern: RegExp = new RegExp('(<.*?>)|(&.*?;)|({.*?})', 'gmi');
|
||||
return pattern.test(input);
|
||||
}
|
||||
|
||||
public static timeStampInSeconds(): number {
|
||||
const date: number = new Date().getTime();
|
||||
return Math.floor(date / 1000);
|
||||
}
|
||||
|
||||
public static formatHTMLContent(htmlContent: string): string {
|
||||
return htmlContent
|
||||
.replace(/<h1>/g, '<h1 style="color: #FFFFFF; text-indent: 5px; font-size: 28px; font-weight: bold; text-align: left; margin-vertical: 10px;">')
|
||||
.replace(/<p>/g, '<p style="color: #d0d0d0; text-indent: 30px; font-size: 16px; line-height: 22px; margin-vertical: 5px;">')
|
||||
.replace(/<blockquote>/g, '<blockquote style="border-left-width: 4px; border-left-color: #ccc; padding-left: 10px; font-style: italic; color: #555;">');
|
||||
}
|
||||
|
||||
public static textContentToHtml(content: string): string {
|
||||
const paragraphs: string[] = content
|
||||
.split(/\n+/)
|
||||
.map((paragraph: string) => paragraph.trim())
|
||||
.filter((paragraph: string) => paragraph.length > 0);
|
||||
|
||||
return paragraphs
|
||||
.map((paragraph: string) => `<p>${paragraph}</p>`)
|
||||
.join('');
|
||||
}
|
||||
|
||||
public static async authGetQueryToServer<T>(url: string, auth: string, lang: string = "fr", params: Record<string, any> = {}): Promise<T> {
|
||||
try {
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${auth}`
|
||||
},
|
||||
params: {
|
||||
lang: lang,
|
||||
plateforme: 'desktop',
|
||||
...params
|
||||
},
|
||||
url: configs.apiUrl + url,
|
||||
})
|
||||
return response.data;
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const serverMessage: string = e.response?.data?.message || e.response?.data || e.message;
|
||||
throw new Error(serverMessage as string);
|
||||
} else if (e instanceof Error) {
|
||||
throw new Error(e.message);
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static setCookie(name: string, value: string, days: number): void {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = `expires=${date.toUTCString()}`;
|
||||
let domain: string = '';
|
||||
if (!/localhost|127\.0\.0\.1/.test(window.location.hostname)) {
|
||||
domain = `domain=${window.location.hostname};`;
|
||||
}
|
||||
const secure = 'Secure;';
|
||||
const sameSite = 'SameSite=Strict;';
|
||||
document.cookie = `${name}=${value}; ${expires}; ${domain} path=/; ${secure} ${sameSite}`;
|
||||
}
|
||||
|
||||
public static async authPutToServer<T>(url: string, data: {}, auth: string, lang: string = "fr"): Promise<T> {
|
||||
try {
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${auth}`
|
||||
},
|
||||
params: {
|
||||
lang: lang,
|
||||
plateforme: 'desktop',
|
||||
},
|
||||
url: configs.apiUrl + url,
|
||||
data: data
|
||||
})
|
||||
return response.data;
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const serverMessage: string = e.response?.data?.message || e.response?.data || e.message;
|
||||
throw new Error(serverMessage as string);
|
||||
} else if (e instanceof Error) {
|
||||
throw new Error(e.message);
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async authPatchToServer<T>(url: string, data: {}, auth: string, lang: string = "fr"): Promise<T> {
|
||||
try {
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: 'patch',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${auth}`
|
||||
},
|
||||
params: {
|
||||
lang: lang,
|
||||
plateforme: 'desktop',
|
||||
},
|
||||
url: configs.apiUrl + url,
|
||||
data: data
|
||||
})
|
||||
return response.data;
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const serverMessage: string = e.response?.data?.message || e.response?.data || e.message;
|
||||
throw new Error(serverMessage as string);
|
||||
} else if (e instanceof Error) {
|
||||
throw new Error(e.message);
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async postToServer<T>(url: string, data: {}, lang: string = "fr"): Promise<T> {
|
||||
try {
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
url: configs.apiUrl + url,
|
||||
params: {
|
||||
lang: lang,
|
||||
plateforme: 'desktop',
|
||||
},
|
||||
data: data
|
||||
})
|
||||
return response.data;
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const serverMessage: string = e.response?.data?.message || e.response?.data || e.message;
|
||||
throw new Error(serverMessage as string);
|
||||
} else if (e instanceof Error) {
|
||||
throw new Error(e.message);
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async authPostToServer<T>(url: string, data: {}, auth: string, lang: string = "fr"): Promise<T> {
|
||||
try {
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${auth}`
|
||||
},
|
||||
url: configs.apiUrl + url,
|
||||
params: {
|
||||
lang: lang,
|
||||
plateforme: 'desktop',
|
||||
},
|
||||
data: data
|
||||
})
|
||||
return response.data;
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const serverMessage: string = e.response?.data?.message || e.response?.data || e.message;
|
||||
throw new Error(serverMessage as string);
|
||||
} else if (e instanceof Error) {
|
||||
throw new Error(e.message);
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static htmlToText(html: string) {
|
||||
return html
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<\/?(p|h[1-6]|div)(\s+[^>]*)?>/gi, '\n')
|
||||
.replace(/<\/?[^>]+(>|$)/g, '')
|
||||
.replace(/(\n\s*){2,}/g, '\n\n')
|
||||
.replace(/^\s+|\s+$|(?<=\s)\s+/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
public static getCookie(name: string): string | null {
|
||||
const nameEQ = `${name}=`;
|
||||
const allCookies: string[] = document.cookie.split(';');
|
||||
for (let i: number = 0; i < allCookies.length; i++) {
|
||||
let cookie: string = allCookies[i];
|
||||
while (cookie.charAt(0) === ' ') cookie = cookie.substring(1, cookie.length);
|
||||
if (cookie.indexOf(nameEQ) === 0) return cookie.substring(nameEQ.length, cookie.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static removeCookie(name: string): void {
|
||||
let domain: string = '';
|
||||
if (!/localhost|127\.0\.0\.1/.test(window.location.hostname)) {
|
||||
domain = `domain=${window.location.hostname};`;
|
||||
}
|
||||
const secure = 'Secure;';
|
||||
const sameSite = 'SameSite=Strict;';
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; ${domain} path=/; ${secure} ${sameSite}`;
|
||||
}
|
||||
|
||||
public static async authUploadFileToServer<T>(url: string, file: File, auth: string, lang: string = "fr"): Promise<T> {
|
||||
try {
|
||||
const formData: FormData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('lang', lang);
|
||||
formData.append('plateforme', 'desktop');
|
||||
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${auth}`,
|
||||
},
|
||||
url: configs.apiUrl + url,
|
||||
params: {
|
||||
lang: lang,
|
||||
plateforme: 'desktop',
|
||||
},
|
||||
data: formData,
|
||||
});
|
||||
return response.data;
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const serverMessage: string = e.response?.data?.message || e.response?.data || e.message;
|
||||
throw new Error(serverMessage as string);
|
||||
} else if (e instanceof Error) {
|
||||
throw new Error(e.message);
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async authDeleteToServer<T>(url: string, data: {}, auth: string, lang: string = "fr"): Promise<T> {
|
||||
try {
|
||||
const response: AxiosResponse<T> = await axios({
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${auth}`
|
||||
},
|
||||
url: configs.apiUrl + url,
|
||||
params: {
|
||||
lang: lang,
|
||||
plateforme: 'desktop',
|
||||
},
|
||||
data: data
|
||||
})
|
||||
return response.data;
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const serverMessage: string = e.response?.data?.message || e.response?.data || e.message;
|
||||
throw new Error(serverMessage as string);
|
||||
} else if (e instanceof Error) {
|
||||
throw new Error(e.message);
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import {SelectBoxProps} from "@/shared/interface";
|
||||
import {BookProps} from "@/lib/models/Book";
|
||||
import {SessionProps} from "@/lib/models/Session";
|
||||
|
||||
export interface Author {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UserProps {
|
||||
id: string;
|
||||
name: string;
|
||||
lastName: string;
|
||||
username: string;
|
||||
authorName?: string;
|
||||
email?: string;
|
||||
accountVerified?: boolean;
|
||||
termsAccepted?: boolean;
|
||||
aiUsage: number,
|
||||
apiKeys: {
|
||||
gemini: boolean
|
||||
openai: boolean,
|
||||
anthropic: boolean,
|
||||
},
|
||||
guideTour?: GuideTour[];
|
||||
subscription?: Subscription[];
|
||||
writingLang: number;
|
||||
writingLevel: number;
|
||||
ritePoints: number;
|
||||
creditsBalance:number;
|
||||
groupId: number;
|
||||
}
|
||||
|
||||
export interface GuideTour {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
export interface Subscription {
|
||||
subType: string;
|
||||
subTier: number;
|
||||
status: boolean;
|
||||
}
|
||||
|
||||
export const writingLevel: SelectBoxProps[] = [
|
||||
{value: '0', label: 'Sélectionner un niveau d\'écriture'},
|
||||
{value: '1', label: 'Je suis débutant'},
|
||||
{value: '2', label: 'Je suis intermédiaire'},
|
||||
{value: '3', label: 'Je suis avancé'},
|
||||
];
|
||||
|
||||
export default class User {
|
||||
static getCurrentSubscription(user: UserProps | null, type: "quill-sense" | "use-your-keys" | "quill-trial"): Subscription | null {
|
||||
if (!user || !user.subscription || user.subscription.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return user.subscription.find((sub: Subscription): boolean => {
|
||||
return sub.subType === type && sub.status;
|
||||
}) || null;
|
||||
}
|
||||
static getWritingLevel(level: number): string {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return 'Débutant';
|
||||
case 2:
|
||||
return 'Intermédiaire';
|
||||
case 3:
|
||||
return 'Avancé';
|
||||
default:
|
||||
return 'Débutant';
|
||||
}
|
||||
}
|
||||
|
||||
static guideTourDone(guide: GuideTour[], tour: string): boolean {
|
||||
if (!tour) return false;
|
||||
|
||||
if (guide && guide.find((guide: GuideTour): boolean => guide[tour]) !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier ensuite dans localStorage pour le mode offline
|
||||
if (typeof window !== 'undefined' && window.localStorage) {
|
||||
const completedGuides = JSON.parse(localStorage.getItem('completedGuides') || '[]');
|
||||
if (completedGuides.includes(tour)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static setNewGuideTour(session: SessionProps, tour: string): SessionProps {
|
||||
const newGuideTour: { [key: string]: boolean }[] = [
|
||||
...(session?.user?.guideTour ?? []),
|
||||
{[tour]: true}
|
||||
];
|
||||
return {
|
||||
...session,
|
||||
user: {
|
||||
...session?.user as UserProps,
|
||||
guideTour: newGuideTour
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import {
|
||||
faCrown,
|
||||
faExclamationTriangle,
|
||||
faFlag,
|
||||
faGavel,
|
||||
faIndustry,
|
||||
faLeaf,
|
||||
faMountain,
|
||||
faMusic,
|
||||
faPeopleArrows,
|
||||
faSnowflake,
|
||||
faUserCog,
|
||||
faUserFriends,
|
||||
IconDefinition,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export interface ElementSection {
|
||||
title: string;
|
||||
section: keyof WorldProps;
|
||||
icon: IconDefinition;
|
||||
}
|
||||
|
||||
export interface WorldElement {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface WorldProps {
|
||||
id: string;
|
||||
name: string;
|
||||
history: string;
|
||||
politics: string;
|
||||
economy: string;
|
||||
religion: string;
|
||||
languages: string;
|
||||
laws: WorldElement[];
|
||||
biomes: WorldElement[];
|
||||
issues: WorldElement[];
|
||||
customs: WorldElement[];
|
||||
kingdoms: WorldElement[];
|
||||
climate: WorldElement[];
|
||||
resources: WorldElement[];
|
||||
wildlife: WorldElement[];
|
||||
arts: WorldElement[];
|
||||
ethnicGroups: WorldElement[];
|
||||
socialClasses: WorldElement[];
|
||||
importantCharacters: WorldElement[];
|
||||
seriesWorldId?: string | null;
|
||||
}
|
||||
|
||||
export interface WorldListResponse {
|
||||
worlds: WorldProps[];
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export const elementSections: ElementSection[] = [
|
||||
{
|
||||
title: 'Lois',
|
||||
section: 'laws',
|
||||
icon: faGavel,
|
||||
},
|
||||
{
|
||||
title: 'Biomes',
|
||||
section: 'biomes',
|
||||
icon: faMountain,
|
||||
},
|
||||
{
|
||||
title: 'Problèmes',
|
||||
section: 'issues',
|
||||
icon: faExclamationTriangle,
|
||||
},
|
||||
{
|
||||
title: 'Coutumes',
|
||||
section: 'customs',
|
||||
icon: faPeopleArrows,
|
||||
},
|
||||
{
|
||||
title: 'Royaumes',
|
||||
section: 'kingdoms',
|
||||
icon: faFlag,
|
||||
},
|
||||
{
|
||||
title: 'Climat',
|
||||
section: 'climate',
|
||||
icon: faSnowflake,
|
||||
},
|
||||
{
|
||||
title: 'Ressources',
|
||||
section: 'resources',
|
||||
icon: faIndustry,
|
||||
},
|
||||
{
|
||||
title: 'Faune',
|
||||
section: 'wildlife',
|
||||
icon: faLeaf,
|
||||
},
|
||||
{
|
||||
title: 'Arts',
|
||||
section: 'arts',
|
||||
icon: faMusic,
|
||||
},
|
||||
{
|
||||
title: 'Groupes ethniques',
|
||||
section: 'ethnicGroups',
|
||||
icon: faUserFriends,
|
||||
},
|
||||
{
|
||||
title: 'Classes sociales',
|
||||
section: 'socialClasses',
|
||||
icon: faUserCog,
|
||||
},
|
||||
{
|
||||
title: 'Personnages importants',
|
||||
section: 'importantCharacters',
|
||||
icon: faCrown,
|
||||
},
|
||||
];
|
||||
|
||||
export default class World {
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
20
lib/navigation.ts
Normal file
20
lib/navigation.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {useNavigate, useParams as useRouterParams, Link, useLocation} from 'react-router-dom';
|
||||
|
||||
function useRouter() {
|
||||
const navigate = useNavigate();
|
||||
return {
|
||||
push: (path: string) => navigate(path),
|
||||
replace: (path: string) => navigate(path, {replace: true}),
|
||||
back: () => navigate(-1),
|
||||
};
|
||||
}
|
||||
|
||||
function useParams<T extends Record<string, string>>(): T {
|
||||
return useRouterParams() as T;
|
||||
}
|
||||
|
||||
function usePathname(): string {
|
||||
return useLocation().pathname;
|
||||
}
|
||||
|
||||
export {Link, useRouter, useParams, usePathname};
|
||||
735
lib/tauri.ts
Normal file
735
lib/tauri.ts
Normal file
@@ -0,0 +1,735 @@
|
||||
import {invoke as tauriInvoke} from '@tauri-apps/api/core';
|
||||
|
||||
async function invoke<T>(command: string, args?: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await tauriInvoke<T>(command, args);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) throw e;
|
||||
if (typeof e === 'string') throw new Error(e);
|
||||
if (typeof e === 'object' && e !== null && 'message' in e) throw new Error(String((e as {message: string}).message));
|
||||
throw new Error(String(e));
|
||||
}
|
||||
}
|
||||
|
||||
export {invoke};
|
||||
|
||||
// ─── Types ─────────────────────────────────────────────────
|
||||
|
||||
import {BookProps, GuideLine, GuideLineAI} from '@/lib/types/book';
|
||||
import {ChapterProps} from '@/lib/types/chapter';
|
||||
import {UserProps} from '@/lib/types/user';
|
||||
|
||||
export interface InitUserResult {
|
||||
success: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface OfflineResult {
|
||||
success: boolean;
|
||||
error: string | null;
|
||||
userId: string | null;
|
||||
}
|
||||
|
||||
export interface OfflineModeStatus {
|
||||
enabled: boolean;
|
||||
syncInterval: number;
|
||||
hasPin: boolean;
|
||||
lastUserId: string | null;
|
||||
}
|
||||
|
||||
export interface SyncCheckResult {
|
||||
shouldSync: boolean;
|
||||
daysSinceSync: number | null;
|
||||
syncInterval: number | null;
|
||||
}
|
||||
|
||||
export interface TombstoneRecord {
|
||||
tableName: string;
|
||||
entityId: string;
|
||||
bookId: string | null;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
// ─── User & Auth ───────────────────────────────────────────
|
||||
|
||||
export async function initUser(userId: string): Promise<InitUserResult> {
|
||||
return invoke<InitUserResult>('init_user', {data: {userId}});
|
||||
}
|
||||
|
||||
export async function dbInitialize(userId: string, encryptionKey: string): Promise<boolean> {
|
||||
return invoke<boolean>('db_initialize', {userId, encryptionKey});
|
||||
}
|
||||
|
||||
export async function getToken(): Promise<string | null> {
|
||||
return invoke<string | null>('get_token');
|
||||
}
|
||||
|
||||
export async function setToken(token: string): Promise<void> {
|
||||
return invoke<void>('set_token', {token});
|
||||
}
|
||||
|
||||
export async function removeToken(): Promise<void> {
|
||||
return invoke<void>('remove_token');
|
||||
}
|
||||
|
||||
export async function getUserEncryptionKey(userId: string): Promise<string | null> {
|
||||
return invoke<string | null>('get_user_encryption_key', {userId});
|
||||
}
|
||||
|
||||
export async function getPlatform(): Promise<string> {
|
||||
return invoke<string>('get_platform');
|
||||
}
|
||||
|
||||
export async function getUserInfo(): Promise<UserProps> {
|
||||
return invoke<UserProps>('get_user_info');
|
||||
}
|
||||
|
||||
export async function syncUser(data: {
|
||||
userId: string;
|
||||
username: string;
|
||||
email: string;
|
||||
}): Promise<boolean> {
|
||||
return invoke<boolean>('sync_user', {data});
|
||||
}
|
||||
|
||||
// ─── Dev ──────────────────────────────────────────────────
|
||||
|
||||
export async function devResetAll(): Promise<boolean> {
|
||||
return invoke<boolean>('dev_reset_all');
|
||||
}
|
||||
|
||||
// ─── Offline ───────────────────────────────────────────────
|
||||
|
||||
export async function offlinePinSet(pin: string): Promise<OfflineResult> {
|
||||
return invoke<OfflineResult>('offline_pin_set', {data: {pin}});
|
||||
}
|
||||
|
||||
export async function offlinePinVerify(pin: string): Promise<OfflineResult> {
|
||||
return invoke<OfflineResult>('offline_pin_verify', {data: {pin}});
|
||||
}
|
||||
|
||||
export async function offlineModeGet(): Promise<OfflineModeStatus> {
|
||||
return invoke<OfflineModeStatus>('offline_mode_get');
|
||||
}
|
||||
|
||||
export async function offlineModeSet(enabled: boolean, syncIntervalDays: number): Promise<boolean> {
|
||||
return invoke<boolean>('offline_mode_set', {data: {enabled, syncIntervalDays}});
|
||||
}
|
||||
|
||||
export async function offlineSyncCheck(): Promise<SyncCheckResult> {
|
||||
return invoke<SyncCheckResult>('offline_sync_check');
|
||||
}
|
||||
|
||||
// ─── Book ──────────────────────────────────────────────────
|
||||
|
||||
export async function getBooks(): Promise<BookProps[]> {
|
||||
return invoke<BookProps[]>('get_books');
|
||||
}
|
||||
|
||||
export async function getBook(bookId: string): Promise<BookProps> {
|
||||
return invoke<BookProps>('get_book', {bookId});
|
||||
}
|
||||
|
||||
export async function createBook(data: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
summary?: string;
|
||||
type: string;
|
||||
serieId?: number;
|
||||
desiredReleaseDate?: string;
|
||||
desiredWordCount?: number;
|
||||
}): Promise<string> {
|
||||
return invoke<string>('create_book', {data});
|
||||
}
|
||||
|
||||
export async function updateBookBasicInfo(data: {
|
||||
bookId: string;
|
||||
title: string;
|
||||
subTitle: string;
|
||||
summary: string;
|
||||
publicationDate: string;
|
||||
wordCount: number;
|
||||
}): Promise<boolean> {
|
||||
return invoke<boolean>('update_book_basic_info', {data});
|
||||
}
|
||||
|
||||
export async function deleteBook(id: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_book', {data: {id, deletedAt}});
|
||||
}
|
||||
|
||||
export async function updateBookToolSetting(bookId: string, toolName: string, enabled: boolean): Promise<boolean> {
|
||||
return invoke<boolean>('update_book_tool_setting', {data: {bookId, toolName, enabled}});
|
||||
}
|
||||
|
||||
export async function getBookStory(bookId: string): Promise<unknown> {
|
||||
return invoke<unknown>('get_book_story', {data: {bookId}});
|
||||
}
|
||||
|
||||
export async function updateBookStory(data: {
|
||||
bookId: string;
|
||||
acts: unknown[];
|
||||
mainChapters: unknown[];
|
||||
issues: unknown[];
|
||||
}): Promise<boolean> {
|
||||
return invoke<boolean>('update_book_story', {data});
|
||||
}
|
||||
|
||||
export async function addIncident(bookId: string, name: string, incidentId?: string): Promise<string> {
|
||||
return invoke<string>('add_incident', {data: {bookId, name, incidentId}});
|
||||
}
|
||||
|
||||
export async function removeIncident(bookId: string, incidentId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('remove_incident', {data: {bookId, incidentId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function addPlotPoint(bookId: string, name: string, incidentId: string, plotId?: string): Promise<string> {
|
||||
return invoke<string>('add_plot_point', {data: {bookId, name, incidentId, plotId}});
|
||||
}
|
||||
|
||||
export async function removePlotPoint(plotId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('remove_plot_point', {data: {plotId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function addIssue(bookId: string, name: string, issueId?: string): Promise<string> {
|
||||
return invoke<string>('add_issue', {data: {bookId, name, issueId}});
|
||||
}
|
||||
|
||||
export async function removeIssue(bookId: string, issueId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('remove_issue', {data: {bookId, issueId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function getBookBasicInformation(bookId: string): Promise<BookProps> {
|
||||
return invoke<BookProps>('get_book_basic_information', {bookId});
|
||||
}
|
||||
|
||||
export async function getBookExportInfo(bookId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_book_export_info', {data: {bookId}});
|
||||
}
|
||||
|
||||
export async function exportBook(data: {
|
||||
bookId: string;
|
||||
format: string;
|
||||
selections: unknown[] | null;
|
||||
}): Promise<boolean> {
|
||||
return invoke<boolean>('export_book', {data});
|
||||
}
|
||||
|
||||
// ─── Book Guidelines ────────────────────────────────────────
|
||||
|
||||
|
||||
export async function getGuideLine(bookId: string): Promise<GuideLine> {
|
||||
return invoke<GuideLine>('get_guideline', {data: {id: bookId}});
|
||||
}
|
||||
|
||||
export async function getAIGuideLine(bookId: string): Promise<GuideLineAI> {
|
||||
return invoke<GuideLineAI>('get_ai_guideline', {data: {id: bookId}});
|
||||
}
|
||||
|
||||
export async function updateGuideLine(data: {
|
||||
bookId: string;
|
||||
tone: string;
|
||||
atmosphere: string;
|
||||
writingStyle: string;
|
||||
themes: string;
|
||||
symbolism: string;
|
||||
motifs: string;
|
||||
narrativeVoice: string;
|
||||
pacing: string;
|
||||
intendedAudience: string;
|
||||
keyMessages: string;
|
||||
}): Promise<boolean> {
|
||||
return invoke<boolean>('update_guideline', {data});
|
||||
}
|
||||
|
||||
export async function updateAIGuideLine(data: {
|
||||
bookId: string;
|
||||
plotSummary: string;
|
||||
verbTense: string;
|
||||
narrativeType: string;
|
||||
dialogueType: string;
|
||||
toneAtmosphere: string;
|
||||
language: string;
|
||||
themes: string;
|
||||
}): Promise<boolean> {
|
||||
return invoke<boolean>('update_ai_guideline', {data});
|
||||
}
|
||||
|
||||
// ─── Chapter ───────────────────────────────────────────────
|
||||
|
||||
export async function getChapters(bookId: string): Promise<ChapterProps[]> {
|
||||
return invoke<ChapterProps[]>('get_chapters', {bookId});
|
||||
}
|
||||
|
||||
export async function getWholeChapter(id: string, version: number, bookId: string): Promise<ChapterProps> {
|
||||
return invoke<ChapterProps>('get_whole_chapter', {data: {id, version, bookId}});
|
||||
}
|
||||
|
||||
export async function getChapterStory(chapterId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_chapter_story', {chapterId});
|
||||
}
|
||||
|
||||
export async function getCompanionContent(chapterId: string, version: number): Promise<unknown> {
|
||||
return invoke<unknown>('get_companion_content', {data: {chapterId, version}});
|
||||
}
|
||||
|
||||
export async function getChapterContent(chapterId: string, version: number): Promise<string> {
|
||||
return invoke<string>('get_chapter_content', {data: {chapterId, version}});
|
||||
}
|
||||
|
||||
export async function saveChapterContent(data: {
|
||||
chapterId: string;
|
||||
version: number;
|
||||
content: unknown;
|
||||
totalWordCount: number;
|
||||
contentId: string;
|
||||
}): Promise<boolean> {
|
||||
return invoke<boolean>('save_chapter_content', {data});
|
||||
}
|
||||
|
||||
export async function getLastChapter(bookId: string): Promise<ChapterProps | null> {
|
||||
return invoke<ChapterProps | null>('get_last_chapter', {bookId});
|
||||
}
|
||||
|
||||
export async function addChapter(data: {
|
||||
bookId: string;
|
||||
title: string;
|
||||
chapterOrder: number;
|
||||
chapterId?: string;
|
||||
}): Promise<string> {
|
||||
return invoke<string>('add_chapter', {data});
|
||||
}
|
||||
|
||||
export async function removeChapter(chapterId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('remove_chapter', {data: {chapterId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function updateChapter(chapterId: string, title: string, chapterOrder: number): Promise<boolean> {
|
||||
return invoke<boolean>('update_chapter', {data: {chapterId, title, chapterOrder}});
|
||||
}
|
||||
|
||||
export async function addChapterInformation(data: {
|
||||
chapterId: string;
|
||||
actId: number;
|
||||
bookId: string;
|
||||
plotId?: string;
|
||||
incidentId?: string;
|
||||
chapterInfoId?: string;
|
||||
}): Promise<string> {
|
||||
return invoke<string>('add_chapter_information', {data});
|
||||
}
|
||||
|
||||
export async function removeChapterInformation(chapterInfoId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('remove_chapter_information', {data: {chapterInfoId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function getBookTags(bookId: string): Promise<unknown> {
|
||||
return invoke<unknown>('get_book_tags', {bookId});
|
||||
}
|
||||
|
||||
// ─── Character ─────────────────────────────────────────────
|
||||
|
||||
export async function getCharacterList(bookId: string, enabled: boolean): Promise<unknown> {
|
||||
return invoke<unknown>('get_character_list', {data: {bookId, enabled}});
|
||||
}
|
||||
|
||||
export async function getCharacterAttributes(characterId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_character_attributes', {data: {characterId}});
|
||||
}
|
||||
|
||||
export async function createCharacter(character: unknown, bookId: string, id?: string): Promise<string> {
|
||||
return invoke<string>('create_character', {data: {character, bookId, id}});
|
||||
}
|
||||
|
||||
export async function addCharacterAttribute(characterId: string, type: string, name: string, id?: string): Promise<string> {
|
||||
return invoke<string>('add_character_attribute', {data: {characterId, type, name, id}});
|
||||
}
|
||||
|
||||
export async function deleteCharacterAttribute(attributeId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_character_attribute', {data: {attributeId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function updateCharacter(character: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('update_character', {data: {character}});
|
||||
}
|
||||
|
||||
export async function deleteCharacter(characterId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_character', {data: {characterId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
// ─── Location ──────────────────────────────────────────────
|
||||
|
||||
export async function getAllLocations(bookId: string, enabled: boolean): Promise<unknown> {
|
||||
return invoke<unknown>('get_all_locations', {data: {bookId, enabled}});
|
||||
}
|
||||
|
||||
export async function addLocationSection(locationName: string, bookId: string, id?: string, seriesLocationId?: string): Promise<string> {
|
||||
return invoke<string>('add_location_section', {data: {locationName, bookId, id, seriesLocationId}});
|
||||
}
|
||||
|
||||
export async function addLocationElement(locationId: string, elementName: string, id?: string): Promise<string> {
|
||||
return invoke<string>('add_location_element', {data: {locationId, elementName, id}});
|
||||
}
|
||||
|
||||
export async function addLocationSubElement(elementId: string, subElementName: string, id?: string): Promise<string> {
|
||||
return invoke<string>('add_location_sub_element', {data: {elementId, subElementName, id}});
|
||||
}
|
||||
|
||||
export async function updateLocations(locations: unknown[]): Promise<unknown> {
|
||||
return invoke<unknown>('update_locations', {data: {locations}});
|
||||
}
|
||||
|
||||
export async function updateLocationSectionWithSeriesLink(sectionId: string, sectionName?: string, seriesLocationId?: string): Promise<boolean> {
|
||||
return invoke<boolean>('update_location_section_with_series_link', {data: {sectionId, sectionName, seriesLocationId}});
|
||||
}
|
||||
|
||||
export async function deleteLocationSection(locationId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_location_section', {data: {locationId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function deleteLocationElement(elementId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_location_element', {data: {elementId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function deleteLocationSubElement(subElementId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_location_sub_element', {data: {subElementId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
// ─── Spell ─────────────────────────────────────────────────
|
||||
|
||||
export async function getSpellList(bookId: string, enabled: boolean): Promise<unknown> {
|
||||
return invoke<unknown>('get_spell_list', {data: {bookId, enabled}});
|
||||
}
|
||||
|
||||
export async function getSpellTags(bookId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_spell_tags', {data: {bookId}});
|
||||
}
|
||||
|
||||
export async function getSpellDetail(spellId: string): Promise<unknown> {
|
||||
return invoke<unknown>('get_spell_detail', {data: {spellId}});
|
||||
}
|
||||
|
||||
export async function createSpell(bookId: string, spell: unknown): Promise<unknown> {
|
||||
return invoke<unknown>('create_spell', {data: {bookId, spell}});
|
||||
}
|
||||
|
||||
export async function updateSpell(spellId: string, spell: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('update_spell', {data: {spellId, spell}});
|
||||
}
|
||||
|
||||
export async function deleteSpell(spellId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_spell', {data: {spellId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function createSpellTag(bookId: string, name: string, color?: string, id?: string): Promise<unknown> {
|
||||
return invoke<unknown>('create_spell_tag', {data: {bookId, name, color, id}});
|
||||
}
|
||||
|
||||
export async function updateSpellTag(tagId: string, name: string, color?: string): Promise<boolean> {
|
||||
return invoke<boolean>('update_spell_tag', {data: {tagId, name, color}});
|
||||
}
|
||||
|
||||
export async function deleteSpellTag(tagId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_spell_tag', {data: {tagId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
// ─── World ─────────────────────────────────────────────────
|
||||
|
||||
export async function getWorlds(bookId: string, enabled: boolean): Promise<unknown> {
|
||||
return invoke<unknown>('get_worlds', {data: {bookId, enabled}});
|
||||
}
|
||||
|
||||
export async function addWorld(bookId: string, worldName: string, id?: string, seriesWorldId?: string): Promise<string> {
|
||||
return invoke<string>('add_world', {data: {bookId, worldName, id, seriesWorldId}});
|
||||
}
|
||||
|
||||
export async function addWorldElement(worldId: string, elementName: string, elementType: string, id?: string): Promise<string> {
|
||||
return invoke<string>('add_world_element', {data: {worldId, elementName, elementType, id}});
|
||||
}
|
||||
|
||||
export async function removeWorldElement(elementId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('remove_world_element', {data: {elementId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function updateWorld(world: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('update_world', {data: {world}});
|
||||
}
|
||||
|
||||
// ─── Series ────────────────────────────────────────────────
|
||||
|
||||
export async function getSeriesList(): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_series_list');
|
||||
}
|
||||
|
||||
export async function getSeriesDetail(seriesId: string): Promise<unknown> {
|
||||
return invoke<unknown>('get_series_detail', {data: {seriesId}});
|
||||
}
|
||||
|
||||
export async function createSeries(data: unknown): Promise<string> {
|
||||
return invoke<string>('create_series', {data});
|
||||
}
|
||||
|
||||
export async function updateSeries(data: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('update_series', {data});
|
||||
}
|
||||
|
||||
export async function deleteSeries(seriesId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series', {data: {seriesId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function getSeriesBooks(seriesId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_series_books', {data: {seriesId}});
|
||||
}
|
||||
|
||||
export async function addBookToSeries(seriesId: string, bookId: string): Promise<boolean> {
|
||||
return invoke<boolean>('add_book_to_series', {data: {seriesId, bookId}});
|
||||
}
|
||||
|
||||
export async function removeBookFromSeries(seriesId: string, bookId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('remove_book_from_series', {data: {seriesId, bookId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function reorderSeriesBooks(seriesId: string, bookIds: string[]): Promise<boolean> {
|
||||
return invoke<boolean>('reorder_series_books', {data: {seriesId, bookIds}});
|
||||
}
|
||||
|
||||
export async function getSeriesForBook(bookId: string): Promise<string | null> {
|
||||
return invoke<string | null>('get_series_for_book', {data: {bookId}});
|
||||
}
|
||||
|
||||
// ─── Series Characters ─────────────────────────────────────
|
||||
|
||||
export async function getSeriesCharacterList(seriesId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_series_character_list', {data: {seriesId}});
|
||||
}
|
||||
|
||||
export async function getSeriesCharacterAttributes(characterId: string): Promise<unknown> {
|
||||
return invoke<unknown>('get_series_character_attributes', {data: {characterId}});
|
||||
}
|
||||
|
||||
export async function addSeriesCharacter(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_character', {data});
|
||||
}
|
||||
|
||||
export async function updateSeriesCharacter(data: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('update_series_character', {data});
|
||||
}
|
||||
|
||||
export async function deleteSeriesCharacter(characterId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series_character', {data: {characterId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function addSeriesCharacterAttribute(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_character_attribute', {data});
|
||||
}
|
||||
|
||||
export async function deleteSeriesCharacterAttribute(attributeId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series_character_attribute', {data: {attributeId, deletedAt}});
|
||||
}
|
||||
|
||||
// ─── Series Locations ──────────────────────────────────────
|
||||
|
||||
export async function getSeriesLocationList(seriesId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_series_location_list', {data: {seriesId}});
|
||||
}
|
||||
|
||||
export async function addSeriesLocationSection(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_location_section', {data});
|
||||
}
|
||||
|
||||
export async function addSeriesLocationElement(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_location_element', {data});
|
||||
}
|
||||
|
||||
export async function addSeriesLocationSubElement(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_location_sub_element', {data});
|
||||
}
|
||||
|
||||
export async function deleteSeriesLocation(locationId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series_location', {data: {locationId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function deleteSeriesLocationElement(elementId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series_location_element', {data: {elementId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function deleteSeriesLocationSubElement(subElementId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series_location_sub_element', {data: {subElementId, deletedAt}});
|
||||
}
|
||||
|
||||
// ─── Series Worlds ─────────────────────────────────────────
|
||||
|
||||
export async function getSeriesWorldList(seriesId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_series_world_list', {data: {seriesId}});
|
||||
}
|
||||
|
||||
export async function addSeriesWorld(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_world', {data});
|
||||
}
|
||||
|
||||
export async function updateSeriesWorld(data: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('update_series_world', {data});
|
||||
}
|
||||
|
||||
export async function addSeriesWorldElement(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_world_element', {data});
|
||||
}
|
||||
|
||||
export async function deleteSeriesWorldElement(elementId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series_world_element', {data: {elementId, deletedAt}});
|
||||
}
|
||||
|
||||
// ─── Series Spells ─────────────────────────────────────────
|
||||
|
||||
export async function getSeriesSpellList(seriesId: string): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_series_spell_list', {data: {seriesId}});
|
||||
}
|
||||
|
||||
export async function getSeriesSpellDetail(spellId: string): Promise<unknown> {
|
||||
return invoke<unknown>('get_series_spell_detail', {data: {spellId}});
|
||||
}
|
||||
|
||||
export async function addSeriesSpell(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_spell', {data});
|
||||
}
|
||||
|
||||
export async function updateSeriesSpell(data: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('update_series_spell', {data});
|
||||
}
|
||||
|
||||
export async function deleteSeriesSpell(spellId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series_spell', {data: {spellId, deletedAt}});
|
||||
}
|
||||
|
||||
export async function addSeriesSpellTag(data: unknown): Promise<string> {
|
||||
return invoke<string>('add_series_spell_tag', {data});
|
||||
}
|
||||
|
||||
export async function updateSeriesSpellTag(data: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('update_series_spell_tag', {data});
|
||||
}
|
||||
|
||||
export async function deleteSeriesSpellTag(tagId: string, deletedAt: number): Promise<boolean> {
|
||||
return invoke<boolean>('delete_series_spell_tag', {data: {tagId, deletedAt}});
|
||||
}
|
||||
|
||||
// ─── Sync (books/series) ───────────────────────────────────
|
||||
|
||||
export async function getSyncedBooks(): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_synced_books');
|
||||
}
|
||||
|
||||
export async function getSyncedSeries(): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_synced_series');
|
||||
}
|
||||
|
||||
export async function uploadBookToServer(bookId: string): Promise<unknown> {
|
||||
return invoke<unknown>('upload_book_to_server', {bookId});
|
||||
}
|
||||
|
||||
export async function syncSaveBook(book: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('sync_save_book', {data: book});
|
||||
}
|
||||
|
||||
export async function syncBookToClient(book: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('sync_book_to_client', {data: book});
|
||||
}
|
||||
|
||||
export async function syncBookToServer(bookToSync: unknown): Promise<unknown> {
|
||||
return invoke<unknown>('sync_book_to_server', {data: bookToSync});
|
||||
}
|
||||
|
||||
export async function uploadSeriesToServer(seriesId: string): Promise<unknown> {
|
||||
return invoke<unknown>('upload_series_to_server', {seriesId});
|
||||
}
|
||||
|
||||
export async function syncSaveSeries(series: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('sync_save_series', {data: series});
|
||||
}
|
||||
|
||||
export async function syncSeriesToClient(series: unknown): Promise<boolean> {
|
||||
return invoke<boolean>('sync_series_to_client', {data: series});
|
||||
}
|
||||
|
||||
export async function syncSeriesToServer(seriesToSync: unknown): Promise<unknown> {
|
||||
return invoke<unknown>('sync_series_to_server', {data: seriesToSync});
|
||||
}
|
||||
|
||||
export async function seriesSyncUpload(data: {
|
||||
type: string;
|
||||
bookElementId: string;
|
||||
field: string;
|
||||
value: string;
|
||||
}): Promise<unknown> {
|
||||
return invoke<unknown>('series_sync_upload', {data});
|
||||
}
|
||||
|
||||
// ─── Tombstones ────────────────────────────────────────────
|
||||
|
||||
export async function getTombstonesSince(since: number): Promise<unknown[]> {
|
||||
return invoke<unknown[]>('get_tombstones_since', {since});
|
||||
}
|
||||
|
||||
export async function applyBookTombstones(tombstones: TombstoneRecord[]): Promise<void> {
|
||||
return invoke<void>('apply_book_tombstones', {tombstones});
|
||||
}
|
||||
|
||||
export async function applySeriesTombstones(tombstones: TombstoneRecord[]): Promise<void> {
|
||||
return invoke<void>('apply_series_tombstones', {tombstones});
|
||||
}
|
||||
|
||||
// ─── Window Management ──────────────────────────────────────
|
||||
|
||||
export async function openLoginWindow(): Promise<void> {
|
||||
const {WebviewWindow} = await import('@tauri-apps/api/webviewWindow');
|
||||
const {getCurrentWindow} = await import('@tauri-apps/api/window');
|
||||
|
||||
const existing = await WebviewWindow.getByLabel('login');
|
||||
if (existing) {
|
||||
await existing.setFocus();
|
||||
await getCurrentWindow().hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const loginWindow = new WebviewWindow('login', {
|
||||
url: '/login/login/',
|
||||
title: 'ERitors - Connexion',
|
||||
width: 500,
|
||||
height: 900,
|
||||
resizable: false,
|
||||
center: true,
|
||||
decorations: true,
|
||||
});
|
||||
|
||||
loginWindow.once('tauri://created', async function () {
|
||||
await getCurrentWindow().hide();
|
||||
});
|
||||
}
|
||||
|
||||
export async function loginSuccess(): Promise<void> {
|
||||
const {WebviewWindow} = await import('@tauri-apps/api/webviewWindow');
|
||||
const {getCurrentWindow} = await import('@tauri-apps/api/window');
|
||||
|
||||
const currentLabel = getCurrentWindow().label;
|
||||
|
||||
if (currentLabel === 'login') {
|
||||
const {emit} = await import('@tauri-apps/api/event');
|
||||
await emit('auth-success');
|
||||
const mainWindow = await WebviewWindow.getByLabel('main');
|
||||
if (mainWindow) {
|
||||
await mainWindow.show();
|
||||
await mainWindow.setFocus();
|
||||
}
|
||||
await getCurrentWindow().close();
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
export async function logout(): Promise<void> {
|
||||
await openLoginWindow();
|
||||
}
|
||||
|
||||
export async function openExternal(url: string): Promise<void> {
|
||||
const {open} = await import('@tauri-apps/plugin-shell');
|
||||
return open(url);
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
export interface EritBooksTable {
|
||||
book_id:string;
|
||||
type:string;
|
||||
author_id:string;
|
||||
title:string;
|
||||
hashed_title:string;
|
||||
sub_title:string|null;
|
||||
hashed_sub_title:string|null;
|
||||
summary:string|null;
|
||||
serie_id:number|null;
|
||||
desired_release_date:string|null;
|
||||
desired_word_count:number|null;
|
||||
words_count:number|null;
|
||||
cover_image:string|null;
|
||||
last_update:number;
|
||||
book_id: string;
|
||||
type: string;
|
||||
author_id: string;
|
||||
title: string;
|
||||
hashed_title: string;
|
||||
sub_title: string | null;
|
||||
hashed_sub_title: string | null;
|
||||
summary: string | null;
|
||||
serie_id: number | null;
|
||||
desired_release_date: string | null;
|
||||
desired_word_count: number | null;
|
||||
words_count: number | null;
|
||||
cover_image: string | null;
|
||||
last_update: number;
|
||||
}
|
||||
|
||||
export interface BookActSummariesTable {
|
||||
@@ -140,6 +140,26 @@ export interface BookLocationTable {
|
||||
last_update: number;
|
||||
}
|
||||
|
||||
export interface LocationElementTable {
|
||||
element_id: string;
|
||||
location: string;
|
||||
user_id: string;
|
||||
element_name: string;
|
||||
original_name: string;
|
||||
element_description: string | null;
|
||||
last_update: number;
|
||||
}
|
||||
|
||||
export interface LocationSubElementTable {
|
||||
sub_element_id: string;
|
||||
element_id: string;
|
||||
user_id: string;
|
||||
sub_elem_name: string;
|
||||
original_name: string;
|
||||
sub_elem_description: string | null;
|
||||
last_update: number;
|
||||
}
|
||||
|
||||
export interface BookPlotPointsTable {
|
||||
plot_point_id: string;
|
||||
title: string;
|
||||
@@ -176,29 +196,29 @@ export interface BookWorldElementsTable {
|
||||
last_update: number;
|
||||
}
|
||||
|
||||
export interface LocationElementTable {
|
||||
element_id: string;
|
||||
location: string;
|
||||
user_id: string;
|
||||
element_name: string;
|
||||
original_name: string;
|
||||
element_description: string | null;
|
||||
last_update: number;
|
||||
}
|
||||
|
||||
export interface LocationSubElementTable {
|
||||
sub_element_id: string;
|
||||
element_id: string;
|
||||
user_id: string;
|
||||
sub_elem_name: string;
|
||||
original_name: string;
|
||||
sub_elem_description: string | null;
|
||||
last_update: number;
|
||||
}
|
||||
|
||||
export interface GhostWriterSettingsTable {
|
||||
book_id: string;
|
||||
user_id: string;
|
||||
advanced_prompt: string | null;
|
||||
quillsense_enabled: number;
|
||||
}
|
||||
}
|
||||
|
||||
export interface CompleteBook {
|
||||
eritBooks: EritBooksTable[];
|
||||
actSummaries: BookActSummariesTable[];
|
||||
aiGuideLine: BookAIGuideLineTable[];
|
||||
chapters: BookChaptersTable[];
|
||||
chapterContents: BookChapterContentTable[];
|
||||
chapterInfos: BookChapterInfosTable[];
|
||||
characters: BookCharactersTable[];
|
||||
characterAttributes: BookCharactersAttributesTable[];
|
||||
guideLine: BookGuideLineTable[];
|
||||
incidents: BookIncidentsTable[];
|
||||
issues: BookIssuesTable[];
|
||||
locations: BookLocationTable[];
|
||||
plotPoints: BookPlotPointsTable[];
|
||||
worlds: BookWorldTable[];
|
||||
worldElements: BookWorldElementsTable[];
|
||||
locationElements: LocationElementTable[];
|
||||
locationSubElements: LocationSubElementTable[];
|
||||
}
|
||||
108
lib/types/book.ts
Normal file
108
lib/types/book.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {Author} from "@/lib/types/user";
|
||||
import {ActChapter, ChapterProps} from "@/lib/types/chapter";
|
||||
|
||||
export interface BookToolsSettings {
|
||||
characters: boolean;
|
||||
worlds: boolean;
|
||||
locations: boolean;
|
||||
spells: boolean;
|
||||
}
|
||||
|
||||
export interface BookProps {
|
||||
bookId: string;
|
||||
type: string;
|
||||
title: string;
|
||||
author?: Author;
|
||||
serie?: number;
|
||||
seriesId?: string | null;
|
||||
subTitle?: string;
|
||||
summary?: string;
|
||||
publicationDate?: string;
|
||||
desiredWordCount?: number;
|
||||
totalWordCount?: number;
|
||||
coverImage?: string;
|
||||
chapters?: ChapterProps[];
|
||||
quillsenseEnabled?: boolean;
|
||||
tools?: BookToolsSettings;
|
||||
localBook?: boolean;
|
||||
}
|
||||
|
||||
export interface BookListProps {
|
||||
id: string;
|
||||
type: string;
|
||||
authorId: string;
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
summary?: string;
|
||||
serieId?: number;
|
||||
desiredReleaseDate?: string;
|
||||
desiredWordCount?: number;
|
||||
wordCount?: number;
|
||||
coverImage?: string;
|
||||
bookMeta?: string;
|
||||
quillsenseEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface GuideLine {
|
||||
tone: string;
|
||||
atmosphere: string;
|
||||
writingStyle: string;
|
||||
themes: string;
|
||||
symbolism: string;
|
||||
motifs: string;
|
||||
narrativeVoice: string;
|
||||
pacing: string;
|
||||
intendedAudience: string;
|
||||
keyMessages: string;
|
||||
}
|
||||
|
||||
export interface GuideLineAI {
|
||||
narrativeType: number | null;
|
||||
dialogueType: number | null;
|
||||
globalResume: string | null;
|
||||
atmosphere: string | null;
|
||||
verbeTense: number | null;
|
||||
langue: number | null;
|
||||
currentResume: string | null;
|
||||
themes: string | null;
|
||||
}
|
||||
|
||||
export interface PlotPoint {
|
||||
plotPointId: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
linkedIncidentId: string;
|
||||
chapters?: ActChapter[];
|
||||
}
|
||||
|
||||
export interface Incident {
|
||||
incidentId: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
chapters?: ActChapter[];
|
||||
}
|
||||
|
||||
export interface Issue {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface Act {
|
||||
id: number;
|
||||
summary: string | null;
|
||||
incidents?: Incident[];
|
||||
plotPoints?: PlotPoint[];
|
||||
chapters?: ActChapter[];
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface BookTags {
|
||||
characters: Tag[];
|
||||
locations: Tag[];
|
||||
objects: Tag[];
|
||||
worldElements: Tag[];
|
||||
}
|
||||
80
lib/types/chapter.ts
Normal file
80
lib/types/chapter.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
export interface ActChapter {
|
||||
chapterInfoId: string;
|
||||
chapterId: string;
|
||||
title: string;
|
||||
chapterOrder: number;
|
||||
actId: number;
|
||||
incidentId?: string;
|
||||
plotPointId?: string;
|
||||
summary: string;
|
||||
goal: string;
|
||||
}
|
||||
|
||||
export interface ChapterListProps {
|
||||
chapterId: string;
|
||||
title: string;
|
||||
summary?: string;
|
||||
chapterOrder?: number;
|
||||
goal?: string;
|
||||
}
|
||||
|
||||
export interface ChapterProps {
|
||||
chapterId: string;
|
||||
chapterOrder: number;
|
||||
title: string;
|
||||
chapterContent: ChapterContent;
|
||||
}
|
||||
|
||||
export interface ChapterContent {
|
||||
version: number;
|
||||
content: string;
|
||||
wordsCount: number;
|
||||
}
|
||||
|
||||
export interface ChapterVersion {
|
||||
value: number;
|
||||
label: 'Invite' | 'Brouillon' | 'Perfectionnement' | 'Révision' | 'Finale';
|
||||
}
|
||||
|
||||
export interface TiptapLinkAttrs {
|
||||
href: string;
|
||||
target?: string;
|
||||
}
|
||||
|
||||
export type TiptapAttrValue = string | number | boolean | null | TiptapLinkAttrs;
|
||||
|
||||
export type TiptapNode = {
|
||||
type: string;
|
||||
content?: TiptapNode[];
|
||||
text?: string;
|
||||
attrs?: {
|
||||
[key: string]: TiptapAttrValue;
|
||||
};
|
||||
};
|
||||
|
||||
export interface CompanionContent {
|
||||
version: number;
|
||||
content: string;
|
||||
wordsCount: number;
|
||||
}
|
||||
|
||||
export type ExportFormat = 'epub' | 'pdf' | 'docx';
|
||||
|
||||
export interface ChapterExportInfo {
|
||||
chapterId: string;
|
||||
title: string;
|
||||
chapterOrder: number;
|
||||
availableVersions: number[];
|
||||
}
|
||||
|
||||
export interface ChapterExportSelection {
|
||||
chapterId: string;
|
||||
version: number;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export interface ExportRequestBody {
|
||||
bookId: string;
|
||||
format: ExportFormat;
|
||||
chapters: { chapterId: string; version: number }[];
|
||||
}
|
||||
105
lib/types/character.ts
Normal file
105
lib/types/character.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import {LucideIcon} from 'lucide-react';
|
||||
|
||||
export type CharacterCategory = 'main' | 'secondary' | 'recurring' | 'none';
|
||||
export type CharacterStatus = 'alive' | 'dead' | 'unknown';
|
||||
export type CharacterAttributeSection =
|
||||
'physical'
|
||||
| 'psychological'
|
||||
| 'relations'
|
||||
| 'skills'
|
||||
| 'weaknesses'
|
||||
| 'strengths'
|
||||
| 'goals'
|
||||
| 'motivations'
|
||||
| 'arc'
|
||||
| 'secrets'
|
||||
| 'fears'
|
||||
| 'flaws'
|
||||
| 'beliefs'
|
||||
| 'conflicts'
|
||||
| 'quotes'
|
||||
| 'distinguishingMarks'
|
||||
| 'items'
|
||||
| 'affiliations';
|
||||
|
||||
export function isCharacterCategory(value: string): value is CharacterCategory {
|
||||
return value === 'main' || value === 'secondary' || value === 'recurring' || value === 'none';
|
||||
}
|
||||
|
||||
export function isCharacterStatus(value: string): value is CharacterStatus {
|
||||
return value === 'alive' || value === 'dead' || value === 'unknown';
|
||||
}
|
||||
|
||||
export interface Relation {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
history: string;
|
||||
}
|
||||
|
||||
export interface Attribute {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CharacterAttribute {
|
||||
[key: string]: Array<Attribute>;
|
||||
}
|
||||
|
||||
export interface CharacterProps {
|
||||
id: string | null;
|
||||
name: string;
|
||||
lastName: string;
|
||||
nickname: string;
|
||||
age: number | null;
|
||||
gender: string;
|
||||
species: string;
|
||||
nationality: string;
|
||||
status: CharacterStatus;
|
||||
category: CharacterCategory;
|
||||
title: string;
|
||||
image: string;
|
||||
physical: Attribute[];
|
||||
psychological: Attribute[];
|
||||
relations: Attribute[];
|
||||
skills: Attribute[];
|
||||
weaknesses: Attribute[];
|
||||
strengths: Attribute[];
|
||||
goals: Attribute[];
|
||||
motivations: Attribute[];
|
||||
arc: Attribute[];
|
||||
secrets: Attribute[];
|
||||
fears: Attribute[];
|
||||
flaws: Attribute[];
|
||||
beliefs: Attribute[];
|
||||
conflicts: Attribute[];
|
||||
quotes: Attribute[];
|
||||
distinguishingMarks: Attribute[];
|
||||
items: Attribute[];
|
||||
affiliations: Attribute[];
|
||||
role: string;
|
||||
biography?: string;
|
||||
history?: string;
|
||||
speechPattern?: string;
|
||||
catchphrase?: string;
|
||||
residence?: string;
|
||||
notes?: string;
|
||||
color?: string;
|
||||
seriesCharacterId?: string | null;
|
||||
}
|
||||
|
||||
export interface CharacterListResponse {
|
||||
characters: CharacterProps[];
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface CharacterElement {
|
||||
title: string;
|
||||
section: CharacterAttributeSection;
|
||||
placeholder: string;
|
||||
icon: LucideIcon;
|
||||
}
|
||||
|
||||
export interface AttributeResponse {
|
||||
attributes: Attribute[];
|
||||
}
|
||||
10
lib/types/editor.ts
Normal file
10
lib/types/editor.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import {LucideIcon} from 'lucide-react';
|
||||
|
||||
export interface PanelComponent {
|
||||
id: number;
|
||||
title: string;
|
||||
badge: string;
|
||||
description: string;
|
||||
icon: LucideIcon;
|
||||
action?: () => void;
|
||||
}
|
||||
@@ -15,3 +15,13 @@ export interface ImportChapterSelection {
|
||||
wordCount: number;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export interface ImportConfirmBody {
|
||||
importId: string;
|
||||
title: string;
|
||||
subTitle: string;
|
||||
summary: string;
|
||||
type: string;
|
||||
version: number;
|
||||
selectedChapterIndexes: number[];
|
||||
}
|
||||
109
lib/types/quillsense.ts
Normal file
109
lib/types/quillsense.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
export type MessageType = "user" | "model";
|
||||
export type QSView = 'list' | 'chat' | 'ghostwritter' | 'dictionary' | 'synonyms' | 'conjugator' | 'inspiration';
|
||||
export type ConversationType = 'dictionary' | 'synonyms' | 'conjugator' | 'chatbot' | 'inspire';
|
||||
|
||||
export interface Message {
|
||||
id: number;
|
||||
type: MessageType;
|
||||
message: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: string;
|
||||
title?: string;
|
||||
date?: string;
|
||||
type?: ConversationType;
|
||||
messages: Message[];
|
||||
status: number;
|
||||
totalPrice?: number;
|
||||
useYourKey?: boolean;
|
||||
}
|
||||
|
||||
export interface AIGeneratedTextData {
|
||||
totalCost: number;
|
||||
response: string;
|
||||
}
|
||||
|
||||
export interface AIResponseWithCredits<T> {
|
||||
useYourKey: boolean;
|
||||
totalPrice: number;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface AIDictionary extends AIResponseWithCredits<DictionaryAIResponse> {
|
||||
}
|
||||
|
||||
export interface AIGeneratedText extends AIResponseWithCredits<AIGeneratedTextData> {
|
||||
}
|
||||
|
||||
export interface AIInspire extends AIResponseWithCredits<InspireAIResponse> {
|
||||
}
|
||||
|
||||
export interface AISynonyms extends AIResponseWithCredits<SynonymsAIResponse> {
|
||||
}
|
||||
|
||||
export interface ConjugationTenses {
|
||||
[tense: string]: {
|
||||
firstPersonSingular?: string;
|
||||
secondPersonSingular?: string;
|
||||
thirdPersonSingular?: string;
|
||||
firstPersonPlural?: string;
|
||||
secondPersonPlural?: string;
|
||||
thirdPersonPlural?: string;
|
||||
présent?: string;
|
||||
passé?: string;
|
||||
} | string;
|
||||
}
|
||||
|
||||
export interface ConjugationResponse {
|
||||
conjugations: {
|
||||
[mode: string]: ConjugationTenses;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AIVerbConjugation extends AIResponseWithCredits<ConjugationResponse> {
|
||||
}
|
||||
|
||||
export interface InspireAIResponse {
|
||||
ideas: {
|
||||
idea: string;
|
||||
reason: string;
|
||||
relatedTo: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface DictionaryAIResponse {
|
||||
word: string;
|
||||
definition: string;
|
||||
example: string;
|
||||
literaryUsage: string;
|
||||
}
|
||||
|
||||
export interface SynonymAI {
|
||||
word: string;
|
||||
context: string;
|
||||
}
|
||||
|
||||
export interface SynonymsAIResponse {
|
||||
words: SynonymAI[];
|
||||
}
|
||||
|
||||
export interface InspirationAIIdea {
|
||||
idea: string;
|
||||
reason: string;
|
||||
relatedTo: string;
|
||||
}
|
||||
|
||||
export interface ConversationProps {
|
||||
id: string;
|
||||
mode: string;
|
||||
title: string;
|
||||
startDate: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface QuillSenseSettingsProps {
|
||||
quillsenseEnabled: boolean;
|
||||
advancedPrompt: string | null;
|
||||
}
|
||||
@@ -37,7 +37,6 @@ export interface SeriesListItemProps {
|
||||
bookIds: string[];
|
||||
}
|
||||
|
||||
// Personnages de série
|
||||
export interface SeriesCharacterListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -80,7 +79,6 @@ export interface SeriesCharacterAttribute {
|
||||
|
||||
export type SeriesCharacterProps = SeriesCharacterDetailResponse;
|
||||
|
||||
// Mondes de série
|
||||
export interface SeriesWorldElementItem {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -118,7 +116,6 @@ export interface SeriesWorldElement {
|
||||
description: string;
|
||||
}
|
||||
|
||||
// Lieux de série
|
||||
export interface SeriesLocationSubElement {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -138,7 +135,6 @@ export interface SeriesLocationItem {
|
||||
elements: SeriesLocationElement[];
|
||||
}
|
||||
|
||||
// Sorts de série (Grimoire)
|
||||
export interface SeriesSpellTag {
|
||||
id: string;
|
||||
name: string;
|
||||
14
lib/types/session.ts
Normal file
14
lib/types/session.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {UserProps} from "@/lib/types/user";
|
||||
|
||||
export interface SessionProps {
|
||||
isConnected: boolean;
|
||||
accessToken: string;
|
||||
user: UserProps | null;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
valid: boolean;
|
||||
message?: string;
|
||||
token?: string;
|
||||
userid?: string;
|
||||
}
|
||||
5
lib/types/settings.ts
Normal file
5
lib/types/settings.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type ViewMode = 'list' | 'detail' | 'edit';
|
||||
|
||||
export interface SettingRef {
|
||||
handleSave: () => Promise<void>;
|
||||
}
|
||||
58
lib/types/spell.ts
Normal file
58
lib/types/spell.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export interface SpellTagProps {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string | null;
|
||||
}
|
||||
|
||||
export interface SpellProps {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string[];
|
||||
powerLevel: string | null;
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
notes: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
export interface SpellPropsPost {
|
||||
id?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string[];
|
||||
powerLevel?: string | null;
|
||||
components?: string | null;
|
||||
limitations?: string | null;
|
||||
notes?: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
export interface SpellListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
tags: SpellTagProps[];
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
|
||||
export interface SpellListResponse {
|
||||
enabled: boolean;
|
||||
spells: SpellListItem[];
|
||||
tags: SpellTagProps[];
|
||||
}
|
||||
|
||||
export interface SpellEditState {
|
||||
id: string | null;
|
||||
name: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string[];
|
||||
powerLevel: string | null;
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
notes: string | null;
|
||||
seriesSpellId?: string | null;
|
||||
}
|
||||
29
lib/types/story.ts
Normal file
29
lib/types/story.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {ChapterListProps} from "@/lib/types/chapter";
|
||||
import {Act, Issue} from "@/lib/types/book";
|
||||
|
||||
export interface StoryProps {
|
||||
mainChapter: ChapterListProps[];
|
||||
acts: Act[];
|
||||
issues: Issue[];
|
||||
}
|
||||
|
||||
export interface VerbalTimeProps {
|
||||
actions: string;
|
||||
descriptions: string;
|
||||
dialogues: string;
|
||||
thoughts: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface DialogueProps {
|
||||
description: string;
|
||||
example: string;
|
||||
}
|
||||
|
||||
export interface GeneratedShortStory {
|
||||
title: string;
|
||||
short: string;
|
||||
resume: string;
|
||||
totalPrice: number;
|
||||
totalTokens: number;
|
||||
}
|
||||
@@ -1,392 +1,338 @@
|
||||
export interface SyncedBookTools {
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedBook {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
subTitle: string | null;
|
||||
seriesId: string | null;
|
||||
lastUpdate: number;
|
||||
chapters: SyncedChapter[];
|
||||
characters: SyncedCharacter[];
|
||||
locations: SyncedLocation[];
|
||||
worlds: SyncedWorld[];
|
||||
incidents: SyncedIncident[];
|
||||
plotPoints: SyncedPlotPoint[];
|
||||
issues: SyncedIssue[];
|
||||
actSummaries: SyncedActSummary[];
|
||||
guideLine: SyncedGuideLine | null;
|
||||
aiGuideLine: SyncedAIGuideLine | null;
|
||||
bookTools: SyncedBookTools | null;
|
||||
spells: SyncedSpell[];
|
||||
spellTags: SyncedSpellTag[];
|
||||
}
|
||||
|
||||
export interface SyncedChapter {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
contents: SyncedChapterContent[];
|
||||
info: SyncedChapterInfo | null;
|
||||
}
|
||||
|
||||
export interface SyncedChapterContent {
|
||||
id: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedChapterInfo {
|
||||
id: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedCharacter {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
attributes: SyncedCharacterAttribute[];
|
||||
}
|
||||
|
||||
export interface SyncedCharacterAttribute {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedLocation {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
elements: SyncedLocationElement[];
|
||||
}
|
||||
|
||||
export interface SyncedLocationElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
subElements: SyncedLocationSubElement[];
|
||||
}
|
||||
|
||||
export interface SyncedLocationSubElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedWorld {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
elements: SyncedWorldElement[];
|
||||
}
|
||||
|
||||
export interface SyncedWorldElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedIncident {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedPlotPoint {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedIssue {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedActSummary {
|
||||
id: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedGuideLine {
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedAIGuideLine {
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSpell {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSpellTag {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface BookSyncCompare {
|
||||
id: string;
|
||||
chapters: string[];
|
||||
chapterContents: string[];
|
||||
chapterInfos: string[];
|
||||
characters: string[];
|
||||
characterAttributes: string[];
|
||||
locations: string[];
|
||||
locationElements: string[];
|
||||
locationSubElements: string[];
|
||||
worlds: string[];
|
||||
worldElements: string[];
|
||||
incidents: string[];
|
||||
plotPoints: string[];
|
||||
issues: string[];
|
||||
actSummaries: string[];
|
||||
guideLine: boolean;
|
||||
aiGuideLine: boolean;
|
||||
bookTools: boolean;
|
||||
spells: string[];
|
||||
spellTags: string[];
|
||||
}
|
||||
|
||||
export function compareBookSyncs(newerBook: SyncedBook, olderBook: SyncedBook): BookSyncCompare | null {
|
||||
const changedChapterIds: string[] = [];
|
||||
const changedChapterContentIds: string[] = [];
|
||||
const changedChapterInfoIds: string[] = [];
|
||||
const changedCharacterIds: string[] = [];
|
||||
const changedCharacterAttributeIds: string[] = [];
|
||||
const changedLocationIds: string[] = [];
|
||||
const changedLocationElementIds: string[] = [];
|
||||
const changedLocationSubElementIds: string[] = [];
|
||||
const changedWorldIds: string[] = [];
|
||||
const changedWorldElementIds: string[] = [];
|
||||
const changedIncidentIds: string[] = [];
|
||||
const changedPlotPointIds: string[] = [];
|
||||
const changedIssueIds: string[] = [];
|
||||
const changedActSummaryIds: string[] = [];
|
||||
const changedSpellIds: string[] = [];
|
||||
const changedSpellTagIds: string[] = [];
|
||||
let guideLineChanged: boolean = false;
|
||||
let aiGuideLineChanged: boolean = false;
|
||||
let bookToolsChanged: boolean = false;
|
||||
|
||||
newerBook.chapters.forEach((newerChapter: SyncedChapter): void => {
|
||||
const olderChapter: SyncedChapter | undefined = olderBook.chapters.find((chapter: SyncedChapter): boolean => chapter.id === newerChapter.id);
|
||||
|
||||
if (!olderChapter) {
|
||||
changedChapterIds.push(newerChapter.id);
|
||||
newerChapter.contents.forEach((content: SyncedChapterContent): void => {
|
||||
changedChapterContentIds.push(content.id);
|
||||
});
|
||||
if (newerChapter.info) {
|
||||
changedChapterInfoIds.push(newerChapter.info.id);
|
||||
}
|
||||
} else if (newerChapter.lastUpdate > olderChapter.lastUpdate) {
|
||||
changedChapterIds.push(newerChapter.id);
|
||||
} else {
|
||||
newerChapter.contents.forEach((newerContent: SyncedChapterContent): void => {
|
||||
const olderContent: SyncedChapterContent | undefined = olderChapter.contents.find((content: SyncedChapterContent): boolean => content.id === newerContent.id);
|
||||
if (!olderContent || newerContent.lastUpdate > olderContent.lastUpdate) {
|
||||
changedChapterContentIds.push(newerContent.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (newerChapter.info && olderChapter.info) {
|
||||
if (newerChapter.info.lastUpdate > olderChapter.info.lastUpdate) {
|
||||
changedChapterInfoIds.push(newerChapter.info.id);
|
||||
}
|
||||
} else if (newerChapter.info && !olderChapter.info) {
|
||||
changedChapterInfoIds.push(newerChapter.info.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.characters.forEach((newerCharacter: SyncedCharacter): void => {
|
||||
const olderCharacter: SyncedCharacter | undefined = olderBook.characters.find((character: SyncedCharacter): boolean => character.id === newerCharacter.id);
|
||||
|
||||
if (!olderCharacter) {
|
||||
changedCharacterIds.push(newerCharacter.id);
|
||||
newerCharacter.attributes.forEach((attribute: SyncedCharacterAttribute): void => {
|
||||
changedCharacterAttributeIds.push(attribute.id);
|
||||
});
|
||||
} else if (newerCharacter.lastUpdate > olderCharacter.lastUpdate) {
|
||||
changedCharacterIds.push(newerCharacter.id);
|
||||
} else {
|
||||
newerCharacter.attributes.forEach((newerAttribute: SyncedCharacterAttribute): void => {
|
||||
const olderAttribute: SyncedCharacterAttribute | undefined = olderCharacter.attributes.find((attribute: SyncedCharacterAttribute): boolean => attribute.id === newerAttribute.id);
|
||||
if (!olderAttribute || newerAttribute.lastUpdate > olderAttribute.lastUpdate) {
|
||||
changedCharacterAttributeIds.push(newerAttribute.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.locations.forEach((newerLocation: SyncedLocation): void => {
|
||||
const olderLocation: SyncedLocation | undefined = olderBook.locations.find((location: SyncedLocation): boolean => location.id === newerLocation.id);
|
||||
|
||||
if (!olderLocation) {
|
||||
changedLocationIds.push(newerLocation.id);
|
||||
newerLocation.elements.forEach((element: SyncedLocationElement): void => {
|
||||
changedLocationElementIds.push(element.id);
|
||||
element.subElements.forEach((subElement: SyncedLocationSubElement): void => {
|
||||
changedLocationSubElementIds.push(subElement.id);
|
||||
});
|
||||
});
|
||||
} else if (newerLocation.lastUpdate > olderLocation.lastUpdate) {
|
||||
changedLocationIds.push(newerLocation.id);
|
||||
} else {
|
||||
newerLocation.elements.forEach((newerElement: SyncedLocationElement): void => {
|
||||
const olderElement: SyncedLocationElement | undefined = olderLocation.elements.find((element: SyncedLocationElement): boolean => element.id === newerElement.id);
|
||||
|
||||
if (!olderElement) {
|
||||
changedLocationElementIds.push(newerElement.id);
|
||||
newerElement.subElements.forEach((subElement: SyncedLocationSubElement): void => {
|
||||
changedLocationSubElementIds.push(subElement.id);
|
||||
});
|
||||
} else if (newerElement.lastUpdate > olderElement.lastUpdate) {
|
||||
changedLocationElementIds.push(newerElement.id);
|
||||
} else {
|
||||
newerElement.subElements.forEach((newerSubElement: SyncedLocationSubElement): void => {
|
||||
const olderSubElement: SyncedLocationSubElement | undefined = olderElement.subElements.find((subElement: SyncedLocationSubElement): boolean => subElement.id === newerSubElement.id);
|
||||
if (!olderSubElement || newerSubElement.lastUpdate > olderSubElement.lastUpdate) {
|
||||
changedLocationSubElementIds.push(newerSubElement.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.worlds.forEach((newerWorld: SyncedWorld): void => {
|
||||
const olderWorld: SyncedWorld | undefined = olderBook.worlds.find((world: SyncedWorld): boolean => world.id === newerWorld.id);
|
||||
|
||||
if (!olderWorld) {
|
||||
changedWorldIds.push(newerWorld.id);
|
||||
newerWorld.elements.forEach((element: SyncedWorldElement): void => {
|
||||
changedWorldElementIds.push(element.id);
|
||||
});
|
||||
} else if (newerWorld.lastUpdate > olderWorld.lastUpdate) {
|
||||
changedWorldIds.push(newerWorld.id);
|
||||
} else {
|
||||
newerWorld.elements.forEach((newerElement: SyncedWorldElement): void => {
|
||||
const olderElement: SyncedWorldElement | undefined = olderWorld.elements.find((element: SyncedWorldElement): boolean => element.id === newerElement.id);
|
||||
if (!olderElement || newerElement.lastUpdate > olderElement.lastUpdate) {
|
||||
changedWorldElementIds.push(newerElement.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.incidents.forEach((newerIncident: SyncedIncident): void => {
|
||||
const olderIncident: SyncedIncident | undefined = olderBook.incidents.find((incident: SyncedIncident): boolean => incident.id === newerIncident.id);
|
||||
if (!olderIncident || newerIncident.lastUpdate > olderIncident.lastUpdate) {
|
||||
changedIncidentIds.push(newerIncident.id);
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.plotPoints.forEach((newerPlotPoint: SyncedPlotPoint): void => {
|
||||
const olderPlotPoint: SyncedPlotPoint | undefined = olderBook.plotPoints.find((plotPoint: SyncedPlotPoint): boolean => plotPoint.id === newerPlotPoint.id);
|
||||
if (!olderPlotPoint || newerPlotPoint.lastUpdate > olderPlotPoint.lastUpdate) {
|
||||
changedPlotPointIds.push(newerPlotPoint.id);
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.issues.forEach((newerIssue: SyncedIssue): void => {
|
||||
const olderIssue: SyncedIssue | undefined = olderBook.issues.find((issue: SyncedIssue): boolean => issue.id === newerIssue.id);
|
||||
if (!olderIssue || newerIssue.lastUpdate > olderIssue.lastUpdate) {
|
||||
changedIssueIds.push(newerIssue.id);
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.actSummaries.forEach((newerActSummary: SyncedActSummary): void => {
|
||||
const olderActSummary: SyncedActSummary | undefined = olderBook.actSummaries.find((actSummary: SyncedActSummary): boolean => actSummary.id === newerActSummary.id);
|
||||
if (!olderActSummary || newerActSummary.lastUpdate > olderActSummary.lastUpdate) {
|
||||
changedActSummaryIds.push(newerActSummary.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (newerBook.guideLine && olderBook.guideLine) {
|
||||
guideLineChanged = newerBook.guideLine.lastUpdate > olderBook.guideLine.lastUpdate;
|
||||
} else if (newerBook.guideLine && !olderBook.guideLine) {
|
||||
guideLineChanged = true;
|
||||
}
|
||||
|
||||
if (newerBook.aiGuideLine && olderBook.aiGuideLine) {
|
||||
aiGuideLineChanged = newerBook.aiGuideLine.lastUpdate > olderBook.aiGuideLine.lastUpdate;
|
||||
} else if (newerBook.aiGuideLine && !olderBook.aiGuideLine) {
|
||||
aiGuideLineChanged = true;
|
||||
}
|
||||
|
||||
if (newerBook.bookTools && olderBook.bookTools) {
|
||||
bookToolsChanged = newerBook.bookTools.lastUpdate > olderBook.bookTools.lastUpdate;
|
||||
} else if (newerBook.bookTools && !olderBook.bookTools) {
|
||||
bookToolsChanged = true;
|
||||
}
|
||||
|
||||
newerBook.spellTags.forEach((newerSpellTag: SyncedSpellTag): void => {
|
||||
const olderSpellTag: SyncedSpellTag | undefined = olderBook.spellTags.find((spellTag: SyncedSpellTag): boolean => spellTag.id === newerSpellTag.id);
|
||||
if (!olderSpellTag || newerSpellTag.lastUpdate > olderSpellTag.lastUpdate) {
|
||||
changedSpellTagIds.push(newerSpellTag.id);
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.spells.forEach((newerSpell: SyncedSpell): void => {
|
||||
const olderSpell: SyncedSpell | undefined = olderBook.spells.find((spell: SyncedSpell): boolean => spell.id === newerSpell.id);
|
||||
if (!olderSpell || newerSpell.lastUpdate > olderSpell.lastUpdate) {
|
||||
changedSpellIds.push(newerSpell.id);
|
||||
}
|
||||
});
|
||||
|
||||
const hasChanges: boolean =
|
||||
changedChapterIds.length > 0 ||
|
||||
changedChapterContentIds.length > 0 ||
|
||||
changedChapterInfoIds.length > 0 ||
|
||||
changedCharacterIds.length > 0 ||
|
||||
changedCharacterAttributeIds.length > 0 ||
|
||||
changedLocationIds.length > 0 ||
|
||||
changedLocationElementIds.length > 0 ||
|
||||
changedLocationSubElementIds.length > 0 ||
|
||||
changedWorldIds.length > 0 ||
|
||||
changedWorldElementIds.length > 0 ||
|
||||
changedIncidentIds.length > 0 ||
|
||||
changedPlotPointIds.length > 0 ||
|
||||
changedIssueIds.length > 0 ||
|
||||
changedActSummaryIds.length > 0 ||
|
||||
changedSpellIds.length > 0 ||
|
||||
changedSpellTagIds.length > 0 ||
|
||||
guideLineChanged ||
|
||||
aiGuideLineChanged ||
|
||||
bookToolsChanged;
|
||||
|
||||
if (!hasChanges) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: newerBook.id,
|
||||
chapters: changedChapterIds,
|
||||
chapterContents: changedChapterContentIds,
|
||||
chapterInfos: changedChapterInfoIds,
|
||||
characters: changedCharacterIds,
|
||||
characterAttributes: changedCharacterAttributeIds,
|
||||
locations: changedLocationIds,
|
||||
locationElements: changedLocationElementIds,
|
||||
locationSubElements: changedLocationSubElementIds,
|
||||
worlds: changedWorldIds,
|
||||
worldElements: changedWorldElementIds,
|
||||
incidents: changedIncidentIds,
|
||||
plotPoints: changedPlotPointIds,
|
||||
issues: changedIssueIds,
|
||||
actSummaries: changedActSummaryIds,
|
||||
guideLine: guideLineChanged,
|
||||
aiGuideLine: aiGuideLineChanged,
|
||||
bookTools: bookToolsChanged,
|
||||
spells: changedSpellIds,
|
||||
spellTags: changedSpellTagIds
|
||||
};
|
||||
}
|
||||
export interface SyncedBook {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
subTitle: string | null;
|
||||
seriesId: string | null;
|
||||
lastUpdate: number;
|
||||
chapters: SyncedChapter[];
|
||||
characters: SyncedCharacter[];
|
||||
locations: SyncedLocation[];
|
||||
worlds: SyncedWorld[];
|
||||
incidents: SyncedIncident[];
|
||||
plotPoints: SyncedPlotPoint[];
|
||||
issues: SyncedIssue[];
|
||||
actSummaries: SyncedActSummary[];
|
||||
guideLine: SyncedGuideLine | null;
|
||||
aiGuideLine: SyncedAIGuideLine | null;
|
||||
bookTools: SyncedBookTools | null;
|
||||
spells: SyncedSpell[];
|
||||
spellTags: SyncedSpellTag[];
|
||||
}
|
||||
|
||||
export interface SyncedChapter {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
contents: SyncedChapterContent[];
|
||||
info: SyncedChapterInfo | null;
|
||||
}
|
||||
|
||||
export interface SyncedChapterContent {
|
||||
id: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedChapterInfo {
|
||||
id: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedCharacter {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
attributes: SyncedCharacterAttribute[];
|
||||
}
|
||||
|
||||
export interface SyncedCharacterAttribute {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedLocation {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
elements: SyncedLocationElement[];
|
||||
}
|
||||
|
||||
export interface SyncedLocationElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
subElements: SyncedLocationSubElement[];
|
||||
}
|
||||
|
||||
export interface SyncedLocationSubElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedWorld {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
elements: SyncedWorldElement[];
|
||||
}
|
||||
|
||||
export interface SyncedWorldElement {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedIncident {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedPlotPoint {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedIssue {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedActSummary {
|
||||
id: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedGuideLine {
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedAIGuideLine {
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedBookTools {
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSpell {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface SyncedSpellTag {
|
||||
id: string;
|
||||
name: string;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface BookSyncCompare {
|
||||
id: string;
|
||||
chapters: string[];
|
||||
chapterContents: string[];
|
||||
chapterInfos: string[];
|
||||
characters: string[];
|
||||
characterAttributes: string[];
|
||||
locations: string[];
|
||||
locationElements: string[];
|
||||
locationSubElements: string[];
|
||||
worlds: string[];
|
||||
worldElements: string[];
|
||||
incidents: string[];
|
||||
plotPoints: string[];
|
||||
issues: string[];
|
||||
actSummaries: string[];
|
||||
guideLine: boolean;
|
||||
aiGuideLine: boolean;
|
||||
bookTools: boolean;
|
||||
spells: string[];
|
||||
spellTags: string[];
|
||||
}
|
||||
|
||||
export function compareBookSyncs(newerBook: SyncedBook, olderBook: SyncedBook): BookSyncCompare | null {
|
||||
const changedChapterIds: string[] = [];
|
||||
const changedChapterContentIds: string[] = [];
|
||||
const changedChapterInfoIds: string[] = [];
|
||||
const changedCharacterIds: string[] = [];
|
||||
const changedCharacterAttributeIds: string[] = [];
|
||||
const changedLocationIds: string[] = [];
|
||||
const changedLocationElementIds: string[] = [];
|
||||
const changedLocationSubElementIds: string[] = [];
|
||||
const changedWorldIds: string[] = [];
|
||||
const changedWorldElementIds: string[] = [];
|
||||
const changedIncidentIds: string[] = [];
|
||||
const changedPlotPointIds: string[] = [];
|
||||
const changedIssueIds: string[] = [];
|
||||
const changedActSummaryIds: string[] = [];
|
||||
const changedSpellIds: string[] = [];
|
||||
const changedSpellTagIds: string[] = [];
|
||||
let guideLineChanged: boolean = false;
|
||||
let aiGuideLineChanged: boolean = false;
|
||||
let bookToolsChanged: boolean = false;
|
||||
|
||||
newerBook.chapters.forEach((newerChapter: SyncedChapter): void => {
|
||||
const olderChapter: SyncedChapter | undefined = olderBook.chapters.find((chapter: SyncedChapter): boolean => chapter.id === newerChapter.id);
|
||||
if (!olderChapter) {
|
||||
changedChapterIds.push(newerChapter.id);
|
||||
newerChapter.contents.forEach((content: SyncedChapterContent): void => { changedChapterContentIds.push(content.id); });
|
||||
if (newerChapter.info) { changedChapterInfoIds.push(newerChapter.info.id); }
|
||||
} else if (newerChapter.lastUpdate > olderChapter.lastUpdate) {
|
||||
changedChapterIds.push(newerChapter.id);
|
||||
} else {
|
||||
newerChapter.contents.forEach((newerContent: SyncedChapterContent): void => {
|
||||
const olderContent: SyncedChapterContent | undefined = olderChapter.contents.find((content: SyncedChapterContent): boolean => content.id === newerContent.id);
|
||||
if (!olderContent || newerContent.lastUpdate > olderContent.lastUpdate) { changedChapterContentIds.push(newerContent.id); }
|
||||
});
|
||||
if (newerChapter.info && olderChapter.info) {
|
||||
if (newerChapter.info.lastUpdate > olderChapter.info.lastUpdate) { changedChapterInfoIds.push(newerChapter.info.id); }
|
||||
} else if (newerChapter.info && !olderChapter.info) {
|
||||
changedChapterInfoIds.push(newerChapter.info.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.characters.forEach((newerCharacter: SyncedCharacter): void => {
|
||||
const olderCharacter: SyncedCharacter | undefined = olderBook.characters.find((character: SyncedCharacter): boolean => character.id === newerCharacter.id);
|
||||
if (!olderCharacter) {
|
||||
changedCharacterIds.push(newerCharacter.id);
|
||||
newerCharacter.attributes.forEach((attribute: SyncedCharacterAttribute): void => { changedCharacterAttributeIds.push(attribute.id); });
|
||||
} else if (newerCharacter.lastUpdate > olderCharacter.lastUpdate) {
|
||||
changedCharacterIds.push(newerCharacter.id);
|
||||
} else {
|
||||
newerCharacter.attributes.forEach((newerAttribute: SyncedCharacterAttribute): void => {
|
||||
const olderAttribute: SyncedCharacterAttribute | undefined = olderCharacter.attributes.find((attribute: SyncedCharacterAttribute): boolean => attribute.id === newerAttribute.id);
|
||||
if (!olderAttribute || newerAttribute.lastUpdate > olderAttribute.lastUpdate) { changedCharacterAttributeIds.push(newerAttribute.id); }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.locations.forEach((newerLocation: SyncedLocation): void => {
|
||||
const olderLocation: SyncedLocation | undefined = olderBook.locations.find((location: SyncedLocation): boolean => location.id === newerLocation.id);
|
||||
if (!olderLocation) {
|
||||
changedLocationIds.push(newerLocation.id);
|
||||
newerLocation.elements.forEach((element: SyncedLocationElement): void => {
|
||||
changedLocationElementIds.push(element.id);
|
||||
element.subElements.forEach((subElement: SyncedLocationSubElement): void => { changedLocationSubElementIds.push(subElement.id); });
|
||||
});
|
||||
} else if (newerLocation.lastUpdate > olderLocation.lastUpdate) {
|
||||
changedLocationIds.push(newerLocation.id);
|
||||
} else {
|
||||
newerLocation.elements.forEach((newerElement: SyncedLocationElement): void => {
|
||||
const olderElement: SyncedLocationElement | undefined = olderLocation.elements.find((element: SyncedLocationElement): boolean => element.id === newerElement.id);
|
||||
if (!olderElement) {
|
||||
changedLocationElementIds.push(newerElement.id);
|
||||
newerElement.subElements.forEach((subElement: SyncedLocationSubElement): void => { changedLocationSubElementIds.push(subElement.id); });
|
||||
} else if (newerElement.lastUpdate > olderElement.lastUpdate) {
|
||||
changedLocationElementIds.push(newerElement.id);
|
||||
} else {
|
||||
newerElement.subElements.forEach((newerSubElement: SyncedLocationSubElement): void => {
|
||||
const olderSubElement: SyncedLocationSubElement | undefined = olderElement.subElements.find((subElement: SyncedLocationSubElement): boolean => subElement.id === newerSubElement.id);
|
||||
if (!olderSubElement || newerSubElement.lastUpdate > olderSubElement.lastUpdate) { changedLocationSubElementIds.push(newerSubElement.id); }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.worlds.forEach((newerWorld: SyncedWorld): void => {
|
||||
const olderWorld: SyncedWorld | undefined = olderBook.worlds.find((world: SyncedWorld): boolean => world.id === newerWorld.id);
|
||||
if (!olderWorld) {
|
||||
changedWorldIds.push(newerWorld.id);
|
||||
newerWorld.elements.forEach((element: SyncedWorldElement): void => { changedWorldElementIds.push(element.id); });
|
||||
} else if (newerWorld.lastUpdate > olderWorld.lastUpdate) {
|
||||
changedWorldIds.push(newerWorld.id);
|
||||
} else {
|
||||
newerWorld.elements.forEach((newerElement: SyncedWorldElement): void => {
|
||||
const olderElement: SyncedWorldElement | undefined = olderWorld.elements.find((element: SyncedWorldElement): boolean => element.id === newerElement.id);
|
||||
if (!olderElement || newerElement.lastUpdate > olderElement.lastUpdate) { changedWorldElementIds.push(newerElement.id); }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
newerBook.incidents.forEach((newerIncident: SyncedIncident): void => {
|
||||
const olderIncident: SyncedIncident | undefined = olderBook.incidents.find((incident: SyncedIncident): boolean => incident.id === newerIncident.id);
|
||||
if (!olderIncident || newerIncident.lastUpdate > olderIncident.lastUpdate) { changedIncidentIds.push(newerIncident.id); }
|
||||
});
|
||||
|
||||
newerBook.plotPoints.forEach((newerPlotPoint: SyncedPlotPoint): void => {
|
||||
const olderPlotPoint: SyncedPlotPoint | undefined = olderBook.plotPoints.find((plotPoint: SyncedPlotPoint): boolean => plotPoint.id === newerPlotPoint.id);
|
||||
if (!olderPlotPoint || newerPlotPoint.lastUpdate > olderPlotPoint.lastUpdate) { changedPlotPointIds.push(newerPlotPoint.id); }
|
||||
});
|
||||
|
||||
newerBook.issues.forEach((newerIssue: SyncedIssue): void => {
|
||||
const olderIssue: SyncedIssue | undefined = olderBook.issues.find((issue: SyncedIssue): boolean => issue.id === newerIssue.id);
|
||||
if (!olderIssue || newerIssue.lastUpdate > olderIssue.lastUpdate) { changedIssueIds.push(newerIssue.id); }
|
||||
});
|
||||
|
||||
newerBook.actSummaries.forEach((newerActSummary: SyncedActSummary): void => {
|
||||
const olderActSummary: SyncedActSummary | undefined = olderBook.actSummaries.find((actSummary: SyncedActSummary): boolean => actSummary.id === newerActSummary.id);
|
||||
if (!olderActSummary || newerActSummary.lastUpdate > olderActSummary.lastUpdate) { changedActSummaryIds.push(newerActSummary.id); }
|
||||
});
|
||||
|
||||
if (newerBook.guideLine && olderBook.guideLine) {
|
||||
guideLineChanged = newerBook.guideLine.lastUpdate > olderBook.guideLine.lastUpdate;
|
||||
} else if (newerBook.guideLine && !olderBook.guideLine) {
|
||||
guideLineChanged = true;
|
||||
}
|
||||
|
||||
if (newerBook.aiGuideLine && olderBook.aiGuideLine) {
|
||||
aiGuideLineChanged = newerBook.aiGuideLine.lastUpdate > olderBook.aiGuideLine.lastUpdate;
|
||||
} else if (newerBook.aiGuideLine && !olderBook.aiGuideLine) {
|
||||
aiGuideLineChanged = true;
|
||||
}
|
||||
|
||||
if (newerBook.bookTools && olderBook.bookTools) {
|
||||
bookToolsChanged = newerBook.bookTools.lastUpdate > olderBook.bookTools.lastUpdate;
|
||||
} else if (newerBook.bookTools && !olderBook.bookTools) {
|
||||
bookToolsChanged = true;
|
||||
}
|
||||
|
||||
newerBook.spellTags.forEach((newerSpellTag: SyncedSpellTag): void => {
|
||||
const olderSpellTag: SyncedSpellTag | undefined = olderBook.spellTags.find((spellTag: SyncedSpellTag): boolean => spellTag.id === newerSpellTag.id);
|
||||
if (!olderSpellTag || newerSpellTag.lastUpdate > olderSpellTag.lastUpdate) { changedSpellTagIds.push(newerSpellTag.id); }
|
||||
});
|
||||
|
||||
newerBook.spells.forEach((newerSpell: SyncedSpell): void => {
|
||||
const olderSpell: SyncedSpell | undefined = olderBook.spells.find((spell: SyncedSpell): boolean => spell.id === newerSpell.id);
|
||||
if (!olderSpell || newerSpell.lastUpdate > olderSpell.lastUpdate) { changedSpellIds.push(newerSpell.id); }
|
||||
});
|
||||
|
||||
const hasChanges: boolean =
|
||||
changedChapterIds.length > 0 || changedChapterContentIds.length > 0 || changedChapterInfoIds.length > 0 ||
|
||||
changedCharacterIds.length > 0 || changedCharacterAttributeIds.length > 0 ||
|
||||
changedLocationIds.length > 0 || changedLocationElementIds.length > 0 || changedLocationSubElementIds.length > 0 ||
|
||||
changedWorldIds.length > 0 || changedWorldElementIds.length > 0 ||
|
||||
changedIncidentIds.length > 0 || changedPlotPointIds.length > 0 || changedIssueIds.length > 0 || changedActSummaryIds.length > 0 ||
|
||||
changedSpellIds.length > 0 || changedSpellTagIds.length > 0 ||
|
||||
guideLineChanged || aiGuideLineChanged || bookToolsChanged;
|
||||
|
||||
if (!hasChanges) { return null; }
|
||||
|
||||
return {
|
||||
id: newerBook.id,
|
||||
chapters: changedChapterIds,
|
||||
chapterContents: changedChapterContentIds,
|
||||
chapterInfos: changedChapterInfoIds,
|
||||
characters: changedCharacterIds,
|
||||
characterAttributes: changedCharacterAttributeIds,
|
||||
locations: changedLocationIds,
|
||||
locationElements: changedLocationElementIds,
|
||||
locationSubElements: changedLocationSubElementIds,
|
||||
worlds: changedWorldIds,
|
||||
worldElements: changedWorldElementIds,
|
||||
incidents: changedIncidentIds,
|
||||
plotPoints: changedPlotPointIds,
|
||||
issues: changedIssueIds,
|
||||
actSummaries: changedActSummaryIds,
|
||||
guideLine: guideLineChanged,
|
||||
aiGuideLine: aiGuideLineChanged,
|
||||
bookTools: bookToolsChanged,
|
||||
spells: changedSpellIds,
|
||||
spellTags: changedSpellTagIds
|
||||
};
|
||||
}
|
||||
36
lib/types/user.ts
Normal file
36
lib/types/user.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export interface Author {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UserProps {
|
||||
id: string;
|
||||
username: string;
|
||||
authorName?: string;
|
||||
email?: string;
|
||||
accountVerified?: boolean;
|
||||
termsAccepted?: boolean;
|
||||
aiUsage: number;
|
||||
apiKeys: {
|
||||
gemini: boolean;
|
||||
openai: boolean;
|
||||
anthropic: boolean;
|
||||
};
|
||||
guideTour?: GuideTour[];
|
||||
subscription?: Subscription[];
|
||||
writingLang: number | null;
|
||||
writingLevel: number | null;
|
||||
ritePoints: number;
|
||||
creditsBalance: number;
|
||||
groupId: number;
|
||||
}
|
||||
|
||||
export interface GuideTour {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
export interface Subscription {
|
||||
subType: string;
|
||||
subTier: number;
|
||||
status: boolean;
|
||||
}
|
||||
57
lib/types/world.ts
Normal file
57
lib/types/world.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import {LucideIcon} from 'lucide-react';
|
||||
|
||||
export interface ElementSection {
|
||||
title: string;
|
||||
section: WorldElementSection;
|
||||
icon: LucideIcon;
|
||||
}
|
||||
|
||||
export interface WorldElement {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export type WorldElementSection =
|
||||
'laws'
|
||||
| 'biomes'
|
||||
| 'issues'
|
||||
| 'customs'
|
||||
| 'kingdoms'
|
||||
| 'climate'
|
||||
| 'resources'
|
||||
| 'wildlife'
|
||||
| 'arts'
|
||||
| 'ethnicGroups'
|
||||
| 'socialClasses'
|
||||
| 'importantCharacters';
|
||||
|
||||
export type WorldTextField = 'name' | 'history' | 'politics' | 'economy' | 'religion' | 'languages';
|
||||
|
||||
export interface WorldProps {
|
||||
id: string;
|
||||
name: string;
|
||||
history: string;
|
||||
politics: string;
|
||||
economy: string;
|
||||
religion: string;
|
||||
languages: string;
|
||||
laws: WorldElement[];
|
||||
biomes: WorldElement[];
|
||||
issues: WorldElement[];
|
||||
customs: WorldElement[];
|
||||
kingdoms: WorldElement[];
|
||||
climate: WorldElement[];
|
||||
resources: WorldElement[];
|
||||
wildlife: WorldElement[];
|
||||
arts: WorldElement[];
|
||||
ethnicGroups: WorldElement[];
|
||||
socialClasses: WorldElement[];
|
||||
importantCharacters: WorldElement[];
|
||||
seriesWorldId?: string | null;
|
||||
}
|
||||
|
||||
export interface WorldListResponse {
|
||||
worlds: WorldProps[];
|
||||
enabled: boolean;
|
||||
}
|
||||
28
lib/utils/book.ts
Normal file
28
lib/utils/book.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {SelectBoxProps} from "@/components/form/SelectBox";
|
||||
import {SyncedBook} from "@/lib/types/synced-book";
|
||||
|
||||
export function booksToSelectBox(books: SyncedBook[]): SelectBoxProps[] {
|
||||
return books.map((book: SyncedBook): SelectBoxProps => {
|
||||
return {
|
||||
label: book.title,
|
||||
value: book.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function getBookTypeLabel(value: string): string {
|
||||
switch (value) {
|
||||
case 'short':
|
||||
return 'bookTypes.short';
|
||||
case 'novelette':
|
||||
return 'bookTypes.novelette';
|
||||
case 'long':
|
||||
return 'bookTypes.novella';
|
||||
case 'chapbook':
|
||||
return 'bookTypes.chapbook';
|
||||
case 'novel':
|
||||
return 'bookTypes.novel';
|
||||
default:
|
||||
return 'bookTypes.novel';
|
||||
}
|
||||
}
|
||||
33
lib/utils/cookies.ts
Normal file
33
lib/utils/cookies.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export function getCookie(name: string): string | null {
|
||||
const nameEQ: string = `${name}=`;
|
||||
const allCookies: string[] = document.cookie.split(';');
|
||||
for (let i: number = 0; i < allCookies.length; i++) {
|
||||
let cookie: string = allCookies[i];
|
||||
while (cookie.charAt(0) === ' ') cookie = cookie.substring(1, cookie.length);
|
||||
if (cookie.indexOf(nameEQ) === 0) return cookie.substring(nameEQ.length, cookie.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function setCookie(name: string, value: string, days: number): void {
|
||||
const date: Date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires: string = `expires=${date.toUTCString()}`;
|
||||
let domain: string = '';
|
||||
if (!/localhost|127\.0\.0\.1/.test(window.location.hostname)) {
|
||||
domain = `domain=${window.location.hostname};`;
|
||||
}
|
||||
const secure: string = 'Secure;';
|
||||
const sameSite: string = 'SameSite=Strict;';
|
||||
document.cookie = `${name}=${value}; ${expires}; ${domain} path=/; ${secure} ${sameSite}`;
|
||||
}
|
||||
|
||||
export function removeCookie(name: string): void {
|
||||
let domain: string = '';
|
||||
if (!/localhost|127\.0\.0\.1/.test(window.location.hostname)) {
|
||||
domain = `domain=${window.location.hostname};`;
|
||||
}
|
||||
const secure: string = 'Secure;';
|
||||
const sameSite: string = 'SameSite=Strict;';
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; ${domain} path=/; ${secure} ${sameSite}`;
|
||||
}
|
||||
75
lib/utils/dynamicStyles.ts
Normal file
75
lib/utils/dynamicStyles.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
const styleMap: Map<string, string> = new Map();
|
||||
let sheet: CSSStyleSheet | null = null;
|
||||
|
||||
function getSheet(): CSSStyleSheet {
|
||||
if (!sheet) {
|
||||
sheet = new CSSStyleSheet();
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
|
||||
function sanitizeForClassName(value: string): string {
|
||||
return value.replace(/[^a-zA-Z0-9]/g, '');
|
||||
}
|
||||
|
||||
export function dynamicBg(color: string): string {
|
||||
const key: string = `bg-${color}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-bg-${sanitizeForClassName(color)}`;
|
||||
getSheet().insertRule(`.${className} { background-color: ${color}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicText(color: string): string {
|
||||
const key: string = `text-${color}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-text-${sanitizeForClassName(color)}`;
|
||||
getSheet().insertRule(`.${className} { color: ${color}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicBorder(color: string, side: string = ''): string {
|
||||
const prop: string = side ? `border-${side}-color` : 'border-color';
|
||||
const key: string = `border-${side}-${color}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-border-${side ? side + '-' : ''}${sanitizeForClassName(color)}`;
|
||||
getSheet().insertRule(`.${className} { ${prop}: ${color}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicBgWithOpacity(hexColor: string, opacityHex: string): string {
|
||||
const key: string = `bg-${hexColor}-${opacityHex}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-bg-${sanitizeForClassName(hexColor)}-${opacityHex}`;
|
||||
getSheet().insertRule(`.${className} { background-color: ${hexColor}${opacityHex}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicBorderWithOpacity(hexColor: string, opacityHex: string): string {
|
||||
const key: string = `border-${hexColor}-${opacityHex}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-border-${sanitizeForClassName(hexColor)}-${opacityHex}`;
|
||||
getSheet().insertRule(`.${className} { border-color: ${hexColor}${opacityHex}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
|
||||
export function dynamicBorderLeft(color: string, width: string = '3px'): string {
|
||||
const key: string = `bl-${width}-${color}`;
|
||||
if (styleMap.has(key)) return styleMap.get(key)!;
|
||||
|
||||
const className: string = `dyn-bl-${sanitizeForClassName(color)}`;
|
||||
getSheet().insertRule(`.${className} { border-left: ${width} solid ${color}; }`, getSheet().cssRules.length);
|
||||
styleMap.set(key, className);
|
||||
return className;
|
||||
}
|
||||
6
lib/utils/editor.ts
Normal file
6
lib/utils/editor.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function convertToHtml(text: string): string {
|
||||
return text
|
||||
.split(/\n\s*\n/)
|
||||
.map((paragraph: string): string => `<p>${paragraph.trim()}</p>`)
|
||||
.join('');
|
||||
}
|
||||
27
lib/utils/html.ts
Normal file
27
lib/utils/html.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export function formatHTMLContent(htmlContent: string): string {
|
||||
return htmlContent
|
||||
.replace(/<h1>/g, '<h1 style="color: var(--color-text-primary); text-indent: 5px; font-size: 28px; font-weight: bold; text-align: left; margin-vertical: 10px;">')
|
||||
.replace(/<p>/g, '<p style="color: var(--color-editor-text); text-indent: 30px; font-size: 16px; line-height: 22px; margin-vertical: 5px;">')
|
||||
.replace(/<blockquote>/g, '<blockquote style="border-left-width: 4px; border-left-color: var(--color-gray-light); padding-left: 10px; font-style: italic; color: var(--color-text-dimmed);">');
|
||||
}
|
||||
|
||||
export function textContentToHtml(content: string): string {
|
||||
const paragraphs: string[] = content
|
||||
.split(/\n+/)
|
||||
.map((paragraph: string) => paragraph.trim())
|
||||
.filter((paragraph: string) => paragraph.length > 0);
|
||||
|
||||
return paragraphs
|
||||
.map((paragraph: string) => `<p>${paragraph}</p>`)
|
||||
.join('');
|
||||
}
|
||||
|
||||
export function htmlToText(html: string): string {
|
||||
return html
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<\/?(p|h[1-6]|div)(\s+[^>]*)?>/gi, '\n')
|
||||
.replace(/<\/?[^>]+(>|$)/g, '')
|
||||
.replace(/(\n\s*){2,}/g, '\n\n')
|
||||
.replace(/^\s+|\s+$|(?<=\s)\s+/g, '')
|
||||
.trim();
|
||||
}
|
||||
41
lib/utils/quillsense.ts
Normal file
41
lib/utils/quillsense.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {SessionProps} from "@/lib/types/session";
|
||||
import {Subscription} from "@/lib/types/user";
|
||||
import {getCurrentSubscription} from "@/lib/utils/user";
|
||||
|
||||
export function getSubLevel(session: SessionProps): number {
|
||||
let currentSub: Subscription | null = getCurrentSubscription(session?.user, 'quill-sense');
|
||||
if (!currentSub) {
|
||||
currentSub = getCurrentSubscription(session?.user, 'quill-trial');
|
||||
if (!currentSub) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
switch (currentSub?.subTier) {
|
||||
case 1:
|
||||
return 1;
|
||||
case 2:
|
||||
return 2;
|
||||
case 3:
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function isBringYourKeys(session: SessionProps): boolean {
|
||||
if (!session?.user) return false;
|
||||
const currentSub: Subscription | null = getCurrentSubscription(session?.user, 'use-your-keys');
|
||||
return currentSub?.status || session.user.groupId <= 4;
|
||||
}
|
||||
|
||||
export function isGeminiEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.gemini || false;
|
||||
}
|
||||
|
||||
export function isAnthropicEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.anthropic || false;
|
||||
}
|
||||
|
||||
export function isOpenAIEnabled(session: SessionProps): boolean {
|
||||
return session.user?.apiKeys.openai || false;
|
||||
}
|
||||
430
lib/utils/story.ts
Normal file
430
lib/utils/story.ts
Normal file
@@ -0,0 +1,430 @@
|
||||
import {Dispatch, SetStateAction} from "react";
|
||||
import {VerbalTimeProps} from "@/lib/types/story";
|
||||
|
||||
export function getVerbesStyle(verbalTimeValue: number, level: number): VerbalTimeProps {
|
||||
switch (verbalTimeValue) {
|
||||
case 1:
|
||||
return {
|
||||
actions: level === 1 ? 'Passé composé' : 'Passé simple',
|
||||
descriptions: 'Imparfait',
|
||||
dialogues: 'Passé composé',
|
||||
thoughts: level === 3 ? 'Subjonctif imparfait' : 'Plus-que-parfait',
|
||||
summary: '→ Narrations épurées, style classique',
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
actions: 'Passé composé',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Imparfait + infinitifs' : 'Conditionnel présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Présent + impératif' : 'Impératif',
|
||||
thoughts: level === 1 ? 'Futur proche' : level === 2 ? 'Conditionnel présent' : 'Subjonctif présent',
|
||||
summary: '→ Témoignages, récits autobiographiques',
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
actions: level === 1 ? 'Plus-que-parfait' : 'Passé antérieur',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Plus-que-parfait' : 'Conditionnel passé',
|
||||
dialogues: level === 3 ? 'Passé antérieur' : 'Passé simple',
|
||||
thoughts: level === 1 ? 'Plus-que-parfait' : 'Subjonctif imparfait',
|
||||
summary: '→ Flashbacks littéraires, tragédies',
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
actions: level === 1 ? 'Présent simple' : level === 2 ? 'Présent' : 'Présent + participe présent',
|
||||
descriptions: level === 1 ? 'Participe présent' : level === 2 ? 'Participe présent + infinitifs' : 'Participes présents enchaînés',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Impératif' : 'Impératif + infinitifs',
|
||||
thoughts: level === 1 ? 'Futur proche' : level === 2 ? 'Futur simple' : 'Futur antérieur',
|
||||
summary: '→ Urgence, immersion totale',
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
actions: 'Présent',
|
||||
descriptions: level === 1 ? 'Gérondif' : level === 2 ? 'Gérondif + infinitifs' : 'Gérondif + conditionnel',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Conditionnel présent' : 'Infinitif',
|
||||
thoughts: level === 1 ? 'Infinitif' : 'Infinitif passé',
|
||||
summary: '→ Méditations philosophiques',
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
actions: level === 1 ? 'Futur simple' : 'Futur antérieur',
|
||||
descriptions: level === 1 ? 'Futur proche' : level === 2 ? 'Futur antérieur' : 'Futur antérieur',
|
||||
dialogues: level === 1 ? 'Futur simple' : 'Futur proche',
|
||||
thoughts: level === 1 ? 'Futur proche' : 'Futur antérieur',
|
||||
summary: '→ Prophéties, plans stratégiques',
|
||||
};
|
||||
case 7:
|
||||
return {
|
||||
actions: level === 1 ? 'Futur simple' : 'Futur antérieur',
|
||||
descriptions: level === 1 ? 'Futur proche' : level === 2 ? 'Futur simple + conditionnel' : 'Conditionnel passé',
|
||||
dialogues: level === 1 ? 'Futur proche' : level === 2 ? 'Futur antérieur' : 'Futur simple',
|
||||
thoughts: level === 1 ? 'Futur simple' : level === 2 ? 'Conditionnel passé' : 'Futur antérieur',
|
||||
summary: '→ Dystopies, récits post-apocalyptiques',
|
||||
};
|
||||
case 8:
|
||||
return {
|
||||
actions: 'Imparfait',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Conditionnel présent' : 'Conditionnel passé',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Infinitif' : 'Infinitifs',
|
||||
thoughts: level === 1 ? 'Subjonctif présent' : level === 2 ? 'Subjonctif imparfait' : 'Subjonctif imparfait',
|
||||
summary: '→ Rêves, souvenirs déformés',
|
||||
};
|
||||
case 9:
|
||||
return {
|
||||
actions: 'Conditionnel présent',
|
||||
descriptions: 'Conditionnel passé',
|
||||
dialogues: 'Subjonctif imparfait',
|
||||
thoughts: level === 3 ? 'Subjonctif imparfait' : 'Plus-que-parfait',
|
||||
summary: '→ Uchronies, réalités alternatives',
|
||||
};
|
||||
case 10:
|
||||
return {
|
||||
actions: level === 1 ? 'Subjonctif présent' : 'Subjonctif imparfait',
|
||||
descriptions: level === 1 ? 'Subjonctif présent' : 'Subjonctif imparfait',
|
||||
dialogues: 'Impératif',
|
||||
thoughts: level === 3 ? 'Subjonctif imparfait' : 'Conditionnel passé',
|
||||
summary: '→ Drames psychologiques, dilemmes',
|
||||
};
|
||||
case 11:
|
||||
return {
|
||||
actions: 'Passé composé',
|
||||
descriptions: 'Imparfait',
|
||||
dialogues: 'Plus-que-parfait',
|
||||
thoughts: 'Infinitif passé',
|
||||
summary: '→ Regrets, introspection nostalgique',
|
||||
};
|
||||
case 12:
|
||||
return {
|
||||
actions: 'Présent',
|
||||
descriptions: level === 1 ? 'Passé composé' : 'Passé composé + futur antérieur',
|
||||
dialogues: level === 1 ? 'Futur proche' : 'Futur antérieur',
|
||||
thoughts: 'Participe présent',
|
||||
summary: '→ Crise en cours, compte à rebours',
|
||||
};
|
||||
case 13:
|
||||
return {
|
||||
actions: level === 1 ? 'Présent simple' : level === 2 ? 'Présent + participe présent' : 'Participes présents enchaînés',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Participe présent + adjectifs' : 'Subjonctif présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Conditionnel présent' : 'Subjonctif présent',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif imparfait',
|
||||
summary: '→ Émotions intenses, introspections vives (romances, drames psychologiques)',
|
||||
};
|
||||
case 14:
|
||||
return {
|
||||
actions: 'Présent',
|
||||
descriptions: level === 1 ? 'Gérondif' : level === 2 ? 'Gérondif + infinitifs' : 'Conditionnel présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Impératif' : 'Infinitif',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif imparfait',
|
||||
summary: '→ Réflexions profondes, analyse des émotions (nouvelles philosophiques, récits introspectifs)',
|
||||
};
|
||||
case 15:
|
||||
return {
|
||||
actions: level === 1 ? 'Présent simple' : level === 2 ? 'Présent + passé simple' : 'Présent + passé antérieur',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Passé composé' : 'Conditionnel passé',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Passé simple' : 'Futur antérieur',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Plus-que-parfait' : 'Subjonctif imparfait',
|
||||
summary: '→ Histoires historiques avec une intensité immédiate (batailles, moments décisifs)',
|
||||
};
|
||||
case 16:
|
||||
return {
|
||||
actions: level === 1 ? 'Passé composé' : level === 2 ? 'Imparfait + passé simple' : 'Plus-que-parfait',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Participe passé' : 'Conditionnel passé',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Imparfait' : 'Subjonctif imparfait',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif présent',
|
||||
summary: '→ Récits introspectifs, auto-analyse (autofictions, récits de croissance personnelle)',
|
||||
};
|
||||
case 17:
|
||||
return {
|
||||
actions: level === 1 ? 'Futur simple' : level === 2 ? 'Futur antérieur' : 'Conditionnel passé',
|
||||
descriptions: level === 1 ? 'Futur proche' : level === 2 ? 'Futur antérieur' : 'Conditionnel présent',
|
||||
dialogues: level === 1 ? 'Futur simple' : level === 2 ? 'Futur antérieur' : 'Subjonctif présent',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Futur antérieur' : 'Conditionnel passé',
|
||||
summary: '→ Prophéties, visions apocalyptiques (récits mystiques, romans de science-fiction)',
|
||||
};
|
||||
case 18:
|
||||
return {
|
||||
actions: level === 1 ? 'Conditionnel présent' : level === 2 ? 'Conditionnel passé' : 'Subjonctif imparfait',
|
||||
descriptions: level === 1 ? 'Conditionnel présent' : level === 2 ? 'Conditionnel passé' : 'Subjonctif présent',
|
||||
dialogues: level === 1 ? 'Conditionnel présent' : level === 2 ? 'Subjonctif imparfait' : 'Impératif',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif imparfait',
|
||||
summary: '→ Mondes parallèles, uchronies (romans alternatifs, récits de fantasy)',
|
||||
};
|
||||
case 19:
|
||||
return {
|
||||
actions: level === 1 ? 'Imparfait' : level === 2 ? 'Imparfait + participe présent' : 'Participes présents enchaînés',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Participe présent + adjectifs' : 'Subjonctif présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Imparfait' : 'Subjonctif imparfait',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Infinitif passé' : 'Subjonctif présent',
|
||||
summary: '→ Lyrisme, poésie narrative (récits oniriques, nouvelles littéraires)',
|
||||
};
|
||||
case 20:
|
||||
return {
|
||||
actions: level === 1 ? 'Présent simple' : level === 2 ? 'Imparfait' : 'Futur simple',
|
||||
descriptions: level === 1 ? 'Imparfait' : level === 2 ? 'Participe présent' : 'Conditionnel présent',
|
||||
dialogues: level === 1 ? 'Présent' : level === 2 ? 'Imparfait' : 'Futur proche',
|
||||
thoughts: level === 1 ? 'Infinitif' : level === 2 ? 'Plus-que-parfait' : 'Subjonctif présent',
|
||||
summary: '→ Immersion totale (récits interactifs, jeux de rôle, romans à choix multiples)',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
actions: 'Passé simple',
|
||||
descriptions: 'Imparfait',
|
||||
dialogues: 'Passé composé',
|
||||
thoughts: 'Plus-que-parfait',
|
||||
summary: '→ Narrations épurées, style classique',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function presetStoryType(
|
||||
presetType: string,
|
||||
setTone: Dispatch<SetStateAction<string>>,
|
||||
setAtmosphere: Dispatch<SetStateAction<string>>,
|
||||
setVerbTense: Dispatch<SetStateAction<string>>,
|
||||
setPerson: Dispatch<SetStateAction<string>>,
|
||||
setDialogueType: Dispatch<SetStateAction<string>>,
|
||||
setIsExplicit: Dispatch<SetStateAction<boolean>>,
|
||||
): void {
|
||||
switch (presetType) {
|
||||
case '1':
|
||||
setTone('Suspense angoissant, mystère troublant');
|
||||
setAtmosphere('Tension oppressante, ombres menaçantes');
|
||||
setVerbTense('3');
|
||||
setPerson('1');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '2':
|
||||
setTone('Brutalité crue, terreur psychologique');
|
||||
setAtmosphere('Claustrophobie, clair-obscur sinistre');
|
||||
setVerbTense('10');
|
||||
setPerson('4');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '3':
|
||||
setTone('Magie envoûtante, innocence poétique');
|
||||
setAtmosphere('Forêt luminescente, brume enchantée');
|
||||
setVerbTense('19');
|
||||
setPerson('3');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '4':
|
||||
setTone('Froidure technologique, désespoir systémique');
|
||||
setAtmosphere('Métal rouillé, lumières néon vacillantes');
|
||||
setVerbTense('7');
|
||||
setPerson('5');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '5':
|
||||
setTone('Passion tourmentée, mélancolie sensuelle');
|
||||
setAtmosphere('Pluie fine, chambres aux rideaux lourds');
|
||||
setVerbTense('13');
|
||||
setPerson('1');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '6':
|
||||
setTone('Héroïsme grandiose, dangers exaltants');
|
||||
setAtmosphere('Vastes paysages, ruines anciennes');
|
||||
setVerbTense('4');
|
||||
setPerson('6');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '7':
|
||||
setTone('Méditation existentielle, questions sans réponses');
|
||||
setAtmosphere('Bibliothèque poussiéreuse, nuit silencieuse');
|
||||
setVerbTense('5');
|
||||
setPerson('5');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '8':
|
||||
setTone('Tension psychologique, suspense mental');
|
||||
setAtmosphere('Isolation, paranoïa croissante');
|
||||
setVerbTense('10');
|
||||
setPerson('4');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '9':
|
||||
setTone('Mystère obscur, surnaturel inquiétant');
|
||||
setAtmosphere('Forêts sombres, créatures cachées');
|
||||
setVerbTense('3');
|
||||
setPerson('1');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '10':
|
||||
setTone('Amour interdit, passion à travers les âges');
|
||||
setAtmosphere('Châteaux majestueux, bals somptueux');
|
||||
setVerbTense('1');
|
||||
setPerson('3');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '11':
|
||||
setTone('Dure réalité, enquête sombre');
|
||||
setAtmosphere('Rues sombres, ambiance de crime');
|
||||
setVerbTense('16');
|
||||
setPerson('5');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '12':
|
||||
setTone('Espoir futuriste, société idéale');
|
||||
setAtmosphere('Villes lumineuses, technologie avancée');
|
||||
setVerbTense('6');
|
||||
setPerson('4');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '13':
|
||||
setTone('Magie contemporaine, réalisme enchanté');
|
||||
setAtmosphere('Ville moderne, éléments féeriques');
|
||||
setVerbTense('4');
|
||||
setPerson('1');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '14':
|
||||
setTone('Conflits émotionnels, relations complexes');
|
||||
setAtmosphere('Intérieur chaleureux, tensions sous-jacentes');
|
||||
setVerbTense('13');
|
||||
setPerson('1');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '15':
|
||||
setTone('Exploration audacieuse, dangers marins');
|
||||
setAtmosphere('Océan infini, navires anciens');
|
||||
setVerbTense('4');
|
||||
setPerson('6');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '16':
|
||||
setTone('Quête héroïque, magie puissante');
|
||||
setAtmosphere('Mondes imaginaires, créatures mythiques');
|
||||
setVerbTense('19');
|
||||
setPerson('3');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '17':
|
||||
setTone('Amour moderne, relations actuelles');
|
||||
setAtmosphere('Ville animée, cafés cosy');
|
||||
setVerbTense('13');
|
||||
setPerson('1');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '18':
|
||||
setTone("Intrigue internationale, secrets d'État");
|
||||
setAtmosphere('Villes étrangères, tensions diplomatiques');
|
||||
setVerbTense('16');
|
||||
setPerson('5');
|
||||
setDialogueType('4');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
case '19':
|
||||
setTone('Survie désespérée, monde en ruines');
|
||||
setAtmosphere('Paysages dévastés, ressources rares');
|
||||
setVerbTense('7');
|
||||
setPerson('4');
|
||||
setDialogueType('3');
|
||||
setIsExplicit(false);
|
||||
break;
|
||||
case '20':
|
||||
setTone('Leçons de vie, valeurs profondes');
|
||||
setAtmosphere('Village paisible, nature environnante');
|
||||
setVerbTense('1');
|
||||
setPerson('3');
|
||||
setDialogueType('1');
|
||||
setIsExplicit(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function getNarrativePerson(value: number, level: number): string {
|
||||
if (level === 1) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Première personne (Je acteur) - Parfait pour les débuts (ex: Je marchais)';
|
||||
case 3:
|
||||
return 'Troisième omnisciente - Narration globale (ex: Il marchait)';
|
||||
default:
|
||||
return 'Première personne';
|
||||
}
|
||||
} else if (level === 2) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Première personne (Je acteur)';
|
||||
case 2:
|
||||
return 'Première personne (Je témoin) - Observateur (ex: Je le regardais marcher)';
|
||||
case 3:
|
||||
return 'Troisième omnisciente';
|
||||
case 4:
|
||||
return 'Troisième limitée - Focus sur un personnage (ex: Il marchait, ignorant le danger)';
|
||||
default:
|
||||
return 'Première personne';
|
||||
}
|
||||
} else if (level === 3) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Première personne (Je acteur)';
|
||||
case 2:
|
||||
return 'Première personne (Je témoin)';
|
||||
case 3:
|
||||
return 'Troisième omnisciente';
|
||||
case 4:
|
||||
return 'Troisième limitée';
|
||||
case 5:
|
||||
return 'Deuxième personne (Tu) - Immersion forte (ex: Tu marches vers la mort)';
|
||||
case 6:
|
||||
return 'Nous collectif - Voix chorale (ex: Nous marchions, unis par le destin)';
|
||||
default:
|
||||
return 'Troisième omnisciente';
|
||||
}
|
||||
}
|
||||
return 'Première personne';
|
||||
}
|
||||
|
||||
export function getDialogueType(value: number, level: number): string {
|
||||
if (level === 1) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Dialogue direct - Paroles exactes (ex: "Je t\'aime !")';
|
||||
case 2:
|
||||
return 'Dialogue indirect - Résumé par le narrateur (ex: Il dit qu\'il m\'aime)';
|
||||
default:
|
||||
return 'Dialogue direct';
|
||||
}
|
||||
} else if (level === 2) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Dialogue direct';
|
||||
case 2:
|
||||
return 'Dialogue indirect';
|
||||
case 3:
|
||||
return 'Dialogue mixte (ex: "Je t\'aime" dit-il, puis explique ses sentiments)';
|
||||
default:
|
||||
return 'Dialogue direct';
|
||||
}
|
||||
} else if (level === 3) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return 'Dialogue direct';
|
||||
case 2:
|
||||
return 'Dialogue indirect';
|
||||
case 3:
|
||||
return 'Dialogue mixte';
|
||||
case 4:
|
||||
return 'Monologue intérieur (ex: *Je ne peux pas le perdre...*)';
|
||||
default:
|
||||
return 'Dialogue direct';
|
||||
}
|
||||
}
|
||||
return 'Dialogue direct';
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
SyncedActSummary,
|
||||
SyncedGuideLine,
|
||||
SyncedAIGuideLine
|
||||
} from "@/lib/models/SyncedBook";
|
||||
} from "@/lib/types/synced-book";
|
||||
|
||||
/**
|
||||
* Résultat de comparaison pour un livre
|
||||
|
||||
3
lib/utils/time.ts
Normal file
3
lib/utils/time.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function timeStampInSeconds(): number {
|
||||
return Math.floor(new Date().getTime() / 1000);
|
||||
}
|
||||
140
lib/utils/tiptap.ts
Normal file
140
lib/utils/tiptap.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import {TiptapAttrValue, TiptapLinkAttrs, TiptapNode} from "@/lib/types/chapter";
|
||||
|
||||
function isTiptapLinkAttrs(value: TiptapAttrValue): value is TiptapLinkAttrs {
|
||||
return typeof value === 'object' && value !== null && 'href' in value;
|
||||
}
|
||||
|
||||
export function getPageCount(text: string): number {
|
||||
const charactersPerLine: number = 90;
|
||||
const linesPerPage: number = 40;
|
||||
|
||||
const lines: string[] = text.split('\n');
|
||||
let totalLines: number = 0;
|
||||
|
||||
lines.forEach((line: string) => {
|
||||
const lineLength: number = line.length;
|
||||
const estimatedLines: number = Math.ceil(lineLength / charactersPerLine);
|
||||
totalLines += estimatedLines;
|
||||
});
|
||||
|
||||
return Math.ceil(totalLines / linesPerPage);
|
||||
}
|
||||
|
||||
export function convertTiptapToHTML(node: TiptapNode): string {
|
||||
let html: string = '';
|
||||
|
||||
switch (node.type) {
|
||||
case 'doc':
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'paragraph':
|
||||
html += '<p>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</p>';
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
let textContent: string = node.text || '';
|
||||
|
||||
if (node.attrs) {
|
||||
if (node.attrs.bold) {
|
||||
textContent = `<strong>${textContent}</strong>`;
|
||||
}
|
||||
if (node.attrs.italic) {
|
||||
textContent = `<em>${textContent}</em>`;
|
||||
}
|
||||
if (node.attrs.underline) {
|
||||
textContent = `<u>${textContent}</u>`;
|
||||
}
|
||||
if (node.attrs.strike) {
|
||||
textContent = `<s>${textContent}</s>`;
|
||||
}
|
||||
if (node.attrs.link && isTiptapLinkAttrs(node.attrs.link)) {
|
||||
textContent = `<a href="${node.attrs.link.href}">${textContent}</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += textContent;
|
||||
break;
|
||||
|
||||
case 'heading':
|
||||
const level: number = typeof node.attrs?.level === 'number' ? node.attrs.level : 1;
|
||||
html += `<h${level}>`;
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += `</h${level}>`;
|
||||
break;
|
||||
|
||||
case 'bulletList':
|
||||
html += '<ul>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</ul>';
|
||||
break;
|
||||
|
||||
case 'orderedList':
|
||||
html += '<ol>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</ol>';
|
||||
break;
|
||||
|
||||
case 'listItem':
|
||||
html += '<li>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</li>';
|
||||
break;
|
||||
|
||||
case 'blockquote':
|
||||
html += '<blockquote>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</blockquote>';
|
||||
break;
|
||||
|
||||
case 'codeBlock':
|
||||
html += '<pre><code>';
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
html += '</code></pre>';
|
||||
break;
|
||||
|
||||
default:
|
||||
if (node.content) {
|
||||
node.content.forEach((childNode: TiptapNode) => {
|
||||
html += convertTiptapToHTML(childNode);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
44
lib/utils/user.ts
Normal file
44
lib/utils/user.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {GuideTour, Subscription, UserProps} from "@/lib/types/user";
|
||||
import {SessionProps} from "@/lib/types/session";
|
||||
|
||||
export function getCurrentSubscription(user: UserProps | null, type: "quill-sense" | "use-your-keys" | "quill-trial"): Subscription | null {
|
||||
if (!user || !user.subscription || user.subscription.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return user.subscription.find((sub: Subscription): boolean => {
|
||||
return sub.subType === type && sub.status;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
export function getWritingLevel(level: number): string {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return 'Débutant';
|
||||
case 2:
|
||||
return 'Intermédiaire';
|
||||
case 3:
|
||||
return 'Avancé';
|
||||
default:
|
||||
return 'Débutant';
|
||||
}
|
||||
}
|
||||
|
||||
export function guideTourDone(guide: GuideTour[], tour: string): boolean {
|
||||
if (!guide || !tour) return false;
|
||||
return guide.find((guide: GuideTour): boolean => guide[tour]) === undefined;
|
||||
}
|
||||
|
||||
export function setNewGuideTour(session: SessionProps, tour: string): SessionProps {
|
||||
if (!session.user) return session;
|
||||
const newGuideTour: GuideTour[] = [
|
||||
...(session.user.guideTour ?? []),
|
||||
{[tour]: true}
|
||||
];
|
||||
return {
|
||||
...session,
|
||||
user: {
|
||||
...session.user,
|
||||
guideTour: newGuideTour
|
||||
}
|
||||
};
|
||||
}
|
||||
4
lib/utils/validation.ts
Normal file
4
lib/utils/validation.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export function verifyInput(input: string): boolean {
|
||||
const pattern: RegExp = new RegExp('(<.*?>)|(&.*?;)|({.*?})', 'gmi');
|
||||
return pattern.test(input);
|
||||
}
|
||||
Reference in New Issue
Block a user