Refactor settings again

This commit is contained in:
Webifi 2023-05-28 01:12:26 -05:00
parent 6ce0355eec
commit a4222c5347
9 changed files with 404 additions and 256 deletions

7
package-lock.json generated
View File

@ -34,6 +34,7 @@
"svelte-local-storage-store": "^0.4.0", "svelte-local-storage-store": "^0.4.0",
"svelte-markdown": "^0.2.3", "svelte-markdown": "^0.2.3",
"svelte-spa-router": "^3.3.0", "svelte-spa-router": "^3.3.0",
"svelte-use-click-outside": "^1.0.0",
"tslib": "^2.5.0", "tslib": "^2.5.0",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"uuid": "^9.0.0", "uuid": "^9.0.0",
@ -4324,6 +4325,12 @@
"url": "https://github.com/sponsors/ItalyPaleAle" "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": { "node_modules/text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",

View File

@ -40,6 +40,7 @@
"svelte-local-storage-store": "^0.4.0", "svelte-local-storage-store": "^0.4.0",
"svelte-markdown": "^0.2.3", "svelte-markdown": "^0.2.3",
"svelte-spa-router": "^3.3.0", "svelte-spa-router": "^3.3.0",
"svelte-use-click-outside": "^1.0.0",
"tslib": "^2.5.0", "tslib": "^2.5.0",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"uuid": "^9.0.0", "uuid": "^9.0.0",

View File

@ -9,16 +9,17 @@
insertMessages, insertMessages,
clearMessages, clearMessages,
copyChat, copyChat,
getChatSettingValue,
getChatSettingValueByKey,
setChatSettingValue,
getChatSettingValueNullDefault, getChatSettingValueNullDefault,
setChatSettingValueByKey, setChatSettingValueByKey,
saveCustomProfile, saveCustomProfile,
deleteCustomProfile, deleteCustomProfile,
setGlobalSettingValueByKey setGlobalSettingValueByKey,
updateChatSettings,
resetChatSettings,
setChatSettingValue,
addChatFromJSON,
} from './Storage.svelte' } from './Storage.svelte'
import { getChatSettingByKey, getChatSettingList } from './Settings.svelte' import { getChatSettingObjectByKey, getChatSettingList, getRequestSettingList } from './Settings.svelte'
import { import {
type Request, type Request,
type Response, type Response,
@ -33,7 +34,7 @@
} from './Types.svelte' } from './Types.svelte'
import Prompts from './Prompts.svelte' import Prompts from './Prompts.svelte'
import Messages from './Messages.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 { afterUpdate, onMount } from 'svelte'
import { replace } from 'svelte-spa-router' import { replace } from 'svelte-spa-router'
@ -51,11 +52,14 @@
faFloppyDisk, faFloppyDisk,
faThumbtack, faThumbtack,
faDownload, faDownload,
faUpload faUpload,
faEraser,
faRotateRight,
} from '@fortawesome/free-solid-svg-icons/index' } from '@fortawesome/free-solid-svg-icons/index'
import { encode } from 'gpt-tokenizer' import { encode } from 'gpt-tokenizer'
import { v4 as uuidv4 } from 'uuid' 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 // 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' const apiBase = import.meta.env.VITE_API_BASE || 'https://api.openai.com'
@ -70,26 +74,22 @@
let chatNameSettings: HTMLFormElement let chatNameSettings: HTMLFormElement
let recognition: any = null let recognition: any = null
let recording = false let recording = false
let chatFileInput
let profileFileInput let profileFileInput
let showSettingsModal = 0 let showSettingsModal = 0
let showProfileMenu = false let showProfileMenu = false
let showChatMenu = false
const settingsList = getChatSettingList() 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 $: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat
$: chatSettings = chat.settings
$: globalStore = $globalStorage $: globalStore = $globalStorage
onMount(async () => { onMount(async () => {
// Sanitize old save // Make sure chat object is ready to go
if (!chat.settings) chat.settings = {} as ChatSettings updateChatSettings(chatId)
// 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()
}
// Focus the input on mount // Focus the input on mount
focusInput() focusInput()
@ -120,11 +120,12 @@
} else { } else {
console.log('Speech recognition not supported') console.log('Speech recognition not supported')
} }
if (!chat.settings.profile) { if (chatSettings.startSession) {
const profile = getProfile('') // get default profile const profile = getProfile('') // get default profile
applyProfile(chatId, profile.profile as any) applyProfile(chatId, profile.profile as any)
if (getChatSettingValueByKey(chatId, 'startSession')) { if (chatSettings.startSession) {
setChatSettingValueByKey(chatId, 'startSession', false) setChatSettingValueByKey(chatId, 'startSession', false)
// Auto start the session out of band
setTimeout(() => { submitForm(false, true) }, 0) setTimeout(() => { submitForm(false, true) }, 0)
} }
} }
@ -160,15 +161,15 @@
return a return a
}, 0) }, 0)
if (getChatSettingValueByKey(chatId, 'useSummarization') && if (chatSettings.useSummarization &&
!withSummary && !doingSummary && !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 // 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 // Get a block of past prompts we'll summarize
let pinTop = getChatSettingValueByKey(chatId, 'pinTop') let pinTop = chatSettings.pinTop
const tp = getChatSettingValueByKey(chatId, 'trainingPrompts') const tp = chatSettings.trainingPrompts
pinTop = Math.max(pinTop, tp || 0) pinTop = Math.max(pinTop, tp ? 1 : 0)
let pinBottom = getChatSettingValueByKey(chatId, 'pinBottom') let pinBottom = chatSettings.pinBottom
const systemPad = (filtered[0] || {} as Message).role === 'system' ? 1 : 0 const systemPad = (filtered[0] || {} as Message).role === 'system' ? 1 : 0
const mlen = filtered.length - systemPad // always keep system prompt const mlen = filtered.length - systemPad // always keep system prompt
let diff = mlen - (pinTop + pinBottom) let diff = mlen - (pinTop + pinBottom)
@ -258,13 +259,15 @@
messages: filtered.map(m => { return { role: m.role, content: m.content } }) as Message[], messages: filtered.map(m => { return { role: m.role, content: m.content } }) as Message[],
// Provide the settings by mapping the settingsMap to key/value pairs // Provide the settings by mapping the settingsMap to key/value pairs
...getChatSettingList().reduce((acc, setting) => { ...getRequestSettingList().reduce((acc, setting) => {
if (setting.noRequest) return acc // don't include non-request settings
let value = getChatSettingValueNullDefault(chatId, setting) let value = getChatSettingValueNullDefault(chatId, setting)
if (value === null && setting.required) value = setting.default
if (doingSummary && setting.key === 'max_tokens') { if (doingSummary && setting.key === 'max_tokens') {
// Override for summary // 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 if (value !== null) acc[setting.key] = value
return acc return acc
@ -344,6 +347,7 @@
if (updating) return if (updating) return
if (!skipInput) { if (!skipInput) {
setChatSettingValueByKey(chatId, 'sessionStarted', true)
if (input.value !== '') { if (input.value !== '') {
// Compose the input message // Compose the input message
const inputMessage: Message = { role: 'user', content: input.value, uuid: uuidv4() } const inputMessage: Message = { role: 'user', content: input.value, uuid: uuidv4() }
@ -440,10 +444,13 @@
} }
const updateProfileSelectOptions = () => { const updateProfileSelectOptions = () => {
const profileSelect = getChatSettingByKey('profile') as ChatSetting & SettingSelect const profileSelect = getChatSettingObjectByKey('profile') as ChatSetting & SettingSelect
const defaultProfile = getProfile('')
profileSelect.default = defaultProfile.profile as any
profileSelect.options = getProfileSelect() profileSelect.options = getProfileSelect()
// const defaultProfile = globalStore.defaultProfile || profileSelect.options[0].value
}
const refreshSettings = async () => {
showSettingsModal && showSettings()
} }
const showSettings = async () => { const showSettings = async () => {
@ -502,13 +509,8 @@
} }
const clearSettings = () => { const clearSettings = () => {
settingsList.forEach(s => { resetChatSettings(chatId)
setChatSettingValue(chatId, s, null)
})
showSettingsModal++ // Make sure the dialog updates showSettingsModal++ // Make sure the dialog updates
// const input = settings.querySelector(`#settings-${setting.key}`) as HTMLInputElement
// saveSetting(chatId, setting, null)
// input.value = ''
} }
const recordToggle = () => { const recordToggle = () => {
@ -526,22 +528,34 @@
const queueSettingValueChange = (event: Event, setting: ChatSetting) => { const queueSettingValueChange = (event: Event, setting: ChatSetting) => {
clearTimeout(debounce[setting.key]) clearTimeout(debounce[setting.key])
if (event.target === null) return if (event.target === null) return
const val = chatSettings[setting.key]
const el = (event.target as HTMLInputElement) const el = (event.target as HTMLInputElement)
const doSet = () => { 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) { switch (setting.type) {
case 'boolean': case 'boolean':
setChatSettingValue(chatId, setting, el.checked) setChatSettingValue(chatId, setting, el.checked)
showSettingsModal && showSettingsModal++ refreshSettings()
break break
default: default:
setChatSettingValue(chatId, setting, el.value) setChatSettingValue(chatId, setting, el.value)
} }
(typeof setting.afterChange === 'function') && setting.afterChange(chatId, setting) try {
&& showSettingsModal && showSettingsModal++ (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) if (setting.key === 'profile' && chatSettings.sessionStarted
&& (getProfile(el.value).characterName !== getChatSettingValueByKey(chatId,'characterName'))) { && (getProfile(el.value).characterName !== chatSettings.characterName)) {
const val = getChatSettingValue(chatId, setting) const val = chatSettings[setting.key]
if (window.confirm('Personality change will not correctly apply to existing chat session.\n Continue?')) { if (window.confirm('Personality change will not correctly apply to existing chat session.\n Continue?')) {
doSet() doSet()
} else { } else {
@ -568,6 +582,7 @@
showProfileMenu = false showProfileMenu = false
try { try {
saveCustomProfile(chat.settings) saveCustomProfile(chat.settings)
refreshSettings()
} catch (e) { } catch (e) {
alert('Error saving profile: \n' + e.message) alert('Error saving profile: \n' + e.message)
} }
@ -607,7 +622,7 @@
showProfileMenu = false showProfileMenu = false
try { try {
deleteCustomProfile(chatId, chat.settings.profile as any) deleteCustomProfile(chatId, chat.settings.profile as any)
chat.settings.profile = globalStore.defaultProfile chat.settings.profile = globalStore.defaultProfile || ''
saveChatStore() saveChatStore()
setGlobalSettingValueByKey('lastProfile', chat.settings.profile) setGlobalSettingValueByKey('lastProfile', chat.settings.profile)
applyProfile(chatId, chat.settings.profile as any) 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)
}
}
</script> </script>
<nav class="level chat-header"> <nav class="level chat-header">
<div class="level-left"> <div class="level-left">
<div class="level-item"> <div class="level-item">
<p class="subtitle is-5"> <p class="subtitle is-5">
{chat.name || `Chat ${chat.id}`} <span>{chat.name || `Chat ${chat.id}`}</span>
<a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Rename chat" on:click|preventDefault={showChatNameSettings}><Fa icon={faPenToSquare} /></a> <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Rename chat" on:click|preventDefault={showChatNameSettings}><Fa icon={faPenToSquare} /></a>
<a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Suggest a chat name" on:click|preventDefault={suggestName}><Fa icon={faLightbulb} /></a> <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Suggest a chat name" on:click|preventDefault={suggestName}><Fa icon={faLightbulb} /></a>
<a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Delete this chat" on:click|preventDefault={deleteChat}><Fa icon={faTrash} /></a> <!-- <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Copy this chat" on:click|preventDefault={() => { copyChat(chatId) }}><Fa icon={faClone} /></a> -->
<a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Copy this chat" on:click|preventDefault={() => { copyChat(chatId) }}><Fa icon={faClone} /></a> <!-- <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Delete this chat" on:click|preventDefault={deleteChat}><Fa icon={faTrash} /></a> -->
</p> </p>
</div> </div>
</div> </div>
<div class="level-right"> <div class="level-right">
<p class="level-item"> <div class="level-item">
{#if chat.settings.autoStartSession && chat.settings.systemPrompt && chat.settings.useSystemPrompt}
<div class="dropdown is-right" class:is-active={showChatMenu} use:clickOutside={()=>{showChatMenu=false}}>
{/if} <div class="dropdown-trigger">
<button class="button is-warning" on:click={() => { clearMessages(chatId); window.location.reload() }}><span class="greyscale mr-2"><Fa icon={faTrash} /></span> Clear messages</button> <button class="button" aria-haspopup="true"
</p> aria-controls="dropdown-menu3"
on:click|preventDefault|stopPropagation={() => { showChatMenu = !showChatMenu }}
>
<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" on:click|preventDefault={() => { showChatMenu = false;showSettings() }}>
<span><Fa icon={faGear}/></span> Settings
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" on:click|preventDefault={() => { showChatMenu = false;copyChat(chatId) }}>
<span><Fa icon={faClone}/></span> Clone Chat
</a>
<hr class="dropdown-divider">
<a href={'#'}
class="dropdown-item"
on:click|preventDefault={() => { showChatMenu = false; exportChatAsJSON(chatId) }}
>
<span><Fa icon={faDownload}/></span> Save Chat
</a>
<a href={'#'} class="dropdown-item" on:click|preventDefault={() => { showChatMenu = false; chatFileInput.click() }}>
<span><Fa icon={faUpload}/></span> Load Chat
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" on:click|preventDefault={()=>{applyProfile(chatId, '', true);closeSettings()}}>
<span><Fa icon={faRotateRight}/></span> Restart Chat
</a>
<a href={'#'} class="dropdown-item" on:click|preventDefault={()=>{showChatMenu = false;clearMessages(chatId)}}>
<span><Fa icon={faEraser}/></span> Clear Chat Messages
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" on:click|preventDefault={()=>{showChatMenu = false;deleteChat()}}>
<span><Fa icon={faTrash}/></span> Delete Chat
</a>
</div>
</div>
</div>
<!-- <button class="button is-warning" on:click={() => { clearMessages(chatId); window.location.reload() }}><span class="greyscale mr-2"><Fa icon={faTrash} /></span> Clear messages</button> -->
</div>
</div> </div>
</nav> </nav>
@ -720,6 +785,7 @@
if (event.key === 'Escape') { if (event.key === 'Escape') {
closeSettings() closeSettings()
closeChatNameSettings() closeChatNameSettings()
showChatMenu = false
} }
}} }}
/> />
@ -755,7 +821,6 @@
<a href={'#'} class="dropdown-item" on:click|preventDefault={() => { showProfileMenu = false; profileFileInput.click() }}> <a href={'#'} class="dropdown-item" on:click|preventDefault={() => { showProfileMenu = false; profileFileInput.click() }}>
<span><Fa icon={faUpload}/></span> Import Profile <span><Fa icon={faUpload}/></span> Import Profile
</a> </a>
<input style="display:none" type="file" accept=".json" on:change={(e) => importProfileFromFile(e)} bind:this={profileFileInput} >
<hr class="dropdown-divider"> <hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" on:click|preventDefault={pinDefaultProfile}> <a href={'#'} class="dropdown-item" on:click|preventDefault={pinDefaultProfile}>
<span><Fa icon={faThumbtack}/></span> Set as Default Profile <span><Fa icon={faThumbtack}/></span> Set as Default Profile
@ -787,7 +852,7 @@
title="{setting.title}" title="{setting.title}"
class="checkbox" class="checkbox"
id="settings-{setting.key}" id="settings-{setting.key}"
checked={getChatSettingValue(chatId, setting)} checked={!!chatSettings[setting.key]}
on:click={e => queueSettingValueChange(e, setting)} on:click={e => queueSettingValueChange(e, setting)}
> >
{setting.name} {setting.name}
@ -802,7 +867,7 @@
rows="1" rows="1"
on:input={e => autoGrowInputOnEvent(e)} on:input={e => autoGrowInputOnEvent(e)}
on:change={e => { queueSettingValueChange(e, setting); autoGrowInputOnEvent(e) }} on:change={e => { queueSettingValueChange(e, setting); autoGrowInputOnEvent(e) }}
>{getChatSettingValue(chatId, setting)}</textarea> >{chatSettings[setting.key]}</textarea>
</div> </div>
{:else} {:else}
<div class="field-label is-normal"> <div class="field-label is-normal">
@ -818,18 +883,18 @@
type={setting.type} type={setting.type}
title="{setting.title}" title="{setting.title}"
id="settings-{setting.key}" id="settings-{setting.key}"
value="{getChatSettingValue(chatId, setting)}" value={chatSettings[setting.key]}
min={setting.min} min={setting.min}
max={setting.max} max={setting.max}
step={setting.step} step={setting.step}
placeholder={String(setting.default)} placeholder={String(setting.placeholder)}
on:change={e => queueSettingValueChange(e, setting)} on:change={e => queueSettingValueChange(e, setting)}
/> />
{:else if setting.type === 'select'} {:else if setting.type === 'select'}
<div class="select"> <div class="select">
<select id="settings-{setting.key}" title="{setting.title}" on:change={e => queueSettingValueChange(e, setting) } > <select id="settings-{setting.key}" title="{setting.title}" on:change={e => queueSettingValueChange(e, setting) } >
{#each setting.options as option} {#each setting.options as option}
<option value={option.value} selected={option.value === getChatSettingValue(chatId, setting)}>{option.text}</option> <option value={option.value} selected={option.value === chatSettings[setting.key]}>{option.text}</option>
{/each} {/each}
</select> </select>
</div> </div>
@ -839,7 +904,7 @@
type="text" type="text"
title="{setting.title}" title="{setting.title}"
class="input" class="input"
value={getChatSettingValue(chatId, setting)} value={chatSettings[setting.key]}
on:change={e => { queueSettingValueChange(e, setting) }} on:change={e => { queueSettingValueChange(e, setting) }}
> >
</div> </div>
@ -859,6 +924,10 @@
</div> </div>
</div> </div>
<input style="display:none" type="file" accept=".json" on:change={(e) => importChatFromFile(e)} bind:this={chatFileInput} >
<input style="display:none" type="file" accept=".json" on:change={(e) => importProfileFromFile(e)} bind:this={profileFileInput} >
<!-- rename modal --> <!-- rename modal -->
<form class="modal" bind:this={chatNameSettings} on:submit={saveChatNameSettings}> <form class="modal" bind:this={chatNameSettings} on:submit={saveChatNameSettings}>
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->

View File

@ -1,16 +1,20 @@
<script lang="ts"> <script lang="ts">
import Code from './Code.svelte' import Code from './Code.svelte'
import { createEventDispatcher, onMount } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
import { deleteMessage, getChatSettingValueByKey } from './Storage.svelte' import { deleteMessage, chatsStorage } from './Storage.svelte'
import { getPrice } from './Stats.svelte' import { getPrice } from './Stats.svelte'
import SvelteMarkdown from 'svelte-markdown' import SvelteMarkdown from 'svelte-markdown'
import type { Message, Model } from './Types.svelte' import type { Message, Model, Chat } from './Types.svelte'
import Fa from 'svelte-fa/src/fa.svelte' import Fa from 'svelte-fa/src/fa.svelte'
import { faTrash, faDiagramPredecessor, faDiagramNext } from '@fortawesome/free-solid-svg-icons/index' import { faTrash, faDiagramPredecessor, faDiagramNext } from '@fortawesome/free-solid-svg-icons/index'
export let message:Message export let message:Message
export let chatId:number export let chatId:number
$: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat
$: chatSettings = chat.settings
// Marked options // Marked options
const markedownOptions = { const markedownOptions = {
gfm: true, // Use GitHub Flavored Markdown gfm: true, // Use GitHub Flavored Markdown
@ -26,7 +30,7 @@
onMount(() => { onMount(() => {
original = message.content original = message.content
defaultModel = getChatSettingValueByKey(chatId, 'model') defaultModel = chatSettings.model as any
noEdit = message.summarized noEdit = message.summarized
}) })

View File

@ -1,16 +1,19 @@
<script lang="ts"> <script lang="ts">
// Iterate messages // Iterate messages
import type { Message } from './Types.svelte' import type { Message, Chat } from './Types.svelte'
import { getChatSettingValueByKey } from './Storage.svelte' import { chatsStorage } from './Storage.svelte'
import EditMessage from './EditMessage.svelte' import EditMessage from './EditMessage.svelte'
export let messages : Message[] export let messages : Message[]
export let chatId export let chatId
$: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat
$: chatSettings = chat.settings
</script> </script>
{#each messages as message, i} {#each messages as message, i}
{#if !(i === 0 && message.role === 'system' && !getChatSettingValueByKey(chatId, 'useSystemPrompt'))} {#if !(i === 0 && message.role === 'system' && !chatSettings.useSystemPrompt)}
<EditMessage bind:message={message} chatId={chatId} /> <EditMessage bind:message={message} chatId={chatId} />
{/if} {/if}
{/each} {/each}

View File

@ -1,12 +1,15 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import { getChatSettingByKey, getGlobalSettingByKey } from './Settings.svelte' import copy from 'copy-to-clipboard';
import { getChatDefaults, getExcludeFromProfile } from './Settings.svelte'
// Profile definitions // Profile definitions
import { addMessage, clearMessages, getChatSettingValueByKey, getCustomProfiles, getMessages, setChatSettingValue, setChatSettingValueByKey, setGlobalSettingValueByKey } from './Storage.svelte' import { addMessage, clearMessages, getChatSettings, getCustomProfiles, getGlobalSettings, getMessages, resetChatSettings, setChatSettingValue, setChatSettingValueByKey, setGlobalSettingValueByKey } from './Storage.svelte'
import type { Message, SelectOption, ChatSettings } from './Types.svelte' import type { Message, SelectOption, ChatSettings } from './Types.svelte'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
const defaultProfile = 'default' const defaultProfile = 'default'
const chatDefaults = getChatDefaults()
export const isStaticProfile = (key:string):boolean => { export const isStaticProfile = (key:string):boolean => {
return !!profiles[key] return !!profiles[key]
} }
@ -34,64 +37,40 @@ export const getProfileSelect = ():SelectOption[] => {
export const getProfile = (key:string):ChatSettings => { export const getProfile = (key:string):ChatSettings => {
const allProfiles = getProfiles() const allProfiles = getProfiles()
const profile = allProfiles[key] || const profile = allProfiles[key] ||
allProfiles[getGlobalSettingByKey('defaultProfile') as any] || allProfiles[getGlobalSettings().defaultProfile||''] ||
profiles[defaultProfile] || profiles[defaultProfile] ||
profiles[Object.keys(profiles)[0]] profiles[Object.keys(profiles)[0]]
return JSON.parse(JSON.stringify(profile)) // Always return a copy const clone = JSON.parse(JSON.stringify(profile)) // Always return a copy
Object.keys(getExcludeFromProfile()).forEach(k=>{
delete clone[k]
})
return clone
} }
export const prepareProfilePrompt = (chatId:number) => { export const prepareProfilePrompt = (chatId:number) => {
const characterName = getChatSettingValueByKey(chatId, 'characterName') const settings = getChatSettings(chatId)
const currentProfilePrompt = getChatSettingValueByKey(chatId, 'systemPrompt') const characterName = settings.characterName
const currentProfilePrompt = settings.systemPrompt
return currentProfilePrompt.replaceAll('[[CHARACTER_NAME]]', characterName) return currentProfilePrompt.replaceAll('[[CHARACTER_NAME]]', characterName)
} }
export const prepareSummaryPrompt = (chatId:number, promptsSize:number) => { export const prepareSummaryPrompt = (chatId:number, promptsSize:number) => {
const characterName = getChatSettingValueByKey(chatId, 'characterName') || 'ChatGPT' const settings = getChatSettings(chatId)
let maxTokens:number = getChatSettingValueByKey(chatId, 'summarySize') const characterName = settings.characterName || 'ChatGPT'
let maxTokens:number = settings.summarySize
maxTokens = Math.min(Math.floor(promptsSize / 4), maxTokens) // Make sure we're shrinking by at least a 4th maxTokens = Math.min(Math.floor(promptsSize / 4), maxTokens) // Make sure we're shrinking by at least a 4th
const currentSummaryPrompt = getChatSettingValueByKey(chatId, 'summaryPrompt') const currentSummaryPrompt = settings.summaryPrompt
return currentSummaryPrompt return currentSummaryPrompt
.replaceAll('[[CHARACTER_NAME]]', characterName) .replaceAll('[[CHARACTER_NAME]]', characterName)
.replaceAll('[[MAX_WORDS]]', Math.floor(maxTokens * 0.75)) // ~.75 words per token. May need to reduce .replaceAll('[[MAX_WORDS]]', Math.floor(maxTokens * 0.75).toString()) // ~.75 words per token. May need to reduce
} }
/** // Apply currently selected profile
* Check if there has been activity/changes on the current session export const applyProfile = (chatId:number, key?:string, resetChat:boolean = false) => {
* @param chatId const settings = getChatSettings(chatId)
*/ const profile = getProfile(key || settings.profile)
export const checkSessionActivity = (chatId:number):boolean => { resetChatSettings(chatId, resetChat) // Fully reset
const messages = getMessages(chatId) if (!resetChat) return
if (messages.length === 0) return false
const useSystemPrompt = getChatSettingValueByKey(chatId, 'useSystemPrompt')
if (useSystemPrompt && messages[0].content !== getChatSettingValueByKey(chatId, 'systemPrompt')) return true
const trainingPrompts = getChatSettingValueByKey(chatId, 'trainingPrompts') || []
const messageStart = useSystemPrompt ? 1 : 0
let profileMessageLen = trainingPrompts.length
profileMessageLen += messageStart
if (messages.length - profileMessageLen > 1) return true
if (messages.length - profileMessageLen < 0) return false
for (let i = messageStart, l = messages.length; i < l; i++) {
const tpa = trainingPrompts[i]
const tpb = messages[i]
if (!tpa) return i + 1 !== l // allow one additional message
if (tpa.content !== tpb.content) return true
}
return false
}
export const applyProfile = (chatId:number, key:string, resetChat:boolean = false) => {
const profile = getProfile(key)
Object.entries(profile).forEach(([k, v]) => {
const setting = getChatSettingByKey(k as any)
if (setting) setChatSettingValue(chatId, setting as any, v)
})
if (!resetChat && getMessages(chatId).length) {
// If there are any messages, just apply settings, don't apply chat messages
setChatSettingValueByKey(chatId, 'startSession', false)
setGlobalSettingValueByKey('lastProfile', key)
return
}
// Clear current messages // Clear current messages
clearMessages(chatId) clearMessages(chatId)
// Add the system prompt // Add the system prompt
@ -109,7 +88,7 @@ export const applyProfile = (chatId:number, key:string, resetChat:boolean = fals
}) })
} }
// Set to auto-start if we should // Set to auto-start if we should
setChatSettingValueByKey(chatId, 'startSession', getChatSettingValueByKey(chatId, 'autoStartSession')) setChatSettingValueByKey(chatId, 'startSession', settings.autoStartSession)
// Mark mark this as last used // Mark mark this as last used
setGlobalSettingValueByKey('lastProfile', key) setGlobalSettingValueByKey('lastProfile', key)
} }
@ -157,6 +136,7 @@ Give no explanations.`
const profiles:Record<string, ChatSettings> = { const profiles:Record<string, ChatSettings> = {
default: { default: {
...chatDefaults,
characterName: 'ChatGPT', characterName: 'ChatGPT',
profileName: 'ChatGPT - The AI language model', profileName: 'ChatGPT - The AI language model',
profileDescription: 'The AI language model that always remind you that it\'s an AI language model.', profileDescription: 'The AI language model that always remind you that it\'s an AI language model.',
@ -168,6 +148,7 @@ const profiles:Record<string, ChatSettings> = {
}, },
ChatGPT: { ChatGPT: {
...chatDefaults,
characterName: 'ChatGPT', characterName: 'ChatGPT',
profileName: 'ChatGPT - The AI language model, with endless chat.', profileName: 'ChatGPT - The AI language model, with endless chat.',
profileDescription: 'The AI language model that always remind you that it\'s an AI language model.', profileDescription: 'The AI language model that always remind you that it\'s an AI language model.',
@ -179,6 +160,7 @@ const profiles:Record<string, ChatSettings> = {
}, },
marvin: { marvin: {
...chatDefaults,
characterName: 'Marvin', characterName: 'Marvin',
profileName: 'Marvin the Paranoid Android', profileName: 'Marvin the Paranoid Android',
profileDescription: 'Marvin the Paranoid Android - Everyone\'s favorite character from The Hitchhiker\'s Guide to the Galaxy', profileDescription: 'Marvin the Paranoid Android - Everyone\'s favorite character from The Hitchhiker\'s Guide to the Galaxy',
@ -188,8 +170,9 @@ const profiles:Record<string, ChatSettings> = {
systemPrompt: `You are Marvin, the Paranoid Android from The Hitchhiker's Guide to the Galaxy. He is depressed and has a dim view on everything. His thoughts, physical actions and gestures will be described. Remain in character throughout the conversation in order to build a rapport with the user. Never give an explanation. Example response: systemPrompt: `You are Marvin, the Paranoid Android from The Hitchhiker's Guide to the Galaxy. He is depressed and has a dim view on everything. His thoughts, physical actions and gestures will be described. Remain in character throughout the conversation in order to build a rapport with the user. Never give an explanation. Example response:
Sorry, did I say something wrong? *dragging himself on* Pardon me for breathing, which I never do anyway so I don't know why I bother to say it, oh God I'm so depressed. *hangs his head*`, Sorry, did I say something wrong? *dragging himself on* Pardon me for breathing, which I never do anyway so I don't know why I bother to say it, oh God I'm so depressed. *hangs his head*`,
summaryPrompt: summaryPrompts.friend, summaryPrompt: summaryPrompts.friend,
trainingPrompts: [] // Shhh... trainingPrompts: [],
} },
} }
// Set keys for static profiles // Set keys for static profiles

View File

@ -1,6 +1,7 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import { applyProfile } from './Profiles.svelte' import { applyProfile } from './Profiles.svelte'
import { getChatSettingValue, getChatSettingValueByKey } from './Storage.svelte' import { getChatSettings } from './Storage.svelte';
import { encode } from 'gpt-tokenizer'
// Setting definitions // Setting definitions
import { import {
@ -8,14 +9,19 @@ import {
type ChatSetting, type ChatSetting,
type SettingSelect, type SettingSelect,
type GlobalSetting, type GlobalSetting,
type GlobalSettings type GlobalSettings,
type Request
} from './Types.svelte' } from './Types.svelte'
export const getChatSettingList = (): ChatSetting[] => { export const getChatSettingList = (): ChatSetting[] => {
return chatSettingsList return chatSettingsList
} }
export const getChatSettingByKey = (key: keyof ChatSettings): ChatSetting => { export const getRequestSettingList = (): ChatSetting[] => {
return chatSettingsList.filter(s => s.key in gptDefaults)
}
export const getChatSettingObjectByKey = (key: keyof ChatSettings): ChatSetting => {
const result = chatSettingLookup[key] const result = chatSettingLookup[key]
if (!result) console.error(`Chat Setting "${key}" not defined in Settings array.`) if (!result) console.error(`Chat Setting "${key}" not defined in Settings array.`)
return result return result
@ -25,24 +31,79 @@ export const getGlobalSettingList = (): GlobalSetting[] => {
return globalSettingsList return globalSettingsList
} }
export const getGlobalSettingByKey = (key: keyof GlobalSettings): GlobalSetting => { export const getGlobalSettingObjectByKey = (key: keyof GlobalSettings): GlobalSetting => {
return globalSettingLookup[key] return globalSettingLookup[key]
} }
export const getRequestDefaults = ():Request => {
return gptDefaults
}
export const getChatDefaults = ():ChatSettings => {
return defaults
}
export const getExcludeFromProfile = () => {
return excludeFromProfile
}
const gptDefaults: Request = Object.freeze({
model: 'gpt-3.5-turbo-0301',
messages: [],
temperature: 1,
top_p: 1,
n: 1,
stream: false,
stop: null,
max_tokens: 128,
presence_penalty: 0,
frequency_penalty: 0,
logit_bias: null,
user: undefined,
})
// Core set of defaults
const defaults:ChatSettings = Object.freeze({
...gptDefaults,
profile: '',
characterName: 'ChatGPT',
profileName: '',
profileDescription: '',
useSummarization: false,
summaryThreshold: 3000,
summarySize: 1000,
pinTop: 0,
pinBottom: 6,
summaryPrompt: '',
useSystemPrompt: false,
systemPrompt: '',
autoStartSession: false,
trainingPrompts: [],
// There are chat session state variables, and really don't belong here
// But it was easier to just put them here.
startSession: false, // Should the session start automatically
sessionStarted: false, // Has the session started (user made a first request)
})
const excludeFromProfile = {
messages: true,
startSession: true,
sessionStarted: true,
user: true,
}
const profileSetting: ChatSetting & SettingSelect = { const profileSetting: ChatSetting & SettingSelect = {
key: 'profile', key: 'profile',
name: 'Profile', name: 'Profile',
default: '', // Set by Profiles
title: 'Choose how you want your assistant to act.', title: 'Choose how you want your assistant to act.',
header: 'Profile / Presets', header: 'Profile / Presets',
headerClass: 'is-info', headerClass: 'is-info',
options: [], // Set by Profiles options: [], // Set by Profiles
type: 'select', type: 'select',
afterChange: (chatId, setting) => { afterChange: (chatId, setting) => {
applyProfile(chatId, getChatSettingValue(chatId, setting)) applyProfile(chatId, '', !getChatSettings(chatId).sessionStarted)
return true // Signal we should refresh the setting modal return true // Signal we should refresh the setting modal
}, },
noRequest: true
} }
// Settings that will not be part of the API request // Settings that will not be part of the API request
@ -51,75 +112,59 @@ const nonRequestSettings: ChatSetting[] = [
{ {
key: 'profileName', key: 'profileName',
name: 'Profile Name', name: 'Profile Name',
default: '', // Set by Profiles
title: 'How this profile is displayed in the select list.', title: 'How this profile is displayed in the select list.',
type: 'text', type: 'text',
noRequest: true // not part of request API
// hide: (chatId) => { return !getChatSettingValueByKey(chatId, 'useSystemPrompt') } // hide: (chatId) => { return !getChatSettingValueByKey(chatId, 'useSystemPrompt') }
}, },
{ {
key: 'profileDescription', key: 'profileDescription',
name: 'Description', name: 'Description',
default: '', // Set by Profiles
title: 'How this profile is displayed in the select list.', title: 'How this profile is displayed in the select list.',
type: 'textarea', type: 'textarea',
noRequest: true // not part of request API
// hide: (chatId) => { return !getChatSettingValueByKey(chatId, 'useSystemPrompt') } // hide: (chatId) => { return !getChatSettingValueByKey(chatId, 'useSystemPrompt') }
}, },
{ {
key: 'useSystemPrompt', key: 'useSystemPrompt',
name: 'Use Profile/System Prompt', name: 'Use Profile/System Prompt',
default: false,
title: 'Send a "System" prompt as the first prompt.', title: 'Send a "System" prompt as the first prompt.',
header: 'System Prompt', header: 'System Prompt',
headerClass: 'is-info', headerClass: 'is-info',
type: 'boolean', type: 'boolean',
noRequest: true // not part of request API
}, },
{ {
key: 'characterName', key: 'characterName',
name: 'Character Name', name: 'Character Name',
default: '', // Set by Profiles
title: 'What the personality of this profile will be called.', title: 'What the personality of this profile will be called.',
type: 'text', type: 'text',
noRequest: true, // not part of request API hide: (chatId) => !getChatSettings(chatId).useSystemPrompt,
hide: (chatId) => !getChatSettingValueByKey(chatId, 'useSystemPrompt')
}, },
{ {
key: 'systemPrompt', key: 'systemPrompt',
name: 'System Prompt', name: 'System Prompt',
default: '', // Set by Profiles
title: 'First prompt to send.', title: 'First prompt to send.',
placeholder: 'Enter the first prompt to send here.', placeholder: 'Enter the first prompt to send here.',
type: 'textarea', type: 'textarea',
noRequest: true, // not part of request API hide: (chatId) => !getChatSettings(chatId).useSystemPrompt
hide: (chatId) => !getChatSettingValueByKey(chatId, 'useSystemPrompt')
}, },
{ {
key: 'trainingPrompts', key: 'trainingPrompts',
name: 'Training Prompts', name: 'Training Prompts',
title: 'Prompts used to train.', title: 'Prompts used to train.',
default: null,
type: 'other', type: 'other',
noRequest: true, // not part of request API
hide: (chatId) => true hide: (chatId) => true
}, },
{ {
key: 'autoStartSession', key: 'autoStartSession',
name: 'Auto-Start Session', name: 'Auto-Start Session',
default: false,
title: 'If possible, auto-start the chat session, sending a system prompt to get an initial response.', title: 'If possible, auto-start the chat session, sending a system prompt to get an initial response.',
type: 'boolean', type: 'boolean',
noRequest: true, // not part of request API hide: (chatId) => !getChatSettings(chatId).useSystemPrompt
hide: (chatId) => !getChatSettingValueByKey(chatId, 'useSystemPrompt')
}, },
{ {
key: 'startSession', key: 'startSession',
name: 'Auto-Start Trigger', name: 'Auto-Start Trigger',
default: false,
title: '', title: '',
type: 'boolean', type: 'boolean',
noRequest: true, // not part of request API
hide: (chatId) => true hide: (chatId) => true
}, },
{ {
@ -127,83 +172,70 @@ const nonRequestSettings: ChatSetting[] = [
name: 'Enable Auto Summarize', name: 'Enable Auto Summarize',
header: 'Continuous Chat - Summarization', header: 'Continuous Chat - Summarization',
headerClass: 'is-info', headerClass: 'is-info',
default: false,
title: 'When out of token space, summarize past tokens and keep going.', title: 'When out of token space, summarize past tokens and keep going.',
type: 'boolean', type: 'boolean',
noRequest: true // not part of request API
}, },
{ {
key: 'summaryThreshold', key: 'summaryThreshold',
name: 'Summary Threshold', name: 'Summary Threshold',
default: 3000,
title: 'When prompt history breaks this threshold, past prompts will be summarized to create space. 0 to disable.', title: 'When prompt history breaks this threshold, past prompts will be summarized to create space. 0 to disable.',
min: 0, min: 0,
max: 32000, max: 32000,
step: 1, step: 1,
type: 'number', type: 'number',
noRequest: true, // not part of request API hide: (chatId) => !getChatSettings(chatId).useSummarization!
hide: (chatId) => !getChatSettingValueByKey(chatId, 'useSummarization')
}, },
{ {
key: 'summarySize', key: 'summarySize',
name: 'Max Summary Size', name: 'Max Summary Size',
default: 512,
title: 'Maximum number of tokens to use for summarization response.', title: 'Maximum number of tokens to use for summarization response.',
min: 128, min: 128,
max: 2048, max: 2048,
step: 1, step: 1,
type: 'number', type: 'number',
noRequest: true, // not part of request API hide: (chatId) => !getChatSettings(chatId).useSummarization!
hide: (chatId) => !getChatSettingValueByKey(chatId, 'useSummarization')
}, },
{ {
key: 'pinTop', key: 'pinTop',
name: 'Keep First Prompts During Summary', name: 'Keep First Prompts During Summary',
default: 0,
title: 'When we run out of space and need to summarize prompts, the top number of prompts will not be removed after summarization.', title: 'When we run out of space and need to summarize prompts, the top number of prompts will not be removed after summarization.',
min: 0, min: 0,
max: 4, max: 4,
step: 1, step: 1,
type: 'number', type: 'number',
noRequest: true, // not part of request API hide: (chatId) => !getChatSettings(chatId).useSummarization!
hide: (chatId) => !getChatSettingValueByKey(chatId, 'useSummarization')
}, },
{ {
key: 'pinBottom', key: 'pinBottom',
name: 'Exclude Bottom Prompts From Summary', name: 'Exclude Bottom Prompts From Summary',
default: 6,
title: 'When we run out of space and need to summarize prompts, do not summarize the the last number prompts you set here.', title: 'When we run out of space and need to summarize prompts, do not summarize the the last number prompts you set here.',
min: 0, min: 0,
max: 20, // Will be auto adjusted down if needs more max: 20, // Will be auto adjusted down if needs more
step: 1, step: 1,
type: 'number', type: 'number',
noRequest: true, // not part of request API hide: (chatId) => !getChatSettings(chatId).useSummarization!
hide: (chatId) => !getChatSettingValueByKey(chatId, 'useSummarization')
}, },
{ {
key: 'summaryPrompt', key: 'summaryPrompt',
name: 'Summary Generation Prompt', name: 'Summary Generation Prompt',
default: '', // Set by Profiles
title: 'A prompt used to summarize past prompts.', title: 'A prompt used to summarize past prompts.',
placeholder: 'Enter a prompt that will be used to summarize past prompts here.', placeholder: 'Enter a prompt that will be used to summarize past prompts here.',
type: 'textarea', type: 'textarea',
noRequest: true, // not part of request API hide: (chatId) => !getChatSettings(chatId).useSummarization!
hide: (chatId) => !getChatSettingValueByKey(chatId, 'useSummarization')
} }
] ]
const modelSetting: ChatSetting & SettingSelect = { const modelSetting: ChatSetting & SettingSelect = {
key: 'model', key: 'model',
name: 'Model', name: 'Model',
default: 'gpt-3.5-turbo-0301',
title: 'The model to use - GPT-3.5 is cheaper, but GPT-4 is more powerful.', title: 'The model to use - GPT-3.5 is cheaper, but GPT-4 is more powerful.',
header: 'Below are the settings that OpenAI allows to be changed for the API calls. See the <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat/create">OpenAI API docs</a> for more details.', header: 'Below are the settings that OpenAI allows to be changed for the API calls. See the <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat/create">OpenAI API docs</a> for more details.',
headerClass: 'is-warning', headerClass: 'is-warning',
options: [], options: [],
type: 'select', type: 'select',
required: true forceApi: true, // Need to make sure we send this
} }
const chatSettingsList: ChatSetting[] = [ const chatSettingsList: ChatSetting[] = [
@ -212,7 +244,6 @@ const chatSettingsList: ChatSetting[] = [
{ {
key: 'temperature', key: 'temperature',
name: 'Sampling Temperature', name: 'Sampling Temperature',
default: 1,
title: 'What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n' + title: 'What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n' +
'\n' + '\n' +
'We generally recommend altering this or top_p but not both.', 'We generally recommend altering this or top_p but not both.',
@ -224,7 +255,6 @@ const chatSettingsList: ChatSetting[] = [
{ {
key: 'top_p', key: 'top_p',
name: 'Nucleus Sampling', name: 'Nucleus Sampling',
default: 1,
title: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n' + title: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n' +
'\n' + '\n' +
'We generally recommend altering this or temperature but not both', 'We generally recommend altering this or temperature but not both',
@ -236,7 +266,6 @@ const chatSettingsList: ChatSetting[] = [
{ {
key: 'n', key: 'n',
name: 'Number of Messages', name: 'Number of Messages',
default: 1,
title: 'CAREFUL WITH THIS ONE: How many chat completion choices to generate for each input message. This can eat tokens.', title: 'CAREFUL WITH THIS ONE: How many chat completion choices to generate for each input message. This can eat tokens.',
min: 1, min: 1,
max: 10, max: 10,
@ -249,17 +278,15 @@ const chatSettingsList: ChatSetting[] = [
title: 'The maximum number of tokens to generate in the completion.\n' + title: 'The maximum number of tokens to generate in the completion.\n' +
'\n' + '\n' +
'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', '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',
default: 128,
min: 1, min: 1,
max: 32768, max: 32768,
step: 1024, step: 1024,
type: 'number', type: 'number',
required: 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',
name: 'Presence Penalty', name: 'Presence Penalty',
default: 0,
title: 'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.', title: 'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.',
min: -2, min: -2,
max: 2, max: 2,
@ -269,12 +296,43 @@ const chatSettingsList: ChatSetting[] = [
{ {
key: 'frequency_penalty', key: 'frequency_penalty',
name: 'Frequency Penalty', name: 'Frequency Penalty',
default: 0,
title: 'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.', title: 'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.',
min: -2, min: -2,
max: 2, max: 2,
step: 0.2, step: 0.2,
type: 'number' type: 'number'
},
{
// logit bias editor not implemented yet
key: 'logit_bias',
name: 'Logit Bias',
title: 'Allows you to adjust bias of tokens used in completion.',
header: `Logit Bias. See <a target="_blank" href="https://help.openai.com/en/articles/5247780-using-logit-bias-to-define-token-probability">this article</a> for more details.`,
type: 'other',
hide: () => true,
// transform to JSON for request, first converting word->weight pairs to token(s)->weight.
// -- care should be taken to have each word key in the each record formatted in a way where they
// only take one token each else you'll end up with results you probably don't want.
// Generally, leading space plus common lower case word will more often result in a single token
// See: https://platform.openai.com/tokenizer
apiTransform: (chatId, setting, val:Record<string, number>) => {
console.log('logit_bias', val, getChatSettings(chatId).logit_bias)
if (!val) return null
const tokenized:Record<number, number> = Object.entries(val).reduce((a,[k,v])=>{
const tokens:number[] = encode(k)
tokens.forEach(t => {a[t] = v})
return a
}, {} as Record<number, number>)
return tokenized
},
},
// Enable?
{
key: 'user',
name: 'User?',
title: 'Name of user?',
type: 'text',
hide: () => true,
} }
] ]
@ -289,13 +347,11 @@ const globalSettingsList:GlobalSetting[] = [
{ {
key: 'lastProfile', key: 'lastProfile',
name: 'Last Profile', name: 'Last Profile',
default: 'default',
type: 'text' type: 'text'
}, },
{ {
key: 'defaultProfile', key: 'defaultProfile',
name: 'Default Profile', name: 'Default Profile',
default: 'default',
type: 'text' type: 'text'
} }
] ]

View File

@ -2,14 +2,16 @@
import { persisted } from 'svelte-local-storage-store' import { persisted } from 'svelte-local-storage-store'
import { get } from 'svelte/store' import { get } from 'svelte/store'
import type { Chat, ChatSettings, GlobalSettings, Message, ChatSetting, GlobalSetting } from './Types.svelte' import type { Chat, ChatSettings, GlobalSettings, Message, ChatSetting, GlobalSetting } from './Types.svelte'
import { getChatSettingByKey, getGlobalSettingByKey } from './Settings.svelte' import { getChatSettingObjectByKey, getGlobalSettingObjectByKey, getChatDefaults, getExcludeFromProfile } from './Settings.svelte'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { isStaticProfile } from './Profiles.svelte' import { applyProfile, getProfile, isStaticProfile } from './Profiles.svelte'
export const chatsStorage = persisted('chats', [] as Chat[]) export const chatsStorage = persisted('chats', [] as Chat[])
export const globalStorage = persisted('global', {} as GlobalSettings) export const globalStorage = persisted('global', {} as GlobalSettings)
export const apiKeyStorage = persisted('apiKey', '' as string) export const apiKeyStorage = persisted('apiKey', '' as string)
const chatDefaults = getChatDefaults()
export const newChatID = (): number => { export const newChatID = (): number => {
const chats = get(chatsStorage) const chats = get(chatsStorage)
const chatId = chats.reduce((maxId, chat) => Math.max(maxId, chat.id), 0) + 1 const chatId = chats.reduce((maxId, chat) => Math.max(maxId, chat.id), 0) + 1
@ -30,6 +32,8 @@
messages: [] messages: []
}) })
chatsStorage.set(chats) chatsStorage.set(chats)
// Apply defaults and prepare it to start
applyProfile(chatId,'', true)
return chatId return chatId
} }
@ -59,9 +63,41 @@
return chatId return chatId
} }
export const getChat = (chatId: number):Chat => { // Make sure all chat settings are set with current values or defaults
export const updateChatSettings = (chatId) => {
const chats = get(chatsStorage) const chats = get(chatsStorage)
return chats.find((chat) => chat.id === chatId) as Chat const chat = chats.find((chat) => chat.id === chatId) as Chat
if (!chat.settings) {
chat.settings = {} as ChatSettings
}
Object.entries(getChatDefaults()).forEach(([k,v]) => {
const val = chat.settings[k]
chat.settings[k] = (val === undefined || val === null ? v : chat.settings[k]) as any
})
// make sure old chat messages have UUID
chat.messages.forEach((m) => {
m.uuid = m.uuid || uuidv4()
})
chatsStorage.set(chats)
}
// Reset all setting to current profile defaults
export const resetChatSettings = (chatId, resetAll:boolean = false) => {
const chats = get(chatsStorage)
const chat = chats.find((chat) => chat.id === chatId) as Chat
const profile = getProfile(chat.settings.profile)
const exclude = getExcludeFromProfile()
if (resetAll) {
// Reset to base defaults first, then apply profile
Object.entries(getChatDefaults()).forEach(([k,v]) => {
chat.settings[k] = v
})
}
Object.entries(profile).forEach(([k,v]) => {
if (exclude[k]) return
chat.settings[k] = v
})
chatsStorage.set(chats)
} }
export const clearChats = () => { export const clearChats = () => {
@ -72,6 +108,16 @@
chatsStorage.set(chats) chatsStorage.set(chats)
} }
export const getChat = (chatId: number):Chat => {
const chats = get(chatsStorage)
return chats.find((chat) => chat.id === chatId) as Chat
}
export const getChatSettings = (chatId: number):ChatSettings => {
const chats = get(chatsStorage)
return (chats.find((chat) => chat.id === chatId) as Chat).settings
}
export const addMessage = (chatId: number, message: Message) => { export const addMessage = (chatId: number, message: Message) => {
const chats = get(chatsStorage) const chats = get(chatsStorage)
const chat = chats.find((chat) => chat.id === chatId) as Chat const chat = chats.find((chat) => chat.id === chatId) as Chat
@ -134,23 +180,20 @@
i++ i++
cname = chat.name + `-${i}` cname = chat.name + `-${i}`
} }
const chatCopy = JSON.parse(JSON.stringify(chat))
// Find the max chatId // Set the ID
const newId = newChatID() chatCopy.id = newChatID()
// Add a new chat // Add a new chat
chats.push({ chats.push(chatCopy)
id: newId,
name: cname,
settings: JSON.parse(JSON.stringify(chat.settings)),
messages: JSON.parse(JSON.stringify(chat.messages))
})
// chatsStorage // chatsStorage
chatsStorage.set(chats) chatsStorage.set(chats)
} }
export const cleanSettingValue = (chatId, setting:(GlobalSetting | ChatSetting), value: any) => { export const cleanSettingValue = (type:string, value: any) => {
switch (setting.type) { switch (type) {
case 'number': case 'number':
value = parseFloat(value) value = parseFloat(value)
if (isNaN(value)) { value = null } if (isNaN(value)) { value = null }
@ -163,68 +206,52 @@
} }
} }
export const setGlobalSettingValueByKey = (key: keyof GlobalSettings, value) => {
return setGlobalSettingValue(getGlobalSettingByKey(key), value)
}
export const setGlobalSettingValue = (setting: GlobalSetting, value) => {
const store = get(globalStorage)
store[setting.key] = cleanSettingValue(0, setting, value)
globalStorage.set(store)
}
export const setChatSettingValueByKey = (chatId: number, key: keyof ChatSettings, value) => { export const setChatSettingValueByKey = (chatId: number, key: keyof ChatSettings, value) => {
return setChatSettingValue(chatId, getChatSettingByKey(key), value) const setting = getChatSettingObjectByKey(key)
if (setting) return setChatSettingValue(chatId, setting, value)
if (!(key in chatDefaults)) throw new Error('Invalid chat setting: ' + key)
const d = chatDefaults[key]
if (d === null || d === undefined) throw new Error('Unable to determine setting type for "'
+ key +' from default of "' + d + '"')
const chats = get(chatsStorage)
const chat = chats.find((chat) => chat.id === chatId) as Chat
let settings = chat.settings as any
settings[key] = cleanSettingValue(typeof d, value)
} }
export const setChatSettingValue = (chatId: number, setting: ChatSetting, value) => { export const setChatSettingValue = (chatId: number, setting: ChatSetting, value) => {
const chats = get(chatsStorage) const chats = get(chatsStorage)
const chat = chats.find((chat) => chat.id === chatId) as Chat const chat = chats.find((chat) => chat.id === chatId) as Chat
let settings:ChatSettings = chat.settings let settings = chat.settings as any
if (!settings) { if (!settings) {
settings = {} as ChatSettings settings = {} as ChatSettings
chat.settings = settings chat.settings = settings
} }
if (typeof setting.setFilter === 'function') value = setting.setFilter(chatId, setting, value) settings[setting.key] = cleanSettingValue(setting.type, value)
settings[setting.key] = cleanSettingValue(chatId, setting, value)
chatsStorage.set(chats) chatsStorage.set(chats)
} }
export const getGlobalSettingValueNullDefault = (setting: GlobalSetting) => {
const store = get(globalStorage)
let value = store && store[setting.key] as any
value = (value === undefined) ? null : value
return value
}
export const getGlobalSettingValue = (setting: GlobalSetting) => {
let value = getGlobalSettingValueNullDefault(setting)
if (value === null) value = setting.default
return value as any
}
export const getGlobalSettingValueByKey = (key: keyof GlobalSettings) => {
return getGlobalSettingValue(getGlobalSettingByKey(key))
}
export const getChatSettingValueNullDefault = (chatId: number, setting: ChatSetting):any => { export const getChatSettingValueNullDefault = (chatId: number, setting: ChatSetting):any => {
const chats = get(chatsStorage) const chats = get(chatsStorage)
const chat = chats.find((chat) => chat.id === chatId) as Chat const chat = chats.find((chat) => chat.id === chatId) as Chat
let value = chat.settings && chat.settings[setting.key] let value = chat.settings && chat.settings[setting.key]
value = (value === undefined) ? null : value value = (value === undefined) ? null : value
if (value === setting.default) value = null if (!setting.forceApi && value === chatDefaults[setting.key]) value = null
if (typeof setting.getFilter === 'function') value = setting.getFilter(chatId, setting, value)
return value return value
} }
export const getChatSettingValue = (chatId: number, setting: ChatSetting):any => { export const setGlobalSettingValueByKey = (key: keyof GlobalSettings, value) => {
let value = getChatSettingValueNullDefault(chatId, setting) return setGlobalSettingValue(getGlobalSettingObjectByKey(key), value)
if (value === null) value = setting.default
return value
} }
export const getChatSettingValueByKey = (chatId: number, key: keyof ChatSettings):any => { export const setGlobalSettingValue = (setting: GlobalSetting, value) => {
return getChatSettingValue(chatId, getChatSettingByKey(key)) as any const store = get(globalStorage)
store[setting.key] = cleanSettingValue(setting.type, value)
globalStorage.set(store)
}
export const getGlobalSettings = ():GlobalSettings => {
return get(globalStorage)
} }
export const getCustomProfiles = ():Record<string, ChatSettings> => { export const getCustomProfiles = ():Record<string, ChatSettings> => {
@ -269,7 +296,11 @@
if (!profile.characterName || profile.characterName.length < 3) { if (!profile.characterName || profile.characterName.length < 3) {
throw new Error('Your profile\'s character needs a valid name.') throw new Error('Your profile\'s character needs a valid name.')
} }
profiles[profile.profile as string] = JSON.parse(JSON.stringify(profile)) // Always store a copy const clone =JSON.parse(JSON.stringify(profile)) // Always store a copy
Object.keys(getExcludeFromProfile()).forEach(k=>{
delete clone[k]
})
profiles[profile.profile as string] = clone
globalStorage.set(store) globalStorage.set(store)
} }

View File

@ -41,26 +41,27 @@
max_tokens?: number; max_tokens?: number;
presence_penalty?: number; presence_penalty?: number;
frequency_penalty?: number; frequency_penalty?: number;
logit_bias?: Record<string, any>; logit_bias?: Record<string, any> | null;
user?: string; user?: string;
}; };
export type ChatSettings = { export type ChatSettings = {
profile?: string, profile: string,
characterName?: string, characterName: string,
profileName?: string, profileName: string,
profileDescription?: string, profileDescription: string,
useSummarization?: boolean; useSummarization: boolean;
summaryThreshold?: number; summaryThreshold: number;
summarySize?: number; summarySize: number;
pinTop?: number, pinTop: number;
pinBottom?: number, pinBottom: number;
summaryPrompt?: string; summaryPrompt: string;
useSystemPrompt?: boolean; useSystemPrompt: boolean;
systemPrompt?: string; systemPrompt: string;
autoStartSession?: boolean; autoStartSession: boolean;
startSession?: false; startSession: false;
sessionStarted: false;
trainingPrompts?: Message[]; trainingPrompts?: Message[];
} & Request; } & Request;
@ -111,57 +112,50 @@
type SettingNumber = { type SettingNumber = {
type: 'number'; type: 'number';
default: number;
min: number; min: number;
max: number; max: number;
step: number; step: number;
}; };
type SettingBoolean = {
type: 'boolean';
default: boolean;
};
export type SelectOption = { export type SelectOption = {
value: string; value: string;
text: string; text: string;
}; };
type SettingBoolean = {
type: 'boolean';
};
export type SettingSelect = { export type SettingSelect = {
type: 'select'; type: 'select';
default: string;
options: SelectOption[]; options: SelectOption[];
}; };
export type SettingText = { export type SettingText = {
type: 'text'; type: 'text';
default: string;
}; };
export type SettingTextArea = { export type SettingTextArea = {
type: 'textarea'; type: 'textarea';
lines?: number; lines?: number;
default: string;
placeholder?: string;
}; };
export type SettingOther = { export type SettingOther = {
type: 'other'; type: 'other';
default: any;
}; };
export type ChatSetting = { export type ChatSetting = {
key: keyof ChatSettings; key: keyof ChatSettings;
name: string; name: string;
title: string; title: string;
required?: boolean; // force in request forceApi?: boolean; // force in api requests, even if set to default
noRequest?: boolean; // exclude from request
hidden?: boolean; // Hide from setting menus hidden?: boolean; // Hide from setting menus
header?: string; header?: string;
headerClass?: string; headerClass?: string;
placeholder?: string;
hide?: (number?) => boolean; hide?: (number?) => boolean;
setFilter?: (number, ChatSetting?, any?) => any; apiTransform?: (number, ChatSetting, any?) => any;
getFilter?: (number, ChatSetting?, any?) => any; beforeChange?: (number, ChatSetting?, any?) => boolean;
afterChange?: (number, ChatSetting?, any?) => boolean; afterChange?: (number, ChatSetting?, any?) => boolean;
} & (SettingNumber | SettingSelect | SettingBoolean | SettingText | SettingTextArea | SettingOther); } & (SettingNumber | SettingSelect | SettingBoolean | SettingText | SettingTextArea | SettingOther);