236 lines
7.1 KiB
TypeScript
Raw Normal View History

2024-10-21 13:57:45 -06:00
import React from "react"
// Stringify content for chat message component
2024-10-21 13:57:45 -06:00
export const stringifyContent = (
content: any,
seen = new WeakSet()
): string => {
// Stringify content if it's a string
2024-10-21 13:57:45 -06:00
if (typeof content === "string") {
return content
}
// Stringify content if it's null
if (content === null) {
2024-10-21 13:57:45 -06:00
return "null"
}
// Stringify content if it's undefined
if (content === undefined) {
2024-10-21 13:57:45 -06:00
return "undefined"
}
// Stringify content if it's a number or boolean
2024-10-21 13:57:45 -06:00
if (typeof content === "number" || typeof content === "boolean") {
return content.toString()
}
// Stringify content if it's a function
2024-10-21 13:57:45 -06:00
if (typeof content === "function") {
return content.toString()
}
// Stringify content if it's a symbol
2024-10-21 13:57:45 -06:00
if (typeof content === "symbol") {
return content.toString()
}
// Stringify content if it's a bigint
2024-10-21 13:57:45 -06:00
if (typeof content === "bigint") {
return content.toString() + "n"
}
// Stringify content if it's a valid React element
if (React.isValidElement(content)) {
2024-10-21 13:57:45 -06:00
return React.Children.toArray(
(content as React.ReactElement).props.children
)
.map((child) => stringifyContent(child, seen))
.join("")
}
// Stringify content if it's an array
if (Array.isArray(content)) {
2024-10-21 13:57:45 -06:00
return (
"[" + content.map((item) => stringifyContent(item, seen)).join(", ") + "]"
)
}
// Stringify content if it's an object
2024-10-21 13:57:45 -06:00
if (typeof content === "object") {
if (seen.has(content)) {
2024-10-21 13:57:45 -06:00
return "[Circular]"
}
2024-10-21 13:57:45 -06:00
seen.add(content)
try {
const pairs = Object.entries(content).map(
([key, value]) => `${key}: ${stringifyContent(value, seen)}`
2024-10-21 13:57:45 -06:00
)
return "{" + pairs.join(", ") + "}"
} catch (error) {
2024-10-21 13:57:45 -06:00
return Object.prototype.toString.call(content)
}
}
// Stringify content if it's a primitive value
2024-10-21 13:57:45 -06:00
return String(content)
}
// Copy to clipboard for chat message component
2024-10-21 13:57:45 -06:00
export const copyToClipboard = (
text: string,
setCopiedText: (text: string | null) => void
) => {
// Copy text to clipboard for chat message component
navigator.clipboard.writeText(text).then(() => {
2024-10-21 13:57:45 -06:00
setCopiedText(text)
setTimeout(() => setCopiedText(null), 2000)
})
}
// Handle send for chat message component
export const handleSend = async (
input: string,
context: string | null,
messages: any[],
setMessages: React.Dispatch<React.SetStateAction<any[]>>,
setInput: React.Dispatch<React.SetStateAction<string>>,
setIsContextExpanded: React.Dispatch<React.SetStateAction<boolean>>,
setIsGenerating: React.Dispatch<React.SetStateAction<boolean>>,
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>,
abortControllerRef: React.MutableRefObject<AbortController | null>,
activeFileContent: string
) => {
// Return if input is empty and context is null
if (input.trim() === "" && !context) return
// Get timestamp for chat message component
const timestamp = new Date().toLocaleTimeString('en-US', {
hour12: true,
hour: '2-digit',
minute: '2-digit'
}).replace(/(\d{2}):(\d{2})/, '$1:$2')
// Create user message for chat message component
const userMessage = {
2024-10-21 13:57:45 -06:00
role: "user" as const,
content: input,
2024-10-21 13:57:45 -06:00
context: context || undefined,
timestamp: timestamp
2024-10-21 13:57:45 -06:00
}
// Update messages for chat message component
const updatedMessages = [...messages, userMessage]
2024-10-21 13:57:45 -06:00
setMessages(updatedMessages)
setInput("")
setIsContextExpanded(false)
setIsGenerating(true)
setIsLoading(true)
2024-10-21 13:57:45 -06:00
abortControllerRef.current = new AbortController()
try {
// Create anthropic messages for chat message component
2024-10-21 13:57:45 -06:00
const anthropicMessages = updatedMessages.map((msg) => ({
role: msg.role === "user" ? "human" : "assistant",
content: msg.content,
}))
// Fetch AI response for chat message component
2024-10-21 13:57:45 -06:00
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,
}
)
// Throw error if response is not ok
if (!response.ok) {
2024-10-21 13:57:45 -06:00
throw new Error("Failed to get AI response")
}
// Get reader for chat message component
2024-10-21 13:57:45 -06:00
const reader = response.body?.getReader()
const decoder = new TextDecoder()
const assistantMessage = { role: "assistant" as const, content: "" }
setMessages([...updatedMessages, assistantMessage])
setIsLoading(false)
// Initialize buffer for chat message component
2024-10-21 13:57:45 -06:00
let buffer = ""
const updateInterval = 100
let lastUpdateTime = Date.now()
// Read response from reader for chat message component
if (reader) {
while (true) {
2024-10-21 13:57:45 -06:00
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
2024-10-21 13:57:45 -06:00
const currentTime = Date.now()
if (currentTime - lastUpdateTime > updateInterval) {
2024-10-21 13:57:45 -06:00
setMessages((prev) => {
const updatedMessages = [...prev]
const lastMessage = updatedMessages[updatedMessages.length - 1]
lastMessage.content = buffer
return updatedMessages
})
lastUpdateTime = currentTime
}
}
// Update messages for chat message component
2024-10-21 13:57:45 -06:00
setMessages((prev) => {
const updatedMessages = [...prev]
const lastMessage = updatedMessages[updatedMessages.length - 1]
lastMessage.content = buffer
return updatedMessages
})
}
} catch (error: any) {
// Handle abort error for chat message component
2024-10-21 13:57:45 -06:00
if (error.name === "AbortError") {
console.log("Generation aborted")
} else {
2024-10-21 13:57:45 -06:00
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 {
2024-10-21 13:57:45 -06:00
setIsGenerating(false)
setIsLoading(false)
abortControllerRef.current = null
}
2024-10-21 13:57:45 -06:00
}
// Handle stop generation for chat message component
2024-10-21 13:57:45 -06:00
export const handleStopGeneration = (
abortControllerRef: React.MutableRefObject<AbortController | null>
) => {
if (abortControllerRef.current) {
2024-10-21 13:57:45 -06:00
abortControllerRef.current.abort()
}
2024-10-21 13:57:45 -06:00
}
// Check if text looks like code for chat message component
export const looksLikeCode = (text: string): boolean => {
const codeIndicators = [
/^import\s+/m, // import statements
/^function\s+/m, // function declarations
/^class\s+/m, // class declarations
/^const\s+/m, // const declarations
/^let\s+/m, // let declarations
/^var\s+/m, // var declarations
/[{}\[\]();]/, // common code syntax
/^\s*\/\//m, // comments
/^\s*\/\*/m, // multi-line comments
/=>/, // arrow functions
/^export\s+/m, // export statements
];
return codeIndicators.some(pattern => pattern.test(text));
};