Track change state of profile editor
This commit is contained in:
parent
f8fc158861
commit
dfe30b12bd
|
@ -1,25 +1,54 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getProfile } from './Profiles.svelte'
|
||||
import { setChatSettingValue } from './Storage.svelte'
|
||||
import type { Chat, ChatSetting, ChatSettings } from './Types.svelte'
|
||||
import { cleanSettingValue, setChatSettingValue } from './Storage.svelte'
|
||||
import type { Chat, ChatSetting, ChatSettings, SettingPrompt } from './Types.svelte'
|
||||
import { autoGrowInputOnEvent } from './Util.svelte'
|
||||
|
||||
export let setting:ChatSetting
|
||||
export let chatSettings:ChatSettings
|
||||
export let chat:Chat
|
||||
export let chatDefaults:Record<string, any>
|
||||
export let defaultProfile:String
|
||||
export let originalProfile:String
|
||||
|
||||
const chatId = chat.id
|
||||
|
||||
if (originalProfile) {
|
||||
// eventually...
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const refreshSettings = () => {
|
||||
dispatch('refresh')
|
||||
}
|
||||
|
||||
let debounce:any
|
||||
const settingChecks:Record<string, SettingPrompt[]> = {
|
||||
'profile': [
|
||||
{
|
||||
prompt:'Unsaved changes to the current profile will be lost.\n Continue?',
|
||||
fn: (setting, newVal, oldVal) => {
|
||||
return !!chatSettings.isDirty && newVal !== oldVal
|
||||
},
|
||||
passed: false,
|
||||
},
|
||||
{
|
||||
prompt:'Personality change will not correctly apply to existing chat session.\n Continue?',
|
||||
fn: (setting, newVal, oldVal) => {
|
||||
return chat.sessionStarted && newVal !== originalProfile &&
|
||||
(getProfile(newVal).characterName !== chatSettings.characterName)
|
||||
},
|
||||
passed: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const resetSettingCheck = (key:keyof ChatSettings) => {
|
||||
const checks = settingChecks[key]
|
||||
checks && checks.forEach((c)=>{c.passed=false})
|
||||
}
|
||||
|
||||
let debounce: any
|
||||
|
||||
const queueSettingValueChange = (event: Event, setting: ChatSetting) => {
|
||||
clearTimeout(debounce)
|
||||
|
@ -44,26 +73,42 @@
|
|||
const newVal = chatSettings[setting.key]
|
||||
if (val === newVal) return
|
||||
try {
|
||||
(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)
|
||||
refreshSettings()
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
setChatSettingValue(chatId, setting, val)
|
||||
window.alert('Unable to change:\n' + e.message)
|
||||
}
|
||||
dispatch('change', setting)
|
||||
}
|
||||
if (setting.key === 'profile' && chat.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()
|
||||
const checks = settingChecks[setting.key] || []
|
||||
const newVal = cleanSettingValue(setting.type, el.checked||el.value)
|
||||
for (let i = 0, l = checks.length; i < l; i++) {
|
||||
let c = checks[i]
|
||||
if(c.passed) continue
|
||||
if (c.fn(setting, newVal, val)) {
|
||||
// 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
|
||||
// state to deal with inevitable issues once a non-blocking modal is used.
|
||||
if (window.confirm(c.prompt)) {
|
||||
c.passed = true
|
||||
} else {
|
||||
// roll-back
|
||||
setChatSettingValue(chatId, setting, val)
|
||||
// refresh setting modal, if open
|
||||
refreshSettings()
|
||||
resetSettingCheck(setting.key)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.passed = true
|
||||
}
|
||||
}
|
||||
// passed all
|
||||
resetSettingCheck(setting.key)
|
||||
debounce = setTimeout(doSet, 250)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { applyProfile, getDefaultProfileKey, getProfile, getProfileSelect } from './Profiles.svelte'
|
||||
import { getChatDefaults, getChatSettingList, getChatSettingObjectByKey } from './Settings.svelte'
|
||||
import { getChatDefaults, getChatSettingList, getChatSettingObjectByKey, getExcludeFromProfile } from './Settings.svelte'
|
||||
import {
|
||||
saveChatStore,
|
||||
apiKeyStorage,
|
||||
|
@ -44,11 +44,14 @@
|
|||
const settingsList = getChatSettingList()
|
||||
const modelSetting = getChatSettingObjectByKey('model') as ChatSetting & SettingSelect
|
||||
const chatDefaults = getChatDefaults()
|
||||
const excludeFromProfile = getExcludeFromProfile()
|
||||
|
||||
$: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat
|
||||
$: chatSettings = chat.settings
|
||||
$: globalStore = $globalStorage
|
||||
|
||||
let originalProfile = chatSettings && chatSettings.profile
|
||||
|
||||
afterUpdate(() => {
|
||||
sizeTextElements()
|
||||
})
|
||||
|
@ -66,6 +69,7 @@
|
|||
|
||||
const refreshSettings = async () => {
|
||||
showSettingsModal && showSettings()
|
||||
setDirty()
|
||||
}
|
||||
|
||||
const cloneProfile = () => {
|
||||
|
@ -197,26 +201,33 @@
|
|||
return cname
|
||||
}
|
||||
|
||||
const setDirty = (e:CustomEvent) => {
|
||||
// excludeFromProfile
|
||||
|
||||
const deepEqual = (x:any, y:any) => {
|
||||
const ok = Object.keys, tx = typeof x, ty = typeof y
|
||||
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)))
|
||||
}
|
||||
|
||||
const setDirty = (e:CustomEvent|undefined = undefined) => {
|
||||
if (e) {
|
||||
const setting = e.detail as ChatSetting
|
||||
const key = setting.key
|
||||
if (key === 'profile') return
|
||||
}
|
||||
const profile = getProfile(chatSettings.profile)
|
||||
const isDirty = profile[key] !== chatSettings[key]
|
||||
console.log('Is dirty?', setting, isDirty, profile[key], chatSettings[key])
|
||||
chatSettings.isDirty = isDirty
|
||||
chatSettings.isDirty = !deepEqual(profile, chatSettings)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="modal" class:is-active={showSettingsModal}>
|
||||
<div class="modal-background" on:click={closeSettings} />
|
||||
<div class="modal-card" on:click={() => { showProfileMenu = false }}>
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Chat Settings</p>
|
||||
|
||||
<div class="dropdown is-right" class:is-active={showProfileMenu}>
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3" on:click|preventDefault|stopPropagation={() => { showProfileMenu = !showProfileMenu }}>
|
||||
|
@ -256,7 +267,7 @@
|
|||
<section class="modal-card-body">
|
||||
{#key showSettingsModal}
|
||||
{#each settingsList as setting}
|
||||
<ChatSettingField on:refresh={refreshSettings} on:change={setDirty} chat={chat} chatDefaults={chatDefaults} chatSettings={chatSettings} setting={setting} defaultProfile={defaultProfile} />
|
||||
<ChatSettingField on:refresh={refreshSettings} on:change={setDirty} chat={chat} chatDefaults={chatDefaults} chatSettings={chatSettings} setting={setting} originalProfile={originalProfile} />
|
||||
{/each}
|
||||
{/key}
|
||||
</section>
|
||||
|
|
|
@ -100,12 +100,10 @@ const profileSetting: ChatSetting & SettingSelect = {
|
|||
options: [], // Set by Profiles
|
||||
type: 'select',
|
||||
afterChange: (chatId, setting) => {
|
||||
applyProfile(chatId, '', !getChat(chatId).sessionStarted)
|
||||
applyProfile(chatId)
|
||||
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
|
||||
|
@ -230,7 +228,8 @@ const modelSetting: ChatSetting & SettingSelect = {
|
|||
headerClass: 'is-warning',
|
||||
options: [],
|
||||
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
|
||||
}
|
||||
|
||||
const chatSettingsList: ChatSetting[] = [
|
||||
|
@ -275,10 +274,9 @@ const chatSettingsList: ChatSetting[] = [
|
|||
'The token count of your prompt plus max_tokens cannot exceed the model\'s context length. Most models have a context length of 2048 tokens (except for the newest models, which support 4096).\n',
|
||||
min: 1,
|
||||
max: 32768,
|
||||
step: 128,
|
||||
step: 1,
|
||||
type: 'number',
|
||||
forceApi: true, // Since default here is different than gpt default, will make sure we always send it
|
||||
afterChange: (chatId, setting) => true, // refresh settings
|
||||
},
|
||||
{
|
||||
key: 'presence_penalty',
|
||||
|
|
|
@ -170,4 +170,10 @@ type SettingBoolean = {
|
|||
headerClass?: string;
|
||||
} & (SettingNumber | SettingSelect | SettingBoolean | SettingText | SettingOther);
|
||||
|
||||
export type SettingPrompt = {
|
||||
prompt: string;
|
||||
fn: (setting:ChatSetting, newVal:any, oldVal:any)=>boolean;
|
||||
passed: boolean;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
|
|
@ -7,12 +7,15 @@
|
|||
export const autoGrowInputOnEvent = (event: Event) => {
|
||||
// Resize the textarea to fit the content - auto is important to reset the height after deleting content
|
||||
if (event.target === null) return
|
||||
(event.target as any).__didAutoGrow = false
|
||||
autoGrowInput(event.target as HTMLTextAreaElement)
|
||||
}
|
||||
|
||||
export const autoGrowInput = (el: HTMLTextAreaElement) => {
|
||||
el.style.height = '38px' // don't use "auto" here. Firefox will over-size.
|
||||
const anyEl = el as any // Oh how I hate typescript. All the markup of Java with no real payoff..
|
||||
if (!anyEl.__didAutoGrow) el.style.height = '38px' // don't use "auto" here. Firefox will over-size.
|
||||
el.style.height = el.scrollHeight + 'px'
|
||||
anyEl.__didAutoGrow = true // don't resize this one again unless it's via an event
|
||||
}
|
||||
|
||||
export const scrollIntoViewWithOffset = (element:HTMLElement, offset:number) => {
|
||||
|
|
Loading…
Reference in New Issue