Skip to content

Commit 1990ace

Browse files
Add files via upload
1 parent dec3761 commit 1990ace

1 file changed

Lines changed: 63 additions & 23 deletions

File tree

dev/_static/ai-assistant.js

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,9 @@
741741
// GitHub Octicon "upload" — additive and not wired to a control yet.
742742
// Mirrors upload.svg / _SVG_UPLOAD in _static/__init__.py.
743743
upload: '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"/><path d="M11.78 4.72a.749.749 0 1 1-1.06 1.06L8.75 3.811V9.5a.75.75 0 0 1-1.5 0V3.811L5.28 5.78a.749.749 0 1 1-1.06-1.06l3.25-3.25a.749.749 0 0 1 1.06 0l3.25 3.25Z"/></svg>',
744+
// GitHub Octicon "database" — additive and not wired to a control yet.
745+
// Mirrors database.svg / _SVG_DATABASE in _static/__init__.py.
746+
database: '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M1 3.5c0-.626.292-1.165.7-1.59.406-.422.956-.767 1.579-1.041C4.525.32 6.195 0 8 0c1.805 0 3.475.32 4.722.869.622.274 1.172.62 1.578 1.04.408.426.7.965.7 1.591v9c0 .626-.292 1.165-.7 1.59-.406.422-.956.767-1.579 1.041C11.476 15.68 9.806 16 8 16c-1.805 0-3.475-.32-4.721-.869-.623-.274-1.173-.62-1.579-1.04-.408-.426-.7-.965-.7-1.591Zm1.5 0c0 .133.058.318.282.551.227.237.591.483 1.101.707C4.898 5.205 6.353 5.5 8 5.5c1.646 0 3.101-.295 4.118-.742.508-.224.873-.471 1.1-.708.224-.232.282-.417.282-.55 0-.133-.058-.318-.282-.551-.227-.237-.591-.483-1.101-.707C11.102 1.795 9.647 1.5 8 1.5c-1.646 0-3.101.295-4.118.742-.508.224-.873.471-1.1.708-.224.232-.282.417-.282.55Zm0 4.5c0 .133.058.318.282.551.227.237.591.483 1.101.707C4.898 9.705 6.353 10 8 10c1.646 0 3.101-.295 4.118-.742.508-.224.873-.471 1.1-.708.224-.232.282-.417.282-.55V5.724c-.241.15-.503.286-.778.407C11.475 6.68 9.805 7 8 7c-1.805 0-3.475-.32-4.721-.869a6.15 6.15 0 0 1-.779-.407Zm0 2.225V12.5c0 .133.058.318.282.55.227.237.592.484 1.1.708 1.016.447 2.471.742 4.118.742 1.647 0 3.102-.295 4.117-.742.51-.224.874-.47 1.101-.707.224-.233.282-.418.282-.551v-2.275c-.241.15-.503.285-.778.406-1.247.549-2.917.869-4.722.869-1.805 0-3.475-.32-4.721-.869a6.327 6.327 0 0 1-.779-.406Z"/></svg>',
744747
// Octicon-style printer — used by the inline PDF method switch and
745748
// mirrored by _SVG_PRINTER / printer.svg.
746749
printer: '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M4 2.75C4 1.784 4.784 1 5.75 1h4.5C11.216 1 12 1.784 12 2.75V5h.25A2.75 2.75 0 0 1 15 7.75v3.5A1.75 1.75 0 0 1 13.25 13H12v.25A1.75 1.75 0 0 1 10.25 15h-4.5A1.75 1.75 0 0 1 4 13.25V13H2.75A1.75 1.75 0 0 1 1 11.25v-3.5A2.75 2.75 0 0 1 3.75 5H4V2.75Zm1.5 0V5h5V2.75a.25.25 0 0 0-.25-.25h-4.5a.25.25 0 0 0-.25.25ZM3.75 6.5A1.25 1.25 0 0 0 2.5 7.75v3.5c0 .138.112.25.25.25H4v-.75C4 9.784 4.784 9 5.75 9h4.5c.966 0 1.75.784 1.75 1.75v.75h1.25a.25.25 0 0 0 .25-.25v-3.5a1.25 1.25 0 0 0-1.25-1.25h-8.5Zm1.75 4.25v2.5c0 .138.112.25.25.25h4.5a.25.25 0 0 0 .25-.25v-2.5a.25.25 0 0 0-.25-.25h-4.5a.25.25 0 0 0-.25.25ZM12 8a.75.75 0 1 1 1.5 0A.75.75 0 0 1 12 8Z"/></svg>',
@@ -974,14 +977,12 @@
974977
return document.createTextNode('🕺');
975978
}
976979
const img = document.createElement('img');
977-
// const version = window.location.pathname.split("/")[1]; // "dev" or "stable"
978-
// const root = window.location.pathname.match(/^\/[^/]+/)[0];
979-
// if the script itself is inside _static, even better because
980-
// DOCUMENTATION_OPTIONS.URL_ROOT points back to the documentation root.:
981-
// img.src = new URL("animated_gif/dancer_anim.gif", import.meta.url).href;
982-
// img.src = new URL("_static/animated_gif/dancer_anim.gif", document.baseURI).href;
983-
const urlRoot = window.DOCUMENTATION_OPTIONS?.URL_ROOT ?? "/dev/";
984-
img.src = `${urlRoot}_static/animated_gif/dancer_anim.gif`;
980+
// getStaticAssetUrl() derives _static/ from the loaded <script>/<link>
981+
// tag itself (see "Static path detection" below), so this resolves
982+
// correctly under /dev/, /stable/, a version tag, or a ReadTheDocs
983+
// /en/latest/-style prefix — unlike a URL_ROOT fallback hardcoded to
984+
// one deployment.
985+
img.src = getStaticAssetUrl('animated_gif/dancer_anim.gif');
985986
img.className = 'ai-assistant-panel-dancer-gif';
986987
img.height = 11;
987988
img.alt = '';
@@ -1904,7 +1905,7 @@
19041905
mainBtn.title = 'Copy this page as Markdown for AI / LLMs';
19051906

