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
@@ -0,0 +1,72 @@
"use client";
import { useEffect, useState } from "react";
import { openHome, openProject, openProjects } from "@/application/route";
import { designGateway } from "@/infrastructure/designGateway";
import { useAuth } from "@/ui/auth/AuthProvider";
import { useGlobalToast } from "@/ui/components/CanvasToast";
import { useUserMessage } from "@/ui/hooks/useUserMessage";
import { ProjectsPage } from "@/ui/pages/ProjectsPage";
import { SideNav, TopBar } from "@/ui/pages/HomeShell";
export function ProjectsRoutePage() {
const toUserMessageText = useUserMessage();
const { showError } = useGlobalToast();
const { isAuthenticated, openLogin, requireAuth } = useAuth();
const [credits, setCredits] = useState(30);
const [creating, setCreating] = useState(false);
useEffect(() => {
if (!isAuthenticated) {
setCredits(30);
return;
}
designGateway
.dashboard()
.then((dashboard) => setCredits(dashboard.credits))
.catch(() => undefined);
}, [isAuthenticated]);
useEffect(() => {
if (!isAuthenticated) openLogin();
}, [isAuthenticated, openLogin]);
const createProject = () => {
requireAuth(createAuthenticatedProject);
};
const createAuthenticatedProject = async () => {
setCreating(true);
try {
const { project } = await designGateway.createProjectAsync("", undefined, {
imageModel: "gpt-image-2",
allowEmptyPrompt: true
});
openProject(project.id);
} catch (err) {
showError(toUserMessageText(err, "createError"));
} finally {
setCreating(false);
}
};
return (
<div className={`home-shell projects-shell ${isAuthenticated ? "" : "is-auth-locked"}`}>
<TopBar credits={credits} onHome={openHome} />
<SideNav
active="projects"
onCreate={createProject}
onHome={openHome}
onProjects={() => {
if (isAuthenticated) {
openProjects();
return;
}
openLogin();
}}
onInspiration={openHome}
/>
<ProjectsPage locked={!isAuthenticated} creating={creating} onCreateProject={createProject} />
</div>
);
}