Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 14 additions & 119 deletions chat/templates/chat/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,9 @@
line-height: 1;
margin-left: -1px;
}
.toolbar-btn .mistral-logo {
width: 18px;
height: 14px;
.toolbar-btn .model-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}

Expand Down Expand Up @@ -888,7 +888,7 @@
}
.model-option:hover { background: var(--chat-toolbar-hover); }
.model-option.active { border-color: var(--chat-chatbox-text); }
.model-option .mistral-logo { width: 18px; height: 14px; }
.model-option .model-icon { width: 16px; height: 16px; }
.toolbar-btn--model { gap: 5px; }
.toolbar-btn--model .model-label { pointer-events: none; }

Expand Down Expand Up @@ -1364,11 +1364,10 @@ <h1 class="landing-title" id="landingTitle">{% trans "Assistant documentaire" %}
</div>
</div>
<div class="model-dropdown-wrap">
<button class="toolbar-btn toolbar-btn--model" type="button">
<svg class="mistral-logo" viewBox="0 0 190 135"><rect x="27" y="0" width="27" height="27" fill="#FFD800"/><rect x="136" y="0" width="27" height="27" fill="#FFD800"/><rect x="27" y="27" width="54" height="27" fill="#FFAF00"/><rect x="109" y="27" width="54" height="27" fill="#FFAF00"/><rect x="27" y="54" width="136" height="27" fill="#FF8205"/><rect x="27" y="81" width="27" height="27" fill="#FA500F"/><rect x="81" y="81" width="27" height="27" fill="#FA500F"/><rect x="136" y="81" width="27" height="27" fill="#FA500F"/><rect x="0" y="108" width="81" height="27" fill="#E10500"/><rect x="109" y="108" width="81" height="27" fill="#E10500"/></svg>
<span class="model-label">Medium</span>
<button class="toolbar-btn toolbar-btn--model" type="button" title="{% trans "Modèle LLM actif" %}">
<svg class="model-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M5 6.5a1 1 0 011-1h0a1 1 0 011 1v0a1 1 0 01-.4.8L5.5 8.2a1 1 0 00-.5.8V10"/><circle cx="5.5" cy="11.5" r=".5" fill="currentColor" stroke="none"/><path d="M10 6.5a1 1 0 011-1h0a1 1 0 011 1v0a1 1 0 01-.4.8l-1.1.9a1 1 0 00-.5.8V10"/><circle cx="10.5" cy="11.5" r=".5" fill="currentColor" stroke="none"/></svg>
<span class="model-label">{{ chat_model_name }}</span>
</button>
<div class="model-dropdown"></div>
</div>
<button class="toolbar-btn toolbar-btn--prompt" type="button" title="{% trans "Prompt système" %}">
<svg viewBox="0 0 12 12" fill="none"><path d="M1.5 2h9v8h-9V2z" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 5l1.5 1L4 7" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.5 7h2" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/></svg>
Expand Down Expand Up @@ -1436,11 +1435,10 @@ <h1 class="landing-title" id="landingTitle">{% trans "Assistant documentaire" %}
</div>
</div>
<div class="model-dropdown-wrap">
<button class="toolbar-btn toolbar-btn--model" type="button">
<svg class="mistral-logo" viewBox="0 0 190 135"><rect x="27" y="0" width="27" height="27" fill="#FFD800"/><rect x="136" y="0" width="27" height="27" fill="#FFD800"/><rect x="27" y="27" width="54" height="27" fill="#FFAF00"/><rect x="109" y="27" width="54" height="27" fill="#FFAF00"/><rect x="27" y="54" width="136" height="27" fill="#FF8205"/><rect x="27" y="81" width="27" height="27" fill="#FA500F"/><rect x="81" y="81" width="27" height="27" fill="#FA500F"/><rect x="136" y="81" width="27" height="27" fill="#FA500F"/><rect x="0" y="108" width="81" height="27" fill="#E10500"/><rect x="109" y="108" width="81" height="27" fill="#E10500"/></svg>
<span class="model-label">Medium</span>
<button class="toolbar-btn toolbar-btn--model" type="button" title="{% trans "Modèle LLM actif" %}">
<svg class="model-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M5 6.5a1 1 0 011-1h0a1 1 0 011 1v0a1 1 0 01-.4.8L5.5 8.2a1 1 0 00-.5.8V10"/><circle cx="5.5" cy="11.5" r=".5" fill="currentColor" stroke="none"/><path d="M10 6.5a1 1 0 011-1h0a1 1 0 011 1v0a1 1 0 01-.4.8l-1.1.9a1 1 0 00-.5.8V10"/><circle cx="10.5" cy="11.5" r=".5" fill="currentColor" stroke="none"/></svg>
<span class="model-label">{{ chat_model_name }}</span>
</button>
<div class="model-dropdown"></div>
</div>
<button class="toolbar-btn toolbar-btn--prompt" type="button" title="{% trans "Prompt système" %}">
<svg viewBox="0 0 12 12" fill="none"><path d="M1.5 2h9v8h-9V2z" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 5l1.5 1L4 7" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.5 7h2" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/></svg>
Expand Down Expand Up @@ -2017,7 +2015,6 @@ <h5 class="modal-title" id="spModalLabel">{% 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
Expand All @@ -2037,11 +2034,6 @@ <h5 class="modal-title" id="spModalLabel">{% 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
Expand Down Expand Up @@ -2097,7 +2089,7 @@ <h5 class="modal-title" id="spModalLabel">{% 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 = '<span class="context-bar-empty">{% trans "Aucun outil actif" %}</span>';
return;
}
Expand All @@ -2113,108 +2105,11 @@ <h5 class="modal-title" id="spModalLabel">{% 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 = '<svg class="mistral-logo" viewBox="0 0 190 135"><rect x="27" y="0" width="27" height="27" fill="#FFD800"/><rect x="136" y="0" width="27" height="27" fill="#FFD800"/><rect x="27" y="27" width="54" height="27" fill="#FFAF00"/><rect x="109" y="27" width="54" height="27" fill="#FFAF00"/><rect x="27" y="54" width="136" height="27" fill="#FF8205"/><rect x="27" y="81" width="27" height="27" fill="#FA500F"/><rect x="81" y="81" width="27" height="27" fill="#FA500F"/><rect x="136" y="81" width="27" height="27" fill="#FA500F"/><rect x="0" y="108" width="81" height="27" fill="#E10500"/><rect x="109" y="108" width="81" height="27" fill="#E10500"/></svg>';
var MISTRAL_SVG_SMALL = '<svg style="width:14px;height:14px;" viewBox="0 0 190 135"><rect x="27" y="0" width="27" height="27" fill="#FFD800"/><rect x="136" y="0" width="27" height="27" fill="#FFD800"/><rect x="27" y="27" width="54" height="27" fill="#FFAF00"/><rect x="109" y="27" width="54" height="27" fill="#FFAF00"/><rect x="27" y="54" width="136" height="27" fill="#FF8205"/><rect x="27" y="81" width="27" height="27" fill="#FA500F"/><rect x="81" y="81" width="27" height="27" fill="#FA500F"/><rect x="136" y="81" width="27" height="27" fill="#FA500F"/><rect x="0" y="108" width="81" height="27" fill="#E10500"/><rect x="109" y="108" width="81" height="27" fill="#E10500"/></svg>';
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');
Expand Down
13 changes: 13 additions & 0 deletions chat/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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,
},
)

Expand Down
9 changes: 6 additions & 3 deletions llm/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions score/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Loading