Import profile on chat context menu. Fix file load bug

This commit is contained in:
Webifi 2023-07-17 14:01:16 -05:00
parent 74cb9bd77a
commit d186f0c190
3 changed files with 56 additions and 6 deletions

View File

@ -18,14 +18,15 @@
faEyeSlash faEyeSlash
} from '@fortawesome/free-solid-svg-icons/index' } from '@fortawesome/free-solid-svg-icons/index'
import { faSquareMinus, faSquarePlus as faSquarePlusOutline } from '@fortawesome/free-regular-svg-icons/index' import { faSquareMinus, faSquarePlus as faSquarePlusOutline } from '@fortawesome/free-regular-svg-icons/index'
import { apiKeyStorage, addChatFromJSON, chatsStorage, checkStateChange, clearChats, clearMessages, copyChat, globalStorage, setGlobalSettingValueByKey, showSetChatSettings, pinMainMenu, getChat, deleteChat, saveChatStore } from './Storage.svelte' import { apiKeyStorage, addChatFromJSON, chatsStorage, checkStateChange, clearChats, clearMessages, copyChat, globalStorage, setGlobalSettingValueByKey, showSetChatSettings, pinMainMenu, getChat, deleteChat, saveChatStore, saveCustomProfile } from './Storage.svelte'
import { exportAsMarkdown, exportChatAsJSON } from './Export.svelte' import { exportAsMarkdown, exportChatAsJSON } from './Export.svelte'
import { restartProfile } from './Profiles.svelte' import { newNameForProfile, restartProfile } from './Profiles.svelte'
import { replace } from 'svelte-spa-router' import { replace } from 'svelte-spa-router'
import { clickOutside } from 'svelte-use-click-outside' import { clickOutside } from 'svelte-use-click-outside'
import { openModal } from 'svelte-modals' import { openModal } from 'svelte-modals'
import PromptConfirm from './PromptConfirm.svelte' import PromptConfirm from './PromptConfirm.svelte'
import { startNewChatWithWarning, startNewChatFromChatId } from './Util.svelte' import { startNewChatWithWarning, startNewChatFromChatId, errorNotice, encodeHTMLEntities } from './Util.svelte'
import type { ChatSettings } from './Types.svelte'
export let chatId export let chatId
export const show = (showHide:boolean = true) => { export const show = (showHide:boolean = true) => {
@ -37,10 +38,12 @@
let showChatMenu = false let showChatMenu = false
let chatFileInput let chatFileInput
let profileFileInput
const importChatFromFile = (e) => { const importChatFromFile = (e) => {
close() close()
const image = e.target.files[0] const image = e.target.files[0]
e.target.value = null
const reader = new FileReader() const reader = new FileReader()
reader.readAsText(image) reader.readAsText(image)
reader.onload = e => { reader.onload = e => {
@ -121,6 +124,38 @@
}) })
} }
const importProfileFromFile = (e) => {
const image = e.target.files[0]
e.target.value = null
const reader = new FileReader()
reader.onload = e => {
const json = (e.target || {}).result as string
try {
const profile = JSON.parse(json) as ChatSettings
profile.profileName = newNameForProfile(profile.profileName || '')
profile.profile = null as any
saveCustomProfile(profile)
openModal(PromptConfirm, {
title: 'Profile Restored',
class: 'is-info',
message: 'Profile restored as:<br><strong>' + encodeHTMLEntities(profile.profileName) +
'</strong><br><br>Start new chat with this profile?',
asHtml: true,
onConfirm: () => {
startNewChatWithWarning(chatId, profile)
},
onCancel: () => {}
})
} catch (e) {
errorNotice('Unable to import profile:', e)
}
}
reader.onerror = e => {
errorNotice('Unable to import profile:', new Error('Unknown error'))
}
reader.readAsText(image)
}
</script> </script>
<div class="dropdown {style}" class:is-active={showChatMenu} use:clickOutside={() => { showChatMenu = false }}> <div class="dropdown {style}" class:is-active={showChatMenu} use:clickOutside={() => { showChatMenu = false }}>
@ -168,6 +203,10 @@
<span class="menu-icon"><Fa icon={faFileExport}/></span> Export Chat Markdown <span class="menu-icon"><Fa icon={faFileExport}/></span> Export Chat Markdown
</a> </a>
<hr class="dropdown-divider"> <hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" class:is-disabled={!$apiKeyStorage} on:click|preventDefault={() => { if (chatId) close(); profileFileInput.click() }}>
<span class="menu-icon"><Fa icon={faUpload}/></span> Restore Profile JSON
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { if (chatId) close(); delChat() }}> <a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { if (chatId) close(); delChat() }}>
<span class="menu-icon"><Fa icon={faTrash}/></span> Delete Chat <span class="menu-icon"><Fa icon={faTrash}/></span> Delete Chat
</a> </a>
@ -191,3 +230,4 @@
</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) => importChatFromFile(e)} bind:this={chatFileInput} >
<input style="display:none" type="file" accept=".json" on:change={(e) => importProfileFromFile(e)} bind:this={profileFileInput} >

