Add truncated completion joining
This commit is contained in:
parent
d768f4b355
commit
c3172eb4aa
18
src/app.scss
18
src/app.scss
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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={'#'}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue