<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki3.rohmega.com/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3ACommon.js</id>
	<title>MediaWiki:Common.js - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki3.rohmega.com/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3ACommon.js"/>
	<link rel="alternate" type="text/html" href="https://wiki3.rohmega.com/index.php?title=MediaWiki:Common.js&amp;action=history"/>
	<updated>2026-09-04T12:35:21Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://wiki3.rohmega.com/index.php?title=MediaWiki:Common.js&amp;diff=49&amp;oldid=prev</id>
		<title>Ronald: add chat widget</title>
		<link rel="alternate" type="text/html" href="https://wiki3.rohmega.com/index.php?title=MediaWiki:Common.js&amp;diff=49&amp;oldid=prev"/>
		<updated>2026-09-04T09:19:08Z</updated>

		<summary type="html">&lt;p&gt;add chat widget&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;/**&lt;br /&gt;
 * Retina Chip Wiki chat widget (wiki3.rohmega.com).&lt;br /&gt;
 * Floating chat bubble in the bottom-right. Grounded Q&amp;amp;A over the wiki.&lt;br /&gt;
 * Public wiki -&amp;gt; the backend enforces a daily token budget.&lt;br /&gt;
 *&lt;br /&gt;
 * ES5-safe only: ResourceLoader minifier rejects trailing commas in call args,&lt;br /&gt;
 * arrow functions, template literals.&lt;br /&gt;
 */&lt;br /&gt;
