|
1 | 1 | // src/utils/chatUtils.ts |
2 | 2 | import l2d from '@/utils/json/l2d.json' |
3 | 3 |
|
4 | | -// Helper to identify speaker labels |
5 | | -const isSpeakerLabel = (s: string) => /^\s*(?:\*\*)?[^*]+?(?:\*\*)?\s*:\s*$/.test(s) |
| 4 | +// Helper to identify speaker labels. |
| 5 | +// Supports plain "Name:" and bolded "**Name:**" (colon inside the bold). |
| 6 | +const isSpeakerLabel = (s: string) => { |
| 7 | + const normalized = (s || '').replace(/\u00A0/g, ' ').trim() |
| 8 | + return /^\s*(?:\*\*)?\s*[^*:\n]+?\s*:\s*(?:\*\*)?\s*$/.test(normalized) |
| 9 | +} |
6 | 10 |
|
7 | 11 | // Helper to check if a word appears as a whole word in text (case-insensitive) |
8 | 12 | export const isWholeWordPresent = (text: string, word: string): boolean => { |
@@ -282,13 +286,18 @@ export const sanitizeActions = (actions: any[]): any[] => { |
282 | 286 | while ((match = dialogueRegex.exec(text)) !== null) { |
283 | 287 | // Add any narration before this dialogue |
284 | 288 | const narrationBefore = text.substring(lastIndex, match.index).trim() |
285 | | - if (narrationBefore) { |
| 289 | + // If this chunk is ONLY a speaker label (e.g. "**Chime:**"), merge it into the dialogue. |
| 290 | + // This prevents creating a separate narration action for the label. |
| 291 | + const isLabelOnly = !!narrationBefore && isSpeakerLabel(narrationBefore) |
| 292 | + |
| 293 | + if (narrationBefore && !isLabelOnly) { |
286 | 294 | parts.push({ text: narrationBefore, isDialogue: false }) |
287 | 295 | } |
288 | | - |
| 296 | + |
289 | 297 | // Add the dialogue (with quotes) |
290 | 298 | const dialogue = match[0] // Full match including quotes |
291 | | - parts.push({ text: dialogue, isDialogue: true }) |
| 299 | + const mergedDialogue = isLabelOnly ? `${narrationBefore} ${dialogue}`.trim() : dialogue |
| 300 | + parts.push({ text: mergedDialogue, isDialogue: true }) |
292 | 301 |
|
293 | 302 | lastIndex = match.index + match[0].length |
294 | 303 | } |
|
0 commit comments