Feature UI optimization nov 2025#16
Conversation
…into dev-nov-2025
added voice input, grouping in compose image, face cleanp, alias prompt.
…oject structure for a professional AI creative studio.
…ov-2025 Feature adding payment system nov 2025
…into feature-ui-optimization-nov-2025
…u, modals, and dedicated pages.
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
Summary of ChangesHello @saravatpt, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly expands the application's capabilities, transforming it into a full-featured AI creative studio. It introduces core functionalities such as user authentication, a credit-based payment system, and advanced image generation and editing tools. The changes also include robust data persistence for user creations and a comprehensive set of documentation to guide users and developers through setup and usage, marking a major step towards a production-ready platform. Highlights
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This is a massive and impressive feature update that transforms the project from a simple quickstart into a full-fledged, production-ready application. The architecture, incorporating Supabase for authentication, Prisma with a serverless-friendly adapter, and Stripe for payments, is well-thought-out. The new UI is rich with features like image history, folders, and advanced composition controls. My review focuses on enhancing the robustness and maintainability of the new backend APIs and database schema, with particular attention to potential race conditions, API versioning risks, and opportunities for code simplification.
| let promptRecord = await prisma.prompt.findFirst({ | ||
| where: { | ||
| userId: user.id, | ||
| text: prompt | ||
| } | ||
| }) | ||
|
|
||
| if (!promptRecord) { | ||
| promptRecord = await prisma.prompt.create({ | ||
| data: { | ||
| text: prompt, | ||
| userId: user.id | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
This logic for finding or creating a prompt has a potential race condition. If two requests for the same new prompt arrive concurrently, both could pass the findFirst check and then both attempt to create, leading to a unique constraint violation. To make this atomic and prevent errors, you should add a unique constraint to the Prompt model for userId and text, and then use prisma.prompt.upsert here.
| const stripe = process.env.STRIPE_SECRET_KEY | ||
| ? new Stripe(process.env.STRIPE_SECRET_KEY, { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| apiVersion: '2024-12-18.acacia' as any, |
There was a problem hiding this comment.
Using a future date for the Stripe apiVersion and casting it with as any is risky. Stripe API versions can introduce breaking changes, and this approach bypasses type safety. It's safer to use a specific, stable version that is known to be compatible with your code. Please use a valid, current API version string without the as any cast.
| apiVersion: '2024-12-18.acacia' as any, | |
| apiVersion: '2024-06-20', |
| model Prompt { | ||
| id String @id @default(uuid()) | ||
| text String | ||
| createdAt DateTime @default(now()) | ||
|
|
||
| userId String | ||
| user UserProfile @relation(fields: [userId], references: [id]) | ||
|
|
||
| images Image[] | ||
| } |
There was a problem hiding this comment.
The Prompt model should enforce that a user cannot have multiple saved prompts with the exact same text. This helps maintain data integrity and prevents the race condition identified in app/api/images/save/route.ts. Please add a composite unique constraint on the userId and text fields.
model Prompt {
id String @id @default(uuid())
text String
createdAt DateTime @default(now())
userId String
user UserProfile @relation(fields: [userId], references: [id])
images Image[]
@@unique([userId, text])
}
| if [ -f yarn.lock ]; then npx prisma generate && yarn run build; \ | ||
| elif [ -f package-lock.json ]; then npx prisma generate && npm run build; \ | ||
| elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && npx prisma generate && pnpm run build; \ |
There was a problem hiding this comment.
The build command for each package manager duplicates npx prisma generate. To improve maintainability and reduce redundancy, you could run npx prisma generate once before the conditional package manager logic.
npx prisma generate && \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
| } catch (error: unknown) { | ||
| console.error("Error editing image with Gemini:", error instanceof Error ? error.message : String(error)); | ||
| if (error && typeof error === 'object' && 'response' in error) { | ||
| console.error("Gemini API Error Response:", JSON.stringify(error.response, null, 2)); | ||
| } |
There was a problem hiding this comment.
Your error handling has been significantly improved with more specific error messages. However, you're using error instanceof Error ? error.message : String(error) which is good, but for API errors from libraries like @google/genai, the actual error details are often nested inside the error object. Logging the full error object or a stringified version can provide more context for debugging, especially for failed API requests.
console.error("Error editing image with Gemini:", error);
if (error instanceof Error && 'cause' in error) {
console.error("Error Cause:", (error as any).cause);
}| } catch (error: unknown) { | ||
| console.error("Error generating image with Gemini:", error instanceof Error ? error.message : String(error)); | ||
| if (error && typeof error === 'object' && 'response' in error) { | ||
| console.error("Gemini API Error Response:", JSON.stringify(error.response, null, 2)); | ||
| } |
There was a problem hiding this comment.
The error handling here is much better. To make debugging even easier, consider logging the entire error object, as API-specific details from the Gemini client might be nested within it. This can be invaluable for diagnosing issues with API requests.
| } catch (error: unknown) { | |
| console.error("Error generating image with Gemini:", error instanceof Error ? error.message : String(error)); | |
| if (error && typeof error === 'object' && 'response' in error) { | |
| console.error("Gemini API Error Response:", JSON.stringify(error.response, null, 2)); | |
| } | |
| } catch (error: unknown) { | |
| console.error("Error generating image with Gemini:", error); | |
| if (error instanceof Error && 'cause' in error) { | |
| console.error("Error Cause:", (error as any).cause); | |
| } |
| } catch (error: unknown) { | ||
| console.error("Error generating image:", error instanceof Error ? error.message : String(error)); | ||
| if (error && typeof error === 'object' && 'response' in error) { | ||
| console.error("Imagen API Error Response:", JSON.stringify(error.response, null, 2)); | ||
| } |
There was a problem hiding this comment.
The improved error handling is a great addition. For better debugging of API-related failures, it would be beneficial to log the full error object. Client libraries often wrap the original API response or error details, and logging the whole object can reveal crucial information that isn't in error.message.
| } catch (error: unknown) { | |
| console.error("Error generating image:", error instanceof Error ? error.message : String(error)); | |
| if (error && typeof error === 'object' && 'response' in error) { | |
| console.error("Imagen API Error Response:", JSON.stringify(error.response, null, 2)); | |
| } | |
| } catch (error: unknown) { | |
| console.error("Error generating image:", error); | |
| if (error instanceof Error && 'cause' in error) { | |
| console.error("Imagen API Error Cause:", (error as any).cause); | |
| } |
| } catch (error: unknown) { | ||
| console.error("Error starting Veo generation:", error); | ||
| console.error("Error starting Veo generation:", error instanceof Error ? error.message : String(error)); | ||
| if (error && typeof error === 'object' && 'response' in error) { | ||
| console.error("Veo API Error Response:", JSON.stringify(error.response, null, 2)); | ||
| } |
There was a problem hiding this comment.
The enhanced error handling is a good improvement. To further aid in debugging, especially for issues related to the Veo API, consider logging the entire error object. API client libraries often nest important details within the error object, which can be lost when only logging error.message.
| } catch (error: unknown) { | |
| console.error("Error starting Veo generation:", error); | |
| console.error("Error starting Veo generation:", error instanceof Error ? error.message : String(error)); | |
| if (error && typeof error === 'object' && 'response' in error) { | |
| console.error("Veo API Error Response:", JSON.stringify(error.response, null, 2)); | |
| } | |
| } catch (error: unknown) { | |
| console.error("Error starting Veo generation:", error); | |
| if (error instanceof Error && 'cause' in error) { | |
| console.error("Veo API Error Cause:", (error as any).cause); | |
| } |
| const existingProfile = await prisma.userProfile.findUnique({ | ||
| where: { id: user.id } | ||
| }) | ||
|
|
||
| if (!existingProfile) { | ||
| // New user: Give 14 days trial + 100 credits | ||
| const trialEndsAt = new Date() | ||
| trialEndsAt.setDate(trialEndsAt.getDate() + 14) | ||
|
|
||
| await prisma.userProfile.create({ | ||
| data: { | ||
| id: user.id, | ||
| email: user.email!, | ||
| fullName: user.user_metadata.full_name, | ||
| avatarUrl: user.user_metadata.avatar_url, | ||
| credits: 100, | ||
| trialEndsAt: trialEndsAt | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
This logic for creating a user profile if it doesn't exist can be simplified and made more robust by using prisma.userProfile.upsert. This combines the find and create operations into a single atomic database call, which is cleaner and avoids potential race conditions.
const trialEndsAt = new Date();
trialEndsAt.setDate(trialEndsAt.getDate() + 14);
await prisma.userProfile.upsert({
where: { id: user.id },
update: {},
create: {
id: user.id,
email: user.email!,
fullName: user.user_metadata.full_name,
avatarUrl: user.user_metadata.avatar_url,
credits: 100,
trialEndsAt: trialEndsAt
}
});| @@ -0,0 +1,19 @@ | |||
| $p1 = "d:\AIgnite\AIG-POCs\PhotoAIG\PhotoAIG\public\prompt.json" | |||
There was a problem hiding this comment.
No description provided.