Use modals instead of window.alert & confirm

This commit is contained in:
Webifi 2023-06-05 21:29:20 -05:00
parent db6e5898df
commit 5853b9e451
9 changed files with 220 additions and 44 deletions

View File

@ -17,11 +17,13 @@
faEye,
faEyeSlash
} from '@fortawesome/free-solid-svg-icons/index'
import { apiKeyStorage, addChatFromJSON, chatsStorage, checkStateChange, clearChats, clearMessages, copyChat, globalStorage, setGlobalSettingValueByKey, showSetChatSettings, pinMainMenu } from './Storage.svelte'
import { apiKeyStorage, addChatFromJSON, chatsStorage, checkStateChange, clearChats, clearMessages, copyChat, globalStorage, setGlobalSettingValueByKey, showSetChatSettings, pinMainMenu, getChat, deleteChat } from './Storage.svelte'
import { exportAsMarkdown, exportChatAsJSON } from './Export.svelte'
import { restartProfile } from './Profiles.svelte'
import { replace } from 'svelte-spa-router'
import { clickOutside } from 'svelte-use-click-outside'
import { openModal } from 'svelte-modals'
import PromptConfirm from './PromptConfirm.svelte'
export let chatId
export const show = (showHide:boolean = true) => {
@ -29,6 +31,8 @@
}
export let style: string = 'is-right'
$: sortedChats = $chatsStorage.sort((a, b) => b.id - a.id)
let showChatMenu = false
let chatFileInput
@ -43,20 +47,45 @@
}
}
const deleteChat = () => {
const delChat = () => {
close()
if (window.confirm('Are you sure you want to delete this chat?')) {
replace('/').then(() => {
chatsStorage.update((chats) => chats.filter((chat) => chat.id !== chatId))
})
}
openModal(PromptConfirm, {
title: 'Delete Chat',
message: 'Are you sure you want to delete this chat?',
class: 'is-warning',
confirmButtonClass: 'is-warning',
confirmButton: 'Delete Chat',
onConfirm: () => {
const thisChat = getChat(chatId)
const thisIndex = sortedChats.indexOf(thisChat)
const prevChat = sortedChats[thisIndex - 1]
const nextChat = sortedChats[thisIndex + 1]
const newChat = nextChat || prevChat
if (!newChat) {
// No other chats, clear all and go to home
replace('/').then(() => { deleteChat(chatId) })
} else {
// Delete the current chat and go to the max chatId
replace(`/chat/${newChat.id}`).then(() => { deleteChat(chatId) })
}
},
onCancel: () => {}
})
}
const confirmClearChats = () => {
close()
if (window.confirm('Are you sure you want to delete ALL of your chats?')) {
clearChats()
}
openModal(PromptConfirm, {
title: 'Delete ALL Chat',
message: 'Are you sure you want to delete ALL of your chats?',
class: 'is-danger',
confirmButtonClass: 'is-danger',
confirmButton: 'Delete ALL',
onConfirm: () => {
clearChats()
},
onCancel: () => {}
})
}
const close = () => {
@ -116,7 +145,7 @@
<span class="menu-icon"><Fa icon={faFileExport}/></span> Export Chat Markdown
</a>
<hr class="dropdown-divider">
<a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { if (chatId) close(); deleteChat() }}>
<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
</a>
<a href={'#'} class="dropdown-item" on:click|preventDefault={() => { if (chatId) confirmClearChats() }}>

View File

@ -3,11 +3,12 @@
// import { getProfile } from './Profiles.svelte'
import { cleanSettingValue, setChatSettingValue } from './Storage.svelte'
import type { Chat, ChatSetting, ChatSettings, ControlAction, FieldControl, SettingPrompt } from './Types.svelte'
import { autoGrowInputOnEvent } from './Util.svelte'
import { autoGrowInputOnEvent, errorNotice } from './Util.svelte'
// import { replace } from 'svelte-spa-router'
import Fa from 'svelte-fa/src/fa.svelte'
import { openModal } from 'svelte-modals'
import PromptConfirm from './PromptConfirm.svelte'
import PromptNotice from './PromptNotice.svelte'
export let setting:ChatSetting
export let chatSettings:ChatSettings
@ -87,7 +88,7 @@
(typeof setting.beforeChange === 'function') && setting.beforeChange(chatId, setting, el.checked || el.value) &&
refreshSettings()
} catch (e) {
window.alert('Unable to change:\n' + e.message)
openModal(PromptNotice, errorNotice('Unable to change:', e))
}
switch (setting.type) {
case 'boolean':
@ -106,7 +107,7 @@
}
} catch (e) {
setChatSettingValue(chatId, setting, val)
window.alert('Unable to change:\n' + e.message)
openModal(PromptNotice, errorNotice('Unable to change:', e))
}
dispatch('change', setting)
}

