feat: added AI chat

backend implementation remaining
This commit is contained in:
Akhileshrangani4 2024-10-13 01:41:48 -04:00
parent f192d9f3ab
commit 62e282da63
2 changed files with 127 additions and 51 deletions

View File

@ -0,0 +1,64 @@
import React, { useState, useRef, useEffect } from 'react';
import { Button } from '../ui/button';
import { Send } from 'lucide-react';
interface Message {
role: 'user' | 'assistant';
content: string;
}
export default function AIChat() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const chatContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (chatContainerRef.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}
}, [messages]);
const handleSend = async () => {
if (input.trim() === '') return;
const newMessage: Message = { role: 'user', content: input };
setMessages(prev => [...prev, newMessage]);
setInput('');
// TODO: Implement actual API call to LLM here
// For now, we'll just simulate a response
setTimeout(() => {
const assistantMessage: Message = { role: 'assistant', content: 'This is a simulated response from the AI.' };
setMessages(prev => [...prev, assistantMessage]);
}, 1000);
};
return (
<div className="flex flex-col h-full">
<div ref={chatContainerRef} className="flex-grow overflow-y-auto p-4 space-y-4">
{messages.map((message, index) => (
<div key={index} className={`${message.role === 'user' ? 'text-right' : 'text-left'}`}>
<div className={`inline-block p-2 rounded-lg ${message.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200 text-black'}`}>
{message.content}
</div>
</div>
))}
</div>
<div className="p-4 border-t">
<div className="flex space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
className="flex-grow p-2 border rounded-lg"
placeholder="Type your message..."
/>
<Button onClick={handleSend}>
<Send className="w-4 h-4" />
</Button>
</div>
</div>
</div>
);
}

View File

