Remove the Brain panel from the Brain and AI page
The page is back to the chat and the plan side by side. With nothing to tick, new schedules always use the whole Brain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'
|
|||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { Icon, ruleIcon } from '../components/Icon'
|
import { Icon, ruleIcon } from '../components/Icon'
|
||||||
import { PageHead } from '../components/Page'
|
import { PageHead } from '../components/Page'
|
||||||
import { BrainPanel, choosable, useDocuments } from './Brain'
|
import { choosable, useDocuments } from './Brain'
|
||||||
import {
|
import {
|
||||||
dayName, describeRule, kindLabel, KINDS, LANGUAGES, languageLabel, monthNote, videoCadence, weeklyTarget,
|
dayName, describeRule, kindLabel, KINDS, LANGUAGES, languageLabel, monthNote, videoCadence, weeklyTarget,
|
||||||
type ContentKind, type ContentRule, type OutputLanguage, type ResearchRule, type Rule,
|
type ContentKind, type ContentRule, type OutputLanguage, type ResearchRule, type Rule,
|
||||||
@@ -36,14 +36,7 @@ export function InstructionPage() {
|
|||||||
const [agent, setAgent] = useState<AgentState>(EMPTY)
|
const [agent, setAgent] = useState<AgentState>(EMPTY)
|
||||||
const [messages, setMessages] = useState<Message[]>([])
|
const [messages, setMessages] = useState<Message[]>([])
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
// Brain material the schedules are made from; none chosen means the whole Brain.
|
|
||||||
const [selected, setSelected] = useState<Set<number>>(new Set())
|
|
||||||
const docs = useDocuments()
|
const docs = useDocuments()
|
||||||
// A chosen document that was deleted, or failed since, no longer counts.
|
|
||||||
const chosenDocs = choosable(docs.data).filter(d => selected.has(d.docId))
|
|
||||||
const chosen = chosenDocs.map(d => d.docId)
|
|
||||||
// How the chat and the plan name the chosen material: by title when there is one.
|
|
||||||
const sources = chosenDocs.length === 1 ? `“${chosenDocs[0].title}”` : chosenDocs.length ? t('brainPanel.nChosen', { n: chosenDocs.length }) : null
|
|
||||||
const [phase, setPhase] = useState<Phase>('idle')
|
const [phase, setPhase] = useState<Phase>('idle')
|
||||||
// The live question whose typing has finished, so its reply options can show.
|
// The live question whose typing has finished, so its reply options can show.
|
||||||
const [typedLive, setTypedLive] = useState('')
|
const [typedLive, setTypedLive] = useState('')
|
||||||
@@ -55,19 +48,16 @@ export function InstructionPage() {
|
|||||||
const later = (fn: () => void, ms: number) => { timers.current.push(window.setTimeout(fn, ms)) }
|
const later = (fn: () => void, ms: number) => { timers.current.push(window.setTimeout(fn, ms)) }
|
||||||
useEffect(() => () => timers.current.forEach(clearTimeout), [])
|
useEffect(() => () => timers.current.forEach(clearTimeout), [])
|
||||||
|
|
||||||
// The agent sees the current choice, so ticking material in the Brain panel updates its question and plan.
|
const question = nextQuestion(agent)
|
||||||
const view: AgentState = { ...agent, sources }
|
|
||||||
const question = nextQuestion(view)
|
|
||||||
const ready = agent.items.filter(i => canSchedule(i.rule))
|
const ready = agent.items.filter(i => canSchedule(i.rule))
|
||||||
// Without a choice the whole Brain is attached, so every ready document counts.
|
// The whole Brain is attached, so every ready document counts.
|
||||||
const sourceCount = chosen.length || choosable(docs.data).length
|
const tooFewSources = choosable(docs.data).length < MIN_PRODUCTION_SOURCES && ready.some(i => needsTwoSources(i.rule))
|
||||||
const tooFewSources = sourceCount < MIN_PRODUCTION_SOURCES && ready.some(i => needsTwoSources(i.rule))
|
|
||||||
const hasContent = agent.items.some(i => i.rule.type === 'content')
|
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.
|
// 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 = inLang(lang, () => saving
|
const live = inLang(lang, () => saving
|
||||||
? { text: t('agent.approving'), options: [] as string[] }
|
? { text: t('agent.approving'), options: [] as string[] }
|
||||||
: question
|
: question
|
||||||
? ask(question, view)
|
? ask(question, agent)
|
||||||
: agent.items.length
|
: agent.items.length
|
||||||
? {
|
? {
|
||||||
text: hasContent && agent.language
|
text: hasContent && agent.language
|
||||||
@@ -89,7 +79,7 @@ export function InstructionPage() {
|
|||||||
// A picked option is in the conversation's language already; typed text may switch it.
|
// A picked option is in the conversation's language already; typed text may switch it.
|
||||||
const replyLang = live.options.includes(input) ? lang : chatLangOf(input) ?? lang
|
const replyLang = live.options.includes(input) ? lang : chatLangOf(input) ?? lang
|
||||||
setChatLang(replyLang)
|
setChatLang(replyLang)
|
||||||
const turn = inLang(replyLang, () => respond(view, input))
|
const turn = inLang(replyLang, () => respond(agent, input))
|
||||||
// The question being answered is kept in the history, with the reply after it.
|
// The question being answered is kept in the history, with the reply after it.
|
||||||
setMessages(ms => [...ms, ...(live.text ? [{ role: 'agent' as const, text: live.text }] : []), { role: 'user', text: input.trim() }])
|
setMessages(ms => [...ms, ...(live.text ? [{ role: 'agent' as const, text: live.text }] : []), { role: 'user', text: input.trim() }])
|
||||||
setText('')
|
setText('')
|
||||||
@@ -120,8 +110,8 @@ export function InstructionPage() {
|
|||||||
// One at a time so a failure (e.g. not an organization owner) is reported per schedule.
|
// One at a time so a failure (e.g. not an organization owner) is reported per schedule.
|
||||||
for (const { id, rule } of items) {
|
for (const { id, rule } of items) {
|
||||||
try {
|
try {
|
||||||
await createSchedule(rule.type === 'content' ? { ...rule, language } : rule, chosen)
|
await createSchedule(rule.type === 'content' ? { ...rule, language } : rule)
|
||||||
done.push({ rule: rule.type === 'content' && chosen.length ? { ...rule, chosen: true } : rule, ok: true })
|
done.push({ rule, ok: true })
|
||||||
created.add(id)
|
created.add(id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done.push({ rule, ok: false, message: (e as Error).message })
|
done.push({ rule, ok: false, message: (e as Error).message })
|
||||||
@@ -142,9 +132,7 @@ export function InstructionPage() {
|
|||||||
<>
|
<>
|
||||||
<PageHead title={t('ins.title')} description={t('ins.description')} />
|
<PageHead title={t('ins.title')} description={t('ins.description')} />
|
||||||
|
|
||||||
<div className="unified-workspace">
|
<div className="ai-workspace">
|
||||||
<BrainPanel selected={selected} onSelect={setSelected} />
|
|
||||||
<div className="ai-column">
|
|
||||||
<div className="card agent-chat">
|
<div className="card agent-chat">
|
||||||
<div className="card-head">
|
<div className="card-head">
|
||||||
<div className="assistant-title">
|
<div className="assistant-title">
|
||||||
@@ -196,15 +184,15 @@ export function InstructionPage() {
|
|||||||
)}
|
)}
|
||||||
<div className="rule-list">
|
<div className="rule-list">
|
||||||
{agent.items.map(({ id, rule }) => (
|
{agent.items.map(({ id, rule }) => (
|
||||||
<RuleCard key={id} rule={rule} from={sources ?? undefined} onChange={p => setAgent(s => editRule(s, id, p))} onRemove={() => setAgent(s => removeRule(s, id))} />
|
<RuleCard key={id} rule={rule} onChange={p => setAgent(s => editRule(s, id, p))} onRemove={() => setAgent(s => removeRule(s, id))} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{ready.length > 0 && (
|
{ready.length > 0 && (
|
||||||
<div className="rule-approve">
|
<div className="rule-approve">
|
||||||
<small>
|
<small>
|
||||||
<b className="rule-sources"><Icon name="brain" />{chosen.length ? t('ins.sourcesChosen', { n: chosen.length }) : t('ins.sourcesAll')}</b>
|
<b className="rule-sources"><Icon name="brain" />{t('ins.sourcesAll')}</b>
|
||||||
{t('ins.approveNote')}
|
{t('ins.approveNote')}
|
||||||
{tooFewSources && <span className="error">{t(chosen.length ? 'ins.tooFewChosen' : 'ins.tooFewInBrain', { n: MIN_PRODUCTION_SOURCES })}</span>}
|
{tooFewSources && <span className="error">{t('ins.tooFewInBrain', { n: MIN_PRODUCTION_SOURCES })}</span>}
|
||||||
</small>
|
</small>
|
||||||
<button className="btn primary" disabled={saving || !idle || tooFewSources} onClick={() => approve()}>
|
<button className="btn primary" disabled={saving || !idle || tooFewSources} onClick={() => approve()}>
|
||||||
<Icon name="check" />{saving ? t('ins.approving') : t('ins.approve', { n: ready.length })}
|
<Icon name="check" />{saving ? t('ins.approving') : t('ins.approve', { n: ready.length })}
|
||||||
@@ -213,7 +201,6 @@ export function InstructionPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user