Merge pull request #184 from Webifi/main

Fix choppy streaming, mask UI blocking
This commit is contained in:
Niek van der Maas 2023-06-16 18:35:15 +02:00 committed by GitHub
commit c592cd639b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 76 deletions

View File

@ -9,7 +9,12 @@
showSetChatSettings, showSetChatSettings,
submitExitingPromptsNow, submitExitingPromptsNow,
continueMessage, continueMessage,
getMessage getMessage,
currentChatMessages,
setCurrentChat,
currentChatId
} from './Storage.svelte' } from './Storage.svelte'
import { import {
type Message, type Message,
@ -82,9 +87,9 @@
submitForm(false, true) submitForm(false, true)
} }
if ($continueMessage) { if ($continueMessage) {
const message = getMessage(chat, $continueMessage) const message = getMessage(chatId, $continueMessage)
$continueMessage = '' $continueMessage = ''
if (message && chat.messages.indexOf(message) === (chat.messages.length - 1)) { if (message && $currentChatMessages.indexOf(message) === ($currentChatMessages.length - 1)) {
submitForm(lastSubmitRecorded, true, message) submitForm(lastSubmitRecorded, true, message)
} }
} }
@ -93,6 +98,7 @@
$: onStateChange($checkStateChange, $showSetChatSettings, $submitExitingPromptsNow, $continueMessage) $: onStateChange($checkStateChange, $showSetChatSettings, $submitExitingPromptsNow, $continueMessage)
setCurrentChat(0)
// Make sure chat object is ready to go // Make sure chat object is ready to go
updateChatSettings(chatId) updateChatSettings(chatId)
@ -106,6 +112,8 @@
onMount(async () => { onMount(async () => {
if (!chat) return if (!chat) return
setCurrentChat(chatId)
chatRequest = new ChatRequest() chatRequest = new ChatRequest()
chatRequest.setChat(chat) chatRequest.setChat(chat)
// Focus the input on mount // Focus the input on mount
@ -164,9 +172,9 @@
const addNewMessage = () => { const addNewMessage = () => {
if (chatRequest.updating) return if (chatRequest.updating) return
let inputMessage: Message let inputMessage: Message
const lastMessage = chat.messages[chat.messages.length - 1] const lastMessage = $currentChatMessages[$currentChatMessages.length - 1]
const uuid = uuidv4() const uuid = uuidv4()
if (chat.messages.length === 0) { if ($currentChatMessages.length === 0) {
inputMessage = { role: 'system', content: input.value, uuid } inputMessage = { role: 'system', content: input.value, uuid }
} else if (lastMessage && lastMessage.role === 'user') { } else if (lastMessage && lastMessage.role === 'user') {
inputMessage = { role: 'assistant', content: input.value, uuid } inputMessage = { role: 'assistant', content: input.value, uuid }
@ -220,8 +228,8 @@
// Compose the input message // Compose the input message
const inputMessage: Message = { role: 'user', content: input.value, uuid: uuidv4() } const inputMessage: Message = { role: 'user', content: input.value, uuid: uuidv4() }
addMessage(chatId, inputMessage) addMessage(chatId, inputMessage)
} else if (!fillMessage && chat.messages.length && chat.messages[chat.messages.length - 1].finish_reason === 'length') { } else if (!fillMessage && $currentChatMessages.length && $currentChatMessages[$currentChatMessages.length - 1].finish_reason === 'length') {
fillMessage = chat.messages[chat.messages.length - 1] fillMessage = $currentChatMessages[$currentChatMessages.length - 1]
} }
// Clear the input value // Clear the input value
@ -237,7 +245,7 @@
chatRequest.updatingMessage = '' chatRequest.updatingMessage = ''
try { try {
const response = await chatRequest.sendRequest(chat.messages, { const response = await chatRequest.sendRequest($currentChatMessages, {
chat, chat,
autoAddMessages: true, // Auto-add and update messages in array autoAddMessages: true, // Auto-add and update messages in array
streaming: chatSettings.stream, streaming: chatSettings.stream,
@ -268,7 +276,7 @@
uuid: uuidv4() uuid: uuidv4()
} }
const suggestMessages = chat.messages.slice(0, 10) // limit to first 10 messages const suggestMessages = $currentChatMessages.slice(0, 10) // limit to first 10 messages
suggestMessages.push(suggestMessage) suggestMessages.push(suggestMessage)
const response = await chatRequest.sendRequest(suggestMessages, { const response = await chatRequest.sendRequest(suggestMessages, {
@ -346,9 +354,9 @@
</div> </div>
</nav> </nav>
<Messages messages={chat.messages} chatId={chatId} /> <Messages messages={$currentChatMessages} chatId={chatId} chat={chat} />
{#if chatRequest.updating === true} {#if chatRequest.updating === true || $currentChatId === 0}
<article class="message is-success assistant-message"> <article class="message is-success assistant-message">
<div class="message-body content"> <div class="message-body content">
<span class="is-loading" ></span> <span class="is-loading" ></span>
@ -357,7 +365,7 @@
</article> </article>
{/if} {/if}
{#if chat.messages.length === 0 || (chat.messages.length === 1 && chat.messages[0].role === 'system')} {#if $currentChatId !== 0 && ($currentChatMessages.length === 0 || ($currentChatMessages.length === 1 && $currentChatMessages[0].role === 'system'))}
<Prompts bind:input /> <Prompts bind:input />
{/if} {/if}
</div> </div>

View File

@ -3,12 +3,13 @@
import { mergeProfileFields, prepareSummaryPrompt } from './Profiles.svelte' import { mergeProfileFields, prepareSummaryPrompt } from './Profiles.svelte'
import { countMessageTokens, countPromptTokens, getModelMaxTokens } from './Stats.svelte' import { countMessageTokens, countPromptTokens, getModelMaxTokens } from './Stats.svelte'
import type { Chat, ChatCompletionOpts, ChatSettings, Message, Model, Request, RequestImageGeneration } from './Types.svelte' import type { Chat, ChatCompletionOpts, ChatSettings, Message, Model, Request, RequestImageGeneration } from './Types.svelte'
import { deleteMessage, getChatSettingValueNullDefault, insertMessages, saveChatStore, getApiKey, addError } from './Storage.svelte' import { deleteMessage, getChatSettingValueNullDefault, insertMessages, getApiKey, addError, currentChatMessages, getMessages, updateMessages } from './Storage.svelte'
import { scrollToBottom, scrollToMessage } from './Util.svelte' import { scrollToBottom, scrollToMessage } from './Util.svelte'
import { getRequestSettingList, defaultModel } from './Settings.svelte' import { getRequestSettingList, defaultModel } from './Settings.svelte'
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source' import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source'
import { getApiBase, getEndpointCompletions, getEndpointGenerations } from './ApiUtil.svelte' import { getApiBase, getEndpointCompletions, getEndpointGenerations } from './ApiUtil.svelte'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { get } from 'svelte/store'
export class ChatRequest { export class ChatRequest {
constructor () { constructor () {
@ -317,7 +318,7 @@ export class ChatRequest {
const maxTokens = getModelMaxTokens(model) // max tokens for model const maxTokens = getModelMaxTokens(model) // max tokens for model
const continueRequest = async () => { const continueRequest = async () => {
return await _this.sendRequest(chat.messages, { return await _this.sendRequest(getMessages(chatId), {
...opts, ...opts,
didSummary: true didSummary: true
}, overrides) }, overrides)
@ -358,7 +359,7 @@ export class ChatRequest {
promptSize = countPromptTokens(top.concat(rw), model) + countPadding promptSize = countPromptTokens(top.concat(rw), model) + countPadding
} }
// Run a new request, now with the rolled messages hidden // Run a new request, now with the rolled messages hidden
return await _this.sendRequest(chat.messages, { return await _this.sendRequest(get(currentChatMessages), {
...opts, ...opts,
didSummary: true // our "summary" was simply dropping some messages didSummary: true // our "summary" was simply dropping some messages
}, overrides) }, overrides)
@ -466,11 +467,11 @@ export class ChatRequest {
summaryResponse.summary = summarizedIds summaryResponse.summary = summarizedIds
// Disable the messages we summarized so they still show in history // Disable the messages we summarized so they still show in history
rw.forEach((m, i) => { m.summarized = summaryIds }) rw.forEach((m, i) => { m.summarized = summaryIds })
saveChatStore() updateMessages(chatId)
// Re-run request with summarized prompts // Re-run request with summarized prompts
_this.updatingMessage = 'Continuing...' _this.updatingMessage = 'Continuing...'
scrollToBottom(true) scrollToBottom(true)
return await _this.sendRequest(chat.messages, { return await _this.sendRequest(get(currentChatMessages), {
...opts, ...opts,
didSummary: true didSummary: true
}, },

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import Code from './Code.svelte' import Code from './Code.svelte'
import { afterUpdate, createEventDispatcher, onMount } from 'svelte' import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
import { deleteMessage, chatsStorage, deleteSummaryMessage, truncateFromMessage, submitExitingPromptsNow, saveChatStore, continueMessage } from './Storage.svelte' import { deleteMessage, deleteSummaryMessage, truncateFromMessage, submitExitingPromptsNow, continueMessage, updateMessages } from './Storage.svelte'
import { getPrice } from './Stats.svelte' import { getPrice } from './Stats.svelte'
import SvelteMarkdown from 'svelte-markdown' import SvelteMarkdown from 'svelte-markdown'
import type { Message, Model, Chat } from './Types.svelte' import type { Message, Model, Chat } from './Types.svelte'
@ -14,9 +14,8 @@
export let message:Message export let message:Message
export let chatId:number export let chatId:number
export let chat:Chat
$: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat
$: chatSettings = chat.settings $: chatSettings = chat.settings
const isError = message.role === 'error' const isError = message.role === 'error'
@ -49,7 +48,7 @@
}) })
afterUpdate(() => { afterUpdate(() => {
if (message.content.slice(-5).includes('```')) refreshCounter++ if (message.streaming && message.content.slice(-5).includes('```')) refreshCounter++
}) })
const edit = () => { const edit = () => {
@ -178,7 +177,7 @@
return return
} }
message.suppress = value message.suppress = value
saveChatStore() updateMessages(chatId)
} }
const downloadImage = () => { const downloadImage = () => {

View File

@ -1,19 +1,19 @@
<script lang="ts"> <script lang="ts">
// Iterate messages // Iterate messages
import type { Message, Chat } from './Types.svelte' import type { Message, Chat } from './Types.svelte'
import { chatsStorage, globalStorage } from './Storage.svelte' import { globalStorage } from './Storage.svelte'
import EditMessage from './EditMessage.svelte' import EditMessage from './EditMessage.svelte'
export let messages : Message[] export let messages : Message[]
export let chatId: number export let chatId: number
export let chat: Chat
$: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat
$: chatSettings = chat.settings $: chatSettings = chat.settings
</script> </script>
{#each messages as message, i} {#each messages as message, i}
{#if !((message.summarized) && $globalStorage.hideSummarized) && !(i === 0 && message.role === 'system' && !chatSettings.useSystemPrompt)} {#if !((message.summarized) && $globalStorage.hideSummarized) && !(i === 0 && message.role === 'system' && !chatSettings.useSystemPrompt)}
{#key message.uuid}<EditMessage bind:message={message} chatId={chatId} />{/key} <EditMessage bind:message={message} chatId={chatId} chat={chat} />
{/if} {/if}
{/each} {/each}

View File

@ -2,7 +2,7 @@
import { getChatDefaults, getExcludeFromProfile } from './Settings.svelte' import { getChatDefaults, getExcludeFromProfile } from './Settings.svelte'
import { get, writable } from 'svelte/store' import { get, writable } from 'svelte/store'
// Profile definitions // Profile definitions
import { addMessage, clearMessages, deleteMessage, getChat, getChatSettings, getCustomProfiles, getGlobalSettings, newName, resetChatSettings, saveChatStore, setGlobalSettingValueByKey, updateProfile } from './Storage.svelte' import { addMessage, clearMessages, deleteMessage, getChat, getChatSettings, getCustomProfiles, getGlobalSettings, getMessages, newName, resetChatSettings, saveChatStore, setGlobalSettingValueByKey, setMessages, updateProfile } from './Storage.svelte'
import type { Message, SelectOption, ChatSettings } from './Types.svelte' import type { Message, SelectOption, ChatSettings } from './Types.svelte'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -90,15 +90,15 @@ export const prepareSummaryPrompt = (chatId:number, maxTokens:number) => {
} }
export const setSystemPrompt = (chatId: number) => { export const setSystemPrompt = (chatId: number) => {
const chat = getChat(chatId) const messages = getMessages(chatId)
const systemPromptMessage:Message = { const systemPromptMessage:Message = {
role: 'system', role: 'system',
content: prepareProfilePrompt(chatId), content: prepareProfilePrompt(chatId),
uuid: uuidv4() uuid: uuidv4()
} }
if (chat.messages[0]?.role === 'system') deleteMessage(chatId, chat.messages[0].uuid) if (messages[0]?.role === 'system') deleteMessage(chatId, messages[0].uuid)
chat.messages.unshift(systemPromptMessage) messages.unshift(systemPromptMessage)
saveChatStore() setMessages(chatId, messages.filter(m => true))
} }
// Restart currently loaded profile // Restart currently loaded profile

View File

@ -19,6 +19,8 @@
export let submitExitingPromptsNow = writable(false) // for them to go now. Will not submit anything in the input export let submitExitingPromptsNow = writable(false) // for them to go now. Will not submit anything in the input
export let pinMainMenu = writable(false) // Show menu (for mobile use) export let pinMainMenu = writable(false) // Show menu (for mobile use)
export let continueMessage = writable('') // export let continueMessage = writable('') //
export let currentChatMessages = writable([] as Message[])
export let currentChatId = writable(0)
const chatDefaults = getChatDefaults() const chatDefaults = getChatDefaults()
@ -215,70 +217,97 @@
chatsStorage.set(chats) chatsStorage.set(chats)
} }
export const getMessages = (chatId: number): Message[] => {
if (get(currentChatId) === chatId) return get(currentChatMessages)
return getChat(chatId).messages
}
let setChatTimer: any
export const setCurrentChat = (chatId: number) => {
clearTimeout(setChatTimer)
if (!chatId) {
currentChatId.set(0)
currentChatMessages.set([])
}
setChatTimer = setTimeout(() => {
currentChatId.set(chatId)
currentChatMessages.set(getChat(chatId).messages)
}, 10)
}
let setMessagesTimer: any
export const setMessages = (chatId: number, messages: Message[]) => {
if (get(currentChatId) === chatId) {
// update current message cache right away
currentChatMessages.set(messages)
clearTimeout(setMessagesTimer)
// delay expensive all chats update for a bit
setMessagesTimer = setTimeout(() => {
getChat(chatId).messages = messages
saveChatStore()
}, 100)
} else {
getChat(chatId).messages = messages
saveChatStore()
}
}
export const updateMessages = (chatId: number) => {
setMessages(chatId, getMessages(chatId))
}
export const addError = (chatId: number, error: string) => { export const addError = (chatId: number, error: string) => {
addMessage(chatId, { content: error } as Message) addMessage(chatId, { content: error } as Message)
} }
export const addMessage = (chatId: number, message: Message) => { export const addMessage = (chatId: number, message: Message) => {
const chats = get(chatsStorage) const messages = getMessages(chatId)
const chat = chats.find((chat) => chat.id === chatId) as Chat
if (!message.uuid) message.uuid = uuidv4() if (!message.uuid) message.uuid = uuidv4()
if (chat.messages.indexOf(message) < 0) { if (messages.indexOf(message) < 0) {
// Don't have message, add it // Don't have message, add it
chat.messages.push(message) messages[messages.length] = message
} }
chatsStorage.set(chats) setMessages(chatId, messages)
} }
export const getMessages = (chatId: number):Message[] => { export const getMessage = (chatId: number, uuid:string):Message|undefined => {
const chats = get(chatsStorage) return getMessages(chatId).find((m) => m.uuid === uuid)
const chat = chats.find((chat) => chat.id === chatId) as Chat
return chat.messages
}
export const getMessage = (chat: Chat, uuid:string):Message|undefined => {
return chat.messages.find((m) => m.uuid === uuid)
} }
export const insertMessages = (chatId: number, insertAfter: Message, newMessages: Message[]) => { export const insertMessages = (chatId: number, insertAfter: Message, newMessages: Message[]) => {
const chats = get(chatsStorage) const messages = getMessages(chatId)
const chat = chats.find((chat) => chat.id === chatId) as Chat const index = messages.findIndex((m) => m.uuid === insertAfter.uuid)
const index = chat.messages.findIndex((m) => m.uuid === insertAfter.uuid)
if (index === undefined || index < 0) { if (index === undefined || index < 0) {
console.error("Couldn't insert after message:", insertAfter) console.error("Couldn't insert after message:", insertAfter)
return return
} }
newMessages.forEach(m => { m.uuid = m.uuid || uuidv4() }) newMessages.forEach(m => { m.uuid = m.uuid || uuidv4() })
chat.messages.splice(index + 1, 0, ...newMessages) messages.splice(index + 1, 0, ...newMessages)
chatsStorage.set(chats) setMessages(chatId, messages.filter(m => true))
} }
export const deleteSummaryMessage = (chatId: number, uuid: string) => { export const deleteSummaryMessage = (chatId: number, uuid: string) => {
const chats = get(chatsStorage) const message = getMessage(chatId, uuid)
const chat = chats.find((chat) => chat.id === chatId) as Chat
const message = getMessage(chat, uuid)
if (message && message.summarized) throw new Error('Unable to delete summarized message') if (message && message.summarized) throw new Error('Unable to delete summarized message')
if (message && message.summary) { // messages we summarized if (message && message.summary) { // messages we summarized
message.summary.forEach(sid => { message.summary.forEach(sid => {
const m = getMessage(chat, sid) const m = getMessage(chatId, sid)
if (m) { if (m) {
delete m.summarized // unbind to this summary delete m.summarized // unbind to this summary
} }
}) })
delete message.summary delete message.summary
} }
chatsStorage.set(chats) updateMessages(chatId)
deleteMessage(chatId, uuid) deleteMessage(chatId, uuid)
} }
export const deleteMessage = (chatId: number, uuid: string) => { export const deleteMessage = (chatId: number, uuid: string) => {
const chats = get(chatsStorage) const messages = getMessages(chatId)
const chat = chats.find((chat) => chat.id === chatId) as Chat const index = messages.findIndex((m) => m.uuid === uuid)
const index = chat.messages.findIndex((m) => m.uuid === uuid) const message = getMessage(chatId, uuid)
const message = getMessage(chat, uuid)
if (message?.summarized) throw new Error('Unable to delete summarized message') if (message?.summarized) throw new Error('Unable to delete summarized message')
if (message?.summary) throw new Error('Unable to directly delete message summary') if (message?.summary) throw new Error('Unable to directly delete message summary')
// const found = chat.messages.filter((m) => m.uuid === uuid)
if (index < 0) { if (index < 0) {
console.error(`Unable to find and delete message with ID: ${uuid}`) console.error(`Unable to find and delete message with ID: ${uuid}`)
return return
@ -287,8 +316,8 @@
deleteImage(chatId, message.image.id) deleteImage(chatId, message.image.id)
} }
// console.warn(`Deleting message with ID: ${uuid}`, found, index) // console.warn(`Deleting message with ID: ${uuid}`, found, index)
chat.messages.splice(index, 1) // remove item messages.splice(index, 1) // remove item
chatsStorage.set(chats) setMessages(chatId, messages.filter(m => true))
} }
const clearImages = (chatId: number, messages: Message[]) => { const clearImages = (chatId: number, messages: Message[]) => {
@ -298,38 +327,33 @@
} }
export const truncateFromMessage = (chatId: number, uuid: string) => { export const truncateFromMessage = (chatId: number, uuid: string) => {
const chats = get(chatsStorage) const messages = getMessages(chatId)
const chat = chats.find((chat) => chat.id === chatId) as Chat const index = messages.findIndex((m) => m.uuid === uuid)
const index = chat.messages.findIndex((m) => m.uuid === uuid) const message = getMessage(chatId, uuid)
const message = getMessage(chat, uuid)
if (message && message.summarized) throw new Error('Unable to truncate from a summarized message') if (message && message.summarized) throw new Error('Unable to truncate from a summarized message')
// const found = chat.messages.filter((m) => m.uuid === uuid)
if (index < 0) { if (index < 0) {
throw new Error(`Unable to find message with ID: ${uuid}`) throw new Error(`Unable to find message with ID: ${uuid}`)
} }
const truncated = chat.messages.splice(index + 1) // remove every item after const truncated = messages.splice(index + 1) // remove every item after
clearImages(chatId, truncated) clearImages(chatId, truncated)
chatsStorage.set(chats) setMessages(chatId, messages.filter(m => true))
} }
export const clearMessages = (chatId: number) => { export const clearMessages = (chatId: number) => {
const chats = get(chatsStorage) clearImages(chatId, getMessages(chatId))
const chat = chats.find((chat) => chat.id === chatId) as Chat setMessages(chatId, [])
clearImages(chatId, chat.messages)
chat.messages = []
chatsStorage.set(chats)
} }
export const deleteChat = (chatId: number) => { export const deleteChat = (chatId: number) => {
const chats = get(chatsStorage) const chats = get(chatsStorage)
const chat = getChat(chatId) clearImages(chatId, getMessages(chatId) || [])
clearImages(chatId, chat?.messages || [])
chatsStorage.set(chats.filter((chat) => chat.id !== chatId)) chatsStorage.set(chats.filter((chat) => chat.id !== chatId))
} }
export const updateChatImages = async (chatId: number, chat: Chat) => { export const updateChatImages = async (chatId: number, chat: Chat) => {
for (let i = 0; i < chat.messages.length; i++) { const messages = getMessages(chatId)
const m = chat.messages[i] for (let i = 0; i < messages.length; i++) {
const m = messages[i]
if (m.image) m.image = await setImage(chatId, m.image) if (m.image) m.image = await setImage(chatId, m.image)
} }
} }