{% trans "Prompt système de l'assista
document.querySelectorAll('.toolbar-btn--tools').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
- document.querySelectorAll('.model-dropdown.open').forEach(function(d) { d.classList.remove('open'); });
var dropdown = this.nextElementSibling;
var isOpen = dropdown.classList.contains('open');
// Close all dropdowns first
@@ -2037,11 +2034,6 @@
{% trans "Prompt système de l'assista
d.classList.remove('open');
});
}
- if (!e.target.closest('.model-dropdown-wrap')) {
- document.querySelectorAll('.model-dropdown.open').forEach(function(d) {
- d.classList.remove('open');
- });
- }
});
// Toggle tool on dropdown item click
@@ -2097,7 +2089,7 @@
{% trans "Prompt système de l'assista
if (!contextBarItems) return;
contextBarItems.innerHTML = '';
var toolIds = Object.keys(activeTools);
- if (toolIds.length === 0 && selectedModel === 'medium') {
+ if (toolIds.length === 0) {
contextBarItems.innerHTML = '{% trans "Aucun outil actif" %}';
return;
}
@@ -2113,108 +2105,11 @@
{% trans "Prompt système de l'assista
item.innerHTML = (TOOL_ICONS[toolId] || TOOL_ICON_SVG_DEFAULT) + ' ' + escapeHtml(TOOL_LABELS[toolId] || toolId);
contextBarItems.appendChild(item);
});
- // Show model if non-default
- if (selectedModel !== 'medium') {
- if (toolIds.length > 0) {
- var sep = document.createElement('span');
- sep.className = 'context-bar-sep';
- contextBarItems.appendChild(sep);
- }
- var modelName = '';
- MODEL_CATEGORIES.forEach(function(cat) {
- cat.models.forEach(function(m) { if (m.id === selectedModel) modelName = m.name; });
- });
- var item = document.createElement('span');
- item.className = 'context-bar-item';
- item.innerHTML = MISTRAL_SVG_SMALL + ' ' + escapeHtml(modelName);
- contextBarItems.appendChild(item);
- }
- }
-
- // ── Model selector dropdown ──
- var MISTRAL_SVG = '';
- var MISTRAL_SVG_SMALL = '';
- var MODEL_CATEGORIES = [
- { label: '{% trans "Textuel" %}', models: [
- { id: 'medium', name: 'Medium' },
- { id: 'large', name: 'Large' },
- ]},
- { label: '{% trans "Codage" %}', models: [
- { id: 'codestral', name: 'Codestral' },
- ]},
- { label: '{% trans "Raisonnement" %}', models: [
- { id: 'magistral', name: 'Magistral' },
- ]},
- ];
- var selectedModel = 'medium';
-
- function buildModelDropdown(container) {
- container.innerHTML = '';
- MODEL_CATEGORIES.forEach(function(cat) {
- var row = document.createElement('div');
- row.className = 'model-category';
- var label = document.createElement('span');
- label.className = 'model-category-label';
- label.textContent = cat.label + ' :';
- row.appendChild(label);
- var opts = document.createElement('div');
- opts.className = 'model-category-options';
- cat.models.forEach(function(m) {
- var btn = document.createElement('button');
- btn.className = 'model-option' + (m.id === selectedModel ? ' active' : '');
- btn.type = 'button';
- btn.setAttribute('data-model', m.id);
- btn.innerHTML = MISTRAL_SVG + ' ' + m.name;
- btn.addEventListener('click', function(e) {
- e.stopPropagation();
- selectedModel = m.id;
- syncModelUI();
- document.querySelectorAll('.model-dropdown.open').forEach(function(d) {
- d.classList.remove('open');
- });
- });
- opts.appendChild(btn);
- });
- row.appendChild(opts);
- container.appendChild(row);
- });
- }
-
- function syncModelUI() {
- var name = '';
- MODEL_CATEGORIES.forEach(function(cat) {
- cat.models.forEach(function(m) {
- if (m.id === selectedModel) name = m.name;
- });
- });
- document.querySelectorAll('.model-label').forEach(function(el) {
- el.textContent = name;
- });
- document.querySelectorAll('.model-option').forEach(function(opt) {
- opt.classList.toggle('active', opt.getAttribute('data-model') === selectedModel);
- });
- syncContextBar();
}
- // Build dropdowns on init
- document.querySelectorAll('.model-dropdown').forEach(buildModelDropdown);
-
- // Toggle model dropdown
- document.querySelectorAll('.toolbar-btn--model').forEach(function(btn) {
- btn.addEventListener('click', function(e) {
- e.stopPropagation();
- document.querySelectorAll('.tools-dropdown.open').forEach(function(d) { d.classList.remove('open'); });
- var dropdown = this.nextElementSibling;
- var isOpen = dropdown.classList.contains('open');
- document.querySelectorAll('.model-dropdown.open').forEach(function(d) {
- d.classList.remove('open');
- });
- if (!isOpen) {
- buildModelDropdown(dropdown);
- dropdown.classList.add('open');
- }
- });
- });
+ // ── Model display (read-only, shows configured model) ──
+ // Model selector will become interactive when multi-model support is added.
+ // For now, the button just shows the active model name from config.yaml.
// ── Conversation menu dropdown ──
var convMenuWrap = document.getElementById('convMenuWrap');
diff --git a/chat/views.py b/chat/views.py
index 9c7c5fa..ddbe1e9 100644
--- a/chat/views.py
+++ b/chat/views.py
@@ -8,6 +8,8 @@
from django.utils.translation import gettext as _
from django.views.decorators.http import require_POST
+from django.conf import settings
+
from score.ratelimit import ratelimit
from score.utils import parse_json_body
from ingestion.models import Document
@@ -49,6 +51,16 @@ def chat_home(request):
.exists()
)
+ # Active chat model name for UI display
+ llm_config = settings.LLM_CONFIG
+ provider = llm_config.get("provider", "openai")
+ if provider == "azure":
+ chat_model_name = llm_config.get("azure", {}).get("chat_deployment", "")
+ elif provider == "azure_mistral":
+ chat_model_name = llm_config.get("azure_mistral", {}).get("chat_model", "")
+ else:
+ chat_model_name = llm_config.get("openai", {}).get("chat_model", "")
+
return render(
request,
"chat/home.html",
@@ -57,6 +69,7 @@ def chat_home(request):
"default_system_prompt": get_prompt("CHAT_QA_SYSTEM"),
"custom_system_prompt": custom_system_prompt,
"has_documents": has_documents,
+ "chat_model_name": chat_model_name,
},
)
diff --git a/llm/client.py b/llm/client.py
index 73eccb2..3810732 100644
--- a/llm/client.py
+++ b/llm/client.py
@@ -99,7 +99,10 @@ def __init__(self):
)
else:
openai_cfg = config["openai"]
- self._client = OpenAI(api_key=openai_cfg["api_key"])
+ client_kwargs: dict[str, str] = {"api_key": openai_cfg["api_key"]}
+ if openai_cfg.get("base_url"):
+ client_kwargs["base_url"] = openai_cfg["base_url"]
+ self._client = OpenAI(**client_kwargs)
self._embed_client = self._client
self._chat_model = openai_cfg["chat_model"]
self._embed_model = openai_cfg["embedding_model"]
@@ -331,8 +334,8 @@ def embed(self, texts: list[str], on_progress=None) -> list[list[float]]:
batch = texts[i : i + self._batch_size]
kwargs = {"model": self._embed_model, "input": batch}
- # OpenAI and Azure OpenAI support dimensions param for text-embedding-3-* models
- if self._embed_dimensions:
+ # Only send dimensions for models that support it (text-embedding-3-*)
+ if self._embed_dimensions and self._embed_model.startswith("text-embedding-"):
kwargs["dimensions"] = self._embed_dimensions
response = self._call_with_retry(self._embed_client.embeddings.create, **kwargs)
diff --git a/pyproject.toml b/pyproject.toml
index dfb7680..2c0577d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -75,6 +75,21 @@ python_files = ["tests/*.py", "tests/**/*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
+[tool.setuptools.packages.find]
+include = [
+ "score*",
+ "analysis*",
+ "chat*",
+ "connectors*",
+ "dashboard*",
+ "ingestion*",
+ "llm*",
+ "nsg*",
+ "reports*",
+ "tenants*",
+ "vectorstore*",
+]
+
[tool.ruff]
target-version = "py312"
line-length = 100
diff --git a/score/settings.py b/score/settings.py
index c5dd0c0..7ca59d6 100644
--- a/score/settings.py
+++ b/score/settings.py
@@ -226,6 +226,7 @@
"provider": LLM_PROVIDER,
"openai": {
"api_key": env("OPENAI_API_KEY", default=""),
+ "base_url": env("OPENAI_BASE_URL", default=""),
"chat_model": _llm_yaml.get("chat_model", "gpt-4o"),
"embedding_model": _llm_yaml.get("embedding_model", "text-embedding-3-small"),
"embedding_dimensions": _llm_yaml.get("embedding_dimensions", 1536),