Skip to content

Commit

Permalink
add improvements to chat functions (#68)
Browse files Browse the repository at this point in the history
* retrieve only user message to avoid getting system prompt

Signed-off-by: cbh778899 <[email protected]>

* display only user and assistant messages

Signed-off-by: cbh778899 <[email protected]>

* add system instructions

Signed-off-by: cbh778899 <[email protected]>

* add close event

Signed-off-by: cbh778899 <[email protected]>

* add empty system instruction when start new chat

Signed-off-by: cbh778899 <[email protected]>

* add reset_everytime

Signed-off-by: cbh778899 <[email protected]>

* retrieve only content[0] for system prompt to fit bedrock request

Signed-off-by: cbh778899 <[email protected]>

* remove unused log

Signed-off-by: cbh778899 <[email protected]>

* select all when click on input component

Signed-off-by: cbh778899 <[email protected]>

* update style

Signed-off-by: cbh778899 <[email protected]>

* implement llama_reset_everytime

Signed-off-by: cbh778899 <[email protected]>

---------

Signed-off-by: cbh778899 <[email protected]>
  • Loading branch information
cbh778899 authored Oct 20, 2024
1 parent c8113d2 commit 7c12b86
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 52 deletions.
18 changes: 13 additions & 5 deletions preloader/node-llama-cpp-preloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}
Expand All @@ -97,10 +99,16 @@ function updateModelSettings(settings) {
* @returns {Promise<String>} 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 = {
Expand Down
2 changes: 1 addition & 1 deletion src/components/chat/Bubbles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function Bubbles({ conversation, pending_message }) {

return (
<div className="bubbles" ref={mainRef}>
{ conversation.map(({role, content}, idx) => {
{ conversation.filter(({role})=>/^(user|assistant)$/.test(role)).map(({role, content}, idx) => {
return (
<ConversationBubble
key={`conversation-history-${idx}`}
Expand Down
9 changes: 7 additions & 2 deletions src/components/chat/ChatPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import UserMessage from "./UserMessage";

export default function ChatPage({
chat, chat_history, updateTitle,
sendMessage, pending_message, abort
sendMessage, pending_message, abort,
updateSystemInstruction
}) {

return (
<>
<div className="conversation-main">{chat.uid?<>
<TitleBar current_title={chat.title} updateTitle={updateTitle} />
<TitleBar
current_title={chat.title} updateTitle={updateTitle}
updateSystemInstruction={updateSystemInstruction}
current_instruction={chat['system-instruction']}
/>
<Bubbles conversation={chat_history} pending_message={pending_message} />
<UserMessage
uid={chat.uid} enable_send={pending_message === null}
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat/DeleteConfirm.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { useRef } from "react";

export default function DeleteConfirm({showConfirm, deleteHistory, resetRequestDelete, conv_to_delete}) {
export default function DeleteConfirm({showConfirm, closeDialog, deleteHistory, resetRequestDelete, conv_to_delete}) {
const dialogRef = useRef(null);

useEffect(()=>{
Expand All @@ -12,7 +12,7 @@ export default function DeleteConfirm({showConfirm, deleteHistory, resetRequestD
}, [showConfirm])

return (
<dialog ref={dialogRef}>
<dialog ref={dialogRef} onClose={closeDialog}>
<div>
Delete <strong>{conv_to_delete && conv_to_delete.title}</strong>?
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/components/chat/Tickets.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
49 changes: 43 additions & 6 deletions src/components/chat/TitleBar.jsx
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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])
Expand All @@ -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 (
<div className="title-bar">
{
is_editing ?
<form onSubmit={evt=>{evt.preventDefault(); submitUpdateTitle()}}>
<form className="edit-mode" onSubmit={evt=>{evt.preventDefault(); submitUpdateTitle()}}>
<input className="edit-title" ref={inputRef} value={title} onChange={evt=>setTitle(evt.target.value)} />
<CheckCircle className="btn clickable" onClick={submitUpdateTitle} />
<XCircle className="btn clickable" onClick={()=>{setTitle(current_title); toggleEditTitle(false)}} />
</form>:
<div className="display-title clickable" onClick={()=>toggleEditTitle(true)}>
<div className="text">{ current_title }</div>
<PencilFill className="edit-icon" />
<div className="normal-mode">
<div className="display-title clickable" onClick={()=>toggleEditTitle(true)}>
<div className="text">{ current_title }</div>
<PencilFill className="edit-icon" />
</div>
<ChatRightText className="icon clickable" title="Set the system instruction" onClick={()=>toggleEditSI(true)} />
<Save className="icon clickable" title="Save history" />
</div>
}
<dialog className="system-instruction" ref={systemInstructionDialogRef} onClose={()=>toggleEditSI(false)}>
<form onSubmit={evt=>{evt.preventDefault(); submitSystemInstruction()}}>
<div className="title">
Set your system instruction for this conversation here:
</div>
<input type="text" placeholder="You are a helpful assistant." value={system_instruction} onChange={evt=>setSystemInstruction(evt.target.value)} />
<div className="btn clickable" onClick={submitSystemInstruction} >Update System Instruction</div>
<div className="btn clickable" onClick={()=>toggleEditSI(false)}>Cancel</div>
</form>
</dialog>
</div>
)
}
74 changes: 50 additions & 24 deletions src/components/chat/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,46 +94,71 @@ 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])

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])
Expand All @@ -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}
/>
<DeleteConfirm
showConfirm={show_delete_confirm}
closeDialog={()=>toggleConfirm(false)}
deleteHistory={deleteHistory}
resetRequestDelete={resetRequestDelete}
conv_to_delete={conv_to_delete}
Expand Down
12 changes: 10 additions & 2 deletions src/components/settings/LlamaSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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 (
Expand All @@ -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}
/>
<TrueFalseComponent
title={"Reset conversation when send new message"}
description={"Reset the conversation, only keeps the latest message and the system instruction, this is useful for many one-shot operations."}
value={reset_everytime} cb={setResetEveryTime}
/>
</SettingSection>
)
}
1 change: 0 additions & 1 deletion src/components/settings/ModelSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/components/settings/components/PasswordComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function PasswordComponent({ cb, value, disabled, title, descript
<input
type={show_password ? 'text' : 'password'} value={value}
placeholder={placeholder || ''} onInput={(evt)=>cb(evt.target.value)}
disabled={disabled}
disabled={disabled} onClick={evt=>evt.target.select()}
/>
<div className="controller clickable" onClick={()=>toggleShowPassword(!show_password)}>
{
Expand Down
6 changes: 5 additions & 1 deletion src/components/settings/components/ScrollBarComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
/>
<input type="text" value={textValue} onInput={evt=>setValue(evt.target.value, false)} />
<input
type="text" value={textValue}
onInput={evt=>setValue(evt.target.value, false)}
onClick={evt=>evt.target.select()}
/>
</div>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/settings/components/TextComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
/>
</div>
)
Expand Down
Loading

0 comments on commit 7c12b86

Please sign in to comment.