Right-click or chevron-click on the sidebar strip's AI button now opens a borderless overlay instead of a plain native menu: enabled engines are listed with their bundled icons, the current engine is highlighted with the acid accent + check, and a settings row at the bottom jumps to Search settings. Clicking an engine selects it as the default and opens it in the left panel in one click. Bundled engine icons gained an svg-first fallback: engine-icons/<id>.svg is picked up before <id>.png so plain color-coded letter tiles ship alongside the existing bitmap favicons. Added tiles for ChatGPT, Grok, Gemini and Phind — each a simple initial-on-color mark.
106 lines
5.2 KiB
HTML
106 lines
5.2 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Pick AI</title>
|
|
<style>
|
|
:root {
|
|
--bg: #141a24; --card: #1a2130; --card-hi: #212a3b; --line: #2a3344;
|
|
--ink: #e7eaf1; --mut: #8b98a9; --acid: #d6ff3d; --acid-tint: #d6ff3d1a;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
html, body { margin: 0; padding: 0; background: transparent; }
|
|
body {
|
|
display: inline-block;
|
|
font: 13px/1.3 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
color: var(--ink);
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
}
|
|
.pop {
|
|
display: flex; flex-direction: column;
|
|
background: var(--bg); border: 1px solid var(--line); border-radius: 12px;
|
|
box-shadow: 0 12px 40px rgba(0,0,0,.5), 0 2px 8px rgba(0,0,0,.4);
|
|
overflow: hidden; width: 240px;
|
|
}
|
|
.hdr { padding: 10px 14px 6px; font-size: 11px; font-weight: 600;
|
|
color: var(--mut); text-transform: uppercase; letter-spacing: .5px;
|
|
border-bottom: 1px solid var(--line); }
|
|
.list { display: flex; flex-direction: column; padding: 4px; }
|
|
.row { display: flex; align-items: center; gap: 10px;
|
|
padding: 8px 10px; border-radius: 8px; cursor: pointer; position: relative;
|
|
transition: background .08s; }
|
|
.row:hover { background: var(--card-hi); }
|
|
.row.cur { background: var(--acid-tint); }
|
|
.row.cur::before { content: ""; position: absolute; left: 2px; top: 20%; bottom: 20%;
|
|
width: 3px; border-radius: 2px; background: var(--acid); }
|
|
.ico { flex: none; width: 28px; height: 28px; border-radius: 8px; overflow: hidden;
|
|
display: grid; place-items: center; background: #0b0e14; }
|
|
.ico img, .ico svg { width: 100%; height: 100%; display: block; }
|
|
.ico .letter { font-weight: 800; font-size: 15px; color: var(--acid); }
|
|
.name { flex: 1; font-weight: 600; font-size: 13px; color: var(--ink); }
|
|
.check { flex: none; width: 14px; height: 14px; color: var(--acid); opacity: 0; }
|
|
.row.cur .check { opacity: 1; }
|
|
.footer { border-top: 1px solid var(--line); padding: 4px; }
|
|
.footer .row { color: var(--mut); font-weight: 500; }
|
|
.footer .ico { background: transparent; }
|
|
.footer .ico svg { width: 18px; height: 18px; color: var(--mut); }
|
|
.empty { padding: 16px; text-align: center; color: var(--mut); font-size: 12px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="pop" id="pop">
|
|
<div class="hdr">AI engine</div>
|
|
<div class="list" id="list"></div>
|
|
<div class="footer">
|
|
<div class="row" id="settingsRow">
|
|
<div class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></div>
|
|
<div class="name">Pick AI engines…</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
const api = window.llmPicker;
|
|
const list = document.getElementById("list");
|
|
const esc = (s) => String(s || "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
function render(state) {
|
|
const engines = state.engines || [];
|
|
const cur = state.current;
|
|
list.innerHTML = "";
|
|
if (!engines.length) {
|
|
list.innerHTML = '<div class="empty">No AI engine enabled.</div>';
|
|
return;
|
|
}
|
|
for (const e of engines) {
|
|
const row = document.createElement("div");
|
|
row.className = "row" + (e.id === cur ? " cur" : "");
|
|
const ico = e.icon
|
|
? '<img src="' + esc(e.icon) + '" alt="">'
|
|
: '<div class="letter">' + esc((e.name || "?").slice(0, 1).toUpperCase()) + '</div>';
|
|
row.innerHTML =
|
|
'<div class="ico">' + ico + '</div>' +
|
|
'<div class="name">' + esc(e.name) + '</div>' +
|
|
'<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
row.addEventListener("click", () => api.pick(e.id));
|
|
list.appendChild(row);
|
|
}
|
|
}
|
|
document.getElementById("settingsRow").addEventListener("click", () => api.settings());
|
|
document.addEventListener("keydown", (e) => { if (e.key === "Escape") api.close(); });
|
|
window.addEventListener("blur", () => api.close());
|
|
api.onState(render);
|
|
api.ready();
|
|
// Report rendered size so the main process can size the WebContentsView
|
|
// to match the pop's natural content. Measuring documentElement's scroll
|
|
// size — the pop's own getBoundingClientRect may still be clipped by the
|
|
// view bounds until the host resizes to our ask.
|
|
function reportSize() {
|
|
const w = document.documentElement.scrollWidth;
|
|
const h = document.documentElement.scrollHeight;
|
|
api.resize(w, h);
|
|
}
|
|
new ResizeObserver(reportSize).observe(document.body);
|
|
requestAnimationFrame(reportSize);
|
|
</script>
|
|
</body>
|
|
</html>
|