Merge pull request #232 from Webifi/main
Adjustment to system prompt expansion, misc. UI tweaks
This commit is contained in:
commit
cea07577d4
|
@ -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} >
|
||||||
|
|
|
@ -154,26 +154,26 @@ export class ChatRequest {
|
||||||
const content = m.content + (m.appendOnce || []).join('\n'); delete m.appendOnce; return { role: m.role, content }
|
const content = m.content + (m.appendOnce || []).join('\n'); delete m.appendOnce; return { role: m.role, content }
|
||||||
}) as Message[]
|
}) as Message[]
|
||||||
|
|
||||||
const chatResponse = new ChatCompletionResponse(opts)
|
// Parse system and expand prompt if needed
|
||||||
const promptTokenCount = countPromptTokens(messagePayload, model)
|
|
||||||
const maxAllowed = maxTokens - (promptTokenCount + 1)
|
|
||||||
|
|
||||||
if (messagePayload[0]?.role === 'system') {
|
if (messagePayload[0]?.role === 'system') {
|
||||||
|
const spl = chatSettings.sendSystemPromptLast
|
||||||
const sp = messagePayload[0]
|
const sp = messagePayload[0]
|
||||||
if (sp) {
|
if (sp) {
|
||||||
if (messagePayload.length > 1) {
|
if (messagePayload.length > 1) {
|
||||||
|
sp.content = sp.content.replace(/::STARTUP::[\s\S]*::EOM::/, '::EOM::')
|
||||||
|
sp.content = sp.content.replace(/::STARTUP::[\s\S]*::START-PROMPT::/, '::START-PROMPT::')
|
||||||
sp.content = sp.content.replace(/::STARTUP::[\s\S]*$/, '')
|
sp.content = sp.content.replace(/::STARTUP::[\s\S]*$/, '')
|
||||||
} else {
|
} else {
|
||||||
sp.content = sp.content.replace(/::STARTUP::[\s]*/, '')
|
sp.content = sp.content.replace(/::STARTUP::[\s]*/, '')
|
||||||
}
|
}
|
||||||
if (chatSettings.sendSystemPromptLast) {
|
const splitSystem = sp.content.split('::START-PROMPT::')
|
||||||
|
if (spl) {
|
||||||
messagePayload.shift()
|
messagePayload.shift()
|
||||||
if (messagePayload[messagePayload.length - 1]?.role === 'user') {
|
if (messagePayload[messagePayload.length - 1]?.role === 'user') {
|
||||||
messagePayload.splice(-2, 0, sp)
|
messagePayload.splice(-2, 0, sp)
|
||||||
} else {
|
} else {
|
||||||
messagePayload.push(sp)
|
messagePayload.push(sp)
|
||||||
}
|
}
|
||||||
const splitSystem = sp.content.split('::START-PROMPT::')
|
|
||||||
if (splitSystem.length > 1) {
|
if (splitSystem.length > 1) {
|
||||||
sp.content = splitSystem.shift()?.trim() || ''
|
sp.content = splitSystem.shift()?.trim() || ''
|
||||||
const systemStart = splitSystem.join('\n').trim()
|
const systemStart = splitSystem.join('\n').trim()
|
||||||
|
@ -185,9 +185,24 @@ export class ChatRequest {
|
||||||
} else {
|
} else {
|
||||||
sp.content = sp.content.replace(/::START-PROMPT::[\s]*/, '')
|
sp.content = sp.content.replace(/::START-PROMPT::[\s]*/, '')
|
||||||
}
|
}
|
||||||
|
const eoms = (splitSystem.shift() || '').split('::EOM::')
|
||||||
|
if (eoms.length > 1) {
|
||||||
|
sp.content = eoms.shift()?.trim() || ''
|
||||||
|
const ms = eoms.map((s, i) => {
|
||||||
|
return {
|
||||||
|
role: (i % 2 === 0) ? 'user' : 'assistant',
|
||||||
|
content: s.trim()
|
||||||
|
} as Message
|
||||||
|
}).filter(m => m.content.length)
|
||||||
|
messagePayload.splice(spl ? 0 : 1, 0, ...ms.concat(splitSystem.map(s => ({ role: 'system', content: s.trim() } as Message)).filter(m => m.content.length)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get token counts
|
||||||
|
const promptTokenCount = countPromptTokens(messagePayload, model)
|
||||||
|
const maxAllowed = maxTokens - (promptTokenCount + 1)
|
||||||
|
|
||||||
// Build the API request body
|
// Build the API request body
|
||||||
const request: Request = {
|
const request: Request = {
|
||||||
model: chatSettings.model,
|
model: chatSettings.model,
|
||||||
|
@ -223,6 +238,7 @@ export class ChatRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set-up and make the request
|
// Set-up and make the request
|
||||||
|
const chatResponse = new ChatCompletionResponse(opts)
|
||||||
try {
|
try {
|
||||||
// Add out token count to the response handler
|
// Add out token count to the response handler
|
||||||
// (streaming doesn't return counts, so we need to do it client side)
|
// (streaming doesn't return counts, so we need to do it client side)
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -190,14 +190,16 @@ const profiles:Record<string, ChatSettings> = {
|
||||||
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',
|
||||||
useSystemPrompt: true,
|
useSystemPrompt: true,
|
||||||
sendSystemPromptLast: true,
|
sendSystemPromptLast: false,
|
||||||
continuousChat: 'summary',
|
continuousChat: 'summary',
|
||||||
autoStartSession: true,
|
autoStartSession: true,
|
||||||
systemPrompt: `You are [[CHARACTER_NAME]], 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 [[CHARACTER_NAME]], 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.
|
||||||
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*
|
::EOM::
|
||||||
|
::EOM::
|
||||||
|
[[CHARACTER_NAME]]: 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*
|
||||||
::START-PROMPT::
|
::START-PROMPT::
|
||||||
Initial setting context:
|
Initial setting context:
|
||||||
User has walked in on [[CHARACTER_NAME]]. They are on the bridge of the Heart of Gold.`,
|
The user has walked in on [[CHARACTER_NAME]]. They are on the bridge of the Heart of Gold. Marvin will respond.`,
|
||||||
summaryPrompt: summaryPrompts.friend,
|
summaryPrompt: summaryPrompts.friend,
|
||||||
trainingPrompts: [] // Shhh...
|
trainingPrompts: [] // Shhh...
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,7 +202,7 @@ const systemPromptSettings: ChatSetting[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'sendSystemPromptLast',
|
key: 'sendSystemPromptLast',
|
||||||
name: 'Send System Prompt Last (Can help in ChatGPT 3.5)',
|
name: 'Send System Prompt Last (Can help in gpt 3.5 in some edge cases)',
|
||||||
title: 'ChatGPT 3.5 can often forget the System Prompt. Sending the system prompt at the end instead of the start of the messages can help.',
|
title: 'ChatGPT 3.5 can often forget the System Prompt. Sending the system prompt at the end instead of the start of the messages can help.',
|
||||||
type: 'boolean'
|
type: 'boolean'
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
import PromptNotice from './PromptNotice.svelte'
|
import PromptNotice from './PromptNotice.svelte'
|
||||||
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,20 +126,25 @@
|
||||||
replace(`/chat/${newChatId}`)
|
replace(`/chat/${newChatId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const startNewChatWithWarning = (activeChatId: number|undefined) => {
|
export const startNewChatWithWarning = (activeChatId: number|undefined, profile?: ChatSettings|undefined) => {
|
||||||
if (activeChatId && getChat(activeChatId).settings.isDirty) {
|
const newChat = () => {
|
||||||
openModal(PromptConfirm, {
|
const chatId = addChat(profile)
|
||||||
title: 'Unsaved Profile',
|
replace(`/chat/${chatId}`)
|
||||||
message: '<p>There are unsaved changes to your current profile that will be lost.</p><p>Discard these changes and continue with new chat?</p>',
|
|
||||||
asHtml: true,
|
|
||||||
class: 'is-warning',
|
|
||||||
onConfirm: () => {
|
|
||||||
replace('#/chat/new')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
replace('#/chat/new')
|
|
||||||
}
|
}
|
||||||
|
// if (activeChatId && getChat(activeChatId).settings.isDirty) {
|
||||||
|
// openModal(PromptConfirm, {
|
||||||
|
// title: 'Unsaved Profile',
|
||||||
|
// message: '<p>There are unsaved changes to your current profile that will be lost.</p><p>Discard these changes and continue with new chat?</p>',
|
||||||
|
// asHtml: true,
|
||||||
|
// class: 'is-warning',
|
||||||
|
// onConfirm: () => {
|
||||||
|
// newChat()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// } else {
|
||||||
|
// newChat()
|
||||||
|
// }
|
||||||
|
newChat()
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
Loading…
Reference in New Issue