diff --git a/backend/ai/src/index.ts b/backend/ai/src/index.ts index af564f0..c170aab 100644 --- a/backend/ai/src/index.ts +++ b/backend/ai/src/index.ts @@ -1,4 +1,5 @@ import { Anthropic } from "@anthropic-ai/sdk"; +import { MessageParam } from "@anthropic-ai/sdk/src/resources/messages.js"; export interface Env { ANTHROPIC_API_KEY: string; @@ -17,31 +18,31 @@ export default { }); } - if (request.method !== "GET") { + if (request.method !== "POST") { return new Response("Method Not Allowed", { status: 405 }); } + const body = await request.json() as { messages: unknown; context: unknown }; + const messages = body.messages; + const context = body.context; - const url = new URL(request.url); - const instructions = url.searchParams.get("instructions"); - const context = url.searchParams.get("context"); - - if (!instructions) { - return new Response("Missing instructions parameter", { status: 400 }); + if (!Array.isArray(messages)) { + return new Response("Invalid messages format", { status: 400 }); } - const prompt = `You are an intelligent programming assistant. Please respond to the following request concisely: - -${instructions} - -${context ? `Context:\n${context}\n` : ''} - -If your response includes code, please format it using triple backticks (\`\`\`) with the appropriate language identifier. For example: + const systemMessage = `You are an intelligent programming assistant. Please respond to the following request concisely. If your response includes code, please format it using triple backticks (\`\`\`) with the appropriate language identifier. For example: \`\`\`python print("Hello, World!") \`\`\` -Provide a clear and concise explanation along with any code snippets. Keep your response brief and to the point`; +Provide a clear and concise explanation along with any code snippets. Keep your response brief and to the point. + +${context ? `Context:\n${context}\n` : ''}`; + + const anthropicMessages = messages.map(msg => ({ + role: msg.role === 'human' ? 'user' : 'assistant', + content: msg.content + })) as MessageParam[]; try { const anthropic = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY }); @@ -49,7 +50,8 @@ Provide a clear and concise explanation along with any code snippets. Keep your const stream = await anthropic.messages.create({ model: "claude-3-5-sonnet-20240620", max_tokens: 1024, - messages: [{ role: "user", content: prompt }], + system: systemMessage, + messages: anthropicMessages, stream: true, }); diff --git a/frontend/components/editor/AIChat.tsx b/frontend/components/editor/AIChat.tsx deleted file mode 100644 index d37cc53..0000000 --- a/frontend/components/editor/AIChat.tsx +++ /dev/null @@ -1,352 +0,0 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { Button } from '../ui/button'; -import { Send, StopCircle, Copy, Check, ChevronDown, ChevronUp, X, CornerUpLeft, Loader2 } 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 LoadingDots from '../ui/LoadingDots'; - -interface Message { - role: 'user' | 'assistant'; - content: string; - context?: string; -} - -export default function AIChat() { - const [messages, setMessages] = useState([]); - const [input, setInput] = useState(''); - const [isGenerating, setIsGenerating] = useState(false); - const chatContainerRef = useRef(null); - const abortControllerRef = useRef(null); - const [copiedIndex, setCopiedIndex] = useState(null); - const [context, setContext] = useState(null); - const [isContextExpanded, setIsContextExpanded] = useState(false); - const [expandedMessageIndex, setExpandedMessageIndex] = useState(null); - const [isLoading, setIsLoading] = useState(false); - - useEffect(() => { - scrollToBottom(); - }, [messages]); - - const scrollToBottom = () => { - if (chatContainerRef.current) { - setTimeout(() => { - chatContainerRef.current?.scrollTo({ - top: chatContainerRef.current.scrollHeight, - behavior: 'smooth' - }); - }, 100); - } - }; - - const handleSend = useCallback(async () => { - if (input.trim() === '' && !context) return; - - const newMessage: Message = { - role: 'user', - content: input, - context: context || undefined - }; - setMessages(prev => [...prev, newMessage]); - setInput(''); - setIsContextExpanded(false); - setIsGenerating(true); - setIsLoading(true); - - abortControllerRef.current = new AbortController(); - - try { - const queryParams = new URLSearchParams({ - instructions: input, - ...(context && { context }) - }); - const response = await fetch(`${process.env.NEXT_PUBLIC_AI_WORKER_URL}/api?${queryParams}`, { - method: 'GET', - signal: abortControllerRef.current.signal, - }); - - if (!response.ok) { - throw new Error('Failed to get AI response'); - } - - const reader = response.body?.getReader(); - const decoder = new TextDecoder(); - const assistantMessage: Message = { role: 'assistant', content: '' }; - setMessages(prev => [...prev, assistantMessage]); - setIsLoading(false); - - let buffer = ''; - const updateInterval = 100; // Update every 100ms - let lastUpdateTime = Date.now(); - - if (reader) { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - buffer += decoder.decode(value, { stream: true }); - - 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; - } - } - - // Final update to ensure all content is displayed - 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'); - } else { - console.error('Error fetching AI response:', error); - const errorMessage: Message = { role: 'assistant', content: 'Sorry, I encountered an error. Please try again.' }; - setMessages(prev => [...prev, errorMessage]); - } - } finally { - setIsGenerating(false); - setIsLoading(false); - abortControllerRef.current = null; - } - }, [input, context]); - - const handleStopGeneration = () => { - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - }; - - const copyToClipboard = (text: string, index: number) => { - navigator.clipboard.writeText(text).then(() => { - setCopiedIndex(index); - setTimeout(() => setCopiedIndex(null), 1000); // Reset after 1 seconds - }); - }; - - const askAboutCode = (code: string) => { - setContext(`Regarding this code:\n${code}`); - setIsContextExpanded(false); - }; - - const removeContext = () => { - setContext(null); - setIsContextExpanded(false); - }; - - return ( -
- CHAT -
- {messages.map((message, messageIndex) => ( -
-
- {message.context && ( -
-
setExpandedMessageIndex(expandedMessageIndex === messageIndex ? null : messageIndex)} - > - - Context - - {expandedMessageIndex === messageIndex ? ( - - ) : ( - - )} -
- {expandedMessageIndex === messageIndex && ( -
-
- -
- {(() => { - // need to fix the language detection - const code = message.context!.replace(/^Regarding this code:\n/, ''); - const match = /language-(\w+)/.exec(code); - const language = match ? match[1] : 'typescript'; - return ( -
- - {code} - -
- ); - })()} -
- )} -
- )} - {message.role === 'assistant' ? ( - -
- {language} -
-
- - -
-
- - {String(children).replace(/\n$/, '')} - -
-
- ) : ( - - {children} - - ); - }, - p({children}) { - return

{children}

; - }, - ul({children}) { - return
    {children}
; - }, - ol({children}) { - return
    {children}
; - }, - li({children}) { - return
  • {children}
  • ; - }, - }} - > - {message.content} - - ) : ( -
    {message.content}
    - )} -
    -
    - ))} - {isLoading && ( - - )} -
    -
    - {context && ( -
    -
    -
    setIsContextExpanded(!isContextExpanded)} - > - - Context - -
    -
    - {isContextExpanded ? ( - setIsContextExpanded(false)} /> - ) : ( - setIsContextExpanded(true)} /> - )} - -
    -
    - {isContextExpanded && ( -