From e79faebde139e219bc9d4de2401e08aac0d9e6c3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 00:23:04 +0530 Subject: [PATCH] SimpleChatTC:ToolTemp and ChatShow Add a new role ToolTemp, which is used to maintain any tool call response on the client ui side, without submitting it to the server ie till user or auto submit triggers the submitting of that tool call response. When ever a tool call response is got, create a ToolTemp role based message in the corresponding chat session. And dont directly update the user query input area, rather leave it to the updated simplechat show and the new multichatui chat_show helper and inturn whether the current chat session active in ui is same as the one for which the tool call response has been recieved. TODO: Currently the response message is added to the current active chat session, but this needs to be changed by tracking chatId/session through the full tool call cycle and then adding the tool call response in the related chat session, and inturn updating or not the ui based on whether that chat session is still the active chat session in ui or not, given that tool call gets handled in a asynchronous way. Now when that tool call response is submitted, promote the equiv tool temp role based message that should be in the session's chat history as the last message into becoming a normal tool response message. SimpleChat.show has been updated to take care of showing any ToolTemp role message in the user query input area. A newer chat_show helper added to MultiChatUI, that takes care of calling SimpleChat.show, provided the chat_show is being requested for the currently active in ui, chat session. As well as to take care of passing both the ChatDiv and elInUser. Converts users of SimpleChat.show to use MultiChatUI.chat_show --- tools/server/public_simplechat/simplechat.js | 82 ++++++++++++++++---- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 21ebb008f8..f68cc12f60 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -12,6 +12,7 @@ class Roles { static User = "user"; static Assistant = "assistant"; static Tool = "tool"; + static ToolTemp = "TOOL.TEMP"; } class ApiEP { @@ -319,7 +320,7 @@ class SimpleChat { this.iLastSys = ods.iLastSys; this.xchat = []; for (const cur of ods.xchat) { - if (cur.ns == undefined) { + if (cur.ns == undefined) { // this relates to the old on-disk-structure/format, needs to be removed later /** @typedef {{role: string, content: string}} OldChatMessage */ let tcur = /** @type {OldChatMessage} */(/** @type {unknown} */(cur)); this.xchat.push(new ChatMessageEx(tcur.role, tcur.content)) @@ -383,6 +384,12 @@ class SimpleChat { let xchat = this.recent_chat(iRecentUserMsgCnt); let chat = []; for (const msg of xchat) { + if (msg.ns.role == Roles.ToolTemp) { + // Skip (temp) tool response which has not yet been accepted by user + // In future need to check that it is the last message + // and not something in between, which shouldnt occur normally. + continue + } let tmsg = ChatMessageEx.newFrom(msg); if (!tmsg.has_toolcall()) { tmsg.ns_delete("tool_calls") @@ -413,25 +420,55 @@ class SimpleChat { return true; } + /** + * Check if the last message in the chat history is a ToolTemp role based one. + * If so, then + * * update that to a regular Tool role based message. + * * also update the content of that message to what is passed. + * @param {string} content + */ + promote_tooltemp(content) { + let lastIndex = this.xchat.length - 1; + if (lastIndex < 0) { + console.error("DBUG:SimpleChat:PromoteToolTemp:No chat messages including ToolTemp") + return + } + if (this.xchat[lastIndex].ns.role != Roles.ToolTemp) { + console.error("DBUG:SimpleChat:PromoteToolTemp:LastChatMsg not ToolTemp") + return + } + this.xchat[lastIndex].ns.role = Roles.Tool; + this.xchat[lastIndex].ns.content = content; + } + /** * Show the chat contents in the specified div. + * Also update the user query input box, with ToolTemp role message, if any. + * * If requested to clear prev stuff and inturn no chat content then show * * usage info * * option to load prev saved chat if any * * as well as settings/info. * @param {HTMLDivElement} div + * @param {HTMLInputElement} elInUser * @param {boolean} bClear * @param {boolean} bShowInfoAll */ - show(div, bClear=true, bShowInfoAll=false) { + show(div, elInUser, bClear=true, bShowInfoAll=false) { if (bClear) { div.replaceChildren(); } let last = undefined; for(const x of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) { - let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); - entry.className = `role-${x.ns.role}`; - last = entry; + if (x.ns.role != Roles.ToolTemp) { + let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); + entry.className = `role-${x.ns.role}`; + last = entry; + } else { + if (elInUser) { + elInUser.value = x.ns.content; + } + } } if (last !== undefined) { last.scrollIntoView(false); @@ -792,6 +829,20 @@ class MultiChatUI { this.elInUser.focus(); } + /** + * Refresh UI wrt given chatId, provided it matches the currently selected chatId + * @param {string} chatId + * @param {boolean} bClear + * @param {boolean} bShowInfoAll + */ + chat_show(chatId, bClear=true, bShowInfoAll=false) { + if (chatId != this.curChatId) { + return + } + let chat = this.simpleChats[this.curChatId]; + chat.show(this.elDivChat, this.elInUser, bClear, bShowInfoAll) + } + /** * Setup the needed callbacks wrt UI, curChatId to defaultChatId and * optionally switch to specified defaultChatId. @@ -839,7 +890,12 @@ class MultiChatUI { tools.setup((id, name, data)=>{ clearTimeout(this.timers.toolcallResponseTimeout) this.timers.toolcallResponseTimeout = undefined - this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(id, name, data); + // TODO: Check for chat id in future so as to + // identify the right chat session to add the tc response to + // as well as to decide whether to show this chat currently or not and same with auto submit + let chat = this.simpleChats[this.curChatId]; // rather we should pick chat based on tool response's chatId + chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(id, name, data))) + this.chat_show(chat.chatId) // one needs to use tool response's chatId this.ui_reset_userinput(false) if (gMe.tools.auto > 0) { this.timers.toolcallResponseSubmitClick = setTimeout(()=>{ @@ -867,7 +923,7 @@ class MultiChatUI { 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); + this.chat_show(chat.chatId) ev.preventDefault(); } }); @@ -922,11 +978,11 @@ class MultiChatUI { return; } if (content.startsWith("")) { - chat.add(new ChatMessageEx(Roles.Tool, content)) + chat.promote_tooltemp(content) } else { chat.add(new ChatMessageEx(Roles.User, content)) } - chat.show(this.elDivChat); + this.chat_show(chat.chatId); let theUrl = ApiEP.Url(gMe.baseURL, apiEP); let theBody = chat.request_jsonstr(apiEP); @@ -943,7 +999,7 @@ class MultiChatUI { let theResp = await chat.handle_response(resp, apiEP, this.elDivChat); if (chatId == this.curChatId) { - chat.show(this.elDivChat); + this.chat_show(chatId); if (theResp.trimmedContent.length > 0) { let p = ui.el_create_append_p(`TRIMMED:${theResp.trimmedContent}`, this.elDivChat); p.className="role-trim"; @@ -1053,9 +1109,9 @@ class MultiChatUI { } this.elInSystem.value = chat.get_system_latest().ns.content; this.elInUser.value = ""; - chat.show(this.elDivChat, true, true); - this.elInUser.focus(); this.curChatId = chatId; + this.chat_show(chatId, true, true); + this.elInUser.focus(); console.log(`INFO:SimpleChat:MCUI:HandleSessionSwitch:${chatId} entered...`); } @@ -1159,7 +1215,7 @@ class Me { console.log("DBUG:SimpleChat:SC:Load", chat); chat.load(); queueMicrotask(()=>{ - chat.show(div, true, true); + chat.show(div, this.multiChat.elInUser, true, true); this.multiChat.elInSystem.value = chat.get_system_latest().ns.content; }); });