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:
natreex
2026-03-22 22:37:31 -04:00
parent e8aaef108b
commit 64ed90d993
229 changed files with 15091 additions and 21289 deletions

224
lib/types/book-tables.ts Normal file
View File

@@ -0,0 +1,224 @@
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;
}
export interface BookActSummariesTable {
act_sum_id: string;
book_id: string;
user_id: string;
act_index: number;
summary: string | null;
last_update: number;
}
export interface BookAIGuideLineTable {
user_id: string;
book_id: string;
global_resume: string | null;
themes: string | null;
verbe_tense: number | null;
narrative_type: number | null;
langue: number | null;
dialogue_type: number | null;
tone: string | null;
atmosphere: string | null;
current_resume: string | null;
}
export interface BookChaptersTable {
chapter_id: string;
book_id: string;
author_id: string;
title: string;
hashed_title: string;
words_count: number | null;
chapter_order: number;
last_update: number;
}
export interface BookChapterContentTable {
content_id: string;
chapter_id: string;
author_id: string;
version: number;
content: string | null;
words_count: number;
time_on_it: number;
last_update: number;
}
export interface BookChapterInfosTable {
chapter_info_id: string;
chapter_id: string;
act_id: number;
incident_id: string | null;
plot_point_id: string | null;
book_id: string;
author_id: string;
summary: string | null;
goal: string | null;
last_update: number;
}
export interface BookCharactersTable {
character_id: string;
book_id: string;
user_id: string;
first_name: string;
last_name: string | null;
category: string;
title: string | null;
image: string | null;
role: string | null;
biography: string | null;
history: string | null;
last_update: number;
}
export interface BookCharactersAttributesTable {
attr_id: string;
character_id: string;
user_id: string;
attribute_name: string;
attribute_value: string;
last_update: number;
}
export interface BookGuideLineTable {
user_id: string;
book_id: string;
tone: string | null;
atmosphere: string | null;
writing_style: string | null;
themes: string | null;
symbolism: string | null;
motifs: string | null;
narrative_voice: string | null;
pacing: string | null;
intended_audience: string | null;
key_messages: string | null;
}
export interface BookIncidentsTable {
incident_id: string;
author_id: string;
book_id: string;
title: string;
hashed_title: string;
summary: string | null;
last_update: number;
}
export interface BookIssuesTable {
issue_id: string;
author_id: string;
book_id: string;
name: string;
hashed_issue_name: string;
last_update: number;
}
export interface BookLocationTable {
loc_id: string;
book_id: string;
user_id: string;
loc_name: string;
loc_original_name: string;
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;
hashed_title: string;
summary: string | null;
linked_incident_id: string | null;
author_id: string;
book_id: string;
last_update: number;
}
export interface BookWorldTable {
world_id: string;
name: string;
hashed_name: string;
author_id: string;
book_id: string;
history: string | null;
politics: string | null;
economy: string | null;
religion: string | null;
languages: string | null;
last_update: number;
}
export interface BookWorldElementsTable {
element_id: string;
world_id: string;
user_id: string;
element_type: number;
name: string;
original_name: string;
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
View 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
View 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
View 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
View 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;
}

27
lib/types/import.ts Normal file
View File

@@ -0,0 +1,27 @@
export interface ParsedChapterPreview {
index: number;
title: string;
wordCount: number;
}
export interface ParsedDocxResponse {
importId: string;
chapters: ParsedChapterPreview[];
}
export interface ImportChapterSelection {
index: number;
title: string;
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
View 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;
}

166
lib/types/series.ts Normal file
View File

