diff --git a/preloader/node-llama-cpp-preloader.js b/preloader/node-llama-cpp-preloader.js index 0b8599b..ceae26f 100644 --- a/preloader/node-llama-cpp-preloader.js +++ b/preloader/node-llama-cpp-preloader.js @@ -70,9 +70,11 @@ function findMessageText(message) { if(typeof message === "string") return message; else if(typeof message === "object") { if(Array.isArray(message)) { - message = message.pop(); - if(typeof message === 'object' && message.content) { - return message.content; + while(message.length) { + message = message.pop(); + if(typeof message === 'object' && message.role && message.role === 'user' && message.content) { + return message.content; + } } } } @@ -97,10 +99,16 @@ function updateModelSettings(settings) { * @returns {Promise} the response text */ async function chatCompletions(latest_message, cb=null) { - if(!llama_session) await loadModel(); + const {max_tokens, top_p, temperature, llama_reset_everytime} = model_settings; + if(!llama_session) { + cb && cb('> **ERROR: MODEL NOT LOADED**', true); + return ''; + } latest_message = findMessageText(latest_message); + if(llama_reset_everytime) { + setClient(null, llama_session.getChatHistory().filter(({type})=>type === 'system')) + } - const {max_tokens, top_p, temperature} = model_settings; stop_signal = new AbortController(); const options = { diff --git a/src/components/chat/Bubbles.jsx b/src/components/chat/Bubbles.jsx index a816b8e..2a6036d 100644 --- a/src/components/chat/Bubbles.jsx +++ b/src/components/chat/Bubbles.jsx @@ -15,7 +15,7 @@ export default function Bubbles({ conversation, pending_message }) { return (
- { conversation.map(({role, content}, idx) => { + { conversation.filter(({role})=>/^(user|assistant)$/.test(role)).map(({role, content}, idx) => { return (
{chat.uid?<> - + { @@ -12,7 +12,7 @@ export default function DeleteConfirm({showConfirm, deleteHistory, resetRequestD }, [showConfirm]) return ( - +
Delete {conv_to_delete && conv_to_delete.title}?
diff --git a/src/components/chat/Tickets.jsx b/src/components/chat/Tickets.jsx index 95a5b3a..bfaf5f4 100644 --- a/src/components/chat/Tickets.jsx +++ b/src/components/chat/Tickets.jsx @@ -21,7 +21,8 @@ export default function Tickets({selectChat, current_chat, history, setHistory, createdAt: timestamp, updatedAt: timestamp, uid: genRandomID(), - platform + platform, + 'system-instruction': '' } ) const new_conv_info = await idb.getByID('chat-history', conv_id); diff --git a/src/components/chat/TitleBar.jsx b/src/components/chat/TitleBar.jsx index b36c870..29b3c33 100644 --- a/src/components/chat/TitleBar.jsx +++ b/src/components/chat/TitleBar.jsx @@ -1,13 +1,16 @@ import { useEffect, useRef, useState } from "react"; -import { PencilFill } from "react-bootstrap-icons"; +import { ChatRightText, PencilFill, Save } from "react-bootstrap-icons"; import { XCircle } from "react-bootstrap-icons"; import { CheckCircle } from "react-bootstrap-icons"; -export default function TitleBar({current_title, updateTitle}) { +export default function TitleBar({current_title, updateTitle, current_instruction, updateSystemInstruction}) { const [title, setTitle] = useState(current_title); const [is_editing, toggleEditTitle] = useState(false); + const [system_instruction, setSystemInstruction] = useState(current_instruction || ''); + const [is_editing_si, toggleEditSI] = useState(false); const inputRef = useRef(null); + const systemInstructionDialogRef = useRef(); function submitUpdateTitle() { if(is_editing && title !== current_title) { @@ -16,6 +19,18 @@ export default function TitleBar({current_title, updateTitle}) { toggleEditTitle(false); } + function submitSystemInstruction() { + is_editing_si && + system_instruction !== current_instruction && + updateSystemInstruction(system_instruction) + + toggleEditSI(false) + } + + useEffect(()=>{ + setSystemInstruction(current_instruction); + }, [current_instruction]) + useEffect(()=>{ setTitle(current_title); }, [current_title]) @@ -27,20 +42,42 @@ export default function TitleBar({current_title, updateTitle}) { } }, [is_editing]) + useEffect(()=>{ + if(is_editing_si) { + systemInstructionDialogRef.current.showModal(); + } else { + systemInstructionDialogRef.current.close(); + } + }, [is_editing_si]) + return (
{ is_editing ? -
{evt.preventDefault(); submitUpdateTitle()}}> + {evt.preventDefault(); submitUpdateTitle()}}> setTitle(evt.target.value)} /> {setTitle(current_title); toggleEditTitle(false)}} /> : -
toggleEditTitle(true)}> -
{ current_title }
- +
+
toggleEditTitle(true)}> +
{ current_title }
+ +
+ toggleEditSI(true)} /> +
} + toggleEditSI(false)}> +
{evt.preventDefault(); submitSystemInstruction()}}> +
+ Set your system instruction for this conversation here: +
+ setSystemInstruction(evt.target.value)} /> +
Update System Instruction
+
toggleEditSI(false)}>Cancel
+
+
) } \ No newline at end of file diff --git a/src/components/chat/index.jsx b/src/components/chat/index.jsx index dd969fc..3d121ee 100644 --- a/src/components/chat/index.jsx +++ b/src/components/chat/index.jsx @@ -94,20 +94,63 @@ export default function Chat() { resetRequestDelete(); } - async function updateTitle(title) { - await idb.updateOne("chat-history", {title}, [{uid: chat.uid}]) + async function attributeUpdater(name, value) { + await idb.updateOne("chat-history", {[name]: value}, [{uid: chat.uid}]) selectChat({ - ...chat, title: title + ...chat, [name]: value }) let history_cp = [...tickets]; history_cp[ history_cp.findIndex(e=>e.uid === chat.uid) - ].title = title; + ][name] = value; setTickets(history_cp); } + async function updateTitle(title) { + await attributeUpdater('title', title) + } + + async function updateSystemInstruction(instruction) { + if(!chat['system-instruction']) { + await idb.insert('messages', { + role: 'system', + content: instruction, + 'history-uid': chat.uid, + createdAt: Date.now() + }) + } else { + await idb.updateOne('messages', { + content: instruction + }, [{'history-uid': chat.uid, role: 'system'}]) + } + switchConversation(); + await attributeUpdater('system-instruction', instruction) + } + + function switchConversation() { + let message_history = null; + // update messages + idb.getAll( + 'messages', + { + where: [{'history-uid': chat.uid}], + select: ['role', 'content'] + } + ).then(messages=>{ + message_history = messages; + setChatHistory(messages) + }).finally(()=>{ + const ss = getCompletionFunctions(chat.platform); + const client = ss.initClient(chat.client || null, message_history) + if(!chat.client) { + updateChatClient(client) + } + setSessionSetting(ss); + }) + } + useEffect(()=>{ conv_to_delete && toggleConfirm(true); }, [conv_to_delete]) @@ -115,25 +158,7 @@ export default function Chat() { useEffect(()=>{ if(chat.uid && chat.uid !== current_uid) { setCurrentUid(chat.uid); - let message_history = null; - // update messages - idb.getAll( - 'messages', - { - where: [{'history-uid': chat.uid}], - select: ['role', 'content'] - } - ).then(messages=>{ - message_history = messages; - setChatHistory(messages) - }).finally(()=>{ - const ss = getCompletionFunctions(chat.platform); - const client = ss.initClient(chat.client || null, message_history) - if(!chat.client) { - updateChatClient(client) - } - setSessionSetting(ss); - }) + switchConversation(); } // eslint-disable-next-line }, [chat]) @@ -150,10 +175,11 @@ export default function Chat() { updateTitle={updateTitle} chat={chat} chat_history={chat_history} pending_message={pending_message} abort={session_setting.abort} - sendMessage={sendMessage} + sendMessage={sendMessage} updateSystemInstruction={updateSystemInstruction} /> toggleConfirm(false)} deleteHistory={deleteHistory} resetRequestDelete={resetRequestDelete} conv_to_delete={conv_to_delete} diff --git a/src/components/settings/LlamaSettings.jsx b/src/components/settings/LlamaSettings.jsx index ddcc04b..763ed3d 100644 --- a/src/components/settings/LlamaSettings.jsx +++ b/src/components/settings/LlamaSettings.jsx @@ -9,6 +9,7 @@ import { DEFAULT_LLAMA_CPP_MODEL_URL } from "../../utils/types"; export default function LlamaSettings({ trigger, enabled, updateEnabled, openDownloadProtector, updateState }) { const [model_download_link, setModelDownloadLink] = useState(''); + const [reset_everytime, setResetEveryTime] = useState(false); const idb = useIDB(); async function saveSettings() { @@ -17,7 +18,8 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow if(url !== model_download_link) setModelDownloadLink(url) updatePlatformSettings({ - llama_model_url: url + llama_model_url: url, + llama_reset_everytime: reset_everytime }) if(enabled) { // check if model with this url already downloaded @@ -53,8 +55,9 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow }, [trigger]) useEffect(()=>{ - const { llama_model_url } = getPlatformSettings(); + const { llama_model_url, llama_reset_everytime } = getPlatformSettings(); setModelDownloadLink(llama_model_url || DEFAULT_LLAMA_CPP_MODEL_URL); + setResetEveryTime(llama_reset_everytime); }, []) return ( @@ -70,6 +73,11 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow placeholder={"Default model is Microsoft Phi-3"} value={model_download_link} cb={setModelDownloadLink} /> + ) } \ No newline at end of file diff --git a/src/components/settings/ModelSettings.jsx b/src/components/settings/ModelSettings.jsx index afbaafa..45b66d1 100644 --- a/src/components/settings/ModelSettings.jsx +++ b/src/components/settings/ModelSettings.jsx @@ -12,7 +12,6 @@ export default function ModelSettings({ trigger, updateState }) { function saveSettings() { let validate_max_token = max_tokens; - console.log(max_tokens) if(max_tokens < MIN_TOKENS && max_tokens !== 0) validate_max_token = MIN_TOKENS; updateModelSettings({ max_tokens: validate_max_token, top_p, temperature diff --git a/src/components/settings/components/PasswordComponent.jsx b/src/components/settings/components/PasswordComponent.jsx index f410b2b..8369341 100644 --- a/src/components/settings/components/PasswordComponent.jsx +++ b/src/components/settings/components/PasswordComponent.jsx @@ -13,7 +13,7 @@ export default function PasswordComponent({ cb, value, disabled, title, descript cb(evt.target.value)} - disabled={disabled} + disabled={disabled} onClick={evt=>evt.target.select()} />
toggleShowPassword(!show_password)}> { diff --git a/src/components/settings/components/ScrollBarComponent.jsx b/src/components/settings/components/ScrollBarComponent.jsx index fa623f5..6c60fd4 100644 --- a/src/components/settings/components/ScrollBarComponent.jsx +++ b/src/components/settings/components/ScrollBarComponent.jsx @@ -46,7 +46,11 @@ export default function ScrollBarComponent({ cb, value, disabled, title, descrip min={(times_10 ? 10 : 1) * min} max={(times_10 ? 10 : 1) * max} disabled={disabled} /> - setValue(evt.target.value, false)} /> + setValue(evt.target.value, false)} + onClick={evt=>evt.target.select()} + />
) diff --git a/src/components/settings/components/TextComponent.jsx b/src/components/settings/components/TextComponent.jsx index 8eaa7bb..ac04080 100644 --- a/src/components/settings/components/TextComponent.jsx +++ b/src/components/settings/components/TextComponent.jsx @@ -7,7 +7,7 @@ export default function TextComponent({ cb, value, disabled, title, description, type={type || "text"} value={value} disabled={disabled} placeholder={placeholder || ''} onInput={(evt)=>cb(evt.target.value)} - className="main-part" + className="main-part" onClick={evt=>evt.target.select()} />
) diff --git a/src/styles/chat.css b/src/styles/chat.css index 1450f11..7b4ad5e 100644 --- a/src/styles/chat.css +++ b/src/styles/chat.css @@ -157,11 +157,25 @@ --elem-height: calc(var(--title-bar-height) - 14px); } +.chat > .conversation-main > .title-bar > .normal-mode { + display: flex; + align-items: center; +} + .chat > .conversation-main > .title-bar .display-title { width: fit-content; height: 100%; display: flex; align-items: center; + flex-basis: calc(100% - 50px); +} + +.chat > .conversation-main > .title-bar > .normal-mode > .icon.clickable { + --size: 17px; + width: var(--size); + height: var(--size); + flex-basis: var(--size); + margin: auto; } .chat > .conversation-main > .title-bar .display-title > .edit-icon { @@ -172,6 +186,43 @@ display: flex; } +dialog.system-instruction { + border: none; + border-radius: 20px; + padding: 20px 30px; + background-color: white; +} + +dialog.system-instruction > form { + display: block; + position: relative; +} + +.system-instruction > form > .title { + font-size: 18px; +} + +.system-instruction > form > input[type="text"] { + width: 100%; + height: 40px; + border: 1px solid gray; + border-radius: 7px; + padding: 0px 10px; + margin-top: 10px; +} + + +.system-instruction > form > .btn { + width: fit-content; + text-align: center; + margin: auto; + margin-top: 10px; + color: rgb(70, 70, 70); +} +.system-instruction > form > .btn:hover { + color: black; +} + .chat > .conversation-main > .title-bar .edit-title { border: none; background-color: transparent; @@ -182,7 +233,7 @@ height: var(--elem-height); } -.chat > .conversation-main > .title-bar .btn { +.chat > .conversation-main > .title-bar > .edit-mode > .btn { height: var(--elem-height); width: var(--elem-height); margin-left: 15px; diff --git a/src/utils/general_settings.js b/src/utils/general_settings.js index e116dc0..73db825 100644 --- a/src/utils/general_settings.js +++ b/src/utils/general_settings.js @@ -10,6 +10,7 @@ const PLATFORM_SETTINGS_KEY = 'platform-settings' * @property {Number} wllama_context_length * @property {Boolean} wllama_continue_conv * @property {String} llama_model_url + * @property {Boolean} llama_reset_everytime */ const DEFAULT_PLATFORM_SETTINGS = { enabled_platform: null, @@ -23,7 +24,8 @@ const DEFAULT_PLATFORM_SETTINGS = { wllama_context_length: 4096, wllama_continue_conv: false, // llama - llama_model_url: '' + llama_model_url: '', + llama_reset_everytime: false } const MODEL_SETTINGS_KEY = 'general-model-settings' diff --git a/src/utils/idb/settings.js b/src/utils/idb/settings.js index 141b661..bc59101 100644 --- a/src/utils/idb/settings.js +++ b/src/utils/idb/settings.js @@ -51,4 +51,11 @@ export const versions = [ ] } }, + { + 'chat-history': { + columns: [ + { name: 'system-instruction' }, + ] + } + } ] \ No newline at end of file diff --git a/src/utils/workers/aws-worker.js b/src/utils/workers/aws-worker.js index 6f6e85e..ed9e418 100644 --- a/src/utils/workers/aws-worker.js +++ b/src/utils/workers/aws-worker.js @@ -119,7 +119,7 @@ export async function chatCompletions(messages, cb = null) { const { role } = message; if(role === 'system') { - system.push(message); + system.push(message.content[0]); } else { normal_messages.push(message); } diff --git a/src/utils/workers/index.js b/src/utils/workers/index.js index a994429..dc8cd21 100644 --- a/src/utils/workers/index.js +++ b/src/utils/workers/index.js @@ -17,7 +17,8 @@ import { chatCompletions as OpenaiCompletions, abortCompletion as OpenaiAbort, s * @returns {CompletionFunctions} */ export function getCompletionFunctions(platform = null) { - platform = platform || getPlatformSettings().enabled_platform; + const { enabled_platform, llama_reset_everytime } = getPlatformSettings(); + platform = platform || enabled_platform; switch(platform) { case 'AWS': @@ -34,7 +35,7 @@ export function getCompletionFunctions(platform = null) { initClient: OpenAISetClient } case 'Llama': - window['node-llama-cpp'].updateModelSettings(getModelSettings()) + window['node-llama-cpp'].updateModelSettings({...getModelSettings(), llama_reset_everytime}) return { completions: window['node-llama-cpp'].chatCompletions, abort: window['node-llama-cpp'].abortCompletion,