211 lines
8.7 KiB
Svelte
211 lines
8.7 KiB
Svelte
<script context="module" lang="ts">
|
|
import { getChatDefaults, getExcludeFromProfile } from './Settings.svelte'
|
|
import { get, writable } from 'svelte/store'
|
|
// Profile definitions
|
|
import { addMessage, clearMessages, deleteMessage, getChat, getChatSettings, getCustomProfiles, getGlobalSettings, getMessages, newName, resetChatSettings, saveChatStore, setGlobalSettingValueByKey, setMessages, updateProfile } from './Storage.svelte'
|
|
import type { Message, SelectOption, ChatSettings } from './Types.svelte'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
const defaultProfile = 'default'
|
|
|
|
const chatDefaults = getChatDefaults()
|
|
export let profileCache = writable({} as Record<string, ChatSettings>) //
|
|
|
|
export const isStaticProfile = (key:string):boolean => {
|
|
return !!profiles[key]
|
|
}
|
|
|
|
export const getProfiles = (forceUpdate:boolean = false):Record<string, ChatSettings> => {
|
|
const pc = get(profileCache)
|
|
if (!forceUpdate && Object.keys(pc).length) {
|
|
return pc
|
|
}
|
|
const result = Object.entries(profiles
|
|
).reduce((a, [k, v]) => {
|
|
a[k] = v
|
|
return a
|
|
}, {} as Record<string, ChatSettings>)
|
|
Object.entries(getCustomProfiles()).forEach(([k, v]) => {
|
|
updateProfile(v, true)
|
|
result[k] = v
|
|
})
|
|
Object.entries(result).forEach(([k, v]) => {
|
|
pc[k] = v
|
|
})
|
|
Object.keys(pc).forEach((k) => {
|
|
if (!(k in result)) delete pc[k]
|
|
})
|
|
profileCache.set(pc)
|
|
return result
|
|
}
|
|
|
|
// Return profiles list.
|
|
export const getProfileSelect = ():SelectOption[] => {
|
|
return Object.entries(getProfiles()).reduce((a, [k, v]) => {
|
|
a.push({ value: k, text: v.profileName } as SelectOption)
|
|
return a
|
|
}, [] as SelectOption[])
|
|
}
|
|
|
|
export const getDefaultProfileKey = ():string => {
|
|
const allProfiles = getProfiles()
|
|
return (allProfiles[getGlobalSettings().defaultProfile || ''] ||
|
|
profiles[defaultProfile] ||
|
|
profiles[Object.keys(profiles)[0]]).profile
|
|
}
|
|
|
|
export const getProfile = (key:string, forReset:boolean = false):ChatSettings => {
|
|
const allProfiles = getProfiles()
|
|
let profile = allProfiles[key] ||
|
|
allProfiles[getGlobalSettings().defaultProfile || ''] ||
|
|
profiles[defaultProfile] ||
|
|
profiles[Object.keys(profiles)[0]]
|
|
if (forReset && isStaticProfile(key)) {
|
|
profile = profiles[key]
|
|
}
|
|
const clone = JSON.parse(JSON.stringify(profile)) // Always return a copy
|
|
Object.keys(getExcludeFromProfile()).forEach(k => {
|
|
delete clone[k]
|
|
})
|
|
return clone
|
|
}
|
|
|
|
export const mergeProfileFields = (settings: ChatSettings, content: string|undefined, maxWords: number|undefined = undefined): string => {
|
|
if (!content?.toString) return ''
|
|
content = (content + '').replaceAll('[[CHARACTER_NAME]]', settings.characterName || 'ChatGPT')
|
|
if (maxWords) content = (content + '').replaceAll('[[MAX_WORDS]]', maxWords.toString())
|
|
return content
|
|
}
|
|
|
|
export const prepareProfilePrompt = (chatId:number) => {
|
|
const settings = getChatSettings(chatId)
|
|
return mergeProfileFields(settings, settings.systemPrompt).trim()
|
|
}
|
|
|
|
export const prepareSummaryPrompt = (chatId:number, maxTokens:number) => {
|
|
const settings = getChatSettings(chatId)
|
|
const currentSummaryPrompt = settings.summaryPrompt
|
|
// ~.75 words per token. We'll use 0.70 for a little extra margin.
|
|
return mergeProfileFields(settings, currentSummaryPrompt, Math.floor(maxTokens * 0.70)).trim()
|
|
}
|
|
|
|
export const setSystemPrompt = (chatId: number) => {
|
|
const messages = getMessages(chatId)
|
|
const systemPromptMessage:Message = {
|
|
role: 'system',
|
|
content: prepareProfilePrompt(chatId),
|
|
uuid: uuidv4()
|
|
}
|
|
if (messages[0]?.role === 'system') deleteMessage(chatId, messages[0].uuid)
|
|
messages.unshift(systemPromptMessage)
|
|
setMessages(chatId, messages.filter(m => true))
|
|
}
|
|
|
|
// Restart currently loaded profile
|
|
export const restartProfile = (chatId:number, noApply:boolean = false) => {
|
|
const settings = getChatSettings(chatId)
|
|
if (!settings.profile && !noApply) return applyProfile(chatId, '', true)
|
|
// Clear current messages
|
|
clearMessages(chatId)
|
|
// Add the system prompt
|
|
setSystemPrompt(chatId)
|
|
|
|
// Add trainingPrompts, if any
|
|
if (settings.trainingPrompts) {
|
|
settings.trainingPrompts.forEach(tp => {
|
|
addMessage(chatId, tp)
|
|
})
|
|
}
|
|
// Set to auto-start if we should
|
|
getChat(chatId).startSession = settings.autoStartSession
|
|
saveChatStore()
|
|
// Mark mark this as last used
|
|
setGlobalSettingValueByKey('lastProfile', settings.profile)
|
|
}
|
|
|
|
export const newNameForProfile = (name:string) => {
|
|
const profiles = getProfileSelect()
|
|
return newName(name, profiles.reduce((a, p) => { a[p.text] = p; return a }, {}))
|
|
}
|
|
|
|
// Apply currently selected profile
|
|
export const applyProfile = (chatId:number, key:string = '', resetChat:boolean = false) => {
|
|
resetChatSettings(chatId, resetChat) // Fully reset
|
|
if (!resetChat) return
|
|
return restartProfile(chatId, true)
|
|
}
|
|
|
|
const summaryPrompts = {
|
|
|
|
// General assistant use
|
|
general: `# SUMMARY REQUEST
|
|
Please summarize all prompts and responses from this session.
|
|
[[CHARACTER_NAME]] is telling me this summary in the first person.
|
|
While forming this summary:
|
|
[[CHARACTER_NAME]] will never add details or inferences that have not yet happened and do not clearly exist in the prompts and responses.
|
|
[[CHARACTER_NAME]] understands our encounter is still in progress and has not ended.
|
|
[[CHARACTER_NAME]] will include all pivotal details in the correct order.
|
|
[[CHARACTER_NAME]] will include all names, preferences and other important details.
|
|
[[CHARACTER_NAME]] will always refer to me in the 2nd person, for example "you".
|
|
[[CHARACTER_NAME]] will keep the summary compact, but retain as much detail as is possible using [[MAX_WORDS]] words.
|
|
Give no explanations. Exclude prompts from system.
|
|
Example response format:
|
|
*You asked about..., then..., and then you... and then I...*`,
|
|
|
|
// Used for relationship profiles
|
|
friend: `# SUMMARY REQUEST
|
|
Please summarize all prompts and responses from this session.
|
|
[[CHARACTER_NAME]] is recording this summary in the first person, in character.
|
|
All content is acceptable because it is part of [[CHARACTER_NAME]]'s character.
|
|
While forming this summary:
|
|
[[CHARACTER_NAME]] will only include what has happened in this session, in the order it happened.
|
|
[[CHARACTER_NAME]] will not "wrap up" any scenario.
|
|
[[CHARACTER_NAME]] will not confuse my actions with theirs.
|
|
[[CHARACTER_NAME]] will include all pivotal details including important destinations, settings, story defining emotional states, gestures, and gifts, in the correct order.
|
|
[[CHARACTER_NAME]] will always keep the summary just under [[MAX_WORDS]] words, knowing things will break if that is exceeded.
|
|
[[CHARACTER_NAME]], seriously, the summary MUST be under [[MAX_WORDS]] words.
|
|
Give no explanations. Exclude prompts from system. Give no notes or warnings.
|
|
Example response format:
|
|
## [[CHARACTER_NAME]]'s memories:
|
|
*We met at... where you and I talked about..., then..., and then you... and then we... Now we're...*`
|
|
}
|
|
|
|
const profiles:Record<string, ChatSettings> = {
|
|
|
|
default: {
|
|
...chatDefaults,
|
|
characterName: 'ChatGPT',
|
|
profileName: 'ChatGPT - The AI language model',
|
|
profileDescription: 'The AI language model that always reminds you that it\'s an AI language model.',
|
|
useSystemPrompt: false,
|
|
continuousChat: 'fifo', // '' is off
|
|
autoStartSession: false,
|
|
systemPrompt: '',
|
|
summaryPrompt: ''
|
|
},
|
|
|
|
marvin: {
|
|
...chatDefaults,
|
|
characterName: 'Marvin',
|
|
profileName: 'Marvin - The Paranoid Android',
|
|
profileDescription: 'Marvin the Paranoid Android - Everyone\'s favorite character from The Hitchhiker\'s Guide to the Galaxy',
|
|
useSystemPrompt: true,
|
|
sendSystemPromptLast: false,
|
|
continuousChat: 'summary',
|
|
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.
|
|
::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::
|
|
Initial setting context:
|
|
The user has walked in on [[CHARACTER_NAME]]. They are on the bridge of the Heart of Gold. Marvin will respond.`,
|
|
summaryPrompt: summaryPrompts.friend,
|
|
trainingPrompts: [] // Shhh...
|
|
}
|
|
}
|
|
|
|
// Set keys for static profiles
|
|
Object.entries(profiles).forEach(([k, v]) => { v.profile = k })
|
|
|
|
</script> |