1/*! 2 * MASM ÑÐ°Ñ Ð²Ð¸Ð´Ð¶ÐµÑ â masm.gov.mn ÑайÑад ÑÑÑÐ»Ð³Ð°Ñ ÑÐ°Ñ ÑÐ¾Ð½Ñ . 3 * 4 * СÑÑÐ»Ð³Ð°Ñ : 5 * <script src="/chatbot/widget/masm-chat.js" defer></script> 6 * 7 * Ð¢Ð¾Ñ Ð¸Ñгоо (script Ñаг дÑÑÑÑ data-* аÑÑибÑÑÑÑд, бүгд заавал биÑ): 8 * data-api API-ийн Ñ Ð°Ñг. Өгөөгүй бол ÑкÑипÑийн байÑÐ»Ð°Ð»Ð°Ð°Ñ Ð°Ð²ÑомаÑÐ°Ð°Ñ Ð¾Ð»Ð½Ð¾. 9 * data-title Ð¦Ð¾Ð½Ñ Ð½Ñ Ð³Ð°ÑÑиг 10 * data-subtitle ÐаÑÑгийн дооÑÑ Ð¶Ð¸Ð¶Ð¸Ð³ Ð¼Ó©Ñ 11 * data-accent ҮндÑÑн өнгө (HEX). ÐÑÑад өнгө үүнÑÑÑ ÑооÑоологдоно. 12 * data-greeting ÐÑ Ð½Ð¸Ð¹ мÑндÑилгÑÑ 13 * data-suggestions Санал Ð±Ð¾Ð»Ð³Ð¾Ñ Ð°ÑÑÑлÑÑÑд, "|"-ÑÑÑ ÑÑÑгааÑлана 14 * data-position "right" (өгөгдмөл) ÑÑвÑл "left" 15 * data-tooltip ÐÑÑÑ ÑÐ¾Ð²Ñ Ð´ÑÑÑ Ñ Ñлгана оÑÐ¸Ñ Ð¾Ð´ гаÑÐ°Ñ Ð±Ð¸Ñиг, мөÑийг "|"-ÑÑÑ ÑÑÑгааÑлана 16 * data-logo Ð¦Ð¾Ð½Ñ Ð½Ñ Ñолгойн лого (зÑÑгийн URL). "none" â логогүй 17 * data-sound "off" бол илгÑÑÑ Ð´ÑÑ Ð°Ð½Ñ Ð½Ð°Ð°Ñаа ÑнÑÑаалÑÑай (Ñ ÑÑÑглÑÐ³Ñ ÑÐ¾Ð»Ð³Ð¾Ð¹Ð½Ð¾Ð¾Ñ Ð°Ñаана) 18 * data-sources "on" бол Ñ Ð°ÑиÑлÑÑн Ð´Ð¾Ð¾Ñ ÑÑ ÑÑÑвалжийн жагÑÐ°Ð°Ð»Ñ Ð³Ð°Ñгана (Ð¾Ð´Ð¾Ð¾Ð³Ð¾Ð¾Ñ Ó©Ð³Ó©Ð³Ð´Ð¼Ó©Ð» "off") 19 * 20 * ÐÒ¯Ñ Ð±Ò¯ÑÑÑ Shadow DOM доÑÐ¾Ñ Ð±Ð°Ð¹Ñлана â ÑайÑÑн CSS виджеÑÑд, виджеÑийн CSS 21 * ÑайÑад Ð½Ó©Ð»Ó©Ó©Ð»Ó©Ñ Ð³Ò¯Ð¹. Ð¥Ð°Ñ Ð·Ð°Ð³Ð²Ð°ÑÑг (dark mode) ÑиÑÑемийн ÑÐ¾Ñ Ð¸ÑÐ³Ð¾Ð¾Ð½Ð¾Ð¾Ñ Ð´Ð°Ð³Ð°Ð½Ð°. 22 */ 23(function () { 24 "use strict"; 25 26 if (window.__masmChatLoaded) return; 27 window.__masmChatLoaded = true; 28 29 // ÐÑÑÑ ÑовÑÐ½Ñ Ð±Ñланд Ñ Ð°ÑÐ°Ð³Ð´Ð°Ñ Ñ ÑÐ²Ð¸Ð»Ð±Ð°Ñ â ÑÐ¸Ð½Ñ Ñ ÑÐ²Ð¸Ð»Ð±Ð°Ñ Ð³Ð°ÑÐ³Ð°Ñ Ð°Ð´ Ñнд Ñолино. 30 var VERSION = "1.1.2"; 31 32 var script = document.currentScript || 33 document.querySelector('script[src*="masm-chat.js"]'); 34 35 function attr(name, fallback) { 36 var value = script && script.getAttribute("data-" + name); 37 return value === null || value === undefined || value === "" ? fallback : value; 38 } 39 40 // API-ийн Ñ Ð°Ñгийг ÑкÑипÑийн src-ÑÑÑ Ð³Ð°Ñгана: .../widget/masm-chat.js -> ... 41 function defaultApi() { 42 try { 43 var url = new URL(script.src, location.href); 44 return url.origin + url.pathname.replace(/\/widget\/[^/]*$/, ""); 45 } catch (e) { 46 return location.origin; 47 } 48 } 49 50 var CFG = { 51 api: attr("api", defaultApi()).replace(/\/$/, ""), 52 title: attr("title", "MASM ÑÑÑÐ»Ð°Ñ "), 53 subtitle: attr("subtitle", "Ðлбан ÑÑÐ½Ñ ÑÑ ÑÑÑвалжид ÑÑлгÑÑÑлана"), 54 accent: attr("accent", "#001f60"), // masm.gov.mn-ийн үндÑÑн Ñ Ð°Ñ Ñ Ó©Ñ 55 greeting: attr("greeting", 56 "СÑандаÑÑ Ñ Ñмжил зүйн газÑÑн AI Ð°Ð³ÐµÐ½Ñ ÑанÑг ÑонÑож байна."), 57 // ÐÐ½Ñ Ð½Ð°Ð°Ñаа жиÑÑÑ Ð°ÑÑÑлÑгүй â Ñ ÑÑÑгÑÑй бол data-suggestions-ÑÑÑ Ó©Ð³Ð½Ó©. 58 suggestions: attr("suggestions", "") 59 .split("|").map(function (s) { return s.trim(); }).filter(Boolean), 60 position: attr("position", "right"), 61 // ХаÑиÑлÑÑн дооÑÑ ÑÑ ÑÑÑвалжийн жагÑаалÑ. Ð¢Ò¯Ñ Ð½ÑÑÑан â бÑÑааж нÑÑÑ Ð´ÑÑ 62 // өгөгдмөлийг "on" Ð±Ð¾Ð»Ð³Ð¾Ñ ÑÑвÑл script Ñаг дÑÑÑ data-sources="on" өгнө. 63 sources: attr("sources", "off") === "on", 64 // "none" гÑвÑл лого аÑÐ¸Ð³Ð»Ð°Ñ Ð³Ò¯Ð¹ (бамбайн дүÑÑ) 65 logo: (function (v) { return v === "none" ? "" : v; })( 66 attr("logo", "https://masm.gov.mn/images/favoicon/favicon-32x32.png")), 67 tooltip: attr("tooltip", "СÑандаÑÑ Ñ Ñмжил зүйн газÑÑн ÑÑ Ð°Ð°Ð»Ð°Ð³ Ñ Ó©ÑÓ©Ñ|Masm AI Chatbot") 68 .split("|").map(function (s) { return s.trim(); }).filter(Boolean), 69 // Ð§Ð°Ñ Ñ Ð°Ð°Ð»ÑÑай үед ÑовÑÐ½Ñ Ð´ÑÑÑ Ð±Ð°Ð¹Ð½Ð³Ð° Ñ Ð°ÑÐ°Ð³Ð´Ð°Ñ Ð¼ÑндÑилгÑÑ. ÐÑ Ð½Ð¸Ð¹ Ð¼Ó©Ñ Ð½Ñ 70 // гаÑÑиг, даÑÐ°Ð°Ð³Ð¸Ð¹Ð½Ñ Ð½Ñ Ð´Ð¾Ð³Ð¾Ð» мөÑүүд. "off" гÑвÑл Ð¾Ð³Ñ Ð³Ð°ÑÐ³Ð°Ñ Ð³Ò¯Ð¹. 71 hello: (function (v) { return v === "off" ? [] : v.split("|"); })( 72 attr("hello", "Сайн байна ÑÑ.|Ðи СÑандаÑÑ Ñ Ñмжил зүйн гаÐ
72·ÑÑн Ñ Ð¸Ð¹Ð¼Ñл оÑÑÐ½Ñ " 73 + "ÑаÑÐ±Ð¾Ñ Ð±Ð°Ð¹Ð½Ð°.|Танд ÑÑÐ³Ð°Ð°Ñ ÑÑÑÐ»Ð°Ñ Ð²Ñ?")) 74 .map(function (s) { return s.trim(); }).filter(Boolean) 75 }; 76 77 // --- Өнгөний ÑÑÑÐ»Ð°Ñ ÑÑд: accent-Ð°Ð°Ñ Ð±Ò¯ÑÑн палиÑÑ ÑооÑоолно --- 78 function hexToRgb(hex) { 79 var h = String(hex).replace("#", "").trim(); 80 if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; 81 var n = parseInt(h, 16); 82 if (isNaN(n) || h.length !== 6) return { r: 27, g: 79, b: 156 }; 83 return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 }; 84 } 85 function clamp(v) { return Math.max(0, Math.min(255, Math.round(v))); } 86 function shade(hex, amount) { 87 // amount < 0 бол баÑаан, > 0 бол ÑÐ°Ð¹Ð²Ð°Ñ 88 var c = hexToRgb(hex); 89 var t = amount < 0 ? 0 : 255; 90 var p = Math.abs(amount); 91 return "#" + [c.r, c.g, c.b].map(function (v) { 92 return clamp((t - v) * p + v).toString(16).padStart(2, "0"); 93 }).join(""); 94 } 95 function rgba(hex, alpha) { 96 var c = hexToRgb(hex); 97 return "rgba(" + c.r + "," + c.g + "," + c.b + "," + alpha + ")"; 98 } 99 100 // localStorage â Ñ Ó©ÑÑÓ©Ó© Ñ Ð°Ð°Ð¶ нÑÑÑÑн Ñ ÑÑиа ÑÑÑгÑÐ½Ñ (ÑеÑÐ²ÐµÑ 30 Ñ Ð¾Ð½Ð¾Ð³ Ñ Ð°Ð´Ð³Ð°Ð»Ð½Ð°). 101 // Ó¨Ð¼Ð½Ó©Ñ Ñ ÑÐ²Ð¸Ð»Ð±Ð°Ñ sessionStorage аÑигладаг байÑан ÑÑл ÑÑндÑÑÑ Ð½Ñг Ñдаа ÑилжүүлнÑ. 102 var SESSION_KEY = "masm-chat-session"; 103 var sessionId = null; 104 function saveSession(id) { 105 try { 106 if (id) localStorage.setItem(SESSION_KEY, id); 107 else localStorage.removeItem(SESSION_KEY); 108 sessionStorage.removeItem(SESSION_KEY); 109 } catch (e) { /* Ñ Ð°Ð°Ð»ÑÑай */ } 110 } 111 try { 112 sessionId = localStorage.getItem(SESSION_KEY) || sessionStorage.getItem(SESSION_KEY); 113 if (sessionId) saveSession(sessionId); 114 } catch (e) { /* Ñ Ð°Ð°Ð»ÑÑай */ } 115 116 var STYLE = ` 117 :host { all: initial; } 118 *, *::before, *::after { box-sizing: border-box; } 119 120 .root { 121 /* --- өнгөний ÑокенÑÑд (JS-ÑÑÑ ÑÑга онооно) --- */ 122 --ink: #141821; 123 --ink-soft: #5b6472; 124 --ink-faint: #8d95a3; 125 --surface: #ffffff; 126 --canvas: #f7f8fb; 127 --line: #e7eaf0; 128 --line-soft: #f0f2f6; 129 --bot-bubble: #ffffff; 130 --shadow-sm: 0 1px 2px rgba(16,20,28,.06); 131 --shadow-md: 0 4px 14px rgba(16,20,28,.09), 0 1px 3px rgba(16,20,28,.06); 132 --shadow-lg: 0 18px 50px -12px rgba(16,20,28,.32), 0 6px 18px -8px rgba(16,20,28,.18); 133 /* accent-Ñн ÑнÑÐ¸Ð³Ð´Ð°Ñ Ñ ÑÐ²Ð¸Ð»Ð±Ð°Ñ â ÑÐ°Ð¹Ð²Ð°Ñ / Ñ Ð°Ñ Ð´ÑвÑгÑÑÑ ÑÑÑ ÑÑÑдаа (JS ÑооÑоолно). 134 Inline style @media-г даÑдаг ÑÑл JS ÑдгÑÑÑийг ÑÑÑд Ð±Ð¸Ñ --a-*-l/-d-ÑÑÑ Ð¾Ð½Ð¾Ð¾Ð½Ð¾. */ 135 --accent-text: var(--a-text-l); 136 --accent-ring: var(--a-ring-l); 137 --accent-tint: var(--a-tint-l); 138 --accent-dot: var(--a-dot-l); 139 140 position: fixed; bottom: 24px; z-index: 2147483000; 141 font-family: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif; 142 font-size: 15px; line-height: 1.6; 143 color: var(--ink); 144 -webkit-font-smoothing: antialiased; 145 } 146 .root.right { right: 24px; } 147 .root.left { left: 24px; } 148 149 /* Ð¥Ð°Ñ Ð³Ð¾Ñим â Ñ Ð°Ñ Ñ Ó©Ñ Ð±Ð¸Ñ, Ñөвийг ÑÐ°Ñ Ð¸Ñан Ñ Ð°Ñ ÑааÑал (R=G=B). */ 150 @media (prefers-color-scheme: dark) { 151 .root { 152 --ink: #ececec; 153 --ink-soft: #b4b4b4; 154 --ink-faint: #8a8a8a; 155 --surface: #242424; 156 --canvas: #2b2b2b; 157 --line: #3d3d3d; 158 --line-soft: #333333; 159 --bot-bubble: #3a3a3a; 160 --shadow-lg: 0 18px 50px -12px rgba(0,0,0,.6), 0 6px 18px -8px rgba(0,0,0,.4); 161 /* #001f60 Ñиг баÑаан accent Ñ Ð°Ñ ÑааÑал дÑÑÑ Ñ Ð°ÑÐ°Ð³Ð´Ð°Ñ Ð³Ò¯Ð¹ â ÑÐ°Ð¹Ð²Ð°Ñ Ñ ÑÐ²Ð¸Ð»Ð±Ð°Ñ */ 162 --accent-text: var(--a-text-d); 163 --accent-ring: var(--a-ring-d); 164 --accent-tint: var(--a-tint-d); 165 --accent-dot: var(--a-dot-d); 166 } 167 } 168 169 /* âââââââââââââ ÐÑÑÑ ÑÐ¾Ð²Ñ âââââââââââââ */ 170 .launcher { 171 position: relative; 172 width: 60px; height: 60px; border: 0; border-radius: 50%; 173 cursor: pointer; padding: 0; 174 background: linear-gradient(145deg, var(--accent-lift), var(--accent-deep)); 175 color: #fff; 176 box-shadow: 0 10px 26px -6px var(--accent-glow), 0 2px 6px rgba(16,20,28,.18); 177 display: flex; align-items: center; justify-content: center; 178 transition: transform .22s cubic-bezier(.34,1.3,.64,1), box-shadow .22s ease; 179 } 180 .launcher:hover { transform: translateY(-2px) scale(1.04); 181 box-shadow: 0 16px 34px -8px var(--accent-glow), 0 3px 8px rgba(16,20,28,.2); } 182 .launcher:active { transform: translateY(0) scale(.97); } 183 .launcher:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 3px; } 184 .launcher svg { width: 25px; height: 25px; fill: currentColor; } 185 186 /* Ñ ÑвилбаÑÑн ÑÑмдÑг â нÑÑÑ ÑовÑÐ½Ñ Ð±Ð°ÑÑÑн дÑÑд бÑланд */ 187 .launcher .ver { 188 position: absolute; top: -5px; right: -8px; 189 padding: 1px 6px; border-radius: 999px; 190 background: #f5c542; color: #001f60; border: 2px solid #fff; 191 font: 700 10px/1.35 "Segoe UI", system-ui, -apple-system, Arial, sans-serif; 192 letter-spacing: .2px; white-space: nowrap; pointer-events: none; 193 box-shadow: 0 2px 6px rgba(16,20,28,.28); 194 } 195 196 /* ââ AI ÑаÑÐ±Ð¾Ñ Ð´Ò¯ÑÑ (нÑÑÑ ÑовÑ) ââ */ 197 .launcher svg.bot { width: 34px; height: 34px; overflow: visible; } 198 .bot .face { fill: #fff; } 199 .bot .stem { stroke: #fff; stroke-width: 1.6; stroke-linecap: round; } 200 .bot .ant { fill: #fff; transform-box: fill-box; transform-origin: center; 201 animation: bot-ant 1.8s ease-in-out infinite; } 202 .bot .eyes rect { fill: var(--accent-deep); } 203 .bot .eyes { transform-box: fill-box; transform-origin: center; 204 animation: bot-blink 4.2s ease-in-out infinite; } 205 .bot .mouth { fill: none; stroke: var(--accent-deep); stroke-width: 1.5; stroke-linecap: round; } 206 .bot .spark { fill: #f5c542; transform-box: fill-box; transform-origin: center; 207 animation: bot-spark 2.6s ease-in-out infinite; } 208 .bot .float { animation: bot-float 3.2s ease-in-out infinite; } 209 .launcher:hover .bot .eyes { animation-duration: 1.2s; } /* Ñ Ñлгана оÑÐ¸Ñ Ð¾Ð´ "Ð°Ð½Ñ Ð°Ð°Ñна" */ 210 @keyframes bot-blink { 0%, 90%, 100% { transform: scaleY(1); } 94% { transform: scaleY(.12); } } 211 @keyframes bot-ant { 0%, 100% { opacity: 1; transform: scale(1); } 212 50% { opacity: .55; transform: scale(.72); } } 213 @keyframes bot-spark { 0%, 100% { opacity: 1; transform: scale(1) rotate(0deg); } 214 50% { opacity: .35; transform: scale(.55) rotate(45deg); } } 215 @keyframes bot-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-1.4px); } } 216 217 /* Ð°Ð½Ñ Ð°Ð°Ñал ÑаÑÐ°Ñ Ð·Ó©Ó©Ð»Ó©Ð½ ÑаÑÑаг â нÑг л Ñдаа */ 218 .launcher::after { 219 content: ""; position: absolute; inset: 0; border-radius: 50%; 220 border: 2px solid var(--accent-lift); opacity: 0; 221 animation: ping 2.6s cubic-bezier(0,0,.2,1) 1.2s 3; 222 } 223 @keyframes ping { 224 0% { transform: scale(1); opacity: .55; } 225 70% { transform: scale(1.55); opacity: 0; } 226 100% { transform: scale(1.55); opacity: 0; } 227 } 228 .launcher.seen::after { animation: none; } 229 230 /* нÑÑÑ ÑовÑÐ½Ñ Ñ Ð°Ð¶ÑÑд гаÑÐ°Ñ ÑÐ°Ð¹Ð»Ð±Ð°Ñ â Ð·Ó©Ð²Ñ Ó©Ð½ Ñ ÑлганаÑай ÑÓ©Ñ Ó©Ó©Ñөмж, ÑÑвÑл 231 гаÑÐ°Ð°Ñ (Tab) оÑÐ¸Ñ Ð¾Ð´. УÑÑан дÑÑÑ Ð´ÑлгÑÑÑÑÑ Ñ Ð°Ð»Ð¸Ð¶, ÑовÑийг Ñ Ð°Ð°Ñ ÑÑл гаÑÐ³Ð°Ñ Ð³Ò¯Ð¹. */ 232 .tip { 233 position: absolute; top: 50%; 234 padding: 7px 12px; border-radius: 8px; 235 background: var(--ink); color: var(--surface); 236 font-size: 13px; line-height: 1.35; font-weight: 500; white-space: nowrap; 237 box-shadow: var(--shadow-md); 238 pointer-events: none; opacity: 0; visibility: hidden; 239 transform: translateY(-50%) translateX(4px); 240 transition: opacity .16s ease, transform .16s ease, visibility 0s linear .16s; 241 } 242 .tip span { display: block; } 243 .tip span + span { font-size: 12px; font-weight: 400; opacity: .75; } 244 .root.right .tip { right: calc(100% + 12px); } 245 .root.left .tip { left: calc(100% + 12px); transform: translateY(-50%) translateX(-4px); } 246 .tip::after { 247 content: ""; position: absolute; top: 50%; width: 8px; height: 8px; 248 background: inherit; transform: translateY(-50%) rotate(45deg); 249 } 250 .root.right .tip::after { right: -4px; } 251 .root.left .tip::after { left: -4px; } 252 @media (hover: hover) { 253 .launcher:hover + .tip { opacity: 1; visibility: visible; 254 transform: translateY(-50%); transition-delay: .12s, .12s, 0s; } 255 } 256 .launcher:focus-visible + .tip { opacity: 1; visibility: visible; transform: translateY(-50%); } 257 .launcher[hidden] + .tip { display: none; } 258 259 /* âââââââââ ÐÑндÑилгÑÑний бөмбөлөг (ÑÐ°Ñ Ñ Ð°Ð°Ð»ÑÑай үед ÑовÑÐ½Ñ Ð´ÑÑÑ) âââââââââ */ 260 .hello { 261 position: absolute; bottom: calc(100% + 14px); 262 width: 268px; max-width: calc(100vw - 40px); 263 padding: 13px 34px 14px 15px; 264 background: var(--surface); color: var(--ink); 265 border: 1px solid var(--line); border-radius: 14px; 266 box-shadow: var(--shadow-lg); 267 font-size: 13.5px; line-height: 1.55; text-align: left; 268 cursor: pointer; 269 opacity: 0; visibility: hidden; transform: translateY(6px); 270 transition: opacity .28s ease, transform .28s ease, visibility 0s linear .28s; 271 } 272 .root.right .hello { right: 0; } 273 .root.left .hello { left: 0; } 274 .hello.on { opacity: 1; visibility: visible; transform: translateY(0); transition-delay: 0s; } 275 .hello::after { 276 content: ""; position: absolute; bottom: -6px; width: 11px; height: 11px; 277 background: var(--surface); border-right: 1px solid var(--line); 278 border-bottom: 1px solid var(--line); transform: rotate(45deg); 279 } 280 .root.right .hello::after { right: 22px; } 281 .root.left .hello::after { left: 22px; } 282 .hello .t { font-weight: 650; margin-bottom: 6px; } 283 .hello .t i { font-style: normal; margin-right: 6px; } 284 .hello p { margin: 0 0 7px; } 285 .hello p:last-child { margin-bottom: 0; color: var(--ink-soft); } 286 .hello .x { 287 position: absolute; top: 6px; right: 6px; 288 width: 26px;
288 height: 26px; padding: 0; border: 0; border-radius: 7px; 289 background: none; color: var(--ink-faint); cursor: pointer; 290 font: 400 17px/1 "Segoe UI", system-ui, sans-serif; 291 } 292 .hello .x:hover { background: var(--line-soft); color: var(--ink); } 293 .hello .x:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; } 294 /* Ðөмбөлөг гаÑÑан үед hover-ийн tooltip Ð´Ð°Ð²Ñ Ð°ÑÐ»Ð°Ñ Ð³Ò¯Ð¹ (.tip Ð½Ñ DOM-д өмнө 295 байдаг ÑÑл дүү ÑÐ¾Ð½Ð³Ð¾Ð³Ñ Ð°Ð¶Ð¸Ð»Ð»Ð°Ñ Ð³Ò¯Ð¹ â root дÑÑÑÑ Ð°Ð½Ð³Ð¸Ð°Ñ ÑдиÑдана) */ 296 .root.greeting .tip { display: none; } 297 298 /* âââââââââââââ Ð¦Ð¾Ð½Ñ âââââââââââââ */ 299 .panel { 300 width: 396px; height: 600px; max-height: calc(100vh - 56px); 301 background: var(--surface); border-radius: 18px; overflow: hidden; 302 box-shadow: var(--shadow-lg); 303 border: 1px solid var(--line); 304 display: flex; flex-direction: column; 305 transform-origin: bottom right; 306 animation: rise .24s cubic-bezier(.22,1,.36,1); 307 } 308 .root.left .panel { transform-origin: bottom left; } 309 @keyframes rise { 310 from { opacity: 0; transform: translateY(14px) scale(.965); } 311 to { opacity: 1; transform: translateY(0) scale(1); } 312 } 313 314 /* âââââââââââââ Толгой âââââââââââââ */ 315 .head { 316 flex: none; position: relative; 317 padding: 15px 16px; 318 background: linear-gradient(135deg, var(--accent-lift) 0%, var(--accent-deep) 100%); 319 color: #fff; 320 display: flex; align-items: center; gap: 11px; 321 } 322 .head::after { 323 content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; 324 background: rgba(255,255,255,.14); 325 } 326 .avatar { 327 width: 36px; height: 36px; flex: none; border-radius: 11px; 328 background: rgba(255,255,255,.16); 329 border: 1px solid rgba(255,255,255,.22); 330 display: flex; align-items: center; justify-content: center; 331 } 332 .avatar svg { width: 19px; height: 19px; fill: #fff; } 333 /* ÐайгÑÑллагÑн лого â алÑлаг ÑÑмдÑг Ñ Ð°Ñ Ñ Ó©Ñ Ð´ÑÑÑ Ð±Ò¯Ð´Ñг ÑÑл Ñагаан дÑгÑйд */ 334 .avatar.logo { border-radius: 50%; background: #fff; border: 0; 335 box-shadow: 0 0 0 2px rgba(255,255,255,.28), 0 2px 6px rgba(0,0,0,.25); } 336 .avatar.logo img { width: 30px; height: 30px; display: block; } .head .meta { min-width: 0; flex: 1; } 337 .head .t { 338 font-weight: 600; font-size: 15px; letter-spacing: -.01em; 339 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; 340 } 341 .head .sub { 342 font-size: 11.5px; opacity: .82;
342 margin-top: 1px; 343 display: flex; align-items: center; gap: 5px; 344 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; 345 } 346 .dot { 347 width: 6px; height: 6px; flex: none; border-radius: 50%; 348 background: #46d38a; box-shadow: 0 0 0 2px rgba(70,211,138,.28); 349 } 350 .head .acts { display: flex; gap: 2px; flex: none; } 351 .head button { 352 background: transparent; border: 0; color: #fff; cursor: pointer; 353 width: 32px; height: 32px; border-radius: 9px; opacity: .82; 354 display: flex; align-items: center; justify-content: center; 355 transition: background .15s ease, opacity .15s ease; 356 } 357 .head button svg { width: 16px; height: 16px; fill: currentColor; } 358 .head button:hover { background: rgba(255,255,255,.17); opacity: 1; } 359 .head button:focus-visible { outline: 2px solid #fff; outline-offset: -2px; } 360 361 /* âââââââââââââ ЯÑиа âââââââââââââ */ 362 /* âââââââââââââ Таб (ÐÑÑÑÐ»Ñ Ñ Ð°ÑиÑÐ»Ñ / СÑандаÑÑ Ñ Ð°Ð¹Ñ ) âââââââââââââ */ 363 .tabs { 364 display: flex; background: var(--surface); 365 border-bottom: 1px solid var(--line); padding: 0 8px; 366 } 367 .tab { 368 flex: 1; border: 0; background: none; cursor: pointer; 369 padding: 11px 8px 9px; font: inherit; font-size: 13.5px; font-weight: 600; 370 color: var(--ink-soft); border-bottom: 2px solid transparent; margin-bottom: -1px; 371 } 372 .tab:hover { color: var(--ink); } 373 .tab.on { color: var(--accent-text); border-bottom-color: var(--accent-text); } 374 .tab:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: -3px; } 375 376 /* âââââââââââââ СÑандаÑÑ Ñ Ð°Ð¹Ñ âââââââââââââ */ 377 .std { 378 flex: 1; display: flex; flex-direction: column; min-height: 0; 379 background: var(--canvas); 380 } 381 .std-box { padding: 12px 14px 8px; background: var(--surface); 382 border-bottom: 1px solid var(--line-soft); } 383 .std-q { 384 width: 100%; box-sizing: border-box; font: inherit; font-size: 14px; 385 padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; 386 background: var(--canvas); color: var(--ink); 387 } 388 .std-q:focus { outline: 2px solid var(--accent-ring); border-color: var(--accent-text); } 389 .std-info { padding: 9px 16px 0; font-size: 12.5px; color: var(--ink-soft); } 390 .std-list { 391 flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 10px 14px 14px; 392 scrollbar-width: thin; scrollbar-color: var(--line) transparent; 393 } 394 .std-list::-webkit-scrollbar { width: 8px; } 395 .std-list::-webkit-scrollbar-thumb { 396 background: var(--line); border-radius: 99px; border: 2px solid var(--canvas); 397 } 398 .std-item { 399 background: var(--surface); border: 1px solid var(--line); border-radius: 12px; 400 padding: 11px 13px; margin-bottom: 9px; box-shadow: var(--shadow-sm); 401 } 402 .std-item .num { 403 display: inline-block; font-size: 12px; font-weight: 700; 404 color: var(--accent-text); background: var(--accent-tint); 405 border-radius: 999px; padding: 2px 9px; margin-bottom: 5px; 406 } 407 .std-item .ttl { font-size: 13.5px; line-height: 1.5; margin-bottom: 5px; } 408 .std-item .meta2 { font-size: 12px; color: var(--ink-soft); line-height: 1.45; } 409 .std-item .free { color: #1f7a42; font-weight: 600; } 410 .std-item .go { 411 display: inline-flex; align-items: center; gap: 6px; margin-top: 9px; 412 padding: 6px 12px; border: 1px solid var(--accent-ring); border-radius: 999px; 413 background: var(--accent-tint); color: var(--accent-text); 414 font-size: 12.5px; font-weight: 600; text-decoration: none; 415 } 416 .std-item .go:hover { filter: brightness(.97); } 417 .std-item .go svg { width: 12px; height: 12px; fill: currentColor; } 418 419 .log { 420 flex: 1; overflow-y: auto; overscroll-behavior: contain; 421 padding: 18px 16px 8px; background: var(--canvas); 422 scrollbar-width: thin; scrollbar-color: var(--line) transparent; 423 } 424 .log::-webkit-scrollbar { width: 8px; } 425 .log::-webkit-scrollbar-thumb { 426 background: var(--line); border-radius: 99px; 427 border: 2px solid var(--canvas); 428 } 429 .log::-webkit-scrollbar-track { background: transparent; } 430 431 .msg { margin-bottom: 14px; display: flex; animation: pop .26s cubic-bezier(.22,1,.36,1); } 432 .msg.user { justify-content: flex-end; } 433 @keyframes pop { 434 from { opacity: 0; transform: translateY(7px); } 435 to { opacity: 1; transform: translateY(0); } 436 } 437 438 .bubble { 439 max-width: 86%; padding: 11px 14px; 440 white-space: pre-wrap;
440 overflow-wrap: anywhere; 441 font-size: 14.5px; line-height: 1.62; 442 } 443 .msg.bot .bubble { 444 background: var(--bot-bubble); color: var(--ink); 445 border: 1px solid var(--line); 446 border-radius: 16px 16px 16px 5px; 447 box-shadow: var(--shadow-sm); 448 } 449 .msg.user .bubble { 450 background: linear-gradient(145deg, var(--accent-lift), var(--accent-deep)); 451 color: #fff; border-radius: 16px 16px 5px 16px; 452 box-shadow: 0 3px 10px -3px var(--accent-glow); 453 } 454 .msg.err .bubble { 455 background: var(--err-bg); border: 1px solid var(--err-line); 456 color: var(--err-ink); border-radius: 16px 16px 16px 5px; 457 } 458 .bubble strong, .bubble b { font-weight: 650; } 459 /* ХаÑиÑÐ»Ñ Ð´Ð¾ÑÐ¾Ñ Ð³Ð°ÑÑан Ñ Ð¾Ð»Ð±Ð¾Ð¾ÑÑг "ÐÑлгÑÑÑнгүй" ÑÐ¾Ð²Ñ Ð±Ð¾Ð»Ð³Ð¾Ð¶ Ñ Ð°ÑÑÑлна */ 460 .bubble a.link-btn { 461 display: inline-flex; align-items: center; gap: 6px; 462 margin: 4px 0 2px; padding: 5px 11px; 463 border: 1px solid var(--accent-ring); border-radius: 999px; 464 background: var(--accent-tint); color: var(--accent-text); 465 font-size: 13px; font-weight: 600; line-height: 1.2; 466 text-decoration: none; white-space: nowrap; 467 } 468 .bubble a.link-btn:hover { background: var(--accent-soft, var(--accent-tint)); filter: brightness(0.97); } 469 .bubble a.link-btn:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; } 470 .bubble a.link-btn .ico { display: inline-flex; } 471 .bubble a.link-btn .ico svg { width: 13px; height: 13px; } 472 473 /* âââââââââââââ ÐÑ ÑÑÑвалж âââââââââââââ */ 474 /* ХаÑиÑлÑÑг дÑÑÐ³Ð°Ð°Ñ ÑнÑÑÑÐ»Ð°Ñ ÑÐ¾Ð²Ñ â Ñ Ó©Ó©Ñний баÑÑÑн дÑÑд бÑланд, Ð·Ó©Ð²Ñ Ó©Ð½ дүÑÑ */ 475 .bubble.has-speak { position: relative; padding-right: 40px; } 476 .speak { 477 position: absolute; top: 7px; right: 7px; 478 display: inline-flex; align-items: center; justify-content: center; 479 width: 26px; height: 26px; padding: 0; border-radius: 999px; cursor: pointer; 480 border: 1px solid transparent; background: transparent; color: var(--ink-soft); 481 transition: color .15s ease, border-color .15s ease, background .15s ease; 482 } 483 .speak svg { width: 15px; height: 15px; fill: currentColor; flex: none; } 484 .speak:hover { color: var(--accent-text); border-color: var(--accent-ring); } 485 .speak.on { color: var(--accent-text); border-color: var(--accent-ring); background: var(--accent-tint); } 486 .speak.on svg { animation: speak-pulse 1s ease-in-out infinite; } 487 @keyframes speak-pulse { 50% { opacity: .45; } } 488 .speak:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; } 489 490 .srcs { margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--line-soft); } 491 .srcs .lbl { 492 font-size: 10.5px; font-weight: 600; letter-spacing: .07em; 493 text-transform: uppercase; color: var(--ink-faint); margin-bottom: 7px; 494 } 495 .srcs a { 496 display: flex; align-items: flex-start; gap: 8px; 497 padding: 7px 9px; margin-bottom: 4px; border-radius: 9px; 498 background: var(--canvas); border: 1px solid var(--line-soft); 499 color: var(--ink-soft); text-decoration: none; 500 font-size: 12.5px; line-height: 1.45; 501 transition: border-color .15s ease, background .15s ease, transform .15s ease; 502 } 503 .srcs a:hover { 504 border-color: var(--accent-ring); background: var(--accent-tint); 505 color: var(--accent-text); transform: translateX(2px); 506 } 507 .srcs a:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; } 508 .srcs .ico { flex: none; display: flex; margin-top: 2px; opacity: .65; } 509 .srcs .ico svg { width: 13px; height: 13px; fill: currentColor; } 510 .srcs .nm { min-width: 0; } 511 512 /* âââââââââââââ Цаг үеийн мÑдÑÑ (2 баганаÑай зÑÑагÑай каÑÑ) âââââââââââââ */ 513 .bubble.has-news { width: 86%; } 514 .news { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 10px; white-space: normal; } 515 .news a { 516 display: flex; flex-direction: column; min-width: 0; overflow: hidden; 517 border: 1px solid var(--line-soft); border-radius: 10px; background: var(--canvas); 518 color: var(--ink); text-decoration: none; 519 transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease; 520 } 521 .news a:hover { border-color: var(--accent-ring); transform: translateY(-1px); box-shadow: var(--shadow-sm); } 522 .news a:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; } 523 .news img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--line-soft); } 524 .news .nw-b { padding: 6px 8px 8px; } 525 .news .nw-d { font-size: 11px; color: var(--ink-faint); } 526 .news .nw-t { 527 font-size: 12.5px; line-height: 1.35; font-weight: 600; margin-top: 2px; 528 display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; 529 } 530 531 /* âââââââââââââ Санал Ð±Ð¾Ð»Ð³Ð¾Ñ Ð°ÑÑÑлÑÑÑд âââââââââââââ */ 532 .chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 2px 0 16px 0; } 533 .chips button { 534 font: inherit; font-size: 13px; line-height: 1.4; 535 padding: 8px 13px; border-radius: 99px; cursor: pointer; 536 background: var(--surface); color: var(--accent-text); 537 border: 1px solid var(--accent-ring); 538 transition: background .15s ease, transform .15s ease, box-shadow .15s ease; 539 } 540 .chips button:hover { 541 background: var(--accent-tint); transform: translateY(-1px); 542 box-shadow: var(--shadow-sm); 543 } 544 .chips button:active { transform: translateY(0); } 545 .chips button:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; } 546 547 /* âââââââââââââ ÐÑаалж бÑй байдал âââââââââââââ */ 548 .prog { display: flex; align-items: center; gap: 10px; color: var(--ink-soft); font-size: 13.5px; } 549 .prog .txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } 550 .typing { display: flex; gap: 4px; align-items: center; flex: none; } 551 .typing i { 552 width: 6px; height: 6px; border-radius: 50%; background: var(--accent-dot); 553 display: block; animation: bounce 1.25s ease-in-out infinite; 554 } 555 .typing i:nth-child(2) { animation-delay: .16s; } 556 .typing i:nth-child(3) { animation-delay: .32s; } 557 @keyframes bounce { 558 0%, 60%, 100% { transform: translateY(0); opacity: .4; } 559 30% { transform: translateY(-5px); opacity: 1; } 560 } 561 .caret { 562 display: inline-block; width: 2px; height: 1.05em; margin-left: 2px; 563 background: var(--accent-dot); border-radius: 1px; 564 vertical-align: text-bottom; animation: blink 1.1s steps(2, start) infinite; 565 } 566 @keyframes blink { 50% { opacity: 0; } } 567 568 /* âââââââââââââ ÐÑÑÑÐ»Ð°Ñ Ñ ÑÑÑг âââââââââââââ */ 569 .foot { 570 flex: none; border-top: 1px solid var(--line); 571 padding: 11px 12px 12px; background: var(--surface); 572 } 573 .foot form { 574 display: flex; align-items: flex-end; gap: 8px; 575 background: var(--canvas); border: 1px solid var(--line); 576 border-radius: 14px; padding: 5px 5px 5px 6px; 577 transition: border-color .15s ease, box-shadow .15s ease; 578 } 579 .foot form:focus-within { 580 border-color: var(--accent-ring); 581 box-shadow: 0 0 0 3px var(--accent-tint); 582 } 583 .foot textarea { 584 flex: 1; min-width: 0; resize: none; border: 0; outline: 0; 585 background: transparent; color: var(--ink); 586 font: inherit; font-size: 14.5px; line-height: 1.5; 587 padding: 7px 4px 7px 6px; max-height: 108px; 588 } 589 .foot textarea::placeholder { color: var(--ink-faint); } 590 .send { 591 flex: none; width: 36px; height: 36px; border: 0; border-radius: 10px; 592 cursor: pointer; color: #fff; 593 background: linear-gradient(145deg, var(--accent-lift), var(--accent-deep)); 594 display: flex; align-items: center; justify-content: center; 595 transition: transform .15s ease, opacity .15s ease; 596 } 597 .send svg { width: 16px; height: 16px; fill: currentColor; } 598 .send:hover:not(:disabled) { transform: scale(1.06); } 599 .send:active:not(:disabled) { transform: scale(.94); } 600 .send:disabled { opacity: .38; cursor: default; } 601 .send:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; } 602 603 .note { 604 margin-top: 8px; font-size: 11px; color: var(--ink-faint); 605 text-align: center; line-height: 1.45; 606 } 607 .note a { color: var(--ink-soft); font-weight: 600; text-decoration: none; } 608 .note a:hover { color: var(--accent-text); text-decoration: underline; } 609 .note a:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; border-radius: 3px; } 610 611 [hidden] { display: none !important; } 612 613 /* âââââââââââââ УÑÐ°Ñ âââââââââââââ */ 614 /* âââââââââ ÐÐ°Ñ ÑÑаÑ: нÑÑÑ Ñд бүÑÑн дÑлгÑÑ, Ð·Ó©Ð²Ñ Ó©Ð½ ÑаÑÐ±Ð¾Ñ âââââââââ */ 615 @media (max-width: 540px) { 616 .root { bottom: 14px; } 617 .root.right { right: 14px; }
618 .root.left { left: 14px; } 619 .panel { 620 position: fixed; left: 0; right: 0; top: var(--vvt, 0px); 621 width: 100%; 622 height: 100vh; /* Ñ ÑÑÑин Ñ Ó©ÑÓ©Ñ */ 623 height: var(--vvh, 100dvh); /* Ð³Ð°Ñ Ð³Ð°Ñвал JS багаÑгана (fitViewport) */ 624 max-height: none; 625 border: 0; border-radius: 0; box-shadow: none; 626 animation: sheet .26s cubic-bezier(.22,1,.36,1); 627 } 628 /* УÑаÑÐ½Ñ Ð´ÑÑд/доод Ñ Ò¯ÑÑÑ (notch, home indicator) */ 629 .head { padding-top: calc(15px + env(safe-area-inset-top, 0px)); } 630 .foot { padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); } 631 /* 16px-ÑÑÑ Ð¶Ð¸Ð¶Ð¸Ð³ бол iPhone ÑокÑÑÐ»Ð°Ñ Ð°Ð´ дÑлгÑÑийг ÑомÑÑÑлдаг */ 632 .foot textarea, .std-q { font-size: 16px; } 633 } 634 @keyframes sheet { 635 from { opacity: .6; transform: translateY(24px); } 636 to { opacity: 1; transform: translateY(0); } 637 } 638 639 @media (prefers-reduced-motion: reduce) { 640 .launcher, .launcher svg, .panel, .msg, .typing i, .caret, 641 .srcs a, .chips button, .send, .tip, .hello, 642 .bot .float, .bot .eyes, .bot .ant, .bot .spark, .speak.on svg { animation: none !important; transition: none !important; } 643 .launcher::after { display: none; } 644 }`; 645 646 var ICON = { 647 // AI ÑаÑбоÑ: ÑаÑÑн бөмбөлөг Ñ ÑлбÑÑийн ÑÐ¾Ð±Ð¾Ñ Ñолгой â анивÑдаг нүд, 648 // лÑгÑдаг анÑен, гÑлалздаг алÑан Ð¾Ñ (MASM-Ñн алÑ). Хөдөлгөөн Ð½Ñ CSS-д (.bot). 649 chat: '<svg class="bot" viewBox="0 0 32 32" aria-hidden="true">' + 650 '<g class="float">' + 651 '<line class="stem" x1="14" y1="5" x2="14" y2="8.5"/>' + 652 '<circle class="ant" cx="14" cy="3.8" r="1.8"/>' + 653 '<path class="face" d="M7 8.5h14a5 5 0 0 1 5 5v6.5a5 5 0 0 1-5 5h-6.5L9.5 29v-4H7a5 5 0 0 1-5-5v-6.5a5 5 0 0 1 5-5z"/>' + 654 '<g class="eyes"><rect x="8.4" y="13.6" width="3.2" height="4.4" rx="1.6"/>' + 655 '<rect x="16.4" y="13.6" width="3.2" height="4.4" rx="1.6"/></g>' + 656 '<path class="mouth" d="M11.3 20.6q2.7 1.9 5.4 0"/>' + 657 '</g>' + 658 '<path class="spark" d="M27 1.5l.95 2.3 2.3.95-2.3.95L27 8l-.95-2.3-2.3-.95 2.3-.95z"/>' + 659 '</svg>', 660 close: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18.3 5.71 12 12l6.3 6.29-1.42 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.29l6.29 6.3 6.29-6.3z"/></svg>', 661 shield: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2 4 5v6.1c0 4.7 3.4 9.1 8 10.9 4.6-1.8 8-6.2 8-10.9V5l-8-3zm-1.1 13.4-3.2-3.2 1.4-1.4 1.8 1.8 4.6-4.6 1.4 1.4-6 6z"/></svg>', 662 refresh: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>', 663 soundOn: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3A4.5 4.5 0 0 0 14 7.97v8.05A4.47 4.47 0 0 0 16.5 12zM14 3.23v2.06a7 7 0 0 1 0 13.42v2.06A9 9 0 0 0 14 3.23z"/></svg>', 664 stop: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>', 665 soundOff: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M16.5 12A4.5 4.5 0 0 0 14 7.97v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51A8.8 8.8 0 0 0 21 12a9 9 0 0 0-7-8.77v2.06A7 7 0 0 1 19 12zM4.27 3 3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06a9 9 0 0 0 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4 9.91 6.09 12 8.18V4z"/></svg>', 666 send: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3.4 20.4 21 12 3.4 3.6 3.4 10l12.6 2-12.6 2z"/></svg>', 667 link: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7zM5 5h5V3H3v18h18v-7h-2v5H5V5z"/></svg>' 668 }; 669 670 // --- DOM баÑÐ¸Ñ --- 671 var host = document.createElement("div"); 672 document.body.appendChild(host); 673 var sr = host.attachShadow({ mode: "open" }); 674 675 var style = document.createElement("style"); 676 style.textContent = STYLE; 677 sr.appendChild(style); 678 679 var root = document.createElement("div"); 680 root.className = "root " + (CFG.position === "left" ? "left" : "right"); 681 682 // accent-Ð°Ð°Ñ Ð±Ò¯ÑÑн палиÑÑ 683 root.style.setProperty("--accent", CFG.accent); 684 root.style.setProperty("--accent-lift", shade(CFG.accent, 0.12)); 685 root.style.setProperty("--accent-deep", shade(CFG.accent, -0.22)); 686 // Ð¦Ð°Ð¹Ð²Ð°Ñ (-l) ба Ñ Ð°Ñ (-d) гоÑимÑн Ñ ÑÐ²Ð¸Ð»Ð±Ð°Ñ â Ð°Ð»Ñ Ð½Ñ Ð¸Ð´ÑÐ²Ñ ÑÑйг CSS Ñонгоно. 687 var onDark = shade(CFG.accent, 0.62); // Ñ Ð°Ñ ÑааÑал дÑвÑгÑÑ Ð´ÑÑÑ ÑнÑÐ¸Ð³Ð´Ð°Ñ ÑÐ°Ð¹Ð²Ð°Ñ 688 root.style.setProperty("--a-text-l", shade(CFG.accent, -0.05)); 689 root.style.setProperty("--a-text-d", onDark); 690 root.style.setProperty("--a-ring-l", rgba(CFG.accent, 0.32)); 691 root.style.setProperty("--a-ring-d", rgba(onDark, 0.45)); 692 root.style.setProperty("--a-tint-l", rgba(CFG.accent, 0.07)); 693 root.style.setProperty("--a-tint-d", rgba(onDark, 0.10)); 694 root.style.setProperty("--a-dot-l", shade(CFG.accent, 0.12)); 695 root.style.setProperty("--a-dot-d", onDark); 696 root.style.setProperty("--accent-glow", rgba(CFG.accent, 0.45)); 697 root.style.setProperty("--err-bg", "rgba(203,49,49,.07)"); 698 root.style.setProperty("--err-line", "rgba(203,49,49,.28)"); 699 root.style.setProperty("--err-ink", "#b02727"); 700 701 root.innerHTML = ` 702 <button class="launcher" aria-label="${esc(CFG.title)} â ÑÐ°Ñ Ð½ÑÑÑ " 703 aria-expanded="false" aria-controls="masm-chat-panel">${ICON.chat}<span 704 class="ver" aria-hidden="true">v${esc(VERSION)}</span></button> 705 <span class="tip" aria-hidden="true">${CFG.tooltip.map(function (line) { 706 return "<span>" + esc(line) + "</span>"; 707 }).join("")}</span> 708 ${CFG.hello.length ? `<div class="hello" role="note"> 709 <button class="x" type="button" aria-label="ÐÑндÑилгÑÑг Ñ Ð°Ð°Ñ ">×</button> 710 <div class="t"><i aria-hidden="true">👋</i>${esc(CFG.hello[0])}</div> 711 ${CFG.hello.slice(1).map(function (line) { 712 return "<p>" + esc(line) + "</p>"; 713 }).join("")} 714 </div>` : ""} 715 <div class="panel" id="masm-chat-panel" role="dialog" 716 aria-label="${esc(CFG.title)}" hidden> 717 <div class="head"> 718 <div class="avatar${CFG.logo ? " logo" : ""}">${CFG.logo 719 ? '<img src="' + esc(CFG.logo) + '" alt="" width="30" height="30">' 720 : ICON.shield}</div> 721 <div class="meta"> 722 <div class="t">${esc(CFG.title)}</div> 723 <div class="sub"><span class="dot"></span>${esc(CFG.subtitle)}</div> 724 </div> 725 <div class="acts"> 726 <button class="sound" aria-pressed="true"></button> 727 <button class="reset" title="Ð¨Ð¸Ð½Ñ ÑÑиа ÑÑ Ð»Ò¯Ò¯Ð»ÑÑ " 728 aria-label="Ð¨Ð¸Ð½Ñ ÑÑиа ÑÑ Ð»Ò¯Ò¯Ð»ÑÑ ">${ICON.refresh}</button> 729 <button class="close" title="Ð¥Ð°Ð°Ñ " aria-label="Ð¥Ð°Ð°Ñ ">${ICON.close}</button> 730 </div> 731 </div> 732 <div class="tabs" role="tablist"> 733 <button class="tab on" type="button" role="tab" data-view="chat" 734 aria-selected="true">ÐÑÑÑÐ»Ñ Ñ Ð°ÑиÑлÑ</button> 735 <button class="tab" type="button" role="tab" data-view="std" 736 aria-selected="false">СÑандаÑÑ Ñ Ð°Ð¹Ñ </button> 737 </div> 738 <div class="log" role="log" aria-live="polite" aria-atomic="false"></div> 739 <div class="std" role="tabpanel" hidden> 740 <div class="std-box"> 741 <input type="search" class="std-q" autocomplete="off" 742 placeholder="СÑандаÑÑÑн дÑÐ³Ð°Ð°Ñ ÑÑвÑл нÑÑâ¦" 743 aria-label="СÑандаÑÑ Ñ Ð°Ð¹Ñ "> 744 </div> 745 <div class="std-info"></div> 746 <div class="std-list"></div> 747 </div> 748 <div class="foot"> 749 <form> 750 <textarea rows="1" placeholder="ÐÑÑÑлÑаа биÑÐ½Ñ Ò¯Ò¯â¦" 751 aria-label="ÐÑÑÑлÑ" autocomplete="off"></textarea> 752 <button type="submit" class="send" aria-label="ÐлгÑÑÑ ">${ICON.send}</button> 753 </form> 754 <div class="note">ХөгжүүлÑгÑ: <a href="https://www.facebook.com/tselmegsoft" 755 target="_blank" rel="noopener noreferrer">ЦÑлмÑг СоÑÑ Ð¥Ð¥Ð</a></div> 756 </div> 757 </div>`; 758 sr.appendChild(root); 759 760 // Ðого аÑÐ°Ð°Ð»Ð°Ð³Ð´Ð°Ñ Ð³Ò¯Ð¹ бол Ñолгойд бамбайн дүÑÑ Ñ Ð°ÑÑÑлна. 761 sr.querySelectorAll(".avatar.logo img").forEach(function (img) { 762 img.addEventListener("error", function () { 763 var box = img.parentNode; 764 box.classList.remove("logo"); 765 box.innerHTML = ICON.shield; 766 }); 767 }); 768 769 var elLauncher = sr.querySelector(".launcher"); 770 var elHello = sr.querySelector(".hello"); 771 var elPanel = sr.querySelector(".panel"); 772 var elLog = sr.querySelector(".log"); 773 var elForm = sr.querySelector("form"); 774 var elInput = sr.querySelector("textarea"); 775 var elSend = sr.querySelector(".send"); 776 var elClose = sr.querySelector(".close"); 777 var elReset = sr.querySelector(".reset"); 778 var elSound = sr.querySelector(".sound"); 779 var elFoot = sr.querySelector(".foot"); 780 var elStd = sr.querySelector(".std"); 781 var elStdQ = sr.querySelector(".std-q"); 782 var elStdList = sr.querySelector(".std-list"); 783 var elStdInfo = sr.querySelector(".std-info"); 784 785 // --- ÐлгÑÑÑ Ð´ÑÑ: Web Audio-Ð¾Ð¾Ñ Ð½Ð¸Ð¹Ð»ÑÐ³Ð¶Ò¯Ò¯Ð»Ð½Ñ (аÑдио Ñайл ÑаÑÐ°Ñ Ð³Ò¯Ð¹) --- 786 // Ð¥ÑÑÑглÑÐ³Ñ ÑÐ¾Ð²Ñ Ð´Ð°ÑÑан үед л ÑÐ¾Ð³Ð»Ð¾Ð³Ð´Ð¾Ñ ÑÑл Ñ Ó©ÑÑийн autoplay Ñ ÑзгааÑÑ Ó©ÑÑÓ©Ñ Ð³Ò¯Ð¹.
787 // УнÑÑааÑан ÑÑÑÑ Ð¸Ð¹Ð³ Ñ Ó©ÑÓ©Ñ Ñанана; data-sound="off" бол Ð°Ð½Ñ Ð½Ð°Ð°Ñаа ÑнÑÑаалÑÑай. 788 var SOUND_KEY = "masm-chat-sound"; 789 var soundOn = attr("sound", "on") !== "off"; 790 try { 791 var saved = localStorage.getItem(SOUND_KEY); 792 if (saved !== null) soundOn = saved === "on"; 793 } catch (e) { /* Ñ Ð°Ð°Ð»ÑÑай */ } 794 var audioCtx = null; 795 796 function renderSound() { 797 elSound.innerHTML = soundOn ? ICON.soundOn : ICON.soundOff; 798 elSound.setAttribute("aria-pressed", String(soundOn)); 799 var label = soundOn ? "ÐÑÑ ÑнÑÑÐ°Ð°Ñ " : "ÐÑÑ Ð°ÑÐ°Ð°Ñ "; 800 elSound.title = label; 801 elSound.setAttribute("aria-label", label); 802 } 803 renderSound(); 804 805 // notes: [ÑÑ Ð»ÑÑ Ð´Ð°Ð²Ñамж, ÑөгÑÓ©Ñ Ð´Ð°Ð²Ñамж, Ñ Ð¾ÑÑÐ¾Ñ Ñек, Ò¯ÑгÑлжлÑÑ Ñек, Ñанга, долгион] 806 function playNotes(notes) { 807 if (!soundOn) return; 808 try { 809 var Ctx = window.AudioContext || window.webkitAudioContext; 810 if (!Ctx) return; 811 audioCtx = audioCtx || new Ctx(); 812 if (audioCtx.state === "suspended") audioCtx.resume(); 813 var t = audioCtx.currentTime; 814 notes.forEach(function (n) { 815 var start = t + n[2], dur = n[3]; 816 var osc = audioCtx.createOscillator(), gain = audioCtx.createGain(); 817 osc.type = n[5] || "sine"; 818 osc.frequency.setValueAtTime(n[0], start); 819 if (n[1] !== n[0]) osc.frequency.exponentialRampToValueAtTime(n[1], start + dur * 0.6); 820 // Ð¥Ð¾Ð½Ñ Ñиг: Ñ ÑÑдан дÑÑгаÑÑ, Ñдаан намжина 821 gain.gain.setValueAtTime(0.0001, start); 822 gain.gain.exponentialRampToValueAtTime(n[4], start + 0.012); 823 gain.gain.exponentialRampToValueAtTime(0.0001, start + dur); 824 osc.connect(gain).connect(audioCtx.destination); 825 osc.start(start); 826 osc.stop(start + dur + 0.02); 827 }); 828 } catch (e) { /* дÑÑ Ð·Ð°Ð°Ð²Ð°Ð» Ð±Ð¸Ñ */ } 829 } 830 831 // ÐлгÑÑÑ : Ñ Ð¾ÑÑ Ó©Ð½Ð³Ó©ÑÑй зөөлөн "блип" â дÑÑÑÑÑ Ð³ÑлÑÐ°Ñ Ð½Ñ "илгÑÑгдлÑÑ" гÑÑÑн мÑдÑÑмж. 832 function playSend() { 833 playNotes([[587, 880, 0, 0.14, 0.09], [880, 1175, 0.07, 0.14, 0.09]]); 834 } 835 836 // ÐÑÑÑ : CâEâGâC дÑÑÑÑÑ Ð³Ð°ÑÐ°Ñ Ñ Ð¾Ð½Ñ Ð½Ñ Ð°ÐºÐºÐ¾Ñд + нÑг окÑав дÑÑÑÑ Ð³Ñлбаа â "ÑавÑай моÑил". 837 function playOpen() { 838 playNotes([ 839 [523.25, 523.25, 0.00, 0.55, 0.075], 840 [659.25, 659.25, 0.07, 0.55, 0.070], 841 [783.99, 783.99, 0.14, 0.60, 0.065], 842 [1046.5, 1046.5, 0.21, 0.80, 0.060], 843 [2093.0, 2093.0, 0.21, 0.45, 0.012, "triangle"] // Ñ Ð¾Ð½Ñ Ð½Ñ Ó©Ð½Ð³Ó© 844 ]); 845 } 846 847 elSound.addEventListener("click", function () { 848 soundOn = !soundOn; 849 try { localStorage.setItem(SOUND_KEY, soundOn ? "on" : "off"); } catch (e) {} 850 renderSound(); 851 if (!soundOn) stopSpeech(); 852 playSend(); // аÑÐ°Ð°Ñ Ð°Ð´ жиÑÑÑ Ð±Ð¾Ð»Ð³Ð¾Ð¶ нÑг Ñдаа ÑонÑгоно 853 }); 854 855 // --- ХаÑиÑлÑÑг ÑмÑгÑÑй Ñ Ð¾Ð¾Ð»Ð¾Ð¹Ð³Ð¾Ð¾Ñ ÑнÑÑÑÐ»Ð°Ñ (ÑеÑвеÑ: /tts, edge-tts) --- 856 // СеÑÐ²ÐµÑ Ð·Ó©Ð²Ñ Ó©Ð½ ÑÑÑ Ð°Ð¹Ð½ ÑеÑÑийн боÑÑн Ñ Ð°ÑиÑлÑÑг ÑнÑина: Ð²Ð¸Ð´Ð¶ÐµÑ ÑекÑÑийн 857 // SHA-256-ийн ÑÑ Ð½Ð¸Ð¹ 16 ÑÑмдÑгÑийг илгÑÑж, ÑеÑÐ²ÐµÑ Ñ Ð°Ð´Ð³Ð°Ð»Ñан Ñ Ð°ÑиÑлÑÐ°Ð°Ñ Ñ Ð°Ð¹Ð½Ð°. 858 // ÐвÑомаÑÐ°Ð°Ñ ÑнÑÐ¸Ñ Ð³Ò¯Ð¹ â Ñ Ð°ÑиÑÐ»Ñ Ð±Ò¯Ñийн баÑÑÑн дÑÑд бÑланд байгаа ÑÑигÑийн 859 // дүÑÑийг Ñ ÑÑÑглÑÐ³Ñ Ó©Ó©ÑÓ©Ó© даÑж ÑоглÑÑлна (Ð´Ð°Ñ Ð¸Ð½ даÑвал зогÑоно). 860 var canSpeak = !!(window.crypto && window.crypto.subtle && window.Audio && window.TextEncoder); 861 var speech = null; // { audio, btn } 862 863 async function textHash(text) { 864 var buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text.trim())); 865 return Array.prototype.map.call(new Uint8Array(buf), function (b) { 866 return ("0" + b.toString(16)).slice(-2); 867 }).join("").slice(0, 16); 868 } 869 870 function setSpeakBtn(btn, playing) { 871 btn.innerHTML = playing ? ICON.stop : ICON.soundOn; 872 var label = playing ? "ÐогÑÐ¾Ð¾Ñ " : "ХаÑилÑан ÑÑиаг ÑоглÑÑÐ»Ð°Ñ "; 873 btn.title = label; 874 btn.setAttribute("aria-label", label); 875 btn.classList.toggle("on", playing); 876 btn.setAttribute("aria-pressed", String(playing)); 877 } 878 879 function stopSpeech() { 880 if (!speech) return; 881 var s = speech; 882 speech = null; 883 try { s.audio.pause(); s.audio.removeAttribute("src"); s.audio.load(); } catch (e) {} 884 setSpeakBtn(s.btn, false); 885 } 886 887 async function speak(text, btn) { 888 stopSpeech(); 889 if (!canSpeak || !sessionId) return; 890 var audio = new Audio(); 891 var mine = { audio: audio, btn: btn }; 892 speech = mine; 893 setSpeakBtn(btn, true);
894 var finish = function () { if (speech === mine) stopSpeech(); }; 895 audio.addEventListener("ended", finish); 896 audio.addEventListener("error", finish); 897 try { 898 var h = await textHash(text); 899 if (speech !== mine) return; // Ñ Ò¯Ð»ÑÑÑ Ñ Ð¾Ð¾Ñонд зогÑооÑон 900 audio.src = CFG.api + "/tts?s=" + encodeURIComponent(sessionId) + "&h=" + h; 901 await audio.play(); 902 } catch (e) { 903 // СүлжÑÑ ÑÑвÑл Ñ Ó©ÑÑийн алдаа â ÑовÑийг Ð°Ð½Ñ Ð½Ñ ÑÓ©Ð»Ó©Ð²Ñ Ð½Ñ Ð±ÑÑаана. 904 finish(); 905 } 906 } 907 908 function addSpeakButton(bubble, text) { 909 if (!canSpeak || !text) return; 910 var btn = document.createElement("button"); 911 btn.type = "button"; 912 btn.className = "speak"; 913 setSpeakBtn(btn, false); 914 btn.addEventListener("click", function () { 915 if (speech && speech.btn === btn) stopSpeech(); else speak(text, btn); 916 }); 917 bubble.classList.add("has-speak"); 918 bubble.appendChild(btn); 919 } 920 921 var open = false; 922 var busy = false; 923 924 function esc(s) { 925 return String(s).replace(/[&<>"']/g, function (c) { 926 return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]; 927 }); 928 } 929 930 function scrollDown() { elLog.scrollTop = elLog.scrollHeight; } 931 932 // ХаÑиÑÐ»Ñ Ð´Ð¾ÑÐ¾Ñ Ð±Ð¸ÑигдÑÑн Ñ Ð¾Ð»Ð±Ð¾Ð¾ÑÑг (ж Ð½Ñ ÑÑандаÑÑÑн дÑлгÑÑÑнгүй Ñ ÑÑдаÑ) 933 // ÑÑÑ Ñ Ð°ÑÐ³Ð°Ð°Ñ Ð±ÑÑ "ÐÑлгÑÑÑнгүй" ÑовÑÐ¾Ð¾Ñ Ñ Ð°ÑÑÑлна. Ó¨Ð¼Ð½Ó©Ñ "ÐÑлгÑÑÑнгүй:" гÑÑÑн 934 // ÑоÑгÑг ÑÐ¾Ð²Ñ Ð´Ð°Ð²Ñ Ð°ÑÐ»Ð°Ñ Ð³Ò¯Ð¹Ð½ ÑÑлд залгина. innerHTML аÑÐ¸Ð³Ð»Ð°Ñ Ð³Ò¯Ð¹ â Ð·Ó©Ð²Ñ Ó©Ð½ DOM. 935 var URL_RE = /(дÑлгÑÑÑнгүй|Ñ Ð¾Ð»Ð±Ð¾Ð¾Ñ|линк|дÑлгÑÑÑнгүйг)\s*:?\s*(https?:\/\/[^\s<>"')]+)|(https?:\/\/[^\s<>"')]+)/gi; 936 // Ð¢Ð¾Ð²Ñ Ð±Ð¾Ð»Ð³Ð¾Ñ ÑоÑÑн Ð³Ð°Ð½Ñ Ñ Ð¾Ð»Ð±Ð¾Ð¾Ñ â ÑÑандаÑÑÑн ÑÐ°Ñ Ð¸Ð¼ Ñангийн дÑлгÑÑÑнгүй Ñ ÑÑдаÑ. 937 // ÐÑдлÑгийн Ñангийн Ñайл (/chatbot/kb/...) зÑÑÑг бÑÑад Ñ Ð¾Ð»Ð±Ð¾Ð¾Ñ ÑекÑÑ Ñ ÑвÑÑÑÑÑ 938 // үлдÑнÑ; ÑÑдгÑÑÑ Ð½Ñ Ñ Ð°ÑиÑлÑÑн дооÑÑ "ÐÑ ÑÑÑвалж" жагÑаалÑад гаÑна. 939 var DETAIL_RE = /^https?:\/\/(?:www\.)?estandard\.gov\.mn\/website\/masm\/standards_details\.aspx\?/i; 940 941 function linkButton(url, label) { 942 var a = document.createElement("a"); 943 a.href = url; 944 a.target = "_blank"; 945 a.rel = "noopener noreferrer"; 946 a.className = "link-btn"; 947 a.title = url; 948 var icon = document.createElement("span"); 949 icon.className = "ico"; 950 icon.innerHTML = ICON.link; 951 var name = document.createElement("span"); 952 name.textContent = label; 953 a.appendChild(icon); 954 a.appendChild(name); 955 return a; 956 } 957 958 function linkify(host, text) { 959 host.textContent = ""; 960 var last = 0, match; 961 URL_RE.lastIndex = 0; 962 while ((match = URL_RE.exec(text)) !== null) { 963 // ӨгүүлбÑÑийн ÑөгÑгөлийн ÑÑг, ÑаÑлалÑг Ñ Ð¾Ð»Ð±Ð¾Ð¾ÑÐ¾Ð¾Ñ Ð³Ð°Ñгана 964 var raw = match[2] || match[3]; 965 var url = raw.replace(/[.,;:!?]+$/, ""); 966 if (match.index > last) { 967 host.appendChild(document.createTextNode(text.slice(last, match.index))); 968 } 969 // ХаÑÐ³Ð°Ð°Ñ ÑаÑлаад авÑан ÑÑг, ÑаÑлалÑг ÑекÑÑ Ñ ÑвÑÑÑ Ð½Ñ Ò¯Ð»Ð´ÑÑÐ½Ñ 970 var end = match.index + match[0].length - (raw.length - url.length); 971 if (DETAIL_RE.test(url)) { 972 host.appendChild(linkButton(url, "ÐÑлгÑÑÑнгүй")); 973 } else { 974 host.appendChild(document.createTextNode(text.slice(match.index, end))); 975 } 976 last = end; 977 URL_RE.lastIndex = last; 978 } 979 if (last < text.length) host.appendChild(document.createTextNode(text.slice(last))); 980 } 981 982 function addMessage(kind, text) { 983 var row = document.createElement("div"); 984 row.className = "msg " + kind; 985 var bubble = document.createElement("div"); 986 bubble.className = "bubble"; 987 if (text) { 988 if (kind === "bot") linkify(bubble, text); 989 else bubble.textContent = text; 990 } 991 row.appendChild(bubble); 992 elLog.appendChild(row); 993 scrollDown(); 994 return bubble; 995 } 996 997 // Цаг үеийн мÑдÑÑ: ÑеÑÐ²ÐµÑ `news` event (ÑÑвÑл ÑÑÑгÑÑÑÑн ÑÑиаг `kind: "news"` 998 // ÑÑ ÑÑÑвалжааÑ) илгÑÑÐ½Ñ â 2 баганаÑай зÑÑагÑай каÑÑ, даÑÐ°Ñ Ð°Ð´ мÑдÑÑ ÑÐ¸Ð½Ñ Ñабд. 999 function renderNews(bubble, items) { 1000 items = (items || []).filter(function (n) { return n && n.kind === "
1000news" && n.url; }); 1001 if (!items.length) return; 1002 var grid = document.createElement("div"); 1003 grid.className = "news"; 1004 items.forEach(function (n) { 1005 var a = document.createElement("a"); 1006 a.href = n.url; 1007 a.target = "_blank"; 1008 a.rel = "noopener noreferrer"; 1009 a.title = n.title || ""; 1010 if (n.image) { 1011 var img = document.createElement("img"); 1012 img.src = n.image; 1013 img.alt = ""; 1014 img.loading = "lazy"; 1015 img.addEventListener("error", function () { img.remove(); }); 1016 img.addEventListener("load", scrollDown); 1017 a.appendChild(img); 1018 } 1019 var body = document.createElement("div"); 1020 body.className = "nw-b"; 1021 if (n.date) { 1022 var d = document.createElement("div"); 1023 d.className = "nw-d"; 1024 d.textContent = n.date; 1025 body.appendChild(d); 1026 } 1027 var t = document.createElement("div"); 1028 t.className = "nw-t"; 1029 t.textContent = n.title || n.url; 1030 body.appendChild(t); 1031 a.appendChild(body); 1032 grid.appendChild(a); 1033 }); 1034 bubble.classList.add("has-news"); 1035 bubble.appendChild(grid); 1036 } 1037
1038 function renderSources(bubble, sources) { 1039 sources = (sources || []).filter(function (s) { return s.kind !== "news"; }); 1040 if (!CFG.sources || !sources.length) return; 1041 // ÐÑг URL олон Ñдаа давÑагдвал нÑг л Ñдаа Ñ Ð°ÑÑÑлна. 1042 var seen = {}, unique = []; 1043 sources.forEach(function (s) { 1044 if (!seen[s.url]) { seen[s.url] = 1; unique.push(s); } 1045 }); 1046 1047 var box = document.createElement("div"); 1048 box.className = "srcs"; 1049 var label = document.createElement("div"); 1050 label.className = "lbl"; 1051 label.textContent = "ÐÑ ÑÑÑвалж"; 1052 box.appendChild(label); 1053 1054 unique.slice(0, 4).forEach(function (s) { 1055 var a = document.createElement("a"); 1056 a.href = s.url; 1057 a.target = "_blank"; 1058 a.rel = "noopener noreferrer"; 1059 var icon = document.createElement("span"); 1060 icon.className = "ico"; 1061 icon.innerHTML = ICON.link; 1062 1063 var name = document.createElement("span"); 1064 name.className = "nm"; 1065 name.textContent = s.title || s.url; 1066 1067 a.appendChild(icon); 1068 a.appendChild(name); 1069 box.appendChild(a); 1070 }); 1071 bubble.appendChild(box); 1072 scrollDown(); 1073 } 1074 1075 function setBusy(state) { 1076 busy = state; 1077 elSend.disabled = state; 1078 elInput.disabled = state; 1079 } 1080 1081 function autoGrow() { 1082 elInput.style.height = "auto"; 1083 elInput.style.height = Math.min(elInput.scrollHeight, 108) + "px"; 1084 } 1085 1086 function greet() { 1087 elLog.innerHTML = ""; 1088 addMessage("bot", CFG.greeting); 1089 if (!CFG.suggestions.length) return; 1090 1091 var chips = document.createElement("div"); 1092 chips.className = "chips"; 1093 CFG.suggestions.forEach(function (question) { 1094 var b = document.createElement("button"); 1095 b.type = "button"; 1096 b.textContent = question; 1097 b.addEventListener("click", function () { 1098 if (busy) return; 1099 chips.remove(); 1100 send(question); 1101 }); 1102 chips.appendChild(b); 1103 }); 1104 elLog.appendChild(chips); 1105 scrollDown(); 1106 } 1107 1108 // --- SSE ÑнÑиж, Ñ Ð°ÑиÑг ÑÑÑÐ³Ð°Ñ --- 1109 async function send(question) { 1110 var chips = sr.querySelector(".chips"); 1111 if (chips) chips.remove(); 1112 1113 stopSpeech(); 1114 playSend(); 1115 addMessage("user", question); 1116 setBusy(true); 1117 1118 var bubble = addMessage("bot", ""); 1119 var prog = document.createElement("div"); 1120 prog.className = "prog"; 1121 prog.innerHTML = '<span class="typing"><i></i><i></i><i></i></span>' + 1122 '<span class="txt">Ðодож байнаâ¦</span>'; 1123 bubble.appendChild(prog); 1124 1125 var textNode = document.createElement("span"); 1126 var caret = document.createElement("span"); 1127 caret.className = "caret"; 1128 caret.hidden = true; 1129 bubble.appendChild(textNode); 1130 bubble.appendChild(caret); 1131 scrollDown(); 1132 1133 var answer = ""; 1134 1135 try { 1136 var payload = { question: question, top_k: 5 }; 1137 if (sessionId) payload.session_id = sessionId; 1138 1139 var res = await fetch(CFG.api + "/chat/stream", { 1140 method: "POST", 1141 headers: { "Content-Type": "application/json" }, 1142 body: JSON.stringify(payload) 1143 }); 1144 1145 if (!res.ok) { 1146 prog.remove(); 1147 bubble.parentElement.className = "msg err"; 1148 textNode.textContent = "УÑÑлааÑай, алдаа гаÑлаа (" + res.status + ")."; 1149 return; 1150 } 1151 1152 var reader = res.body.getReader(); 1153 var decoder = new TextDecoder(); 1154 var buffer = ""; 1155 1156 while (true) { 1157 var step = await reader.read(); 1158 if (step.done) break; 1159 buffer += decoder.decode(step.value, { stream: true }); 1160 1161 var parts = buffer.split("\n\n"); 1162 buffer = parts.pop(); 1163 1164 for (var i = 0; i < parts.length; i++) { 1165 var evName = "message", raw = ""; 1166 var lines = parts[i].split("\n"); 1167 for (var j = 0; j < lines.length; j++) { 1168 if (lines[j].indexOf("event:") === 0) evName = lines[j].slice(6).trim(); 1169 else if (lines[j].indexOf("data:") === 0) raw += lines[j].slice(5).trim(); 1170 } 1171 if (!raw) continue; 1172 var data = JSON.parse(raw); 1173 1174 if (evName === "session") { 1175 sessionId = data.session_id; 1176 saveSession(sessionId); 1177 } else if (evName === "status") { 1178 var t = prog.querySelector(".txt"); 1179 if (t) t.textContent = data.text; 1180 } else if (evName === "sources") { 1181 bubble.__sources = data.sources; 1182 } else if (evName === "news") { 1183 bubble.__news = data.items; 1184 } else if (evName === "delta") { 1185 if (!answer) { prog.remove();
1185 caret.hidden = false; } 1186 answer += data.text; 1187 textNode.textContent = answer; 1188 scrollDown(); 1189 } else if (evName === "error") { 1190 prog.remove(); 1191 caret.hidden = true; 1192 bubble.parentElement.className = "msg err"; 1193 textNode.textContent = data.detail; 1194 } else if (evName === "done") { 1195 caret.hidden = true; 1196 if (answer) { 1197 linkify(textNode, answer); // ÑÑÑгаж дÑÑÑÐ¼Ð°Ð³Ñ Ñ Ð¾Ð»Ð±Ð¾Ð¾ÑÑг идÑÐ²Ñ Ð¶Ò¯Ò¯Ð»Ð½Ñ 1198 renderNews(bubble, bubble.__news); 1199 renderSources(bubble, bubble.__sources); 1200 addSpeakButton(bubble, answer.trim()); 1201 } 1202 } 1203 } 1204 } 1205 } catch (e) { 1206 if (prog.isConnected) prog.remove(); 1207 caret.hidden = true; 1208 bubble.parentElement.className = "msg err"; 1209 textNode.textContent = "СеÑвеÑÑÑй Ñ Ð¾Ð»Ð±Ð¾Ð³Ð´Ð¾Ð¶ ÑадÑангүй. ÐаÑаа Ð´Ð°Ñ Ð¸Ð½ оÑолдоно ÑÑ."; 1210 } finally { 1211 caret.hidden = true; 1212 setBusy(false); 1213 if (open) elInput.focus(); 1214 } 1215 } 1216 1217 // --- Үйлдлүүд --- 1218 // --- Таб: "ÐÑÑÑÐ»Ñ Ñ Ð°ÑиÑлÑ" (ÑаÑ) ба "СÑандаÑÑ Ñ Ð°Ð¹Ñ " --- 1219 function view(name) { 1220 var isChat = name !== "std"; 1221 sr.querySelectorAll(".tab").forEach(function (t) { 1222 var on = (t.getAttribute("data-view") === "chat") === isChat; 1223 t.classList.toggle("on", on); 1224 t.setAttribute("aria-selected", String(on)); 1225 }); 1226 elLog.hidden = !isChat; 1227 elFoot.hidden = !isChat; 1228 elStd.hidden = isChat; 1229 if (isChat) { 1230 elInput.focus(); 1231 } else { 1232 elStdQ.focus(); 1233 if (!elStdList.children.length && !elStdQ.value) { 1234 elStdInfo.textContent = "СÑандаÑÑÑн дÑÐ³Ð°Ð°Ñ (ж Ð½Ñ 6452, MNS ISO 9001) ÑÑвÑл " 1235 + "нÑÑÐ½Ð¸Ð¹Ñ Ð½Ñ Ò¯Ð³ÑÑÑ (ж Ð½Ñ ÑеменÑ, ÑоолÑÑÑ) Ñ Ð°Ð¹Ð½Ð°."; 1236 } 1237 } 1238 } 1239 1240 sr.querySelectorAll(".tab").forEach(function (t) { 1241 t.addEventListener("click", function () { view(t.getAttribute("data-view")); }); 1242 }); 1243 1244 // --- СÑандаÑÑÑн Ñ Ð°Ð¹Ð»Ñ --- 1245 var stdTimer = null, stdSeq = 0; 1246 1247 function money(value) { 1248 var n = parseInt(value, 10); 1249 return isNaN(n) ? "" : String(n).replace(/\B(?=(\d{3})+(?!\d))/g, " ") + "â®"; 1250 } 1251 1252 function stdRow(item) { 1253 var row = document.createElement("div"); 1254 row.className = "std-item"; 1255 1256 var num = document.createElement("span"); 1257 num.className = "num"; 1258 num.textContent = item.n; 1259 row.appendChild(num); 1260 1261 var ttl = document.createElement("div"); 1262 ttl.className = "ttl"; 1263 ttl.textContent = item.t; 1264 row.appendChild(ttl); 1265 1266 var bits = []; 1267 if (item.c) bits.push(item.c); 1268 if (item.s) bits.push(item.s); 1269 if (bits.length) { 1270 var meta = document.createElement("div"); 1271 meta.className = "meta2"; 1272 meta.textContent = bits.join(" · "); 1273 row.appendChild(meta); 1274 } 1275 1276 var price = document.createElement("div"); 1277 price.className = "meta2"; 1278 if (item.f) { 1279 price.innerHTML = '<span class="free">ҮндÑÑний ÑÑандаÑÑ â нÑвÑÑÑÑ Ò¯Ð½Ñгүй ÑаÑна</span>'; 1280 } else if (item.p) { 1281 price.textContent = "ҮнÑ: " + money(item.p); 1282 } 1283 if (price.textContent || price.innerHTML) row.appendChild(price); 1284 1285 var go = document.createElement("a"); 1286 go.className = "go"; 1287 go.href = item.u; 1288 go.target = "_blank"; 1289 go.rel = "noopener noreferrer"; 1290 go.innerHTML = ICON.link + "<span>ÐÑлгÑÑÑнгүй Ñ Ð°ÑÐ°Ñ </span>"; 1291 row.appendChild(go); 1292 return row; 1293 } 1294 1295 async function stdSearch(query) { 1296 var seq = ++stdSeq; 1297 elStdInfo.textContent = "Хайж байнаâ¦"; 1298 try { 1299 var res = await fetch(CFG.api + "/standards?q=" + encodeURIComponent(query) + "&limit=30"); 1300 var data = await res.json(); 1301 if (seq !== stdSeq) return; // Ñ ÑÑÑиÑÑан Ñ Ð°ÑÐ¸Ñ â оÑÑ Ð¸Ð½Ð¾ 1302 if (!res.ok) throw new Error(data.detail || "Ðлдаа"); 1303 elStdList.innerHTML = ""; 1304 if (!data.items.length) { 1305 elStdInfo.textContent = "«" + query + "» гÑÑÑн ÑÑандаÑÑ Ð¾Ð»Ð´Ñонгүй. " 1306 + "ÐÑгааÑÐ°Ð°Ñ (6452) ÑÑвÑл Ó©Ó©Ñ Ò¯Ð³ÑÑÑ Ñ Ð°Ð¹Ð¶ Ò¯Ð·Ð½Ñ Ò¯Ò¯."; 1307 return; 1308 } 1309 elStdInfo.textContent = data.items.length >= 30 1310 ? "Хамгийн ÑÐ¾Ñ Ð¸ÑÐ¾Ñ 30 ÑÑандаÑÑ (Ð½Ð¸Ð¹Ñ Ñан: " + data.total + ")" 1311 : data.items.length + " ÑÑандаÑÑ Ð¾Ð»Ð´Ð»Ð¾Ð¾";
1312 data.items.forEach(function (item) { elStdList.appendChild(stdRow(item)); }); 1313 elStdList.scrollTop = 0; 1314 } catch (e) { 1315 if (seq !== stdSeq) return; 1316 elStdList.innerHTML = ""; 1317 elStdInfo.textContent = "Ð¥Ð°Ð¹Ð»Ñ ÑÒ¯Ñ Ð°Ð¶Ð¸Ð»Ð»Ð°Ñ Ð³Ò¯Ð¹ байна. ÐаÑаа оÑолдоно ÑÑ."; 1318 } 1319 } 1320 1321 elStdQ.addEventListener("input", function () { 1322 clearTimeout(stdTimer); 1323 var value = this.value.trim(); 1324 if (value.length < 2) { 1325 stdSeq++; 1326 elStdList.innerHTML = ""; 1327 elStdInfo.textContent = "ÐÐ¾Ñ Ñ Ð°Ñж 2 ÑÑмдÑÐ³Ñ Ð±Ð¸ÑÐ½Ñ Ò¯Ò¯."; 1328 return; 1329 } 1330 stdTimer = setTimeout(function () { stdSearch(value); }, 280); 1331 }); 1332 1333 // --- ÐÑндÑилгÑÑний бөмбөлөг --- 1334 // Ð§Ð°Ñ Ñ Ð°Ð°Ð»ÑÑай Ð±Ð°Ð¹Ñ Ð±Ò¯Ñ Ñ ÑгаÑаанд Ñ Ð°Ñагдана. "Ã" даÑвал Ð·Ó©Ð²Ñ Ó©Ð½ ÑÑÑ Ð°Ð¹Ð½ 1335 // үзÑлÑÑд нÑÑгдана â Ñ ÑÑÐ´Ð°Ñ ÑÑÑгÑÑÑ Ð±Ò¯Ñд Ð´Ð°Ñ Ð¸Ð½ мÑндÑÐ¸Ð»Ð½Ñ (Ñ Ó©ÑÑид ÑÑ Ñ 1336 // Ñ Ð°Ð´Ð³Ð°Ð»Ð°Ñ Ð³Ò¯Ð¹). 1337 var helloOff = false; 1338 1339 function showHello(on) { 1340 if (!elHello) return; 1341 var visible = on && !helloOff; 1342 elHello.classList.toggle("on", visible); 1343 root.classList.toggle("greeting", visible); 1344 } 1345 1346 if (elHello) { 1347 elHello.querySelector(".x").addEventListener("click", function (ev) { 1348 ev.stopPropagation(); 1349 helloOff = true; 1350 showHello(false); 1351 elLauncher.focus(); 1352 }); 1353 elHello.addEventListener("click", function () { if (!open) toggle(true); }); 1354 // Ð¥ÑÑÐ´Ð°Ñ Ð°ÑÐ°Ð°Ð»Ð¼Ð°Ð³Ñ Ð±Ð¸Ñ, бага зÑÑÑг Ñ Ò¯Ð»ÑÑгÑÑд гаÑгана â Ð°Ð½Ñ Ð°Ð°Ñал ÑаÑна 1355 setTimeout(function () { showHello(!open); }, 900); 1356 } 1357 1358 // --- ÐÐ°Ñ ÑÑаÑ: бүÑÑн дÑлгÑÑ --- 1359 // CSS Ð½Ñ ÑÐ¾Ð½Ñ Ñг бүÑÑн дÑлгÑÑ Ð±Ð¾Ð»Ð³Ð¾Ð½Ð¾; Ñнд (1) аÑд байгаа ÑайÑÑн гүйлгÑÑг 1360 // ÑүгжинÑ, (2) Ð³Ð°Ñ (keyboard) гаÑÐ°Ñ Ð°Ð´ ÑÐ¾Ð½Ñ Ñг Ñ Ð°ÑÐ°Ð³Ð´Ð°Ñ Ñ ÑÑÑÐ³Ñ Ð±Ð°Ð³Ñаана â 1361 // ÑÑ Ð±Ó©Ð³Ó©Ó©Ñ Ð±Ð¸ÑÐ¸Ñ ÑÐ°Ð»Ð±Ð°Ñ Ð³Ð°ÑÑн аÑд нÑÑгдана (visualViewport). 1362 var mobileQuery = window.matchMedia("(max-width: 540px)"); 1363 var savedOverflow = null; 1364 1365 function fitViewport() { 1366 var vv = window.visualViewport; 1367 if (open && mobileQuery.matches && vv) { 1368 root.style.setProperty("--vvh", vv.height + "px"); 1369 root.style.setProperty("--vvt", vv.offsetTop + "px"); 1370 } else { 1371 root.style.removeProperty("--vvh"); 1372 root.style.removeProperty("--vvt"); 1373 } 1374 } 1375 1376 function lockPage(lock) { 1377 var el = document.documentElement; 1378 if (lock && savedOverflow === null) { 1379 savedOverflow = el.style.overflow; 1380 el.style.overflow = "hidden"; 1381 } else if (!lock && savedOverflow !== null) { 1382 el.style.overflow = savedOverflow; 1383 savedOverflow = null; 1384 } 1385 } 1386 1387 function mobileMode() { 1388 lockPage(open && mobileQuery.matches); 1389 fitViewport(); 1390 } 1391 1392 if (window.visualViewport) { 1393 window.visualViewport.addEventListener("resize", fitViewport); 1394 window.visualViewport.addEventListener("scroll", fitViewport); 1395 } 1396 // УÑÑаа ÑÑгүүлÑÑ , ÑÐ¾Ð½Ñ Ð½Ñ Ñ ÑмжÑÑ Ó©Ó©ÑÑÐ»Ó©Ð³Ð´Ó©Ñ Ó©Ð´ (ÑÑÐ°Ñ <-> Ñом дÑлгÑÑ) 1397 if (mobileQuery.addEventListener) mobileQuery.addEventListener("change", mobileMode); 1398 else if (mobileQuery.addListener) mobileQuery.addListener(mobileMode); 1399 1400 function toggle(show) { 1401 open = show === undefined ? !open : show; 1402 elPanel.hidden = !open; 1403 mobileMode(); 1404 showHello(!open); 1405 if (!open) stopSpeech();
1406 // Ð¦Ð¾Ð½Ñ Ð½ÑÑлÑÑÑй үед нÑÑÑ ÑовÑийг нÑана â ÑÑ Ð±Ó©Ð³Ó©Ó©Ñ ÑÐ¾Ð½Ñ Ð½Ñ Ð´ÑÑÑ Ð´Ð°Ð²Ñ Ð°Ñлана. 1407 // Ð¥Ð°Ð°Ñ ÑÐ¾Ð²Ñ Ñолгой Ñ ÑÑÑÐ³Ñ Ð±Ð°Ð¹Ð³Ð°Ð°, мөн Esc ажиллана. 1408 elLauncher.hidden = open; 1409 elLauncher.classList.add("seen"); 1410 elLauncher.setAttribute("aria-expanded", String(open)); 1411 if (open) { 1412 if (!elLog.children.length) { 1413 if (sessionId) restore(); else greet(); 1414 } 1415 elInput.focus(); 1416 scrollDown(); 1417 } 1418 } 1419 1420 // --- Ó¨Ð¼Ð½Ó©Ñ ÑÑиаг ÑеÑвеÑÑÑÑ ÑÑÑгÑÑÑ --- 1421 async function restore() { 1422 greet(); // ÑүлжÑÑ Ñдаан бол Ñ Ð¾Ð¾Ñон ÑÐ¾Ð½Ñ Ñ Ð°ÑÐ°Ð³Ð´Ð°Ñ Ð³Ò¯Ð¹Ð½ ÑÑлд ÑÑ Ð»ÑÑд мÑндÑÐ¸Ð»Ð½Ñ 1423 var id = sessionId; 1424 try { 1425 var res = await fetch(CFG.api + "/session/" + encodeURIComponent(id)); 1426 if (res.status === 404) { if (sessionId === id) { sessionId = null; saveSession(null); } return; } 1427 if (!res.ok) return; 1428 var data = await res.json(); 1429 // ХүлÑÑÑ Ñ Ð¾Ð¾Ñонд Ñ ÑÑÑглÑÐ³Ñ Ð°ÑÑÑÐ»Ñ Ð¸Ð»Ð³ÑÑÑÑн ÑÑвÑл ÑÐ¸Ð½Ñ ÑÑиа ÑÑ Ð»Ò¯Ò¯Ð»ÑÑн бол бүү даÑ. 1430 if (!data.messages || !data.messages.length || busy || sessionId !== id) return; 1431 if (elLog.querySelector(".msg.user")) return; 1432 1433 elLog.innerHTML = ""; 1434 addMessage("bot", CFG.greeting); 1435 data.messages.forEach(function (m) { 1436 var bubble = addMessage(m.role === "user" ? "user" : "bot", m.content); 1437 if (m.role !== "user") { 1438 renderNews(bubble, m.sources); 1439 renderSources(bubble, m.sources); 1440 addSpeakButton(bubble, m.content); 1441 } 1442 }); 1443 scrollDown(); 1444 } catch (e) { /* ÑÑÑгÑÑж ÑÐ°Ð´Ð°Ñ Ð³Ò¯Ð¹ бол мÑндÑилгÑÑ Ñ ÑвÑÑÑ Ò¯Ð»Ð´ÑÐ½Ñ */ } 1445 } 1446 1447 elLauncher.addEventListener("click", function () { 1448 if (!open) playOpen(); // Ð·Ó©Ð²Ñ Ó©Ð½ нÑÑÑ Ñд (Ñ ÑÑÑглÑгÑийн даÑÑан үйлдÑл ÑÑл autoplay Ñ ÑзгааÑÑ Ó©ÑÑÓ©Ñ Ð³Ò¯Ð¹) 1449 toggle(); 1450 }); 1451 elClose.addEventListener("click", function () { toggle(false); elLauncher.focus(); }); 1452 1453 elReset.addEventListener("click", async function () { 1454 if (busy) return; 1455 stopSpeech(); 1456 var old = sessionId; 1457 sessionId = null; 1458 saveSession(null); 1459 greet(); 1460 elInput.focus(); 1461 if (old) { 1462 try { 1463 await fetch(CFG.api + "/session/" + encodeURIComponent(old), { method: "DELETE" }); 1464 } catch (e) { /* ÑеÑÐ²ÐµÑ ÑалÑн ÑÑвÑÑлÑгÑÑ Ð·Ð°Ð°Ð²Ð°Ð» Ð±Ð¸Ñ */ } 1465 } 1466 }); 1467 1468 function submit() { 1469 var question = elInput.value.trim(); 1470 if (!question || busy) return; 1471 elInput.value = ""; 1472 autoGrow(); 1473 send(question); 1474 } 1475 1476 elForm.addEventListener("submit", function (event) { 1477 event.preventDefault(); 1478 submit(); 1479 }); 1480 1481 elInput.addEventListener("input", autoGrow); 1482 elInput.addEventListener("keydown", function (event) { 1483 // Enter â илгÑÑÑ , Shift+Enter â ÑÐ¸Ð½Ñ Ð¼Ó©Ñ 1484 if (event.key === "Enter" && !event.shiftKey) { 1485 event.preventDefault(); 1486 submit(); 1487 } 1488 }); 1489 1490 document.addEventListener("keydown", function (event) { 1491 if (event.key === "Escape" && open) { toggle(false); elLauncher.focus(); } 1492 }); 1493 1494 // ÐÐ°Ð´Ð½Ð°Ð°Ñ ÑдиÑÐ´Ð°Ñ Ð±Ð°Ð³Ð° зÑÑгийн API 1495 window.MasmChat = { 1496 version: VERSION, 1497 open: function () { toggle(true); }, 1498 close: function () { toggle(false); }, 1499 // "СÑандаÑÑ Ñ Ð°Ð¹Ñ " Ñаб дÑÑÑ Ð±Ð°Ð¹Ñан Ñ Ð°ÑÑÑлÑÑг ÑÐ°Ñ Ñаб ÑÑÑ Ñилжүүлж аÑÑÑна 1500 ask: function (question) { toggle(true); view("chat"); send(question); }, 1501 // "СÑандаÑÑ Ñ Ð°Ð¹Ñ " ÑабÑг нÑÑж, өгÑөн үгÑÑÑ ÑÑÑд Ñ Ð°Ð¹Ð½Ð° (ж Ð½Ñ "ÑеменÑ", "6452") 1502 standards: function (query) { 1503 toggle(true); 1504 view("std"); 1505 if (query) { 1506 elStdQ.value = String(query); 1507 elStdQ.dispatchEvent(new Event("input")); 1508 } 1509 } 1510 }; 1511})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.