"use strict"; import { Fieldset, Section, Form, Div, Nav, UL, LI, Span, Pre, Article } from "../Utils/HTMLDSL.ecma.js"; import {MarkDown} from "../Utils/MarkDown.ecma.js"; import {Common} from "../Utils/Common.ecma.js"; /** * @typedef {import("../Application/AnP.ecma.js").AnP} AnP */ /** * @class AIChatComponent * @constructor * @param {!AnP} anp * @returns {void} * @access private * @static */ export const AIChatComponent = (function(){ /** * @constructs AIChatComponent * @param {!AnP} anp * @returns {void} * @access private * @static */ const AIChatComponent = function(anp){ /** @type {AIChatComponent} */ const self = this, /** @type {Object.>} */ cache = {}; /** * @returns {void} * @access private */ const constructor = () => { setTimeout(() => { anp.components.aichat = self.build; }, 0); }; /** * @param {!HTMLElement} item * @returns {HTMLFielsetElement} * @access private */ const get_chat_box = item => { while((!item.classList || !item.classList.contains("aichat")) && (item = item.parentElement)); return item; }; /** * @param {?(Object.|Array.)} [inputs = null] * @returns {Array.} * @access public */ this.build = (inputs = null) => { /** @type {string} */ const name = Common.get_value("name", inputs, "aichat"); return Fieldset({ class : "aichat", data_name : Common.get_value("name", inputs, "aichat") }, [ anp.components.i18n(name, "legend"), Section({class : "conversations"}, [ Article({ class : "messages", data_conversation : anp.unique_keys.get(), data_selected : true }) ]), Form({ method : "post", action : "#", on_submit : send, on_key_down : check_keys, data_connection : Common.data_encode([ Common.get_value("web_socket", inputs, "anp"), Common.get_value("controller", inputs, "ai"), Common.get_value(["action", "method"], inputs, "message") ]) }, [ anp.components.text("message", { multiline : true }), anp.components.button("send", "submit") ]) ]); }; /** * @param {!string} name * @param {!string} value * @returns {Array.} * @access private */ const build_data_icon = (name, value) => LI({ data_name : name, data_i18n : value, data_i18n_without : true, title : anp.i18n.get(value) }, [ anp.components.icon(value), anp.components.i18n(value) ]); /** * @param {!string} name * @param {?string} [value = null] * @returns {Array.} * @access private */ const build_data_item = (name, value = null) => LI({ data_name : name, data_i18n : name, data_i18n_without : true, title : anp.i18n.get(name) }, [ anp.components.icon(name), anp.components.i18n(name), value === null ? null : Span({class : "value"}, "" + value) ]); /** * @param {!HTMLElement} item * @returns {HTMLTextAreaElement} * @access private */ const get_message_box = item => { while((!item.classList || !item.classList.contains("message")) && (item = item.parentElement)); return item; }; /** * @param {!HTMLElement} item * @param {!Event} event * @returns {void} * @access private */ const set_view = (item, event) => { /** @type {HTMLTextAreaElement} */ const box = get_message_box(item), /** @type {string} */ mode = item.getAttribute("data-i18n"); box.getAttribute("data-mode") != mode && box.setAttribute("data-mode", mode); }; /** * @param {!string} content * @returns {[string, string, string]} * @access private */ const format = content => { /** @type {string} */ const html = MarkDown.to_html(content); return [html, html.replace(//g, ">"), content]; }; /** * @param {!string} id * @param {!string} type * @param {?string} [content = ""] * @param {?string} [mode = "waiting"] * @returns {HTMLFielsetElement} * @access private */ const build_message = (id, type, content = "", mode = "waiting") => { /** @type {[string, string, string]} */ const [html, raw_html, md] = format(content), /** @type {number} */ date = Date.now(); return Fieldset({ class : "message", data_id : id, data_type : type, data_ok : true, data_status : mode, data_done : mode == "done", data_mode : "html" }, [ anp.components.i18n(type, "legend"), Div({class : "message-box html-content"}, html), Pre({class : "message-box raw-html-content"}, raw_html), Pre({class : "message-box md-content"}, md), Nav({class : "view buttons"}, [ anp.components.button("html", set_view), anp.components.button("raw_html", set_view), anp.components.button("md", set_view), ]), UL({class : "data"}, [ build_data_icon("status", mode), build_data_item("ok"), build_data_item("done"), build_data_item("date_from", date), build_data_item("date_to", date), build_data_item("time", 0), build_data_item("length", content.length), build_data_item("response_tokens", content.replace(/(?:[^a-z0-9]+|[\r\n]+)+/gi, " ").trim().split(" ").length) ]) ]); }; /** * * @param {!HTMLElement} item * @param {!Event} event * @returns {boolean} * @access private */ const send = (item, event) => { /** @type {HTMLTextAreaElement} */ const text_box = item.querySelector("[name=message]"), /** @type {string} */ text = text_box.value.trim(), /** @type {HTMLArticleElement} */ conversation = anp.item_self.querySelector(".aichat [data-conversation][data-selected=true]").getAttribute("data-conversation"); event.preventDefault(); if(text){ console.log([item, item.querySelector("[name=message]"), item.querySelector("[name=message]").parentNode.parentNode.getAttribute("data-connection")]); /** @type {string} */ const data_id = anp.unique_keys.get(), /** @type {[string, string, string]} */ [web_socket, controller, method] = Common.data_decode(item.querySelector("[name=message]").parentNode.parentNode.getAttribute("data-connection")); Common.HTML( ".aichat [data-conversation=" + conversation + "]", build_message(data_id, "user", text, "done"), build_message(data_id, "bot") ); text_box.value = ""; anp.web_sockets_clients.send(web_socket, controller, method, { message_id : data_id, message : text, conversation : conversation }); }; return false; }; /** * @param {!HTMLElement} item * @param {!Event} event * @returns {void} * @access private */ const check_keys = (item, event) => { event.key == "Enter" && !event.shiftKey && send(item, event); }; /** * @param {!string} conversation * @param {!string} message * @param {!string} status * @returns {void} * @access private */ const set_status = (conversation, message, status) => { /** @type {HTMLFieldSetElement} */ const box = anp.item_self.querySelector(".aichat [data-conversation='" + conversation + "']>[data-type=bot][data-id='" + message + "']"), /** @type {HTMLSpanElement} */ status_box = box.querySelector("[data-name=status]"), /** @type {HTMLSpanElement} */ status_i18n_box = status_box.querySelector("[data-i18n]"), /** @type {string} */ status_text = anp.i18n.get(status); box.setAttribute("data-status", status); status_box.setAttribute("data-i18n", status); status_box.setAttribute("title", status_text); status_box.querySelector("[data-icon]").setAttribute("data-icon", status); status_i18n_box.setAttribute("data-i18n", status_text); status_i18n_box.innerHTML = status_text; }; /** * @param {!string} conversation * @param {!string} message * @param {!string} fragment * @param {!boolean} ok * @param {!boolean} done * @returns {void} * @access public */ this.write_response = (conversation, message, fragment, ok, done) => { /** @type {HTMLFieldSetElement} */ const box = anp.item_self.querySelector(".aichat .messages>[data-type=bot][data-id='" + message + "']"), /** @type {number} */ date = Date.now(), /** @type {HTMLSpanElement} */ tokens_box = box.querySelector("[data-name=response_tokens] .value"), /** @type {[string, string, string]} */ [html, raw_html, md] = format(box.querySelector(".md-content").textContent += fragment); box.querySelector(".html-content").innerHTML = html; box.querySelector(".raw-html-content").innerHTML = raw_html; box.querySelector("[data-name=length] .value").innerText = md.length; box.setAttribute("data-ok", ok); box.setAttribute("data-done", done); set_status(conversation, message, ( !ok ? "error" : done ? "done" : "loading")); box.querySelector("[data-name=date_to]").querySelector(".value").innerHTML = date; box.querySelector("[data-name=time]").querySelector(".value").innerHTML = date - Number(box.querySelector("[data-name=date_from] .value").innerText); tokens_box.innerText = Number(tokens_box.innerText) + 1; }; /** * @param {!string} conversation * @param {!string} message * @param {!string} status * @returns {void} * @access public */ this.change_status = (conversation, message, status) => { set_status(conversation, message, status); }; constructor(); }; return AIChatComponent; })();