├── .eslintrc.json ├── src ├── app │ ├── favicon.ico │ ├── ConvexClientProvider.tsx │ ├── Navbar.tsx │ ├── collection │ │ └── page.tsx │ ├── layout.tsx │ ├── globals.css │ └── page.tsx ├── lib │ └── utils.ts └── components │ └── ui │ ├── label.tsx │ ├── input.tsx │ └── button.tsx ├── next.config.js ├── postcss.config.js ├── convex ├── schema.ts ├── _generated │ ├── api.js │ ├── api.d.ts │ ├── dataModel.d.ts │ ├── server.js │ └── server.d.ts ├── tsconfig.json ├── sketches.ts ├── generate.ts └── README.md ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── LICENSE ├── package.json ├── tailwind.config.js └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdevcody/convex-replicate/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /convex/schema.ts: -------------------------------------------------------------------------------- 1 | import { defineSchema, defineTable } from "convex/server"; 2 | import { v } from "convex/values"; 3 | 4 | export default defineSchema({ 5 | sketches: defineTable({ 6 | prompt: v.string(), 7 | result: v.optional(v.string()), 8 | }), 9 | }); 10 | -------------------------------------------------------------------------------- /src/app/ConvexClientProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { ReactNode } from "react"; 3 | import { ConvexProvider, ConvexReactClient } from "convex/react"; 4 | 5 | const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); 6 | 7 | export default function ConvexClientProvider({ 8 | children, 9 | }: { 10 | children: ReactNode; 11 | }) { 12 | return {children}; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/Navbar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | 5 | export function Navbar() { 6 | return ( 7 |
8 |
9 |
LOGO
10 | 14 |
Sign In
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /.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 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | .env.local 38 | -------------------------------------------------------------------------------- /convex/_generated/api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.7.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { anyApi } from "convex/server"; 13 | 14 | /** 15 | * A utility for referencing Convex functions in your app's API. 16 | * 17 | * Usage: 18 | * ```js 19 | * const myFunctionReference = api.myModule.myFunction; 20 | * ``` 21 | */ 22 | export const api = anyApi; 23 | export const internal = anyApi; 24 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/collection/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { api } from "../../../convex/_generated/api"; 3 | import { useQuery } from "convex/react"; 4 | 5 | export default function Home() { 6 | const sketches = useQuery(api.sketches.getSketches); 7 | 8 | const sortedSketches = (sketches ?? []).sort((a, b) => { 9 | return b._creationTime - a._creationTime; 10 | }); 11 | 12 | return ( 13 |
14 |

Recent Sketches

15 |
16 | {sortedSketches.map((sketch) => ( 17 | 18 | ))} 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import { Inter } from "next/font/google"; 3 | import ConvexClientProvider from "./ConvexClientProvider"; 4 | import { Navbar } from "./Navbar"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export const metadata = { 9 | title: "Create Next App", 10 | description: "Generated by create next app", 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: { 16 | children: React.ReactNode; 17 | }) { 18 | return ( 19 | 20 | 21 | 22 | 23 | {children} 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /convex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | /* This TypeScript project config describes the environment that 3 | * Convex functions run in and is used to typecheck them. 4 | * You can modify it, but some settings required to use Convex. 5 | */ 6 | "compilerOptions": { 7 | /* These settings are not required by Convex and can be modified. */ 8 | "allowJs": true, 9 | "strict": true, 10 | 11 | /* These compiler options are required by Convex */ 12 | "target": "ESNext", 13 | "lib": ["ES2021", "dom"], 14 | "forceConsistentCasingInFileNames": true, 15 | "allowSyntheticDefaultImports": true, 16 | "module": "ESNext", 17 | "moduleResolution": "Node", 18 | "isolatedModules": true, 19 | "noEmit": true 20 | }, 21 | "include": ["./**/*"], 22 | "exclude": ["./_generated"] 23 | } 24 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /convex/_generated/api.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.7.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import type { 13 | ApiFromModules, 14 | FilterApi, 15 | FunctionReference, 16 | } from "convex/server"; 17 | import type * as generate from "../generate.js"; 18 | import type * as sketches from "../sketches.js"; 19 | 20 | /** 21 | * A utility for referencing Convex functions in your app's API. 22 | * 23 | * Usage: 24 | * ```js 25 | * const myFunctionReference = api.myModule.myFunction; 26 | * ``` 27 | */ 28 | declare const fullApi: ApiFromModules<{ 29 | generate: typeof generate; 30 | sketches: typeof sketches; 31 | }>; 32 | export declare const api: FilterApi< 33 | typeof fullApi, 34 | FunctionReference 35 | >; 36 | export declare const internal: FilterApi< 37 | typeof fullApi, 38 | FunctionReference 39 | >; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Web Dev Cody 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. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "convex-replicate", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "npm-run-all --parallel dev:backend dev:frontend", 7 | "build": "tsc && next build", 8 | "dev:backend": "convex dev", 9 | "dev:frontend": "next dev", 10 | "predev": "convex dev --until-success", 11 | "start": "next start", 12 | "lint": "next lint" 13 | }, 14 | "dependencies": { 15 | "@radix-ui/react-label": "^2.0.2", 16 | "@radix-ui/react-slot": "^1.0.2", 17 | "@types/node": "20.3.1", 18 | "@types/react": "18.2.12", 19 | "@types/react-dom": "18.2.5", 20 | "autoprefixer": "10.4.14", 21 | "class-variance-authority": "^0.6.0", 22 | "clsx": "^1.2.1", 23 | "convex": "^1.7.1", 24 | "eslint": "8.42.0", 25 | "eslint-config-next": "13.4.6", 26 | "lucide-react": "^0.244.0", 27 | "next": "13.4.6", 28 | "npm-run-all": "^4.1.5", 29 | "postcss": "8.4.24", 30 | "react": "18.2.0", 31 | "react-dom": "18.2.0", 32 | "react-hook-form": "^7.44.3", 33 | "react-sketch-canvas": "^6.2.0", 34 | "replicate": "^0.12.3", 35 | "tailwind-merge": "^1.13.2", 36 | "tailwindcss": "3.3.2", 37 | "tailwindcss-animate": "^1.0.6", 38 | "typescript": "5.1.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /convex/sketches.ts: -------------------------------------------------------------------------------- 1 | import { internal } from "./_generated/api"; 2 | import { internalMutation, mutation, query } from "./_generated/server"; 3 | import { v } from "convex/values"; 4 | 5 | export const saveSketch = mutation({ 6 | args: { prompt: v.string(), image: v.string() }, 7 | handler: async (ctx, { prompt, image }) => { 8 | const sketch = await ctx.db.insert("sketches", { 9 | prompt, 10 | }); 11 | 12 | await ctx.scheduler.runAfter(0, internal.generate.generate, { 13 | sketchId: sketch, 14 | prompt, 15 | image, 16 | }); 17 | 18 | return sketch; 19 | }, 20 | }); 21 | 22 | export const getSketch = query({ 23 | args: { sketchId: v.id("sketches") }, 24 | handler: (ctx, { sketchId }) => { 25 | if (!sketchId) return null; 26 | return ctx.db.get(sketchId); 27 | }, 28 | }); 29 | 30 | export const updateSketchResult = internalMutation({ 31 | args: { sketchId: v.id("sketches"), result: v.string() }, 32 | handler: async (ctx, { sketchId, result }) => { 33 | await ctx.db.patch(sketchId, { 34 | result, 35 | }); 36 | }, 37 | }); 38 | 39 | export const getSketches = query({ 40 | handler: async (ctx) => { 41 | const sketches = await ctx.db.query("sketches").collect(); 42 | return sketches; 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /convex/generate.ts: -------------------------------------------------------------------------------- 1 | "use node"; 2 | 3 | import { v } from "convex/values"; 4 | import { internal } from "./_generated/api"; 5 | import { internalAction } from "./_generated/server"; 6 | import Replicate from "replicate"; 7 | 8 | export const generate = internalAction({ 9 | args: { sketchId: v.id("sketches"), prompt: v.string(), image: v.string() }, 10 | handler: async (ctx, { prompt, image, sketchId }) => { 11 | if (!process.env.REPLICATE_API_TOKEN) { 12 | throw new Error( 13 | "Add REPLICATE_API_TOKEN to your environment variables: " + 14 | "https://docs.convex.dev/production/environment-variables" 15 | ); 16 | } 17 | const replicate = new Replicate({ 18 | auth: process.env.REPLICATE_API_TOKEN, 19 | }); 20 | 21 | const output = (await replicate.run( 22 | "jagilley/controlnet-scribble:435061a1b5a4c1e26740464bf786efdfa9cb3a3ac488595a2de23e143fdb0117", 23 | { 24 | input: { 25 | image, 26 | scale: 7, 27 | prompt, 28 | image_resolution: "512", 29 | n_prompt: 30 | "longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality", 31 | }, 32 | } 33 | )) as [string, string]; 34 | 35 | await ctx.runMutation(internal.sketches.updateSketchResult, { 36 | sketchId, 37 | result: output[1], 38 | }); 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /src/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: 222.2 47.4% 11.2%; 9 | 10 | --muted: 210 40% 96.1%; 11 | --muted-foreground: 215.4 16.3% 46.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 47.4% 11.2%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 222.2 47.4% 11.2%; 18 | 19 | --border: 214.3 31.8% 91.4%; 20 | --input: 214.3 31.8% 91.4%; 21 | 22 | --primary: 222.2 47.4% 11.2%; 23 | --primary-foreground: 210 40% 98%; 24 | 25 | --secondary: 210 40% 96.1%; 26 | --secondary-foreground: 222.2 47.4% 11.2%; 27 | 28 | --accent: 210 40% 96.1%; 29 | --accent-foreground: 222.2 47.4% 11.2%; 30 | 31 | --destructive: 0 100% 50%; 32 | --destructive-foreground: 210 40% 98%; 33 | 34 | --ring: 215 20.2% 65.1%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | --background: 224 71% 4%; 41 | --foreground: 213 31% 91%; 42 | 43 | --muted: 223 47% 11%; 44 | --muted-foreground: 215.4 16.3% 56.9%; 45 | 46 | --popover: 224 71% 4%; 47 | --popover-foreground: 215 20.2% 65.1%; 48 | 49 | --card: 224 71% 4%; 50 | --card-foreground: 213 31% 91%; 51 | 52 | --border: 216 34% 17%; 53 | --input: 216 34% 17%; 54 | 55 | --primary: 210 40% 98%; 56 | --primary-foreground: 222.2 47.4% 1.2%; 57 | 58 | --secondary: 222.2 47.4% 11.2%; 59 | --secondary-foreground: 210 40% 98%; 60 | 61 | --accent: 216 34% 17%; 62 | --accent-foreground: 210 40% 98%; 63 | 64 | --destructive: 0 63% 31%; 65 | --destructive-foreground: 210 40% 98%; 66 | 67 | --ring: 216 34% 17%; 68 | 69 | --radius: 0.5rem; 70 | } 71 | } 72 | 73 | @layer base { 74 | * { 75 | @apply border-border; 76 | } 77 | body { 78 | @apply bg-background text-foreground; 79 | font-feature-settings: "rlig" 1, "calt" 1; 80 | } 81 | } -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "underline-offset-4 hover:underline text-primary", 21 | }, 22 | size: { 23 | default: "h-10 py-2 px-4", 24 | sm: "h-9 px-3 rounded-md", 25 | lg: "h-11 px-8 rounded-md", 26 | }, 27 | }, 28 | defaultVariants: { 29 | variant: "default", 30 | size: "default", 31 | }, 32 | } 33 | ) 34 | 35 | export interface ButtonProps 36 | extends React.ButtonHTMLAttributes, 37 | VariantProps { 38 | asChild?: boolean 39 | } 40 | 41 | const Button = React.forwardRef( 42 | ({ className, variant, size, asChild = false, ...props }, ref) => { 43 | const Comp = asChild ? Slot : "button" 44 | return ( 45 | 50 | ) 51 | } 52 | ) 53 | Button.displayName = "Button" 54 | 55 | export { Button, buttonVariants } 56 | -------------------------------------------------------------------------------- /convex/_generated/dataModel.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated data model types. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.7.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import type { 13 | DataModelFromSchemaDefinition, 14 | DocumentByName, 15 | TableNamesInDataModel, 16 | SystemTableNames, 17 | } from "convex/server"; 18 | import type { GenericId } from "convex/values"; 19 | import schema from "../schema.js"; 20 | 21 | /** 22 | * The names of all of your Convex tables. 23 | */ 24 | export type TableNames = TableNamesInDataModel; 25 | 26 | /** 27 | * The type of a document stored in Convex. 28 | * 29 | * @typeParam TableName - A string literal type of the table name (like "users"). 30 | */ 31 | export type Doc = DocumentByName< 32 | DataModel, 33 | TableName 34 | >; 35 | 36 | /** 37 | * An identifier for a document in Convex. 38 | * 39 | * Convex documents are uniquely identified by their `Id`, which is accessible 40 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). 41 | * 42 | * Documents can be loaded using `db.get(id)` in query and mutation functions. 43 | * 44 | * IDs are just strings at runtime, but this type can be used to distinguish them from other 45 | * strings when type checking. 46 | * 47 | * @typeParam TableName - A string literal type of the table name (like "users"). 48 | */ 49 | export type Id = 50 | GenericId; 51 | 52 | /** 53 | * A type describing your Convex data model. 54 | * 55 | * This type includes information about what tables you have, the type of 56 | * documents stored in those tables, and the indexes defined on them. 57 | * 58 | * This type is used to parameterize methods like `queryGeneric` and 59 | * `mutationGeneric` to make them type-safe. 60 | */ 61 | export type DataModel = DataModelFromSchemaDefinition; 62 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | "./src/pages/**/*.{ts,tsx}", 6 | "./src/components/**/*.{ts,tsx}", 7 | "./src/app/**/*.{ts,tsx}", 8 | ], 9 | theme: { 10 | container: { 11 | center: true, 12 | padding: "2rem", 13 | screens: { 14 | "2xl": "1400px", 15 | }, 16 | }, 17 | extend: { 18 | colors: { 19 | border: "hsl(var(--border))", 20 | input: "hsl(var(--input))", 21 | ring: "hsl(var(--ring))", 22 | background: "hsl(var(--background))", 23 | foreground: "hsl(var(--foreground))", 24 | primary: { 25 | DEFAULT: "hsl(var(--primary))", 26 | foreground: "hsl(var(--primary-foreground))", 27 | }, 28 | secondary: { 29 | DEFAULT: "hsl(var(--secondary))", 30 | foreground: "hsl(var(--secondary-foreground))", 31 | }, 32 | destructive: { 33 | DEFAULT: "hsl(var(--destructive))", 34 | foreground: "hsl(var(--destructive-foreground))", 35 | }, 36 | muted: { 37 | DEFAULT: "hsl(var(--muted))", 38 | foreground: "hsl(var(--muted-foreground))", 39 | }, 40 | accent: { 41 | DEFAULT: "hsl(var(--accent))", 42 | foreground: "hsl(var(--accent-foreground))", 43 | }, 44 | popover: { 45 | DEFAULT: "hsl(var(--popover))", 46 | foreground: "hsl(var(--popover-foreground))", 47 | }, 48 | card: { 49 | DEFAULT: "hsl(var(--card))", 50 | foreground: "hsl(var(--card-foreground))", 51 | }, 52 | }, 53 | borderRadius: { 54 | lg: "var(--radius)", 55 | md: "calc(var(--radius) - 2px)", 56 | sm: "calc(var(--radius) - 4px)", 57 | }, 58 | keyframes: { 59 | "accordion-down": { 60 | from: { height: 0 }, 61 | to: { height: "var(--radix-accordion-content-height)" }, 62 | }, 63 | "accordion-up": { 64 | from: { height: "var(--radix-accordion-content-height)" }, 65 | to: { height: 0 }, 66 | }, 67 | }, 68 | animation: { 69 | "accordion-down": "accordion-down 0.2s ease-out", 70 | "accordion-up": "accordion-up 0.2s ease-out", 71 | }, 72 | }, 73 | }, 74 | plugins: [require("tailwindcss-animate")], 75 | }; 76 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { api } from "../../convex/_generated/api"; 3 | import { useMutation, useQuery } from "convex/react"; 4 | import { useForm } from "react-hook-form"; 5 | import { ReactSketchCanvas, ReactSketchCanvasRef } from "react-sketch-canvas"; 6 | import { useRef } from "react"; 7 | import { Input } from "@/components/ui/input"; 8 | import { Label } from "@/components/ui/label"; 9 | import { Button } from "@/components/ui/button"; 10 | 11 | export default function Home() { 12 | const saveSketchMutation = useMutation(api.sketches.saveSketch); 13 | const sketchesQuery = useQuery(api.sketches.getSketches); 14 | 15 | const { 16 | register, 17 | handleSubmit, 18 | formState: { errors }, 19 | } = useForm<{ 20 | prompt: string; 21 | }>(); 22 | 23 | const canvasRef = useRef(null); 24 | 25 | const sortedSketches = (sketchesQuery ?? []).sort((a, b) => { 26 | return b._creationTime - a._creationTime; 27 | }); 28 | 29 | return ( 30 |
31 |
32 |
{ 35 | if (!canvasRef.current) return; 36 | const image = await canvasRef.current.exportImage("jpeg"); 37 | await saveSketchMutation({ ...formData, image }); 38 | })} 39 | > 40 | 41 | 42 | {errors.prompt && This field is required} 43 | 44 | 45 | 51 | 52 | 61 | 62 | 63 | 64 | 65 |
66 |

Recent Sketches

67 |
68 | {sortedSketches.map((sketch) => ( 69 | 75 | ))} 76 |
77 |
78 |
79 |
80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /convex/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your Convex functions directory! 2 | 3 | Write your Convex functions here. See 4 | https://docs.convex.dev/using/writing-convex-functions for more. 5 | 6 | A query function that takes two arguments looks like: 7 | 8 | ```ts 9 | // functions.js 10 | import { query } from "./_generated/server"; 11 | import { v } from "convex/values"; 12 | 13 | export const myQueryFunction = query({ 14 | // Validators for arguments. 15 | args: { 16 | first: v.number(), 17 | second: v.string(), 18 | }, 19 | 20 | // Function implementation. 21 | hander: async (ctx, args) => { 22 | // Read the database as many times as you need here. 23 | // See https://docs.convex.dev/database/reading-data. 24 | const documents = await ctx.db.query("tablename").collect(); 25 | 26 | // Arguments passed from the client are properties of the args object. 27 | console.log(args.first, args.second); 28 | 29 | // Write arbitrary JavaScript here: filter, aggregate, build derived data, 30 | // remove non-public properties, or create new objects. 31 | return documents; 32 | }, 33 | }); 34 | ``` 35 | 36 | Using this query function in a React component looks like: 37 | 38 | ```ts 39 | const data = useQuery(api.functions.myQueryFunction, { 40 | first: 10, 41 | second: "hello", 42 | }); 43 | ``` 44 | 45 | A mutation function looks like: 46 | 47 | ```ts 48 | // functions.js 49 | import { mutation } from "./_generated/server"; 50 | import { v } from "convex/values"; 51 | 52 | export const myMutationFunction = mutation({ 53 | // Validators for arguments. 54 | args: { 55 | first: v.string(), 56 | second: v.string(), 57 | }, 58 | 59 | // Function implementation. 60 | hander: async (ctx, args) => { 61 | // Insert or modify documents in the database here. 62 | // Mutations can also read from the database like queries. 63 | // See https://docs.convex.dev/database/writing-data. 64 | const message = { body: args.first, author: args.second }; 65 | const id = await ctx.db.insert("messages", message); 66 | 67 | // Optionally, return a value from your mutation. 68 | return await ctx.db.get(id); 69 | }, 70 | }); 71 | ``` 72 | 73 | Using this mutation function in a React component looks like: 74 | 75 | ```ts 76 | const mutation = useMutation(api.functions.myMutationFunction); 77 | function handleButtonPress() { 78 | // fire and forget, the most common way to use mutations 79 | mutation({ first: "Hello!", second: "me" }); 80 | // OR 81 | // use the result once the mutation has completed 82 | mutation({ first: "Hello!", second: "me" }).then((result) => 83 | console.log(result) 84 | ); 85 | } 86 | ``` 87 | 88 | Use the Convex CLI to push your functions to a deployment. See everything 89 | the Convex CLI can do by running `npx convex -h` in your project root 90 | directory. To learn more, launch the docs with `npx convex docs`. 91 | -------------------------------------------------------------------------------- /convex/_generated/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated utilities for implementing server-side Convex query and mutation functions. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.7.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { 13 | actionGeneric, 14 | httpActionGeneric, 15 | queryGeneric, 16 | mutationGeneric, 17 | internalActionGeneric, 18 | internalMutationGeneric, 19 | internalQueryGeneric, 20 | } from "convex/server"; 21 | 22 | /** 23 | * Define a query in this Convex app's public API. 24 | * 25 | * This function will be allowed to read your Convex database and will be accessible from the client. 26 | * 27 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 28 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 29 | */ 30 | export const query = queryGeneric; 31 | 32 | /** 33 | * Define a query that is only accessible from other Convex functions (but not from the client). 34 | * 35 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 36 | * 37 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 38 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 39 | */ 40 | export const internalQuery = internalQueryGeneric; 41 | 42 | /** 43 | * Define a mutation in this Convex app's public API. 44 | * 45 | * This function will be allowed to modify your Convex database and will be accessible from the client. 46 | * 47 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 48 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 49 | */ 50 | export const mutation = mutationGeneric; 51 | 52 | /** 53 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 54 | * 55 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 56 | * 57 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 58 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 59 | */ 60 | export const internalMutation = internalMutationGeneric; 61 | 62 | /** 63 | * Define an action in this Convex app's public API. 64 | * 65 | * An action is a function which can execute any JavaScript code, including non-deterministic 66 | * code and code with side-effects, like calling third-party services. 67 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 68 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 69 | * 70 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 71 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 72 | */ 73 | export const action = actionGeneric; 74 | 75 | /** 76 | * Define an action that is only accessible from other Convex functions (but not from the client). 77 | * 78 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 79 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 80 | */ 81 | export const internalAction = internalActionGeneric; 82 | 83 | /** 84 | * Define a Convex HTTP action. 85 | * 86 | * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object 87 | * as its second. 88 | * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`. 89 | */ 90 | export const httpAction = httpActionGeneric; 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Use Convex with Replicate to create images from doodles 2 | 3 | Stack: 4 | - [Convex](https://convex.dev) 5 | - [Replicate](https://replicate.com/) 6 | - [Next.js](https://nextjs.org/) 7 | - [Tailwind CSS](https://tailwindcss.com/) 8 | 9 | ## Getting Started 10 | 11 | ```bash 12 | npm install 13 | npm run dev 14 | ``` 15 | This will create a (free) Convex account if you don't have one. 16 | Under the hood, run dev runs both Next(locally) and Convex (continuous deploy). 17 | 18 | Get a [Replicate API key](https://replicate.com/account/api-tokens) 19 | (free to try) and add it to your 20 | [environment variables](https://docs.convex.dev/production/environment-variables) 21 | in the [Convex Dashboard](https://dashboard.convex.dev)(`npx convex dashboard`). 22 | 23 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 24 | 25 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 26 | 27 | Editing files in `convex/` will auto-update your "Dev" deployment in Convex. 28 | 29 | 30 | ## Deploy the frontend on Vercel or Netlify 31 | 32 | The easiest way to deploy your Next.js frontend is to use the [Vercel Platform](https://vercel.com/new?filter=next.js) from the creators of Next.js, or [Netlify](https://netlify.com). 33 | 34 | See the [Convex docs on production deployments](https://docs.convex.dev/production/hosting/) 35 | for details on deploying a production backend. 36 | 37 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 38 | 39 | ## What is Convex? 40 | 41 | [Convex](https://convex.dev) is a hosted backend platform with a 42 | built-in database that lets you write your 43 | [database schema](https://docs.convex.dev/database/schemas) and 44 | [server functions](https://docs.convex.dev/functions) in 45 | [TypeScript](https://docs.convex.dev/typescript). Server-side database 46 | [queries](https://docs.convex.dev/functions/query-functions) automatically 47 | [cache](https://docs.convex.dev/functions/query-functions#caching--reactivity) and 48 | [subscribe](https://docs.convex.dev/client/react#reactivity) to data, powering a 49 | [realtime `useQuery` hook](https://docs.convex.dev/client/react#fetching-data) in our 50 | [React client](https://docs.convex.dev/client/react). There are also 51 | [Python](https://docs.convex.dev/client/python), 52 | [Rust](https://docs.convex.dev/client/rust), 53 | [ReactNative](https://docs.convex.dev/client/react-native), and 54 | [Node](https://docs.convex.dev/client/javascript) clients, as well as a straightforward 55 | [HTTP API](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L40). 56 | 57 | The database support 58 | [NoSQL-style documents](https://docs.convex.dev/database/document-storage) with 59 | [relationships](https://docs.convex.dev/database/document-ids) and 60 | [custom indexes](https://docs.convex.dev/database/indexes/) 61 | (including on fields in nested objects). 62 | 63 | The 64 | [`query`](https://docs.convex.dev/functions/query-functions) and 65 | [`mutation`](https://docs.convex.dev/functions/mutation-functions) server functions have transactional, 66 | low latency access to the database and leverage our 67 | [`v8` runtime](https://docs.convex.dev/functions/runtimes) with 68 | [determinism guardrails](https://docs.convex.dev/functions/runtimes#using-randomness-and-time-in-queries-and-mutations) 69 | to provide the strongest ACID guarantees on the market: 70 | immediate consistency, 71 | serializable isolation, and 72 | automatic conflict resolution via 73 | [optimistic multi-version concurrency control](https://docs.convex.dev/database/advanced/occ) (OCC / MVCC). 74 | 75 | The [`action` server functions](https://docs.convex.dev/functions/actions) have 76 | access to external APIs and enable other side-effects and non-determinism in 77 | either our 78 | [optimized `v8` runtime](https://docs.convex.dev/functions/runtimes) or a more 79 | [flexible `node` runtime](https://docs.convex.dev/functions/runtimes#nodejs-runtime). 80 | 81 | Functions can run in the background via 82 | [scheduling](https://docs.convex.dev/scheduling/scheduled-functions) and 83 | [cron jobs](https://docs.convex.dev/scheduling/cron-jobs). 84 | 85 | Development is cloud-first, with 86 | [hot reloads for server function](https://docs.convex.dev/cli#run-the-convex-dev-server) editing via the 87 | [CLI](https://docs.convex.dev/cli). There is a 88 | [dashbord UI](https://docs.convex.dev/dashboard) to 89 | [browse and edit data](https://docs.convex.dev/dashboard/deployments/data), 90 | [edit environment variables](https://docs.convex.dev/production/environment-variables), 91 | [view logs](https://docs.convex.dev/dashboard/deployments/logs), 92 | [run server functions](https://docs.convex.dev/dashboard/deployments/functions), and more. 93 | 94 | There are built-in features for 95 | [reactive pagination](https://docs.convex.dev/database/pagination), 96 | [file storage](https://docs.convex.dev/file-storage), 97 | [reactive search](https://docs.convex.dev/text-search), 98 | [https endpoints](https://docs.convex.dev/functions/http-actions) (for webhooks), 99 | [streaming import/export](https://docs.convex.dev/database/import-export/), and 100 | [runtime data validation](https://docs.convex.dev/database/schemas#validators) for 101 | [function arguments](https://docs.convex.dev/functions/args-validation) and 102 | [database data](https://docs.convex.dev/database/schemas#schema-validation). 103 | 104 | Everything scales automatically, and it’s [free to start](https://www.convex.dev/plans). 105 | -------------------------------------------------------------------------------- /convex/_generated/server.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated utilities for implementing server-side Convex query and mutation functions. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.7.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { 13 | ActionBuilder, 14 | HttpActionBuilder, 15 | MutationBuilder, 16 | QueryBuilder, 17 | GenericActionCtx, 18 | GenericMutationCtx, 19 | GenericQueryCtx, 20 | GenericDatabaseReader, 21 | GenericDatabaseWriter, 22 | } from "convex/server"; 23 | import type { DataModel } from "./dataModel.js"; 24 | 25 | /** 26 | * Define a query in this Convex app's public API. 27 | * 28 | * This function will be allowed to read your Convex database and will be accessible from the client. 29 | * 30 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 31 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 32 | */ 33 | export declare const query: QueryBuilder; 34 | 35 | /** 36 | * Define a query that is only accessible from other Convex functions (but not from the client). 37 | * 38 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 39 | * 40 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 41 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 42 | */ 43 | export declare const internalQuery: QueryBuilder; 44 | 45 | /** 46 | * Define a mutation in this Convex app's public API. 47 | * 48 | * This function will be allowed to modify your Convex database and will be accessible from the client. 49 | * 50 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 51 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 52 | */ 53 | export declare const mutation: MutationBuilder; 54 | 55 | /** 56 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 57 | * 58 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 59 | * 60 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 61 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 62 | */ 63 | export declare const internalMutation: MutationBuilder; 64 | 65 | /** 66 | * Define an action in this Convex app's public API. 67 | * 68 | * An action is a function which can execute any JavaScript code, including non-deterministic 69 | * code and code with side-effects, like calling third-party services. 70 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 71 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 72 | * 73 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 74 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 75 | */ 76 | export declare const action: ActionBuilder; 77 | 78 | /** 79 | * Define an action that is only accessible from other Convex functions (but not from the client). 80 | * 81 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 82 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 83 | */ 84 | export declare const internalAction: ActionBuilder; 85 | 86 | /** 87 | * Define an HTTP action. 88 | * 89 | * This function will be used to respond to HTTP requests received by a Convex 90 | * deployment if the requests matches the path and method where this action 91 | * is routed. Be sure to route your action in `convex/http.js`. 92 | * 93 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 94 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. 95 | */ 96 | export declare const httpAction: HttpActionBuilder; 97 | 98 | /** 99 | * A set of services for use within Convex query functions. 100 | * 101 | * The query context is passed as the first argument to any Convex query 102 | * function run on the server. 103 | * 104 | * This differs from the {@link MutationCtx} because all of the services are 105 | * read-only. 106 | */ 107 | export type QueryCtx = GenericQueryCtx; 108 | 109 | /** 110 | * A set of services for use within Convex mutation functions. 111 | * 112 | * The mutation context is passed as the first argument to any Convex mutation 113 | * function run on the server. 114 | */ 115 | export type MutationCtx = GenericMutationCtx; 116 | 117 | /** 118 | * A set of services for use within Convex action functions. 119 | * 120 | * The action context is passed as the first argument to any Convex action 121 | * function run on the server. 122 | */ 123 | export type ActionCtx = GenericActionCtx; 124 | 125 | /** 126 | * An interface to read from the database within Convex query functions. 127 | * 128 | * The two entry points are {@link DatabaseReader.get}, which fetches a single 129 | * document by its {@link Id}, or {@link DatabaseReader.query}, which starts 130 | * building a query. 131 | */ 132 | export type DatabaseReader = GenericDatabaseReader; 133 | 134 | /** 135 | * An interface to read from and write to the database within Convex mutation 136 | * functions. 137 | * 138 | * Convex guarantees that all writes within a single mutation are 139 | * executed atomically, so you never have to worry about partial writes leaving 140 | * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control) 141 | * for the guarantees Convex provides your functions. 142 | */ 143 | export type DatabaseWriter = GenericDatabaseWriter; 144 | --------------------------------------------------------------------------------