Rename Brain to Upload material and Instruction to Brain and AI
Upload material (/upload) drops the metric cards for a larger upload box above the material list; Brain and AI moves to /brain. Old paths redirect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
status = 200
|
||||
force = true
|
||||
|
||||
# BrowserRouter: /brain, /results, ... all load index.html.
|
||||
# BrowserRouter: /upload, /results, ... all load index.html.
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
|
||||
11
src/App.tsx
11
src/App.tsx
@@ -13,14 +13,15 @@ export default function App() {
|
||||
<Routes>
|
||||
<Route element={<AppLayout />}>
|
||||
<Route path="/production" element={<ProductionPage />} />
|
||||
<Route path="/brain" element={<BrainPage />} />
|
||||
<Route path="/instruction" element={<InstructionPage />} />
|
||||
<Route path="/upload" element={<BrainPage />} />
|
||||
<Route path="/brain" element={<InstructionPage />} />
|
||||
<Route path="/schedule" element={<SchedulePage />} />
|
||||
<Route path="/results" element={<ResultsPage />} />
|
||||
</Route>
|
||||
{/* Indonesian paths from before the routes were renamed, so old links keep working. */}
|
||||
<Route path="/otak" element={<Navigate replace to="/brain" />} />
|
||||
<Route path="/perintah" element={<Navigate replace to="/instruction" />} />
|
||||
{/* Paths from before the routes were renamed, so old links keep working. */}
|
||||
<Route path="/instruction" element={<Navigate replace to="/brain" />} />
|
||||
<Route path="/otak" element={<Navigate replace to="/upload" />} />
|
||||
<Route path="/perintah" element={<Navigate replace to="/brain" />} />
|
||||
<Route path="/jadwal" element={<Navigate replace to="/schedule" />} />
|
||||
<Route path="/hasil" element={<Navigate replace to="/results" />} />
|
||||
<Route path="*" element={<Navigate replace to="/production" />} />
|
||||
|
||||
@@ -33,7 +33,7 @@ export function AuthGate({ children }: { children: ReactNode }) {
|
||||
<div>
|
||||
<h2>{t('auth.pickOrg')}</h2>
|
||||
<p>{t('auth.pickOrgCopy')}</p>
|
||||
<OrganizationList hidePersonal afterSelectOrganizationUrl="/brain" afterCreateOrganizationUrl="/brain" />
|
||||
<OrganizationList hidePersonal afterSelectOrganizationUrl="/upload" afterCreateOrganizationUrl="/upload" />
|
||||
</div>
|
||||
</AuthScreen>
|
||||
)
|
||||
|
||||
@@ -17,8 +17,8 @@ export const NAV_GROUPS: { label: Key; items: { to: string; label: Key; icon: Ic
|
||||
{
|
||||
label: 'nav.settings',
|
||||
items: [
|
||||
{ to: '/brain', label: 'nav.brain', icon: 'brain' },
|
||||
{ to: '/instruction', label: 'nav.instruction', icon: 'spark' },
|
||||
{ to: '/upload', label: 'nav.brain', icon: 'brain' },
|
||||
{ to: '/brain', label: 'nav.instruction', icon: 'spark' },
|
||||
{ to: '/schedule', label: 'nav.schedule', icon: 'calendar' },
|
||||
{ to: '/results', label: 'nav.results', icon: 'layers' },
|
||||
],
|
||||
|
||||
36
src/i18n.ts
36
src/i18n.ts
@@ -7,8 +7,8 @@ export const LANGS: { code: Lang; label: string }[] = [{ code: 'en', label: 'EN'
|
||||
|
||||
const en = {
|
||||
// Layout
|
||||
'nav.brain': 'Brain',
|
||||
'nav.instruction': 'Instruction',
|
||||
'nav.brain': 'Upload material',
|
||||
'nav.instruction': 'Brain and AI',
|
||||
'nav.schedule': 'Schedule',
|
||||
'nav.results': 'Results',
|
||||
'nav.dashboard': 'Dashboard',
|
||||
@@ -81,8 +81,8 @@ const en = {
|
||||
'prod.more': '+{n} more in Results',
|
||||
|
||||
// Brain
|
||||
'brain.title': 'Brain',
|
||||
'brain.description': 'All team material is processed into one Brain that keeps growing. New content the AI creates goes in here too.',
|
||||
'brain.title': 'Upload material',
|
||||
'brain.description': 'Upload your team’s training material here. Every file is processed into the Brain the AI creates content from.',
|
||||
'brain.metricMaterial': 'Material',
|
||||
'brain.metricMaterialSub': 'documents in the workspace',
|
||||
'brain.metricReady': 'In the Brain',
|
||||
@@ -126,8 +126,8 @@ const en = {
|
||||
'brain.deleteConfirm': 'Delete “{name}” from the Brain?',
|
||||
|
||||
// Instruction
|
||||
'ins.title': 'Instruction',
|
||||
'ins.description': 'Write a plain instruction. The AI turns it into research and content schedules that keep running until you stop them.',
|
||||
'ins.title': 'Brain and AI',
|
||||
'ins.description': 'Tell the AI what to make from the Brain. It turns your instruction into research and content schedules that keep running until you stop them.',
|
||||
'ins.label': 'Instruction',
|
||||
'ins.ex1': 'Make 2 summaries per day and 1 quiz per week about leadership',
|
||||
'ins.ex2': 'Research the internet every day about leadership',
|
||||
@@ -402,10 +402,10 @@ const en = {
|
||||
'help.boxBody': 'Four steps from your material to finished content.',
|
||||
'help.open': 'View the guide',
|
||||
'help.title': 'How Learning Lab Express works',
|
||||
'help.subtitle': 'Brain → Instruction → Schedule → Results',
|
||||
'help.brainTitle': 'Brain',
|
||||
'help.subtitle': 'Upload material → Brain and AI → Schedule → Results',
|
||||
'help.brainTitle': 'Upload material',
|
||||
'help.brainBody': 'Upload your training material. Once its knowledge is built, the AI creates everything from it.',
|
||||
'help.instructionTitle': 'Instruction',
|
||||
'help.instructionTitle': 'Brain and AI',
|
||||
'help.instructionBody': 'Say what you need in one sentence, e.g. “3 quizzes per week and 1 video per week about leadership”. Check the schedules, then approve them.',
|
||||
'help.scheduleTitle': 'Schedule',
|
||||
'help.scheduleBody': 'Schedules run on their own. Pause them, change the topic, or produce now; each stays within its credit limit.',
|
||||
@@ -458,8 +458,8 @@ const id: Record<Key, string> = {
|
||||
'prod.nowTitle': 'Sedang berjalan',
|
||||
'prod.nothingNow': 'Tidak ada yang sedang dibuat.',
|
||||
'prod.more': '+{n} lagi di Hasil',
|
||||
'nav.brain': 'Otak',
|
||||
'nav.instruction': 'Perintah',
|
||||
'nav.brain': 'Upload materi',
|
||||
'nav.instruction': 'Brain and AI',
|
||||
'nav.schedule': 'Jadwal',
|
||||
'nav.results': 'Hasil',
|
||||
'nav.dashboard': 'Dasbor',
|
||||
@@ -496,8 +496,8 @@ const id: Record<Key, string> = {
|
||||
'common.retry': 'Coba lagi',
|
||||
'common.credits': '{n} kredit',
|
||||
|
||||
'brain.title': 'Otak',
|
||||
'brain.description': 'Semua materi tim diproses menjadi satu Otak yang terus bertambah. Konten baru yang dihasilkan juga masuk ke sini.',
|
||||
'brain.title': 'Upload materi',
|
||||
'brain.description': 'Unggah materi pelatihan tim di sini. Setiap file diproses ke dalam Otak yang menjadi sumber AI membuat konten.',
|
||||
'brain.metricMaterial': 'Materi',
|
||||
'brain.metricMaterialSub': 'dokumen di ruang kerja',
|
||||
'brain.metricReady': 'Masuk Otak',
|
||||
@@ -540,8 +540,8 @@ const id: Record<Key, string> = {
|
||||
'brain.open': 'Buka {name}',
|
||||
'brain.deleteConfirm': 'Hapus “{name}” dari Otak?',
|
||||
|
||||
'ins.title': 'Perintah',
|
||||
'ins.description': 'Tulis instruksi biasa. AI menyusunnya menjadi jadwal riset dan pembuatan konten, lalu terus berjalan sampai Anda menghentikannya.',
|
||||
'ins.title': 'Brain and AI',
|
||||
'ins.description': 'Beri tahu AI apa yang perlu dibuat dari Otak. AI menyusun instruksi Anda menjadi jadwal riset dan pembuatan konten yang terus berjalan sampai Anda menghentikannya.',
|
||||
'ins.label': 'Instruksi',
|
||||
'ins.ex1': 'Bikin 2 ringkasan per hari dan 1 kuis per minggu soal leadership',
|
||||
'ins.ex2': 'Riset internet tiap hari soal leadership',
|
||||
@@ -813,10 +813,10 @@ const id: Record<Key, string> = {
|
||||
'help.boxBody': 'Empat langkah dari materi sampai konten jadi.',
|
||||
'help.open': 'Lihat panduan',
|
||||
'help.title': 'Cara kerja Learning Lab Express',
|
||||
'help.subtitle': 'Otak → Perintah → Jadwal → Hasil',
|
||||
'help.brainTitle': 'Otak',
|
||||
'help.subtitle': 'Upload materi → Brain and AI → Jadwal → Hasil',
|
||||
'help.brainTitle': 'Upload materi',
|
||||
'help.brainBody': 'Unggah materi pelatihan. Setelah pengetahuannya dibangun, AI membuat semua konten dari situ.',
|
||||
'help.instructionTitle': 'Perintah',
|
||||
'help.instructionTitle': 'Brain and AI',
|
||||
'help.instructionBody': 'Tulis kebutuhan Anda dalam satu kalimat, misalnya “3 kuis per minggu dan 1 video per minggu soal leadership”. Periksa jadwalnya, lalu setujui.',
|
||||
'help.scheduleTitle': 'Jadwal',
|
||||
'help.scheduleBody': 'Jadwal berjalan sendiri. Jeda, ganti topik, atau produksi sekarang; masing-masing tetap dalam batas kreditnya.',
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type Source,
|
||||
} from '../api/engine'
|
||||
import { Icon } from '../components/Icon'
|
||||
import { Empty, PageHead, Skeleton, SkeletonLines, SkeletonRows } from '../components/Page'
|
||||
import { Empty, PageHead, SkeletonLines, SkeletonRows } from '../components/Page'
|
||||
import { toast } from '../components/Toast'
|
||||
import { locale, t } from '../i18n'
|
||||
|
||||
@@ -28,17 +28,10 @@ export function BrainPage() {
|
||||
// Poll quickly while anything is processing or building; slowly otherwise (colleagues may upload too).
|
||||
refetchInterval: q => (q.state.data?.some(d => phaseOf(d) === 'working') ? 3000 : 20_000),
|
||||
})
|
||||
const ready = docs.data?.filter(d => phaseOf(d) === 'ready').length ?? 0
|
||||
const waiting = docs.data?.filter(d => phaseOf(d) === 'answer').length ?? 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={t('brain.title')} description={t('brain.description')} />
|
||||
<div className="metrics">
|
||||
<div className="metric"><span>{t('brain.metricMaterial')}</span>{docs.data ? <strong>{docs.data.length}</strong> : <Skeleton className="skel-num" />}<small>{t('brain.metricMaterialSub')}</small></div>
|
||||
<div className="metric"><span>{t('brain.metricReady')}</span>{docs.data ? <strong>{ready}</strong> : <Skeleton className="skel-num" />}<small>{t('brain.metricReadySub')}</small></div>
|
||||
<div className="metric"><span>{t('brain.metricAction')}</span>{docs.data ? <strong>{waiting}</strong> : <Skeleton className="skel-num" />}<small>{t('brain.metricActionSub')}</small></div>
|
||||
</div>
|
||||
<UploadBox />
|
||||
<div className="card">
|
||||
<div className="card-head"><h3>{t('brain.listTitle')}</h3><Icon name="brain" /></div>
|
||||
@@ -116,7 +109,7 @@ function UploadBox() {
|
||||
|
||||
const busy = items.some(i => i.state === 'uploading')
|
||||
return (
|
||||
<div className={`upload-box upload-compact${drag ? ' drag' : ''}`}>
|
||||
<div className={`upload-box upload-large${drag ? ' drag' : ''}`}>
|
||||
{drag && (
|
||||
<div className="drop-overlay" aria-hidden="true">
|
||||
<div><Icon name="upload" /><b>{t('brain.dropHere')}</b></div>
|
||||
|
||||
@@ -67,7 +67,7 @@ export function ProductionPage() {
|
||||
<div><small>{t('prod.links')}</small>{graph ? <b>{graph.links.filter(l => !l.hub).length}</b> : <b>-</b>}<span>{t('prod.linksSub')}</span></div>
|
||||
</div>
|
||||
<h3>{t('prod.scheduleTitle')}</h3>
|
||||
{schedules.data?.length === 0 && <p className="graph-empty">{t('prod.noSchedules')} <Link to="/instruction">{t('prod.giveInstruction')}</Link></p>}
|
||||
{schedules.data?.length === 0 && <p className="graph-empty">{t('prod.noSchedules')} <Link to="/brain">{t('prod.giveInstruction')}</Link></p>}
|
||||
<ul className="graph-list">
|
||||
{schedules.data?.slice(0, 4).map(s => (
|
||||
<li key={s.agent.id}>
|
||||
|
||||
@@ -105,7 +105,7 @@ function NoResults({ filtered, icon }: { filtered: boolean; icon: IconName }) {
|
||||
<span className="empty-icon"><Icon name={icon} /></span>
|
||||
<h3>{t('res.empty')}</h3>
|
||||
<p>{t('res.emptyCopy')}</p>
|
||||
<Link className="btn light" to="/instruction"><Icon name="plus" />{t('sch.newInstruction')}</Link>
|
||||
<Link className="btn light" to="/brain"><Icon name="plus" />{t('sch.newInstruction')}</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -427,7 +427,7 @@ function SourceRow({ source: s }: { source: FoundSource }) {
|
||||
<div>
|
||||
<span className={`status ${tone}`}><i className="dot"></i>{label}</span>
|
||||
<h3>{s.url ? <a href={s.url} target="_blank" rel="noreferrer">{s.title}</a> : s.title}</h3>
|
||||
<p>{describeRule(s.rule)} · {fmt(s.createdAt)}{s.status === 'accepted' && <> · <Link to="/brain">{t('res.inBrain')}</Link></>}</p>
|
||||
<p>{describeRule(s.rule)} · {fmt(s.createdAt)}{s.status === 'accepted' && <> · <Link to="/upload">{t('res.inBrain')}</Link></>}</p>
|
||||
{s.preview && <p className="source-preview">{s.preview}</p>}
|
||||
{review.error && <span className="error">{review.error.message}</span>}
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function SchedulePage() {
|
||||
return (
|
||||
<>
|
||||
<PageHead title={t('sch.title')} description={t('sch.description')}
|
||||
actions={<Link className="btn primary" to="/instruction"><Icon name="plus" />{t('sch.newInstruction')}</Link>} />
|
||||
actions={<Link className="btn primary" to="/brain"><Icon name="plus" />{t('sch.newInstruction')}</Link>} />
|
||||
<div className="card">
|
||||
<div className="card-head"><h3>{t('sch.cardTitle')}</h3><Icon name="calendar" /></div>
|
||||
{schedules.isPending && <SkeletonRows label={t('sch.loading')} />}
|
||||
|
||||
@@ -180,3 +180,12 @@ textarea{resize:vertical;line-height:1.6}
|
||||
@keyframes blink{50%{opacity:0}}
|
||||
@keyframes appear{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
||||
@media (prefers-reduced-motion:reduce){.dots b,.agent-chat .ai-message,.appear,.ai-message .typing:after{animation:none}}
|
||||
.upload-box.upload-large{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;padding:56px 32px;margin-bottom:24px}
|
||||
.upload-large .upload-symbol{width:64px;height:64px;justify-content:center;align-items:center;margin-bottom:10px}
|
||||
.upload-large .upload-symbol svg{width:28px;height:28px}
|
||||
.upload-large h3{font-size:20px}
|
||||
.upload-large p{font-size:14px;max-width:520px;margin:6px auto 4px}
|
||||
.upload-large small{font-size:12px;color:var(--muted)}
|
||||
.upload-large .upload-progress{text-align:left;max-width:520px;margin:12px auto 0}
|
||||
.upload-large .btn{margin-top:14px;padding-inline:22px}
|
||||
@media(max-width:600px){.upload-box.upload-large{padding:32px 20px}.upload-large .btn{width:100%}}
|
||||
|
||||
Reference in New Issue
Block a user