Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/mobile responsive and store voice #31

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions app/components/Controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -21,6 +27,7 @@ export const Controls = ({
messages: Message[];
}) => {
const { startMicrophone, stopMicrophone, microphoneOpen } = useMicrophone();
const { formRef, onKeyDown } = useSubmit()

useEffect(() => {
startMicrophone();
Expand All @@ -46,21 +53,23 @@ export const Controls = ({
(e: any) => {
handleSubmit(e);
stopAudio();
e.target.value = '';
handleInputChange(e)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[stopAudio, handleSubmit]
);

return (
<form onSubmit={submitter}>
<form onSubmit={submitter} ref={formRef}>
<div className="relative">
<div className="absolute w-full -top-[4.5rem] py-4 flex justify-between">
<Settings />
<Download messages={messages} />
</div>
<div className="flex bg-[#101014] rounded-full">
<span
className={`rounded-s-full ps-0.5 py-0.5 ${
className={`rounded-tl-md rounded-bl-md ps-0.5 py-0.5 ${
lukeocodes marked this conversation as resolved.
Show resolved Hide resolved
microphoneOpen
? "bg-gradient-to-r bg-gradient to-[#13EF93]/50 from-red-500"
: "bg-gradient-to-r bg-gradient to-[#13EF93]/50 from-[#149AFB]/80"
Expand All @@ -70,18 +79,18 @@ export const Controls = ({
<a
href="#"
onClick={(e: any) => 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-md rounded-bl-md 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`}
lukeocodes marked this conversation as resolved.
Show resolved Hide resolved
>
{microphoneOpen && (
<div className="w-auto items-center justify-center hidden sm:flex absolute shrink-0">
<MicrophoneIcon
micOpen={microphoneOpen}
className="h-6 animate-ping-short"
className="h-5 md:h-6 animate-ping-short"
/>
</div>
)}
<div className="w-auto flex items-center justify-center shrink-0">
<MicrophoneIcon micOpen={microphoneOpen} className="h-6" />
<MicrophoneIcon micOpen={microphoneOpen} className="h-5 md:h-6" />
</div>
{/* <span>
{microphoneOpen ? (
Expand All @@ -94,24 +103,30 @@ export const Controls = ({
</Tooltip>
</span>

<span className="flex-grow bg-[#13EF93]/50 py-0.5">
<input
type="text"
className="py-4 sm:px-4 w-full h-full bg-[#101014] text-light-900 border-0 text-sm sm:text-base outline-none focus:ring-0"
placeholder="Type a message to send..."
value={input}
onChange={handleInputChange}
/>
</span>
<div className="flex-grow bg-[#13EF93]/50 py-0.5 inline">
<div className=" bg-[#101014] h-full">
<TextareaAutosize
onKeyDown={onKeyDown}
rows={1}
spellCheck={false}
autoCorrect="off"
className="py-2 md:py-4 -mb-[0.4rem] min-h-10 overflow-hidden sm:px-4 w-full resize-none bg-[#101014] text-light-900 border-0 text-sm sm:text-base outline-none focus:ring-0"
placeholder="Send a message"
value={input}
onChange={handleInputChange}
/>
</div>

<span className="rounded-e-full bg-gradient-to-l to-[#13EF93]/50 from-[#149AFB]/80 pe-0.5 py-0.5">
</div>

<div className="inline h-auto rounded-tr-md rounded-br-md bg-gradient-to-l to-[#13EF93]/50 from-[#149AFB]/80 pe-0.5 py-0.5">
<Tooltip showArrow content="Send a message.">
<button className="w-20 sm:w-24 py-4 px-2 sm:px-8 rounded-e-full font-bold bg-[#101014] text-light-900 text-sm sm:text-base flex items-center justify-center">
<button type="submit" className="w-16 md:w-20 h-full sm:w-24 py-2 md:py-4 px-2 sm:px-8 rounded-tr-md rounded-br-md font-bold bg-[#101014] text-light-900 text-sm sm:text-base flex items-center justify-center">
{/* <span>Send text</span> */}
<SendIcon className="h-6 w-6" />
<SendIcon className="h-5 w-5 md:h-6 md:w-6" />
</button>
</Tooltip>
</span>
</div>
</div>
</div>
</form>
Expand Down
5 changes: 1 addition & 4 deletions app/components/Conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,7 @@ export default function Conversation(): JSX.Element {
>
<div className="grid grid-cols-12 overflow-x-auto gap-y-2">
{initialLoad ? (
<InitialLoad
fn={startConversation}
connecting={!connection}
/>
<InitialLoad fn={startConversation} connecting={!connection} />
) : (
<>
{chatMessages.length > 0 &&
Expand Down
2 changes: 1 addition & 1 deletion app/components/Download.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const DownloadButton = ({ content }: { content: string }) => {
return (
<span className="bg-white/10 rounded-full flex">
<a
className={`relative m-px bg-black w-[10.5rem] md:w-10 h-10 rounded-full text-sm p-2.5 group hover:w-[10.5rem] transition-all ease-in-out duration-1000 overflow-hidden whitespace-nowrap`}
className={`relative m-px bg-black md:w-[10.5rem] w-10 h-10 rounded-full text-sm p-2.5 group md:hover:w-[10.5rem] transition-all ease-in-out duration-1000 overflow-hidden whitespace-nowrap`}
download="transcript.txt"
target="_blank"
rel="noreferrer"
Expand Down
8 changes: 3 additions & 5 deletions app/components/InitialLoad.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { ExclamationIcon } from "./icons/ExclamationIcon";
import { Headphones } from "./Headphones";
import { isBrowser, isIOS } from "react-device-detect";
import Image from "next/image";
import { isBrowser } from "react-device-detect";
import { Spinner } from "@nextui-org/react";

export const InitialLoad = ({ fn, connecting = true }: { fn: () => void, connecting: boolean }) => {
Expand All @@ -27,9 +25,9 @@ export const InitialLoad = ({ fn, connecting = true }: { fn: () => void, connect
</ul>
</div>
<span className="mt-4 block font-semibold">
<div className="bg-white text-black rounded px-10 py-3 font-semibold sm:w-fit sm:mx-auto opacity-90">
<div className="bg-white text-black rounded px-6 md:px-8 py-3 font-semibold sm:w-fit sm:mx-auto opacity-90">
{connecting ? (
<div className="w-auto h-full items-center flex justify-center opacity-40 cursor-not-allowed">
<div className="w-full h-full items-center flex justify-center opacity-40 cursor-not-allowed">
<Spinner size={"sm"} className="-mt-1 mr-2" />
Connecting...
</div>
Expand Down
33 changes: 19 additions & 14 deletions app/components/LeftBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,31 @@ import { TextContent } from "./TextContext";
export const LeftBubble = ({ message }: { message: Message }) => {
return (
<>
<div className="col-start-1 col-end-13 sm:col-end-11 md:col-end-9 lg:col-end-8 xl:col-end-7 px-3 pt-3">
<div className="flex items-start gap-2">
<div className="h-5 w-12 text-white shrink-0">
<AgentAvatar message={message} />
</div>
<div className="glass flex p-4 rounded-e-xl rounded-es-xl">
<div className="flex flex-col">
<MessageHeader message={message} />
<div className="text-sm font-normal pt-2 text-white/80 markdown">
<TextContent text={message.content} />
<div className="col-start-1 col-end-13 sm:col-end-11 md:col-end-9 lg:col-end-8 xl:col-end-7 md:px-3 pt-3">
<div className="flex items-start gap-2 flex-col md:flex-row">
<div className="flex items-start gap-2 flex-col md:flex-row max-w-full md:max-w-none">
<div className="min-w-12 text-white shrink-0">
<AgentAvatar message={message} />
</div>
<div className="glass flex p-4 rounded-e-xl rounded-es-xl max-w-full md:max-w-none">
<div className="flex flex-col overflow-hidden pre-overflow-y-auto">
<MessageHeader message={message} />
<div className="text-sm font-normal pt-2 text-white/80 markdown">
<TextContent text={message.content} />
</div>
</div>
</div>
</div>
<div className="h-6 w-6 shrink-0 self-center">
<MessageAudio message={message} />
<div className="md:px-1 pb-3 flex gap-2 self-start md:self-center">
<div className="h-6 w-6 shrink-0">
<MessageAudio message={message} />
</div>
<MessageMeta className="md:hidden" message={message} />
</div>
</div>
</div>
<div className="col-start-1 col-end-13 px-3 pb-3">
<MessageMeta className="ml-14" message={message} />
<div className="hidden col-start-1 col-end-13 md:px-3 pb-3 md:flex gap-2">
<MessageMeta className="md:ml-14" message={message} />
</div>
</>
);
Expand Down
6 changes: 3 additions & 3 deletions app/components/MessageMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ const MessageMeta = ({
const ttsTotal = foundAudio.networkLatency;

return (
<>
<div className="flex flex-col">
<div
className={`flex gap-x-2.5 text-xs text-[#BBBBBF] ${className} flex-wrap`}
className={`flex gap-x-2.5 pt-1 text-xs text-[#BBBBBF] ${className} flex-wrap`}
>
<span>
<BoltIcon className="w-[1em] h-[1em]" />
Expand Down Expand Up @@ -81,7 +81,7 @@ const MessageMeta = ({
TTS total: {(ttsTotal / 1000).toFixed(1)}s
</span>
</div>
</>
</div>
);
}
};
Expand Down
19 changes: 11 additions & 8 deletions app/components/RightBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ export const RightBubble = ({
}) => {
return (
<>
<div className="col-start-6 col-end-13 p-3">
<div className="flex justify-start flex-row-reverse gap-2">
<div className="h-6 w-6 text-white shrink-0 pt-1 mt-1 rounded-full bg-black border border-zinc-300 overflow-hidden">
<UserAvatar />
</div>
<div className="glass relative text-sm py-2 px-4 shadow rounded-s-xl rounded-ee-xl">
<div className="text-sm font-normal text-white/80 markdown min-w-[10em]">
<TextContent text={message?.content ?? text ?? ""} />
<div className="col-start-1 col-end-13 md:p-3">
<div className="flex flex-row justify-end">
<div className="flex justify-end md:justify-start gap-2 flex-col md:flex-row-reverse">
<div className="self-end md:self-start h-6 w-6 text-white shrink-0 pt-1 mt-1 rounded-full bg-black border border-zinc-300 overflow-hidden">
<UserAvatar />
</div>
<div className="glass relative text-sm py-2 px-4 shadow rounded-s-xl rounded-ee-xl">
<div className="text-sm font-normal text-white/80 markdown word-break">
<TextContent text={message?.content ?? text ?? ""} />
</div>
</div>
</div>
</div>

</div>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion app/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const Settings = () => {
<div className="flex items-center gap-2.5 text-sm">
<span className="bg-gradient-to-r to-[#13EF93]/50 from-[#149AFB]/80 rounded-full flex">
<a
className={`relative m-px bg-black w-[9.25rem] md:w-10 h-10 rounded-full text-sm p-2.5 group hover:w-[9.25rem] transition-all ease-in-out duration-1000 overflow-hidden whitespace-nowrap`}
className={`relative m-px bg-black md:w-[9.25rem] w-10 h-10 rounded-full text-sm p-2.5 group md:hover:w-[9.25rem] transition-all ease-in-out duration-1000 overflow-hidden whitespace-nowrap`}
href="#"
onClick={onOpen}
>
Expand Down
56 changes: 21 additions & 35 deletions app/context/Deepgram.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<SetStateAction<SpeakSchema | undefined>>;
sttOptions: LiveSchema | undefined;
setSttOptions: Dispatch<SetStateAction<LiveSchema | undefined>>;
lukeocodes marked this conversation as resolved.
Show resolved Hide resolved
ttsOptions: SpeakSchema;
setTtsOptions: (value: SpeakSchema) => void;
sttOptions: LiveSchema;
setSttOptions: (value: LiveSchema) => void;
connection: LiveClient | undefined;
connectionReady: boolean;
};
Expand All @@ -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
*/
Expand All @@ -44,7 +48,7 @@ const voices: {
accent: string;
};
} = {
"aura-asteria-en": {
[DEFAULT_TTS_MODEL]: {
name: "Asteria",
avatar: "/aura-asteria-en.svg",
language: "English",
Expand Down Expand Up @@ -132,8 +136,17 @@ const getApiKey = async (): Promise<string> => {

const DeepgramContextProvider = ({ children }: DeepgramContextInterface) => {
const { toast } = useToast();
const [ttsOptions, setTtsOptions] = useState<SpeakSchema>();
const [sttOptions, setSttOptions] = useState<LiveSchema>();
const [ttsOptions, setTtsOptions] = useLocalStorage<SpeakSchema>('ttsModel', {
model: DEFAULT_TTS_MODEL
});
const [sttOptions, setSttOptions] = useLocalStorage<LiveSchema>('sttModel', {
model: DEFAULT_STT_MODEL,
interim_results: true,
smart_format: true,
endpointing: 350,
utterance_end_ms: 1000,
filler_words: true,
});
const [connection, setConnection] = useState<LiveClient>();
const [connecting, setConnecting] = useState<boolean>(false);
const [connectionReady, setConnectionReady] = useState<boolean>(false);
Expand All @@ -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);
Expand All @@ -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();
}
Expand Down
7 changes: 7 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
24 changes: 24 additions & 0 deletions app/lib/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect, useState } from 'react'

export const useLocalStorage = <T>(
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]
}
Loading