Add truncated completion joining

This commit is contained in:
Webifi 2023-06-07 16:54:20 -05:00
parent d768f4b355
commit c3172eb4aa
5 changed files with 130 additions and 23 deletions

View File

@ -622,6 +622,24 @@ aside.menu.main-menu .menu-expanse {
animation: cursor-blink 1s steps(2) infinite; animation: cursor-blink 1s steps(2) infinite;
} }
.message:last-of-type.incomplete .message-display p:last-of-type::after {
position: relative;
content: '...';
margin-left: 4px;
font-weight: bold;
animation: cursor-blink 1s steps(2) infinite;
}
.message.incomplete .tool-drawer .msg-incomplete {
display: none;
}
.message:last-of-type.incomplete .tool-drawer .msg-incomplete {
display: block;
}
.modal { .modal {
z-index:100; z-index:100;
} }

View File

@ -11,17 +11,16 @@
checkStateChange, checkStateChange,
showSetChatSettings, showSetChatSettings,
submitExitingPromptsNow, submitExitingPromptsNow,
deleteMessage deleteMessage,
continueMessage,
getMessage
} from './Storage.svelte' } from './Storage.svelte'
import { getRequestSettingList, defaultModel } from './Settings.svelte' import { getRequestSettingList, defaultModel } from './Settings.svelte'
import { import {
type Request, type Request,
type Message, type Message,
type Chat, type Chat,
type ChatCompletionOpts, type ChatCompletionOpts
type Usage
} from './Types.svelte' } from './Types.svelte'
import Prompts from './Prompts.svelte' import Prompts from './Prompts.svelte'
import Messages from './Messages.svelte' import Messages from './Messages.svelte'
@ -36,9 +35,7 @@
faPenToSquare, faPenToSquare,
faMicrophone, faMicrophone,
faLightbulb, faLightbulb,
faCommentSlash faCommentSlash
} from '@fortawesome/free-solid-svg-icons/index' } from '@fortawesome/free-solid-svg-icons/index'
// import { encode } from 'gpt-tokenizer' // import { encode } from 'gpt-tokenizer'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -62,6 +59,7 @@
let input: HTMLTextAreaElement let input: HTMLTextAreaElement
let recognition: any = null let recognition: any = null
let recording = false let recording = false
let lastSubmitRecorded = false
$: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat $: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat
$: chatSettings = chat.settings $: chatSettings = chat.settings
@ -88,10 +86,17 @@
$submitExitingPromptsNow = false $submitExitingPromptsNow = false
submitForm(false, true) submitForm(false, true)
} }
if ($continueMessage) {
const message = getMessage(chat, $continueMessage)
$continueMessage = ''
if (message && chat.messages.indexOf(message) === (chat.messages.length - 1)) {
submitForm(lastSubmitRecorded, true, message)
}
}
}) })
} }
$: onStateChange($checkStateChange, $showSetChatSettings, $submitExitingPromptsNow) $: onStateChange($checkStateChange, $showSetChatSettings, $submitExitingPromptsNow, $continueMessage)
// Make sure chat object is ready to go // Make sure chat object is ready to go
updateChatSettings(chatId) updateChatSettings(chatId)
@ -263,9 +268,6 @@
streaming: opts.streaming, streaming: opts.streaming,
summary: [] summary: []
} }
summaryResponse.usage = {
prompt_tokens: 0
} as Usage
summaryResponse.model = model summaryResponse.model = model
// Insert summary prompt // Insert summary prompt
@ -433,6 +435,7 @@
scrollToBottom() scrollToBottom()
} }
} catch (e) { } catch (e) {
console.error(e)
updating = false updating = false
updatingMessage = '' updatingMessage = ''
chatResponse.updateFromError(e.message) chatResponse.updateFromError(e.message)
@ -477,9 +480,11 @@
} }
} }
const submitForm = async (recorded: boolean = false, skipInput: boolean = false): Promise<void> => { const submitForm = async (recorded: boolean = false, skipInput: boolean = false, fillMessage: Message|undefined = undefined): Promise<void> => {
// Compose the system prompt message if there are no messages yet - disabled for now // Compose the system prompt message if there are no messages yet - disabled for now
if (updating) return if (updating) return
lastSubmitRecorded = recorded
if (!skipInput) { if (!skipInput) {
chat.sessionStarted = true chat.sessionStarted = true
@ -488,8 +493,12 @@
// 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') {
fillMessage = chat.messages[chat.messages.length - 1]
} }
if (fillMessage && fillMessage.content) fillMessage.content += ' ' // add a space
// Clear the input value // Clear the input value
input.value = '' input.value = ''
input.blur() input.blur()
@ -503,6 +512,7 @@
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,
fillMessage,
onMessageChange: (messages) => { onMessageChange: (messages) => {
scrollToBottom(true) scrollToBottom(true)
} }

View File

@ -1,6 +1,6 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
// TODO: Integrate API calls // TODO: Integrate API calls
import { addMessage, saveChatStore, updateRunningTotal } from './Storage.svelte' import { addMessage, saveChatStore, subtractRunningTotal, updateRunningTotal } from './Storage.svelte'
import type { Chat, ChatCompletionOpts, Message, Response, Usage } from './Types.svelte' import type { Chat, ChatCompletionOpts, Message, Response, Usage } from './Types.svelte'
import { encode } from 'gpt-tokenizer' import { encode } from 'gpt-tokenizer'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -10,10 +10,17 @@ export class ChatCompletionResponse {
this.opts = opts this.opts = opts
this.chat = opts.chat this.chat = opts.chat
this.messages = [] this.messages = []
if (opts.fillMessage) this.messages.push(opts.fillMessage) if (opts.fillMessage) {
this.messages.push(opts.fillMessage)
this.offsetTotals = JSON.parse(JSON.stringify(opts.fillMessage.usage))
this.isFill = true
}
if (opts.onMessageChange) this.messageChangeListeners.push(opts.onMessageChange) if (opts.onMessageChange) this.messageChangeListeners.push(opts.onMessageChange)
} }
private offsetTotals: Usage
private isFill: boolean = false
private opts: ChatCompletionOpts private opts: ChatCompletionOpts
private chat: Chat private chat: Chat
@ -39,11 +46,30 @@ export class ChatCompletionResponse {
updateFromSyncResponse (response: Response) { updateFromSyncResponse (response: Response) {
response.choices.forEach((choice, i) => { response.choices.forEach((choice, i) => {
const message = this.messages[i] || choice.message const exitingMessage = this.messages[i]
message.content = choice.message.content const message = exitingMessage || choice.message
message.usage = response.usage if (exitingMessage) {
message.model = response.model if (this.isFill && choice.message.content.match(/^'(t|ll|ve|m|d|re)[^a-z]/i)) {
// deal with merging contractions since we've added an extra space to your fill message
message.content.replace(/ $/, '')
}
this.isFill = false
message.content += choice.message.content
message.usage = message.usage || {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
} as Usage
message.usage.completion_tokens += response.usage.completion_tokens
message.usage.prompt_tokens += response.usage.prompt_tokens
message.usage.total_tokens += response.usage.total_tokens
} else {
message.content = choice.message.content
message.usage = response.usage
}
message.finish_reason = choice.finish_reason
message.role = choice.message.role message.role = choice.message.role
message.model = response.model
this.messages[i] = message this.messages[i] = message
if (this.opts.autoAddMessages) addMessage(this.chat.id, message) if (this.opts.autoAddMessages) addMessage(this.chat.id, message)
}) })
@ -60,7 +86,14 @@ export class ChatCompletionResponse {
uuid: uuidv4() uuid: uuidv4()
} as Message } as Message
choice.delta?.role && (message.role = choice.delta.role) choice.delta?.role && (message.role = choice.delta.role)
choice.delta?.content && (message.content += choice.delta.content) if (choice.delta?.content) {
if (this.isFill && choice.delta.content.match(/^'(t|ll|ve|m|d|re)[^a-z]/i)) {
// deal with merging contractions since we've added an extra space to your fill message
message.content.replace(/([a-z]) $/i, '$1')
}
this.isFill = false
message.content += choice.delta.content
}
completionTokenCount += encode(message.content).length completionTokenCount += encode(message.content).length
message.usage = response.usage || { message.usage = response.usage || {
prompt_tokens: this.promptTokenCount prompt_tokens: this.promptTokenCount
@ -135,6 +168,10 @@ export class ChatCompletionResponse {
saveChatStore() saveChatStore()
const message = this.messages[0] const message = this.messages[0]
if (message) { if (message) {
if (this.offsetTotals) {
// Need to subtract some previous message totals before we add new combined message totals
subtractRunningTotal(this.chat.id, this.offsetTotals, this.chat.settings.model as any)
}
updateRunningTotal(this.chat.id, message.usage as any, message.model as any) updateRunningTotal(this.chat.id, message.usage as any, message.model as any)
} else { } else {
// If no messages it's probably because of an error or user initiated abort. // If no messages it's probably because of an error or user initiated abort.

View File

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import Code from './Code.svelte' import Code from './Code.svelte'
import { createEventDispatcher, onMount } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
import { deleteMessage, chatsStorage, deleteSummaryMessage, truncateFromMessage, submitExitingPromptsNow, saveChatStore } from './Storage.svelte' import { deleteMessage, chatsStorage, deleteSummaryMessage, truncateFromMessage, submitExitingPromptsNow, saveChatStore, continueMessage } 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'
import Fa from 'svelte-fa/src/fa.svelte' import Fa from 'svelte-fa/src/fa.svelte'
import { faTrash, faDiagramPredecessor, faDiagramNext, faCircleCheck, faPaperPlane, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons/index' import { faTrash, faDiagramPredecessor, faDiagramNext, faCircleCheck, faPaperPlane, faEye, faEyeSlash, faEllipsis } from '@fortawesome/free-solid-svg-icons/index'
import { errorNotice, scrollToMessage } from './Util.svelte' import { errorNotice, scrollToMessage } from './Util.svelte'
import { openModal } from 'svelte-modals' import { openModal } from 'svelte-modals'
import PromptConfirm from './PromptConfirm.svelte' import PromptConfirm from './PromptConfirm.svelte'
@ -63,6 +63,11 @@
} }
} }
const continueIncomplete = () => {
editing = false
$continueMessage = message.uuid
}
const exit = () => { const exit = () => {
doChange() doChange()
editing = false editing = false
@ -175,10 +180,11 @@
class:is-danger={isError} class:is-danger={isError}
class:user-message={isUser || isSystem} class:user-message={isUser || isSystem}
class:assistant-message={isError || isAssistant} class:assistant-message={isError || isAssistant}
class:summarized={message.summarized} class:summarized={message.summarized}
class:suppress={message.suppress} class:suppress={message.suppress}
class:editing={editing} class:editing={editing}
class:streaming={message.streaming} class:streaming={message.streaming}
class:incomplete={message.finish_reason === 'length'}
> >
<div class="message-body content"> <div class="message-body content">
@ -216,6 +222,18 @@
<div class="tool-drawer-mask"></div> <div class="tool-drawer-mask"></div>
<div class="tool-drawer"> <div class="tool-drawer">
<div class="button-pack"> <div class="button-pack">
{#if message.finish_reason === 'length'}
<a
href={'#'}
title="Continue "
class="msg-incomplete button is-small"
on:click|preventDefault={() => {
continueIncomplete()
}}
>
<span class="icon"><Fa icon={faEllipsis} /></span>
</a>
{/if}
{#if message.summarized} {#if message.summarized}
<a <a
href={'#'} href={'#'}

View File

@ -14,6 +14,7 @@
export let showSetChatSettings = writable(false) // export let showSetChatSettings = writable(false) //
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('') //
const chatDefaults = getChatDefaults() const chatDefaults = getChatDefaults()
@ -160,6 +161,24 @@
chatsStorage.set(chats) chatsStorage.set(chats)
} }
export const subtractRunningTotal = (chatId: number, usage: Usage, model:Model) => {
const chats = get(chatsStorage)
const chat = chats.find((chat) => chat.id === chatId) as Chat
let total:Usage = chat.usage[model]
if (!total) {
total = {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
}
chat.usage[model] = total
}
total.completion_tokens -= usage.completion_tokens
total.prompt_tokens -= usage.prompt_tokens
total.total_tokens -= usage.total_tokens
chatsStorage.set(chats)
}
export const addMessage = (chatId: number, message: Message) => { export const addMessage = (chatId: number, message: Message) => {
const chats = get(chatsStorage) const chats = get(chatsStorage)
const chat = chats.find((chat) => chat.id === chatId) as Chat const chat = chats.find((chat) => chat.id === chatId) as Chat
@ -177,7 +196,7 @@
return chat.messages return chat.messages
} }
const getMessage = (chat: Chat, uuid:string):Message|undefined => { export const getMessage = (chat: Chat, uuid:string):Message|undefined => {
return chat.messages.find((m) => m.uuid === uuid) return chat.messages.find((m) => m.uuid === uuid)
} }
@ -394,9 +413,14 @@
profile.profile = uuidv4() profile.profile = uuidv4()
} }
const clone = JSON.parse(JSON.stringify(profile)) // Always store a copy const clone = JSON.parse(JSON.stringify(profile)) // Always store a copy
// pull excluded
Object.keys(getExcludeFromProfile()).forEach(k => { Object.keys(getExcludeFromProfile()).forEach(k => {
delete clone[k] delete clone[k]
}) })
// pull defaults
// Object.entries(getChatDefaults()).forEach(([k, v]) => {
// if (clone[k] === v || (v === undefined && clone[k] === null)) delete clone[k]
// })
profiles[profile.profile as string] = clone profiles[profile.profile as string] = clone
globalStorage.set(store) globalStorage.set(store)
profile.isDirty = false profile.isDirty = false