mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-05-20 23:26:44 +08:00
feat: build an AI agent from 0 to 1 -- 11 progressive sessions
- 11 sessions from basic agent loop to autonomous teams - Python MVP implementations for each session - Mental-model-first docs in en/zh/ja - Interactive web platform with step-through visualizations - Incremental architecture: each session adds one mechanism
This commit is contained in:
95
web/src/components/simulator/agent-loop-simulator.tsx
Normal file
95
web/src/components/simulator/agent-loop-simulator.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import { useTranslations } from "@/lib/i18n";
|
||||
import { useSimulator } from "@/hooks/useSimulator";
|
||||
import { SimulatorControls } from "./simulator-controls";
|
||||
import { SimulatorMessage } from "./simulator-message";
|
||||
import type { Scenario } from "@/types/agent-data";
|
||||
|
||||
const scenarioModules: Record<string, () => Promise<{ default: Scenario }>> = {
|
||||
s01: () => import("@/data/scenarios/s01.json") as Promise<{ default: Scenario }>,
|
||||
s02: () => import("@/data/scenarios/s02.json") as Promise<{ default: Scenario }>,
|
||||
s03: () => import("@/data/scenarios/s03.json") as Promise<{ default: Scenario }>,
|
||||
s04: () => import("@/data/scenarios/s04.json") as Promise<{ default: Scenario }>,
|
||||
s05: () => import("@/data/scenarios/s05.json") as Promise<{ default: Scenario }>,
|
||||
s06: () => import("@/data/scenarios/s06.json") as Promise<{ default: Scenario }>,
|
||||
s07: () => import("@/data/scenarios/s07.json") as Promise<{ default: Scenario }>,
|
||||
s08: () => import("@/data/scenarios/s08.json") as Promise<{ default: Scenario }>,
|
||||
s09: () => import("@/data/scenarios/s09.json") as Promise<{ default: Scenario }>,
|
||||
s10: () => import("@/data/scenarios/s10.json") as Promise<{ default: Scenario }>,
|
||||
s11: () => import("@/data/scenarios/s11.json") as Promise<{ default: Scenario }>,
|
||||
};
|
||||
|
||||
interface AgentLoopSimulatorProps {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export function AgentLoopSimulator({ version }: AgentLoopSimulatorProps) {
|
||||
const t = useTranslations("version");
|
||||
const [scenario, setScenario] = useState<Scenario | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loader = scenarioModules[version];
|
||||
if (loader) {
|
||||
loader().then((mod) => setScenario(mod.default));
|
||||
}
|
||||
}, [version]);
|
||||
|
||||
const sim = useSimulator(scenario?.steps ?? []);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTo({
|
||||
top: scrollRef.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, [sim.visibleSteps.length]);
|
||||
|
||||
if (!scenario) return null;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h2 className="mb-2 text-xl font-semibold">{t("simulator")}</h2>
|
||||
<p className="mb-4 text-sm text-[var(--color-text-secondary)]">
|
||||
{scenario.description}
|
||||
</p>
|
||||
|
||||
<div className="overflow-hidden rounded-xl border border-[var(--color-border)]">
|
||||
<div className="border-b border-[var(--color-border)] bg-zinc-50 px-4 py-3 dark:bg-zinc-900">
|
||||
<SimulatorControls
|
||||
isPlaying={sim.isPlaying}
|
||||
isComplete={sim.isComplete}
|
||||
currentIndex={sim.currentIndex}
|
||||
totalSteps={sim.totalSteps}
|
||||
speed={sim.speed}
|
||||
onPlay={sim.play}
|
||||
onPause={sim.pause}
|
||||
onStep={sim.stepForward}
|
||||
onReset={sim.reset}
|
||||
onSpeedChange={sim.setSpeed}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex max-h-[500px] min-h-[200px] flex-col gap-3 overflow-y-auto p-4"
|
||||
>
|
||||
{sim.visibleSteps.length === 0 && (
|
||||
<div className="flex flex-1 items-center justify-center text-sm text-[var(--color-text-secondary)]">
|
||||
Press Play or Step to begin
|
||||
</div>
|
||||
)}
|
||||
<AnimatePresence mode="popLayout">
|
||||
{sim.visibleSteps.map((step, i) => (
|
||||
<SimulatorMessage key={i} step={step} index={i} />
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
99
web/src/components/simulator/simulator-controls.tsx
Normal file
99
web/src/components/simulator/simulator-controls.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "@/lib/i18n";
|
||||
import { Play, Pause, SkipForward, RotateCcw } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SimulatorControlsProps {
|
||||
isPlaying: boolean;
|
||||
isComplete: boolean;
|
||||
currentIndex: number;
|
||||
totalSteps: number;
|
||||
speed: number;
|
||||
onPlay: () => void;
|
||||
onPause: () => void;
|
||||
onStep: () => void;
|
||||
onReset: () => void;
|
||||
onSpeedChange: (speed: number) => void;
|
||||
}
|
||||
|
||||
const SPEEDS = [0.5, 1, 2, 4];
|
||||
|
||||
export function SimulatorControls({
|
||||
isPlaying,
|
||||
isComplete,
|
||||
currentIndex,
|
||||
totalSteps,
|
||||
speed,
|
||||
onPlay,
|
||||
onPause,
|
||||
onStep,
|
||||
onReset,
|
||||
onSpeedChange,
|
||||
}: SimulatorControlsProps) {
|
||||
const t = useTranslations("sim");
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{isPlaying ? (
|
||||
<button
|
||||
onClick={onPause}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg bg-zinc-900 text-white transition-colors hover:bg-zinc-700 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-200"
|
||||
title={t("pause")}
|
||||
>
|
||||
<Pause size={16} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={onPlay}
|
||||
disabled={isComplete}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg bg-zinc-900 text-white transition-colors hover:bg-zinc-700 disabled:opacity-40 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-200"
|
||||
title={t("play")}
|
||||
>
|
||||
<Play size={16} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={onStep}
|
||||
disabled={isComplete}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg border border-[var(--color-border)] transition-colors hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800"
|
||||
title={t("step")}
|
||||
>
|
||||
<SkipForward size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={onReset}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg border border-[var(--color-border)] transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800"
|
||||
title={t("reset")}
|
||||
>
|
||||
<RotateCcw size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-xs text-[var(--color-text-secondary)]">
|
||||
{t("speed")}:
|
||||
</span>
|
||||
{SPEEDS.map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
onClick={() => onSpeedChange(s)}
|
||||
className={cn(
|
||||
"rounded px-2 py-1 text-xs font-medium transition-colors",
|
||||
speed === s
|
||||
? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900"
|
||||
: "text-[var(--color-text-secondary)] hover:text-[var(--color-text)]"
|
||||
)}
|
||||
>
|
||||
{s}x
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<span className="ml-auto text-xs tabular-nums text-[var(--color-text-secondary)]">
|
||||
{Math.max(0, currentIndex + 1)} {t("step_of")} {totalSteps}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
93
web/src/components/simulator/simulator-message.tsx
Normal file
93
web/src/components/simulator/simulator-message.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { SimStep } from "@/types/agent-data";
|
||||
import { User, Bot, Terminal, ArrowRight, AlertCircle } from "lucide-react";
|
||||
|
||||
interface SimulatorMessageProps {
|
||||
step: SimStep;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const TYPE_CONFIG: Record<
|
||||
string,
|
||||
{ icon: typeof User; label: string; bgClass: string; borderClass: string }
|
||||
> = {
|
||||
user_message: {
|
||||
icon: User,
|
||||
label: "User",
|
||||
bgClass: "bg-blue-50 dark:bg-blue-950/30",
|
||||
borderClass: "border-blue-200 dark:border-blue-800",
|
||||
},
|
||||
assistant_text: {
|
||||
icon: Bot,
|
||||
label: "Assistant",
|
||||
bgClass: "bg-zinc-50 dark:bg-zinc-900",
|
||||
borderClass: "border-zinc-200 dark:border-zinc-700",
|
||||
},
|
||||
tool_call: {
|
||||
icon: Terminal,
|
||||
label: "Tool Call",
|
||||
bgClass: "bg-amber-50 dark:bg-amber-950/30",
|
||||
borderClass: "border-amber-200 dark:border-amber-800",
|
||||
},
|
||||
tool_result: {
|
||||
icon: ArrowRight,
|
||||
label: "Tool Result",
|
||||
bgClass: "bg-emerald-50 dark:bg-emerald-950/30",
|
||||
borderClass: "border-emerald-200 dark:border-emerald-800",
|
||||
},
|
||||
system_event: {
|
||||
icon: AlertCircle,
|
||||
label: "System",
|
||||
bgClass: "bg-purple-50 dark:bg-purple-950/30",
|
||||
borderClass: "border-purple-200 dark:border-purple-800",
|
||||
},
|
||||
};
|
||||
|
||||
export function SimulatorMessage({ step, index }: SimulatorMessageProps) {
|
||||
const config = TYPE_CONFIG[step.type] || TYPE_CONFIG.assistant_text;
|
||||
const Icon = config.icon;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
className={cn(
|
||||
"rounded-lg border p-3",
|
||||
config.bgClass,
|
||||
config.borderClass
|
||||
)}
|
||||
>
|
||||
<div className="mb-1.5 flex items-center gap-2">
|
||||
<Icon size={14} className="shrink-0 text-[var(--color-text-secondary)]" />
|
||||
<span className="text-xs font-medium text-[var(--color-text-secondary)]">
|
||||
{config.label}
|
||||
{step.toolName && (
|
||||
<span className="ml-1.5 font-mono text-[var(--color-text)]">
|
||||
{step.toolName}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{step.type === "tool_call" || step.type === "tool_result" ? (
|
||||
<pre className="overflow-x-auto whitespace-pre-wrap rounded bg-zinc-900 p-2.5 font-mono text-xs leading-relaxed text-zinc-100 dark:bg-zinc-950">
|
||||
{step.content || "(empty)"}
|
||||
</pre>
|
||||
) : step.type === "system_event" ? (
|
||||
<pre className="overflow-x-auto whitespace-pre-wrap rounded bg-purple-900/80 p-2.5 font-mono text-xs leading-relaxed text-purple-100 dark:bg-purple-950">
|
||||
{step.content}
|
||||
</pre>
|
||||
) : (
|
||||
<p className="text-sm leading-relaxed">{step.content}</p>
|
||||
)}
|
||||
|
||||
<p className="mt-2 text-xs italic text-[var(--color-text-secondary)]">
|
||||
{step.annotation}
|
||||
</p>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user