KBaba7/llama.cpp
0
1// @ts-check2// A simple completions and chat/completions test related web front end logic3// by Humans for All4 5import * as du from "./datautils.mjs";6import * as ui from "./ui.mjs"7 8class Roles {9 static System = "system";10 static User = "user";11 static Assistant = "assistant";12}13 14class ApiEP {15 static Type = {16 Chat: "chat",17 Completion: "completion",18 }19 static UrlSuffix = {20 'chat': `/chat/completions`,21 'completion': `/completions`,22 }23 24 /**25 * Build the url from given baseUrl and apiEp id.26 * @param {string} baseUrl27 * @param {string} apiEP28 */29 static Url(baseUrl, apiEP) {30 if (baseUrl.endsWith("/")) {31 baseUrl = baseUrl.substring(0, baseUrl.length-1);32 }33 return `${baseUrl}${this.UrlSuffix[apiEP]}`;34 }35 36}37 38 39let gUsageMsg = `40 <p class="role-system">Usage</p>41 <ul class="ul1">42 <li> System prompt above, to try control ai response characteristics.</li>43 <ul class="ul2">44 <li> Completion mode - no system prompt normally.</li>45 </ul>46 <li> Use shift+enter for inserting enter/newline.</li>47 <li> Enter your query to ai assistant below.</li>48 <li> Default ContextWindow = [System, Last Query+Resp, Cur Query].</li>49 <ul class="ul2">50 <li> ChatHistInCtxt, MaxTokens, ModelCtxt window to expand</li>51 </ul>52 </ul>53`;54 55 56/** @typedef {{role: string, content: string}[]} ChatMessages */57 58/** @typedef {{iLastSys: number, xchat: ChatMessages}} SimpleChatODS */59 60class SimpleChat {61 62 /**63 * @param {string} chatId64 */65 constructor(chatId) {66 this.chatId = chatId;67 /**68 * Maintain in a form suitable for common LLM web service chat/completions' messages entry69 * @type {ChatMessages}70 */71 this.xchat = [];72 this.iLastSys = -1;73 this.latestResponse = "";74 }75 76 clear() {77 this.xchat = [];78 this.iLastSys = -1;79 }80 81 ods_key() {82 return `SimpleChat-${this.chatId}`83 }84 85 save() {86 /** @type {SimpleChatODS} */87 let ods = {iLastSys: this.iLastSys, xchat: this.xchat};88 localStorage.setItem(this.ods_key(), JSON.stringify(ods));89 }90 91 load() {92 let sods = localStorage.getItem(this.ods_key());93 if (sods == null) {94 return;95 }96 /** @type {SimpleChatODS} */97 let ods = JSON.parse(sods);98 this.iLastSys = ods.iLastSys;99 this.xchat = ods.xchat;100 }101 102 /**103 * Recent chat messages.104 * If iRecentUserMsgCnt < 0105 * Then return the full chat history106 * Else107 * Return chat messages from latest going back till the last/latest system prompt.108 * While keeping track that the number of user queries/messages doesnt exceed iRecentUserMsgCnt.109 * @param {number} iRecentUserMsgCnt110 */111 recent_chat(iRecentUserMsgCnt) {112 if (iRecentUserMsgCnt < 0) {113 return this.xchat;114 }115 if (iRecentUserMsgCnt == 0) {116 console.warn("WARN:SimpleChat:SC:RecentChat:iRecentUsermsgCnt of 0 means no user message/query sent");117 }118 /** @type{ChatMessages} */119 let rchat = [];120 let sysMsg = this.get_system_latest();121 if (sysMsg.length != 0) {122 rchat.push({role: Roles.System, content: sysMsg});123 }124 let iUserCnt = 0;125 let iStart = this.xchat.length;126 for(let i=this.xchat.length-1; i > this.iLastSys; i--) {127 if (iUserCnt >= iRecentUserMsgCnt) {128 break;129 }130 let msg = this.xchat[i];131 if (msg.role == Roles.User) {132 iStart = i;133 iUserCnt += 1;134 }135 }136 for(let i = iStart; i < this.xchat.length; i++) {137 let msg = this.xchat[i];138 if (msg.role == Roles.System) {139 continue;140 }141 rchat.push({role: msg.role, content: msg.content});142 }143 return rchat;144 }145 146 /**147 * Collate the latest response from the server/ai-model, as it is becoming available.148 * This is mainly useful for the stream mode.149 * @param {string} content150 */151 append_response(content) {152 this.latestResponse += content;153 }154 155 /**156 * Add an entry into xchat157 * @param {string} role158 * @param {string|undefined|null} content159 */160 add(role, content) {161 if ((content == undefined) || (content == null) || (content == "")) {162 return false;163 }164 this.xchat.push( {role: role, content: content} );165 if (role == Roles.System) {166 this.iLastSys = this.xchat.length - 1;167 }168 this.save();169 return true;170 }171 172 /**173 * Show the contents in the specified div174 * @param {HTMLDivElement} div175 * @param {boolean} bClear176 */177 show(div, bClear=true) {178 if (bClear) {179 div.replaceChildren();180 }181 let last = undefined;182 for(const x of this.recent_chat(gMe.iRecentUserMsgCnt)) {183 let entry = ui.el_create_append_p(`${x.role}: ${x.content}`, div);184 entry.className = `role-${x.role}`;185 last = entry;186 }187 if (last !== undefined) {188 last.scrollIntoView(false);189 } else {190 if (bClear) {191 div.innerHTML = gUsageMsg;192 gMe.setup_load(div, this);193 gMe.show_info(div);194 }195 }196 return last;197 }198 199 /**200 * Setup the fetch headers.201 * It picks the headers from gMe.headers.202 * It inserts Authorization only if its non-empty.203 * @param {string} apiEP204 */205 fetch_headers(apiEP) {206 let headers = new Headers();207 for(let k in gMe.headers) {208 let v = gMe.headers[k];209 if ((k == "Authorization") && (v.trim() == "")) {210 continue;211 }212 headers.append(k, v);213 }214 return headers;215 }216 217 /**218 * Add needed fields wrt json object to be sent wrt LLM web services completions endpoint.219 * The needed fields/options are picked from a global object.220 * Add optional stream flag, if required.221 * Convert the json into string.222 * @param {Object} obj223 */224 request_jsonstr_extend(obj) {225 for(let k in gMe.apiRequestOptions) {226 obj[k] = gMe.apiRequestOptions[k];227 }228 if (gMe.bStream) {229 obj["stream"] = true;230 }231 return JSON.stringify(obj);232 }233 234 /**235 * Return a string form of json object suitable for chat/completions236 */237 request_messages_jsonstr() {238 let req = {239 messages: this.recent_chat(gMe.iRecentUserMsgCnt),240 }241 return this.request_jsonstr_extend(req);242 }243 244 /**245 * Return a string form of json object suitable for /completions246 * @param {boolean} bInsertStandardRolePrefix Insert "<THE_ROLE>: " as prefix wrt each role's message247 */248 request_prompt_jsonstr(bInsertStandardRolePrefix) {249 let prompt = "";250 let iCnt = 0;251 for(const chat of this.recent_chat(gMe.iRecentUserMsgCnt)) {252 iCnt += 1;253 if (iCnt > 1) {254 prompt += "\n";255 }256 if (bInsertStandardRolePrefix) {257 prompt += `${chat.role}: `;258 }259 prompt += `${chat.content}`;260 }261 let req = {262 prompt: prompt,263 }264 return this.request_jsonstr_extend(req);265 }266 267 /**268 * Return a string form of json object suitable for specified api endpoint.269 * @param {string} apiEP270 */271 request_jsonstr(apiEP) {272 if (apiEP == ApiEP.Type.Chat) {273 return this.request_messages_jsonstr();274 } else {275 return this.request_prompt_jsonstr(gMe.bCompletionInsertStandardRolePrefix);276 }277 }278 279 /**280 * Extract the ai-model/assistant's response from the http response got.281 * Optionally trim the message wrt any garbage at the end.282 * @param {any} respBody283 * @param {string} apiEP284 */285 response_extract(respBody, apiEP) {286 let assistant = "";287 if (apiEP == ApiEP.Type.Chat) {288 assistant = respBody["choices"][0]["message"]["content"];289 } else {290 try {291 assistant = respBody["choices"][0]["text"];292 } catch {293 assistant = respBody["content"];294 }295 }296 return assistant;297 }298 299 /**300 * Extract the ai-model/assistant's response from the http response got in streaming mode.301 * @param {any} respBody302 * @param {string} apiEP303 */304 response_extract_stream(respBody, apiEP) {305 let assistant = "";306 if (apiEP == ApiEP.Type.Chat) {307 if (respBody["choices"][0]["finish_reason"] !== "stop") {308 assistant = respBody["choices"][0]["delta"]["content"];309 }310 } else {311 try {312 assistant = respBody["choices"][0]["text"];313 } catch {314 assistant = respBody["content"];315 }316 }317 return assistant;318 }319 320 /**321 * Allow setting of system prompt, but only at begining.322 * @param {string} sysPrompt323 * @param {string} msgTag324 */325 add_system_begin(sysPrompt, msgTag) {326 if (this.xchat.length == 0) {327 if (sysPrompt.length > 0) {328 return this.add(Roles.System, sysPrompt);329 }330 } else {331 if (sysPrompt.length > 0) {332 if (this.xchat[0].role !== Roles.System) {333 console.error(`ERRR:SimpleChat:SC:${msgTag}:You need to specify system prompt before any user query, ignoring...`);334 } else {335 if (this.xchat[0].content !== sysPrompt) {336 console.error(`ERRR:SimpleChat:SC:${msgTag}:You cant change system prompt, mid way through, ignoring...`);337 }338 }339 }340 }341 return false;342 }343 344 /**345 * Allow setting of system prompt, at any time.346 * @param {string} sysPrompt347 * @param {string} msgTag348 */349 add_system_anytime(sysPrompt, msgTag) {350 if (sysPrompt.length <= 0) {351 return false;352 }353 354 if (this.iLastSys < 0) {355 return this.add(Roles.System, sysPrompt);356 }357 358 let lastSys = this.xchat[this.iLastSys].content;359 if (lastSys !== sysPrompt) {360 return this.add(Roles.System, sysPrompt);361 }362 return false;363 }364 365 /**366 * Retrieve the latest system prompt.367 */368 get_system_latest() {369 if (this.iLastSys == -1) {370 return "";371 }372 let sysPrompt = this.xchat[this.iLastSys].content;373 return sysPrompt;374 }375 376 377 /**378 * Handle the multipart response from server/ai-model379 * @param {Response} resp380 * @param {string} apiEP381 * @param {HTMLDivElement} elDiv382 */383 async handle_response_multipart(resp, apiEP, elDiv) {384 let elP = ui.el_create_append_p("", elDiv);385 if (!resp.body) {386 throw Error("ERRR:SimpleChat:SC:HandleResponseMultiPart:No body...");387 }388 let tdUtf8 = new TextDecoder("utf-8");389 let rr = resp.body.getReader();390 this.latestResponse = "";391 let xLines = new du.NewLines();392 while(true) {393 let { value: cur, done: done } = await rr.read();394 if (cur) {395 let curBody = tdUtf8.decode(cur, {stream: true});396 console.debug("DBUG:SC:PART:Str:", curBody);397 xLines.add_append(curBody);398 }399 while(true) {400 let curLine = xLines.shift(!done);401 if (curLine == undefined) {402 break;403 }404 if (curLine.trim() == "") {405 continue;406 }407 if (curLine.startsWith("data:")) {408 curLine = curLine.substring(5);409 }410 if (curLine.trim() === "[DONE]") {411 break;412 }413 let curJson = JSON.parse(curLine);414 console.debug("DBUG:SC:PART:Json:", curJson);415 this.append_response(this.response_extract_stream(curJson, apiEP));416 }417 elP.innerText = this.latestResponse;418 elP.scrollIntoView(false);419 if (done) {420 break;421 }422 }423 console.debug("DBUG:SC:PART:Full:", this.latestResponse);424 return this.latestResponse;425 }426 427 /**428 * Handle the oneshot response from server/ai-model429 * @param {Response} resp430 * @param {string} apiEP431 */432 async handle_response_oneshot(resp, apiEP) {433 let respBody = await resp.json();434 console.debug(`DBUG:SimpleChat:SC:${this.chatId}:HandleUserSubmit:RespBody:${JSON.stringify(respBody)}`);435 return this.response_extract(respBody, apiEP);436 }437 438 /**439 * Handle the response from the server be it in oneshot or multipart/stream mode.440 * Also take care of the optional garbage trimming.441 * @param {Response} resp442 * @param {string} apiEP443 * @param {HTMLDivElement} elDiv444 */445 async handle_response(resp, apiEP, elDiv) {446 let theResp = {447 assistant: "",448 trimmed: "",449 }450 if (gMe.bStream) {451 try {452 theResp.assistant = await this.handle_response_multipart(resp, apiEP, elDiv);453 this.latestResponse = "";454 } catch (error) {455 theResp.assistant = this.latestResponse;456 this.add(Roles.Assistant, theResp.assistant);457 this.latestResponse = "";458 throw error;459 }460 } else {461 theResp.assistant = await this.handle_response_oneshot(resp, apiEP);462 }463 if (gMe.bTrimGarbage) {464 let origMsg = theResp.assistant;465 theResp.assistant = du.trim_garbage_at_end(origMsg);466 theResp.trimmed = origMsg.substring(theResp.assistant.length);467 }468 this.add(Roles.Assistant, theResp.assistant);469 return theResp;470 }471 472}473 474 475class MultiChatUI {476 477 constructor() {478 /** @type {Object<string, SimpleChat>} */479 this.simpleChats = {};480 /** @type {string} */481 this.curChatId = "";482 483 // the ui elements484 this.elInSystem = /** @type{HTMLInputElement} */(document.getElementById("system-in"));485 this.elDivChat = /** @type{HTMLDivElement} */(document.getElementById("chat-div"));486 this.elBtnUser = /** @type{HTMLButtonElement} */(document.getElementById("user-btn"));487 this.elInUser = /** @type{HTMLInputElement} */(document.getElementById("user-in"));488 this.elDivHeading = /** @type{HTMLSelectElement} */(document.getElementById("heading"));489 this.elDivSessions = /** @type{HTMLDivElement} */(document.getElementById("sessions-div"));490 this.elBtnSettings = /** @type{HTMLButtonElement} */(document.getElementById("settings"));491 492 this.validate_element(this.elInSystem, "system-in");493 this.validate_element(this.elDivChat, "chat-div");494 this.validate_element(this.elInUser, "user-in");495 this.validate_element(this.elDivHeading, "heading");496 this.validate_element(this.elDivChat, "sessions-div");497 this.validate_element(this.elBtnSettings, "settings");498 }499 500 /**501 * Check if the element got502 * @param {HTMLElement | null} el503 * @param {string} msgTag504 */505 validate_element(el, msgTag) {506 if (el == null) {507 throw Error(`ERRR:SimpleChat:MCUI:${msgTag} element missing in html...`);508 } else {509 console.debug(`INFO:SimpleChat:MCUI:${msgTag} Id[${el.id}] Name[${el["name"]}]`);510 }511 }512 513 /**514 * Reset user input ui.515 * * clear user input516 * * enable user input517 * * set focus to user input518 */519 ui_reset_userinput() {520 this.elInUser.value = "";521 this.elInUser.disabled = false;522 this.elInUser.focus();523 }524 525 /**526 * Setup the needed callbacks wrt UI, curChatId to defaultChatId and527 * optionally switch to specified defaultChatId.528 * @param {string} defaultChatId529 * @param {boolean} bSwitchSession530 */531 setup_ui(defaultChatId, bSwitchSession=false) {532 533 this.curChatId = defaultChatId;534 if (bSwitchSession) {535 this.handle_session_switch(this.curChatId);536 }537 538 this.elBtnSettings.addEventListener("click", (ev)=>{539 this.elDivChat.replaceChildren();540 gMe.show_settings(this.elDivChat);541 });542 543 this.elBtnUser.addEventListener("click", (ev)=>{544 if (this.elInUser.disabled) {545 return;546 }547 this.handle_user_submit(this.curChatId, gMe.apiEP).catch((/** @type{Error} */reason)=>{548 let msg = `ERRR:SimpleChat\nMCUI:HandleUserSubmit:${this.curChatId}\n${reason.name}:${reason.message}`;549 console.error(msg.replace("\n", ":"));550 alert(msg);551 this.ui_reset_userinput();552 });553 });554 555 this.elInUser.addEventListener("keyup", (ev)=> {556 // allow user to insert enter into their message using shift+enter.557 // while just pressing enter key will lead to submitting.558 if ((ev.key === "Enter") && (!ev.shiftKey)) {559 let value = this.elInUser.value;560 this.elInUser.value = value.substring(0,value.length-1);561 this.elBtnUser.click();562 ev.preventDefault();563 }564 });565 566 this.elInSystem.addEventListener("keyup", (ev)=> {567 // allow user to insert enter into the system prompt using shift+enter.568 // while just pressing enter key will lead to setting the system prompt.569 if ((ev.key === "Enter") && (!ev.shiftKey)) {570 let value = this.elInSystem.value;571 this.elInSystem.value = value.substring(0,value.length-1);572 let chat = this.simpleChats[this.curChatId];573 chat.add_system_anytime(this.elInSystem.value, this.curChatId);574 chat.show(this.elDivChat);575 ev.preventDefault();576 }577 });578 579 }580 581 /**582 * Setup a new chat session and optionally switch to it.583 * @param {string} chatId584 * @param {boolean} bSwitchSession585 */586 new_chat_session(chatId, bSwitchSession=false) {587 this.simpleChats[chatId] = new SimpleChat(chatId);588 if (bSwitchSession) {589 this.handle_session_switch(chatId);590 }591 }592 593 594 /**595 * Handle user query submit request, wrt specified chat session.596 * @param {string} chatId597 * @param {string} apiEP598 */599 async handle_user_submit(chatId, apiEP) {600 601 let chat = this.simpleChats[chatId];602 603 // In completion mode, if configured, clear any previous chat history.604 // So if user wants to simulate a multi-chat based completion query,605 // they will have to enter the full thing, as a suitable multiline606 // user input/query.607 if ((apiEP == ApiEP.Type.Completion) && (gMe.bCompletionFreshChatAlways)) {608 chat.clear();609 }610 611 chat.add_system_anytime(this.elInSystem.value, chatId);612 613 let content = this.elInUser.value;614 if (!chat.add(Roles.User, content)) {615 console.debug(`WARN:SimpleChat:MCUI:${chatId}:HandleUserSubmit:Ignoring empty user input...`);616 return;617 }618 chat.show(this.elDivChat);619 620 let theUrl = ApiEP.Url(gMe.baseURL, apiEP);621 let theBody = chat.request_jsonstr(apiEP);622 623 this.elInUser.value = "working...";624 this.elInUser.disabled = true;625 console.debug(`DBUG:SimpleChat:MCUI:${chatId}:HandleUserSubmit:${theUrl}:ReqBody:${theBody}`);626 let theHeaders = chat.fetch_headers(apiEP);627 let resp = await fetch(theUrl, {628 method: "POST",629 headers: theHeaders,630 body: theBody,631 });632 633 let theResp = await chat.handle_response(resp, apiEP, this.elDivChat);634 if (chatId == this.curChatId) {635 chat.show(this.elDivChat);636 if (theResp.trimmed.length > 0) {637 let p = ui.el_create_append_p(`TRIMMED:${theResp.trimmed}`, this.elDivChat);638 p.className="role-trim";639 }640 } else {641 console.debug(`DBUG:SimpleChat:MCUI:HandleUserSubmit:ChatId has changed:[${chatId}] [${this.curChatId}]`);642 }643 this.ui_reset_userinput();644 }645 646 /**647 * Show buttons for NewChat and available chat sessions, in the passed elDiv.648 * If elDiv is undefined/null, then use this.elDivSessions.649 * Take care of highlighting the selected chat-session's btn.650 * @param {HTMLDivElement | undefined} elDiv651 */652 show_sessions(elDiv=undefined) {653 if (!elDiv) {654 elDiv = this.elDivSessions;655 }656 elDiv.replaceChildren();657 // Btn for creating new chat session658 let btnNew = ui.el_create_button("New CHAT", (ev)=> {659 if (this.elInUser.disabled) {660 console.error(`ERRR:SimpleChat:MCUI:NewChat:Current session [${this.curChatId}] awaiting response, ignoring request...`);661 alert("ERRR:SimpleChat\nMCUI:NewChat\nWait for response to pending query, before starting new chat session");662 return;663 }664 let chatId = `Chat${Object.keys(this.simpleChats).length}`;665 let chatIdGot = prompt("INFO:SimpleChat\nMCUI:NewChat\nEnter id for new chat session", chatId);666 if (!chatIdGot) {667 console.error("ERRR:SimpleChat:MCUI:NewChat:Skipping based on user request...");668 return;669 }670 this.new_chat_session(chatIdGot, true);671 this.create_session_btn(elDiv, chatIdGot);672 ui.el_children_config_class(elDiv, chatIdGot, "session-selected", "");673 });674 elDiv.appendChild(btnNew);675 // Btns for existing chat sessions676 let chatIds = Object.keys(this.simpleChats);677 for(let cid of chatIds) {678 let btn = this.create_session_btn(elDiv, cid);679 if (cid == this.curChatId) {680 btn.className = "session-selected";681 }682 }683 }684 685 create_session_btn(elDiv, cid) {686 let btn = ui.el_create_button(cid, (ev)=>{687 let target = /** @type{HTMLButtonElement} */(ev.target);688 console.debug(`DBUG:SimpleChat:MCUI:SessionClick:${target.id}`);689 if (this.elInUser.disabled) {690 console.error(`ERRR:SimpleChat:MCUI:SessionClick:${target.id}:Current session [${this.curChatId}] awaiting response, ignoring switch...`);691 alert("ERRR:SimpleChat\nMCUI:SessionClick\nWait for response to pending query, before switching");692 return;693 }694 this.handle_session_switch(target.id);695 ui.el_children_config_class(elDiv, target.id, "session-selected", "");696 });697 elDiv.appendChild(btn);698 return btn;699 }700 701 /**702 * Switch ui to the specified chatId and set curChatId to same.703 * @param {string} chatId704 */705 async handle_session_switch(chatId) {706 let chat = this.simpleChats[chatId];707 if (chat == undefined) {708 console.error(`ERRR:SimpleChat:MCUI:HandleSessionSwitch:${chatId} missing...`);709 return;710 }711 this.elInSystem.value = chat.get_system_latest();712 this.elInUser.value = "";713 chat.show(this.elDivChat);714 this.elInUser.focus();715 this.curChatId = chatId;716 console.log(`INFO:SimpleChat:MCUI:HandleSessionSwitch:${chatId} entered...`);717 }718 719}720 721 722class Me {723 724 constructor() {725 this.baseURL = "http://127.0.0.1:8080";726 this.defaultChatIds = [ "Default", "Other" ];727 this.multiChat = new MultiChatUI();728 this.bStream = true;729 this.bCompletionFreshChatAlways = true;730 this.bCompletionInsertStandardRolePrefix = false;731 this.bTrimGarbage = true;732 this.iRecentUserMsgCnt = 2;733 this.sRecentUserMsgCnt = {734 "Full": -1,735 "Last0": 1,736 "Last1": 2,737 "Last2": 3,738 "Last4": 5,739 };740 this.apiEP = ApiEP.Type.Chat;741 this.headers = {742 "Content-Type": "application/json",743 "Authorization": "", // Authorization: Bearer OPENAI_API_KEY744 }745 // Add needed fields wrt json object to be sent wrt LLM web services completions endpoint.746 this.apiRequestOptions = {747 "model": "gpt-3.5-turbo",748 "temperature": 0.7,749 "max_tokens": 1024,750 "n_predict": 1024,751 "cache_prompt": false,752 //"frequency_penalty": 1.2,753 //"presence_penalty": 1.2,754 };755 }756 757 /**758 * Disable console.debug by mapping it to a empty function.759 */760 debug_disable() {761 this.console_debug = console.debug;762 console.debug = () => {763 764 };765 }766 767 /**768 * Setup the load saved chat ui.769 * @param {HTMLDivElement} div770 * @param {SimpleChat} chat771 */772 setup_load(div, chat) {773 if (!(chat.ods_key() in localStorage)) {774 return;775 }776 div.innerHTML += `<p class="role-system">Restore</p>777 <p>Load previously saved chat session, if available</p>`;778 let btn = ui.el_create_button(chat.ods_key(), (ev)=>{779 console.log("DBUG:SimpleChat:SC:Load", chat);780 chat.load();781 queueMicrotask(()=>{782 chat.show(div);783 this.multiChat.elInSystem.value = chat.get_system_latest();784 });785 });786 div.appendChild(btn);787 }788 789 /**790 * Show the configurable parameters info in the passed Div element.791 * @param {HTMLDivElement} elDiv792 * @param {boolean} bAll793 */794 show_info(elDiv, bAll=false) {795 796 let p = ui.el_create_append_p("Settings (devel-tools-console document[gMe])", elDiv);797 p.className = "role-system";798 799 if (bAll) {800 801 ui.el_create_append_p(`baseURL:${this.baseURL}`, elDiv);802 803 ui.el_create_append_p(`Authorization:${this.headers["Authorization"]}`, elDiv);804 805 ui.el_create_append_p(`bStream:${this.bStream}`, elDiv);806 807 ui.el_create_append_p(`bTrimGarbage:${this.bTrimGarbage}`, elDiv);808 809 ui.el_create_append_p(`ApiEndPoint:${this.apiEP}`, elDiv);810 811 ui.el_create_append_p(`iRecentUserMsgCnt:${this.iRecentUserMsgCnt}`, elDiv);812 813 ui.el_create_append_p(`bCompletionFreshChatAlways:${this.bCompletionFreshChatAlways}`, elDiv);814 815 ui.el_create_append_p(`bCompletionInsertStandardRolePrefix:${this.bCompletionInsertStandardRolePrefix}`, elDiv);816 817 }818 819 ui.el_create_append_p(`apiRequestOptions:${JSON.stringify(this.apiRequestOptions, null, " - ")}`, elDiv);820 ui.el_create_append_p(`headers:${JSON.stringify(this.headers, null, " - ")}`, elDiv);821 822 }823 824 /**825 * Auto create ui input elements for fields in apiRequestOptions826 * Currently supports text and number field types.827 * @param {HTMLDivElement} elDiv828 */829 show_settings_apirequestoptions(elDiv) {830 let typeDict = {831 "string": "text",832 "number": "number",833 };834 let fs = document.createElement("fieldset");835 let legend = document.createElement("legend");836 legend.innerText = "ApiRequestOptions";837 fs.appendChild(legend);838 elDiv.appendChild(fs);839 for(const k in this.apiRequestOptions) {840 let val = this.apiRequestOptions[k];841 let type = typeof(val);842 if (((type == "string") || (type == "number"))) {843 let inp = ui.el_creatediv_input(`Set${k}`, k, typeDict[type], this.apiRequestOptions[k], (val)=>{844 if (type == "number") {845 val = Number(val);846 }847 this.apiRequestOptions[k] = val;848 });849 fs.appendChild(inp.div);850 } else if (type == "boolean") {851 let bbtn = ui.el_creatediv_boolbutton(`Set{k}`, k, {true: "true", false: "false"}, val, (userVal)=>{852 this.apiRequestOptions[k] = userVal;853 });854 fs.appendChild(bbtn.div);855 }856 }857 }858 859 /**860 * Show settings ui for configurable parameters, in the passed Div element.861 * @param {HTMLDivElement} elDiv862 */863 show_settings(elDiv) {864 865 let inp = ui.el_creatediv_input("SetBaseURL", "BaseURL", "text", this.baseURL, (val)=>{866 this.baseURL = val;867 });868 elDiv.appendChild(inp.div);869 870 inp = ui.el_creatediv_input("SetAuthorization", "Authorization", "text", this.headers["Authorization"], (val)=>{871 this.headers["Authorization"] = val;872 });873 inp.el.placeholder = "Bearer OPENAI_API_KEY";874 elDiv.appendChild(inp.div);875 876 let bb = ui.el_creatediv_boolbutton("SetStream", "Stream", {true: "[+] yes stream", false: "[-] do oneshot"}, this.bStream, (val)=>{877 this.bStream = val;878 });879 elDiv.appendChild(bb.div);880 881 bb = ui.el_creatediv_boolbutton("SetTrimGarbage", "TrimGarbage", {true: "[+] yes trim", false: "[-] dont trim"}, this.bTrimGarbage, (val)=>{882 this.bTrimGarbage = val;883 });884 elDiv.appendChild(bb.div);885 886 this.show_settings_apirequestoptions(elDiv);887 888 let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{889 this.apiEP = ApiEP.Type[val];890 });891 elDiv.appendChild(sel.div);892 893 sel = ui.el_creatediv_select("SetChatHistoryInCtxt", "ChatHistoryInCtxt", this.sRecentUserMsgCnt, this.iRecentUserMsgCnt, (val)=>{894 this.iRecentUserMsgCnt = this.sRecentUserMsgCnt[val];895 });896 elDiv.appendChild(sel.div);897 898 bb = ui.el_creatediv_boolbutton("SetCompletionFreshChatAlways", "CompletionFreshChatAlways", {true: "[+] yes fresh", false: "[-] no, with history"}, this.bCompletionFreshChatAlways, (val)=>{899 this.bCompletionFreshChatAlways = val;900 });901 elDiv.appendChild(bb.div);902 903 bb = ui.el_creatediv_boolbutton("SetCompletionInsertStandardRolePrefix", "CompletionInsertStandardRolePrefix", {true: "[+] yes insert", false: "[-] dont insert"}, this.bCompletionInsertStandardRolePrefix, (val)=>{904 this.bCompletionInsertStandardRolePrefix = val;905 });906 elDiv.appendChild(bb.div);907 908 }909 910}911 912 913/** @type {Me} */914let gMe;915 916function startme() {917 console.log("INFO:SimpleChat:StartMe:Starting...");918 gMe = new Me();919 gMe.debug_disable();920 document["gMe"] = gMe;921 document["du"] = du;922 for (let cid of gMe.defaultChatIds) {923 gMe.multiChat.new_chat_session(cid);924 }925 gMe.multiChat.setup_ui(gMe.defaultChatIds[0], true);926 gMe.multiChat.show_sessions();927}928 929document.addEventListener("DOMContentLoaded", startme);930 