Merge branch 'main' into feat/profile-page
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { Room } from "@/components/editor/live/room"
|
||||
// import { Room } from "@/components/editor/live/room"
|
||||
import Loading from "@/components/editor/loading"
|
||||
import Navbar from "@/components/editor/navbar"
|
||||
import { TerminalProvider } from "@/context/TerminalContext"
|
||||
@ -51,7 +51,11 @@ const getSharedUsers = async (usersToSandboxes: UsersToSandboxes[]) => {
|
||||
}
|
||||
)
|
||||
const userData: User = await userRes.json()
|
||||
return { id: userData.id, name: userData.name, avatarUrl: userData.avatarUrl }
|
||||
return {
|
||||
id: userData.id,
|
||||
name: userData.name,
|
||||
avatarUrl: userData.avatarUrl,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
@ -89,18 +93,20 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
||||
return (
|
||||
<>
|
||||
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
||||
<Room id={sandboxId}>
|
||||
{/* <Room id={sandboxId}> */}
|
||||
<TerminalProvider>
|
||||
<Navbar
|
||||
userData={userData}
|
||||
sandboxData={sandboxData}
|
||||
shared={shared}
|
||||
shared={
|
||||
shared as { id: string; name: string; avatarUrl: string }[]
|
||||
}
|
||||
/>
|
||||
<div className="w-screen flex grow">
|
||||
<CodeEditor userData={userData} sandboxData={sandboxData} />
|
||||
</div>
|
||||
</TerminalProvider>
|
||||
</Room>
|
||||
{/* </Room> */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
171
frontend/app/api/ai/route.ts
Normal file
171
frontend/app/api/ai/route.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import { currentUser } from "@clerk/nextjs"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { TIERS } from "@/lib/tiers"
|
||||
import { templateConfigs } from "@/lib/templates"
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY!,
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const user = await currentUser()
|
||||
if (!user) {
|
||||
return new Response("Unauthorized", { status: 401 })
|
||||
}
|
||||
|
||||
// Check and potentially reset monthly usage
|
||||
const resetResponse = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user/check-reset`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ userId: user.id }),
|
||||
}
|
||||
)
|
||||
|
||||
if (!resetResponse.ok) {
|
||||
console.error("Failed to check usage reset")
|
||||
}
|
||||
|
||||
// Get user data and check tier
|
||||
const dbUser = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${user.id}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
const userData = await dbUser.json()
|
||||
|
||||
// Get tier settings
|
||||
const tierSettings = TIERS[userData.tier as keyof typeof TIERS] || TIERS.FREE
|
||||
if (userData.generations >= tierSettings.generations) {
|
||||
return new Response(
|
||||
`AI generation limit reached for your ${userData.tier || "FREE"} tier`,
|
||||
{ status: 429 }
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
messages,
|
||||
context,
|
||||
activeFileContent,
|
||||
isEditMode,
|
||||
fileName,
|
||||
line,
|
||||
templateType
|
||||
} = await request.json()
|
||||
|
||||
// Get template configuration
|
||||
const templateConfig = templateConfigs[templateType]
|
||||
|
||||
// Create template context
|
||||
const templateContext = templateConfig ? `
|
||||
Project Template: ${templateConfig.name}
|
||||
|
||||
File Structure:
|
||||
${Object.entries(templateConfig.fileStructure)
|
||||
.map(([path, info]) => `${path} - ${info.description}`)
|
||||
.join('\n')}
|
||||
|
||||
Conventions:
|
||||
${templateConfig.conventions.join('\n')}
|
||||
|
||||
Dependencies:
|
||||
${JSON.stringify(templateConfig.dependencies, null, 2)}
|
||||
` : ''
|
||||
|
||||
// Create system message based on mode
|
||||
let systemMessage
|
||||
if (isEditMode) {
|
||||
systemMessage = `You are an AI code editor working in a ${templateType} project. Your task is to modify the given code based on the user's instructions. Only output the modified code, without any explanations or markdown formatting. The code should be a direct replacement for the existing code. If there is no code to modify, refer to the active file content and only output the code that is relevant to the user's instructions.
|
||||
|
||||
${templateContext}
|
||||
|
||||
File: ${fileName}
|
||||
Line: ${line}
|
||||
|
||||
Context:
|
||||
${context || "No additional context provided"}
|
||||
|
||||
Active File Content:
|
||||
${activeFileContent}
|
||||
|
||||
Instructions: ${messages[0].content}
|
||||
|
||||
Respond only with the modified code that can directly replace the existing code.`
|
||||
} else {
|
||||
systemMessage = `You are an intelligent programming assistant for a ${templateType} project. Please respond to the following request concisely. If your response includes code, please format it using triple backticks (\`\`\`) with the appropriate language identifier. For example:
|
||||
|
||||
\`\`\`python
|
||||
print("Hello, World!")
|
||||
\`\`\`
|
||||
|
||||
Provide a clear and concise explanation along with any code snippets. Keep your response brief and to the point.
|
||||
|
||||
This is the project template:
|
||||
${templateContext}
|
||||
|
||||
${context ? `Context:\n${context}\n` : ""}
|
||||
${activeFileContent ? `Active File Content:\n${activeFileContent}\n` : ""}`
|
||||
}
|
||||
|
||||
// Create stream response
|
||||
const stream = await anthropic.messages.create({
|
||||
model: tierSettings.model,
|
||||
max_tokens: tierSettings.maxTokens,
|
||||
system: systemMessage,
|
||||
messages: messages.map((msg: { role: string; content: string }) => ({
|
||||
role: msg.role === "human" ? "user" : "assistant",
|
||||
content: msg.content,
|
||||
})),
|
||||
stream: true,
|
||||
})
|
||||
|
||||
// Increment user's generation count
|
||||
await fetch(
|
||||
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user/increment-generations`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ userId: user.id }),
|
||||
}
|
||||
)
|
||||
|
||||
// Return streaming response
|
||||
const encoder = new TextEncoder()
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
|
||||
controller.enqueue(encoder.encode(chunk.delta.text))
|
||||
}
|
||||
}
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("AI generation error:", error)
|
||||
return new Response(
|
||||
error instanceof Error ? error.message : "Internal Server Error",
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
@ -1,57 +1,61 @@
|
||||
import { colors } from "@/lib/colors"
|
||||
import { User } from "@/lib/types"
|
||||
// import { colors } from "@/lib/colors"
|
||||
// import { User } from "@/lib/types"
|
||||
import { currentUser } from "@clerk/nextjs"
|
||||
import { Liveblocks } from "@liveblocks/node"
|
||||
// import { Liveblocks } from "@liveblocks/node"
|
||||
import { NextRequest } from "next/server"
|
||||
|
||||
const API_KEY = process.env.LIVEBLOCKS_SECRET_KEY!
|
||||
// const API_KEY = process.env.LIVEBLOCKS_SECRET_KEY!
|
||||
|
||||
const liveblocks = new Liveblocks({
|
||||
secret: API_KEY!,
|
||||
})
|
||||
// const liveblocks = new Liveblocks({
|
||||
// secret: API_KEY!,
|
||||
// })
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const clerkUser = await currentUser()
|
||||
// Temporarily return unauthorized while Liveblocks is disabled
|
||||
return new Response("Liveblocks collaboration temporarily disabled", { status: 503 })
|
||||
|
||||
if (!clerkUser) {
|
||||
return new Response("Unauthorized", { status: 401 })
|
||||
}
|
||||
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${clerkUser.id}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
const user = (await res.json()) as User
|
||||
|
||||
const colorNames = Object.keys(colors)
|
||||
const randomColor = colorNames[
|
||||
Math.floor(Math.random() * colorNames.length)
|
||||
] as keyof typeof colors
|
||||
const code = colors[randomColor]
|
||||
|
||||
// Create a session for the current user
|
||||
// userInfo is made available in Liveblocks presence hooks, e.g. useOthers
|
||||
const session = liveblocks.prepareSession(user.id, {
|
||||
userInfo: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
color: randomColor,
|
||||
},
|
||||
})
|
||||
|
||||
// Give the user access to the room
|
||||
user.sandbox.forEach((sandbox) => {
|
||||
session.allow(`${sandbox.id}`, session.FULL_ACCESS)
|
||||
})
|
||||
user.usersToSandboxes.forEach((userToSandbox) => {
|
||||
session.allow(`${userToSandbox.sandboxId}`, session.FULL_ACCESS)
|
||||
})
|
||||
|
||||
// Authorize the user and return the result
|
||||
const { body, status } = await session.authorize()
|
||||
return new Response(body, { status })
|
||||
// Original implementation commented out:
|
||||
// const clerkUser = await currentUser()
|
||||
//
|
||||
// if (!clerkUser) {
|
||||
// return new Response("Unauthorized", { status: 401 })
|
||||
// }
|
||||
//
|
||||
// const res = await fetch(
|
||||
// `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${clerkUser.id}`,
|
||||
// {
|
||||
// headers: {
|
||||
// Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
||||
// },
|
||||
// }
|
||||
// )
|
||||
// const user = (await res.json()) as User
|
||||
//
|
||||
// const colorNames = Object.keys(colors)
|
||||
// const randomColor = colorNames[
|
||||
// Math.floor(Math.random() * colorNames.length)
|
||||
// ] as keyof typeof colors
|
||||
// const code = colors[randomColor]
|
||||
//
|
||||
// // Create a session for the current user
|
||||
// // userInfo is made available in Liveblocks presence hooks, e.g. useOthers
|
||||
// const session = liveblocks.prepareSession(user.id, {
|
||||
// userInfo: {
|
||||
// name: user.name,
|
||||
// email: user.email,
|
||||
// color: randomColor,
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// // Give the user access to the room
|
||||
// user.sandbox.forEach((sandbox) => {
|
||||
// session.allow(`${sandbox.id}`, session.FULL_ACCESS)
|
||||
// })
|
||||
// user.usersToSandboxes.forEach((userToSandbox) => {
|
||||
// session.allow(`${userToSandbox.sandboxId}`, session.FULL_ACCESS)
|
||||
// })
|
||||
//
|
||||
// // Authorize the user and return the result
|
||||
// const { body, status } = await session.authorize()
|
||||
// return new Response(body, { status })
|
||||
}
|
||||
|
42
frontend/app/api/user/upgrade-tier/route.ts
Normal file
42
frontend/app/api/user/upgrade-tier/route.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { currentUser } from "@clerk/nextjs"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const user = await currentUser()
|
||||
if (!user) {
|
||||
return new Response("Unauthorized", { status: 401 })
|
||||
}
|
||||
|
||||
const { tier } = await request.json()
|
||||
|
||||
// handle payment processing here
|
||||
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user/update-tier`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userId: user.id,
|
||||
tier,
|
||||
tierExpiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to upgrade tier")
|
||||
}
|
||||
|
||||
return new Response("Tier upgraded successfully")
|
||||
} catch (error) {
|
||||
console.error("Tier upgrade error:", error)
|
||||
return new Response(
|
||||
error instanceof Error ? error.message : "Internal Server Error",
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { Toaster } from "@/components/ui/sonner"
|
||||
import { ThemeProvider } from "@/components/ui/theme-provider"
|
||||
import { PreviewProvider } from "@/context/PreviewContext"
|
||||
import { SocketProvider } from '@/context/SocketContext'
|
||||
import { SocketProvider } from "@/context/SocketContext"
|
||||
import { ClerkProvider } from "@clerk/nextjs"
|
||||
import { Analytics } from "@vercel/analytics/react"
|
||||
import { GeistMono } from "geist/font/mono"
|
||||
|
Reference in New Issue
Block a user