SimpleChatTC:Cleanup:Make main chat related classes importable

Have main classes defined independent of and away from runtime flow

Move out the entry point including runtime instantiation of the
core Me class (which inturn brings other class instances as neede)
into its own main.js file.

With this one should be able to import simplechat.js into other
files, where one might need the SimpleChat or MultiChat or Me class
definitions.
This commit is contained in:
hanishkvc 2025-11-05 14:42:39 +05:30
parent 83a4b1a3fa
commit d56a4a06b0
3 changed files with 81 additions and 59 deletions

View File

@ -11,12 +11,13 @@
<script type="importmap">
{
"imports": {
"simplechat": "./simplechat.js"
"datautils": "./datautils.mjs",
"ui": "./ui.mjs"
}
}
</script>
<script src="simplechat.js" type="module" defer></script>
<script src="main.js" type="module" defer></script>
<link rel="stylesheet" href="simplechat.css" />
</head>
<body>

View File

@ -0,0 +1,33 @@
// @ts-check
// A simple completions and chat/completions test related web front end logic
// by Humans for All
import * as mChatMagic from './simplechat.js'
import * as tools from "./tools.mjs"
import * as du from "./datautils.mjs";
/** @type {mChatMagic.Me} */
let gMe;
function startme() {
console.log("INFO:SimpleChat:StartMe:Starting...");
gMe = new mChatMagic.Me();
gMe.debug_disable();
// @ts-ignore
document["gMe"] = gMe;
// @ts-ignore
document["du"] = du;
// @ts-ignore
document["tools"] = tools;
tools.init().then((toolNames)=>gMe.tools.toolNames=toolNames).then(()=>gMe.multiChat.chat_show(gMe.multiChat.curChatId))
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);

View File

