diff --git a/src/backend.ts b/src/backend.ts index ffbb8fa..4662c29 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -22,15 +22,13 @@ chatManager.on('title', async (id: string, title: string) => { */ -export let backend: Backend | null = null; +export let backend: { + current?: Backend | null +} = {}; export class Backend extends EventEmitter { - constructor() { - super(); - } - register() { - backend = this; + backend.current = this; } get isAuthenticated() { diff --git a/src/chat-manager.ts b/src/chat-manager.ts index a4b96e2..a0f82cd 100644 --- a/src/chat-manager.ts +++ b/src/chat-manager.ts @@ -8,7 +8,7 @@ import { createStreamingChatCompletion } from './openai'; import { createTitle } from './titles'; import { ellipsize, sleep } from './utils'; import * as idb from './idb'; -import { getTokenCountForMessages, selectMessagesToSendSafely } from './tokenizer'; +import { selectMessagesToSendSafely } from './tokenizer'; export const channel = new BroadcastChannel('chats'); diff --git a/src/components/header.tsx b/src/components/header.tsx index 1ac8c64..3aa683e 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -158,7 +158,7 @@ export default function Header(props: HeaderProps) { Share } {backend && !context.authenticated && ( - backend?.signIn()}>Sign in to sync + backend.current?.signIn()}>Sign in to sync )} New Chat diff --git a/src/components/input.tsx b/src/components/input.tsx index d13eaca..32b1eb9 100644 --- a/src/components/input.tsx +++ b/src/components/input.tsx @@ -45,13 +45,13 @@ export default function MessageInput(props: MessageInputProps) { const onChange = useCallback((e: React.ChangeEvent) => { context.setMessage(e.target.value); - }, [context.setMessage]); + }, [context]); const onSubmit = useCallback(async () => { if (await context.onNewMessage(context.message)) { context.setMessage(''); } - }, [context.message, context.onNewMessage, context.setMessage]); + }, [context]); const onKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter' && e.shiftKey === false && !props.disabled) { @@ -71,8 +71,8 @@ export default function MessageInput(props: MessageInputProps) { ); }, [onSubmit, props.disabled]); - const openSystemPromptPanel = useCallback(() => context.settings.open('options', 'system-prompt'), []); - const openTemperaturePanel = useCallback(() => context.settings.open('options', 'temperature'), []); + const openSystemPromptPanel = useCallback(() => context.settings.open('options', 'system-prompt'), [context.settings]); + const openTemperaturePanel = useCallback(() => context.settings.open('options', 'temperature'), [context.settings]); const messagesToDisplay = context.currentChat.messagesToDisplay; const disabled = context.generating diff --git a/src/components/pages/chat.tsx b/src/components/pages/chat.tsx index 4a5863f..4a14b32 100644 --- a/src/components/pages/chat.tsx +++ b/src/components/pages/chat.tsx @@ -54,7 +54,7 @@ export default function ChatPage(props: any) { container?.scrollTo({ top: offset, behavior: 'smooth' }); }, 500); } - }, [context.currentChat?.chatLoadedAt, context.currentChat?.messagesToDisplay.length]); + }, [context.currentChat?.chatLoadedAt, context.currentChat?.messagesToDisplay.length, props.share]); const messagesToDisplay = context.currentChat.messagesToDisplay; @@ -67,7 +67,7 @@ export default function ChatPage(props: any) { title: (id && messagesToDisplay.length) ? context.currentChat.chat?.title : null, onShare: async () => { if (context.currentChat.chat) { - const id = await backend?.shareChat(context.currentChat.chat); + const id = await backend.current?.shareChat(context.currentChat.chat); if (id) { const slug = context.currentChat.chat.title ? '/' + slugify(context.currentChat.chat.title.toLocaleLowerCase()) diff --git a/src/components/pages/landing.tsx b/src/components/pages/landing.tsx index 9bbc592..e94c907 100644 --- a/src/components/pages/landing.tsx +++ b/src/components/pages/landing.tsx @@ -1,7 +1,5 @@ import styled from '@emotion/styled'; import { Button } from '@mantine/core'; -import MessageInput from '../input'; -import SettingsDrawer from '../settings'; import { useAppContext } from '../../context'; import { Page } from '../page'; diff --git a/src/components/settings.tsx b/src/components/settings.tsx index 8323323..10fc23f 100644 --- a/src/components/settings.tsx +++ b/src/components/settings.tsx @@ -167,7 +167,7 @@ export default function SettingsDrawer(props: SettingsDrawerProps) { setParameters({ ...parameters, apiKey: event.currentTarget.value }); context.apiKeys.setOpenAIApiKey(event.currentTarget.value); }} /> -

