forked from mito-systems/sol-mem-gen
173 lines
5.7 KiB
TypeScript
173 lines
5.7 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
import BN from 'bn.js';
|
|
import Big from 'big.js';
|
|
import dynamic from 'next/dynamic'
|
|
|
|
interface AIServiceCardProps {
|
|
title: string
|
|
description: string
|
|
isWalletConnected: boolean
|
|
onGenerate: (prompt: string) => Promise<{ imageUrl?: string, transactionSignature?: string, error?: string }>
|
|
priceMTM: BN
|
|
}
|
|
|
|
interface GenerationState {
|
|
loading: boolean
|
|
processing: boolean
|
|
imageUrl: string | null
|
|
transactionSignature: string | null
|
|
error: string | null
|
|
}
|
|
|
|
const baseUnitToWholeNumber = (value: BN, decimals: number): string => {
|
|
const bigValue = new Big(value.toString());
|
|
const factor = new Big(10).pow(decimals);
|
|
|
|
return bigValue.div(factor).round(0, Big.roundUp).toFixed(0);
|
|
}
|
|
|
|
const AIServiceCard: React.FC<AIServiceCardProps> = ({
|
|
title,
|
|
description,
|
|
isWalletConnected,
|
|
onGenerate,
|
|
priceMTM
|
|
}) => {
|
|
const [inputText, setInputText] = useState<string>('')
|
|
const [generationState, setGenerationState] = useState<GenerationState>({
|
|
loading: false,
|
|
processing: false,
|
|
imageUrl: null,
|
|
transactionSignature: null,
|
|
error: null,
|
|
})
|
|
|
|
const handleGenerate = async (): Promise<void> => {
|
|
if (!inputText || !isWalletConnected) return
|
|
|
|
setGenerationState({
|
|
...generationState,
|
|
loading: true,
|
|
error: null,
|
|
})
|
|
|
|
try {
|
|
const result = await onGenerate(inputText)
|
|
|
|
if (result.error) {
|
|
setGenerationState({
|
|
...generationState,
|
|
loading: false,
|
|
error: result.error,
|
|
})
|
|
// Reload the page to get latest prices
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 3000);
|
|
|
|
return
|
|
}
|
|
|
|
if (result.imageUrl && result.transactionSignature) {
|
|
setGenerationState({
|
|
loading: false,
|
|
processing: false,
|
|
imageUrl: result.imageUrl,
|
|
transactionSignature: result.transactionSignature,
|
|
error: null,
|
|
})
|
|
} else {
|
|
throw new Error('No image URL received')
|
|
}
|
|
} catch (error) {
|
|
setGenerationState({
|
|
...generationState,
|
|
loading: false,
|
|
error: error instanceof Error ? error.message : 'Generation failed',
|
|
})
|
|
}
|
|
}
|
|
|
|
const generateTwitterShareUrl = (imageUrl: string, transactionSignature: string): string => {
|
|
const baseUrl = window.location.href;
|
|
const cid = imageUrl.split("/image/")[1];
|
|
const memePageUrl = `${baseUrl}memes/${cid}`;
|
|
|
|
const tweetText = `Check out this meme that I generated! \n TX Hash: '${transactionSignature}' \n @${process.env.NEXT_PUBLIC_ACCOUNT_HANDLE} \n`;
|
|
|
|
return `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(memePageUrl)}`;
|
|
};
|
|
|
|
return (
|
|
<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">
|
|
<div className="mb-4">
|
|
<h2 className="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 to-emerald-600">
|
|
{title}
|
|
</h2>
|
|
<p className="text-gray-400 mt-2">{description}</p>
|
|
<div className="mt-2 inline-block px-3 py-1 bg-green-500/20 rounded-full text-green-300 text-sm">
|
|
Cost: {priceMTM ? baseUnitToWholeNumber(priceMTM, 6) : '...'} MTM
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<textarea
|
|
value={inputText}
|
|
onChange={(e) => setInputText(e.target.value)}
|
|
placeholder="Enter your prompt here..."
|
|
disabled={!isWalletConnected}
|
|
className="w-full bg-gray-900/50 text-gray-100 border border-gray-700 rounded-xl p-4
|
|
placeholder-gray-500 focus:border-green-500 focus:ring-2 focus:ring-green-500/20
|
|
focus:outline-none min-h-[120px] transition-all duration-200
|
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
rows={4}
|
|
/>
|
|
<button
|
|
onClick={handleGenerate}
|
|
disabled={!isWalletConnected || generationState.loading || !inputText}
|
|
className="w-full bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600
|
|
hover:to-emerald-600 text-white font-semibold py-4 px-6 rounded-xl
|
|
transition-all duration-200 shadow-lg hover:shadow-green-500/25
|
|
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none"
|
|
>
|
|
{generationState.loading ? 'Processing...' : `Pay ${priceMTM ? baseUnitToWholeNumber(priceMTM, 6) : '...'} MTM & Generate`}
|
|
</button>
|
|
</div>
|
|
|
|
{generationState.error && (
|
|
<div className="mt-4 bg-red-900/20 border border-red-500/20 text-red-400 px-4 py-3 rounded-xl text-center">
|
|
{generationState.error}
|
|
</div>
|
|
)}
|
|
|
|
{generationState.imageUrl && generationState.transactionSignature && (
|
|
<div className="mt-4">
|
|
<img
|
|
src={generationState.imageUrl}
|
|
alt="Generated content"
|
|
className="w-full h-auto rounded-xl shadow-2xl"
|
|
/>
|
|
<div className="mt-4 text-center">
|
|
<a
|
|
href={generateTwitterShareUrl(generationState.imageUrl, generationState.transactionSignature)}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-block w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-4 px-6 rounded-xl transition-all duration-200 shadow-lg hover:shadow-blue-500/25"
|
|
>
|
|
Share on Twitter
|
|
</a>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default dynamic(() => Promise.resolve(AIServiceCard), {
|
|
ssr: false
|
|
})
|