Ask which language the content is written in

The Instruction agent now confirms the output language before the plan is
ready: Bahasa Indonesia, English or 中文, the languages the engine writes
in. It can also be named in the instruction ("pakai bahasa Inggris"), is
read from short replies, and can be changed in the plan. Research alone
is not asked about.

The engine's content production has no language field, so the choice
goes into the agent's instructions; video schedules also set
video_options.language. ruleOf reads it back, so editing a topic keeps
it. The audio player recognises Chinese scripts and uses a zh-CN voice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iqbal
2026-10-23 11:32:06 +07:00
parent 2b37d80e26
commit f55cccb6eb
9 changed files with 164 additions and 31 deletions

View File

@@ -4,12 +4,12 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Icon, ruleIcon } from '../components/Icon'
import { PageHead } from '../components/Page'
import {
dayName, describeRule, kindLabel, KINDS, monthNote, videoCadence, weeklyTarget,
type ContentKind, type ContentRule, type ResearchRule, type Rule,
dayName, describeRule, kindLabel, KINDS, LANGUAGES, languageLabel, monthNote, videoCadence, weeklyTarget,
type ContentKind, type ContentRule, type OutputLanguage, type ResearchRule, type Rule,
} from '../express/parse'
import { ask, editRule, EMPTY, nextQuestion, removeRule, respond, type AgentState } from '../express/agent'
import { t, type Key } from '../i18n'
import { budgetFor, canSchedule, createSchedule, VIDEO, videoQuote } from '../express/schedules'
import { budgetFor, canSchedule, createSchedule, defaultLanguage, VIDEO, videoQuote } from '../express/schedules'
const EXAMPLES: Key[] = ['ins.ex1', 'ins.ex2', 'ins.ex3', 'ins.ex4', 'ins.ex5']
@@ -29,13 +29,18 @@ export function InstructionPage() {
const question = nextQuestion(agent)
const ready = agent.items.filter(i => canSchedule(i.rule))
const hasContent = agent.items.some(i => i.rule.type === 'content')
// The agent's current turn: its question, or what it is waiting for. It stays live, so an edit in the plan updates it.
const live = saving
? { text: t('agent.approving'), options: [] }
: question
? ask(question, agent)
: agent.items.length
? { text: t('agent.ready', { n: ready.length }), options: [t('agent.optApprove'), t('agent.optReset')] }
? {
text: hasContent && agent.language
? t('agent.readyLanguage', { n: ready.length, language: languageLabel(agent.language) })
: t('agent.ready', { n: ready.length }),
options: [t('agent.optApprove'), t('agent.optReset')] }
// The greeting comes once; after that the examples alone invite the next request.
: { text: messages.length ? '' : t('agent.greeting'), options: EXAMPLES.map(k => t(k)) }
@@ -64,9 +69,11 @@ export function InstructionPage() {
const done: Outcome[] = []
const created = new Set<number>()
// One at a time so a failure (e.g. not an organization owner) is reported per schedule.
// Approving from the plan before the agent asked about the language keeps the old default.
const language = state.language ?? defaultLanguage()
for (const { id, rule } of items) {
try {
await createSchedule(rule)
await createSchedule(rule.type === 'content' ? { ...rule, language } : rule)
done.push({ rule, ok: true })
created.add(id)
} catch (e) {
@@ -120,6 +127,15 @@ export function InstructionPage() {
<div className="card">
<div className="card-head"><h3>{t('ins.previewTitle')}</h3><span className="badge neutral">{t('ins.readyCount', { n: ready.length })}</span></div>
{agent.items.length === 0 && <div className="empty"><p>{t('agent.planEmpty')}</p></div>}
{hasContent && (
<label className="plan-language">
<span>{t('ins.language')}</span>
<select value={agent.language ?? ''} onChange={e => setAgent(s => ({ ...s, language: e.target.value as OutputLanguage }))}>
{!agent.language && <option value="" disabled>{t('ins.languagePending')}</option>}
{LANGUAGES.map(l => <option key={l} value={l}>{languageLabel(l)}</option>)}
</select>
</label>
)}
<div className="rule-list">
{agent.items.map(({ id, rule }) => (
<RuleCard key={id} rule={rule} onChange={p => setAgent(s => editRule(s, id, p))} onRemove={() => setAgent(s => removeRule(s, id))} />