View File

@ -16,7 +16,7 @@
} from './Storage.svelte'
import { supportedModels, type Chat, type ChatSetting, type ResponseModels, type SettingSelect, type SelectOption, type ChatSettings } from './Types.svelte'
import { sizeTextElements } from './Util.svelte'
import { errorNotice, sizeTextElements } from './Util.svelte'
import Fa from 'svelte-fa/src/fa.svelte'
import {
faTrash,
@ -33,6 +33,8 @@
import ChatSettingField from './ChatSettingField.svelte'
import { getModelMaxTokens } from './Stats.svelte'
import { replace } from 'svelte-spa-router'
import { openModal } from 'svelte-modals'
import PromptNotice from './PromptNotice.svelte'
export let chatId:number
export const show = () => { showSettings() }
@ -101,7 +103,7 @@
applyProfile(chatId, clone.profile)
refreshSettings()
} catch (e) {
window.alert('Error cloning profile: \n' + e.message)
openModal(PromptNotice, errorNotice('Error cloning profile:', e))
}
}
@ -115,7 +117,7 @@
applyProfile(chatId, chat.settings.profile as any)
refreshSettings()
} catch (e) {
window.alert('Error deleting profile: \n' + e.message)
openModal(PromptNotice, errorNotice('Error deleting profile:', e))
}
}
@ -138,7 +140,7 @@
saveCustomProfile(profile)
refreshSettings()
} catch (e) {
window.alert('Unable to import profile: \n' + e.message)
openModal(PromptNotice, errorNotice('Unable to import profile:', e))
}
}
}
@ -201,7 +203,7 @@
saveCustomProfile(chat.settings)
refreshSettings()
} catch (e) {
window.alert('Error saving profile: \n' + e.message)
openModal(PromptNotice, errorNotice('Error saving profile:', e))
}
}

View File

