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