Team Ai
Apppublic

KBaba7/llama.cpp

sourceHugging Faceapache-2.0updated 2y agoView on Hugging Face
0likes
simplechat.js930 linesDownload Raw Back to public_simplechat
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