





























@@ -16,6 +16,11 @@ type ParseTtsDirectiveOptions = {
1616preferredProviderId?: string;
1717};
181819+type TextRange = {
20+start: number;
21+end: number;
22+};
23+1924function buildProviderOrder(left: SpeechProviderPlugin, right: SpeechProviderPlugin): number {
2025const leftOrder = left.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
2126const rightOrder = right.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
@@ -53,6 +58,46 @@ function prioritizeProvider(
5358return [preferredProvider, ...providers.filter((provider) => provider.id !== providerId)];
5459}
556061+function collectMarkdownCodeRanges(text: string): TextRange[] {
62+const ranges: TextRange[] = [];
63+const addMatches = (regex: RegExp) => {
64+for (const match of text.matchAll(regex)) {
65+if (match.index == null) {
66+continue;
67+}
68+ranges.push({ start: match.index, end: match.index + match[0].length });
69+}
70+};
71+72+addMatches(/```[\s\S]*?```/g);
73+addMatches(/~~~[\s\S]*?~~~/g);
74+addMatches(/^(?: {4}|\t).*(?:\n|$)/gm);
75+addMatches(/`+[^`\n]*`+/g);
76+77+return ranges.toSorted((left, right) => left.start - right.start);
78+}
79+80+function isInsideRange(index: number, ranges: readonly TextRange[]): boolean {
81+return ranges.some((range) => index >= range.start && index < range.end);
82+}
83+84+function replaceOutsideMarkdownCode(
85+text: string,
86+regex: RegExp,
87+replace: (match: string, captures: readonly string[]) => string,
88+): string {
89+const codeRanges = collectMarkdownCodeRanges(text);
90+return text.replace(regex, (...args: unknown[]) => {
91+const match = String(args[0]);
92+const offset = args.at(-2);
93+if (typeof offset === "number" && isInsideRange(offset, codeRanges)) {
94+return match;
95+}
96+const captures = args.slice(1, -2).map((capture) => String(capture));
97+return replace(match, captures);
98+});
99+}
100+56101export function parseTtsDirectives(
57102text: string,
58103policy: SpeechModelOverridePolicy,
@@ -62,7 +107,7 @@ export function parseTtsDirectives(
62107return { cleanedText: text, overrides: {}, warnings: [], hasDirective: false };
63108}
6410965-if (!/\[\[tts:/iu.test(text)) {
110+if (!/\[\[\s*\/?\s*tts(?:\s*:|\s*\]\])/iu.test(text)) {
66111return { cleanedText: text, overrides: {}, warnings: [], hasDirective: false };
67112}
68113@@ -76,17 +121,27 @@ export function parseTtsDirectives(
76121let cleanedText = text;
77122let hasDirective = false;
7812379-const blockRegex = /\[\[tts:text\]\]([\s\S]*?)\[\[\/tts:text\]\]/gi;
80-cleanedText = cleanedText.replace(blockRegex, (_match, inner: string) => {
124+const blockRegex = /\[\[\s*tts\s*:\s*text\s*\]\]([\s\S]*?)\[\[\s*\/\s*tts\s*:\s*text\s*\]\]/gi;
125+cleanedText = replaceOutsideMarkdownCode(cleanedText, blockRegex, (_match, [inner = ""]) => {
81126hasDirective = true;
82127if (policy.allowText && overrides.ttsText == null) {
83128overrides.ttsText = inner.trim();
84129}
85130return "";
86131});
8713288-const directiveRegex = /\[\[tts:([^\]]+)\]\]/gi;
89-cleanedText = cleanedText.replace(directiveRegex, (_match, body: string) => {
133+const plainBlockRegex = /\[\[\s*tts\s*\]\]([\s\S]*?)\[\[\s*\/\s*tts\s*\]\]/gi;
134+cleanedText = replaceOutsideMarkdownCode(cleanedText, plainBlockRegex, (_match, [inner = ""]) => {
135+hasDirective = true;
136+const visible = inner.trim();
137+if (policy.allowText && overrides.ttsText == null) {
138+overrides.ttsText = visible;
139+}
140+return visible;
141+});
142+143+const directiveRegex = /\[\[\s*tts\s*:\s*([^\]]+)\]\]/gi;
144+cleanedText = replaceOutsideMarkdownCode(cleanedText, directiveRegex, (_match, [body = ""]) => {
90145hasDirective = true;
91146const tokens = body.split(/\s+/).filter(Boolean);
92147@@ -168,6 +223,18 @@ export function parseTtsDirectives(
168223return "";
169224});
170225226+const bareTagRegex = /\[\[\s*tts\s*\]\]/gi;
227+cleanedText = replaceOutsideMarkdownCode(cleanedText, bareTagRegex, () => {
228+hasDirective = true;
229+return "";
230+});
231+232+const closingTagRegex = /\[\[\s*\/\s*tts(?:\s*:\s*[^\]]*)?\]\]/gi;
233+cleanedText = replaceOutsideMarkdownCode(cleanedText, closingTagRegex, () => {
234+hasDirective = true;
235+return "";
236+});
237+171238return {
172239 cleanedText,
173240ttsText: overrides.ttsText,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。