From 52b1f29942e889a6a0af920429febb0d45392962 Mon Sep 17 00:00:00 2001 From: Webifi Date: Sun, 28 May 2023 01:11:00 -0500 Subject: [PATCH] Refactor settings again --- package-lock.json | 7 ++ package.json | 1 + src/lib/Chat.svelte | 193 +++++++++++++++++++++++++------------ src/lib/EditMessage.svelte | 10 +- src/lib/Messages.svelte | 9 +- src/lib/Profiles.svelte | 111 +++++++++++---------- src/lib/Settings.svelte | 164 ++++++++++++++++++++----------- src/lib/Storage.svelte | 141 ++++++++++++++++----------- src/lib/Types.svelte | 54 +++++------ 9 files changed, 434 insertions(+), 256 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebce529..1dfc881 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "svelte-local-storage-store": "^0.4.0", "svelte-markdown": "^0.2.3", "svelte-spa-router": "^3.3.0", + "svelte-use-click-outside": "^1.0.0", "tslib": "^2.5.0", "typescript": "^5.0.4", "uuid": "^9.0.0", @@ -4324,6 +4325,12 @@ "url": "https://github.com/sponsors/ItalyPaleAle" } }, + "node_modules/svelte-use-click-outside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svelte-use-click-outside/-/svelte-use-click-outside-1.0.0.tgz", + "integrity": "sha512-tOWeMPxeIoW9RshS0WbogRhdYdbxcJV0ebkzSh1lwR7Ihl0hSZMmB4YyCHHoXJK4xcbxCCFh0AnQ1vkzGZfLVQ==", + "dev": true + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index db47d00..0c47a2e 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "svelte-local-storage-store": "^0.4.0", "svelte-markdown": "^0.2.3", "svelte-spa-router": "^3.3.0", + "svelte-use-click-outside": "^1.0.0", "tslib": "^2.5.0", "typescript": "^5.0.4", "uuid": "^9.0.0", diff --git a/src/lib/Chat.svelte b/src/lib/Chat.svelte index f715401..38cf453 100644 --- a/src/lib/Chat.svelte +++ b/src/lib/Chat.svelte @@ -9,16 +9,17 @@ insertMessages, clearMessages, copyChat, - getChatSettingValue, - getChatSettingValueByKey, - setChatSettingValue, getChatSettingValueNullDefault, setChatSettingValueByKey, saveCustomProfile, deleteCustomProfile, - setGlobalSettingValueByKey + setGlobalSettingValueByKey, + updateChatSettings, + resetChatSettings, + setChatSettingValue, + addChatFromJSON, } from './Storage.svelte' - import { getChatSettingByKey, getChatSettingList } from './Settings.svelte' + import { getChatSettingObjectByKey, getChatSettingList, getRequestSettingList } from './Settings.svelte' import { type Request, type Response, @@ -33,7 +34,7 @@ } from './Types.svelte' import Prompts from './Prompts.svelte' import Messages from './Messages.svelte' - import { applyProfile, checkSessionActivity, getProfile, getProfileSelect, prepareSummaryPrompt } from './Profiles.svelte' + import { applyProfile, getProfile, getProfileSelect, prepareSummaryPrompt } from './Profiles.svelte' import { afterUpdate, onMount } from 'svelte' import { replace } from 'svelte-spa-router' @@ -51,11 +52,14 @@ faFloppyDisk, faThumbtack, faDownload, - faUpload + faUpload, + faEraser, + faRotateRight, } from '@fortawesome/free-solid-svg-icons/index' import { encode } from 'gpt-tokenizer' import { v4 as uuidv4 } from 'uuid' - import { exportProfileAsJSON } from './Export.svelte' + import { exportChatAsJSON, exportProfileAsJSON } from './Export.svelte' + import { clickOutside } from 'svelte-use-click-outside' // This makes it possible to override the OpenAI API base URL in the .env file const apiBase = import.meta.env.VITE_API_BASE || 'https://api.openai.com' @@ -70,26 +74,22 @@ let chatNameSettings: HTMLFormElement let recognition: any = null let recording = false + let chatFileInput let profileFileInput let showSettingsModal = 0 let showProfileMenu = false + let showChatMenu = false const settingsList = getChatSettingList() - const modelSetting = getChatSettingByKey('model') as ChatSetting & SettingSelect + const modelSetting = getChatSettingObjectByKey('model') as ChatSetting & SettingSelect $: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat + $: chatSettings = chat.settings $: globalStore = $globalStorage onMount(async () => { - // Sanitize old save - if (!chat.settings) chat.settings = {} as ChatSettings - // make sure old chat has UUID - if (chat && chat.messages && chat.messages[0] && !chat.messages[0].uuid) { - chat.messages.forEach((m) => { - m.uuid = uuidv4() - }) - saveChatStore() - } + // Make sure chat object is ready to go + updateChatSettings(chatId) // Focus the input on mount focusInput() @@ -120,11 +120,12 @@ } else { console.log('Speech recognition not supported') } - if (!chat.settings.profile) { + if (chatSettings.startSession) { const profile = getProfile('') // get default profile applyProfile(chatId, profile.profile as any) - if (getChatSettingValueByKey(chatId, 'startSession')) { + if (chatSettings.startSession) { setChatSettingValueByKey(chatId, 'startSession', false) + // Auto start the session out of band setTimeout(() => { submitForm(false, true) }, 0) } } @@ -160,15 +161,15 @@ return a }, 0) - if (getChatSettingValueByKey(chatId, 'useSummarization') && + if (chatSettings.useSummarization && !withSummary && !doingSummary && - (promptTokenCount > getChatSettingValueByKey(chatId, 'summaryThreshold'))) { + promptTokenCount > chatSettings.summaryThreshold) { // Too many tokens -- well need to sumarize some past ones else we'll run out of space // Get a block of past prompts we'll summarize - let pinTop = getChatSettingValueByKey(chatId, 'pinTop') - const tp = getChatSettingValueByKey(chatId, 'trainingPrompts') - pinTop = Math.max(pinTop, tp || 0) - let pinBottom = getChatSettingValueByKey(chatId, 'pinBottom') + let pinTop = chatSettings.pinTop + const tp = chatSettings.trainingPrompts + pinTop = Math.max(pinTop, tp ? 1 : 0) + let pinBottom = chatSettings.pinBottom const systemPad = (filtered[0] || {} as Message).role === 'system' ? 1 : 0 const mlen = filtered.length - systemPad // always keep system prompt let diff = mlen - (pinTop + pinBottom) @@ -258,13 +259,15 @@ messages: filtered.map(m => { return { role: m.role, content: m.content } }) as Message[], // Provide the settings by mapping the settingsMap to key/value pairs - ...getChatSettingList().reduce((acc, setting) => { - if (setting.noRequest) return acc // don't include non-request settings + ...getRequestSettingList().reduce((acc, setting) => { let value = getChatSettingValueNullDefault(chatId, setting) - if (value === null && setting.required) value = setting.default if (doingSummary && setting.key === 'max_tokens') { // Override for summary - value = getChatSettingValueByKey(chatId, 'summarySize') + // TODO: Auto adjust this above to make sure it doesn't go over avail token space + value = chatSettings.summarySize + } + if (typeof setting.apiTransform === 'function') { + value = setting.apiTransform(chatId, setting, value) } if (value !== null) acc[setting.key] = value return acc @@ -344,6 +347,7 @@ if (updating) return if (!skipInput) { + setChatSettingValueByKey(chatId, 'sessionStarted', true) if (input.value !== '') { // Compose the input message const inputMessage: Message = { role: 'user', content: input.value, uuid: uuidv4() } @@ -440,10 +444,13 @@ } const updateProfileSelectOptions = () => { - const profileSelect = getChatSettingByKey('profile') as ChatSetting & SettingSelect - const defaultProfile = getProfile('') - profileSelect.default = defaultProfile.profile as any + const profileSelect = getChatSettingObjectByKey('profile') as ChatSetting & SettingSelect profileSelect.options = getProfileSelect() + // const defaultProfile = globalStore.defaultProfile || profileSelect.options[0].value + } + + const refreshSettings = async () => { + showSettingsModal && showSettings() } const showSettings = async () => { @@ -502,13 +509,8 @@ } const clearSettings = () => { - settingsList.forEach(s => { - setChatSettingValue(chatId, s, null) - }) + resetChatSettings(chatId) showSettingsModal++ // Make sure the dialog updates - // const input = settings.querySelector(`#settings-${setting.key}`) as HTMLInputElement - // saveSetting(chatId, setting, null) - // input.value = '' } const recordToggle = () => { @@ -526,22 +528,34 @@ const queueSettingValueChange = (event: Event, setting: ChatSetting) => { clearTimeout(debounce[setting.key]) if (event.target === null) return + const val = chatSettings[setting.key] const el = (event.target as HTMLInputElement) const doSet = () => { + try { + (typeof setting.beforeChange === 'function') && setting.beforeChange(chatId, setting, el.checked || el.value) + && refreshSettings() + } catch (e) { + alert('Unable to change:\n' + e.message) + } switch (setting.type) { case 'boolean': setChatSettingValue(chatId, setting, el.checked) - showSettingsModal && showSettingsModal++ + refreshSettings() break default: setChatSettingValue(chatId, setting, el.value) } - (typeof setting.afterChange === 'function') && setting.afterChange(chatId, setting) - && showSettingsModal && showSettingsModal++ + try { + (typeof setting.afterChange === 'function') && setting.afterChange(chatId, setting, chatSettings[setting.key]) + && refreshSettings() + } catch (e) { + setChatSettingValue(chatId, setting, val) + alert('Unable to change:\n' + e.message) + } } - if (setting.key === 'profile' && checkSessionActivity(chatId) - && (getProfile(el.value).characterName !== getChatSettingValueByKey(chatId,'characterName'))) { - const val = getChatSettingValue(chatId, setting) + if (setting.key === 'profile' && chatSettings.sessionStarted + && (getProfile(el.value).characterName !== chatSettings.characterName)) { + const val = chatSettings[setting.key] if (window.confirm('Personality change will not correctly apply to existing chat session.\n Continue?')) { doSet() } else { @@ -568,6 +582,7 @@ showProfileMenu = false try { saveCustomProfile(chat.settings) + refreshSettings() } catch (e) { alert('Error saving profile: \n' + e.message) } @@ -607,7 +622,7 @@ showProfileMenu = false try { deleteCustomProfile(chatId, chat.settings.profile as any) - chat.settings.profile = globalStore.defaultProfile + chat.settings.profile = globalStore.defaultProfile || '' saveChatStore() setGlobalSettingValueByKey('lastProfile', chat.settings.profile) applyProfile(chatId, chat.settings.profile as any) @@ -641,28 +656,78 @@ } } } + + const importChatFromFile = (e) => { + const image = e.target.files[0] + const reader = new FileReader() + reader.readAsText(image) + reader.onload = e => { + const json = (e.target || {}).result as string + addChatFromJSON(json) + } + } @@ -720,6 +785,7 @@ if (event.key === 'Escape') { closeSettings() closeChatNameSettings() + showChatMenu = false } }} /> @@ -755,7 +821,6 @@ { showProfileMenu = false; profileFileInput.click() }}> Import Profile - importProfileFromFile(e)} bind:this={profileFileInput} > Set as Default Profile @@ -787,7 +852,7 @@ title="{setting.title}" class="checkbox" id="settings-{setting.key}" - checked={getChatSettingValue(chatId, setting)} + checked={!!chatSettings[setting.key]} on:click={e => queueSettingValueChange(e, setting)} > {setting.name} @@ -802,7 +867,7 @@ rows="1" on:input={e => autoGrowInputOnEvent(e)} on:change={e => { queueSettingValueChange(e, setting); autoGrowInputOnEvent(e) }} - >{getChatSettingValue(chatId, setting)} + >{chatSettings[setting.key]} {:else}
@@ -818,18 +883,18 @@ type={setting.type} title="{setting.title}" id="settings-{setting.key}" - value="{getChatSettingValue(chatId, setting)}" + value={chatSettings[setting.key]} min={setting.min} max={setting.max} step={setting.step} - placeholder={String(setting.default)} + placeholder={String(setting.placeholder)} on:change={e => queueSettingValueChange(e, setting)} /> {:else if setting.type === 'select'}
@@ -839,7 +904,7 @@ type="text" title="{setting.title}" class="input" - value={getChatSettingValue(chatId, setting)} + value={chatSettings[setting.key]} on:change={e => { queueSettingValueChange(e, setting) }} >
@@ -859,6 +924,10 @@ + + importChatFromFile(e)} bind:this={chatFileInput} > + importProfileFromFile(e)} bind:this={profileFileInput} > +