├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── app ├── api │ ├── assistants │ │ └── threads │ │ │ └── messages │ │ │ └── route.ts │ ├── auth │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── cron │ │ └── route.ts │ ├── deploy-contract │ │ └── route.ts │ ├── external-deploy │ │ └── route.ts │ ├── ipfs-upload │ │ └── route.ts │ ├── og │ │ └── route.tsx │ ├── v1 │ │ └── completions │ │ │ └── route.ts │ └── verify-contract │ │ └── route.ts ├── chat │ └── [id] │ │ ├── opengraph-image.png │ │ ├── page.tsx │ │ └── twitter-image.png ├── globals.css ├── layout.tsx ├── manifest.ts ├── opengraph-image.png ├── page.tsx ├── robots.ts ├── share │ └── [id] │ │ └── page.tsx ├── sign-in │ └── page.tsx ├── sitemap.ts ├── state │ └── global-store.tsx └── twitter-image.png ├── auth.ts ├── biome.json ├── bun.lockb ├── components.json ├── components ├── agent-card.tsx ├── chat │ ├── button-scroll-to-bottom.tsx │ ├── chat-list.tsx │ ├── chat-message-actions.tsx │ ├── chat-message.tsx │ ├── chat-panel.tsx │ ├── chat-scroll-anchor.tsx │ ├── chat.tsx │ └── prompt-form.tsx ├── connect-button.tsx ├── deploy-contract-button.tsx ├── deploy-tokenscript-button.tsx ├── external-link.tsx ├── footer.tsx ├── header │ ├── clear-history.tsx │ ├── header.tsx │ ├── login-button.tsx │ ├── settings-drop-down.tsx │ └── user-menu.tsx ├── landing.tsx ├── markdown.tsx ├── metis-teaser.tsx ├── providers │ ├── ui-providers.tsx │ └── web3-provider.tsx ├── sidebar │ ├── sidebar-actions.tsx │ ├── sidebar-agents.tsx │ ├── sidebar-footer.tsx │ ├── sidebar-item.tsx │ ├── sidebar-list.tsx │ └── sidebar.tsx ├── sign-out-button.tsx └── ui │ ├── alert-dialog.tsx │ ├── badge.tsx │ ├── button.tsx │ ├── code-block.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── icons.tsx │ ├── input.tsx │ ├── label.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── sonner.tsx │ ├── textarea.tsx │ └── tooltip.tsx ├── env.ts ├── lib ├── actions │ ├── ai.ts │ ├── chat.ts │ ├── db.ts │ ├── deploy-contract.ts │ ├── ipfs.ts │ ├── solidity │ │ ├── compile-contract.ts │ │ ├── deploy-contract.ts │ │ ├── deploy-tokenscript.ts │ │ └── verify-contract.ts │ ├── unstoppable-domains.ts │ └── verification.ts ├── blockscout.ts ├── config-server.ts ├── config.ts ├── constants.ts ├── contracts │ ├── contract-utils.ts │ └── resolve-imports.ts ├── data │ ├── ipfs.ts │ ├── kv.ts │ └── openai.ts ├── hooks │ ├── use-copy-to-clipboard.tsx │ ├── use-enter-submit.tsx │ ├── use-is-client.tsx │ ├── use-local-storage.ts │ ├── use-safe-auto-connect.ts │ ├── use-scroll-to-bottom.tsx │ ├── use-tokenscript-deploy.ts │ ├── use-tokenscript-deploy.tsx │ └── use-wallet-deploy.ts ├── openai.ts ├── rainbowkit.ts ├── solc.d.ts ├── solidity │ ├── deploy.ts │ ├── utils.ts │ └── verification.ts ├── tools.ts ├── types.ts ├── utils.ts └── viem.ts ├── middleware.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── apple-touch-icon.png ├── assets │ ├── agent-factory.png │ ├── erc20.png │ ├── erc721.png │ ├── metis-logo.png │ ├── rootstock.png │ ├── safe-logo.svg │ ├── tokenscript.png │ └── web3gpt.png ├── favicon-16x16.png ├── favicon.ico ├── favicon.png ├── logo.svg ├── lotties │ ├── clock.json │ ├── globe.json │ └── puzzle.json ├── manifest.json ├── mantle-logo.jpeg ├── polygon-logo.png ├── web3gpt-logo-beta.svg └── web3gpt-logo.svg ├── tailwind.config.ts ├── tsconfig.json └── vercel.json /.env.example: -------------------------------------------------------------------------------- 1 | AUTH_GITHUB_ID= 2 | AUTH_GITHUB_SECRET= 3 | AUTH_REDIRECT_PROXY_URL= 4 | AUTH_SECRET= 5 | CRON_SECRET= 6 | DEPLOYER_PRIVATE_KEY= 7 | KV_REST_API_READ_ONLY_TOKEN= 8 | KV_REST_API_TOKEN= 9 | KV_REST_API_URL= 10 | KV_URL= 11 | NEXT_PUBLIC_ALCHEMY_API_KEY= 12 | NEXT_PUBLIC_ARBISCAN_API_KEY= 13 | NEXT_PUBLIC_BASESCAN_API_KEY= 14 | NEXT_PUBLIC_BLOCKSCOUT_API_KEY= 15 | NEXT_PUBLIC_ETHERSCAN_API_KEY= 16 | NEXT_PUBLIC_INFURA_API_KEY= 17 | NEXT_PUBLIC_IPFS_GATEWAY= 18 | NEXT_PUBLIC_MANTLESCAN_API_KEY= 19 | NEXT_PUBLIC_OPSCAN_API_KEY= 20 | NEXT_PUBLIC_POLYGONSCAN_API_KEY= 21 | NEXT_PUBLIC_QUICKNODE_API_KEY= 22 | NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID= 23 | OPENAI_API_KEY= 24 | PINATA_API_KEY= 25 | PINATA_API_SECRET= 26 | PINATA_JWT= 27 | STABILITY_API_KEY= 28 | XAI_API_KEY= 29 | UNKEY_COMPLETIONS_API_ID= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .pnpm-debug.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # turbo 33 | .turbo 34 | 35 | .contentlayer 36 | .env 37 | .vercel 38 | .vscode 39 | .env*.local 40 | 41 | # tsc 42 | tsconfig.tsbuildinfo 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 W3GPT Corp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web3GPT 🚀 2 | 3 | Web3GPT is an AI-powered smart contract development platform that combines Large Language Models (LLMs) with specialized AI agents to streamline blockchain development. Try it live at [w3gpt.ai](https://w3gpt.ai) or check out our [documentation](https://docs.w3gpt.ai). 4 | 5 | ![image](https://github.com/Markeljan/Web3GPT/assets/12901349/c84ec7ed-3657-4d19-a739-2285e25c29a1) 6 | 7 | ## Key Features 🌟 8 | 9 | - **Multi-Chain Smart Contract Development:** Deploy contracts across multiple EVM-compatible testnets including: 10 | - Arbitrum Sepolia 11 | - Optimism Sepolia 12 | - Base Sepolia 13 | - Metis Sepolia 14 | - Mantle Sepolia 15 | - Polygon Amoy 16 | - Sepolia 17 | 18 | - **Specialized AI Agents:** 19 | - Web3GPT - Core smart contract development agent 20 | - Unstoppable Domains - Domain resolution specialist 21 | - OpenZeppelin 5.0 - Security-focused development using latest OZ libraries 22 | - CTF Agent - Interactive Capture The Flag challenges 23 | - Creator - Custom AI agent creation 24 | - Smart Token - TokenScript-powered token deployment 25 | 26 | - **GitHub Authentication:** Secure login and persistence of your development sessions 27 | 28 | - **Share & Collaborate:** Share your smart contract development conversations with unique shareable URLs 29 | 30 | ## Getting Started 🛠️ 31 | 32 | 1. Clone the repository 33 | 2. Configure environment variables (see `.env.example`) 34 | 3. Install dependencies and run the development server 35 | 36 | ```bash 37 | bun install 38 | ``` 39 | 40 | ```bash 41 | bun dev 42 | ``` 43 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | export const runtime = "edge" 2 | 3 | export { GET, POST } from "@/auth" 4 | -------------------------------------------------------------------------------- /app/api/cron/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from "next/server" 2 | 3 | import { deleteVerification, getVerifications } from "@/lib/data/kv" 4 | import { checkVerifyStatus, verifyContract } from "@/lib/solidity/verification" 5 | 6 | const PASS_MESSAGE = "Pass - Verified" 7 | const ALREADY_VERIFIED_MESSAGE = "Smart-contract already verified." 8 | 9 | const CRON_SECRET = process.env.CRON_SECRET 10 | 11 | export const GET = async (req: NextRequest) => { 12 | const token = req.headers.get("Authorization") 13 | if (!token) { 14 | return NextResponse.json("Unauthorized", { status: 401 }) 15 | } 16 | if (token.replace("Bearer ", "") !== CRON_SECRET) { 17 | return NextResponse.json("Unauthorized", { status: 401 }) 18 | } 19 | const verifications = await getVerifications() 20 | 21 | for (const verificationData of verifications) { 22 | try { 23 | const { result: guid } = await verifyContract(verificationData) 24 | 25 | if (guid === ALREADY_VERIFIED_MESSAGE) { 26 | console.log(`${verificationData.viemChain.name} ${verificationData.contractAddress}`) 27 | await deleteVerification(verificationData.deployHash) 28 | continue 29 | } 30 | const verificationStatus = await checkVerifyStatus(guid, verificationData.viemChain) 31 | if (verificationStatus.result === PASS_MESSAGE) { 32 | console.log(`${verificationData.viemChain.name} ${verificationData.contractAddress}`) 33 | await deleteVerification(verificationData.deployHash) 34 | } 35 | } catch (error) { 36 | console.error(error instanceof Error ? error.message : "Unknown error") 37 | } 38 | } 39 | 40 | if (verifications.length > 5) console.error(`Too many verifications in queue: ${verifications.length}`) 41 | 42 | return NextResponse.json({ success: true }) 43 | } 44 | -------------------------------------------------------------------------------- /app/api/deploy-contract/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from "next/server" 2 | 3 | import { deployContract } from "@/lib/actions/solidity/deploy-contract" 4 | 5 | export const runtime = "nodejs" 6 | 7 | export async function POST(req: NextRequest) { 8 | const data = await req.json() 9 | const { chainId, contractName, sourceCode, constructorArgs } = data 10 | 11 | try { 12 | const deployResult = await deployContract({ 13 | chainId, 14 | contractName, 15 | sourceCode, 16 | constructorArgs 17 | }) 18 | return NextResponse.json(deployResult) 19 | } catch (error) { 20 | return NextResponse.json(`Error in deployContract API ROUTE: ${error}`) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/api/external-deploy/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from "next/server" 2 | 3 | import { parseEther } from "viem" 4 | import { arbitrumSepolia } from "viem/chains" 5 | 6 | import { deployContract } from "@/lib/actions/solidity/deploy-contract" 7 | import { WEB3GPT_API_SECRET } from "@/lib/config-server" 8 | 9 | export const runtime = "nodejs" 10 | 11 | type ContractBuilderParams = { 12 | ownerAddress: string 13 | name?: string 14 | symbol?: string 15 | maxSupply?: number 16 | mintPrice?: number 17 | baseURI?: string 18 | } 19 | 20 | const contractBuilder = ({ 21 | ownerAddress, 22 | name = "NFT", 23 | symbol = "NFT", 24 | maxSupply = 1000, 25 | mintPrice = Number(parseEther("0.001")), 26 | baseURI = "ipfs://" 27 | }: ContractBuilderParams) => { 28 | const sourceCode = `// SPDX-License-Identifier: MIT 29 | pragma solidity ^0.8.0; 30 | 31 | import "@openzeppelin/contracts@4.9.3/token/ERC721/extensions/ERC721URIStorage.sol"; 32 | import "@openzeppelin/contracts@4.9.3/access/Ownable.sol"; 33 | import "@openzeppelin/contracts@4.9.3/utils/Counters.sol"; 34 | 35 | contract ConfigurableNFT is ERC721URIStorage, Ownable { 36 | using Counters for Counters.Counter; 37 | 38 | Counters.Counter private _tokenIdCounter; 39 | 40 | uint256 public immutable MAX_SUPPLY; 41 | uint256 public immutable MINT_PRICE; 42 | 43 | string private baseTokenURI; 44 | 45 | constructor( 46 | address owner, 47 | string memory name, 48 | string memory symbol, 49 | uint256 maxSupply, 50 | uint256 mintPrice, 51 | string memory initialBaseURI 52 | ) ERC721(name, symbol) { 53 | MAX_SUPPLY = maxSupply; 54 | MINT_PRICE = mintPrice; 55 | setBaseURI(initialBaseURI); 56 | transferOwnership(owner); 57 | } 58 | 59 | function mintNFT() public payable { 60 | require(_tokenIdCounter.current() < MAX_SUPPLY, "Max supply reached"); 61 | require(msg.value >= MINT_PRICE, "Ether sent is not correct"); 62 | 63 | uint256 tokenId = _tokenIdCounter.current(); 64 | _tokenIdCounter.increment(); 65 | _safeMint(msg.sender, tokenId); 66 | _setTokenURI(tokenId, string(abi.encodePacked(baseTokenURI, Strings.toString(tokenId)))); 67 | } 68 | 69 | function setBaseURI(string memory newBaseURI) public onlyOwner { 70 | baseTokenURI = newBaseURI; 71 | } 72 | 73 | function _baseURI() internal view override returns (string memory) { 74 | return baseTokenURI; 75 | } 76 | 77 | function withdraw() public onlyOwner { 78 | uint256 balance = address(this).balance; 79 | payable(owner()).transfer(balance); 80 | } 81 | } 82 | ` 83 | 84 | const constructorArgs = [ownerAddress, name, symbol, maxSupply?.toString(), mintPrice?.toString(), baseURI] 85 | 86 | return { sourceCode, constructorArgs } 87 | } 88 | export async function POST(req: NextRequest) { 89 | const apiSecret = req.headers.get("web3gpt-api-key") 90 | if (apiSecret !== WEB3GPT_API_SECRET) { 91 | return NextResponse.json({ error: "Unauthorized: invalid web3gpt-api-key" }, { status: 401 }) 92 | } 93 | const json = await req.json() 94 | 95 | const { ownerAddress, size, price, baseUri, name, symbol } = json 96 | 97 | if (!ownerAddress) { 98 | return NextResponse.json({ error: "OwnerAddress missing in body" }, { status: 400 }) 99 | } 100 | 101 | const { sourceCode, constructorArgs } = contractBuilder({ 102 | ownerAddress, 103 | name, 104 | symbol, 105 | maxSupply: size, 106 | mintPrice: price, 107 | baseURI: baseUri 108 | }) 109 | 110 | const chainId = arbitrumSepolia.id.toString() 111 | 112 | try { 113 | const deployResult = await deployContract({ 114 | chainId, 115 | contractName: "ConfigurableNFT", 116 | sourceCode, 117 | constructorArgs 118 | }) 119 | return NextResponse.json(deployResult) 120 | } catch (error) { 121 | return NextResponse.json({ error: `Error in deployContract external: ${error}` }, { status: 500 }) 122 | } 123 | } 124 | 125 | export const OPTIONS = async (_req: NextRequest) => { 126 | return NextResponse.json("", { 127 | status: 200 128 | }) 129 | } 130 | -------------------------------------------------------------------------------- /app/api/ipfs-upload/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from "next/server" 2 | 3 | import { ipfsUploadDir } from "@/lib/actions/ipfs" 4 | 5 | export const runtime = "nodejs" 6 | 7 | export async function POST(req: NextRequest) { 8 | const json = await req.json() 9 | const { sources, abi, bytecode, standardJsonInput } = json 10 | try { 11 | const deployResult = await ipfsUploadDir(sources, abi, bytecode, standardJsonInput) 12 | 13 | return NextResponse.json(deployResult) 14 | } catch (error) { 15 | const err = error as Error 16 | console.error(`Error in verifyContract: ${err.message}`) 17 | return NextResponse.json(`Error in verifyContract: ${err.message}`) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/api/v1/completions/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server" 2 | 3 | import { openai } from "@ai-sdk/openai" 4 | import { type NextRequestWithUnkeyContext, withUnkey } from "@unkey/nextjs" 5 | import { track } from "@vercel/analytics/server" 6 | import { generateText, streamText } from "ai" 7 | 8 | const UNKEY_COMPLETIONS_API_ID = process.env.UNKEY_COMPLETIONS_API_ID 9 | 10 | export const maxDuration = 30 11 | 12 | export const POST = withUnkey( 13 | async (req: NextRequestWithUnkeyContext) => { 14 | // Set CORS headers 15 | const headers = { 16 | "Access-Control-Allow-Origin": "*", 17 | "Access-Control-Allow-Methods": "POST", 18 | "Access-Control-Allow-Headers": "Content-Type, Authorization", 19 | } 20 | 21 | // Validate API key 22 | if (!req.unkey?.valid) { 23 | return new NextResponse("unauthorized get api key at https://t.me/w3gptai", { 24 | status: 403, 25 | headers, 26 | }) 27 | } 28 | 29 | try { 30 | // Extract request parameters 31 | const { prompt, stream = false, system } = await req.json() 32 | 33 | // Validate prompt 34 | if (!prompt || typeof prompt !== "string") { 35 | return new NextResponse("Prompt is required and must be a string", { 36 | status: 400, 37 | headers, 38 | }) 39 | } 40 | 41 | const { keyId = "unknown_key", remaining = 0, ownerId = "unknown_owner" } = req.unkey 42 | console.log("keyId", keyId) 43 | console.log("remaining", remaining) 44 | console.log("ownerId", ownerId) 45 | // Track analytics 46 | track("completions_request", { 47 | apiId: UNKEY_COMPLETIONS_API_ID, 48 | keyId, 49 | ownerId, 50 | remaining, 51 | stream, 52 | }) 53 | 54 | // Handle streaming completion 55 | if (stream) { 56 | const result = streamText({ 57 | system, 58 | prompt, 59 | model: openai("gpt-4o"), 60 | }) 61 | 62 | const response = result.toDataStreamResponse() 63 | // Add CORS headers to stream response 64 | response.headers.set("Access-Control-Allow-Origin", "*") 65 | return response 66 | } 67 | 68 | // Handle non-streaming completion 69 | const { text } = await generateText({ 70 | system, 71 | prompt, 72 | model: openai("gpt-4o"), 73 | }) 74 | 75 | return NextResponse.json({ text }, { headers }) 76 | } catch (error) { 77 | console.error("Completion error:", error) 78 | return new NextResponse("An error occurred while generating the completion", { 79 | status: 500, 80 | headers, 81 | }) 82 | } 83 | }, 84 | { 85 | handleInvalidKey(req, result) { 86 | console.log("handleInvalidKey", req, result) 87 | return new NextResponse("Unauthorized get api key at https://t.me/w3gptai", { 88 | status: 403, 89 | }) 90 | }, 91 | disableTelemetry: true, 92 | apiId: UNKEY_COMPLETIONS_API_ID, 93 | }, 94 | ) 95 | 96 | // Handle CORS preflight requests 97 | export async function OPTIONS() { 98 | return new NextResponse(null, { 99 | status: 200, 100 | headers: { 101 | "Access-Control-Allow-Origin": "*", 102 | "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", 103 | "Access-Control-Allow-Headers": "Content-Type, Authorization", 104 | }, 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /app/api/verify-contract/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from "next/server" 2 | 3 | import { verifyContract } from "@/lib/actions/solidity/verify-contract" 4 | 5 | export const runtime = "nodejs" 6 | 7 | export async function POST(req: NextRequest) { 8 | const data = await req.json() 9 | const { deployHash, contractAddress, standardJsonInput, encodedConstructorArgs, fileName, contractName, viemChain } = 10 | data 11 | 12 | try { 13 | const deployResult = await verifyContract({ 14 | deployHash, 15 | contractAddress, 16 | standardJsonInput, 17 | encodedConstructorArgs, 18 | fileName, 19 | contractName, 20 | viemChain 21 | }) 22 | return NextResponse.json(deployResult) 23 | } catch (error) { 24 | return NextResponse.json(`Error in verifyContract: ${error}`) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/chat/[id]/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3-gpt/web3gpt/6b60b15f35a7018e89fcdae3bb37493b91a4fa9e/app/chat/[id]/opengraph-image.png -------------------------------------------------------------------------------- /app/chat/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound, redirect } from "next/navigation" 2 | 3 | import { auth } from "@/auth" 4 | import { Chat } from "@/components/chat/chat" 5 | import { DEFAULT_AGENT } from "@/lib/constants" 6 | import { getAgent, getChat } from "@/lib/data/kv" 7 | import { getAiThreadMessages } from "@/lib/data/openai" 8 | import type { NextPageProps } from "@/lib/types" 9 | 10 | export default async function ChatPage({ params, searchParams }: NextPageProps) { 11 | const session = await auth() 12 | 13 | if (!session?.user.id) { 14 | redirect(`/sign-in?next=/chat/${params.id}`) 15 | } 16 | 17 | const chat = await getChat(params.id) 18 | 19 | if (!chat) { 20 | redirect("/") 21 | } 22 | 23 | if (String(chat?.userId) !== session?.user.id) { 24 | notFound() 25 | } 26 | 27 | const agentId = chat.agentId || searchParams?.a 28 | if (typeof agentId !== "string") { 29 | notFound() 30 | } 31 | 32 | const [agent, messages] = await Promise.all([getAgent(agentId), getAiThreadMessages(params.id)]) 33 | 34 | return ( 35 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /app/chat/[id]/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web3-gpt/web3gpt/6b60b15f35a7018e89fcdae3bb37493b91a4fa9e/app/chat/[id]/twitter-image.png -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 240 10% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 240 10% 3.9%; 13 | --primary: 142.1 76.2% 36.3%; 14 | --primary-foreground: 355.7 100% 97.3%; 15 | --secondary: 240 4.8% 95.9%; 16 | --secondary-foreground: 240 5.9% 10%; 17 | --muted: 240 4.8% 95.9%; 18 | --muted-foreground: 240 3.8% 46.1%; 19 | --accent: 240 4.8% 95.9%; 20 | --accent-foreground: 240 5.9% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 240 5.9% 90%; 24 | --input: 240 5.9% 90%; 25 | --ring: 142.1 76.2% 36.3%; 26 | --radius: 0.5rem; 27 | } 28 | 29 | .dark { 30 | --background: 0 0% 0%; 31 | --foreground: 0 0% 95%; 32 | --card: 0 0% 0%; 33 | --card-foreground: 0 0% 95%; 34 | --popover: 0 0% 9%; 35 | --popover-foreground: 0 0% 95%; 36 | --primary: 142.1 70.6% 45.3%; 37 | --primary-foreground: 144.9 80.4% 10%; 38 | --secondary: 240 3.7% 15.9%; 39 | --secondary-foreground: 0 0% 98%; 40 | --muted: 0 0% 15%; 41 | --muted-foreground: 240 5% 64.9%; 42 | --accent: 12 6.5% 15.1%; 43 | --accent-foreground: 0 0% 98%; 44 | --destructive: 0 62.8% 30.6%; 45 | --destructive-foreground: 0 85.7% 97.3%; 46 | --border: 240 3.7% 15.9%; 47 | --input: 240 3.7% 15.9%; 48 | --ring: 142.4 71.8% 29.2%; 49 | } 50 | } 51 | 52 | @layer base { 53 | * { 54 | @apply border-border; 55 | } 56 | 57 | body { 58 | @apply bg-background text-foreground; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata, Viewport } from "next" 2 | import { JetBrains_Mono as FontMono, Inter as FontSans } from "next/font/google" 3 | import { headers } from "next/headers" 4 | import Script from "next/script" 5 | import type { ReactNode } from "react" 6 | 7 | import { Analytics } from "@vercel/analytics/react" 8 | import { ThemeProvider } from "next-themes" 9 | import { cookieToInitialState } from "wagmi" 10 | 11 | import "@/app/globals.css" 12 | import { Header } from "@/components/header/header" 13 | import { Web3Provider } from "@/components/providers/web3-provider" 14 | import { Toaster } from "@/components/ui/sonner" 15 | import { TooltipProvider } from "@/components/ui/tooltip" 16 | import { APP_URL, getWagmiConfig } from "@/lib/config" 17 | import { cn } from "@/lib/utils" 18 | 19 | const fontSans = FontSans({ 20 | subsets: ["latin"], 21 | variable: "--font-sans", 22 | }) 23 | 24 | const fontMono = FontMono({ 25 | subsets: ["latin"], 26 | variable: "--font-mono", 27 | }) 28 | 29 | export const metadata: Metadata = { 30 | title: { 31 | default: "Web3GPT", 32 | template: "%s - Web3GPT", 33 | }, 34 | description: "Deploy smart contracts, create AI Agents, do more onchain with AI.", 35 | keywords: ["smart contracts", "AI", "web3", "blockchain", "ethereum", "solidity", "development"], 36 | authors: [{ name: "Markeljan" }], 37 | creator: "Markeljan", 38 | publisher: "W3GPT", 39 | robots: { 40 | index: true, 41 | follow: true, 42 | googleBot: { 43 | index: true, 44 | follow: true, 45 | }, 46 | }, 47 | icons: { 48 | icon: "/logo.svg", 49 | shortcut: "/favicon-16x16.png", 50 | apple: "/apple-touch-icon.png", 51 | }, 52 | metadataBase: new URL(APP_URL), 53 | openGraph: { 54 | type: "website", 55 | locale: "en_US", 56 | url: APP_URL, 57 | title: "Web3GPT", 58 | description: "Deploy smart contracts, create AI Agents, do more onchain with AI.", 59 | siteName: "Web3GPT", 60 | images: [ 61 | { 62 | url: `${APP_URL}/og-image.png`, 63 | width: 1200, 64 | height: 630, 65 | alt: "Web3GPT", 66 | }, 67 | ], 68 | }, 69 | twitter: { 70 | card: "summary_large_image", 71 | title: "Web3GPT", 72 | description: "Deploy smart contracts, create AI Agents, do more onchain with AI.", 73 | site: "@w3gptai", 74 | creator: "@0xSoko", 75 | images: [`${APP_URL}/twitter-image.png`], 76 | }, 77 | } 78 | 79 | export const viewport: Viewport = { 80 | themeColor: [ 81 | { media: "(prefers-color-scheme: light)", color: "white" }, 82 | { media: "(prefers-color-scheme: dark)", color: "black" }, 83 | ], 84 | } 85 | 86 | export default function Layout({ children }: { children: ReactNode }) { 87 | const initialState = cookieToInitialState(getWagmiConfig(), headers().get("cookie")) 88 | 89 | return ( 90 | 91 | 92 | 100 | 101 | 102 |
103 |
104 |
105 | {children} 106 |
107 |
108 | 113 |
114 |
115 |
116 | 117 |