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()
|
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} />
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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" && (
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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))
|
||||||
};
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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 = () => {
|
||||||
|
@ -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} />
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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 }
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
@ -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 }
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
@ -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"
|
||||||
|
|
||||||
|
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"
|
||||||
declare module 'react-syntax-highlighter/dist/esm/styles/prism';
|
declare module "react-syntax-highlighter/dist/esm/styles/prism"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user