eslint fixes
parent
d9aa5aefbe
commit
7c5ffbbac7
|
@ -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 {
|
export class Backend extends EventEmitter {
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
register() {
|
register() {
|
||||||
backend = this;
|
backend.current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAuthenticated() {
|
get isAuthenticated() {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { createStreamingChatCompletion } from './openai';
|
||||||
import { createTitle } from './titles';
|
import { createTitle } from './titles';
|
||||||
import { ellipsize, sleep } from './utils';
|
import { ellipsize, sleep } from './utils';
|
||||||
import * as idb from './idb';
|
import * as idb from './idb';
|
||||||
import { getTokenCountForMessages, selectMessagesToSendSafely } from './tokenizer';
|
import { selectMessagesToSendSafely } from './tokenizer';
|
||||||
|
|
||||||
export const channel = new BroadcastChannel('chats');
|
export const channel = new BroadcastChannel('chats');
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,7 @@ export default function Header(props: HeaderProps) {
|
||||||
Share
|
Share
|
||||||
</HeaderButton>}
|
</HeaderButton>}
|
||||||
{backend && !context.authenticated && (
|
{backend && !context.authenticated && (
|
||||||
<HeaderButton onClick={() => backend?.signIn()}>Sign in <span className="hide-on-mobile">to sync</span></HeaderButton>
|
<HeaderButton onClick={() => backend.current?.signIn()}>Sign in <span className="hide-on-mobile">to sync</span></HeaderButton>
|
||||||
)}
|
)}
|
||||||
<HeaderButton icon="plus" onClick={onNewChat} loading={loading} variant="light">
|
<HeaderButton icon="plus" onClick={onNewChat} loading={loading} variant="light">
|
||||||
New Chat
|
New Chat
|
||||||
|
|
|
@ -45,13 +45,13 @@ export default function MessageInput(props: MessageInputProps) {
|
||||||
|
|
||||||
const onChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const onChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
context.setMessage(e.target.value);
|
context.setMessage(e.target.value);
|
||||||
}, [context.setMessage]);
|
}, [context]);
|
||||||
|
|
||||||
const onSubmit = useCallback(async () => {
|
const onSubmit = useCallback(async () => {
|
||||||
if (await context.onNewMessage(context.message)) {
|
if (await context.onNewMessage(context.message)) {
|
||||||
context.setMessage('');
|
context.setMessage('');
|
||||||
}
|
}
|
||||||
}, [context.message, context.onNewMessage, context.setMessage]);
|
}, [context]);
|
||||||
|
|
||||||
const onKeyDown = useCallback((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const onKeyDown = useCallback((e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.key === 'Enter' && e.shiftKey === false && !props.disabled) {
|
if (e.key === 'Enter' && e.shiftKey === false && !props.disabled) {
|
||||||
|
@ -71,8 +71,8 @@ export default function MessageInput(props: MessageInputProps) {
|
||||||
);
|
);
|
||||||
}, [onSubmit, props.disabled]);
|
}, [onSubmit, props.disabled]);
|
||||||
|
|
||||||
const openSystemPromptPanel = useCallback(() => context.settings.open('options', 'system-prompt'), []);
|
const openSystemPromptPanel = useCallback(() => context.settings.open('options', 'system-prompt'), [context.settings]);
|
||||||
const openTemperaturePanel = useCallback(() => context.settings.open('options', 'temperature'), []);
|
const openTemperaturePanel = useCallback(() => context.settings.open('options', 'temperature'), [context.settings]);
|
||||||
|
|
||||||
const messagesToDisplay = context.currentChat.messagesToDisplay;
|
const messagesToDisplay = context.currentChat.messagesToDisplay;
|
||||||
const disabled = context.generating
|
const disabled = context.generating
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default function ChatPage(props: any) {
|
||||||
container?.scrollTo({ top: offset, behavior: 'smooth' });
|
container?.scrollTo({ top: offset, behavior: 'smooth' });
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
}, [context.currentChat?.chatLoadedAt, context.currentChat?.messagesToDisplay.length]);
|
}, [context.currentChat?.chatLoadedAt, context.currentChat?.messagesToDisplay.length, props.share]);
|
||||||
|
|
||||||
const messagesToDisplay = context.currentChat.messagesToDisplay;
|
const messagesToDisplay = context.currentChat.messagesToDisplay;
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ export default function ChatPage(props: any) {
|
||||||
title: (id && messagesToDisplay.length) ? context.currentChat.chat?.title : null,
|
title: (id && messagesToDisplay.length) ? context.currentChat.chat?.title : null,
|
||||||
onShare: async () => {
|
onShare: async () => {
|
||||||
if (context.currentChat.chat) {
|
if (context.currentChat.chat) {
|
||||||
const id = await backend?.shareChat(context.currentChat.chat);
|
const id = await backend.current?.shareChat(context.currentChat.chat);
|
||||||
if (id) {
|
if (id) {
|
||||||
const slug = context.currentChat.chat.title
|
const slug = context.currentChat.chat.title
|
||||||
? '/' + slugify(context.currentChat.chat.title.toLocaleLowerCase())
|
? '/' + slugify(context.currentChat.chat.title.toLocaleLowerCase())
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Button } from '@mantine/core';
|
import { Button } from '@mantine/core';
|
||||||
import MessageInput from '../input';
|
|
||||||
import SettingsDrawer from '../settings';
|
|
||||||
import { useAppContext } from '../../context';
|
import { useAppContext } from '../../context';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,7 @@ export default function SettingsDrawer(props: SettingsDrawerProps) {
|
||||||
setParameters({ ...parameters, apiKey: event.currentTarget.value });
|
setParameters({ ...parameters, apiKey: event.currentTarget.value });
|
||||||
context.apiKeys.setOpenAIApiKey(event.currentTarget.value);
|
context.apiKeys.setOpenAIApiKey(event.currentTarget.value);
|
||||||
}} />
|
}} />
|
||||||
<p><a href="https://platform.openai.com/account/api-keys" target="_blank">Find your API key here.</a> Your API key is stored only on this device and never transmitted to anyone except OpenAI.</p>
|
<p><a href="https://platform.openai.com/account/api-keys" target="_blank" rel="noreferrer">Find your API key here.</a> Your API key is stored only on this device and never transmitted to anyone except OpenAI.</p>
|
||||||
<p>OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription.</p>
|
<p>OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription.</p>
|
||||||
</section>
|
</section>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
@ -211,7 +211,7 @@ export default function SettingsDrawer(props: SettingsDrawerProps) {
|
||||||
<section className={context.settings.option === 'elevenlabs-api-key' ? 'focused' : ''}>
|
<section className={context.settings.option === 'elevenlabs-api-key' ? 'focused' : ''}>
|
||||||
<h3>Your ElevenLabs Text-to-Speech API Key (optional)</h3>
|
<h3>Your ElevenLabs Text-to-Speech API Key (optional)</h3>
|
||||||
<TextInput placeholder="Paste your API key here" value={context.apiKeys.elevenlabs || ''} onChange={event => context.apiKeys.setElevenLabsApiKey(event.currentTarget.value)} />
|
<TextInput placeholder="Paste your API key here" value={context.apiKeys.elevenlabs || ''} onChange={event => context.apiKeys.setElevenLabsApiKey(event.currentTarget.value)} />
|
||||||
<p>Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). <a href="https://beta.elevenlabs.io" target="_blank">Click here to sign up.</a></p>
|
<p>Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). <a href="https://beta.elevenlabs.io" target="_blank" rel="noreferrer">Click here to sign up.</a></p>
|
||||||
<p>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.</p>
|
<p>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.</p>
|
||||||
</section>
|
</section>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
|
@ -50,16 +50,16 @@ export function useCreateAppContext(): Context {
|
||||||
|
|
||||||
const chatManager = useRef(ChatManagerInstance);
|
const chatManager = useRef(ChatManagerInstance);
|
||||||
const currentChat = useChat(chatManager.current, id, isShare);
|
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), []);
|
const updateAuth = useCallback((authenticated: boolean) => setAuthenticated(authenticated), []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
backend?.on('authenticated', updateAuth);
|
backend.current?.on('authenticated', updateAuth);
|
||||||
return () => {
|
return () => {
|
||||||
backend?.off('authenticated', updateAuth)
|
backend.current?.off('authenticated', updateAuth)
|
||||||
};
|
};
|
||||||
}, [backend]);
|
}, [updateAuth]);
|
||||||
|
|
||||||
const [openaiApiKey, setOpenAIApiKey] = useState<string | null>(
|
const [openaiApiKey, setOpenAIApiKey] = useState<string | null>(
|
||||||
localStorage.getItem('openai-api-key') || ''
|
localStorage.getItem('openai-api-key') || ''
|
||||||
|
@ -150,7 +150,7 @@ export function useCreateAppContext(): Context {
|
||||||
setTimeout(() => setGenerating(false), 4000);
|
setTimeout(() => setGenerating(false), 4000);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, [chatManager, openaiApiKey, id, parameters, message, currentChat.leaf]);
|
}, [chatManager, openaiApiKey, id, parameters, currentChat.leaf, navigate, isShare]);
|
||||||
|
|
||||||
const regenerateMessage = useCallback(async (message: Message) => {
|
const regenerateMessage = useCallback(async (message: Message) => {
|
||||||
if (isShare) {
|
if (isShare) {
|
||||||
|
@ -173,7 +173,7 @@ export function useCreateAppContext(): Context {
|
||||||
setTimeout(() => setGenerating(false), 4000);
|
setTimeout(() => setGenerating(false), 4000);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, [chatManager, openaiApiKey, id, parameters]);
|
}, [chatManager, openaiApiKey, parameters, isShare]);
|
||||||
|
|
||||||
const editMessage = useCallback(async (message: Message, content: string) => {
|
const editMessage = useCallback(async (message: Message, content: string) => {
|
||||||
if (isShare) {
|
if (isShare) {
|
||||||
|
@ -219,7 +219,7 @@ export function useCreateAppContext(): Context {
|
||||||
setTimeout(() => setGenerating(false), 4000);
|
setTimeout(() => setGenerating(false), 4000);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, [chatManager, openaiApiKey, id, parameters, message, currentChat.leaf]);
|
}, [chatManager, openaiApiKey, id, parameters, isShare, navigate]);
|
||||||
|
|
||||||
const context = useMemo<Context>(() => ({
|
const context = useMemo<Context>(() => ({
|
||||||
authenticated,
|
authenticated,
|
||||||
|
@ -258,7 +258,8 @@ export function useCreateAppContext(): Context {
|
||||||
regenerateMessage,
|
regenerateMessage,
|
||||||
editMessage,
|
editMessage,
|
||||||
}), [chatManager, authenticated, openaiApiKey, elevenLabsApiKey, settingsTab, option, voiceID,
|
}), [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;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,24 +273,26 @@ export default class ElevenLabsReader extends EventEmitter {
|
||||||
export function ElevenLabsReaderButton(props: { selector: string }) {
|
export function ElevenLabsReaderButton(props: { selector: string }) {
|
||||||
const context = useAppContext();
|
const context = useAppContext();
|
||||||
const [status, setStatus] = useState<'idle' | 'init' | 'playing' | 'buffering'>('idle');
|
const [status, setStatus] = useState<'idle' | 'init' | 'playing' | 'buffering'>('idle');
|
||||||
const [error, setError] = useState(false);
|
// const [error, setError] = useState(false);
|
||||||
const reader = useRef(new ElevenLabsReader());
|
const reader = useRef(new ElevenLabsReader());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reader.current.on('init', () => setStatus('init'));
|
const currentReader = reader.current;
|
||||||
reader.current.on('playing', () => setStatus('playing'));
|
|
||||||
reader.current.on('buffering', () => setStatus('buffering'));
|
currentReader.on('init', () => setStatus('init'));
|
||||||
reader.current.on('error', () => {
|
currentReader.on('playing', () => setStatus('playing'));
|
||||||
|
currentReader.on('buffering', () => setStatus('buffering'));
|
||||||
|
currentReader.on('error', () => {
|
||||||
setStatus('idle');
|
setStatus('idle');
|
||||||
setError(true);
|
// setError(true);
|
||||||
});
|
});
|
||||||
reader.current.on('done', () => setStatus('idle'));
|
currentReader.on('done', () => setStatus('idle'));
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
reader.current.removeAllListeners();
|
currentReader.removeAllListeners();
|
||||||
reader.current.stop();
|
currentReader.stop();
|
||||||
};
|
};
|
||||||
}, [reader.current, props.selector]);
|
}, [props.selector]);
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
if (status === 'idle') {
|
if (status === 'idle') {
|
||||||
|
|
10
src/idb.ts
10
src/idb.ts
|
@ -3,12 +3,10 @@ import * as idb from 'idb-keyval';
|
||||||
let supported = true;
|
let supported = true;
|
||||||
const inMemoryCache = new Map<string, any>();
|
const inMemoryCache = new Map<string, any>();
|
||||||
|
|
||||||
{
|
const testDB = indexedDB.open('idb-test');
|
||||||
var db = indexedDB.open('idb-test');
|
testDB.onerror = () => {
|
||||||
db.onerror = () => {
|
supported = false;
|
||||||
supported = false;
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function keys() {
|
export async function keys() {
|
||||||
if (supported) {
|
if (supported) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ export function useChatSpotlightProps() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
context.chat.on('update', () => setVersion(v => v + 1));
|
context.chat.on('update', () => setVersion(v => v + 1));
|
||||||
}, []);
|
}, [context.chat]);
|
||||||
|
|
||||||
const search = useCallback((query: string) => {
|
const search = useCallback((query: string) => {
|
||||||
return context.chat.search.query(query)
|
return context.chat.search.query(query)
|
||||||
|
@ -18,7 +18,7 @@ export function useChatSpotlightProps() {
|
||||||
...result,
|
...result,
|
||||||
onTrigger: () => navigate('/chat/' + result.chatID + (result.messageID ? '#msg-' + result.messageID : '')),
|
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(() => ({
|
const props = useMemo(() => ({
|
||||||
shortcut: ['mod + P'],
|
shortcut: ['mod + P'],
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default class SSE {
|
||||||
|
|
||||||
public headers = this.options.headers || {};
|
public headers = this.options.headers || {};
|
||||||
public payload = this.options.payload !== undefined ? this.options.payload : '';
|
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 withCredentials = !!this.options.withCredentials;
|
||||||
|
|
||||||
public FIELD_SEPARATOR = ':';
|
public FIELD_SEPARATOR = ':';
|
||||||
|
@ -109,7 +109,7 @@ export default class SSE {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.readyState == this.CONNECTING) {
|
if (this.readyState === this.CONNECTING) {
|
||||||
this.dispatchEvent(new CustomEvent('open'));
|
this.dispatchEvent(new CustomEvent('open'));
|
||||||
this._setReadyState(this.OPEN);
|
this._setReadyState(this.OPEN);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { backend } from "./backend";
|
import { backend } from "./backend";
|
||||||
import { ChatManager } from "./chat-manager";
|
import { ChatManager } from "./chat-manager";
|
||||||
import { useAppContext } from "./context";
|
|
||||||
import { Chat, Message } from './types';
|
import { Chat, Message } from './types';
|
||||||
|
|
||||||
export interface UseChatResult {
|
export interface UseChatResult {
|
||||||
|
@ -14,7 +13,7 @@ export interface UseChatResult {
|
||||||
|
|
||||||
export function useChat(chatManager: ChatManager, id: string | undefined | null, share = false): UseChatResult {
|
export function useChat(chatManager: ChatManager, id: string | undefined | null, share = false): UseChatResult {
|
||||||
const [chat, setChat] = useState<Chat | null | undefined>(null);
|
const [chat, setChat] = useState<Chat | null | undefined>(null);
|
||||||
const [version, setVersion] = useState(0);
|
const [_, setVersion] = useState(0);
|
||||||
|
|
||||||
// used to prevent auto-scroll when chat is first opened
|
// used to prevent auto-scroll when chat is first opened
|
||||||
const [chatLoadedAt, setLoadedAt] = useState(0);
|
const [chatLoadedAt, setLoadedAt] = useState(0);
|
||||||
|
@ -29,7 +28,7 @@ export function useChat(chatManager: ChatManager, id: string | undefined | null,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const c = await backend?.getSharedChat(id);
|
const c = await backend.current?.getSharedChat(id);
|
||||||
if (c) {
|
if (c) {
|
||||||
setChat(c);
|
setChat(c);
|
||||||
setVersion(v => v + 1);
|
setVersion(v => v + 1);
|
||||||
|
@ -38,7 +37,7 @@ export function useChat(chatManager: ChatManager, id: string | undefined | null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setChat(null);
|
setChat(null);
|
||||||
}, [id, share]);
|
}, [id, share, chatManager]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
@ -55,7 +54,7 @@ export function useChat(chatManager: ChatManager, id: string | undefined | null,
|
||||||
chatManager.off(id, update);
|
chatManager.off(id, update);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [id, update]);
|
}, [id, update, chatManager]);
|
||||||
|
|
||||||
const leaf = chat?.messages.mostRecentLeaf();
|
const leaf = chat?.messages.mostRecentLeaf();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue