diff --git a/interfaces/admin/static/chat.html b/interfaces/admin/static/chat.html
index e624c93f..09d62fe8 100644
--- a/interfaces/admin/static/chat.html
+++ b/interfaces/admin/static/chat.html
@@ -337,55 +337,87 @@
display: flex;
flex-wrap: wrap;
align-items: center;
- gap: 8px;
- padding: 2px 4px 8px;
+ gap: 4px;
+ padding: 0 2px 6px;
}
.chat-composer-chip,
.chat-composer-chip--select {
appearance: none;
-webkit-appearance: none;
- height: 30px;
- min-height: 30px;
- max-height: 30px;
- padding: 0 12px;
+ height: 28px;
+ min-height: 28px;
+ max-height: 28px;
+ padding: 0 10px;
border-radius: 999px;
- border: 1px solid var(--ds-border, rgba(148, 163, 184, 0.28));
- background-color: var(--ds-surface-2, #152033);
- color: var(--ds-text, #e2e8f0);
+ border: none;
+ background-color: transparent;
+ color: var(--ds-text-muted, rgba(148, 163, 184, 0.92));
font-size: 12px;
font-weight: 500;
line-height: 1;
box-sizing: border-box;
cursor: pointer;
+ transition: background-color 0.15s ease, color 0.15s ease;
}
.chat-composer-chip--select {
- min-width: 148px;
- max-width: 240px;
- padding-right: 28px;
- background-color: var(--ds-surface-2, #152033);
+ min-width: 0;
+ max-width: 220px;
+ width: auto;
+ padding-right: 22px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ background-color: transparent;
background-image:
- linear-gradient(45deg, transparent 50%, var(--ds-text-muted, rgba(148, 163, 184, 0.85)) 50%),
- linear-gradient(135deg, var(--ds-text-muted, rgba(148, 163, 184, 0.85)) 50%, transparent 50%);
+ linear-gradient(45deg, transparent 50%, var(--ds-text-muted, rgba(148, 163, 184, 0.7)) 50%),
+ linear-gradient(135deg, var(--ds-text-muted, rgba(148, 163, 184, 0.7)) 50%, transparent 50%);
background-position:
- calc(100% - 14px) calc(50% - 2px),
- calc(100% - 9px) calc(50% - 2px);
- background-size: 5px 5px, 5px 5px;
+ calc(100% - 10px) calc(50% - 1.5px),
+ calc(100% - 6px) calc(50% - 1.5px);
+ background-size: 4px 4px, 4px 4px;
background-repeat: no-repeat;
}
- .chat-composer-chip--select option {
- background-color: var(--ds-surface, #0f172a);
- color: var(--ds-text, #e2e8f0);
- }
.chat-composer-chip:hover,
.chat-composer-chip--select:hover {
- background-color: var(--ds-menu-hover-bg, rgba(148, 163, 184, 0.16));
- border-color: var(--ds-accent-border, rgba(94, 179, 255, 0.35));
+ background-color: var(--ds-menu-hover-bg, rgba(148, 163, 184, 0.12));
+ color: var(--ds-text, #e2e8f0);
}
.chat-composer-chip:focus-visible,
.chat-composer-chip--select:focus-visible {
outline: none;
- border-color: var(--ds-accent-border-strong, rgba(94, 179, 255, 0.5));
- box-shadow: 0 0 0 2px var(--ds-accent-soft, rgba(94, 179, 255, 0.2));
+ background-color: var(--ds-accent-soft, rgba(94, 179, 255, 0.14));
+ color: var(--ds-text, #e2e8f0);
+ box-shadow: none;
+ }
+ .chat-model-select {
+ position: relative;
+ display: inline-flex;
+ max-width: 100%;
+ }
+ .chat-model-select-pop {
+ border-radius: 12px;
+ padding: 6px;
+ min-width: 11rem;
+ max-width: min(320px, calc(100vw - 16px));
+ max-height: min(320px, 50vh);
+ overflow-y: auto;
+ border: 1px solid var(--ds-border, rgba(148, 163, 184, 0.18));
+ background: var(--ds-panel, var(--ds-surface, #0f172a));
+ box-shadow: var(--ds-pop-shadow, 0 12px 32px rgba(2, 6, 23, 0.45));
+ }
+ .chat-model-select-pop .chat-sess-menu-item {
+ border-radius: 8px;
+ padding: 9px 12px;
+ font-size: 13px;
+ line-height: 1.35;
+ }
+ .chat-model-select-pop .chat-sess-menu-item--active {
+ background: var(--ds-accent-soft, rgba(59, 130, 246, 0.18));
+ color: var(--ds-text, #e2e8f0);
+ font-weight: 600;
+ }
+ .chat-model-select-pop .chat-sess-menu-item--active:hover {
+ background: var(--ds-accent-soft, rgba(59, 130, 246, 0.22));
}
.chat-composer-shell {
border-radius: 12px;
diff --git a/interfaces/admin/static/chat.js b/interfaces/admin/static/chat.js
index d9eb5073..fef673c5 100644
--- a/interfaces/admin/static/chat.js
+++ b/interfaces/admin/static/chat.js
@@ -3473,11 +3473,71 @@ async function renderChatUi() {
});
let globalMenuModeValue = String(localStorage.getItem(CHAT_USER_MENU_MODE_KEY) || MAIN_MODE_VALUE).toLowerCase();
if (globalMenuModeValue === "comprehensive" || globalMenuModeValue === "main") globalMenuModeValue = MAIN_MODE_VALUE;
- const modelSelect = el("select", {
+ const modelSelectBtn = el("button", {
+ type: "button",
class: "chat-composer-chip chat-composer-chip--select",
title: t("chat.activeModelLabel"),
+ text: "-",
});
+ const modelSelectWrap = el("div", { class: "chat-model-select" }, [modelSelectBtn]);
let modelSelectNameToId = new Map();
+ let modelSelectOptions = [];
+ let modelSelectedKey = "";
+ const syncModelSelectBtn = () => {
+ const hit = modelSelectOptions.find((o) => o.key === modelSelectedKey);
+ modelSelectBtn.textContent = String((hit && hit.label) || "-");
+ };
+ const openModelSelectMenu = (anchorEl) => {
+ dismissChatMenus();
+ const items = modelSelectOptions.length
+ ? modelSelectOptions.map((opt) =>
+ el("button", {
+ type: "button",
+ class:
+ "chat-sess-menu-item" +
+ (opt.key === modelSelectedKey ? " chat-sess-menu-item--active" : ""),
+ text: opt.label,
+ onclick: async (ev) => {
+ ev.stopPropagation();
+ dismissChatMenus();
+ const key = String(opt.key || "").trim();
+ if (!key || key === modelSelectedKey) return;
+ modelSelectedKey = key;
+ syncModelSelectBtn();
+ const pid = String(modelSelectNameToId.get(key) || "").trim();
+ if (!pid) return;
+ try {
+ await apiPost("/admin/api/models/active", { profile_id: pid });
+ await refreshActiveModelText();
+ } catch (e) {
+ statusBar.textContent = `${t("chat.error")}: ${String(e)}`;
+ }
+ },
+ }),
+ )
+ : [el("div", { class: "chat-sess-menu-item muted", text: "-" })];
+ const menu = el("div", { class: "chat-sess-menu-pop chat-model-select-pop u-pop-menu" }, items);
+ menu.style.position = "fixed";
+ menu.style.zIndex = "300";
+ const rect = (anchorEl || modelSelectBtn).getBoundingClientRect();
+ attachChatMenuDismiss(menu);
+ const mrect = menu.getBoundingClientRect();
+ const pad = 8;
+ let left = rect.left;
+ let top = rect.bottom + 6;
+ if (top + mrect.height > window.innerHeight - pad) {
+ top = Math.max(pad, rect.top - 6 - mrect.height);
+ }
+ left = Math.max(pad, Math.min(left, window.innerWidth - pad - mrect.width));
+ menu.style.left = `${left}px`;
+ menu.style.top = `${top}px`;
+ menu.style.minWidth = `${Math.max(168, Math.ceil(rect.width))}px`;
+ };
+ modelSelectBtn.addEventListener("click", (ev) => {
+ ev.stopPropagation();
+ if (modelSelectBtn.disabled) return;
+ openModelSelectMenu(ev.currentTarget);
+ });
const normalizeExecutionMode = (v) => {
const raw = String(v || "").trim().toLowerCase();
return raw === EXECUTION_MODE_PLAN ? EXECUTION_MODE_PLAN : EXECUTION_MODE_AGENT;
@@ -3585,8 +3645,8 @@ async function renderChatUi() {
const activeModelText = el("span", { class: "muted", text: `${t("chat.activeModelLabel")}: -` });
const applyModelSelectorVisibility = (modelsState) => {
const show = !(modelsState && modelsState.chat_model_selector_visible === false);
- modelSelect.style.display = show ? "" : "none";
- modelSelect.disabled = !show;
+ modelSelectWrap.style.display = show ? "" : "none";
+ modelSelectBtn.disabled = !show;
};
const refreshActiveModelText = async () => {
try {
@@ -3597,8 +3657,8 @@ async function renderChatUi() {
const p = profiles.find((x) => String(x.id || "") === aid) || null;
const modelName = String((p && p.model) || "").trim() || "-";
activeModelText.textContent = `${t("chat.activeModelLabel")}: ${modelName}`;
- modelSelect.innerHTML = "";
modelSelectNameToId = new Map();
+ modelSelectOptions = [];
const usedKeys = new Set();
profiles.forEach((row) => {
const pid = String(row && row.id ? row.id : "");
@@ -3613,38 +3673,30 @@ async function renderChatUi() {
}
usedKeys.add(key);
modelSelectNameToId.set(key, pid);
- const display = String(row.model || row.name || pid);
- modelSelect.appendChild(el("option", { value: key, text: display }));
+ modelSelectOptions.push({
+ key,
+ id: pid,
+ label: String(row.model || row.name || pid),
+ });
});
if (p) {
- const rawName = String(p.name || "").trim();
const firstKey = Array.from(modelSelectNameToId.keys()).find((k) => modelSelectNameToId.get(k) === aid) || "";
- const key = firstKey || rawName;
- if (key && Array.from(modelSelect.options).some((o) => String(o.value || "") === key)) {
- modelSelect.value = key;
- }
- } else if (modelSelect.options.length > 0) {
- modelSelect.value = String(modelSelect.options[0].value || "");
+ modelSelectedKey = firstKey || "";
+ } else if (modelSelectOptions.length > 0) {
+ modelSelectedKey = String(modelSelectOptions[0].key || "");
+ } else {
+ modelSelectedKey = "";
}
+ syncModelSelectBtn();
} catch (_) {
applyModelSelectorVisibility(null);
activeModelText.textContent = `${t("chat.activeModelLabel")}: -`;
- modelSelect.innerHTML = "";
- modelSelect.appendChild(el("option", { value: "", text: "-" }));
- modelSelect.value = "";
+ modelSelectNameToId = new Map();
+ modelSelectOptions = [];
+ modelSelectedKey = "";
+ syncModelSelectBtn();
}
};
- modelSelect.addEventListener("change", async () => {
- const selectedKey = String(modelSelect.value || "").trim();
- const pid = String(modelSelectNameToId.get(selectedKey) || "").trim();
- if (!pid) return;
- try {
- await apiPost("/admin/api/models/active", { profile_id: pid });
- await refreshActiveModelText();
- } catch (e) {
- statusBar.textContent = `${t("chat.error")}: ${String(e)}`;
- }
- });
const loadSessionModePreference = async () => {
if (!activeId) {
setExecutionMode(currentExecutionMode, { persistLocal: true, saveSession: false });
@@ -3781,7 +3833,7 @@ async function renderChatUi() {
});
const composerMetaBar = el("div", { class: "chat-composer-meta" }, [
execSelectWrap,
- modelSelect,
+ modelSelectWrap,
compressBtn,
]);
diff --git a/interfaces/admin/static/theme-presets.css b/interfaces/admin/static/theme-presets.css
index 136e761b..ee6e4fb0 100644
--- a/interfaces/admin/static/theme-presets.css
+++ b/interfaces/admin/static/theme-presets.css
@@ -321,32 +321,45 @@ body.theme-ds-body[data-admin-theme="light"] .chat-composer {
body.theme-ds-body[data-admin-theme="light"] .chat-composer-chip,
body.theme-ds-body[data-admin-theme="light"] .chat-composer-chip--select {
- background-color: #ffffff;
- border-color: rgba(15, 23, 42, 0.16);
- color: #0f172a;
+ background-color: transparent;
+ border: none;
+ color: rgba(15, 23, 42, 0.62);
}
body.theme-ds-body[data-admin-theme="light"] .chat-composer-chip--select {
- background-color: #ffffff;
+ background-color: transparent;
background-image:
- linear-gradient(45deg, transparent 50%, rgba(15, 23, 42, 0.55) 50%),
- linear-gradient(135deg, rgba(15, 23, 42, 0.55) 50%, transparent 50%);
+ linear-gradient(45deg, transparent 50%, rgba(15, 23, 42, 0.45) 50%),
+ linear-gradient(135deg, rgba(15, 23, 42, 0.45) 50%, transparent 50%);
background-position:
- calc(100% - 14px) calc(50% - 2px),
- calc(100% - 9px) calc(50% - 2px);
- background-size: 5px 5px, 5px 5px;
+ calc(100% - 10px) calc(50% - 1.5px),
+ calc(100% - 6px) calc(50% - 1.5px);
+ background-size: 4px 4px, 4px 4px;
background-repeat: no-repeat;
}
-body.theme-ds-body[data-admin-theme="light"] .chat-composer-chip--select option {
- background-color: #ffffff;
+body.theme-ds-body[data-admin-theme="light"] .chat-model-select-pop {
+ background: #ffffff;
+ border-color: rgba(15, 23, 42, 0.12);
+ box-shadow: 0 12px 32px rgba(15, 23, 42, 0.12);
+}
+
+body.theme-ds-body[data-admin-theme="light"] .chat-model-select-pop .chat-sess-menu-item--active {
+ background: rgba(37, 99, 235, 0.12);
color: #0f172a;
}
body.theme-ds-body[data-admin-theme="light"] .chat-composer-chip:hover,
body.theme-ds-body[data-admin-theme="light"] .chat-composer-chip--select:hover {
- background-color: #f8fafc;
- border-color: rgba(37, 99, 235, 0.35);
+ background-color: rgba(15, 23, 42, 0.05);
+ color: #0f172a;
+}
+
+body.theme-ds-body[data-admin-theme="light"] .chat-composer-chip:focus-visible,
+body.theme-ds-body[data-admin-theme="light"] .chat-composer-chip--select:focus-visible {
+ background-color: rgba(37, 99, 235, 0.1);
+ color: #0f172a;
+ box-shadow: none;
}
body.theme-ds-body[data-admin-theme="light"] .chat-composer-shell {