 !"#$%%&'()*
+,$-*./)0+-.(12'3$456$.7898:;;<8=>?@ABC
8%*(.?"D8-8!$.EF8=G8?HI!=!JKLMB
@NOP!=
E6!QB!4Q1!Q =JQ!=RBE)=!I5R!S+=PJ8T
$%.?U/V+WVXVY+O
$J-.$'Z.[)E8\8BA\J
:J*!=!O
@O#$%%&'()*+,$-*./)0+-.(12'3$456$.7
8!O#\8?]?8^_`B8>F97
aJ9bacO
^O+=#?8@d7ea"O
f #?e7E!J
f #+ghijN?7E!J
%?O+=#j]?7
f8^k8dl@8a=m8O
#*?n"oRL>p@AB7
#*MB"q@"D8^Sgh?7
rJ8"#?e78\8BO
)MO#3F??E%J7
!R!!=!O#1s=bO+!nB@S@^`C^^8C
8^98?7
-O#3F?8C]?8sR^7
c@;I:;;;ERt!8u:;;;J
#v>87O1F8E:O:JwS@lxC)88E:yOzJCZTEzO:yJ
)eO#?7E!=8J
{JP!O
#+8L"p-8!-!$%.|^888_`7
J}4V)-j*~.V%t'$ZE@•qCJ
€S8B=e9%Ce#v>87
@O#??n"7
Z•!‚?"8ƒeE8Q=J)c=8O
#j‚??@ABCS"L@dTE%)-I„%-I…5%C†a;3J7
4TOdl!! eQF8E!C=8N8JQ#B?7C
#Kƒ7
GBTE88!JO
f #jƒFSEP!1!tJ7wS@l')ƒ?x
f #KƒF8E4-6!J7w'PPx
f #-\=iƒE!B!!!J7w')x
+‚:?#(7
+J+*‡+)ˆ)-4.)*()*EXVY++*&U*‰J
j"L#?7C!=8T"!=%'4II8!!!e„4')O
Š!RO8C!6O‹:O:‹Œ‹:yOz‹Œ
‹zO:y‹C!!!!.8!!y{•O8CIIƒ
?=!tO"!C!!-O
"!C"!B%!!!O"!C]BO‹Ž‹
•
!=h=b8-8!$.••.8!-!!••E.8!rI1!!R$..8!J‘
j*’)-=G8=!!RB
f ?!!6@MT
f )ƒ!!!!.8!!y{i!tu!O=G8!!8\=8E!"!==8
FSQ=!B!!!Je?
f jsebO=“Ž:zaRE$%.8eL”Cp‘9@•
!46s!!@d@“ŽjJ
!=?O
Š8!3!B!O‹8!I8‹C8!!y{O‹–—‹C =C!8C!!=•
VU[€~.j*’)-??HI!=!
ZJ*.˜)*™jš›V(
+!Bd"!y{œ"œV6t!!'"•!V6t8^ž–8—
^8B?O
#?R98%)-7C#?R98„%-7C#4‚7C#Kƒ7
+ƒ!!I8!@8?)kCdlI"!Ÿ8
5Jj *V‰›V$)6¡)-E+*~)-t¢.#6$()*3£V7J
jF8==!"L¤V6t?H
)8T$%.kE{;:I{;rI{azI¥RRJCdlF8"^j*’)-!=!?S@l
€S$%.!Bp!!E5)1JC"+'64@88jF8"8¦@S!Bp!
jd=\>?Ogƒ8!!y{N>e!=!?
1!!a!#?7O!!?"!y{Cd–8—
PJ$#+*§)*4¨$ijš*©%()*7
-\8B"9b=”8|O
f V=#(E8\FSJ7
f !R!#3F?ghIN7
f 88!#jƒFSEP!1!tJ7
f +Tv>8E:O:C:yOzCzO:yJ
f )#?7
-TG8!=II8!!!88hª!!!!.8!!y{i!t
-JVKI$++544..t.U
%sqO+I+=Q5!@d#?7
P8ŸC«{¥O:CkN!B"=
*J6.˜)j*$.
jª65$Z35Oœ=!E!=JCO=!S=!B
E"!=J
Kƒ!a{¬5K.PRL?

Preview text:

Bạn là kiến trúc sư phần mềm kiêm lập trình viên full-stack. Tạo web app tên “APP TẠO ẢNH CỦA GHI TÊN TÁC GIẢ VÀO MASTER AI” giống 100% giao diện ảnh tham chiếu sau đây, nhưng PHẢI sinh ảnh thật bằng Google AI (không dùng ảnh mẫu/placeholder). Xuất mã chạy được: Frontend

(React+TypeScript+Vite+Tailwind) + Backend proxy (Node/Express hoặc Cloud Functions) gọi API sinh ảnh. YÊU CẦU BẮT BUỘC:

A) GIAO DIỆN (giữ nguyên câu chữ)

1) Header:

  • Tiêu đề: “APP TẠO ẢNH CỦA GHI TÊN TÁC GIẢ VÀO MASTER AI”
  • Tagline: “Tạo ra những hình ảnh quảng cáo mỹ phẩm chuyên nghiệp và lôi cuốn.”

2) Bố cục 2 cột:

- Trái: Card “Bảng điều khiển” với 2 tab:

  • “Tạo ảnh mới” (active)
  • “Chỉnh sửa & Kết hợp ảnh” (inactive)

- Phải: Card “Kết quả”

• Trạng thái rỗng hiển thị đúng 2 dòng:

“Hình ảnh của bạn sẽ xuất hiện ở đây”

“Hãy bắt đầu bằng cách tạo hoặc chỉnh sửa ảnh.”

3) Trong tab “Tạo ảnh mới” giữ nguyên:

  • Nhãn: “Mô tả hình ảnh (Prompt)”
  • Textarea placeholder: “Ví dụ: Chai serum thủy tinh đặt trên nền đá cẩm thạch, ánh sáng mềm, phong cách tối giản...”
  • Ghi chú: “Mô tả càng chi tiết, kết quả càng chính xác.”
  • Bộ đếm 0/1000 (maxLength=1000).
  • “Tỷ lệ khung hình”: Vuông (1:1) [mặc định], Ngang (16:9), Dọc (9:16)
  • Nút lớn: “Tạo ảnh” (spinner khi loading).

