diff --git a/src/app.scss b/src/app.scss index ca1f4be..c7d2a98 100644 --- a/src/app.scss +++ b/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 { diff --git a/src/lib/Chat.svelte b/src/lib/Chat.svelte index 8a45a81..b54a54a 100644 --- a/src/lib/Chat.svelte +++ b/src/lib/Chat.svelte @@ -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 => { 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++ } } diff --git a/src/lib/ChatMenuItem.svelte b/src/lib/ChatMenuItem.svelte index d4eeb93..4503042 100644 --- a/src/lib/ChatMenuItem.svelte +++ b/src/lib/ChatMenuItem.svelte @@ -1,19 +1,52 @@
  • - { $pinMainMenu = false }} class:is-waiting={waitingForConfirm} class:is-disabled={!$apiKeyStorage} class:is-active={activeChatId === chat.id}> + {#if editing} +
  • \ No newline at end of file diff --git a/src/lib/Profiles.svelte b/src/lib/Profiles.svelte index c899c97..92d0d66 100644 --- a/src/lib/Profiles.svelte +++ b/src/lib/Profiles.svelte @@ -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 diff --git a/src/lib/Settings.svelte b/src/lib/Settings.svelte index a77dea6..4c4e437 100644 --- a/src/lib/Settings.svelte +++ b/src/lib/Settings.svelte @@ -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', diff --git a/src/lib/Types.svelte b/src/lib/Types.svelte index 3bd41de..7eabc94 100644 --- a/src/lib/Types.svelte +++ b/src/lib/Types.svelte @@ -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[];