Add comprehensive models and script for handling inline scripts and structured data
- Implement `remove-inline-scripts.js` to externalize Next.js inline scripts, enhancing CSP compliance. - Add models for `Book`, `Character`, `Story`, `Editor`, `System`, and `BookSerie` with relevant properties and utilities. - Include macOS entitlements plist for app development with advanced permissions. - Add utility functions to handle script hashing, cookie management, and content formatting.
This commit is contained in:
135
lib/models/Book.ts
Normal file
135
lib/models/Book.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import {Author} from './User';
|
||||
import {ActChapter, ChapterProps} from "@/lib/models/Chapter";
|
||||
import {SelectBoxProps} from "@/shared/interface";
|
||||
|
||||
export interface BookProps {
|
||||
bookId: string;
|
||||
type: string;
|
||||
title: string;
|
||||
author?: Author;
|
||||
serie?: number;
|
||||
subTitle?: string;
|
||||
summary?: string;
|
||||
publicationDate?: string;
|
||||
desiredWordCount?: number;
|
||||
totalWordCount?: number;
|
||||
coverImage?: string;
|
||||
chapters?: ChapterProps[];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
dialogueType: number;
|
||||
globalResume: string;
|
||||
atmosphere: string;
|
||||
verbeTense: number;
|
||||
langue: number;
|
||||
themes: string;
|
||||
}
|
||||
|
||||
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: BookProps[]): SelectBoxProps[] {
|
||||
return books.map((book: BookProps) => {
|
||||
return {
|
||||
label: book.title,
|
||||
value: book.bookId,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
3
lib/models/BookSerie.ts
Executable file
3
lib/models/BookSerie.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
export default class BookSerie{
|
||||
|
||||
}
|
||||
196
lib/models/Chapter.ts
Normal file
196
lib/models/Chapter.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
222
lib/models/Character.ts
Executable file
222
lib/models/Character.ts
Executable file
@@ -0,0 +1,222 @@
|
||||
import {
|
||||
faBrain,
|
||||
faBullseye,
|
||||
faExclamationTriangle,
|
||||
faFire,
|
||||
faRuler,
|
||||
faShieldAlt,
|
||||
faUsers,
|
||||
faWrench,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {SelectBoxProps} from "@/shared/interface";
|
||||
|
||||
type CharacterCategory = 'main' | 'secondary' | 'recurring' | 'none';
|
||||
|
||||
export const characterCategories: SelectBoxProps[] = [
|
||||
{
|
||||
value: 'none',
|
||||
label: 'Sélectionner son rôle',
|
||||
},
|
||||
{
|
||||
value: 'main',
|
||||
label: 'Principal',
|
||||
},
|
||||
{
|
||||
value: 'secondary',
|
||||
label: 'Secondaire',
|
||||
},
|
||||
{
|
||||
value: 'recurring',
|
||||
label: 'Récurrent',
|
||||
},
|
||||
];
|
||||
|
||||
export const characterTitle: SelectBoxProps[] = [
|
||||
{value: 'none', label: 'Aucun'},
|
||||
{value: 'king', label: 'Roi'},
|
||||
{value: 'queen', label: 'Reine'},
|
||||
{value: 'emperor', label: 'Empereur'},
|
||||
{value: 'empress', label: 'Impératrice'},
|
||||
{value: 'prince', label: 'Prince'},
|
||||
{value: 'princess', label: 'Princesse'},
|
||||
{value: 'duke', label: 'Duc'},
|
||||
{value: 'duchess', label: 'Duchesse'},
|
||||
{value: 'count', label: 'Comte'},
|
||||
{value: 'countess', label: 'Comtesse'},
|
||||
{value: 'baron', label: 'Baron'},
|
||||
{value: 'baroness', label: 'Baronne'},
|
||||
{value: 'lord', label: 'Seigneur'},
|
||||
{value: 'lady', label: 'Dame'},
|
||||
{value: 'knight', label: 'Chevalier'},
|
||||
{value: 'squire', label: 'Écuyer'},
|
||||
{value: 'warrior', label: 'Guerrier'},
|
||||
{value: 'general', label: 'Général'},
|
||||
{value: 'commander', label: 'Commandant'},
|
||||
{value: 'captain', label: 'Capitaine'},
|
||||
{value: 'soldier', label: 'Soldat'},
|
||||
{value: 'mercenary', label: 'Mercenaire'},
|
||||
{value: 'assassin', label: 'Assassin'},
|
||||
{value: 'thief', label: 'Voleur'},
|
||||
{value: 'spy', label: 'Espion'},
|
||||
{value: 'archmage', label: 'Archimage'},
|
||||
{value: 'sorcerer', label: 'Sorcier'},
|
||||
{value: 'witch', label: 'Sorcière'},
|
||||
{value: 'warlock', label: 'Mage Noir'},
|
||||
{value: 'druid', label: 'Druide'},
|
||||
{value: 'priest', label: 'Prêtre'},
|
||||
{value: 'prophet', label: 'Prophète'},
|
||||
{value: 'oracle', label: 'Oracle'},
|
||||
{value: 'seer', label: 'Voyant'},
|
||||
{value: 'scholar', label: 'Érudit'},
|
||||
{value: 'alchemist', label: 'Alchimiste'},
|
||||
{value: 'healer', label: 'Guérisseur'},
|
||||
{value: 'bard', label: 'Barde'},
|
||||
{value: 'hermit', label: 'Ermite'},
|
||||
{value: 'noble', label: 'Noble'},
|
||||
{value: 'peasant', label: 'Paysan'},
|
||||
{value: 'merchant', label: 'Marchand'},
|
||||
{value: 'sailor', label: 'Marin'},
|
||||
{value: 'pirate', label: 'Pirate'},
|
||||
{value: 'slave', label: 'Esclave'},
|
||||
{value: 'gladiator', label: 'Gladiateur'},
|
||||
{value: 'champion', label: 'Champion'},
|
||||
{value: 'outlaw', label: 'Hors-la-loi'},
|
||||
{value: 'hunter', label: 'Chasseur'},
|
||||
{value: 'beastmaster', label: 'Maître des Bêtes'},
|
||||
{value: 'ranger', label: 'Rôdeur'},
|
||||
{value: 'warden', label: 'Gardien'},
|
||||
{value: 'sentinel', label: 'Sentinelle'},
|
||||
{value: 'herald', label: 'Héraut'},
|
||||
{value: 'messenger', label: 'Messager'},
|
||||
{value: 'pilgrim', label: 'Pèlerin'},
|
||||
{value: 'nomad', label: 'Nomade'},
|
||||
{value: 'chieftain', label: 'Chef de Clan'},
|
||||
{value: 'high-priest', label: 'Grand Prêtre'},
|
||||
{value: 'inquisitor', label: 'Inquisiteur'},
|
||||
{value: 'judge', label: 'Juge'},
|
||||
{value: 'executioner', label: 'Bourreau'},
|
||||
{value: 'warden', label: 'Gardien de Prison'},
|
||||
{value: 'monk', label: 'Moine'},
|
||||
{value: 'abbot', label: 'Abbé'},
|
||||
{value: 'nun', label: 'Nonne'},
|
||||
{value: 'diplomat', label: 'Diplomate'},
|
||||
{value: 'ambassador', label: 'Ambassadeur'},
|
||||
{value: 'scientist', label: 'Scientifique'},
|
||||
{value: 'engineer', label: 'Ingénieur'},
|
||||
{value: 'inventor', label: 'Inventeur'},
|
||||
{value: 'architect', label: 'Architecte'},
|
||||
{value: 'scribe', label: 'Scribe'},
|
||||
{value: 'chronicler', label: 'Chroniqueur'},
|
||||
{value: 'storyteller', label: 'Conteur'},
|
||||
{value: 'actor', label: 'Acteur'},
|
||||
{value: 'musician', label: 'Musicien'},
|
||||
{value: 'artist', label: 'Artiste'},
|
||||
{value: 'sculptor', label: 'Sculpteur'},
|
||||
{value: 'orator', label: 'Orateur'},
|
||||
{value: 'revolutionary', label: 'Révolutionnaire'},
|
||||
{value: 'resistance-fighter', label: 'Résistant'},
|
||||
{value: 'freedom-fighter', label: 'Combattant de la Liberté'},
|
||||
{value: 'cult-leader', label: 'Chef de Secte'},
|
||||
{value: 'warlock-lord', label: 'Seigneur Noir'},
|
||||
{value: 'dark-prophet', label: 'Prophète du Chaos'},
|
||||
{value: 'warlord', label: 'Seigneur de Guerre'},
|
||||
{value: 'grandmaster', label: 'Grand Maître'},
|
||||
{value: 'tactician', label: 'Tacticien'},
|
||||
{value: 'archduke', label: 'Archiduc'},
|
||||
{value: 'high-king', label: 'Haut Roi'},
|
||||
{value: 'divine-champion', label: 'Champion Divin'},
|
||||
];
|
||||
|
||||
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;
|
||||
category: CharacterCategory;
|
||||
title: string;
|
||||
image: string;
|
||||
physical: Attribute[];
|
||||
psychological: Attribute[];
|
||||
relations: Attribute[];
|
||||
skills: Attribute[];
|
||||
weaknesses: Attribute[];
|
||||
strengths: Attribute[];
|
||||
goals: Attribute[];
|
||||
motivations: Attribute[];
|
||||
role: string;
|
||||
biography?: string;
|
||||
history?: string;
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
export const characterElementCategory: CharacterElement[] = [
|
||||
{
|
||||
title: 'Descriptions physiques',
|
||||
section: 'physical',
|
||||
placeholder: 'Nouvelle Description Physique',
|
||||
icon: faRuler,
|
||||
},
|
||||
{
|
||||
title: 'Descriptions psychologiques',
|
||||
section: 'psychological',
|
||||
placeholder: 'Nouvelle Description Psychologique',
|
||||
icon: faBrain,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
];
|
||||
20
lib/models/Editor.ts
Executable file
20
lib/models/Editor.ts
Executable file
@@ -0,0 +1,20 @@
|
||||
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('');
|
||||
}
|
||||
|
||||
}
|
||||
128
lib/models/QuillSense.ts
Normal file
128
lib/models/QuillSense.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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> {}
|
||||
|
||||
export interface AISimpleText extends AIResponseWithCredits<string> {}
|
||||
|
||||
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 InspiredAIResponse {
|
||||
ideas: InspirationAIIdea[];
|
||||
}
|
||||
|
||||
export interface ConversationProps {
|
||||
id: string;
|
||||
mode: string;
|
||||
title: string;
|
||||
startDate: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export default class QuillSense {
|
||||
static getSubLevel(session: SessionProps): number {
|
||||
const currentSub: Subscription | null = User.getCurrentSubscription(session?.user, 'quill-sense');
|
||||
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;
|
||||
}
|
||||
}
|
||||
19
lib/models/Session.ts
Executable file
19
lib/models/Session.ts
Executable file
@@ -0,0 +1,19 @@
|
||||
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() {
|
||||
}
|
||||
}
|
||||
1116
lib/models/Story.ts
Executable file
1116
lib/models/Story.ts
Executable file
File diff suppressed because it is too large
Load Diff
209
lib/models/System.ts
Normal file
209
lib/models/System.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
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 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: 'web',
|
||||
...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: 'web',
|
||||
},
|
||||
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: 'web',
|
||||
},
|
||||
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: 'web',
|
||||
},
|
||||
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 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: 'web',
|
||||
},
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
lib/models/User.ts
Normal file
92
lib/models/User.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
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,
|
||||
},
|
||||
books?: BookProps[];
|
||||
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"): 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 (!guide || !tour) return false;
|
||||
return guide.find((guide: GuideTour): boolean => guide[tour]) === undefined;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
lib/models/World.ts
Executable file
111
lib/models/World.ts
Executable file
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
faCrown,
|
||||
faExclamationTriangle,
|
||||
faFlag,
|
||||
faGavel,
|
||||
faIndustry,
|
||||
faLeaf,
|
||||
faMountain,
|
||||
faMusic,
|
||||
faPeopleArrows,
|
||||
faSnowflake,
|
||||
faUserCog,
|
||||
faUserFriends,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {ElementSection} from "@/components/book/settings/world/WorldSetting";
|
||||
|
||||
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[];
|
||||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user