Find your API key here. Your API key is stored only on this device and never transmitted to anyone except OpenAI.

+

Find your API key here. Your API key is stored only on this device and never transmitted to anyone except OpenAI.

OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription.

@@ -211,7 +211,7 @@ export default function SettingsDrawer(props: SettingsDrawerProps) {

Your ElevenLabs Text-to-Speech API Key (optional)

context.apiKeys.setElevenLabsApiKey(event.currentTarget.value)} /> -

Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). Click here to sign up.

+

Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). Click here to sign up.

You can find your API key by clicking your avatar or initials in the top right of the ElevenLabs website, then clicking Profile. Your API key is stored only on this device and never transmitted to anyone except ElevenLabs.

diff --git a/src/context.tsx b/src/context.tsx index 10492c3..60a1442 100644 --- a/src/context.tsx +++ b/src/context.tsx @@ -50,16 +50,16 @@ export function useCreateAppContext(): Context { const chatManager = useRef(ChatManagerInstance); const currentChat = useChat(chatManager.current, id, isShare); - const [authenticated, setAuthenticated] = useState(backend?.isAuthenticated || false); + const [authenticated, setAuthenticated] = useState(backend.current?.isAuthenticated || false); const updateAuth = useCallback((authenticated: boolean) => setAuthenticated(authenticated), []); useEffect(() => { - backend?.on('authenticated', updateAuth); + backend.current?.on('authenticated', updateAuth); return () => { - backend?.off('authenticated', updateAuth) + backend.current?.off('authenticated', updateAuth) }; - }, [backend]); + }, [updateAuth]); const [openaiApiKey, setOpenAIApiKey] = useState( localStorage.getItem('openai-api-key') || '' @@ -150,7 +150,7 @@ export function useCreateAppContext(): Context { setTimeout(() => setGenerating(false), 4000); return true; - }, [chatManager, openaiApiKey, id, parameters, message, currentChat.leaf]); + }, [chatManager, openaiApiKey, id, parameters, currentChat.leaf, navigate, isShare]); const regenerateMessage = useCallback(async (message: Message) => { if (isShare) { @@ -173,7 +173,7 @@ export function useCreateAppContext(): Context { setTimeout(() => setGenerating(false), 4000); return true; - }, [chatManager, openaiApiKey, id, parameters]); + }, [chatManager, openaiApiKey, parameters, isShare]); const editMessage = useCallback(async (message: Message, content: string) => { if (isShare) { @@ -219,7 +219,7 @@ export function useCreateAppContext(): Context { setTimeout(() => setGenerating(false), 4000); return true; - }, [chatManager, openaiApiKey, id, parameters, message, currentChat.leaf]); + }, [chatManager, openaiApiKey, id, parameters, isShare, navigate]); const context = useMemo(() => ({ authenticated, @@ -258,7 +258,8 @@ export function useCreateAppContext(): Context { regenerateMessage, editMessage, }), [chatManager, authenticated, openaiApiKey, elevenLabsApiKey, settingsTab, option, voiceID, - generating, message, parameters, onNewMessage, regenerateMessage, editMessage, currentChat]); + generating, message, parameters, onNewMessage, regenerateMessage, editMessage, currentChat, + id, isShare]); return context; } diff --git a/src/elevenlabs.tsx b/src/elevenlabs.tsx index 68d6f74..ad62955 100644 --- a/src/elevenlabs.tsx +++ b/src/elevenlabs.tsx @@ -273,24 +273,26 @@ export default class ElevenLabsReader extends EventEmitter { export function ElevenLabsReaderButton(props: { selector: string }) { const context = useAppContext(); const [status, setStatus] = useState<'idle' | 'init' | 'playing' | 'buffering'>('idle'); - const [error, setError] = useState(false); + // const [error, setError] = useState(false); const reader = useRef(new ElevenLabsReader()); useEffect(() => { - reader.current.on('init', () => setStatus('init')); - reader.current.on('playing', () => setStatus('playing')); - reader.current.on('buffering', () => setStatus('buffering')); - reader.current.on('error', () => { + const currentReader = reader.current; + + currentReader.on('init', () => setStatus('init')); + currentReader.on('playing', () => setStatus('playing')); + currentReader.on('buffering', () => setStatus('buffering')); + currentReader.on('error', () => { setStatus('idle'); - setError(true); + // setError(true); }); - reader.current.on('done', () => setStatus('idle')); + currentReader.on('done', () => setStatus('idle')); return () => { - reader.current.removeAllListeners(); - reader.current.stop(); + currentReader.removeAllListeners(); + currentReader.stop(); }; - }, [reader.current, props.selector]); + }, [props.selector]); const onClick = useCallback(() => { if (status === 'idle') { diff --git a/src/idb.ts b/src/idb.ts index ff3e787..aeb5ecd 100644 --- a/src/idb.ts +++ b/src/idb.ts @@ -3,12 +3,10 @@ import * as idb from 'idb-keyval'; let supported = true; const inMemoryCache = new Map(); -{ - var db = indexedDB.open('idb-test'); - db.onerror = () => { - supported = false; - }; -} +const testDB = indexedDB.open('idb-test'); +testDB.onerror = () => { + supported = false; +}; export async function keys() { if (supported) { diff --git a/src/spotlight.tsx b/src/spotlight.tsx index ffb66fd..7160091 100644 --- a/src/spotlight.tsx +++ b/src/spotlight.tsx @@ -10,7 +10,7 @@ export function useChatSpotlightProps() { useEffect(() => { context.chat.on('update', () => setVersion(v => v + 1)); - }, []); + }, [context.chat]); const search = useCallback((query: string) => { return context.chat.search.query(query) @@ -18,7 +18,7 @@ export function useChatSpotlightProps() { ...result, onTrigger: () => navigate('/chat/' + result.chatID + (result.messageID ? '#msg-' + result.messageID : '')), })) - }, [navigate, version]); + }, [context.chat, navigate, version]); // eslint-disable-line react-hooks/exhaustive-deps const props = useMemo(() => ({ shortcut: ['mod + P'], diff --git a/src/sse.ts b/src/sse.ts index a422f95..1f3d35d 100644 --- a/src/sse.ts +++ b/src/sse.ts @@ -11,7 +11,7 @@ export default class SSE { public headers = this.options.headers || {}; public payload = this.options.payload !== undefined ? this.options.payload : ''; - public method = this.options.method || (this.payload && 'POST' || 'GET'); + public method = this.options.method ? this.options.method : (this.payload ? 'POST' : 'GET'); public withCredentials = !!this.options.withCredentials; public FIELD_SEPARATOR = ':'; @@ -109,7 +109,7 @@ export default class SSE { return; } - if (this.readyState == this.CONNECTING) { + if (this.readyState === this.CONNECTING) { this.dispatchEvent(new CustomEvent('open')); this._setReadyState(this.OPEN); } diff --git a/src/use-chat.ts b/src/use-chat.ts index 4cd3964..37ae507 100644 --- a/src/use-chat.ts +++ b/src/use-chat.ts @@ -1,7 +1,6 @@ import { useCallback, useEffect, useState } from "react"; import { backend } from "./backend"; import { ChatManager } from "./chat-manager"; -import { useAppContext } from "./context"; import { Chat, Message } from './types'; export interface UseChatResult { @@ -14,7 +13,7 @@ export interface UseChatResult { export function useChat(chatManager: ChatManager, id: string | undefined | null, share = false): UseChatResult { const [chat, setChat] = useState(null); - const [version, setVersion] = useState(0); + const [_, setVersion] = useState(0); // used to prevent auto-scroll when chat is first opened const [chatLoadedAt, setLoadedAt] = useState(0); @@ -29,7 +28,7 @@ export function useChat(chatManager: ChatManager, id: string | undefined | null, return; } } else { - const c = await backend?.getSharedChat(id); + const c = await backend.current?.getSharedChat(id); if (c) { setChat(c); setVersion(v => v + 1); @@ -38,7 +37,7 @@ export function useChat(chatManager: ChatManager, id: string | undefined | null, } } setChat(null); - }, [id, share]); + }, [id, share, chatManager]); useEffect(() => { if (id) { @@ -55,7 +54,7 @@ export function useChat(chatManager: ChatManager, id: string | undefined | null, chatManager.off(id, update); } }; - }, [id, update]); + }, [id, update, chatManager]); const leaf = chat?.messages.mostRecentLeaf();