Skip to content

Commit 7c12b86

Browse files
authored
add improvements to chat functions (#68)
* 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]>
1 parent c8113d2 commit 7c12b86

17 files changed

+201
-52
lines changed

preloader/node-llama-cpp-preloader.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ function findMessageText(message) {
7070
if(typeof message === "string") return message;
7171
else if(typeof message === "object") {
7272
if(Array.isArray(message)) {
73-
message = message.pop();
74-
if(typeof message === 'object' && message.content) {
75-
return message.content;
73+
while(message.length) {
74+
message = message.pop();
75+
if(typeof message === 'object' && message.role && message.role === 'user' && message.content) {
76+
return message.content;
77+
}
7678
}
7779
}
7880
}
@@ -97,10 +99,16 @@ function updateModelSettings(settings) {
9799
* @returns {Promise<String>} the response text
98100
*/
99101
async function chatCompletions(latest_message, cb=null) {
100-
if(!llama_session) await loadModel();
102+
const {max_tokens, top_p, temperature, llama_reset_everytime} = model_settings;
103+
if(!llama_session) {
104+
cb && cb('> **ERROR: MODEL NOT LOADED**', true);
105+
return '';
106+
}
101107
latest_message = findMessageText(latest_message);
108+
if(llama_reset_everytime) {
109+
setClient(null, llama_session.getChatHistory().filter(({type})=>type === 'system'))
110+
}
102111

103-
const {max_tokens, top_p, temperature} = model_settings;
104112

105113
stop_signal = new AbortController();
106114
const options = {

src/components/chat/Bubbles.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default function Bubbles({ conversation, pending_message }) {
1515

1616
return (
1717
<div className="bubbles" ref={mainRef}>
18-
{ conversation.map(({role, content}, idx) => {
18+
{ conversation.filter(({role})=>/^(user|assistant)$/.test(role)).map(({role, content}, idx) => {
1919
return (
2020
<ConversationBubble
2121
key={`conversation-history-${idx}`}

src/components/chat/ChatPage.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@ import UserMessage from "./UserMessage";
44

55
export default function ChatPage({
66
chat, chat_history, updateTitle,
7-
sendMessage, pending_message, abort
7+
sendMessage, pending_message, abort,
8+
updateSystemInstruction
89
}) {
910

1011
return (
1112
<>
1213
<div className="conversation-main">{chat.uid?<>
13-
<TitleBar current_title={chat.title} updateTitle={updateTitle} />
14+
<TitleBar
15+
current_title={chat.title} updateTitle={updateTitle}
16+
updateSystemInstruction={updateSystemInstruction}
17+
current_instruction={chat['system-instruction']}
18+
/>
1419
<Bubbles conversation={chat_history} pending_message={pending_message} />
1520
<UserMessage
1621
uid={chat.uid} enable_send={pending_message === null}

src/components/chat/DeleteConfirm.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect } from "react";
22
import { useRef } from "react";
33

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

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

1414
return (
15-
<dialog ref={dialogRef}>
15+
<dialog ref={dialogRef} onClose={closeDialog}>
1616
<div>
1717
Delete <strong>{conv_to_delete && conv_to_delete.title}</strong>?
1818
</div>

src/components/chat/Tickets.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export default function Tickets({selectChat, current_chat, history, setHistory,
2121
createdAt: timestamp,
2222
updatedAt: timestamp,
2323
uid: genRandomID(),
24-
platform
24+
platform,
25+
'system-instruction': ''
2526
}
2627
)
2728
const new_conv_info = await idb.getByID('chat-history', conv_id);

src/components/chat/TitleBar.jsx

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { useEffect, useRef, useState } from "react";
2-
import { PencilFill } from "react-bootstrap-icons";
2+
import { ChatRightText, PencilFill, Save } from "react-bootstrap-icons";
33
import { XCircle } from "react-bootstrap-icons";
44
import { CheckCircle } from "react-bootstrap-icons";
55

6-
export default function TitleBar({current_title, updateTitle}) {
6+
export default function TitleBar({current_title, updateTitle, current_instruction, updateSystemInstruction}) {
77
const [title, setTitle] = useState(current_title);
88
const [is_editing, toggleEditTitle] = useState(false);
9+
const [system_instruction, setSystemInstruction] = useState(current_instruction || '');
10+
const [is_editing_si, toggleEditSI] = useState(false);
911

1012
const inputRef = useRef(null);
13+
const systemInstructionDialogRef = useRef();
1114

1215
function submitUpdateTitle() {
1316
if(is_editing && title !== current_title) {
@@ -16,6 +19,18 @@ export default function TitleBar({current_title, updateTitle}) {
1619
toggleEditTitle(false);
1720
}
1821

22+
function submitSystemInstruction() {
23+
is_editing_si &&
24+
system_instruction !== current_instruction &&
25+
updateSystemInstruction(system_instruction)
26+
27+
toggleEditSI(false)
28+
}
29+
30+
useEffect(()=>{
31+
setSystemInstruction(current_instruction);
32+
}, [current_instruction])
33+
1934
useEffect(()=>{
2035
setTitle(current_title);
2136
}, [current_title])
@@ -27,20 +42,42 @@ export default function TitleBar({current_title, updateTitle}) {
2742
}
2843
}, [is_editing])
2944

45+
useEffect(()=>{
46+
if(is_editing_si) {
47+
systemInstructionDialogRef.current.showModal();
48+
} else {
49+
systemInstructionDialogRef.current.close();
50+
}
51+
}, [is_editing_si])
52+
3053
return (
3154
<div className="title-bar">
3255
{
3356
is_editing ?
34-
<form onSubmit={evt=>{evt.preventDefault(); submitUpdateTitle()}}>
57+
<form className="edit-mode" onSubmit={evt=>{evt.preventDefault(); submitUpdateTitle()}}>
3558
<input className="edit-title" ref={inputRef} value={title} onChange={evt=>setTitle(evt.target.value)} />
3659
<CheckCircle className="btn clickable" onClick={submitUpdateTitle} />
3760
<XCircle className="btn clickable" onClick={()=>{setTitle(current_title); toggleEditTitle(false)}} />
3861
</form>:
39-
<div className="display-title clickable" onClick={()=>toggleEditTitle(true)}>
40-
<div className="text">{ current_title }</div>
41-
<PencilFill className="edit-icon" />
62+
<div className="normal-mode">
63+
<div className="display-title clickable" onClick={()=>toggleEditTitle(true)}>
64+
<div className="text">{ current_title }</div>
65+
<PencilFill className="edit-icon" />
66+
</div>
67+
<ChatRightText className="icon clickable" title="Set the system instruction" onClick={()=>toggleEditSI(true)} />
68+
<Save className="icon clickable" title="Save history" />
4269
</div>
4370
}
71+
<dialog className="system-instruction" ref={systemInstructionDialogRef} onClose={()=>toggleEditSI(false)}>
72+
<form onSubmit={evt=>{evt.preventDefault(); submitSystemInstruction()}}>
73+
<div className="title">
74+
Set your system instruction for this conversation here:
75+
</div>
76+
<input type="text" placeholder="You are a helpful assistant." value={system_instruction} onChange={evt=>setSystemInstruction(evt.target.value)} />
77+
<div className="btn clickable" onClick={submitSystemInstruction} >Update System Instruction</div>
78+
<div className="btn clickable" onClick={()=>toggleEditSI(false)}>Cancel</div>
79+
</form>
80+
</dialog>
4481
</div>
4582
)
4683
}

src/components/chat/index.jsx

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -94,46 +94,71 @@ export default function Chat() {
9494
resetRequestDelete();
9595
}
9696

97-
async function updateTitle(title) {
98-
await idb.updateOne("chat-history", {title}, [{uid: chat.uid}])
97+
async function attributeUpdater(name, value) {
98+
await idb.updateOne("chat-history", {[name]: value}, [{uid: chat.uid}])
9999

100100
selectChat({
101-
...chat, title: title
101+
...chat, [name]: value
102102
})
103103

104104
let history_cp = [...tickets];
105105
history_cp[
106106
history_cp.findIndex(e=>e.uid === chat.uid)
107-
].title = title;
107+
][name] = value;
108108
setTickets(history_cp);
109109
}
110110

111+
async function updateTitle(title) {
112+
await attributeUpdater('title', title)
113+
}
114+
115+
async function updateSystemInstruction(instruction) {
116+
if(!chat['system-instruction']) {
117+
await idb.insert('messages', {
118+
role: 'system',
119+
content: instruction,
120+
'history-uid': chat.uid,
121+
createdAt: Date.now()
122+
})
123+
} else {
124+
await idb.updateOne('messages', {
125+
content: instruction
126+
}, [{'history-uid': chat.uid, role: 'system'}])
127+
}
128+
switchConversation();
129+
await attributeUpdater('system-instruction', instruction)
130+
}
131+
132+
function switchConversation() {
133+
let message_history = null;
134+
// update messages
135+
idb.getAll(
136+
'messages',
137+
{
138+
where: [{'history-uid': chat.uid}],
139+
select: ['role', 'content']
140+
}
141+
).then(messages=>{
142+
message_history = messages;
143+
setChatHistory(messages)
144+
}).finally(()=>{
145+
const ss = getCompletionFunctions(chat.platform);
146+
const client = ss.initClient(chat.client || null, message_history)
147+
if(!chat.client) {
148+
updateChatClient(client)
149+
}
150+
setSessionSetting(ss);
151+
})
152+
}
153+
111154
useEffect(()=>{
112155
conv_to_delete && toggleConfirm(true);
113156
}, [conv_to_delete])
114157

115158
useEffect(()=>{
116159
if(chat.uid && chat.uid !== current_uid) {
117160
setCurrentUid(chat.uid);
118-
let message_history = null;
119-
// update messages
120-
idb.getAll(
121-
'messages',
122-
{
123-
where: [{'history-uid': chat.uid}],
124-
select: ['role', 'content']
125-
}
126-
).then(messages=>{
127-
message_history = messages;
128-
setChatHistory(messages)
129-
}).finally(()=>{
130-
const ss = getCompletionFunctions(chat.platform);
131-
const client = ss.initClient(chat.client || null, message_history)
132-
if(!chat.client) {
133-
updateChatClient(client)
134-
}
135-
setSessionSetting(ss);
136-
})
161+
switchConversation();
137162
}
138163
// eslint-disable-next-line
139164
}, [chat])
@@ -150,10 +175,11 @@ export default function Chat() {
150175
updateTitle={updateTitle}
151176
chat={chat} chat_history={chat_history}
152177
pending_message={pending_message} abort={session_setting.abort}
153-
sendMessage={sendMessage}
178+
sendMessage={sendMessage} updateSystemInstruction={updateSystemInstruction}
154179
/>
155180
<DeleteConfirm
156181
showConfirm={show_delete_confirm}
182+
closeDialog={()=>toggleConfirm(false)}
157183
deleteHistory={deleteHistory}
158184
resetRequestDelete={resetRequestDelete}
159185
conv_to_delete={conv_to_delete}

src/components/settings/LlamaSettings.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { DEFAULT_LLAMA_CPP_MODEL_URL } from "../../utils/types";
99
export default function LlamaSettings({ trigger, enabled, updateEnabled, openDownloadProtector, updateState }) {
1010

1111
const [model_download_link, setModelDownloadLink] = useState('');
12+
const [reset_everytime, setResetEveryTime] = useState(false);
1213
const idb = useIDB();
1314

1415
async function saveSettings() {
@@ -17,7 +18,8 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow
1718
if(url !== model_download_link) setModelDownloadLink(url)
1819

1920
updatePlatformSettings({
20-
llama_model_url: url
21+
llama_model_url: url,
22+
llama_reset_everytime: reset_everytime
2123
})
2224
if(enabled) {
2325
// check if model with this url already downloaded
@@ -53,8 +55,9 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow
5355
}, [trigger])
5456

5557
useEffect(()=>{
56-
const { llama_model_url } = getPlatformSettings();
58+
const { llama_model_url, llama_reset_everytime } = getPlatformSettings();
5759
setModelDownloadLink(llama_model_url || DEFAULT_LLAMA_CPP_MODEL_URL);
60+
setResetEveryTime(llama_reset_everytime);
5861
}, [])
5962

6063
return (
@@ -70,6 +73,11 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow
7073
placeholder={"Default model is Microsoft Phi-3"}
7174
value={model_download_link} cb={setModelDownloadLink}
7275
/>
76+
<TrueFalseComponent
77+
title={"Reset conversation when send new message"}
78+
description={"Reset the conversation, only keeps the latest message and the system instruction, this is useful for many one-shot operations."}
79+
value={reset_everytime} cb={setResetEveryTime}
80+
/>
7381
</SettingSection>
7482
)
7583
}

src/components/settings/ModelSettings.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export default function ModelSettings({ trigger, updateState }) {
1212

1313
function saveSettings() {
1414
let validate_max_token = max_tokens;
15-
console.log(max_tokens)
1615
if(max_tokens < MIN_TOKENS && max_tokens !== 0) validate_max_token = MIN_TOKENS;
1716
updateModelSettings({
1817
max_tokens: validate_max_token, top_p, temperature

src/components/settings/components/PasswordComponent.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function PasswordComponent({ cb, value, disabled, title, descript
1313
<input
1414
type={show_password ? 'text' : 'password'} value={value}
1515
placeholder={placeholder || ''} onInput={(evt)=>cb(evt.target.value)}
16-
disabled={disabled}
16+
disabled={disabled} onClick={evt=>evt.target.select()}
1717
/>
1818
<div className="controller clickable" onClick={()=>toggleShowPassword(!show_password)}>
1919
{

0 commit comments

Comments
 (0)