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.
)}
);
}