19061907
var mainIcon = document.createElement('img');
1907-
mainIcon.src = staticPath + '/copy-to-clipboard.svg';
1908+
mainIcon.src = getStaticAssetUrl('copy-to-clipboard.svg', staticPath);
19081909
mainIcon.className = 'ai-assistant-icon';
19091910
mainIcon.setAttribute('aria-hidden', 'true');
19101911
mainIcon.alt = '';
@@ -1928,7 +1929,7 @@
19281929
dropBtn.setAttribute('aria-haspopup', 'true');
19291930

19301931
var dropIcon = document.createElement('img');
1931-
dropIcon.src = staticPath + '/arrow-down.svg';
1932+
dropIcon.src = getStaticAssetUrl('arrow-down.svg', staticPath);
19321933
dropIcon.className = 'ai-assistant-dropdown-icon';
19331934
dropIcon.setAttribute('aria-hidden', 'true');
19341935
dropIcon.alt = '';
@@ -1956,12 +1957,12 @@
19561957

19571958
// 1. Markdown export
19581959
if (features.markdown_export) {
1959-
dropdown.appendChild(createMenuItem('copy-markdown', 'Copy page', 'Copy this page as Markdown for LLMs.', staticPath + '/copy-to-clipboard.svg'));
1960+
dropdown.appendChild(createMenuItem('copy-markdown', 'Copy page', 'Copy this page as Markdown for LLMs.', getStaticAssetUrl('copy-to-clipboard.svg', staticPath)));
19601961
hasItems = true;
19611962
}
19621963

19631964
if (features.view_markdown) {
1964-
dropdown.appendChild(createMenuItem('view-markdown', 'View as Markdown', 'View this page as Markdown.', staticPath + '/markdown.svg'));
1965+
dropdown.appendChild(createMenuItem('view-markdown', 'View as Markdown', 'View this page as Markdown.', getStaticAssetUrl('markdown.svg', staticPath)));
19651966
hasItems = true;
19661967
}
19671968

