Files
learning-lab-express/src/express/agent.test.ts
Iqbal 3b56c27210 Show the Brain beside the AI chat and make schedules from chosen material
The Brain and AI page lists Brain material (failed documents hidden) with
search, open, delete and per-material or select-all ticks. New schedules use
only the ticked material, kept through topic edits and resumes; nothing ticked
means the whole Brain. With material ticked, the agent skips the content topic
question and names the material in the chat and plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:53:38 +07:00

97 lines
4.5 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { chatLangOf, EMPTY, nextQuestion, respond, type AgentState } from './agent'
import { t } from '../i18n'
const say = (...inputs: string[]) => inputs.reduce<AgentState>((s, input) => respond(s, input).state, EMPTY)
const rules = (s: AgentState) => s.items.map(i => i.rule)
describe('instruction agent', () => {
it('needs no question when the instruction is complete', () => {
const s = say('make 2 summaries per day about leadership in english')
expect(s.language).toBe('English')
expect(nextQuestion(s)).toBeNull()
expect(respond(s, 'yes').approve).toBe(true)
})
it('asks for a topic, and takes the reply as the topic', () => {
const s = say('make 3 quizzes per week')
expect(nextQuestion(s)?.type).toBe('topic')
expect(rules(say('make 3 quizzes per week', 'negotiation'))[0]).toMatchObject({ topic: 'negotiation' })
})
it('stops asking for a topic once the whole Brain is chosen', () => {
expect(nextQuestion(say('make 3 quizzes per week', t('agent.optWholeBrain')))?.type).toBe('language')
})
it('asks no topic for content when material is chosen, and names that material in the plan', () => {
const s = { ...say('make 1 scorm per week'), sources: '“LLM”' }
expect(nextQuestion(s)?.type).toBe('language')
expect(respond({ ...EMPTY, sources: '“LLM”' }, 'make 1 scorm per week').reply).toMatch(/from “LLM”/)
})
it('still asks what to research on the internet when material is chosen', () => {
expect(nextQuestion({ ...say('research every day'), sources: '“LLM”' })?.type).toBe('topic')
})
it('asks about a video count that has no cadence', () => {
const s = say('3 videos per week about onboarding')
expect(nextQuestion(s)?.type).toBe('video')
expect(rules(say('3 videos per week about onboarding', t('agent.optPerDay')))[0]).toMatchObject({ count: 1, per: 'day' })
})
it('asks which day weekly research runs when none was named', () => {
const s = say('research every week about sales')
expect(nextQuestion(s)?.type).toBe('weekday')
const answered = say('research every week about sales', 'thursday')
expect(rules(answered)[0]).toMatchObject({ weekday: 3, weekdayGuessed: undefined })
expect(nextQuestion(answered)).toBeNull()
})
it('asks about parts it did not understand, and can skip them', () => {
const s = say('2 summaries per day about sales. hello there')
expect(nextQuestion(s)).toMatchObject({ type: 'unknown', text: 'hello there' })
expect(nextQuestion(say('2 summaries per day about sales. hello there', 'skip'))?.type).toBe('language')
})
it('asks which language the content is written in', () => {
expect(nextQuestion(say('3 quizzes per week about sales'))?.type).toBe('language')
expect(say('3 quizzes per week about sales', 'mandarin').language).toBe('中文')
expect(say('3 quizzes per week about sales', 'indo').language).toBe('Bahasa Indonesia')
expect(nextQuestion(say('3 quizzes per week about sales', '中文 (Mandarin)'))).toBeNull()
})
it('reads the language from the instruction without leaving it in the topic', () => {
const s = say('bikin 3 kuis per minggu soal negosiasi pakai bahasa inggris')
expect(s.language).toBe('English')
expect(rules(s)[0]).toMatchObject({ topic: 'negosiasi' })
expect(s.unknown).toEqual([])
})
it('does not ask about the language for research alone', () => {
expect(nextQuestion(say('research every day about sales in China'))).toBeNull()
})
it('treats a reply with a frequency as a new request', () => {
const s = say('make 3 quizzes per week', '1 video per week about onboarding')
expect(rules(s).map(r => r.type === 'content' && r.kind)).toEqual(['quiz', 'video'])
})
it('caps a schedule that is over the weekly limit', () => {
const s = say('10 summaries per day about sales')
expect(nextQuestion(s)?.type).toBe('tooMany')
expect(rules(say('10 summaries per day about sales', '30 per week'))[0]).toMatchObject({ count: 30, per: 'week' })
})
it('takes "dari file X" as the topic', () => {
expect(rules(say('Saya mau buat video dari file LLM'))[0]).toMatchObject({ kind: 'video', topic: 'LLM' })
})
})
describe('chatLangOf', () => {
it('tells Indonesian from English, and stays undecided on short replies', () => {
expect(chatLangOf('Saya mau buat video dari file LLM')).toBe('id')
expect(chatLangOf('I want to make 2 quizzes per week')).toBe('en')
expect(chatLangOf('LLM')).toBeNull()
})
})