mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 03:52:48 +05:30
fix(voice): Quote splitting based on single and double quotes
This commit is contained in:
@@ -88,7 +88,7 @@
|
||||
"locationId": "white-room",
|
||||
"intent": {
|
||||
"type": "action",
|
||||
"content": "entity@bf3f29d2-cf11-4b11-9a99-b13c126d400e[I] wake up from my sleep.",
|
||||
"content": "entity@bf3f29d2-cf11-4b11-9a99-b13c126d400e wakes up from his sleep.",
|
||||
"actorId": "bf3f29d2-cf11-4b11-9a99-b13c126d400e",
|
||||
"targetIds": []
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WorldState, resolveAlias } from "@omnia/core";
|
||||
import { ILLMProvider } from "@omnia/llm";
|
||||
import { dehydrate } from "@omnia/voice";
|
||||
import { dehydrate, expandContractions } from "@omnia/voice";
|
||||
import { Intent, IntentSequence, LLMIntentSequenceSchema } from "./intent.js";
|
||||
|
||||
export class IntentDecoder {
|
||||
@@ -21,6 +21,7 @@ export class IntentDecoder {
|
||||
narrativeProse: string,
|
||||
recentIntents: Intent[] = [],
|
||||
): Promise<IntentSequence> {
|
||||
const processedProse = expandContractions(narrativeProse);
|
||||
const actor = worldState.getEntity(actorId);
|
||||
|
||||
// 1. Get other entities co-located in the same context
|
||||
@@ -75,6 +76,8 @@ For each intent you must:
|
||||
2. Extract the original narrative text fragment from the prose that corresponds to this intent and populate it as "content". Do not paraphrase, do not convert to third person, and do not convert to second person. Keep the original text fragment exactly as written in the prose (first-person voice).
|
||||
3. Identify targetIds — the entity IDs of the receiving parties. Use the "Other entities in context" list to resolve any subjective names, aliases, or descriptions used in the prose to their correct entity IDs. If no specific target, use an empty array.
|
||||
4. Identify modifiers — a list of strings representing additional qualities or modifiers extracted from the narrative prose. This includes emotions, tone of voice, speed, manner of action, or statement type (e.g., "question", "anxious", "whispering", "slowly", "quietly", "forcefully"). If no modifiers are present, use an empty array.
|
||||
5. For dialogue intents always use the following format for content field:
|
||||
I say "<dialogue>" (optionally: to him/her/alias).
|
||||
`.trim();
|
||||
|
||||
const userContext = `
|
||||
@@ -85,7 +88,7 @@ Historical Context:
|
||||
${historicalContext}
|
||||
|
||||
=== NARRATIVE PROSE ===
|
||||
${narrativeProse}
|
||||
${processedProse}
|
||||
`.trim();
|
||||
|
||||
const response = await this.llmProvider.generateStructuredResponse({
|
||||
|
||||
82
packages/voice/src/contractions.ts
Normal file
82
packages/voice/src/contractions.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { splitQuotes } from "./dehydration.js";
|
||||
|
||||
/**
|
||||
* Preprocessor that expands common contractions (e.g. "he's" -> "he is", "I'm" -> "I am")
|
||||
* in non-quote segments of a text, keeping dialogue segments untouched.
|
||||
*/
|
||||
export function expandContractions(text: string): string {
|
||||
if (!text) return "";
|
||||
|
||||
const contractionMap: Record<string, string> = {
|
||||
"i'm": "I am",
|
||||
"you're": "you are",
|
||||
"he's": "he is",
|
||||
"she's": "she is",
|
||||
"it's": "it is",
|
||||
"we're": "we are",
|
||||
"they're": "they are",
|
||||
"i've": "I have",
|
||||
"you've": "you have",
|
||||
"we've": "we have",
|
||||
"they've": "they have",
|
||||
"i'd": "I would",
|
||||
"you'd": "you would",
|
||||
"he'd": "he would",
|
||||
"she'd": "she would",
|
||||
"we'd": "we would",
|
||||
"they'd": "they would",
|
||||
"i'll": "I will",
|
||||
"you'll": "you will",
|
||||
"he'll": "he will",
|
||||
"she'll": "she will",
|
||||
"we'll": "we will",
|
||||
"they'll": "they will",
|
||||
"isn't": "is not",
|
||||
"aren't": "are not",
|
||||
"wasn't": "was not",
|
||||
"weren't": "were not",
|
||||
"haven't": "have not",
|
||||
"hasn't": "has not",
|
||||
"hadn't": "had not",
|
||||
"won't": "will not",
|
||||
"wouldn't": "would not",
|
||||
"don't": "do not",
|
||||
"doesn't": "does not",
|
||||
"didn't": "did not",
|
||||
"can't": "cannot",
|
||||
"couldn't": "could not",
|
||||
"shouldn't": "should not",
|
||||
"mightn't": "might not",
|
||||
"mustn't": "must not",
|
||||
};
|
||||
|
||||
const segments = splitQuotes(text);
|
||||
const processed = segments.map((seg) => {
|
||||
if (seg.isQuote) {
|
||||
return `"${seg.text}"`;
|
||||
}
|
||||
|
||||
let chunk = seg.text;
|
||||
Object.entries(contractionMap).forEach(([contraction, replacement]) => {
|
||||
const escaped = contraction.replace("'", "'");
|
||||
const regex = new RegExp(`\\b${escaped}\\b`, "gi");
|
||||
chunk = chunk.replace(regex, (matched) => {
|
||||
const isCapitalized = matched[0] === matched[0].toUpperCase();
|
||||
let finalRep = replacement;
|
||||
if (isCapitalized) {
|
||||
finalRep = finalRep[0].toUpperCase() + finalRep.slice(1);
|
||||
} else {
|
||||
if (finalRep.startsWith("I ")) {
|
||||
// Keep I capitalized
|
||||
} else {
|
||||
finalRep = finalRep[0].toLowerCase() + finalRep.slice(1);
|
||||
}
|
||||
}
|
||||
return finalRep;
|
||||
});
|
||||
});
|
||||
return chunk;
|
||||
});
|
||||
|
||||
return processed.join("");
|
||||
}
|
||||
@@ -14,13 +14,12 @@ export function splitQuotes(text: string): Segment[] {
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i];
|
||||
if ((char === '"' || char === "'") && (!inQuote || char === quoteChar)) {
|
||||
if (char === '"') {
|
||||
if (current) {
|
||||
segments.push({ text: current, isQuote: inQuote });
|
||||
current = "";
|
||||
}
|
||||
inQuote = !inQuote;
|
||||
quoteChar = inQuote ? char : "";
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./dehydration.js";
|
||||
export * from "./hydration.js";
|
||||
export * from "./contractions.js";
|
||||
|
||||
Reference in New Issue
Block a user