chore: format frontend code

This commit is contained in:
Akhilesh Rangani
2024-10-21 13:57:45 -06:00
parent 2897b908fd
commit 6fb1364d6f
64 changed files with 1421 additions and 1272 deletions

View File

@ -1,36 +1,51 @@
import React from 'react';
import { Button } from '../../ui/button';
import { Send, StopCircle } from 'lucide-react';
import { Send, StopCircle } from "lucide-react"
import { Button } from "../../ui/button"
interface ChatInputProps {
input: string;
setInput: (input: string) => void;
isGenerating: boolean;
handleSend: () => void;
handleStopGeneration: () => void;
input: string
setInput: (input: string) => void
isGenerating: boolean
handleSend: () => void
handleStopGeneration: () => void
}
export default function ChatInput({ input, setInput, isGenerating, handleSend, handleStopGeneration }: ChatInputProps) {
export default function ChatInput({
input,
setInput,
isGenerating,
handleSend,
handleStopGeneration,
}: ChatInputProps) {
return (
<div className="flex space-x-2 min-w-0">
<input
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && !isGenerating && handleSend()}
onKeyPress={(e) => e.key === "Enter" && !isGenerating && handleSend()}
className="flex-grow p-2 border rounded-lg min-w-0 bg-input"
placeholder="Type your message..."
disabled={isGenerating}
/>
{isGenerating ? (
<Button onClick={handleStopGeneration} variant="destructive" size="icon" className="h-10 w-10">
<Button
onClick={handleStopGeneration}
variant="destructive"
size="icon"
className="h-10 w-10"
>
<StopCircle className="w-4 h-4" />
</Button>
) : (
<Button onClick={handleSend} disabled={isGenerating} size="icon" className="h-10 w-10">
<Button
onClick={handleSend}
disabled={isGenerating}
size="icon"
className="h-10 w-10"
>
<Send className="w-4 h-4" />
</Button>
)}
</div>
);
)
}

View File

