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;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,10 +480,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
saveChatStore()
|
saveChatStore()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
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.content = choice.message.content
|
||||||
message.usage = response.usage
|
message.usage = response.usage
|
||||||
message.model = response.model
|
}
|
||||||
|
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.
|
||||||
|
|
|
@ -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
|
||||||
|
@ -179,6 +184,7 @@
|
||||||
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={'#'}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue