Clean up UI for settings modal, better for mobile.

This commit is contained in:
Webifi 2023-06-03 09:40:49 -05:00
parent 43f52e1fd1
commit a38ce6d702
6 changed files with 104 additions and 77 deletions

View File

@ -157,9 +157,9 @@ html {
resize: vertical; resize: vertical;
} }
$footer-padding: 1.5rem 1.5rem; // $footer-padding: 1.5rem 1.5rem;
$fullhd: 2000px; // $fullhd: 2000px;
$modal-content-width: 1000px; // $modal-content-width: 1000px;
@import "/node_modules/bulma/bulma.sass"; @import "/node_modules/bulma/bulma.sass";
@ -277,6 +277,12 @@ $modal-background-background-color-dark: rgba($dark, 0.86) !default; // remove t
max-height: calc(100vh - 60px); max-height: calc(100vh - 60px);
overflow-y: auto; overflow-y: auto;
} }
.modal-card .dropdown-menu .dropdown-content {
max-height: calc(100vh - 80px);
}
.modal-card {
overflow: visible;
}
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
.main-menu .dropdown-menu .dropdown-content { .main-menu .dropdown-menu .dropdown-content {
@ -594,3 +600,18 @@ aside.menu.main-menu .menu-expanse {
border-top-left-radius: 0px !important; border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important; border-bottom-left-radius: 0px !important;
} }
.modal-card footer {
justify-content: space-between;
}
.modal-card footer .level {
width: 100%;
}
.modal-card header, .modal-card footer, .modal-card .notification {
padding: .8em;
}
.modal-card .notification {
margin-left: -.5em;
margin-right: -.5em;
}

View File

@ -92,7 +92,7 @@
<span class="menu-icon"><Fa icon={faGear}/></span> Chat Profile Settings <span class="menu-icon"><Fa icon={faGear}/></span> Chat Profile Settings
</a> </a>
<hr class="dropdown-divider"> <hr class="dropdown-divider">
<a href={$apiKeyStorage?'#/chat/new':'#/'} class:is-disabled={!$apiKeyStorage} on:click={() => close()} class="dropdown-item"> <a href={$apiKeyStorage ? '#/chat/new' : '#/'} class:is-disabled={!$apiKeyStorage} on:click={() => close()} class="dropdown-item">
<span class="menu-icon"><Fa icon={faSquarePlus}/></span> New Chat <span class="menu-icon"><Fa icon={faSquarePlus}/></span> New Chat
</a> </a>
<a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { if (chatId) close(); copyChat(chatId) }}> <a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { if (chatId) close(); copyChat(chatId) }}>

View File

