diff --git a/frontend/app/[locale]/(search)/dashboard/page.tsx b/frontend/app/[locale]/(dashboard)/dashboard/page.tsx similarity index 97% rename from frontend/app/[locale]/(search)/dashboard/page.tsx rename to frontend/app/[locale]/(dashboard)/dashboard/page.tsx index 73894402..84307fff 100644 --- a/frontend/app/[locale]/(search)/dashboard/page.tsx +++ b/frontend/app/[locale]/(dashboard)/dashboard/page.tsx @@ -16,7 +16,7 @@ export default async function page() { const [urls, failedUrls, indexCount, searchCount] = await getUserStatistics(user.id); return ( - +

Search & Index Statistics

diff --git a/frontend/app/[locale]/(dashboard)/images/page.tsx b/frontend/app/[locale]/(dashboard)/images/page.tsx new file mode 100644 index 00000000..9b0150e5 --- /dev/null +++ b/frontend/app/[locale]/(dashboard)/images/page.tsx @@ -0,0 +1,21 @@ +import { getCurrentUser } from '@/lib/session'; +import { getUserImages } from '@/lib/store/image'; +import { ImageList } from '@/components/dashboard/image-list'; +import { redirect } from 'next/navigation'; + +export default async function MyPages() { + const user = await getCurrentUser(); + if (!user) { + redirect('/login'); + } + const images = await getUserImages(user.id); + + return ( +
+
+

MemFree Image Gallery

+
+ +
+ ); +} diff --git a/frontend/app/[locale]/(dashboard)/layout.tsx b/frontend/app/[locale]/(dashboard)/layout.tsx new file mode 100644 index 00000000..d3556c66 --- /dev/null +++ b/frontend/app/[locale]/(dashboard)/layout.tsx @@ -0,0 +1,53 @@ +import { Separator } from '@/components/ui/separator'; +import { redirect } from 'next/navigation'; +import { getCurrentUser } from '@/lib/session'; +import SiteHeader from '@/components/layout/site-header'; +import { mainNavConfig } from '@/config'; +import { DashBoardSidebarNav } from '@/components/dashboard/sidebar-nav'; + +const sidebarNavItems = [ + { + title: 'Images', + href: '/images', + }, + { + title: 'Settings', + href: '/settings', + }, + { + title: 'AI Search', + href: '/', + is_target_blank: true, + }, + { + title: 'AI Image ', + href: '/generate-image', + is_target_blank: true, + }, + { + title: 'AI Page', + href: 'https://pagegen.ai', + is_target_blank: true, + }, +]; + +export default async function DashboardLayout({ children }: { children: React.ReactNode }) { + const user = await getCurrentUser(); + if (!user?.id) { + redirect('/login'); + } + return ( + <> + +
+
+ + +
{children}
+
+
+ + ); +} diff --git a/frontend/app/[locale]/(search)/settings/loading.tsx b/frontend/app/[locale]/(dashboard)/settings/loading.tsx similarity index 82% rename from frontend/app/[locale]/(search)/settings/loading.tsx rename to frontend/app/[locale]/(dashboard)/settings/loading.tsx index d3eb51b3..28fe65b5 100644 --- a/frontend/app/[locale]/(search)/settings/loading.tsx +++ b/frontend/app/[locale]/(dashboard)/settings/loading.tsx @@ -4,7 +4,7 @@ import { DashboardShell } from '@/components/dashboard/shell'; export default function DashboardSettingsLoading() { return ( -
+
diff --git a/frontend/app/[locale]/(search)/settings/page.tsx b/frontend/app/[locale]/(dashboard)/settings/page.tsx similarity index 90% rename from frontend/app/[locale]/(search)/settings/page.tsx rename to frontend/app/[locale]/(dashboard)/settings/page.tsx index 9f6dbdbf..28c0dbfb 100644 --- a/frontend/app/[locale]/(search)/settings/page.tsx +++ b/frontend/app/[locale]/(dashboard)/settings/page.tsx @@ -17,14 +17,13 @@ export const metadata = { export default async function SettingsPage() { const user = await getCurrentUser(); - if (!user) { redirect('/login'); } const userSubscriptionPlan = await getUserSubscriptionPlan(user.id); return ( -
+
diff --git a/frontend/app/[locale]/(marketing)/generate-image/page.tsx b/frontend/app/[locale]/(marketing)/generate-image/page.tsx index 19215ea4..3f8b5aba 100644 --- a/frontend/app/[locale]/(marketing)/generate-image/page.tsx +++ b/frontend/app/[locale]/(marketing)/generate-image/page.tsx @@ -25,7 +25,7 @@ export const metadata: Metadata = { export default async function Page() { return (
-

Generate Image With AI

+

Generate Image With AI

diff --git a/frontend/app/[locale]/(marketing)/layout.tsx b/frontend/app/[locale]/(marketing)/layout.tsx index f34a7a5f..837e0b3a 100644 --- a/frontend/app/[locale]/(marketing)/layout.tsx +++ b/frontend/app/[locale]/(marketing)/layout.tsx @@ -1,5 +1,4 @@ import { getCurrentUser } from '@/lib/session'; -import { Suspense } from 'react'; import SiteHeader from '@/components/layout/site-header'; import { mainNavConfig } from '@/config'; import { unstable_setRequestLocale } from 'next-intl/server'; @@ -9,9 +8,7 @@ export default async function MarketingLayout({ children, params: { locale } }) const user = await getCurrentUser(); return (
- - - +
{children}
); diff --git a/frontend/app/api/generate-image/route.ts b/frontend/app/api/generate-image/route.ts index 88bfffb2..c6e50fc6 100644 --- a/frontend/app/api/generate-image/route.ts +++ b/frontend/app/api/generate-image/route.ts @@ -72,9 +72,10 @@ export async function POST(req: NextRequest) { const image = result.data?.images?.[0]?.url; await saveImage({ id: generateId(), + title: prompt, userId, isPublic: false, - prompt, + prompt: newPrompt.replace(/^```\n/, ''), createdAt: new Date(), imageUrl: image, }); diff --git a/frontend/components/dashboard/image-card.tsx b/frontend/components/dashboard/image-card.tsx new file mode 100644 index 00000000..8e6c4b9b --- /dev/null +++ b/frontend/components/dashboard/image-card.tsx @@ -0,0 +1,81 @@ +'use client'; + +import Image from 'next/image'; +import { Button } from '@/components/ui/button'; +import { Card } from '@/components/ui/card'; +import { Fullscreen, Link as LinkIcon, Download, Loader2 } from 'lucide-react'; +import { GenImage } from '@/lib/types'; +import React from 'react'; +import Link from 'next/link'; +import useCopyToClipboard from '@/hooks/use-copy-clipboard'; +import { useDownloadImage } from '@/hooks/use-download-image'; +import { toast } from 'sonner'; + +const ImageCard = ({ item, isPriority }: { item: GenImage; isPriority: boolean }) => { + const { copyToClipboard } = useCopyToClipboard(); + const { downloadImage, isDownloading } = useDownloadImage(); + + const handleCopyToClipboard = async () => { + await copyToClipboard(item.imageUrl); + toast.success('Image url copied to clipboard'); + }; + + return ( + +
+ {item.title} +
+
+ +
+
+

{item.title}

+

+ {item.prompt} +

+
+
+ +
+
+ + + + + +
+
+ + ); +}; + +export default ImageCard; diff --git a/frontend/components/dashboard/image-list.tsx b/frontend/components/dashboard/image-list.tsx new file mode 100644 index 00000000..872a9f62 --- /dev/null +++ b/frontend/components/dashboard/image-list.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { Loader2 } from 'lucide-react'; + +import { useState } from 'react'; +import { GenImage } from '@/lib/types'; +import React from 'react'; +import { type User } from 'next-auth'; +import { getUserImages } from '@/lib/store/image'; +import InfiniteScroll from '@/components/ui/infinite-scroll'; +import ImageCard from '@/components/dashboard/image-card'; + +const limit = 20; +export const ImageList = ({ items, user }: { items: GenImage[]; user: User }) => { + const [loading, setLoading] = useState(false); + const [offset, setOffset] = useState(0); + const [hasMore, setHasMore] = useState(true); + + const next = async () => { + if (!user) { + setHasMore(false); + return; + } + setLoading(true); + const newSearches = await getUserImages(user.id, offset); + if (newSearches.length < limit) { + setHasMore(false); + } + setOffset((prev) => prev + limit); + setLoading(false); + }; + + return ( + <> +
+ {items?.length > 0 ? ( + items.map((item, index) => ) + ) : ( +
No items in this category
+ )} +
+ + + {hasMore && ( +
+ + Loading More +
+ )} +
+ + ); +}; diff --git a/frontend/components/dashboard/sidebar-nav.tsx b/frontend/components/dashboard/sidebar-nav.tsx new file mode 100644 index 00000000..44d10567 --- /dev/null +++ b/frontend/components/dashboard/sidebar-nav.tsx @@ -0,0 +1,38 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; + +import { cn } from '@/lib/utils'; +import { buttonVariants } from '@/components/ui/button'; + +interface SidebarNavProps extends React.HTMLAttributes { + items: { + href: string; + title: string; + is_target_blank?: boolean; + }[]; +} + +export function DashBoardSidebarNav({ className, items, ...props }: SidebarNavProps) { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/frontend/components/layout/changelog-banner.tsx b/frontend/components/layout/changelog-banner.tsx index 8d205dd1..b7c8c828 100644 --- a/frontend/components/layout/changelog-banner.tsx +++ b/frontend/components/layout/changelog-banner.tsx @@ -27,7 +27,7 @@ export default function ChangelogBanner() {

MemFree Supports DeepSeek-V3 and Google Gemini-2.0 AI Models Now 🎉

- + Learn More
diff --git a/frontend/lib/store/image.ts b/frontend/lib/store/image.ts index d1670d25..dba0284f 100644 --- a/frontend/lib/store/image.ts +++ b/frontend/lib/store/image.ts @@ -28,8 +28,12 @@ export async function getUserImages(userId: string, offset: number = 0, limit: n rev: true, }); + if (imageIds.length === 0) { + return []; + } + for (const id of imageIds) { - pipeline.hgetall(id); + pipeline.hgetall(IMAGE_KEY + id); } const results = await pipeline.exec(); @@ -41,9 +45,13 @@ export async function getLatestPublicImages(offset: number = 0, limit: number = rev: true, }); + if (imageIds.length === 0) { + return []; + } + const pipeline = redisDB.pipeline(); for (const id of imageIds) { - pipeline.hgetall(id); + pipeline.hgetall(IMAGE_KEY + id); } const results = await pipeline.exec(); diff --git a/frontend/lib/tools/improve-image-prompt.ts b/frontend/lib/tools/improve-image-prompt.ts index 29f5f7fa..8445bb13 100644 --- a/frontend/lib/tools/improve-image-prompt.ts +++ b/frontend/lib/tools/improve-image-prompt.ts @@ -43,7 +43,7 @@ export async function generatePrompt(query: string, showText: boolean, useCase: } try { const prompt = format(PROMPT, query, useCase, showTextInstructions); - // console.log("generatePrompt", prompt); + // console.log('generatePrompt', prompt); const { text } = await generateText({ model: getLLM(GPT_4o_MIMI), prompt: prompt, diff --git a/frontend/lib/types.ts b/frontend/lib/types.ts index 9b98592f..5c3a0f46 100644 --- a/frontend/lib/types.ts +++ b/frontend/lib/types.ts @@ -99,6 +99,7 @@ export interface Search extends Record { export interface GenImage extends Record { id: string; userId: string; + title: string; prompt: string; createdAt: Date; isPublic: boolean;