2024-10-21 13:57:45 -06:00
|
|
|
import React from "react"
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Stringify content for chat message component
|
2024-10-21 13:57:45 -06:00
|
|
|
export const stringifyContent = (
|
|
|
|
content: any,
|
|
|
|
seen = new WeakSet()
|
|
|
|
): string => {
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's a string
|
2024-10-21 13:57:45 -06:00
|
|
|
if (typeof content === "string") {
|
|
|
|
return content
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's null
|
2024-10-14 22:34:26 -04:00
|
|
|
if (content === null) {
|
2024-10-21 13:57:45 -06:00
|
|
|
return "null"
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's undefined
|
2024-10-14 22:34:26 -04:00
|
|
|
if (content === undefined) {
|
2024-10-21 13:57:45 -06:00
|
|
|
return "undefined"
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// 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()
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's a function
|
2024-10-21 13:57:45 -06:00
|
|
|
if (typeof content === "function") {
|
|
|
|
return content.toString()
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's a symbol
|
2024-10-21 13:57:45 -06:00
|
|
|
if (typeof content === "symbol") {
|
|
|
|
return content.toString()
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's a bigint
|
2024-10-21 13:57:45 -06:00
|
|
|
if (typeof content === "bigint") {
|
|
|
|
return content.toString() + "n"
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's a valid React element
|
2024-10-14 22:34:26 -04:00
|
|
|
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("")
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's an array
|
2024-10-14 22:34:26 -04:00
|
|
|
if (Array.isArray(content)) {
|
2024-10-21 13:57:45 -06:00
|
|
|
return (
|
|
|
|
"[" + content.map((item) => stringifyContent(item, seen)).join(", ") + "]"
|
|
|
|
)
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's an object
|
2024-10-21 13:57:45 -06:00
|
|
|
if (typeof content === "object") {
|
2024-10-14 22:34:26 -04:00
|
|
|
if (seen.has(content)) {
|
2024-10-21 13:57:45 -06:00
|
|
|
return "[Circular]"
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-10-21 13:57:45 -06:00
|
|
|
seen.add(content)
|
2024-10-14 22:34:26 -04:00
|
|
|
try {
|
|
|
|
const pairs = Object.entries(content).map(
|
|
|
|
([key, value]) => `${key}: ${stringifyContent(value, seen)}`
|
2024-10-21 13:57:45 -06:00
|
|
|
)
|
|
|
|
return "{" + pairs.join(", ") + "}"
|
2024-10-14 22:34:26 -04:00
|
|
|
} catch (error) {
|
2024-10-21 13:57:45 -06:00
|
|
|
return Object.prototype.toString.call(content)
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
// Stringify content if it's a primitive value
|
2024-10-21 13:57:45 -06:00
|
|
|
return String(content)
|
|
|
|
}
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// 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
|
|
|
|
) => {
|
2024-11-17 12:35:56 -05:00
|
|
|
// Copy text to clipboard for chat message component
|
2024-10-14 22:34:26 -04:00
|
|
|
navigator.clipboard.writeText(text).then(() => {
|
2024-10-21 13:57:45 -06:00
|
|
|
setCopiedText(text)
|
|
|
|
setTimeout(() => setCopiedText(null), 2000)
|
|
|
|
})
|
|
|
|
}
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Handle send for chat message component
|
2024-10-14 22:34:26 -04:00
|
|
|
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>>,
|
2024-10-14 23:01:25 -04:00
|
|
|
abortControllerRef: React.MutableRefObject<AbortController | null>,
|
2024-11-23 20:31:24 -05:00
|
|
|
activeFileContent: string,
|
2024-11-24 00:22:10 -05:00
|
|
|
isEditMode: boolean = false,
|
|
|
|
templateType: string
|
2024-10-14 22:34:26 -04:00
|
|
|
) => {
|
2024-11-04 14:21:13 -05:00
|
|
|
// Return if input is empty and context is null
|
2024-11-17 12:35:56 -05:00
|
|
|
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
|
2024-11-04 14:21:13 -05:00
|
|
|
const userMessage = {
|
2024-10-21 13:57:45 -06:00
|
|
|
role: "user" as const,
|
2024-10-14 22:34:26 -04:00
|
|
|
content: input,
|
2024-10-21 13:57:45 -06:00
|
|
|
context: context || undefined,
|
2024-11-17 12:35:56 -05:00
|
|
|
timestamp: timestamp,
|
2024-10-21 13:57:45 -06:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Update messages for chat message component
|
2024-11-04 14:21:13 -05:00
|
|
|
const updatedMessages = [...messages, userMessage]
|
2024-10-21 13:57:45 -06:00
|
|
|
setMessages(updatedMessages)
|
|
|
|
setInput("")
|
|
|
|
setIsContextExpanded(false)
|
|
|
|
setIsGenerating(true)
|
|
|
|
setIsLoading(true)
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-10-21 13:57:45 -06:00
|
|
|
abortControllerRef.current = new AbortController()
|
2024-10-14 22:34:26 -04:00
|
|
|
|
|
|
|
try {
|
2024-11-17 12:35:56 -05:00
|
|
|
// 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,
|
|
|
|
}))
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Fetch AI response for chat message component
|
2024-11-29 13:05:35 -05:00
|
|
|
const response = await fetch("/api/ai", {
|
2024-11-23 20:31:24 -05:00
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
2024-11-29 13:05:35 -05:00
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
messages: anthropicMessages,
|
|
|
|
context: context || undefined,
|
|
|
|
activeFileContent: activeFileContent,
|
|
|
|
isEditMode: isEditMode,
|
|
|
|
templateType: templateType,
|
|
|
|
}),
|
|
|
|
signal: abortControllerRef.current.signal,
|
|
|
|
})
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-11-04 14:21:13 -05:00
|
|
|
// Throw error if response is not ok
|
2024-10-14 22:34:26 -04:00
|
|
|
if (!response.ok) {
|
2024-11-23 20:31:24 -05:00
|
|
|
const error = await response.text()
|
|
|
|
throw new Error(error)
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// 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)
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Initialize buffer for chat message component
|
2024-10-21 13:57:45 -06:00
|
|
|
let buffer = ""
|
|
|
|
const updateInterval = 100
|
|
|
|
let lastUpdateTime = Date.now()
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Read response from reader for chat message component
|
2024-10-14 22:34:26 -04:00
|
|
|
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-14 22:34:26 -04:00
|
|
|
|
2024-10-21 13:57:45 -06:00
|
|
|
const currentTime = Date.now()
|
2024-10-14 22:34:26 -04:00
|
|
|
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
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// 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
|
|
|
|
})
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
|
|
|
} catch (error: any) {
|
2024-11-17 12:35:56 -05:00
|
|
|
// Handle abort error for chat message component
|
2024-10-21 13:57:45 -06:00
|
|
|
if (error.name === "AbortError") {
|
|
|
|
console.log("Generation aborted")
|
2024-10-14 22:34:26 -04:00
|
|
|
} else {
|
2024-10-21 13:57:45 -06:00
|
|
|
console.error("Error fetching AI response:", error)
|
|
|
|
const errorMessage = {
|
|
|
|
role: "assistant" as const,
|
2024-11-29 13:05:35 -05:00
|
|
|
content:
|
|
|
|
error.message || "Sorry, I encountered an error. Please try again.",
|
2024-10-21 13:57:45 -06:00
|
|
|
}
|
|
|
|
setMessages((prev) => [...prev, errorMessage])
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
|
|
|
} finally {
|
2024-10-21 13:57:45 -06:00
|
|
|
setIsGenerating(false)
|
|
|
|
setIsLoading(false)
|
|
|
|
abortControllerRef.current = null
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-10-21 13:57:45 -06:00
|
|
|
}
|
2024-10-14 22:34:26 -04:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Handle stop generation for chat message component
|
2024-10-21 13:57:45 -06:00
|
|
|
export const handleStopGeneration = (
|
|
|
|
abortControllerRef: React.MutableRefObject<AbortController | null>
|
|
|
|
) => {
|
2024-10-14 22:34:26 -04:00
|
|
|
if (abortControllerRef.current) {
|
2024-10-21 13:57:45 -06:00
|
|
|
abortControllerRef.current.abort()
|
2024-10-14 22:34:26 -04:00
|
|
|
}
|
2024-10-21 13:57:45 -06:00
|
|
|
}
|
2024-11-04 14:21:13 -05:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Check if text looks like code for chat message component
|
2024-11-04 14:21:13 -05:00
|
|
|
export const looksLikeCode = (text: string): boolean => {
|
|
|
|
const codeIndicators = [
|
2024-11-17 12:35:56 -05:00
|
|
|
/^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))
|
|
|
|
}
|