4) Footer:

- “Cung cấp bởi Google Gemini API. Thiết kế cho sự sáng tạo trong ngành mỹ phẩm.”

B) BỔ SUNG KHỐI UPLOAD (đẹp mắt, thu hút)

  • Đặt ngay dưới khối Prompt, trước “Tỷ lệ khung hình”.
  • Tiêu đề: “Tải ảnh của bạn lên”
  • Dropzone kéo-thả bo góc lớn (glassmorphism + icon upload). Nội dung:

“Kéo-thả ảnh vào đây, hoặc bấm để chọn (PNG/JPG/WEBP, ≤ 20MB).”

  • Sau khi chọn: hiển thị preview lớn + thanh thông tin (tên file, dung lượng) + nút “Thay ảnh”, “Xóa”.
  • Tùy chọn (toggle):
  • “Khóa khuôn mặt (Face Vector Lock)” [mặc định ON khi có ảnh]
  • “Xóa nền thông minh (Smart BG Removal)” [OFF]
  • “Giữ chi tiết da & tóc (Beauty-preserve)” [ON]

- Cho phép 1 ảnh tham chiếu làm “Ảnh tham chiếu”.

C) CHỨC NĂNG SINH ẢNH (BẮT BUỘC CHẠY THẬT)

  • Khi bấm “Tạo ảnh”, frontend gọi backend POST /api/generate với JSON:

{ promptText: string, aspectRatio: "1:1" | "16:9" | "9:16", referenceImageBase64?: string, // nếu có ảnh upload faceLock: boolean, removeBG: boolean, beautyPreserve: boolean, quality: "8k"

}

  • Backend sử dụng Google AI **Image Generation** (Imagen 3 / Vertex AI Images) chứ KHÔNG dùng model text-only.
  • Tạo ảnh theo prompt và aspectRatio đã chọn.
  • Nếu có referenceImageBase64 & faceLock=true: dùng pipeline giữ nhận dạng (embedding khuôn mặt + identity-preserve) trước khi trả về.
  • Kích thước mục tiêu: cạnh dài ~8192px (nếu API giới hạn thấp hơn, sinh ở mức tối đa rồi upscale SR phía server để đạt ~8K).
  • Backend trả về:

{ imageMimeType: "image/png", imageBase64: "<...>", width, height, seed }

  • TUYỆT ĐỐI KHÔNG trả về ảnh mẫu/placeholder.

D) HIỂN THỊ KẾT QUẢ

  • Client chuyển base64 → Blob → URL.createObjectURL và gán vào thẻ <img>.
  • Thêm các nút ngay trên ảnh:

“Tải xuống PNG”, “Tải xuống JPG”, “Sao chép link”, “Xóa”.

  • Có skeleton/progress khi đang sinh ảnh. Nếu lỗi, hiển thị toast/banner rõ ràng.

E) KỸ THUẬT QUAN TRỌNG (CHỐNG LỖI “RA ẢNH MẪU”)

  • Không hard-code bất kỳ URL ảnh mẫu nào.
  • Nếu gọi API lỗi (401/403/429/5xx), hiển thị thông báo và KHÔNG render ảnh mặc định.
  • Đặt API key ở server (ENV), bật CORS đúng origin. Không bao giờ đặt key ở client.
  • Kiểm tra dữ liệu trả về: chỉ khi có imageBase64 hợp lệ mới render ảnh.
  • Viết test e2e cho nút “Tạo ảnh”: mock server trả về base64, kiểm tra <img> cập nhật src.

F) TAB “CHỈNH SỬA & KẾT HỢP ẢNH”

- Giữ nguyên tên tab. Bố cục card tương tự:

  • Upload “Ảnh tham chiếu (giữ khuôn mặt)”
  • Textarea “Mô tả chỉnh sửa/kết hợp”
  • Toggle “Khóa khuôn mặt (Face Vector Lock)”
  • Chọn tỷ lệ khung hình (1:1, 16:9, 9:16)
  • Nút “Tạo ảnh”

- Gọi cùng endpoint /api/generate nhưng gửi kèm referenceImageBase64 & faceLock.

G) UX/ACCESSIBILITY

  • Phím tắt: Ctrl/Cmd+Enter để “Tạo ảnh”.
  • Focus ring rõ, contrast ≥ 4.5:1, hỗ trợ keyboard.

H) TRIỂN KHAI

  • Kèm README: npm i → npm run dev (frontend), npm run functions:dev hoặc npm run deploy (backend).
  • Xóa file tạm sau 24h. Ẩn EXIF khi xuất ảnh.