chore: formatting the code of recent changes

This commit is contained in:
Akhileshrangani4 2024-11-29 13:05:35 -05:00
parent fbc56dc7fd
commit ba7a1dcc2c
13 changed files with 956 additions and 911 deletions

View File

@ -6,18 +6,18 @@ import { z } from "zod"
import { and, eq, sql } from "drizzle-orm" import { and, eq, sql } from "drizzle-orm"
import * as schema from "./schema" import * as schema from "./schema"
import { import {
Sandbox, Sandbox,
sandbox, sandbox,
sandboxLikes, sandboxLikes,
user, user,
usersToSandboxes, usersToSandboxes,
} from "./schema" } from "./schema"
export interface Env { export interface Env {
DB: D1Database DB: D1Database
STORAGE: any STORAGE: any
KEY: string KEY: string
STORAGE_WORKER_URL: string STORAGE_WORKER_URL: string
} }
// https://github.com/drizzle-team/drizzle-orm/tree/main/examples/cloudflare-d1 // https://github.com/drizzle-team/drizzle-orm/tree/main/examples/cloudflare-d1
@ -25,469 +25,488 @@ export interface Env {
// npm run generate // npm run generate
// npx wrangler d1 execute d1-sandbox --local --file=./drizzle/<FILE> // npx wrangler d1 execute d1-sandbox --local --file=./drizzle/<FILE>
interface SandboxWithLiked extends Sandbox { interface SandboxWithLiked extends Sandbox {
liked: boolean liked: boolean
} }
interface UserResponse extends Omit<schema.User, "sandbox"> { interface UserResponse extends Omit<schema.User, "sandbox"> {
sandbox: SandboxWithLiked[] sandbox: SandboxWithLiked[]
} }
export default { export default {
async fetch( async fetch(
request: Request, request: Request,
env: Env, env: Env,
ctx: ExecutionContext ctx: ExecutionContext
): Promise<Response> { ): Promise<Response> {
const success = new Response("Success", { status: 200 }) const success = new Response("Success", { status: 200 })
const invalidRequest = new Response("Invalid Request", { status: 400 }) const invalidRequest = new Response("Invalid Request", { status: 400 })
const notFound = new Response("Not Found", { status: 404 }) const notFound = new Response("Not Found", { status: 404 })
const methodNotAllowed = new Response("Method Not Allowed", { status: 405 }) const methodNotAllowed = new Response("Method Not Allowed", { status: 405 })
const url = new URL(request.url) const url = new URL(request.url)
const path = url.pathname const path = url.pathname
const method = request.method const method = request.method
if (request.headers.get("Authorization") !== env.KEY) { if (request.headers.get("Authorization") !== env.KEY) {
return new Response("Unauthorized", { status: 401 }) return new Response("Unauthorized", { status: 401 })
} }
const db = drizzle(env.DB, { schema }) const db = drizzle(env.DB, { schema })
if (path === "/api/sandbox") { if (path === "/api/sandbox") {
if (method === "GET") { if (method === "GET") {
const params = url.searchParams const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string const id = params.get("id") as string
const res = await db.query.sandbox.findFirst({ const res = await db.query.sandbox.findFirst({
where: (sandbox, { eq }) => eq(sandbox.id, id), where: (sandbox, { eq }) => eq(sandbox.id, id),
with: { with: {
usersToSandboxes: true, usersToSandboxes: true,
}, },
}) })
return json(res ?? {}) return json(res ?? {})
} else { } else {
const res = await db.select().from(sandbox).all() const res = await db.select().from(sandbox).all()
return json(res ?? {}) return json(res ?? {})
} }
} else if (method === "DELETE") { } else if (method === "DELETE") {
const params = url.searchParams const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string const id = params.get("id") as string
await db await db
.delete(usersToSandboxes) .delete(usersToSandboxes)
.where(eq(usersToSandboxes.sandboxId, id)) .where(eq(usersToSandboxes.sandboxId, id))
await db.delete(sandbox).where(eq(sandbox.id, id)) await db.delete(sandbox).where(eq(sandbox.id, id))
const deleteStorageRequest = new Request( const deleteStorageRequest = new Request(
`${env.STORAGE_WORKER_URL}/api/project`, `${env.STORAGE_WORKER_URL}/api/project`,
{ {
method: "DELETE", method: "DELETE",
body: JSON.stringify({ sandboxId: id }), body: JSON.stringify({ sandboxId: id }),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${env.KEY}`, Authorization: `${env.KEY}`,
}, },
} }
) )
await env.STORAGE.fetch(deleteStorageRequest) await env.STORAGE.fetch(deleteStorageRequest)
return success return success
} else { } else {
return invalidRequest return invalidRequest
} }
} else if (method === "POST") { } else if (method === "POST") {
const postSchema = z.object({ const postSchema = z.object({
id: z.string(), id: z.string(),
name: z.string().optional(), name: z.string().optional(),
visibility: z.enum(["public", "private"]).optional(), visibility: z.enum(["public", "private"]).optional(),
}) })
const body = await request.json() const body = await request.json()
const { id, name, visibility } = postSchema.parse(body) const { id, name, visibility } = postSchema.parse(body)
const sb = await db const sb = await db
.update(sandbox) .update(sandbox)
.set({ name, visibility }) .set({ name, visibility })
.where(eq(sandbox.id, id)) .where(eq(sandbox.id, id))
.returning() .returning()
.get() .get()
return success return success
} else if (method === "PUT") { } else if (method === "PUT") {
const initSchema = z.object({ const initSchema = z.object({
type: z.string(), type: z.string(),
name: z.string(), name: z.string(),
userId: z.string(), userId: z.string(),
visibility: z.enum(["public", "private"]), visibility: z.enum(["public", "private"]),
}) })
const body = await request.json() const body = await request.json()
const { type, name, userId, visibility } = initSchema.parse(body) const { type, name, userId, visibility } = initSchema.parse(body)
const userSandboxes = await db const userSandboxes = await db
.select() .select()
.from(sandbox) .from(sandbox)
.where(eq(sandbox.userId, userId)) .where(eq(sandbox.userId, userId))
.all() .all()
if (userSandboxes.length >= 8) { if (userSandboxes.length >= 8) {
return new Response("You reached the maximum # of sandboxes.", { return new Response("You reached the maximum # of sandboxes.", {
status: 400, status: 400,
}) })
} }
const sb = await db const sb = await db
.insert(sandbox) .insert(sandbox)
.values({ type, name, userId, visibility, createdAt: new Date() }) .values({ type, name, userId, visibility, createdAt: new Date() })
.returning() .returning()
.get() .get()
const initStorageRequest = new Request( const initStorageRequest = new Request(
`${env.STORAGE_WORKER_URL}/api/init`, `${env.STORAGE_WORKER_URL}/api/init`,
{ {
method: "POST", method: "POST",
body: JSON.stringify({ sandboxId: sb.id, type }), body: JSON.stringify({ sandboxId: sb.id, type }),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${env.KEY}`, Authorization: `${env.KEY}`,
}, },
} }
) )
await env.STORAGE.fetch(initStorageRequest) await env.STORAGE.fetch(initStorageRequest)
return new Response(sb.id, { status: 200 }) return new Response(sb.id, { status: 200 })
} else { } else {
return methodNotAllowed return methodNotAllowed
} }
} else if (path === "/api/sandbox/share") { } else if (path === "/api/sandbox/share") {
if (method === "GET") { if (method === "GET") {
const params = url.searchParams const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string const id = params.get("id") as string
const res = await db.query.usersToSandboxes.findMany({ const res = await db.query.usersToSandboxes.findMany({
where: (uts, { eq }) => eq(uts.userId, id), where: (uts, { eq }) => eq(uts.userId, id),
}) })
const owners = await Promise.all( const owners = await Promise.all(
res.map(async (r) => { res.map(async (r) => {
const sb = await db.query.sandbox.findFirst({ const sb = await db.query.sandbox.findFirst({
where: (sandbox, { eq }) => eq(sandbox.id, r.sandboxId), where: (sandbox, { eq }) => eq(sandbox.id, r.sandboxId),
with: { with: {
author: true, author: true,
}, },
}) })
if (!sb) return if (!sb) return
return { return {
id: sb.id, id: sb.id,
name: sb.name, name: sb.name,
type: sb.type, type: sb.type,
author: sb.author.name, author: sb.author.name,
authorAvatarUrl: sb.author.avatarUrl, authorAvatarUrl: sb.author.avatarUrl,
sharedOn: r.sharedOn, sharedOn: r.sharedOn,
} }
}) })
) )
return json(owners ?? {}) return json(owners ?? {})
} else return invalidRequest } else return invalidRequest
} else if (method === "POST") { } else if (method === "POST") {
const shareSchema = z.object({ const shareSchema = z.object({
sandboxId: z.string(), sandboxId: z.string(),
email: z.string(), email: z.string(),
}) })
const body = await request.json() const body = await request.json()
const { sandboxId, email } = shareSchema.parse(body) const { sandboxId, email } = shareSchema.parse(body)
const user = await db.query.user.findFirst({ const user = await db.query.user.findFirst({
where: (user, { eq }) => eq(user.email, email), where: (user, { eq }) => eq(user.email, email),
with: { with: {
sandbox: true, sandbox: true,
usersToSandboxes: true, usersToSandboxes: true,
}, },
}) })
if (!user) { if (!user) {
return new Response("No user associated with email.", { status: 400 }) return new Response("No user associated with email.", { status: 400 })
} }
if (user.sandbox.find((sb) => sb.id === sandboxId)) { if (user.sandbox.find((sb) => sb.id === sandboxId)) {
return new Response("Cannot share with yourself!", { status: 400 }) return new Response("Cannot share with yourself!", { status: 400 })
} }
if (user.usersToSandboxes.find((uts) => uts.sandboxId === sandboxId)) { if (user.usersToSandboxes.find((uts) => uts.sandboxId === sandboxId)) {
return new Response("User already has access.", { status: 400 }) return new Response("User already has access.", { status: 400 })
} }
await db await db
.insert(usersToSandboxes) .insert(usersToSandboxes)
.values({ userId: user.id, sandboxId, sharedOn: new Date() }) .values({ userId: user.id, sandboxId, sharedOn: new Date() })
.get() .get()
return success return success
} else if (method === "DELETE") { } else if (method === "DELETE") {
const deleteShareSchema = z.object({ const deleteShareSchema = z.object({
sandboxId: z.string(), sandboxId: z.string(),
userId: z.string(), userId: z.string(),
}) })
const body = await request.json() const body = await request.json()
const { sandboxId, userId } = deleteShareSchema.parse(body) const { sandboxId, userId } = deleteShareSchema.parse(body)
await db await db
.delete(usersToSandboxes) .delete(usersToSandboxes)
.where( .where(
and( and(
eq(usersToSandboxes.userId, userId), eq(usersToSandboxes.userId, userId),
eq(usersToSandboxes.sandboxId, sandboxId) eq(usersToSandboxes.sandboxId, sandboxId)
) )
) )
return success return success
} else return methodNotAllowed } else return methodNotAllowed
} else if (path === "/api/user") { } else if (path === "/api/user") {
if (method === "GET") { if (method === "GET") {
const params = url.searchParams const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string const id = params.get("id") as string
const res = await db.query.user.findFirst({ const res = await db.query.user.findFirst({
where: (user, { eq }) => eq(user.id, id), where: (user, { eq }) => eq(user.id, id),
with: { with: {
sandbox: { sandbox: {
orderBy: (sandbox, { desc }) => [desc(sandbox.createdAt)], orderBy: (sandbox, { desc }) => [desc(sandbox.createdAt)],
with: { with: {
likes: true, likes: true,
}, },
}, },
usersToSandboxes: true, usersToSandboxes: true,
}, },
}) })
if (res) { if (res) {
const transformedUser: UserResponse = { const transformedUser: UserResponse = {
...res, ...res,
sandbox: res.sandbox.map( sandbox: res.sandbox.map(
(sb): SandboxWithLiked => ({ (sb): SandboxWithLiked => ({
...sb, ...sb,
liked: sb.likes.some((like) => like.userId === id), liked: sb.likes.some((like) => like.userId === id),
}) })
), ),
} }
return json(transformedUser) return json(transformedUser)
} }
return json(res ?? {}) return json(res ?? {})
} else if (params.has("username")) { } else if (params.has("username")) {
const username = params.get("username") as string const username = params.get("username") as string
const userId = params.get("currentUserId") const userId = params.get("currentUserId")
const res = await db.query.user.findFirst({ const res = await db.query.user.findFirst({
where: (user, { eq }) => eq(user.username, username), where: (user, { eq }) => eq(user.username, username),
with: { with: {
sandbox: { sandbox: {
orderBy: (sandbox, { desc }) => [desc(sandbox.createdAt)], orderBy: (sandbox, { desc }) => [desc(sandbox.createdAt)],
with: { with: {
likes: true, likes: true,
}, },
}, },
usersToSandboxes: true, usersToSandboxes: true,
}, },
}) })
if (res) { if (res) {
const transformedUser: UserResponse = { const transformedUser: UserResponse = {
...res, ...res,
sandbox: res.sandbox.map( sandbox: res.sandbox.map(
(sb): SandboxWithLiked => ({ (sb): SandboxWithLiked => ({
...sb, ...sb,
liked: sb.likes.some((like) => like.userId === userId), liked: sb.likes.some((like) => like.userId === userId),
}) })
), ),
} }
return json(transformedUser) return json(transformedUser)
} }
return json(res ?? {}) return json(res ?? {})
} else { } else {
const res = await db.select().from(user).all() const res = await db.select().from(user).all()
return json(res ?? {}) return json(res ?? {})
} }
} else if (method === "POST") { } else if (method === "POST") {
const userSchema = z.object({ const userSchema = z.object({
id: z.string(), id: z.string(),
name: z.string(), name: z.string(),
email: z.string().email(), email: z.string().email(),
username: z.string(), username: z.string(),
avatarUrl: z.string().optional(), avatarUrl: z.string().optional(),
createdAt: z.string().optional(), createdAt: z.string().optional(),
generations: z.number().optional(), generations: z.number().optional(),
tier: z.enum(["FREE", "PRO", "ENTERPRISE"]).optional(), tier: z.enum(["FREE", "PRO", "ENTERPRISE"]).optional(),
tierExpiresAt: z.number().optional(), tierExpiresAt: z.number().optional(),
lastResetDate: z.number().optional(), lastResetDate: z.number().optional(),
}) })
const body = await request.json() const body = await request.json()
const { id, name, email, username, avatarUrl, createdAt, generations, tier, tierExpiresAt, lastResetDate } = userSchema.parse(body) const {
const res = await db id,
.insert(user) name,
.values({ email,
id, username,
name, avatarUrl,
email, createdAt,
username, generations,
avatarUrl, tier,
createdAt: createdAt ? new Date(createdAt) : new Date(), tierExpiresAt,
generations, lastResetDate,
tier, } = userSchema.parse(body)
tierExpiresAt, const res = await db
lastResetDate, .insert(user)
}) .values({
.returning() id,
.get() name,
return json({ res }) email,
} else if (method === "DELETE") { username,
const params = url.searchParams avatarUrl,
if (params.has("id")) { createdAt: createdAt ? new Date(createdAt) : new Date(),
const id = params.get("id") as string generations,
await db.delete(user).where(eq(user.id, id)) tier,
return success tierExpiresAt,
} else return invalidRequest lastResetDate,
} else if (method === "PUT") { })
const updateUserSchema = z.object({ .returning()
id: z.string(), .get()
name: z.string().optional(), return json({ res })
email: z.string().email().optional(), } else if (method === "DELETE") {
username: z.string().optional(), const params = url.searchParams
avatarUrl: z.string().optional(), if (params.has("id")) {
generations: z.number().optional(), const id = params.get("id") as string
}) await db.delete(user).where(eq(user.id, id))
return success
} else return invalidRequest
} else if (method === "PUT") {
const updateUserSchema = z.object({
id: z.string(),
name: z.string().optional(),
email: z.string().email().optional(),
username: z.string().optional(),
avatarUrl: z.string().optional(),
generations: z.number().optional(),
})
try { try {
const body = await request.json() const body = await request.json()
const validatedData = updateUserSchema.parse(body) const validatedData = updateUserSchema.parse(body)
const { id, username, ...updateData } = validatedData const { id, username, ...updateData } = validatedData
// If username is being updated, check for existing username // If username is being updated, check for existing username
if (username) { if (username) {
const existingUser = await db const existingUser = await db
.select() .select()
.from(user) .from(user)
.where(eq(user.username, username)) .where(eq(user.username, username))
.get() .get()
if (existingUser && existingUser.id !== id) { if (existingUser && existingUser.id !== id) {
return json({ error: "Username already exists" }, { status: 409 }) return json({ error: "Username already exists" }, { status: 409 })
} }
} }
const cleanUpdateData = { const cleanUpdateData = {
...updateData, ...updateData,
...(username ? { username } : {}), ...(username ? { username } : {}),
} }
const res = await db const res = await db
.update(user) .update(user)
.set(cleanUpdateData) .set(cleanUpdateData)
.where(eq(user.id, id)) .where(eq(user.id, id))
.returning() .returning()
.get() .get()
if (!res) { if (!res) {
return json({ error: "User not found" }, { status: 404 }) return json({ error: "User not found" }, { status: 404 })
} }
return json({ res }) return json({ res })
} catch (error) { } catch (error) {
if (error instanceof z.ZodError) { if (error instanceof z.ZodError) {
return json({ error: error.errors }, { status: 400 }) return json({ error: error.errors }, { status: 400 })
} }
return json({ error: "Internal server error" }, { status: 500 }) return json({ error: "Internal server error" }, { status: 500 })
} }
} else { } else {
return methodNotAllowed return methodNotAllowed
} }
} else if (path === "/api/user/check-username") { } else if (path === "/api/user/check-username") {
if (method === "GET") { if (method === "GET") {
const params = url.searchParams const params = url.searchParams
const username = params.get("username") const username = params.get("username")
if (!username) return invalidRequest if (!username) return invalidRequest
const exists = await db.query.user.findFirst({ const exists = await db.query.user.findFirst({
where: (user, { eq }) => eq(user.username, username), where: (user, { eq }) => eq(user.username, username),
}) })
return json({ exists: !!exists }) return json({ exists: !!exists })
} }
return methodNotAllowed return methodNotAllowed
} else if (path === "/api/user/increment-generations" && method === "POST") { } else if (
const schema = z.object({ path === "/api/user/increment-generations" &&
userId: z.string(), method === "POST"
}) ) {
const schema = z.object({
userId: z.string(),
})
const body = await request.json() const body = await request.json()
const { userId } = schema.parse(body) const { userId } = schema.parse(body)
await db await db
.update(user) .update(user)
.set({ generations: sql`${user.generations} + 1` }) .set({ generations: sql`${user.generations} + 1` })
.where(eq(user.id, userId)) .where(eq(user.id, userId))
.get() .get()
return success return success
} else if (path === "/api/user/update-tier" && method === "POST") { } else if (path === "/api/user/update-tier" && method === "POST") {
const schema = z.object({ const schema = z.object({
userId: z.string(), userId: z.string(),
tier: z.enum(["FREE", "PRO", "ENTERPRISE"]), tier: z.enum(["FREE", "PRO", "ENTERPRISE"]),
tierExpiresAt: z.date(), tierExpiresAt: z.date(),
}) })
const body = await request.json() const body = await request.json()
const { userId, tier, tierExpiresAt } = schema.parse(body) const { userId, tier, tierExpiresAt } = schema.parse(body)
await db await db
.update(user) .update(user)
.set({ .set({
tier, tier,
tierExpiresAt: tierExpiresAt.getTime(), tierExpiresAt: tierExpiresAt.getTime(),
// Reset generations when upgrading tier // Reset generations when upgrading tier
generations: 0 generations: 0,
}) })
.where(eq(user.id, userId)) .where(eq(user.id, userId))
.get() .get()
return success return success
} else if (path === "/api/user/check-reset" && method === "POST") { } else if (path === "/api/user/check-reset" && method === "POST") {
const schema = z.object({ const schema = z.object({
userId: z.string(), userId: z.string(),
}) })
const body = await request.json() const body = await request.json()
const { userId } = schema.parse(body) const { userId } = schema.parse(body)
const dbUser = await db.query.user.findFirst({ const dbUser = await db.query.user.findFirst({
where: (user, { eq }) => eq(user.id, userId), where: (user, { eq }) => eq(user.id, userId),
}) })
if (!dbUser) { if (!dbUser) {
return new Response("User not found", { status: 404 }) return new Response("User not found", { status: 404 })
} }
const now = new Date() const now = new Date()
const lastReset = dbUser.lastResetDate ? new Date(dbUser.lastResetDate) : new Date(0) const lastReset = dbUser.lastResetDate
? new Date(dbUser.lastResetDate)
: new Date(0)
if (now.getMonth() !== lastReset.getMonth() || now.getFullYear() !== lastReset.getFullYear()) { if (
await db now.getMonth() !== lastReset.getMonth() ||
.update(user) now.getFullYear() !== lastReset.getFullYear()
.set({ ) {
generations: 0, await db
lastResetDate: now.getTime() .update(user)
}) .set({
.where(eq(user.id, userId)) generations: 0,
.get() lastResetDate: now.getTime(),
})
return new Response("Reset successful", { status: 200 }) .where(eq(user.id, userId))
} .get()
return new Response("No reset needed", { status: 200 }) return new Response("Reset successful", { status: 200 })
} else return notFound }
},
return new Response("No reset needed", { status: 200 })
} else return notFound
},
} }

