make better ux for model selecting

This commit is contained in:
2026-04-08 14:55:44 +02:00
parent 1aa629d1f8
commit c76a63043d
3 changed files with 360 additions and 26 deletions
+26 -5
View File
@@ -115,11 +115,32 @@
</div>
</div>
<div class="chat-header-actions">
<div class="model-select">
<label for="model">Model</label>
<select id="model">
<option disabled selected>Loading models...</option>
</select>
<div class="model-menu">
<button id="modelMenuButton" class="model-badge-button" type="button" aria-expanded="false" aria-controls="modelPopover">
<span class="model-badge-label">Equipped Model:</span>
<strong id="modelBadgeName">Loading...</strong>
</button>
<div id="modelPopover" class="model-popover hidden" role="dialog">
<div class="model-select">
<div class="model-select-header">
<label for="model">Model</label>
<span id="modelCount" class="model-count">Loading</span>
</div>
<div class="model-select-control">
<select id="model">
<option disabled selected>Loading models...</option>
</select>
<span class="model-select-chevron" aria-hidden="true"></span>
</div>
</div>
<div id="modelHelp" class="model-help" aria-live="polite">
<div class="model-help-header">
<strong id="modelHelpTitle">Choose a model</strong>
<span id="modelHelpBadge" class="model-help-badge">Waiting</span>
</div>
<p id="modelHelpSummary" class="model-help-summary">Pick a model to see what it is best suited for.</p>
</div>
</div>
</div>
<div class="persona-menu">
<button id="personaMenuButton" class="secondary-action" type="button" aria-expanded="false" aria-controls="personaPopover">
+89 -3
View File
@@ -1,6 +1,9 @@
const $ = (id) => document.getElementById(id);
const authDiv = $("auth"), chatDiv = $("chat"), authMsg = $("authMsg");
const messagesDiv = $("messages"), modelSelect = $("model");
const modelMenuButton = $("modelMenuButton"), modelPopover = $("modelPopover"), modelBadgeName = $("modelBadgeName");
const modelCount = $("modelCount");
const modelHelpTitle = $("modelHelpTitle"), modelHelpBadge = $("modelHelpBadge"), modelHelpSummary = $("modelHelpSummary");
const loginForm = $("loginForm"), registerForm = $("registerForm"), msgInput = $("msgInput"), sendBtn = $("send");
const loginUsernameInput = $("loginUsername"), loginPasswordInput = $("loginPassword");
const registerUsernameInput = $("registerUsername"), registerPasswordInput = $("registerPassword");
@@ -32,6 +35,11 @@ const themes = {
"confusity": {name: "Confusity", short: "CF"}
};
const defaultModelProfile = {
badge: "General",
summary: "General-purpose chat model. Start here if you are unsure which model to use."
};
async function request(url, data, method = "POST") {
try {
const res = await fetch(url, {method, headers: {"Content-Type": "application/json"}, body: data ? JSON.stringify(data) : undefined});
@@ -116,6 +124,67 @@ function updateContextRail() {
return modelSelect?.selectedOptions[0]?.textContent || "";
}
function getModelProfile(modelId, modelName) {
const haystack = `${modelId || ""} ${modelName || ""}`.toLowerCase();
if (haystack.includes("codellama") || haystack.includes("code")) {
return {
badge: "Coding",
summary: "Best for code generation, debugging, and technical explanations. Usually stronger on programming tasks than on creative chat."
};
}
if (haystack.includes("gemma")) {
return {
badge: "Fast",
summary: "A lighter general chat model. Good for quick answers and lower-latency replies, with less depth than larger models."
};
}
if (haystack.includes("dolphin")) {
return {
badge: "Chatty",
summary: "Instruction-following conversational model. Good for open-ended chat, brainstorming, and longer natural responses."
};
}
if (haystack.includes("mistral")) {
return {
badge: "Balanced",
summary: "Strong default for everyday use. Usually a good balance between speed, clarity, and response quality."
};
}
if (haystack.includes("catgirl") || haystack.includes("femboy") || haystack.includes("buenzli")) {
return {
badge: "Persona",
summary: "Specialized character-style model. Best for roleplay or stylized voice, and less reliable for factual or neutral answers."
};
}
return defaultModelProfile;
}
function updateModelHelp() {
if (!modelSelect || !modelHelpTitle || !modelHelpBadge || !modelHelpSummary) return;
const selectedOption = modelSelect.selectedOptions[0];
if (!selectedOption || selectedOption.disabled) {
if (modelCount) modelCount.textContent = "No models";
if (modelBadgeName) modelBadgeName.textContent = "Unavailable";
modelHelpBadge.dataset.badge = "waiting";
modelHelpTitle.textContent = "Choose a model";
modelHelpBadge.textContent = "Waiting";
modelHelpSummary.textContent = "Pick a model to see what it is best suited for.";
return;
}
if (modelCount) {
const totalModels = Array.from(modelSelect.options).filter((option) => !option.disabled).length;
modelCount.textContent = `${totalModels} ${totalModels === 1 ? "model" : "models"}`;
}
const modelId = selectedOption.value;
const modelName = selectedOption.textContent || modelId;
const profile = getModelProfile(modelId, modelName);
if (modelBadgeName) modelBadgeName.textContent = modelName;
modelHelpTitle.textContent = modelName;
modelHelpBadge.textContent = profile.badge;
modelHelpBadge.dataset.badge = profile.badge.toLowerCase();
modelHelpSummary.textContent = profile.summary;
}
function applyTheme(themeKey, persist = true) {
const nextTheme = themes[themeKey] ? themeKey : "fakegpt";
const theme = themes[nextTheme];
@@ -191,6 +260,10 @@ function openPersonaForm(persona = null, personaType = "assistant") {
function closePersonaForm() { personaModal.classList.add("hidden"); editingPersonaId = null; editingPersonaType = "assistant"; personaTypeSelect.disabled = false; setPersonaFormNotice(""); }
function closePersonaPopover() { personaPopover.classList.add("hidden"); personaMenuButton.setAttribute("aria-expanded", "false"); }
function closeModelPopover() {
modelPopover.classList.add("hidden");
modelMenuButton.setAttribute("aria-expanded", "false");
}
function renderPersonaList(items, activeId, listElement, personaType) {
listElement.innerHTML = "";
@@ -354,6 +427,7 @@ async function displayModels() {
const models = Array.isArray(res.models) ? res.models : null;
if (res.error || !models) {
modelSelect.innerHTML = "<option>Error loading models</option>";
updateModelHelp();
updateContextRail();
return setNotice("Models could not be loaded.", "error");
}
@@ -362,6 +436,7 @@ async function displayModels() {
const option = document.createElement("option");
option.value = model.model; option.textContent = model.name; option.selected = index === 0; modelSelect.appendChild(option);
});
updateModelHelp();
updateContextRail();
}
@@ -421,11 +496,20 @@ async function sendMessage() {
function togglePersonaPopover(event) {
event.stopPropagation();
closeModelPopover();
const isHidden = personaPopover.classList.contains("hidden");
personaPopover.classList.toggle("hidden", !isHidden);
personaMenuButton.setAttribute("aria-expanded", String(isHidden));
}
function toggleModelPopover(event) {
event.stopPropagation();
closePersonaPopover();
const isHidden = modelPopover.classList.contains("hidden");
modelPopover.classList.toggle("hidden", !isHidden);
modelMenuButton.setAttribute("aria-expanded", String(isHidden));
}
toggleButtons.forEach((btn) => btn.addEventListener("click", () => showAuthScreen(btn.dataset.target)));
loginForm.addEventListener("submit", (event) => { event.preventDefault(); void handleAuth("login", {username: loginUsernameInput.value.trim(), password: loginPasswordInput.value.trim()}); });
registerForm.addEventListener("submit", (event) => { event.preventDefault(); void handleAuth("register", {username: registerUsernameInput.value.trim(), password: registerPasswordInput.value.trim()}); });
@@ -441,13 +525,15 @@ msgInput.addEventListener("input", function () { this.style.height = "auto"; thi
newChatBtn.addEventListener("click", () => { void createNewChat(); });
renameChatBtn.addEventListener("click", () => { if (activeChatId) void renameChat(activeChatId); });
clearChatBtn.addEventListener("click", () => { if (activeChatId) void clearChat(activeChatId); }); exportChatBtn.addEventListener("click", exportCurrentChat);
chatSearchInput.addEventListener("input", renderChatList); modelSelect.addEventListener("change", updateContextRail);
chatSearchInput.addEventListener("input", renderChatList); modelSelect.addEventListener("change", () => { updateModelHelp(); updateContextRail(); });
newPersonaBtn.addEventListener("click", () => openPersonaForm()); newUserPersonaBtn.addEventListener("click", () => openPersonaForm(null, "user"));
clearPersonaBtn.addEventListener("click", () => { void clearPersona("assistant"); }); clearUserPersonaBtn.addEventListener("click", () => { void clearPersona("user"); });
personaForm.addEventListener("submit", (event) => { event.preventDefault(); void savePersona(); }); personaCloseBtn.addEventListener("click", closePersonaForm);
personaModal.addEventListener("click", (event) => { if (event.target === personaModal) closePersonaForm(); });
personaMenuButton.addEventListener("click", togglePersonaPopover); personaPopover.addEventListener("click", (event) => event.stopPropagation()); document.addEventListener("click", closePersonaPopover);
document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closePersonaPopover(); closePersonaForm(); } });
modelMenuButton.addEventListener("click", toggleModelPopover); modelPopover.addEventListener("click", (event) => event.stopPropagation());
personaMenuButton.addEventListener("click", togglePersonaPopover); personaPopover.addEventListener("click", (event) => event.stopPropagation());
document.addEventListener("click", () => { closeModelPopover(); closePersonaPopover(); });
document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closeModelPopover(); closePersonaPopover(); closePersonaForm(); } });
window.addEventListener("load", async () => {
applyTheme(localStorage.getItem("krishd-theme") || "fakegpt", false);
setNotice("Ready."); updateChatActionState(); await checkSession();
+245 -18
View File
@@ -807,15 +807,74 @@ select:focus {
.chat-header-actions {
justify-content: flex-end;
align-items: end;
flex-wrap: nowrap;
align-items: stretch;
flex-wrap: wrap;
}
.model-menu,
.persona-menu {
position: relative;
}
.model-badge-button {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 44px;
padding: 10px 14px;
border-radius: 999px;
background: rgba(164, 85, 45, 0.1);
color: var(--text);
border: 1px solid rgba(164, 85, 45, 0.18);
box-shadow: none;
}
.model-badge-label {
font-size: 0.74rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-muted);
}
.model-badge-button strong {
font-family: "Space Grotesk", sans-serif;
font-size: 0.95rem;
color: var(--text);
}
.model-badge-button[aria-expanded="true"] {
background: rgba(164, 85, 45, 0.16);
border-color: rgba(164, 85, 45, 0.28);
}
.model-popover {
position: absolute;
top: calc(100% + 10px);
right: 0;
width: min(420px, 88vw);
border-radius: var(--radius-lg);
padding: 16px;
box-shadow: var(--shadow);
display: grid;
gap: 14px;
z-index: 20;
background: var(--surface-strong);
border: 1px solid rgba(24, 32, 31, 0.08);
}
.model-select {
display: grid;
gap: 6px;
min-width: 180px;
align-self: end;
gap: 10px;
min-width: 0;
max-width: none;
align-self: stretch;
}
.model-select-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.model-select label {
@@ -825,6 +884,119 @@ select:focus {
color: var(--text-muted);
}
.model-count {
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--brand-strong);
}
.model-select-control {
position: relative;
}
.model-select-control select {
padding-right: 48px;
border-radius: 14px;
appearance: none;
background: rgba(255, 253, 249, 0.96);
border-color: rgba(24, 32, 31, 0.12);
font-weight: 700;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.model-select-chevron {
position: absolute;
right: 16px;
top: 50%;
width: 10px;
height: 10px;
border-right: 2px solid var(--text-soft);
border-bottom: 2px solid var(--text-soft);
transform: translateY(-65%) rotate(45deg);
pointer-events: none;
}
.model-help {
display: grid;
gap: 8px;
padding: 14px;
border-radius: 14px;
border: 1px solid rgba(24, 32, 31, 0.08);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.74), rgba(255, 252, 246, 0.92));
min-height: 92px;
}
.model-help-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.model-help-header strong {
font-family: "Space Grotesk", sans-serif;
font-size: 0.98rem;
color: var(--text);
}
.model-help-badge {
padding: 5px 10px;
border-radius: 999px;
background: rgba(164, 85, 45, 0.1);
border: 1px solid rgba(164, 85, 45, 0.18);
color: var(--brand-strong);
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
white-space: nowrap;
}
.model-help-badge[data-badge="coding"] {
background: rgba(38, 101, 214, 0.12);
border-color: rgba(38, 101, 214, 0.18);
color: #214ea7;
}
.model-help-badge[data-badge="fast"] {
background: rgba(15, 138, 103, 0.12);
border-color: rgba(15, 138, 103, 0.18);
color: var(--success);
}
.model-help-badge[data-badge="chatty"] {
background: rgba(168, 85, 247, 0.12);
border-color: rgba(168, 85, 247, 0.2);
color: #7c3aed;
}
.model-help-badge[data-badge="balanced"] {
background: rgba(217, 119, 6, 0.12);
border-color: rgba(217, 119, 6, 0.18);
color: #b45309;
}
.model-help-badge[data-badge="persona"] {
background: rgba(190, 24, 93, 0.12);
border-color: rgba(190, 24, 93, 0.18);
color: #be185d;
}
.model-help-badge[data-badge="waiting"] {
background: rgba(24, 32, 31, 0.06);
border-color: rgba(24, 32, 31, 0.1);
color: var(--text-soft);
}
.model-help-summary {
margin: 0;
color: var(--text-soft);
font-size: 0.86rem;
line-height: 1.5;
}
.context-rail,
.quick-prompts {
display: flex;
@@ -1076,18 +1248,11 @@ select:focus {
min-width: 88px;
}
.chat-header-actions .model-menu,
.chat-header-actions .persona-menu {
display: flex;
align-items: end;
align-self: end;
}
.chat-header-actions .model-select select {
min-width: 190px;
}
.persona-menu {
position: relative;
align-items: stretch;
align-self: stretch;
}
.persona-popover {
@@ -1468,7 +1633,10 @@ body[data-theme="fakegpt"] .hero-stat,
body[data-theme="fakegpt"] .summary-card,
body[data-theme="fakegpt"] .context-pill,
body[data-theme="fakegpt"] .prompt-chip,
body[data-theme="fakegpt"] .status {
body[data-theme="fakegpt"] .status,
body[data-theme="fakegpt"] .model-popover,
body[data-theme="fakegpt"] .model-select,
body[data-theme="fakegpt"] .model-help {
background: rgba(18, 24, 21, 0.9);
color: #e8fff7;
border-color: rgba(36, 255, 180, 0.12);
@@ -1515,12 +1683,32 @@ body[data-theme="fakegpt"] .persona-form .field {
body[data-theme="fakegpt"] input,
body[data-theme="fakegpt"] textarea,
body[data-theme="fakegpt"] select {
body[data-theme="fakegpt"] select,
body[data-theme="fakegpt"] .model-select-control select {
background: rgba(10, 15, 13, 0.96);
color: #e8fff7;
border-color: rgba(36, 255, 180, 0.12);
}
body[data-theme="fakegpt"] .model-count,
body[data-theme="fakegpt"] .model-help-badge[data-badge="waiting"] {
color: #87aea3;
}
body[data-theme="fakegpt"] .model-badge-button {
background: rgba(18, 24, 21, 0.96);
color: #d7fff3;
border-color: rgba(36, 255, 180, 0.16);
}
body[data-theme="fakegpt"] .model-badge-label {
color: #87aea3;
}
body[data-theme="fakegpt"] .model-select-chevron {
border-color: #b7d8ce;
}
body[data-theme="fakegpt"] option {
background: #101513;
color: #e8fff7;
@@ -1590,6 +1778,9 @@ body[data-theme="fraud"] .toggle.active {
body[data-theme="fraud"] input,
body[data-theme="fraud"] textarea,
body[data-theme="fraud"] select,
body[data-theme="fraud"] .model-popover,
body[data-theme="fraud"] .model-select,
body[data-theme="fraud"] .model-help,
body[data-theme="fraud"] .secondary-action,
body[data-theme="fraud"] .icon-button,
body[data-theme="fraud"] .session-chip,
@@ -1622,6 +1813,9 @@ body[data-theme="germini"] .market-shell,
body[data-theme="germini"] .settings-panel,
body[data-theme="germini"] .modal-card,
body[data-theme="germini"] .persona-popover,
body[data-theme="germini"] .model-popover,
body[data-theme="germini"] .model-select,
body[data-theme="germini"] .model-help,
body[data-theme="germini"] .hero-panel {
background: rgba(255, 255, 255, 0.32);
backdrop-filter: blur(22px);
@@ -1654,7 +1848,8 @@ body[data-theme="germini"] .settings-shell {
body[data-theme="germini"] input,
body[data-theme="germini"] textarea,
body[data-theme="germini"] select {
body[data-theme="germini"] select,
body[data-theme="germini"] .model-select-control select {
background: rgba(255, 255, 255, 0.4);
border-color: rgba(255, 255, 255, 0.42);
}
@@ -1712,6 +1907,9 @@ body[data-theme="slopilot"] .settings-nav,
body[data-theme="slopilot"] .settings-nav-item:not(.active),
body[data-theme="slopilot"] .settings-theme-note,
body[data-theme="slopilot"] .persona-form-intro,
body[data-theme="slopilot"] .model-popover,
body[data-theme="slopilot"] .model-select,
body[data-theme="slopilot"] .model-help,
body[data-theme="slopilot"] .persona-form .field {
background: #ffffff;
border-color: rgba(13, 104, 255, 0.12);
@@ -1732,6 +1930,9 @@ body[data-theme="beta-ai"] .chat-sidebar,
body[data-theme="beta-ai"] .messages-panel,
body[data-theme="beta-ai"] .input-shell,
body[data-theme="beta-ai"] .settings-panel,
body[data-theme="beta-ai"] .model-popover,
body[data-theme="beta-ai"] .model-select,
body[data-theme="beta-ai"] .model-help,
body[data-theme="beta-ai"] .market-shell {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.86), rgba(243, 238, 255, 0.92));
}
@@ -1761,6 +1962,9 @@ body[data-theme="confusity"] .messages-panel,
body[data-theme="confusity"] .input-shell,
body[data-theme="confusity"] .chat-sidebar,
body[data-theme="confusity"] .settings-panel,
body[data-theme="confusity"] .model-popover,
body[data-theme="confusity"] .model-select,
body[data-theme="confusity"] .model-help,
body[data-theme="confusity"] .market-shell {
background: #ffffff;
}
@@ -1970,6 +2174,12 @@ body[data-theme="beta-ai"] .persona-form .field {
.settings-theme-row {
grid-template-columns: 1fr;
}
.model-select {
width: 100%;
min-width: 0;
max-width: none;
}
}
@media (max-width: 820px) {
@@ -2037,6 +2247,11 @@ body[data-theme="beta-ai"] .persona-form .field {
.settings-shell .market-header-actions .secondary-action {
width: 100%;
}
.chat-header-actions {
display: grid;
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
@@ -2069,11 +2284,23 @@ body[data-theme="beta-ai"] .persona-form .field {
.chat-toolbar .icon-button,
.chat-header-actions .secondary-action,
.chat-header-actions .model-badge-button,
.chat-header-actions .model-select select {
width: 100%;
min-width: 0;
}
.model-popover {
left: 0;
right: auto;
width: min(92vw, 420px);
}
.model-help-header {
align-items: flex-start;
flex-direction: column;
}
.context-pill {
min-width: 0;
width: 100%;