Initial commit: img-infinite-canvas AI design workbench MVP

Moteva-style AI design workbench replica. Home prompt creates a project;
the project page provides an infinite canvas with node drag, zoom/pan,
a design chat panel, history replay, image asset upload, and project
save/regenerate.

- Backend: go-zero, DDD layering, sqlc/pgx, optional PostgreSQL; memory
  or Redis cache; asynq job queue; MinIO/S3/R2/OSS object storage;
  sky-valley/pi agent runtime adapter.
- Frontend: Next.js App Router + Vite artifact build, TypeScript, i18n,
  shadcn/ui components, auth (OTP/Turnstile/Google/WeChat).
- Deploy: Docker Compose and k3s manifests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-07 23:15:37 +08:00
commit 44406b72db
349 changed files with 73265 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
"use client";
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { readRoute, type AppRoute } from "@/application/route";
import { CanvasWorkspace } from "@/ui/pages/CanvasWorkspace";
import { HomePage } from "@/ui/pages/HomePage";
import { ProjectsRoutePage } from "@/ui/pages/ProjectsRoutePage";
import { useAuth } from "@/ui/auth/AuthProvider";
export function App() {
const { isAuthenticated, openLogin } = useAuth();
const [route, setRoute] = useState<AppRoute | null>(null);
useEffect(() => {
setRoute(readRoute());
const handleRoute = () => setRoute(readRoute());
window.addEventListener("hashchange", handleRoute);
return () => window.removeEventListener("hashchange", handleRoute);
}, []);
useEffect(() => {
if (route?.name === "project" && !isAuthenticated) {
openLogin();
}
}, [isAuthenticated, openLogin, route]);
if (!route) {
return (
<div className="workspace-loading">
<Loader2 className="spin" size={28} />
</div>
);
}
if (route.name === "project" && !isAuthenticated) return <HomePage />;
if (route.name === "project") return <CanvasWorkspace projectId={route.id} />;
if (route.name === "projects") return <ProjectsRoutePage />;
return <HomePage />;
}