View File

@ -4,112 +4,112 @@ import { integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"
// #region Tables // #region Tables
export const user = sqliteTable("user", { export const user = sqliteTable("user", {
id: text("id") id: text("id")
.$defaultFn(() => createId()) .$defaultFn(() => createId())
.primaryKey() .primaryKey()
.unique(), .unique(),
name: text("name").notNull(), name: text("name").notNull(),
email: text("email").notNull(), email: text("email").notNull(),
username: text("username").notNull().unique(), username: text("username").notNull().unique(),
avatarUrl: text("avatarUrl"), avatarUrl: text("avatarUrl"),
createdAt: integer("createdAt", { mode: "timestamp_ms" }).default( createdAt: integer("createdAt", { mode: "timestamp_ms" }).default(
sql`CURRENT_TIMESTAMP` sql`CURRENT_TIMESTAMP`
), ),
generations: integer("generations").default(0), generations: integer("generations").default(0),
tier: text("tier", { enum: ["FREE", "PRO", "ENTERPRISE"] }).default("FREE"), tier: text("tier", { enum: ["FREE", "PRO", "ENTERPRISE"] }).default("FREE"),
tierExpiresAt: integer("tierExpiresAt"), tierExpiresAt: integer("tierExpiresAt"),
lastResetDate: integer("lastResetDate"), lastResetDate: integer("lastResetDate"),
}) })
export type User = typeof user.$inferSelect export type User = typeof user.$inferSelect
export const sandbox = sqliteTable("sandbox", { export const sandbox = sqliteTable("sandbox", {
id: text("id") id: text("id")
.$defaultFn(() => createId()) .$defaultFn(() => createId())
.primaryKey() .primaryKey()
.unique(), .unique(),
name: text("name").notNull(), name: text("name").notNull(),
type: text("type").notNull(), type: text("type").notNull(),
visibility: text("visibility", { enum: ["public", "private"] }), visibility: text("visibility", { enum: ["public", "private"] }),
createdAt: integer("createdAt", { mode: "timestamp_ms" }).default( createdAt: integer("createdAt", { mode: "timestamp_ms" }).default(
sql`CURRENT_TIMESTAMP` sql`CURRENT_TIMESTAMP`
), ),
userId: text("user_id") userId: text("user_id")
.notNull() .notNull()
.references(() => user.id), .references(() => user.id),
likeCount: integer("likeCount").default(0), likeCount: integer("likeCount").default(0),
viewCount: integer("viewCount").default(0), viewCount: integer("viewCount").default(0),
}) })
export type Sandbox = typeof sandbox.$inferSelect export type Sandbox = typeof sandbox.$inferSelect
export const sandboxLikes = sqliteTable( export const sandboxLikes = sqliteTable(
"sandbox_likes", "sandbox_likes",
{ {
userId: text("user_id") userId: text("user_id")
.notNull() .notNull()
.references(() => user.id), .references(() => user.id),
sandboxId: text("sandbox_id") sandboxId: text("sandbox_id")
.notNull() .notNull()
.references(() => sandbox.id), .references(() => sandbox.id),
createdAt: integer("createdAt", { mode: "timestamp_ms" }).default( createdAt: integer("createdAt", { mode: "timestamp_ms" }).default(
sql`CURRENT_TIMESTAMP` sql`CURRENT_TIMESTAMP`
), ),
}, },
(table) => ({ (table) => ({
pk: primaryKey({ columns: [table.sandboxId, table.userId] }), pk: primaryKey({ columns: [table.sandboxId, table.userId] }),
}) })
) )
export const usersToSandboxes = sqliteTable("users_to_sandboxes", { export const usersToSandboxes = sqliteTable("users_to_sandboxes", {
userId: text("userId") userId: text("userId")
.notNull() .notNull()
.references(() => user.id), .references(() => user.id),
sandboxId: text("sandboxId") sandboxId: text("sandboxId")
.notNull() .notNull()
.references(() => sandbox.id), .references(() => sandbox.id),
sharedOn: integer("sharedOn", { mode: "timestamp_ms" }), sharedOn: integer("sharedOn", { mode: "timestamp_ms" }),
}) })
// #region Relations // #region Relations
export const userRelations = relations(user, ({ many }) => ({ export const userRelations = relations(user, ({ many }) => ({
sandbox: many(sandbox), sandbox: many(sandbox),
usersToSandboxes: many(usersToSandboxes), usersToSandboxes: many(usersToSandboxes),
likes: many(sandboxLikes), likes: many(sandboxLikes),
})) }))
export const sandboxRelations = relations(sandbox, ({ one, many }) => ({ export const sandboxRelations = relations(sandbox, ({ one, many }) => ({
author: one(user, { author: one(user, {
fields: [sandbox.userId], fields: [sandbox.userId],
references: [user.id], references: [user.id],
}), }),
usersToSandboxes: many(usersToSandboxes), usersToSandboxes: many(usersToSandboxes),
likes: many(sandboxLikes), likes: many(sandboxLikes),
})) }))
export const sandboxLikesRelations = relations(sandboxLikes, ({ one }) => ({ export const sandboxLikesRelations = relations(sandboxLikes, ({ one }) => ({
user: one(user, { user: one(user, {
fields: [sandboxLikes.userId], fields: [sandboxLikes.userId],
references: [user.id], references: [user.id],
}), }),
sandbox: one(sandbox, { sandbox: one(sandbox, {
fields: [sandboxLikes.sandboxId], fields: [sandboxLikes.sandboxId],
references: [sandbox.id], references: [sandbox.id],
}), }),
})) }))
export const usersToSandboxesRelations = relations( export const usersToSandboxesRelations = relations(
usersToSandboxes, usersToSandboxes,
({ one }) => ({ ({ one }) => ({
group: one(sandbox, { group: one(sandbox, {
fields: [usersToSandboxes.sandboxId], fields: [usersToSandboxes.sandboxId],
references: [sandbox.id], references: [sandbox.id],
}), }),
user: one(user, { user: one(user, {
fields: [usersToSandboxes.userId], fields: [usersToSandboxes.userId],
references: [user.id], references: [user.id],
}), }),
}) })
) )
// #endregion // #endregion

View File

@ -43,42 +43,45 @@ export async function POST(request: Request) {
const userData = await dbUser.json() const userData = await dbUser.json()
// Get tier settings // Get tier settings
const tierSettings = TIERS[userData.tier as keyof typeof TIERS] || TIERS.FREE const tierSettings =
TIERS[userData.tier as keyof typeof TIERS] || TIERS.FREE
if (userData.generations >= tierSettings.generations) { if (userData.generations >= tierSettings.generations) {
return new Response( return new Response(
`AI generation limit reached for your ${userData.tier || "FREE"} tier`, `AI generation limit reached for your ${userData.tier || "FREE"} tier`,
{ status: 429 } { status: 429 }
) )
} }
const { const {
messages, messages,
context, context,
activeFileContent, activeFileContent,
isEditMode, isEditMode,
fileName, fileName,
line, line,
templateType templateType,
} = await request.json() } = await request.json()
// Get template configuration // Get template configuration
const templateConfig = templateConfigs[templateType] const templateConfig = templateConfigs[templateType]
// Create template context // Create template context
const templateContext = templateConfig ? ` const templateContext = templateConfig
? `
Project Template: ${templateConfig.name} Project Template: ${templateConfig.name}
File Structure: File Structure:
${Object.entries(templateConfig.fileStructure) ${Object.entries(templateConfig.fileStructure)
.map(([path, info]) => `${path} - ${info.description}`) .map(([path, info]) => `${path} - ${info.description}`)
.join('\n')} .join("\n")}
Conventions: Conventions:
${templateConfig.conventions.join('\n')} ${templateConfig.conventions.join("\n")}
Dependencies: Dependencies:
${JSON.stringify(templateConfig.dependencies, null, 2)} ${JSON.stringify(templateConfig.dependencies, null, 2)}
` : '' `
: ""
// Create system message based on mode // Create system message based on mode
let systemMessage let systemMessage
@ -146,7 +149,10 @@ ${activeFileContent ? `Active File Content:\n${activeFileContent}\n` : ""}`
new ReadableStream({ new ReadableStream({
async start(controller) { async start(controller) {
for await (const chunk of stream) { for await (const chunk of stream) {
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") { if (
chunk.type === "content_block_delta" &&
chunk.delta.type === "text_delta"
) {
controller.enqueue(encoder.encode(chunk.delta.text)) controller.enqueue(encoder.encode(chunk.delta.text))
} }
} }
@ -164,8 +170,8 @@ ${activeFileContent ? `Active File Content:\n${activeFileContent}\n` : ""}`
} catch (error) { } catch (error) {
console.error("AI generation error:", error) console.error("AI generation error:", error)
return new Response( return new Response(
error instanceof Error ? error.message : "Internal Server Error", error instanceof Error ? error.message : "Internal Server Error",
{ status: 500 } { status: 500 }
) )
} }
} }

