forked from mito-systems/sol-mem-gen
cool
This commit is contained in:
parent
201a8510cc
commit
ac358c4cb6
455
package-lock.json
generated
455
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@ -12,21 +12,19 @@
|
|||||||
"@fal-ai/client": "^1.2.1",
|
"@fal-ai/client": "^1.2.1",
|
||||||
"@solana/spl-token": "^0.3.8",
|
"@solana/spl-token": "^0.3.8",
|
||||||
"@solana/web3.js": "^1.78.4",
|
"@solana/web3.js": "^1.78.4",
|
||||||
"@types/node": "20.5.7",
|
"next": "13.5.4",
|
||||||
"@types/react": "18.2.21",
|
"react": "^18",
|
||||||
"@types/react-dom": "18.2.7",
|
"react-dom": "^18"
|
||||||
"autoprefixer": "10.4.15",
|
|
||||||
"next": "13.4.19",
|
|
||||||
"postcss": "8.4.28",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"tailwindcss": "3.3.3",
|
|
||||||
"typescript": "5.2.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
"@types/node": "^20",
|
||||||
"@typescript-eslint/parser": "^6.5.0",
|
"@types/react": "^18",
|
||||||
"eslint": "8.48.0",
|
"@types/react-dom": "^18",
|
||||||
"eslint-config-next": "13.4.19"
|
"autoprefixer": "^10",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "13.5.4",
|
||||||
|
"postcss": "^8",
|
||||||
|
"tailwindcss": "^3",
|
||||||
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
@ -1,16 +1,13 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { fal } from "@fal-ai/client"
|
import { fal } from "@fal-ai/client"
|
||||||
|
|
||||||
// Add debug logging
|
if (!process.env.FAL_AI_KEY) {
|
||||||
const FAL_KEY = process.env.FAL_AI_KEY
|
|
||||||
|
|
||||||
if (!FAL_KEY) {
|
|
||||||
throw new Error('FAL_AI_KEY is not configured in environment variables')
|
throw new Error('FAL_AI_KEY is not configured in environment variables')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure fal client server-side only
|
// Configure fal client server-side only
|
||||||
fal.config({
|
fal.config({
|
||||||
credentials: FAL_KEY
|
credentials: process.env.FAL_AI_KEY
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function POST(req: NextRequest): Promise<NextResponse> {
|
export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||||
@ -38,7 +35,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('Fal.ai response received:', result ? 'Success' : 'Empty response')
|
console.log('Fal.ai response received:', result)
|
||||||
return NextResponse.json(result)
|
return NextResponse.json(result)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Image generation error:', error)
|
console.error('Image generation error:', error)
|
||||||
|
75
src/app/globals.css
Normal file
75
src/app/globals.css
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--foreground-rgb: 255, 255, 255;
|
||||||
|
--background-start-rgb: 0, 0, 0;
|
||||||
|
--background-end-rgb: 0, 0, 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: rgb(var(--foreground-rgb));
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgb(var(--background-start-rgb)),
|
||||||
|
rgb(var(--background-end-rgb))
|
||||||
|
);
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom animations */
|
||||||
|
@keyframes bounce-delay {
|
||||||
|
0%, 80%, 100% { transform: scale(0); }
|
||||||
|
40% { transform: scale(1.0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-100 {
|
||||||
|
animation-delay: 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-200 {
|
||||||
|
animation-delay: 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset default focus styles */
|
||||||
|
*:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar for textareas */
|
||||||
|
textarea {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(139, 92, 246, 0.5) rgba(17, 24, 39, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar-track {
|
||||||
|
background: rgba(17, 24, 39, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(139, 92, 246, 0.5);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure proper backdrop-filter support */
|
||||||
|
@supports (backdrop-filter: blur(12px)) {
|
||||||
|
.backdrop-blur-lg {
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure proper text-gradient support */
|
||||||
|
@supports (-webkit-background-clip: text) or (background-clip: text) {
|
||||||
|
.bg-clip-text {
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,12 @@
|
|||||||
export const metadata = {
|
import type { Metadata } from 'next'
|
||||||
title: 'Next.js',
|
import { Inter } from 'next/font/google'
|
||||||
description: 'Generated by Next.js',
|
import './globals.css'
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ['latin'] })
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'AI Meme Generator',
|
||||||
|
description: 'Generate memes using AI and MTM tokens',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@ -10,7 +16,7 @@ export default function RootLayout({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>{children}</body>
|
<body className={inter.className}>{children}</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -243,64 +243,100 @@ const processPaymentAndGenerate = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100 py-12 px-4 sm:px-6 lg:px-8">
|
<div className="min-h-screen w-full flex flex-col items-center bg-gradient-to-b from-gray-900 via-gray-800 to-gray-900">
|
||||||
<div className="max-w-3xl mx-auto space-y-8">
|
<div className="container max-w-2xl mx-auto px-4 py-8 flex flex-col items-center">
|
||||||
<div className="text-center">
|
{/* Header */}
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
<div className="w-full text-center mb-8">
|
||||||
|
<h1 className="text-4xl sm:text-5xl font-bold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-600">
|
||||||
AI Meme Generator
|
AI Meme Generator
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg text-gray-600">
|
<p className="text-gray-400 text-lg">
|
||||||
Connect your Solflare wallet and pay {REQUIRED_PAYMENT_AMOUNT} MTM token per meme generation!
|
Connect your Solflare wallet and pay {REQUIRED_PAYMENT_AMOUNT} MTM token per meme
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white p-6 rounded-lg shadow-md">
|
{/* Main Content */}
|
||||||
|
<div className="w-full bg-gray-800/50 backdrop-blur-lg rounded-2xl shadow-xl border border-gray-700/50 mb-8">
|
||||||
|
<div className="p-6">
|
||||||
{!walletState.connected ? (
|
{!walletState.connected ? (
|
||||||
<button
|
<button
|
||||||
onClick={connectWallet}
|
onClick={connectWallet}
|
||||||
className="w-full bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 transition-colors"
|
className="w-full bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600
|
||||||
|
text-white font-semibold py-4 px-6 rounded-xl transition-all duration-200
|
||||||
|
shadow-lg hover:shadow-purple-500/25"
|
||||||
>
|
>
|
||||||
Connect Solflare Wallet
|
Connect Solflare Wallet
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Wallet Info */}
|
||||||
|
<div className="flex items-center justify-between bg-gray-900/30 rounded-lg p-3">
|
||||||
|
<span className="text-gray-400">Connected Wallet: </span>
|
||||||
|
<span className="px-3 py-1 bg-purple-500/20 rounded-full text-purple-300 text-sm">
|
||||||
|
{walletState.publicKey?.slice(0, 22)}...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Input Area */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-green-600">
|
|
||||||
Wallet Connected: {walletState.publicKey?.slice(0, 8)}...
|
|
||||||
</p>
|
|
||||||
<textarea
|
<textarea
|
||||||
value={inputText}
|
value={inputText}
|
||||||
onChange={(e) => setInputText(e.target.value)}
|
onChange={(e) => setInputText(e.target.value)}
|
||||||
placeholder="Enter your meme text here..."
|
placeholder="Describe your meme idea here..."
|
||||||
className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full bg-gray-900/50 text-gray-100 border border-gray-700 rounded-xl p-4
|
||||||
rows={3}
|
placeholder-gray-500 focus:border-purple-500 focus:ring-2 focus:ring-purple-500/20
|
||||||
|
focus:outline-none min-h-[120px] transition-all duration-200"
|
||||||
|
rows={4}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={processPaymentAndGenerate}
|
onClick={processPaymentAndGenerate}
|
||||||
disabled={generationState.processing || generationState.loading || !inputText}
|
disabled={generationState.processing || generationState.loading || !inputText}
|
||||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors disabled:bg-blue-300"
|
className="w-full bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600
|
||||||
|
hover:to-pink-600 text-white font-semibold py-4 px-6 rounded-xl
|
||||||
|
transition-all duration-200 shadow-lg hover:shadow-purple-500/25
|
||||||
|
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none"
|
||||||
>
|
>
|
||||||
{generationState.processing ? 'Processing Payment...' :
|
{generationState.processing ? 'Processing Payment...' :
|
||||||
generationState.loading ? 'Generating...' :
|
generationState.loading ? 'Generating Meme...' :
|
||||||
`Pay ${REQUIRED_PAYMENT_AMOUNT} MTM & Generate Meme`}
|
`Pay ${REQUIRED_PAYMENT_AMOUNT} MTM & Generate Meme`}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error Message */}
|
||||||
{generationState.error && (
|
{generationState.error && (
|
||||||
<div className="text-red-600 text-center">
|
<div className="w-full mb-8">
|
||||||
|
<div className="bg-red-900/20 border border-red-500/20 text-red-400 px-4 py-3 rounded-xl text-center">
|
||||||
{generationState.error}
|
{generationState.error}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Loading Indicator */}
|
||||||
|
{(generationState.processing || generationState.loading) && (
|
||||||
|
<div className="flex justify-center items-center w-full mb-8">
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<div className="w-3 h-3 bg-purple-400 rounded-full animate-bounce"></div>
|
||||||
|
<div className="w-3 h-3 bg-purple-400 rounded-full animate-bounce delay-100"></div>
|
||||||
|
<div className="w-3 h-3 bg-purple-400 rounded-full animate-bounce delay-200"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Generated Image */}
|
||||||
{generationState.imageUrl && (
|
{generationState.imageUrl && (
|
||||||
<div className="bg-white p-6 rounded-lg shadow-md">
|
<div className="w-full bg-gray-800/50 backdrop-blur-lg rounded-2xl shadow-xl border border-gray-700/50 overflow-hidden">
|
||||||
|
<div className="p-4">
|
||||||
<img
|
<img
|
||||||
src={generationState.imageUrl}
|
src={generationState.imageUrl}
|
||||||
alt="Generated meme"
|
alt="Generated meme"
|
||||||
className="w-full h-auto rounded-md"
|
className="w-full h-auto rounded-xl shadow-2xl"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
29
tailwind.config.ts
Normal file
29
tailwind.config.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { Config } from 'tailwindcss'
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: [
|
||||||
|
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
animation: {
|
||||||
|
'bounce-delay': 'bounce-delay 1.4s infinite ease-in-out both',
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
'bounce-delay': {
|
||||||
|
'0%, 80%, 100%': { transform: 'scale(0)' },
|
||||||
|
'40%': { transform: 'scale(1.0)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backgroundImage: {
|
||||||
|
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||||
|
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
Loading…
Reference in New Issue
Block a user