summaryrefslogtreecommitdiff
path: root/examples/server/public_simplechat/simplechat.js
diff options
context:
space:
mode:
Diffstat (limited to 'examples/server/public_simplechat/simplechat.js')
-rw-r--r--examples/server/public_simplechat/simplechat.js207
1 files changed, 179 insertions, 28 deletions
diff --git a/examples/server/public_simplechat/simplechat.js b/examples/server/public_simplechat/simplechat.js
index 3fc4dbc2..0c48da87 100644
--- a/examples/server/public_simplechat/simplechat.js
+++ b/examples/server/public_simplechat/simplechat.js
@@ -14,23 +14,86 @@ class ApiEP {
}
let gUsageMsg = `
- <p> Enter the system prompt above, before entering/submitting any user query.</p>
- <p> Enter your text to the ai assistant below.</p>
- <p> Use shift+enter for inserting enter.</p>
- <p> Refresh the page to start over fresh.</p>
+ <p class="role-system">Usage</p>
+ <ul class="ul1">
+ <li> Set system prompt above, to try control ai response charactersitic, if model supports same.</li>
+ <ul class="ul2">
+ <li> Completion mode normally wont have a system prompt.</li>
+ </ul>
+ <li> Enter your query to ai assistant below.</li>
+ <ul class="ul2">
+ <li> Completion mode doesnt insert user/role: prefix implicitly.</li>
+ <li> Use shift+enter for inserting enter/newline.</li>
+ </ul>
+ <li> Default ContextWindow = [System, Last Query+Resp, Cur Query].</li>
+ <ul class="ul2">
+ <li> experiment iRecentUserMsgCnt, max_tokens, model ctxt window to expand</li>
+ </ul>
+ </ul>
`;
+/** @typedef {{role: string, content: string}[]} ChatMessages */
+
class SimpleChat {
constructor() {
/**
* Maintain in a form suitable for common LLM web service chat/completions' messages entry
- * @type {{role: string, content: string}[]}
+ * @type {ChatMessages}
*/
this.xchat = [];
this.iLastSys = -1;
}
+ clear() {
+ this.xchat = [];
+ this.iLastSys = -1;
+ }
+
+ /**
+ * Recent chat messages.
+ * If iRecentUserMsgCnt < 0
+ * Then return the full chat history
+ * Else
+ * Return chat messages from latest going back till the last/latest system prompt.
+ * While keeping track that the number of user queries/messages doesnt exceed iRecentUserMsgCnt.
+ * @param {number} iRecentUserMsgCnt
+ */
+ recent_chat(iRecentUserMsgCnt) {
+ if (iRecentUserMsgCnt < 0) {
+ return this.xchat;
+ }
+ if (iRecentUserMsgCnt == 0) {
+ console.warn("WARN:SimpleChat:SC:RecentChat:iRecentUsermsgCnt of 0 means no user message/query sent");
+ }
+ /** @type{ChatMessages} */
+ let rchat = [];
+ let sysMsg = this.get_system_latest();
+ if (sysMsg.length != 0) {
+ rchat.push({role: Roles.System, content: sysMsg});
+ }
+ let iUserCnt = 0;
+ let iStart = this.xchat.length;
+ for(let i=this.xchat.length-1; i > this.iLastSys; i--) {
+ if (iUserCnt >= iRecentUserMsgCnt) {
+ break;
+ }
+ let msg = this.xchat[i];
+ if (msg.role == Roles.User) {
+ iStart = i;
+ iUserCnt += 1;
+ }
+ }
+ for(let i = iStart; i < this.xchat.length; i++) {
+ let msg = this.xchat[i];
+ if (msg.role == Roles.System) {
+ continue;
+ }
+ rchat.push({role: msg.role, content: msg.content});
+ }
+ return rchat;
+ }
+
/**
* Add an entry into xchat
* @param {string} role
@@ -57,7 +120,7 @@ class SimpleChat {
div.replaceChildren();
}
let last = undefined;
- for(const x of this.xchat) {
+ for(const x of this.recent_chat(gMe.iRecentUserMsgCnt)) {
let entry = document.createElement("p");
entry.className = `role-${x.role}`;
entry.innerText = `${x.role}: ${x.content}`;
@@ -69,17 +132,21 @@ class SimpleChat {
} else {
if (bClear) {
div.innerHTML = gUsageMsg;
+ gMe.show_info(div);
}
}
}
/**
- * Add needed fields wrt json object to be sent wrt LLM web services completions endpoint
+ * Add needed fields wrt json object to be sent wrt LLM web services completions endpoint.
+ * The needed fields/options are picked from a global object.
* Convert the json into string.
* @param {Object} obj
*/
request_jsonstr(obj) {
- obj["temperature"] = 0.7;
+ for(let k in gMe.chatRequestOptions) {
+ obj[k] = gMe.chatRequestOptions[k];
+ }
return JSON.stringify(obj);
}
@@ -88,18 +155,27 @@ class SimpleChat {
*/
request_messages_jsonstr() {
let req = {
- messages: this.xchat,
+ messages: this.recent_chat(gMe.iRecentUserMsgCnt),
}
return this.request_jsonstr(req);
}
/**
* Return a string form of json object suitable for /completions
+ * @param {boolean} bInsertStandardRolePrefix Insert "<THE_ROLE>: " as prefix wrt each role's message
*/
- request_prompt_jsonstr() {
+ request_prompt_jsonstr(bInsertStandardRolePrefix) {
let prompt = "";
- for(const chat of this.xchat) {
- prompt += `${chat.role}: ${chat.content}\n`;
+ let iCnt = 0;
+ for(const chat of this.recent_chat(gMe.iRecentUserMsgCnt)) {
+ iCnt += 1;
+ if (iCnt > 1) {
+ prompt += "\n";
+ }
+ if (bInsertStandardRolePrefix) {
+ prompt += `${chat.role}: `;
+ }
+ prompt += `${chat.content}`;
}
let req = {
prompt: prompt,
@@ -171,7 +247,6 @@ let gChatURL = {
'chat': `${gBaseURL}/chat/completions`,
'completion': `${gBaseURL}/completions`,
}
-const gbCompletionFreshChatAlways = true;
/**
@@ -291,6 +366,8 @@ class MultiChatUI {
// allow user to insert enter into their message using shift+enter.
// while just pressing enter key will lead to submitting.
if ((ev.key === "Enter") && (!ev.shiftKey)) {
+ let value = this.elInUser.value;
+ this.elInUser.value = value.substring(0,value.length-1);
this.elBtnUser.click();
ev.preventDefault();
}
@@ -322,6 +399,29 @@ class MultiChatUI {
}
/**
+ * Try read json response early, if available.
+ * @param {Response} resp
+ */
+ async read_json_early(resp) {
+ if (!resp.body) {
+ throw Error("ERRR:SimpleChat:MCUI:ReadJsonEarly:No body...");
+ }
+ let tdUtf8 = new TextDecoder("utf-8");
+ let rr = resp.body.getReader();
+ let gotBody = "";
+ while(true) {
+ let { value: cur, done: done} = await rr.read();
+ let curBody = tdUtf8.decode(cur);
+ console.debug("DBUG:SC:PART:", curBody);
+ gotBody += curBody;
+ if (done) {
+ break;
+ }
+ }
+ return JSON.parse(gotBody);
+ }
+
+ /**
* Handle user query submit request, wrt specified chat session.
* @param {string} chatId
* @param {string} apiEP
@@ -330,6 +430,14 @@ class MultiChatUI {
let chat = this.simpleChats[chatId];
+ // In completion mode, if configured, clear any previous chat history.
+ // So if user wants to simulate a multi-chat based completion query,
+ // they will have to enter the full thing, as a suitable multiline
+ // user input/query.
+ if ((apiEP == ApiEP.Completion) && (gMe.bCompletionFreshChatAlways)) {
+ chat.clear();
+ }
+
chat.add_system_anytime(this.elInSystem.value, chatId);
let content = this.elInUser.value;
@@ -344,7 +452,7 @@ class MultiChatUI {
if (apiEP == ApiEP.Chat) {
theBody = chat.request_messages_jsonstr();
} else {
- theBody = chat.request_prompt_jsonstr();
+ theBody = chat.request_prompt_jsonstr(gMe.bCompletionInsertStandardRolePrefix);
}
this.elInUser.value = "working...";
@@ -359,6 +467,7 @@ class MultiChatUI {
});
let respBody = await resp.json();
+ //let respBody = await this.read_json_early(resp);
console.debug(`DBUG:SimpleChat:MCUI:${chatId}:HandleUserSubmit:RespBody:${JSON.stringify(respBody)}`);
let assistantMsg;
if (apiEP == ApiEP.Chat) {
@@ -376,13 +485,6 @@ class MultiChatUI {
} else {
console.debug(`DBUG:SimpleChat:MCUI:HandleUserSubmit:ChatId has changed:[${chatId}] [${this.curChatId}]`);
}
- // Purposefully clear at end rather than begin of this function
- // so that one can switch from chat to completion mode and sequece
- // in a completion mode with multiple user-assistant chat data
- // from before to be sent/occur once.
- if ((apiEP == ApiEP.Completion) && (gbCompletionFreshChatAlways)) {
- chat.xchat.length = 0;
- }
this.ui_reset_userinput();
}
@@ -462,17 +564,66 @@ class MultiChatUI {
}
-let gMuitChat;
-const gChatIds = [ "Default", "Other" ];
+class Me {
+
+ constructor() {
+ this.defaultChatIds = [ "Default", "Other" ];
+ this.multiChat = new MultiChatUI();
+ this.bCompletionFreshChatAlways = true;
+ this.bCompletionInsertStandardRolePrefix = false;
+ this.iRecentUserMsgCnt = 2;
+ // Add needed fields wrt json object to be sent wrt LLM web services completions endpoint.
+ this.chatRequestOptions = {
+ "temperature": 0.7,
+ "max_tokens": 1024,
+ "frequency_penalty": 1.2,
+ "presence_penalty": 1.2,
+ "n_predict": 1024
+ };
+ }
+
+ /**
+ * @param {HTMLDivElement} elDiv
+ */
+ show_info(elDiv) {
+
+ var p = document.createElement("p");
+ p.innerText = "Settings (devel-tools-console gMe)";
+ p.className = "role-system";
+ elDiv.appendChild(p);
+
+ var p = document.createElement("p");
+ p.innerText = `bCompletionFreshChatAlways:${this.bCompletionFreshChatAlways}`;
+ elDiv.appendChild(p);
+
+ p = document.createElement("p");
+ p.innerText = `bCompletionInsertStandardRolePrefix:${this.bCompletionInsertStandardRolePrefix}`;
+ elDiv.appendChild(p);
+
+ p = document.createElement("p");
+ p.innerText = `iRecentUserMsgCnt:${this.iRecentUserMsgCnt}`;
+ elDiv.appendChild(p);
+
+ p = document.createElement("p");
+ p.innerText = `chatRequestOptions:${JSON.stringify(this.chatRequestOptions)}`;
+ elDiv.appendChild(p);
+
+ }
+
+}
+
+
+/** @type {Me} */
+let gMe;
function startme() {
console.log("INFO:SimpleChat:StartMe:Starting...");
- gMuitChat = new MultiChatUI();
- for (let cid of gChatIds) {
- gMuitChat.new_chat_session(cid);
+ gMe = new Me();
+ for (let cid of gMe.defaultChatIds) {
+ gMe.multiChat.new_chat_session(cid);
}
- gMuitChat.setup_ui(gChatIds[0]);
- gMuitChat.show_sessions();
+ gMe.multiChat.setup_ui(gMe.defaultChatIds[0], true);
+ gMe.multiChat.show_sessions();
}
document.addEventListener("DOMContentLoaded", startme);