@ -1,10 +1,10 @@
import { Check, ChevronDown, ChevronUp, Copy, CornerUpLeft } from 'lucide-react';
import React, { useState } from 'react';
import { Button } from '../../ui/button';
import { ChevronUp, ChevronDown, Copy, Check, CornerUpLeft } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import remarkGfm from 'remark-gfm';
import { Button } from '../../ui/button';
import { copyToClipboard, stringifyContent } from './lib/chatUtils';
interface MessageProps {

View File

@ -1,5 +1,4 @@
import React from 'react';
import { ChevronUp, ChevronDown, X } from 'lucide-react';
import { ChevronDown, ChevronUp, X } from 'lucide-react';
interface ContextDisplayProps {
context: string | null;

View File

@ -1,48 +1,58 @@
import React, { useState, useEffect, useRef } from 'react';
import LoadingDots from '../../ui/LoadingDots';
import ChatMessage from './ChatMessage';
import ChatInput from './ChatInput';
import ContextDisplay from './ContextDisplay';
import { handleSend, handleStopGeneration } from './lib/chatUtils';
import { X } from 'lucide-react';
import { X } from "lucide-react"
import { useEffect, useRef, useState } from "react"
import LoadingDots from "../../ui/LoadingDots"
import ChatInput from "./ChatInput"
import ChatMessage from "./ChatMessage"
import ContextDisplay from "./ContextDisplay"
import { handleSend, handleStopGeneration } from "./lib/chatUtils"
interface Message {
role: 'user' | 'assistant';
content: string;
context?: string;
role: "user" | "assistant"
content: string
context?: string
}
export default function AIChat({ activeFileContent, activeFileName, onClose }: { activeFileContent: string, activeFileName: string, onClose: () => void }) {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const chatContainerRef = useRef<HTMLDivElement>(null);
const abortControllerRef = useRef<AbortController | null>(null);
const [context, setContext] = useState<string | null>(null);
const [isContextExpanded, setIsContextExpanded] = useState(false);
const [isLoading, setIsLoading] = useState(false);
export default function AIChat({
activeFileContent,
activeFileName,
onClose,
}: {
activeFileContent: string
activeFileName: string
onClose: () => void
}) {
const [messages, setMessages] = useState<Message[]>([])
const [input, setInput] = useState("")
const [isGenerating, setIsGenerating] = useState(false)
const chatContainerRef = useRef<HTMLDivElement>(null)
const abortControllerRef = useRef<AbortController | null>(null)
const [context, setContext] = useState<string | null>(null)
const [isContextExpanded, setIsContextExpanded] = useState(false)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
scrollToBottom();
}, [messages]);
scrollToBottom()
}, [messages])
const scrollToBottom = () => {
if (chatContainerRef.current) {
setTimeout(() => {
chatContainerRef.current?.scrollTo({
top: chatContainerRef.current.scrollHeight,
behavior: 'smooth'
});
}, 100);
behavior: "smooth",
})
}, 100)
}
};
}
return (
<div className="flex flex-col h-screen w-full">
<div className="flex justify-between items-center p-2 border-b">
<span className="text-muted-foreground/50 font-medium">CHAT</span>
<div className="flex items-center h-full">
<span className="text-muted-foreground/50 font-medium">{activeFileName}</span>
<span className="text-muted-foreground/50 font-medium">
{activeFileName}
</span>
<div className="mx-2 h-full w-px bg-muted-foreground/20"></div>
<button
onClick={onClose}
@ -53,11 +63,14 @@ export default function AIChat({ activeFileContent, activeFileName, onClose }: {
</button>
</div>
</div>
<div ref={chatContainerRef} className="flex-grow overflow-y-auto p-4 space-y-4">
<div
ref={chatContainerRef}
className="flex-grow overflow-y-auto p-4 space-y-4"
>
{messages.map((message, messageIndex) => (
<ChatMessage
key={messageIndex}
message={message}
<ChatMessage
key={messageIndex}
message={message}
setContext={setContext}
setIsContextExpanded={setIsContextExpanded}
/>
@ -65,20 +78,33 @@ export default function AIChat({ activeFileContent, activeFileName, onClose }: {
{isLoading && <LoadingDots />}
</div>
<div className="p-4 border-t mb-14">
<ContextDisplay
context={context}
<ContextDisplay
context={context}
isContextExpanded={isContextExpanded}
setIsContextExpanded={setIsContextExpanded}
setContext={setContext}
/>
<ChatInput
<ChatInput
input={input}
setInput={setInput}
isGenerating={isGenerating}
handleSend={() => handleSend(input, context, messages, setMessages, setInput, setIsContextExpanded, setIsGenerating, setIsLoading, abortControllerRef, activeFileContent)}
handleSend={() =>
handleSend(
input,
context,
messages,
setMessages,
setInput,
setIsContextExpanded,
setIsGenerating,
setIsLoading,
abortControllerRef,
activeFileContent
)
}
handleStopGeneration={() => handleStopGeneration(abortControllerRef)}
/>
</div>
</div>
);
)
}

View File

@ -1,58 +1,68 @@
import React from 'react';
import React from "react"
export const stringifyContent = (content: any, seen = new WeakSet()): string => {
if (typeof content === 'string') {
return content;
export const stringifyContent = (
content: any,
seen = new WeakSet()
): string => {
if (typeof content === "string") {
return content
}
if (content === null) {
return 'null';
return "null"
}
if (content === undefined) {
return 'undefined';
return "undefined"
}
if (typeof content === 'number' || typeof content === 'boolean') {
return content.toString();
if (typeof content === "number" || typeof content === "boolean") {
return content.toString()
}
if (typeof content === 'function') {
return content.toString();
if (typeof content === "function") {
return content.toString()
}
if (typeof content === 'symbol') {
return content.toString();
if (typeof content === "symbol") {
return content.toString()
}
if (typeof content === 'bigint') {
return content.toString() + 'n';
if (typeof content === "bigint") {
return content.toString() + "n"
}
if (React.isValidElement(content)) {
return React.Children.toArray((content as React.ReactElement).props.children)
.map(child => stringifyContent(child, seen))
.join('');
return React.Children.toArray(
(content as React.ReactElement).props.children
)
.map((child) => stringifyContent(child, seen))
.join("")
}
if (Array.isArray(content)) {
return '[' + content.map(item => stringifyContent(item, seen)).join(', ') + ']';
return (
"[" + content.map((item) => stringifyContent(item, seen)).join(", ") + "]"
)
}
if (typeof content === 'object') {
if (typeof content === "object") {
if (seen.has(content)) {
return '[Circular]';
return "[Circular]"
}
seen.add(content);
seen.add(content)
try {
const pairs = Object.entries(content).map(
([key, value]) => `${key}: ${stringifyContent(value, seen)}`
);
return '{' + pairs.join(', ') + '}';
)
return "{" + pairs.join(", ") + "}"
} catch (error) {
return Object.prototype.toString.call(content);
return Object.prototype.toString.call(content)
}
}
return String(content);
};
return String(content)
}
export const copyToClipboard = (text: string, setCopiedText: (text: string | null) => void) => {
export const copyToClipboard = (
text: string,
setCopiedText: (text: string | null) => void
) => {
navigator.clipboard.writeText(text).then(() => {
setCopiedText(text);
setTimeout(() => setCopiedText(null), 2000);
});
};
setCopiedText(text)
setTimeout(() => setCopiedText(null), 2000)
})
}
export const handleSend = async (
input: string,
@ -66,97 +76,105 @@ export const handleSend = async (
abortControllerRef: React.MutableRefObject<AbortController | null>,
activeFileContent: string
) => {
if (input.trim() === '' && !context) return;
if (input.trim() === "" && !context) return
const newMessage = {
role: 'user' as const,
const newMessage = {
role: "user" as const,
content: input,
context: context || undefined
};
const updatedMessages = [...messages, newMessage];
setMessages(updatedMessages);
setInput('');
setIsContextExpanded(false);
setIsGenerating(true);
setIsLoading(true);
context: context || undefined,
}
const updatedMessages = [...messages, newMessage]
setMessages(updatedMessages)
setInput("")
setIsContextExpanded(false)
setIsGenerating(true)
setIsLoading(true)
abortControllerRef.current = new AbortController();
abortControllerRef.current = new AbortController()
try {
const anthropicMessages = updatedMessages.map(msg => ({
role: msg.role === 'user' ? 'human' : 'assistant',
content: msg.content
}));
const anthropicMessages = updatedMessages.map((msg) => ({
role: msg.role === "user" ? "human" : "assistant",
content: msg.content,
}))
const response = await fetch(`${process.env.NEXT_PUBLIC_AI_WORKER_URL}/api`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: anthropicMessages,
context: context || undefined,
activeFileContent: activeFileContent,
}),
signal: abortControllerRef.current.signal,
});
const response = await fetch(
`${process.env.NEXT_PUBLIC_AI_WORKER_URL}/api`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
messages: anthropicMessages,
context: context || undefined,
activeFileContent: activeFileContent,
}),
signal: abortControllerRef.current.signal,
}
)
if (!response.ok) {
throw new Error('Failed to get AI response');
throw new Error("Failed to get AI response")
}
const reader = response.body?.getReader();
const decoder = new TextDecoder();
const assistantMessage = { role: 'assistant' as const, content: '' };
setMessages([...updatedMessages, assistantMessage]);
setIsLoading(false);
const reader = response.body?.getReader()
const decoder = new TextDecoder()
const assistantMessage = { role: "assistant" as const, content: "" }
setMessages([...updatedMessages, assistantMessage])
setIsLoading(false)
let buffer = '';
const updateInterval = 100;
let lastUpdateTime = Date.now();
let buffer = ""
const updateInterval = 100
let lastUpdateTime = Date.now()
if (reader) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const currentTime = Date.now();
const currentTime = Date.now()
if (currentTime - lastUpdateTime > updateInterval) {
setMessages(prev => {
const updatedMessages = [...prev];
const lastMessage = updatedMessages[updatedMessages.length - 1];
lastMessage.content = buffer;
return updatedMessages;
});
lastUpdateTime = currentTime;
setMessages((prev) => {
const updatedMessages = [...prev]
const lastMessage = updatedMessages[updatedMessages.length - 1]
lastMessage.content = buffer
return updatedMessages
})
lastUpdateTime = currentTime
}
}
setMessages(prev => {
const updatedMessages = [...prev];
const lastMessage = updatedMessages[updatedMessages.length - 1];
lastMessage.content = buffer;
return updatedMessages;
});
setMessages((prev) => {
const updatedMessages = [...prev]
const lastMessage = updatedMessages[updatedMessages.length - 1]
lastMessage.content = buffer
return updatedMessages
})
}
} catch (error: any) {
if (error.name === 'AbortError') {
console.log('Generation aborted');
if (error.name === "AbortError") {
console.log("Generation aborted")
} else {
console.error('Error fetching AI response:', error);
const errorMessage = { role: 'assistant' as const, content: 'Sorry, I encountered an error. Please try again.' };
setMessages(prev => [...prev, errorMessage]);
console.error("Error fetching AI response:", error)
const errorMessage = {
role: "assistant" as const,
content: "Sorry, I encountered an error. Please try again.",
}
setMessages((prev) => [...prev, errorMessage])
}
} finally {
setIsGenerating(false);
setIsLoading(false);
abortControllerRef.current = null;
setIsGenerating(false)
setIsLoading(false)
abortControllerRef.current = null
}
};
}
export const handleStopGeneration = (abortControllerRef: React.MutableRefObject<AbortController | null>) => {
export const handleStopGeneration = (
abortControllerRef: React.MutableRefObject<AbortController | null>
) => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
abortControllerRef.current.abort()
}
};
}