chore: format frontend code

This commit is contained in:
Akhilesh Rangani 2024-11-17 12:35:56 -05:00 committed by James Murdza
parent 5216a9d897
commit 062e8d9226
30 changed files with 735 additions and 568 deletions

View File

@ -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,
}
})
)
@ -94,7 +98,9 @@ export default async function CodePage({ params }: { params: { id: string } }) {
<Navbar
userData={userData}
sandboxData={sandboxData}
shared={shared as { id: string; name: string; avatarUrl: string }[]}
shared={
shared as { id: string; name: string; avatarUrl: string }[]
}
/>
<div className="w-screen flex grow">
<CodeEditor userData={userData} sandboxData={sandboxData} />

View File

@ -1,7 +1,7 @@
import { User } from "@/lib/types"
import { generateUniqueUsername } from "@/lib/username-generator"
import { currentUser } from "@clerk/nextjs"
import { redirect } from "next/navigation"
import { generateUniqueUsername } from "@/lib/username-generator";
export default async function AppAuthLayout({
children,
@ -27,10 +27,12 @@ export default async function AppAuthLayout({
if (!dbUserJSON.id) {
// Try to get GitHub username if available
const githubUsername = user.externalAccounts.find(
account => account.provider === "github"
)?.username;
(account) => account.provider === "github"
)?.username
const username = githubUsername || await generateUniqueUsername(async (username) => {
const username =
githubUsername ||
(await generateUniqueUsername(async (username) => {
// Check if username exists in database
const userCheck = await fetch(
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user/check-username?username=${username}`,
@ -42,7 +44,7 @@ export default async function AppAuthLayout({
)
const exists = await userCheck.json()
return exists.exists
});
}))
const res = await fetch(
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user`,
@ -64,11 +66,11 @@ export default async function AppAuthLayout({
)
if (!res.ok) {
const error = await res.text();
console.error("Failed to create user:", error);
const error = await res.text()
console.error("Failed to create user:", error)
} else {
const data = await res.json();
console.log("User created successfully:", data);
const data = await res.json()
console.log("User created successfully:", data)
}
}

View File

@ -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"

View File

@ -6,12 +6,12 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table"
import { projectTemplates } from "@/lib/data"
import { ChevronRight } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import Avatar from "../ui/avatar"
import Button from "../ui/customButton"
import { projectTemplates } from "@/lib/data"
export default function DashboardSharedWithMe({
shared,
@ -47,7 +47,8 @@ export default function DashboardSharedWithMe({
<Image
alt=""
src={
projectTemplates.find((p) => p.id === sandbox.type)?.icon ?? "/project-icons/node.svg"
projectTemplates.find((p) => p.id === sandbox.type)
?.icon ?? "/project-icons/node.svg"
}
width={20}
height={20}

View File

@ -1,10 +1,9 @@
import { Send, StopCircle, Image as ImageIcon, Paperclip } from "lucide-react"
import { Button } from "../../ui/button"
import { useEffect } from "react"
import { TFile, TFolder } from "@/lib/types"
import { ALLOWED_FILE_TYPES } from "./types"
import { Image as ImageIcon, Paperclip, Send, StopCircle } from "lucide-react"
import { useEffect } from "react"
import { Button } from "../../ui/button"
import { looksLikeCode } from "./lib/chatUtils"
import { ChatInputProps } from "./types"
import { ALLOWED_FILE_TYPES, ChatInputProps } from "./types"
export default function ChatInput({
input,
@ -21,12 +20,11 @@ export default function ChatInput({
onRemoveTab,
textareaRef,
}: ChatInputProps) {
// Auto-resize textarea as content changes
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = 'auto'
textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'
textareaRef.current.style.height = "auto"
textareaRef.current.style.height = textareaRef.current.scrollHeight + "px"
}
}, [input])
@ -40,7 +38,11 @@ export default function ChatInput({
e.preventDefault()
handleSend(false)
}
} else if (e.key === "Backspace" && input === "" && contextTabs.length > 0) {
} else if (
e.key === "Backspace" &&
input === "" &&
contextTabs.length > 0
) {
e.preventDefault()
// Remove the last context tab
const lastTab = contextTabs[contextTabs.length - 1]
@ -51,49 +53,51 @@ export default function ChatInput({
// Handle paste events for image and code
const handlePaste = async (e: React.ClipboardEvent) => {
// Handle image paste
const items = Array.from(e.clipboardData.items);
const items = Array.from(e.clipboardData.items)
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
if (item.type.startsWith("image/")) {
e.preventDefault()
const file = item.getAsFile();
if (!file) continue;
const file = item.getAsFile()
if (!file) continue
try {
// Convert image to base64 string for context tab title and timestamp
const reader = new FileReader();
const reader = new FileReader()
reader.onload = () => {
const base64String = reader.result as string;
const base64String = reader.result as string
addContextTab(
"image",
`Image ${new Date().toLocaleTimeString('en-US', {
`Image ${new Date()
.toLocaleTimeString("en-US", {
hour12: true,
hour: '2-digit',
minute: '2-digit'
}).replace(/(\d{2}):(\d{2})/, '$1:$2')}`,
hour: "2-digit",
minute: "2-digit",
})
.replace(/(\d{2}):(\d{2})/, "$1:$2")}`,
base64String
);
};
reader.readAsDataURL(file);
} catch (error) {
console.error('Error processing pasted image:', error);
)
}
return;
reader.readAsDataURL(file)
} catch (error) {
console.error("Error processing pasted image:", error)
}
return
}
}
// Get text from clipboard
const text = e.clipboardData.getData('text');
const text = e.clipboardData.getData("text")
// If text doesn't contain newlines or doesn't look like code, let it paste normally
if (!text || !text.includes('\n') || !looksLikeCode(text)) {
return;
if (!text || !text.includes("\n") || !looksLikeCode(text)) {
return
}
e.preventDefault();
const editor = editorRef.current;
const currentSelection = editor?.getSelection();
const lines = text.split('\n');
e.preventDefault()
const editor = editorRef.current
const currentSelection = editor?.getSelection()
const lines = text.split("\n")
// TODO: FIX THIS: even when i paste the outside code, it shows the active file name,it works when no tabs are open, just does not work when the tab is open
@ -103,37 +107,38 @@ export default function ChatInput({
"code",
`${activeFileName} (${currentSelection.startLineNumber}-${currentSelection.endLineNumber})`,
text,
{ start: currentSelection.startLineNumber, end: currentSelection.endLineNumber }
);
return;
{
start: currentSelection.startLineNumber,
end: currentSelection.endLineNumber,
}
)
return
}
// If we have stored line range from a copy operation in the editor
if (lastCopiedRangeRef.current) {
const range = lastCopiedRangeRef.current;
const range = lastCopiedRangeRef.current
addContextTab(
"code",
`${activeFileName} (${range.startLine}-${range.endLine})`,
text,
{ start: range.startLine, end: range.endLine }
);
return;
)
return
}
// For code pasted from outside the editor
addContextTab(
"code",
`Pasted Code (1-${lines.length})`,
text,
{ start: 1, end: lines.length }
);
};
addContextTab("code", `Pasted Code (1-${lines.length})`, text, {
start: 1,
end: lines.length,
})
}
// Handle image upload from local machine via input
const handleImageUpload = () => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
const input = document.createElement("input")
input.type = "file"
input.accept = "image/*"
input.onchange = (e) => {
const file = (e.target as HTMLInputElement).files?.[0]
if (file) onImageUpload(file)
@ -155,14 +160,16 @@ export default function ChatInput({
// Handle file upload from local machine via input
const handleFileUpload = () => {
const input = document.createElement('input')
input.type = 'file'
input.accept = '.txt,.md,.csv,.json,.js,.ts,.html,.css,.pdf'
const input = document.createElement("input")
input.type = "file"
input.accept = ".txt,.md,.csv,.json,.js,.ts,.html,.css,.pdf"
input.onchange = (e) => {
const file = (e.target as HTMLInputElement).files?.[0]
if (file) {
if (!(file.type in ALLOWED_FILE_TYPES)) {
alert('Unsupported file type. Please upload text, code, or PDF files.')
alert(
"Unsupported file type. Please upload text, code, or PDF files."
)
return
}
@ -236,4 +243,3 @@ export default function ChatInput({
</div>
)
}

View File

@ -3,9 +3,9 @@ import React, { useState } from "react"
import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm"
import { Button } from "../../ui/button"
import { copyToClipboard, stringifyContent } from "./lib/chatUtils"
import ContextTabs from "./ContextTabs"
import { createMarkdownComponents } from './lib/markdownComponents'
import { copyToClipboard, stringifyContent } from "./lib/chatUtils"
import { createMarkdownComponents } from "./lib/markdownComponents"
import { MessageProps } from "./types"
export default function ChatMessage({
@ -14,7 +14,6 @@ export default function ChatMessage({
setIsContextExpanded,
socket,
}: MessageProps) {
// State for expanded message index
const [expandedMessageIndex, setExpandedMessageIndex] = useState<
number | null
@ -45,10 +44,10 @@ export default function ChatMessage({
const newContext = `Regarding this code:\n${contextString}`
// Format timestamp to match chat message format (HH:MM PM)
const timestamp = new Date().toLocaleTimeString('en-US', {
const timestamp = new Date().toLocaleTimeString("en-US", {
hour12: true,
hour: '2-digit',
minute: '2-digit',
hour: "2-digit",
minute: "2-digit",
})
// Instead of replacing context, append to it
@ -56,13 +55,13 @@ export default function ChatMessage({
// For assistant messages, create a new context tab with the response content and timestamp
setContext(newContext, `AI Response (${timestamp})`, {
start: 1,
end: contextString.split('\n').length
end: contextString.split("\n").length,
})
} else {
// For user messages, create a new context tab with the selected content and timestamp
setContext(newContext, `User Chat (${timestamp})`, {
start: 1,
end: contextString.split('\n').length
end: contextString.split("\n").length,
})
}
setIsContextExpanded(false)
@ -127,7 +126,9 @@ export default function ChatMessage({
contextTabs={parseContextToTabs(message.context)}
onRemoveTab={() => {}}
isExpanded={expandedMessageIndex === 0}
onToggleExpand={() => setExpandedMessageIndex(expandedMessageIndex === 0 ? null : 0)}
onToggleExpand={() =>
setExpandedMessageIndex(expandedMessageIndex === 0 ? null : 0)
}
className="[&_div:first-child>div:first-child>div]:bg-[#0D0D0D] [&_button:first-child]:hidden [&_button:last-child]:hidden"
/>
{expandedMessageIndex === 0 && (
@ -153,7 +154,7 @@ export default function ChatMessage({
const updatedContext = `Regarding this code:\n${e.target.value}`
setContext(updatedContext, "Selected Content", {
start: 1,
end: e.target.value.split('\n').length
end: e.target.value.split("\n").length,
})
}}
className="w-full p-2 bg-[#1e1e1e] text-white font-mono text-sm rounded"
@ -187,10 +188,7 @@ export default function ChatMessage({
)}
{/* Render markdown content */}
{message.role === "assistant" ? (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={components}
>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
{message.content}
</ReactMarkdown>
) : (
@ -204,23 +202,25 @@ export default function ChatMessage({
// Parse context to tabs for context tabs component
function parseContextToTabs(context: string) {
const sections = context.split(/(?=File |Code from )/)
return sections.map((section, index) => {
const lines = section.trim().split('\n')
return sections
.map((section, index) => {
const lines = section.trim().split("\n")
const titleLine = lines[0]
let content = lines.slice(1).join('\n').trim()
let content = lines.slice(1).join("\n").trim()
// Remove code block markers for display
content = content.replace(/^```[\w-]*\n/, '').replace(/\n```$/, '')
content = content.replace(/^```[\w-]*\n/, "").replace(/\n```$/, "")
// Determine if the context is a file or code
const isFile = titleLine.startsWith('File ')
const name = titleLine.replace(/^(File |Code from )/, '').replace(':', '')
const isFile = titleLine.startsWith("File ")
const name = titleLine.replace(/^(File |Code from )/, "").replace(":", "")
return {
id: `context-${index}`,
type: isFile ? "file" as const : "code" as const,
type: isFile ? ("file" as const) : ("code" as const),
name: name,
content: content
content: content,
}
}).filter(tab => tab.content.length > 0)
})
.filter((tab) => tab.content.length > 0)
}

View File

@ -1,15 +1,14 @@
import { Plus, X, Image as ImageIcon, FileText } from "lucide-react"
import { useState } from "react"
import { Button } from "../../ui/button"
import { TFile, TFolder } from "@/lib/types"
import { Input } from "@/components/ui/input"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { Input } from "@/components/ui/input"
import { ContextTab } from "./types"
import { ContextTabsProps } from "./types"
import { TFile, TFolder } from "@/lib/types"
import { FileText, Image as ImageIcon, Plus, X } from "lucide-react"
import { useState } from "react"
import { Button } from "../../ui/button"
import { ContextTab, ContextTabsProps } from "./types"
// Ignore certain folders and files from the file tree
import { ignoredFiles, ignoredFolders } from "./lib/ignored-paths"
@ -20,7 +19,6 @@ export default function ContextTabs({
files = [],
onFileSelect,
}: ContextTabsProps & { className?: string }) {
// State for preview tab
const [previewTab, setPreviewTab] = useState<ContextTab | null>(null)
const [searchQuery, setSearchQuery] = useState("")
@ -28,7 +26,7 @@ export default function ContextTabs({
// Allow preview for images and code selections from editor
const togglePreview = (tab: ContextTab) => {
if (!tab.lineRange && tab.type !== "image") {
return;
return
}
// Toggle preview for images and code selections from editor
@ -51,12 +49,20 @@ export default function ContextTabs({
const getAllFiles = (items: (TFile | TFolder)[]): TFile[] => {
return items.reduce((acc: TFile[], item) => {
// Add file if it's not ignored
if (item.type === "file" && !ignoredFiles.some((pattern: string) =>
item.name.endsWith(pattern.replace('*', '')) || item.name === pattern
)) {
if (
item.type === "file" &&
!ignoredFiles.some(
(pattern: string) =>
item.name.endsWith(pattern.replace("*", "")) ||
item.name === pattern
)
) {
acc.push(item)
// Add all files from folder if it's not ignored
} else if (item.type === "folder" && !ignoredFolders.some((folder: string) => folder === item.name)) {
} else if (
item.type === "folder" &&
!ignoredFolders.some((folder: string) => folder === item.name)
) {
acc.push(...getAllFiles(item.children))
}
return acc
@ -65,22 +71,18 @@ export default function ContextTabs({
// Get all files from the file tree to search for context when adding context
const allFiles = getAllFiles(files)
const filteredFiles = allFiles.filter(file =>
const filteredFiles = allFiles.filter((file) =>
file.name.toLowerCase().includes(searchQuery.toLowerCase())
)
return (
<div className={`border-none ${className || ''}`}>
<div className={`border-none ${className || ""}`}>
<div className="flex flex-col">
<div className="flex items-center gap-1 overflow-hidden mb-2 flex-wrap">
{/* Add context tab button */}
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
>
<Button variant="ghost" size="icon" className="h-6 w-6">
<Plus className="h-4 w-4" />
</Button>
</PopoverTrigger>
@ -148,15 +150,18 @@ export default function ContextTabs({
alt={previewTab.name}
className="max-w-full h-auto"
/>
) : previewTab.lineRange && (
) : (
previewTab.lineRange && (
<>
<div className="text-xs text-muted-foreground mt-1">
Lines {previewTab.lineRange.start}-{previewTab.lineRange.end}
Lines {previewTab.lineRange.start}-
{previewTab.lineRange.end}
</div>
<pre className="text-xs font-mono whitespace-pre-wrap">
{previewTab.content}
</pre>
</>
)
)}
{/* Render file context tab */}
{previewTab.type === "file" && (

View File

@ -1,14 +1,14 @@
import { useSocket } from "@/context/SocketContext"
import { TFile } from "@/lib/types"
import { X } from "lucide-react"
import { nanoid } from "nanoid"
import { useEffect, useRef, useState } from "react"
import LoadingDots from "../../ui/LoadingDots"
import ChatInput from "./ChatInput"
import ChatMessage from "./ChatMessage"
import ContextTabs from "./ContextTabs"
import { handleSend, handleStopGeneration } from "./lib/chatUtils"
import { nanoid } from 'nanoid'
import { TFile } from "@/lib/types"
import { useSocket } from "@/context/SocketContext"
import { Message, ContextTab, AIChatProps } from './types'
import { AIChatProps, ContextTab, Message } from "./types"
export default function AIChat({
activeFileContent,
@ -56,47 +56,54 @@ export default function AIChat({
}
// Add context tab to context tabs
const addContextTab = (type: string, name: string, content: string, lineRange?: { start: number; end: number }) => {
const addContextTab = (
type: string,
name: string,
content: string,
lineRange?: { start: number; end: number }
) => {
const newTab = {
id: nanoid(),
type: type as "file" | "code" | "image",
name,
content,
lineRange
lineRange,
}
setContextTabs(prev => [...prev, newTab])
setContextTabs((prev) => [...prev, newTab])
}
// Remove context tab from context tabs
const removeContextTab = (id: string) => {
setContextTabs(prev => prev.filter(tab => tab.id !== id))
setContextTabs((prev) => prev.filter((tab) => tab.id !== id))
}
// Add file to context tabs
const handleAddFile = (tab: ContextTab) => {
setContextTabs(prev => [...prev, tab])
setContextTabs((prev) => [...prev, tab])
}
// Format code content to remove starting and ending code block markers if they exist
const formatCodeContent = (content: string) => {
return content.replace(/^```[\w-]*\n/, '').replace(/\n```$/, '')
return content.replace(/^```[\w-]*\n/, "").replace(/\n```$/, "")
}
// Get combined context from context tabs
const getCombinedContext = () => {
if (contextTabs.length === 0) return ''
if (contextTabs.length === 0) return ""
return contextTabs.map(tab => {
if (tab.type === 'file') {
const fileExt = tab.name.split('.').pop() || 'txt'
return contextTabs
.map((tab) => {
if (tab.type === "file") {
const fileExt = tab.name.split(".").pop() || "txt"
const cleanContent = formatCodeContent(tab.content)
return `File ${tab.name}:\n\`\`\`${fileExt}\n${cleanContent}\n\`\`\``
} else if (tab.type === 'code') {
} else if (tab.type === "code") {
const cleanContent = formatCodeContent(tab.content)
return `Code from ${tab.name}:\n\`\`\`typescript\n${cleanContent}\n\`\`\``
}
return `${tab.name}:\n${tab.content}`
}).join('\n\n')
})
.join("\n\n")
}
// Handle sending message with context
@ -122,7 +129,7 @@ export default function AIChat({
const setContext = (
context: string | null,
name: string,
range?: { start: number, end: number }
range?: { start: number; end: number }
) => {
if (!context) {
setContextTabs([])
@ -130,7 +137,7 @@ export default function AIChat({
}
// Always add a new tab instead of updating existing ones
addContextTab('code', name, context, range)
addContextTab("code", name, context, range)
}
return (
@ -180,9 +187,9 @@ export default function AIChat({
socket={socket}
onFileSelect={(file: TFile) => {
socket?.emit("getFile", { fileId: file.id }, (response: string) => {
const fileExt = file.name.split('.').pop() || 'txt'
const fileExt = file.name.split(".").pop() || "txt"
const formattedContent = `\`\`\`${fileExt}\n${response}\n\`\`\``
addContextTab('file', file.name, formattedContent)
addContextTab("file", file.name, formattedContent)
if (textareaRef.current) {
textareaRef.current.focus()
}
@ -210,9 +217,9 @@ export default function AIChat({
}}
lastCopiedRangeRef={lastCopiedRangeRef}
activeFileName={activeFileName}
contextTabs={contextTabs.map(tab => ({
contextTabs={contextTabs.map((tab) => ({
...tab,
title: tab.id
title: tab.id,
}))}
onRemoveTab={removeContextTab}
/>

View File

@ -95,18 +95,20 @@ export const handleSend = async (
if (input.trim() === "" && !context) return
// Get timestamp for chat message component
const timestamp = new Date().toLocaleTimeString('en-US', {
const timestamp = new Date()
.toLocaleTimeString("en-US", {
hour12: true,
hour: '2-digit',
minute: '2-digit'
}).replace(/(\d{2}):(\d{2})/, '$1:$2')
hour: "2-digit",
minute: "2-digit",
})
.replace(/(\d{2}):(\d{2})/, "$1:$2")
// Create user message for chat message component
const userMessage = {
role: "user" as const,
content: input,
context: context || undefined,
timestamp: timestamp
timestamp: timestamp,
}
// Update messages for chat message component
@ -229,7 +231,7 @@ export const looksLikeCode = (text: string): boolean => {
/^\s*\/\*/m, // multi-line comments
/=>/, // arrow functions
/^export\s+/m, // export statements
];
]
return codeIndicators.some(pattern => pattern.test(text));
};
return codeIndicators.some((pattern) => pattern.test(text))
}

View File

@ -2,101 +2,101 @@
export const ignoredFolders = [
// Package managers
'node_modules',
'venv',
'.env',
'env',
'.venv',
'virtualenv',
'pip-wheel-metadata',
"node_modules",
"venv",
".env",
"env",
".venv",
"virtualenv",
"pip-wheel-metadata",
// Build outputs
'.next',
'dist',
'build',
'out',
'__pycache__',
'.webpack',
'.serverless',
'storybook-static',
".next",
"dist",
"build",
"out",
"__pycache__",
".webpack",
".serverless",
"storybook-static",
// Version control
'.git',
'.svn',
'.hg', // Mercurial
".git",
".svn",
".hg", // Mercurial
// Cache and temp files
'.cache',
'coverage',
'tmp',
'.temp',
'.npm',
'.pnpm',
'.yarn',
'.eslintcache',
'.stylelintcache',
".cache",
"coverage",
"tmp",
".temp",
".npm",
".pnpm",
".yarn",
".eslintcache",
".stylelintcache",
// IDE specific
'.idea',
'.vscode',
'.vs',
'.sublime',
".idea",
".vscode",
".vs",
".sublime",
// Framework specific
'.streamlit',
'.next',
'static',
'.pytest_cache',
'.nuxt',
'.docusaurus',
'.remix',
'.parcel-cache',
'public/build', // Remix/Rails
'.turbo', // Turborepo
".streamlit",
".next",
"static",
".pytest_cache",
".nuxt",
".docusaurus",
".remix",
".parcel-cache",
"public/build", // Remix/Rails
".turbo", // Turborepo
// Logs
'logs',
'*.log',
'npm-debug.log*',
'yarn-debug.log*',
'yarn-error.log*',
'pnpm-debug.log*',
] as const;
"logs",
"*.log",
"npm-debug.log*",
"yarn-debug.log*",
"yarn-error.log*",
"pnpm-debug.log*",
] as const
export const ignoredFiles = [
'.DS_Store',
'.env.local',
'.env.development',
'.env.production',
'.env.test',
'.env*.local',
'.gitignore',
'.npmrc',
'.yarnrc',
'.editorconfig',
'.prettierrc',
'.eslintrc',
'.browserslistrc',
'tsconfig.tsbuildinfo',
'*.pyc',
'*.pyo',
'*.pyd',
'*.so',
'*.dll',
'*.dylib',
'*.class',
'*.exe',
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml',
'composer.lock',
'poetry.lock',
'Gemfile.lock',
'*.min.js',
'*.min.css',
'*.map',
'*.chunk.*',
'*.hot-update.*',
'.vercel',
'.netlify'
] as const;
export const ignoredFiles = [
".DS_Store",
".env.local",
".env.development",
".env.production",
".env.test",
".env*.local",
".gitignore",
".npmrc",
".yarnrc",
".editorconfig",
".prettierrc",
".eslintrc",
".browserslistrc",
"tsconfig.tsbuildinfo",
"*.pyc",
"*.pyo",
"*.pyd",
"*.so",
"*.dll",
"*.dylib",
"*.class",
"*.exe",
"package-lock.json",
"yarn.lock",
"pnpm-lock.yaml",
"composer.lock",
"poetry.lock",
"Gemfile.lock",
"*.min.js",
"*.min.css",
"*.map",
"*.chunk.*",
"*.hot-update.*",
".vercel",
".netlify",
] as const

View File

@ -1,8 +1,8 @@
import { CornerUpLeft } from "lucide-react"
import { Components } from "react-markdown"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"
import { Button } from "../../../ui/button"
import { CornerUpLeft } from "lucide-react"
import { stringifyContent } from "./chatUtils"
// Create markdown components for chat message component
@ -11,11 +11,16 @@ export const createMarkdownComponents = (
renderMarkdownElement: (props: any) => JSX.Element,
askAboutCode: (code: any) => void
): Components => ({
code: ({ node, className, children, ...props }: {
node?: import('hast').Element,
className?: string,
children?: React.ReactNode,
[key: string]: any,
code: ({
node,
className,
children,
...props
}: {
node?: import("hast").Element
className?: string
children?: React.ReactNode
[key: string]: any
}) => {
const match = /language-(\w+)/.exec(className || "")
@ -55,25 +60,30 @@ export const createMarkdownComponents = (
</div>
</div>
) : (
<code className={className} {...props}>{children}</code>
<code className={className} {...props}>
{children}
</code>
)
},
// Render markdown elements
p: ({ node, children, ...props }) => renderMarkdownElement({ node, children, ...props }),
h1: ({ node, children, ...props }) => renderMarkdownElement({ node, children, ...props }),
h2: ({ node, children, ...props }) => renderMarkdownElement({ node, children, ...props }),
h3: ({ node, children, ...props }) => renderMarkdownElement({ node, children, ...props }),
h4: ({ node, children, ...props }) => renderMarkdownElement({ node, children, ...props }),
h5: ({ node, children, ...props }) => renderMarkdownElement({ node, children, ...props }),
h6: ({ node, children, ...props }) => renderMarkdownElement({ node, children, ...props }),
p: ({ node, children, ...props }) =>
renderMarkdownElement({ node, children, ...props }),
h1: ({ node, children, ...props }) =>
renderMarkdownElement({ node, children, ...props }),
h2: ({ node, children, ...props }) =>
renderMarkdownElement({ node, children, ...props }),
h3: ({ node, children, ...props }) =>
renderMarkdownElement({ node, children, ...props }),
h4: ({ node, children, ...props }) =>
renderMarkdownElement({ node, children, ...props }),
h5: ({ node, children, ...props }) =>
renderMarkdownElement({ node, children, ...props }),
h6: ({ node, children, ...props }) =>
renderMarkdownElement({ node, children, ...props }),
ul: (props) => (
<ul className="list-disc pl-6 mb-4 space-y-2">
{props.children}
</ul>
<ul className="list-disc pl-6 mb-4 space-y-2">{props.children}</ul>
),
ol: (props) => (
<ol className="list-decimal pl-6 mb-4 space-y-2">
{props.children}
</ol>
<ol className="list-decimal pl-6 mb-4 space-y-2">{props.children}</ol>
),
})

View File

@ -1,28 +1,28 @@
import * as monaco from 'monaco-editor'
import { TFile, TFolder } from "@/lib/types"
import { Socket } from 'socket.io-client';
import * as monaco from "monaco-editor"
import { Socket } from "socket.io-client"
// Allowed file types for context tabs
export const ALLOWED_FILE_TYPES = {
// Text files
'text/plain': true,
'text/markdown': true,
'text/csv': true,
"text/plain": true,
"text/markdown": true,
"text/csv": true,
// Code files
'application/json': true,
'text/javascript': true,
'text/typescript': true,
'text/html': true,
'text/css': true,
"application/json": true,
"text/javascript": true,
"text/typescript": true,
"text/html": true,
"text/css": true,
// Documents
'application/pdf': true,
"application/pdf": true,
// Images
'image/jpeg': true,
'image/png': true,
'image/gif': true,
'image/webp': true,
'image/svg+xml': true,
} as const;
"image/jpeg": true,
"image/png": true,
"image/gif": true,
"image/webp": true,
"image/svg+xml": true,
} as const
// Message interface
export interface Message {
@ -45,8 +45,13 @@ export interface AIChatProps {
activeFileContent: string
activeFileName: string
onClose: () => void
editorRef: React.MutableRefObject<monaco.editor.IStandaloneCodeEditor | undefined>
lastCopiedRangeRef: React.MutableRefObject<{ startLine: number; endLine: number } | null>
editorRef: React.MutableRefObject<
monaco.editor.IStandaloneCodeEditor | undefined
>
lastCopiedRangeRef: React.MutableRefObject<{
startLine: number
endLine: number
} | null>
files: (TFile | TFolder)[]
}
@ -58,11 +63,27 @@ export interface ChatInputProps {
handleSend: (useFullContext?: boolean) => void
handleStopGeneration: () => void
onImageUpload: (file: File) => void
addContextTab: (type: string, title: string, content: string, lineRange?: { start: number, end: number }) => void
addContextTab: (
type: string,
title: string,
content: string,
lineRange?: { start: number; end: number }
) => void
activeFileName?: string
editorRef: React.MutableRefObject<monaco.editor.IStandaloneCodeEditor | undefined>
lastCopiedRangeRef: React.MutableRefObject<{ startLine: number; endLine: number } | null>
contextTabs: { id: string; type: string; title: string; content: string; lineRange?: { start: number; end: number } }[]
editorRef: React.MutableRefObject<
monaco.editor.IStandaloneCodeEditor | undefined
>
lastCopiedRangeRef: React.MutableRefObject<{
startLine: number
endLine: number
} | null>
contextTabs: {
id: string
type: string
title: string
content: string
lineRange?: { start: number; end: number }
}[]
onRemoveTab: (id: string) => void
textareaRef: React.RefObject<HTMLTextAreaElement>
}
@ -74,7 +95,11 @@ export interface MessageProps {
content: string
context?: string
}
setContext: (context: string | null, name: string, range?: { start: number, end: number }) => void
setContext: (
context: string | null,
name: string,
range?: { start: number; end: number }
) => void
setIsContextExpanded: (isExpanded: boolean) => void
socket: Socket | null
}

View File

@ -72,7 +72,7 @@ export default function GenerateInput({
fileName: data.fileName,
code: data.code,
line: data.line,
instructions: regenerate ? currentPrompt : input
instructions: regenerate ? currentPrompt : input,
},
(res: { response: string; success: boolean }) => {
console.log("Generated code", res.response, res.success)

View File

@ -173,7 +173,10 @@ export default function CodeEditor({
const previewWindowRef = useRef<{ refreshIframe: () => void }>(null)
// Ref to store the last copied range in the editor to be used in the AIChat component
const lastCopiedRangeRef = useRef<{ startLine: number; endLine: number } | null>(null);
const lastCopiedRangeRef = useRef<{
startLine: number
endLine: number
} | null>(null)
const debouncedSetIsSelected = useRef(
debounce((value: boolean) => {
@ -260,14 +263,14 @@ export default function CodeEditor({
// Store the last copied range in the editor to be used in the AIChat component
editor.onDidChangeCursorSelection((e) => {
const selection = editor.getSelection();
const selection = editor.getSelection()
if (selection) {
lastCopiedRangeRef.current = {
startLine: selection.startLineNumber,
endLine: selection.endLineNumber
};
endLine: selection.endLineNumber,
}
});
}
})
}
// Call the function with your file structure
@ -658,7 +661,7 @@ export default function CodeEditor({
// Socket event listener effect
useEffect(() => {
const onConnect = () => { }
const onConnect = () => {}
const onDisconnect = () => {
setTerminals([])
@ -853,9 +856,13 @@ export default function CodeEditor({
}
const handleDeleteFile = (file: TFile) => {
socket?.emit("deleteFile", { fileId: file.id }, (response: (TFolder | TFile)[]) => {
socket?.emit(
"deleteFile",
{ fileId: file.id },
(response: (TFolder | TFile)[]) => {
setFiles(response)
})
}
)
closeTab(file.id)
}
@ -867,10 +874,14 @@ export default function CodeEditor({
closeTabs(response)
)
socket?.emit("deleteFolder", { folderId: folder.id }, (response: (TFolder | TFile)[]) => {
socket?.emit(
"deleteFolder",
{ folderId: folder.id },
(response: (TFolder | TFile)[]) => {
setFiles(response)
setDeletingFolderId("")
})
}
)
}
const togglePreviewPanel = () => {
@ -911,7 +922,7 @@ export default function CodeEditor({
<DisableAccessModal
message={disableAccess.message}
open={disableAccess.isDisabled}
setOpen={() => { }}
setOpen={() => {}}
/>
<Loading />
</>

View File

@ -11,10 +11,10 @@ import Link from "next/link"
import { useState } from "react"
import { Avatars } from "../live/avatars"
import DeployButtonModal from "./deploy"
import DownloadButton from "./downloadButton"
import EditSandboxModal from "./edit"
import RunButtonModal from "./run"
import ShareSandboxModal from "./share"
import DownloadButton from "./downloadButton"
export default function Navbar({
userData,
@ -79,7 +79,8 @@ export default function Navbar({
<Users className="w-4 h-4 mr-2" />
Share
</Button>
<DownloadButton name={sandboxData.name} /></>
<DownloadButton name={sandboxData.name} />
</>
) : null}
<ThemeSwitcher />
<UserButton userData={userData} />

View File

@ -143,11 +143,7 @@ export default function ShareSandboxModal({
</DialogHeader>
<div className="space-y-2">
{shared.map((user) => (
<SharedUser
key={user.id}
user={user}
sandboxId={data.id}
/>
<SharedUser key={user.id} user={user} sandboxId={data.id} />
))}
</div>
</div>

View File

@ -93,7 +93,7 @@ export default function Sidebar({
"moveFile",
{
fileId,
folderId
folderId,
},
(response: (TFolder | TFile)[]) => {
setFiles(response)
@ -214,9 +214,7 @@ export default function Sidebar({
<MessageSquareMore
className={cn(
"h-4 w-4 mr-2",
isAIChatOpen
? "text-indigo-500"
: "text-indigo-500 opacity-70"
isAIChatOpen ? "text-indigo-500" : "text-indigo-500 opacity-70"
)}
/>
AI Chat

View File

@ -1,5 +1,5 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"
import { cn } from "@/lib/utils"
@ -56,4 +56,4 @@ const AlertDescription = React.forwardRef<
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }
export { Alert, AlertDescription, AlertTitle }

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import * as React from "react"
import { cn } from "@/lib/utils"

View File

@ -6,4 +6,3 @@ import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import * as React from "react"
import { cn } from "@/lib/utils"
@ -29,4 +29,4 @@ const TooltipContent = React.forwardRef<
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }

View File

@ -63,7 +63,7 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({
terminals,
setTerminals,
setActiveTerminalId,
setClosingTerminal: () => { },
setClosingTerminal: () => {},
socket,
activeTerminalId,
})

View File

@ -38,7 +38,7 @@
"csl": "xml",
"cson": "coffeescript",
"csproj": "xml",
"css":"css",
"css": "css",
"ct": "xml",
"ctp": "php",
"cxx": "cpp",

View File

@ -74,7 +74,7 @@ function mapModule(module: string): monaco.languages.typescript.ModuleKind {
}
function mapJSX(jsx: string | undefined): monaco.languages.typescript.JsxEmit {
if (!jsx || typeof jsx !== 'string') {
if (!jsx || typeof jsx !== "string") {
return monaco.languages.typescript.JsxEmit.React // Default value
}

View File

@ -1,82 +1,181 @@
// Constants for username generation
const WORDS = {
adjectives: [
"azure", "crimson", "golden", "silver", "violet", "emerald", "cobalt", "amber", "coral", "jade",
"cyber", "digital", "quantum", "neural", "binary", "cosmic", "stellar", "atomic", "crypto", "nano",
"swift", "brave", "clever", "wise", "noble", "rapid", "bright", "sharp", "keen", "bold",
"dynamic", "epic", "mega", "ultra", "hyper", "super", "prime", "elite", "alpha", "omega",
"pixel", "vector", "sonic", "laser", "matrix", "nexus", "proxy", "cloud", "data", "tech",
"azure",
"crimson",
"golden",
"silver",
"violet",
"emerald",
"cobalt",
"amber",
"coral",
"jade",
"cyber",
"digital",
"quantum",
"neural",
"binary",
"cosmic",
"stellar",
"atomic",
"crypto",
"nano",
"swift",
"brave",
"clever",
"wise",
"noble",
"rapid",
"bright",
"sharp",
"keen",
"bold",
"dynamic",
"epic",
"mega",
"ultra",
"hyper",
"super",
"prime",
"elite",
"alpha",
"omega",
"pixel",
"vector",
"sonic",
"laser",
"matrix",
"nexus",
"proxy",
"cloud",
"data",
"tech",
],
nouns: [
"coder", "hacker", "dev", "ninja", "guru", "wizard", "admin", "mod", "chief", "boss",
"wolf", "eagle", "phoenix", "dragon", "tiger", "falcon", "shark", "lion", "hawk", "bear",
"byte", "bit", "node", "stack", "cache", "chip", "core", "net", "web", "app",
"star", "nova", "pulsar", "comet", "nebula", "quasar", "cosmos", "orbit", "astro", "solar",
"mind", "soul", "spark", "pulse", "force", "power", "wave", "storm", "flash", "surge",
"coder",
"hacker",
"dev",
"ninja",
"guru",
"wizard",
"admin",
"mod",
"chief",
"boss",
"wolf",
"eagle",
"phoenix",
"dragon",
"tiger",
"falcon",
"shark",
"lion",
"hawk",
"bear",
"byte",
"bit",
"node",
"stack",
"cache",
"chip",
"core",
"net",
"web",
"app",
"star",
"nova",
"pulsar",
"comet",
"nebula",
"quasar",
"cosmos",
"orbit",
"astro",
"solar",
"mind",
"soul",
"spark",
"pulse",
"force",
"power",
"wave",
"storm",
"flash",
"surge",
],
prefixes: [
"the", "mr", "ms", "dr", "pro", "master", "lord", "captain", "chief", "agent",
"the",
"mr",
"ms",
"dr",
"pro",
"master",
"lord",
"captain",
"chief",
"agent",
],
} as const;
} as const
// Helper function to get random element from array
const getRandomElement = <T>(array: readonly T[]): T => {
return array[Math.floor(Math.random() * array.length)];
};
// Helper function to get random element from array
const getRandomElement = <T>(array: readonly T[]): T => {
return array[Math.floor(Math.random() * array.length)]
}
// Username pattern generators
const usernamePatterns = {
// Username pattern generators
const usernamePatterns = {
basic: (): string => {
const adjective = getRandomElement(WORDS.adjectives);
const noun = getRandomElement(WORDS.nouns);
const number = Math.floor(Math.random() * 10000);
return `${adjective}${noun}${number}`;
const adjective = getRandomElement(WORDS.adjectives)
const noun = getRandomElement(WORDS.nouns)
const number = Math.floor(Math.random() * 10000)
return `${adjective}${noun}${number}`
},
prefixed: (): string => {
const prefix = getRandomElement(WORDS.prefixes);
const noun = getRandomElement(WORDS.nouns);
const number = Math.floor(Math.random() * 100);
return `${prefix}${noun}${number}`;
const prefix = getRandomElement(WORDS.prefixes)
const noun = getRandomElement(WORDS.nouns)
const number = Math.floor(Math.random() * 100)
return `${prefix}${noun}${number}`
},
doubleAdjective: (): string => {
const adj1 = getRandomElement(WORDS.adjectives);
const adj2 = getRandomElement(WORDS.adjectives);
const noun = getRandomElement(WORDS.nouns);
return `${adj1}${adj2}${noun}`;
const adj1 = getRandomElement(WORDS.adjectives)
const adj2 = getRandomElement(WORDS.adjectives)
const noun = getRandomElement(WORDS.nouns)
return `${adj1}${adj2}${noun}`
},
doubleNoun: (): string => {
const noun1 = getRandomElement(WORDS.nouns);
const noun2 = getRandomElement(WORDS.nouns);
const number = Math.floor(Math.random() * 100);
return `${noun1}${number}${noun2}`;
const noun1 = getRandomElement(WORDS.nouns)
const noun2 = getRandomElement(WORDS.nouns)
const number = Math.floor(Math.random() * 100)
return `${noun1}${number}${noun2}`
},
};
}
export function generateUsername(): string {
const patterns = Object.values(usernamePatterns);
const selectedPattern = getRandomElement(patterns);
return selectedPattern();
}
export function generateUsername(): string {
const patterns = Object.values(usernamePatterns)
const selectedPattern = getRandomElement(patterns)
return selectedPattern()
}
export async function generateUniqueUsername(
export async function generateUniqueUsername(
checkExists: (username: string) => Promise<boolean>
): Promise<string> {
const MAX_ATTEMPTS = 10;
let attempts = 0;
let username = generateUsername();
): Promise<string> {
const MAX_ATTEMPTS = 10
let attempts = 0
let username = generateUsername()
while (await checkExists(username) && attempts < MAX_ATTEMPTS) {
username = generateUsername();
attempts++;
while ((await checkExists(username)) && attempts < MAX_ATTEMPTS) {
username = generateUsername()
attempts++
}
if (attempts >= MAX_ATTEMPTS) {
// Add a large random number to ensure uniqueness
username = generateUsername() + Math.floor(Math.random() * 1000000);
username = generateUsername() + Math.floor(Math.random() * 1000000)
}
return username;
}
return username
}

View File

@ -1,5 +1,5 @@
import { createClient } from "@liveblocks/client"
import { createRoomContext, createLiveblocksContext } from "@liveblocks/react"
import { createLiveblocksContext, createRoomContext } from "@liveblocks/react"
import YLiveblocksProvider from "@liveblocks/yjs"
import { colors } from "./lib/colors"

View File

@ -1,3 +1,2 @@
declare module 'react-syntax-highlighter';
declare module 'react-syntax-highlighter/dist/esm/styles/prism';
declare module "react-syntax-highlighter"
declare module "react-syntax-highlighter/dist/esm/styles/prism"