@ -24,28 +24,28 @@
} }
const settingChecks:Record<string, SettingPrompt[]> = { const settingChecks:Record<string, SettingPrompt[]> = {
'profile': [ profile: [
{ {
prompt:'Unsaved changes to the current profile will be lost.\n Continue?', prompt: 'Unsaved changes to the current profile will be lost.\n Continue?',
fn: (setting, newVal, oldVal) => { fn: (setting, newVal, oldVal) => {
return !!chatSettings.isDirty && newVal !== oldVal return !!chatSettings.isDirty && newVal !== oldVal
}, },
passed: false, passed: false
}, },
{ {
prompt:'Personality change will not correctly apply to existing chat session.\n Continue?', prompt: 'Personality change will not correctly apply to existing chat session.\n Continue?',
fn: (setting, newVal, oldVal) => { fn: (setting, newVal, oldVal) => {
return chat.sessionStarted && newVal !== originalProfile && return chat.sessionStarted && newVal !== originalProfile &&
(getProfile(newVal).characterName !== chatSettings.characterName) (getProfile(newVal).characterName !== chatSettings.characterName)
}, },
passed: false, passed: false
}, }
] ]
} }
const resetSettingCheck = (key:keyof ChatSettings) => { const resetSettingCheck = (key:keyof ChatSettings) => {
const checks = settingChecks[key] const checks = settingChecks[key]
checks && checks.forEach((c)=>{c.passed=false}) checks && checks.forEach((c) => { c.passed = false })
} }
let debounce: any let debounce: any
@ -73,11 +73,10 @@
const newVal = chatSettings[setting.key] const newVal = chatSettings[setting.key]
if (val === newVal) return if (val === newVal) return
try { try {
if((typeof setting.afterChange === 'function') && setting.afterChange(chatId, setting, chatSettings[setting.key])){ if ((typeof setting.afterChange === 'function') && setting.afterChange(chatId, setting, chatSettings[setting.key])) {
console.log('Refreshed from setting', setting.key, chatSettings[setting.key], val) console.log('Refreshed from setting', setting.key, chatSettings[setting.key], val)
refreshSettings() refreshSettings()
} }
} catch (e) { } catch (e) {
setChatSettingValue(chatId, setting, val) setChatSettingValue(chatId, setting, val)
window.alert('Unable to change:\n' + e.message) window.alert('Unable to change:\n' + e.message)
@ -85,13 +84,13 @@
dispatch('change', setting) dispatch('change', setting)
} }
const checks = settingChecks[setting.key] || [] const checks = settingChecks[setting.key] || []
const newVal = cleanSettingValue(setting.type, el.checked||el.value) const newVal = cleanSettingValue(setting.type, el.checked || el.value)
for (let i = 0, l = checks.length; i < l; i++) { for (let i = 0, l = checks.length; i < l; i++) {
let c = checks[i] const c = checks[i]
if(c.passed) continue if (c.passed) continue
if (c.fn(setting, newVal, val)) { if (c.fn(setting, newVal, val)) {
// eventually this needs to be an async call to a confirmation modal where // eventually this needs to be an async call to a confirmation modal where
// "passed", not really being used here, will be reworked to some other // "passed", not really being used here, will be reworked to some other
// state to deal with inevitable issues once a non-blocking modal is used. // state to deal with inevitable issues once a non-blocking modal is used.
if (window.confirm(c.prompt)) { if (window.confirm(c.prompt)) {
c.passed = true c.passed = true
@ -101,7 +100,7 @@
// refresh setting modal, if open // refresh setting modal, if open
refreshSettings() refreshSettings()
resetSettingCheck(setting.key) resetSettingCheck(setting.key)
return return
} }
} else { } else {
c.passed = true c.passed = true

View File

@ -18,7 +18,7 @@
import { import {
faTrash, faTrash,
faClone, faClone,
faEllipsisVertical, faEllipsis,
faFloppyDisk, faFloppyDisk,
faThumbtack, faThumbtack,
faDownload, faDownload,
@ -27,7 +27,7 @@
import { exportProfileAsJSON } from './Export.svelte' import { exportProfileAsJSON } from './Export.svelte'
import { afterUpdate } from 'svelte' import { afterUpdate } from 'svelte'
import ChatSettingField from './ChatSettingField.svelte' import ChatSettingField from './ChatSettingField.svelte'
import { getModelMaxTokens } from './Stats.svelte'; import { getModelMaxTokens } from './Stats.svelte'
export let chatId:number export let chatId:number
export const show = () => { showSettings() } export const show = () => { showSettings() }
@ -50,7 +50,7 @@
$: chatSettings = chat.settings $: chatSettings = chat.settings
$: globalStore = $globalStorage $: globalStore = $globalStorage
let originalProfile = chatSettings && chatSettings.profile const originalProfile = chatSettings && chatSettings.profile
afterUpdate(() => { afterUpdate(() => {
sizeTextElements() sizeTextElements()
@ -64,7 +64,7 @@
const clearSettings = () => { const clearSettings = () => {
resetChatSettings(chatId) resetChatSettings(chatId)
showSettingsModal++ // Make sure the dialog updates refreshSettings()
} }
const refreshSettings = async () => { const refreshSettings = async () => {
@ -131,7 +131,7 @@
const profileSelect = getChatSettingObjectByKey('profile') as ChatSetting & SettingSelect const profileSelect = getChatSettingObjectByKey('profile') as ChatSetting & SettingSelect
profileSelect.options = getProfileSelect() profileSelect.options = getProfileSelect()
chatDefaults.profile = getDefaultProfileKey() chatDefaults.profile = getDefaultProfileKey()
chatDefaults.max_tokens = getModelMaxTokens(chatSettings.model||'') chatDefaults.max_tokens = getModelMaxTokens(chatSettings.model || '')
// const defaultProfile = globalStore.defaultProfile || profileSelect.options[0].value // const defaultProfile = globalStore.defaultProfile || profileSelect.options[0].value
defaultProfile = getDefaultProfileKey() defaultProfile = getDefaultProfileKey()
isDefault = defaultProfile === chatSettings.profile isDefault = defaultProfile === chatSettings.profile
@ -204,10 +204,12 @@
// excludeFromProfile // excludeFromProfile
const deepEqual = (x:any, y:any) => { const deepEqual = (x:any, y:any) => {
const ok = Object.keys, tx = typeof x, ty = typeof y const ok = Object.keys; const tx = typeof x; const ty = typeof y
return x && y && tx === 'object' && tx === ty ? ( return x && y && tx === 'object' && tx === ty
ok(x).every(key => excludeFromProfile[key] || deepEqual(x[key], y[key])) ? (
) : (x === y || ((x===undefined||x===null||x===false) && (y===undefined||y===null||y===false))) ok(x).every(key => excludeFromProfile[key] || deepEqual(x[key], y[key]))
)
: (x === y || ((x === undefined || x === null || x === false) && (y === undefined || y === null || y === false)))
} }
const setDirty = (e:CustomEvent|undefined = undefined) => { const setDirty = (e:CustomEvent|undefined = undefined) => {
@ -228,41 +230,7 @@
<div class="modal-card" on:click={() => { showProfileMenu = false }}> <div class="modal-card" on:click={() => { showProfileMenu = false }}>
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">Chat Settings</p> <p class="modal-card-title">Chat Settings</p>
<div class="dropdown is-right" class:is-active={showProfileMenu}> <button class="delete" aria-label="close" on:click={closeSettings}></button>
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3" on:click|preventDefault|stopPropagation={() => { showProfileMenu = !showProfileMenu }}>
<span><Fa icon={faEllipsisVertical}/></span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu">
<div class="dropdown-content">
<a href={'#'} class="dropdown-item" class:is-disabled={!chatSettings.isDirty} on:click|preventDefault={saveProfile}>
<span class="menu-icon"><Fa icon={faFloppyDisk}/></span> Save Changes
</a>
<a href={'#'} class="dropdown-item" on:click|preventDefault={cloneProfile}>
<span class="menu-icon"><Fa icon={faClone}/></span> Clone Profile
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" class:is-disabled={isDefault} on:click|preventDefault={pinDefaultProfile}>
<span class="menu-icon"><Fa icon={faThumbtack}/></span> Set as Default Profile
</a>
<hr class="dropdown-divider">
<a href={'#'}
class="dropdown-item"
on:click|preventDefault={() => { showProfileMenu = false; exportProfileAsJSON(chatId) }}
>
<span class="menu-icon"><Fa icon={faDownload}/></span> Backup Profile JSON
</a>
<a href={'#'} class="dropdown-item" on:click|preventDefault={() => { showProfileMenu = false; profileFileInput.click() }}>
<span class="menu-icon"><Fa icon={faUpload}/></span> Restore Profile JSON
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" on:click|preventDefault={deleteProfile}>
<span class="menu-icon"><Fa icon={faTrash}/></span> Delete Profile
</a>
</div>
</div>
</div>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
{#key showSettingsModal} {#key showSettingsModal}
@ -273,9 +241,50 @@
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button class="button is-info" on:click={closeSettings}>Close</button> <div class="level is-mobile">
<button class="button is-warning" on:click={clearSettings}>Reset</button> <div class="level-left">
<button class="button" class:is-disabled={!chatSettings.isDirty} on:click={saveProfile}>Save Changes</button> <!-- <button class="button is-info" on:click={closeSettings}>Close</button> -->
<button class="button" class:is-disabled={!chatSettings.isDirty} on:click={saveProfile}>Save Changes</button>
<button class="button is-warning" class:is-disabled={!chatSettings.isDirty} on:click={clearSettings}>Reset</button>
</div>
<div class="level-right">
<div class="dropdown is-right is-up" class:is-active={showProfileMenu}>
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3" on:click|preventDefault|stopPropagation={() => { showProfileMenu = !showProfileMenu }}>
<span class="icon"><Fa icon={faEllipsis}/></span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu">
<div class="dropdown-content">
<a href={'#'} class="dropdown-item" class:is-disabled={!chatSettings.isDirty} on:click|preventDefault={saveProfile}>
<span class="menu-icon"><Fa icon={faFloppyDisk}/></span> Save Changes
</a>
<a href={'#'} class="dropdown-item" on:click|preventDefault={cloneProfile}>
<span class="menu-icon"><Fa icon={faClone}/></span> Clone Profile
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" class:is-disabled={isDefault} on:click|preventDefault={pinDefaultProfile}>
<span class="menu-icon"><Fa icon={faThumbtack}/></span> Set as Default Profile
</a>
<hr class="dropdown-divider">
<a href={'#'}
class="dropdown-item"
on:click|preventDefault={() => { showProfileMenu = false; exportProfileAsJSON(chatId) }}
>
<span class="menu-icon"><Fa icon={faDownload}/></span> Backup Profile JSON
</a>
<a href={'#'} class="dropdown-item" on:click|preventDefault={() => { showProfileMenu = false; profileFileInput.click() }}>
<span class="menu-icon"><Fa icon={faUpload}/></span> Restore Profile JSON
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" on:click|preventDefault={deleteProfile}>
<span class="menu-icon"><Fa icon={faTrash}/></span> Delete Profile
</a>
</div>
</div>
</div>
</div>
</div>
</footer> </footer>
</div> </div>
</div> </div>

View File

@ -72,7 +72,7 @@ export const prepareSummaryPrompt = (chatId:number, promptsSize:number, maxToken
} }
// Restart currently loaded profile // Restart currently loaded profile
export const restartProfile = (chatId:number, noApply:boolean=false) => { export const restartProfile = (chatId:number, noApply:boolean = false) => {
const settings = getChatSettings(chatId) const settings = getChatSettings(chatId)
if (!settings.profile && !noApply) return applyProfile(chatId, '', true) if (!settings.profile && !noApply) return applyProfile(chatId, '', true)
// Clear current messages // Clear current messages
@ -87,7 +87,7 @@ export const restartProfile = (chatId:number, noApply:boolean=false) => {
// Add trainingPrompts, if any // Add trainingPrompts, if any
if (settings.trainingPrompts) { if (settings.trainingPrompts) {
settings.trainingPrompts.forEach(tp => { settings.trainingPrompts.forEach(tp => {
addMessage(chatId, tp) addMessage(chatId, tp)
}) })
} }
@ -96,12 +96,10 @@ export const restartProfile = (chatId:number, noApply:boolean=false) => {
saveChatStore() saveChatStore()
// Mark mark this as last used // Mark mark this as last used
setGlobalSettingValueByKey('lastProfile', settings.profile) setGlobalSettingValueByKey('lastProfile', settings.profile)
} }
// Apply currently selected profile // Apply currently selected profile
export const applyProfile = (chatId:number, key:string = '', resetChat:boolean = false) => { export const applyProfile = (chatId:number, key:string = '', resetChat:boolean = false) => {
const settings = getChatSettings(chatId)
const profile = getProfile(key || settings.profile)
resetChatSettings(chatId, resetChat) // Fully reset resetChatSettings(chatId, resetChat) // Fully reset
if (!resetChat) return if (!resetChat) return
return restartProfile(chatId, true) return restartProfile(chatId, true)

View File

@ -1,6 +1,6 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import { applyProfile } from './Profiles.svelte' import { applyProfile } from './Profiles.svelte'
import { getChat, getChatSettings } from './Storage.svelte' import { getChatSettings } from './Storage.svelte'
import { encode } from 'gpt-tokenizer' import { encode } from 'gpt-tokenizer'
// Setting definitions // Setting definitions
@ -62,7 +62,7 @@ const gptDefaults = {
presence_penalty: 0, presence_penalty: 0,
frequency_penalty: 0, frequency_penalty: 0,
logit_bias: null, logit_bias: null,
user: undefined, user: undefined
} }
// Core set of defaults // Core set of defaults
@ -82,13 +82,13 @@ const defaults:ChatSettings = {
systemPrompt: '', systemPrompt: '',
autoStartSession: false, autoStartSession: false,
trainingPrompts: [], trainingPrompts: [],
isDirty: false, isDirty: false
} }
const excludeFromProfile = { const excludeFromProfile = {
messages: true, messages: true,
user: true, user: true,
isDirty: true, isDirty: true
} }
const profileSetting: ChatSetting & SettingSelect = { const profileSetting: ChatSetting & SettingSelect = {
@ -103,7 +103,7 @@ const profileSetting: ChatSetting & SettingSelect = {
applyProfile(chatId) applyProfile(chatId)
return true // Signal we should refresh the setting modal return true // Signal we should refresh the setting modal
}, },
setDefault: (chatId, setting, value) => {}, setDefault: (chatId, setting, value) => {}
} }
// Settings that will not be part of the API request // Settings that will not be part of the API request
@ -229,7 +229,7 @@ const modelSetting: ChatSetting & SettingSelect = {
options: [], options: [],
type: 'select', type: 'select',
forceApi: true, // Need to make sure we send this forceApi: true, // Need to make sure we send this
afterChange: (chatId, setting) => true, // refresh settings afterChange: (chatId, setting) => true // refresh settings
} }
const chatSettingsList: ChatSetting[] = [ const chatSettingsList: ChatSetting[] = [
@ -276,7 +276,7 @@ const chatSettingsList: ChatSetting[] = [
max: 32768, max: 32768,
step: 1, step: 1,
type: 'number', type: 'number',
forceApi: true, // Since default here is different than gpt default, will make sure we always send it forceApi: true // Since default here is different than gpt default, will make sure we always send it
}, },
{ {
key: 'presence_penalty', key: 'presence_penalty',