Files
digital-me-history/digital-me-backend/public/widget.js
T
admin 0593ff7e62
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
数字我 Digital Me:全量入库(大文件走 Git LFS)
2026-10-10 16:24:43 +08:00

95 lines
3.5 KiB
JavaScript

/*!
* 数字我 / Digital Me — embeddable chat widget.
* Usage:
* <script src="https://YOUR-API/widget.js"
* data-slug="your-slug"
* data-site="https://your-web-origin"
* data-name="你的名字"></script>
* Injects a floating launcher button that opens「和我分身聊天」in a popup.
*/
(function () {
var s =
document.currentScript ||
(function () {
var all = document.getElementsByTagName('script');
return all[all.length - 1];
})();
if (!s) return;
var slug = s.getAttribute('data-slug');
if (!slug) {
console.warn('[digital-me] widget: missing data-slug');
return;
}
// Where the /me/:slug page is served (the web app). Defaults to the
// script's own origin (works when web+api share an origin behind a proxy).
var site = s.getAttribute('data-site') || new URL(s.src).origin;
var name = s.getAttribute('data-name') || '我的数字分身';
var accent = s.getAttribute('data-accent') || '#6d5efc';
var chatUrl = site.replace(/\/$/, '') + '/me/' + encodeURIComponent(slug);
if (document.getElementById('dm-widget-root')) return; // idempotent
var root = document.createElement('div');
root.id = 'dm-widget-root';
root.style.cssText =
'position:fixed;right:20px;bottom:20px;z-index:2147483000;font-family:system-ui,-apple-system,sans-serif';
// Popup (iframe) — hidden until opened.
var panel = document.createElement('div');
panel.style.cssText =
'width:380px;max-width:calc(100vw - 32px);height:560px;max-height:calc(100vh - 120px);' +
'background:#0f1117;border-radius:16px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.45);' +
'margin-bottom:12px;display:none;transform-origin:bottom right;transition:opacity .15s,transform .15s;' +
'opacity:0;transform:scale(.96)';
var iframe = document.createElement('iframe');
iframe.src = chatUrl;
iframe.title = name;
iframe.setAttribute('allow', 'autoplay; microphone');
iframe.style.cssText = 'width:100%;height:100%;border:0';
panel.appendChild(iframe);
// Launcher button.
var btn = document.createElement('button');
btn.setAttribute('aria-label', '和' + name + '聊天');
btn.style.cssText =
'width:58px;height:58px;border-radius:50%;border:0;cursor:pointer;float:right;' +
'background:' +
accent +
';box-shadow:0 8px 24px rgba(0,0,0,.35);display:flex;align-items:center;' +
'justify-content:center;transition:transform .12s';
btn.innerHTML =
'<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#fff" ' +
'stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
'<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>';
btn.onmouseenter = function () {
btn.style.transform = 'scale(1.06)';
};
btn.onmouseleave = function () {
btn.style.transform = 'scale(1)';
};
var open = false;
btn.onclick = function () {
open = !open;
if (open) {
panel.style.display = 'block';
requestAnimationFrame(function () {
panel.style.opacity = '1';
panel.style.transform = 'scale(1)';
});
} else {
panel.style.opacity = '0';
panel.style.transform = 'scale(.96)';
setTimeout(function () {
if (!open) panel.style.display = 'none';
}, 150);
}
};
root.appendChild(panel);
root.appendChild(btn);
(document.body || document.documentElement).appendChild(root);
})();