├── .env.example ├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── README.md ├── components.json ├── convex ├── README.md ├── _generated │ ├── api.d.ts │ ├── api.js │ ├── dataModel.d.ts │ ├── server.d.ts │ └── server.js ├── generate.ts ├── rooms.ts ├── schema.ts └── tsconfig.json ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── icon.png ├── living-room-image.png ├── og.png └── themes │ ├── arabian.png │ ├── classic.png │ ├── coastal.png │ ├── industrial.png │ ├── minimalist.png │ ├── modern.png │ ├── professional.png │ ├── realistic.png │ └── vintage.png ├── src ├── app │ ├── explore │ │ └── page.tsx │ ├── favicon.ico │ ├── generate │ │ └── page.tsx │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── footer.tsx │ ├── generate-panel.tsx │ ├── header.tsx │ ├── mode-toggle.tsx │ ├── providers.tsx │ ├── room-preview.tsx │ ├── room.tsx │ └── ui │ │ ├── button.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── radio-group.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ └── skeleton.tsx ├── config │ └── site.ts ├── env.ts └── lib │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | CONVEX_DEPLOYMENT="" 2 | CONVEX_DEPLOY_KEY="" 3 | 4 | NEXT_PUBLIC_CONVEX_URL="" 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "plugins": ["prettier-plugin-tailwindcss"] 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Room Generator](https://roomgenerator.vercel.app) - Create your unique rooms using AI 2 | 3 | This is a simple room generator application powered by AI. I am using [Replicate](https://replicate.com) for the API and stability-ai/sdxl model. This application uses [Convex](https://convex.dev) as a backend service and I built this as a fun project. 4 | 5 | [![Room Generator Demo](https://img.youtube.com/vi/h-8QpqYG6EI/maxresdefault.jpg)](https://www.youtube.com/watch?v=h-8QpqYG6EI) 6 | 7 | ## Running Locally 8 | 9 | ### Cloning the repository 10 | 11 | ```bash 12 | git clone https://github.com/musabdev/roomgenerator 13 | ``` 14 | 15 | ### Setting up environment variables (`.env`) 16 | 17 | ``` 18 | CONVEX_DEPLOYMENT="" # Required for development 19 | CONVEX_DEPLOY_KEY="" # Required for production 20 | 21 | NEXT_PUBLIC_CONVEX_URL="" 22 | ``` 23 | 24 | ### Installing the dependencies 25 | 26 | ``` 27 | npm install 28 | ``` 29 | 30 | ### Setting up Convex 31 | 32 | You have to add Replicate API key as an environment variable in the [Convex](https://convex.dev) dashboard. You can check their guide at [https://docs.convex.dev/production/environment-variables](https://docs.convex.dev/production/environment-variables) 33 | 34 | ``` 35 | REPLICATE_API_TOKEN="" 36 | ``` 37 | 38 | ## Running the project 39 | 40 | First, run the development server for Next.js: 41 | 42 | ```bash 43 | npm run dev 44 | ``` 45 | 46 | Then, run the development server for Convex: 47 | 48 | ```bash 49 | npx run convex 50 | ``` 51 | 52 | ### Changing configuration 53 | 54 | You can change the configuration in `src/config/site.ts` file. The 'enable' feature is the most important option that ensures whether the app will work or not. 55 | 56 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. Finally, you have successfully set up the project on your local machine. 57 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "~/components", 14 | "utils": "~/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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/api.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.2.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"; 18 | import type * as rooms from "../rooms"; 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 | rooms: typeof rooms; 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 | -------------------------------------------------------------------------------- /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.2.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 | -------------------------------------------------------------------------------- /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.2.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import type { DataModelFromSchemaDefinition } from "convex/server"; 13 | import type { DocumentByName, TableNamesInDataModel } from "convex/server"; 14 | import type { GenericId } from "convex/values"; 15 | import schema from "../schema"; 16 | 17 | /** 18 | * The names of all of your Convex tables. 19 | */ 20 | export type TableNames = TableNamesInDataModel; 21 | 22 | /** 23 | * The type of a document stored in Convex. 24 | * 25 | * @typeParam TableName - A string literal type of the table name (like "users"). 26 | */ 27 | export type Doc = DocumentByName< 28 | DataModel, 29 | TableName 30 | >; 31 | 32 | /** 33 | * An identifier for a document in Convex. 34 | * 35 | * Convex documents are uniquely identified by their `Id`, which is accessible 36 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). 37 | * 38 | * Documents can be loaded using `db.get(id)` in query and mutation functions. 39 | * 40 | * IDs are just strings at runtime, but this type can be used to distinguish them from other 41 | * strings when type checking. 42 | * 43 | * @typeParam TableName - A string literal type of the table name (like "users"). 44 | */ 45 | export type Id = GenericId; 46 | 47 | /** 48 | * A type describing your Convex data model. 49 | * 50 | * This type includes information about what tables you have, the type of 51 | * documents stored in those tables, and the indexes defined on them. 52 | * 53 | * This type is used to parameterize methods like `queryGeneric` and 54 | * `mutationGeneric` to make them type-safe. 55 | */ 56 | export type DataModel = DataModelFromSchemaDefinition; 57 | -------------------------------------------------------------------------------- /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.2.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 | -------------------------------------------------------------------------------- /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.2.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 | -------------------------------------------------------------------------------- /convex/generate.ts: -------------------------------------------------------------------------------- 1 | 'use node' 2 | 3 | import Replicate from 'replicate' 4 | import { internal } from './_generated/api' 5 | import { Id } from './_generated/dataModel' 6 | import { internalAction } from './_generated/server' 7 | 8 | export const generate = internalAction( 9 | async ( 10 | { runMutation }, 11 | { prompt, roomId }: { roomId: Id<'rooms'>; prompt: string }, 12 | ) => { 13 | if (!process.env.REPLICATE_API_TOKEN) { 14 | throw new Error( 15 | 'Add REPLICATE_API_TOKEN to your environment variables: ' + 16 | 'https://docs.convex.dev/production/environment-variables', 17 | ) 18 | } 19 | const replicate = new Replicate({ 20 | auth: process.env.REPLICATE_API_TOKEN, 21 | }) 22 | 23 | const output = (await replicate.run( 24 | 'stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f', 25 | { 26 | input: { 27 | prompt, 28 | negative_prompt: 29 | 'longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality', 30 | }, 31 | }, 32 | )) as string[] 33 | 34 | await runMutation(internal.rooms.updateRoomResult, { 35 | roomId, 36 | image: output[0], 37 | }) 38 | }, 39 | ) 40 | -------------------------------------------------------------------------------- /convex/rooms.ts: -------------------------------------------------------------------------------- 1 | import { paginationOptsValidator } from 'convex/server' 2 | import { v } from 'convex/values' 3 | import { internal } from './_generated/api' 4 | import { Id } from './_generated/dataModel' 5 | import { internalMutation, mutation, query } from './_generated/server' 6 | 7 | export const getRooms = query({ 8 | args: { 9 | paginationOpts: paginationOptsValidator, 10 | query: v.optional(v.string()), 11 | }, 12 | handler: async ({ db }, { paginationOpts, query }) => { 13 | if (query && query !== '') { 14 | return await db 15 | .query('rooms') 16 | .withSearchIndex('search_prompt', (q) => 17 | q.search('displayPrompt', query), 18 | ) 19 | .paginate(paginationOpts) 20 | } else { 21 | return await db 22 | .query('rooms') 23 | .filter((q) => q.neq(q.field('image'), undefined)) 24 | .order('desc') 25 | .paginate(paginationOpts) 26 | } 27 | }, 28 | }) 29 | 30 | export const getRoom = query( 31 | async ({ db }, { roomId }: { roomId: Id<'rooms'> }) => { 32 | const room = await db.get(roomId) 33 | 34 | return room 35 | }, 36 | ) 37 | 38 | export const createRoom = mutation({ 39 | args: { 40 | room: v.string(), 41 | theme: v.string(), 42 | color: v.optional(v.string()), 43 | prompt: v.optional(v.string()), 44 | }, 45 | handler: async ({ db, scheduler }, args) => { 46 | let displayPrompt = `a ${args.theme} ${args.room}${ 47 | args.color ? ` in ${args.color} color` : '' 48 | }${args.prompt ? `, ${args.prompt}` : ''}`.toLowerCase() 49 | 50 | const room = await db.insert('rooms', { ...args, displayPrompt }) 51 | 52 | await scheduler.runAfter(0, internal.generate.generate, { 53 | roomId: room, 54 | prompt: `${ 55 | args.room === 'Programming Room' 56 | ? `a ${args.theme} programming room, a desk and computer, a bed, ${args.color} color` 57 | : displayPrompt 58 | }, 4k, unreal engine`, 59 | }) 60 | 61 | return room 62 | }, 63 | }) 64 | 65 | export const updateRoomResult = internalMutation({ 66 | args: { 67 | roomId: v.id('rooms'), 68 | image: v.string(), 69 | }, 70 | handler: async ({ db }, { roomId, image }) => { 71 | const room = await db.patch(roomId, { 72 | image, 73 | }) 74 | 75 | return room 76 | }, 77 | }) 78 | -------------------------------------------------------------------------------- /convex/schema.ts: -------------------------------------------------------------------------------- 1 | import { defineSchema, defineTable } from 'convex/server' 2 | import { v } from 'convex/values' 3 | 4 | export default defineSchema({ 5 | rooms: defineTable({ 6 | room: v.string(), 7 | theme: v.string(), 8 | color: v.optional(v.string()), 9 | prompt: v.optional(v.string()), 10 | displayPrompt: v.string(), 11 | image: v.optional(v.string()), 12 | }).searchIndex('search_prompt', { 13 | searchField: 'displayPrompt', 14 | }), 15 | }) 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: 'https', 7 | hostname: '**', 8 | }, 9 | ], 10 | }, 11 | } 12 | 13 | module.exports = nextConfig 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roomgenerator", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build && convex deploy", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@hookform/resolvers": "^3.3.1", 13 | "@radix-ui/react-dropdown-menu": "^2.0.5", 14 | "@radix-ui/react-label": "^2.0.2", 15 | "@radix-ui/react-radio-group": "^1.1.3", 16 | "@radix-ui/react-select": "^1.2.2", 17 | "@radix-ui/react-separator": "^1.0.3", 18 | "@radix-ui/react-slot": "^1.0.2", 19 | "@radix-ui/react-toggle": "^1.0.3", 20 | "@t3-oss/env-nextjs": "^0.6.1", 21 | "@types/node": "20.6.0", 22 | "@types/react": "18.2.21", 23 | "@types/react-dom": "18.2.7", 24 | "autoprefixer": "10.4.15", 25 | "class-variance-authority": "^0.7.0", 26 | "clsx": "^2.0.0", 27 | "convex": "^1.2.1", 28 | "eslint": "8.49.0", 29 | "eslint-config-next": "13.5.4", 30 | "lodash.debounce": "^4.0.8", 31 | "lucide-react": "^0.274.0", 32 | "next": "13.5.4", 33 | "next-themes": "^0.2.1", 34 | "postcss": "8.4.29", 35 | "react": "18.2.0", 36 | "react-dom": "18.2.0", 37 | "react-hook-form": "^7.46.1", 38 | "react-infinite-scroll-component": "^6.1.0", 39 | "replicate": "^0.17.0", 40 | "tailwind-merge": "^1.14.0", 41 | "tailwindcss": "3.3.3", 42 | "tailwindcss-animate": "^1.0.7", 43 | "typescript": "5.2.2", 44 | "zod": "^3.22.2" 45 | }, 46 | "devDependencies": { 47 | "@types/lodash.debounce": "^4.0.7", 48 | "prettier": "^3.0.3", 49 | "prettier-plugin-tailwindcss": "^0.5.4" 50 | } 51 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/icon.png -------------------------------------------------------------------------------- /public/living-room-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/living-room-image.png -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/og.png -------------------------------------------------------------------------------- /public/themes/arabian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/arabian.png -------------------------------------------------------------------------------- /public/themes/classic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/classic.png -------------------------------------------------------------------------------- /public/themes/coastal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/coastal.png -------------------------------------------------------------------------------- /public/themes/industrial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/industrial.png -------------------------------------------------------------------------------- /public/themes/minimalist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/minimalist.png -------------------------------------------------------------------------------- /public/themes/modern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/modern.png -------------------------------------------------------------------------------- /public/themes/professional.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/professional.png -------------------------------------------------------------------------------- /public/themes/realistic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/realistic.png -------------------------------------------------------------------------------- /public/themes/vintage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/public/themes/vintage.png -------------------------------------------------------------------------------- /src/app/explore/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { usePaginatedQuery } from 'convex/react' 4 | import InfiniteScroll from 'react-infinite-scroll-component' 5 | 6 | import { useState } from 'react' 7 | import { Room } from '~/components/room' 8 | import { Input } from '~/components/ui/input' 9 | import { Skeleton } from '~/components/ui/skeleton' 10 | import { api } from '../../../convex/_generated/api' 11 | 12 | export default function Explore() { 13 | const [searchText, setSearchText] = useState('') 14 | const roomsQuery = 15 | usePaginatedQuery( 16 | api.rooms.getRooms, 17 | { query: searchText }, 18 | { initialNumItems: 18 }, 19 | ) || [] 20 | 21 | return ( 22 |
23 |
24 |
25 | setSearchText(e.target.value)} 29 | /> 30 |
31 | 32 | {roomsQuery?.results.length > 0 ? ( 33 | roomsQuery.loadMore(9)} 37 | hasMore={roomsQuery.status === 'CanLoadMore'} 38 | loader={} 39 | > 40 | {roomsQuery.results 41 | ?.filter((item) => item.image) 42 | .map((item) => )} 43 | 44 | ) : ( 45 |
46 | 47 | 48 | 49 |
50 | )} 51 |
52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusabDev/roomgenerator/653da5370f3b636d2a4b2f84f53a8b9da82050aa/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/generate/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useState } from 'react' 4 | 5 | import { GeneratePanel } from '~/components/generate-panel' 6 | import { RoomPreview } from '~/components/room-preview' 7 | import { Id } from '../../../convex/_generated/dataModel' 8 | 9 | export default function Generate() { 10 | const [roomIds, setRoomIds] = useState[]>([]) 11 | 12 | return ( 13 |
14 |
15 |
16 | 17 |
18 |
19 | {roomIds && 20 | roomIds.map((roomId) => ( 21 | 22 | ))} 23 |
24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /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: 230 11.54% 10.2%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 230 11.54% 10.2%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 230 11.54% 10.2%; 13 | --primary: 221.2 83.2% 53.3%; 14 | --primary-foreground: 0 0% 100%; 15 | --secondary: 210 40% 96.1%; 16 | --secondary-foreground: 222.2 47.4% 11.2%; 17 | --muted: 210 40% 96.1%; 18 | --muted-foreground: 215.4 16.3% 46.9%; 19 | --accent: 210 40% 96.1%; 20 | --accent-foreground: 222.2 47.4% 11.2%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 100%; 23 | --border: 214.3 31.8% 91.4%; 24 | --input: 214.3 31.8% 91.4%; 25 | --ring: 221.2 83.2% 53.3%; 26 | --radius: 0.75rem; 27 | } 28 | 29 | .dark { 30 | --background: 230 11.54% 10.2%; 31 | --foreground: 0 0% 100%; 32 | --card: 230 11.54% 10.2%; 33 | --card-foreground: 0 0% 100%; 34 | --popover: 230 11.54% 10.2%; 35 | --popover-foreground: 0 0% 100%; 36 | --primary: 221.2 83.2% 53.3%; 37 | --primary-foreground: 0 0% 100%; 38 | --secondary: 220 11.51% 18.56%; 39 | --secondary-foreground: 0 0% 100%; 40 | --muted: 220 11.51% 18.56%; 41 | --muted-foreground: 215 20.2% 65.1%; 42 | --accent: 220 11.51% 18.56%; 43 | --accent-foreground: 0 0% 100%; 44 | --destructive: 0 84.2% 60.2%; 45 | --destructive-foreground: 0 0% 100%; 46 | --border: 220 11.51% 18.56%; 47 | --input: 220 11.51% 18.56%; 48 | --ring: 224.3 76.3% 48%; 49 | } 50 | } 51 | 52 | @layer base { 53 | * { 54 | @apply border-border; 55 | } 56 | body { 57 | @apply bg-background text-foreground antialiased; 58 | } 59 | } 60 | 61 | @media (max-width: 640px) { 62 | .container { 63 | @apply px-4; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import { Footer } from '~/components/footer' 4 | import { Header } from '~/components/header' 5 | import { Providers } from '~/components/providers' 6 | import './globals.css' 7 | 8 | const inter = Inter({ subsets: ['latin'] }) 9 | 10 | export const metadata: Metadata = { 11 | title: 'Room Generator', 12 | description: 'Instant room creation powered by AI', 13 | icons: { 14 | icon: '/favicon.ico', 15 | }, 16 | openGraph: { 17 | images: ['https://roomgenerator.vercel.app/og.png'], 18 | title: 'Room Generator', 19 | description: 'Instant room creation powered by AI', 20 | url: 'https://roomgenerator.vercel.app', 21 | siteName: 'Room Generator', 22 | locale: 'en_US', 23 | type: 'website', 24 | }, 25 | twitter: { 26 | card: 'summary_large_image', 27 | images: ['https://roomgenerator.vercel.app/og.png'], 28 | title: 'Room Generator', 29 | description: 'Instant room creation powered by AI', 30 | }, 31 | } 32 | 33 | export default function RootLayout({ 34 | children, 35 | }: { 36 | children: React.ReactNode 37 | }) { 38 | return ( 39 | 40 | 41 | 42 |
43 |
{children}
44 |