@ -37,6 +37,7 @@ import { Button } from "../ui/button"
import React from "react" import React from "react"
import { parseTSConfigToMonacoOptions } from "@/lib/tsconfig" import { parseTSConfigToMonacoOptions } from "@/lib/tsconfig"
import { deepMerge } from "@/lib/utils" import { deepMerge } from "@/lib/utils"
import AIChat from "./AIChat"
export default function CodeEditor({ export default function CodeEditor({
userData, userData,
@ -73,6 +74,12 @@ export default function CodeEditor({
message: "", message: "",
}) })
// Layout state
const [isHorizontalLayout, setIsHorizontalLayout] = useState(false);
// AI Chat state
const [isAIChatOpen, setIsAIChatOpen] = useState(false);
// File state // File state
const [files, setFiles] = useState<(TFolder | TFile)[]>([]) const [files, setFiles] = useState<(TFolder | TFile)[]>([])
const [tabs, setTabs] = useState<TTab[]>([]) const [tabs, setTabs] = useState<TTab[]>([])
@ -513,12 +520,15 @@ export default function CodeEditor({
[socket, fileContents] [socket, fileContents]
); );
// Keydown event listener to trigger file save on Ctrl+S or Cmd+S // Keydown event listener to trigger file save on Ctrl+S or Cmd+S, and toggle AI chat on Ctrl+L or Cmd+L
useEffect(() => { useEffect(() => {
const down = (e: KeyboardEvent) => { const down = (e: KeyboardEvent) => {
if (e.key === "s" && (e.metaKey || e.ctrlKey)) { if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
e.preventDefault() e.preventDefault()
debouncedSaveData(activeFileId); debouncedSaveData(activeFileId);
} else if (e.key === "l" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setIsAIChatOpen(prev => !prev);
} }
} }
document.addEventListener("keydown", down) document.addEventListener("keydown", down)
@ -526,7 +536,7 @@ export default function CodeEditor({
return () => { return () => {
document.removeEventListener("keydown", down) document.removeEventListener("keydown", down)
} }
}, [activeFileId, tabs, debouncedSaveData]) }, [activeFileId, tabs, debouncedSaveData, setIsAIChatOpen])
// Liveblocks live collaboration setup effect // Liveblocks live collaboration setup effect
useEffect(() => { useEffect(() => {
@ -837,8 +847,6 @@ export default function CodeEditor({
} }
}; };
const [isHorizontalLayout, setIsHorizontalLayout] = useState(false);
const toggleLayout = () => { const toggleLayout = () => {
setIsHorizontalLayout(prev => !prev); setIsHorizontalLayout(prev => !prev);
}; };
@ -1075,54 +1083,58 @@ export default function CodeEditor({
</ResizablePanel> </ResizablePanel>
<ResizableHandle /> <ResizableHandle />
<ResizablePanel defaultSize={isHorizontalLayout ? 30 : 30}> <ResizablePanel defaultSize={isHorizontalLayout ? 30 : 30}>
<ResizablePanelGroup direction={isHorizontalLayout ? "horizontal" : "vertical"}> {isAIChatOpen ? (
<ResizablePanel <AIChat />
ref={previewPanelRef} ) : (
defaultSize={4} <ResizablePanelGroup direction={isHorizontalLayout ? "horizontal" : "vertical"}>
collapsedSize={isHorizontalLayout ? 20 : 4} <ResizablePanel
minSize={25} ref={previewPanelRef}
collapsible defaultSize={4}
className="p-2 flex flex-col" collapsedSize={isHorizontalLayout ? 20 : 4}
onCollapse={() => setIsPreviewCollapsed(true)} minSize={25}
onExpand={() => setIsPreviewCollapsed(false)} collapsible
> className="p-2 flex flex-col"
<div className="flex items-center justify-between"> onCollapse={() => setIsPreviewCollapsed(true)}
<Button onClick={toggleLayout} size="sm" variant="ghost" className="mr-2 border"> onExpand={() => setIsPreviewCollapsed(false)}
{isHorizontalLayout ? <ArrowRightToLine className="w-4 h-4" /> : <ArrowDownToLine className="w-4 h-4" />} >
</Button> <div className="flex items-center justify-between">
<PreviewWindow <Button onClick={toggleLayout} size="sm" variant="ghost" className="mr-2 border">
open={togglePreviewPanel} {isHorizontalLayout ? <ArrowRightToLine className="w-4 h-4" /> : <ArrowDownToLine className="w-4 h-4" />}
collapsed={isPreviewCollapsed} </Button>
src={previewURL} <PreviewWindow
ref={previewWindowRef} open={togglePreviewPanel}
/> collapsed={isPreviewCollapsed}
</div>
{!isPreviewCollapsed && (
<div className="w-full grow rounded-md overflow-hidden bg-foreground mt-2">
<iframe
width={"100%"}
height={"100%"}
src={previewURL} src={previewURL}
/> ref={previewWindowRef}
</div> />
)}
</ResizablePanel>
<ResizableHandle />
<ResizablePanel
defaultSize={50}
minSize={20}
className="p-2 flex flex-col"
>
{isOwner ? (
<Terminals />
) : (
<div className="w-full h-full flex items-center justify-center text-lg font-medium text-muted-foreground/50 select-none">
<TerminalSquare className="w-4 h-4 mr-2" />
No terminal access.
</div> </div>
)} {!isPreviewCollapsed && (
</ResizablePanel> <div className="w-full grow rounded-md overflow-hidden bg-foreground mt-2">
</ResizablePanelGroup> <iframe
width={"100%"}
height={"100%"}
src={previewURL}
/>
</div>
)}
</ResizablePanel>
<ResizableHandle />
<ResizablePanel
defaultSize={50}
minSize={20}
className="p-2 flex flex-col"
>
{isOwner ? (
<Terminals />
) : (
<div className="w-full h-full flex items-center justify-center text-lg font-medium text-muted-foreground/50 select-none">
<TerminalSquare className="w-4 h-4 mr-2" />
No terminal access.
</div>
)}
</ResizablePanel>
</ResizablePanelGroup>
)}
</ResizablePanel> </ResizablePanel>
</ResizablePanelGroup> </ResizablePanelGroup>
</PreviewProvider> </PreviewProvider>