chore: formatting the code of recent changes
This commit is contained in:
parent
fbc56dc7fd
commit
5af35f6920
@ -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
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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])
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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 />
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -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",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user