commit
28dee69fb9
|
@ -2,7 +2,7 @@ import { BroadcastChannel } from 'broadcast-channel';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import MiniSearch, { SearchResult } from 'minisearch'
|
import MiniSearch, { SearchResult } from 'minisearch'
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { Chat, getOpenAIMessageFromMessage, Message, UserSubmittedMessage } from './types';
|
import { Chat, getOpenAIMessageFromMessage, Message, Parameters, UserSubmittedMessage } from './types';
|
||||||
import { MessageTree } from './message-tree';
|
import { MessageTree } from './message-tree';
|
||||||
import { createStreamingChatCompletion } from './openai';
|
import { createStreamingChatCompletion } from './openai';
|
||||||
import { createTitle } from './titles';
|
import { createTitle } from './titles';
|
||||||
|
@ -81,18 +81,7 @@ export class ChatManager extends EventEmitter {
|
||||||
done: true,
|
done: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const reply: Message = {
|
|
||||||
id: uuidv4(),
|
|
||||||
parentID: newMessage.id,
|
|
||||||
chatID: chat.id,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
role: 'assistant',
|
|
||||||
content: '',
|
|
||||||
done: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
chat.messages.addMessage(newMessage);
|
chat.messages.addMessage(newMessage);
|
||||||
chat.messages.addMessage(reply);
|
|
||||||
chat.updated = Date.now();
|
chat.updated = Date.now();
|
||||||
|
|
||||||
this.emit(chat.id);
|
this.emit(chat.id);
|
||||||
|
@ -104,9 +93,50 @@ export class ChatManager extends EventEmitter {
|
||||||
: [];
|
: [];
|
||||||
messages.push(newMessage);
|
messages.push(newMessage);
|
||||||
|
|
||||||
|
await this.getReply(messages, message.requestedParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async regenerate(message: Message, requestedParameters: Parameters) {
|
||||||
|
const chat = this.chats.get(message.chatID);
|
||||||
|
|
||||||
|
if (!chat) {
|
||||||
|
throw new Error('Chat not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages: Message[] = message.parentID
|
||||||
|
? chat.messages.getMessageChainTo(message.parentID)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
await this.getReply(messages, requestedParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getReply(messages: Message[], requestedParameters: Parameters) {
|
||||||
|
const latestMessage = messages[messages.length - 1];
|
||||||
|
const chat = this.chats.get(latestMessage.chatID);
|
||||||
|
|
||||||
|
if (!chat) {
|
||||||
|
throw new Error('Chat not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const reply: Message = {
|
||||||
|
id: uuidv4(),
|
||||||
|
parentID: latestMessage.id,
|
||||||
|
chatID: latestMessage.chatID,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
role: 'assistant',
|
||||||
|
content: '',
|
||||||
|
done: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
chat.messages.addMessage(reply);
|
||||||
|
chat.updated = Date.now();
|
||||||
|
|
||||||
|
this.emit(chat.id);
|
||||||
|
channel.postMessage({ type: 'chat-update', data: chat });
|
||||||
|
|
||||||
const messagesToSend = selectMessagesToSendSafely(messages.map(getOpenAIMessageFromMessage));
|
const messagesToSend = selectMessagesToSendSafely(messages.map(getOpenAIMessageFromMessage));
|
||||||
|
|
||||||
const response = await createStreamingChatCompletion(messagesToSend, message.requestedParameters);
|
const response = await createStreamingChatCompletion(messagesToSend, requestedParameters);
|
||||||
|
|
||||||
response.on('error', () => {
|
response.on('error', () => {
|
||||||
if (!reply.content) {
|
if (!reply.content) {
|
||||||
|
@ -139,7 +169,7 @@ export class ChatManager extends EventEmitter {
|
||||||
setTimeout(() => this.search.update(chat), 500);
|
setTimeout(() => this.search.update(chat), 500);
|
||||||
|
|
||||||
if (!chat.title) {
|
if (!chat.title) {
|
||||||
chat.title = await createTitle(chat, message.requestedParameters.apiKey);
|
chat.title = await createTitle(chat, requestedParameters.apiKey);
|
||||||
if (chat.title) {
|
if (chat.title) {
|
||||||
this.emit(chat.id);
|
this.emit(chat.id);
|
||||||
this.emit('title', chat.id, chat.title);
|
this.emit('title', chat.id, chat.title);
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Button, CopyButton, Loader } from '@mantine/core';
|
import { Button, CopyButton, Loader, Textarea } from '@mantine/core';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { Message } from "../types";
|
import { Message } from "../types";
|
||||||
import { share } from '../utils';
|
import { share } from '../utils';
|
||||||
import { ElevenLabsReaderButton } from '../elevenlabs';
|
import { ElevenLabsReaderButton } from '../elevenlabs';
|
||||||
import { Markdown } from './markdown';
|
import { Markdown } from './markdown';
|
||||||
|
import { useAppContext } from '../context';
|
||||||
|
|
||||||
// hide for everyone but screen readers
|
// hide for everyone but screen readers
|
||||||
const SROnly = styled.span`
|
const SROnly = styled.span`
|
||||||
|
@ -169,6 +171,17 @@ const EndOfChatMarker = styled.div`
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const Editor = styled.div`
|
||||||
|
max-width: 50rem;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
|
||||||
|
.mantine-Button-root {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
function getRoleName(role: string, share = false) {
|
function getRoleName(role: string, share = false) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case 'user':
|
case 'user':
|
||||||
|
@ -193,6 +206,10 @@ function InlineLoader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MessageComponent(props: { message: Message, last: boolean, share?: boolean }) {
|
export default function MessageComponent(props: { message: Message, last: boolean, share?: boolean }) {
|
||||||
|
const context = useAppContext();
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
const [content, setContent] = useState('');
|
||||||
|
|
||||||
if (props.message.role === 'system') {
|
if (props.message.role === 'system') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -222,8 +239,30 @@ export default function MessageComponent(props: { message: Message, last: boolea
|
||||||
<span>Share</span>
|
<span>Share</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{!context.isShare && props.message.role === 'user' && (
|
||||||
|
<Button variant="subtle" size="sm" compact onClick={() => {
|
||||||
|
setContent(props.message.content);
|
||||||
|
setEditing(true);
|
||||||
|
}}>
|
||||||
|
<i className="fa fa-edit" />
|
||||||
|
<span>Edit</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!context.isShare && props.message.role === 'assistant' && (
|
||||||
|
<Button variant="subtle" size="sm" compact onClick={() => context.regenerateMessage(props.message)}>
|
||||||
|
<i className="fa fa-refresh" />
|
||||||
|
<span>Regenerate</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Markdown content={props.message.content} className={"content content-" + props.message.id} />
|
{!editing && <Markdown content={props.message.content} className={"content content-" + props.message.id} />}
|
||||||
|
{editing && (<Editor>
|
||||||
|
<Textarea value={content}
|
||||||
|
onChange={e => setContent(e.currentTarget.value)}
|
||||||
|
autosize={true} />
|
||||||
|
<Button variant="light" onClick={() => context.editMessage(props.message, content)}>Save changes</Button>
|
||||||
|
<Button variant="subtle" onClick={() => setEditing(false)}>Cancel</Button>
|
||||||
|
</Editor>)}
|
||||||
</div>
|
</div>
|
||||||
{props.last && <EndOfChatMarker />}
|
{props.last && <EndOfChatMarker />}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { backend } from "./backend";
|
||||||
import ChatManagerInstance, { ChatManager } from "./chat-manager";
|
import ChatManagerInstance, { ChatManager } from "./chat-manager";
|
||||||
import { defaultElevenLabsVoiceID } from "./elevenlabs";
|
import { defaultElevenLabsVoiceID } from "./elevenlabs";
|
||||||
import { loadParameters, saveParameters } from "./parameters";
|
import { loadParameters, saveParameters } from "./parameters";
|
||||||
import { Parameters } from "./types";
|
import { Message, Parameters } from "./types";
|
||||||
import { useChat, UseChatResult } from "./use-chat";
|
import { useChat, UseChatResult } from "./use-chat";
|
||||||
|
|
||||||
export interface Context {
|
export interface Context {
|
||||||
|
@ -33,9 +33,11 @@ export interface Context {
|
||||||
generating: boolean;
|
generating: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
parameters: Parameters;
|
parameters: Parameters;
|
||||||
setMessage: (message: string) => void;
|
setMessage: (message: string, parentID?: string) => void;
|
||||||
setParameters: (parameters: Parameters) => void;
|
setParameters: (parameters: Parameters) => void;
|
||||||
onNewMessage: (message?: string) => Promise<boolean>;
|
onNewMessage: (message?: string) => Promise<boolean>;
|
||||||
|
regenerateMessage: (message: Message) => Promise<boolean>;
|
||||||
|
editMessage: (message: Message, content: string) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppContext = React.createContext<Context>({} as any);
|
const AppContext = React.createContext<Context>({} as any);
|
||||||
|
@ -150,6 +152,75 @@ export function useCreateAppContext(): Context {
|
||||||
return true;
|
return true;
|
||||||
}, [chatManager, openaiApiKey, id, parameters, message, currentChat.leaf]);
|
}, [chatManager, openaiApiKey, id, parameters, message, currentChat.leaf]);
|
||||||
|
|
||||||
|
const regenerateMessage = useCallback(async (message: Message) => {
|
||||||
|
if (isShare) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!openaiApiKey) {
|
||||||
|
setSettingsTab('user');
|
||||||
|
setOption('openai-api-key');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGenerating(true);
|
||||||
|
|
||||||
|
await chatManager.current.regenerate(message, {
|
||||||
|
...parameters,
|
||||||
|
apiKey: openaiApiKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => setGenerating(false), 4000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, [chatManager, openaiApiKey, id, parameters]);
|
||||||
|
|
||||||
|
const editMessage = useCallback(async (message: Message, content: string) => {
|
||||||
|
if (isShare) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content?.trim().length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!openaiApiKey) {
|
||||||
|
setSettingsTab('user');
|
||||||
|
setOption('openai-api-key');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGenerating(true);
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
await chatManager.current.sendMessage({
|
||||||
|
chatID: id,
|
||||||
|
content: content.trim(),
|
||||||
|
requestedParameters: {
|
||||||
|
...parameters,
|
||||||
|
apiKey: openaiApiKey,
|
||||||
|
},
|
||||||
|
parentID: message.parentID,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const id = await chatManager.current.createChat();
|
||||||
|
await chatManager.current.sendMessage({
|
||||||
|
chatID: id,
|
||||||
|
content: content.trim(),
|
||||||
|
requestedParameters: {
|
||||||
|
...parameters,
|
||||||
|
apiKey: openaiApiKey,
|
||||||
|
},
|
||||||
|
parentID: message.parentID,
|
||||||
|
});
|
||||||
|
navigate('/chat/' + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => setGenerating(false), 4000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, [chatManager, openaiApiKey, id, parameters, message, currentChat.leaf]);
|
||||||
|
|
||||||
const context = useMemo<Context>(() => ({
|
const context = useMemo<Context>(() => ({
|
||||||
authenticated,
|
authenticated,
|
||||||
id,
|
id,
|
||||||
|
@ -184,8 +255,10 @@ export function useCreateAppContext(): Context {
|
||||||
setMessage,
|
setMessage,
|
||||||
setParameters,
|
setParameters,
|
||||||
onNewMessage,
|
onNewMessage,
|
||||||
|
regenerateMessage,
|
||||||
|
editMessage,
|
||||||
}), [chatManager, authenticated, openaiApiKey, elevenLabsApiKey, settingsTab, option, voiceID,
|
}), [chatManager, authenticated, openaiApiKey, elevenLabsApiKey, settingsTab, option, voiceID,
|
||||||
generating, message, parameters, onNewMessage, currentChat]);
|
generating, message, parameters, onNewMessage, regenerateMessage, editMessage, currentChat]);
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue