Merge pull request #160 from Webifi/main

Force short suggested names; rename from chat menu; hidden prompt prefix
This commit is contained in:
Niek van der Maas 2023-06-10 05:09:48 +02:00 committed by GitHub
commit 284589b6f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 30 deletions

View File

@ -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 {

View File

@ -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++
}
}

View File

@ -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>

View File

@ -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

View File

@ -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',

View File

@ -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[];