summaryrefslogtreecommitdiff
path: root/llama.cpp/tools/server/public_simplechat/simplechat.js
diff options
context:
space:
mode:
Diffstat (limited to 'llama.cpp/tools/server/public_simplechat/simplechat.js')
-rw-r--r--llama.cpp/tools/server/public_simplechat/simplechat.js929
1 files changed, 929 insertions, 0 deletions
diff --git a/llama.cpp/tools/server/public_simplechat/simplechat.js b/llama.cpp/tools/server/public_simplechat/simplechat.js
new file mode 100644
index 0000000..2fcd24a
--- /dev/null
+++ b/llama.cpp/tools/server/public_simplechat/simplechat.js
@@ -0,0 +1,929 @@
+// @ts-check
+// A simple completions and chat/completions test related web front end logic
+// by Humans for All
+
+import * as du from "./datautils.mjs";
+import * as ui from "./ui.mjs"
+
+class Roles {
+ static System = "system";
+ static User = "user";
+ static Assistant = "assistant";
+}
+
+class ApiEP {
+ static Type = {
+ Chat: "chat",
+ Completion: "completion",
+ }
+ static UrlSuffix = {
+ 'chat': `/chat/completions`,
+ 'completion': `/completions`,
+ }
+
+ /**
+ * Build the url from given baseUrl and apiEp id.
+ * @param {string} baseUrl
+ * @param {string} apiEP
+ */
+ static Url(baseUrl, apiEP) {
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.substring(0, baseUrl.length-1);
+ }
+ return `${baseUrl}${this.UrlSuffix[apiEP]}`;
+ }
+
+}
+
+
+let gUsageMsg = `
+ <p class="role-system">Usage</p>
+ <ul class="ul1">
+ <li> System prompt above, to try control ai response characteristics.</li>
+ <ul class="ul2">
+ <li> Completion mode - no system prompt normally.</li>
+ </ul>
+ <li> Use shift+enter for inserting enter/newline.</li>
+ <li> Enter your query to ai assistant below.</li>
+ <li> Default ContextWindow = [System, Last Query+Resp, Cur Query].</li>
+ <ul class="ul2">
+ <li> ChatHistInCtxt, MaxTokens, ModelCtxt window to expand</li>
+ </ul>
+ </ul>
+`;
+
+
+/** @typedef {{role: string, content: string}[]} ChatMessages */
+
+/** @typedef {{iLastSys: number, xchat: ChatMessages}} SimpleChatODS */
+
+class SimpleChat {
+
+ /**
+ * @param {string} chatId
+ */
+ constructor(chatId) {
+ this.chatId = chatId;
+ /**
+ * Maintain in a form suitable for common LLM web service chat/completions' messages entry
+ * @type {ChatMessages}
+ */
+ this.xchat = [];
+ this.iLastSys = -1;
+ this.latestResponse = "";
+ }
+
+ clear() {
+ this.xchat = [];
+ this.iLastSys = -1;
+ }
+
+ ods_key() {
+ return `SimpleChat-${this.chatId}`
+ }
+
+ save() {
+ /** @type {SimpleChatODS} */
+ let ods = {iLastSys: this.iLastSys, xchat: this.xchat};
+ localStorage.setItem(this.ods_key(), JSON.stringify(ods));
+ }
+
+ load() {
+ let sods = localStorage.getItem(this.ods_key());
+ if (sods == null) {
+ return;
+ }
+ /** @type {SimpleChatODS} */
+ let ods = JSON.parse(sods);
+ this.iLastSys = ods.iLastSys;
+ this.xchat = ods.xchat;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Collate the latest response from the server/ai-model, as it is becoming available.
+ * This is mainly useful for the stream mode.
+ * @param {string} content
+ */
+ append_response(content) {
+ this.latestResponse += content;
+ }
+
+ /**
+ * Add an entry into xchat
+ * @param {string} role
+ * @param {string|undefined|null} content
+ */
+ add(role, content) {
+ if ((content == undefined) || (content == null) || (content == "")) {
+ return false;
+ }
+ this.xchat.push( {role: role, content: content} );
+ if (role == Roles.System) {
+ this.iLastSys = this.xchat.length - 1;
+ }
+ this.save();
+ return true;
+ }
+
+ /**
+ * Show the contents in the specified div
+ * @param {HTMLDivElement} div
+ * @param {boolean} bClear
+ */
+ show(div, bClear=true) {
+ if (bClear) {
+ div.replaceChildren();
+ }
+ let last = undefined;
+ for(const x of this.recent_chat(gMe.iRecentUserMsgCnt)) {
+ let entry = ui.el_create_append_p(`${x.role}: ${x.content}`, div);
+ entry.className = `role-${x.role}`;
+ last = entry;
+ }
+ if (last !== undefined) {
+ last.scrollIntoView(false);
+ } else {
+ if (bClear) {
+ div.innerHTML = gUsageMsg;
+ gMe.setup_load(div, this);
+ gMe.show_info(div);
+ }
+ }
+ return last;
+ }
+
+ /**
+ * Setup the fetch headers.
+ * It picks the headers from gMe.headers.
+ * It inserts Authorization only if its non-empty.
+ * @param {string} apiEP
+ */
+ fetch_headers(apiEP) {
+ let headers = new Headers();
+ for(let k in gMe.headers) {
+ let v = gMe.headers[k];
+ if ((k == "Authorization") && (v.trim() == "")) {
+ continue;
+ }
+ headers.append(k, v);
+ }
+ return headers;
+ }
+
+ /**
+ * 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.
+ * Add optional stream flag, if required.
+ * Convert the json into string.
+ * @param {Object} obj
+ */
+ request_jsonstr_extend(obj) {
+ for(let k in gMe.apiRequestOptions) {
+ obj[k] = gMe.apiRequestOptions[k];
+ }
+ if (gMe.bStream) {
+ obj["stream"] = true;
+ }
+ return JSON.stringify(obj);
+ }
+
+ /**
+ * Return a string form of json object suitable for chat/completions
+ */
+ request_messages_jsonstr() {
+ let req = {
+ messages: this.recent_chat(gMe.iRecentUserMsgCnt),
+ }
+ return this.request_jsonstr_extend(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(bInsertStandardRolePrefix) {
+ let prompt = "";
+ 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,
+ }
+ return this.request_jsonstr_extend(req);
+ }
+
+ /**
+ * Return a string form of json object suitable for specified api endpoint.
+ * @param {string} apiEP
+ */
+ request_jsonstr(apiEP) {
+ if (apiEP == ApiEP.Type.Chat) {
+ return this.request_messages_jsonstr();
+ } else {
+ return this.request_prompt_jsonstr(gMe.bCompletionInsertStandardRolePrefix);
+ }
+ }
+
+ /**
+ * Extract the ai-model/assistant's response from the http response got.
+ * Optionally trim the message wrt any garbage at the end.
+ * @param {any} respBody
+ * @param {string} apiEP
+ */
+ response_extract(respBody, apiEP) {
+ let assistant = "";
+ if (apiEP == ApiEP.Type.Chat) {
+ assistant = respBody["choices"][0]["message"]["content"];
+ } else {
+ try {
+ assistant = respBody["choices"][0]["text"];
+ } catch {
+ assistant = respBody["content"];
+ }
+ }
+ return assistant;
+ }
+
+ /**
+ * Extract the ai-model/assistant's response from the http response got in streaming mode.
+ * @param {any} respBody
+ * @param {string} apiEP
+ */
+ response_extract_stream(respBody, apiEP) {
+ let assistant = "";
+ if (apiEP == ApiEP.Type.Chat) {
+ if (respBody["choices"][0]["finish_reason"] !== "stop") {
+ assistant = respBody["choices"][0]["delta"]["content"];
+ }
+ } else {
+ try {
+ assistant = respBody["choices"][0]["text"];
+ } catch {
+ assistant = respBody["content"];
+ }
+ }
+ return assistant;
+ }
+
+ /**
+ * Allow setting of system prompt, but only at begining.
+ * @param {string} sysPrompt
+ * @param {string} msgTag
+ */
+ add_system_begin(sysPrompt, msgTag) {
+ if (this.xchat.length == 0) {
+ if (sysPrompt.length > 0) {
+ return this.add(Roles.System, sysPrompt);
+ }
+ } else {
+ if (sysPrompt.length > 0) {
+ if (this.xchat[0].role !== Roles.System) {
+ console.error(`ERRR:SimpleChat:SC:${msgTag}:You need to specify system prompt before any user query, ignoring...`);
+ } else {
+ if (this.xchat[0].content !== sysPrompt) {
+ console.error(`ERRR:SimpleChat:SC:${msgTag}:You cant change system prompt, mid way through, ignoring...`);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Allow setting of system prompt, at any time.
+ * @param {string} sysPrompt
+ * @param {string} msgTag
+ */
+ add_system_anytime(sysPrompt, msgTag) {
+ if (sysPrompt.length <= 0) {
+ return false;
+ }
+
+ if (this.iLastSys < 0) {
+ return this.add(Roles.System, sysPrompt);
+ }
+
+ let lastSys = this.xchat[this.iLastSys].content;
+ if (lastSys !== sysPrompt) {
+ return this.add(Roles.System, sysPrompt);
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the latest system prompt.
+ */
+ get_system_latest() {
+ if (this.iLastSys == -1) {
+ return "";
+ }
+ let sysPrompt = this.xchat[this.iLastSys].content;
+ return sysPrompt;
+ }
+
+
+ /**
+ * Handle the multipart response from server/ai-model
+ * @param {Response} resp
+ * @param {string} apiEP
+ * @param {HTMLDivElement} elDiv
+ */
+ async handle_response_multipart(resp, apiEP, elDiv) {
+ let elP = ui.el_create_append_p("", elDiv);
+ if (!resp.body) {
+ throw Error("ERRR:SimpleChat:SC:HandleResponseMultiPart:No body...");
+ }
+ let tdUtf8 = new TextDecoder("utf-8");
+ let rr = resp.body.getReader();
+ this.latestResponse = "";
+ let xLines = new du.NewLines();
+ while(true) {
+ let { value: cur, done: done } = await rr.read();
+ if (cur) {
+ let curBody = tdUtf8.decode(cur, {stream: true});
+ console.debug("DBUG:SC:PART:Str:", curBody);
+ xLines.add_append(curBody);
+ }
+ while(true) {
+ let curLine = xLines.shift(!done);
+ if (curLine == undefined) {
+ break;
+ }
+ if (curLine.trim() == "") {
+ continue;
+ }
+ if (curLine.startsWith("data:")) {
+ curLine = curLine.substring(5);
+ }
+ if (curLine.trim() === "[DONE]") {
+ break;
+ }
+ let curJson = JSON.parse(curLine);
+ console.debug("DBUG:SC:PART:Json:", curJson);
+ this.append_response(this.response_extract_stream(curJson, apiEP));
+ }
+ elP.innerText = this.latestResponse;
+ elP.scrollIntoView(false);
+ if (done) {
+ break;
+ }
+ }
+ console.debug("DBUG:SC:PART:Full:", this.latestResponse);
+ return this.latestResponse;
+ }
+
+ /**
+ * Handle the oneshot response from server/ai-model
+ * @param {Response} resp
+ * @param {string} apiEP
+ */
+ async handle_response_oneshot(resp, apiEP) {
+ let respBody = await resp.json();
+ console.debug(`DBUG:SimpleChat:SC:${this.chatId}:HandleUserSubmit:RespBody:${JSON.stringify(respBody)}`);
+ return this.response_extract(respBody, apiEP);
+ }
+
+ /**
+ * Handle the response from the server be it in oneshot or multipart/stream mode.
+ * Also take care of the optional garbage trimming.
+ * @param {Response} resp
+ * @param {string} apiEP
+ * @param {HTMLDivElement} elDiv
+ */
+ async handle_response(resp, apiEP, elDiv) {
+ let theResp = {
+ assistant: "",
+ trimmed: "",
+ }
+ if (gMe.bStream) {
+ try {
+ theResp.assistant = await this.handle_response_multipart(resp, apiEP, elDiv);
+ this.latestResponse = "";
+ } catch (error) {
+ theResp.assistant = this.latestResponse;
+ this.add(Roles.Assistant, theResp.assistant);
+ this.latestResponse = "";
+ throw error;
+ }
+ } else {
+ theResp.assistant = await this.handle_response_oneshot(resp, apiEP);
+ }
+ if (gMe.bTrimGarbage) {
+ let origMsg = theResp.assistant;
+ theResp.assistant = du.trim_garbage_at_end(origMsg);
+ theResp.trimmed = origMsg.substring(theResp.assistant.length);
+ }
+ this.add(Roles.Assistant, theResp.assistant);
+ return theResp;
+ }
+
+}
+
+
+class MultiChatUI {
+
+ constructor() {
+ /** @type {Object<string, SimpleChat>} */
+ this.simpleChats = {};
+ /** @type {string} */
+ this.curChatId = "";
+
+ // the ui elements
+ this.elInSystem = /** @type{HTMLInputElement} */(document.getElementById("system-in"));
+ this.elDivChat = /** @type{HTMLDivElement} */(document.getElementById("chat-div"));
+ this.elBtnUser = /** @type{HTMLButtonElement} */(document.getElementById("user-btn"));
+ this.elInUser = /** @type{HTMLInputElement} */(document.getElementById("user-in"));
+ this.elDivHeading = /** @type{HTMLSelectElement} */(document.getElementById("heading"));
+ this.elDivSessions = /** @type{HTMLDivElement} */(document.getElementById("sessions-div"));
+ this.elBtnSettings = /** @type{HTMLButtonElement} */(document.getElementById("settings"));
+
+ this.validate_element(this.elInSystem, "system-in");
+ this.validate_element(this.elDivChat, "chat-div");
+ this.validate_element(this.elInUser, "user-in");
+ this.validate_element(this.elDivHeading, "heading");
+ this.validate_element(this.elDivChat, "sessions-div");
+ this.validate_element(this.elBtnSettings, "settings");
+ }
+
+ /**
+ * Check if the element got
+ * @param {HTMLElement | null} el
+ * @param {string} msgTag
+ */
+ validate_element(el, msgTag) {
+ if (el == null) {
+ throw Error(`ERRR:SimpleChat:MCUI:${msgTag} element missing in html...`);
+ } else {
+ console.debug(`INFO:SimpleChat:MCUI:${msgTag} Id[${el.id}] Name[${el["name"]}]`);
+ }
+ }
+
+ /**
+ * Reset user input ui.
+ * * clear user input
+ * * enable user input
+ * * set focus to user input
+ */
+ ui_reset_userinput() {
+ this.elInUser.value = "";
+ this.elInUser.disabled = false;
+ this.elInUser.focus();
+ }
+
+ /**
+ * Setup the needed callbacks wrt UI, curChatId to defaultChatId and
+ * optionally switch to specified defaultChatId.
+ * @param {string} defaultChatId
+ * @param {boolean} bSwitchSession
+ */
+ setup_ui(defaultChatId, bSwitchSession=false) {
+
+ this.curChatId = defaultChatId;
+ if (bSwitchSession) {
+ this.handle_session_switch(this.curChatId);
+ }
+
+ this.elBtnSettings.addEventListener("click", (ev)=>{
+ this.elDivChat.replaceChildren();
+ gMe.show_settings(this.elDivChat);
+ });
+
+ this.elBtnUser.addEventListener("click", (ev)=>{
+ if (this.elInUser.disabled) {
+ return;
+ }
+ this.handle_user_submit(this.curChatId, gMe.apiEP).catch((/** @type{Error} */reason)=>{
+ let msg = `ERRR:SimpleChat\nMCUI:HandleUserSubmit:${this.curChatId}\n${reason.name}:${reason.message}`;
+ console.error(msg.replace("\n", ":"));
+ alert(msg);
+ this.ui_reset_userinput();
+ });
+ });
+
+ this.elInUser.addEventListener("keyup", (ev)=> {
+ // 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();
+ }
+ });
+
+ this.elInSystem.addEventListener("keyup", (ev)=> {
+ // allow user to insert enter into the system prompt using shift+enter.
+ // while just pressing enter key will lead to setting the system prompt.
+ if ((ev.key === "Enter") && (!ev.shiftKey)) {
+ let value = this.elInSystem.value;
+ this.elInSystem.value = value.substring(0,value.length-1);
+ let chat = this.simpleChats[this.curChatId];
+ chat.add_system_anytime(this.elInSystem.value, this.curChatId);
+ chat.show(this.elDivChat);
+ ev.preventDefault();
+ }
+ });
+
+ }
+
+ /**
+ * Setup a new chat session and optionally switch to it.
+ * @param {string} chatId
+ * @param {boolean} bSwitchSession
+ */
+ new_chat_session(chatId, bSwitchSession=false) {
+ this.simpleChats[chatId] = new SimpleChat(chatId);
+ if (bSwitchSession) {
+ this.handle_session_switch(chatId);
+ }
+ }
+
+
+ /**
+ * Handle user query submit request, wrt specified chat session.
+ * @param {string} chatId
+ * @param {string} apiEP
+ */
+ async handle_user_submit(chatId, apiEP) {
+
+ 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.Type.Completion) && (gMe.bCompletionFreshChatAlways)) {
+ chat.clear();
+ }
+
+ chat.add_system_anytime(this.elInSystem.value, chatId);
+
+ let content = this.elInUser.value;
+ if (!chat.add(Roles.User, content)) {
+ console.debug(`WARN:SimpleChat:MCUI:${chatId}:HandleUserSubmit:Ignoring empty user input...`);
+ return;
+ }
+ chat.show(this.elDivChat);
+
+ let theUrl = ApiEP.Url(gMe.baseURL, apiEP);
+ let theBody = chat.request_jsonstr(apiEP);
+
+ this.elInUser.value = "working...";
+ this.elInUser.disabled = true;
+ console.debug(`DBUG:SimpleChat:MCUI:${chatId}:HandleUserSubmit:${theUrl}:ReqBody:${theBody}`);
+ let theHeaders = chat.fetch_headers(apiEP);
+ let resp = await fetch(theUrl, {
+ method: "POST",
+ headers: theHeaders,
+ body: theBody,
+ });
+
+ let theResp = await chat.handle_response(resp, apiEP, this.elDivChat);
+ if (chatId == this.curChatId) {
+ chat.show(this.elDivChat);
+ if (theResp.trimmed.length > 0) {
+ let p = ui.el_create_append_p(`TRIMMED:${theResp.trimmed}`, this.elDivChat);
+ p.className="role-trim";
+ }
+ } else {
+ console.debug(`DBUG:SimpleChat:MCUI:HandleUserSubmit:ChatId has changed:[${chatId}] [${this.curChatId}]`);
+ }
+ this.ui_reset_userinput();
+ }
+
+ /**
+ * Show buttons for NewChat and available chat sessions, in the passed elDiv.
+ * If elDiv is undefined/null, then use this.elDivSessions.
+ * Take care of highlighting the selected chat-session's btn.
+ * @param {HTMLDivElement | undefined} elDiv
+ */
+ show_sessions(elDiv=undefined) {
+ if (!elDiv) {
+ elDiv = this.elDivSessions;
+ }
+ elDiv.replaceChildren();
+ // Btn for creating new chat session
+ let btnNew = ui.el_create_button("New CHAT", (ev)=> {
+ if (this.elInUser.disabled) {
+ console.error(`ERRR:SimpleChat:MCUI:NewChat:Current session [${this.curChatId}] awaiting response, ignoring request...`);
+ alert("ERRR:SimpleChat\nMCUI:NewChat\nWait for response to pending query, before starting new chat session");
+ return;
+ }
+ let chatId = `Chat${Object.keys(this.simpleChats).length}`;
+ let chatIdGot = prompt("INFO:SimpleChat\nMCUI:NewChat\nEnter id for new chat session", chatId);
+ if (!chatIdGot) {
+ console.error("ERRR:SimpleChat:MCUI:NewChat:Skipping based on user request...");
+ return;
+ }
+ this.new_chat_session(chatIdGot, true);
+ this.create_session_btn(elDiv, chatIdGot);
+ ui.el_children_config_class(elDiv, chatIdGot, "session-selected", "");
+ });
+ elDiv.appendChild(btnNew);
+ // Btns for existing chat sessions
+ let chatIds = Object.keys(this.simpleChats);
+ for(let cid of chatIds) {
+ let btn = this.create_session_btn(elDiv, cid);
+ if (cid == this.curChatId) {
+ btn.className = "session-selected";
+ }
+ }
+ }
+
+ create_session_btn(elDiv, cid) {
+ let btn = ui.el_create_button(cid, (ev)=>{
+ let target = /** @type{HTMLButtonElement} */(ev.target);
+ console.debug(`DBUG:SimpleChat:MCUI:SessionClick:${target.id}`);
+ if (this.elInUser.disabled) {
+ console.error(`ERRR:SimpleChat:MCUI:SessionClick:${target.id}:Current session [${this.curChatId}] awaiting response, ignoring switch...`);
+ alert("ERRR:SimpleChat\nMCUI:SessionClick\nWait for response to pending query, before switching");
+ return;
+ }
+ this.handle_session_switch(target.id);
+ ui.el_children_config_class(elDiv, target.id, "session-selected", "");
+ });
+ elDiv.appendChild(btn);
+ return btn;
+ }
+
+ /**
+ * Switch ui to the specified chatId and set curChatId to same.
+ * @param {string} chatId
+ */
+ async handle_session_switch(chatId) {
+ let chat = this.simpleChats[chatId];
+ if (chat == undefined) {
+ console.error(`ERRR:SimpleChat:MCUI:HandleSessionSwitch:${chatId} missing...`);
+ return;
+ }
+ this.elInSystem.value = chat.get_system_latest();
+ this.elInUser.value = "";
+ chat.show(this.elDivChat);
+ this.elInUser.focus();
+ this.curChatId = chatId;
+ console.log(`INFO:SimpleChat:MCUI:HandleSessionSwitch:${chatId} entered...`);
+ }
+
+}
+
+
+class Me {
+
+ constructor() {
+ this.baseURL = "http://127.0.0.1:8080";
+ this.defaultChatIds = [ "Default", "Other" ];
+ this.multiChat = new MultiChatUI();
+ this.bStream = true;
+ this.bCompletionFreshChatAlways = true;
+ this.bCompletionInsertStandardRolePrefix = false;
+ this.bTrimGarbage = true;
+ this.iRecentUserMsgCnt = 2;
+ this.sRecentUserMsgCnt = {
+ "Full": -1,
+ "Last0": 1,
+ "Last1": 2,
+ "Last2": 3,
+ "Last4": 5,
+ };
+ this.apiEP = ApiEP.Type.Chat;
+ this.headers = {
+ "Content-Type": "application/json",
+ "Authorization": "", // Authorization: Bearer OPENAI_API_KEY
+ }
+ // Add needed fields wrt json object to be sent wrt LLM web services completions endpoint.
+ this.apiRequestOptions = {
+ "model": "gpt-3.5-turbo",
+ "temperature": 0.7,
+ "max_tokens": 1024,
+ "n_predict": 1024,
+ "cache_prompt": false,
+ //"frequency_penalty": 1.2,
+ //"presence_penalty": 1.2,
+ };
+ }
+
+ /**
+ * Disable console.debug by mapping it to a empty function.
+ */
+ debug_disable() {
+ this.console_debug = console.debug;
+ console.debug = () => {
+
+ };
+ }
+
+ /**
+ * Setup the load saved chat ui.
+ * @param {HTMLDivElement} div
+ * @param {SimpleChat} chat
+ */
+ setup_load(div, chat) {
+ if (!(chat.ods_key() in localStorage)) {
+ return;
+ }
+ div.innerHTML += `<p class="role-system">Restore</p>
+ <p>Load previously saved chat session, if available</p>`;
+ let btn = ui.el_create_button(chat.ods_key(), (ev)=>{
+ console.log("DBUG:SimpleChat:SC:Load", chat);
+ chat.load();
+ queueMicrotask(()=>{
+ chat.show(div);
+ this.multiChat.elInSystem.value = chat.get_system_latest();
+ });
+ });
+ div.appendChild(btn);
+ }
+
+ /**
+ * Show the configurable parameters info in the passed Div element.
+ * @param {HTMLDivElement} elDiv
+ * @param {boolean} bAll
+ */
+ show_info(elDiv, bAll=false) {
+
+ let p = ui.el_create_append_p("Settings (devel-tools-console document[gMe])", elDiv);
+ p.className = "role-system";
+
+ if (bAll) {
+
+ ui.el_create_append_p(`baseURL:${this.baseURL}`, elDiv);
+
+ ui.el_create_append_p(`Authorization:${this.headers["Authorization"]}`, elDiv);
+
+ ui.el_create_append_p(`bStream:${this.bStream}`, elDiv);
+
+ ui.el_create_append_p(`bTrimGarbage:${this.bTrimGarbage}`, elDiv);
+
+ ui.el_create_append_p(`ApiEndPoint:${this.apiEP}`, elDiv);
+
+ ui.el_create_append_p(`iRecentUserMsgCnt:${this.iRecentUserMsgCnt}`, elDiv);
+
+ ui.el_create_append_p(`bCompletionFreshChatAlways:${this.bCompletionFreshChatAlways}`, elDiv);
+
+ ui.el_create_append_p(`bCompletionInsertStandardRolePrefix:${this.bCompletionInsertStandardRolePrefix}`, elDiv);
+
+ }
+
+ ui.el_create_append_p(`apiRequestOptions:${JSON.stringify(this.apiRequestOptions, null, " - ")}`, elDiv);
+ ui.el_create_append_p(`headers:${JSON.stringify(this.headers, null, " - ")}`, elDiv);
+
+ }
+
+ /**
+ * Auto create ui input elements for fields in apiRequestOptions
+ * Currently supports text and number field types.
+ * @param {HTMLDivElement} elDiv
+ */
+ show_settings_apirequestoptions(elDiv) {
+ let typeDict = {
+ "string": "text",
+ "number": "number",
+ };
+ let fs = document.createElement("fieldset");
+ let legend = document.createElement("legend");
+ legend.innerText = "ApiRequestOptions";
+ fs.appendChild(legend);
+ elDiv.appendChild(fs);
+ for(const k in this.apiRequestOptions) {
+ let val = this.apiRequestOptions[k];
+ let type = typeof(val);
+ if (((type == "string") || (type == "number"))) {
+ let inp = ui.el_creatediv_input(`Set${k}`, k, typeDict[type], this.apiRequestOptions[k], (val)=>{
+ if (type == "number") {
+ val = Number(val);
+ }
+ this.apiRequestOptions[k] = val;
+ });
+ fs.appendChild(inp.div);
+ } else if (type == "boolean") {
+ let bbtn = ui.el_creatediv_boolbutton(`Set{k}`, k, {true: "true", false: "false"}, val, (userVal)=>{
+ this.apiRequestOptions[k] = userVal;
+ });
+ fs.appendChild(bbtn.div);
+ }
+ }
+ }
+
+ /**
+ * Show settings ui for configurable parameters, in the passed Div element.
+ * @param {HTMLDivElement} elDiv
+ */
+ show_settings(elDiv) {
+
+ let inp = ui.el_creatediv_input("SetBaseURL", "BaseURL", "text", this.baseURL, (val)=>{
+ this.baseURL = val;
+ });
+ elDiv.appendChild(inp.div);
+
+ inp = ui.el_creatediv_input("SetAuthorization", "Authorization", "text", this.headers["Authorization"], (val)=>{
+ this.headers["Authorization"] = val;
+ });
+ inp.el.placeholder = "Bearer OPENAI_API_KEY";
+ elDiv.appendChild(inp.div);
+
+ let bb = ui.el_creatediv_boolbutton("SetStream", "Stream", {true: "[+] yes stream", false: "[-] do oneshot"}, this.bStream, (val)=>{
+ this.bStream = val;
+ });
+ elDiv.appendChild(bb.div);
+
+ bb = ui.el_creatediv_boolbutton("SetTrimGarbage", "TrimGarbage", {true: "[+] yes trim", false: "[-] dont trim"}, this.bTrimGarbage, (val)=>{
+ this.bTrimGarbage = val;
+ });
+ elDiv.appendChild(bb.div);
+
+ this.show_settings_apirequestoptions(elDiv);
+
+ let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{
+ this.apiEP = ApiEP.Type[val];
+ });
+ elDiv.appendChild(sel.div);
+
+ sel = ui.el_creatediv_select("SetChatHistoryInCtxt", "ChatHistoryInCtxt", this.sRecentUserMsgCnt, this.iRecentUserMsgCnt, (val)=>{
+ this.iRecentUserMsgCnt = this.sRecentUserMsgCnt[val];
+ });
+ elDiv.appendChild(sel.div);
+
+ bb = ui.el_creatediv_boolbutton("SetCompletionFreshChatAlways", "CompletionFreshChatAlways", {true: "[+] yes fresh", false: "[-] no, with history"}, this.bCompletionFreshChatAlways, (val)=>{
+ this.bCompletionFreshChatAlways = val;
+ });
+ elDiv.appendChild(bb.div);
+
+ bb = ui.el_creatediv_boolbutton("SetCompletionInsertStandardRolePrefix", "CompletionInsertStandardRolePrefix", {true: "[+] yes insert", false: "[-] dont insert"}, this.bCompletionInsertStandardRolePrefix, (val)=>{
+ this.bCompletionInsertStandardRolePrefix = val;
+ });
+ elDiv.appendChild(bb.div);
+
+ }
+
+}
+
+
+/** @type {Me} */
+let gMe;
+
+function startme() {
+ console.log("INFO:SimpleChat:StartMe:Starting...");
+ gMe = new Me();
+ gMe.debug_disable();
+ document["gMe"] = gMe;
+ document["du"] = du;
+ for (let cid of gMe.defaultChatIds) {
+ gMe.multiChat.new_chat_session(cid);
+ }
+ gMe.multiChat.setup_ui(gMe.defaultChatIds[0], true);
+ gMe.multiChat.show_sessions();
+}
+
+document.addEventListener("DOMContentLoaded", startme);