|
|
import { useState, useEffect, useContext } from "react"; |
|
|
import ChatHistory from "./ChatHistory"; |
|
|
import { CLEAR_ATTACHMENTS_EVENT, DndUploaderContext } from "./DnDWrapper"; |
|
|
import PromptInput, { |
|
|
PROMPT_INPUT_EVENT, |
|
|
PROMPT_INPUT_ID, |
|
|
} from "./PromptInput"; |
|
|
import Workspace from "@/models/workspace"; |
|
|
import handleChat, { ABORT_STREAM_EVENT } from "@/utils/chat"; |
|
|
import { isMobile } from "react-device-detect"; |
|
|
import { SidebarMobileHeader } from "../../Sidebar"; |
|
|
import { useParams } from "react-router-dom"; |
|
|
import { v4 } from "uuid"; |
|
|
import handleSocketResponse, { |
|
|
websocketURI, |
|
|
AGENT_SESSION_END, |
|
|
AGENT_SESSION_START, |
|
|
} from "@/utils/chat/agent"; |
|
|
import DnDFileUploaderWrapper from "./DnDWrapper"; |
|
|
import SpeechRecognition, { |
|
|
useSpeechRecognition, |
|
|
} from "react-speech-recognition"; |
|
|
import { ChatTooltips } from "./ChatTooltips"; |
|
|
import { MetricsProvider } from "./ChatHistory/HistoricalMessage/Actions/RenderMetrics"; |
|
|
|
|
|
export default function ChatContainer({ workspace, knownHistory = [] }) { |
|
|
const { threadSlug = null } = useParams(); |
|
|
const [message, setMessage] = useState(""); |
|
|
const [loadingResponse, setLoadingResponse] = useState(false); |
|
|
const [chatHistory, setChatHistory] = useState(knownHistory); |
|
|
const [socketId, setSocketId] = useState(null); |
|
|
const [websocket, setWebsocket] = useState(null); |
|
|
const { files, parseAttachments } = useContext(DndUploaderContext); |
|
|
|
|
|
|
|
|
const handleMessageChange = (event) => { |
|
|
setMessage(event.target.value); |
|
|
}; |
|
|
|
|
|
const { listening, resetTranscript } = useSpeechRecognition({ |
|
|
clearTranscriptOnListen: true, |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function setMessageEmit(messageContent = "", writeMode = "replace") { |
|
|
if (writeMode === "append") setMessage((prev) => prev + messageContent); |
|
|
else setMessage(messageContent ?? ""); |
|
|
|
|
|
|
|
|
window.dispatchEvent( |
|
|
new CustomEvent(PROMPT_INPUT_EVENT, { |
|
|
detail: { messageContent, writeMode }, |
|
|
}) |
|
|
); |
|
|
} |
|
|
|
|
|
const handleSubmit = async (event) => { |
|
|
event.preventDefault(); |
|
|
if (!message || message === "") return false; |
|
|
const prevChatHistory = [ |
|
|
...chatHistory, |
|
|
{ |
|
|
content: message, |
|
|
role: "user", |
|
|
attachments: parseAttachments(), |
|
|
}, |
|
|
{ |
|
|
content: "", |
|
|
role: "assistant", |
|
|
pending: true, |
|
|
userMessage: message, |
|
|
animate: true, |
|
|
}, |
|
|
]; |
|
|
|
|
|
if (listening) { |
|
|
|
|
|
endSTTSession(); |
|
|
} |
|
|
setChatHistory(prevChatHistory); |
|
|
setMessageEmit(""); |
|
|
setLoadingResponse(true); |
|
|
}; |
|
|
|
|
|
function endSTTSession() { |
|
|
SpeechRecognition.stopListening(); |
|
|
resetTranscript(); |
|
|
} |
|
|
|
|
|
const regenerateAssistantMessage = (chatId) => { |
|
|
const updatedHistory = chatHistory.slice(0, -1); |
|
|
const lastUserMessage = updatedHistory.slice(-1)[0]; |
|
|
Workspace.deleteChats(workspace.slug, [chatId]) |
|
|
.then(() => |
|
|
sendCommand({ |
|
|
text: lastUserMessage.content, |
|
|
autoSubmit: true, |
|
|
history: updatedHistory, |
|
|
attachments: lastUserMessage?.attachments, |
|
|
}) |
|
|
) |
|
|
.catch((e) => console.error(e)); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const sendCommand = async ({ |
|
|
text = "", |
|
|
autoSubmit = false, |
|
|
history = [], |
|
|
attachments = [], |
|
|
writeMode = "replace", |
|
|
} = {}) => { |
|
|
|
|
|
if (!autoSubmit) { |
|
|
setMessageEmit(text, writeMode); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (writeMode === "append") { |
|
|
const currentText = document.getElementById(PROMPT_INPUT_ID)?.value; |
|
|
text = currentText + text; |
|
|
} |
|
|
|
|
|
if (!text || text === "") return false; |
|
|
|
|
|
|
|
|
let prevChatHistory; |
|
|
if (history.length > 0) { |
|
|
|
|
|
prevChatHistory = [ |
|
|
...history, |
|
|
{ |
|
|
content: "", |
|
|
role: "assistant", |
|
|
pending: true, |
|
|
userMessage: text, |
|
|
attachments, |
|
|
animate: true, |
|
|
}, |
|
|
]; |
|
|
} else { |
|
|
prevChatHistory = [ |
|
|
...chatHistory, |
|
|
{ |
|
|
content: text, |
|
|
role: "user", |
|
|
attachments, |
|
|
}, |
|
|
{ |
|
|
content: "", |
|
|
role: "assistant", |
|
|
pending: true, |
|
|
userMessage: text, |
|
|
animate: true, |
|
|
}, |
|
|
]; |
|
|
} |
|
|
|
|
|
setChatHistory(prevChatHistory); |
|
|
setMessageEmit(""); |
|
|
setLoadingResponse(true); |
|
|
}; |
|
|
|
|
|
useEffect(() => { |
|
|
async function fetchReply() { |
|
|
const promptMessage = |
|
|
chatHistory.length > 0 ? chatHistory[chatHistory.length - 1] : null; |
|
|
const remHistory = chatHistory.length > 0 ? chatHistory.slice(0, -1) : []; |
|
|
var _chatHistory = [...remHistory]; |
|
|
|
|
|
|
|
|
if (!!websocket) { |
|
|
if (!promptMessage || !promptMessage?.userMessage) return false; |
|
|
window.dispatchEvent(new CustomEvent(CLEAR_ATTACHMENTS_EVENT)); |
|
|
websocket.send( |
|
|
JSON.stringify({ |
|
|
type: "awaitingFeedback", |
|
|
feedback: promptMessage?.userMessage, |
|
|
}) |
|
|
); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!promptMessage || !promptMessage?.userMessage) return false; |
|
|
|
|
|
|
|
|
|
|
|
const attachments = promptMessage?.attachments ?? parseAttachments(); |
|
|
window.dispatchEvent(new CustomEvent(CLEAR_ATTACHMENTS_EVENT)); |
|
|
|
|
|
await Workspace.multiplexStream({ |
|
|
workspaceSlug: workspace.slug, |
|
|
threadSlug, |
|
|
prompt: promptMessage.userMessage, |
|
|
chatHandler: (chatResult) => |
|
|
handleChat( |
|
|
chatResult, |
|
|
setLoadingResponse, |
|
|
setChatHistory, |
|
|
remHistory, |
|
|
_chatHistory, |
|
|
setSocketId |
|
|
), |
|
|
attachments, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
loadingResponse === true && fetchReply(); |
|
|
}, [loadingResponse, chatHistory, workspace]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
function handleWSS() { |
|
|
try { |
|
|
if (!socketId || !!websocket) return; |
|
|
const socket = new WebSocket( |
|
|
`${websocketURI()}/api/agent-invocation/${socketId}` |
|
|
); |
|
|
|
|
|
window.addEventListener(ABORT_STREAM_EVENT, () => { |
|
|
window.dispatchEvent(new CustomEvent(AGENT_SESSION_END)); |
|
|
websocket.close(); |
|
|
}); |
|
|
|
|
|
socket.addEventListener("message", (event) => { |
|
|
setLoadingResponse(true); |
|
|
try { |
|
|
handleSocketResponse(event, setChatHistory); |
|
|
} catch (e) { |
|
|
console.error("Failed to parse data"); |
|
|
window.dispatchEvent(new CustomEvent(AGENT_SESSION_END)); |
|
|
socket.close(); |
|
|
} |
|
|
setLoadingResponse(false); |
|
|
}); |
|
|
|
|
|
socket.addEventListener("close", (_event) => { |
|
|
window.dispatchEvent(new CustomEvent(AGENT_SESSION_END)); |
|
|
setChatHistory((prev) => [ |
|
|
...prev.filter((msg) => !!msg.content), |
|
|
{ |
|
|
uuid: v4(), |
|
|
type: "statusResponse", |
|
|
content: "Agent session complete.", |
|
|
role: "assistant", |
|
|
sources: [], |
|
|
closed: true, |
|
|
error: null, |
|
|
animate: false, |
|
|
pending: false, |
|
|
}, |
|
|
]); |
|
|
setLoadingResponse(false); |
|
|
setWebsocket(null); |
|
|
setSocketId(null); |
|
|
}); |
|
|
setWebsocket(socket); |
|
|
window.dispatchEvent(new CustomEvent(AGENT_SESSION_START)); |
|
|
window.dispatchEvent(new CustomEvent(CLEAR_ATTACHMENTS_EVENT)); |
|
|
} catch (e) { |
|
|
setChatHistory((prev) => [ |
|
|
...prev.filter((msg) => !!msg.content), |
|
|
{ |
|
|
uuid: v4(), |
|
|
type: "abort", |
|
|
content: e.message, |
|
|
role: "assistant", |
|
|
sources: [], |
|
|
closed: true, |
|
|
error: e.message, |
|
|
animate: false, |
|
|
pending: false, |
|
|
}, |
|
|
]); |
|
|
setLoadingResponse(false); |
|
|
setWebsocket(null); |
|
|
setSocketId(null); |
|
|
} |
|
|
} |
|
|
handleWSS(); |
|
|
}, [socketId]); |
|
|
|
|
|
return ( |
|
|
<div |
|
|
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }} |
|
|
className="transition-all duration-500 relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll no-scroll z-[2]" |
|
|
> |
|
|
{isMobile && <SidebarMobileHeader />} |
|
|
<DnDFileUploaderWrapper> |
|
|
<MetricsProvider> |
|
|
<ChatHistory |
|
|
history={chatHistory} |
|
|
workspace={workspace} |
|
|
sendCommand={sendCommand} |
|
|
updateHistory={setChatHistory} |
|
|
regenerateAssistantMessage={regenerateAssistantMessage} |
|
|
hasAttachments={files.length > 0} |
|
|
/> |
|
|
</MetricsProvider> |
|
|
<PromptInput |
|
|
submit={handleSubmit} |
|
|
onChange={handleMessageChange} |
|
|
isStreaming={loadingResponse} |
|
|
sendCommand={sendCommand} |
|
|
attachments={files} |
|
|
/> |
|
|
</DnDFileUploaderWrapper> |
|
|
<ChatTooltips /> |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|