View File

@ -8,9 +8,9 @@ export async function POST(request: Request) {
} }
const { tier } = await request.json() const { tier } = await request.json()
// handle payment processing here // handle payment processing here
const response = await fetch( const response = await fetch(
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user/update-tier`, `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user/update-tier`,
{ {
@ -39,4 +39,4 @@ export async function POST(request: Request) {
{ status: 500 } { status: 500 }
) )
} }
} }

View File

@ -53,7 +53,7 @@ export default function AIChat({
// scroll to bottom of chat when messages change // scroll to bottom of chat when messages change
const scrollToBottom = (force: boolean = false) => { const scrollToBottom = (force: boolean = false) => {
if (!chatContainerRef.current || (!autoScroll && !force)) return if (!chatContainerRef.current || (!autoScroll && !force)) return
chatContainerRef.current.scrollTo({ chatContainerRef.current.scrollTo({
top: chatContainerRef.current.scrollHeight, top: chatContainerRef.current.scrollHeight,
behavior: force ? "smooth" : "auto", behavior: force ? "smooth" : "auto",
@ -63,10 +63,10 @@ export default function AIChat({
// function to handle scroll events // function to handle scroll events
const handleScroll = () => { const handleScroll = () => {
if (!chatContainerRef.current) return if (!chatContainerRef.current) return
const { scrollTop, scrollHeight, clientHeight } = chatContainerRef.current const { scrollTop, scrollHeight, clientHeight } = chatContainerRef.current
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 50 const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 50
setAutoScroll(isAtBottom) setAutoScroll(isAtBottom)
setShowScrollButton(!isAtBottom) setShowScrollButton(!isAtBottom)
} }
@ -75,8 +75,8 @@ export default function AIChat({
useEffect(() => { useEffect(() => {
const container = chatContainerRef.current const container = chatContainerRef.current
if (container) { if (container) {
container.addEventListener('scroll', handleScroll) container.addEventListener("scroll", handleScroll)
return () => container.removeEventListener('scroll', handleScroll) return () => container.removeEventListener("scroll", handleScroll)
} }
}, []) }, [])
@ -200,7 +200,7 @@ export default function AIChat({
/> />
))} ))}
{isLoading && <LoadingDots />} {isLoading && <LoadingDots />}
{/* Add scroll to bottom button */} {/* Add scroll to bottom button */}
{showScrollButton && ( {showScrollButton && (
<button <button

View File

@ -131,22 +131,20 @@ export const handleSend = async (
})) }))
// Fetch AI response for chat message component // Fetch AI response for chat message component
const response = await fetch("/api/ai", const response = await fetch("/api/ai", {
{
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
messages: anthropicMessages, messages: anthropicMessages,
context: context || undefined, context: context || undefined,
activeFileContent: activeFileContent, activeFileContent: activeFileContent,
isEditMode: isEditMode, isEditMode: isEditMode,
templateType: templateType, templateType: templateType,
}), }),
signal: abortControllerRef.current.signal, signal: abortControllerRef.current.signal,
} })
)
// Throw error if response is not ok // Throw error if response is not ok
if (!response.ok) { if (!response.ok) {
@ -201,7 +199,8 @@ export const handleSend = async (
console.error("Error fetching AI response:", error) console.error("Error fetching AI response:", error)
const errorMessage = { const errorMessage = {
role: "assistant" as const, role: "assistant" as const,
content: error.message || "Sorry, I encountered an error. Please try again.", content:
error.message || "Sorry, I encountered an error. Please try again.",
} }
setMessages((prev) => [...prev, errorMessage]) setMessages((prev) => [...prev, errorMessage])
} }

View File

@ -66,15 +66,17 @@ export default function GenerateInput({
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
messages: [{ messages: [
role: "user", {
content: regenerate ? currentPrompt : input role: "user",
}], content: regenerate ? currentPrompt : input,
},
],
context: null, context: null,
activeFileContent: data.code, activeFileContent: data.code,
isEditMode: true, isEditMode: true,
fileName: data.fileName, fileName: data.fileName,
line: data.line line: data.line,
}), }),
}) })

View File

@ -1087,62 +1087,62 @@ export default function CodeEditor({
</div> </div>
</> </>
) : // Note clerk.loaded is required here due to a bug: https://github.com/clerk/javascript/issues/1643 ) : // Note clerk.loaded is required here due to a bug: https://github.com/clerk/javascript/issues/1643
clerk.loaded ? ( clerk.loaded ? (
<> <>
{/* {provider && userInfo ? ( {/* {provider && userInfo ? (
<Cursors yProvider={provider} userInfo={userInfo} /> <Cursors yProvider={provider} userInfo={userInfo} />
) : null} */} ) : null} */}
<Editor <Editor
height="100%" height="100%"
language={editorLanguage} language={editorLanguage}
beforeMount={handleEditorWillMount} beforeMount={handleEditorWillMount}
onMount={handleEditorMount} onMount={handleEditorMount}
onChange={(value) => { onChange={(value) => {
// If the new content is different from the cached content, update it // If the new content is different from the cached content, update it
if (value !== fileContents[activeFileId]) { if (value !== fileContents[activeFileId]) {
setActiveFileContent(value ?? "") // Update the active file content setActiveFileContent(value ?? "") // Update the active file content
// Mark the file as unsaved by setting 'saved' to false // Mark the file as unsaved by setting 'saved' to false
setTabs((prev) => setTabs((prev) =>
prev.map((tab) => prev.map((tab) =>
tab.id === activeFileId tab.id === activeFileId
? { ...tab, saved: false } ? { ...tab, saved: false }
: tab : tab
)
) )
} else { )
// If the content matches the cached content, mark the file as saved } else {
setTabs((prev) => // If the content matches the cached content, mark the file as saved
prev.map((tab) => setTabs((prev) =>
tab.id === activeFileId prev.map((tab) =>
? { ...tab, saved: true } tab.id === activeFileId
: tab ? { ...tab, saved: true }
) : tab
) )
} )
}} }
options={{ }}
tabSize: 2, options={{
minimap: { tabSize: 2,
enabled: false, minimap: {
}, enabled: false,
padding: { },
bottom: 4, padding: {
top: 4, bottom: 4,
}, top: 4,
scrollBeyondLastLine: false, },
fixedOverflowWidgets: true, scrollBeyondLastLine: false,
fontFamily: "var(--font-geist-mono)", fixedOverflowWidgets: true,
}} fontFamily: "var(--font-geist-mono)",
theme={theme === "light" ? "vs" : "vs-dark"} }}
value={activeFileContent} theme={theme === "light" ? "vs" : "vs-dark"}
/> value={activeFileContent}
</> />
) : ( </>
<div className="w-full h-full flex items-center justify-center text-xl font-medium text-muted-foreground/50 select-none"> ) : (
<Loader2 className="animate-spin w-6 h-6 mr-3" /> <div className="w-full h-full flex items-center justify-center text-xl font-medium text-muted-foreground/50 select-none">
Waiting for Clerk to load... <Loader2 className="animate-spin w-6 h-6 mr-3" />
</div> Waiting for Clerk to load...
)} </div>
)}
</div> </div>
</ResizablePanel> </ResizablePanel>
<ResizableHandle /> <ResizableHandle />

View File

@ -231,7 +231,10 @@ function ProfileCard({
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<p className="text-xs text-muted-foreground">{joinedAt}</p> <p className="text-xs text-muted-foreground">{joinedAt}</p>
{typeof generations === "number" && ( {typeof generations === "number" && (
<SubscriptionBadge generations={generations} tier={tier as keyof typeof TIERS} /> <SubscriptionBadge
generations={generations}
tier={tier as keyof typeof TIERS}
/>
)} )}
</div> </div>
</> </>
@ -445,7 +448,13 @@ const StatsItem = ({ icon: Icon, label }: StatsItemProps) => (
// #endregion // #endregion
// #region Sub Badge // #region Sub Badge
const SubscriptionBadge = ({ generations, tier = "FREE" }: { generations: number, tier?: keyof typeof TIERS }) => { const SubscriptionBadge = ({
generations,
tier = "FREE",
}: {
generations: number
tier?: keyof typeof TIERS
}) => {
return ( return (
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Badge variant="secondary" className="text-sm cursor-pointer"> <Badge variant="secondary" className="text-sm cursor-pointer">

View File

@ -10,7 +10,7 @@ import {
import { User } from "@/lib/types" import { User } from "@/lib/types"
import { useClerk } from "@clerk/nextjs" import { useClerk } from "@clerk/nextjs"
import { import {
Crown, Crown,
LayoutDashboard, LayoutDashboard,
LogOut, LogOut,
Sparkles, Sparkles,
@ -43,7 +43,11 @@ const TIER_INFO = {
}, },
} as const } as const
export default function UserButton({ userData: initialUserData }: { userData: User }) { export default function UserButton({
userData: initialUserData,
}: {
userData: User
}) {
const [userData, setUserData] = useState<User>(initialUserData) const [userData, setUserData] = useState<User>(initialUserData)
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const { signOut } = useClerk() const { signOut } = useClerk()
@ -57,7 +61,7 @@ export default function UserButton({ userData: initialUserData }: { userData: Us
headers: { headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`, Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
}, },
cache: 'no-store' cache: "no-store",
} }
) )
if (res.ok) { if (res.ok) {
@ -75,9 +79,12 @@ export default function UserButton({ userData: initialUserData }: { userData: Us
} }
}, [isOpen]) }, [isOpen])
const tierInfo = TIER_INFO[userData.tier as keyof typeof TIER_INFO] || TIER_INFO.FREE const tierInfo =
TIER_INFO[userData.tier as keyof typeof TIER_INFO] || TIER_INFO.FREE
const TierIcon = tierInfo.icon const TierIcon = tierInfo.icon
const usagePercentage = Math.floor((userData.generations || 0) * 100 / tierInfo.limit) const usagePercentage = Math.floor(
((userData.generations || 0) * 100) / tierInfo.limit
)
const handleUpgrade = async () => { const handleUpgrade = async () => {
router.push(`/@${userData.username}`) router.push(`/@${userData.username}`)
@ -98,7 +105,6 @@ export default function UserButton({ userData: initialUserData }: { userData: Us
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem className="cursor-pointer" asChild> <DropdownMenuItem className="cursor-pointer" asChild>
<Link href={"/dashboard"}> <Link href={"/dashboard"}>
<LayoutDashboard className="mr-2 size-4" /> <LayoutDashboard className="mr-2 size-4" />
@ -114,12 +120,13 @@ export default function UserButton({ userData: initialUserData }: { userData: Us
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="py-1.5 px-2 w-full"> <div className="py-1.5 px-2 w-full">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<TierIcon className={`h-4 w-4 ${tierInfo.color}`} /> <TierIcon className={`h-4 w-4 ${tierInfo.color}`} />
<span className="text-sm font-medium">{userData.tier || "FREE"} Plan</span> <span className="text-sm font-medium">
{userData.tier || "FREE"} Plan
</span>
</div> </div>
{(userData.tier === "FREE" || userData.tier === "PRO") && ( {(userData.tier === "FREE" || userData.tier === "PRO") && (
<Button <Button
@ -139,16 +146,20 @@ export default function UserButton({ userData: initialUserData }: { userData: Us
<div className="w-full"> <div className="w-full">
<div className="flex items-center justify-between text-sm text-muted-foreground mb-2"> <div className="flex items-center justify-between text-sm text-muted-foreground mb-2">
<span>AI Usage</span> <span>AI Usage</span>
<span>{userData.generations}/{tierInfo.limit}</span> <span>
{userData.generations}/{tierInfo.limit}
</span>
</div> </div>
<div className="rounded-full w-full h-2 overflow-hidden bg-secondary mb-1"> <div className="rounded-full w-full h-2 overflow-hidden bg-secondary mb-1">
<div <div
className={`h-full rounded-full transition-all duration-300 ${ className={`h-full rounded-full transition-all duration-300 ${
usagePercentage > 90 ? 'bg-red-500' : usagePercentage > 90
usagePercentage > 75 ? 'bg-yellow-500' : ? "bg-red-500"
tierInfo.color.replace('text-', 'bg-') : usagePercentage > 75
}`} ? "bg-yellow-500"
: tierInfo.color.replace("text-", "bg-")
}`}
style={{ style={{
width: `${Math.min(usagePercentage, 100)}%`, width: `${Math.min(usagePercentage, 100)}%`,
}} }}
@ -173,4 +184,3 @@ export default function UserButton({ userData: initialUserData }: { userData: Us
</DropdownMenu> </DropdownMenu>
) )
} }

View File

@ -38,6 +38,6 @@ export const projectTemplates: {
name: "PHP", name: "PHP",
description: "PHP development environment", description: "PHP development environment",
icon: "/project-icons/php.svg", icon: "/project-icons/php.svg",
disabled: false disabled: false,
}, },
] ]

View File

@ -1,290 +1,290 @@
export interface TemplateConfig { export interface TemplateConfig {
id: string id: string
name: string, name: string
runCommand: string, runCommand: string
fileStructure: { fileStructure: {
[key: string]: { [key: string]: {
purpose: string purpose: string
description: string description: string
}
}
conventions: string[]
dependencies?: {
[key: string]: string
}
scripts?: {
[key: string]: string
} }
} }
conventions: string[]
export const templateConfigs: { [key: string]: TemplateConfig } = { dependencies?: {
reactjs: { [key: string]: string
id: "reactjs", }
name: "React", scripts?: {
runCommand: "npm run dev", [key: string]: string
fileStructure: { }
"src/": { }
purpose: "source",
description: "Contains all React components and application logic" export const templateConfigs: { [key: string]: TemplateConfig } = {
}, reactjs: {
"src/components/": { id: "reactjs",
purpose: "components", name: "React",
description: "Reusable React components" runCommand: "npm run dev",
}, fileStructure: {
"src/lib/": { "src/": {
purpose: "utilities", purpose: "source",
description: "Utility functions and shared code" description: "Contains all React components and application logic",
}, },
"src/App.tsx": { "src/components/": {
purpose: "entry", purpose: "components",
description: "Main application component" description: "Reusable React components",
}, },
"src/index.tsx": { "src/lib/": {
purpose: "entry", purpose: "utilities",
description: "Application entry point" description: "Utility functions and shared code",
}, },
"src/index.css": { "src/App.tsx": {
purpose: "styles", purpose: "entry",
description: "Global CSS styles" description: "Main application component",
}, },
"public/": { "src/index.tsx": {
purpose: "static", purpose: "entry",
description: "Static assets and index.html" description: "Application entry point",
}, },
"tsconfig.json": { "src/index.css": {
purpose: "config", purpose: "styles",
description: "TypeScript configuration" description: "Global CSS styles",
}, },
"vite.config.ts": { "public/": {
purpose: "config", purpose: "static",
description: "Vite bundler configuration" description: "Static assets and index.html",
}, },
"package.json": { "tsconfig.json": {
purpose: "config", purpose: "config",
description: "Project dependencies and scripts" description: "TypeScript configuration",
} },
}, "vite.config.ts": {
conventions: [ purpose: "config",
"Use functional components with hooks", description: "Vite bundler configuration",
"Follow React naming conventions (PascalCase for components)", },
"Keep components small and focused", "package.json": {
"Use TypeScript for type safety" purpose: "config",
], description: "Project dependencies and scripts",
dependencies: { },
"@radix-ui/react-icons": "^1.3.0", },
"@radix-ui/react-slot": "^1.1.0", conventions: [
"class-variance-authority": "^0.7.0", "Use functional components with hooks",
"clsx": "^2.1.1", "Follow React naming conventions (PascalCase for components)",
"lucide-react": "^0.441.0", "Keep components small and focused",
"react": "^18.3.1", "Use TypeScript for type safety",
"react-dom": "^18.3.1", ],
"tailwind-merge": "^2.5.2", dependencies: {
"tailwindcss-animate": "^1.0.7" "@radix-ui/react-icons": "^1.3.0",
}, "@radix-ui/react-slot": "^1.1.0",
scripts: { "class-variance-authority": "^0.7.0",
"dev": "vite", clsx: "^2.1.1",
"build": "tsc && vite build", "lucide-react": "^0.441.0",
"preview": "vite preview", react: "^18.3.1",
} "react-dom": "^18.3.1",
}, "tailwind-merge": "^2.5.2",
// Next.js template config "tailwindcss-animate": "^1.0.7",
nextjs: { },
id: "nextjs", scripts: {
name: "NextJS", dev: "vite",
runCommand: "npm run dev", build: "tsc && vite build",
fileStructure: { preview: "vite preview",
"pages/": { },
purpose: "routing", },
description: "Page components and API routes" // Next.js template config
}, nextjs: {
"pages/api/": { id: "nextjs",
purpose: "api", name: "NextJS",
description: "API route handlers" runCommand: "npm run dev",
}, fileStructure: {
"pages/_app.tsx": { "pages/": {
purpose: "entry", purpose: "routing",
description: "Application wrapper component" description: "Page components and API routes",
}, },
"pages/index.tsx": { "pages/api/": {
purpose: "page", purpose: "api",
description: "Homepage component" description: "API route handlers",
}, },
"public/": { "pages/_app.tsx": {
purpose: "static", purpose: "entry",
description: "Static assets and files" description: "Application wrapper component",
}, },
"styles/": { "pages/index.tsx": {
purpose: "styles", purpose: "page",
description: "CSS modules and global styles" description: "Homepage component",
}, },
"styles/globals.css": { "public/": {
purpose: "styles", purpose: "static",
description: "Global CSS styles" description: "Static assets and files",
}, },
"styles/Home.module.css": { "styles/": {
purpose: "styles", purpose: "styles",
description: "Homepage-specific styles" description: "CSS modules and global styles",
}, },
"next.config.js": { "styles/globals.css": {
purpose: "config", purpose: "styles",
description: "Next.js configuration" description: "Global CSS styles",
}, },
"next-env.d.ts": { "styles/Home.module.css": {
purpose: "types", purpose: "styles",
description: "Next.js TypeScript declarations" description: "Homepage-specific styles",
}, },
"tsconfig.json": { "next.config.js": {
purpose: "config", purpose: "config",
description: "TypeScript configuration" description: "Next.js configuration",
}, },
"package.json": { "next-env.d.ts": {
purpose: "config", purpose: "types",
description: "Project dependencies and scripts" description: "Next.js TypeScript declarations",
} },
}, "tsconfig.json": {
conventions: [ purpose: "config",
"Use file-system based routing", description: "TypeScript configuration",
"Keep API routes in pages/api", },
"Use CSS Modules for component styles", "package.json": {
"Follow Next.js data fetching patterns" purpose: "config",
], description: "Project dependencies and scripts",
dependencies: { },
"next": "^14.1.0", },
"react": "^18.2.0", conventions: [
"react-dom": "18.2.0", "Use file-system based routing",
"tailwindcss": "^3.4.1" "Keep API routes in pages/api",
}, "Use CSS Modules for component styles",
scripts: { "Follow Next.js data fetching patterns",
"dev": "next dev", ],
"build": "next build", dependencies: {
"start": "next start", next: "^14.1.0",
"lint": "next lint", react: "^18.2.0",
} "react-dom": "18.2.0",
}, tailwindcss: "^3.4.1",
// Streamlit template config },
streamlit: { scripts: {
id: "streamlit", dev: "next dev",
name: "Streamlit", build: "next build",
runCommand: "./venv/bin/streamlit run main.py --server.runOnSave true", start: "next start",
fileStructure: { lint: "next lint",
"main.py": { },
purpose: "entry", },
description: "Main Streamlit application file" // Streamlit template config
}, streamlit: {
"requirements.txt": { id: "streamlit",
purpose: "dependencies", name: "Streamlit",
description: "Python package dependencies" runCommand: "./venv/bin/streamlit run main.py --server.runOnSave true",
}, fileStructure: {
"Procfile": { "main.py": {
purpose: "deployment", purpose: "entry",
description: "Deployment configuration for hosting platforms" description: "Main Streamlit application file",
}, },
"venv/": { "requirements.txt": {
purpose: "environment", purpose: "dependencies",
description: "Python virtual environment directory" description: "Python package dependencies",
} },
}, Procfile: {
conventions: [ purpose: "deployment",
"Use Streamlit components for UI", description: "Deployment configuration for hosting platforms",
"Follow PEP 8 style guide", },
"Keep dependencies in requirements.txt", "venv/": {
"Use virtual environment for isolation" purpose: "environment",
], description: "Python virtual environment directory",
dependencies: { },
"streamlit": "^1.40.0", },
"altair": "^5.5.0" conventions: [
}, "Use Streamlit components for UI",
scripts: { "Follow PEP 8 style guide",
"start": "streamlit run main.py", "Keep dependencies in requirements.txt",
"dev": "./venv/bin/streamlit run main.py --server.runOnSave true" "Use virtual environment for isolation",
} ],
}, dependencies: {
// HTML template config streamlit: "^1.40.0",
vanillajs: { altair: "^5.5.0",
id: "vanillajs", },
name: "HTML/JS", scripts: {
runCommand: "npm run dev", start: "streamlit run main.py",
fileStructure: { dev: "./venv/bin/streamlit run main.py --server.runOnSave true",
"index.html": { },
purpose: "entry", },
description: "Main HTML entry point" // HTML template config
}, vanillajs: {
"style.css": { id: "vanillajs",
purpose: "styles", name: "HTML/JS",
description: "Global CSS styles" runCommand: "npm run dev",
}, fileStructure: {
"script.js": { "index.html": {
purpose: "scripts", purpose: "entry",
description: "JavaScript application logic" description: "Main HTML entry point",
}, },
"package.json": { "style.css": {
purpose: "config", purpose: "styles",
description: "Project dependencies and scripts" description: "Global CSS styles",
}, },
"package-lock.json": { "script.js": {
purpose: "config", purpose: "scripts",
description: "Locked dependency versions" description: "JavaScript application logic",
}, },
"vite.config.js": { "package.json": {
purpose: "config", purpose: "config",
description: "Vite bundler configuration" description: "Project dependencies and scripts",
} },
}, "package-lock.json": {
conventions: [ purpose: "config",
"Use semantic HTML elements", description: "Locked dependency versions",
"Keep CSS modular and organized", },
"Write clean, modular JavaScript", "vite.config.js": {
"Follow modern ES6+ practices" purpose: "config",
], description: "Vite bundler configuration",
dependencies: { },
"vite": "^5.0.12" },
}, conventions: [
scripts: { "Use semantic HTML elements",
"dev": "vite", "Keep CSS modular and organized",
"build": "vite build", "Write clean, modular JavaScript",
"preview": "vite preview" "Follow modern ES6+ practices",
} ],
}, dependencies: {
// PHP template config vite: "^5.0.12",
php: { },
id: "php", scripts: {
name: "PHP", dev: "vite",
runCommand: "npx vite", build: "vite build",
fileStructure: { preview: "vite preview",
"index.php": { },
purpose: "entry", },
description: "Main PHP entry point" // PHP template config
}, php: {
"package.json": { id: "php",
purpose: "config", name: "PHP",
description: "Frontend dependencies and scripts" runCommand: "npx vite",
}, fileStructure: {
"package-lock.json": { "index.php": {
purpose: "config", purpose: "entry",
description: "Locked dependency versions" description: "Main PHP entry point",
}, },
"vite.config.js": { "package.json": {
purpose: "config", purpose: "config",
description: "Vite configuration for frontend assets" description: "Frontend dependencies and scripts",
}, },
"node_modules/": { "package-lock.json": {
purpose: "dependencies", purpose: "config",
description: "Frontend dependency files" description: "Locked dependency versions",
} },
}, "vite.config.js": {
conventions: [ purpose: "config",
"Follow PSR-12 coding standards", description: "Vite configuration for frontend assets",
"Use modern PHP 8+ features", },
"Organize assets with Vite", "node_modules/": {
"Keep PHP logic separate from presentation" purpose: "dependencies",
], description: "Frontend dependency files",
dependencies: { },
"vite": "^5.0.0" },
}, conventions: [
scripts: { "Follow PSR-12 coding standards",
"dev": "vite", "Use modern PHP 8+ features",
"build": "vite build", "Organize assets with Vite",
"preview": "vite preview" "Keep PHP logic separate from presentation",
} ],
} dependencies: {
vite: "^5.0.0",
},
scripts: {
dev: "vite",
build: "vite build",
preview: "vite preview",
},
},
} }

View File

@ -1,19 +1,19 @@
export const TIERS = { export const TIERS = {
FREE: { FREE: {
// generations: 100, // generations: 100,
// maxTokens: 1024, // maxTokens: 1024,
generations: 1000, generations: 1000,
maxTokens: 4096, maxTokens: 4096,
model: "claude-3-5-sonnet-20240620", model: "claude-3-5-sonnet-20240620",
}, },
PRO: { PRO: {
generations: 500, generations: 500,
maxTokens: 2048, maxTokens: 2048,
model: "claude-3-5-sonnet-20240620", model: "claude-3-5-sonnet-20240620",
}, },
ENTERPRISE: { ENTERPRISE: {
generations: 1000, generations: 1000,
maxTokens: 4096, maxTokens: 4096,
model: "claude-3-5-sonnet-20240620", model: "claude-3-5-sonnet-20240620",
}, },
} }