@ -1,5 +1,6 @@
// @ts-check
// A simple completions and chat/completions test related web front end logic
// Core classes which provide a simple implementation of handshake with ai server's completions and chat/completions endpoints
// as well as related web front end logic for basic usage and testing.
// by Humans for All
import * as du from "./datautils.mjs";
@ -277,7 +278,10 @@ class ChatMessageEx {
}
function usage_note() {
/**
* @param {number} iRecentUserMsgCnt
*/
function usage_note(iRecentUserMsgCnt) {
let sUsageNote = `
<details>
<summary id="UsageNote" class="role-system">Usage Note</summary>
@ -293,7 +297,7 @@ function usage_note() {
<li> If ai assistant requests a tool call, verify same before triggering.</li>
<li> submit tool response placed into user query/response text area</li>
</ul>
<li> ContextWindow = [System, Last[${gMe.chatProps.iRecentUserMsgCnt-1}] User Query/Resp, Cur Query].</li>
<li> ContextWindow = [System, Last[${iRecentUserMsgCnt}] User Query/Resp, Cur Query].</li>
<ul class="ul2">
<li> ChatHistInCtxt, MaxTokens, ModelCtxt window to expand</li>
</ul>
@ -311,8 +315,9 @@ class SimpleChat {
/**
* @param {string} chatId
* @param {Me} me
*/
constructor(chatId) {
constructor(chatId, me) {
this.chatId = chatId;
/**
* Maintain in a form suitable for common LLM web service chat/completions' messages entry
@ -321,6 +326,7 @@ class SimpleChat {
this.xchat = [];
this.iLastSys = -1;
this.latestResponse = new ChatMessageEx();
this.me = me;
}
clear() {
@ -485,14 +491,14 @@ class SimpleChat {
/**
* Setup the fetch headers.
* It picks the headers from gMe.headers.
* It picks the headers from this.me.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];
for(let k in this.me.headers) {
let v = this.me.headers[k];
if ((k == "Authorization") && (v.trim() == "")) {
continue;
}
@ -509,13 +515,13 @@ class SimpleChat {
* @param {Object<string, any>} obj
*/
request_jsonstr_extend(obj) {
for(let k in gMe.apiRequestOptions) {
obj[k] = gMe.apiRequestOptions[k];
for(let k in this.me.apiRequestOptions) {
obj[k] = this.me.apiRequestOptions[k];
}
if (gMe.chatProps.stream) {
if (this.me.chatProps.stream) {
obj["stream"] = true;
}
if (gMe.tools.enabled) {
if (this.me.tools.enabled) {
obj["tools"] = tools.meta();
}
return JSON.stringify(obj);
@ -526,7 +532,7 @@ class SimpleChat {
*/
request_messages_jsonstr() {
let req = {
messages: this.recent_chat_ns(gMe.chatProps.iRecentUserMsgCnt),
messages: this.recent_chat_ns(this.me.chatProps.iRecentUserMsgCnt),
}
return this.request_jsonstr_extend(req);
}
@ -538,7 +544,7 @@ class SimpleChat {
request_prompt_jsonstr(bInsertStandardRolePrefix) {
let prompt = "";
let iCnt = 0;
for(const msg of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) {
for(const msg of this.recent_chat(this.me.chatProps.iRecentUserMsgCnt)) {
iCnt += 1;
if (iCnt > 1) {
prompt += "\n";
@ -562,7 +568,7 @@ class SimpleChat {
if (apiEP == ApiEP.Type.Chat) {
return this.request_messages_jsonstr();
} else {
return this.request_prompt_jsonstr(gMe.chatProps.bCompletionInsertStandardRolePrefix);
return this.request_prompt_jsonstr(this.me.chatProps.bCompletionInsertStandardRolePrefix);
}
}
@ -676,7 +682,7 @@ class SimpleChat {
*/
async handle_response(resp, apiEP, elDiv) {
let theResp = null;
if (gMe.chatProps.stream) {
if (this.me.chatProps.stream) {
try {
theResp = await this.handle_response_multipart(resp, apiEP, elDiv);
this.latestResponse.clear();
@ -690,7 +696,7 @@ class SimpleChat {
} else {
theResp = await this.handle_response_oneshot(resp, apiEP);
}
if (gMe.chatProps.bTrimGarbage) {
if (this.me.chatProps.bTrimGarbage) {
let origMsg = theResp.ns.content;
theResp.ns.content = du.trim_garbage_at_end(origMsg);
theResp.trimmedContent = origMsg.substring(theResp.ns.content.length);
@ -756,7 +762,11 @@ class SimpleChat {
class MultiChatUI {
constructor() {
/**
* @param {Me} me
*/
constructor(me) {
this.me = me
/** @type {Object<string, SimpleChat>} */
this.simpleChats = {};
/** @type {string} */
@ -842,10 +852,10 @@ class MultiChatUI {
this.elInToolName.dataset.tool_call_id = ar.ns.tool_calls[0].id
this.elInToolArgs.value = ar.ns.tool_calls[0].function.arguments
this.elBtnTool.disabled = false
if ((gMe.tools.autoSecs > 0) && (bAuto)) {
if ((this.me.tools.autoSecs > 0) && (bAuto)) {
this.timers.toolcallTriggerClick = setTimeout(()=>{
this.elBtnTool.click()
}, gMe.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit)
}, this.me.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit)
}
} else {
this.elDivTool.hidden = true
@ -992,7 +1002,7 @@ class MultiChatUI {
this.ui_reset_toolcall_as_needed(new ChatMessageEx());
}
this.elLastChatMessage = null
let chatToShow = chat.recent_chat(gMe.chatProps.iRecentUserMsgCnt);
let chatToShow = chat.recent_chat(this.me.chatProps.iRecentUserMsgCnt);
for(const [i, x] of chatToShow.entries()) {
let iFromLast = (chatToShow.length - 1)-i
let nextMsg = undefined
@ -1005,9 +1015,9 @@ class MultiChatUI {
/** @type{HTMLElement} */(this.elLastChatMessage).scrollIntoView(false); // Stupid ts-check js-doc intersection ???
} else {
if (bClear) {
this.elDivChat.innerHTML = usage_note();
gMe.setup_load(this.elDivChat, chat);
gMe.show_info(this.elDivChat, bShowInfoAll);
this.elDivChat.innerHTML = usage_note(this.me.chatProps.iRecentUserMsgCnt-1);
this.me.setup_load(this.elDivChat, chat);
this.me.show_info(this.elDivChat, bShowInfoAll);
}
}
return true
@ -1030,7 +1040,7 @@ class MultiChatUI {
this.elBtnSettings.addEventListener("click", (ev)=>{
this.elDivChat.replaceChildren();
gMe.show_settings(this.elDivChat);
this.me.show_settings(this.elDivChat);
});
this.elBtnClearChat.addEventListener("click", (ev)=>{
this.simpleChats[this.curChatId].clear()
@ -1043,7 +1053,7 @@ class MultiChatUI {
if (this.elInUser.disabled) {
return;
}
this.handle_user_submit(this.curChatId, gMe.chatProps.apiEP).catch((/** @type{Error} */reason)=>{
this.handle_user_submit(this.curChatId, this.me.chatProps.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);
@ -1065,17 +1075,17 @@ class MultiChatUI {
this.timers.toolcallResponseTimeout = undefined
let chat = this.simpleChats[cid];
let limitedData = data
if (gMe.tools.iResultMaxDataLength > 0) {
if (data.length > gMe.tools.iResultMaxDataLength) {
limitedData = data.slice(0, gMe.tools.iResultMaxDataLength) + `\n\n\nALERT: Data too long, was chopped ....`
if (this.me.tools.iResultMaxDataLength > 0) {
if (data.length > this.me.tools.iResultMaxDataLength) {
limitedData = data.slice(0, this.me.tools.iResultMaxDataLength) + `\n\n\nALERT: Data too long, was chopped ....`
}
}
chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(tcid, name, limitedData)))
if (this.chat_show(cid)) {
if (gMe.tools.autoSecs > 0) {
if (this.me.tools.autoSecs > 0) {
this.timers.toolcallResponseSubmitClick = setTimeout(()=>{
this.elBtnUser.click()
}, gMe.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit)
}, this.me.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit)
}
}
this.ui_reset_userinput(false)
@ -1113,7 +1123,7 @@ class MultiChatUI {
* @param {boolean} bSwitchSession
*/
new_chat_session(chatId, bSwitchSession=false) {
this.simpleChats[chatId] = new SimpleChat(chatId);
this.simpleChats[chatId] = new SimpleChat(chatId, this.me);
if (bSwitchSession) {
this.handle_session_switch(chatId);
}
@ -1143,7 +1153,7 @@ class MultiChatUI {
// 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.chatProps.bCompletionFreshChatAlways)) {
if ((apiEP == ApiEP.Type.Completion) && (this.me.chatProps.bCompletionFreshChatAlways)) {
chat.clear();
}
@ -1167,7 +1177,7 @@ class MultiChatUI {
this.elInUser.disabled = true;
try {
let theResp = await chat.handle_chat_hs(gMe.baseURL, apiEP, this.elDivChat)
let theResp = await chat.handle_chat_hs(this.me.baseURL, apiEP, this.elDivChat)
if (chatId == this.curChatId) {
this.chat_show(chatId);
if (theResp.trimmedContent.length > 0) {
@ -1206,7 +1216,7 @@ class MultiChatUI {
chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, `Tool/Function call ${toolname} taking too much time, aborting...`)))
this.chat_show(chat.chatId)
this.ui_reset_userinput(false)
}, gMe.tools.toolCallResponseTimeoutMS)
}, this.me.tools.toolCallResponseTimeoutMS)
}
}
@ -1312,12 +1322,12 @@ const SearchURLS = {
}
class Me {
export class Me {
constructor() {
this.baseURL = "http://127.0.0.1:8080";
this.defaultChatIds = [ "Default", "Other" ];
this.multiChat = new MultiChatUI();
this.multiChat = new MultiChatUI(this);
this.tools = {
enabled: true,
proxyUrl: "http://127.0.0.1:3128",
@ -1462,25 +1472,3 @@ class Me {
}
/** @type {Me} */
let gMe;
function startme() {
console.log("INFO:SimpleChat:StartMe:Starting...");
gMe = new Me();
gMe.debug_disable();
// @ts-ignore
document["gMe"] = gMe;
// @ts-ignore
document["du"] = du;
// @ts-ignore
document["tools"] = tools;
tools.init().then((toolNames)=>gMe.tools.toolNames=toolNames).then(()=>gMe.multiChat.chat_show(gMe.multiChat.curChatId))
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);