- Incoming:
+ {copy.incoming}
{request && (
@@ -141,7 +189,7 @@ export default function ToolDispatch({ title }: { title?: string }) {
animate={{ opacity: 0.6 }}
className="text-xs text-zinc-400 dark:text-zinc-600"
>
- waiting for tool_call...
+ {copy.waiting}
)}
{isAllActive && (
@@ -151,7 +199,7 @@ export default function ToolDispatch({ title }: { title?: string }) {
animate={{ opacity: 1 }}
className="text-xs font-medium text-emerald-600 dark:text-emerald-400"
>
- All routes active
+ {copy.allRoutes}
)}
@@ -303,7 +351,7 @@ export default function ToolDispatch({ title }: { title?: string }) {
animate={{ fill: isActive ? "rgba(255,255,255,0.8)" : palette.labelFill }}
transition={{ duration: 0.4 }}
>
- {tool.desc}
+ {copy.toolDescriptions[i]}
);
diff --git a/web/src/components/visualizations/s03-todo-write.tsx b/web/src/components/visualizations/s03-todo-write.tsx
index 223327ea7..1cab7a4e2 100644
--- a/web/src/components/visualizations/s03-todo-write.tsx
+++ b/web/src/components/visualizations/s03-todo-write.tsx
@@ -3,6 +3,7 @@
import { motion, AnimatePresence } from "framer-motion";
import { useSteppedVisualization } from "@/hooks/useSteppedVisualization";
import { StepControls } from "@/components/visualizations/shared/step-controls";
+import { useLocale } from "@/lib/i18n";
// -- Task definitions --
@@ -14,59 +15,6 @@ interface Task {
status: TaskStatus;
}
-// Snapshot of all 4 tasks at each step
-const TASK_STATES: Task[][] = [
- // Step 0: all pending
- [
- { id: 1, label: "Write auth tests", status: "pending" },
- { id: 2, label: "Fix mobile layout", status: "pending" },
- { id: 3, label: "Add error handling", status: "pending" },
- { id: 4, label: "Update config loader", status: "pending" },
- ],
- // Step 1: still all pending (idle round 1)
- [
- { id: 1, label: "Write auth tests", status: "pending" },
- { id: 2, label: "Fix mobile layout", status: "pending" },
- { id: 3, label: "Add error handling", status: "pending" },
- { id: 4, label: "Update config loader", status: "pending" },
- ],
- // Step 2: still all pending (idle round 2)
- [
- { id: 1, label: "Write auth tests", status: "pending" },
- { id: 2, label: "Fix mobile layout", status: "pending" },
- { id: 3, label: "Add error handling", status: "pending" },
- { id: 4, label: "Update config loader", status: "pending" },
- ],
- // Step 3: NAG fires, task 1 moves to in_progress
- [
- { id: 1, label: "Write auth tests", status: "in_progress" },
- { id: 2, label: "Fix mobile layout", status: "pending" },
- { id: 3, label: "Add error handling", status: "pending" },
- { id: 4, label: "Update config loader", status: "pending" },
- ],
- // Step 4: task 1 done
- [
- { id: 1, label: "Write auth tests", status: "done" },
- { id: 2, label: "Fix mobile layout", status: "pending" },
- { id: 3, label: "Add error handling", status: "pending" },
- { id: 4, label: "Update config loader", status: "pending" },
- ],
- // Step 5: task 2 self-directed to in_progress
- [
- { id: 1, label: "Write auth tests", status: "done" },
- { id: 2, label: "Fix mobile layout", status: "in_progress" },
- { id: 3, label: "Add error handling", status: "pending" },
- { id: 4, label: "Update config loader", status: "pending" },
- ],
- // Step 6: tasks 2,3 done, task 4 in_progress
- [
- { id: 1, label: "Write auth tests", status: "done" },
- { id: 2, label: "Fix mobile layout", status: "done" },
- { id: 3, label: "Add error handling", status: "done" },
- { id: 4, label: "Update config loader", status: "in_progress" },
- ],
-];
-
// Nag timer value at each step (out of 3)
const NAG_TIMER_PER_STEP = [0, 1, 2, 3, 0, 0, 0];
const NAG_THRESHOLD = 3;
@@ -74,16 +22,135 @@ const NAG_THRESHOLD = 3;
// Whether the nag fires at this step
const NAG_FIRES_PER_STEP = [false, false, false, true, false, false, false];
-// Step annotations
-const STEP_INFO = [
- { title: "The Plan", desc: "TodoWrite gives the model a visible plan. All tasks start as pending." },
- { title: "Round 1 -- Idle", desc: "The model does work but doesn't touch its todos. The nag counter increments." },
- { title: "Round 2 -- Still Idle", desc: "Two rounds without progress. Pressure builds." },
- { title: "NAG!", desc: "Threshold reached! System message injected: 'You have pending tasks. Pick one up now!'" },
- { title: "Task Complete", desc: "The model completes the task. Timer stays at 0 -- working on todos resets the counter." },
- { title: "Self-Directed", desc: "Once the model learns the pattern, it picks up tasks voluntarily." },
- { title: "Mission Accomplished", desc: "Visible plan + nag pressure = reliable task completion." },
-];
+interface TodoWriteCopy {
+ title: string;
+ tasks: string[];
+ stepInfo: { title: string; desc: string }[];
+ statuses: Record
;
+ nagTimer: string;
+ nagMessage: string;
+ columns: {
+ pending: string;
+ inProgress: string;
+ done: string;
+ };
+ progress: string;
+ complete: string;
+}
+
+const COPY: Record = {
+ en: {
+ title: "TodoWrite Nag System",
+ tasks: [
+ "Write auth tests",
+ "Fix mobile layout",
+ "Add error handling",
+ "Update config loader",
+ ],
+ stepInfo: [
+ { title: "The Plan", desc: "TodoWrite gives the model a visible plan. All tasks start as pending." },
+ { title: "Round 1 -- Idle", desc: "The model does work but doesn't touch its todos. The nag counter increments." },
+ { title: "Round 2 -- Still Idle", desc: "Two rounds without progress. Pressure builds." },
+ { title: "NAG!", desc: "Threshold reached! System message injected: 'You have pending tasks. Pick one up now!'" },
+ { title: "Task Complete", desc: "The model completes the task. Timer stays at 0 -- working on todos resets the counter." },
+ { title: "Self-Directed", desc: "Once the model learns the pattern, it picks up tasks voluntarily." },
+ { title: "Mission Accomplished", desc: "Visible plan + nag pressure = reliable task completion." },
+ ],
+ statuses: {
+ pending: "pending",
+ in_progress: "in progress",
+ done: "done",
+ },
+ nagTimer: "Nag Timer",
+ nagMessage: 'SYSTEM: "You have pending tasks. Pick one up now!"',
+ columns: {
+ pending: "Pending",
+ inProgress: "In Progress",
+ done: "Done",
+ },
+ progress: "Progress",
+ complete: "complete",
+ },
+ ru: {
+ title: "Система напоминаний плана задач",
+ tasks: [
+ "Написать auth-тесты",
+ "Исправить мобильную вёрстку",
+ "Добавить обработку ошибок",
+ "Обновить загрузчик конфига",
+ ],
+ stepInfo: [
+ { title: "План", desc: "Инструмент плана задач даёт модели видимый план. Все задачи стартуют в статусе pending." },
+ { title: "Раунд 1 -- без движения", desc: "Модель что-то делает, но не трогает todo. Счётчик напоминаний растёт." },
+ { title: "Раунд 2 -- всё ещё без движения", desc: "Два раунда без прогресса. Давление усиливается." },
+ { title: "НАПОМИНАНИЕ!", desc: "Порог достигнут. Вкалывается системное сообщение: 'У тебя есть незавершённые задачи. Возьми одну сейчас.'" },
+ { title: "Задача завершена", desc: "Модель закрывает задачу. Таймер остаётся на 0: работа с todo сбрасывает счётчик." },
+ { title: "Самонаведение", desc: "Когда модель понимает паттерн, она начинает подхватывать задачи сама." },
+ { title: "Миссия выполнена", desc: "Видимый план + давление напоминаний = надёжное завершение задач." },
+ ],
+ statuses: {
+ pending: "ожидает",
+ in_progress: "в работе",
+ done: "готово",
+ },
+ nagTimer: "Таймер напоминаний",
+ nagMessage: 'СИСТЕМА: "У тебя есть незавершённые задачи. Возьми одну сейчас!"',
+ columns: {
+ pending: "Ожидают",
+ inProgress: "В работе",
+ done: "Готово",
+ },
+ progress: "Прогресс",
+ complete: "завершено",
+ },
+};
+
+function getTaskStates(labels: string[]): Task[][] {
+ return [
+ [
+ { id: 1, label: labels[0], status: "pending" },
+ { id: 2, label: labels[1], status: "pending" },
+ { id: 3, label: labels[2], status: "pending" },
+ { id: 4, label: labels[3], status: "pending" },
+ ],
+ [
+ { id: 1, label: labels[0], status: "pending" },
+ { id: 2, label: labels[1], status: "pending" },
+ { id: 3, label: labels[2], status: "pending" },
+ { id: 4, label: labels[3], status: "pending" },
+ ],
+ [
+ { id: 1, label: labels[0], status: "pending" },
+ { id: 2, label: labels[1], status: "pending" },
+ { id: 3, label: labels[2], status: "pending" },
+ { id: 4, label: labels[3], status: "pending" },
+ ],
+ [
+ { id: 1, label: labels[0], status: "in_progress" },
+ { id: 2, label: labels[1], status: "pending" },
+ { id: 3, label: labels[2], status: "pending" },
+ { id: 4, label: labels[3], status: "pending" },
+ ],
+ [
+ { id: 1, label: labels[0], status: "done" },
+ { id: 2, label: labels[1], status: "pending" },
+ { id: 3, label: labels[2], status: "pending" },
+ { id: 4, label: labels[3], status: "pending" },
+ ],
+ [
+ { id: 1, label: labels[0], status: "done" },
+ { id: 2, label: labels[1], status: "in_progress" },
+ { id: 3, label: labels[2], status: "pending" },
+ { id: 4, label: labels[3], status: "pending" },
+ ],
+ [
+ { id: 1, label: labels[0], status: "done" },
+ { id: 2, label: labels[1], status: "done" },
+ { id: 3, label: labels[2], status: "done" },
+ { id: 4, label: labels[3], status: "in_progress" },
+ ],
+ ];
+}
// -- Column component --
@@ -127,6 +194,8 @@ function KanbanColumn({
// -- Task card --
function TaskCard({ task }: { task: Task }) {
+ const locale = useLocale();
+ const copy = COPY[locale] || COPY.en;
const statusStyles: Record = {
pending: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
in_progress: "bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300",
@@ -156,7 +225,7 @@ function TaskCard({ task }: { task: Task }) {
- {task.status.replace("_", " ")}
+ {copy.statuses[task.status]}
@@ -169,6 +238,8 @@ function TaskCard({ task }: { task: Task }) {
// -- Nag gauge --
function NagGauge({ value, max, firing }: { value: number; max: number; firing: boolean }) {
+ const locale = useLocale();
+ const copy = COPY[locale] || COPY.en;
const pct = Math.min((value / max) * 100, 100);
const barColor =
@@ -184,7 +255,7 @@ function NagGauge({ value, max, firing }: { value: number; max: number; firing:
- Nag Timer
+ {copy.nagTimer}
{value}/{max}
@@ -219,6 +290,8 @@ function NagGauge({ value, max, firing }: { value: number; max: number; firing:
// -- Main component --
export default function TodoWrite({ title }: { title?: string }) {
+ const locale = useLocale();
+ const copy = COPY[locale] || COPY.en;
const {
currentStep,
totalSteps,
@@ -229,10 +302,10 @@ export default function TodoWrite({ title }: { title?: string }) {
toggleAutoPlay,
} = useSteppedVisualization({ totalSteps: 7, autoPlayInterval: 2500 });
- const tasks = TASK_STATES[currentStep];
+ const tasks = getTaskStates(copy.tasks)[currentStep];
const nagValue = NAG_TIMER_PER_STEP[currentStep];
const nagFires = NAG_FIRES_PER_STEP[currentStep];
- const stepInfo = STEP_INFO[currentStep];
+ const stepInfo = copy.stepInfo[currentStep];
const pendingTasks = tasks.filter((t) => t.status === "pending");
const inProgressTasks = tasks.filter((t) => t.status === "in_progress");
@@ -241,7 +314,7 @@ export default function TodoWrite({ title }: { title?: string }) {
return (
- {title || "TodoWrite Nag System"}
+ {title || copy.title}
@@ -257,7 +330,7 @@ export default function TodoWrite({ title }: { title?: string }) {
exit={{ opacity: 0, y: -8, height: 0 }}
className="rounded-md border border-red-300 bg-red-50 px-3 py-2 text-center text-xs font-bold text-red-700 dark:border-red-700 dark:bg-red-950/30 dark:text-red-300"
>
- SYSTEM: "You have pending tasks. Pick one up now!"
+ {copy.nagMessage}
)}
@@ -266,19 +339,19 @@ export default function TodoWrite({ title }: { title?: string }) {
{/* Kanban board */}
- Progress: {doneTasks.length}/{tasks.length} complete
+ {copy.progress}: {doneTasks.length}/{tasks.length} {copy.complete}
{tasks.map((t) => (
diff --git a/web/src/components/visualizations/s04-subagent.tsx b/web/src/components/visualizations/s04-subagent.tsx
index d4b05b793..b54c8d284 100644
--- a/web/src/components/visualizations/s04-subagent.tsx
+++ b/web/src/components/visualizations/s04-subagent.tsx
@@ -3,6 +3,7 @@
import { motion, AnimatePresence } from "framer-motion";
import { useSteppedVisualization } from "@/hooks/useSteppedVisualization";
import { StepControls } from "@/components/visualizations/shared/step-controls";
+import { useLocale } from "@/lib/i18n";
interface MessageBlock {
id: string;
@@ -10,63 +11,95 @@ interface MessageBlock {
color: string;
}
-const PARENT_BASE_MESSAGES: MessageBlock[] = [
- { id: "p1", label: "user: Build login + tests", color: "bg-blue-500" },
- { id: "p2", label: "assistant: Planning approach...", color: "bg-zinc-600" },
- { id: "p3", label: "tool_result: project structure", color: "bg-emerald-500" },
-];
-
-const TASK_PROMPT: MessageBlock = {
- id: "task",
- label: "task: Write unit tests for auth",
- color: "bg-purple-500",
-};
-
-const CHILD_WORK_MESSAGES: MessageBlock[] = [
- { id: "c1", label: "tool_use: read auth.ts", color: "bg-amber-500" },
- { id: "c2", label: "tool_use: write test.ts", color: "bg-amber-500" },
-];
-
-const SUMMARY_BLOCK: MessageBlock = {
- id: "summary",
- label: "summary: 3 tests written, all passing",
- color: "bg-teal-500",
-};
+interface SubagentCopy {
+ title: string;
+ parentBaseMessages: MessageBlock[];
+ taskPrompt: MessageBlock;
+ childWorkMessages: MessageBlock[];
+ summaryBlock: MessageBlock;
+ steps: { title: string; description: string }[];
+ parentProcess: string;
+ childProcess: string;
+ messagesFresh: string;
+ notSpawned: string;
+ cleanContext: string;
+ isolation: string;
+ compressing: string;
+ discarded: string;
+ taskPromptChip: string;
+ summaryChip: string;
+}
-const STEPS = [
- {
- title: "Parent Context",
- description:
- "The parent agent has accumulated messages from the conversation.",
+const COPY: Record
= {
+ en: {
+ title: "Subagent Context Isolation",
+ parentBaseMessages: [
+ { id: "p1", label: "user: Build login + tests", color: "bg-blue-500" },
+ { id: "p2", label: "assistant: Planning approach...", color: "bg-zinc-600" },
+ { id: "p3", label: "tool_result: project structure", color: "bg-emerald-500" },
+ ],
+ taskPrompt: { id: "task", label: "task: Write unit tests for auth", color: "bg-purple-500" },
+ childWorkMessages: [
+ { id: "c1", label: "tool_use: read auth.ts", color: "bg-amber-500" },
+ { id: "c2", label: "tool_use: write test.ts", color: "bg-amber-500" },
+ ],
+ summaryBlock: { id: "summary", label: "summary: 3 tests written, all passing", color: "bg-teal-500" },
+ steps: [
+ { title: "Parent Context", description: "The parent agent has accumulated messages from the conversation." },
+ { title: "Spawn Subagent", description: "Task tool creates a child with fresh messages[]. Only the task description is passed." },
+ { title: "Independent Work", description: "The child has its own context. It doesn't see the parent's history." },
+ { title: "Compress Result", description: "The child's full conversation compresses into one summary." },
+ { title: "Return Summary", description: "Only the summary returns. The child's full context is discarded." },
+ { title: "Clean Context", description: "The parent gets a clean summary without context bloat. This is fresh-context isolation via messages[]." },
+ ],
+ parentProcess: "Parent Process",
+ childProcess: "Child Process",
+ messagesFresh: "messages[] (fresh)",
+ notSpawned: "not yet spawned",
+ cleanContext: "3 original + 1 summary = clean context",
+ isolation: "ISOLATION",
+ compressing: "Compressing full context into summary...",
+ discarded: "context discarded",
+ taskPromptChip: "task prompt",
+ summaryChip: "summary",
},
- {
- title: "Spawn Subagent",
- description:
- "Task tool creates a child with fresh messages[]. Only the task description is passed.",
+ ru: {
+ title: "Изоляция контекста сабагента",
+ parentBaseMessages: [
+ { id: "p1", label: "user: Собрать логин + тесты", color: "bg-blue-500" },
+ { id: "p2", label: "assistant: Планирую подход...", color: "bg-zinc-600" },
+ { id: "p3", label: "tool_result: структура проекта", color: "bg-emerald-500" },
+ ],
+ taskPrompt: { id: "task", label: "task: Написать unit-тесты для auth", color: "bg-purple-500" },
+ childWorkMessages: [
+ { id: "c1", label: "tool_use: read auth.ts", color: "bg-amber-500" },
+ { id: "c2", label: "tool_use: write test.ts", color: "bg-amber-500" },
+ ],
+ summaryBlock: { id: "summary", label: "summary: написаны 3 теста, всё проходит", color: "bg-teal-500" },
+ steps: [
+ { title: "Контекст родителя", description: "Родительский агент уже накопил сообщения из разговора." },
+ { title: "Запуск сабагента", description: "Инструмент Task создаёт дочернего агента со свежим messages[]. Передаётся только описание задачи." },
+ { title: "Независимая работа", description: "У дочернего агента свой собственный контекст. Историю родителя он не видит." },
+ { title: "Сжать результат", description: "Весь диалог дочернего агента сжимается в одну сводку." },
+ { title: "Вернуть сводку", description: "Назад возвращается только сводка. Полный контекст дочернего агента выбрасывается." },
+ { title: "Чистый контекст", description: "Родитель получает чистую сводку без раздувания контекста. Это и есть изоляция через свежий messages[]." },
+ ],
+ parentProcess: "Родительский процесс",
+ childProcess: "Дочерний процесс",
+ messagesFresh: "messages[] (свежий)",
+ notSpawned: "ещё не запущен",
+ cleanContext: "3 исходных + 1 сводка = чистый контекст",
+ isolation: "ИЗОЛЯЦИЯ",
+ compressing: "Сжимаем полный контекст в сводку...",
+ discarded: "контекст отброшен",
+ taskPromptChip: "описание задачи",
+ summaryChip: "сводка",
},
- {
- title: "Independent Work",
- description:
- "The child has its own context. It doesn't see the parent's history.",
- },
- {
- title: "Compress Result",
- description:
- "The child's full conversation compresses into one summary.",
- },
- {
- title: "Return Summary",
- description:
- "Only the summary returns. The child's full context is discarded.",
- },
- {
- title: "Clean Context",
- description:
- "The parent gets a clean summary without context bloat. This is fresh-context isolation via messages[].",
- },
-];
+};
export default function SubagentIsolation({ title }: { title?: string }) {
+ const locale = useLocale();
+ const copy = COPY[locale] || COPY.en;
const {
currentStep,
totalSteps,
@@ -75,23 +108,23 @@ export default function SubagentIsolation({ title }: { title?: string }) {
reset,
isPlaying,
toggleAutoPlay,
- } = useSteppedVisualization({ totalSteps: STEPS.length, autoPlayInterval: 2500 });
+ } = useSteppedVisualization({ totalSteps: copy.steps.length, autoPlayInterval: 2500 });
// Derive what to show in each container based on step
const parentMessages: MessageBlock[] = (() => {
- const base = [...PARENT_BASE_MESSAGES];
+ const base = [...copy.parentBaseMessages];
if (currentStep >= 5) {
- base.push(SUMMARY_BLOCK);
+ base.push(copy.summaryBlock);
}
return base;
})();
const childMessages: MessageBlock[] = (() => {
if (currentStep < 1) return [];
- if (currentStep === 1) return [TASK_PROMPT];
- if (currentStep === 2) return [TASK_PROMPT, ...CHILD_WORK_MESSAGES];
- if (currentStep === 3) return [SUMMARY_BLOCK];
- return currentStep >= 4 ? [TASK_PROMPT, ...CHILD_WORK_MESSAGES] : [];
+ if (currentStep === 1) return [copy.taskPrompt];
+ if (currentStep === 2) return [copy.taskPrompt, ...copy.childWorkMessages];
+ if (currentStep === 3) return [copy.summaryBlock];
+ return currentStep >= 4 ? [copy.taskPrompt, ...copy.childWorkMessages] : [];
})();
const showChildEmpty = currentStep === 0;
@@ -104,7 +137,7 @@ export default function SubagentIsolation({ title }: { title?: string }) {
return (
- {title || "Subagent Context Isolation"}
+ {title || copy.title}
- Parent Process
+ {copy.parentProcess}
@@ -146,7 +179,7 @@ export default function SubagentIsolation({ title }: { title?: string }) {
transition={{ delay: 0.5 }}
className="mt-3 rounded border border-blue-200 bg-white/60 px-2 py-1 text-center text-xs text-blue-600 dark:border-blue-700 dark:bg-blue-950/30 dark:text-blue-300"
>
- 3 original + 1 summary = clean context
+ {copy.cleanContext}
)}
@@ -161,7 +194,7 @@ export default function SubagentIsolation({ title }: { title?: string }) {
className="rounded bg-zinc-200 px-2 py-1 text-center font-mono text-[10px] text-zinc-500 dark:bg-zinc-700 dark:text-zinc-400"
style={{ writingMode: "vertical-rl", textOrientation: "mixed" }}
>
- ISOLATION
+ {copy.isolation}
@@ -195,11 +228,11 @@ export default function SubagentIsolation({ title }: { title?: string }) {
: "text-purple-700 dark:text-purple-300"
}`}
>
- Child Process
+ {copy.childProcess}
- messages[] (fresh)
+ {copy.messagesFresh}
{showChildEmpty && (
@@ -209,7 +242,7 @@ export default function SubagentIsolation({ title }: { title?: string }) {
className="flex h-24 items-center justify-center rounded-lg border border-dashed border-zinc-200 dark:border-zinc-700"
>
- not yet spawned
+ {copy.notSpawned}
)}
@@ -237,7 +270,7 @@ export default function SubagentIsolation({ title }: { title?: string }) {
animate={{ opacity: 1, scale: 1 }}
className="mt-3 rounded border border-amber-300 bg-amber-50 px-2 py-1 text-center text-xs text-amber-700 dark:border-amber-600 dark:bg-amber-900/20 dark:text-amber-300"
>
- Compressing full context into summary...
+ {copy.compressing}
)}
@@ -247,7 +280,7 @@ export default function SubagentIsolation({ title }: { title?: string }) {
animate={{ opacity: 1 }}
className="mt-3 rounded border border-red-200 bg-red-50 px-2 py-1 text-center text-xs text-red-500 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400"
>
- context discarded
+ {copy.discarded}
)}
@@ -264,7 +297,7 @@ export default function SubagentIsolation({ title }: { title?: string }) {
style={{ zIndex: 10 }}
>
- task prompt
+ {copy.taskPromptChip}
)}
@@ -281,7 +314,7 @@ export default function SubagentIsolation({ title }: { title?: string }) {
style={{ zIndex: 10 }}
>
- summary
+ {copy.summaryChip}
)}
@@ -298,8 +331,8 @@ export default function SubagentIsolation({ title }: { title?: string }) {
onReset={reset}
isPlaying={isPlaying}
onToggleAutoPlay={toggleAutoPlay}
- stepTitle={STEPS[currentStep].title}
- stepDescription={STEPS[currentStep].description}
+ stepTitle={copy.steps[currentStep].title}
+ stepDescription={copy.steps[currentStep].description}
/>
diff --git a/web/src/components/visualizations/s05-skill-loading.tsx b/web/src/components/visualizations/s05-skill-loading.tsx
index c0331644f..5c8f27a57 100644
--- a/web/src/components/visualizations/s05-skill-loading.tsx
+++ b/web/src/components/visualizations/s05-skill-loading.tsx
@@ -3,98 +3,169 @@
import { motion, AnimatePresence } from "framer-motion";
import { useSteppedVisualization } from "@/hooks/useSteppedVisualization";
import { StepControls } from "@/components/visualizations/shared/step-controls";
+import { useLocale } from "@/lib/i18n";
interface SkillEntry {
name: string;
- summary: string;
fullTokens: number;
- content: string[];
}
const SKILLS: SkillEntry[] = [
{
name: "/commit",
- summary: "Create git commits following repo conventions",
fullTokens: 320,
- content: [
- "1. Run git status + git diff to see changes",
- "2. Analyze all staged changes and draft message",
- "3. Create commit with Co-Authored-By trailer",
- "4. Run git status after commit to verify",
- ],
},
{
name: "/review-pr",
- summary: "Review pull requests for bugs and style",
fullTokens: 480,
- content: [
- "1. Fetch PR diff via gh pr view",
- "2. Analyze changes file by file for issues",
- "3. Check for bugs, security, and style problems",
- "4. Post review comments with gh pr review",
- ],
},
{
name: "/test",
- summary: "Run and analyze test suites",
fullTokens: 290,
- content: [
- "1. Detect test framework from package.json",
- "2. Run test suite and capture output",
- "3. Analyze failures and suggest fixes",
- "4. Re-run after applying fixes",
- ],
},
{
name: "/deploy",
- summary: "Deploy application to target environment",
fullTokens: 350,
- content: [
- "1. Verify all tests pass before deploy",
- "2. Build production bundle",
- "3. Push to deployment target via CI",
- "4. Verify health check on deployed URL",
- ],
},
];
const TOKEN_STATES = [120, 120, 440, 440, 780, 780];
const MAX_TOKEN_DISPLAY = 1000;
-const STEPS = [
- {
- title: "Layer 1: Compact Summaries",
- description:
- "All skills are summarized in the system prompt. Compact, always present.",
- },
- {
- title: "Skill Invocation",
- description:
- 'The model recognizes a skill invocation and triggers the Skill tool.',
- },
- {
- title: "Layer 2: Full Injection",
- description:
- "The full skill instructions are injected as a tool_result, not into the system prompt.",
- },
- {
- title: "In Context Now",
- description:
- "The detailed instructions appear as if a tool returned them. The model follows them precisely.",
- },
- {
- title: "Stack Skills",
- description:
- "Multiple skills can be loaded. Only summaries are permanent; full content comes and goes.",
+interface SkillLoadingCopy {
+ title: string;
+ systemPrompt: string;
+ alwaysPresent: string;
+ availableSkills: string;
+ skillSummaries: string[];
+ skillContent: string[][];
+ userTypes: string;
+ toolResult: string;
+ mechanism: string;
+ layer1: string;
+ layer2: string;
+ layer1Desc: string;
+ layer2Desc: string;
+ tokens: string;
+ steps: { title: string; description: string }[];
+}
+
+const COPY: Record
= {
+ en: {
+ title: "On-Demand Skill Loading",
+ systemPrompt: "System Prompt",
+ alwaysPresent: "always present",
+ availableSkills: "# Available Skills",
+ skillSummaries: [
+ "Create git commits following repo conventions",
+ "Review pull requests for bugs and style",
+ "Run and analyze test suites",
+ "Deploy application to target environment",
+ ],
+ skillContent: [
+ [
+ "1. Run git status + git diff to see changes",
+ "2. Analyze all staged changes and draft message",
+ "3. Create commit with Co-Authored-By trailer",
+ "4. Run git status after commit to verify",
+ ],
+ [
+ "1. Fetch PR diff via gh pr view",
+ "2. Analyze changes file by file for issues",
+ "3. Check for bugs, security, and style problems",
+ "4. Post review comments with gh pr review",
+ ],
+ [
+ "1. Detect test framework from package.json",
+ "2. Run test suite and capture output",
+ "3. Analyze failures and suggest fixes",
+ "4. Re-run after applying fixes",
+ ],
+ [
+ "1. Verify all tests pass before deploy",
+ "2. Build production bundle",
+ "3. Push to deployment target via CI",
+ "4. Verify health check on deployed URL",
+ ],
+ ],
+ userTypes: "User types:",
+ toolResult: "tool_result",
+ mechanism:
+ "The Skill tool returns content as a tool_result message. The model sees it in context and follows the instructions. No system prompt bloat.",
+ layer1: "LAYER 1",
+ layer2: "LAYER 2",
+ layer1Desc: "Always present, ~120 tokens",
+ layer2Desc: "On demand, ~300-500 tokens each",
+ tokens: "Tokens",
+ steps: [
+ { title: "Layer 1: Compact Summaries", description: "All skills are summarized in the system prompt. Compact, always present." },
+ { title: "Skill Invocation", description: "The model recognizes a skill invocation and triggers the Skill tool." },
+ { title: "Layer 2: Full Injection", description: "The full skill instructions are injected as a tool_result, not into the system prompt." },
+ { title: "In Context Now", description: "The detailed instructions appear as if a tool returned them. The model follows them precisely." },
+ { title: "Stack Skills", description: "Multiple skills can be loaded. Only summaries are permanent; full content comes and goes." },
+ { title: "Two-Layer Architecture", description: "Layer 1: always present, tiny. Layer 2: loaded on demand, detailed. Elegant separation." },
+ ],
},
- {
- title: "Two-Layer Architecture",
- description:
- "Layer 1: always present, tiny. Layer 2: loaded on demand, detailed. Elegant separation.",
+ ru: {
+ title: "Загрузка навыков по требованию",
+ systemPrompt: "Системный промпт",
+ alwaysPresent: "всегда присутствует",
+ availableSkills: "# Доступные навыки",
+ skillSummaries: [
+ "Создаёт git-коммиты по правилам репозитория",
+ "Проверяет pull request на баги и стиль",
+ "Запускает и анализирует тестовые наборы",
+ "Деплоит приложение в целевое окружение",
+ ],
+ skillContent: [
+ [
+ "1. Запустить git status + git diff и посмотреть изменения",
+ "2. Проанализировать staged-правки и набросать сообщение",
+ "3. Создать коммит с trailer Co-Authored-By",
+ "4. После коммита снова проверить git status",
+ ],
+ [
+ "1. Получить diff PR через gh pr view",
+ "2. Разобрать изменения по файлам и найти проблемы",
+ "3. Проверить баги, безопасность и стиль",
+ "4. Отправить комментарии через gh pr review",
+ ],
+ [
+ "1. Определить тестовый фреймворк по package.json",
+ "2. Запустить тесты и собрать вывод",
+ "3. Разобрать падения и предложить исправления",
+ "4. Повторно прогнать после правок",
+ ],
+ [
+ "1. Убедиться, что перед деплоем все тесты проходят",
+ "2. Собрать production-бандл",
+ "3. Отправить в целевое окружение через CI",
+ "4. Проверить health-check на задеплоенном URL",
+ ],
+ ],
+ userTypes: "Пользователь вводит:",
+ toolResult: "tool_result",
+ mechanism:
+ "Инструмент Skill возвращает содержимое как сообщение tool_result. Модель видит его в контексте и следует инструкциям. Системный промпт не раздувается.",
+ layer1: "СЛОЙ 1",
+ layer2: "СЛОЙ 2",
+ layer1Desc: "Всегда в контексте, около 120 токенов",
+ layer2Desc: "Подгружается по запросу, по 300-500 токенов",
+ tokens: "Токены",
+ steps: [
+ { title: "Слой 1: компактные сводки", description: "Все навыки кратко перечислены в системном промпте. Коротко и постоянно." },
+ { title: "Вызов навыка", description: "Модель распознаёт вызов навыка и активирует инструмент Skill." },
+ { title: "Слой 2: полная подгрузка", description: "Полные инструкции навыка приходят как tool_result, а не живут в системном промпте." },
+ { title: "Теперь это в контексте", description: "Подробные инструкции выглядят так, будто их вернул инструмент. Модель следует им буквально." },
+ { title: "Стек навыков", description: "Можно подгружать несколько навыков. Постоянны только сводки, полный контент приходит и уходит." },
+ { title: "Двухслойная архитектура", description: "Слой 1: всегда присутствует и мал. Слой 2: загружается по требованию и содержит детали." },
+ ],
},
-];
+};
export default function SkillLoading({ title }: { title?: string }) {
+ const locale = useLocale();
+ const copy = COPY[locale] || COPY.en;
const {
currentStep,
totalSteps,
@@ -103,7 +174,7 @@ export default function SkillLoading({ title }: { title?: string }) {
reset,
isPlaying,
toggleAutoPlay,
- } = useSteppedVisualization({ totalSteps: STEPS.length, autoPlayInterval: 2500 });
+ } = useSteppedVisualization({ totalSteps: copy.steps.length, autoPlayInterval: 2500 });
const tokenCount = TOKEN_STATES[currentStep];
const highlightedSkill = currentStep >= 1 && currentStep <= 3 ? 0 : currentStep >= 4 ? 1 : -1;
@@ -114,7 +185,7 @@ export default function SkillLoading({ title }: { title?: string }) {
return (
- {title || "On-Demand Skill Loading"}
+ {title || copy.title}
- System Prompt
+ {copy.systemPrompt}
- always present
+ {copy.alwaysPresent}
- # Available Skills
+ {copy.availableSkills}
{SKILLS.map((skill, i) => {
@@ -161,7 +232,7 @@ export default function SkillLoading({ title }: { title?: string }) {
{skill.name}
{" - "}
- {skill.summary}
+ {copy.skillSummaries[i]}
);
})}
@@ -179,7 +250,7 @@ export default function SkillLoading({ title }: { title?: string }) {
className="flex items-center gap-2 rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 dark:border-blue-800 dark:bg-blue-950/30"
>
- User types:
+ {copy.userTypes}
/commit
@@ -194,7 +265,7 @@ export default function SkillLoading({ title }: { title?: string }) {
className="flex items-center gap-2 rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 dark:border-blue-800 dark:bg-blue-950/30"
>
- User types:
+ {copy.userTypes}
/review-pr
@@ -243,11 +314,11 @@ export default function SkillLoading({ title }: { title?: string }) {
- tool_result
+ {copy.toolResult}
- {SKILLS[0].content.map((line, i) => (
+ {copy.skillContent[0].map((line, i) => (
- tool_result
+ {copy.toolResult}
diff --git a/web/src/components/visualizations/s06-context-compact.tsx b/web/src/components/visualizations/s06-context-compact.tsx
index 3c4270d32..5711fe8ee 100644
--- a/web/src/components/visualizations/s06-context-compact.tsx
+++ b/web/src/components/visualizations/s06-context-compact.tsx
@@ -4,6 +4,7 @@ import { useMemo } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useSteppedVisualization } from "@/hooks/useSteppedVisualization";
import { StepControls } from "@/components/visualizations/shared/step-controls";
+import { useLocale } from "@/lib/i18n";
type BlockType = "user" | "assistant" | "tool_result";
@@ -14,6 +15,30 @@ interface ContextBlock {
tokens: number;
}
+interface CompactCopy {
+ title: string;
+ contextWindow: string;
+ tokenUsage: string;
+ user: string;
+ assistant: string;
+ toolResult: string;
+ largestBlocks: string;
+ largestBlocksDesc: string;
+ microCompact: string;
+ autoCompact: string;
+ summary: string;
+ compactSummary: string;
+ microCompactDesc: string;
+ autoCompactDesc: string;
+ compactDesc: string;
+ stage1: string;
+ stage1Mode: string;
+ stage2: string;
+ stage2Mode: string;
+ stage3: string;
+ stage3Mode: string;
+}
+
const BLOCK_COLORS: Record