diff --git a/app/components/Controls.tsx b/app/components/Controls.tsx index d6822e13..3dc5d2a1 100644 --- a/app/components/Controls.tsx +++ b/app/components/Controls.tsx @@ -8,6 +8,12 @@ import { SendIcon } from "./icons/SendIcon"; import { Settings } from "./Settings"; import { useMicrophone } from "../context/Microphone"; import { useNowPlaying } from "react-nowplaying"; +import { useSubmit } from "../lib/hooks/useSubmit"; + +// Better to use library, a lot of complexity is involved +// in building the resizable input +import TextareaAutosize from 'react-textarea-autosize'; + export const Controls = ({ input, @@ -21,6 +27,7 @@ export const Controls = ({ messages: Message[]; }) => { const { startMicrophone, stopMicrophone, microphoneOpen } = useMicrophone(); + const { formRef, onKeyDown } = useSubmit() useEffect(() => { startMicrophone(); @@ -46,13 +53,15 @@ export const Controls = ({ (e: any) => { handleSubmit(e); stopAudio(); + e.target.value = ''; + handleInputChange(e) }, // eslint-disable-next-line react-hooks/exhaustive-deps [stopAudio, handleSubmit] ); return ( -
+
@@ -60,7 +69,7 @@ export const Controls = ({
microphoneToggle(e)} - className={`w-20 sm:w-24 py-4 px-2 sm:px-8 rounded-s-full font-bold bg-[#101014] text-light-900 text-sm sm:text-base flex items-center justify-center group`} + className={`rounded-tl-[2rem] rounded-bl-[2rem] w-16 md:w-20 sm:w-24 py-2 md:py-4 px-2 h-full sm:px-8 font-bold bg-[#101014] text-light-900 text-sm sm:text-base flex items-center justify-center group`} > {microphoneOpen && (
)}
- +
{/* {microphoneOpen ? ( @@ -94,24 +103,30 @@ export const Controls = ({ - - - +
+
+ +
- +
+ +
- - +
diff --git a/app/components/Conversation.tsx b/app/components/Conversation.tsx index 9f715a10..66c90bd5 100644 --- a/app/components/Conversation.tsx +++ b/app/components/Conversation.tsx @@ -452,10 +452,7 @@ export default function Conversation(): JSX.Element { >
{initialLoad ? ( - + ) : ( <> {chatMessages.length > 0 && diff --git a/app/components/Download.tsx b/app/components/Download.tsx index 3dfceb0c..f9fe92c0 100644 --- a/app/components/Download.tsx +++ b/app/components/Download.tsx @@ -9,7 +9,7 @@ const DownloadButton = ({ content }: { content: string }) => { return ( void, connecting: boolean }) => { @@ -27,9 +25,9 @@ export const InitialLoad = ({ fn, connecting = true }: { fn: () => void, connect
-
+
{connecting ? ( -
+
Connecting...
diff --git a/app/components/LeftBubble.tsx b/app/components/LeftBubble.tsx index 467165b6..746f3caf 100644 --- a/app/components/LeftBubble.tsx +++ b/app/components/LeftBubble.tsx @@ -8,26 +8,31 @@ import { TextContent } from "./TextContext"; export const LeftBubble = ({ message }: { message: Message }) => { return ( <> -
-
-
- -
-
-
- -
- +
+
+
+
+ +
+
+
+ +
+ +
-
- +
+
+ +
+
-
- +
+
); diff --git a/app/components/MessageMeta.tsx b/app/components/MessageMeta.tsx index 0e928733..68f6857d 100644 --- a/app/components/MessageMeta.tsx +++ b/app/components/MessageMeta.tsx @@ -38,9 +38,9 @@ const MessageMeta = ({ const ttsTotal = foundAudio.networkLatency; return ( - <> +
@@ -81,7 +81,7 @@ const MessageMeta = ({ TTS total: {(ttsTotal / 1000).toFixed(1)}s
- +
); } }; diff --git a/app/components/RightBubble.tsx b/app/components/RightBubble.tsx index fa537f74..819d5b5f 100644 --- a/app/components/RightBubble.tsx +++ b/app/components/RightBubble.tsx @@ -12,17 +12,20 @@ export const RightBubble = ({ }) => { return ( <> -
-
-
- -
-
-
- +
+
+
+
+ +
+
+
+ +
+
); diff --git a/app/components/Settings.tsx b/app/components/Settings.tsx index 918b8767..a2f7e86b 100644 --- a/app/components/Settings.tsx +++ b/app/components/Settings.tsx @@ -117,7 +117,7 @@ export const Settings = () => {
diff --git a/app/context/Deepgram.tsx b/app/context/Deepgram.tsx index 358bc7b1..0e8213a7 100644 --- a/app/context/Deepgram.tsx +++ b/app/context/Deepgram.tsx @@ -17,12 +17,13 @@ import { useState, } from "react"; import { useToast } from "./Toast"; +import { useLocalStorage } from "../lib/hooks/useLocalStorage"; type DeepgramContext = { - ttsOptions: SpeakSchema | undefined; - setTtsOptions: Dispatch>; - sttOptions: LiveSchema | undefined; - setSttOptions: Dispatch>; + ttsOptions: SpeakSchema; + setTtsOptions: (value: SpeakSchema) => void; + sttOptions: LiveSchema; + setSttOptions: (value: LiveSchema) => void; connection: LiveClient | undefined; connectionReady: boolean; }; @@ -33,6 +34,9 @@ interface DeepgramContextInterface { const DeepgramContext = createContext({} as DeepgramContext); +const DEFAULT_TTS_MODEL = 'aura-asteria-en'; +const DEFAULT_STT_MODEL = 'nova-2'; +; /** * TTS Voice Options */ @@ -44,7 +48,7 @@ const voices: { accent: string; }; } = { - "aura-asteria-en": { + [DEFAULT_TTS_MODEL]: { name: "Asteria", avatar: "/aura-asteria-en.svg", language: "English", @@ -132,8 +136,17 @@ const getApiKey = async (): Promise => { const DeepgramContextProvider = ({ children }: DeepgramContextInterface) => { const { toast } = useToast(); - const [ttsOptions, setTtsOptions] = useState(); - const [sttOptions, setSttOptions] = useState(); + const [ttsOptions, setTtsOptions] = useLocalStorage('ttsModel', { + model: DEFAULT_TTS_MODEL + }); + const [sttOptions, setSttOptions] = useLocalStorage('sttModel', { + model: DEFAULT_STT_MODEL, + interim_results: true, + smart_format: true, + endpointing: 350, + utterance_end_ms: 1000, + filler_words: true, + }); const [connection, setConnection] = useState(); const [connecting, setConnecting] = useState(false); const [connectionReady, setConnectionReady] = useState(false); @@ -145,14 +158,7 @@ const DeepgramContextProvider = ({ children }: DeepgramContextInterface) => { const connection = new LiveClient( await getApiKey(), {}, - { - model: "nova-2", - interim_results: true, - smart_format: true, - endpointing: 550, - utterance_end_ms: 1500, - filler_words: true, - } + sttOptions ); setConnection(connection); @@ -164,26 +170,6 @@ const DeepgramContextProvider = ({ children }: DeepgramContextInterface) => { useEffect(() => { // it must be the first open of the page, let's set up the defaults - /** - * Default TTS Voice when the app loads. - */ - if (ttsOptions === undefined) { - setTtsOptions({ - model: "aura-asteria-en", - }); - } - - if (!sttOptions === undefined) { - setSttOptions({ - model: "nova-2", - interim_results: true, - smart_format: true, - endpointing: 350, - utterance_end_ms: 1000, - filler_words: true, - }); - } - if (connection === undefined) { connect(); } diff --git a/app/globals.css b/app/globals.css index f09b225f..8a0c5a08 100644 --- a/app/globals.css +++ b/app/globals.css @@ -68,6 +68,13 @@ body { @apply leading-normal break-words; } +.pre-overflow-y-auto pre { + @apply overflow-y-auto; +} + +.word-break { + word-break: break-word; +} .markdown > * + * { @apply my-2; } diff --git a/app/lib/hooks/useLocalStorage.ts b/app/lib/hooks/useLocalStorage.ts new file mode 100644 index 00000000..b8eff6c3 --- /dev/null +++ b/app/lib/hooks/useLocalStorage.ts @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react' + +export const useLocalStorage = ( + key: string, + initialValue: T +): [T, (value: T) => void] => { + const [storedValue, setStoredValue] = useState(initialValue) + + useEffect(() => { + // Retrieve from localStorage + const item = window.localStorage.getItem(key) + if (item) { + setStoredValue(JSON.parse(item)) + } + }, [key]) + + const setValue = (value: T) => { + // Save state + setStoredValue(value) + // Save to localStorage + window.localStorage.setItem(key, JSON.stringify(value)) + } + return [storedValue, setValue] +} \ No newline at end of file diff --git a/app/lib/hooks/useSubmit.tsx b/app/lib/hooks/useSubmit.tsx new file mode 100644 index 00000000..7a22a6ad --- /dev/null +++ b/app/lib/hooks/useSubmit.tsx @@ -0,0 +1,25 @@ +// help, taken from, +// https://github.com/vercel/ai-chatbot/blob/fa9f0947f0a7983cf5022cbbc1416910349dd5e4/lib/hooks/use-enter-submit.tsx +import { useRef, type RefObject } from 'react' + +export function useSubmit(): { + formRef: RefObject + onKeyDown: (event: React.KeyboardEvent) => void +} { + const formRef = useRef(null) + + const handleKeyDown = ( + event: React.KeyboardEvent + ): void => { + if ( + !event.shiftKey && + !event.nativeEvent.isComposing && + event.key === 'Enter' + ) { + formRef.current?.requestSubmit() + event.preventDefault() + } + } + + return { formRef, onKeyDown: handleKeyDown } +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 0af434de..cae5fcd9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -21,11 +21,11 @@ export default function Home() {