import { ActionIcon, Alert, Badge, Box, Card, Group, ScrollArea, Text, Tooltip, } from "@mantine/core"; import { IconArrowsMaximize, IconArrowsMinimize, IconHandStop, IconInfoCircle, IconRefresh, IconVolume2, } from "@tabler/icons-react"; import type { PublishFunction } from "create-pubsub"; import { usePubSub } from "create-pubsub/react"; import { type ReactNode, Suspense, lazy, useMemo, useState } from "react"; import { addLogEntry } from "../../modules/logEntries"; import { settingsPubSub } from "../../modules/pubSub"; import { searchAndRespond } from "../../modules/textGeneration"; const FormattedMarkdown = lazy(() => import("./FormattedMarkdown")); const CopyIconButton = lazy(() => import("./CopyIconButton")); export default function AiResponseContent({ textGenerationState, response, setTextGenerationState, }: { textGenerationState: string; response: string; setTextGenerationState: PublishFunction< | "failed" | "awaitingSearchResults" | "preparingToGenerate" | "idle" | "loadingModel" | "generating" | "interrupted" | "completed" >; }) { const [settings, setSettings] = usePubSub(settingsPubSub); const [isSpeaking, setIsSpeaking] = useState(false); const ConditionalScrollArea = useMemo( () => ({ children }: { children: ReactNode }) => { return settings.enableAiResponseScrolling ? ( {children} ) : ( {children} ); }, [settings.enableAiResponseScrolling], ); function speakResponse(text: string) { if (isSpeaking) { self.speechSynthesis.cancel(); setIsSpeaking(false); return; } const prepareTextForSpeech = (textToClean: string) => { const withoutLinks = textToClean.replace(/\[([^\]]+)\]\([^)]+\)/g, ""); const withoutMarkdown = withoutLinks.replace(/[#*`_~\[\]]/g, ""); return withoutMarkdown; }; const utterance = new SpeechSynthesisUtterance(prepareTextForSpeech(text)); const voices = self.speechSynthesis.getVoices(); if (voices.length > 0 && settings.selectedVoiceId) { const voice = voices.find( (voice) => voice.voiceURI === settings.selectedVoiceId, ); if (voice) { utterance.voice = voice; utterance.lang = voice.lang; } } utterance.onerror = () => { addLogEntry("Failed to speak response"); setIsSpeaking(false); }; utterance.onend = () => setIsSpeaking(false); setIsSpeaking(true); self.speechSynthesis.speak(utterance); } return ( {textGenerationState === "generating" ? "Generating AI Response..." : "AI Response"} {textGenerationState === "interrupted" && ( Interrupted )} {textGenerationState === "generating" ? ( setTextGenerationState("interrupted")} variant="subtle" color="gray" > ) : ( searchAndRespond()} variant="subtle" color="gray" > )} speakResponse(response)} variant="subtle" color={isSpeaking ? "blue" : "gray"} > {settings.enableAiResponseScrolling ? ( { setSettings({ ...settings, enableAiResponseScrolling: false, }); }} variant="subtle" color="gray" > ) : ( { setSettings({ ...settings, enableAiResponseScrolling: true, }); }} variant="subtle" color="gray" > )} {response} {textGenerationState === "failed" && ( } > Could not generate response. Please try refreshing the page. )} ); }