chore: format frontend code
This commit is contained in:
parent
5216a9d897
commit
062e8d9226
@ -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} />
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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" && (
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
),
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 />
|
||||
</>
|
||||
|
@ -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} />
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
|
@ -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"
|
||||
|
||||
|
@ -6,4 +6,3 @@ import { type ThemeProviderProps } from "next-themes/dist/types"
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -63,7 +63,7 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
terminals,
|
||||
setTerminals,
|
||||
setActiveTerminalId,
|
||||
setClosingTerminal: () => { },
|
||||
setClosingTerminal: () => {},
|
||||
socket,
|
||||
activeTerminalId,
|
||||
})
|
||||
|
@ -38,7 +38,7 @@
|
||||
"csl": "xml",
|
||||
"cson": "coffeescript",
|
||||
"csproj": "xml",
|
||||
"css":"css",
|
||||
"css": "css",
|
||||
"ct": "xml",
|
||||
"ctp": "php",
|
||||
"cxx": "cpp",
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
5
frontend/react-syntax-highlighter.d.ts
vendored
5
frontend/react-syntax-highlighter.d.ts
vendored
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user