@cnt-dev/chat-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +202 -0
- package/LICENSE +21 -0
- package/README.md +303 -0
- package/index.d.ts +23 -0
- package/package.json +41 -0
- package/resources/chatui/.library +14 -0
- package/resources/chatui/ChatBox-dbg.js +1099 -0
- package/resources/chatui/ChatBox-dbg.js.map +1 -0
- package/resources/chatui/ChatBox.d.ts +308 -0
- package/resources/chatui/ChatBox.d.ts.map +1 -0
- package/resources/chatui/ChatBox.gen.d.ts +545 -0
- package/resources/chatui/ChatBox.js +5 -0
- package/resources/chatui/ChatBox.js.map +1 -0
- package/resources/chatui/ChatBox.ts +1153 -0
- package/resources/chatui/library-dbg.js +28 -0
- package/resources/chatui/library-dbg.js.map +1 -0
- package/resources/chatui/library-preload.js +17 -0
- package/resources/chatui/library-preload.js.map +1 -0
- package/resources/chatui/library.d.ts +3 -0
- package/resources/chatui/library.d.ts.map +1 -0
- package/resources/chatui/library.js +5 -0
- package/resources/chatui/library.js.map +1 -0
- package/resources/chatui/library.ts +30 -0
- package/resources/chatui/manifest.json +52 -0
- package/resources/chatui/markdown-dbg.js +116 -0
- package/resources/chatui/markdown-dbg.js.map +1 -0
- package/resources/chatui/markdown.d.ts +21 -0
- package/resources/chatui/markdown.d.ts.map +1 -0
- package/resources/chatui/markdown.js +5 -0
- package/resources/chatui/markdown.js.map +1 -0
- package/resources/chatui/markdown.ts +140 -0
- package/resources/chatui/messagebundle.properties +20 -0
- package/resources/chatui/messagebundle_en.properties +20 -0
- package/resources/chatui/themes/base/ChatBox.less +702 -0
- package/resources/chatui/themes/base/library-RTL.css +3 -0
- package/resources/chatui/themes/base/library-parameters.json +1 -0
- package/resources/chatui/themes/base/library.css +3 -0
- package/resources/chatui/themes/base/library.source.less +4 -0
- package/resources/chatui/themes/sap_fiori_3/library-RTL.css +3 -0
- package/resources/chatui/themes/sap_fiori_3/library-parameters.json +1 -0
- package/resources/chatui/themes/sap_fiori_3/library.css +3 -0
- package/resources/chatui/themes/sap_fiori_3/library.source.less +3 -0
- package/resources/chatui/themes/sap_fiori_3_dark/library-RTL.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_dark/library-parameters.json +1 -0
- package/resources/chatui/themes/sap_fiori_3_dark/library.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_dark/library.source.less +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcb/library-RTL.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcb/library-parameters.json +1 -0
- package/resources/chatui/themes/sap_fiori_3_hcb/library.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcb/library.source.less +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcw/library-RTL.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcw/library-parameters.json +1 -0
- package/resources/chatui/themes/sap_fiori_3_hcw/library.css +3 -0
- package/resources/chatui/themes/sap_fiori_3_hcw/library.source.less +3 -0
- package/ui5.yaml +15 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ${copyright}
|
|
3
|
+
*/
|
|
4
|
+
sap.ui.define(["sap/ui/core/Lib"], function (Lib) {
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Initialization code and shared classes of library
|
|
9
|
+
* <code>chatui</code>. The library ships a single control,
|
|
10
|
+
* {@link chatui.ChatBox}, an embeddable AI chat block for UI5
|
|
11
|
+
* applications.
|
|
12
|
+
*
|
|
13
|
+
* @namespace chatui
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
Lib.init({
|
|
17
|
+
name: "chatui",
|
|
18
|
+
version: "0.1.0",
|
|
19
|
+
dependencies: ["sap.ui.core"],
|
|
20
|
+
types: [],
|
|
21
|
+
interfaces: [],
|
|
22
|
+
controls: ["chatui.ChatBox"],
|
|
23
|
+
elements: [],
|
|
24
|
+
noLibraryCSS: false,
|
|
25
|
+
apiVersion: 2
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=library-dbg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library-dbg.js","names":["sap","ui","define","Lib","init","name","version","dependencies","types","interfaces","controls","elements","noLibraryCSS","apiVersion"],"sources":["library.ts"],"sourcesContent":["/*!\n * ${copyright}\n */\n\nimport Lib from \"sap/ui/core/Lib\";\n\n/**\n * Initialization code and shared classes of library\n * <code>chatui</code>. The library ships a single control,\n * {@link chatui.ChatBox}, an embeddable AI chat block for UI5\n * applications.\n *\n * @namespace chatui\n * @public\n */\nLib.init({\n name: \"chatui\",\n version: \"0.1.0\",\n dependencies: [\n \"sap.ui.core\"\n ],\n types: [],\n interfaces: [],\n controls: [\n \"chatui.ChatBox\"\n ],\n elements: [],\n noLibraryCSS: false,\n apiVersion: 2\n});\n"],"mappings":"AAAA;AACA;AACA;AAFAA,GAAA,CAAAC,EAAA,CAAAC,MAAA,gCAAAC,GAAA;EAAA;;EAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACAA,GAAG,CAACC,IAAI,CAAC;IACLC,IAAI,EAAE,QAAQ;IACdC,OAAO,EAAE,OAAO;IAChBC,YAAY,EAAE,CACV,aAAa,CAChB;IACDC,KAAK,EAAE,EAAE;IACTC,UAAU,EAAE,EAAE;IACdC,QAAQ,EAAE,CACN,gBAAgB,CACnB;IACDC,QAAQ,EAAE,EAAE;IACZC,YAAY,EAAE,KAAK;IACnBC,UAAU,EAAE;EAChB,CAAC,CAAC;AAAC","ignoreList":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//@ui5-bundle chatui/library-preload.js
|
|
2
|
+
/*!
|
|
3
|
+
* ${copyright}
|
|
4
|
+
*/
|
|
5
|
+
sap.ui.predefine("chatui/ChatBox", ["sap/ui/core/Control","sap/ui/core/IconPool","sap/ui/core/Lib","sap/base/i18n/Localization","./markdown"],function(t,e,n,s,i){"use strict";function a(t){return t&&t.__esModule&&typeof t.default!=="undefined"?t.default:t}const o=a(i);const c=t.extend("chatui.ChatBox",{renderer:{apiVersion:2,render(t,n){const s=e.getIconInfo("sap-icon://paper-plane");const i=e.getIconInfo("sap-icon://add");const a=e.getIconInfo("sap-icon://attachment");const o=n._i18n();const c=n.getPlaceholder()||o.getText("chatbox.placeholder")||"";const r=n.getAllowAttachments();t.openStart("div",n);t.class("cnetChatBox");t.style("width",n.getWidth());t.style("height",n.getHeight());t.openEnd();t.openStart("aside");t.class("cnetChatBox-sidebar");t.openEnd();t.openStart("div");t.class("cnetChatBox-sidebar-header");t.openEnd();t.openStart("span");t.class("cnetChatBox-sidebar-title");t.openEnd();t.text(o.getText("chatbox.conversations")??"");t.close("span");t.close("div");t.openStart("div",n.getId()+"-conversations");t.class("cnetChatBox-conversations");t.openEnd();t.close("div");t.close("aside");t.openStart("div");t.class("cnetChatBox-main");t.openEnd();t.openStart("div");t.class("cnetChatBox-header");t.openEnd();t.openStart("div",n.getId()+"-headerTitle");t.class("cnetChatBox-header-title");t.openEnd();t.close("div");t.openStart("button",n.getId()+"-newChat");t.class("cnetChatBox-newChatButton");t.attr("type","button");t.attr("title",o.getText("chatbox.newConversation")??"");t.openEnd();t.openStart("span");t.class("cnetChatBox-icon");if(i)t.style("font-family",i.fontFamily);t.openEnd();t.text(i?.content??"+");t.close("span");t.openStart("span");t.class("cnetChatBox-newChatButton-label");t.openEnd();t.text(o.getText("chatbox.newConversation")??"");t.close("span");t.close("button");t.close("div");t.openStart("div",n.getId()+"-messages");t.class("cnetChatBox-messages");t.openEnd();t.openStart("div");t.class("cnetChatBox-empty");t.openEnd();t.text(o.getText("chatbox.emptyHint")??"");t.close("div");t.close("div");t.openStart("div");t.class("cnetChatBox-inputArea");t.openEnd();t.openStart("div");t.class("cnetChatBox-composer");t.openEnd();t.openStart("div",n.getId()+"-pending");t.class("cnetChatBox-attachmentsPending");t.openEnd();t.close("div");t.openStart("textarea",n.getId()+"-input");t.class("cnetChatBox-textarea");t.attr("placeholder",c);t.attr("rows","1");if(n.getBusy())t.attr("disabled","disabled");t.openEnd();t.close("textarea");t.openStart("div");t.class("cnetChatBox-composerBar");t.openEnd();t.openStart("div");t.class("cnetChatBox-composerBar-left");t.openEnd();if(r){t.openStart("button",n.getId()+"-attach");t.class("cnetChatBox-toolButton");t.class("cnetChatBox-attach");t.attr("type","button");t.attr("title",o.getText("chatbox.attach")??"");t.attr("aria-label",o.getText("chatbox.attach")??"");t.openEnd();t.openStart("span");t.class("cnetChatBox-icon");if(a)t.style("font-family",a.fontFamily);t.openEnd();t.text(a?.content??"📎");t.close("span");t.close("button");t.voidStart("input",n.getId()+"-fileinput");t.class("cnetChatBox-fileinput");t.attr("type","file");t.attr("multiple","multiple");if(n.getAttachmentAccept())t.attr("accept",n.getAttachmentAccept());t.voidEnd()}t.close("div");t.openStart("button",n.getId()+"-send");t.class("cnetChatBox-send");t.attr("type","button");t.attr("title",o.getText("chatbox.send")??"");t.attr("aria-label",o.getText("chatbox.send")??"");t.openEnd();t.openStart("span");t.class("cnetChatBox-icon");if(s)t.style("font-family",s.fontFamily);t.openEnd();t.text(s?.content??"➤");t.close("span");t.close("button");t.close("div");t.close("div");t.close("div");t.close("div");if(r){t.openStart("div",n.getId()+"-drop");t.class("cnetChatBox-dropOverlay");t.openEnd();t.openStart("span");t.class("cnetChatBox-dropLabel");t.openEnd();t.text(o.getText("chatbox.dropHere")??"");t.close("span");t.close("div")}t.close("div")}},metadata:{library:"chatui",properties:{placeholder:{type:"string",group:"Appearance",defaultValue:""},busy:{type:"boolean",group:"Behavior",defaultValue:false},width:{type:"sap.ui.core.CSSSize",group:"Dimension",defaultValue:"100%"},height:{type:"sap.ui.core.CSSSize",group:"Dimension",defaultValue:"100%"},allowAttachments:{type:"boolean",group:"Behavior",defaultValue:true},attachmentAccept:{type:"string",group:"Behavior",defaultValue:""},conversations:{type:"object",group:"Data",defaultValue:[]},activeConversationId:{type:"string",group:"Data",defaultValue:""}},events:{send:{parameters:{message:{type:"string"},files:{type:"object"},conversationId:{type:"string"}}},newChat:{},conversationSelect:{parameters:{id:{type:"string"}}},conversationDelete:{parameters:{id:{type:"string"}}}}},constructor:function e(n,s){t.prototype.constructor.call(this,n,s);this._messages=[];this._pendingAttachments=[];this._conversations=[];this._initialised=false;this._onLocaleChange=()=>{this.invalidate()}},init:function t(){s.attachChange(this._onLocaleChange)},exit:function t(){s.detachChange(this._onLocaleChange)},onAfterRendering:function t(){const e=this._dom("input");const n=this._dom("send");const s=this._dom("newChat");e?.addEventListener("input",()=>{e.style.height="auto";e.style.height=Math.min(e.scrollHeight,160)+"px"});e?.addEventListener("keydown",t=>{if(t.key==="Enter"&&!t.shiftKey){t.preventDefault();this._submit()}});n?.addEventListener("click",()=>this._submit());s?.addEventListener("click",()=>this._startNewChat());if(this.getAllowAttachments()){this._wireAttachments()}this._renderSidebar();this._renderHeaderTitle();this._restoreMessages();this._renderPending()},addAssistantMessage:function t(e){this._messages.push({role:"assistant",content:e??""});this._appendMessageDom(this._messages.length-1);return this},appendToLastAssistantMessage:function t(e){if(!e)return this;const n=this._messages[this._messages.length-1];if(!n||n.role!=="assistant"||n.control){return this.addAssistantMessage(e)}n.content+=e;this._updateBubble(this._messages.length-1);return this},addAssistantControl:function t(e){this.addDependent(e);this._messages.push({role:"assistant",content:"",control:e});this._appendMessageDom(this._messages.length-1);return this},clear:function t(){this._messages=[];this._pendingAttachments=[];this.destroyDependents();this._restoreMessages();this._renderPending();return this},getMessages:function t(){return this._messages.slice()},setMessages:function t(e){this._messages=this._sanitiseMessages(e);this.destroyDependents();this._restoreMessages();return this},setConversations:function t(e){const n=this._sanitiseConversations(e);this._conversations=n;this.setProperty("conversations",n,true);if(!this._initialised&&n.length>0&&!this.getActiveConversationId()){const t=n[0];this.setProperty("activeConversationId",t.id,true);this._messages=this._sanitiseMessages(t.messages??[]);this.destroyDependents();this._restoreMessages();this._renderHeaderTitle()}this._initialised=true;this._renderSidebar();return this},getConversations:function t(){return this._conversations.slice()},setActiveConversationId:function t(e){const n=typeof e==="string"?e:"";if(n===this.getActiveConversationId())return this;this.setProperty("activeConversationId",n,true);const s=this._conversations??[];const i=s.find(t=>t.id===n);this._messages=i?this._sanitiseMessages(i.messages??[]):[];this._pendingAttachments=[];this.destroyDependents();this._renderSidebar();this._renderHeaderTitle();this._restoreMessages();this._renderPending();return this},_i18n:function t(){return n.getResourceBundleFor("chatui")},_dom:function t(e){return document.getElementById(this.getId()+"-"+e)},_messagesContainer:function t(){return this._dom("messages")},_sanitiseMessages:function t(e){const n=[];if(!Array.isArray(e))return n;for(const t of e){if(!t||t.role!=="user"&&t.role!=="assistant")continue;if(typeof t.content!=="string")continue;if(t.control)continue;const e={role:t.role,content:t.content};if(Array.isArray(t.files)&&t.files.length>0){e.files=t.files.slice()}n.push(e)}return n},_sanitiseConversations:function t(e){const n=[];if(!Array.isArray(e))return n;for(const t of e){if(!t||typeof t.id!=="string"||!t.id)continue;const e={id:t.id};if(typeof t.title==="string")e.title=t.title;if(Array.isArray(t.messages))e.messages=this._sanitiseMessages(t.messages);if(t.updatedAt!==undefined&&t.updatedAt!==null)e.updatedAt=t.updatedAt;n.push(e)}return n},_submit:function t(){if(this.getBusy())return;const e=this._dom("input");const n=e?.value.trim()??"";const s=this._pendingAttachments.slice();if(!n&&s.length===0)return;if(e){e.value="";e.style.height="auto"}const i={role:"user",content:n};if(s.length>0)i.files=s;this._messages.push(i);this._pendingAttachments=[];this._appendMessageDom(this._messages.length-1);this._renderPending();this.fireEvent("send",{message:n,files:s,conversationId:this.getActiveConversationId()})},_startNewChat:function t(){if(this.getBusy())return;this._messages=[];this._pendingAttachments=[];this.destroyDependents();this.setProperty("activeConversationId","",true);this._renderSidebar();this._renderHeaderTitle();this._restoreMessages();this._renderPending();this.fireEvent("newChat")},_selectConversation:function t(e){if(this.getBusy())return;if(e===this.getActiveConversationId())return;this.setActiveConversationId(e);this.fireEvent("conversationSelect",{id:e})},_requestDeleteConversation:function t(e){if(this.getBusy())return;this.fireEvent("conversationDelete",{id:e})},_buildMessageDom:function t(e){const n=this._messages[e];const s=document.createElement("div");const i=n.role==="assistant";s.className=`cnetChatBox-message cnetChatBox-message--${i?"assistant":"user"}`;const a=document.createElement("div");a.className="cnetChatBox-bubble";this._renderBubble(a,n);s.appendChild(a);if(!n.control){const t=this._buildActions(e,n);if(t)s.appendChild(t)}return s},_buildActions:function t(e,n){const s=document.createElement("div");s.className="cnetChatBox-actions";if(n.role==="assistant"){s.appendChild(this._buildCopyButton(e))}else{s.appendChild(this._buildEditButton(e))}return s},_buildIconButton:function t(n,s,i,a){const o=e.getIconInfo(n);const c=document.createElement("button");c.type="button";c.className=`cnetChatBox-action ${s}`;c.title=i;c.setAttribute("aria-label",i);const r=document.createElement("span");r.className="cnetChatBox-icon";if(o)r.style.fontFamily=o.fontFamily;r.textContent=o?.content??a;c.appendChild(r);return c},_buildCopyButton:function t(e){const n=this._i18n().getText("chatbox.copy")??"Copy";const s=this._buildIconButton("sap-icon://copy","cnetChatBox-copy",n,"⎘");s.addEventListener("click",()=>{const t=this._messages[e];if(!t||!t.content)return;void this._copyMessage(t.content,s)});return s},_buildEditButton:function t(e){const n=this._i18n().getText("chatbox.edit")??"Edit";const s=this._buildIconButton("sap-icon://edit","cnetChatBox-edit",n,"✎");s.addEventListener("click",()=>this._enterEditMode(e));return s},_copyMessage:async function t(e,n){const s=await this._writeClipboard(e);if(!s)return;const i=this._i18n();const a=n.getAttribute("aria-label")??"";const o=i.getText("chatbox.copied")??"Copied";n.classList.add("cnetChatBox-action--done");n.title=o;window.setTimeout(()=>{n.classList.remove("cnetChatBox-action--done");n.title=a},1200)},_enterEditMode:function t(e){if(this.getBusy())return;const n=this._messagesContainer()?.children[e];const s=this._messages[e];if(!n||!s||s.role!=="user")return;const i=n.querySelector(".cnetChatBox-bubble");if(!i)return;const a=n.querySelector(".cnetChatBox-actions");if(a)a.style.display="none";i.textContent="";i.classList.add("cnetChatBox-bubble--editing");if(s.files&&s.files.length>0){i.appendChild(this._buildAttachmentList(s.files,"sent"))}const o=document.createElement("textarea");o.className="cnetChatBox-editTextarea";o.value=s.content;o.rows=2;i.appendChild(o);const c=document.createElement("div");c.className="cnetChatBox-editActions";const r=this._i18n();const d=document.createElement("button");d.type="button";d.className="cnetChatBox-editCancel";d.textContent=r.getText("chatbox.cancel")??"Cancel";const l=document.createElement("button");l.type="button";l.className="cnetChatBox-editSave";l.textContent=r.getText("chatbox.save")??"Save";d.addEventListener("click",()=>this._cancelEdit(e));l.addEventListener("click",()=>this._commitEdit(e,o.value));o.addEventListener("keydown",t=>{if(t.key==="Enter"&&!t.shiftKey){t.preventDefault();this._commitEdit(e,o.value)}else if(t.key==="Escape"){t.preventDefault();this._cancelEdit(e)}});c.appendChild(d);c.appendChild(l);i.appendChild(c);o.focus();o.setSelectionRange(o.value.length,o.value.length)},_cancelEdit:function t(e){const n=this._messagesContainer()?.children[e];const s=this._messages[e];if(!n||!s)return;const i=n.querySelector(".cnetChatBox-bubble");if(!i)return;i.classList.remove("cnetChatBox-bubble--editing");this._renderBubble(i,s);const a=n.querySelector(".cnetChatBox-actions");if(a)a.style.display=""},_commitEdit:function t(e,n){if(this.getBusy())return;const s=this._messages[e];if(!s||s.role!=="user")return;const i=n.trim();const a=s.files?s.files.slice():[];const o=i===s.content;const c=!i&&a.length===0;if(o||c){this._cancelEdit(e);return}for(let t=e+1;t<this._messages.length;t++){const e=this._messages[t];if(e.control)e.control.destroy()}this._messages.length=e+1;s.content=i;this._restoreMessages();this.fireEvent("send",{message:i,files:a,conversationId:this.getActiveConversationId()})},_writeClipboard:async function t(e){try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(e);return true}}catch{}try{const t=document.createElement("textarea");t.value=e;t.setAttribute("readonly","");t.style.position="fixed";t.style.opacity="0";document.body.appendChild(t);t.select();const n=document.execCommand("copy");document.body.removeChild(t);return n}catch{return false}},_renderBubble:function t(e,n){if(n.role==="assistant"){if(n.control){e.classList.remove("cnetChatBox-bubble--pending");e.classList.add("cnetChatBox-bubble--rich");e.innerHTML="";n.control.placeAt(e);return}e.classList.remove("cnetChatBox-bubble--rich");if(!n.content){e.classList.add("cnetChatBox-bubble--pending");e.innerHTML='<span class="cnetChatBox-dots"><span></span><span></span><span></span></span>';return}e.classList.remove("cnetChatBox-bubble--pending");e.innerHTML=o(n.content);return}e.textContent="";if(n.files&&n.files.length>0){e.appendChild(this._buildAttachmentList(n.files,"sent"))}if(n.content){const t=document.createElement("div");t.className="cnetChatBox-bubble-text";t.textContent=n.content;e.appendChild(t)}},_buildAttachmentList:function t(e,n){const s=document.createElement("div");s.className=`cnetChatBox-attachments cnetChatBox-attachments--${n}`;for(let t=0;t<e.length;t++){s.appendChild(this._buildAttachmentItem(e[t],n==="pending"?t:-1))}return s},_buildAttachmentItem:function t(n,s){const i=n.type.startsWith("image/");const a=document.createElement("div");a.className=`cnetChatBox-attachment cnetChatBox-attachment--${i?"image":"file"}`;if(i){const t=document.createElement("img");t.className="cnetChatBox-attachment-thumb";t.alt=n.name;t.src=URL.createObjectURL(n);a.appendChild(t)}else{const t=e.getIconInfo("sap-icon://document");const n=document.createElement("span");n.className="cnetChatBox-icon cnetChatBox-attachment-icon";if(t)n.style.fontFamily=t.fontFamily;n.textContent=t?.content??"📄";a.appendChild(n)}const o=document.createElement("span");o.className="cnetChatBox-attachment-name";o.textContent=n.name;o.title=n.name;a.appendChild(o);if(s>=0){const t=this._i18n();const e=t.getText("chatbox.removeAttachment")??"Remove";const n=document.createElement("button");n.type="button";n.className="cnetChatBox-attachment-remove";n.title=e;n.setAttribute("aria-label",e);n.textContent="×";n.addEventListener("click",()=>this._removePending(s));a.appendChild(n)}return a},_appendMessageDom:function t(e){const n=this._messagesContainer();if(!n)return;n.querySelector(".cnetChatBox-empty")?.remove();n.appendChild(this._buildMessageDom(e));n.scrollTop=n.scrollHeight},_updateBubble:function t(e){const n=this._messagesContainer();const s=n?.children[e];const i=s?.querySelector(".cnetChatBox-bubble");if(!i||!n)return;this._renderBubble(i,this._messages[e]);const a=n.scrollHeight-n.scrollTop-n.clientHeight<80;if(a)n.scrollTop=n.scrollHeight},_restoreMessages:function t(){const e=this._messagesContainer();if(!e)return;e.innerHTML="";if(this._messages.length===0){const t=document.createElement("div");t.className="cnetChatBox-empty";t.textContent=this._i18n().getText("chatbox.emptyHint")??"";e.appendChild(t);return}for(let t=0;t<this._messages.length;t++){e.appendChild(this._buildMessageDom(t))}e.scrollTop=e.scrollHeight},_renderSidebar:function t(){const e=this._dom("conversations");if(!e)return;e.innerHTML="";if(this._conversations.length===0){const t=document.createElement("div");t.className="cnetChatBox-conversations-empty";t.textContent=this._i18n().getText("chatbox.noConversations")??"";e.appendChild(t);return}const n=this.getActiveConversationId();for(const t of this._conversations){e.appendChild(this._buildConversationItem(t,t.id===n))}},_buildConversationItem:function t(e,n){const s=this._i18n();const i=document.createElement("div");i.className="cnetChatBox-conversation"+(n?" cnetChatBox-conversation--active":"");i.setAttribute("role","button");i.tabIndex=0;const a=document.createElement("div");a.className="cnetChatBox-conversation-body";const o=document.createElement("div");o.className="cnetChatBox-conversation-title";o.textContent=this._conversationTitle(e);a.appendChild(o);const c=document.createElement("div");c.className="cnetChatBox-conversation-snippet";c.textContent=this._conversationSnippet(e);a.appendChild(c);const r=document.createElement("div");r.className="cnetChatBox-conversation-time";r.textContent=this._formatTimestamp(e.updatedAt);a.appendChild(r);i.appendChild(a);const d=s.getText("chatbox.deleteConversation")??"Delete conversation";const l=document.createElement("button");l.type="button";l.className="cnetChatBox-conversation-remove";l.title=d;l.setAttribute("aria-label",d);l.textContent="×";l.addEventListener("click",t=>{t.stopPropagation();this._requestDeleteConversation(e.id)});i.appendChild(l);i.addEventListener("click",()=>this._selectConversation(e.id));i.addEventListener("keydown",t=>{if(t.key==="Enter"||t.key===" "){t.preventDefault();this._selectConversation(e.id)}});return i},_conversationTitle:function t(e){if(e.title&&e.title.trim())return e.title.trim();const n=e.messages??[];for(const t of n){if(t.role==="user"&&t.content&&t.content.trim()){return this._truncate(t.content.trim(),40)}}return this._i18n().getText("chatbox.untitledConversation")??"Neue Konversation"},_conversationSnippet:function t(e){const n=e.messages??[];for(let t=n.length-1;t>=0;t--){const e=n[t];if(e.content&&e.content.trim()){const t=e.role==="user"?(this._i18n().getText("chatbox.youPrefix")??"Du:")+" ":"";return this._truncate(t+e.content.trim().replace(/\s+/g," "),60)}}return""},_truncate:function t(e,n){return e.length>n?e.slice(0,n-1).trimEnd()+"…":e},_formatTimestamp:function t(e){if(e===undefined||e===null)return"";const n=e instanceof Date?e:new Date(e);if(isNaN(n.getTime()))return"";const i=new Date;const a=n.toDateString()===i.toDateString();const o=new Date(i);o.setDate(i.getDate()-1);const c=n.toDateString()===o.toDateString();const r=s.getLanguage()||undefined;if(a){return n.toLocaleTimeString(r,{hour:"2-digit",minute:"2-digit"})}if(c){return this._i18n().getText("chatbox.yesterday")??"Gestern"}const d=(i.getTime()-n.getTime())/(1e3*60*60*24);if(d<7){return n.toLocaleDateString(r,{weekday:"short"})}return n.toLocaleDateString(r,{day:"2-digit",month:"2-digit",year:"2-digit"})},_renderHeaderTitle:function t(){const e=this._dom("headerTitle");if(!e)return;const n=this.getActiveConversationId();const s=n?this._conversations.find(t=>t.id===n):undefined;e.textContent=s?this._conversationTitle(s):""},_wireAttachments:function t(){const e=this._dom("attach");const n=this._dom("fileinput");const s=this.getDomRef();e?.addEventListener("click",()=>n?.click());n?.addEventListener("change",()=>{if(!n.files)return;this._addPending(Array.from(n.files));n.value=""});if(!s)return;let i=0;s.addEventListener("dragenter",t=>{if(!this._hasFiles(t))return;t.preventDefault();i++;s.classList.add("cnetChatBox--dragOver")});s.addEventListener("dragover",t=>{if(!this._hasFiles(t))return;t.preventDefault();if(t.dataTransfer)t.dataTransfer.dropEffect="copy"});s.addEventListener("dragleave",()=>{i=Math.max(0,i-1);if(i===0)s.classList.remove("cnetChatBox--dragOver")});s.addEventListener("drop",t=>{if(!this._hasFiles(t))return;t.preventDefault();i=0;s.classList.remove("cnetChatBox--dragOver");const e=t.dataTransfer?.files;if(!e||e.length===0)return;this._addPending(Array.from(e))})},_hasFiles:function t(e){const n=e.dataTransfer?.types;if(!n)return false;for(let t=0;t<n.length;t++){if(n[t]==="Files")return true}return false},_addPending:function t(e){if(this.getBusy())return;if(e.length===0)return;this._pendingAttachments=this._pendingAttachments.concat(e);this._renderPending()},_removePending:function t(e){if(e<0||e>=this._pendingAttachments.length)return;this._pendingAttachments.splice(e,1);this._renderPending()},_renderPending:function t(){const e=this._dom("pending");if(!e)return;e.innerHTML="";if(this._pendingAttachments.length===0){e.classList.remove("cnetChatBox-attachmentsPending--hasItems");return}e.classList.add("cnetChatBox-attachmentsPending--hasItems");e.appendChild(this._buildAttachmentList(this._pendingAttachments,"pending"))}});return c});
|
|
6
|
+
/*!
|
|
7
|
+
* ${copyright}
|
|
8
|
+
*/
|
|
9
|
+
sap.ui.predefine("chatui/library", ["sap/ui/core/Lib"],function(e){"use strict";e.init({name:"chatui",version:"0.1.0",dependencies:["sap.ui.core"],types:[],interfaces:[],controls:["chatui.ChatBox"],elements:[],noLibraryCSS:false,apiVersion:2})});
|
|
10
|
+
/*!
|
|
11
|
+
* ${copyright}
|
|
12
|
+
*/
|
|
13
|
+
sap.ui.predefine("chatui/markdown", [],function(){"use strict";const t=t=>t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");const e=e=>t(e).replace(/`([^`]+)`/g,"<code>$1</code>").replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/__(.+?)__/g,"<strong>$1</strong>").replace(/\*(.+?)\*/g,"<em>$1</em>").replace(/_(.+?)_/g,"<em>$1</em>").replace(/\[([^\]]+)\]\(([^)]+)\)/g,(t,e,s)=>`<a href="${s}" target="_blank" rel="noopener">${e}</a>`);const s=t=>t.split("|").slice(1,-1).map(t=>t.trim());const n=t=>/^\|[\s:-]+\|/.test(t)&&s(t).every(t=>/^:?-+:?$/.test(t));function o(o){if(!o)return"";const c=o.split("\n");const r=[];let i=false;let p=[];let l=false;let a=false;const u=()=>{if(l){r.push("</ul>");l=false}};const h=()=>{if(a){r.push("</tbody></table>");a=false}};for(let o=0;o<c.length;o++){const f=c[o];if(f.startsWith("```")){u();h();if(i){r.push(`<pre><code>${t(p.join("\n"))}</code></pre>`);p=[]}i=!i;continue}if(i){p.push(f);continue}const g=f.trim();if(!a&&g.startsWith("|")&&g.endsWith("|")){const t=c[o+1]?.trim();if(t&&n(t)){u();const t=s(g);r.push('<table class="cnetChatBox-table"><thead><tr>');for(const s of t)r.push(`<th>${e(s)}</th>`);r.push("</tr></thead><tbody>");a=true;o++;continue}}if(a){if(g.startsWith("|")&&g.endsWith("|")){const t=s(g);r.push("<tr>");for(const s of t)r.push(`<td>${e(s)}</td>`);r.push("</tr>");continue}h()}if(!g){u();h();continue}const $=g.match(/^(#{1,6})\s+(.+)$/);if($){u();h();const t=Math.min($[1].length,6);r.push(`<h${t}>${e($[2])}</h${t}>`);continue}const d=g.match(/^[-*]\s+(.*)/);if(d){h();if(!l){r.push("<ul>");l=true}r.push(`<li>${e(d[1])}</li>`);continue}u();h();r.push(`<p>${e(g)}</p>`)}u();h();if(i)r.push(`<pre><code>${t(p.join("\n"))}</code></pre>`);return r.join("")}return o});
|
|
14
|
+
sap.ui.require.preload({
|
|
15
|
+
"chatui/manifest.json":'{"_version":"1.21.0","sap.app":{"id":"chatui","type":"library","embeds":[],"applicationVersion":{"version":"0.1.0"},"title":"chatui","description":"Embeddable AI chat UI control for UI5 applications.","resources":"resources.json","offline":true},"sap.ui":{"technology":"UI5","supportedThemes":["base","sap_fiori_3","sap_fiori_3_dark","sap_fiori_3_hcb","sap_fiori_3_hcw"]},"sap.ui5":{"dependencies":{"minUI5Version":"1.136","libs":{"sap.ui.core":{"minVersion":"1.136.0"}}},"library":{"i18n":{"bundleUrl":"messagebundle.properties","supportedLocales":["","en"]},"content":{"controls":["chatui.ChatBox"],"elements":[],"types":[],"interfaces":[]}}}}'
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=library-preload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","Control","IconPool","Lib","Localization","__markdownToHtml","_interopRequireDefault","obj","__esModule","default","markdownToHtml","ChatBox","extend","renderer","apiVersion","render","rm","control","sendIcon","getIconInfo","newChatIcon","attachIcon","i18n","_i18n","placeholder","getPlaceholder","getText","allowAttachments","getAllowAttachments","openStart","class","style","getWidth","getHeight","openEnd","text","close","getId","attr","fontFamily","content","getBusy","voidStart","getAttachmentAccept","voidEnd","metadata","library","properties","type","group","defaultValue","busy","width","height","attachmentAccept","conversations","activeConversationId","events","send","parameters","message","files","conversationId","newChat","conversationSelect","id","conversationDelete","constructor","_constructor","settings","Control.prototype.constructor.call","this","_messages","_pendingAttachments","_conversations","_initialised","_onLocaleChange","invalidate","init","_init","attachChange","exit","_exit","detachChange","onAfterRendering","_onAfterRendering","textarea","_dom","sendBtn","newChatBtn","addEventListener","Math","min","scrollHeight","e","key","shiftKey","preventDefault","_submit","_startNewChat","_wireAttachments","_renderSidebar","_renderHeaderTitle","_restoreMessages","_renderPending","addAssistantMessage","_addAssistantMessage","push","role","_appendMessageDom","length","appendToLastAssistantMessage","_appendToLastAssistantMessage","chunk","last","_updateBubble","addAssistantControl","_addAssistantControl","addDependent","clear","_clear","destroyDependents","getMessages","_getMessages","slice","setMessages","_setMessages","messages","_sanitiseMessages","setConversations","_setConversations","list","_sanitiseConversations","setProperty","getActiveConversationId","first","getConversations","_getConversations","setActiveConversationId","_setActiveConversationId","next","conv","find","c","getResourceBundleFor","suffix","document","getElementById","_messagesContainer","clean","Array","isArray","raw","msg","title","updatedAt","undefined","value","trim","fireEvent","_selectConversation","_requestDeleteConversation","_buildMessageDom","index","div","createElement","isAssistant","className","bubble","_renderBubble","appendChild","actions","_buildActions","_buildCopyButton","_buildEditButton","_buildIconButton","iconName","label","fallback","icon","btn","setAttribute","iconSpan","textContent","_copyMessage","_enterEditMode","async","ok","_writeClipboard","original","getAttribute","done","classList","add","window","setTimeout","remove","wrapper","children","querySelector","actionsRow","display","_buildAttachmentList","editor","rows","editBar","cancelBtn","saveBtn","_cancelEdit","_commitEdit","focus","setSelectionRange","newText","trimmed","unchanged","empty","i","later","destroy","navigator","clipboard","writeText","ta","position","opacity","body","select","execCommand","removeChild","innerHTML","placeAt","variant","wrap","_buildAttachmentItem","file","pendingIndex","isImage","startsWith","item","img","alt","name","src","URL","createObjectURL","iconInfo","removeLabel","_removePending","container","scrollTop","atBottom","clientHeight","hint","active","_buildConversationItem","isActive","tabIndex","_conversationTitle","snippet","_conversationSnippet","time","_formatTimestamp","del","stopPropagation","msgs","m","_truncate","prefix","replace","max","trimEnd","d","Date","isNaN","getTime","now","sameDay","toDateString","yesterday","setDate","getDate","wasYesterday","locale","getLanguage","toLocaleTimeString","hour","minute","weekDelta","toLocaleDateString","weekday","day","month","year","target","attachBtn","fileInput","root","getDomRef","click","_addPending","from","dragDepth","_hasFiles","dataTransfer","dropEffect","types","concat","splice"],"sources":["ChatBox.ts"],"sourcesContent":["/*!\n * ${copyright}\n */\n\nimport Control from \"sap/ui/core/Control\";\nimport RenderManager from \"sap/ui/core/RenderManager\";\nimport IconPool from \"sap/ui/core/IconPool\";\nimport Lib from \"sap/ui/core/Lib\";\nimport Localization from \"sap/base/i18n/Localization\";\nimport ResourceBundle from \"sap/base/i18n/ResourceBundle\";\nimport markdownToHtml from \"./markdown\";\n\ninterface IconInfo {\n fontFamily: string;\n content: string;\n}\n\nexport interface Message {\n role: \"user\" | \"assistant\";\n content: string;\n files?: File[];\n control?: Control;\n}\n\nexport interface Conversation {\n id: string;\n title?: string;\n messages?: ReadonlyArray<Message>;\n updatedAt?: number | string | Date;\n}\n\n/**\n * Constructor for a new <code>chatui.ChatBox</code> control.\n *\n * A flexible chat UI block that can be embedded into any UI5 application.\n * It renders a conversation sidebar, a scrollable message list, an\n * auto-growing input textarea and a send button. The control is\n * intentionally transport-agnostic: it fires a <code>send</code> event with\n * the user's message and expects the embedding application to feed the\n * assistant's reply back via {@link #addAssistantMessage} /\n * {@link #appendToLastAssistantMessage}.\n *\n * The control fills the space provided by its parent. For standalone use\n * a sensible default size is applied via CSS.\n *\n * @extends sap.ui.core.Control\n *\n * @author CONET\n * @version 0.1.0\n *\n * @constructor\n * @public\n * @name chatui.ChatBox\n */\nexport default class ChatBox extends Control {\n\n // The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures\n constructor(id?: string | $ChatBoxSettings);\n constructor(id?: string, settings?: $ChatBoxSettings);\n constructor(id?: string, settings?: $ChatBoxSettings) { super(id, settings); }\n\n static readonly metadata = {\n library: \"chatui\",\n properties: {\n /**\n * Placeholder shown in the empty input field. If empty, a\n * localised default is used.\n */\n placeholder: { type: \"string\", group: \"Appearance\", defaultValue: \"\" },\n /**\n * When true, the input and buttons are disabled — typically set\n * while the assistant is producing a reply.\n */\n busy: { type: \"boolean\", group: \"Behavior\", defaultValue: false },\n /**\n * CSS width of the control. Defaults to <code>100%</code> so the\n * chat adapts to its parent; override for a fixed size.\n */\n width: { type: \"sap.ui.core.CSSSize\", group: \"Dimension\", defaultValue: \"100%\" },\n /**\n * CSS height of the control. Defaults to <code>100%</code>; falls\n * back to a sensible min-height when the parent has no explicit\n * height.\n */\n height: { type: \"sap.ui.core.CSSSize\", group: \"Dimension\", defaultValue: \"100%\" },\n /**\n * When true (default), the user can attach files via the paperclip\n * button and drag-and-drop. Uploaded <code>File</code> objects are\n * handed to the consumer via the <code>files</code> parameter of\n * the <code>send</code> event — the library never performs any\n * network transfer itself.\n */\n allowAttachments: { type: \"boolean\", group: \"Behavior\", defaultValue: true },\n /**\n * Value passed to the underlying <code><input type=\"file\"></code>\n * as its <code>accept</code> attribute (e.g. <code>\"image/*\"</code>\n * or <code>\".pdf,.docx\"</code>). Empty string means \"any file\".\n */\n attachmentAccept: { type: \"string\", group: \"Behavior\", defaultValue: \"\" },\n /**\n * The list of conversations shown in the sidebar. Each entry has\n * an <code>id</code>, an optional <code>title</code>, an optional\n * ordered array of <code>messages</code>, and an optional\n * <code>updatedAt</code> timestamp (number, ISO string or Date).\n * Passed in by the embedding application and never mutated by\n * the library. When set for the first time, the library\n * automatically selects and displays the first conversation. If\n * the property is empty or unset, an empty new-chat view is\n * shown.\n */\n conversations: { type: \"object\", group: \"Data\", defaultValue: [] },\n /**\n * The id of the currently displayed conversation. Empty string\n * means \"new chat draft\" — no history is shown, and the next\n * <code>send</code> event carries an empty <code>conversationId</code>\n * so the consumer can allocate a fresh id. When set to a value\n * that matches an entry in {@link #getConversations}, that\n * conversation's messages are loaded into the transcript.\n */\n activeConversationId: { type: \"string\", group: \"Data\", defaultValue: \"\" }\n },\n events: {\n /**\n * Fired when the user submits a prompt. The embedding\n * application should produce an assistant response and feed it\n * back via {@link #addAssistantMessage} or\n * {@link #appendToLastAssistantMessage}.\n */\n send: {\n parameters: {\n /** The prompt the user submitted (trimmed; empty when only files are sent). */\n message: { type: \"string\" },\n /** File objects the user attached; empty array if none. Not transferred by the library — the consumer is responsible for upload/transport. */\n files: { type: \"object\" },\n /** Id of the conversation the message belongs to. Empty string when the user is composing in a new-chat draft — the consumer typically allocates a fresh id and pushes an updated conversation list back. */\n conversationId: { type: \"string\" }\n }\n },\n /**\n * Fired when the user clicks the \"Neue Konversation\" button.\n * The transcript view has already been cleared and\n * <code>activeConversationId</code> reset to <code>\"\"</code> by\n * the time the event fires — listeners typically use this to\n * reset any server-side session state.\n */\n newChat: {},\n /**\n * Fired when the user picks a conversation from the sidebar.\n * The library has already switched the transcript view to that\n * conversation and updated <code>activeConversationId</code>.\n */\n conversationSelect: {\n parameters: {\n /** Id of the conversation the user selected. */\n id: { type: \"string\" }\n }\n },\n /**\n * Fired when the user clicks the × on a conversation in the\n * sidebar. The library does not mutate the conversations\n * property itself — the consumer is expected to drop the entry\n * from its store and push an updated list back via\n * {@link #setConversations}.\n */\n conversationDelete: {\n parameters: {\n /** Id of the conversation the user asked to delete. */\n id: { type: \"string\" }\n }\n }\n }\n };\n\n private _messages: Message[] = [];\n private _pendingAttachments: File[] = [];\n private _conversations: Conversation[] = [];\n private _initialised = false;\n private _onLocaleChange = (): void => { this.invalidate(); };\n\n override init(): void {\n Localization.attachChange(this._onLocaleChange);\n }\n\n override exit(): void {\n Localization.detachChange(this._onLocaleChange);\n }\n\n static renderer = {\n apiVersion: 2,\n render(rm: RenderManager, control: ChatBox): void {\n const sendIcon = IconPool.getIconInfo(\"sap-icon://paper-plane\") as IconInfo | undefined;\n const newChatIcon = IconPool.getIconInfo(\"sap-icon://add\") as IconInfo | undefined;\n const attachIcon = IconPool.getIconInfo(\"sap-icon://attachment\") as IconInfo | undefined;\n const i18n = control._i18n();\n const placeholder = control.getPlaceholder() || i18n.getText(\"chatbox.placeholder\") || \"\";\n const allowAttachments = control.getAllowAttachments();\n\n rm.openStart(\"div\", control);\n rm.class(\"cnetChatBox\");\n rm.style(\"width\", control.getWidth());\n rm.style(\"height\", control.getHeight());\n rm.openEnd();\n\n // ---- Sidebar ----\n rm.openStart(\"aside\");\n rm.class(\"cnetChatBox-sidebar\");\n rm.openEnd();\n rm.openStart(\"div\");\n rm.class(\"cnetChatBox-sidebar-header\");\n rm.openEnd();\n rm.openStart(\"span\");\n rm.class(\"cnetChatBox-sidebar-title\");\n rm.openEnd();\n rm.text(i18n.getText(\"chatbox.conversations\") ?? \"\");\n rm.close(\"span\");\n rm.close(\"div\");\n rm.openStart(\"div\", control.getId() + \"-conversations\");\n rm.class(\"cnetChatBox-conversations\");\n rm.openEnd();\n rm.close(\"div\");\n rm.close(\"aside\");\n\n // ---- Main column ----\n rm.openStart(\"div\");\n rm.class(\"cnetChatBox-main\");\n rm.openEnd();\n\n // Header\n rm.openStart(\"div\");\n rm.class(\"cnetChatBox-header\");\n rm.openEnd();\n rm.openStart(\"div\", control.getId() + \"-headerTitle\");\n rm.class(\"cnetChatBox-header-title\");\n rm.openEnd();\n rm.close(\"div\");\n rm.openStart(\"button\", control.getId() + \"-newChat\");\n rm.class(\"cnetChatBox-newChatButton\");\n rm.attr(\"type\", \"button\");\n rm.attr(\"title\", i18n.getText(\"chatbox.newConversation\") ?? \"\");\n rm.openEnd();\n rm.openStart(\"span\");\n rm.class(\"cnetChatBox-icon\");\n if (newChatIcon) rm.style(\"font-family\", newChatIcon.fontFamily);\n rm.openEnd();\n rm.text(newChatIcon?.content ?? \"+\");\n rm.close(\"span\");\n rm.openStart(\"span\");\n rm.class(\"cnetChatBox-newChatButton-label\");\n rm.openEnd();\n rm.text(i18n.getText(\"chatbox.newConversation\") ?? \"\");\n rm.close(\"span\");\n rm.close(\"button\");\n rm.close(\"div\");\n\n // Messages\n rm.openStart(\"div\", control.getId() + \"-messages\");\n rm.class(\"cnetChatBox-messages\");\n rm.openEnd();\n rm.openStart(\"div\");\n rm.class(\"cnetChatBox-empty\");\n rm.openEnd();\n rm.text(i18n.getText(\"chatbox.emptyHint\") ?? \"\");\n rm.close(\"div\");\n rm.close(\"div\");\n\n // Input area\n rm.openStart(\"div\");\n rm.class(\"cnetChatBox-inputArea\");\n rm.openEnd();\n\n rm.openStart(\"div\");\n rm.class(\"cnetChatBox-composer\");\n rm.openEnd();\n\n rm.openStart(\"div\", control.getId() + \"-pending\");\n rm.class(\"cnetChatBox-attachmentsPending\");\n rm.openEnd();\n rm.close(\"div\");\n\n rm.openStart(\"textarea\", control.getId() + \"-input\");\n rm.class(\"cnetChatBox-textarea\");\n rm.attr(\"placeholder\", placeholder);\n rm.attr(\"rows\", \"1\");\n if (control.getBusy()) rm.attr(\"disabled\", \"disabled\");\n rm.openEnd();\n rm.close(\"textarea\");\n\n rm.openStart(\"div\");\n rm.class(\"cnetChatBox-composerBar\");\n rm.openEnd();\n\n rm.openStart(\"div\");\n rm.class(\"cnetChatBox-composerBar-left\");\n rm.openEnd();\n\n if (allowAttachments) {\n rm.openStart(\"button\", control.getId() + \"-attach\");\n rm.class(\"cnetChatBox-toolButton\");\n rm.class(\"cnetChatBox-attach\");\n rm.attr(\"type\", \"button\");\n rm.attr(\"title\", i18n.getText(\"chatbox.attach\") ?? \"\");\n rm.attr(\"aria-label\", i18n.getText(\"chatbox.attach\") ?? \"\");\n rm.openEnd();\n rm.openStart(\"span\");\n rm.class(\"cnetChatBox-icon\");\n if (attachIcon) rm.style(\"font-family\", attachIcon.fontFamily);\n rm.openEnd();\n rm.text(attachIcon?.content ?? \"📎\");\n rm.close(\"span\");\n rm.close(\"button\");\n\n rm.voidStart(\"input\", control.getId() + \"-fileinput\");\n rm.class(\"cnetChatBox-fileinput\");\n rm.attr(\"type\", \"file\");\n rm.attr(\"multiple\", \"multiple\");\n if (control.getAttachmentAccept()) rm.attr(\"accept\", control.getAttachmentAccept());\n rm.voidEnd();\n }\n\n rm.close(\"div\");\n\n rm.openStart(\"button\", control.getId() + \"-send\");\n rm.class(\"cnetChatBox-send\");\n rm.attr(\"type\", \"button\");\n rm.attr(\"title\", i18n.getText(\"chatbox.send\") ?? \"\");\n rm.attr(\"aria-label\", i18n.getText(\"chatbox.send\") ?? \"\");\n rm.openEnd();\n rm.openStart(\"span\");\n rm.class(\"cnetChatBox-icon\");\n if (sendIcon) rm.style(\"font-family\", sendIcon.fontFamily);\n rm.openEnd();\n rm.text(sendIcon?.content ?? \"➤\");\n rm.close(\"span\");\n rm.close(\"button\");\n\n rm.close(\"div\");\n rm.close(\"div\");\n rm.close(\"div\");\n\n rm.close(\"div\"); // main\n\n if (allowAttachments) {\n rm.openStart(\"div\", control.getId() + \"-drop\");\n rm.class(\"cnetChatBox-dropOverlay\");\n rm.openEnd();\n rm.openStart(\"span\");\n rm.class(\"cnetChatBox-dropLabel\");\n rm.openEnd();\n rm.text(i18n.getText(\"chatbox.dropHere\") ?? \"\");\n rm.close(\"span\");\n rm.close(\"div\");\n }\n\n rm.close(\"div\");\n }\n };\n\n override onAfterRendering(): void {\n const textarea = this._dom(\"input\") as HTMLTextAreaElement | null;\n const sendBtn = this._dom(\"send\");\n const newChatBtn = this._dom(\"newChat\");\n\n textarea?.addEventListener(\"input\", () => {\n textarea.style.height = \"auto\";\n textarea.style.height = Math.min(textarea.scrollHeight, 160) + \"px\";\n });\n\n textarea?.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n this._submit();\n }\n });\n\n sendBtn?.addEventListener(\"click\", () => this._submit());\n newChatBtn?.addEventListener(\"click\", () => this._startNewChat());\n\n if (this.getAllowAttachments()) {\n this._wireAttachments();\n }\n\n this._renderSidebar();\n this._renderHeaderTitle();\n this._restoreMessages();\n this._renderPending();\n }\n\n /**\n * Append an assistant message to the transcript. Pass an empty string\n * to open a \"pending\" bubble that can then be filled incrementally via\n * {@link #appendToLastAssistantMessage}.\n *\n * @public\n */\n addAssistantMessage(text: string): this {\n this._messages.push({ role: \"assistant\", content: text ?? \"\" });\n this._appendMessageDom(this._messages.length - 1);\n return this;\n }\n\n /**\n * Append a chunk to the last assistant message. Useful when streaming\n * tokens from a language model. If the last message is not a plain\n * assistant text message, a new one is started.\n *\n * @public\n */\n appendToLastAssistantMessage(chunk: string): this {\n if (!chunk) return this;\n const last = this._messages[this._messages.length - 1];\n if (!last || last.role !== \"assistant\" || last.control) {\n return this.addAssistantMessage(chunk);\n }\n last.content += chunk;\n this._updateBubble(this._messages.length - 1);\n return this;\n }\n\n /**\n * Attach an arbitrary UI5 control as a stand-alone assistant bubble\n * (e.g. a <code>sap.viz.ui5.controls.VizFrame</code>, an\n * <code>sap.ui.integration.widgets.Card</code>, or any custom\n * control). The library owns the control's lifecycle from here on —\n * it is destroyed together with the ChatBox as well as on\n * {@link #clear}, {@link #setMessages}, or the user-driven new-chat\n * action. Do not add the same control twice.\n *\n * @public\n */\n addAssistantControl(control: Control): this {\n this.addDependent(control);\n this._messages.push({ role: \"assistant\", content: \"\", control });\n this._appendMessageDom(this._messages.length - 1);\n return this;\n }\n\n /**\n * Remove all messages from the transcript. Does not fire\n * <code>newChat</code> — that event is reserved for the user-driven\n * clear action. Does not touch the conversations list.\n *\n * @public\n */\n clear(): this {\n this._messages = [];\n this._pendingAttachments = [];\n this.destroyDependents();\n this._restoreMessages();\n this._renderPending();\n return this;\n }\n\n /**\n * Read-only snapshot of the current transcript. Intended for\n * persistence — do not mutate.\n *\n * @public\n */\n getMessages(): ReadonlyArray<Message> {\n return this._messages.slice();\n }\n\n /**\n * Replace the transcript with the given messages. Intended as a\n * restore path — the consumer stores {@link #getMessages} somewhere\n * (localStorage, backend, …) and hands it back after a reload. Does\n * not fire <code>newChat</code> or <code>send</code>. Pending\n * attachments (a possibly ongoing draft) are left untouched. Invalid\n * entries (missing/unknown <code>role</code> or non-string\n * <code>content</code>) are silently skipped.\n *\n * @public\n */\n setMessages(messages: ReadonlyArray<Message>): this {\n this._messages = this._sanitiseMessages(messages);\n this.destroyDependents();\n this._restoreMessages();\n return this;\n }\n\n /**\n * Replace the conversations list shown in the sidebar. On the first\n * call with a non-empty list, the first entry is automatically\n * selected and its messages are loaded into the transcript. Later\n * calls only update the sidebar — call {@link #setActiveConversationId}\n * to switch the visible transcript.\n *\n * @public\n */\n setConversations(conversations: ReadonlyArray<Conversation> | null | undefined): this {\n const list = this._sanitiseConversations(conversations);\n this._conversations = list;\n this.setProperty(\"conversations\", list, true);\n\n if (!this._initialised && list.length > 0 && !this.getActiveConversationId()) {\n const first = list[0];\n this.setProperty(\"activeConversationId\", first.id, true);\n this._messages = this._sanitiseMessages(first.messages ?? []);\n this.destroyDependents();\n this._restoreMessages();\n this._renderHeaderTitle();\n }\n this._initialised = true;\n this._renderSidebar();\n return this;\n }\n\n /**\n * Read-only snapshot of the conversations currently shown in the\n * sidebar.\n *\n * @public\n */\n getConversations(): ReadonlyArray<Conversation> {\n return this._conversations.slice();\n }\n\n /**\n * Switch the transcript view to the given conversation. If the id\n * does not match any conversation (including the empty string), the\n * transcript is cleared — that is the \"new chat draft\" state.\n *\n * @public\n */\n setActiveConversationId(id: string): this {\n const next = typeof id === \"string\" ? id : \"\";\n if (next === this.getActiveConversationId()) return this;\n this.setProperty(\"activeConversationId\", next, true);\n const list = this._conversations ?? [];\n const conv = list.find((c) => c.id === next);\n this._messages = conv ? this._sanitiseMessages(conv.messages ?? []) : [];\n this._pendingAttachments = [];\n this.destroyDependents();\n this._renderSidebar();\n this._renderHeaderTitle();\n this._restoreMessages();\n this._renderPending();\n return this;\n }\n\n private _i18n(): ResourceBundle {\n return Lib.getResourceBundleFor(\"chatui\") as ResourceBundle;\n }\n\n private _dom(suffix: string): HTMLElement | null {\n return document.getElementById(this.getId() + \"-\" + suffix);\n }\n\n private _messagesContainer(): HTMLElement | null {\n return this._dom(\"messages\");\n }\n\n private _sanitiseMessages(messages: ReadonlyArray<Message> | null | undefined): Message[] {\n const clean: Message[] = [];\n if (!Array.isArray(messages)) return clean;\n for (const raw of messages) {\n if (!raw || (raw.role !== \"user\" && raw.role !== \"assistant\")) continue;\n if (typeof raw.content !== \"string\") continue;\n if (raw.control) continue;\n const msg: Message = { role: raw.role, content: raw.content };\n if (Array.isArray(raw.files) && raw.files.length > 0) {\n msg.files = raw.files.slice();\n }\n clean.push(msg);\n }\n return clean;\n }\n\n private _sanitiseConversations(conversations: ReadonlyArray<Conversation> | null | undefined): Conversation[] {\n const clean: Conversation[] = [];\n if (!Array.isArray(conversations)) return clean;\n for (const raw of conversations) {\n if (!raw || typeof raw.id !== \"string\" || !raw.id) continue;\n const conv: Conversation = { id: raw.id };\n if (typeof raw.title === \"string\") conv.title = raw.title;\n if (Array.isArray(raw.messages)) conv.messages = this._sanitiseMessages(raw.messages);\n if (raw.updatedAt !== undefined && raw.updatedAt !== null) conv.updatedAt = raw.updatedAt;\n clean.push(conv);\n }\n return clean;\n }\n\n private _submit(): void {\n if (this.getBusy()) return;\n const textarea = this._dom(\"input\") as HTMLTextAreaElement | null;\n const text = textarea?.value.trim() ?? \"\";\n const files = this._pendingAttachments.slice();\n if (!text && files.length === 0) return;\n\n if (textarea) {\n textarea.value = \"\";\n textarea.style.height = \"auto\";\n }\n\n const message: Message = { role: \"user\", content: text };\n if (files.length > 0) message.files = files;\n this._messages.push(message);\n this._pendingAttachments = [];\n this._appendMessageDom(this._messages.length - 1);\n this._renderPending();\n\n this.fireEvent(\"send\", { message: text, files, conversationId: this.getActiveConversationId() });\n }\n\n private _startNewChat(): void {\n if (this.getBusy()) return;\n this._messages = [];\n this._pendingAttachments = [];\n this.destroyDependents();\n this.setProperty(\"activeConversationId\", \"\", true);\n this._renderSidebar();\n this._renderHeaderTitle();\n this._restoreMessages();\n this._renderPending();\n this.fireEvent(\"newChat\");\n }\n\n private _selectConversation(id: string): void {\n if (this.getBusy()) return;\n if (id === this.getActiveConversationId()) return;\n this.setActiveConversationId(id);\n this.fireEvent(\"conversationSelect\", { id });\n }\n\n private _requestDeleteConversation(id: string): void {\n if (this.getBusy()) return;\n this.fireEvent(\"conversationDelete\", { id });\n }\n\n private _buildMessageDom(index: number): HTMLDivElement {\n const msg = this._messages[index];\n const div = document.createElement(\"div\");\n const isAssistant = msg.role === \"assistant\";\n div.className = `cnetChatBox-message cnetChatBox-message--${isAssistant ? \"assistant\" : \"user\"}`;\n\n const bubble = document.createElement(\"div\");\n bubble.className = \"cnetChatBox-bubble\";\n this._renderBubble(bubble, msg);\n div.appendChild(bubble);\n\n if (!msg.control) {\n const actions = this._buildActions(index, msg);\n if (actions) div.appendChild(actions);\n }\n return div;\n }\n\n private _buildActions(index: number, msg: Message): HTMLDivElement | null {\n const actions = document.createElement(\"div\");\n actions.className = \"cnetChatBox-actions\";\n if (msg.role === \"assistant\") {\n actions.appendChild(this._buildCopyButton(index));\n } else {\n actions.appendChild(this._buildEditButton(index));\n }\n return actions;\n }\n\n private _buildIconButton(iconName: string, className: string, label: string, fallback: string): HTMLButtonElement {\n const icon = IconPool.getIconInfo(iconName) as IconInfo | undefined;\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = `cnetChatBox-action ${className}`;\n btn.title = label;\n btn.setAttribute(\"aria-label\", label);\n\n const iconSpan = document.createElement(\"span\");\n iconSpan.className = \"cnetChatBox-icon\";\n if (icon) iconSpan.style.fontFamily = icon.fontFamily;\n iconSpan.textContent = icon?.content ?? fallback;\n btn.appendChild(iconSpan);\n return btn;\n }\n\n private _buildCopyButton(index: number): HTMLButtonElement {\n const label = this._i18n().getText(\"chatbox.copy\") ?? \"Copy\";\n const btn = this._buildIconButton(\"sap-icon://copy\", \"cnetChatBox-copy\", label, \"⎘\");\n btn.addEventListener(\"click\", () => {\n const msg = this._messages[index];\n if (!msg || !msg.content) return;\n void this._copyMessage(msg.content, btn);\n });\n return btn;\n }\n\n private _buildEditButton(index: number): HTMLButtonElement {\n const label = this._i18n().getText(\"chatbox.edit\") ?? \"Edit\";\n const btn = this._buildIconButton(\"sap-icon://edit\", \"cnetChatBox-edit\", label, \"✎\");\n btn.addEventListener(\"click\", () => this._enterEditMode(index));\n return btn;\n }\n\n private async _copyMessage(text: string, btn: HTMLButtonElement): Promise<void> {\n const ok = await this._writeClipboard(text);\n if (!ok) return;\n\n const i18n = this._i18n();\n const original = btn.getAttribute(\"aria-label\") ?? \"\";\n const done = i18n.getText(\"chatbox.copied\") ?? \"Copied\";\n btn.classList.add(\"cnetChatBox-action--done\");\n btn.title = done;\n window.setTimeout(() => {\n btn.classList.remove(\"cnetChatBox-action--done\");\n btn.title = original;\n }, 1200);\n }\n\n private _enterEditMode(index: number): void {\n if (this.getBusy()) return;\n const wrapper = this._messagesContainer()?.children[index] as HTMLDivElement | undefined;\n const msg = this._messages[index];\n if (!wrapper || !msg || msg.role !== \"user\") return;\n\n const bubble = wrapper.querySelector<HTMLElement>(\".cnetChatBox-bubble\");\n if (!bubble) return;\n\n const actionsRow = wrapper.querySelector<HTMLElement>(\".cnetChatBox-actions\");\n if (actionsRow) actionsRow.style.display = \"none\";\n\n bubble.textContent = \"\";\n bubble.classList.add(\"cnetChatBox-bubble--editing\");\n if (msg.files && msg.files.length > 0) {\n bubble.appendChild(this._buildAttachmentList(msg.files, \"sent\"));\n }\n\n const editor = document.createElement(\"textarea\");\n editor.className = \"cnetChatBox-editTextarea\";\n editor.value = msg.content;\n editor.rows = 2;\n bubble.appendChild(editor);\n\n const editBar = document.createElement(\"div\");\n editBar.className = \"cnetChatBox-editActions\";\n const i18n = this._i18n();\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"cnetChatBox-editCancel\";\n cancelBtn.textContent = i18n.getText(\"chatbox.cancel\") ?? \"Cancel\";\n\n const saveBtn = document.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.className = \"cnetChatBox-editSave\";\n saveBtn.textContent = i18n.getText(\"chatbox.save\") ?? \"Save\";\n\n cancelBtn.addEventListener(\"click\", () => this._cancelEdit(index));\n saveBtn.addEventListener(\"click\", () => this._commitEdit(index, editor.value));\n\n editor.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n this._commitEdit(index, editor.value);\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n this._cancelEdit(index);\n }\n });\n\n editBar.appendChild(cancelBtn);\n editBar.appendChild(saveBtn);\n bubble.appendChild(editBar);\n\n editor.focus();\n editor.setSelectionRange(editor.value.length, editor.value.length);\n }\n\n private _cancelEdit(index: number): void {\n const wrapper = this._messagesContainer()?.children[index] as HTMLDivElement | undefined;\n const msg = this._messages[index];\n if (!wrapper || !msg) return;\n const bubble = wrapper.querySelector<HTMLElement>(\".cnetChatBox-bubble\");\n if (!bubble) return;\n bubble.classList.remove(\"cnetChatBox-bubble--editing\");\n this._renderBubble(bubble, msg);\n const actionsRow = wrapper.querySelector<HTMLElement>(\".cnetChatBox-actions\");\n if (actionsRow) actionsRow.style.display = \"\";\n }\n\n private _commitEdit(index: number, newText: string): void {\n if (this.getBusy()) return;\n const msg = this._messages[index];\n if (!msg || msg.role !== \"user\") return;\n\n const trimmed = newText.trim();\n const files = msg.files ? msg.files.slice() : [];\n const unchanged = trimmed === msg.content;\n const empty = !trimmed && files.length === 0;\n\n if (unchanged || empty) {\n this._cancelEdit(index);\n return;\n }\n\n for (let i = index + 1; i < this._messages.length; i++) {\n const later = this._messages[i];\n if (later.control) later.control.destroy();\n }\n this._messages.length = index + 1;\n msg.content = trimmed;\n\n this._restoreMessages();\n this.fireEvent(\"send\", { message: trimmed, files, conversationId: this.getActiveConversationId() });\n }\n\n private async _writeClipboard(text: string): Promise<boolean> {\n try {\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n return true;\n }\n } catch {\n // fall through to legacy path\n }\n try {\n const ta = document.createElement(\"textarea\");\n ta.value = text;\n ta.setAttribute(\"readonly\", \"\");\n ta.style.position = \"fixed\";\n ta.style.opacity = \"0\";\n document.body.appendChild(ta);\n ta.select();\n const ok = document.execCommand(\"copy\");\n document.body.removeChild(ta);\n return ok;\n } catch {\n return false;\n }\n }\n\n private _renderBubble(bubble: HTMLElement, msg: Message): void {\n if (msg.role === \"assistant\") {\n if (msg.control) {\n bubble.classList.remove(\"cnetChatBox-bubble--pending\");\n bubble.classList.add(\"cnetChatBox-bubble--rich\");\n bubble.innerHTML = \"\";\n msg.control.placeAt(bubble);\n return;\n }\n bubble.classList.remove(\"cnetChatBox-bubble--rich\");\n if (!msg.content) {\n bubble.classList.add(\"cnetChatBox-bubble--pending\");\n bubble.innerHTML = '<span class=\"cnetChatBox-dots\"><span></span><span></span><span></span></span>';\n return;\n }\n bubble.classList.remove(\"cnetChatBox-bubble--pending\");\n bubble.innerHTML = markdownToHtml(msg.content);\n return;\n }\n\n bubble.textContent = \"\";\n if (msg.files && msg.files.length > 0) {\n bubble.appendChild(this._buildAttachmentList(msg.files, \"sent\"));\n }\n if (msg.content) {\n const text = document.createElement(\"div\");\n text.className = \"cnetChatBox-bubble-text\";\n text.textContent = msg.content;\n bubble.appendChild(text);\n }\n }\n\n private _buildAttachmentList(files: File[], variant: \"sent\" | \"pending\"): HTMLDivElement {\n const wrap = document.createElement(\"div\");\n wrap.className = `cnetChatBox-attachments cnetChatBox-attachments--${variant}`;\n for (let i = 0; i < files.length; i++) {\n wrap.appendChild(this._buildAttachmentItem(files[i], variant === \"pending\" ? i : -1));\n }\n return wrap;\n }\n\n private _buildAttachmentItem(file: File, pendingIndex: number): HTMLDivElement {\n const isImage = file.type.startsWith(\"image/\");\n const item = document.createElement(\"div\");\n item.className = `cnetChatBox-attachment cnetChatBox-attachment--${isImage ? \"image\" : \"file\"}`;\n\n if (isImage) {\n const img = document.createElement(\"img\");\n img.className = \"cnetChatBox-attachment-thumb\";\n img.alt = file.name;\n img.src = URL.createObjectURL(file);\n item.appendChild(img);\n } else {\n const iconInfo = IconPool.getIconInfo(\"sap-icon://document\") as IconInfo | undefined;\n const iconSpan = document.createElement(\"span\");\n iconSpan.className = \"cnetChatBox-icon cnetChatBox-attachment-icon\";\n if (iconInfo) iconSpan.style.fontFamily = iconInfo.fontFamily;\n iconSpan.textContent = iconInfo?.content ?? \"📄\";\n item.appendChild(iconSpan);\n }\n\n const name = document.createElement(\"span\");\n name.className = \"cnetChatBox-attachment-name\";\n name.textContent = file.name;\n name.title = file.name;\n item.appendChild(name);\n\n if (pendingIndex >= 0) {\n const i18n = this._i18n();\n const removeLabel = i18n.getText(\"chatbox.removeAttachment\") ?? \"Remove\";\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = \"cnetChatBox-attachment-remove\";\n btn.title = removeLabel;\n btn.setAttribute(\"aria-label\", removeLabel);\n btn.textContent = \"×\";\n btn.addEventListener(\"click\", () => this._removePending(pendingIndex));\n item.appendChild(btn);\n }\n return item;\n }\n\n private _appendMessageDom(index: number): void {\n const container = this._messagesContainer();\n if (!container) return;\n container.querySelector(\".cnetChatBox-empty\")?.remove();\n container.appendChild(this._buildMessageDom(index));\n container.scrollTop = container.scrollHeight;\n }\n\n private _updateBubble(index: number): void {\n const container = this._messagesContainer();\n const wrapper = container?.children[index] as HTMLElement | undefined;\n const bubble = wrapper?.querySelector(\".cnetChatBox-bubble\") as HTMLElement | null;\n if (!bubble || !container) return;\n\n this._renderBubble(bubble, this._messages[index]);\n\n const atBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 80;\n if (atBottom) container.scrollTop = container.scrollHeight;\n }\n\n private _restoreMessages(): void {\n const container = this._messagesContainer();\n if (!container) return;\n container.innerHTML = \"\";\n\n if (this._messages.length === 0) {\n const hint = document.createElement(\"div\");\n hint.className = \"cnetChatBox-empty\";\n hint.textContent = this._i18n().getText(\"chatbox.emptyHint\") ?? \"\";\n container.appendChild(hint);\n return;\n }\n\n for (let i = 0; i < this._messages.length; i++) {\n container.appendChild(this._buildMessageDom(i));\n }\n container.scrollTop = container.scrollHeight;\n }\n\n private _renderSidebar(): void {\n const container = this._dom(\"conversations\");\n if (!container) return;\n container.innerHTML = \"\";\n\n if (this._conversations.length === 0) {\n const empty = document.createElement(\"div\");\n empty.className = \"cnetChatBox-conversations-empty\";\n empty.textContent = this._i18n().getText(\"chatbox.noConversations\") ?? \"\";\n container.appendChild(empty);\n return;\n }\n\n const active = this.getActiveConversationId();\n for (const conv of this._conversations) {\n container.appendChild(this._buildConversationItem(conv, conv.id === active));\n }\n }\n\n private _buildConversationItem(conv: Conversation, isActive: boolean): HTMLDivElement {\n const i18n = this._i18n();\n const item = document.createElement(\"div\");\n item.className = \"cnetChatBox-conversation\" + (isActive ? \" cnetChatBox-conversation--active\" : \"\");\n item.setAttribute(\"role\", \"button\");\n item.tabIndex = 0;\n\n const body = document.createElement(\"div\");\n body.className = \"cnetChatBox-conversation-body\";\n\n const title = document.createElement(\"div\");\n title.className = \"cnetChatBox-conversation-title\";\n title.textContent = this._conversationTitle(conv);\n body.appendChild(title);\n\n const snippet = document.createElement(\"div\");\n snippet.className = \"cnetChatBox-conversation-snippet\";\n snippet.textContent = this._conversationSnippet(conv);\n body.appendChild(snippet);\n\n const time = document.createElement(\"div\");\n time.className = \"cnetChatBox-conversation-time\";\n time.textContent = this._formatTimestamp(conv.updatedAt);\n body.appendChild(time);\n\n item.appendChild(body);\n\n const removeLabel = i18n.getText(\"chatbox.deleteConversation\") ?? \"Delete conversation\";\n const del = document.createElement(\"button\");\n del.type = \"button\";\n del.className = \"cnetChatBox-conversation-remove\";\n del.title = removeLabel;\n del.setAttribute(\"aria-label\", removeLabel);\n del.textContent = \"×\";\n del.addEventListener(\"click\", (e: MouseEvent) => {\n e.stopPropagation();\n this._requestDeleteConversation(conv.id);\n });\n item.appendChild(del);\n\n item.addEventListener(\"click\", () => this._selectConversation(conv.id));\n item.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n this._selectConversation(conv.id);\n }\n });\n\n return item;\n }\n\n private _conversationTitle(conv: Conversation): string {\n if (conv.title && conv.title.trim()) return conv.title.trim();\n const msgs = conv.messages ?? [];\n for (const m of msgs) {\n if (m.role === \"user\" && m.content && m.content.trim()) {\n return this._truncate(m.content.trim(), 40);\n }\n }\n return this._i18n().getText(\"chatbox.untitledConversation\") ?? \"Neue Konversation\";\n }\n\n private _conversationSnippet(conv: Conversation): string {\n const msgs = conv.messages ?? [];\n for (let i = msgs.length - 1; i >= 0; i--) {\n const m = msgs[i];\n if (m.content && m.content.trim()) {\n const prefix = m.role === \"user\" ? (this._i18n().getText(\"chatbox.youPrefix\") ?? \"Du:\") + \" \" : \"\";\n return this._truncate(prefix + m.content.trim().replace(/\\s+/g, \" \"), 60);\n }\n }\n return \"\";\n }\n\n private _truncate(text: string, max: number): string {\n return text.length > max ? text.slice(0, max - 1).trimEnd() + \"…\" : text;\n }\n\n private _formatTimestamp(raw: number | string | Date | undefined): string {\n if (raw === undefined || raw === null) return \"\";\n const d = raw instanceof Date ? raw : new Date(raw);\n if (isNaN(d.getTime())) return \"\";\n const now = new Date();\n const sameDay = d.toDateString() === now.toDateString();\n const yesterday = new Date(now);\n yesterday.setDate(now.getDate() - 1);\n const wasYesterday = d.toDateString() === yesterday.toDateString();\n const locale = Localization.getLanguage() || undefined;\n if (sameDay) {\n return d.toLocaleTimeString(locale, { hour: \"2-digit\", minute: \"2-digit\" });\n }\n if (wasYesterday) {\n return this._i18n().getText(\"chatbox.yesterday\") ?? \"Gestern\";\n }\n const weekDelta = (now.getTime() - d.getTime()) / (1000 * 60 * 60 * 24);\n if (weekDelta < 7) {\n return d.toLocaleDateString(locale, { weekday: \"short\" });\n }\n return d.toLocaleDateString(locale, { day: \"2-digit\", month: \"2-digit\", year: \"2-digit\" });\n }\n\n private _renderHeaderTitle(): void {\n const target = this._dom(\"headerTitle\");\n if (!target) return;\n const active = this.getActiveConversationId();\n const conv = active ? this._conversations.find((c) => c.id === active) : undefined;\n target.textContent = conv ? this._conversationTitle(conv) : \"\";\n }\n\n private _wireAttachments(): void {\n const attachBtn = this._dom(\"attach\");\n const fileInput = this._dom(\"fileinput\") as HTMLInputElement | null;\n const root = this.getDomRef() as HTMLElement | null;\n\n attachBtn?.addEventListener(\"click\", () => fileInput?.click());\n fileInput?.addEventListener(\"change\", () => {\n if (!fileInput.files) return;\n this._addPending(Array.from(fileInput.files));\n fileInput.value = \"\";\n });\n\n if (!root) return;\n\n let dragDepth = 0;\n root.addEventListener(\"dragenter\", (e: DragEvent) => {\n if (!this._hasFiles(e)) return;\n e.preventDefault();\n dragDepth++;\n root.classList.add(\"cnetChatBox--dragOver\");\n });\n root.addEventListener(\"dragover\", (e: DragEvent) => {\n if (!this._hasFiles(e)) return;\n e.preventDefault();\n if (e.dataTransfer) e.dataTransfer.dropEffect = \"copy\";\n });\n root.addEventListener(\"dragleave\", () => {\n dragDepth = Math.max(0, dragDepth - 1);\n if (dragDepth === 0) root.classList.remove(\"cnetChatBox--dragOver\");\n });\n root.addEventListener(\"drop\", (e: DragEvent) => {\n if (!this._hasFiles(e)) return;\n e.preventDefault();\n dragDepth = 0;\n root.classList.remove(\"cnetChatBox--dragOver\");\n const files = e.dataTransfer?.files;\n if (!files || files.length === 0) return;\n this._addPending(Array.from(files));\n });\n }\n\n private _hasFiles(e: DragEvent): boolean {\n const types = e.dataTransfer?.types;\n if (!types) return false;\n for (let i = 0; i < types.length; i++) {\n if (types[i] === \"Files\") return true;\n }\n return false;\n }\n\n private _addPending(files: File[]): void {\n if (this.getBusy()) return;\n if (files.length === 0) return;\n this._pendingAttachments = this._pendingAttachments.concat(files);\n this._renderPending();\n }\n\n private _removePending(index: number): void {\n if (index < 0 || index >= this._pendingAttachments.length) return;\n this._pendingAttachments.splice(index, 1);\n this._renderPending();\n }\n\n private _renderPending(): void {\n const container = this._dom(\"pending\");\n if (!container) return;\n container.innerHTML = \"\";\n if (this._pendingAttachments.length === 0) {\n container.classList.remove(\"cnetChatBox-attachmentsPending--hasItems\");\n return;\n }\n container.classList.add(\"cnetChatBox-attachmentsPending--hasItems\");\n container.appendChild(this._buildAttachmentList(this._pendingAttachments, \"pending\"));\n }\n}\n"],"mappings":"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","ignoreList":[],"sourceRoot":""}},{"offset":{"line":5,"column":0},"map":{"version":3,"names":["sap","ui","define","Lib","init","name","version","dependencies","types","interfaces","controls","elements","noLibraryCSS","apiVersion"],"sources":["library.ts"],"sourcesContent":["/*!\n * ${copyright}\n */\n\nimport Lib from \"sap/ui/core/Lib\";\n\n/**\n * Initialization code and shared classes of library\n * <code>chatui</code>. The library ships a single control,\n * {@link chatui.ChatBox}, an embeddable AI chat block for UI5\n * applications.\n *\n * @namespace chatui\n * @public\n */\nLib.init({\n name: \"chatui\",\n version: \"0.1.0\",\n dependencies: [\n \"sap.ui.core\"\n ],\n types: [],\n interfaces: [],\n controls: [\n \"chatui.ChatBox\"\n ],\n elements: [],\n noLibraryCSS: false,\n apiVersion: 2\n});\n"],"mappings":"AAAA;;;AAAAA,IAAAC,GAAAC,4BAAA,6BAAAC,GAAA,aAeAA,EAAIC,KAAK,CACLC,KAAM,SACNC,QAAS,QACTC,aAAc,CACV,eAEJC,MAAO,GACPC,WAAY,GACZC,SAAU,CACN,kBAEJC,SAAU,GACVC,aAAc,MACdC,WAAY,GACb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":["sap","ui","define","escape","s","replace","inline","text","_m","label","href","parseTableRow","line","split","slice","map","cell","trim","isSeparatorRow","test","every","c","markdownToHtml","md","lines","out","inCode","codeLines","inList","inTable","flushList","push","flushTable","i","length","startsWith","join","trimmed","endsWith","next","headers","h","cells","heading","match","level","Math","min","li"],"sources":["markdown.ts"],"sourcesContent":["/*!\n * ${copyright}\n */\n\nconst escape = (s: string): string =>\n s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n\nconst inline = (text: string): string =>\n escape(text)\n .replace(/`([^`]+)`/g, \"<code>$1</code>\")\n .replace(/\\*\\*(.+?)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/__(.+?)__/g, \"<strong>$1</strong>\")\n .replace(/\\*(.+?)\\*/g, \"<em>$1</em>\")\n .replace(/_(.+?)_/g, \"<em>$1</em>\")\n .replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_m: string, label: string, href: string) =>\n `<a href=\"${href}\" target=\"_blank\" rel=\"noopener\">${label}</a>`\n );\n\nconst parseTableRow = (line: string): string[] =>\n line.split(\"|\").slice(1, -1).map((cell) => cell.trim());\n\nconst isSeparatorRow = (line: string): boolean =>\n /^\\|[\\s:-]+\\|/.test(line) && parseTableRow(line).every((c) => /^:?-+:?$/.test(c));\n\n/**\n * Minimal Markdown → HTML converter used to format assistant messages.\n *\n * Supports paragraphs, headings, unordered lists, fenced code blocks,\n * pipe-tables and the usual inline formatting (bold, italic, inline code,\n * links). Output is escaped before formatting is applied, so it is safe to\n * assign the result to `innerHTML`.\n *\n * @namespace chatui\n */\nexport default function markdownToHtml(md: string): string {\n if (!md) return \"\";\n\n const lines = md.split(\"\\n\");\n const out: string[] = [];\n let inCode = false;\n let codeLines: string[] = [];\n let inList = false;\n let inTable = false;\n\n const flushList = (): void => {\n if (inList) {\n out.push(\"</ul>\");\n inList = false;\n }\n };\n const flushTable = (): void => {\n if (inTable) {\n out.push(\"</tbody></table>\");\n inTable = false;\n }\n };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n if (line.startsWith(\"```\")) {\n flushList();\n flushTable();\n if (inCode) {\n out.push(`<pre><code>${escape(codeLines.join(\"\\n\"))}</code></pre>`);\n codeLines = [];\n }\n inCode = !inCode;\n continue;\n }\n if (inCode) {\n codeLines.push(line);\n continue;\n }\n\n const trimmed = line.trim();\n\n if (!inTable && trimmed.startsWith(\"|\") && trimmed.endsWith(\"|\")) {\n const next = lines[i + 1]?.trim();\n if (next && isSeparatorRow(next)) {\n flushList();\n const headers = parseTableRow(trimmed);\n out.push('<table class=\"cnetChatBox-table\"><thead><tr>');\n for (const h of headers) out.push(`<th>${inline(h)}</th>`);\n out.push(\"</tr></thead><tbody>\");\n inTable = true;\n i++;\n continue;\n }\n }\n\n if (inTable) {\n if (trimmed.startsWith(\"|\") && trimmed.endsWith(\"|\")) {\n const cells = parseTableRow(trimmed);\n out.push(\"<tr>\");\n for (const c of cells) out.push(`<td>${inline(c)}</td>`);\n out.push(\"</tr>\");\n continue;\n }\n flushTable();\n }\n\n if (!trimmed) {\n flushList();\n flushTable();\n continue;\n }\n\n const heading = trimmed.match(/^(#{1,6})\\s+(.+)$/);\n if (heading) {\n flushList();\n flushTable();\n const level = Math.min(heading[1].length, 6);\n out.push(`<h${level}>${inline(heading[2])}</h${level}>`);\n continue;\n }\n\n const li = trimmed.match(/^[-*]\\s+(.*)/);\n if (li) {\n flushTable();\n if (!inList) {\n out.push(\"<ul>\");\n inList = true;\n }\n out.push(`<li>${inline(li[1])}</li>`);\n continue;\n }\n\n flushList();\n flushTable();\n out.push(`<p>${inline(trimmed)}</p>`);\n }\n\n flushList();\n flushTable();\n if (inCode) out.push(`<pre><code>${escape(codeLines.join(\"\\n\"))}</code></pre>`);\n return out.join(\"\");\n}\n"],"mappings":"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","ignoreList":[],"sourceRoot":""}},{"offset":{"line":13,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library.d.ts","sourceRoot":"../../..","sources":["src/chatui/library.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,gBAAgB,CAAC;CAgC/B"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ${copyright}
|
|
3
|
+
*/
|
|
4
|
+
sap.ui.define(["sap/ui/core/Lib"],function(e){"use strict";e.init({name:"chatui",version:"0.1.0",dependencies:["sap.ui.core"],types:[],interfaces:[],controls:["chatui.ChatBox"],elements:[],noLibraryCSS:false,apiVersion:2})});
|
|
5
|
+
//# sourceMappingURL=library.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library.js","names":["sap","ui","define","Lib","init","name","version","dependencies","types","interfaces","controls","elements","noLibraryCSS","apiVersion"],"sources":["library.ts"],"sourcesContent":["/*!\n * ${copyright}\n */\n\nimport Lib from \"sap/ui/core/Lib\";\n\n/**\n * Initialization code and shared classes of library\n * <code>chatui</code>. The library ships a single control,\n * {@link chatui.ChatBox}, an embeddable AI chat block for UI5\n * applications.\n *\n * @namespace chatui\n * @public\n */\nLib.init({\n name: \"chatui\",\n version: \"0.1.0\",\n dependencies: [\n \"sap.ui.core\"\n ],\n types: [],\n interfaces: [],\n controls: [\n \"chatui.ChatBox\"\n ],\n elements: [],\n noLibraryCSS: false,\n apiVersion: 2\n});\n"],"mappings":";;;AAAAA,IAAAC,GAAAC,OAAA,6BAAAC,GAAA,aAeAA,EAAIC,KAAK,CACLC,KAAM,SACNC,QAAS,QACTC,aAAc,CACV,eAEJC,MAAO,GACPC,WAAY,GACZC,SAAU,CACN,kBAEJC,SAAU,GACVC,aAAc,MACdC,WAAY,GACb","ignoreList":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ${copyright}
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Lib from "sap/ui/core/Lib";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Initialization code and shared classes of library
|
|
9
|
+
* <code>chatui</code>. The library ships a single control,
|
|
10
|
+
* {@link chatui.ChatBox}, an embeddable AI chat block for UI5
|
|
11
|
+
* applications.
|
|
12
|
+
*
|
|
13
|
+
* @namespace chatui
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
Lib.init({
|
|
17
|
+
name: "chatui",
|
|
18
|
+
version: "0.1.0",
|
|
19
|
+
dependencies: [
|
|
20
|
+
"sap.ui.core"
|
|
21
|
+
],
|
|
22
|
+
types: [],
|
|
23
|
+
interfaces: [],
|
|
24
|
+
controls: [
|
|
25
|
+
"chatui.ChatBox"
|
|
26
|
+
],
|
|
27
|
+
elements: [],
|
|
28
|
+
noLibraryCSS: false,
|
|
29
|
+
apiVersion: 2
|
|
30
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_version": "1.21.0",
|
|
3
|
+
"sap.app": {
|
|
4
|
+
"id": "chatui",
|
|
5
|
+
"type": "library",
|
|
6
|
+
"embeds": [],
|
|
7
|
+
"applicationVersion": {
|
|
8
|
+
"version": "0.1.0"
|
|
9
|
+
},
|
|
10
|
+
"title": "chatui",
|
|
11
|
+
"description": "Embeddable AI chat UI control for UI5 applications.",
|
|
12
|
+
"resources": "resources.json",
|
|
13
|
+
"offline": true
|
|
14
|
+
},
|
|
15
|
+
"sap.ui": {
|
|
16
|
+
"technology": "UI5",
|
|
17
|
+
"supportedThemes": [
|
|
18
|
+
"base",
|
|
19
|
+
"sap_fiori_3",
|
|
20
|
+
"sap_fiori_3_dark",
|
|
21
|
+
"sap_fiori_3_hcb",
|
|
22
|
+
"sap_fiori_3_hcw"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"sap.ui5": {
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"minUI5Version": "1.136",
|
|
28
|
+
"libs": {
|
|
29
|
+
"sap.ui.core": {
|
|
30
|
+
"minVersion": "1.136.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"library": {
|
|
35
|
+
"i18n": {
|
|
36
|
+
"bundleUrl": "messagebundle.properties",
|
|
37
|
+
"supportedLocales": [
|
|
38
|
+
"",
|
|
39
|
+
"en"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"content": {
|
|
43
|
+
"controls": [
|
|
44
|
+
"chatui.ChatBox"
|
|
45
|
+
],
|
|
46
|
+
"elements": [],
|
|
47
|
+
"types": [],
|
|
48
|
+
"interfaces": []
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ${copyright}
|
|
3
|
+
*/
|
|
4
|
+
sap.ui.define([], function () {
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
const escape = s => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
8
|
+
const inline = text => escape(text).replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/__(.+?)__/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/_(.+?)_/g, "<em>$1</em>").replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, label, href) => `<a href="${href}" target="_blank" rel="noopener">${label}</a>`);
|
|
9
|
+
const parseTableRow = line => line.split("|").slice(1, -1).map(cell => cell.trim());
|
|
10
|
+
const isSeparatorRow = line => /^\|[\s:-]+\|/.test(line) && parseTableRow(line).every(c => /^:?-+:?$/.test(c));
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal Markdown → HTML converter used to format assistant messages.
|
|
14
|
+
*
|
|
15
|
+
* Supports paragraphs, headings, unordered lists, fenced code blocks,
|
|
16
|
+
* pipe-tables and the usual inline formatting (bold, italic, inline code,
|
|
17
|
+
* links). Output is escaped before formatting is applied, so it is safe to
|
|
18
|
+
* assign the result to `innerHTML`.
|
|
19
|
+
*
|
|
20
|
+
* @namespace chatui
|
|
21
|
+
*/
|
|
22
|
+
function markdownToHtml(md) {
|
|
23
|
+
if (!md) return "";
|
|
24
|
+
const lines = md.split("\n");
|
|
25
|
+
const out = [];
|
|
26
|
+
let inCode = false;
|
|
27
|
+
let codeLines = [];
|
|
28
|
+
let inList = false;
|
|
29
|
+
let inTable = false;
|
|
30
|
+
const flushList = () => {
|
|
31
|
+
if (inList) {
|
|
32
|
+
out.push("</ul>");
|
|
33
|
+
inList = false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const flushTable = () => {
|
|
37
|
+
if (inTable) {
|
|
38
|
+
out.push("</tbody></table>");
|
|
39
|
+
inTable = false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
for (let i = 0; i < lines.length; i++) {
|
|
43
|
+
const line = lines[i];
|
|
44
|
+
if (line.startsWith("```")) {
|
|
45
|
+
flushList();
|
|
46
|
+
flushTable();
|
|
47
|
+
if (inCode) {
|
|
48
|
+
out.push(`<pre><code>${escape(codeLines.join("\n"))}</code></pre>`);
|
|
49
|
+
codeLines = [];
|
|
50
|
+
}
|
|
51
|
+
inCode = !inCode;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (inCode) {
|
|
55
|
+
codeLines.push(line);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const trimmed = line.trim();
|
|
59
|
+
if (!inTable && trimmed.startsWith("|") && trimmed.endsWith("|")) {
|
|
60
|
+
const next = lines[i + 1]?.trim();
|
|
61
|
+
if (next && isSeparatorRow(next)) {
|
|
62
|
+
flushList();
|
|
63
|
+
const headers = parseTableRow(trimmed);
|
|
64
|
+
out.push('<table class="cnetChatBox-table"><thead><tr>');
|
|
65
|
+
for (const h of headers) out.push(`<th>${inline(h)}</th>`);
|
|
66
|
+
out.push("</tr></thead><tbody>");
|
|
67
|
+
inTable = true;
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (inTable) {
|
|
73
|
+
if (trimmed.startsWith("|") && trimmed.endsWith("|")) {
|
|
74
|
+
const cells = parseTableRow(trimmed);
|
|
75
|
+
out.push("<tr>");
|
|
76
|
+
for (const c of cells) out.push(`<td>${inline(c)}</td>`);
|
|
77
|
+
out.push("</tr>");
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
flushTable();
|
|
81
|
+
}
|
|
82
|
+
if (!trimmed) {
|
|
83
|
+
flushList();
|
|
84
|
+
flushTable();
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const heading = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
88
|
+
if (heading) {
|
|
89
|
+
flushList();
|
|
90
|
+
flushTable();
|
|
91
|
+
const level = Math.min(heading[1].length, 6);
|
|
92
|
+
out.push(`<h${level}>${inline(heading[2])}</h${level}>`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const li = trimmed.match(/^[-*]\s+(.*)/);
|
|
96
|
+
if (li) {
|
|
97
|
+
flushTable();
|
|
98
|
+
if (!inList) {
|
|
99
|
+
out.push("<ul>");
|
|
100
|
+
inList = true;
|
|
101
|
+
}
|
|
102
|
+
out.push(`<li>${inline(li[1])}</li>`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
flushList();
|
|
106
|
+
flushTable();
|
|
107
|
+
out.push(`<p>${inline(trimmed)}</p>`);
|
|
108
|
+
}
|
|
109
|
+
flushList();
|
|
110
|
+
flushTable();
|
|
111
|
+
if (inCode) out.push(`<pre><code>${escape(codeLines.join("\n"))}</code></pre>`);
|
|
112
|
+
return out.join("");
|
|
113
|
+
}
|
|
114
|
+
return markdownToHtml;
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=markdown-dbg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-dbg.js","names":["sap","ui","define","escape","s","replace","inline","text","_m","label","href","parseTableRow","line","split","slice","map","cell","trim","isSeparatorRow","test","every","c","markdownToHtml","md","lines","out","inCode","codeLines","inList","inTable","flushList","push","flushTable","i","length","startsWith","join","trimmed","endsWith","next","headers","h","cells","heading","match","level","Math","min","li"],"sources":["markdown.ts"],"sourcesContent":["/*!\n * ${copyright}\n */\n\nconst escape = (s: string): string =>\n s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n\nconst inline = (text: string): string =>\n escape(text)\n .replace(/`([^`]+)`/g, \"<code>$1</code>\")\n .replace(/\\*\\*(.+?)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/__(.+?)__/g, \"<strong>$1</strong>\")\n .replace(/\\*(.+?)\\*/g, \"<em>$1</em>\")\n .replace(/_(.+?)_/g, \"<em>$1</em>\")\n .replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_m: string, label: string, href: string) =>\n `<a href=\"${href}\" target=\"_blank\" rel=\"noopener\">${label}</a>`\n );\n\nconst parseTableRow = (line: string): string[] =>\n line.split(\"|\").slice(1, -1).map((cell) => cell.trim());\n\nconst isSeparatorRow = (line: string): boolean =>\n /^\\|[\\s:-]+\\|/.test(line) && parseTableRow(line).every((c) => /^:?-+:?$/.test(c));\n\n/**\n * Minimal Markdown → HTML converter used to format assistant messages.\n *\n * Supports paragraphs, headings, unordered lists, fenced code blocks,\n * pipe-tables and the usual inline formatting (bold, italic, inline code,\n * links). Output is escaped before formatting is applied, so it is safe to\n * assign the result to `innerHTML`.\n *\n * @namespace chatui\n */\nexport default function markdownToHtml(md: string): string {\n if (!md) return \"\";\n\n const lines = md.split(\"\\n\");\n const out: string[] = [];\n let inCode = false;\n let codeLines: string[] = [];\n let inList = false;\n let inTable = false;\n\n const flushList = (): void => {\n if (inList) {\n out.push(\"</ul>\");\n inList = false;\n }\n };\n const flushTable = (): void => {\n if (inTable) {\n out.push(\"</tbody></table>\");\n inTable = false;\n }\n };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n if (line.startsWith(\"```\")) {\n flushList();\n flushTable();\n if (inCode) {\n out.push(`<pre><code>${escape(codeLines.join(\"\\n\"))}</code></pre>`);\n codeLines = [];\n }\n inCode = !inCode;\n continue;\n }\n if (inCode) {\n codeLines.push(line);\n continue;\n }\n\n const trimmed = line.trim();\n\n if (!inTable && trimmed.startsWith(\"|\") && trimmed.endsWith(\"|\")) {\n const next = lines[i + 1]?.trim();\n if (next && isSeparatorRow(next)) {\n flushList();\n const headers = parseTableRow(trimmed);\n out.push('<table class=\"cnetChatBox-table\"><thead><tr>');\n for (const h of headers) out.push(`<th>${inline(h)}</th>`);\n out.push(\"</tr></thead><tbody>\");\n inTable = true;\n i++;\n continue;\n }\n }\n\n if (inTable) {\n if (trimmed.startsWith(\"|\") && trimmed.endsWith(\"|\")) {\n const cells = parseTableRow(trimmed);\n out.push(\"<tr>\");\n for (const c of cells) out.push(`<td>${inline(c)}</td>`);\n out.push(\"</tr>\");\n continue;\n }\n flushTable();\n }\n\n if (!trimmed) {\n flushList();\n flushTable();\n continue;\n }\n\n const heading = trimmed.match(/^(#{1,6})\\s+(.+)$/);\n if (heading) {\n flushList();\n flushTable();\n const level = Math.min(heading[1].length, 6);\n out.push(`<h${level}>${inline(heading[2])}</h${level}>`);\n continue;\n }\n\n const li = trimmed.match(/^[-*]\\s+(.*)/);\n if (li) {\n flushTable();\n if (!inList) {\n out.push(\"<ul>\");\n inList = true;\n }\n out.push(`<li>${inline(li[1])}</li>`);\n continue;\n }\n\n flushList();\n flushTable();\n out.push(`<p>${inline(trimmed)}</p>`);\n }\n\n flushList();\n flushTable();\n if (inCode) out.push(`<pre><code>${escape(codeLines.join(\"\\n\"))}</code></pre>`);\n return out.join(\"\");\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare module "chatui/markdown" {
|
|
2
|
+
/*!
|
|
3
|
+
* ${copyright}
|
|
4
|
+
*/
|
|
5
|
+
const escape: (s: string) => string;
|
|
6
|
+
const inline: (text: string) => string;
|
|
7
|
+
const parseTableRow: (line: string) => string[];
|
|
8
|
+
const isSeparatorRow: (line: string) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Minimal Markdown → HTML converter used to format assistant messages.
|
|
11
|
+
*
|
|
12
|
+
* Supports paragraphs, headings, unordered lists, fenced code blocks,
|
|
13
|
+
* pipe-tables and the usual inline formatting (bold, italic, inline code,
|
|
14
|
+
* links). Output is escaped before formatting is applied, so it is safe to
|
|
15
|
+
* assign the result to `innerHTML`.
|
|
16
|
+
*
|
|
17
|
+
* @namespace chatui
|
|
18
|
+
*/
|
|
19
|
+
export default function markdownToHtml(md: string): string;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"../../..","sources":["src/chatui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,iBAAiB,CAAC;IACjC;;OAEG;IAEH,MAAM,MAAM,GAAI,GAAG,MAAM,KAAG,MACoE,CAAC;IAEjG,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,MAWtB,CAAC;IAEV,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAAM,EACe,CAAC;IAE5D,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,OAC8C,CAAC;IAEtF;;;;;;;;;OASG;IACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAuGzD;CAEA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ${copyright}
|
|
3
|
+
*/
|
|
4
|
+
sap.ui.define([],function(){"use strict";const t=t=>t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");const e=e=>t(e).replace(/`([^`]+)`/g,"<code>$1</code>").replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/__(.+?)__/g,"<strong>$1</strong>").replace(/\*(.+?)\*/g,"<em>$1</em>").replace(/_(.+?)_/g,"<em>$1</em>").replace(/\[([^\]]+)\]\(([^)]+)\)/g,(t,e,s)=>`<a href="${s}" target="_blank" rel="noopener">${e}</a>`);const s=t=>t.split("|").slice(1,-1).map(t=>t.trim());const n=t=>/^\|[\s:-]+\|/.test(t)&&s(t).every(t=>/^:?-+:?$/.test(t));function o(o){if(!o)return"";const c=o.split("\n");const r=[];let i=false;let p=[];let l=false;let a=false;const u=()=>{if(l){r.push("</ul>");l=false}};const h=()=>{if(a){r.push("</tbody></table>");a=false}};for(let o=0;o<c.length;o++){const f=c[o];if(f.startsWith("```")){u();h();if(i){r.push(`<pre><code>${t(p.join("\n"))}</code></pre>`);p=[]}i=!i;continue}if(i){p.push(f);continue}const g=f.trim();if(!a&&g.startsWith("|")&&g.endsWith("|")){const t=c[o+1]?.trim();if(t&&n(t)){u();const t=s(g);r.push('<table class="cnetChatBox-table"><thead><tr>');for(const s of t)r.push(`<th>${e(s)}</th>`);r.push("</tr></thead><tbody>");a=true;o++;continue}}if(a){if(g.startsWith("|")&&g.endsWith("|")){const t=s(g);r.push("<tr>");for(const s of t)r.push(`<td>${e(s)}</td>`);r.push("</tr>");continue}h()}if(!g){u();h();continue}const $=g.match(/^(#{1,6})\s+(.+)$/);if($){u();h();const t=Math.min($[1].length,6);r.push(`<h${t}>${e($[2])}</h${t}>`);continue}const d=g.match(/^[-*]\s+(.*)/);if(d){h();if(!l){r.push("<ul>");l=true}r.push(`<li>${e(d[1])}</li>`);continue}u();h();r.push(`<p>${e(g)}</p>`)}u();h();if(i)r.push(`<pre><code>${t(p.join("\n"))}</code></pre>`);return r.join("")}return o});
|
|
5
|
+
//# sourceMappingURL=markdown.js.map
|