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,169 @@
-- name: ListProjects :many
SELECT id, user_id, title, brief, status, thumbnail, updated_at
FROM projects
WHERE user_id = $1
ORDER BY updated_at DESC;
-- name: ListProjectsPage :many
SELECT id, user_id, title, brief, status, thumbnail, updated_at
FROM projects
WHERE user_id = $1
ORDER BY updated_at DESC
LIMIT $2 OFFSET $3;
-- name: GetProject :one
SELECT id, user_id, title, brief, status, thumbnail, canvas, version, snapshot_id, last_thread_id, viewport_x, viewport_y, viewport_k, updated_at
FROM projects
WHERE id = $1 AND user_id = $2;
-- name: LockProject :one
SELECT id, user_id, version
FROM projects
WHERE id = $1 AND user_id = $2
FOR UPDATE;
-- name: DeleteProject :exec
DELETE FROM projects
WHERE id = $1 AND user_id = $2;
-- name: UpsertProject :exec
INSERT INTO projects (id, user_id, title, brief, status, thumbnail, canvas, version, snapshot_id, last_thread_id, viewport_x, viewport_y, viewport_k, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
ON CONFLICT (id) DO UPDATE SET
user_id = EXCLUDED.user_id,
title = EXCLUDED.title,
brief = EXCLUDED.brief,
status = EXCLUDED.status,
thumbnail = EXCLUDED.thumbnail,
canvas = EXCLUDED.canvas,
version = EXCLUDED.version,
snapshot_id = EXCLUDED.snapshot_id,
last_thread_id = EXCLUDED.last_thread_id,
viewport_x = EXCLUDED.viewport_x,
viewport_y = EXCLUDED.viewport_y,
viewport_k = EXCLUDED.viewport_k,
updated_at = EXCLUDED.updated_at;
-- name: CreateCanvasSnapshot :exec
INSERT INTO canvas_snapshots (id, project_id, user_id, version, canvas, viewport_json, nodes_json, connections_json, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (id) DO UPDATE SET
project_id = EXCLUDED.project_id,
user_id = EXCLUDED.user_id,
version = EXCLUDED.version,
canvas = EXCLUDED.canvas,
viewport_json = EXCLUDED.viewport_json,
nodes_json = EXCLUDED.nodes_json,
connections_json = EXCLUDED.connections_json,
created_at = EXCLUDED.created_at;
-- name: GetCanvasSnapshot :one
SELECT id, project_id, user_id, version, canvas, viewport_json, nodes_json, connections_json, created_at
FROM canvas_snapshots
WHERE id = $1 AND project_id = $2 AND user_id = $3;
-- name: UpdateProjectCanvasSnapshot :exec
UPDATE projects
SET thumbnail = $3,
canvas = $4,
version = $5,
snapshot_id = $6,
viewport_x = $7,
viewport_y = $8,
viewport_k = $9,
updated_at = $10
WHERE id = $1 AND user_id = $2;
-- name: ListNodesByProject :many
SELECT id, project_id, user_id, node_type, title, x, y, width, height, content, tone, status, sort_order, parent_id, layer_role, font_size, font_family, font_weight, color, text_align, line_height, letter_spacing, text_decoration, text_transform, opacity, fill_color, stroke_color, stroke_width, stroke_style, flip_x, flip_y, rotation
FROM canvas_nodes
WHERE project_id = $1 AND user_id = $2
ORDER BY sort_order ASC, id ASC;
-- name: DeleteNodesByProject :exec
DELETE FROM canvas_nodes
WHERE project_id = $1 AND user_id = $2;
-- name: UpsertCanvasNode :exec
INSERT INTO canvas_nodes (id, project_id, user_id, node_type, title, x, y, width, height, content, tone, status, sort_order, parent_id, layer_role, font_size, font_family, font_weight, color, text_align, line_height, letter_spacing, text_decoration, text_transform, opacity, fill_color, stroke_color, stroke_width, stroke_style, flip_x, flip_y, rotation)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32)
ON CONFLICT (id) DO UPDATE SET
project_id = EXCLUDED.project_id,
user_id = EXCLUDED.user_id,
node_type = EXCLUDED.node_type,
title = EXCLUDED.title,
x = EXCLUDED.x,
y = EXCLUDED.y,
width = EXCLUDED.width,
height = EXCLUDED.height,
content = EXCLUDED.content,
tone = EXCLUDED.tone,
status = EXCLUDED.status,
sort_order = EXCLUDED.sort_order,
parent_id = EXCLUDED.parent_id,
layer_role = EXCLUDED.layer_role,
font_size = EXCLUDED.font_size,
font_family = EXCLUDED.font_family,
font_weight = EXCLUDED.font_weight,
color = EXCLUDED.color,
text_align = EXCLUDED.text_align,
line_height = EXCLUDED.line_height,
letter_spacing = EXCLUDED.letter_spacing,
text_decoration = EXCLUDED.text_decoration,
text_transform = EXCLUDED.text_transform,
opacity = EXCLUDED.opacity,
fill_color = EXCLUDED.fill_color,
stroke_color = EXCLUDED.stroke_color,
stroke_width = EXCLUDED.stroke_width,
stroke_style = EXCLUDED.stroke_style,
flip_x = EXCLUDED.flip_x,
flip_y = EXCLUDED.flip_y,
rotation = EXCLUDED.rotation;
-- name: ListConnectionsByProject :many
SELECT id, project_id, user_id, from_node_id, to_node_id
FROM canvas_connections
WHERE project_id = $1 AND user_id = $2
ORDER BY id;
-- name: DeleteConnectionsByProject :exec
DELETE FROM canvas_connections
WHERE project_id = $1 AND user_id = $2;
-- name: CreateConnection :exec
INSERT INTO canvas_connections (id, project_id, user_id, from_node_id, to_node_id)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (id) DO UPDATE SET
project_id = EXCLUDED.project_id,
user_id = EXCLUDED.user_id,
from_node_id = EXCLUDED.from_node_id,
to_node_id = EXCLUDED.to_node_id;
-- name: ListMessagesByProject :many
SELECT id, project_id, user_id, role, message_type, title, content, thread_id, action_id, step_id, tool_call_id, name, tool_hint, status, created_at
FROM agent_messages
WHERE project_id = $1 AND user_id = $2
ORDER BY created_at ASC;
-- name: DeleteMessagesByProject :exec
DELETE FROM agent_messages
WHERE project_id = $1 AND user_id = $2;
-- name: CreateMessage :exec
INSERT INTO agent_messages (id, project_id, user_id, role, message_type, title, content, thread_id, action_id, step_id, tool_call_id, name, tool_hint, status, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
ON CONFLICT (id) DO UPDATE SET
project_id = EXCLUDED.project_id,
user_id = EXCLUDED.user_id,
role = EXCLUDED.role,
message_type = EXCLUDED.message_type,
title = EXCLUDED.title,
content = EXCLUDED.content,
thread_id = EXCLUDED.thread_id,
action_id = EXCLUDED.action_id,
step_id = EXCLUDED.step_id,
tool_call_id = EXCLUDED.tool_call_id,
name = EXCLUDED.name,
tool_hint = EXCLUDED.tool_hint,
status = EXCLUDED.status,
created_at = EXCLUDED.created_at;