Update TTS providers and story markup

This commit is contained in:
2026-05-20 22:13:31 +02:00
parent b911c40d89
commit 8258ea2321
36 changed files with 1482 additions and 197 deletions
+78 -2
View File
@@ -161,6 +161,7 @@ export class InkEngine {
const paragraphs: TurnResult['paragraphs'] = [];
const globalTags: StoryTag[] = [];
const turnTags: StoryTag[] = [];
let pendingParagraphTags: StoryTag[] = [];
while (this.story.canContinue) {
const rawText = this.story.Continue();
@@ -173,11 +174,24 @@ export class InkEngine {
.forEach((tag) => globalTags.push(tag));
if (text) {
paragraphs.push({ text, tags });
const paragraphTags = this.reassignTrailingGlossTags(text, [...pendingParagraphTags, ...tags], paragraphs);
pendingParagraphTags = [];
paragraphs.push({ text, tags: paragraphTags });
} else {
tags.forEach((tag) => globalTags.push(tag));
const paragraphTags = this.reassignTrailingGlossTags('', tags, paragraphs);
paragraphTags.forEach((tag) => {
if (this.isParagraphScopedTag(tag)) {
pendingParagraphTags.push(tag);
} else {
globalTags.push(tag);
}
});
}
}
if (pendingParagraphTags.length > 0) {
globalTags.push(...pendingParagraphTags);
pendingParagraphTags = [];
}
const choices = this.story.currentChoices.map((choice): ChoiceResult => {
const tags = this.getChoiceTags(choice);
@@ -234,6 +248,68 @@ export class InkEngine {
};
}
private isParagraphScopedTag(tag: StoryTag): boolean {
const key = String(tag?.key || '').toLowerCase();
return ['chapter', 'heading', 'section', 'textblock', 'image', 'music', 'sfx', 'sound', 'audio', 'gloss', 'tts']
.includes(key) || key.startsWith('tts-');
}
private reassignTrailingGlossTags(
text: string,
tags: StoryTag[],
paragraphs: TurnResult['paragraphs'],
): StoryTag[] {
if (!Array.isArray(tags) || tags.length === 0) return [];
const previous = paragraphs.length > 0 ? paragraphs[paragraphs.length - 1] : null;
if (!previous) return tags;
const currentText = this.normalizeGlossMatchText(text);
const previousText = this.normalizeGlossMatchText(previous.text);
const remainingTags: StoryTag[] = [];
tags.forEach((tag) => {
if (tag.key === 'tts' || tag.key.startsWith('tts-')) {
if (!currentText) {
previous.tags.push(tag);
} else {
remainingTags.push(tag);
}
return;
}
if (tag.key !== 'gloss') {
remainingTags.push(tag);
return;
}
const term = this.normalizeGlossMatchText(tag.value || '');
if (!term) {
remainingTags.push(tag);
return;
}
const matchesCurrent = currentText.includes(term);
const matchesPrevious = previousText.includes(term);
if (!matchesCurrent && matchesPrevious) {
previous.tags.push(tag);
} else {
remainingTags.push(tag);
}
});
return remainingTags;
}
private normalizeGlossMatchText(value: string): string {
return String(value || '')
.normalize('NFC')
.toLocaleLowerCase('de-DE')
.replace(/[.,;:!?()[\]{}"'„“”‚‘’»«]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
private getChoiceTags(choice: any): StoryTag[] {
const directTags = parseTags(choice?.tags || []);
const previewTags = this.extractChoicePreviewTags(choice);