Make the Instruction agent reply in the user's language, at a human pace

The agent answers in the language each message is written in (Indonesian
or English), whatever the UI language; short replies such as "LLM" keep
the conversation's language. inLang() runs t() in that language.

Each turn now reads like a person: a thinking step with what the agent is
doing, the reply typed out with "Thought for Ns" above it, a short typing
pause, then one question, with its options shown once it is typed.
Reduced motion skips the animation.

"dari file LLM" / "from the document X" is read as the topic, and topics
keep the user's capitals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iqbal
2026-10-23 11:38:08 +07:00
parent f55cccb6eb
commit a1d49b37b5
6 changed files with 195 additions and 38 deletions

View File

@@ -97,7 +97,8 @@ export interface ParseResult {
language?: OutputLanguage
}
const TOPIC = /\b(?:soal|tentang|mengenai|seputar|terkait|topik|bertema|perihal|about|regarding|covering|related to|on the topic of|topic)\s+(?!latihan\b)(.+)$/
// "dari file LLM" / "from the document onboarding" names a Brain file, which works as the topic.
const TOPIC = /\b(?:soal|tentang|mengenai|seputar|terkait|topik|bertema|perihal|about|regarding|covering|related to|on the topic of|topic|(?:dari|from)(?: the)? (?:file|berkas|dokumen|documents?|materi|material))\s+(?!latihan\b)(.+)$/
const FREQUENCY_TAIL = /\b(tiap|setiap|per|sehari|seminggu|every|each|daily|weekly|monthly|a day|a week|a month)\b.*$/
function topicOf(clause: string): string {
@@ -176,6 +177,14 @@ export function parseInstruction(text: string): ParseResult {
const topics = new Set(rules.map(r => r.topic).filter(Boolean))
const fallback = shared || (topics.size === 1 ? [...topics][0] : '')
for (const r of rules) if (!r.topic) r.topic = fallback
// Topics are found in lower case; they keep the user's own capitals ("LLM", "HR").
const lowered = text.toLowerCase()
if (lowered.length === text.length) {
for (const r of rules) {
const at = r.topic ? lowered.indexOf(r.topic) : -1
if (at >= 0) r.topic = text.slice(at, at + r.topic.length)
}
}
return language ? { rules, unknown, language } : { rules, unknown }
}