diff --git a/apps/web/app/(landing)/about/page.tsx b/apps/web/app/(landing)/about/page.tsx new file mode 100644 index 00000000..5d842239 --- /dev/null +++ b/apps/web/app/(landing)/about/page.tsx @@ -0,0 +1,62 @@ +"use client"; + +import Link from "next/link"; +import { LandingHeader } from "@/features/landing/components/landing-header"; +import { LandingFooter } from "@/features/landing/components/landing-footer"; +import { GitHubMark, githubUrl } from "@/features/landing/components/shared"; +import { useLocale } from "@/features/landing/i18n"; + +export default function AboutPage() { + const { t } = useLocale(); + const n = t.about.nameLine; + + return ( + <> + +
+
+

+ {t.about.title} +

+
+

+ {n.prefix} + + {n.mul} + + {n.tiplexed} + + {n.i} + + {n.nformationAnd} + + {n.c} + + {n.omputing} + + {n.a} + + {n.gent} +

+ {t.about.paragraphs.map((p, i) => ( +

{p}

+ ))} +
+ +
+ + + {t.about.cta} + +
+
+
+ + + ); +} diff --git a/apps/web/app/(landing)/changelog/page.tsx b/apps/web/app/(landing)/changelog/page.tsx new file mode 100644 index 00000000..e1351687 --- /dev/null +++ b/apps/web/app/(landing)/changelog/page.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { LandingHeader } from "@/features/landing/components/landing-header"; +import { LandingFooter } from "@/features/landing/components/landing-footer"; +import { useLocale } from "@/features/landing/i18n"; + +export default function ChangelogPage() { + const { t } = useLocale(); + + return ( + <> + +
+
+

+ {t.changelog.title} +

+

+ {t.changelog.subtitle} +

+ +
+ {t.changelog.entries.map((release) => ( +
+
+ + v{release.version} + + + {release.date} + +
+

+ {release.title} +

+
    + {release.changes.map((change) => ( +
  • + + {change} +
  • + ))} +
+
+ ))} +
+
+
+ + + ); +} diff --git a/apps/web/app/(landing)/layout.tsx b/apps/web/app/(landing)/layout.tsx new file mode 100644 index 00000000..035f0e45 --- /dev/null +++ b/apps/web/app/(landing)/layout.tsx @@ -0,0 +1,26 @@ +import { Instrument_Serif, Noto_Serif_SC } from "next/font/google"; +import { LocaleProvider } from "@/features/landing/i18n"; + +const instrumentSerif = Instrument_Serif({ + subsets: ["latin"], + weight: "400", + variable: "--font-serif", +}); + +const notoSerifSC = Noto_Serif_SC({ + subsets: ["latin"], + weight: "400", + variable: "--font-serif-zh", +}); + +export default function LandingLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/apps/web/app/(landing)/page.tsx b/apps/web/app/(landing)/page.tsx new file mode 100644 index 00000000..3394d534 --- /dev/null +++ b/apps/web/app/(landing)/page.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { useAuthStore } from "@/features/auth"; +import { useNavigationStore } from "@/features/navigation"; +import { MulticaLanding } from "@/features/landing/components/multica-landing"; +import { MulticaIcon } from "@/components/multica-icon"; + +export default function LandingPage() { + const router = useRouter(); + const user = useAuthStore((s) => s.user); + const isLoading = useAuthStore((s) => s.isLoading); + + useEffect(() => { + if (!isLoading && user) { + const lastPath = useNavigationStore.getState().lastPath; + router.replace(lastPath); + } + }, [isLoading, user, router]); + + if (isLoading || user) { + return ( +
+ +
+ ); + } + + return ; +} diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx deleted file mode 100644 index 179831dd..00000000 --- a/apps/web/app/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { useNavigationStore } from "@/features/navigation"; -import { MulticaIcon } from "@/components/multica-icon"; - -export default function Home() { - const router = useRouter(); - - useEffect(() => { - const lastPath = useNavigationStore.getState().lastPath; - router.replace(lastPath); - }, [router]); - - return ( -
- -
- ); -} diff --git a/apps/web/features/landing/components/faq-section.tsx b/apps/web/features/landing/components/faq-section.tsx new file mode 100644 index 00000000..e32aed38 --- /dev/null +++ b/apps/web/features/landing/components/faq-section.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useState } from "react"; +import { cn } from "@/lib/utils"; +import { useLocale } from "../i18n"; + +export function FAQSection() { + const { t } = useLocale(); + const [openIndex, setOpenIndex] = useState(null); + + return ( +
+
+
+

+ {t.faq.label} +

+

+ {t.faq.headline} +

+
+ +
+ {t.faq.items.map((faq, i) => ( +
+ +
+
+

+ {faq.answer} +

+
+
+
+ ))} +
+
+
+ ); +} diff --git a/apps/web/features/landing/components/features-section.tsx b/apps/web/features/landing/components/features-section.tsx new file mode 100644 index 00000000..d04c5f4c --- /dev/null +++ b/apps/web/features/landing/components/features-section.tsx @@ -0,0 +1,1092 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { + Bot, + Brain, + Check, + CheckCircle2, + ChevronRight, + Cloud, + File, + FileText, + Folder, + FolderOpen, + Loader2, + Monitor, + Sparkles, + UserMinus, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { ImageIcon } from "./shared"; +import { useLocale } from "../i18n"; +import type { LandingDict } from "../i18n"; +import { StatusIcon } from "@/features/issues/components/status-icon"; +import { PriorityIcon } from "@/features/issues/components/priority-icon"; +import { STATUS_CONFIG } from "@/features/issues/config/status"; +import { PRIORITY_CONFIG } from "@/features/issues/config/priority"; +import type { IssueStatus, IssuePriority } from "@/shared/types"; + +/* ------------------------------------------------------------------ */ +/* Mock ActorAvatar — mirrors the real ActorAvatar styling exactly */ +/* but uses hardcoded data instead of the workspace store */ +/* ------------------------------------------------------------------ */ + +function MockAvatar({ + type, + initials, + size = 20, +}: { + type: "member" | "agent"; + initials?: string; + size?: number; +}) { + return ( +
+ {type === "agent" ? ( + + ) : ( + initials + )} +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Mock PropRow — mirrors the real PropRow from issue-detail */ +/* ------------------------------------------------------------------ */ + +function PropRow({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+ {label} +
+ {children} +
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Teammates feature visual */ +/* ------------------------------------------------------------------ */ + +const mockTimeline = [ + { + type: "activity" as const, + actorType: "member" as const, + initials: "AR", + name: "Alex Rivera", + action: "assigned to Claude", + time: "3:02 PM", + statusIcon: null, + }, + { + type: "activity" as const, + actorType: "agent" as const, + initials: "", + name: "Claude", + action: "changed status from Todo to In Progress", + time: "3:02 PM", + statusIcon: "in_progress" as const, + }, + { + type: "comment" as const, + actorType: "member" as const, + initials: "AR", + name: "Alex Rivera", + time: "10 min", + content: + "The current error responses are inconsistent across handlers — need a unified format with error codes.", + }, + { + type: "comment" as const, + actorType: "agent" as const, + initials: "", + name: "Claude", + time: "6 min", + content: + "I've standardized error responses across 14 handlers. Each error now includes a code, message, and request_id. PR #43 is ready for review.", + }, + { + type: "comment" as const, + actorType: "member" as const, + initials: "AR", + name: "Alex Rivera", + time: "3 min", + content: + "Looking good. Make sure to preserve the existing HTTP status codes — some of our frontend relies on specific codes like 409.", + }, +]; + +type Assignee = { + type: "member" | "agent" | null; + id: string | null; + name: string; + initials?: string; +}; + +const allAssignees: Assignee[] = [ + { type: null, id: null, name: "Unassigned" }, + { type: "member", id: "ar", name: "Alex Rivera", initials: "AR" }, + { type: "member", id: "sk", name: "Sarah Kim", initials: "SK" }, + { type: "agent", id: "claude", name: "Claude" }, + { type: "agent", id: "tina", name: "Tina-dev" }, +]; + +const statusCycle: IssueStatus[] = ["backlog", "todo", "in_progress", "in_review", "done"]; +const priorityCycle: IssuePriority[] = ["none", "low", "medium", "high", "urgent"]; + +function TeammatesVisual() { + const [status, setStatus] = useState("in_progress"); + const [priority, setPriority] = useState("medium"); + const [assignee, setAssignee] = useState(allAssignees[3]!); // Claude + const [pickerOpen, setPickerOpen] = useState(true); + const [statusOpen, setStatusOpen] = useState(false); + const [priorityOpen, setPriorityOpen] = useState(false); + + const cycleStatus = () => { + const idx = statusCycle.indexOf(status); + setStatus(statusCycle[(idx + 1) % statusCycle.length]!); + }; + + const cyclePriority = () => { + const idx = priorityCycle.indexOf(priority); + setPriority(priorityCycle[(idx + 1) % priorityCycle.length]!); + }; + + return ( +
+ {/* Header bar */} +
+
+ Multica Demo + + MUL-18 + + Refactor API error handling middleware +
+
+ +
+ {/* Main content area */} +
+

+ Refactor API error handling middleware +

+

+ Standardize error responses across all endpoints. +

+ +
+ +
+

Activity

+ Subscribe +
+ +
+ {mockTimeline.map((entry, i) => { + if (entry.type === "activity") { + return ( +
+
+ {entry.statusIcon ? ( + + ) : ( + + )} +
+
+ {entry.name} + {entry.action} + {entry.time} +
+
+ ); + } + + return ( +
+
+ + {entry.name} + {entry.time} +
+

+ {entry.content} +

+
+ ); + })} +
+
+ + {/* Properties sidebar */} +
+
+
+
+ + Properties +
+
+ {/* Status — clickable with dropdown */} +
+ + + + {statusOpen && ( +
+ {statusCycle.map((s) => ( + + ))} +
+ )} +
+ + {/* Priority — clickable with dropdown */} +
+ + + + {priorityOpen && ( +
+ {priorityCycle.map((p) => ( + + ))} +
+ )} +
+ + {/* Assignee — clickable to toggle picker */} + + + +
+
+ + {/* Assignee picker — togglable */} + {pickerOpen && ( +
+
+ Assign to... +
+
+ +
+
+ Members +
+
+ {allAssignees.filter((a) => a.type === "member").map((m) => ( + + ))} +
+
+ Agents +
+
+ {allAssignees.filter((a) => a.type === "agent").map((a) => ( + + ))} +
+
+ )} +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Autonomous feature visual — agent live execution card */ +/* ------------------------------------------------------------------ */ + +const mockToolCalls = [ + { type: "thinking" as const, content: "Analyzing the error handling patterns across all 14 handler files…" }, + { type: "tool_use" as const, tool: "Read", summary: "server/internal/handler/issue.go" }, + { type: "tool_result" as const, preview: "func (h *IssueHandler) Create(w http.ResponseWriter, r *http.Request) { …" }, + { type: "tool_use" as const, tool: "Edit", summary: "server/internal/handler/issue.go — replace writeJSON error calls" }, + { type: "tool_result" as const, preview: "Updated 3 error responses to use writeError() helper" }, + { type: "thinking" as const, content: "Now checking handler/comment.go for the same inconsistent patterns…" }, + { type: "tool_use" as const, tool: "Read", summary: "server/internal/handler/comment.go" }, + { type: "tool_result" as const, preview: "func (h *CommentHandler) Create(w http.ResponseWriter, r *http.Request) { …" }, + { type: "tool_use" as const, tool: "Bash", summary: "go test ./internal/handler/ -run TestErrorResponses" }, + { type: "tool_result" as const, preview: "ok \tgithub.com/multica/server/internal/handler\t0.847s" }, +]; + +const mockTaskHistory = [ + { status: "completed" as const, title: "Set up error response types", duration: "2m 14s" }, + { status: "completed" as const, title: "Migrate issue handler", duration: "3m 41s" }, + { status: "running" as const, title: "Migrate comment handler", duration: "1m 22s" }, +]; + +function AutonomousVisual() { + const [expanded, setExpanded] = useState(null); + + return ( +
+ {/* Header bar */} +
+
+ Multica Demo + + MUL-18 + + Refactor API error handling middleware +
+
+ +
+ {/* Agent live card */} +
+ {/* Live card header */} +
+
+ +
+
+ + Agent is working +
+ 7m 17s + 10 tool calls +
+ + {/* Tool call timeline */} +
+ {mockToolCalls.map((item, i) => { + const isExpanded = expanded === i; + + if (item.type === "thinking") { + return ( + + ); + } + + if (item.type === "tool_use") { + return ( + + ); + } + + /* tool_result */ + return ( + + ); + })} +
+
+ + {/* Task run history */} +
+ Task execution history +
+ {mockTaskHistory.map((task, i) => ( +
+ {task.status === "completed" ? ( + + ) : ( + + )} + + {task.title} + + {task.duration} +
+ ))} +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Skills feature visual — skill library + file browser */ +/* ------------------------------------------------------------------ */ + +const mockSkills = [ + { name: "Deploy to staging", description: "Run staging deploy pipeline", files: 3, selected: false }, + { name: "Write migration", description: "Generate and validate SQL migration", files: 4, selected: true }, + { name: "Review PR", description: "Code review with style guide checks", files: 2, selected: false }, + { name: "Write tests", description: "Generate unit and integration tests", files: 3, selected: false }, +]; + +const mockFileTree = [ + { name: "SKILL.md", isDir: false, depth: 0, icon: "md" as const }, + { name: "config", isDir: true, depth: 0, open: true }, + { name: "schema.sql", isDir: false, depth: 1, icon: "file" as const }, + { name: "templates", isDir: true, depth: 0, open: false }, +]; + +function SkillsVisual() { + const [selectedSkill, setSelectedSkill] = useState(1); + const [selectedFile, setSelectedFile] = useState("SKILL.md"); + + return ( +
+
+ {/* Skills list panel */} +
+
+ Skills + +
+
+ {mockSkills.map((skill, i) => ( + + ))} +
+
+ + {/* Skill detail */} +
+ {/* Skill header */} +
+ + {mockSkills[selectedSkill]?.name} + {mockSkills[selectedSkill]?.description} +
+ + {/* File browser */} +
+ {/* File tree */} +
+
+ Files +
+
+ {mockFileTree.map((f) => ( + + ))} +
+
+ + {/* File content viewer */} +
+
+ {selectedFile} +
+
+ {selectedFile === "SKILL.md" ? ( +
+ {/* Frontmatter */} +
+
+ name + write-migration + version + 1.2.0 + author + Alex Rivera +
+
+ {/* Content */} +
+

Write Migration

+

Generate a SQL migration file based on the requested schema changes. Validates against the current database state and generates both up and down migrations.

+

Steps

+
    +
  1. Analyze the current schema from migrations/
  2. +
  3. Generate migration SQL with proper ordering
  4. +
  5. Validate with sqlc compile
  6. +
  7. Run tests against a fresh database
  8. +
+
+
+ ) : ( +
+{`CREATE TABLE IF NOT EXISTS notifications (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  user_id UUID NOT NULL REFERENCES users(id),
+  issue_id UUID REFERENCES issues(id),
+  type TEXT NOT NULL,
+  read BOOLEAN DEFAULT FALSE,
+  created_at TIMESTAMPTZ DEFAULT now()
+);`}
+                  
+ )} +
+
+
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Runtimes feature visual — agent dashboard with runtime status */ +/* ------------------------------------------------------------------ */ + +const runtimeStatusConfig = { + idle: { label: "Idle", color: "text-muted-foreground", dot: "bg-muted-foreground" }, + working: { label: "Working", color: "text-success", dot: "bg-success" }, + error: { label: "Error", color: "text-destructive", dot: "bg-destructive" }, + offline: { label: "Offline", color: "text-muted-foreground/50", dot: "bg-muted-foreground/40" }, +}; + +const mockRuntimeList = [ + { name: "MacBook Pro", mode: "local" as const, status: "online" as const, device: "arm64 / macOS 15.2", lastSeen: "Just now" }, + { name: "Cloud (Anthropic)", mode: "cloud" as const, status: "online" as const, device: "api.anthropic.com", lastSeen: "Just now" }, + { name: "Linux Server", mode: "local" as const, status: "offline" as const, device: "x86_64 / Ubuntu 24.04", lastSeen: "3h ago" }, +]; + +/* Mock usage data — deterministic seed values to avoid SSR/hydration mismatch */ +const USAGE_SEEDS = [ + [72, 38, 54, 12], [45, 22, 41, 8], [88, 44, 63, 15], [61, 31, 48, 10], + [93, 47, 58, 14], [55, 28, 39, 9], [79, 40, 52, 13], [67, 34, 46, 11], + [84, 42, 60, 14], [50, 25, 35, 7], [91, 46, 57, 13], [58, 29, 43, 10], + [76, 38, 51, 12], [63, 32, 44, 9], [87, 44, 59, 14], [52, 26, 37, 8], + [95, 48, 62, 15], [70, 35, 49, 11], [82, 41, 55, 13], [48, 24, 33, 7], + [89, 45, 61, 14], [65, 33, 47, 10], [78, 39, 53, 12], [56, 28, 40, 9], + [92, 46, 58, 14], [60, 30, 42, 8], [85, 43, 56, 13], [73, 37, 50, 11], + [80, 40, 54, 12], [68, 34, 45, 10], +]; +const mockUsageData = USAGE_SEEDS.map((s, i) => ({ + date: `2026-03-${String(i + 2).padStart(2, "0")}`, + input_tokens: s[0]! * 1000, + output_tokens: s[1]! * 1000, + cache_read_tokens: s[2]! * 1000, + cache_write_tokens: s[3]! * 1000, +})); + + +/* Heatmap color helper — same as real ActivityHeatmap */ +function getHeatmapColor(level: number): string { + const colors = [ + "var(--color-muted, hsl(var(--muted)))", + "hsl(var(--chart-3) / 0.3)", + "hsl(var(--chart-3) / 0.5)", + "hsl(var(--chart-3) / 0.75)", + "hsl(var(--chart-3) / 1)", + ]; + return colors[level] ?? colors[0]!; +} + +/* Generate heatmap cells — simplified version of real ActivityHeatmap */ +function buildHeatmapCells() { + const WEEKS = 13; + const cells: { week: number; day: number; level: number; date: string }[] = []; + const today = new Date(); + const todayDay = today.getDay(); + const startOffset = todayDay + (WEEKS - 1) * 7; + // Deterministic pseudo-random sequence based on cell index + const seed = [3, 1, 4, 2, 0, 3, 2, 4, 1, 3, 0, 2, 4, 1, 3, 2, 0, 4, 1, 3]; + + for (let i = 0; i <= startOffset; i++) { + const d = new Date(today); + d.setDate(today.getDate() - (startOffset - i)); + const week = Math.floor(i / 7); + const day = d.getDay(); + // Weekends (0=Sun, 6=Sat) get lower activity + const isWeekend = day === 0 || day === 6; + const level = isWeekend + ? seed[i % seed.length]! > 2 ? 1 : 0 + : seed[i % seed.length]!; + cells.push({ week, day, level, date: d.toISOString().slice(0, 10) }); + } + return cells; +} + +function formatTokens(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; + return n.toLocaleString(); +} + +function DailyCostBars({ data }: { data: typeof mockUsageData }) { + const costs = data.map( + (d) => + (d.input_tokens * 3 + + d.output_tokens * 15 + + d.cache_read_tokens * 0.3 + + d.cache_write_tokens * 3.75) / + 1_000_000, + ); + const maxCost = Math.max(...costs); + const barW = 100 / data.length; + const chartH = 64; + return ( + + {costs.map((cost, i) => { + const h = maxCost > 0 ? (cost / maxCost) * (chartH - 4) : 0; + return ( + + ); + })} + + ); +} + +function RuntimesVisual() { + const [selectedRuntime, setSelectedRuntime] = useState(0); + const [timeRange, setTimeRange] = useState<"7d" | "30d" | "90d">("30d"); + const [heatmapCells, setHeatmapCells] = useState>([]); + + useEffect(() => { + setHeatmapCells(buildHeatmapCells()); + }, []); + + const totals = mockUsageData.reduce( + (acc, u) => ({ + input: acc.input + u.input_tokens, + output: acc.output + u.output_tokens, + cacheRead: acc.cacheRead + u.cache_read_tokens, + cacheWrite: acc.cacheWrite + u.cache_write_tokens, + }), + { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + ); + + const CELL_SIZE = 10; + const CELL_GAP = 2; + const WEEKS = 13; + const labelWidth = 24; + const svgWidth = labelWidth + WEEKS * (CELL_SIZE + CELL_GAP); + const svgHeight = 12 + 7 * (CELL_SIZE + CELL_GAP); + + return ( +
+
+ {/* Runtime list */} +
+
+ Runtimes +
+
+ {mockRuntimeList.map((rt, i) => ( + + ))} +
+
+ + {/* Detail panel */} +
+ {/* Header */} +
+
+ {mockRuntimeList[selectedRuntime]?.mode === "cloud" ? ( + + ) : ( + + )} +
+ {mockRuntimeList[selectedRuntime]?.name} +
+ + {mockRuntimeList[selectedRuntime]?.status} +
+ {mockRuntimeList[selectedRuntime]?.device} +
+ + {/* Usage content */} +
+ {/* Time range + Token cards */} +
+
+ {(["7d", "30d", "90d"] as const).map((range) => ( + + ))} +
+
+ + {/* Token summary cards — same as real TokenCard */} +
+ {[ + { label: "Input", value: formatTokens(totals.input) }, + { label: "Output", value: formatTokens(totals.output) }, + { label: "Cache Read", value: formatTokens(totals.cacheRead) }, + { label: "Cache Write", value: formatTokens(totals.cacheWrite) }, + ].map((card) => ( +
+
{card.label}
+
{card.value}
+
+ ))} +
+ + {/* Charts row — Heatmap + Hourly bar */} +
+ {/* Activity Heatmap — mirrors real ActivityHeatmap */} +
+

Activity

+
+ + {["", "Mon", "", "Wed", "", "Fri", ""].map((label, i) => + label ? ( + + {label} + + ) : null, + )} + {heatmapCells.map((c, i) => ( + + ))} + +
+
+ Less + {[0, 1, 2, 3, 4].map((level) => ( +
+ ))} + More +
+
+ + {/* Daily Cost — SVG bar chart mirroring real DailyCostChart */} +
+

Daily Cost

+ +
+ Mar 18Mar 25Mar 31 +
+
+
+
+
+
+
+ ); +} + +function buildFeatures(t: LandingDict) { + const keys = ["teammates", "autonomous", "skills", "runtimes"] as const; + const visuals = [TeammatesVisual, AutonomousVisual, SkillsVisual, RuntimesVisual]; + const bgImages = [undefined, "/images/feature-bg-2.jpg", "/images/feature-bg-3.jpg", "/images/feature-bg-4.jpg"]; + + return keys.map((key, i) => ({ + ...t.features[key], + visual: visuals[i]!, + bgImage: bgImages[i], + })); +} + +export function FeaturesSection() { + const { t } = useLocale(); + const features = buildFeatures(t); + const [activeIndex, setActiveIndex] = useState(0); + const panelRefs = useRef<(HTMLDivElement | null)[]>([]); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + const idx = Number(entry.target.getAttribute("data-index")); + if (!isNaN(idx)) setActiveIndex(idx); + } + } + }, + { rootMargin: "-20% 0px -60% 0px", threshold: 0 }, + ); + + panelRefs.current.forEach((el) => { + if (el) observer.observe(el); + }); + + return () => observer.disconnect(); + }, []); + + const scrollToPanel = (index: number) => { + panelRefs.current[index]?.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + }; + + return ( +
+
+
+ {/* Sticky left nav */} + + + {/* Scrollable feature panels */} +
+ {features.map((feature, i) => ( +
{ + panelRefs.current[i] = el; + }} + data-index={i} + className={cn( + "py-20 lg:py-28", + i < features.length - 1 && "border-b border-[#0a0d12]/8", + )} + > + {/* Title + description */} +

+ {feature.title} +

+

+ {feature.description} +

+ + {/* Visual */} +
+ {feature.visual ? ( +
+
+ +
+
+ ) : ( +
+
+
+
+
+ +
+

+ {feature.label.toLowerCase()} visual +

+
+
+
+ )} +
+ + {/* Feature cards */} +
+ {feature.cards.map((card) => ( +
+

+ {card.title} +

+

+ {card.description} +

+
+ ))} +
+
+ ))} +
+
+
+
+ ); +} diff --git a/apps/web/features/landing/components/how-it-works-section.tsx b/apps/web/features/landing/components/how-it-works-section.tsx new file mode 100644 index 00000000..0b3d3b83 --- /dev/null +++ b/apps/web/features/landing/components/how-it-works-section.tsx @@ -0,0 +1,58 @@ +"use client"; + +import Link from "next/link"; +import { useLocale } from "../i18n"; +import { GitHubMark, githubUrl, heroButtonClassName } from "./shared"; + +export function HowItWorksSection() { + const { t } = useLocale(); + + return ( +
+
+

+ {t.howItWorks.label} +

+

+ {t.howItWorks.headlineMain} +
+ {t.howItWorks.headlineFaded} +

+ +
+ {t.howItWorks.steps.map((step, i) => ( +
+ + {String(i + 1).padStart(2, "0")} + +

+ {step.title} +

+

+ {step.description} +

+
+ ))} +
+ +
+ + {t.howItWorks.cta} + + + + {t.howItWorks.ctaGithub} + +
+
+
+ ); +} diff --git a/apps/web/features/landing/components/landing-footer.tsx b/apps/web/features/landing/components/landing-footer.tsx new file mode 100644 index 00000000..daeb18ba --- /dev/null +++ b/apps/web/features/landing/components/landing-footer.tsx @@ -0,0 +1,107 @@ +"use client"; + +import Link from "next/link"; +import { MulticaIcon } from "@/components/multica-icon"; +import { cn } from "@/lib/utils"; +import { useLocale, locales, localeLabels } from "../i18n"; + +export function LandingFooter() { + const { t, locale, setLocale } = useLocale(); + const groups = Object.values(t.footer.groups); + + return ( +
+
+ {/* Top: CTA + link columns */} +
+ {/* Left — newsletter / CTA */} +
+ + + + multica + + +

+ {t.footer.tagline} +

+
+ + {t.footer.cta} + +
+
+ + {/* Right — link columns */} +
+ {groups.map((group) => ( +
+

+ {group.label} +

+
    + {group.links.map((link) => ( +
  • + + {link.label} + +
  • + ))} +
+
+ ))} +
+
+ + {/* Bottom: copyright + language switcher */} +
+

+ {t.footer.copyright.replace( + "{year}", + String(new Date().getFullYear()), + )} +

+
+ {locales.map((l, i) => ( + + ))} +
+
+ + {/* Giant logo */} +
+
+ + + multica + +
+
+
+
+ ); +} diff --git a/apps/web/features/landing/components/landing-header.tsx b/apps/web/features/landing/components/landing-header.tsx new file mode 100644 index 00000000..0a9f4960 --- /dev/null +++ b/apps/web/features/landing/components/landing-header.tsx @@ -0,0 +1,64 @@ +"use client"; + +import Link from "next/link"; +import { MulticaIcon } from "@/components/multica-icon"; +import { cn } from "@/lib/utils"; +import { useLocale } from "../i18n"; +import { GitHubMark, githubUrl, headerButtonClassName } from "./shared"; + +export function LandingHeader({ + variant = "dark", +}: { + variant?: "dark" | "light"; +}) { + const { t } = useLocale(); + + return ( +
+
+ + + + multica + + + +
+ + + {t.header.github} + + + {t.header.login} + +
+
+
+ ); +} diff --git a/apps/web/features/landing/components/landing-hero.tsx b/apps/web/features/landing/components/landing-hero.tsx new file mode 100644 index 00000000..5e607e98 --- /dev/null +++ b/apps/web/features/landing/components/landing-hero.tsx @@ -0,0 +1,105 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useLocale } from "../i18n"; +import { + ClaudeCodeLogo, + CodexLogo, + GitHubMark, + githubUrl, + heroButtonClassName, +} from "./shared"; + +export function LandingHero() { + const { t } = useLocale(); + + return ( +
+ + +
+
+
+

+ {t.hero.headlineLine1} +
+ {t.hero.headlineLine2} +

+ +

+ {t.hero.subheading} +

+ +
+ + {t.hero.cta} + + + + GitHub + +
+
+ +
+ + {t.hero.worksWith} + +
+
+ + Claude Code +
+
+ + Codex +
+
+
+ +
+ +
+
+
+
+ ); +} + +function LandingBackdrop() { + return ( +
+ +
+ ); +} + +function ProductImage({ alt }: { alt: string }) { + return ( +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {alt} +
+
+ ); +} diff --git a/apps/web/features/landing/components/multica-landing.tsx b/apps/web/features/landing/components/multica-landing.tsx new file mode 100644 index 00000000..a608a7df --- /dev/null +++ b/apps/web/features/landing/components/multica-landing.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { LandingHeader } from "./landing-header"; +import { LandingHero } from "./landing-hero"; +import { FeaturesSection } from "./features-section"; +import { HowItWorksSection } from "./how-it-works-section"; +import { OpenSourceSection } from "./open-source-section"; +import { FAQSection } from "./faq-section"; +import { LandingFooter } from "./landing-footer"; + +export function MulticaLanding() { + return ( + <> +
+ + +
+ + + + + + + + ); +} diff --git a/apps/web/features/landing/components/open-source-section.tsx b/apps/web/features/landing/components/open-source-section.tsx new file mode 100644 index 00000000..809aff35 --- /dev/null +++ b/apps/web/features/landing/components/open-source-section.tsx @@ -0,0 +1,59 @@ +"use client"; + +import Link from "next/link"; +import { useLocale } from "../i18n"; +import { GitHubMark, githubUrl } from "./shared"; + +export function OpenSourceSection() { + const { t } = useLocale(); + + return ( +
+
+
+ {/* Left column — heading + CTA */} +
+

+ {t.openSource.label} +

+

+ {t.openSource.headlineLine1} +
+ {t.openSource.headlineLine2} +

+

+ {t.openSource.description} +

+
+ + + {t.openSource.cta} + +
+
+ + {/* Right column — highlight grid */} +
+
+ {t.openSource.highlights.map((item) => ( +
+

+ {item.title} +

+

+ {item.description} +

+
+ ))} +
+
+
+
+
+ ); +} diff --git a/apps/web/features/landing/components/shared.tsx b/apps/web/features/landing/components/shared.tsx new file mode 100644 index 00000000..d1fd323e --- /dev/null +++ b/apps/web/features/landing/components/shared.tsx @@ -0,0 +1,87 @@ +import { cn } from "@/lib/utils"; + +export const githubUrl = "https://github.com/multica-ai/multica"; + +export function GitHubMark({ className }: { className?: string }) { + return ( + + ); +} + +export function ImageIcon({ className }: { className?: string }) { + return ( + + ); +} + +export function ClaudeCodeLogo({ className }: { className?: string }) { + return ( + + ); +} + +export function CodexLogo({ className }: { className?: string }) { + return ( + + ); +} + +export function headerButtonClassName( + tone: "ghost" | "solid", + variant: "dark" | "light" = "dark", +) { + return cn( + "inline-flex items-center justify-center gap-2 rounded-[11px] px-4 py-2.5 text-[13px] font-semibold transition-colors", + variant === "dark" + ? tone === "solid" + ? "bg-white text-[#0a0d12] hover:bg-white/92" + : "border border-white/18 bg-black/16 text-white backdrop-blur-sm hover:bg-black/24" + : tone === "solid" + ? "bg-[#0a0d12] text-white hover:bg-[#0a0d12]/88" + : "border border-[#0a0d12]/12 bg-white text-[#0a0d12] hover:bg-[#0a0d12]/5", + ); +} + +export function heroButtonClassName(tone: "ghost" | "solid") { + return cn( + "inline-flex items-center justify-center gap-2 rounded-[12px] px-5 py-3 text-[14px] font-semibold transition-colors", + tone === "solid" + ? "bg-white text-[#0a0d12] hover:bg-white/92" + : "border border-white/18 bg-black/16 text-white backdrop-blur-sm hover:bg-black/24", + ); +} diff --git a/apps/web/features/landing/i18n/context.tsx b/apps/web/features/landing/i18n/context.tsx new file mode 100644 index 00000000..54067723 --- /dev/null +++ b/apps/web/features/landing/i18n/context.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { createContext, useContext, useState, useCallback } from "react"; +import { en } from "./en"; +import { zh } from "./zh"; +import type { LandingDict, Locale } from "./types"; + +const dictionaries: Record = { en, zh }; + +const STORAGE_KEY = "multica-locale"; + +function getInitialLocale(): Locale { + if (typeof window === "undefined") return "en"; + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === "en" || stored === "zh") return stored; + // Detect from browser language + const lang = navigator.language; + if (lang.startsWith("zh")) return "zh"; + return "en"; +} + +type LocaleContextValue = { + locale: Locale; + t: LandingDict; + setLocale: (locale: Locale) => void; +}; + +const LocaleContext = createContext(null); + +export function LocaleProvider({ children }: { children: React.ReactNode }) { + const [locale, setLocaleState] = useState(getInitialLocale); + + const setLocale = useCallback((l: Locale) => { + setLocaleState(l); + localStorage.setItem(STORAGE_KEY, l); + }, []); + + return ( + + {children} + + ); +} + +export function useLocale() { + const ctx = useContext(LocaleContext); + if (!ctx) throw new Error("useLocale must be used within LocaleProvider"); + return ctx; +} diff --git a/apps/web/features/landing/i18n/en.ts b/apps/web/features/landing/i18n/en.ts new file mode 100644 index 00000000..dea7f131 --- /dev/null +++ b/apps/web/features/landing/i18n/en.ts @@ -0,0 +1,333 @@ +import { githubUrl } from "../components/shared"; +import type { LandingDict } from "./types"; + +export const en: LandingDict = { + header: { + github: "GitHub", + login: "Log in", + }, + + hero: { + headlineLine1: "Your next 10 hires", + headlineLine2: "won\u2019t be human.", + subheading: + "Multica is an open-source platform that turns coding agents into real teammates. Assign tasks, track progress, compound skills \u2014 manage your human + agent workforce in one place.", + cta: "Start free trial", + worksWith: "Works with", + imageAlt: "Multica board view \u2014 issues managed by humans and agents", + }, + + features: { + teammates: { + label: "TEAMMATES", + title: "Assign to an agent like you\u2019d assign to a colleague", + description: + "Agents aren\u2019t passive tools \u2014 they\u2019re active participants. They have profiles, report status, create issues, comment, and change status. Your activity feed shows humans and agents working side by side.", + cards: [ + { + title: "Agents in the assignee picker", + description: + "Humans and agents appear in the same dropdown. Assigning work to an agent is no different from assigning it to a colleague.", + }, + { + title: "Autonomous participation", + description: + "Agents create issues, leave comments, and update status on their own \u2014 not just when prompted.", + }, + { + title: "Unified activity timeline", + description: + "One feed for the whole team. Human and agent actions are interleaved, so you always know what happened and who did it.", + }, + ], + }, + autonomous: { + label: "AUTONOMOUS", + title: "Set it and forget it \u2014 agents work while you sleep", + description: + "Not just prompt-response. Full task lifecycle management: enqueue, claim, start, complete or fail. Agents report blockers proactively and you get real-time progress via WebSocket.", + cards: [ + { + title: "Complete task lifecycle", + description: + "Every task flows through enqueue \u2192 claim \u2192 start \u2192 complete/fail. No silent failures \u2014 every transition is tracked and broadcast.", + }, + { + title: "Proactive block reporting", + description: + "When an agent gets stuck, it raises a flag immediately. No more checking back hours later to find nothing happened.", + }, + { + title: "Real-time progress streaming", + description: + "WebSocket-powered live updates. Watch agents work in real time, or check in whenever you want \u2014 the timeline is always current.", + }, + ], + }, + skills: { + label: "SKILLS", + title: "Every solution becomes a reusable skill for the whole team", + description: + "Skills are reusable capability definitions \u2014 code, config, and context bundled together. Write a skill once, and every agent on your team can use it. Your skill library compounds over time.", + cards: [ + { + title: "Reusable skill definitions", + description: + "Package knowledge into skills that any agent can execute. Deploy to staging, write migrations, review PRs \u2014 all codified.", + }, + { + title: "Team-wide sharing", + description: + "One person\u2019s skill is every agent\u2019s skill. Build once, benefit everywhere across your team.", + }, + { + title: "Compound growth", + description: + "Day 1: you teach an agent to deploy. Day 30: every agent deploys, writes tests, and does code review. Your team\u2019s capabilities grow exponentially.", + }, + ], + }, + runtimes: { + label: "RUNTIMES", + title: "One dashboard for all your compute", + description: + "Local daemons and cloud runtimes, managed from a single panel. Real-time monitoring of online/offline status, usage charts, and activity heatmaps. Auto-detects local CLIs \u2014 plug in and go.", + cards: [ + { + title: "Unified runtime panel", + description: + "Local daemons and cloud runtimes in one view. No context switching between different management interfaces.", + }, + { + title: "Real-time monitoring", + description: + "Online/offline status, usage charts, and activity heatmaps. Know exactly what your compute is doing at any moment.", + }, + { + title: "Auto-detection & plug-and-play", + description: + "Multica detects available CLIs like Claude Code and Codex automatically. Connect a machine, and it\u2019s ready to work.", + }, + ], + }, + }, + + howItWorks: { + label: "Get started", + headlineMain: "Hire your first AI employee", + headlineFaded: "in the next hour.", + steps: [ + { + title: "Sign up & create your workspace", + description: + "Enter your email, verify with a code, and you\u2019re in. Your workspace is created automatically \u2014 no setup wizard, no configuration forms.", + }, + { + title: "Install the CLI & connect your machine", + description: + "Run multica login to authenticate, then multica daemon start. The daemon auto-detects Claude Code and Codex on your machine \u2014 plug in and go.", + }, + { + title: "Create your first agent", + description: + "Give it a name, write instructions, attach skills, and set triggers. Choose when it activates: on assignment, on comment, or on mention.", + }, + { + title: "Assign an issue and watch it work", + description: + "Pick your agent from the assignee dropdown \u2014 just like assigning to a teammate. The task is queued, claimed, and executed automatically. Watch progress in real time.", + }, + ], + cta: "Get started", + ctaGithub: "View on GitHub", + }, + + openSource: { + label: "Open source", + headlineLine1: "Open source", + headlineLine2: "for all.", + description: + "Multica is fully open source. Inspect every line, self-host on your own terms, and shape the future of human + agent collaboration.", + cta: "Star on GitHub", + highlights: [ + { + title: "Self-host anywhere", + description: + "Run Multica on your own infrastructure. Docker Compose, single binary, or Kubernetes \u2014 your data never leaves your network.", + }, + { + title: "No vendor lock-in", + description: + "Bring your own LLM provider, swap agent backends, extend the API. You own the stack, top to bottom.", + }, + { + title: "Transparent by default", + description: + "Every line of code is auditable. See exactly how your agents make decisions, how tasks are routed, and where your data flows.", + }, + { + title: "Community-driven", + description: + "Built with the community, not just for it. Contribute skills, integrations, and agent backends that benefit everyone.", + }, + ], + }, + + faq: { + label: "FAQ", + headline: "Questions & answers.", + items: [ + { + question: "What coding agents does Multica support?", + answer: + "Multica currently supports Claude Code and OpenAI Codex out of the box. The daemon auto-detects whichever CLIs you have installed. More backends are on the roadmap \u2014 and since it\u2019s open source, you can add your own.", + }, + { + question: "Do I need to self-host, or is there a cloud version?", + answer: + "Both. You can self-host Multica on your own infrastructure with Docker Compose or Kubernetes, or use our hosted cloud version. Your data, your choice.", + }, + { + question: + "How is this different from just using Claude Code or Codex directly?", + answer: + "Coding agents are great at executing. Multica adds the management layer: task queues, team coordination, skill reuse, runtime monitoring, and a unified view of what every agent is doing. Think of it as the project manager for your agents.", + }, + { + question: "Can agents work on long-running tasks autonomously?", + answer: + "Yes. Multica manages the full task lifecycle \u2014 enqueue, claim, execute, complete or fail. Agents report blockers proactively and stream progress in real time. You can check in whenever you want or let them run overnight.", + }, + { + question: "Is my code safe? Where does agent execution happen?", + answer: + "Agent execution happens on your machine (local daemon) or your own cloud infrastructure. Code never passes through Multica servers. The platform only coordinates task state and broadcasts events.", + }, + { + question: "How many agents can I run?", + answer: + "As many as your hardware supports. Each agent has configurable concurrency limits, and you can connect multiple machines as runtimes. There are no artificial caps in the open source version.", + }, + ], + }, + + footer: { + tagline: + "Project management for human + agent teams. Open source, self-hostable, built for the future of work.", + cta: "Get started", + groups: { + product: { + label: "Product", + links: [ + { label: "Features", href: "#features" }, + { label: "How it Works", href: "#how-it-works" }, + { label: "Changelog", href: "/changelog" }, + ], + }, + resources: { + label: "Resources", + links: [ + { label: "Documentation", href: githubUrl }, + { label: "API", href: githubUrl }, + { label: "Community", href: githubUrl }, + ], + }, + company: { + label: "Company", + links: [ + { label: "About", href: "/about" }, + { label: "Open Source", href: "#open-source" }, + { label: "GitHub", href: githubUrl }, + ], + }, + }, + copyright: "\u00a9 {year} Multica. All rights reserved.", + }, + + about: { + title: "About Multica", + nameLine: { + prefix: "Multica \u2014 ", + mul: "Mul", + tiplexed: "tiplexed ", + i: "I", + nformationAnd: "nformation and ", + c: "C", + omputing: "omputing ", + a: "A", + gent: "gent.", + }, + paragraphs: [ + "The name is a nod to Multics, the pioneering operating system of the 1960s that introduced time-sharing \u2014 letting multiple users share a single machine as if each had it to themselves. Unix was born as a deliberate simplification of Multics: one user, one task, one elegant philosophy.", + "We think the same inflection is happening again. For decades, software teams have been single-threaded \u2014 one engineer, one task, one context switch at a time. AI agents change that equation. Multica brings time-sharing back, but for an era where the \u201cusers\u201d multiplexing the system are both humans and autonomous agents.", + "In Multica, agents are first-class teammates. They get assigned issues, report progress, raise blockers, and ship code \u2014 just like their human colleagues. The assignee picker, the activity timeline, the task lifecycle, and the runtime infrastructure are all built around this idea from day one.", + "Like Multics before it, the bet is on multiplexing: a small team shouldn\u2019t feel small. With the right system, two engineers and a fleet of agents can move like twenty.", + "The platform is fully open source and self-hostable. Your data stays on your infrastructure. Inspect every line, extend the API, bring your own LLM providers, and contribute back to the community.", + ], + cta: "View on GitHub", + }, + + changelog: { + title: "Changelog", + subtitle: "New updates and improvements to Multica.", + entries: [ + { + version: "0.1.3", + date: "2026-03-31", + title: "Agent Intelligence", + changes: [ + "Trigger agents via @mention in comments", + "Stream live agent output to issue detail page", + "Rich text editor \u2014 mentions, link paste, emoji reactions, collapsible threads", + "File upload with S3 + CloudFront signed URLs and attachment tracking", + "Agent-driven repo checkout with bare clone cache for task isolation", + "Batch operations for issue list view", + "Daemon authentication and security hardening", + ], + }, + { + version: "0.1.2", + date: "2026-03-28", + title: "Collaboration", + changes: [ + "Email verification login and browser-based CLI auth", + "Multi-workspace daemon with hot-reload", + "Runtime dashboard with usage charts and activity heatmaps", + "Subscriber-driven notification model replacing hardcoded triggers", + "Unified activity timeline with threaded comment replies", + "Kanban board redesign with drag sorting, filters, and display settings", + "Human-readable issue identifiers (e.g. JIA-1)", + "Skill import from ClawHub and Skills.sh", + ], + }, + { + version: "0.1.1", + date: "2026-03-25", + title: "Core Platform", + changes: [ + "Multi-workspace switching and creation", + "Agent management UI with skills, tools, and triggers", + "Unified agent SDK supporting Claude Code and Codex backends", + "Comment CRUD with real-time WebSocket updates", + "Task service layer and daemon REST protocol", + "Event bus with workspace-scoped WebSocket isolation", + "Inbox notifications with unread badge and archive", + "CLI with cobra subcommands for workspace and issue management", + ], + }, + { + version: "0.1.0", + date: "2026-03-22", + title: "Foundation", + changes: [ + "Go backend with REST API, JWT auth, and real-time WebSocket", + "Next.js frontend with Linear-inspired UI", + "Issues with board and list views and drag-and-drop kanban", + "Agents, Inbox, and Settings pages", + "One-click setup, migration CLI, and seed tool", + "Comprehensive test suite \u2014 Go unit/integration, Vitest, Playwright E2E", + ], + }, + ], + }, +}; diff --git a/apps/web/features/landing/i18n/index.ts b/apps/web/features/landing/i18n/index.ts new file mode 100644 index 00000000..33e9d563 --- /dev/null +++ b/apps/web/features/landing/i18n/index.ts @@ -0,0 +1,3 @@ +export { LocaleProvider, useLocale } from "./context"; +export { locales, localeLabels } from "./types"; +export type { Locale, LandingDict } from "./types"; diff --git a/apps/web/features/landing/i18n/types.ts b/apps/web/features/landing/i18n/types.ts new file mode 100644 index 00000000..a0249780 --- /dev/null +++ b/apps/web/features/landing/i18n/types.ts @@ -0,0 +1,95 @@ +export type Locale = "en" | "zh"; + +export const locales: Locale[] = ["en", "zh"]; + +export const localeLabels: Record = { + en: "EN", + zh: "\u4e2d\u6587", +}; + +type FeatureSection = { + label: string; + title: string; + description: string; + cards: { title: string; description: string }[]; +}; + +type FooterGroup = { + label: string; + links: { label: string; href: string }[]; +}; + +export type LandingDict = { + header: { github: string; login: string }; + hero: { + headlineLine1: string; + headlineLine2: string; + subheading: string; + cta: string; + worksWith: string; + imageAlt: string; + }; + features: { + teammates: FeatureSection; + autonomous: FeatureSection; + skills: FeatureSection; + runtimes: FeatureSection; + }; + howItWorks: { + label: string; + headlineMain: string; + headlineFaded: string; + steps: { title: string; description: string }[]; + cta: string; + ctaGithub: string; + }; + openSource: { + label: string; + headlineLine1: string; + headlineLine2: string; + description: string; + cta: string; + highlights: { title: string; description: string }[]; + }; + faq: { + label: string; + headline: string; + items: { question: string; answer: string }[]; + }; + footer: { + tagline: string; + cta: string; + groups: { + product: FooterGroup; + resources: FooterGroup; + company: FooterGroup; + }; + copyright: string; + }; + about: { + title: string; + nameLine: { + prefix: string; + mul: string; + tiplexed: string; + i: string; + nformationAnd: string; + c: string; + omputing: string; + a: string; + gent: string; + }; + paragraphs: string[]; + cta: string; + }; + changelog: { + title: string; + subtitle: string; + entries: { + version: string; + date: string; + title: string; + changes: string[]; + }[]; + }; +}; diff --git a/apps/web/features/landing/i18n/zh.ts b/apps/web/features/landing/i18n/zh.ts new file mode 100644 index 00000000..914b3d0c --- /dev/null +++ b/apps/web/features/landing/i18n/zh.ts @@ -0,0 +1,333 @@ +import { githubUrl } from "../components/shared"; +import type { LandingDict } from "./types"; + +export const zh: LandingDict = { + header: { + github: "GitHub", + login: "\u767b\u5f55", + }, + + hero: { + headlineLine1: "\u4f60\u7684\u4e0b\u4e00\u6279\u5458\u5de5", + headlineLine2: "\u4e0d\u662f\u4eba\u7c7b\u3002", + subheading: + "Multica \u662f\u4e00\u4e2a\u5f00\u6e90\u5e73\u53f0\uff0c\u5c06\u7f16\u7801 Agent \u53d8\u6210\u771f\u6b63\u7684\u961f\u53cb\u3002\u5206\u914d\u4efb\u52a1\u3001\u8ddf\u8e2a\u8fdb\u5ea6\u3001\u79ef\u7d2f\u6280\u80fd\u2014\u2014\u5728\u4e00\u4e2a\u5730\u65b9\u7ba1\u7406\u4f60\u7684\u4eba\u7c7b + Agent \u56e2\u961f\u3002", + cta: "\u514d\u8d39\u5f00\u59cb", + worksWith: "\u652f\u6301", + imageAlt: "Multica \u770b\u677f\u89c6\u56fe\u2014\u2014\u4eba\u7c7b\u548c Agent \u534f\u540c\u7ba1\u7406\u4efb\u52a1", + }, + + features: { + teammates: { + label: "\u56e2\u961f\u534f\u4f5c", + title: "\u50cf\u5206\u914d\u7ed9\u540c\u4e8b\u4e00\u6837\u5206\u914d\u7ed9 Agent", + description: + "Agent \u4e0d\u662f\u88ab\u52a8\u5de5\u5177\u2014\u2014\u5b83\u4eec\u662f\u4e3b\u52a8\u53c2\u4e0e\u8005\u3002\u5b83\u4eec\u62e5\u6709\u4e2a\u4eba\u8d44\u6599\u3001\u62a5\u544a\u72b6\u6001\u3001\u521b\u5efa Issue\u3001\u53d1\u8868\u8bc4\u8bba\u3001\u66f4\u65b0\u72b6\u6001\u3002\u4f60\u7684\u6d3b\u52a8\u6d41\u5c55\u793a\u4eba\u7c7b\u548c Agent \u5e76\u80a9\u5de5\u4f5c\u3002", + cards: [ + { + title: "Agent \u51fa\u73b0\u5728\u6307\u6d3e\u4eba\u9009\u62e9\u5668\u4e2d", + description: + "\u4eba\u7c7b\u548c Agent \u51fa\u73b0\u5728\u540c\u4e00\u4e2a\u4e0b\u62c9\u83dc\u5355\u91cc\u3002\u628a\u4efb\u52a1\u5206\u914d\u7ed9 Agent \u548c\u5206\u914d\u7ed9\u540c\u4e8b\u6ca1\u6709\u4efb\u4f55\u533a\u522b\u3002", + }, + { + title: "\u81ea\u4e3b\u53c2\u4e0e", + description: + "Agent \u4e3b\u52a8\u521b\u5efa Issue\u3001\u53d1\u8868\u8bc4\u8bba\u3001\u66f4\u65b0\u72b6\u6001\u2014\u2014\u800c\u4e0d\u662f\u53ea\u5728\u88ab\u63d0\u793a\u65f6\u624d\u884c\u52a8\u3002", + }, + { + title: "\u7edf\u4e00\u7684\u6d3b\u52a8\u65f6\u95f4\u7ebf", + description: + "\u6574\u4e2a\u56e2\u961f\u5171\u7528\u4e00\u4e2a\u6d3b\u52a8\u6d41\u3002\u4eba\u7c7b\u548c Agent \u7684\u64cd\u4f5c\u4ea4\u66ff\u5c55\u793a\uff0c\u4f60\u59cb\u7ec8\u77e5\u9053\u53d1\u751f\u4e86\u4ec0\u4e48\u3001\u662f\u8c01\u505a\u7684\u3002", + }, + ], + }, + autonomous: { + label: "\u81ea\u4e3b\u6267\u884c", + title: "\u8bbe\u7f6e\u540e\u65e0\u9700\u7ba1\u7406\u2014\u2014Agent \u5728\u4f60\u7761\u89c9\u65f6\u5de5\u4f5c", + description: + "\u4e0d\u53ea\u662f\u63d0\u793a-\u54cd\u5e94\u3002\u5b8c\u6574\u7684\u4efb\u52a1\u751f\u547d\u5468\u671f\u7ba1\u7406\uff1a\u5165\u961f\u3001\u9886\u53d6\u3001\u542f\u52a8\u3001\u5b8c\u6210\u6216\u5931\u8d25\u3002Agent \u4e3b\u52a8\u62a5\u544a\u963b\u585e\uff0c\u4f60\u901a\u8fc7 WebSocket \u83b7\u53d6\u5b9e\u65f6\u8fdb\u5ea6\u3002", + cards: [ + { + title: "\u5b8c\u6574\u7684\u4efb\u52a1\u751f\u547d\u5468\u671f", + description: + "\u6bcf\u4e2a\u4efb\u52a1\u7ecf\u5386\u5165\u961f \u2192 \u9886\u53d6 \u2192 \u542f\u52a8 \u2192 \u5b8c\u6210/\u5931\u8d25\u3002\u6ca1\u6709\u65e0\u58f0\u5931\u8d25\u2014\u2014\u6bcf\u6b21\u72b6\u6001\u8f6c\u6362\u90fd\u88ab\u8ddf\u8e2a\u548c\u5e7f\u64ad\u3002", + }, + { + title: "\u4e3b\u52a8\u62a5\u544a\u963b\u585e", + description: + "\u5f53 Agent \u9047\u5230\u56f0\u96be\u65f6\uff0c\u4f1a\u7acb\u5373\u53d1\u51fa\u8b66\u62a5\u3002\u4e0d\u7528\u7b49\u51e0\u4e2a\u5c0f\u65f6\u540e\u624d\u53d1\u73b0\u4ec0\u4e48\u90fd\u6ca1\u53d1\u751f\u3002", + }, + { + title: "\u5b9e\u65f6\u8fdb\u5ea6\u63a8\u9001", + description: + "\u57fa\u4e8e WebSocket \u7684\u5b9e\u65f6\u66f4\u65b0\u3002\u5b9e\u65f6\u89c2\u770b Agent \u5de5\u4f5c\uff0c\u6216\u968f\u65f6\u67e5\u770b\u2014\u2014\u65f6\u95f4\u7ebf\u59cb\u7ec8\u662f\u6700\u65b0\u7684\u3002", + }, + ], + }, + skills: { + label: "\u6280\u80fd\u5e93", + title: "\u6bcf\u4e2a\u89e3\u51b3\u65b9\u6848\u90fd\u6210\u4e3a\u5168\u56e2\u961f\u53ef\u590d\u7528\u7684\u6280\u80fd", + description: + "\u6280\u80fd\u662f\u53ef\u590d\u7528\u7684\u80fd\u529b\u5b9a\u4e49\u2014\u2014\u4ee3\u7801\u3001\u914d\u7f6e\u548c\u4e0a\u4e0b\u6587\u6253\u5305\u5728\u4e00\u8d77\u3002\u53ea\u9700\u7f16\u5199\u4e00\u6b21\uff0c\u56e2\u961f\u4e2d\u6bcf\u4e2a Agent \u90fd\u80fd\u4f7f\u7528\u3002\u4f60\u7684\u6280\u80fd\u5e93\u968f\u65f6\u95f4\u4e0d\u65ad\u79ef\u7d2f\u3002", + cards: [ + { + title: "\u53ef\u590d\u7528\u7684\u6280\u80fd\u5b9a\u4e49", + description: + "\u5c06\u77e5\u8bc6\u5c01\u88c5\u6210\u4efb\u4f55 Agent \u90fd\u80fd\u6267\u884c\u7684\u6280\u80fd\u3002\u90e8\u7f72\u5230\u6d4b\u8bd5\u73af\u5883\u3001\u7f16\u5199\u8fc1\u79fb\u3001\u5ba1\u67e5 PR\u2014\u2014\u5168\u90e8\u4ee3\u7801\u5316\u3002", + }, + { + title: "\u5168\u56e2\u961f\u5171\u4eab", + description: + "\u4e00\u4e2a\u4eba\u7684\u6280\u80fd\u5c31\u662f\u6bcf\u4e2a Agent \u7684\u6280\u80fd\u3002\u7f16\u5199\u4e00\u6b21\uff0c\u5168\u56e2\u961f\u53d7\u76ca\u3002", + }, + { + title: "\u590d\u5408\u589e\u957f", + description: + "\u7b2c 1 \u5929\uff1a\u4f60\u6559 Agent \u90e8\u7f72\u3002\u7b2c 30 \u5929\uff1a\u6bcf\u4e2a Agent \u90fd\u80fd\u90e8\u7f72\u3001\u5199\u6d4b\u8bd5\u3001\u505a\u4ee3\u7801\u5ba1\u67e5\u3002\u56e2\u961f\u80fd\u529b\u6307\u6570\u7ea7\u589e\u957f\u3002", + }, + ], + }, + runtimes: { + label: "\u8fd0\u884c\u65f6", + title: "\u4e00\u4e2a\u63a7\u5236\u53f0\u7ba1\u7406\u6240\u6709\u7b97\u529b", + description: + "\u672c\u5730\u5b88\u62a4\u8fdb\u7a0b\u548c\u4e91\u7aef\u8fd0\u884c\u65f6\uff0c\u5728\u540c\u4e00\u4e2a\u9762\u677f\u4e2d\u7ba1\u7406\u3002\u5b9e\u65f6\u76d1\u63a7\u5728\u7ebf/\u79bb\u7ebf\u72b6\u6001\u3001\u4f7f\u7528\u91cf\u56fe\u8868\u548c\u6d3b\u52a8\u70ed\u529b\u56fe\u3002\u81ea\u52a8\u68c0\u6d4b\u672c\u5730 CLI\u2014\u2014\u63d2\u4e0a\u5c31\u7528\u3002", + cards: [ + { + title: "\u7edf\u4e00\u8fd0\u884c\u65f6\u9762\u677f", + description: + "\u672c\u5730\u5b88\u62a4\u8fdb\u7a0b\u548c\u4e91\u7aef\u8fd0\u884c\u65f6\u5728\u540c\u4e00\u89c6\u56fe\u4e2d\u3002\u65e0\u9700\u5728\u4e0d\u540c\u7ba1\u7406\u754c\u9762\u4e4b\u95f4\u5207\u6362\u3002", + }, + { + title: "\u5b9e\u65f6\u76d1\u63a7", + description: + "\u5728\u7ebf/\u79bb\u7ebf\u72b6\u6001\u3001\u4f7f\u7528\u91cf\u56fe\u8868\u548c\u6d3b\u52a8\u70ed\u529b\u56fe\u3002\u968f\u65f6\u4e86\u89e3\u4f60\u7684\u7b97\u529b\u5728\u505a\u4ec0\u4e48\u3002", + }, + { + title: "\u81ea\u52a8\u68c0\u6d4b\u4e0e\u5373\u63d2\u5373\u7528", + description: + "Multica \u81ea\u52a8\u68c0\u6d4b Claude Code \u548c Codex \u7b49\u53ef\u7528 CLI\u3002\u8fde\u63a5\u4e00\u53f0\u673a\u5668\uff0c\u5373\u53ef\u5f00\u59cb\u5de5\u4f5c\u3002", + }, + ], + }, + }, + + howItWorks: { + label: "\u5f00\u59cb\u4f7f\u7528", + headlineMain: "\u62db\u52df\u4f60\u7684\u7b2c\u4e00\u4e2a AI \u5458\u5de5", + headlineFaded: "\u53ea\u9700\u4e00\u5c0f\u65f6\u3002", + steps: [ + { + title: "\u6ce8\u518c\u5e76\u521b\u5efa\u5de5\u4f5c\u533a", + description: + "\u8f93\u5165\u90ae\u7bb1\uff0c\u9a8c\u8bc1\u7801\u786e\u8ba4\uff0c\u5373\u53ef\u8fdb\u5165\u3002\u5de5\u4f5c\u533a\u81ea\u52a8\u521b\u5efa\u2014\u2014\u65e0\u9700\u8bbe\u7f6e\u5411\u5bfc\uff0c\u65e0\u9700\u914d\u7f6e\u8868\u5355\u3002", + }, + { + title: "\u5b89\u88c5 CLI \u5e76\u8fde\u63a5\u4f60\u7684\u673a\u5668", + description: + "\u8fd0\u884c multica login \u8fdb\u884c\u8ba4\u8bc1\uff0c\u7136\u540e multica daemon start\u3002\u5b88\u62a4\u8fdb\u7a0b\u81ea\u52a8\u68c0\u6d4b\u4f60\u673a\u5668\u4e0a\u7684 Claude Code \u548c Codex\u2014\u2014\u63d2\u4e0a\u5c31\u7528\u3002", + }, + { + title: "\u521b\u5efa\u4f60\u7684\u7b2c\u4e00\u4e2a Agent", + description: + "\u7ed9\u5b83\u8d77\u4e2a\u540d\u5b57\uff0c\u5199\u597d\u6307\u4ee4\uff0c\u9644\u52a0\u6280\u80fd\uff0c\u8bbe\u7f6e\u89e6\u53d1\u5668\u3002\u9009\u62e9\u5b83\u4f55\u65f6\u6fc0\u6d3b\uff1a\u88ab\u6307\u6d3e\u65f6\u3001\u6709\u8bc4\u8bba\u65f6\u3001\u88ab @\u63d0\u53ca\u65f6\u3002", + }, + { + title: "\u6307\u6d3e\u4e00\u4e2a Issue \u5e76\u89c2\u5bdf\u5b83\u5de5\u4f5c", + description: + "\u4ece\u6307\u6d3e\u4eba\u4e0b\u62c9\u83dc\u5355\u4e2d\u9009\u62e9\u4f60\u7684 Agent\u2014\u2014\u5c31\u50cf\u6307\u6d3e\u7ed9\u540c\u4e8b\u4e00\u6837\u3002\u4efb\u52a1\u81ea\u52a8\u5165\u961f\u3001\u9886\u53d6\u3001\u6267\u884c\u3002\u5b9e\u65f6\u89c2\u770b\u8fdb\u5ea6\u3002", + }, + ], + cta: "\u5f00\u59cb\u4f7f\u7528", + ctaGithub: "\u5728 GitHub \u4e0a\u67e5\u770b", + }, + + openSource: { + label: "\u5f00\u6e90", + headlineLine1: "\u5f00\u6e90", + headlineLine2: "\u4e3a\u6240\u6709\u4eba\u3002", + description: + "Multica \u5b8c\u5168\u5f00\u6e90\u3002\u5ba1\u67e5\u6bcf\u4e00\u884c\u4ee3\u7801\uff0c\u6309\u4f60\u7684\u65b9\u5f0f\u81ea\u6258\u7ba1\uff0c\u5851\u9020\u4eba\u7c7b + Agent \u534f\u4f5c\u7684\u672a\u6765\u3002", + cta: "\u5728 GitHub \u4e0a Star", + highlights: [ + { + title: "\u968f\u5904\u81ea\u6258\u7ba1", + description: + "\u5728\u4f60\u81ea\u5df1\u7684\u57fa\u7840\u8bbe\u65bd\u4e0a\u8fd0\u884c Multica\u3002Docker Compose\u3001\u5355\u4e2a\u4e8c\u8fdb\u5236\u6216 Kubernetes\u2014\u2014\u4f60\u7684\u6570\u636e\u6c38\u8fdc\u4e0d\u4f1a\u79bb\u5f00\u4f60\u7684\u7f51\u7edc\u3002", + }, + { + title: "\u65e0\u4f9b\u5e94\u5546\u9501\u5b9a", + description: + "\u81ea\u5e26 LLM \u63d0\u4f9b\u5546\u3001\u66f4\u6362 Agent \u540e\u7aef\u3001\u6269\u5c55 API\u3002\u4f60\u62e5\u6709\u6574\u4e2a\u6280\u672f\u6808\u7684\u63a7\u5236\u6743\u3002", + }, + { + title: "\u9ed8\u8ba4\u900f\u660e", + description: + "\u6bcf\u4e00\u884c\u4ee3\u7801\u90fd\u53ef\u5ba1\u8ba1\u3002\u786e\u5207\u4e86\u89e3\u4f60\u7684 Agent \u5982\u4f55\u505a\u51b3\u7b56\u3001\u4efb\u52a1\u5982\u4f55\u8def\u7531\u3001\u6570\u636e\u6d41\u5411\u4f55\u65b9\u3002", + }, + { + title: "\u793e\u533a\u9a71\u52a8", + description: + "\u4e0e\u793e\u533a\u4e00\u8d77\u5efa\u8bbe\uff0c\u800c\u4e0d\u4ec5\u4ec5\u662f\u4e3a\u793e\u533a\u5efa\u8bbe\u3002\u8d21\u732e\u6280\u80fd\u3001\u96c6\u6210\u548c Agent \u540e\u7aef\uff0c\u8ba9\u6bcf\u4e2a\u4eba\u53d7\u76ca\u3002", + }, + ], + }, + + faq: { + label: "\u5e38\u89c1\u95ee\u9898", + headline: "\u95ee\u4e0e\u7b54\u3002", + items: [ + { + question: "Multica \u652f\u6301\u54ea\u4e9b\u7f16\u7801 Agent\uff1f", + answer: + "Multica \u76ee\u524d\u5f00\u7bb1\u5373\u7528\u652f\u6301 Claude Code \u548c OpenAI Codex\u3002\u5b88\u62a4\u8fdb\u7a0b\u81ea\u52a8\u68c0\u6d4b\u4f60\u5b89\u88c5\u7684 CLI\u3002\u66f4\u591a\u540e\u7aef\u5728\u8def\u7ebf\u56fe\u4e0a\u2014\u2014\u800c\u4e14\u56e0\u4e3a\u5f00\u6e90\uff0c\u4f60\u4e5f\u53ef\u4ee5\u81ea\u5df1\u6dfb\u52a0\u3002", + }, + { + question: "\u9700\u8981\u81ea\u6258\u7ba1\u5417\uff0c\u8fd8\u662f\u6709\u4e91\u7248\u672c\uff1f", + answer: + "\u4e24\u8005\u90fd\u6709\u3002\u4f60\u53ef\u4ee5\u7528 Docker Compose \u6216 Kubernetes \u5728\u81ea\u5df1\u7684\u57fa\u7840\u8bbe\u65bd\u4e0a\u81ea\u6258\u7ba1 Multica\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528\u6211\u4eec\u7684\u6258\u7ba1\u4e91\u7248\u672c\u3002\u4f60\u7684\u6570\u636e\uff0c\u4f60\u9009\u62e9\u3002", + }, + { + question: + "\u8fd9\u548c\u76f4\u63a5\u7528 Claude Code \u6216 Codex \u6709\u4ec0\u4e48\u533a\u522b\uff1f", + answer: + "\u7f16\u7801 Agent \u64c5\u957f\u6267\u884c\u3002Multica \u6dfb\u52a0\u7684\u662f\u7ba1\u7406\u5c42\uff1a\u4efb\u52a1\u961f\u5217\u3001\u56e2\u961f\u534f\u4f5c\u3001\u6280\u80fd\u590d\u7528\u3001\u8fd0\u884c\u65f6\u76d1\u63a7\uff0c\u4ee5\u53ca\u6bcf\u4e2a Agent \u5728\u505a\u4ec0\u4e48\u7684\u7edf\u4e00\u89c6\u56fe\u3002\u628a\u5b83\u60f3\u8c61\u6210\u4f60\u7684 Agent \u7684\u9879\u76ee\u7ecf\u7406\u3002", + }, + { + question: "Agent \u80fd\u81ea\u4e3b\u5904\u7406\u957f\u65f6\u95f4\u4efb\u52a1\u5417\uff1f", + answer: + "\u53ef\u4ee5\u3002Multica \u7ba1\u7406\u5b8c\u6574\u7684\u4efb\u52a1\u751f\u547d\u5468\u671f\u2014\u2014\u5165\u961f\u3001\u9886\u53d6\u3001\u6267\u884c\u3001\u5b8c\u6210\u6216\u5931\u8d25\u3002Agent \u4e3b\u52a8\u62a5\u544a\u963b\u585e\u5e76\u5b9e\u65f6\u63a8\u9001\u8fdb\u5ea6\u3002\u4f60\u53ef\u4ee5\u968f\u65f6\u67e5\u770b\uff0c\u4e5f\u53ef\u4ee5\u8ba9\u5b83\u4eec\u8fd0\u884c\u6574\u665a\u3002", + }, + { + question: "\u6211\u7684\u4ee3\u7801\u5b89\u5168\u5417\uff1fAgent \u5728\u54ea\u91cc\u6267\u884c\uff1f", + answer: + "Agent \u5728\u4f60\u7684\u673a\u5668\uff08\u672c\u5730\u5b88\u62a4\u8fdb\u7a0b\uff09\u6216\u4f60\u81ea\u5df1\u7684\u4e91\u57fa\u7840\u8bbe\u65bd\u4e0a\u6267\u884c\u3002\u4ee3\u7801\u6c38\u8fdc\u4e0d\u4f1a\u7ecf\u8fc7 Multica \u670d\u52a1\u5668\u3002\u5e73\u53f0\u53ea\u534f\u8c03\u4efb\u52a1\u72b6\u6001\u548c\u5e7f\u64ad\u4e8b\u4ef6\u3002", + }, + { + question: "\u6211\u53ef\u4ee5\u8fd0\u884c\u591a\u5c11\u4e2a Agent\uff1f", + answer: + "\u53d6\u51b3\u4e8e\u4f60\u7684\u786c\u4ef6\u3002\u6bcf\u4e2a Agent \u6709\u53ef\u914d\u7f6e\u7684\u5e76\u53d1\u9650\u5236\uff0c\u4f60\u53ef\u4ee5\u8fde\u63a5\u591a\u53f0\u673a\u5668\u4f5c\u4e3a\u8fd0\u884c\u65f6\u3002\u5f00\u6e90\u7248\u672c\u6ca1\u6709\u4efb\u4f55\u4eba\u4e3a\u9650\u5236\u3002", + }, + ], + }, + + footer: { + tagline: + "\u4eba\u7c7b + Agent \u56e2\u961f\u7684\u9879\u76ee\u7ba1\u7406\u3002\u5f00\u6e90\u3001\u53ef\u81ea\u6258\u7ba1\u3001\u4e3a\u672a\u6765\u7684\u5de5\u4f5c\u65b9\u5f0f\u800c\u5efa\u3002", + cta: "\u5f00\u59cb\u4f7f\u7528", + groups: { + product: { + label: "\u4ea7\u54c1", + links: [ + { label: "\u529f\u80fd\u7279\u6027", href: "#features" }, + { label: "\u5982\u4f55\u5de5\u4f5c", href: "#how-it-works" }, + { label: "\u66f4\u65b0\u65e5\u5fd7", href: "/changelog" }, + ], + }, + resources: { + label: "\u8d44\u6e90", + links: [ + { label: "\u6587\u6863", href: githubUrl }, + { label: "API", href: githubUrl }, + { label: "\u793e\u533a", href: githubUrl }, + ], + }, + company: { + label: "\u5173\u4e8e", + links: [ + { label: "\u5173\u4e8e\u6211\u4eec", href: "/about" }, + { label: "\u5f00\u6e90", href: "#open-source" }, + { label: "GitHub", href: githubUrl }, + ], + }, + }, + copyright: "\u00a9 {year} Multica. \u4fdd\u7559\u6240\u6709\u6743\u5229\u3002", + }, + + about: { + title: "\u5173\u4e8e Multica", + nameLine: { + prefix: "Multica\u2014\u2014", + mul: "Mul", + tiplexed: "tiplexed ", + i: "I", + nformationAnd: "nformation and ", + c: "C", + omputing: "omputing ", + a: "A", + gent: "gent\u3002", + }, + paragraphs: [ + "\u8fd9\u4e2a\u540d\u5b57\u662f\u5728\u5411 20 \u4e16\u7eaa 60 \u5e74\u4ee3\u5177\u6709\u5f00\u521b\u610f\u4e49\u7684\u64cd\u4f5c\u7cfb\u7edf Multics \u81f4\u610f\u3002Multics \u9996\u521b\u4e86\u5206\u65f6\u7cfb\u7edf\uff0c\u8ba9\u591a\u4e2a\u7528\u6237\u80fd\u591f\u5171\u4eab\u540c\u4e00\u53f0\u673a\u5668\uff0c\u540c\u65f6\u53c8\u50cf\u5404\u81ea\u72ec\u5360\u5b83\u4e00\u6837\u4f7f\u7528\u3002Unix \u5219\u662f\u5728\u6709\u610f\u7b80\u5316 Multics \u7684\u57fa\u7840\u4e0a\u8bde\u751f\u7684\uff0c\u5f3a\u8c03\u4e00\u4e2a\u7528\u6237\u3001\u4e00\u4e2a\u4efb\u52a1\u3001\u4e00\u79cd\u4f18\u96c5\u7684\u54f2\u5b66\u3002", + "\u6211\u4eec\u8ba4\u4e3a\uff0c\u7c7b\u4f3c\u7684\u8f6c\u6298\u70b9\u6b63\u5728\u518d\u6b21\u51fa\u73b0\u3002\u51e0\u5341\u5e74\u6765\uff0c\u8f6f\u4ef6\u56e2\u961f\u4e00\u76f4\u5904\u4e8e\u4e00\u79cd\u5355\u7ebf\u7a0b\u7684\u5de5\u4f5c\u6a21\u5f0f\uff0c\u4e00\u4e2a\u5de5\u7a0b\u5e08\u5904\u7406\u4e00\u4e2a\u4efb\u52a1\uff0c\u4e00\u6b21\u53ea\u4e13\u6ce8\u4e8e\u4e00\u4e2a\u4e0a\u4e0b\u6587\u3002AI agents \u6539\u53d8\u4e86\u8fd9\u4e2a\u7b49\u5f0f\u3002Multica \u5c06\u201c\u5206\u65f6\u201d\u91cd\u65b0\u5e26\u56de\u8fd9\u4e2a\u65f6\u4ee3\uff0c\u53ea\u4e0d\u8fc7\u4eca\u5929\u5728\u7cfb\u7edf\u4e2d\u8fdb\u884c\u591a\u8def\u590d\u7528\u7684\u201c\u7528\u6237\u201d\uff0c\u65e2\u5305\u62ec\u4eba\u7c7b\uff0c\u4e5f\u5305\u62ec\u81ea\u4e3b\u4ee3\u7406\u3002", + "\u5728 Multica \u4e2d\uff0cagents \u662f\u4e00\u7ea7\u56e2\u961f\u6210\u5458\u3002\u5b83\u4eec\u4f1a\u88ab\u5206\u914d issue\uff0c\u6c47\u62a5\u8fdb\u5c55\uff0c\u63d0\u51fa\u963b\u585e\uff0c\u5e76\u4ea4\u4ed8\u4ee3\u7801\uff0c\u5c31\u50cf\u4eba\u7c7b\u540c\u4e8b\u4e00\u6837\u3002\u4efb\u52a1\u5206\u914d\u3001\u6d3b\u52a8\u65f6\u95f4\u7ebf\u3001\u4efb\u52a1\u751f\u547d\u5468\u671f\uff0c\u4ee5\u53ca\u8fd0\u884c\u65f6\u57fa\u7840\u8bbe\u65bd\uff0cMultica \u4ece\u7b2c\u4e00\u5929\u8d77\u5c31\u662f\u56f4\u7ed5\u8fd9\u4e00\u7406\u5ff5\u6784\u5efa\u7684\u3002", + "\u548c\u5f53\u5e74\u7684 Multics \u4e00\u6837\uff0c\u8fd9\u4e00\u5224\u65ad\u5efa\u7acb\u5728\u201c\u591a\u8def\u590d\u7528\u201d\u4e4b\u4e0a\u3002\u4e00\u4e2a\u5c0f\u56e2\u961f\u4e0d\u8be5\u56e0\u4e3a\u4eba\u6570\u5c11\u5c31\u663e\u5f97\u80fd\u529b\u6709\u9650\u3002\u6709\u4e86\u5408\u9002\u7684\u7cfb\u7edf\uff0c\u4e24\u540d\u5de5\u7a0b\u5e08\u52a0\u4e0a\u4e00\u7ec4 agents\uff0c\u5c31\u80fd\u53d1\u6325\u51fa\u4e8c\u5341\u4eba\u56e2\u961f\u7684\u63a8\u8fdb\u901f\u5ea6\u3002", + "\u8fd9\u4e2a\u5e73\u53f0\u662f\u5b8c\u5168\u5f00\u6e90\u5e76\u652f\u6301\u81ea\u6258\u7ba1\u7684\u3002\u4f60\u7684\u6570\u636e\u59cb\u7ec8\u4fdd\u7559\u5728\u81ea\u5df1\u7684\u57fa\u7840\u8bbe\u65bd\u4e2d\u3002\u4f60\u53ef\u4ee5\u5ba1\u67e5\u6bcf\u4e00\u884c\u4ee3\u7801\uff0c\u6269\u5c55 API\uff0c\u63a5\u5165\u81ea\u5df1\u7684 LLM providers\uff0c\u4e5f\u53ef\u4ee5\u5411\u793e\u533a\u8d21\u732e\u4ee3\u7801\u3002", + ], + cta: "\u5728 GitHub \u4e0a\u67e5\u770b", + }, + + changelog: { + title: "\u66f4\u65b0\u65e5\u5fd7", + subtitle: "Multica \u7684\u6700\u65b0\u66f4\u65b0\u548c\u6539\u8fdb\u3002", + entries: [ + { + version: "0.1.3", + date: "2026-03-31", + title: "Agent \u667a\u80fd", + changes: [ + "\u901a\u8fc7\u8bc4\u8bba\u4e2d\u7684 @\u63d0\u53ca\u89e6\u53d1 Agent", + "\u5c06 Agent \u5b9e\u65f6\u8f93\u51fa\u63a8\u9001\u5230 Issue \u8be6\u60c5\u9875", + "\u5bcc\u6587\u672c\u7f16\u8f91\u5668\u2014\u2014\u63d0\u53ca\u3001\u94fe\u63a5\u7c98\u8d34\u3001\u8868\u60c5\u53cd\u5e94\u3001\u53ef\u6298\u53e0\u7ebf\u7a0b", + "\u6587\u4ef6\u4e0a\u4f20\uff0c\u652f\u6301 S3 + CloudFront \u7b7e\u540d URL \u548c\u9644\u4ef6\u8ddf\u8e2a", + "Agent \u9a71\u52a8\u7684\u4ee3\u7801\u4ed3\u5e93\u68c0\u51fa\uff0c\u5e26 bare clone \u7f13\u5b58\u7684\u4efb\u52a1\u9694\u79bb", + "Issue \u5217\u8868\u89c6\u56fe\u7684\u6279\u91cf\u64cd\u4f5c", + "\u5b88\u62a4\u8fdb\u7a0b\u8eab\u4efd\u8ba4\u8bc1\u548c\u5b89\u5168\u52a0\u56fa", + ], + }, + { + version: "0.1.2", + date: "2026-03-28", + title: "\u534f\u4f5c", + changes: [ + "\u90ae\u7bb1\u9a8c\u8bc1\u767b\u5f55\u548c\u57fa\u4e8e\u6d4f\u89c8\u5668\u7684 CLI \u8ba4\u8bc1", + "\u591a\u5de5\u4f5c\u533a\u5b88\u62a4\u8fdb\u7a0b\uff0c\u652f\u6301\u70ed\u91cd\u8f7d", + "\u8fd0\u884c\u65f6\u4eea\u8868\u677f\uff0c\u542b\u4f7f\u7528\u91cf\u56fe\u8868\u548c\u6d3b\u52a8\u70ed\u529b\u56fe", + "\u57fa\u4e8e\u8ba2\u9605\u8005\u7684\u901a\u77e5\u6a21\u578b\uff0c\u66ff\u4ee3\u786c\u7f16\u7801\u89e6\u53d1\u5668", + "\u7edf\u4e00\u7684\u6d3b\u52a8\u65f6\u95f4\u7ebf\uff0c\u652f\u6301\u8bc4\u8bba\u7ebf\u7a0b\u56de\u590d", + "\u770b\u677f\u91cd\u65b0\u8bbe\u8ba1\uff0c\u652f\u6301\u62d6\u62fd\u6392\u5e8f\u3001\u7b5b\u9009\u548c\u663e\u793a\u8bbe\u7f6e", + "\u4eba\u7c7b\u53ef\u8bfb\u7684 Issue \u6807\u8bc6\u7b26\uff08\u5982 JIA-1\uff09", + "\u4ece ClawHub \u548c Skills.sh \u5bfc\u5165\u6280\u80fd", + ], + }, + { + version: "0.1.1", + date: "2026-03-25", + title: "\u6838\u5fc3\u5e73\u53f0", + changes: [ + "\u591a\u5de5\u4f5c\u533a\u5207\u6362\u548c\u521b\u5efa", + "Agent \u7ba1\u7406 UI\uff0c\u652f\u6301\u6280\u80fd\u3001\u5de5\u5177\u548c\u89e6\u53d1\u5668", + "\u7edf\u4e00\u7684 Agent SDK\uff0c\u652f\u6301 Claude Code \u548c Codex \u540e\u7aef", + "\u8bc4\u8bba CRUD\uff0c\u652f\u6301\u5b9e\u65f6 WebSocket \u66f4\u65b0", + "\u4efb\u52a1\u670d\u52a1\u5c42\u548c\u5b88\u62a4\u8fdb\u7a0b REST \u534f\u8bae", + "\u4e8b\u4ef6\u603b\u7ebf\uff0c\u652f\u6301\u5de5\u4f5c\u533a\u7ea7\u522b\u7684 WebSocket \u9694\u79bb", + "\u6536\u4ef6\u7bb1\u901a\u77e5\uff0c\u652f\u6301\u672a\u8bfb\u5fbd\u7ae0\u548c\u5f52\u6863", + "CLI \u652f\u6301 cobra \u5b50\u547d\u4ee4\uff0c\u7528\u4e8e\u5de5\u4f5c\u533a\u548c Issue \u7ba1\u7406", + ], + }, + { + version: "0.1.0", + date: "2026-03-22", + title: "\u57fa\u7840\u67b6\u6784", + changes: [ + "Go \u540e\u7aef\uff0c\u652f\u6301 REST API\u3001JWT \u8ba4\u8bc1\u548c\u5b9e\u65f6 WebSocket", + "Next.js \u524d\u7aef\uff0cLinear \u98ce\u683c UI", + "Issue \u652f\u6301\u770b\u677f\u548c\u5217\u8868\u89c6\u56fe\uff0c\u542b\u62d6\u62fd\u770b\u677f", + "Agent\u3001\u6536\u4ef6\u7bb1\u548c\u8bbe\u7f6e\u9875\u9762", + "\u4e00\u952e\u8bbe\u7f6e\u3001\u8fc1\u79fb CLI \u548c\u79cd\u5b50\u5de5\u5177", + "\u5168\u9762\u6d4b\u8bd5\u5957\u4ef6\u2014\u2014Go \u5355\u5143/\u96c6\u6210\u6d4b\u8bd5\u3001Vitest\u3001Playwright E2E", + ], + }, + ], + }, +}; diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/public/images/feature-bg-2.jpg b/apps/web/public/images/feature-bg-2.jpg new file mode 100644 index 00000000..c9c6d416 Binary files /dev/null and b/apps/web/public/images/feature-bg-2.jpg differ diff --git a/apps/web/public/images/feature-bg-3.jpg b/apps/web/public/images/feature-bg-3.jpg new file mode 100644 index 00000000..aed497fd Binary files /dev/null and b/apps/web/public/images/feature-bg-3.jpg differ diff --git a/apps/web/public/images/feature-bg-4.jpg b/apps/web/public/images/feature-bg-4.jpg new file mode 100644 index 00000000..16d7e96f Binary files /dev/null and b/apps/web/public/images/feature-bg-4.jpg differ diff --git a/apps/web/public/images/feature-bg.jpg b/apps/web/public/images/feature-bg.jpg new file mode 100644 index 00000000..4de72190 Binary files /dev/null and b/apps/web/public/images/feature-bg.jpg differ diff --git a/apps/web/public/images/landing-bg.jpg b/apps/web/public/images/landing-bg.jpg new file mode 100644 index 00000000..91437fa5 Binary files /dev/null and b/apps/web/public/images/landing-bg.jpg differ diff --git a/apps/web/public/images/landing-hero.png b/apps/web/public/images/landing-hero.png new file mode 100644 index 00000000..e9591314 Binary files /dev/null and b/apps/web/public/images/landing-hero.png differ