Merge pull request #160 from Webifi/main
Force short suggested names; rename from chat menu; hidden prompt prefix
This commit is contained in:
commit
284589b6f3
17
src/app.scss
17
src/app.scss
|
@ -231,16 +231,16 @@ $modal-background-background-color-dark: rgba($dark, 0.86) !default; // remove t
|
|||
|
||||
.menu-list {
|
||||
a:hover {
|
||||
.delete-button {
|
||||
.delete-button, .edit-button {
|
||||
display: block !important;
|
||||
background-color: initial;
|
||||
}
|
||||
}
|
||||
.delete-button {
|
||||
.delete-button, .edit-button {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
.delete-button:hover, .edit-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
@ -321,6 +321,17 @@ $modal-background-background-color-dark: rgba($dark, 0.86) !default; // remove t
|
|||
z-index: 200;
|
||||
}
|
||||
|
||||
.chat-menu-item .edit-button {
|
||||
position: absolute;
|
||||
right: 2em;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.chat-name-editor {
|
||||
margin: .5em;
|
||||
padding:.1em;
|
||||
}
|
||||
|
||||
/* Overrides for main layout */
|
||||
|
||||
.side-bar-column {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
} from './Types.svelte'
|
||||
import Prompts from './Prompts.svelte'
|
||||
import Messages from './Messages.svelte'
|
||||
import { prepareSummaryPrompt, restartProfile } from './Profiles.svelte'
|
||||
import { mergeProfileFields, prepareSummaryPrompt, restartProfile } from './Profiles.svelte'
|
||||
|
||||
import { afterUpdate, onMount, onDestroy } from 'svelte'
|
||||
import Fa from 'svelte-fa/src/fa.svelte'
|
||||
|
@ -37,7 +37,7 @@
|
|||
faLightbulb,
|
||||
faCommentSlash
|
||||
} from '@fortawesome/free-solid-svg-icons/index'
|
||||
// import { encode } from 'gpt-tokenizer'
|
||||
import { encode } from 'gpt-tokenizer'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { countPromptTokens, getModelMaxTokens, getPrice } from './Stats.svelte'
|
||||
import { autoGrowInputOnEvent, scrollToMessage, sizeTextElements } from './Util.svelte'
|
||||
|
@ -184,10 +184,17 @@
|
|||
let filtered = messages.filter(messageFilter)
|
||||
|
||||
// Get an estimate of the total prompt size we're sending
|
||||
const promptTokenCount:number = countPromptTokens(filtered, model)
|
||||
let promptTokenCount:number = countPromptTokens(filtered, model)
|
||||
|
||||
let summarySize = chatSettings.summarySize
|
||||
|
||||
const hiddenPromptPrefix = mergeProfileFields(chatSettings, chatSettings.hiddenPromptPrefix).trim()
|
||||
|
||||
if (hiddenPromptPrefix && filtered.length && filtered[filtered.length - 1].role === 'user') {
|
||||
// update estimate with hiddenPromptPrefix token count
|
||||
promptTokenCount += encode(hiddenPromptPrefix + '\n\n').length
|
||||
}
|
||||
|
||||
// console.log('Estimated',promptTokenCount,'prompt token for this request')
|
||||
|
||||
if (chatSettings.continuousChat && !opts.didSummary &&
|
||||
|
@ -340,10 +347,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
const messagePayload = filtered.map((m, i) => {
|
||||
const r = { role: m.role, content: m.content }
|
||||
if (i === filtered.length - 1 && m.role === 'user' && hiddenPromptPrefix && !opts.summaryRequest) {
|
||||
// If the last prompt is a user prompt, and we have a hiddenPromptPrefix, inject it
|
||||
r.content = hiddenPromptPrefix + '\n\n' + m.content
|
||||
}
|
||||
return r
|
||||
}) as Message[]
|
||||
|
||||
// Update token count with actual
|
||||
promptTokenCount = countPromptTokens(messagePayload, model)
|
||||
|
||||
try {
|
||||
const request: Request = {
|
||||
messages: filtered.map(m => { return { role: m.role, content: m.content } }) as Message[],
|
||||
|
||||
messages: messagePayload,
|
||||
// Provide the settings by mapping the settingsMap to key/value pairs
|
||||
...getRequestSettingList().reduce((acc, setting) => {
|
||||
const key = setting.key
|
||||
|
@ -351,16 +369,15 @@
|
|||
if (typeof setting.apiTransform === 'function') {
|
||||
value = setting.apiTransform(chatId, setting, value)
|
||||
}
|
||||
if (opts.summaryRequest && opts.maxTokens) {
|
||||
// requesting summary. do overrides
|
||||
if (key === 'max_tokens') value = opts.maxTokens // only as large as we need for summary
|
||||
if (key === 'n') value = 1 // never more than one completion for summary
|
||||
if (opts.maxTokens) {
|
||||
if (key === 'max_tokens') value = opts.maxTokens // only as large as requested
|
||||
}
|
||||
if (opts.streaming) {
|
||||
if (opts.streaming || opts.summaryRequest) {
|
||||
/*
|
||||
Streaming goes insane with more than one completion.
|
||||
Doesn't seem like there's any way to separate the jumbled mess of deltas for the
|
||||
different completions.
|
||||
Summary should only have one completion
|
||||
*/
|
||||
if (key === 'n') value = 1
|
||||
}
|
||||
|
@ -391,7 +408,11 @@
|
|||
let errorResponse
|
||||
try {
|
||||
const errObj = await response.json()
|
||||
errorResponse = errObj?.error?.code || 'Unexpected Response'
|
||||
errorResponse = errObj?.error?.code
|
||||
if (!errorResponse && response.choices && response.choices[0]) {
|
||||
errorResponse = response.choices[0]?.message?.content
|
||||
}
|
||||
errorResponse = errorResponse || 'Unexpected Response'
|
||||
} catch (e) {
|
||||
errorResponse = 'Unknown Response'
|
||||
}
|
||||
|
@ -555,7 +576,7 @@
|
|||
const suggestName = async (): Promise<void> => {
|
||||
const suggestMessage: Message = {
|
||||
role: 'user',
|
||||
content: "Can you give me a 5 word summary of this conversation's topic?",
|
||||
content: "Using appropriate language, please give a 5 word summary of this conversation's topic.",
|
||||
uuid: uuidv4()
|
||||
}
|
||||
|
||||
|
@ -565,7 +586,9 @@
|
|||
const response = await sendRequest(suggestMessages, {
|
||||
chat,
|
||||
autoAddMessages: false,
|
||||
streaming: false
|
||||
streaming: false,
|
||||
summaryRequest: true,
|
||||
maxTokens: 10
|
||||
})
|
||||
await response.promiseToFinish()
|
||||
|
||||
|
@ -577,10 +600,10 @@
|
|||
})
|
||||
} else {
|
||||
response.getMessages().forEach(m => {
|
||||
chat.name = m.content
|
||||
const name = m.content.split(/\s+/).slice(0, 8).join(' ').trim()
|
||||
if (name) chat.name = name
|
||||
})
|
||||
saveChatStore()
|
||||
$checkStateChange++
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,52 @@
|
|||
<script lang="ts">
|
||||
import { replace } from 'svelte-spa-router'
|
||||
import type { Chat } from './Types.svelte'
|
||||
import { apiKeyStorage, deleteChat, pinMainMenu } from './Storage.svelte'
|
||||
import { apiKeyStorage, deleteChat, pinMainMenu, saveChatStore } from './Storage.svelte'
|
||||
import Fa from 'svelte-fa/src/fa.svelte'
|
||||
import { faTrash, faCircleCheck } from '@fortawesome/free-solid-svg-icons/index'
|
||||
import { faTrash, faCircleCheck, faPencil } from '@fortawesome/free-solid-svg-icons/index'
|
||||
import { faMessage } from '@fortawesome/free-regular-svg-icons/index'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let chat:Chat
|
||||
export let activeChatId:number|undefined
|
||||
export let prevChat:Chat|undefined
|
||||
export let nextChat:Chat|undefined
|
||||
|
||||
let editing:boolean = false
|
||||
let original:string
|
||||
|
||||
let waitingForConfirm:any = 0
|
||||
|
||||
function delChat () {
|
||||
onMount(async () => {
|
||||
if (!chat.name) {
|
||||
chat.name = `Chat ${chat.id}`
|
||||
}
|
||||
})
|
||||
|
||||
const keydown = (event:KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
chat.name = original
|
||||
editing = false
|
||||
}
|
||||
if (event.key === 'Tab' || event.key === 'Enter') {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
editing = false
|
||||
if (!chat.name) {
|
||||
chat.name = original
|
||||
return
|
||||
}
|
||||
saveChatStore()
|
||||
}
|
||||
|
||||
const delChat = () => {
|
||||
if (!waitingForConfirm) {
|
||||
// wait a second for another click to avoid accidental deletes
|
||||
waitingForConfirm = setTimeout(() => { waitingForConfirm = 0 }, 1000)
|
||||
|
@ -35,15 +68,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
const edit = () => {
|
||||
original = chat.name
|
||||
editing = true
|
||||
setTimeout(() => {
|
||||
const el = document.getElementById(`chat-menu-item-${chat.id}`)
|
||||
el && el.focus()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<li>
|
||||
<a class="chat-menu-item" href={`#/chat/${chat.id}`} on:click={() => { $pinMainMenu = false }} class:is-waiting={waitingForConfirm} class:is-disabled={!$apiKeyStorage} class:is-active={activeChatId === chat.id}>
|
||||
{#if editing}
|
||||
<div id="chat-menu-item-{chat.id}" class="chat-name-editor" on:keydown={keydown} contenteditable bind:innerText={chat.name} on:blur={update} />
|
||||
{:else}
|
||||
<a
|
||||
href={`#/chat/${chat.id}`}
|
||||
class="chat-menu-item"
|
||||
class:is-waiting={waitingForConfirm} class:is-disabled={!$apiKeyStorage} class:is-active={activeChatId === chat.id}
|
||||
on:click={() => { $pinMainMenu = false }} >
|
||||
{#if waitingForConfirm}
|
||||
<a class="is-pulled-right is-hidden px-1 py-0 has-text-weight-bold delete-button" href={'$'} on:click|preventDefault={() => delChat()}><Fa icon={faCircleCheck} /></a>
|
||||
{:else}
|
||||
<a class="is-pulled-right is-hidden px-1 py-0 has-text-weight-bold edit-button" href={'$'} on:click|preventDefault={() => edit()}><Fa icon={faPencil} /></a>
|
||||
<a class="is-pulled-right is-hidden px-1 py-0 has-text-weight-bold delete-button" href={'$'} on:click|preventDefault={() => delChat()}><Fa icon={faTrash} /></a>
|
||||
{/if}
|
||||
<span class="chat-item-name"><Fa class="mr-2 chat-icon" size="xs" icon="{faMessage}"/>{chat.name || `Chat ${chat.id}`}</span>
|
||||
</a>
|
||||
{/if}
|
||||
</li>
|
|
@ -70,22 +70,25 @@ export const getProfile = (key:string, forReset:boolean = false):ChatSettings =>
|
|||
return clone
|
||||
}
|
||||
|
||||
export const mergeProfileFields = (settings: ChatSettings, content: string|undefined, maxWords: number|undefined = undefined): string => {
|
||||
if (!content?.toString) return ''
|
||||
content = (content + '').replaceAll('[[CHARACTER_NAME]]', settings.characterName || 'ChatGPT')
|
||||
if (maxWords) content = (content + '').replaceAll('[[MAX_WORDS]]', maxWords.toString())
|
||||
return content
|
||||
}
|
||||
|
||||
export const prepareProfilePrompt = (chatId:number) => {
|
||||
const settings = getChatSettings(chatId)
|
||||
const characterName = settings.characterName
|
||||
const currentProfilePrompt = settings.systemPrompt
|
||||
return currentProfilePrompt.replaceAll('[[CHARACTER_NAME]]', characterName)
|
||||
return mergeProfileFields(settings, settings.systemPrompt).trim()
|
||||
}
|
||||
|
||||
export const prepareSummaryPrompt = (chatId:number, promptsSize:number, maxTokens:number|undefined = undefined) => {
|
||||
const settings = getChatSettings(chatId)
|
||||
const characterName = settings.characterName || 'ChatGPT'
|
||||
maxTokens = maxTokens || settings.summarySize
|
||||
maxTokens = Math.min(Math.floor(promptsSize / 4), maxTokens) // Make sure we're shrinking by at least a 4th
|
||||
const currentSummaryPrompt = settings.summaryPrompt
|
||||
return currentSummaryPrompt
|
||||
.replaceAll('[[CHARACTER_NAME]]', characterName)
|
||||
.replaceAll('[[MAX_WORDS]]', Math.floor(maxTokens * 0.75).toString()) // ~.75 words per token. May need to reduce
|
||||
// ~.75 words per token. May need to reduce
|
||||
return mergeProfileFields(settings, currentSummaryPrompt, Math.floor(maxTokens * 0.75)).trim()
|
||||
}
|
||||
|
||||
// Restart currently loaded profile
|
||||
|
|
|
@ -84,6 +84,7 @@ const defaults:ChatSettings = {
|
|||
systemPrompt: '',
|
||||
autoStartSession: false,
|
||||
trainingPrompts: [],
|
||||
hiddenPromptPrefix: '',
|
||||
// useResponseAlteration: false,
|
||||
// responseAlterations: [],
|
||||
isDirty: false
|
||||
|
@ -167,6 +168,14 @@ const systemPromptSettings: ChatSetting[] = [
|
|||
type: 'textarea',
|
||||
hide: (chatId) => !getChatSettings(chatId).useSystemPrompt
|
||||
},
|
||||
{
|
||||
key: 'hiddenPromptPrefix',
|
||||
name: 'Hidden Prompt Prefix',
|
||||
title: 'A prompt that will be silently injected before every user prompt.',
|
||||
placeholder: 'Enter user prompt prefix here. You can remind ChatGPT how to act.',
|
||||
type: 'textarea',
|
||||
hide: (chatId) => !getChatSettings(chatId).useSystemPrompt
|
||||
},
|
||||
{
|
||||
key: 'trainingPrompts',
|
||||
name: 'Training Prompts',
|
||||
|
|
|
@ -58,7 +58,6 @@
|
|||
profileName: string,
|
||||
profileDescription: string,
|
||||
continuousChat: (''|'fifo'|'summary');
|
||||
// useSummarization: boolean;
|
||||
summaryThreshold: number;
|
||||
summarySize: number;
|
||||
pinTop: number;
|
||||
|
@ -67,6 +66,7 @@
|
|||
useSystemPrompt: boolean;
|
||||
systemPrompt: string;
|
||||
autoStartSession: boolean;
|
||||
hiddenPromptPrefix: string;
|
||||
trainingPrompts?: Message[];
|
||||
useResponseAlteration?: boolean;
|
||||
responseAlterations?: ResponseAlteration[];
|
||||
|
|
Loading…
Reference in New Issue