@@ -0,0 +1,166 @@
export interface SeriesProps {
id: string | null;
name: string;
description: string;
coverImage: string | null;
}
export interface SeriesBookProps {
bookId: string;
title: string;
order: number;
coverImage: string | null;
}
export interface SeriesDetailResponse {
id: string;
name: string;
description: string;
coverImage: string | null;
books: SeriesBookProps[];
}
export interface SeriesAddResponse {
seriesId: string;
}
export interface SeriesUpdateResponse {
success: boolean;
}
export interface SeriesListItemProps {
id: string;
name: string;
description: string;
coverImage: string | null;
bookCount: number;
bookIds: string[];
}
export interface SeriesCharacterListItem {
id: string;
name: string;
lastName: string | null;
category: string;
role: string | null;
color: string | null;
image: string | null;
}
export interface SeriesCharacterDetailResponse {
id: string;
name: string;
lastName: string | null;
nickname: string | null;
age: number | null;
gender: string | null;
species: string | null;
nationality: string | null;
status: string | null;
category: string;
title: string | null;
image: string | null;
role: string | null;
biography: string | null;
history: string | null;
speechPattern: string | null;
catchphrase: string | null;
residence: string | null;
notes: string | null;
color: string | null;
attributes?: SeriesCharacterAttribute[];
}
export interface SeriesCharacterAttribute {
id: string;
name: string;
value: string;
}
export type SeriesCharacterProps = SeriesCharacterDetailResponse;
export interface SeriesWorldElementItem {
id: string;
name: string;
description: string;
}
export interface SeriesWorldListItem {
id: string;
name: string;
history: string;
politics: string;
economy: string;
religion: string;
languages: string;
laws: SeriesWorldElementItem[];
biomes: SeriesWorldElementItem[];
issues: SeriesWorldElementItem[];
customs: SeriesWorldElementItem[];
kingdoms: SeriesWorldElementItem[];
climate: SeriesWorldElementItem[];
resources: SeriesWorldElementItem[];
wildlife: SeriesWorldElementItem[];
arts: SeriesWorldElementItem[];
ethnicGroups: SeriesWorldElementItem[];
socialClasses: SeriesWorldElementItem[];
importantCharacters: SeriesWorldElementItem[];
}
export type SeriesWorldProps = SeriesWorldListItem;
export interface SeriesWorldElement {
id: string;
type: number;
name: string;
description: string;
}
export interface SeriesLocationSubElement {
id: string;
name: string;
description: string;
}
export interface SeriesLocationElement {
id: string;
name: string;
description: string;
subElements: SeriesLocationSubElement[];
}
export interface SeriesLocationItem {
id: string;
name: string;
elements: SeriesLocationElement[];
}
export interface SeriesSpellTag {
id: string;
name: string;
color: string | null;
}
export interface SeriesSpellListItem {
id: string;
name: string;
description: string;
tags: string[] | null;
}
export interface SeriesSpellListResponse {
spells: SeriesSpellListItem[];
tags: SeriesSpellTag[];
}
export interface SeriesSpellDetailResponse {
id: string;
name: string;
description: string;
appearance: string;
tags: string[];
powerLevel: string | null;
components: string | null;
limitations: string | null;
notes: string | null;
}

14
lib/types/session.ts Normal file
View 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
View File

@@ -0,0 +1,5 @@
export type ViewMode = 'list' | 'detail' | 'edit';
export interface SettingRef {
handleSave: () => Promise<void>;
}

58
lib/types/spell.ts Normal file
View 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
View 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;
}

338
lib/types/synced-book.ts Normal file
View File

@@ -0,0 +1,338 @@
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
};
}

280
lib/types/synced-series.ts Normal file
View File

