Merge branch 'main' of github.com:suitenumerique/conversations

This commit is contained in:
Eléonore Voisin
2025-10-15 09:10:41 +02:00
13 changed files with 523 additions and 120 deletions
+12
View File
@@ -21,6 +21,18 @@
"matchPackageNames": ["markitdown"],
"allowedVersions": "==0.0.2"
},
{
"groupName": "ignore recent lxml versions not supported by htmldate==1.9.3",
"matchManagers": ["pep621"],
"matchPackageNames": ["lxml"],
"allowedVersions": "<6"
},
{
"groupName": "ignore recent pylint versions not supported by pylint-django",
"matchManagers": ["pep621"],
"matchPackageNames": ["pylint"],
"allowedVersions": "<4"
},
{
"enabled": false,
"groupName": "ignored js dependencies",
+40 -5
View File
@@ -30,6 +30,9 @@ def prepare_custom_model(configuration: "chat.llm_configuration.LLModel"):
match configuration.provider.kind:
case "mistral":
import pydantic_ai.models.mistral as mistral_models # noqa: PLC0415
from mistralai import TextChunk as MistralTextChunk # noqa: PLC0415
from mistralai import ThinkChunk as MistralThinkChunk # noqa: PLC0415
from mistralai.types.basemodel import Unset as MistralUnset # noqa: PLC0415
from pydantic_ai.providers.mistral import MistralProvider # noqa: PLC0415
# --- Monkey patch for pydantic_ai.models.mistral._map_content ---
@@ -47,13 +50,45 @@ def prepare_custom_model(configuration: "chat.llm_configuration.LLModel"):
if not getattr(mistral_models, "__safe_map_patched__", False):
_original_map_content = mistral_models._map_content # noqa: SLF001
def _safe_map_content(*args, **kwargs):
try:
return _original_map_content(*args, **kwargs)
except AssertionError as exc:
logger.debug("Caught exception in _map_content: %s", exc)
def _safe_map_content(content):
"""
A safe version of _map_content that ignores unsupported data types.
WARNING: this is a monkey patch and may break if the original
function changes in future versions of pydantic_ai.
Current version: pydantic_ai v1.0.18
"""
text: str | None = None
thinking: list[str] = []
if isinstance(content, MistralUnset) or not content:
return None, []
if isinstance(content, list):
for chunk in content:
if isinstance(chunk, MistralTextChunk):
text = (text or "") + chunk.text
elif isinstance(chunk, MistralThinkChunk):
for thought in chunk.thinking:
if thought.type == "text": # pragma: no branch
thinking.append(thought.text)
else:
logger.info( # pragma: no cover
"Other data types like (Image, Reference) are not yet "
"supported, got %s",
type(chunk),
)
elif isinstance(content, str):
text = content
# Note: Check len to handle potential mismatch between function calls and
# responses from the API.
# (`msg: not the same number of function class and responses`)
if text == "": # pragma: no cover
text = None
return text, thinking
# Replace the original module-level function
mistral_models._map_content = _safe_map_content # noqa: SLF001
mistral_models.__safe_map_patched__ = True
@@ -133,7 +133,7 @@ def test_chat_conversation_input_serializer_invalid_role():
),
"type": ErrorDetail(string="literal_error", code="invalid"),
"url": ErrorDetail(
string="https://errors.pydantic.dev/2.11/v/literal_error", code="invalid"
string="https://errors.pydantic.dev/2.12/v/literal_error", code="invalid"
),
}
]
@@ -171,7 +171,7 @@ def test_chat_conversation_input_serializer_invalid_created_at():
),
"type": ErrorDetail(string="datetime_from_date_parsing", code="invalid"),
"url": ErrorDetail(
string="https://errors.pydantic.dev/2.11/v/datetime_from_date_parsing",
string="https://errors.pydantic.dev/2.12/v/datetime_from_date_parsing",
code="invalid",
),
}
@@ -209,7 +209,7 @@ def test_chat_conversation_input_serializer_missing_parts():
],
"msg": ErrorDetail(string="Field required", code="invalid"),
"type": ErrorDetail(string="missing", code="invalid"),
"url": ErrorDetail(string="https://errors.pydantic.dev/2.11/v/missing", code="invalid"),
"url": ErrorDetail(string="https://errors.pydantic.dev/2.12/v/missing", code="invalid"),
}
]
@@ -299,7 +299,7 @@ def test_chat_conversation_input_serializer_missing_id():
"content": ErrorDetail(string="Hello", code="invalid"),
"createdAt": ErrorDetail(string="2025-07-03T15:22:17.105Z", code="invalid"),
},
"url": ErrorDetail(string="https://errors.pydantic.dev/2.11/v/missing", code="invalid"),
"url": ErrorDetail(string="https://errors.pydantic.dev/2.12/v/missing", code="invalid"),
}
]
@@ -69,18 +69,14 @@ def test_agent_web_search_brave_success_with_extra_snippets():
tool_return = web_search_brave("test query")
assert hasattr(tool_return, "return_value")
assert tool_return.return_value == [
{
"link": "https://example.com/a",
assert tool_return.return_value == {
"0": {
"snippets": ["Snippet A1", "Snippet A2"],
"title": "Result A",
"extra_snippets": ["Snippet A1", "Snippet A2"],
"url": "https://example.com/a",
},
{
"link": "https://example.com/b",
"title": "Result B",
"extra_snippets": ["Snippet B1"],
},
]
"1": {"snippets": ["Snippet B1"], "title": "Result B", "url": "https://example.com/b"},
}
assert tool_return.metadata["sources"] == {"https://example.com/a", "https://example.com/b"}
# Check request parameters
@@ -120,13 +116,13 @@ def test_agent_web_search_brave_success_without_extra_snippets():
)
tool_return = web_search_brave("test query")
assert tool_return.return_value == [
{
"link": "https://example.com/c",
assert tool_return.return_value == {
"0": {
"snippets": ["Extracted Content C\nlink"],
"title": "Result C",
"extra_snippets": ["Extracted Content C\nlink"],
"url": "https://example.com/c",
}
]
}
assert tool_return.metadata["sources"] == {"https://example.com/c"}
@@ -159,13 +155,13 @@ def test_agent_web_search_brave_success_without_extra_snippets_summarization(set
mock_llm_summarize.assert_called_with("test query", "Extracted Content C\nlink")
assert tool_return.return_value == [
{
"link": "https://example.com/c",
assert tool_return.return_value == {
"0": {
"snippets": ["Summarized extracted Content C\nlink"],
"title": "Result C",
"extra_snippets": ["Summarized extracted Content C\nlink"],
"url": "https://example.com/c",
}
]
}
assert tool_return.metadata["sources"] == {"https://example.com/c"}
@@ -179,7 +175,7 @@ def test_agent_web_search_brave_empty_results():
status=200,
)
tool_return = web_search_brave("empty query")
assert tool_return.return_value == []
assert tool_return.return_value == {}
assert tool_return.metadata["sources"] == set()
@@ -282,7 +278,7 @@ def test_agent_web_search_brave_single_worker(settings):
tool_return = web_search_brave("single worker query")
assert len(tool_return.return_value) == 1
assert tool_return.return_value[0]["extra_snippets"] == ["Single Content"]
assert tool_return.return_value["0"]["snippets"] == ["Single Content"]
@responses.activate
@@ -377,10 +373,10 @@ def test_web_search_brave_with_document_backend_success():
tool_return = web_search_brave_with_document_backend(mock_ctx, "rag query")
assert len(tool_return.return_value) == 2
assert tool_return.return_value[0]["link"] == "https://example.com/doc1"
assert tool_return.return_value[0]["extra_snippets"] == ["RAG Content 1"]
assert tool_return.return_value[1]["link"] == "https://example.com/doc2"
assert tool_return.return_value[1]["extra_snippets"] == ["RAG Content 2"]
assert tool_return.return_value["0"]["url"] == "https://example.com/doc1"
assert tool_return.return_value["0"]["snippets"] == ["RAG Content 1"]
assert tool_return.return_value["1"]["url"] == "https://example.com/doc2"
assert tool_return.return_value["1"]["snippets"] == ["RAG Content 2"]
assert tool_return.metadata["sources"] == {
"https://example.com/doc1",
"https://example.com/doc2",
@@ -471,7 +467,7 @@ def test_web_search_brave_with_document_backend_fetch_error(settings):
tool_return = web_search_brave_with_document_backend(mock_ctx, "error query")
# Should complete despite error (error is caught and logged in multi-worker path)
assert tool_return.return_value == []
assert tool_return.return_value == {}
@responses.activate
@@ -517,7 +513,7 @@ def test_web_search_brave_with_document_backend_no_matching_rag_results():
tool_return = web_search_brave_with_document_backend(mock_ctx, "query")
# No results should be returned since RAG URL doesn't match search results
assert tool_return.return_value == []
assert tool_return.return_value == {}
assert tool_return.metadata["sources"] == set()
@@ -35,9 +35,6 @@ def ai_settings(settings):
settings.AI_API_KEY = "test-api-key"
settings.AI_MODEL = "test-model"
# Disable web search backend for tests
settings.RAG_WEB_SEARCH_BACKEND = None
return settings
@@ -697,6 +694,7 @@ def test_post_conversation_tool_call(api_client, mock_openai_stream_tool, mock_u
"parts": [
{
"args": '{"location":"Paris", "unit":"celsius"}',
"id": None,
"part_kind": "tool-call",
"tool_call_id": "xLDcIljdsDrz0idal7tATWSMm2jhMj47",
"tool_name": "get_current_weather",
@@ -905,6 +903,7 @@ def test_post_conversation_tool_call_fails(
"parts": [
{
"args": '{"location":"Paris", "unit":"celsius"}',
"id": None,
"part_kind": "tool-call",
"tool_call_id": "xLDcIljdsDrz0idal7tATWSMm2jhMj47",
"tool_name": "get_current_weather",
@@ -397,6 +397,7 @@ def test_post_conversation_with_document_upload( # pylint: disable=too-many-arg
"parts": [
{
"args": '{"query": "What does the document say?"}',
"id": None,
"part_kind": "tool-call",
"tool_call_id": toolcall_id,
"tool_name": "document_search_rag",
@@ -715,6 +716,7 @@ def test_post_conversation_with_document_upload_summarize( # pylint: disable=to
"parts": [
{
"args": "{}",
"id": None,
"part_kind": "tool-call",
"tool_call_id": toolcall_id,
"tool_name": "summarize",
@@ -32,9 +32,6 @@ def ai_settings(settings):
settings.AI_API_KEY = "test-api-key"
settings.AI_MODEL = "test-model"
# Disable web search backend for tests
settings.RAG_WEB_SEARCH_BACKEND = None
return settings
@@ -1364,6 +1361,7 @@ def test_post_conversation_with_existing_tool_history(
"parts": [
{
"args": '{"location":"Paris", "unit":"celsius"}',
"id": None,
"part_kind": "tool-call",
"tool_call_id": "xLDcIljdsDrz0idal7tATWSMm2jhMj47",
"tool_name": "get_current_weather",
+23 -27
View File
@@ -119,6 +119,27 @@ def _query_brave_api(query: str) -> List[dict]:
return json_response.get("web", {}).get("results", [])
def format_tool_return(raw_search_results: List[dict]) -> ToolReturn:
"""Format the raw search results into a ToolReturn object."""
return ToolReturn(
# Format return value "mistral-like": https://docs.mistral.ai/capabilities/citations/
return_value={
str(idx): {
"url": result["url"],
"title": result["title"],
"snippets": result.get("extra_snippets", []),
}
for idx, result in enumerate(raw_search_results)
if result.get("extra_snippets", [])
},
metadata={
"sources": {
result["url"] for result in raw_search_results if result.get("extra_snippets", [])
}
},
)
def web_search_brave(query: str) -> ToolReturn:
"""
Search the web for up-to-date information
@@ -154,17 +175,7 @@ def web_search_brave(query: str) -> ToolReturn:
idx = future_map[future]
raw_search_results[idx]["extra_snippets"] = future.result()
return ToolReturn(
return_value=[
{
"link": result["url"],
"title": result["title"],
"extra_snippets": result.get("extra_snippets", []),
}
for result in raw_search_results
],
metadata={"sources": {result["url"] for result in raw_search_results}},
)
return format_tool_return(raw_search_results)
def web_search_brave_with_document_backend(ctx: RunContext, query: str) -> ToolReturn:
@@ -214,19 +225,4 @@ def web_search_brave_with_document_backend(ctx: RunContext, query: str) -> ToolR
result.setdefault("extra_snippets", []).append(rag_result.content)
break
return ToolReturn(
return_value=[
{
"link": result["url"],
"title": result["title"],
"extra_snippets": result.get("extra_snippets", []),
}
for result in raw_search_results
if result.get("extra_snippets", [])
],
metadata={
"sources": {
result["url"] for result in raw_search_results if result.get("extra_snippets", [])
}
},
)
return format_tool_return(raw_search_results)
+1 -12
View File
@@ -566,12 +566,6 @@ class Base(BraveSettings, Configuration):
)
# Web search
RAG_WEB_SEARCH_BACKEND = values.Value(
# "chat.agent_rag.web_search.albert_api.AlbertWebSearchManager",
"chat.agent_rag.web_search.mocked.MockedWebSearchManager",
environ_name="RAG_WEB_SEARCH_BACKEND",
environ_prefix=None,
)
RAG_WEB_SEARCH_PROMPT_UPDATE = values.Value(
"""
You are a subject-matter expert assistant.
@@ -794,12 +788,7 @@ USER QUESTION:
}
)
# Sanity check to ensure that the RAG_WEB_SEARCH_BACKEND and RAG_DOCUMENT_SEARCH_BACKEND
if features.web_search and not self.RAG_WEB_SEARCH_BACKEND:
raise RuntimeError(
"RAG_WEB_SEARCH_BACKEND is not set, but web_search feature flag is enabled."
)
# Sanity check to ensure that if document upload is enabled, a backend is set
if features.document_upload and not self.RAG_DOCUMENT_SEARCH_BACKEND:
raise RuntimeError(
"RAG_DOCUMENT_SEARCH_BACKEND is not set, "
+183 -10
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: la-suite-conversations\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-02 08:16+0000\n"
"PO-Revision-Date: 2025-10-03 13:44\n"
"POT-Creation-Date: 2025-10-13 18:51+0000\n"
"PO-Revision-Date: 2025-10-14 19:31\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -17,6 +17,187 @@ msgstr ""
"X-Crowdin-File: backend-conversations.pot\n"
"X-Crowdin-File-ID: 26\n"
#: activation_codes/admin.py:55 build/lib/activation_codes/admin.py:55
msgid "Configuration"
msgstr ""
#: activation_codes/admin.py:66 build/lib/activation_codes/admin.py:66
msgid "Usage details"
msgstr ""
#: activation_codes/admin.py:70 activation_codes/admin.py:213
#: build/lib/activation_codes/admin.py:70
#: build/lib/activation_codes/admin.py:213
msgid "Timestamps"
msgstr ""
#: activation_codes/admin.py:96 build/lib/activation_codes/admin.py:96
msgid "Usage"
msgstr ""
#: activation_codes/admin.py:104 build/lib/activation_codes/admin.py:104
msgid "Description"
msgstr ""
#: activation_codes/admin.py:111 build/lib/activation_codes/admin.py:111
msgid "No users have used this code yet"
msgstr ""
#: activation_codes/admin.py:122 build/lib/activation_codes/admin.py:122
msgid "Name"
msgstr ""
#: activation_codes/admin.py:123 activation_codes/admin.py:233
#: build/lib/activation_codes/admin.py:123
#: build/lib/activation_codes/admin.py:233
msgid "Email"
msgstr ""
#: activation_codes/admin.py:124 build/lib/activation_codes/admin.py:124
msgid "Date"
msgstr ""
#: activation_codes/admin.py:148 build/lib/activation_codes/admin.py:148
msgid "Users who used this code"
msgstr ""
#: activation_codes/admin.py:150 build/lib/activation_codes/admin.py:150
msgid "Recompute current uses from related activations"
msgstr ""
#: activation_codes/admin.py:164 build/lib/activation_codes/admin.py:164
msgid "All selected activation codes already have correct usage counts."
msgstr ""
#: activation_codes/admin.py:169 build/lib/activation_codes/admin.py:169
#, python-format
msgid "Successfully recomputed usage counts for %(count)d activation code(s)."
msgstr ""
#: activation_codes/admin.py:227 activation_codes/admin.py:269
#: build/lib/activation_codes/admin.py:227
#: build/lib/activation_codes/admin.py:269
msgid "User"
msgstr ""
#: activation_codes/admin.py:276 build/lib/activation_codes/admin.py:276
msgid "Has used activation code"
msgstr ""
#: activation_codes/models.py:35 activation_codes/models.py:76
#: activation_codes/models.py:157 build/lib/activation_codes/models.py:35
#: build/lib/activation_codes/models.py:76
#: build/lib/activation_codes/models.py:157
msgid "activation code"
msgstr ""
#: activation_codes/models.py:36 build/lib/activation_codes/models.py:36
msgid "The activation code that users will enter"
msgstr ""
#: activation_codes/models.py:43 build/lib/activation_codes/models.py:43
msgid "maximum uses"
msgstr ""
#: activation_codes/models.py:44 build/lib/activation_codes/models.py:44
msgid "Maximum number of times this code can be used. 0 means unlimited."
msgstr ""
#: activation_codes/models.py:49 build/lib/activation_codes/models.py:49
msgid "current uses"
msgstr ""
#: activation_codes/models.py:50 build/lib/activation_codes/models.py:50
msgid "Number of times this code has been used"
msgstr ""
#: activation_codes/models.py:56 build/lib/activation_codes/models.py:56
#: build/lib/core/models.py:151 core/models.py:151
msgid "active"
msgstr ""
#: activation_codes/models.py:57 build/lib/activation_codes/models.py:57
msgid "Whether this code can still be used"
msgstr ""
#: activation_codes/models.py:62 build/lib/activation_codes/models.py:62
msgid "expires at"
msgstr ""
#: activation_codes/models.py:63 build/lib/activation_codes/models.py:63
msgid "Date and time when this code expires"
msgstr ""
#: activation_codes/models.py:69 build/lib/activation_codes/models.py:69
msgid "description"
msgstr ""
#: activation_codes/models.py:70 build/lib/activation_codes/models.py:70
msgid "Internal description or notes about this code"
msgstr ""
#: activation_codes/models.py:77 build/lib/activation_codes/models.py:77
msgid "activation codes"
msgstr ""
#: activation_codes/models.py:119 build/lib/activation_codes/models.py:119
msgid "This activation code is no longer valid"
msgstr ""
#: activation_codes/models.py:127 build/lib/activation_codes/models.py:127
msgid "You have already activated your account"
msgstr ""
#: activation_codes/models.py:149 activation_codes/models.py:181
#: build/lib/activation_codes/models.py:149
#: build/lib/activation_codes/models.py:181 build/lib/core/models.py:173
#: core/models.py:173
msgid "user"
msgstr ""
#: activation_codes/models.py:150 build/lib/activation_codes/models.py:150
msgid "The user who used the activation code"
msgstr ""
#: activation_codes/models.py:158 build/lib/activation_codes/models.py:158
msgid "The activation code that was used"
msgstr ""
#: activation_codes/models.py:165 activation_codes/models.py:189
#: build/lib/activation_codes/models.py:165
#: build/lib/activation_codes/models.py:189
msgid "user activation"
msgstr ""
#: activation_codes/models.py:166 build/lib/activation_codes/models.py:166
msgid "user activations"
msgstr ""
#: activation_codes/models.py:182 build/lib/activation_codes/models.py:182
msgid "The user who made the registration request"
msgstr ""
#: activation_codes/models.py:190 build/lib/activation_codes/models.py:190
msgid "Store if the user received an activation code and used it"
msgstr ""
#: activation_codes/models.py:199 build/lib/activation_codes/models.py:199
msgid "user registration request"
msgstr ""
#: activation_codes/models.py:200 build/lib/activation_codes/models.py:200
msgid "user registration requests"
msgstr ""
#: activation_codes/serializers.py:14
#: build/lib/activation_codes/serializers.py:14
msgid "The activation code to validate"
msgstr ""
#: activation_codes/viewsets.py:106 build/lib/activation_codes/viewsets.py:106
msgid "Your account has been successfully activated"
msgstr ""
#: build/lib/chat/apps.py:12 chat/apps.py:12
msgid "chat application"
msgstr ""
@@ -117,10 +298,6 @@ msgstr ""
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:151 core/models.py:151
msgid "active"
msgstr ""
#: build/lib/core/models.py:154 core/models.py:154
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
@@ -133,10 +310,6 @@ msgstr ""
msgid "Whether the user allows to use their conversations for analytics."
msgstr ""
#: build/lib/core/models.py:173 core/models.py:173
msgid "user"
msgstr ""
#: build/lib/core/models.py:174 core/models.py:174
msgid "users"
msgstr ""
+183 -10
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: la-suite-conversations\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-02 08:16+0000\n"
"PO-Revision-Date: 2025-10-03 13:44\n"
"POT-Creation-Date: 2025-10-13 18:51+0000\n"
"PO-Revision-Date: 2025-10-14 19:31\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -17,6 +17,187 @@ msgstr ""
"X-Crowdin-File: backend-conversations.pot\n"
"X-Crowdin-File-ID: 26\n"
#: activation_codes/admin.py:55 build/lib/activation_codes/admin.py:55
msgid "Configuration"
msgstr "Configuration"
#: activation_codes/admin.py:66 build/lib/activation_codes/admin.py:66
msgid "Usage details"
msgstr "Détails d'utilisation"
#: activation_codes/admin.py:70 activation_codes/admin.py:213
#: build/lib/activation_codes/admin.py:70
#: build/lib/activation_codes/admin.py:213
msgid "Timestamps"
msgstr "Horodatages"
#: activation_codes/admin.py:96 build/lib/activation_codes/admin.py:96
msgid "Usage"
msgstr "Utilisation"
#: activation_codes/admin.py:104 build/lib/activation_codes/admin.py:104
msgid "Description"
msgstr "Description"
#: activation_codes/admin.py:111 build/lib/activation_codes/admin.py:111
msgid "No users have used this code yet"
msgstr "Aucun utilisateur n'a encore utilisé ce code"
#: activation_codes/admin.py:122 build/lib/activation_codes/admin.py:122
msgid "Name"
msgstr "Nom"
#: activation_codes/admin.py:123 activation_codes/admin.py:233
#: build/lib/activation_codes/admin.py:123
#: build/lib/activation_codes/admin.py:233
msgid "Email"
msgstr "E-mail"
#: activation_codes/admin.py:124 build/lib/activation_codes/admin.py:124
msgid "Date"
msgstr "Date"
#: activation_codes/admin.py:148 build/lib/activation_codes/admin.py:148
msgid "Users who used this code"
msgstr "Utilisateurs qui ont utilisé ce code"
#: activation_codes/admin.py:150 build/lib/activation_codes/admin.py:150
msgid "Recompute current uses from related activations"
msgstr "Recalculer les utilisations actuelles à partir des activations liées"
#: activation_codes/admin.py:164 build/lib/activation_codes/admin.py:164
msgid "All selected activation codes already have correct usage counts."
msgstr "Tous les codes d'activation sélectionnés ont déjà un nombre correct d'utilisations."
#: activation_codes/admin.py:169 build/lib/activation_codes/admin.py:169
#, python-format
msgid "Successfully recomputed usage counts for %(count)d activation code(s)."
msgstr "Utilisation recalculée avec succès pour %(count)d code(s) d'activation."
#: activation_codes/admin.py:227 activation_codes/admin.py:269
#: build/lib/activation_codes/admin.py:227
#: build/lib/activation_codes/admin.py:269
msgid "User"
msgstr "Utilisateur"
#: activation_codes/admin.py:276 build/lib/activation_codes/admin.py:276
msgid "Has used activation code"
msgstr "A utilisé le code d'activation"
#: activation_codes/models.py:35 activation_codes/models.py:76
#: activation_codes/models.py:157 build/lib/activation_codes/models.py:35
#: build/lib/activation_codes/models.py:76
#: build/lib/activation_codes/models.py:157
msgid "activation code"
msgstr "code d'activation"
#: activation_codes/models.py:36 build/lib/activation_codes/models.py:36
msgid "The activation code that users will enter"
msgstr "Le code d'activation que les utilisateurs entreront"
#: activation_codes/models.py:43 build/lib/activation_codes/models.py:43
msgid "maximum uses"
msgstr "utilisations maximales"
#: activation_codes/models.py:44 build/lib/activation_codes/models.py:44
msgid "Maximum number of times this code can be used. 0 means unlimited."
msgstr "Nombre maximum d'utilisation de ce code. 0 signifie illimité."
#: activation_codes/models.py:49 build/lib/activation_codes/models.py:49
msgid "current uses"
msgstr "utilisations actuelles"
#: activation_codes/models.py:50 build/lib/activation_codes/models.py:50
msgid "Number of times this code has been used"
msgstr "Nombre de fois où ce code a été utilisé"
#: activation_codes/models.py:56 build/lib/activation_codes/models.py:56
#: build/lib/core/models.py:151 core/models.py:151
msgid "active"
msgstr "actif"
#: activation_codes/models.py:57 build/lib/activation_codes/models.py:57
msgid "Whether this code can still be used"
msgstr "Si ce code peut encore être utilisé"
#: activation_codes/models.py:62 build/lib/activation_codes/models.py:62
msgid "expires at"
msgstr "expiration"
#: activation_codes/models.py:63 build/lib/activation_codes/models.py:63
msgid "Date and time when this code expires"
msgstr "Date et heure d'expiration de ce code"
#: activation_codes/models.py:69 build/lib/activation_codes/models.py:69
msgid "description"
msgstr "description"
#: activation_codes/models.py:70 build/lib/activation_codes/models.py:70
msgid "Internal description or notes about this code"
msgstr "Description interne ou notes à propos de ce code"
#: activation_codes/models.py:77 build/lib/activation_codes/models.py:77
msgid "activation codes"
msgstr "codes d'activation"
#: activation_codes/models.py:119 build/lib/activation_codes/models.py:119
msgid "This activation code is no longer valid"
msgstr "Ce code d'activation n'est plus valide"
#: activation_codes/models.py:127 build/lib/activation_codes/models.py:127
msgid "You have already activated your account"
msgstr "Vous avez déjà activé votre compte"
#: activation_codes/models.py:149 activation_codes/models.py:181
#: build/lib/activation_codes/models.py:149
#: build/lib/activation_codes/models.py:181 build/lib/core/models.py:173
#: core/models.py:173
msgid "user"
msgstr "utilisateur"
#: activation_codes/models.py:150 build/lib/activation_codes/models.py:150
msgid "The user who used the activation code"
msgstr "L'utilisateur qui a utilisé le code d'activation"
#: activation_codes/models.py:158 build/lib/activation_codes/models.py:158
msgid "The activation code that was used"
msgstr "Le code d'activation qui a été utilisé"
#: activation_codes/models.py:165 activation_codes/models.py:189
#: build/lib/activation_codes/models.py:165
#: build/lib/activation_codes/models.py:189
msgid "user activation"
msgstr "activation d'utilisateur"
#: activation_codes/models.py:166 build/lib/activation_codes/models.py:166
msgid "user activations"
msgstr "activations d'utilisateurs"
#: activation_codes/models.py:182 build/lib/activation_codes/models.py:182
msgid "The user who made the registration request"
msgstr "L'utilisateur qui a fait la demande d'enregistrement"
#: activation_codes/models.py:190 build/lib/activation_codes/models.py:190
msgid "Store if the user received an activation code and used it"
msgstr "Enregistrer si l'utilisateur a reçu un code d'activation et l'a utilisé"
#: activation_codes/models.py:199 build/lib/activation_codes/models.py:199
msgid "user registration request"
msgstr "demande d'inscription d'utilisateur"
#: activation_codes/models.py:200 build/lib/activation_codes/models.py:200
msgid "user registration requests"
msgstr "demandes d'inscription d'utilisateur"
#: activation_codes/serializers.py:14
#: build/lib/activation_codes/serializers.py:14
msgid "The activation code to validate"
msgstr "Le code d'activation à valider"
#: activation_codes/viewsets.py:106 build/lib/activation_codes/viewsets.py:106
msgid "Your account has been successfully activated"
msgstr "Votre compte a été activé avec succès"
#: build/lib/chat/apps.py:12 chat/apps.py:12
msgid "chat application"
msgstr "application de chat"
@@ -117,10 +298,6 @@ msgstr "statut d'équipe"
msgid "Whether the user can log into this admin site."
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
#: build/lib/core/models.py:151 core/models.py:151
msgid "active"
msgstr "actif"
#: build/lib/core/models.py:154 core/models.py:154
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "Si cet utilisateur doit être traité comme actif. Désélectionnez ceci au lieu de supprimer des comptes."
@@ -133,10 +310,6 @@ msgstr "autoriser les analyses de conversation"
msgid "Whether the user allows to use their conversations for analytics."
msgstr "Si l'utilisateur autorise l'utilisation de ses conversations pour des analyses."
#: build/lib/core/models.py:173 core/models.py:173
msgid "user"
msgstr "utilisateur"
#: build/lib/core/models.py:174 core/models.py:174
msgid "users"
msgstr "utilisateurs"
+13 -13
View File
@@ -26,13 +26,13 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"deprecated",
"beautifulsoup4==4.13.5",
"boto3==1.40.38",
"beautifulsoup4==4.14.2",
"boto3==1.40.51",
"Brotli==1.1.0",
"django-configurations==2.5.1",
"django-cors-headers==4.9.0",
"django-countries==7.6.1",
"django-filter==25.1",
"django-filter==25.2",
"django-lasuite[all]==0.0.14",
"django-parler==2.3",
"django-pydantic-field==0.3.13",
@@ -47,21 +47,21 @@ dependencies = [
"factory_boy==3.3.3",
"gunicorn==23.0.0",
"jsonschema==4.25.1",
"langfuse==3.5.0",
"langfuse==3.6.2",
"lxml==5.4.0",
"markdown==3.9",
"markitdown==0.0.2",
"mozilla-django-oidc==4.0.1",
"nested-multipart-parser==1.6.0",
"posthog==6.7.6",
"pydantic==2.11.9",
"pydantic-ai-slim[openai,mistral,mcp,evals,logfire]==1.0.14",
"posthog==6.7.7",
"pydantic==2.12.1",
"pydantic-ai-slim[openai,mistral,mcp,evals,logfire]==1.0.18",
"psycopg[binary]==3.2.10",
"PyJWT==2.10.1",
"python-magic==0.4.27",
"redis<6.0.0",
"requests==2.32.5",
"sentry-sdk==2.38.0",
"sentry-sdk==2.41.0",
"trafilatura==2.0.0",
"whitenoise==6.11.0",
]
@@ -76,14 +76,14 @@ dependencies = [
dev = [
"django-extensions==4.1",
"django-test-migrations==1.5.0",
"drf-spectacular-sidecar==2025.9.1",
"drf-spectacular-sidecar==2025.10.1",
"freezegun==1.5.5",
"ipdb==0.13.13",
"ipython==9.5.0",
"pyfakefs==5.9.3",
"ipython==9.6.0",
"pyfakefs==5.10.0",
"pylint-django==2.6.1",
"pylint==3.3.8",
"pylint-pydantic==0.3.5",
"pylint-pydantic==0.4.0",
"pytest-asyncio==1.2.0",
"pytest-cov==7.0.0",
"pytest-django==4.11.1",
@@ -92,7 +92,7 @@ dev = [
"pytest-xdist==3.8.0",
"responses==0.25.8",
"respx==0.22.0",
"ruff==0.13.1",
"ruff==0.14.0",
"types-requests==2.32.4.20250913",
]
@@ -3,7 +3,11 @@
"fr": {
"translation": {
"30 sec to tell us what you think or report a bug": "Prenez 30 secondes pour partager votre avis ou signaler un bug",
"A privacy-first assistant built for French public teams. Natively synced with LaSuite apps to help you draft, search, and decide without leaving your workflow. Beta access is available with a referral code.": "Votre assistant de confiance, créé pour les agents publics français.\nNativement intégré aux applications de LaSuite, il vous accompagne pour rédiger, explorer et décider sans quitter vos outils quotidiens.\nLaccès à la bêta est actuellement réservé aux utilisateurs disposant dun code d'accès.",
"ABC-1234-XY": "ABC-1234-XY",
"Access Denied - Error 403": "Accès refusé - Erreur 403",
"Access is limited to people who have an invitation code. If you have one, please enter it below.": "L'accès est limité aux personnes qui ont un code d'invitation. Si vous en avez un, veuillez le saisir ci-dessous.",
"Account activated successfully!": "Compte activé avec succès !",
"Add attach file": "Ajouter une pièce jointe",
"Allow conversation analysis": "Autoriser les analyses de conversation",
"An error occurred. Please try again.": "Une erreur s'est produite. Veuillez réessayer.",
@@ -17,6 +21,7 @@
"Available soon": "Disponible prochainement",
"Cancel": "Annuler",
"Clear search": "Effacer la recherche",
"Close model selector": "Fermer le sélecteur de modèle",
"Close the modal": "Fermer la modale",
"Confirm deletion": "Confirmer la suppression",
"Content modal to delete conversation": "Contenu modal pour supprimer la conversation",
@@ -25,13 +30,22 @@
"Conversation analysis enabled": "Analyse de la conversation activée",
"Copied": "Copié",
"Copy": "Copier",
"Default": "Par défaut",
"Delete": "Supprimer",
"Delete a conversation": "Supprimer une conversation",
"Delete chat": "Supprimer la conversation",
"Direct exchange with our team": "Échange direct avec notre équipe",
"Explore other LaSuite apps": "Explorer les autres applications LaSuite",
"Failed to activate account. Please try again.": "Échec de l'activation du compte. Veuillez réessayer.",
"Failed to copy": "Impossible de copier",
"Failed to register for notifications. Please try again.": "Échec de l'inscription aux notifications. Veuillez réessayer.",
"Failed to send feedback": "Échec de lenvoi du commentaire",
"Failed to update settings": "Impossible de mettre à jour les paramètres",
"Feedback Négatif": "Retour négatif",
"Feedback positif": "Retour positif",
"Find recent news about...": "Trouver les dernières actualités concernant...",
"Get notified about the Public Beta.": "Soyez informé de la Bêta publique.",
"Get notified for the public beta": "Être notifié pour la bêta publique",
"Give a quick opinion": "Donner un avis rapide",
"Give feedback": "Faire un retour",
"History": "Historique",
@@ -40,27 +54,36 @@
"Illustration": "Image",
"Image 401": "Image 401",
"Image 403": "Image 403",
"Invalid activation code. Please check and try again.": "Code d'activation invalide. Veuillez le vérifier et réessayer.",
"It seems that the page you are looking for does not exist or cannot be displayed correctly.": "Il semble que la page que vous cherchez n'existe pas ou ne puisse pas être affichée correctement.",
"Know more": "En savoir plus",
"Language": "Langue",
"Learn more": "En savoir plus",
"Learn more about data usage.": "En savoir plus sur lusage des données.",
"Load more": "Afficher plus",
"Log in to access the document.": "Connectez-vous pour accéder au document.",
"Login": "Connexion",
"Logo": "Logo",
"Logout": "Se déconnecter",
"Negative feedback sent": "Commentaire négatif envoyé",
"New chat": "Nouvelle conversation",
"New feedback": "Nouveaux commentaires",
"No code? ": "Pas de code ? ",
"No conversation found": "Aucune conversation trouvée",
"Notify me": "Me notifier",
"Open": "Ouvrir",
"Open the header menu": "Ouvrir le menu d'en-tête",
"Page Not Found - Error 404": "Page introuvable - Erreur 404",
"Participate in the alpha": "Participer à l'alpha",
"Please enter an activation code": "Veuillez entrer un code dactivation",
"Positive feedback sent": "Commentaire positif envoyé",
"Proconnect Login": "Connexion Proconnect",
"Quick search input": "Saisie de recherche rapide",
"Research on the web": "Recherche sur le web",
"Search": "Rechercher",
"Search for a chat": "Rechercher une conversation",
"Search results": "Résultats de la recherche",
"Search...": "Recherche...",
"Select": "Sélectionner",
"Select model": "Sélectionner un modèle",
"Send": "Envoyer",
"Settings": "Paramètres",
"Show": "Afficher",
@@ -69,17 +92,24 @@
"Start conversation": "Entamer la conversation",
"Stop": "Stop",
"The Assistant is a sovereign conversational AI designed for public servants. It helps you save time on daily tasks like rephrasing, summarising, translating, or searching information. Your data never leaves France and is stored on secure, state-compliant infrastructures. It is never used for commercial purposes.": "L'Assistant est une IA conversationnelle souveraine conçue pour les fonctionnaires. Il vous aide à gagner du temps dans vos tâches quotidiennes telles que la reformulation, la synthèse, la traduction ou la recherche d'informations. Vos données ne quittent jamais la France et sont stockées sur des infrastructures sécurisées et conformes aux normes nationales. Elles ne sont jamais utilisées à des fins commerciales.",
"The Assistant is in Beta": "L'Assistant est en Bêta",
"The conversation has been deleted.": "La conversation a été supprimée.",
"The summary feature is not supported yet.": "La fonctionnalité de résumé n'est pas encore prise en charge.",
"Thinking...": "Réflexion...",
"Turn this list into bullet points": "Transformer cette liste en points à puces",
"Turn this list into bullet points": "Transformer cette liste en liste à puces...",
"Unlock access": "Déverrouiller l'accès",
"Unlocking...": "Déverrouillage en cours...",
"Untitled conversation": "Conversation sans titre",
"We'll email you at {{email}} when the public beta opens.": "Nous vous enverrons un e-mail à {{email}} lorsque la bêta publique sera ouverte.",
"We'll email you when the public beta opens.": "Nous vous enverrons un email quand la bêta publique sera ouverte.",
"Web": "Web",
"What is on your mind?": "Quavez-vous en tête?",
"What is on your mind?": "Quavez-vous en tête ?",
"Write a short product description": "Écrire une description courte du produit...",
"Write on Tchap": "Écrire sur Tchap",
"You are accessing an alpha version, tested internally, with limited scope and likely to contain bugs. Your feedback is essential to improve it. By clicking on \"Participate\", you agree to the analysis of your usage data to optimize the application; this analysis can be disabled at any time.": "Vous accédez à une version alpha, testée en interne, avec une portée limitée et susceptible de contenir des bogues. Vos commentaires sont essentiels pour l'améliorer. En cliquant sur « Participer », vous acceptez l'analyse de vos données d'utilisation pour optimiser l'application ; cette analyse peut être désactivée à tout moment.",
"You are on the list": "Vous êtes dans la liste",
"You do not have permission to view this document.": "Vous n'avez pas la permission de voir ce document.",
"You will be notified!": "Vous serez notifié !",
"Your account is already activated.": "Votre compte est déjà activé.",
"Your sovereign AI assistant": "Votre assistant IA souverain",
"sources": "sources",
"{{productName}} Logo": "Logo {{productName}}"