View File

@ -147,6 +147,7 @@
const importProfileFromFile = (e) => { const importProfileFromFile = (e) => {
const image = e.target.files[0] const image = e.target.files[0]
e.target.value = null
const reader = new FileReader() const reader = new FileReader()
reader.readAsText(image) reader.readAsText(image)
reader.onload = e => { reader.onload = e => {

View File

@ -5,6 +5,7 @@
import { addChat, getChat } from './Storage.svelte' import { addChat, getChat } from './Storage.svelte'
import { replace } from 'svelte-spa-router' import { replace } from 'svelte-spa-router'
import PromptConfirm from './PromptConfirm.svelte' import PromptConfirm from './PromptConfirm.svelte'
import type { ChatSettings } from './Types.svelte'
export const sizeTextElements = () => { export const sizeTextElements = () => {
const els = document.querySelectorAll('textarea.auto-size') const els = document.querySelectorAll('textarea.auto-size')
for (let i:number = 0, l = els.length; i < l; i++) autoGrowInput(els[i] as HTMLTextAreaElement) for (let i:number = 0, l = els.length; i < l; i++) autoGrowInput(els[i] as HTMLTextAreaElement)
@ -95,6 +96,10 @@
} }
} }
export const encodeHTMLEntities = (rawStr:string) => {
return rawStr.replace(/[\u00A0-\u9999<>&]/g, (i) => `&#${i.charCodeAt(0)};`)
}
export const errorNotice = (message:string, error:Error|undefined = undefined):any => { export const errorNotice = (message:string, error:Error|undefined = undefined):any => {
openModal(PromptNotice, { openModal(PromptNotice, {
title: 'Error', title: 'Error',
@ -121,7 +126,11 @@
replace(`/chat/${newChatId}`) replace(`/chat/${newChatId}`)
} }
export const startNewChatWithWarning = (activeChatId: number|undefined) => { export const startNewChatWithWarning = (activeChatId: number|undefined, profile?: ChatSettings|undefined) => {
const newChat = () => {
const chatId = addChat(profile)
replace(`/chat/${chatId}`)
}
if (activeChatId && getChat(activeChatId).settings.isDirty) { if (activeChatId && getChat(activeChatId).settings.isDirty) {
openModal(PromptConfirm, { openModal(PromptConfirm, {
title: 'Unsaved Profile', title: 'Unsaved Profile',
@ -129,11 +138,11 @@
asHtml: true, asHtml: true,
class: 'is-warning', class: 'is-warning',
onConfirm: () => { onConfirm: () => {
replace('#/chat/new') newChat()
} }
}) })
} else { } else {
replace('#/chat/new') newChat()
} }
} }