Spaces:
Paused
Paused
| import { | |
| useState, | |
| useEffect, | |
| lazy, | |
| Suspense, | |
| useRef, | |
| KeyboardEvent, | |
| } from "react"; | |
| import { | |
| Card, | |
| Text, | |
| Textarea, | |
| Button, | |
| Stack, | |
| Group, | |
| Paper, | |
| } from "@mantine/core"; | |
| import { IconSend } from "@tabler/icons-react"; | |
| import { | |
| ChatMessage, | |
| generateChatResponse, | |
| } from "../../modules/textGeneration"; | |
| import { addLogEntry } from "../../modules/logEntries"; | |
| import { usePubSub } from "create-pubsub/react"; | |
| import { settingsPubSub } from "../../modules/pubSub"; | |
| import { match } from "ts-pattern"; | |
| const FormattedMarkdown = lazy(() => import("./FormattedMarkdown")); | |
| export default function ChatInterface({ | |
| initialQuery, | |
| initialResponse, | |
| }: { | |
| initialQuery: string; | |
| initialResponse: string; | |
| }) { | |
| const [messages, setMessages] = useState<ChatMessage[]>([]); | |
| const [input, setInput] = useState(""); | |
| const [isGenerating, setIsGenerating] = useState(false); | |
| const [streamedResponse, setStreamedResponse] = useState(""); | |
| const latestResponseRef = useRef(""); | |
| const [settings] = usePubSub(settingsPubSub); | |
| useEffect(() => { | |
| setMessages([ | |
| { role: "user", content: initialQuery }, | |
| { role: "assistant", content: initialResponse }, | |
| ]); | |
| }, [initialQuery, initialResponse]); | |
| const handleSend = async () => { | |
| if (input.trim() === "" || isGenerating) return; | |
| const newMessages = [...messages, { role: "user", content: input }]; | |
| setMessages(newMessages); | |
| setInput(""); | |
| setIsGenerating(true); | |
| setStreamedResponse(""); | |
| latestResponseRef.current = ""; | |
| try { | |
| addLogEntry("User sent a follow-up question"); | |
| await generateChatResponse(newMessages, (partialResponse) => { | |
| setStreamedResponse(partialResponse); | |
| latestResponseRef.current = partialResponse; | |
| }); | |
| setMessages((prevMessages) => [ | |
| ...prevMessages, | |
| { role: "assistant", content: latestResponseRef.current }, | |
| ]); | |
| addLogEntry("AI responded to follow-up question"); | |
| } catch (error) { | |
| addLogEntry(`Error generating chat response: ${error}`); | |
| setMessages((prevMessages) => [ | |
| ...prevMessages, | |
| { | |
| role: "assistant", | |
| content: "Sorry, I encountered an error while generating a response.", | |
| }, | |
| ]); | |
| } finally { | |
| setIsGenerating(false); | |
| setStreamedResponse(""); | |
| } | |
| }; | |
| const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => { | |
| match([event, settings.enterToSubmit]) | |
| .with([{ code: "Enter", shiftKey: false }, true], () => { | |
| event.preventDefault(); | |
| handleSend(); | |
| }) | |
| .with([{ code: "Enter", shiftKey: true }, false], () => { | |
| event.preventDefault(); | |
| handleSend(); | |
| }) | |
| .otherwise(() => undefined); | |
| }; | |
| return ( | |
| <Card withBorder shadow="sm" radius="md"> | |
| <Card.Section withBorder inheritPadding py="xs"> | |
| <Text fw={500}>Follow-up questions</Text> | |
| </Card.Section> | |
| <Stack gap="md" pt="md"> | |
| {messages.slice(2).length > 0 && ( | |
| <Stack gap="md"> | |
| {messages.slice(2).map((message, index) => ( | |
| <Paper | |
| key={index} | |
| shadow="xs" | |
| radius="xl" | |
| p="sm" | |
| maw="90%" | |
| style={{ | |
| alignSelf: | |
| message.role === "user" ? "flex-end" : "flex-start", | |
| }} | |
| > | |
| <Suspense> | |
| <FormattedMarkdown>{message.content}</FormattedMarkdown> | |
| </Suspense> | |
| </Paper> | |
| ))} | |
| {isGenerating && streamedResponse.length > 0 && ( | |
| <Paper | |
| shadow="xs" | |
| radius="xl" | |
| p="sm" | |
| maw="90%" | |
| style={{ alignSelf: "flex-start" }} | |
| > | |
| <Suspense> | |
| <FormattedMarkdown>{streamedResponse}</FormattedMarkdown> | |
| </Suspense> | |
| </Paper> | |
| )} | |
| </Stack> | |
| )} | |
| <Group align="flex-end" style={{ position: "relative" }}> | |
| <Textarea | |
| placeholder="Anything else you would like to know?" | |
| value={input} | |
| onChange={(event) => setInput(event.currentTarget.value)} | |
| onKeyDown={handleKeyDown} | |
| autosize | |
| minRows={1} | |
| maxRows={4} | |
| style={{ flexGrow: 1, paddingRight: "50px" }} | |
| disabled={isGenerating} | |
| /> | |
| <Button | |
| size="sm" | |
| variant="default" | |
| onClick={handleSend} | |
| loading={isGenerating} | |
| style={{ | |
| height: "100%", | |
| position: "absolute", | |
| right: 0, | |
| top: 0, | |
| bottom: 0, | |
| borderTopLeftRadius: 0, | |
| borderBottomLeftRadius: 0, | |
| }} | |
| > | |
| <IconSend size={16} /> | |
| </Button> | |
| </Group> | |
| </Stack> | |
| </Card> | |
| ); | |
| } | |