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;
}
.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 {
z-index:100;
}

View File

@ -11,17 +11,16 @@
checkStateChange,
showSetChatSettings,
submitExitingPromptsNow,
deleteMessage
deleteMessage,
continueMessage,
getMessage
} from './Storage.svelte'
import { getRequestSettingList, defaultModel } from './Settings.svelte'
import {
type Request,
type Message,
type Chat,
type ChatCompletionOpts,
type Usage
type ChatCompletionOpts
} from './Types.svelte'
import Prompts from './Prompts.svelte'
import Messages from './Messages.svelte'
@ -36,9 +35,7 @@
faPenToSquare,
faMicrophone,
faLightbulb,
faCommentSlash
} from '@fortawesome/free-solid-svg-icons/index'
// import { encode } from 'gpt-tokenizer'
import { v4 as uuidv4 } from 'uuid'
@ -62,6 +59,7 @@
let input: HTMLTextAreaElement
let recognition: any = null
let recording = false
let lastSubmitRecorded = false
$: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat
$: chatSettings = chat.settings
@ -88,10 +86,17 @@
$submitExitingPromptsNow = false
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
updateChatSettings(chatId)
@ -263,9 +268,6 @@
streaming: opts.streaming,
summary: []
}
summaryResponse.usage = {
prompt_tokens: 0
} as Usage
summaryResponse.model = model
// Insert summary prompt
@ -433,6 +435,7 @@
scrollToBottom()
}
} catch (e) {
console.error(e)
updating = false
updatingMessage = ''
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
if (updating) return
lastSubmitRecorded = recorded
if (!skipInput) {
chat.sessionStarted = true
@ -488,8 +493,12 @@
// Compose the input message
const inputMessage: Message = { role: 'user', content: input.value, uuid: uuidv4() }
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
input.value = ''
input.blur()
@ -503,6 +512,7 @@
chat,
autoAddMessages: true, // Auto-add and update messages in array
streaming: chatSettings.stream,
fillMessage,
onMessageChange: (messages) => {
scrollToBottom(true)
}

View File

@ -1,6 +1,6 @@
<script context="module" lang="ts">
// 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 { encode } from 'gpt-tokenizer'
import { v4 as uuidv4 } from 'uuid'
@ -10,10 +10,17 @@ export class ChatCompletionResponse {
this.opts = opts
this.chat = opts.chat
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)
}
private offsetTotals: Usage
private isFill: boolean = false
private opts: ChatCompletionOpts
private chat: Chat
@ -39,11 +46,30 @@ export class ChatCompletionResponse {
updateFromSyncResponse (response: Response) {
response.choices.forEach((choice, i) => {
const message = this.messages[i] || choice.message
message.content = choice.message.content
message.usage = response.usage
message.model = response.model
const exitingMessage = this.messages[i]
const message = exitingMessage || choice.message
if (exitingMessage) {
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.model = response.model
this.messages[i] = message
if (this.opts.autoAddMessages) addMessage(this.chat.id, message)
})
@ -60,7 +86,14 @@ export class ChatCompletionResponse {
uuid: uuidv4()
} as Message
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
message.usage = response.usage || {
prompt_tokens: this.promptTokenCount
@ -135,6 +168,10 @@ export class ChatCompletionResponse {
saveChatStore()
const message = this.messages[0]
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)
} else {
// If no messages it's probably because of an error or user initiated abort.

View File

@ -1,12 +1,12 @@
<script lang="ts">
import Code from './Code.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 SvelteMarkdown from 'svelte-markdown'
import type { Message, Model, Chat } from './Types.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 { openModal } from 'svelte-modals'
import PromptConfirm from './PromptConfirm.svelte'
@ -63,6 +63,11 @@
}
}
const continueIncomplete = () => {
editing = false
$continueMessage = message.uuid
}
const exit = () => {
doChange()
editing = false
@ -175,10 +180,11 @@
class:is-danger={isError}
class:user-message={isUser || isSystem}
class:assistant-message={isError || isAssistant}
class:summarized={message.summarized}
class:summarized={message.summarized}
class:suppress={message.suppress}
class:editing={editing}
class:streaming={message.streaming}
class:incomplete={message.finish_reason === 'length'}
>
<div class="message-body content">
@ -216,6 +222,18 @@
<div class="tool-drawer-mask"></div>
<div class="tool-drawer">
<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}
<a
href={'#'}

View File

@ -14,6 +14,7 @@
export let showSetChatSettings = writable(false) //
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 continueMessage = writable('') //
const chatDefaults = getChatDefaults()
@ -160,6 +161,24 @@
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) => {
const chats = get(chatsStorage)
const chat = chats.find((chat) => chat.id === chatId) as Chat
@ -177,7 +196,7 @@
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)
}
@ -394,9 +413,14 @@
profile.profile = uuidv4()
}
const clone = JSON.parse(JSON.stringify(profile)) // Always store a copy
// pull excluded
Object.keys(getExcludeFromProfile()).forEach(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
globalStorage.set(store)
profile.isDirty = false