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() 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 <Navbar
userData={userData} userData={userData}
sandboxData={sandboxData} 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"> <div className="w-screen flex grow">
<CodeEditor userData={userData} sandboxData={sandboxData} /> <CodeEditor userData={userData} sandboxData={sandboxData} />

View File

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

View File

@ -1,7 +1,7 @@
import { Toaster } from "@/components/ui/sonner" import { Toaster } from "@/components/ui/sonner"
import { ThemeProvider } from "@/components/ui/theme-provider" import { ThemeProvider } from "@/components/ui/theme-provider"
import { PreviewProvider } from "@/context/PreviewContext" import { PreviewProvider } from "@/context/PreviewContext"
import { SocketProvider } from '@/context/SocketContext' import { SocketProvider } from "@/context/SocketContext"
import { ClerkProvider } from "@clerk/nextjs" import { ClerkProvider } from "@clerk/nextjs"
import { Analytics } from "@vercel/analytics/react" import { Analytics } from "@vercel/analytics/react"
import { GeistMono } from "geist/font/mono" import { GeistMono } from "geist/font/mono"

View File

@ -6,12 +6,12 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table" } from "@/components/ui/table"
import { projectTemplates } from "@/lib/data"
import { ChevronRight } from "lucide-react" import { ChevronRight } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import Link from "next/link" import Link from "next/link"
import Avatar from "../ui/avatar" import Avatar from "../ui/avatar"
import Button from "../ui/customButton" import Button from "../ui/customButton"
import { projectTemplates } from "@/lib/data"
export default function DashboardSharedWithMe({ export default function DashboardSharedWithMe({
shared, shared,
@ -47,7 +47,8 @@ export default function DashboardSharedWithMe({
<Image <Image
alt="" alt=""
src={ 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} width={20}
height={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 { 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 { looksLikeCode } from "./lib/chatUtils"
import { ChatInputProps } from "./types" import { ALLOWED_FILE_TYPES, ChatInputProps } from "./types"
export default function ChatInput({ export default function ChatInput({
input, input,
@ -21,12 +20,11 @@ export default function ChatInput({
onRemoveTab, onRemoveTab,
textareaRef, textareaRef,
}: ChatInputProps) { }: ChatInputProps) {
// Auto-resize textarea as content changes // Auto-resize textarea as content changes
useEffect(() => { useEffect(() => {
if (textareaRef.current) { if (textareaRef.current) {
textareaRef.current.style.height = 'auto' textareaRef.current.style.height = "auto"
textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px' textareaRef.current.style.height = textareaRef.current.scrollHeight + "px"
} }
}, [input]) }, [input])
@ -40,7 +38,11 @@ export default function ChatInput({
e.preventDefault() e.preventDefault()
handleSend(false) handleSend(false)
} }
} else if (e.key === "Backspace" && input === "" && contextTabs.length > 0) { } else if (
e.key === "Backspace" &&
input === "" &&
contextTabs.length > 0
) {
e.preventDefault() e.preventDefault()
// Remove the last context tab // Remove the last context tab
const lastTab = contextTabs[contextTabs.length - 1] const lastTab = contextTabs[contextTabs.length - 1]
@ -51,49 +53,51 @@ export default function ChatInput({
// Handle paste events for image and code // Handle paste events for image and code
const handlePaste = async (e: React.ClipboardEvent) => { const handlePaste = async (e: React.ClipboardEvent) => {
// Handle image paste // Handle image paste
const items = Array.from(e.clipboardData.items); const items = Array.from(e.clipboardData.items)
for (const item of items) { for (const item of items) {
if (item.type.startsWith('image/')) { if (item.type.startsWith("image/")) {
e.preventDefault(); e.preventDefault()
const file = item.getAsFile(); const file = item.getAsFile()
if (!file) continue; if (!file) continue
try { try {
// Convert image to base64 string for context tab title and timestamp // Convert image to base64 string for context tab title and timestamp
const reader = new FileReader(); const reader = new FileReader()
reader.onload = () => { reader.onload = () => {
const base64String = reader.result as string; const base64String = reader.result as string
addContextTab( addContextTab(
"image", "image",
`Image ${new Date().toLocaleTimeString('en-US', { `Image ${new Date()
.toLocaleTimeString("en-US", {
hour12: true, hour12: true,
hour: '2-digit', hour: "2-digit",
minute: '2-digit' minute: "2-digit",
}).replace(/(\d{2}):(\d{2})/, '$1:$2')}`, })
.replace(/(\d{2}):(\d{2})/, "$1:$2")}`,
base64String 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 // 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 doesn't contain newlines or doesn't look like code, let it paste normally
if (!text || !text.includes('\n') || !looksLikeCode(text)) { if (!text || !text.includes("\n") || !looksLikeCode(text)) {
return; return
} }
e.preventDefault(); e.preventDefault()
const editor = editorRef.current; const editor = editorRef.current
const currentSelection = editor?.getSelection(); const currentSelection = editor?.getSelection()
const lines = text.split('\n'); 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 // 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", "code",
`${activeFileName} (${currentSelection.startLineNumber}-${currentSelection.endLineNumber})`, `${activeFileName} (${currentSelection.startLineNumber}-${currentSelection.endLineNumber})`,
text, text,
{ start: currentSelection.startLineNumber, end: currentSelection.endLineNumber } {
); start: currentSelection.startLineNumber,
return; end: currentSelection.endLineNumber,
}
)
return
} }
// If we have stored line range from a copy operation in the editor // If we have stored line range from a copy operation in the editor
if (lastCopiedRangeRef.current) { if (lastCopiedRangeRef.current) {
const range = lastCopiedRangeRef.current; const range = lastCopiedRangeRef.current
addContextTab( addContextTab(
"code", "code",
`${activeFileName} (${range.startLine}-${range.endLine})`, `${activeFileName} (${range.startLine}-${range.endLine})`,
text, text,
{ start: range.startLine, end: range.endLine } { start: range.startLine, end: range.endLine }
); )
return; return
} }
// For code pasted from outside the editor // For code pasted from outside the editor
addContextTab( addContextTab("code", `Pasted Code (1-${lines.length})`, text, {
"code", start: 1,
`Pasted Code (1-${lines.length})`, end: lines.length,
text, })
{ start: 1, end: lines.length } }
);
};
// Handle image upload from local machine via input // Handle image upload from local machine via input
const handleImageUpload = () => { const handleImageUpload = () => {
const input = document.createElement('input') const input = document.createElement("input")
input.type = 'file' input.type = "file"
input.accept = 'image/*' input.accept = "image/*"
input.onchange = (e) => { input.onchange = (e) => {
const file = (e.target as HTMLInputElement).files?.[0] const file = (e.target as HTMLInputElement).files?.[0]
if (file) onImageUpload(file) if (file) onImageUpload(file)
@ -155,14 +160,16 @@ export default function ChatInput({
// Handle file upload from local machine via input // Handle file upload from local machine via input
const handleFileUpload = () => { const handleFileUpload = () => {
const input = document.createElement('input') const input = document.createElement("input")
input.type = 'file' input.type = "file"
input.accept = '.txt,.md,.csv,.json,.js,.ts,.html,.css,.pdf' input.accept = ".txt,.md,.csv,.json,.js,.ts,.html,.css,.pdf"
input.onchange = (e) => { input.onchange = (e) => {
const file = (e.target as HTMLInputElement).files?.[0] const file = (e.target as HTMLInputElement).files?.[0]
if (file) { if (file) {
if (!(file.type in ALLOWED_FILE_TYPES)) { 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 return
} }
@ -236,4 +243,3 @@ export default function ChatInput({
</div> </div>
) )
} }

View File

@ -3,9 +3,9 @@ import React, { useState } from "react"
import ReactMarkdown from "react-markdown" import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm" import remarkGfm from "remark-gfm"
import { Button } from "../../ui/button" import { Button } from "../../ui/button"
import { copyToClipboard, stringifyContent } from "./lib/chatUtils"
import ContextTabs from "./ContextTabs" import ContextTabs from "./ContextTabs"
import { createMarkdownComponents } from './lib/markdownComponents' import { copyToClipboard, stringifyContent } from "./lib/chatUtils"
import { createMarkdownComponents } from "./lib/markdownComponents"
import { MessageProps } from "./types" import { MessageProps } from "./types"
export default function ChatMessage({ export default function ChatMessage({
@ -14,7 +14,6 @@ export default function ChatMessage({
setIsContextExpanded, setIsContextExpanded,
socket, socket,
}: MessageProps) { }: MessageProps) {
// State for expanded message index // State for expanded message index
const [expandedMessageIndex, setExpandedMessageIndex] = useState< const [expandedMessageIndex, setExpandedMessageIndex] = useState<
number | null number | null
@ -45,10 +44,10 @@ export default function ChatMessage({
const newContext = `Regarding this code:\n${contextString}` const newContext = `Regarding this code:\n${contextString}`
// Format timestamp to match chat message format (HH:MM PM) // 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, hour12: true,
hour: '2-digit', hour: "2-digit",
minute: '2-digit', minute: "2-digit",
}) })
// Instead of replacing context, append to it // 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 // For assistant messages, create a new context tab with the response content and timestamp
setContext(newContext, `AI Response (${timestamp})`, { setContext(newContext, `AI Response (${timestamp})`, {
start: 1, start: 1,
end: contextString.split('\n').length end: contextString.split("\n").length,
}) })
} else { } else {
// For user messages, create a new context tab with the selected content and timestamp // For user messages, create a new context tab with the selected content and timestamp
setContext(newContext, `User Chat (${timestamp})`, { setContext(newContext, `User Chat (${timestamp})`, {
start: 1, start: 1,
end: contextString.split('\n').length end: contextString.split("\n").length,
}) })
} }
setIsContextExpanded(false) setIsContextExpanded(false)
@ -127,7 +126,9 @@ export default function ChatMessage({
contextTabs={parseContextToTabs(message.context)} contextTabs={parseContextToTabs(message.context)}
onRemoveTab={() => {}} onRemoveTab={() => {}}
isExpanded={expandedMessageIndex === 0} 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" className="[&_div:first-child>div:first-child>div]:bg-[#0D0D0D] [&_button:first-child]:hidden [&_button:last-child]:hidden"
/> />
{expandedMessageIndex === 0 && ( {expandedMessageIndex === 0 && (
@ -153,7 +154,7 @@ export default function ChatMessage({
const updatedContext = `Regarding this code:\n${e.target.value}` const updatedContext = `Regarding this code:\n${e.target.value}`
setContext(updatedContext, "Selected Content", { setContext(updatedContext, "Selected Content", {
start: 1, 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" 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 */} {/* Render markdown content */}
{message.role === "assistant" ? ( {message.role === "assistant" ? (
<ReactMarkdown <ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
remarkPlugins={[remarkGfm]}
components={components}
>
{message.content} {message.content}
</ReactMarkdown> </ReactMarkdown>
) : ( ) : (
@ -204,23 +202,25 @@ export default function ChatMessage({
// Parse context to tabs for context tabs component // Parse context to tabs for context tabs component
function parseContextToTabs(context: string) { function parseContextToTabs(context: string) {
const sections = context.split(/(?=File |Code from )/) const sections = context.split(/(?=File |Code from )/)
return sections.map((section, index) => { return sections
const lines = section.trim().split('\n') .map((section, index) => {
const lines = section.trim().split("\n")
const titleLine = lines[0] 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 // 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 // Determine if the context is a file or code
const isFile = titleLine.startsWith('File ') const isFile = titleLine.startsWith("File ")
const name = titleLine.replace(/^(File |Code from )/, '').replace(':', '') const name = titleLine.replace(/^(File |Code from )/, "").replace(":", "")
return { return {
id: `context-${index}`, id: `context-${index}`,
type: isFile ? "file" as const : "code" as const, type: isFile ? ("file" as const) : ("code" as const),
name: name, 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 { Input } from "@/components/ui/input"
import { useState } from "react"
import { Button } from "../../ui/button"
import { TFile, TFolder } from "@/lib/types"
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover" } from "@/components/ui/popover"
import { Input } from "@/components/ui/input" import { TFile, TFolder } from "@/lib/types"
import { ContextTab } from "./types" import { FileText, Image as ImageIcon, Plus, X } from "lucide-react"
import { ContextTabsProps } from "./types" import { useState } from "react"
import { Button } from "../../ui/button"
import { ContextTab, ContextTabsProps } from "./types"
// Ignore certain folders and files from the file tree // Ignore certain folders and files from the file tree
import { ignoredFiles, ignoredFolders } from "./lib/ignored-paths" import { ignoredFiles, ignoredFolders } from "./lib/ignored-paths"
@ -20,7 +19,6 @@ export default function ContextTabs({
files = [], files = [],
onFileSelect, onFileSelect,
}: ContextTabsProps & { className?: string }) { }: ContextTabsProps & { className?: string }) {
// State for preview tab // State for preview tab
const [previewTab, setPreviewTab] = useState<ContextTab | null>(null) const [previewTab, setPreviewTab] = useState<ContextTab | null>(null)
const [searchQuery, setSearchQuery] = useState("") const [searchQuery, setSearchQuery] = useState("")
@ -28,7 +26,7 @@ export default function ContextTabs({
// Allow preview for images and code selections from editor // Allow preview for images and code selections from editor
const togglePreview = (tab: ContextTab) => { const togglePreview = (tab: ContextTab) => {
if (!tab.lineRange && tab.type !== "image") { if (!tab.lineRange && tab.type !== "image") {
return; return
} }
// Toggle preview for images and code selections from editor // Toggle preview for images and code selections from editor
@ -51,12 +49,20 @@ export default function ContextTabs({
const getAllFiles = (items: (TFile | TFolder)[]): TFile[] => { const getAllFiles = (items: (TFile | TFolder)[]): TFile[] => {
return items.reduce((acc: TFile[], item) => { return items.reduce((acc: TFile[], item) => {
// Add file if it's not ignored // Add file if it's not ignored
if (item.type === "file" && !ignoredFiles.some((pattern: string) => if (
item.name.endsWith(pattern.replace('*', '')) || item.name === pattern item.type === "file" &&
)) { !ignoredFiles.some(
(pattern: string) =>
item.name.endsWith(pattern.replace("*", "")) ||
item.name === pattern
)
) {
acc.push(item) acc.push(item)
// Add all files from folder if it's not ignored // 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)) acc.push(...getAllFiles(item.children))
} }
return acc return acc
@ -65,22 +71,18 @@ export default function ContextTabs({
// Get all files from the file tree to search for context when adding context // Get all files from the file tree to search for context when adding context
const allFiles = getAllFiles(files) const allFiles = getAllFiles(files)
const filteredFiles = allFiles.filter(file => const filteredFiles = allFiles.filter((file) =>
file.name.toLowerCase().includes(searchQuery.toLowerCase()) file.name.toLowerCase().includes(searchQuery.toLowerCase())
) )
return ( return (
<div className={`border-none ${className || ''}`}> <div className={`border-none ${className || ""}`}>
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex items-center gap-1 overflow-hidden mb-2 flex-wrap"> <div className="flex items-center gap-1 overflow-hidden mb-2 flex-wrap">
{/* Add context tab button */} {/* Add context tab button */}
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button variant="ghost" size="icon" className="h-6 w-6">
variant="ghost"
size="icon"
className="h-6 w-6"
>
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
@ -148,15 +150,18 @@ export default function ContextTabs({
alt={previewTab.name} alt={previewTab.name}
className="max-w-full h-auto" className="max-w-full h-auto"
/> />
) : previewTab.lineRange && ( ) : (
previewTab.lineRange && (
<> <>
<div className="text-xs text-muted-foreground mt-1"> <div className="text-xs text-muted-foreground mt-1">
Lines {previewTab.lineRange.start}-{previewTab.lineRange.end} Lines {previewTab.lineRange.start}-
{previewTab.lineRange.end}
</div> </div>
<pre className="text-xs font-mono whitespace-pre-wrap"> <pre className="text-xs font-mono whitespace-pre-wrap">
{previewTab.content} {previewTab.content}
</pre> </pre>
</> </>
)
)} )}
{/* Render file context tab */} {/* Render file context tab */}
{previewTab.type === "file" && ( {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 { X } from "lucide-react"
import { nanoid } from "nanoid"
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
import LoadingDots from "../../ui/LoadingDots" import LoadingDots from "../../ui/LoadingDots"
import ChatInput from "./ChatInput" import ChatInput from "./ChatInput"
import ChatMessage from "./ChatMessage" import ChatMessage from "./ChatMessage"
import ContextTabs from "./ContextTabs" import ContextTabs from "./ContextTabs"
import { handleSend, handleStopGeneration } from "./lib/chatUtils" import { handleSend, handleStopGeneration } from "./lib/chatUtils"
import { nanoid } from 'nanoid' import { AIChatProps, ContextTab, Message } from "./types"
import { TFile } from "@/lib/types"
import { useSocket } from "@/context/SocketContext"
import { Message, ContextTab, AIChatProps } from './types'
export default function AIChat({ export default function AIChat({
activeFileContent, activeFileContent,
@ -56,47 +56,54 @@ export default function AIChat({
} }
// Add context tab to context tabs // 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 = { const newTab = {
id: nanoid(), id: nanoid(),
type: type as "file" | "code" | "image", type: type as "file" | "code" | "image",
name, name,
content, content,
lineRange lineRange,
} }
setContextTabs(prev => [...prev, newTab]) setContextTabs((prev) => [...prev, newTab])
} }
// Remove context tab from context tabs // Remove context tab from context tabs
const removeContextTab = (id: string) => { 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 // Add file to context tabs
const handleAddFile = (tab: ContextTab) => { 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 // Format code content to remove starting and ending code block markers if they exist
const formatCodeContent = (content: string) => { const formatCodeContent = (content: string) => {
return content.replace(/^```[\w-]*\n/, '').replace(/\n```$/, '') return content.replace(/^```[\w-]*\n/, "").replace(/\n```$/, "")
} }
// Get combined context from context tabs // Get combined context from context tabs
const getCombinedContext = () => { const getCombinedContext = () => {
if (contextTabs.length === 0) return '' if (contextTabs.length === 0) return ""
return contextTabs.map(tab => { return contextTabs
if (tab.type === 'file') { .map((tab) => {
const fileExt = tab.name.split('.').pop() || 'txt' if (tab.type === "file") {
const fileExt = tab.name.split(".").pop() || "txt"
const cleanContent = formatCodeContent(tab.content) const cleanContent = formatCodeContent(tab.content)
return `File ${tab.name}:\n\`\`\`${fileExt}\n${cleanContent}\n\`\`\`` return `File ${tab.name}:\n\`\`\`${fileExt}\n${cleanContent}\n\`\`\``
} else if (tab.type === 'code') { } else if (tab.type === "code") {
const cleanContent = formatCodeContent(tab.content) const cleanContent = formatCodeContent(tab.content)
return `Code from ${tab.name}:\n\`\`\`typescript\n${cleanContent}\n\`\`\`` return `Code from ${tab.name}:\n\`\`\`typescript\n${cleanContent}\n\`\`\``
} }
return `${tab.name}:\n${tab.content}` return `${tab.name}:\n${tab.content}`
}).join('\n\n') })
.join("\n\n")
} }
// Handle sending message with context // Handle sending message with context
@ -122,7 +129,7 @@ export default function AIChat({
const setContext = ( const setContext = (
context: string | null, context: string | null,
name: string, name: string,
range?: { start: number, end: number } range?: { start: number; end: number }
) => { ) => {
if (!context) { if (!context) {
setContextTabs([]) setContextTabs([])
@ -130,7 +137,7 @@ export default function AIChat({
} }
// Always add a new tab instead of updating existing ones // Always add a new tab instead of updating existing ones
addContextTab('code', name, context, range) addContextTab("code", name, context, range)
} }
return ( return (
@ -180,9 +187,9 @@ export default function AIChat({
socket={socket} socket={socket}
onFileSelect={(file: TFile) => { onFileSelect={(file: TFile) => {
socket?.emit("getFile", { fileId: file.id }, (response: string) => { 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\`\`\`` const formattedContent = `\`\`\`${fileExt}\n${response}\n\`\`\``
addContextTab('file', file.name, formattedContent) addContextTab("file", file.name, formattedContent)
if (textareaRef.current) { if (textareaRef.current) {
textareaRef.current.focus() textareaRef.current.focus()
} }
@ -210,9 +217,9 @@ export default function AIChat({
}} }}
lastCopiedRangeRef={lastCopiedRangeRef} lastCopiedRangeRef={lastCopiedRangeRef}
activeFileName={activeFileName} activeFileName={activeFileName}
contextTabs={contextTabs.map(tab => ({ contextTabs={contextTabs.map((tab) => ({
...tab, ...tab,
title: tab.id title: tab.id,
}))} }))}
onRemoveTab={removeContextTab} onRemoveTab={removeContextTab}
/> />

View File

@ -95,18 +95,20 @@ export const handleSend = async (
if (input.trim() === "" && !context) return if (input.trim() === "" && !context) return
// Get timestamp for chat message component // Get timestamp for chat message component
const timestamp = new Date().toLocaleTimeString('en-US', { const timestamp = new Date()
.toLocaleTimeString("en-US", {
hour12: true, hour12: true,
hour: '2-digit', hour: "2-digit",
minute: '2-digit' minute: "2-digit",
}).replace(/(\d{2}):(\d{2})/, '$1:$2') })
.replace(/(\d{2}):(\d{2})/, "$1:$2")
// Create user message for chat message component // Create user message for chat message component
const userMessage = { const userMessage = {
role: "user" as const, role: "user" as const,
content: input, content: input,
context: context || undefined, context: context || undefined,
timestamp: timestamp timestamp: timestamp,
} }
// Update messages for chat message component // Update messages for chat message component
@ -229,7 +231,7 @@ export const looksLikeCode = (text: string): boolean => {
/^\s*\/\*/m, // multi-line comments /^\s*\/\*/m, // multi-line comments
/=>/, // arrow functions /=>/, // arrow functions
/^export\s+/m, // export statements /^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 = [ export const ignoredFolders = [
// Package managers // Package managers
'node_modules', "node_modules",
'venv', "venv",
'.env', ".env",
'env', "env",
'.venv', ".venv",
'virtualenv', "virtualenv",
'pip-wheel-metadata', "pip-wheel-metadata",
// Build outputs // Build outputs
'.next', ".next",
'dist', "dist",
'build', "build",
'out', "out",
'__pycache__', "__pycache__",
'.webpack', ".webpack",
'.serverless', ".serverless",
'storybook-static', "storybook-static",
// Version control // Version control
'.git', ".git",
'.svn', ".svn",
'.hg', // Mercurial ".hg", // Mercurial
// Cache and temp files // Cache and temp files
'.cache', ".cache",
'coverage', "coverage",
'tmp', "tmp",
'.temp', ".temp",
'.npm', ".npm",
'.pnpm', ".pnpm",
'.yarn', ".yarn",
'.eslintcache', ".eslintcache",
'.stylelintcache', ".stylelintcache",
// IDE specific // IDE specific
'.idea', ".idea",
'.vscode', ".vscode",
'.vs', ".vs",
'.sublime', ".sublime",
// Framework specific // Framework specific
'.streamlit', ".streamlit",
'.next', ".next",
'static', "static",
'.pytest_cache', ".pytest_cache",
'.nuxt', ".nuxt",
'.docusaurus', ".docusaurus",
'.remix', ".remix",
'.parcel-cache', ".parcel-cache",
'public/build', // Remix/Rails "public/build", // Remix/Rails
'.turbo', // Turborepo ".turbo", // Turborepo
// Logs // Logs
'logs', "logs",
'*.log', "*.log",
'npm-debug.log*', "npm-debug.log*",
'yarn-debug.log*', "yarn-debug.log*",
'yarn-error.log*', "yarn-error.log*",
'pnpm-debug.log*', "pnpm-debug.log*",
] as const; ] as const
export const ignoredFiles = [ export const ignoredFiles = [
'.DS_Store', ".DS_Store",
'.env.local', ".env.local",
'.env.development', ".env.development",
'.env.production', ".env.production",
'.env.test', ".env.test",
'.env*.local', ".env*.local",
'.gitignore', ".gitignore",
'.npmrc', ".npmrc",
'.yarnrc', ".yarnrc",
'.editorconfig', ".editorconfig",
'.prettierrc', ".prettierrc",
'.eslintrc', ".eslintrc",
'.browserslistrc', ".browserslistrc",
'tsconfig.tsbuildinfo', "tsconfig.tsbuildinfo",
'*.pyc', "*.pyc",
'*.pyo', "*.pyo",
'*.pyd', "*.pyd",
'*.so', "*.so",
'*.dll', "*.dll",
'*.dylib', "*.dylib",
'*.class', "*.class",
'*.exe', "*.exe",
'package-lock.json', "package-lock.json",
'yarn.lock', "yarn.lock",
'pnpm-lock.yaml', "pnpm-lock.yaml",
'composer.lock', "composer.lock",
'poetry.lock', "poetry.lock",
'Gemfile.lock', "Gemfile.lock",
'*.min.js', "*.min.js",
'*.min.css', "*.min.css",
'*.map', "*.map",
'*.chunk.*', "*.chunk.*",
'*.hot-update.*', "*.hot-update.*",
'.vercel', ".vercel",
'.netlify' ".netlify",
] as const; ] as const

View File

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

View File

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

View File

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

View File

@ -173,7 +173,10 @@ export default function CodeEditor({
const previewWindowRef = useRef<{ refreshIframe: () => void }>(null) const previewWindowRef = useRef<{ refreshIframe: () => void }>(null)
// Ref to store the last copied range in the editor to be used in the AIChat component // 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( const debouncedSetIsSelected = useRef(
debounce((value: boolean) => { 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 // Store the last copied range in the editor to be used in the AIChat component
editor.onDidChangeCursorSelection((e) => { editor.onDidChangeCursorSelection((e) => {
const selection = editor.getSelection(); const selection = editor.getSelection()
if (selection) { if (selection) {
lastCopiedRangeRef.current = { lastCopiedRangeRef.current = {
startLine: selection.startLineNumber, startLine: selection.startLineNumber,
endLine: selection.endLineNumber endLine: selection.endLineNumber,
};
} }
}); }
})
} }
// Call the function with your file structure // Call the function with your file structure
@ -853,9 +856,13 @@ export default function CodeEditor({
} }
const handleDeleteFile = (file: TFile) => { const handleDeleteFile = (file: TFile) => {
socket?.emit("deleteFile", { fileId: file.id }, (response: (TFolder | TFile)[]) => { socket?.emit(
"deleteFile",
{ fileId: file.id },
(response: (TFolder | TFile)[]) => {
setFiles(response) setFiles(response)
}) }
)
closeTab(file.id) closeTab(file.id)
} }
@ -867,10 +874,14 @@ export default function CodeEditor({
closeTabs(response) closeTabs(response)
) )
socket?.emit("deleteFolder", { folderId: folder.id }, (response: (TFolder | TFile)[]) => { socket?.emit(
"deleteFolder",
{ folderId: folder.id },
(response: (TFolder | TFile)[]) => {
setFiles(response) setFiles(response)
setDeletingFolderId("") setDeletingFolderId("")
}) }
)
} }
const togglePreviewPanel = () => { const togglePreviewPanel = () => {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
"use client" "use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress" import * as ProgressPrimitive from "@radix-ui/react-progress"
import * as React from "react"
import { cn } from "@/lib/utils" 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) { export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider> return <NextThemesProvider {...props}>{children}</NextThemesProvider>
} }

View File

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

View File

@ -74,7 +74,7 @@ function mapModule(module: string): monaco.languages.typescript.ModuleKind {
} }
function mapJSX(jsx: string | undefined): monaco.languages.typescript.JsxEmit { 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 return monaco.languages.typescript.JsxEmit.React // Default value
} }

View File

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

View File

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