Skip to content

Rohit/33 #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 38 additions & 13 deletions apps/backend/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fal } from "@fal-ai/client";
import express from "express";
import express, { type Request, type Response } from "express";
import {
TrainModel,
GenerateImage,
Expand Down Expand Up @@ -239,28 +239,50 @@ app.get("/pack/bulk", async (req, res) => {
});
});

app.get("/image/bulk", authMiddleware, async (req, res) => {
const ids = req.query.ids as string[];
app.get("/poll/images/:requestId", authMiddleware, (req: Request, res: Response) => {
const requestId = req.params.requestId;
prismaClient.outputImages
.findUnique({ where: { falAiRequestId: requestId } })
.then((imagesData) => {
if (!imagesData) return res.status(404).json({ message: "Image not found" });

switch (imagesData.status) {
case "Failed":
return res.status(404).json({ message: "Image failed" });

case "Pending":
return res.status(400).json({ message: "Pending" });

case "Generated":
return res.json({ images: imagesData });
}
})
.catch((error) => {
console.error("Error in /poll/images/:requestId:", error);
res.status(500).json({ message: "Internal server error" });
});
});



//get all old images of user with pagination
app.get("/images/old", authMiddleware, async (req, res) => {
const limit = (req.query.limit as string) ?? "100";
const offset = (req.query.offset as string) ?? "0";

const imagesData = await prismaClient.outputImages.findMany({
const images = await prismaClient.outputImages.findMany({
where: {
id: { in: ids },
userId: req.userId!,
status: {
not: "Failed",
},
userId: req.userId,
},
orderBy: {
createdAt: "desc",
},
skip: parseInt(offset),
take: parseInt(limit),
skip: parseInt(offset),
});

res.json({
images: imagesData,
images,
});
});

Expand All @@ -276,6 +298,9 @@ app.get("/models", authMiddleware, async (req, res) => {
});
});




app.post("/fal-ai/webhook/train", async (req, res) => {
console.log("====================Received training webhook====================");
console.log("Received training webhook:", req.body);
Expand Down Expand Up @@ -307,7 +332,7 @@ app.post("/fal-ai/webhook/train", async (req, res) => {
trainingStatus: "Failed",
},
});

res.json({
message: "Error recorded",
});
Expand Down Expand Up @@ -408,7 +433,6 @@ app.post("/fal-ai/webhook/train", async (req, res) => {

app.post("/fal-ai/webhook/image", async (req, res) => {
console.log("fal-ai/webhook/image");
console.log(req.body);
// update the status of the image in the DB
const requestId = req.body.request_id;

Expand Down Expand Up @@ -483,6 +507,7 @@ app.get("/model/status/:modelId", authMiddleware, async (req, res) => {
}
});


app.use("/payment", paymentRoutes);
app.use("/api/webhook", webhookRouter);

Expand Down
4 changes: 4 additions & 0 deletions apps/backend/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ export async function authMiddleware(
// Format the public key properly
const formattedKey = publicKey.replace(/\\n/g, "\n");

console.log("Formatted key:", formattedKey);

const decoded = jwt.verify(token, formattedKey, {
algorithms: ["RS256"],
issuer:
process.env.CLERK_ISSUER || "https://clerk.100xdevs.com",
complete: true,
});


console.log("Decoded token:", decoded);

// Extract user ID from the decoded token
Expand All @@ -60,6 +63,7 @@ export async function authMiddleware(
return;
}


// Fetch user details from Clerk
const user = await clerkClient.users.getUser(userId);
const primaryEmail = user.emailAddresses.find(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default async function DashboardPage() {
value="camera"
className="mt-0 focus-visible:outline-none"
>
<Camera />
<Camera />
</TabsContent>
<TabsContent
value="generate"
Expand Down
105 changes: 70 additions & 35 deletions apps/web/components/Camera.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
"use client";

import { useAuth } from "@clerk/nextjs";
import { BACKEND_URL } from "@/app/config";
import axios from "axios";
import { useEffect, useState } from "react";
import { ImageCard } from "./ImageCard";
import { motion } from "framer-motion";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { Button } from "./ui/button";
import { Download, ChevronLeft, ChevronRight } from "lucide-react";
import Image from "next/image";
import { useRequestStore } from "@/store/useRequestStore";
import { getImage, getOldImages } from "../services/image.service";

export interface TImage {
id: string;
Expand All @@ -23,14 +22,18 @@ export interface TImage {
updatedAt: string;
}

export interface CameraProps {
requestIds: string[];
}

export function Camera() {
const { requestIds, removeRequestId } = useRequestStore();
const [images, setImages] = useState<TImage[]>([]);
const [imagesLoading, setImagesLoading] = useState(true);
const [imagesLoading, setImagesLoading] = useState(false);
const [selectedImage, setSelectedImage] = useState<TImage | null>(null);
const [currentImageIndex, setCurrentImageIndex] = useState<number>(0);
const [isDownloading, setIsDownloading] = useState(false);
const { getToken } = useAuth();

const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString("en-US", {
year: "numeric",
Expand All @@ -41,24 +44,54 @@ export function Camera() {
});
};

const fetchImages = async () => {

const fetchImages = async (requestId: string) => {
try {
const token = await getToken();
const response = await axios.get(`${BACKEND_URL}/image/bulk`, {
headers: { Authorization: `Bearer ${token}` },
});
setImages(response.data.images);
setImagesLoading(true);
const token: string | null = await getToken();
if (!token) return;
const response = await getImage(token, requestId);
if (response.data.images) {
setImages(prev => [...prev, response.data.images]);
removeRequestId(requestId);
} else if (response.status === 404) {
removeRequestId(requestId);
}
setImagesLoading(false);
} catch (error) {
console.error("Failed to fetch images:", error);
setImagesLoading(false);
}
};


const fetchOldImages = async () => {
const token: string | null = await getToken();
if (!token) return;
const response = await getOldImages(token);
setImages(response.images);
};


useEffect(() => {
fetchImages();
fetchOldImages();
}, []);

useEffect(() => {
if (!requestIds) {
return;
}
const pollInterval = setInterval(() => {
requestIds?.forEach((requestId: string) => {
fetchImages(requestId);
});
}, 10000);
requestIds?.forEach((requestId: string) => {
fetchImages(requestId);
});
return () => clearInterval(pollInterval);
}, [requestIds]);

const handleImageClick = (image: TImage, index: number) => {
setSelectedImage(image);
setCurrentImageIndex(index);
Expand Down Expand Up @@ -95,6 +128,8 @@ export function Camera() {
}
};



return (
<div className="space-y-4">
<div className="flex items-center justify-between">
Expand All @@ -112,31 +147,31 @@ export function Camera() {
>
{imagesLoading
? [...Array(8)].map((_, i) => (
<motion.div
key={i}
className="bg-neutral-300 h-48 rounded-lg animate-pulse"
/>
))
<motion.div
key={i}
className="bg-neutral-300 h-48 rounded-lg animate-pulse"
/>
))
: images.map((image, index) => (
<div
key={image.id +index}
className="cursor-pointer transition-transform mb-4 hover:scale-[1.02]"
<div
key={image.id + index}
className="cursor-pointer transition-transform mb-4 hover:scale-[1.02]"
onClick={() => handleImageClick(image, index)}
>
<ImageCard
id={image.id}
status={image.status}
imageUrl={image.imageUrl}
onClick={() => handleImageClick(image, index)}
>
<ImageCard
id={image.id}
status={image.status}
imageUrl={image.imageUrl}
onClick={() => handleImageClick(image, index)}
modelId={image.modelId}
userId={image.userId}
prompt={image.prompt}
falAiRequestId={image.falAiRequestId}
createdAt={image.createdAt}
updatedAt={image.updatedAt}
/>
</div>
))}
modelId={image.modelId}
userId={image.userId}
prompt={image.prompt}
falAiRequestId={image.falAiRequestId}
createdAt={image.createdAt}
updatedAt={image.updatedAt}
/>
</div>
))}
</motion.div>

{!imagesLoading && images.length === 0 && (
Expand Down
6 changes: 5 additions & 1 deletion apps/web/components/GenerateImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import { useCredits } from "@/hooks/use-credits";
import { useRouter } from "next/navigation";
import CustomLabel from "./ui/customLabel";
import { GlowEffect } from "./GlowEffect";
import { useRequestStore } from "@/store/useRequestStore";

export function GenerateImage() {
const [prompt, setPrompt] = useState("");
const [selectedModel, setSelectedModel] = useState<string>();
const [isGenerating, setIsGenerating] = useState(false);
const { addRequestId } = useRequestStore();
const { getToken } = useAuth();
const { credits } = useCredits();
const router = useRouter();
Expand All @@ -34,7 +36,7 @@ export function GenerateImage() {
setIsGenerating(true);
try {
const token = await getToken();
await axios.post(
const response = await axios.post(
`${BACKEND_URL}/ai/generate`,
{
prompt,
Expand All @@ -45,6 +47,8 @@ export function GenerateImage() {
headers: { Authorization: `Bearer ${token}` },
}
);

addRequestId(response.data.requestId);
toast.success("Image generation started!");
setPrompt("");
} catch (error) {
Expand Down
7 changes: 5 additions & 2 deletions apps/web/components/PackCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CarouselPrevious,
} from "./ui/carousel";
import Autoplay from "embla-carousel-autoplay";
import { useRequestStore } from "@/store/useRequestStore";

export interface TPack {
id: string;
Expand All @@ -38,7 +39,7 @@ export function PackCard(props: TPack & { selectedModelId: string }) {
const { getToken } = useAuth();
const { credits } = useCredits();
const router = useRouter();

const { requestIds, setRequestIds } = useRequestStore();
// Collect all image URLs into an array
const images = [
props.imageUrl1,
Expand Down Expand Up @@ -66,7 +67,7 @@ export function PackCard(props: TPack & { selectedModelId: string }) {

const generatePack = async () => {
const token = await getToken();
await axios.post(
const response = await axios.post(
`${BACKEND_URL}/pack/generate`,
{
packId: props.id,
Expand All @@ -78,6 +79,8 @@ export function PackCard(props: TPack & { selectedModelId: string }) {
},
}
);
setRequestIds([...(requestIds || []), response.data.requestId]);
toast.success("Pack generation started!");
};

return (
Expand Down
6 changes: 4 additions & 2 deletions apps/web/components/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";

import { RecoilRoot } from "recoil";
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
return <NextThemesProvider {...props}>
{children}
</NextThemesProvider>;
}
4 changes: 0 additions & 4 deletions apps/web/components/ui/upload.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
"use client";

import JSZip from "jszip";
import axios from "axios";
import { useState, useCallback } from "react";
import {
Card,
Expand All @@ -12,7 +9,6 @@ import {
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { BACKEND_URL, CLOUDFLARE_URL } from "@/app/config";
import { cn } from "@/lib/utils";

export function UploadModal({
Expand Down
Loading