The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

new ai landing page

Luna 5bd9610e fb2bdbf4

+288 -222
-170
app/(home)/ai/page.tsx
··· 1 - import { Chip } from "@nextui-org/react"; 2 - import { Metadata } from "next"; 3 - import { Montserrat } from "next/font/google"; 4 - import Image from "next/image"; 5 - import Link from "next/link"; 6 - import { BsDiscord } from "react-icons/bs"; 7 - import { HiUserAdd } from "react-icons/hi"; 8 - 9 - import { getUploads } from "@/app/ai-gallery/api"; 10 - import Notice from "@/components/notice"; 11 - import { ServerButton } from "@/components/server-button"; 12 - import CommandPic from "@/public/image-command.webp"; 13 - import cn from "@/utils/cn"; 14 - import { getBaseUrl, getCanonicalUrl } from "@/utils/urls"; 15 - 16 - import Pagination from "./pagination.component"; 17 - 18 - const montserrat = Montserrat({ subsets: ["latin"] }); 19 - 20 - export const revalidate = 3600; 21 - 22 - interface Props { 23 - searchParams: { page: string, model: string }; 24 - } 25 - 26 - export const generateMetadata = async (): Promise<Metadata> => { 27 - 28 - const title = "Free /image Ai for Discord"; 29 - const description = "Summon the enchantment of AI generated images to your Discord server with our versatile /image command, featuring over 40+ distinct SD and 10+ SDXL models."; 30 - const url = getCanonicalUrl("ai"); 31 - 32 - return { 33 - title, 34 - description, 35 - alternates: { 36 - canonical: url 37 - }, 38 - openGraph: { 39 - title, 40 - description, 41 - type: "website", 42 - url, 43 - images: `${getBaseUrl()}/waya-v3.jpg` 44 - }, 45 - twitter: { 46 - card: "summary", 47 - site: "wamellow.com", 48 - title, 49 - description, 50 - images: `${getBaseUrl()}/waya-v3.jpg` 51 - } 52 - }; 53 - }; 54 - 55 - export default async function Home({ searchParams }: Props) { 56 - const uploads = await getUploads({ page: parseInt(searchParams.page || "1"), model: searchParams.model }); 57 - 58 - const styles = { 59 - h2: cn(montserrat.className, "lg:text-5xl text-4xl bg-gradient-to-b bg-clip-text text-transparent from-neutral-200 from-40% to-neutral-400 font-bold underline decoration-violet-400"), 60 - h3: cn(montserrat.className, "lg:text-2xl text-xl bg-gradient-to-b bg-clip-text text-transparent from-neutral-200 from-40% to-neutral-300 font-semibold") 61 - }; 62 - 63 - return ( 64 - <div className="flex items-center flex-col w-full"> 65 - 66 - <div className="flex flex-col gap-4 w-full items-center mb-20"> 67 - <Chip 68 - color="secondary" 69 - variant="flat" 70 - size="lg" 71 - > 72 - <span className="font-semibold text-lg"> 73 - Free, duh 74 - </span> 75 - </Chip> 76 - 77 - <h1 className={cn(montserrat.className, "lg:text-7xl md:text-6xl text-5xl font-semibold dark:text-neutral-100 text-neutral-900 break-words flex flex-col items-center gap-4")}> 78 - <div> 79 - <span className="underline decoration-blurple break-keep">Create Artwork</span> 80 - {" with "} 81 - </div> 82 - <div className="bg-gradient-to-r from-indigo-400 to-pink-400 bg-clip-text text-transparent h-20 break-keep">Wamellow AI</div> 83 - </h1> 84 - 85 - <span className="text-lg text-center font-medium max-w-xl mb-4"> 86 - Create amazing artwork of animies and more or view {"length" in uploads ? uploads.length - 1 : "..."}+ uploads of our free /image Ai models directly within your Discord server. 87 - </span> 88 - 89 - <div className="flex gap-2 lg:mt-0"> 90 - <ServerButton 91 - as={Link} 92 - startContent={<HiUserAdd />} 93 - className="w-1/2 lg:w-fit !text-xl !font-medium" 94 - color="secondary" 95 - href="/login?invite=true" 96 - size="lg" 97 - > 98 - <span className="block sm:hidden">Invite</span> 99 - <span className="hidden sm:block">Invite Wamellow</span> 100 - </ServerButton> 101 - <ServerButton 102 - as={Link} 103 - startContent={<BsDiscord />} 104 - className="w-1/2 lg:w-fit !text-xl !font-medium" 105 - href="/support" 106 - size="lg" 107 - > 108 - <span className="block sm:hidden">Support</span> 109 - <span className="hidden sm:block">Join support</span> 110 - </ServerButton> 111 - </div> 112 - </div> 113 - 114 - <article 115 - className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-10 w-full" 116 - itemScope 117 - itemType="http://schema.org/Article" 118 - > 119 - {Array.isArray(uploads) ? 120 - uploads 121 - .map((item, i) => ( 122 - <Link 123 - key={item.id + i} 124 - className="gap-10 items-center w-full relative max-w-lg hover:scale-105 transition-transform duration-300 ease-in-out" 125 - href={`/ai-gallery/${item.id}`} 126 - > 127 - <Image 128 - alt="" 129 - className="rounded-xl shadow-md w-full mt-2" 130 - height={512} 131 - itemProp="image" 132 - loading="lazy" 133 - src={`https://r2.wamellow.com/ai-image/${item.id}.webp`} 134 - width={512} 135 - /> 136 - <div className="absolute bottom-0 left-0 w-full z-20"> 137 - <div className="bg-wamellow backdrop-blur-xl backdrop-brightness-[.25] m-2 p-2 rounded-xl space-y-2"> 138 - <Chip 139 - color="secondary" 140 - variant="flat" 141 - > 142 - /image 143 - </Chip> 144 - <h3 className={styles.h3}>{item.model}</h3> 145 - </div> 146 - </div> 147 - </Link> 148 - )) 149 - : 150 - <Notice message={uploads.message || "Something went wrong..."} /> 151 - } 152 - </article> 153 - 154 - <Pagination 155 - key={searchParams.model} 156 - searchParams={searchParams} 157 - pages={2} 158 - /> 159 - 160 - <Image 161 - alt="/image command usage" 162 - className="w-full rounded-md shadow-md mt-6" 163 - height={438} 164 - src={CommandPic} 165 - width={1723} 166 - /> 167 - 168 - </div > 169 - ); 170 - }
+33 -33
app/(home)/ai/pagination.component.tsx app/ai-gallery/(home)/pagination.component.tsx
··· 1 - "use client"; 2 - 3 - import { Pagination as UiPagination } from "@nextui-org/react"; 4 - import { useRouter } from "next/navigation"; 5 - 6 - import { getCanonicalUrl } from "@/utils/urls"; 7 - 8 - export default function Pagination( 9 - { 10 - searchParams, 11 - pages 12 - }: { 13 - searchParams: { page: string, model: string }; 14 - pages: number; 15 - } 16 - ) { 17 - const router = useRouter(); 18 - 19 - return ( 20 - <UiPagination 21 - className="w-full" 22 - classNames={{ prev: "bg-wamellow", item: "bg-wamellow", next: "bg-wamellow" }} 23 - color="secondary" 24 - showControls 25 - total={pages} 26 - size="lg" 27 - page={parseInt(searchParams.page || "0")} 28 - onChange={(now) => { 29 - router.push(getCanonicalUrl("ai", `?page=${now}${searchParams.model ? `&model=${searchParams.model}` : ""}`)); 30 - }} 31 - /> 32 - ); 33 - 1 + "use client"; 2 + 3 + import { Pagination as UiPagination } from "@nextui-org/react"; 4 + import { useRouter } from "next/navigation"; 5 + 6 + import { getCanonicalUrl } from "@/utils/urls"; 7 + 8 + export default function Pagination( 9 + { 10 + searchParams, 11 + pages 12 + }: { 13 + searchParams: { page: string, model: string }; 14 + pages: number; 15 + } 16 + ) { 17 + const router = useRouter(); 18 + 19 + return ( 20 + <UiPagination 21 + className="w-full" 22 + classNames={{ prev: "bg-wamellow", item: "bg-wamellow", next: "bg-wamellow" }} 23 + color="secondary" 24 + showControls 25 + total={pages} 26 + size="lg" 27 + page={parseInt(searchParams.page || "0")} 28 + onChange={(now) => { 29 + router.push(getCanonicalUrl("ai-gallery", `?page=${now}${searchParams.model ? `&model=${searchParams.model}` : ""}`)); 30 + }} 31 + /> 32 + ); 33 + 34 34 }
+3 -1
app/[pathname]/page.tsx
··· 29 29 .then((res) => res.json()) 30 30 .catch(() => null) as { videoUrl: string } | null; 31 31 32 - redirect(res?.videoUrl || "https://www.youtube.com/channel/UClWBeVcz5LUmcCN1gHG_GCg"); 32 + return redirect(res?.videoUrl || "https://www.youtube.com/channel/UClWBeVcz5LUmcCN1gHG_GCg"); 33 33 } 34 + case "ai": 35 + return redirect("/ai-gallery"); 34 36 } 35 37 36 38 notFound();
+125
app/ai-gallery/(home)/layout.tsx
··· 1 + import { Chip } from "@nextui-org/react"; 2 + import { Metadata } from "next"; 3 + import { Montserrat } from "next/font/google"; 4 + import Image from "next/image"; 5 + import Link from "next/link"; 6 + import { BsDiscord } from "react-icons/bs"; 7 + import { HiUserAdd } from "react-icons/hi"; 8 + 9 + import Footer from "@/components/footer"; 10 + import { ServerButton } from "@/components/server-button"; 11 + import CommandPic from "@/public/image-command.webp"; 12 + import cn from "@/utils/cn"; 13 + import { getBaseUrl, getCanonicalUrl } from "@/utils/urls"; 14 + 15 + const montserrat = Montserrat({ subsets: ["latin"] }); 16 + 17 + interface Props { 18 + children: React.ReactNode; 19 + } 20 + 21 + export const generateMetadata = async (): Promise<Metadata> => { 22 + 23 + const title = "Free /image Ai for Discord"; 24 + const description = "Summon the enchantment of AI generated images to your Discord server with our versatile /image command, featuring over 40+ distinct SD and 10+ SDXL models."; 25 + const url = getCanonicalUrl("ai-gallery"); 26 + 27 + return { 28 + title, 29 + description, 30 + alternates: { 31 + canonical: url 32 + }, 33 + openGraph: { 34 + title, 35 + description, 36 + type: "website", 37 + url, 38 + images: `${getBaseUrl()}/waya-v3.jpg` 39 + }, 40 + twitter: { 41 + card: "summary", 42 + site: "wamellow.com", 43 + title, 44 + description, 45 + images: `${getBaseUrl()}/waya-v3.jpg` 46 + } 47 + }; 48 + }; 49 + 50 + export default function RootLayout({ 51 + children 52 + }: Props) { 53 + 54 + 55 + return ( 56 + <div> 57 + 58 + <div className="flex justify-between items-center"> 59 + <h1 60 + className={cn(montserrat.className, "lg:text-5xl text-4xl font-bold dark:text-neutral-100 text-neutral-900 break-words mb-2")} 61 + > 62 + <span className="bg-gradient-to-r from-indigo-400 to-pink-400 bg-clip-text text-transparent h-20 break-keep">/image Ai</span> 63 + {" generated in "} 64 + <span className="underline decoration-blurple break-keep">discord</span> 65 + </h1> 66 + <Chip 67 + className="hidden md:block" 68 + color="secondary" 69 + variant="flat" 70 + size="lg" 71 + > 72 + <span className="font-semibold text-xl"> 73 + Free, duh 74 + </span> 75 + </Chip> 76 + </div> 77 + 78 + <div 79 + className="text-lg font-medium mb-6" 80 + > 81 + Images that were generated using the /image Ai in discord with Wamellow. 82 + </div> 83 + 84 + {children} 85 + 86 + <Link 87 + href="/login?invite=true" 88 + target="_blank" 89 + > 90 + <Image 91 + alt="/image command usage" 92 + className="w-full rounded-md shadow-md mt-12" 93 + height={438} 94 + src={CommandPic} 95 + width={1723} 96 + /> 97 + </Link> 98 + 99 + <div className="flex gap-2 mt-4"> 100 + <ServerButton 101 + as={Link} 102 + startContent={<HiUserAdd />} 103 + className="w-1/2 lg:w-fit !text-xl !font-medium" 104 + color="secondary" 105 + href="/login?invite=true" 106 + size="lg" 107 + > 108 + <span className="hidden sm:block">Invite Wamellow</span> 109 + </ServerButton> 110 + <ServerButton 111 + as={Link} 112 + startContent={<BsDiscord />} 113 + className="w-1/2 lg:w-fit !text-xl !font-medium" 114 + href="/support" 115 + size="lg" 116 + > 117 + <span className="hidden sm:block">Join support</span> 118 + </ServerButton> 119 + </div> 120 + 121 + <Footer /> 122 + 123 + </div> 124 + ); 125 + }
+92
app/ai-gallery/(home)/page.tsx
··· 1 + import { Chip } from "@nextui-org/react"; 2 + import Image from "next/image"; 3 + import Link from "next/link"; 4 + 5 + import Notice from "@/components/notice"; 6 + import { HomeButton, ScreenMessage, SupportButton } from "@/components/screen-message"; 7 + import SadWumpusPic from "@/public/sad-wumpus.gif"; 8 + 9 + import { getUploads } from "../api"; 10 + import Pagination from "./pagination.component"; 11 + 12 + interface Props { 13 + searchParams: { page: string; model: string }; 14 + } 15 + 16 + export default async function Home({ 17 + searchParams 18 + }: Props) { 19 + const uploads = await getUploads({ page: parseInt(searchParams.page || "1"), model: searchParams.model }); 20 + 21 + if ("message" in uploads) { 22 + return ( 23 + <ScreenMessage 24 + top="0rem" 25 + title="Something went wrong on this page.." 26 + description={uploads.message} 27 + buttons={<> 28 + <HomeButton /> 29 + <SupportButton /> 30 + </>} 31 + > 32 + <Image src={SadWumpusPic} alt="" height={141} width={124} /> 33 + </ScreenMessage> 34 + ); 35 + } 36 + 37 + if (!uploads.results.length) { 38 + return ( 39 + <Notice 40 + message="No uploads were found for the specified model." 41 + /> 42 + ); 43 + } 44 + 45 + return ( 46 + <> 47 + <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mb-4"> 48 + {uploads.results.map((upload) => ( 49 + <Link 50 + key={"upload-" + upload.id} 51 + className="bg-wamellow rounded-xl shadow-md relative duration-100 outline-violet-500 hover:outline" 52 + href={`/ai-gallery/${upload.id}`} 53 + target="_blank" 54 + > 55 + <Image 56 + alt="" 57 + className="rounded-xl" 58 + height={512} 59 + itemProp="image" 60 + loading="lazy" 61 + src={`https://r2.wamellow.com/ai-image/${upload.id}.webp`} 62 + width={512} 63 + /> 64 + 65 + <Chip 66 + className="absolute top-2 left-2 z-10 backdrop-blur-xl backdrop-brightness-50" 67 + color="secondary" 68 + size="sm" 69 + variant="dot" 70 + > 71 + {upload.model} 72 + </Chip> 73 + 74 + <div className="p-3 flex gap-2"> 75 + <p className="truncate"> 76 + {upload.prompt} 77 + </p> 78 + </div> 79 + 80 + </Link> 81 + ))} 82 + </div> 83 + 84 + <Pagination 85 + key={searchParams.model} 86 + searchParams={searchParams} 87 + pages={2} 88 + /> 89 + 90 + </> 91 + ); 92 + }
+9 -9
app/ai-gallery/[uploadId]/layout.tsx
··· 1 - import { Button, Chip } from "@nextui-org/react"; 1 + import { Button } from "@nextui-org/react"; 2 2 import { Metadata } from "next"; 3 3 import NextImage from "next/image"; 4 4 import Link from "next/link"; ··· 29 29 const upload = await getUpload(params.uploadId); 30 30 31 31 const title = "Free /image Ai for Discord"; 32 - const description = `View an amazing AI generated image ${"model" in upload ? `using the ${upload.model}` : ""} created with our versatile /image command. ${"prompt" in upload ? `Using the prompt: ${upload.prompt}` : ""}`.replace(" ", " "); 32 + const description = `Amazing AI generated images ${"model" in upload ? `using the ${upload.model}` : ""}, created using our versatile image command.`.replace(" ", " "); 33 33 const images = "id" in upload ? `https://r2.wamellow.com/ai-image/${upload.id}.webp` : `${getBaseUrl()}/waya-v3.jpg`; 34 34 const url = getCanonicalUrl("ai-gallery", params.uploadId); 35 35 ··· 64 64 65 65 const [guild, uploads, analytics] = await Promise.all([ 66 66 "guildId" in upload ? getGuild(upload.guildId) : undefined, 67 - "model" in upload ? getUploads({ model: upload.model }) : undefined, 67 + "model" in upload ? getUploads({ query: upload.prompt }) : undefined, 68 68 getPageAnalytics("/ai-gallery/" + params.uploadId) 69 69 ]); 70 70 ··· 109 109 110 110 <div> 111 111 <h2 className="text-3xl font-bold mb-4 text-neutral-200">More like this /image</h2> 112 - <span className="relative bottom-4 mb-4"> 113 - Images that were also generated with the <Chip>{"model" in upload ? upload.model : "..."}</Chip> model. 112 + <span className="relative bottom-3"> 113 + Images that are similiar to the one you{"'"}re viewing. 114 114 </span> 115 115 116 - {Array.isArray(uploads) ? 117 - <div className="flex flex-wrap gap-4"> 118 - {uploads 116 + {uploads && "results" in uploads ? 117 + <div className="flex flex-wrap gap-4 mt-2"> 118 + {uploads.results 119 119 .map((upload) => ( 120 120 <Link 121 121 key={upload.id} ··· 148 148 <Button 149 149 className="!mt-5" 150 150 as={Link} 151 - href="/ai" 151 + href="/ai-gallery" 152 152 startContent={<HiArrowLeft />} 153 153 > 154 154 View all Uploads
+26 -9
app/ai-gallery/api.ts
··· 1 + import { defaultFetchOptions } from "@/lib/api"; 1 2 import { ApiError } from "@/typings"; 2 3 3 4 export interface Upload { ··· 15 16 createdAt: string; 16 17 } 17 18 18 - export async function getUploads(options?: { model?: string; page?: number }): Promise<Upload[] | ApiError> { 19 - const res = await fetch(`${process.env.NEXT_PUBLIC_API}/ai${options?.model ? `?model=${options.model}` : ""}${options?.page ? `?page=${options.page - 1}` : ""}`, { 20 - headers: { Authorization: process.env.API_SECRET as string }, 21 - next: { revalidate: 60 * 60 } 22 - }); 19 + interface Uploads { 20 + results: Upload[]; 21 + pagination: { 22 + total: number; 23 + page: number; 24 + } 25 + } 26 + 27 + export async function getUploads(options?: Partial<{ 28 + model: string; 29 + nsfw: boolean; 30 + page: number; 31 + query: string; 32 + }>): Promise<Uploads | ApiError> { 33 + 34 + const params = new URLSearchParams(); 35 + if (options?.model) params.append("model", options.model); 36 + if (options?.nsfw) params.append("nsfw", options.nsfw.toString()); 37 + if (options?.page) params.append("page", (options.page - 1).toString()); 38 + if (options?.query) params.append("query", options.query); 39 + 40 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/ai?${params.toString()}`, 41 + defaultFetchOptions 42 + ); 23 43 24 44 return res.json(); 25 45 } ··· 33 53 } 34 54 35 55 export async function getUpload(uploadId: string): Promise<ExtendedUpload | ApiError> { 36 - const res = await fetch(`${process.env.NEXT_PUBLIC_API}/ai/${uploadId}`, { 37 - headers: { Authorization: process.env.API_SECRET as string }, 38 - next: { revalidate: 60 * 60 } 39 - }); 56 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/ai/${uploadId}`, defaultFetchOptions); 40 57 41 58 return res.json(); 42 59 }