@ -7,7 +7,10 @@
import type { Message, Model, Chat } from './Types.svelte'
import Fa from 'svelte-fa/src/fa.svelte'
import { faTrash, faDiagramPredecessor, faDiagramNext, faCircleCheck, faPaperPlane, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons/index'
import { scrollIntoViewWithOffset } from './Util.svelte'
import { errorNotice, scrollIntoViewWithOffset } from './Util.svelte'
import { openModal } from 'svelte-modals'
import PromptConfirm from './PromptConfirm.svelte'
import PromptNotice from './PromptNotice.svelte'
export let message:Message
export let chatId:number
@ -115,24 +118,33 @@
waitingForDeleteConfirm = 0
if (message.summarized) {
// is in a summary, so we're summarized
window.alert('Sorry, you can\'t delete a summarized message')
openModal(PromptNotice, errorNotice('Sorry, you can\'t delete a summarized message'))
return
}
if (message.summary) {
// We're linked to messages we're a summary of
if (window.confirm('Are you sure you want to delete this summary?\nYour session may be too long to submit again after you do.')) {
try {
deleteSummaryMessage(chatId, message.uuid)
} catch (e) {
window.alert('Unable to delete summary:\n' + e.message)
}
openModal(PromptConfirm, {
title: 'Delete Summary',
message: '<p>Are you sure you want to delete this summary?</p><p>Your session may be too long to submit again after you do.</p>',
asHtml: true,
class: 'is-warning',
confirmButtonClass: 'is-warning',
confirmButton: 'Delete Summary',
onConfirm: () => {
try {
deleteSummaryMessage(chatId, message.uuid)
} catch (e) {
openModal(PromptNotice, errorNotice('Unable to delete summary:', e))
}
},
onCancel: () => {}
})
} else {
try {
deleteMessage(chatId, message.uuid)
} catch (e) {
openModal(PromptNotice, errorNotice('Unable to delete:', e))
}
return
}
try {
deleteMessage(chatId, message.uuid)
} catch (e) {
window.alert('Unable to delete:\n' + e.message)
}
}
@ -150,21 +162,21 @@
waitingForTruncateConfirm = 0
if (message.summarized) {
// is in a summary, so we're summarized
window.alert('Sorry, you can\'t truncate a summarized message')
openModal(PromptNotice, errorNotice('Sorry, you can\'t truncate a summarized message'))
return
}
try {
truncateFromMessage(chatId, message.uuid)
$submitExitingPromptsNow = true
} catch (e) {
window.alert('Unable to delete:\n' + e.message)
openModal(PromptNotice, errorNotice('Unable to delete:', e))
}
}
const setSuppress = (value:boolean) => {
if (message.summarized) {
// is in a summary, so we're summarized
window.alert('Sorry, you can\'t suppress a summarized message')
openModal(PromptNotice, errorNotice('Sorry, you can\'t suppress a summarized message'))
return
}
message.suppress = value

View File

@ -0,0 +1,50 @@
<script lang="ts">
import { closeModal } from 'svelte-modals'
export let isOpen:boolean
export let title:string
export let message:string
export let asHtml:boolean = false
export let onConfirm:()=>boolean|void
export let confirmButton:string = 'Okay'
export let confirmButtonClass:string = 'is-info'
let classes:string = ''
export { classes as class }
const doConfirm = () => {
if (!onConfirm || !onConfirm()) closeModal()
}
</script>
{#if isOpen}
<div class="modal is-active" on:modal-esc={doConfirm}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="modal-background" on:click={doConfirm} />
<div class="modal-content nomax">
<article class="message {classes}">
<div class="message-header">
<p>{title}</p>
<button class="delete" aria-label="close" type="button" on:click={doConfirm}></button>
</div>
<div class="message-body">
{#if asHtml}{@html message}{:else}{message}{/if}
</div>
<div class="message-footer">
<div class="level is-mobile">
<div class="level-right">
</div>
<div class="level-right">
<div class="level-item">
<button class="button {confirmButtonClass}" type="button" on:click={doConfirm} >{confirmButton}</button>
</div>
</div>
</div>
</div>
</article>
</div>
</div>
{/if}

View File

@ -18,7 +18,7 @@ import {
} from './Types.svelte'
export const defaultModel:Model = 'gpt-3.5-turbo-0301'
export const defaultModel:Model = 'gpt-3.5-turbo'
export const getChatSettingList = (): ChatSetting[] => {
return chatSettingsList
@ -86,6 +86,8 @@ const defaults:ChatSettings = {
systemPrompt: '',
autoStartSession: false,
trainingPrompts: [],
// useResponseAlteration: false,
// responseAlterations: [],
isDirty: false
}
@ -129,8 +131,7 @@ const profileSetting: ChatSetting & SettingSelect = {
}
// Settings that will not be part of the API request
const nonRequestSettings: ChatSetting[] = [
profileSetting,
const systemPromptSettings: ChatSetting[] = [
{
key: 'profileName',
name: 'Profile Name',
@ -181,7 +182,10 @@ const nonRequestSettings: ChatSetting[] = [
title: 'If possible, auto-start the chat session, sending a system prompt to get an initial response.',
type: 'boolean',
hide: (chatId) => !getChatSettings(chatId).useSystemPrompt
},
}
]
const summarySettings: ChatSetting[] = [
{
key: 'useSummarization',
name: 'Enable Continuous Chat',
@ -242,6 +246,54 @@ const nonRequestSettings: ChatSetting[] = [
}
]
// const responseAlterationSettings: ChatSetting[] = [
// {
// key: 'useResponseAlteration',
// name: 'Alter Responses',
// header: 'Automatic Response Alteration',
// headerClass: 'is-info',
// title: 'When an undesired response is encountered, try to alter it in effort to improve future responses.',
// type: 'boolean',
// hide: () => true
// },
// {
// key: 'responseAlterations',
// name: 'Alterations',
// title: 'Add find/replace or re-prompts.',
// header: 'Profile / Presets',
// headerClass: 'is-info',
// settings: [
// {
// key: 'type',
// type: 'select',
// name: 'Alteration Type',
// default: 'replace',
// options: [{
// value: 'replace',
// text: 'Regexp Find / Replace'
// }, {
// value: 'prompt',
// text: 'Re-prompt with Instructions'
// }]
// },
// {
// key: 'match',
// type: 'text',
// name: 'Match Expression',
// title: 'Regular expression used to match '
// },
// {
// key: 'replace',
// type: 'text',
// name: 'Alteration',
// title: 'Regexp Replacement or Re-prompt'
// }
// ],
// type: 'subset',
// hide: (chatId) => !getChatSettings(chatId).useResponseAlteration!
// }
// ]
const modelSetting: ChatSetting & SettingSelect = {
key: 'model',
name: 'Model',
@ -255,7 +307,10 @@ const modelSetting: ChatSetting & SettingSelect = {
}
const chatSettingsList: ChatSetting[] = [
...nonRequestSettings,
profileSetting,
...systemPromptSettings,
...summarySettings,
// ...responseAlterationSettings,
modelSetting,
{
key: 'temperature',

View File

@ -5,6 +5,9 @@
import { getChatSettingObjectByKey, getGlobalSettingObjectByKey, getChatDefaults, getExcludeFromProfile } from './Settings.svelte'
import { v4 as uuidv4 } from 'uuid'
import { getProfile, getProfiles, isStaticProfile, newNameForProfile, restartProfile } from './Profiles.svelte'
import { openModal } from 'svelte-modals'
import PromptNotice from './PromptNotice.svelte'
import { errorNotice } from './Util.svelte'
export const chatsStorage = persisted('chats', [] as Chat[])
export const globalStorage = persisted('global', {} as GlobalSettings)
@ -56,11 +59,11 @@
try {
chat = JSON.parse(json) as Chat
if (!chat.settings || !chat.messages || isNaN(chat.id)) {
window.alert('Not valid Chat JSON')
openModal(PromptNotice, errorNotice('Not valid Chat JSON'))
return 0
}
} catch (err) {
window.alert("Can't parse file JSON")
openModal(PromptNotice, errorNotice("Can't parse file JSON"))
return 0
}

View File

@ -29,6 +29,12 @@
suppress?: boolean;
};
export type ResponseAlteration = {
type: 'prompt' | 'replace';
match: string;
replace: string;
}
export type Request = {
model?: Model;
messages?: Message[];
@ -59,6 +65,8 @@
systemPrompt: string;
autoStartSession: boolean;
trainingPrompts?: Message[];
useResponseAlteration?: boolean;
responseAlterations?: ResponseAlteration[];
isDirty?: boolean;
} & Request;
@ -157,6 +165,11 @@ type SettingBoolean = {
getAction: (chatId:number, setting:any, value:any) => ControlAction;
};
export type SubSetting = {
type: 'subset';
settings: any[];
};
export type ChatSetting = {
key: keyof ChatSettings;
name: string;
@ -171,7 +184,8 @@ type SettingBoolean = {
fieldControls?: FieldControl[];
beforeChange?: (chatId:number, setting:ChatSetting, value:any) => boolean;
afterChange?: (chatId:number, setting:ChatSetting, value:any) => boolean;
} & (SettingNumber | SettingSelect | SettingBoolean | SettingText | SettingTextArea | SettingOther);
} & (SettingNumber | SettingSelect | SettingBoolean | SettingText | SettingTextArea | SettingOther | SubSetting);
export type GlobalSetting = {
key: keyof GlobalSettings;

View File

@ -52,4 +52,14 @@
}
}
export const errorNotice = (message:string, error:Error|undefined = undefined):any => {
return {
title: 'Error',
class: 'is-danger',
message: message + (error ? '<br>' + error.message : ''),
asHtml: true,
onConfirm: () => {}
}
}
</script>