refactor model profile logic by moving rules to modelProfiles.js and updating relevant references
This commit is contained in:
@@ -14,8 +14,3 @@ export const themes = {
|
|||||||
"beta-ai": {name: "Beta AI", short: "BA"},
|
"beta-ai": {name: "Beta AI", short: "BA"},
|
||||||
"confusity": {name: "Confusity", short: "CF"}
|
"confusity": {name: "Confusity", short: "CF"}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultModelProfile = {
|
|
||||||
badge: "General",
|
|
||||||
summary: "General-purpose chat model. Start here if you are unsure which model to use."
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
export const defaultModelProfile = {
|
||||||
|
badge: "General",
|
||||||
|
summary: "Best for general questions, drafting, and everyday conversation. Use a specialized model when you need stronger coding, reasoning, or roleplay behavior."
|
||||||
|
};
|
||||||
|
|
||||||
|
const modelProfileRules = [
|
||||||
|
{
|
||||||
|
matches: ["catgirl"],
|
||||||
|
badge: "Roleplay",
|
||||||
|
summary: "Best for playful catgirl character roleplay and expressive, in-character conversation. Not intended for neutral factual answers or technical work."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["femboy"],
|
||||||
|
badge: "Roleplay",
|
||||||
|
summary: "Best for personality-led roleplay, casual conversation, and maintaining a stylized character voice. Not intended for precise research or technical tasks."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["buenzli", "bünzli"],
|
||||||
|
badge: "Roleplay",
|
||||||
|
summary: "Best for Swiss-flavored character roleplay, local humor, and casual conversation. Choose a general model for neutral writing or factual questions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["deepseek-r1", "deepseek-r1-distill"],
|
||||||
|
badge: "Reasoning",
|
||||||
|
summary: "Best for problems that benefit from deliberate reasoning, such as logic, mathematics, planning, and difficult technical questions. It can be slower and more verbose for simple chat."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["codellama", "code-llama", "deepseek-coder", "qwen2.5-coder", "qwen3-coder", "starcoder", "codestral", "coder"],
|
||||||
|
badge: "Coding",
|
||||||
|
summary: "Best for writing, explaining, reviewing, and debugging code. Choose a general model for creative conversation or broad non-technical questions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["dolphin"],
|
||||||
|
badge: "Creative",
|
||||||
|
summary: "Best for open-ended conversation, brainstorming, creative writing, and roleplay. Its answers can be less restrained and less dependable for factual or safety-critical topics."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["mixtral"],
|
||||||
|
badge: "Versatile",
|
||||||
|
summary: "Best for detailed writing, summarization, multilingual tasks, and general problem-solving. It is a capable all-rounder but may respond more slowly than smaller models."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["mistral"],
|
||||||
|
badge: "Balanced",
|
||||||
|
summary: "Best for everyday questions, concise writing, summaries, and light coding. A strong default when you want a balance of speed and answer quality."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["gemma"],
|
||||||
|
badge: "Efficient",
|
||||||
|
summary: "Best for quick answers, rewriting, summarization, and lightweight general chat. Larger or reasoning-focused models are better for complex multi-step problems."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["qwen"],
|
||||||
|
badge: "Versatile",
|
||||||
|
summary: "Best for multilingual conversation, structured writing, instruction following, and general technical tasks. Coder variants are the better choice for code-heavy work."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["llama"],
|
||||||
|
badge: "General",
|
||||||
|
summary: "Best for general conversation, writing, summarization, and broad knowledge tasks. Specialized coding or reasoning models may perform better in those areas."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matches: ["phi"],
|
||||||
|
badge: "Compact",
|
||||||
|
summary: "Best for fast, focused questions, short summaries, and lightweight assistance. Its compact size makes it less suitable for demanding reasoning or long, nuanced responses."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function describeBuild(model) {
|
||||||
|
const parameterSize = String(model?.details?.parameter_size || "").trim();
|
||||||
|
const quantization = String(model?.details?.quantization_level || "").trim();
|
||||||
|
const details = [parameterSize, quantization].filter(Boolean);
|
||||||
|
return details.length ? ` Available build: ${details.join(", ")}.` : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelProfile(model = {}) {
|
||||||
|
const modelId = String(model.model || model.name || "");
|
||||||
|
const modelName = String(model.name || model.model || "");
|
||||||
|
const family = String(model?.details?.family || "");
|
||||||
|
const families = Array.isArray(model?.details?.families) ? model.details.families.join(" ") : "";
|
||||||
|
const haystack = `${modelId} ${modelName} ${family} ${families}`.toLowerCase();
|
||||||
|
const match = modelProfileRules.find((profile) => profile.matches.some((token) => haystack.includes(token)));
|
||||||
|
const profile = match || defaultModelProfile;
|
||||||
|
|
||||||
|
return {
|
||||||
|
badge: profile.badge,
|
||||||
|
summary: `${profile.summary}${describeBuild(model)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
+1
-1
@@ -424,6 +424,6 @@
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js" defer></script>
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js" defer></script>
|
||||||
<script type="module" src="script.js?v=20260908-model-controls"></script>
|
<script type="module" src="script.js?v=20260908-model-descriptions"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+12
-41
@@ -1,6 +1,7 @@
|
|||||||
import {del, get, post, put, stream} from "./app/api.js";
|
import {del, get, post, put, stream} from "./app/api.js";
|
||||||
import {closeChatDrawer, openChatDrawer} from "./app/chatDrawer.js";
|
import {closeChatDrawer, openChatDrawer} from "./app/chatDrawer.js";
|
||||||
import {defaultModelProfile, onboardingPrompts, requestedChatId} from "./app/constants.js";
|
import {onboardingPrompts, requestedChatId} from "./app/constants.js";
|
||||||
|
import {getModelProfile} from "./app/modelProfiles.js";
|
||||||
import {applyTheme as applySharedTheme, applyThemeMode as applySharedThemeMode, readStoredAppearance} from "./app/themeController.js";
|
import {applyTheme as applySharedTheme, applyThemeMode as applySharedThemeMode, readStoredAppearance} from "./app/themeController.js";
|
||||||
import {
|
import {
|
||||||
activeChatTitle,
|
activeChatTitle,
|
||||||
@@ -129,6 +130,7 @@ let onboardingStep = 1, onboardingIntent = "ask";
|
|||||||
let chatLoadingDepth = 0;
|
let chatLoadingDepth = 0;
|
||||||
let chatActivityDepth = 0;
|
let chatActivityDepth = 0;
|
||||||
let messageRetryState = new Map();
|
let messageRetryState = new Map();
|
||||||
|
let availableModelsById = new Map();
|
||||||
|
|
||||||
|
|
||||||
function setAuthMessage(message, state = "") { authMsg.textContent = message; authMsg.className = state ? `status ${state}` : "status"; }
|
function setAuthMessage(message, state = "") { authMsg.textContent = message; authMsg.className = state ? `status ${state}` : "status"; }
|
||||||
@@ -918,41 +920,6 @@ function renderModelSection() {
|
|||||||
return modelSelect?.selectedOptions[0]?.textContent || "";
|
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() {
|
function updateModelHelp() {
|
||||||
if (!modelSelect || !modelHelpTitle || !modelHelpBadge || !modelHelpSummary) return;
|
if (!modelSelect || !modelHelpTitle || !modelHelpBadge || !modelHelpSummary) return;
|
||||||
const selectedOption = modelSelect.selectedOptions[0];
|
const selectedOption = modelSelect.selectedOptions[0];
|
||||||
@@ -972,7 +939,7 @@ function updateModelHelp() {
|
|||||||
}
|
}
|
||||||
const modelId = selectedOption.value;
|
const modelId = selectedOption.value;
|
||||||
const modelName = selectedOption.textContent || modelId;
|
const modelName = selectedOption.textContent || modelId;
|
||||||
const profile = getModelProfile(modelId, modelName);
|
const profile = getModelProfile(availableModelsById.get(modelId) || {model: modelId, name: modelName});
|
||||||
if (modelBadgeName) modelBadgeName.textContent = modelName;
|
if (modelBadgeName) modelBadgeName.textContent = modelName;
|
||||||
if (headerModelName) headerModelName.textContent = modelName;
|
if (headerModelName) headerModelName.textContent = modelName;
|
||||||
modelHelpTitle.textContent = modelName;
|
modelHelpTitle.textContent = modelName;
|
||||||
@@ -1810,22 +1777,26 @@ async function displayModels() {
|
|||||||
const res = await get("/models");
|
const res = await get("/models");
|
||||||
const models = Array.isArray(res.models) ? res.models : null;
|
const models = Array.isArray(res.models) ? res.models : null;
|
||||||
if (res.error || !models) {
|
if (res.error || !models) {
|
||||||
|
availableModelsById = new Map();
|
||||||
modelSelect.innerHTML = "<option>Error loading models</option>";
|
modelSelect.innerHTML = "<option>Error loading models</option>";
|
||||||
renderModelSection();
|
renderModelSection();
|
||||||
return setNotice("Models could not be loaded.", "error");
|
return setNotice("Models could not be loaded.", "error");
|
||||||
}
|
}
|
||||||
modelSelect.innerHTML = "";
|
modelSelect.innerHTML = "";
|
||||||
|
availableModelsById = new Map(models.map((model) => [String(model.model || model.name || ""), model]));
|
||||||
const preferredModelId = "mistral:latest";
|
const preferredModelId = "mistral:latest";
|
||||||
const preferredIndex = models.findIndex((model) => model.model === preferredModelId);
|
const preferredIndex = models.findIndex((model) => (model.model || model.name) === preferredModelId);
|
||||||
models.forEach((model, index) => {
|
models.forEach((model, index) => {
|
||||||
|
const modelId = String(model.model || model.name || "");
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = model.model;
|
option.value = modelId;
|
||||||
option.textContent = model.name;
|
option.textContent = model.name || modelId;
|
||||||
|
option.title = getModelProfile(model).summary;
|
||||||
option.selected = preferredIndex >= 0 ? index === preferredIndex : index === 0;
|
option.selected = preferredIndex >= 0 ? index === preferredIndex : index === 0;
|
||||||
modelSelect.appendChild(option);
|
modelSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
const activeChat = getChatById(activeChatId);
|
const activeChat = getChatById(activeChatId);
|
||||||
if (activeChat?.preferred_model && models.some((model) => model.model === activeChat.preferred_model)) {
|
if (activeChat?.preferred_model && models.some((model) => (model.model || model.name) === activeChat.preferred_model)) {
|
||||||
modelSelect.value = activeChat.preferred_model;
|
modelSelect.value = activeChat.preferred_model;
|
||||||
}
|
}
|
||||||
renderModelSection();
|
renderModelSection();
|
||||||
|
|||||||
+2
-1
@@ -24,7 +24,8 @@ const files = [
|
|||||||
"public/app/api.js",
|
"public/app/api.js",
|
||||||
"public/app/chatDrawer.js",
|
"public/app/chatDrawer.js",
|
||||||
"public/app/constants.js",
|
"public/app/constants.js",
|
||||||
"public/app/dom.js"
|
"public/app/dom.js",
|
||||||
|
"public/app/modelProfiles.js"
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
|||||||
Reference in New Issue
Block a user