@@ -0,0 +1,280 @@
/**
* Lightweight sync structures for series comparison.
* These interfaces mirror the backend SyncedSeries* types from Book.ts
* but are used in the frontend for sync status detection.
*/
export interface SyncedSeriesBook {
bookId: string;
order: number;
lastUpdate: number;
}
export interface SyncedSeriesCharacterAttribute {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesCharacter {
id: string;
name: string;
lastUpdate: number;
attributes: SyncedSeriesCharacterAttribute[];
}
export interface SyncedSeriesWorldElement {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesWorld {
id: string;
name: string;
lastUpdate: number;
elements: SyncedSeriesWorldElement[];
}
export interface SyncedSeriesLocationSubElement {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesLocationElement {
id: string;
name: string;
lastUpdate: number;
subElements: SyncedSeriesLocationSubElement[];
}
export interface SyncedSeriesLocation {
id: string;
name: string;
lastUpdate: number;
elements: SyncedSeriesLocationElement[];
}
export interface SyncedSeriesSpell {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesSpellTag {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeries {
id: string;
name: string;
description: string | null;
lastUpdate: number;
books: SyncedSeriesBook[];
characters: SyncedSeriesCharacter[];
worlds: SyncedSeriesWorld[];
locations: SyncedSeriesLocation[];
spells: SyncedSeriesSpell[];
spellTags: SyncedSeriesSpellTag[];
}
/**
* Comparison result containing IDs of changed entities.
* Used for partial synchronization - only changed entities are transferred.
*/
export interface SeriesSyncCompare {
id: string;
books: string[];
characters: string[];
characterAttributes: string[];
worlds: string[];
worldElements: string[];
locations: string[];
locationElements: string[];
locationSubElements: string[];
spells: string[];
spellTags: string[];
}
/**
* Compares two versions of a series to find changed entities.
* The "newer" series is compared against the "older" one to find
* entities that have been added or modified.
*
* @param newerSeries - The series version with potentially newer data
* @param olderSeries - The series version to compare against
* @returns SeriesSyncCompare with IDs of changed entities, or null if no changes
*/
export function compareSeriesSyncs(newerSeries: SyncedSeries, olderSeries: SyncedSeries): SeriesSyncCompare | null {
const changedBookIds: string[] = [];
const changedCharacterIds: string[] = [];
const changedCharacterAttributeIds: string[] = [];
const changedWorldIds: string[] = [];
const changedWorldElementIds: string[] = [];
const changedLocationIds: string[] = [];
const changedLocationElementIds: string[] = [];
const changedLocationSubElementIds: string[] = [];
const changedSpellIds: string[] = [];
const changedSpellTagIds: string[] = [];
// Compare books
newerSeries.books.forEach((newerBook: SyncedSeriesBook): void => {
const olderBook: SyncedSeriesBook | undefined = olderSeries.books.find(
(book: SyncedSeriesBook): boolean => book.bookId === newerBook.bookId
);
if (!olderBook || newerBook.lastUpdate > olderBook.lastUpdate) {
changedBookIds.push(newerBook.bookId);
}
});
// Compare characters and their attributes
newerSeries.characters.forEach((newerCharacter: SyncedSeriesCharacter): void => {
const olderCharacter: SyncedSeriesCharacter | undefined = olderSeries.characters.find(
(character: SyncedSeriesCharacter): boolean => character.id === newerCharacter.id
);
if (!olderCharacter) {
changedCharacterIds.push(newerCharacter.id);
newerCharacter.attributes.forEach((attr: SyncedSeriesCharacterAttribute): void => {
changedCharacterAttributeIds.push(attr.id);
});
} else if (newerCharacter.lastUpdate > olderCharacter.lastUpdate) {
changedCharacterIds.push(newerCharacter.id);
} else {
// Check attributes even if character hasn't changed
newerCharacter.attributes.forEach((newerAttr: SyncedSeriesCharacterAttribute): void => {
const olderAttr: SyncedSeriesCharacterAttribute | undefined = olderCharacter.attributes.find(
(attr: SyncedSeriesCharacterAttribute): boolean => attr.id === newerAttr.id
);
if (!olderAttr || newerAttr.lastUpdate > olderAttr.lastUpdate) {
changedCharacterAttributeIds.push(newerAttr.id);
}
});
}
});
// Compare worlds and their elements
newerSeries.worlds.forEach((newerWorld: SyncedSeriesWorld): void => {
const olderWorld: SyncedSeriesWorld | undefined = olderSeries.worlds.find(
(world: SyncedSeriesWorld): boolean => world.id === newerWorld.id
);
if (!olderWorld) {
changedWorldIds.push(newerWorld.id);
newerWorld.elements.forEach((element: SyncedSeriesWorldElement): void => {
changedWorldElementIds.push(element.id);
});
} else if (newerWorld.lastUpdate > olderWorld.lastUpdate) {
changedWorldIds.push(newerWorld.id);
} else {
// Check elements even if world hasn't changed
newerWorld.elements.forEach((newerElement: SyncedSeriesWorldElement): void => {
const olderElement: SyncedSeriesWorldElement | undefined = olderWorld.elements.find(
(element: SyncedSeriesWorldElement): boolean => element.id === newerElement.id
);
if (!olderElement || newerElement.lastUpdate > olderElement.lastUpdate) {
changedWorldElementIds.push(newerElement.id);
}
});
}
});
// Compare locations, their elements, and sub-elements
newerSeries.locations.forEach((newerLocation: SyncedSeriesLocation): void => {
const olderLocation: SyncedSeriesLocation | undefined = olderSeries.locations.find(
(location: SyncedSeriesLocation): boolean => location.id === newerLocation.id
);
if (!olderLocation) {
changedLocationIds.push(newerLocation.id);
newerLocation.elements.forEach((element: SyncedSeriesLocationElement): void => {
changedLocationElementIds.push(element.id);
element.subElements.forEach((subElement: SyncedSeriesLocationSubElement): void => {
changedLocationSubElementIds.push(subElement.id);
});
});
} else if (newerLocation.lastUpdate > olderLocation.lastUpdate) {
changedLocationIds.push(newerLocation.id);
} else {
// Check elements
newerLocation.elements.forEach((newerElement: SyncedSeriesLocationElement): void => {
const olderElement: SyncedSeriesLocationElement | undefined = olderLocation.elements.find(
(element: SyncedSeriesLocationElement): boolean => element.id === newerElement.id
);
if (!olderElement) {
changedLocationElementIds.push(newerElement.id);
newerElement.subElements.forEach((subElement: SyncedSeriesLocationSubElement): void => {
changedLocationSubElementIds.push(subElement.id);
});
} else if (newerElement.lastUpdate > olderElement.lastUpdate) {
changedLocationElementIds.push(newerElement.id);
} else {
// Check sub-elements
newerElement.subElements.forEach((newerSubElement: SyncedSeriesLocationSubElement): void => {
const olderSubElement: SyncedSeriesLocationSubElement | undefined = olderElement.subElements.find(
(subElement: SyncedSeriesLocationSubElement): boolean => subElement.id === newerSubElement.id
);
if (!olderSubElement || newerSubElement.lastUpdate > olderSubElement.lastUpdate) {
changedLocationSubElementIds.push(newerSubElement.id);
}
});
}
});
}
});
// Compare spells
newerSeries.spells.forEach((newerSpell: SyncedSeriesSpell): void => {
const olderSpell: SyncedSeriesSpell | undefined = olderSeries.spells.find(
(spell: SyncedSeriesSpell): boolean => spell.id === newerSpell.id
);
if (!olderSpell || newerSpell.lastUpdate > olderSpell.lastUpdate) {
changedSpellIds.push(newerSpell.id);
}
});
// Compare spell tags
newerSeries.spellTags.forEach((newerTag: SyncedSeriesSpellTag): void => {
const olderTag: SyncedSeriesSpellTag | undefined = olderSeries.spellTags.find(
(tag: SyncedSeriesSpellTag): boolean => tag.id === newerTag.id
);
if (!olderTag || newerTag.lastUpdate > olderTag.lastUpdate) {
changedSpellTagIds.push(newerTag.id);
}
});
// Check if there are any changes
const hasChanges: boolean =
changedBookIds.length > 0 ||
changedCharacterIds.length > 0 ||
changedCharacterAttributeIds.length > 0 ||
changedWorldIds.length > 0 ||
changedWorldElementIds.length > 0 ||
changedLocationIds.length > 0 ||
changedLocationElementIds.length > 0 ||
changedLocationSubElementIds.length > 0 ||
changedSpellIds.length > 0 ||
changedSpellTagIds.length > 0;
if (!hasChanges) {
return null;
}
return {
id: newerSeries.id,
books: changedBookIds,
characters: changedCharacterIds,
characterAttributes: changedCharacterAttributeIds,
worlds: changedWorldIds,
worldElements: changedWorldElementIds,
locations: changedLocationIds,
locationElements: changedLocationElementIds,
locationSubElements: changedLocationSubElementIds,
spells: changedSpellIds,
spellTags: changedSpellTagIds
};
}

36
lib/types/user.ts Normal file
View 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
View 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;
}