@@ -1974,7 +1975,7 @@
19741975
enabledProviders.forEach(function (kv) {
19751976
var key = kv[0], provider = kv[1];
19761977
var icon = provider.icon || 'comment-discussion.svg';
1977-
var iconPath = _ABSOLUTE_ICON_RE.test(icon) ? icon : (staticPath + '/' + icon);
1978+
var iconPath = getStaticAssetUrl(icon, staticPath);
19781979
var item = createMenuItem('ai-chat-' + key, provider.label, provider.description || 'Open AI chat with this page context.', iconPath);
19791980
item.dataset.provider = key;
19801981
dropdown.appendChild(item);
@@ -1992,7 +1993,7 @@
19921993
enabledTools.forEach(function (kv) {
19931994
var key = kv[0], tool = kv[1];
19941995
var icon = tool.icon || 'ai-tools.svg';
1995-
var iconPath = _ABSOLUTE_ICON_RE.test(icon) ? icon : (staticPath + '/' + icon);
1996+
var iconPath = getStaticAssetUrl(icon, staticPath);
19961997
var item = createMenuItem('mcp-' + key, tool.label, tool.description || 'Install MCP server.', iconPath);
19971998
item.dataset.mcpTool = key;
19981999
dropdown.appendChild(item);
@@ -2012,7 +2013,7 @@
20122013
if (features.ai_panel) {
20132014
var panelTitle = cfg.panelTitle || 'AI Assistant';
20142015
if (hasItems) dropdown.appendChild(createSeparator());
2015-
var panelItem = createMenuItem('ai-panel-open', panelTitle, 'Ask ' + panelTitle + ' about this page', staticPath + '/ai-panel.svg');
2016+
var panelItem = createMenuItem('ai-panel-open', panelTitle, 'Ask ' + panelTitle + ' about this page', getStaticAssetUrl('ai-panel.svg', staticPath));
20162017
dropdown.appendChild(panelItem);
20172018
}
20182019

@@ -2312,7 +2313,7 @@
23122313

23132314
function _pdfIconSource(staticPath, mode) {
23142315
var def = _PDF_MODE_DEFS[mode] || _PDF_MODE_DEFS.print;
2315-
return staticPath.replace(/\/$/, '') + '/' + def.iconFile;
2316+
return getStaticAssetUrl(def.iconFile, staticPath);
23162317
}
23172318

23182319
function _pdfSwitchAccessibleLabel(mode, target) {
@@ -2623,6 +2624,44 @@
26232624
return '_static';
26242625
}
26252626

2627+
/**
2628+
* Resolve `asset` to a URL safe to assign to `img.src` regardless of page
2629+
* depth or deployment prefix (`/dev/`, `/stable/`, a version tag,
2630+
* ReadTheDocs `/en/latest/`, a custom `html_baseurl`, ...).
2631+
*
2632+
* Values already matched by `_ABSOLUTE_ICON_RE` (an `https:`/`data:`/
2633+
* `blob:` URL, or a root-relative `/path`) are returned unchanged — this
2634+
* is what lets a Python-supplied `data:image/svg+xml;base64,...` icon
2635+
* (see `_static/__init__.py`, `_ICON_META` / `_PROVIDER_META`) skip
2636+
* `_static/` and avoid a network request entirely.
2637+
*
2638+
* Otherwise `asset` is treated as a bare filename under the resolved
2639+
* static directory and joined with `new URL()`, the same normalization
2640+
* `_getSphinxDocsRootUrl()` already uses — one canonical way to build a
2641+
* `_static/`-relative URL instead of each call site re-deriving it.
2642+
*
2643+
* @param {string} asset - Bare filename ('checked.svg') or an
2644+
* already-absolute src.
2645+
* @param {string} [base] - Static-dir base to resolve against. Defaults
2646+
* to `getStaticPath()`; pass an already-computed local `staticPath`
2647+
* to skip recomputing it (cheap either way — `getStaticPath()` is
2648+
* memoized).
2649+
* @returns {string} An absolute (or already-absolute) URL, or `''` if
2650+
* `asset` is empty.
2651+
*/
2652+
function getStaticAssetUrl(asset, base) {
2653+
asset = String(asset || '');
2654+
if (!asset) { return ''; }
2655+
if (_ABSOLUTE_ICON_RE.test(asset)) { return asset; }
2656+
2657+
var staticBase = String(base || getStaticPath() || '_static').replace(/\/?$/, '/');
2658+
try {
2659+
return new URL(asset.replace(/^\/+/, ''), new URL(staticBase, document.baseURI)).href;
2660+
} catch (_e) {
2661+
return staticBase + asset.replace(/^\/+/, '');
2662+
}
2663+
}
2664+
26262665
// ── Widget placement ──────────────────────────────────────────────────────
26272666

26282667
function insertContainer(container, position) {
@@ -3032,7 +3071,7 @@
30323071
if (!iconEl || !textSpan) return;
30333072
var origSrc = iconEl.src;
30343073
var origText = textSpan.textContent;
3035-
iconEl.src = getStaticPath() + '/checked.svg';
3074+
iconEl.src = getStaticAssetUrl('checked.svg');
30363075
textSpan.textContent = 'Copied';
30373076
mainButton.classList.add('ai-assistant-button-success');
30383077
setTimeout(function () {
@@ -22578,12 +22617,13 @@ opts.jsonPayload + '\n' +
2257822617
var ns = window.AI_ASSISTANT = window.AI_ASSISTANT || {};
2257922618
if (ns._wired) { return; } // idempotent across re-injection
2258022619
ns._wired = true;
22581-
ns.config = function () { return _cfg(); };
22582-
ns.getStaticPath = getStaticPath; // asset base URL resolver
22583-
ns.icons = ICONS; // inline SVG map
22584-
ns.fetch = _fetch; // AI_COMPAT-aware fetch
22585-
ns.hapticFeedback = _hapticFeedback; // no-op where unsupported
22586-
ns.attachLongPress = _attachLongPress; // pointer long-press helper
22620+
ns.config = function () { return _cfg(); };
22621+
ns.getStaticPath = getStaticPath; // _static/ base path (no trailing slash)
22622+
ns.getStaticAssetUrl = getStaticAssetUrl; // filename or absolute src -> resolved URL
22623+
ns.icons = ICONS; // inline SVG map
22624+
ns.fetch = _fetch; // AI_COMPAT-aware fetch
22625+
ns.hapticFeedback = _hapticFeedback; // no-op where unsupported
22626+
ns.attachLongPress = _attachLongPress; // pointer long-press helper
2258722627
}());
2258822628

2258922629
// ── Bootstrap ─────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)