(function () {&lt;br /&gt;
    &amp;#039;use strict&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
    // Don&amp;#039;t double-inject&lt;br /&gt;
    if (document.getElementById(&amp;#039;rmg-chat-root&amp;#039;)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var CHAT_ENDPOINT = &amp;#039;/chat/chat&amp;#039;;  // same-origin via Caddy /chat/* -&amp;gt; :8111&lt;br /&gt;
&lt;br /&gt;
    function buildUI() {&lt;br /&gt;
        var root = document.createElement(&amp;#039;div&amp;#039;);&lt;br /&gt;
        root.id = &amp;#039;rmg-chat-root&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
        // --- floating toggle button ---&lt;br /&gt;
        var btn = document.createElement(&amp;#039;button&amp;#039;);&lt;br /&gt;
        btn.id = &amp;#039;rmg-chat-toggle&amp;#039;;&lt;br /&gt;
        btn.setAttribute(&amp;#039;aria-label&amp;#039;, &amp;#039;Ask the wiki&amp;#039;);&lt;br /&gt;
        btn.textContent = &amp;#039;💬&amp;#039;;&lt;br /&gt;
        root.appendChild(btn);&lt;br /&gt;
&lt;br /&gt;
        // --- panel ---&lt;br /&gt;
        var panel = document.createElement(&amp;#039;div&amp;#039;);&lt;br /&gt;
        panel.id = &amp;#039;rmg-chat-panel&amp;#039;;&lt;br /&gt;
        panel.style.display = &amp;#039;none&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
        var head = document.createElement(&amp;#039;div&amp;#039;);&lt;br /&gt;
        head.id = &amp;#039;rmg-chat-head&amp;#039;;&lt;br /&gt;
        var title = document.createElement(&amp;#039;span&amp;#039;);&lt;br /&gt;
        title.textContent = &amp;#039;Ask the Retina Chip Wiki&amp;#039;;&lt;br /&gt;
        var close = document.createElement(&amp;#039;button&amp;#039;);&lt;br /&gt;
        close.textContent = &amp;#039;×&amp;#039;;&lt;br /&gt;
        close.setAttribute(&amp;#039;aria-label&amp;#039;, &amp;#039;Close&amp;#039;);&lt;br /&gt;
        head.appendChild(title);&lt;br /&gt;
        head.appendChild(close);&lt;br /&gt;
        panel.appendChild(head);&lt;br /&gt;
&lt;br /&gt;
        var msgs = document.createElement(&amp;#039;div&amp;#039;);&lt;br /&gt;
        msgs.id = &amp;#039;rmg-chat-msgs&amp;#039;;&lt;br /&gt;
        panel.appendChild(msgs);&lt;br /&gt;
&lt;br /&gt;
        var foot = document.createElement(&amp;#039;div&amp;#039;);&lt;br /&gt;
        foot.id = &amp;#039;rmg-chat-foot&amp;#039;;&lt;br /&gt;
        var input = document.createElement(&amp;#039;input&amp;#039;);&lt;br /&gt;
        input.id = &amp;#039;rmg-chat-input&amp;#039;;&lt;br /&gt;
        input.setAttribute(&amp;#039;placeholder&amp;#039;, &amp;#039;Ask about blocks, ADRs, methods…&amp;#039;);&lt;br /&gt;
        var send = document.createElement(&amp;#039;button&amp;#039;);&lt;br /&gt;
        send.id = &amp;#039;rmg-chat-send&amp;#039;;&lt;br /&gt;
        send.textContent = &amp;#039;Send&amp;#039;;&lt;br /&gt;
        foot.appendChild(input);&lt;br /&gt;
        foot.appendChild(send);&lt;br /&gt;
        panel.appendChild(foot);&lt;br /&gt;
&lt;br /&gt;
        root.appendChild(panel);&lt;br /&gt;
        document.body.appendChild(root);&lt;br /&gt;
&lt;br /&gt;
        // wire events&lt;br /&gt;
        btn.addEventListener(&amp;#039;click&amp;#039;, function () {&lt;br /&gt;
            var open = panel.style.display !== &amp;#039;none&amp;#039;;&lt;br /&gt;
            panel.style.display = open ? &amp;#039;none&amp;#039; : &amp;#039;block&amp;#039;;&lt;br /&gt;
        });&lt;br /&gt;
        close.addEventListener(&amp;#039;click&amp;#039;, function () {&lt;br /&gt;
            panel.style.display = &amp;#039;none&amp;#039;;&lt;br /&gt;
        });&lt;br /&gt;
        send.addEventListener(&amp;#039;click&amp;#039;, ask);&lt;br /&gt;
        input.addEventListener(&amp;#039;keydown&amp;#039;, function (e) {&lt;br /&gt;
            if (e.key === &amp;#039;Enter&amp;#039;) {&lt;br /&gt;
                ask();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function addBubble(text, who) {&lt;br /&gt;
        var msgs = document.getElementById(&amp;#039;rmg-chat-msgs&amp;#039;);&lt;br /&gt;
        var b = document.createElement(&amp;#039;div&amp;#039;);&lt;br /&gt;
        b.className = &amp;#039;rmg-bubble &amp;#039; + who;&lt;br /&gt;
        b.textContent = text;&lt;br /&gt;
        msgs.appendChild(b);&lt;br /&gt;
        msgs.scrollTop = msgs.scrollHeight;&lt;br /&gt;
        return b;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ask() {&lt;br /&gt;
        var input = document.getElementById(&amp;#039;rmg-chat-input&amp;#039;);&lt;br /&gt;
        var send = document.getElementById(&amp;#039;rmg-chat-send&amp;#039;);&lt;br /&gt;
        var q = input.value.trim();&lt;br /&gt;
        if (!q) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        input.value = &amp;#039;&amp;#039;;&lt;br /&gt;
        addBubble(q, &amp;#039;user&amp;#039;);&lt;br /&gt;
        send.disabled = true;&lt;br /&gt;
&lt;br /&gt;
        var typing = addBubble(&amp;#039;…&amp;#039;, &amp;#039;bot&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
        var xhr = new XMLHttpRequest();&lt;br /&gt;
        xhr.open(&amp;#039;POST&amp;#039;, CHAT_ENDPOINT, true);&lt;br /&gt;
        xhr.setRequestHeader(&amp;#039;Content-Type&amp;#039;, &amp;#039;application/json&amp;#039;);&lt;br /&gt;
        xhr.onreadystatechange = function () {&lt;br /&gt;
            if (xhr.readyState !== 4) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            send.disabled = false;&lt;br /&gt;
            if (xhr.status === 200) {&lt;br /&gt;
                try {&lt;br /&gt;
                    var d = JSON.parse(xhr.responseText);&lt;br /&gt;
                    typing.textContent = d.answer || &amp;#039;(no answer)&amp;#039;;&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    typing.textContent = &amp;#039;Error reading response.&amp;#039;;&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                try {&lt;br /&gt;
                    var e = JSON.parse(xhr.responseText);&lt;br /&gt;
                    typing.textContent = e.answer || (&amp;#039;Error &amp;#039; + xhr.status);&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    typing.textContent = &amp;#039;Error &amp;#039; + xhr.status;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        xhr.send(JSON.stringify({ message: q }));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Run after DOM ready&lt;br /&gt;
    if (document.readyState === &amp;#039;loading&amp;#039;) {&lt;br /&gt;
        document.addEventListener(&amp;#039;DOMContentLoaded&amp;#039;, buildUI);&lt;br /&gt;
    } else {&lt;br /&gt;
        buildUI();&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;/div&gt;</summary>
		<author><name>Ronald</name></author>
	</entry>
</feed>