فهرست منبع

Merge pull request #7307 from open-webui/dev

0.4.5
Timothy Jaeryang Baek 5 ماه پیش
والد
کامیت
4831c9e57e
82فایلهای تغییر یافته به همراه1108 افزوده شده و 338 حذف شده
  1. 14 0
      CHANGELOG.md
  2. 22 7
      CODE_OF_CONDUCT.md
  3. 47 57
      backend/open_webui/apps/audio/main.py
  4. 35 11
      backend/open_webui/apps/ollama/main.py
  5. 0 2
      backend/open_webui/apps/openai/main.py
  6. 0 40
      backend/open_webui/apps/retrieval/utils.py
  7. 15 4
      backend/open_webui/apps/webui/main.py
  8. 28 17
      backend/open_webui/apps/webui/routers/configs.py
  9. 11 6
      backend/open_webui/apps/webui/utils.py
  10. 16 9
      backend/open_webui/config.py
  11. 52 32
      backend/open_webui/main.py
  12. 47 0
      backend/open_webui/utils/task.py
  13. 32 1
      backend/open_webui/utils/tools.py
  14. 1 0
      backend/requirements.txt
  15. 2 2
      package-lock.json
  16. 1 1
      package.json
  17. 1 0
      pyproject.toml
  18. 9 0
      src/app.css
  19. 31 4
      src/lib/apis/configs/index.ts
  20. 3 26
      src/lib/apis/index.ts
  21. 1 1
      src/lib/components/admin/Functions.svelte
  22. 2 1
      src/lib/components/admin/Settings/Evaluations.svelte
  23. 2 2
      src/lib/components/admin/Settings/Evaluations/ArenaModelModal.svelte
  24. 20 24
      src/lib/components/admin/Settings/Models.svelte
  25. 258 0
      src/lib/components/admin/Settings/Models/ConfigureModelsModal.svelte
  26. 58 0
      src/lib/components/admin/Settings/Models/ModelList.svelte
  27. 14 7
      src/lib/components/chat/Chat.svelte
  28. 58 46
      src/lib/components/chat/MessageInput.svelte
  29. 3 1
      src/lib/components/chat/Messages/CitationsModal.svelte
  30. 0 2
      src/lib/components/chat/ModelSelector.svelte
  31. 2 0
      src/lib/components/chat/Settings/General.svelte
  32. 3 3
      src/lib/components/common/Modal.svelte
  33. 12 16
      src/lib/components/common/RichTextInput.svelte
  34. 7 11
      src/lib/components/common/Textarea.svelte
  35. 16 3
      src/lib/components/workspace/Models/ModelEditor.svelte
  36. 6 0
      src/lib/i18n/locales/ar-BH/translation.json
  37. 6 0
      src/lib/i18n/locales/bg-BG/translation.json
  38. 6 0
      src/lib/i18n/locales/bn-BD/translation.json
  39. 6 0
      src/lib/i18n/locales/ca-ES/translation.json
  40. 6 0
      src/lib/i18n/locales/ceb-PH/translation.json
  41. 6 0
      src/lib/i18n/locales/cs-CZ/translation.json
  42. 6 0
      src/lib/i18n/locales/da-DK/translation.json
  43. 6 0
      src/lib/i18n/locales/de-DE/translation.json
  44. 6 0
      src/lib/i18n/locales/dg-DG/translation.json
  45. 6 0
      src/lib/i18n/locales/en-GB/translation.json
  46. 6 0
      src/lib/i18n/locales/en-US/translation.json
  47. 6 0
      src/lib/i18n/locales/es-ES/translation.json
  48. 6 0
      src/lib/i18n/locales/fa-IR/translation.json
  49. 6 0
      src/lib/i18n/locales/fi-FI/translation.json
  50. 6 0
      src/lib/i18n/locales/fr-CA/translation.json
  51. 6 0
      src/lib/i18n/locales/fr-FR/translation.json
  52. 6 0
      src/lib/i18n/locales/he-IL/translation.json
  53. 6 0
      src/lib/i18n/locales/hi-IN/translation.json
  54. 6 0
      src/lib/i18n/locales/hr-HR/translation.json
  55. 6 0
      src/lib/i18n/locales/hu-HU/translation.json
  56. 6 0
      src/lib/i18n/locales/id-ID/translation.json
  57. 6 0
      src/lib/i18n/locales/ie-GA/translation.json
  58. 6 0
      src/lib/i18n/locales/it-IT/translation.json
  59. 6 0
      src/lib/i18n/locales/ja-JP/translation.json
  60. 6 0
      src/lib/i18n/locales/ka-GE/translation.json
  61. 6 0
      src/lib/i18n/locales/ko-KR/translation.json
  62. 6 0
      src/lib/i18n/locales/lt-LT/translation.json
  63. 6 0
      src/lib/i18n/locales/ms-MY/translation.json
  64. 6 0
      src/lib/i18n/locales/nb-NO/translation.json
  65. 7 1
      src/lib/i18n/locales/nl-NL/translation.json
  66. 6 0
      src/lib/i18n/locales/pa-IN/translation.json
  67. 6 0
      src/lib/i18n/locales/pl-PL/translation.json
  68. 6 0
      src/lib/i18n/locales/pt-BR/translation.json
  69. 6 0
      src/lib/i18n/locales/pt-PT/translation.json
  70. 6 0
      src/lib/i18n/locales/ro-RO/translation.json
  71. 6 0
      src/lib/i18n/locales/ru-RU/translation.json
  72. 6 0
      src/lib/i18n/locales/sr-RS/translation.json
  73. 6 0
      src/lib/i18n/locales/sv-SE/translation.json
  74. 6 0
      src/lib/i18n/locales/th-TH/translation.json
  75. 6 0
      src/lib/i18n/locales/tk-TW/translation.json
  76. 6 0
      src/lib/i18n/locales/tr-TR/translation.json
  77. 6 0
      src/lib/i18n/locales/uk-UA/translation.json
  78. 6 0
      src/lib/i18n/locales/ur-PK/translation.json
  79. 6 0
      src/lib/i18n/locales/vi-VN/translation.json
  80. 6 0
      src/lib/i18n/locales/zh-CN/translation.json
  81. 6 0
      src/lib/i18n/locales/zh-TW/translation.json
  82. 8 1
      src/lib/utils/index.ts

+ 14 - 0
CHANGELOG.md

@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.4.5] - 2024-11-26
+
+### Added
+
+- **🎨 Model Order/Defaults Reintroduced**: Brought back the ability to set model order and default models, now configurable via Admin Settings > Models > Configure (Gear Icon).
+
+### Fixed
+
+- **🔍 Query Generation Issue**: Resolved an error in web search query generation, enhancing search accuracy and ensuring smoother search workflows.
+- **📏 Textarea Auto Height Bug**: Fixed a layout issue where textarea input height was shifting unpredictably, particularly when editing system prompts.
+- **🔑 Ollama Authentication**: Corrected an issue with Ollama’s authorization headers, guaranteeing reliable authentication across all endpoints.
+- **⚙️ Missing Min_P Save**: Resolved an issue where the 'min_p' parameter was not being saved in configurations.
+- **🛠️ Tools Description**: Fixed a key issue that omitted tool descriptions in tools payload.
+
 ## [0.4.4] - 2024-11-22
 
 ### Added

+ 22 - 7
CODE_OF_CONDUCT.md

@@ -1,25 +1,30 @@
 # Contributor Covenant Code of Conduct
 
 ## Our Pledge
+
 As members, contributors, and leaders of this community, we pledge to make participation in our open-source project a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
 
 We are committed to creating and maintaining an open, respectful, and professional environment where positive contributions and meaningful discussions can flourish. By participating in this project, you agree to uphold these values and align your behavior to the standards outlined in this Code of Conduct.
 
 ## Why These Standards Are Important
-Open-source projects rely on a community of volunteers dedicating their time, expertise, and effort toward a shared goal. These projects are inherently collaborative but also fragile, as the success of the project depends on the goodwill, energy, and productivity of those involved. 
+
+Open-source projects rely on a community of volunteers dedicating their time, expertise, and effort toward a shared goal. These projects are inherently collaborative but also fragile, as the success of the project depends on the goodwill, energy, and productivity of those involved.
 
 Maintaining a positive and respectful environment is essential to safeguarding the integrity of this project and protecting contributors' efforts. Behavior that disrupts this atmosphere—whether through hostility, entitlement, or unprofessional conduct—can severely harm the morale and productivity of the community. **Strict enforcement of these standards ensures a safe and supportive space for meaningful collaboration.**
 
 This is a community where **respect and professionalism are mandatory.** Violations of these standards will result in **zero tolerance** and immediate enforcement to prevent disruption and ensure the well-being of all participants.
 
 ## Our Standards
+
 Examples of behavior that contribute to a positive and professional community include:
+
 - **Respecting others.** Be considerate, listen actively, and engage with empathy toward others' viewpoints and experiences.
-- **Constructive feedback.** Provide actionable, thoughtful, and respectful feedback that helps improve the project and encourages collaboration. Avoid unproductive negativity or hypercriticism. 
+- **Constructive feedback.** Provide actionable, thoughtful, and respectful feedback that helps improve the project and encourages collaboration. Avoid unproductive negativity or hypercriticism.
 - **Recognizing volunteer contributions.** Appreciate that contributors dedicate their free time and resources selflessly. Approach them with gratitude and patience.
 - **Focusing on shared goals.** Collaborate in ways that prioritize the health, success, and sustainability of the community over individual agendas.
 
 Examples of unacceptable behavior include:
+
 - The use of discriminatory, demeaning, or sexualized language or behavior.
 - Personal attacks, derogatory comments, trolling, or inflammatory political or ideological arguments.
 - Harassment, intimidation, or any behavior intended to create a hostile, uncomfortable, or unsafe environment.
@@ -29,32 +34,40 @@ Examples of unacceptable behavior include:
 - **Spamming and promotional exploitation.** Sharing irrelevant product promotions or self-promotion in the community is not allowed unless it directly contributes value to the discussion.
 
 ### Feedback and Community Engagement
+
 - **Constructive feedback is encouraged, but hostile or entitled behavior will result in immediate action.** If you disagree with elements of the project, we encourage you to offer meaningful improvements or fork the project if necessary. Healthy discussions and technical disagreements are welcome only when handled with professionalism.
 - **Respect contributors' time and efforts.** No one is entitled to personalized or on-demand assistance. This is a community built on collaboration and shared effort; demanding or demeaning behavior undermines that trust and will not be allowed.
 
 ### Zero Tolerance: No Warnings, Immediate Action
-This community operates under a **zero-tolerance policy.** Any behavior deemed unacceptable under this Code of Conduct will result in **immediate enforcement, without prior warning.** 
+
+This community operates under a **zero-tolerance policy.** Any behavior deemed unacceptable under this Code of Conduct will result in **immediate enforcement, without prior warning.**
 
 We employ this approach to ensure that unproductive or disruptive behavior does not escalate further or cause unnecessary harm to other contributors. The standards are clear, and violations of any kind—whether mild or severe—will be addressed decisively to protect the community.
 
 ## Enforcement Responsibilities
+
 Community leaders are responsible for upholding and enforcing these standards. They are empowered to take **immediate and appropriate action** to address any behaviors they deem unacceptable under this Code of Conduct. These actions are taken with the goal of protecting the community and preserving its safe, positive, and productive environment.
 
 ## Scope
+
 This Code of Conduct applies to all community spaces, including forums, repositories, social media accounts, and in-person events. It also applies when an individual represents the community in public settings, such as conferences or official communications.
 
 Additionally, any behavior outside of these defined spaces that negatively impacts the community or its members may fall within the scope of this Code of Conduct.
 
 ## Reporting Violations
+
 Instances of unacceptable behavior can be reported to the leadership team at **hello@openwebui.com**. Reports will be handled promptly, confidentially, and with consideration for the safety and well-being of the reporter.
 
 All community leaders are required to uphold confidentiality and impartiality when addressing reports of violations.
 
 ## Enforcement Guidelines
+
 ### Ban
+
 **Community Impact**: Community leaders will issue a ban to any participant whose behavior is deemed unacceptable according to this Code of Conduct. Bans are enforced immediately and without prior notice.
 
 A ban may be temporary or permanent, depending on the severity of the violation. This includes—but is not limited to—behavior such as:
+
 - Harassment or abusive behavior toward contributors.
 - Persistent negativity or hostility that disrupts the collaborative environment.
 - Disrespectful, demanding, or aggressive interactions with others.
@@ -65,6 +78,7 @@ A ban may be temporary or permanent, depending on the severity of the violation.
 This approach ensures that disruptive behaviors are addressed swiftly and decisively in order to maintain the integrity and productivity of the community.
 
 ## Why Zero Tolerance Is Necessary
+
 Open-source projects thrive on collaboration, goodwill, and mutual respect. Toxic behaviors—such as entitlement, hostility, or persistent negativity—threaten not just individual contributors but the health of the project as a whole. Allowing such behaviors to persist robs contributors of their time, energy, and enthusiasm for the work they do.
 
 By enforcing a zero-tolerance policy, we ensure that the community remains a safe, welcoming space for all participants. These measures are not about harshness—they are about protecting contributors and fostering a productive environment where innovation can thrive.
@@ -72,13 +86,14 @@ By enforcing a zero-tolerance policy, we ensure that the community remains a saf
 Our expectations are clear, and our enforcement reflects our commitment to this project's long-term success.
 
 ## Attribution
+
 This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at  
-https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.  
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
 
-Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).  
+Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
 
-[homepage]: https://www.contributor-covenant.org  
+[homepage]: https://www.contributor-covenant.org
 
 For answers to common questions about this code of conduct, see the FAQ at  
 https://www.contributor-covenant.org/faq. Translations are available at  
-https://www.contributor-covenant.org/translations.
+https://www.contributor-covenant.org/translations.

+ 47 - 57
backend/open_webui/apps/audio/main.py

@@ -8,6 +8,8 @@ from pathlib import Path
 from pydub import AudioSegment
 from pydub.silence import split_on_silence
 
+import aiohttp
+import aiofiles
 import requests
 from open_webui.config import (
     AUDIO_STT_ENGINE,
@@ -292,46 +294,39 @@ async def speech(request: Request, user=Depends(get_verified_user)):
         except Exception:
             pass
 
-        r = None
         try:
-            r = requests.post(
-                url=f"{app.state.config.TTS_OPENAI_API_BASE_URL}/audio/speech",
-                data=body,
-                headers=headers,
-                stream=True,
-            )
+            async with aiohttp.ClientSession() as session:
+                async with session.post(
+                    url=f"{app.state.config.TTS_OPENAI_API_BASE_URL}/audio/speech",
+                    data=body,
+                    headers=headers,
+                ) as r:
+                    r.raise_for_status()
+                    async with aiofiles.open(file_path, "wb") as f:
+                        await f.write(await r.read())
+
+                    async with aiofiles.open(file_body_path, "w") as f:
+                        await f.write(json.dumps(json.loads(body.decode("utf-8"))))
 
-            r.raise_for_status()
-
-            # Save the streaming content to a file
-            with open(file_path, "wb") as f:
-                for chunk in r.iter_content(chunk_size=8192):
-                    f.write(chunk)
-
-            with open(file_body_path, "w") as f:
-                json.dump(json.loads(body.decode("utf-8")), f)
-
-            # Return the saved file
             return FileResponse(file_path)
 
         except Exception as e:
             log.exception(e)
             error_detail = "Open WebUI: Server Connection Error"
-            if r is not None:
-                try:
-                    res = r.json()
+            try:
+                if r.status != 200:
+                    res = await r.json()
                     if "error" in res:
                         error_detail = f"External: {res['error']['message']}"
-                except Exception:
-                    error_detail = f"External: {e}"
+            except Exception:
+                error_detail = f"External: {e}"
 
             raise HTTPException(
-                status_code=r.status_code if r != None else 500,
+                status_code=getattr(r, "status", 500),
                 detail=error_detail,
             )
 
     elif app.state.config.TTS_ENGINE == "elevenlabs":
-        payload = None
         try:
             payload = json.loads(body.decode("utf-8"))
         except Exception as e:
@@ -339,7 +334,6 @@ async def speech(request: Request, user=Depends(get_verified_user)):
             raise HTTPException(status_code=400, detail="Invalid JSON payload")
 
         voice_id = payload.get("voice", "")
-
         if voice_id not in get_available_voices():
             raise HTTPException(
                 status_code=400,
@@ -347,13 +341,11 @@ async def speech(request: Request, user=Depends(get_verified_user)):
             )
 
         url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}"
-
         headers = {
             "Accept": "audio/mpeg",
             "Content-Type": "application/json",
             "xi-api-key": app.state.config.TTS_API_KEY,
         }
-
         data = {
             "text": payload["input"],
             "model_id": app.state.config.TTS_MODEL,
@@ -361,39 +353,34 @@ async def speech(request: Request, user=Depends(get_verified_user)):
         }
 
         try:
-            r = requests.post(url, json=data, headers=headers)
+            async with aiohttp.ClientSession() as session:
+                async with session.post(url, json=data, headers=headers) as r:
+                    r.raise_for_status()
+                    async with aiofiles.open(file_path, "wb") as f:
+                        await f.write(await r.read())
 
-            r.raise_for_status()
-
-            # Save the streaming content to a file
-            with open(file_path, "wb") as f:
-                for chunk in r.iter_content(chunk_size=8192):
-                    f.write(chunk)
+                    async with aiofiles.open(file_body_path, "w") as f:
+                        await f.write(json.dumps(json.loads(body.decode("utf-8"))))
 
-            with open(file_body_path, "w") as f:
-                json.dump(json.loads(body.decode("utf-8")), f)
-
-            # Return the saved file
             return FileResponse(file_path)
 
         except Exception as e:
             log.exception(e)
             error_detail = "Open WebUI: Server Connection Error"
-            if r is not None:
-                try:
-                    res = r.json()
+            try:
+                if r.status != 200:
+                    res = await r.json()
                     if "error" in res:
                         error_detail = f"External: {res['error']['message']}"
-                except Exception:
-                    error_detail = f"External: {e}"
+            except Exception:
+                error_detail = f"External: {e}"
 
             raise HTTPException(
-                status_code=r.status_code if r != None else 500,
+                status_code=getattr(r, "status", 500),
                 detail=error_detail,
             )
 
     elif app.state.config.TTS_ENGINE == "azure":
-        payload = None
         try:
             payload = json.loads(body.decode("utf-8"))
         except Exception as e:
@@ -416,17 +403,20 @@ async def speech(request: Request, user=Depends(get_verified_user)):
                 <voice name="{language}">{payload["input"]}</voice>
             </speak>"""
 
-        response = requests.post(url, headers=headers, data=data)
-
-        if response.status_code == 200:
-            with open(file_path, "wb") as f:
-                f.write(response.content)
-            return FileResponse(file_path)
-        else:
-            log.error(f"Error synthesizing speech - {response.reason}")
-            raise HTTPException(
-                status_code=500, detail=f"Error synthesizing speech - {response.reason}"
-            )
+        try:
+            async with aiohttp.ClientSession() as session:
+                async with session.post(url, headers=headers, data=data) as response:
+                    if response.status == 200:
+                        async with aiofiles.open(file_path, "wb") as f:
+                            await f.write(await response.read())
+                        return FileResponse(file_path)
+                    else:
+                        error_msg = f"Error synthesizing speech - {response.reason}"
+                        log.error(error_msg)
+                        raise HTTPException(status_code=500, detail=error_msg)
+        except Exception as e:
+            log.exception(e)
+            raise HTTPException(status_code=500, detail=str(e))
     elif app.state.config.TTS_ENGINE == "transformers":
         payload = None
         try:

+ 35 - 11
backend/open_webui/apps/ollama/main.py

@@ -195,7 +195,10 @@ async def post_streaming_url(
             trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
         )
 
-        api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
+        parsed_url = urlparse(url)
+        base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+
+        api_config = app.state.config.OLLAMA_API_CONFIGS.get(base_url, {})
         key = api_config.get("key", None)
 
         headers = {"Content-Type": "application/json"}
@@ -210,13 +213,13 @@ async def post_streaming_url(
         r.raise_for_status()
 
         if stream:
-            headers = dict(r.headers)
+            response_headers = dict(r.headers)
             if content_type:
-                headers["Content-Type"] = content_type
+                response_headers["Content-Type"] = content_type
             return StreamingResponse(
                 r.content,
                 status_code=r.status,
-                headers=headers,
+                headers=response_headers,
                 background=BackgroundTask(
                     cleanup_response, response=r, session=session
                 ),
@@ -324,7 +327,10 @@ async def get_ollama_tags(
     else:
         url = app.state.config.OLLAMA_BASE_URLS[url_idx]
 
-        api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
+        parsed_url = urlparse(url)
+        base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+
+        api_config = app.state.config.OLLAMA_API_CONFIGS.get(base_url, {})
         key = api_config.get("key", None)
 
         headers = {}
@@ -525,7 +531,10 @@ async def copy_model(
     url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
-    api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
+    parsed_url = urlparse(url)
+    base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+
+    api_config = app.state.config.OLLAMA_API_CONFIGS.get(base_url, {})
     key = api_config.get("key", None)
 
     headers = {"Content-Type": "application/json"}
@@ -584,7 +593,10 @@ async def delete_model(
     url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
-    api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
+    parsed_url = urlparse(url)
+    base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+
+    api_config = app.state.config.OLLAMA_API_CONFIGS.get(base_url, {})
     key = api_config.get("key", None)
 
     headers = {"Content-Type": "application/json"}
@@ -635,7 +647,10 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_verified_us
     url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
-    api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
+    parsed_url = urlparse(url)
+    base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+
+    api_config = app.state.config.OLLAMA_API_CONFIGS.get(base_url, {})
     key = api_config.get("key", None)
 
     headers = {"Content-Type": "application/json"}
@@ -730,7 +745,10 @@ async def generate_ollama_embeddings(
     url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
-    api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
+    parsed_url = urlparse(url)
+    base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+
+    api_config = app.state.config.OLLAMA_API_CONFIGS.get(base_url, {})
     key = api_config.get("key", None)
 
     headers = {"Content-Type": "application/json"}
@@ -797,7 +815,10 @@ async def generate_ollama_batch_embeddings(
     url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
-    api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
+    parsed_url = urlparse(url)
+    base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+
+    api_config = app.state.config.OLLAMA_API_CONFIGS.get(base_url, {})
     key = api_config.get("key", None)
 
     headers = {"Content-Type": "application/json"}
@@ -974,7 +995,10 @@ async def generate_chat_completion(
     log.info(f"url: {url}")
     log.debug(f"generate_chat_completion() - 2.payload = {payload}")
 
-    api_config = app.state.config.OLLAMA_API_CONFIGS.get(url, {})
+    parsed_url = urlparse(url)
+    base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+
+    api_config = app.state.config.OLLAMA_API_CONFIGS.get(base_url, {})
     prefix_id = api_config.get("prefix_id", None)
     if prefix_id:
         payload["model"] = payload["model"].replace(f"{prefix_id}.", "")

+ 0 - 2
backend/open_webui/apps/openai/main.py

@@ -585,8 +585,6 @@ async def generate_chat_completion(
     # Convert the modified body back to JSON
     payload = json.dumps(payload)
 
-    log.debug(payload)
-
     headers = {}
     headers["Authorization"] = f"Bearer {key}"
     headers["Content-Type"] = "application/json"

+ 0 - 40
backend/open_webui/apps/retrieval/utils.py

@@ -15,8 +15,6 @@ from open_webui.apps.retrieval.vector.connector import VECTOR_DB_CLIENT
 from open_webui.utils.misc import get_last_user_message
 
 from open_webui.env import SRC_LOG_LEVELS
-from open_webui.config import DEFAULT_RAG_TEMPLATE
-
 
 log = logging.getLogger(__name__)
 log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -238,44 +236,6 @@ def query_collection_with_hybrid_search(
     return merge_and_sort_query_results(results, k=k, reverse=True)
 
 
-def rag_template(template: str, context: str, query: str):
-    if template == "":
-        template = DEFAULT_RAG_TEMPLATE
-
-    if "[context]" not in template and "{{CONTEXT}}" not in template:
-        log.debug(
-            "WARNING: The RAG template does not contain the '[context]' or '{{CONTEXT}}' placeholder."
-        )
-
-    if "<context>" in context and "</context>" in context:
-        log.debug(
-            "WARNING: Potential prompt injection attack: the RAG "
-            "context contains '<context>' and '</context>'. This might be "
-            "nothing, or the user might be trying to hack something."
-        )
-
-    query_placeholders = []
-    if "[query]" in context:
-        query_placeholder = "{{QUERY" + str(uuid.uuid4()) + "}}"
-        template = template.replace("[query]", query_placeholder)
-        query_placeholders.append(query_placeholder)
-
-    if "{{QUERY}}" in context:
-        query_placeholder = "{{QUERY" + str(uuid.uuid4()) + "}}"
-        template = template.replace("{{QUERY}}", query_placeholder)
-        query_placeholders.append(query_placeholder)
-
-    template = template.replace("[context]", context)
-    template = template.replace("{{CONTEXT}}", context)
-    template = template.replace("[query]", query)
-    template = template.replace("{{QUERY}}", query)
-
-    for query_placeholder in query_placeholders:
-        template = template.replace(query_placeholder, query)
-
-    return template
-
-
 def get_embedding_function(
     embedding_engine,
     embedding_model,

+ 15 - 4
backend/open_webui/apps/webui/main.py

@@ -31,6 +31,7 @@ from open_webui.config import (
     DEFAULT_MODELS,
     DEFAULT_PROMPT_SUGGESTIONS,
     DEFAULT_USER_ROLE,
+    MODEL_ORDER_LIST,
     ENABLE_COMMUNITY_SHARING,
     ENABLE_LOGIN_FORM,
     ENABLE_MESSAGE_RATING,
@@ -68,6 +69,7 @@ from open_webui.config import (
 )
 from open_webui.env import (
     ENV,
+    SRC_LOG_LEVELS,
     WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
     WEBUI_AUTH_TRUSTED_NAME_HEADER,
 )
@@ -94,6 +96,7 @@ app = FastAPI(
 )
 
 log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MAIN"])
 
 app.state.config = AppConfig()
 
@@ -118,6 +121,7 @@ app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
 app.state.config.USER_PERMISSIONS = USER_PERMISSIONS
 app.state.config.WEBHOOK_URL = WEBHOOK_URL
 app.state.config.BANNERS = WEBUI_BANNERS
+app.state.config.MODEL_ORDER_LIST = MODEL_ORDER_LIST
 
 app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
 app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING
@@ -270,7 +274,9 @@ async def get_pipe_models():
                 log.exception(e)
                 sub_pipes = []
 
-            print(sub_pipes)
+            log.debug(
+                f"get_pipe_models: function '{pipe.id}' is a manifold of {sub_pipes}"
+            )
 
             for p in sub_pipes:
                 sub_pipe_id = f'{pipe.id}.{p["id"]}'
@@ -280,6 +286,7 @@ async def get_pipe_models():
                     sub_pipe_name = f"{function_module.name}{sub_pipe_name}"
 
                 pipe_flag = {"type": pipe.type}
+
                 pipe_models.append(
                     {
                         "id": sub_pipe_id,
@@ -293,6 +300,10 @@ async def get_pipe_models():
         else:
             pipe_flag = {"type": "pipe"}
 
+            log.debug(
+                f"get_pipe_models: function '{pipe.id}' is a single pipe {{ 'id': {pipe.id}, 'name': {pipe.name} }}"
+            )
+
             pipe_models.append(
                 {
                     "id": pipe.id,
@@ -346,7 +357,7 @@ def get_pipe_id(form_data: dict) -> str:
     pipe_id = form_data["model"]
     if "." in pipe_id:
         pipe_id, _ = pipe_id.split(".", 1)
-    print(pipe_id)
+
     return pipe_id
 
 
@@ -453,7 +464,7 @@ async def generate_function_chat_completion(form_data, user, models: dict = {}):
                     return
 
             except Exception as e:
-                print(f"Error: {e}")
+                log.error(f"Error: {e}")
                 yield f"data: {json.dumps({'error': {'detail':str(e)}})}\n\n"
                 return
 
@@ -483,7 +494,7 @@ async def generate_function_chat_completion(form_data, user, models: dict = {}):
             res = await execute_pipe(pipe, params)
 
         except Exception as e:
-            print(f"Error: {e}")
+            log.error(f"Error: {e}")
             return {"error": {"detail": str(e)}}
 
         if isinstance(res, StreamingResponse) or isinstance(res, dict):

+ 28 - 17
backend/open_webui/apps/webui/routers/configs.py

@@ -34,8 +34,32 @@ async def export_config(user=Depends(get_admin_user)):
     return get_config()
 
 
-class SetDefaultModelsForm(BaseModel):
-    models: str
+############################
+# SetDefaultModels
+############################
+class ModelsConfigForm(BaseModel):
+    DEFAULT_MODELS: str
+    MODEL_ORDER_LIST: list[str]
+
+
+@router.get("/models", response_model=ModelsConfigForm)
+async def get_models_config(request: Request, user=Depends(get_admin_user)):
+    return {
+        "DEFAULT_MODELS": request.app.state.config.DEFAULT_MODELS,
+        "MODEL_ORDER_LIST": request.app.state.config.MODEL_ORDER_LIST,
+    }
+
+
+@router.post("/models", response_model=ModelsConfigForm)
+async def set_models_config(
+    request: Request, form_data: ModelsConfigForm, user=Depends(get_admin_user)
+):
+    request.app.state.config.DEFAULT_MODELS = form_data.DEFAULT_MODELS
+    request.app.state.config.MODEL_ORDER_LIST = form_data.MODEL_ORDER_LIST
+    return {
+        "DEFAULT_MODELS": request.app.state.config.DEFAULT_MODELS,
+        "MODEL_ORDER_LIST": request.app.state.config.MODEL_ORDER_LIST,
+    }
 
 
 class PromptSuggestion(BaseModel):
@@ -47,21 +71,8 @@ class SetDefaultSuggestionsForm(BaseModel):
     suggestions: list[PromptSuggestion]
 
 
-############################
-# SetDefaultModels
-############################
-
-
-@router.post("/default/models", response_model=str)
-async def set_global_default_models(
-    request: Request, form_data: SetDefaultModelsForm, user=Depends(get_admin_user)
-):
-    request.app.state.config.DEFAULT_MODELS = form_data.models
-    return request.app.state.config.DEFAULT_MODELS
-
-
-@router.post("/default/suggestions", response_model=list[PromptSuggestion])
-async def set_global_default_suggestions(
+@router.post("/suggestions", response_model=list[PromptSuggestion])
+async def set_default_suggestions(
     request: Request,
     form_data: SetDefaultSuggestionsForm,
     user=Depends(get_admin_user),

+ 11 - 6
backend/open_webui/apps/webui/utils.py

@@ -5,10 +5,15 @@ import sys
 from importlib import util
 import types
 import tempfile
+import logging
 
+from open_webui.env import SRC_LOG_LEVELS
 from open_webui.apps.webui.models.functions import Functions
 from open_webui.apps.webui.models.tools import Tools
 
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MAIN"])
+
 
 def extract_frontmatter(content):
     """
@@ -95,7 +100,7 @@ def load_tools_module_by_id(toolkit_id, content=None):
         # Executing the modified content in the created module's namespace
         exec(content, module.__dict__)
         frontmatter = extract_frontmatter(content)
-        print(f"Loaded module: {module.__name__}")
+        log.info(f"Loaded module: {module.__name__}")
 
         # Create and return the object if the class 'Tools' is found in the module
         if hasattr(module, "Tools"):
@@ -103,7 +108,7 @@ def load_tools_module_by_id(toolkit_id, content=None):
         else:
             raise Exception("No Tools class found in the module")
     except Exception as e:
-        print(f"Error loading module: {toolkit_id}: {e}")
+        log.error(f"Error loading module: {toolkit_id}: {e}")
         del sys.modules[module_name]  # Clean up
         raise e
     finally:
@@ -139,7 +144,7 @@ def load_function_module_by_id(function_id, content=None):
         # Execute the modified content in the created module's namespace
         exec(content, module.__dict__)
         frontmatter = extract_frontmatter(content)
-        print(f"Loaded module: {module.__name__}")
+        log.info(f"Loaded module: {module.__name__}")
 
         # Create appropriate object based on available class type in the module
         if hasattr(module, "Pipe"):
@@ -151,7 +156,7 @@ def load_function_module_by_id(function_id, content=None):
         else:
             raise Exception("No Function class found in the module")
     except Exception as e:
-        print(f"Error loading module: {function_id}: {e}")
+        log.error(f"Error loading module: {function_id}: {e}")
         del sys.modules[module_name]  # Cleanup by removing the module in case of error
 
         Functions.update_function_by_id(function_id, {"is_active": False})
@@ -164,7 +169,7 @@ def install_frontmatter_requirements(requirements):
     if requirements:
         req_list = [req.strip() for req in requirements.split(",")]
         for req in req_list:
-            print(f"Installing requirement: {req}")
+            log.info(f"Installing requirement: {req}")
             subprocess.check_call([sys.executable, "-m", "pip", "install", req])
     else:
-        print("No requirements found in frontmatter.")
+        log.info("No requirements found in frontmatter.")

+ 16 - 9
backend/open_webui/config.py

@@ -740,6 +740,12 @@ DEFAULT_PROMPT_SUGGESTIONS = PersistentConfig(
     ],
 )
 
+MODEL_ORDER_LIST = PersistentConfig(
+    "MODEL_ORDER_LIST",
+    "ui.model_order_list",
+    [],
+)
+
 DEFAULT_USER_ROLE = PersistentConfig(
     "DEFAULT_USER_ROLE",
     "ui.default_user_role",
@@ -969,19 +975,20 @@ QUERY_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
 )
 
 DEFAULT_QUERY_GENERATION_PROMPT_TEMPLATE = """### Task:
-Based on the chat history, determine whether a search is necessary, and if so, generate a 1-3 broad search queries to retrieve comprehensive and updated information. If no search is required, return an empty list.
+Analyze the chat history to determine the necessity of generating search queries. By default, **prioritize generating 1-3 broad and relevant search queries** unless it is absolutely certain that no additional information is required. The aim is to retrieve comprehensive, updated, and valuable information even with minimal uncertainty. If no search is unequivocally needed, return an empty list.
 
 ### Guidelines:
-- Respond exclusively with a JSON object.
-- If a search query is needed, return an object like: { "queries": ["query1", "query2"] } where each query is distinct and concise.
-- If no search query is necessary, output should be: { "queries": [] }
-- Default to suggesting a search query to ensure accurate and updated information, unless it is definitively clear no search is required.
-- Be concise, focusing strictly on composing search queries with no additional commentary or text.
-- When in doubt, prefer to suggest a search for comprehensiveness.
-- Today's date is: {{CURRENT_DATE}}
+- Respond **EXCLUSIVELY** with a JSON object. Any form of extra commentary, explanation, or additional text is strictly prohibited.
+- When generating search queries, respond in the format: { "queries": ["query1", "query2"] }, ensuring each query is distinct, concise, and relevant to the topic.
+- If and only if it is entirely certain that no useful results can be retrieved by a search, return: { "queries": [] }.
+- Err on the side of suggesting search queries if there is **any chance** they might provide useful or updated information.
+- Be concise and focused on composing high-quality search queries, avoiding unnecessary elaboration, commentary, or assumptions.
+- Assume today's date is: {{CURRENT_DATE}}.
+- Always prioritize providing actionable and broad queries that maximize informational coverage.
 
 ### Output:
-JSON format: {
+Strictly return in JSON format: 
+{
   "queries": ["query1", "query2"]
 }
 

+ 52 - 32
backend/open_webui/main.py

@@ -49,7 +49,9 @@ from open_webui.apps.openai.main import (
     get_all_models_responses as get_openai_models_responses,
 )
 from open_webui.apps.retrieval.main import app as retrieval_app
-from open_webui.apps.retrieval.utils import get_sources_from_files, rag_template
+from open_webui.apps.retrieval.utils import get_sources_from_files
+
+
 from open_webui.apps.socket.main import (
     app as socket_app,
     periodic_usage_pool_cleanup,
@@ -122,11 +124,12 @@ from open_webui.utils.response import (
 )
 from open_webui.utils.security_headers import SecurityHeadersMiddleware
 from open_webui.utils.task import (
-    moa_response_generation_template,
-    tags_generation_template,
+    rag_template,
+    title_generation_template,
     query_generation_template,
+    tags_generation_template,
     emoji_generation_template,
-    title_generation_template,
+    moa_response_generation_template,
     tools_function_calling_generation_template,
 )
 from open_webui.utils.tools import get_tools
@@ -539,8 +542,6 @@ async def chat_completion_files_handler(
         if len(queries) == 0:
             queries = [get_last_user_message(body["messages"])]
 
-        print(f"{queries=}")
-
         sources = get_sources_from_files(
             files=files,
             queries=queries,
@@ -970,7 +971,7 @@ app.add_middleware(SecurityHeadersMiddleware)
 @app.middleware("http")
 async def commit_session_after_request(request: Request, call_next):
     response = await call_next(request)
-    log.debug("Commit session after request")
+    # log.debug("Commit session after request")
     Session.commit()
     return response
 
@@ -1177,6 +1178,8 @@ async def get_all_models():
             model["actions"].extend(
                 get_action_items_from_module(action_function, function_module)
             )
+    log.debug(f"get_all_models() returned {len(models)} models")
+
     return models
 
 
@@ -1191,6 +1194,14 @@ async def get_models(user=Depends(get_verified_user)):
         if "pipeline" not in model or model["pipeline"].get("type", None) != "filter"
     ]
 
+    model_order_list = webui_app.state.config.MODEL_ORDER_LIST
+    if model_order_list:
+        model_order_dict = {model_id: i for i, model_id in enumerate(model_order_list)}
+        # Sort models by order list priority, with fallback for those not in the list
+        models.sort(
+            key=lambda x: (model_order_dict.get(x["id"], float("inf")), x["name"])
+        )
+
     # Filter out models that the user does not have access to
     if user.role == "user":
         filtered_models = []
@@ -1214,6 +1225,10 @@ async def get_models(user=Depends(get_verified_user)):
                     filtered_models.append(model)
         models = filtered_models
 
+    log.debug(
+        f"/api/models returned filtered models accessible to the user: {json.dumps([model['id'] for model in models])}"
+    )
+
     return {"data": models}
 
 
@@ -1704,7 +1719,6 @@ async def update_task_config(form_data: TaskConfigForm, user=Depends(get_admin_u
 
 @app.post("/api/task/title/completions")
 async def generate_title(form_data: dict, user=Depends(get_verified_user)):
-    print("generate_title")
 
     model_list = await get_all_models()
     models = {model["id"]: model for model in model_list}
@@ -1725,9 +1739,9 @@ async def generate_title(form_data: dict, user=Depends(get_verified_user)):
         models,
     )
 
-    print(task_model_id)
-
-    model = models[task_model_id]
+    log.debug(
+        f"generating chat title using model {task_model_id} for user {user.email} "
+    )
 
     if app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE != "":
         template = app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE
@@ -1766,10 +1780,12 @@ Artificial Intelligence in Healthcare
                 "max_completion_tokens": 50,
             }
         ),
-        "chat_id": form_data.get("chat_id", None),
-        "metadata": {"task": str(TASKS.TITLE_GENERATION), "task_body": form_data},
+        "metadata": {
+            "task": str(TASKS.TITLE_GENERATION),
+            "task_body": form_data,
+            "chat_id": form_data.get("chat_id", None),
+        },
     }
-    log.debug(payload)
 
     # Handle pipeline filters
     try:
@@ -1793,7 +1809,7 @@ Artificial Intelligence in Healthcare
 
 @app.post("/api/task/tags/completions")
 async def generate_chat_tags(form_data: dict, user=Depends(get_verified_user)):
-    print("generate_chat_tags")
+
     if not app.state.config.ENABLE_TAGS_GENERATION:
         return JSONResponse(
             status_code=status.HTTP_200_OK,
@@ -1818,7 +1834,10 @@ async def generate_chat_tags(form_data: dict, user=Depends(get_verified_user)):
         app.state.config.TASK_MODEL_EXTERNAL,
         models,
     )
-    print(task_model_id)
+
+    log.debug(
+        f"generating chat tags using model {task_model_id} for user {user.email} "
+    )
 
     if app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE != "":
         template = app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE
@@ -1849,9 +1868,12 @@ JSON format: { "tags": ["tag1", "tag2", "tag3"] }
         "model": task_model_id,
         "messages": [{"role": "user", "content": content}],
         "stream": False,
-        "metadata": {"task": str(TASKS.TAGS_GENERATION), "task_body": form_data},
+        "metadata": {
+            "task": str(TASKS.TAGS_GENERATION),
+            "task_body": form_data,
+            "chat_id": form_data.get("chat_id", None),
+        },
     }
-    log.debug(payload)
 
     # Handle pipeline filters
     try:
@@ -1875,7 +1897,7 @@ JSON format: { "tags": ["tag1", "tag2", "tag3"] }
 
 @app.post("/api/task/queries/completions")
 async def generate_queries(form_data: dict, user=Depends(get_verified_user)):
-    print("generate_queries")
+
     type = form_data.get("type")
     if type == "web_search":
         if not app.state.config.ENABLE_SEARCH_QUERY_GENERATION:
@@ -1908,9 +1930,10 @@ async def generate_queries(form_data: dict, user=Depends(get_verified_user)):
         app.state.config.TASK_MODEL_EXTERNAL,
         models,
     )
-    print(task_model_id)
 
-    model = models[task_model_id]
+    log.debug(
+        f"generating {type} queries using model {task_model_id} for user {user.email}"
+    )
 
     if app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE != "":
         template = app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE
@@ -1925,9 +1948,12 @@ async def generate_queries(form_data: dict, user=Depends(get_verified_user)):
         "model": task_model_id,
         "messages": [{"role": "user", "content": content}],
         "stream": False,
-        "metadata": {"task": str(TASKS.QUERY_GENERATION), "task_body": form_data},
+        "metadata": {
+            "task": str(TASKS.QUERY_GENERATION),
+            "task_body": form_data,
+            "chat_id": form_data.get("chat_id", None),
+        },
     }
-    log.debug(payload)
 
     # Handle pipeline filters
     try:
@@ -1951,7 +1977,6 @@ async def generate_queries(form_data: dict, user=Depends(get_verified_user)):
 
 @app.post("/api/task/emoji/completions")
 async def generate_emoji(form_data: dict, user=Depends(get_verified_user)):
-    print("generate_emoji")
 
     model_list = await get_all_models()
     models = {model["id"]: model for model in model_list}
@@ -1971,9 +1996,8 @@ async def generate_emoji(form_data: dict, user=Depends(get_verified_user)):
         app.state.config.TASK_MODEL_EXTERNAL,
         models,
     )
-    print(task_model_id)
 
-    model = models[task_model_id]
+    log.debug(f"generating emoji using model {task_model_id} for user {user.email} ")
 
     template = '''
 Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., 😊, 😢, 😡, 😱).
@@ -2003,7 +2027,6 @@ Message: """{{prompt}}"""
         "chat_id": form_data.get("chat_id", None),
         "metadata": {"task": str(TASKS.EMOJI_GENERATION), "task_body": form_data},
     }
-    log.debug(payload)
 
     # Handle pipeline filters
     try:
@@ -2027,7 +2050,6 @@ Message: """{{prompt}}"""
 
 @app.post("/api/task/moa/completions")
 async def generate_moa_response(form_data: dict, user=Depends(get_verified_user)):
-    print("generate_moa_response")
 
     model_list = await get_all_models()
     models = {model["id"]: model for model in model_list}
@@ -2047,9 +2069,8 @@ async def generate_moa_response(form_data: dict, user=Depends(get_verified_user)
         app.state.config.TASK_MODEL_EXTERNAL,
         models,
     )
-    print(task_model_id)
 
-    model = models[task_model_id]
+    log.debug(f"generating MOA model {task_model_id} for user {user.email} ")
 
     template = """You have been provided with a set of responses from various models to the latest user query: "{{prompt}}"
 
@@ -2073,7 +2094,6 @@ Responses from models: {{responses}}"""
             "task_body": form_data,
         },
     }
-    log.debug(payload)
 
     try:
         payload = filter_pipeline(payload, user, models)
@@ -2108,7 +2128,7 @@ Responses from models: {{responses}}"""
 async def get_pipelines_list(user=Depends(get_admin_user)):
     responses = await get_openai_models_responses()
 
-    print(responses)
+    log.debug(f"get_pipelines_list: get_openai_models_responses returned {responses}")
     urlIdxs = [
         idx
         for idx, response in enumerate(responses)

+ 47 - 0
backend/open_webui/utils/task.py

@@ -1,11 +1,20 @@
+import logging
 import math
 import re
 from datetime import datetime
 from typing import Optional
+import uuid
 
 
 from open_webui.utils.misc import get_last_user_message, get_messages_content
 
+from open_webui.env import SRC_LOG_LEVELS
+from open_webui.config import DEFAULT_RAG_TEMPLATE
+
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
 
 def prompt_template(
     template: str, user_name: Optional[str] = None, user_location: Optional[str] = None
@@ -110,6 +119,44 @@ def replace_messages_variable(template: str, messages: list[str]) -> str:
 # {{prompt:middletruncate:8000}}
 
 
+def rag_template(template: str, context: str, query: str):
+    if template == "":
+        template = DEFAULT_RAG_TEMPLATE
+
+    if "[context]" not in template and "{{CONTEXT}}" not in template:
+        log.debug(
+            "WARNING: The RAG template does not contain the '[context]' or '{{CONTEXT}}' placeholder."
+        )
+
+    if "<context>" in context and "</context>" in context:
+        log.debug(
+            "WARNING: Potential prompt injection attack: the RAG "
+            "context contains '<context>' and '</context>'. This might be "
+            "nothing, or the user might be trying to hack something."
+        )
+
+    query_placeholders = []
+    if "[query]" in context:
+        query_placeholder = "{{QUERY" + str(uuid.uuid4()) + "}}"
+        template = template.replace("[query]", query_placeholder)
+        query_placeholders.append(query_placeholder)
+
+    if "{{QUERY}}" in context:
+        query_placeholder = "{{QUERY" + str(uuid.uuid4()) + "}}"
+        template = template.replace("{{QUERY}}", query_placeholder)
+        query_placeholders.append(query_placeholder)
+
+    template = template.replace("[context]", context)
+    template = template.replace("{{CONTEXT}}", context)
+    template = template.replace("[query]", query)
+    template = template.replace("{{QUERY}}", query)
+
+    for query_placeholder in query_placeholders:
+        template = template.replace(query_placeholder, query)
+
+    return template
+
+
 def title_generation_template(
     template: str, messages: list[dict], user: Optional[dict] = None
 ) -> str:

+ 32 - 1
backend/open_webui/utils/tools.py

@@ -90,6 +90,32 @@ def get_tools(
     return tools_dict
 
 
+def parse_description(docstring: str | None) -> str:
+    """
+    Parse a function's docstring to extract the description.
+
+    Args:
+        docstring (str): The docstring to parse.
+
+    Returns:
+        str: The description.
+    """
+
+    if not docstring:
+        return ""
+
+    lines = [line.strip() for line in docstring.strip().split("\n")]
+    description_lines: list[str] = []
+
+    for line in lines:
+        if re.match(r":param", line) or re.match(r":return", line):
+            break
+
+        description_lines.append(line)
+
+    return "\n".join(description_lines)
+
+
 def parse_docstring(docstring):
     """
     Parse a function's docstring to extract parameter descriptions in reST format.
@@ -138,6 +164,8 @@ def function_to_pydantic_model(func: Callable) -> type[BaseModel]:
     docstring = func.__doc__
     descriptions = parse_docstring(docstring)
 
+    tool_description = parse_description(docstring)
+
     field_defs = {}
     for name, param in parameters.items():
         type_hint = type_hints.get(name, Any)
@@ -148,7 +176,10 @@ def function_to_pydantic_model(func: Callable) -> type[BaseModel]:
             continue
         field_defs[name] = type_hint, Field(default_value, description=description)
 
-    return create_model(func.__name__, **field_defs)
+    model = create_model(func.__name__, **field_defs)
+    model.__doc__ = tool_description
+
+    return model
 
 
 def get_callable_attributes(tool: object) -> list[Callable]:

+ 1 - 0
backend/requirements.txt

@@ -14,6 +14,7 @@ requests==2.32.3
 aiohttp==3.10.8
 async-timeout
 aiocache
+aiofiles
 
 sqlalchemy==2.0.32
 alembic==1.13.2

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
 	"name": "open-webui",
-	"version": "0.4.4",
+	"version": "0.4.5",
 	"lockfileVersion": 3,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "open-webui",
-			"version": "0.4.4",
+			"version": "0.4.5",
 			"dependencies": {
 				"@codemirror/lang-javascript": "^6.2.2",
 				"@codemirror/lang-python": "^6.1.6",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 	"name": "open-webui",
-	"version": "0.4.4",
+	"version": "0.4.5",
 	"private": true,
 	"scripts": {
 		"dev": "npm run pyodide:fetch && vite dev --host",

+ 1 - 0
pyproject.toml

@@ -22,6 +22,7 @@ dependencies = [
     "aiohttp==3.10.8",
     "async-timeout",
     "aiocache",
+    "aiofiles",
 
     "sqlalchemy==2.0.32",
     "alembic==1.13.2",

+ 9 - 0
src/app.css

@@ -231,6 +231,15 @@ input[type='number'] {
 	@apply dark:bg-gray-800 bg-gray-100;
 }
 
+.tiptap p code {
+	color: #eb5757;
+	border-width: 0px;
+	padding: 3px 8px;
+	font-size: 0.8em;
+	font-weight: 600;
+	@apply rounded-md dark:bg-gray-800 bg-gray-100 mx-0.5;
+}
+
 /* Code styling */
 .hljs-comment,
 .hljs-quote {

+ 31 - 4
src/lib/apis/configs/index.ts

@@ -58,17 +58,44 @@ export const exportConfig = async (token: string) => {
 	return res;
 };
 
-export const setDefaultModels = async (token: string, models: string) => {
+export const getModelsConfig = async (token: string) => {
 	let error = null;
 
-	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/default/models`, {
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/models`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const setModelsConfig = async (token: string, config: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/models`, {
 		method: 'POST',
 		headers: {
 			'Content-Type': 'application/json',
 			Authorization: `Bearer ${token}`
 		},
 		body: JSON.stringify({
-			models: models
+			...config
 		})
 	})
 		.then(async (res) => {
@@ -91,7 +118,7 @@ export const setDefaultModels = async (token: string, models: string) => {
 export const setDefaultPromptSuggestions = async (token: string, promptSuggestions: string) => {
 	let error = null;
 
-	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/default/suggestions`, {
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/suggestions`, {
 		method: 'POST',
 		headers: {
 			'Content-Type': 'application/json',

+ 3 - 26
src/lib/apis/index.ts

@@ -25,26 +25,6 @@ export const getModels = async (token: string = '', base: boolean = false) => {
 	}
 
 	let models = res?.data ?? [];
-	models = models
-		.filter((models) => models)
-		// Sort the models
-		.sort((a, b) => {
-			// Compare case-insensitively by name for models without position property
-			const lowerA = a.name.toLowerCase();
-			const lowerB = b.name.toLowerCase();
-
-			if (lowerA < lowerB) return -1;
-			if (lowerA > lowerB) return 1;
-
-			// If same case-insensitively, sort by original strings,
-			// lowercase will come before uppercase due to ASCII values
-			if (a.name < b.name) return -1;
-			if (a.name > b.name) return 1;
-
-			return 0; // They are equal
-		});
-
-	console.log(models);
 	return models;
 };
 
@@ -391,16 +371,13 @@ export const generateQueries = async (
 		// Step 1: Safely extract the response string
 		const response = res?.choices[0]?.message?.content ?? '';
 
-		// Step 2: Attempt to fix common JSON format issues like single quotes
-		const sanitizedResponse = response.replace(/['‘’`]/g, '"'); // Convert single quotes to double quotes for valid JSON
-
 		// Step 3: Find the relevant JSON block within the response
-		const jsonStartIndex = sanitizedResponse.indexOf('{');
-		const jsonEndIndex = sanitizedResponse.lastIndexOf('}');
+		const jsonStartIndex = response.indexOf('{');
+		const jsonEndIndex = response.lastIndexOf('}');
 
 		// Step 4: Check if we found a valid JSON block (with both `{` and `}`)
 		if (jsonStartIndex !== -1 && jsonEndIndex !== -1) {
-			const jsonResponse = sanitizedResponse.substring(jsonStartIndex, jsonEndIndex + 1);
+			const jsonResponse = response.substring(jsonStartIndex, jsonEndIndex + 1);
 
 			// Step 5: Parse the JSON block
 			const parsed = JSON.parse(jsonResponse);

+ 1 - 1
src/lib/components/admin/Functions.svelte

@@ -219,7 +219,7 @@
 </div>
 
 <div class="mb-5">
-	{#each filteredItems as func}
+	{#each filteredItems as func (func.id)}
 		<div
 			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
 		>

+ 2 - 1
src/lib/components/admin/Settings/Evaluations.svelte

@@ -5,6 +5,7 @@
 
 	const dispatch = createEventDispatcher();
 	import { getModels } from '$lib/apis';
+	import { getConfig, updateConfig } from '$lib/apis/evaluations';
 
 	import Switch from '$lib/components/common/Switch.svelte';
 	import Spinner from '$lib/components/common/Spinner.svelte';
@@ -12,7 +13,6 @@
 	import Plus from '$lib/components/icons/Plus.svelte';
 	import Model from './Evaluations/Model.svelte';
 	import ArenaModelModal from './Evaluations/ArenaModelModal.svelte';
-	import { getConfig, updateConfig } from '$lib/apis/evaluations';
 
 	const i18n = getContext('i18n');
 
@@ -27,6 +27,7 @@
 
 		if (config) {
 			toast.success('Settings saved successfully');
+			models.set(await getModels(localStorage.token));
 		}
 	};
 

+ 2 - 2
src/lib/components/admin/Settings/Evaluations/ArenaModelModal.svelte

@@ -375,7 +375,7 @@
 					<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
 						{#if edit}
 							<button
-								class="px-3.5 py-1.5 text-sm font-medium dark:bg-black dark:hover:bg-gray-900 dark:text-white bg-white text-black hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"
+								class="px-3.5 py-1.5 text-sm font-medium dark:bg-black dark:hover:bg-gray-950 dark:text-white bg-white text-black hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"
 								type="button"
 								on:click={() => {
 									dispatch('delete', model);
@@ -387,7 +387,7 @@
 						{/if}
 
 						<button
-							class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
+							class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-950 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
 								? ' cursor-not-allowed'
 								: ''}"
 							type="submit"

+ 20 - 24
src/lib/components/admin/Settings/Models.svelte

@@ -24,6 +24,8 @@
 	import ModelEditor from '$lib/components/workspace/Models/ModelEditor.svelte';
 	import { toast } from 'svelte-sonner';
 	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import Cog6 from '$lib/components/icons/Cog6.svelte';
+	import ConfigureModelsModal from './Models/ConfigureModelsModal.svelte';
 
 	let importFiles;
 	let modelsImportInputElement: HTMLInputElement;
@@ -35,12 +37,20 @@
 
 	let filteredModels = [];
 	let selectedModelId = null;
-	let showResetModal = false;
+
+	let showConfigModal = false;
 
 	$: if (models) {
-		filteredModels = models.filter(
-			(m) => searchValue === '' || m.name.toLowerCase().includes(searchValue.toLowerCase())
-		);
+		filteredModels = models
+			.filter((m) => searchValue === '' || m.name.toLowerCase().includes(searchValue.toLowerCase()))
+			.sort((a, b) => {
+				// Check if either model is inactive and push them to the bottom
+				if ((a.is_active ?? true) !== (b.is_active ?? true)) {
+					return (b.is_active ?? true) - (a.is_active ?? true);
+				}
+				// If both models' active states are the same, sort alphabetically
+				return a.name.localeCompare(b.name);
+			});
 	}
 
 	let searchValue = '';
@@ -114,12 +124,11 @@
 			}).catch((error) => {
 				return null;
 			});
-
-			await init();
 		} else {
 			await toggleModelById(localStorage.token, model.id);
 		}
 
+		await init();
 		_models.set(await getModels(localStorage.token));
 	};
 
@@ -128,18 +137,7 @@
 	});
 </script>
 
-<ConfirmDialog
-	title={$i18n.t('Delete All Models')}
-	message={$i18n.t('This will delete all models including custom models and cannot be undone.')}
-	bind:show={showResetModal}
-	onConfirm={async () => {
-		const res = deleteAllModels(localStorage.token);
-		if (res) {
-			toast.success($i18n.t('All models deleted successfully'));
-			init();
-		}
-	}}
-/>
+<ConfigureModelsModal bind:show={showConfigModal} {init} />
 
 {#if models !== null}
 	{#if selectedModelId === null}
@@ -154,17 +152,15 @@
 				</div>
 
 				<div>
-					<Tooltip content={$i18n.t('This will delete all models including custom models')}>
+					<Tooltip content={$i18n.t('Configure')}>
 						<button
 							class=" px-2.5 py-1 rounded-full flex gap-1 items-center"
 							type="button"
 							on:click={() => {
-								showResetModal = true;
+								showConfigModal = true;
 							}}
 						>
-							<div class="text-xs flex-shrink-0">
-								{$i18n.t('Reset')}
-							</div>
+							<Cog6 />
 						</button>
 					</Tooltip>
 				</div>
@@ -186,7 +182,7 @@
 
 		<div class=" my-2 mb-5" id="model-list">
 			{#if models.length > 0}
-				{#each filteredModels as model, modelIdx (`${model.id}-${modelIdx}`)}
+				{#each filteredModels as model, modelIdx (model.id)}
 					<div
 						class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-lg transition"
 						id="model-item-{model.id}"

+ 258 - 0
src/lib/components/admin/Settings/Models/ConfigureModelsModal.svelte

@@ -0,0 +1,258 @@
+<script>
+	import { toast } from 'svelte-sonner';
+
+	import { createEventDispatcher, getContext, onMount } from 'svelte';
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import { models } from '$lib/stores';
+	import { deleteAllModels } from '$lib/apis/models';
+
+	import Modal from '$lib/components/common/Modal.svelte';
+	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import ModelList from './ModelList.svelte';
+	import { getModelsConfig, setModelsConfig } from '$lib/apis/configs';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import Minus from '$lib/components/icons/Minus.svelte';
+	import Plus from '$lib/components/icons/Plus.svelte';
+
+	export let show = false;
+	export let init = () => {};
+
+	let config = null;
+
+	let selectedModelId = '';
+	let defaultModelIds = [];
+	let modelIds = [];
+
+	let loading = false;
+	let showResetModal = false;
+
+	const submitHandler = async () => {
+		loading = true;
+
+		const res = await setModelsConfig(localStorage.token, {
+			DEFAULT_MODELS: defaultModelIds.join(','),
+			MODEL_ORDER_LIST: modelIds
+		});
+
+		if (res) {
+			toast.success($i18n.t('Models configuration saved successfully'));
+			init();
+			show = false;
+		} else {
+			toast.error($i18n.t('Failed to save models configuration'));
+		}
+
+		loading = false;
+	};
+
+	onMount(async () => {
+		config = await getModelsConfig(localStorage.token);
+
+		const modelOrderList = config.MODEL_ORDER_LIST || [];
+		const allModelIds = $models.map((model) => model.id);
+
+		// Create a Set for quick lookup of ordered IDs
+		const orderedSet = new Set(modelOrderList);
+
+		modelIds = [
+			// Add all IDs from MODEL_ORDER_LIST that exist in allModelIds
+			...modelOrderList.filter((id) => orderedSet.has(id) && allModelIds.includes(id)),
+			// Add remaining IDs not in MODEL_ORDER_LIST, sorted alphabetically
+			...allModelIds.filter((id) => !orderedSet.has(id)).sort((a, b) => a.localeCompare(b))
+		];
+	});
+</script>
+
+<ConfirmDialog
+	title={$i18n.t('Delete All Models')}
+	message={$i18n.t('This will delete all models including custom models and cannot be undone.')}
+	bind:show={showResetModal}
+	onConfirm={async () => {
+		const res = deleteAllModels(localStorage.token);
+		if (res) {
+			toast.success($i18n.t('All models deleted successfully'));
+			init();
+		}
+	}}
+/>
+
+<Modal size="sm" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-100 px-5 pt-4 pb-2">
+			<div class=" text-lg font-medium self-center font-primary">
+				{$i18n.t('Configure Models')}
+			</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				{#if config}
+					<form
+						class="flex flex-col w-full"
+						on:submit|preventDefault={() => {
+							submitHandler();
+						}}
+					>
+						<div>
+							<div class="flex flex-col w-full">
+								<div class="mb-1 flex justify-between">
+									<div class="text-xs text-gray-500">{$i18n.t('Reorder Models')}</div>
+								</div>
+
+								<ModelList bind:modelIds />
+							</div>
+						</div>
+
+						<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
+
+						<div>
+							<div class="flex flex-col w-full">
+								<div class="mb-1 flex justify-between">
+									<div class="text-xs text-gray-500">{$i18n.t('Default Models')}</div>
+								</div>
+
+								{#if defaultModelIds.length > 0}
+									<div class="flex flex-col">
+										{#each defaultModelIds as modelId, modelIdx}
+											<div class=" flex gap-2 w-full justify-between items-center">
+												<div class=" text-sm flex-1 py-1 rounded-lg">
+													{$models.find((model) => model.id === modelId)?.name}
+												</div>
+												<div class="flex-shrink-0">
+													<button
+														type="button"
+														on:click={() => {
+															defaultModelIds = defaultModelIds.filter(
+																(_, idx) => idx !== modelIdx
+															);
+														}}
+													>
+														<Minus strokeWidth="2" className="size-3.5" />
+													</button>
+												</div>
+											</div>
+										{/each}
+									</div>
+								{:else}
+									<div class="text-gray-500 text-xs text-center py-2">
+										{$i18n.t('No models selected')}
+									</div>
+								{/if}
+
+								<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
+
+								<div class="flex items-center">
+									<select
+										class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
+											? ''
+											: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										bind:value={selectedModelId}
+									>
+										<option value="">{$i18n.t('Select a model')}</option>
+										{#each $models as model}
+											<option value={model.id} class="bg-gray-50 dark:bg-gray-700"
+												>{model.name}</option
+											>
+										{/each}
+									</select>
+
+									<div>
+										<button
+											type="button"
+											on:click={() => {
+												if (defaultModelIds.includes(selectedModelId)) {
+													return;
+												}
+
+												defaultModelIds = [...defaultModelIds, selectedModelId];
+												selectedModelId = '';
+											}}
+										>
+											<Plus className="size-3.5" strokeWidth="2" />
+										</button>
+									</div>
+								</div>
+							</div>
+						</div>
+
+						<div class="flex justify-between pt-3 text-sm font-medium gap-1.5">
+							<Tooltip content={$i18n.t('This will delete all models including custom models')}>
+								<button
+									class="px-3.5 py-1.5 text-sm font-medium dark:bg-black dark:hover:bg-gray-950 dark:text-white bg-white text-black hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"
+									type="button"
+									on:click={() => {
+										showResetModal = true;
+									}}
+								>
+									{$i18n.t('Delete All Models')}
+								</button>
+							</Tooltip>
+
+							<button
+								class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
+									? ' cursor-not-allowed'
+									: ''}"
+								type="submit"
+								disabled={loading}
+							>
+								{$i18n.t('Save')}
+
+								{#if loading}
+									<div class="ml-2 self-center">
+										<svg
+											class=" w-4 h-4"
+											viewBox="0 0 24 24"
+											fill="currentColor"
+											xmlns="http://www.w3.org/2000/svg"
+											><style>
+												.spinner_ajPY {
+													transform-origin: center;
+													animation: spinner_AtaB 0.75s infinite linear;
+												}
+												@keyframes spinner_AtaB {
+													100% {
+														transform: rotate(360deg);
+													}
+												}
+											</style><path
+												d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+												opacity=".25"
+											/><path
+												d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+												class="spinner_ajPY"
+											/></svg
+										>
+									</div>
+								{/if}
+							</button>
+						</div>
+					</form>
+				{:else}
+					<div>
+						<Spinner />
+					</div>
+				{/if}
+			</div>
+		</div>
+	</div>
+</Modal>

+ 58 - 0
src/lib/components/admin/Settings/Models/ModelList.svelte

@@ -0,0 +1,58 @@
+<script lang="ts">
+	import Sortable from 'sortablejs';
+
+	import { createEventDispatcher, getContext, onMount } from 'svelte';
+	const i18n = getContext('i18n');
+
+	import { models } from '$lib/stores';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import EllipsisVertical from '$lib/components/icons/EllipsisVertical.svelte';
+
+	export let modelIds = [];
+
+	let sortable = null;
+	let modelListElement = null;
+
+	const positionChangeHandler = () => {
+		const modelList = Array.from(modelListElement.children).map((child) =>
+			child.id.replace('model-item-', '')
+		);
+
+		modelIds = modelList;
+	};
+
+	onMount(() => {
+		sortable = Sortable.create(modelListElement, {
+			animation: 150,
+			onUpdate: async (event) => {
+				positionChangeHandler();
+			}
+		});
+	});
+</script>
+
+{#if modelIds.length > 0}
+	<div class="flex flex-col -translate-x-1" bind:this={modelListElement}>
+		{#each modelIds as modelId, modelIdx (modelId)}
+			<div class=" flex gap-2 w-full justify-between items-center" id="model-item-{modelId}">
+				<Tooltip content={modelId} placement="top-start">
+					<div class="flex items-center gap-1">
+						<EllipsisVertical className="size-4 cursor-move" />
+
+						<div class=" text-sm flex-1 py-1 rounded-lg">
+							{#if $models.find((model) => model.id === modelId)}
+								{$models.find((model) => model.id === modelId).name}
+							{:else}
+								{modelId}
+							{/if}
+						</div>
+					</div>
+				</Tooltip>
+			</div>
+		{/each}
+	</div>
+{:else}
+	<div class="text-gray-500 text-xs text-center py-2">
+		{$i18n.t('No models found')}
+	</div>
+{/if}

+ 14 - 7
src/lib/components/chat/Chat.svelte

@@ -888,11 +888,10 @@
 		await tick();
 
 		// Reset chat input textarea
-		const chatInputContainer = document.getElementById('chat-input-container');
+		const chatInputElement = document.getElementById('chat-input');
 
-		if (chatInputContainer) {
-			chatInputContainer.value = '';
-			chatInputContainer.style.height = '';
+		if (chatInputElement) {
+			chatInputElement.style.height = '';
 		}
 
 		const _files = JSON.parse(JSON.stringify(files));
@@ -1977,7 +1976,7 @@
 				}
 			);
 
-			return title;
+			return title ? title : (lastUserMessage?.content ?? 'New Chat');
 		} else {
 			return lastUserMessage?.content ?? 'New Chat';
 		}
@@ -2310,7 +2309,11 @@
 								on:submit={async (e) => {
 									if (e.detail) {
 										await tick();
-										submitPrompt(e.detail.replaceAll('\n\n', '\n'));
+										submitPrompt(
+											($settings?.richTextInput ?? true)
+												? e.detail.replaceAll('\n\n', '\n')
+												: e.detail
+										);
 									}
 								}}
 							/>
@@ -2347,7 +2350,11 @@
 								on:submit={async (e) => {
 									if (e.detail) {
 										await tick();
-										submitPrompt(e.detail.replaceAll('\n\n', '\n'));
+										submitPrompt(
+											($settings?.richTextInput ?? true)
+												? e.detail.replaceAll('\n\n', '\n')
+												: e.detail
+										);
 									}
 								}}
 							/>

+ 58 - 46
src/lib/components/chat/MessageInput.svelte

@@ -592,29 +592,6 @@
 												placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
 												largeTextAsFile={$settings?.largeTextAsFile ?? false}
 												bind:value={prompt}
-												on:enter={async (e) => {
-													const commandsContainerElement =
-														document.getElementById('commands-container');
-													if (commandsContainerElement) {
-														e.preventDefault();
-
-														const commandOptionButton = [
-															...document.getElementsByClassName('selected-command-option-button')
-														]?.at(-1);
-
-														if (commandOptionButton) {
-															commandOptionButton?.click();
-															return;
-														}
-													}
-
-													if (prompt !== '') {
-														dispatch('submit', prompt);
-													}
-												}}
-												on:keypress={(e) => {
-													e = e.detail.event;
-												}}
 												on:keydown={async (e) => {
 													e = e.detail.event;
 
@@ -657,34 +634,70 @@
 														editButton?.click();
 													}
 
-													if (commandsContainerElement && e.key === 'ArrowUp') {
-														e.preventDefault();
-														commandsElement.selectUp();
+													if (commandsContainerElement) {
+														if (commandsContainerElement && e.key === 'ArrowUp') {
+															e.preventDefault();
+															commandsElement.selectUp();
+
+															const commandOptionButton = [
+																...document.getElementsByClassName('selected-command-option-button')
+															]?.at(-1);
+															commandOptionButton.scrollIntoView({ block: 'center' });
+														}
 
-														const commandOptionButton = [
-															...document.getElementsByClassName('selected-command-option-button')
-														]?.at(-1);
-														commandOptionButton.scrollIntoView({ block: 'center' });
-													}
+														if (commandsContainerElement && e.key === 'ArrowDown') {
+															e.preventDefault();
+															commandsElement.selectDown();
 
-													if (commandsContainerElement && e.key === 'ArrowDown') {
-														e.preventDefault();
-														commandsElement.selectDown();
+															const commandOptionButton = [
+																...document.getElementsByClassName('selected-command-option-button')
+															]?.at(-1);
+															commandOptionButton.scrollIntoView({ block: 'center' });
+														}
 
-														const commandOptionButton = [
-															...document.getElementsByClassName('selected-command-option-button')
-														]?.at(-1);
-														commandOptionButton.scrollIntoView({ block: 'center' });
-													}
+														if (commandsContainerElement && e.key === 'Tab') {
+															e.preventDefault();
 
-													if (commandsContainerElement && e.key === 'Tab') {
-														e.preventDefault();
+															const commandOptionButton = [
+																...document.getElementsByClassName('selected-command-option-button')
+															]?.at(-1);
 
-														const commandOptionButton = [
-															...document.getElementsByClassName('selected-command-option-button')
-														]?.at(-1);
+															commandOptionButton?.click();
+														}
 
-														commandOptionButton?.click();
+														if (commandsContainerElement && e.key === 'Enter') {
+															e.preventDefault();
+
+															const commandOptionButton = [
+																...document.getElementsByClassName('selected-command-option-button')
+															]?.at(-1);
+
+															if (commandOptionButton) {
+																commandOptionButton?.click();
+															} else {
+																document.getElementById('send-message-button')?.click();
+															}
+														}
+													} else {
+														if (
+															!$mobile ||
+															!(
+																'ontouchstart' in window ||
+																navigator.maxTouchPoints > 0 ||
+																navigator.msMaxTouchPoints > 0
+															)
+														) {
+															// Prevent Enter key from creating a new line
+															// Uses keyCode '13' for Enter key for chinese/japanese keyboards
+															if (e.keyCode === 13 && !e.shiftKey) {
+																e.preventDefault();
+															}
+
+															// Submit the prompt when Enter key is pressed
+															if (prompt !== '' && e.keyCode === 13 && !e.shiftKey) {
+																dispatch('submit', prompt);
+															}
+														}
 													}
 
 													if (e.key === 'Escape') {
@@ -881,7 +894,6 @@
 											on:input={async (e) => {
 												e.target.style.height = '';
 												e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
-												user = null;
 											}}
 											on:focus={async (e) => {
 												e.target.style.height = '';

+ 3 - 1
src/lib/components/chat/Messages/CitationsModal.svelte

@@ -38,7 +38,9 @@
 			};
 		});
 		if (mergedDocuments.every((doc) => doc.distance !== undefined)) {
-			mergedDocuments.sort((a, b) => (a.distance ?? Infinity) - (b.distance ?? Infinity));
+			mergedDocuments = mergedDocuments.sort(
+				(a, b) => (b.distance ?? Infinity) - (a.distance ?? Infinity)
+			);
 		}
 	}
 </script>

+ 0 - 2
src/lib/components/chat/ModelSelector.svelte

@@ -5,9 +5,7 @@
 	import Selector from './ModelSelector/Selector.svelte';
 	import Tooltip from '../common/Tooltip.svelte';
 
-	import { setDefaultModels } from '$lib/apis/configs';
 	import { updateUserSettings } from '$lib/apis/users';
-
 	const i18n = getContext('i18n');
 
 	export let selectedModels = [''];

+ 2 - 0
src/lib/components/chat/Settings/General.svelte

@@ -55,6 +55,7 @@
 		mirostat_tau: null,
 		top_k: null,
 		top_p: null,
+		min_p: null,
 		stop: null,
 		tfs_z: null,
 		num_ctx: null,
@@ -340,6 +341,7 @@
 						mirostat_tau: params.mirostat_tau !== null ? params.mirostat_tau : undefined,
 						top_k: params.top_k !== null ? params.top_k : undefined,
 						top_p: params.top_p !== null ? params.top_p : undefined,
+						min_p: params.min_p !== null ? params.min_p : undefined,
 						tfs_z: params.tfs_z !== null ? params.tfs_z : undefined,
 						num_ctx: params.num_ctx !== null ? params.num_ctx : undefined,
 						num_batch: params.num_batch !== null ? params.num_batch : undefined,

+ 3 - 3
src/lib/components/common/Modal.svelte

@@ -6,7 +6,7 @@
 
 	export let show = true;
 	export let size = 'md';
-	export let className = 'bg-gray-50 dark:bg-gray-900  rounded-2xl';
+	export let className = 'bg-gray-50 dark:bg-gray-900 rounded-2xl';
 
 	let modalElement = null;
 	let mounted = false;
@@ -65,7 +65,7 @@
 	<!-- svelte-ignore a11y-no-static-element-interactions -->
 	<div
 		bind:this={modalElement}
-		class="modal fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[9999] overflow-hidden overscroll-contain"
+		class="modal fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] p-3 flex justify-center z-[9999] overflow-y-auto overscroll-contain"
 		in:fade={{ duration: 10 }}
 		on:mousedown={() => {
 			show = false;
@@ -74,7 +74,7 @@
 		<div
 			class=" m-auto max-w-full {sizeToWidth(size)} {size !== 'full'
 				? 'mx-2'
-				: ''} shadow-3xl max-h-[100dvh] overflow-y-auto scrollbar-hidden {className}"
+				: ''} shadow-3xl min-h-fit scrollbar-hidden {className}"
 			in:flyAndScale
 			on:mousedown={(e) => {
 				e.stopPropagation();

+ 12 - 16
src/lib/components/common/RichTextInput.svelte

@@ -1,7 +1,10 @@
 <script lang="ts">
 	import { marked } from 'marked';
 	import TurndownService from 'turndown';
-	const turndownService = new TurndownService();
+	const turndownService = new TurndownService({
+		codeBlockStyle: 'fenced'
+	});
+	turndownService.escape = (string) => string;
 
 	import { onMount, onDestroy } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
@@ -154,7 +157,11 @@
 
 				const newValue = turndownService.turndown(editor.getHTML());
 				if (value !== newValue) {
-					value = newValue; // Trigger parent updates
+					value = newValue;
+
+					if (value === '') {
+						editor.commands.clearContent();
+					}
 				}
 			},
 			editorProps: {
@@ -164,11 +171,10 @@
 						eventDispatch('focus', { event });
 						return false;
 					},
-					keypress: (view, event) => {
-						eventDispatch('keypress', { event });
+					keyup: (view, event) => {
+						eventDispatch('keyup', { event });
 						return false;
 					},
-
 					keydown: (view, event) => {
 						// Handle Tab Key
 						if (event.key === 'Tab') {
@@ -210,22 +216,12 @@
 
 							// Handle shift + Enter for a line break
 							if (shiftEnter) {
-								if (event.key === 'Enter' && event.shiftKey) {
+								if (event.key === 'Enter' && event.shiftKey && !event.ctrlKey && !event.metaKey) {
 									editor.commands.setHardBreak(); // Insert a hard break
 									view.dispatch(view.state.tr.scrollIntoView()); // Move viewport to the cursor
 									event.preventDefault();
 									return true;
 								}
-								if (event.key === 'Enter') {
-									eventDispatch('enter', { event });
-									event.preventDefault();
-									return true;
-								}
-							}
-							if (event.key === 'Enter') {
-								eventDispatch('enter', { event });
-								event.preventDefault();
-								return true;
 							}
 						}
 						eventDispatch('keydown', { event });

+ 7 - 11
src/lib/components/common/Textarea.svelte

@@ -3,30 +3,27 @@
 
 	export let value = '';
 	export let placeholder = '';
-
 	export let rows = 1;
 	export let required = false;
-
 	export let className =
 		'w-full rounded-lg px-3 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none resize-none h-full';
 
 	let textareaElement;
 
+	// Adjust height on mount and after setting the element.
 	onMount(async () => {
 		await tick();
-		if (textareaElement) {
-			await tick();
-			setTimeout(adjustHeight, 0);
-		}
+		adjustHeight();
 	});
 
-	$: if (value) {
-		setTimeout(adjustHeight, 0);
-	}
+	// This reactive statement runs whenever `value` changes
+	$: adjustHeight();
 
+	// Adjust height to match content
 	const adjustHeight = () => {
 		if (textareaElement) {
-			textareaElement.style.height = '';
+			// Reset height to calculate the correct scroll height
+			textareaElement.style.height = 'auto';
 			textareaElement.style.height = `${textareaElement.scrollHeight}px`;
 		}
 	};
@@ -37,7 +34,6 @@
 	bind:value
 	{placeholder}
 	on:input={adjustHeight}
-	on:focus={adjustHeight}
 	class={className}
 	{rows}
 	{required}

+ 16 - 3
src/lib/components/workspace/Models/ModelEditor.svelte

@@ -317,6 +317,7 @@
 						info.meta.profile_image_url = compressedSrc;
 
 						inputFiles = null;
+						filesInputElement.value = '';
 					};
 				};
 
@@ -345,7 +346,7 @@
 				<div class="self-center md:self-start flex justify-center my-2 flex-shrink-0">
 					<div class="self-center">
 						<button
-							class="rounded-2xl flex flex-shrink-0 items-center {info.meta.profile_image_url !==
+							class="rounded-xl flex flex-shrink-0 items-center {info.meta.profile_image_url !==
 							'/static/favicon.png'
 								? 'bg-transparent'
 								: 'bg-white'} shadow-xl group relative"
@@ -358,13 +359,13 @@
 								<img
 									src={info.meta.profile_image_url}
 									alt="model profile"
-									class="rounded-lg size-72 md:size-60 object-cover shrink-0"
+									class="rounded-xl size-72 md:size-60 object-cover shrink-0"
 								/>
 							{:else}
 								<img
 									src="/static/favicon.png"
 									alt="model profile"
-									class=" rounded-lg size-72 md:size-60 object-cover shrink-0"
+									class=" rounded-xl size-72 md:size-60 object-cover shrink-0"
 								/>
 							{/if}
 
@@ -393,6 +394,18 @@
 								class="absolute top-0 bottom-0 left-0 right-0 bg-white dark:bg-black rounded-lg opacity-0 group-hover:opacity-20 transition"
 							></div>
 						</button>
+
+						<div class="flex w-full mt-1 justify-end">
+							<button
+								class="px-2 py-1 text-gray-500 rounded-lg text-xs"
+								on:click={() => {
+									info.meta.profile_image_url = '/static/favicon.png';
+								}}
+								type="button"
+							>
+								Reset Image</button
+							>
+						</div>
 					</div>
 				</div>
 

+ 6 - 0
src/lib/i18n/locales/ar-BH/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "الطلبات المتزامنة",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "تأكيد كلمة المرور",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "(SentenceTransformers) الإفتراضي",
 	"Default Model": "النموذج الافتراضي",
 	"Default model updated": "الإفتراضي تحديث الموديل",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "الإفتراضي Prompt الاقتراحات",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
 	"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "فبراير",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "محتوى الملف النموذجي",
 	"Models": "الموديلات",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "المزيد",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "لا توجد نتايج",
 	"No search query generated": "لم يتم إنشاء استعلام بحث",
 	"No source available": "لا يوجد مصدر متاح",
@@ -699,6 +704,7 @@
 	"Remove": "إزالة",
 	"Remove Model": "حذف الموديل",
 	"Rename": "إعادة تسمية",
+	"Reorder Models": "",
 	"Repeat Last N": "N كرر آخر",
 	"Request Mode": "وضع الطلب",
 	"Reranking Model": "إعادة تقييم النموذج",

+ 6 - 0
src/lib/i18n/locales/bg-BG/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Едновременни искания",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Потвърди Парола",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "По подразбиране (SentenceTransformers)",
 	"Default Model": "Модел по подразбиране",
 	"Default model updated": "Моделът по подразбиране е обновен",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Промпт Предложения по подразбиране",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Неуспешно създаване на API ключ.",
 	"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "Февруари",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Съдържание на модфайл",
 	"Models": "Модели",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Повече",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Няма намерени резултати",
 	"No search query generated": "Не е генерирана заявка за търсене",
 	"No source available": "Няма наличен източник",
@@ -699,6 +704,7 @@
 	"Remove": "Изтриване",
 	"Remove Model": "Изтриване на модела",
 	"Rename": "Преименуване",
+	"Reorder Models": "",
 	"Repeat Last N": "Repeat Last N",
 	"Request Mode": "Request Mode",
 	"Reranking Model": "Reranking Model",

+ 6 - 0
src/lib/i18n/locales/bn-BD/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "সমকালীন অনুরোধ",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "পাসওয়ার্ড নিশ্চিত করুন",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "ডিফল্ট (SentenceTransformers)",
 	"Default Model": "ডিফল্ট মডেল",
 	"Default model updated": "ডিফল্ট মডেল আপডেট হয়েছে",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "ডিফল্ট প্রম্পট সাজেশন",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "API Key তৈরি করা যায়নি।",
 	"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "ফেব্রুয়ারি",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "মডেলফাইল কনটেন্ট",
 	"Models": "মডেলসমূহ",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "আরো",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "কোন ফলাফল পাওয়া যায়নি",
 	"No search query generated": "কোনও অনুসন্ধান ক্যোয়ারী উত্পন্ন হয়নি",
 	"No source available": "কোন উৎস পাওয়া যায়নি",
@@ -699,6 +704,7 @@
 	"Remove": "রিমুভ করুন",
 	"Remove Model": "মডেল রিমুভ করুন",
 	"Rename": "রেনেম",
+	"Reorder Models": "",
 	"Repeat Last N": "রিপিট Last N",
 	"Request Mode": "রিকোয়েস্ট মোড",
 	"Reranking Model": "রির্যাক্টিং মডেল",

+ 6 - 0
src/lib/i18n/locales/ca-ES/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Completaments",
 	"Concurrent Requests": "Peticions simultànies",
 	"Configure": "Configurar",
+	"Configure Models": "",
 	"Confirm": "Confirmar",
 	"Confirm Password": "Confirmar la contrasenya",
 	"Confirm your action": "Confirma la teva acció",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Per defecte (SentenceTransformers)",
 	"Default Model": "Model per defecte",
 	"Default model updated": "Model per defecte actualitzat",
+	"Default Models": "",
 	"Default permissions": "Permisos per defecte",
 	"Default permissions updated successfully": "Permisos per defecte actualitzats correctament",
 	"Default Prompt Suggestions": "Suggeriments d'indicació per defecte",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "No s'ha pogut afegir l'arxiu.",
 	"Failed to create API Key.": "No s'ha pogut crear la clau API.",
 	"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "No s'han pogut actualitzar les preferències",
 	"Failed to upload file.": "No s'ha pogut pujar l'arxiu.",
 	"February": "Febrer",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Contingut del Modelfile",
 	"Models": "Models",
 	"Models Access": "Accés als models",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "Clau API de Mojeek Search",
 	"more": "més",
 	"More": "Més",
@@ -589,6 +593,7 @@
 	"No knowledge found": "No s'ha trobat Coneixement",
 	"No model IDs": "No hi ha IDs de model",
 	"No models found": "No s'han trobat models",
+	"No models selected": "",
 	"No results found": "No s'han trobat resultats",
 	"No search query generated": "No s'ha generat cap consulta",
 	"No source available": "Sense font disponible",
@@ -699,6 +704,7 @@
 	"Remove": "Eliminar",
 	"Remove Model": "Eliminar el model",
 	"Rename": "Canviar el nom",
+	"Reorder Models": "",
 	"Repeat Last N": "Repeteix els darrers N",
 	"Request Mode": "Mode de sol·licitud",
 	"Reranking Model": "Model de reavaluació",

+ 6 - 0
src/lib/i18n/locales/ceb-PH/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Kumpirma ang password",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "",
 	"Default Model": "",
 	"Default model updated": "Gi-update nga default template",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Default nga prompt nga mga sugyot",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Mga sulod sa template file",
 	"Models": "Mga modelo",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "",
 	"No search query generated": "",
 	"No source available": "Walay tinubdan nga anaa",
@@ -699,6 +704,7 @@
 	"Remove": "",
 	"Remove Model": "",
 	"Rename": "",
+	"Reorder Models": "",
 	"Repeat Last N": "Balika ang katapusang N",
 	"Request Mode": "Query mode",
 	"Reranking Model": "",

+ 6 - 0
src/lib/i18n/locales/cs-CZ/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Doplnění",
 	"Concurrent Requests": "Současné požadavky",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Potvrdit",
 	"Confirm Password": "Potvrzení hesla",
 	"Confirm your action": "Potvrďte svoji akci",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Výchozí (SentenceTransformers)",
 	"Default Model": "Výchozí model",
 	"Default model updated": "Výchozí model aktualizován.",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Výchozí návrhy promptů",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Nepodařilo se přidat soubor.",
 	"Failed to create API Key.": "Nepodařilo se vytvořit API klíč.",
 	"Failed to read clipboard contents": "Nepodařilo se přečíst obsah schránky",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Nepodařilo se aktualizovat nastavení",
 	"Failed to upload file.": "Nepodařilo se nahrát soubor.",
 	"February": "Únor",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Obsah souboru modelfile",
 	"Models": "Modely",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "více",
 	"More": "Více",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Nebyly nalezeny žádné znalosti",
 	"No model IDs": "",
 	"No models found": "Nebyly nalezeny žádné modely",
+	"No models selected": "",
 	"No results found": "Nebyly nalezeny žádné výsledky",
 	"No search query generated": "Nebyl vygenerován žádný vyhledávací dotaz.",
 	"No source available": "Není k dispozici žádný zdroj.",
@@ -699,6 +704,7 @@
 	"Remove": "Odebrat",
 	"Remove Model": "Odebrat model",
 	"Rename": "Přejmenovat",
+	"Reorder Models": "",
 	"Repeat Last N": "Opakovat posledních N",
 	"Request Mode": "Režim žádosti",
 	"Reranking Model": "Model pro přehodnocení pořadí",

+ 6 - 0
src/lib/i18n/locales/da-DK/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Concurrent requests",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Bekræft",
 	"Confirm Password": "Bekræft password",
 	"Confirm your action": "Bekræft din handling",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
 	"Default Model": "Standard model",
 	"Default model updated": "Standard model opdateret",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Standardforslag til prompt",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Kunne ikke oprette API-nøgle.",
 	"Failed to read clipboard contents": "Kunne ikke læse indholdet af udklipsholderen",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Kunne ikke opdatere indstillinger",
 	"Failed to upload file.": "Kunne ikke uploade fil.",
 	"February": "Februar",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modelfilindhold",
 	"Models": "Modeller",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Mere",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Ingen viden fundet",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Ingen resultater fundet",
 	"No search query generated": "Ingen søgeforespørgsel genereret",
 	"No source available": "Ingen kilde tilgængelig",
@@ -699,6 +704,7 @@
 	"Remove": "Fjern",
 	"Remove Model": "Fjern model",
 	"Rename": "Omdøb",
+	"Reorder Models": "",
 	"Repeat Last N": "Gentag sidste N",
 	"Request Mode": "Forespørgselstilstand",
 	"Reranking Model": "Omarrangeringsmodel",

+ 6 - 0
src/lib/i18n/locales/de-DE/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Vervollständigungen",
 	"Concurrent Requests": "Anzahl gleichzeitiger Anfragen",
 	"Configure": "Konfigurieren",
+	"Configure Models": "",
 	"Confirm": "Bestätigen",
 	"Confirm Password": "Passwort bestätigen",
 	"Confirm your action": "Bestätigen Sie Ihre Aktion.",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
 	"Default Model": "Standardmodell",
 	"Default model updated": "Standardmodell aktualisiert",
+	"Default Models": "",
 	"Default permissions": "Standardberechtigungen",
 	"Default permissions updated successfully": "Standardberechtigungen erfolgreich aktualisiert",
 	"Default Prompt Suggestions": "Prompt-Vorschläge",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Fehler beim Hinzufügen der Datei.",
 	"Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.",
 	"Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Fehler beim Aktualisieren der Einstellungen",
 	"Failed to upload file.": "Fehler beim Hochladen der Datei.",
 	"February": "Februar",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modelfile-Inhalt",
 	"Models": "Modelle",
 	"Models Access": "Modell-Zugriff",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "Mojeek Search API-Schlüssel",
 	"more": "mehr",
 	"More": "Mehr",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Kein Wissen gefunden",
 	"No model IDs": "Keine Modell-IDs",
 	"No models found": "Keine Modelle gefunden",
+	"No models selected": "",
 	"No results found": "Keine Ergebnisse gefunden",
 	"No search query generated": "Keine Suchanfrage generiert",
 	"No source available": "Keine Quelle verfügbar",
@@ -699,6 +704,7 @@
 	"Remove": "Entfernen",
 	"Remove Model": "Modell entfernen",
 	"Rename": "Umbenennen",
+	"Reorder Models": "",
 	"Repeat Last N": "Wiederhole die letzten N",
 	"Request Mode": "Anforderungsmodus",
 	"Reranking Model": "Reranking-Modell",

+ 6 - 0
src/lib/i18n/locales/dg-DG/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Confirm Password",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "",
 	"Default Model": "",
 	"Default model updated": "Default model much updated",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Default Prompt Suggestions",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "Failed to read clipboard borks",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modelfile Content",
 	"Models": "Wowdels",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "",
 	"No search query generated": "",
 	"No source available": "No source available",
@@ -699,6 +704,7 @@
 	"Remove": "",
 	"Remove Model": "",
 	"Rename": "",
+	"Reorder Models": "",
 	"Repeat Last N": "Repeat Last N",
 	"Request Mode": "Request Bark",
 	"Reranking Model": "",

+ 6 - 0
src/lib/i18n/locales/en-GB/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "",
 	"Default Model": "",
 	"Default model updated": "",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "",
 	"Models": "",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "",
 	"No search query generated": "",
 	"No source available": "",
@@ -699,6 +704,7 @@
 	"Remove": "",
 	"Remove Model": "",
 	"Rename": "",
+	"Reorder Models": "",
 	"Repeat Last N": "",
 	"Request Mode": "",
 	"Reranking Model": "",

+ 6 - 0
src/lib/i18n/locales/en-US/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "",
 	"Default Model": "",
 	"Default model updated": "",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "",
 	"Models": "",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "",
 	"No search query generated": "",
 	"No source available": "",
@@ -699,6 +704,7 @@
 	"Remove": "",
 	"Remove Model": "",
 	"Rename": "",
+	"Reorder Models": "",
 	"Repeat Last N": "",
 	"Request Mode": "",
 	"Reranking Model": "",

+ 6 - 0
src/lib/i18n/locales/es-ES/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Solicitudes simultáneas",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Confirmar",
 	"Confirm Password": "Confirmar Contraseña",
 	"Confirm your action": "Confirma tu acción",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Predeterminado (SentenceTransformers)",
 	"Default Model": "Modelo predeterminado",
 	"Default model updated": "El modelo por defecto ha sido actualizado",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Sugerencias de mensajes por defecto",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "No se pudo crear la clave API.",
 	"Failed to read clipboard contents": "No se pudo leer el contenido del portapapeles",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Falla al actualizar los ajustes",
 	"Failed to upload file.": "Falla al subir el archivo.",
 	"February": "Febrero",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Contenido del Modelfile",
 	"Models": "Modelos",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Más",
@@ -589,6 +593,7 @@
 	"No knowledge found": "No se encontró ningún conocimiento",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "No se han encontrado resultados",
 	"No search query generated": "No se ha generado ninguna consulta de búsqueda",
 	"No source available": "No hay fuente disponible",
@@ -699,6 +704,7 @@
 	"Remove": "Eliminar",
 	"Remove Model": "Eliminar modelo",
 	"Rename": "Renombrar",
+	"Reorder Models": "",
 	"Repeat Last N": "Repetir las últimas N",
 	"Request Mode": "Modo de petición",
 	"Reranking Model": "Modelo de reranking",

+ 6 - 0
src/lib/i18n/locales/fa-IR/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "درخواست های همزمان",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "تایید",
 	"Confirm Password": "تایید رمز عبور",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "پیشفرض (SentenceTransformers)",
 	"Default Model": "مدل پیشفرض",
 	"Default model updated": "مدل پیشفرض به\u200cروزرسانی شد",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "پیشنهادات پرامپت پیش فرض",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "خطا در افزودن پرونده",
 	"Failed to create API Key.": "ایجاد کلید API با خطا مواجه شد.",
 	"Failed to read clipboard contents": "خواندن محتوای کلیپ بورد ناموفق بود",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "خطا در به\u200cروزرسانی تنظیمات",
 	"Failed to upload file.": "خطا در بارگذاری پرونده",
 	"February": "فوریه",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "محتویات فایل مدل",
 	"Models": "مدل\u200cها",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "بیشتر",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "نتیجه\u200cای یافت نشد",
 	"No search query generated": "پرسوجوی جستجویی ایجاد نشده است",
 	"No source available": "منبعی در دسترس نیست",
@@ -699,6 +704,7 @@
 	"Remove": "حذف",
 	"Remove Model": "حذف مدل",
 	"Rename": "تغییر نام",
+	"Reorder Models": "",
 	"Repeat Last N": "Repeat Last N",
 	"Request Mode": "حالت درخواست",
 	"Reranking Model": "مدل ری\u200cشناسی مجدد غیرفعال است",

+ 6 - 0
src/lib/i18n/locales/fi-FI/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Samanaikaiset pyynnöt",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Vahvista salasana",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Oletus (SentenceTransformers)",
 	"Default Model": "Oletusmalli",
 	"Default model updated": "Oletusmalli päivitetty",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Oletuskehotteiden ehdotukset",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "API-avaimen luonti epäonnistui.",
 	"Failed to read clipboard contents": "Leikepöydän sisällön lukeminen epäonnistui",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "helmikuu",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Mallitiedoston sisältö",
 	"Models": "Mallit",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Lisää",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Ei tuloksia",
 	"No search query generated": "Hakukyselyä ei luotu",
 	"No source available": "Ei lähdettä saatavilla",
@@ -699,6 +704,7 @@
 	"Remove": "Poista",
 	"Remove Model": "Poista malli",
 	"Rename": "Nimeä uudelleen",
+	"Reorder Models": "",
 	"Repeat Last N": "Viimeinen N -toisto",
 	"Request Mode": "Pyyntötila",
 	"Reranking Model": "Uudelleenpisteytysmalli",

+ 6 - 0
src/lib/i18n/locales/fr-CA/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Demandes concurrentes",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Confirmer",
 	"Confirm Password": "Confirmer le mot de passe",
 	"Confirm your action": "Confirmez votre action",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Par défaut (Sentence Transformers)",
 	"Default Model": "Modèle standard",
 	"Default model updated": "Modèle par défaut mis à jour",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Suggestions de prompts par défaut",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Échec de la création de la clé API.",
 	"Failed to read clipboard contents": "Échec de la lecture du contenu du presse-papiers",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Échec de la mise à jour des paramètres",
 	"Failed to upload file.": "",
 	"February": "Février",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Contenu du Fichier de Modèle",
 	"Models": "Modèles",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Plus de",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Aucun résultat trouvé",
 	"No search query generated": "Aucune requête de recherche générée",
 	"No source available": "Aucune source n'est disponible",
@@ -699,6 +704,7 @@
 	"Remove": "Retirer",
 	"Remove Model": "Retirer le modèle",
 	"Rename": "Renommer",
+	"Reorder Models": "",
 	"Repeat Last N": "Répéter les N derniers",
 	"Request Mode": "Mode de Requête",
 	"Reranking Model": "Modèle de ré-ranking",

+ 6 - 0
src/lib/i18n/locales/fr-FR/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Complétions",
 	"Concurrent Requests": "Demandes concurrentes",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Confirmer",
 	"Confirm Password": "Confirmer le mot de passe",
 	"Confirm your action": "Confirmer votre action",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Par défaut (Sentence Transformers)",
 	"Default Model": "Modèle standard",
 	"Default model updated": "Modèle par défaut mis à jour",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Suggestions de prompts par défaut",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Échec de l'ajout du fichier.",
 	"Failed to create API Key.": "Échec de la création de la clé API.",
 	"Failed to read clipboard contents": "Échec de la lecture du contenu du presse-papiers",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Échec de la mise à jour des paramètres",
 	"Failed to upload file.": "Échec du téléchargement du fichier.",
 	"February": "Février",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Contenu du Fichier de Modèle",
 	"Models": "Modèles",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "plus",
 	"More": "Plus",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Aucune connaissance trouvée",
 	"No model IDs": "",
 	"No models found": "Aucun modèle trouvé",
+	"No models selected": "",
 	"No results found": "Aucun résultat trouvé",
 	"No search query generated": "Aucune requête de recherche générée",
 	"No source available": "Aucune source n'est disponible",
@@ -699,6 +704,7 @@
 	"Remove": "Retirer",
 	"Remove Model": "Retirer le modèle",
 	"Rename": "Renommer",
+	"Reorder Models": "",
 	"Repeat Last N": "Répéter les N derniers",
 	"Request Mode": "Mode de requête",
 	"Reranking Model": "Modèle de ré-ranking",

+ 6 - 0
src/lib/i18n/locales/he-IL/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "בקשות בו-זמניות",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "אשר סיסמה",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "ברירת מחדל (SentenceTransformers)",
 	"Default Model": "מודל ברירת מחדל",
 	"Default model updated": "המודל המוגדר כברירת מחדל עודכן",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "הצעות ברירת מחדל לפקודות",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "יצירת מפתח API נכשלה.",
 	"Failed to read clipboard contents": "קריאת תוכן הלוח נכשלה",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "פברואר",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "תוכן קובץ מודל",
 	"Models": "מודלים",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "עוד",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "לא נמצאו תוצאות",
 	"No search query generated": "לא נוצרה שאילתת חיפוש",
 	"No source available": "אין מקור זמין",
@@ -699,6 +704,7 @@
 	"Remove": "הסר",
 	"Remove Model": "הסר מודל",
 	"Rename": "שנה שם",
+	"Reorder Models": "",
 	"Repeat Last N": "חזור על ה-N האחרונים",
 	"Request Mode": "מצב בקשה",
 	"Reranking Model": "מודל דירוג מחדש",

+ 6 - 0
src/lib/i18n/locales/hi-IN/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "समवर्ती अनुरोध",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "पासवर्ड की पुष्टि कीजिये",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "डिफ़ॉल्ट (SentenceTransformers)",
 	"Default Model": "डिफ़ॉल्ट मॉडल",
 	"Default model updated": "डिफ़ॉल्ट मॉडल अपडेट किया गया",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "डिफ़ॉल्ट प्रॉम्प्ट सुझाव",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "एपीआई कुंजी बनाने में विफल.",
 	"Failed to read clipboard contents": "क्लिपबोर्ड सामग्री पढ़ने में विफल",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "फरवरी",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "मॉडल फ़ाइल सामग्री",
 	"Models": "सभी मॉडल",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "और..",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "कोई परिणाम नहीं मिला",
 	"No search query generated": "कोई खोज क्वेरी जनरेट नहीं हुई",
 	"No source available": "कोई स्रोत उपलब्ध नहीं है",
@@ -699,6 +704,7 @@
 	"Remove": "हटा दें",
 	"Remove Model": "मोडेल हटाएँ",
 	"Rename": "नाम बदलें",
+	"Reorder Models": "",
 	"Repeat Last N": "अंतिम N दोहराएँ",
 	"Request Mode": "अनुरोध मोड",
 	"Reranking Model": "रीरैकिंग मोड",

+ 6 - 0
src/lib/i18n/locales/hr-HR/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Istodobni zahtjevi",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Potvrdite lozinku",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Zadano (SentenceTransformers)",
 	"Default Model": "Zadani model",
 	"Default model updated": "Zadani model ažuriran",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Zadani prijedlozi prompta",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Neuspješno stvaranje API ključa.",
 	"Failed to read clipboard contents": "Neuspješno čitanje sadržaja međuspremnika",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Greška kod ažuriranja postavki",
 	"Failed to upload file.": "",
 	"February": "Veljača",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Sadržaj datoteke modela",
 	"Models": "Modeli",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Više",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Nema rezultata",
 	"No search query generated": "Nije generiran upit za pretraživanje",
 	"No source available": "Nema dostupnog izvora",
@@ -699,6 +704,7 @@
 	"Remove": "Ukloni",
 	"Remove Model": "Ukloni model",
 	"Rename": "Preimenuj",
+	"Reorder Models": "",
 	"Repeat Last N": "Ponovi zadnjih N",
 	"Request Mode": "Način zahtjeva",
 	"Reranking Model": "Model za ponovno rangiranje",

+ 6 - 0
src/lib/i18n/locales/hu-HU/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Kiegészítések",
 	"Concurrent Requests": "Párhuzamos kérések",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Megerősítés",
 	"Confirm Password": "Jelszó megerősítése",
 	"Confirm your action": "Erősítsd meg a műveletet",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Alapértelmezett (SentenceTransformers)",
 	"Default Model": "Alapértelmezett modell",
 	"Default model updated": "Alapértelmezett modell frissítve",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Alapértelmezett prompt javaslatok",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Nem sikerült hozzáadni a fájlt.",
 	"Failed to create API Key.": "Nem sikerült létrehozni az API kulcsot.",
 	"Failed to read clipboard contents": "Nem sikerült olvasni a vágólap tartalmát",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Nem sikerült frissíteni a beállításokat",
 	"Failed to upload file.": "Nem sikerült feltölteni a fájlt.",
 	"February": "Február",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modellfájl tartalom",
 	"Models": "Modellek",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "több",
 	"More": "Több",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Nem található tudásbázis",
 	"No model IDs": "",
 	"No models found": "Nem található modell",
+	"No models selected": "",
 	"No results found": "Nincs találat",
 	"No search query generated": "Nem generálódott keresési lekérdezés",
 	"No source available": "Nincs elérhető forrás",
@@ -699,6 +704,7 @@
 	"Remove": "Eltávolítás",
 	"Remove Model": "Modell eltávolítása",
 	"Rename": "Átnevezés",
+	"Reorder Models": "",
 	"Repeat Last N": "Utolsó N ismétlése",
 	"Request Mode": "Kérési mód",
 	"Reranking Model": "Újrarangsoroló modell",

+ 6 - 0
src/lib/i18n/locales/id-ID/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Permintaan Bersamaan",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Konfirmasi",
 	"Confirm Password": "Konfirmasi Kata Sandi",
 	"Confirm your action": "Konfirmasi tindakan Anda",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Default (Pengubah Kalimat)",
 	"Default Model": "Model Default",
 	"Default model updated": "Model default diperbarui",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Saran Permintaan Default",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Gagal membuat API Key.",
 	"Failed to read clipboard contents": "Gagal membaca konten papan klip",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Gagal memperbarui pengaturan",
 	"Failed to upload file.": "",
 	"February": "Februari",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Konten File Model",
 	"Models": "Model",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Lainnya",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Tidak ada hasil yang ditemukan",
 	"No search query generated": "Tidak ada permintaan pencarian yang dibuat",
 	"No source available": "Tidak ada sumber yang tersedia",
@@ -699,6 +704,7 @@
 	"Remove": "Hapus",
 	"Remove Model": "Hapus Model",
 	"Rename": "Ganti nama",
+	"Reorder Models": "",
 	"Repeat Last N": "Ulangi N Terakhir",
 	"Request Mode": "Mode Permintaan",
 	"Reranking Model": "Model Pemeringkatan Ulang",

+ 6 - 0
src/lib/i18n/locales/ie-GA/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Críochnaithe",
 	"Concurrent Requests": "Iarrataí Comhthéime",
 	"Configure": "Cumraigh",
+	"Configure Models": "",
 	"Confirm": "Deimhnigh",
 	"Confirm Password": "Deimhnigh Pasfhocal",
 	"Confirm your action": "Deimhnigh do ghníomh",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Réamhshocraithe (SentenceTransFormers)",
 	"Default Model": "Samhail Réamhshocraithe",
 	"Default model updated": "An tsamhail réamhshocraithe",
+	"Default Models": "",
 	"Default permissions": "Ceadanna réamhshocraithe",
 	"Default permissions updated successfully": "D'éirigh le ceadanna réamhshocraithe a nuashonrú",
 	"Default Prompt Suggestions": "Moltaí Pras Réamhshocraithe",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Theip ar an gcomhad a chur leis.",
 	"Failed to create API Key.": "Theip ar an eochair API a chruthú.",
 	"Failed to read clipboard contents": "Theip ar ábhar gearrthaisce a lé",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Theip ar shocruithe a nuashonrú",
 	"Failed to upload file.": "Theip ar uaslódáil an chomhaid.",
 	"February": "Feabhra",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Ábhar Modelfile",
 	"Models": "Múnlaí",
 	"Models Access": "Rochtain Múnlaí",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "níos mó",
 	"More": "Tuilleadh",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Níor aimsíodh aon eolas",
 	"No model IDs": "Gan IDanna múnla",
 	"No models found": "Níor aimsíodh aon mhúnlaí",
+	"No models selected": "",
 	"No results found": "Níl aon torthaí le fáil",
 	"No search query generated": "Ní ghintear aon cheist cuardaigh",
 	"No source available": "Níl aon fhoinse ar fáil",
@@ -699,6 +704,7 @@
 	"Remove": "Bain",
 	"Remove Model": "Bain Múnla",
 	"Rename": "Athainmnigh",
+	"Reorder Models": "",
 	"Repeat Last N": "Déan an N deireanach arís",
 	"Request Mode": "Mód Iarratais",
 	"Reranking Model": "Múnla Athrangú",

+ 6 - 0
src/lib/i18n/locales/it-IT/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Richieste simultanee",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Conferma password",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Predefinito (SentenceTransformers)",
 	"Default Model": "Modello di default",
 	"Default model updated": "Modello predefinito aggiornato",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Suggerimenti prompt predefiniti",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Impossibile creare la chiave API.",
 	"Failed to read clipboard contents": "Impossibile leggere il contenuto degli appunti",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "Febbraio",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Contenuto del file modello",
 	"Models": "Modelli",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Altro",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Nessun risultato trovato",
 	"No search query generated": "Nessuna query di ricerca generata",
 	"No source available": "Nessuna fonte disponibile",
@@ -699,6 +704,7 @@
 	"Remove": "Rimuovi",
 	"Remove Model": "Rimuovi modello",
 	"Rename": "Rinomina",
+	"Reorder Models": "",
 	"Repeat Last N": "Ripeti ultimi N",
 	"Request Mode": "Modalità richiesta",
 	"Reranking Model": "Modello di riclassificazione",

+ 6 - 0
src/lib/i18n/locales/ja-JP/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "同時リクエスト",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "確認",
 	"Confirm Password": "パスワードを確認",
 	"Confirm your action": "あなたのアクションの確認",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "デフォルト (SentenceTransformers)",
 	"Default Model": "デフォルトモデル",
 	"Default model updated": "デフォルトモデルが更新されました",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "デフォルトのプロンプトの提案",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "APIキーの作成に失敗しました。",
 	"Failed to read clipboard contents": "クリップボードの内容を読み取れませんでした",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "設定アップデート失敗",
 	"Failed to upload file.": "ファイルアップロード失敗",
 	"February": "2月",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "モデルファイルの内容",
 	"Models": "モデル",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "もっと見る",
@@ -589,6 +593,7 @@
 	"No knowledge found": "知識が見つかりません",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "結果が見つかりません",
 	"No search query generated": "検索クエリは生成されません",
 	"No source available": "使用可能なソースがありません",
@@ -699,6 +704,7 @@
 	"Remove": "削除",
 	"Remove Model": "モデルを削除",
 	"Rename": "名前を変更",
+	"Reorder Models": "",
 	"Repeat Last N": "最後の N を繰り返す",
 	"Request Mode": "リクエストモード",
 	"Reranking Model": "モデルの再ランキング",

+ 6 - 0
src/lib/i18n/locales/ka-GE/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "თანმხლები მოთხოვნები",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "პაროლის დამოწმება",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "დეფოლტ (SentenceTransformers)",
 	"Default Model": "ნაგულისხმები მოდელი",
 	"Default model updated": "დეფოლტ მოდელი განახლებულია",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "დეფოლტ პრომპტი პირველი პირველი",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "API ღილაკის შექმნა ვერ მოხერხდა.",
 	"Failed to read clipboard contents": "ბუფერში შიგთავსის წაკითხვა ვერ მოხერხდა",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "თებერვალი",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "მოდელური ფაილის კონტენტი",
 	"Models": "მოდელები",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "ვრცლად",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "ჩვენ ვერ პოულობით ნაპოვნი ჩაწერები",
 	"No search query generated": "ძიების მოთხოვნა არ არის გენერირებული",
 	"No source available": "წყარო არ არის ხელმისაწვდომი",
@@ -699,6 +704,7 @@
 	"Remove": "პოპულარობის რაოდენობა",
 	"Remove Model": "პოპულარობის რაოდენობა",
 	"Rename": "პოპულარობის რაოდენობა",
+	"Reorder Models": "",
 	"Repeat Last N": "გაიმეორეთ ბოლო N",
 	"Request Mode": "მოთხოვნის რეჟიმი",
 	"Reranking Model": "რექვექტირება",

+ 6 - 0
src/lib/i18n/locales/ko-KR/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "완성됨",
 	"Concurrent Requests": "동시 요청 수",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "확인",
 	"Confirm Password": "비밀번호 확인",
 	"Confirm your action": "액션 확인",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "기본값 (SentenceTransformers)",
 	"Default Model": "기본 모델",
 	"Default model updated": "기본 모델이 업데이트되었습니다.",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "기본 프롬프트 제안",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "파일추가에 실패했습니다",
 	"Failed to create API Key.": "API 키 생성에 실패했습니다.",
 	"Failed to read clipboard contents": "클립보드 내용 가져오기를 실패하였습니다.",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "설정 업데이트에 실패하였습니다.",
 	"Failed to upload file.": "파일 업로드에 실패했습니다",
 	"February": "2월",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modelfile 내용",
 	"Models": "모델",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "더보기",
 	"More": "더보기",
@@ -589,6 +593,7 @@
 	"No knowledge found": "지식 기반 없음",
 	"No model IDs": "",
 	"No models found": "모델 없음",
+	"No models selected": "",
 	"No results found": "결과 없음",
 	"No search query generated": "검색어가 생성되지 않았습니다.",
 	"No source available": "사용 가능한 소스 없음",
@@ -699,6 +704,7 @@
 	"Remove": "삭제",
 	"Remove Model": "모델 삭제",
 	"Rename": "이름 변경",
+	"Reorder Models": "",
 	"Repeat Last N": "마지막 N 반복",
 	"Request Mode": "요청 모드",
 	"Reranking Model": "Reranking 모델",

+ 6 - 0
src/lib/i18n/locales/lt-LT/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Kelios užklausos vienu metu",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Patvrtinti",
 	"Confirm Password": "Patvirtinkite slaptažodį",
 	"Confirm your action": "Patvirtinkite veiksmą",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Numatytasis (SentenceTransformers)",
 	"Default Model": "Numatytasis modelis",
 	"Default model updated": "Numatytasis modelis atnaujintas",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Numatytieji užklausų pasiūlymai",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Nepavyko sukurti API rakto",
 	"Failed to read clipboard contents": "Nepavyko perskaityti kopijuoklės",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Nepavyko atnaujinti nustatymų",
 	"Failed to upload file.": "",
 	"February": "Vasaris",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modelio failo turinys",
 	"Models": "Modeliai",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Daugiau",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Rezultatų nerasta",
 	"No search query generated": "Paieškos užklausa nesugeneruota",
 	"No source available": "Šaltinių nerasta",
@@ -699,6 +704,7 @@
 	"Remove": "Pašalinti",
 	"Remove Model": "Pašalinti modelį",
 	"Rename": "Pervadinti",
+	"Reorder Models": "",
 	"Repeat Last N": "Pakartoti paskutinius N",
 	"Request Mode": "Užklausos rėžimas",
 	"Reranking Model": "Reranking modelis",

+ 6 - 0
src/lib/i18n/locales/ms-MY/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Permintaan Serentak",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Sahkan",
 	"Confirm Password": "Sahkan kata laluan",
 	"Confirm your action": "Sahkan tindakan anda",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Lalai (SentenceTransformers)",
 	"Default Model": "Model Lalai",
 	"Default model updated": "Model lalai dikemas kini",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Cadangan Gesaan Lalai",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Gagal mencipta kekunci API",
 	"Failed to read clipboard contents": "Gagal membaca konten papan klip",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Gagal mengemaskini tetapan",
 	"Failed to upload file.": "",
 	"February": "Febuari",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Kandungan Modelfail",
 	"Models": "Model",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Lagi",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Tiada keputusan dijumpai",
 	"No search query generated": "Tiada pertanyaan carian dijana",
 	"No source available": "Tiada sumber tersedia",
@@ -699,6 +704,7 @@
 	"Remove": "Hapuskan",
 	"Remove Model": "Hapuskan Model",
 	"Rename": "Namakan Semula",
+	"Reorder Models": "",
 	"Repeat Last N": "Ulang N Terakhir",
 	"Request Mode": "Mod Permintaan",
 	"Reranking Model": "Model 'Reranking'",

+ 6 - 0
src/lib/i18n/locales/nb-NO/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Fullføringer",
 	"Concurrent Requests": "Samtidige forespørsler",
 	"Configure": "Konfigurer",
+	"Configure Models": "",
 	"Confirm": "Bekreft",
 	"Confirm Password": "Bekreft passordet",
 	"Confirm your action": "Bekreft handlingen",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
 	"Default Model": "Standard modell",
 	"Default model updated": "Standard modell oppdatert",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Standard forslag til ledetekster",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Kan ikke legge til filen.",
 	"Failed to create API Key.": "Kan ikke opprette en API-nøkkel.",
 	"Failed to read clipboard contents": "Kan ikke lese innhold på utklippstavlen",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Kan ikke oppdatere innstillinger",
 	"Failed to upload file.": "Kan ikke laste opp filen.",
 	"February": "februar",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modellfilinnhold",
 	"Models": "Modeller",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "mer",
 	"More": "Mer",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Finner ingen kunnskaper",
 	"No model IDs": "",
 	"No models found": "Finner ingen modeller",
+	"No models selected": "",
 	"No results found": "Finner ingen resultater",
 	"No search query generated": "Ingen søkespørringer er generert",
 	"No source available": "Ingen kilde tilgjengelig",
@@ -699,6 +704,7 @@
 	"Remove": "Fjern",
 	"Remove Model": "Fjern modell",
 	"Rename": "Gi nytt navn",
+	"Reorder Models": "",
 	"Repeat Last N": "Gjenta siste N",
 	"Request Mode": "Forespørselsmodus",
 	"Reranking Model": "Omrangeringsmodell",

+ 7 - 1
src/lib/i18n/locales/nl-NL/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Voltooiingen",
 	"Concurrent Requests": "Gelijktijdige verzoeken",
 	"Configure": "Configureer",
+	"Configure Models": "",
 	"Confirm": "Bevestigen",
 	"Confirm Password": "Bevestig wachtwoord",
 	"Confirm your action": "Bevestig uw actie",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Standaard (SentenceTransformers)",
 	"Default Model": "Standaard model",
 	"Default model updated": "Standaard model bijgewerkt",
+	"Default Models": "",
 	"Default permissions": "Standaardrechten",
 	"Default permissions updated successfully": "Standaardrechten succesvol bijgewerkt",
 	"Default Prompt Suggestions": "Standaard Prompt Suggesties",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Het is niet gelukt om het bestand toe te voegen.",
 	"Failed to create API Key.": "Kan API Key niet aanmaken.",
 	"Failed to read clipboard contents": "Kan klembord inhoud niet lezen",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Instellingen konden niet worden bijgewerkt.",
 	"Failed to upload file.": "Bestand kon niet worden geüpload.",
 	"February": "Februari",
@@ -441,7 +444,7 @@
 	"Group Description": "Groepsbeschrijving",
 	"Group Name": "Groepsnaam",
 	"Group updated successfully": "Groep succesvol bijgewerkt",
-	"Groups": "GRoepen",
+	"Groups": "Groepen",
 	"h:mm a": "h:mm a",
 	"Haptic Feedback": "Haptische feedback",
 	"has no conversations.": "heeft geen gesprekken.",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modelfile Inhoud",
 	"Models": "Modellen",
 	"Models Access": "Modellentoegang",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "Meer",
 	"More": "Meer",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Geen kennis gevonden",
 	"No model IDs": "Geen model-ID's",
 	"No models found": "Geen modellen gevonden",
+	"No models selected": "",
 	"No results found": "Geen resultaten gevonden",
 	"No search query generated": "Geen zoekopdracht gegenereerd",
 	"No source available": "Geen bron beschikbaar",
@@ -699,6 +704,7 @@
 	"Remove": "Verwijderen",
 	"Remove Model": "Verwijder model",
 	"Rename": "Hernoemen",
+	"Reorder Models": "",
 	"Repeat Last N": "Herhaal Laatste N",
 	"Request Mode": "Request Modus",
 	"Reranking Model": "Reranking Model",

+ 6 - 0
src/lib/i18n/locales/pa-IN/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "ਸਮਕਾਲੀ ਬੇਨਤੀਆਂ",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "ਪਾਸਵਰਡ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "ਮੂਲ (ਸੈਂਟੈਂਸਟ੍ਰਾਂਸਫਾਰਮਰਸ)",
 	"Default Model": "ਡਿਫਾਲਟ ਮਾਡਲ",
 	"Default model updated": "ਮੂਲ ਮਾਡਲ ਅੱਪਡੇਟ ਕੀਤਾ ਗਿਆ",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "ਮੂਲ ਪ੍ਰੰਪਟ ਸੁਝਾਅ",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "API ਕੁੰਜੀ ਬਣਾਉਣ ਵਿੱਚ ਅਸਫਲ।",
 	"Failed to read clipboard contents": "ਕਲਿੱਪਬੋਰਡ ਸਮੱਗਰੀ ਪੜ੍ਹਣ ਵਿੱਚ ਅਸਫਲ",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "ਫਰਵਰੀ",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "ਮਾਡਲਫਾਈਲ ਸਮੱਗਰੀ",
 	"Models": "ਮਾਡਲ",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "ਹੋਰ",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਮਿਲੇ",
 	"No search query generated": "ਕੋਈ ਖੋਜ ਪੁੱਛਗਿੱਛ ਤਿਆਰ ਨਹੀਂ ਕੀਤੀ ਗਈ",
 	"No source available": "ਕੋਈ ਸਰੋਤ ਉਪਲਬਧ ਨਹੀਂ",
@@ -699,6 +704,7 @@
 	"Remove": "ਹਟਾਓ",
 	"Remove Model": "ਮਾਡਲ ਹਟਾਓ",
 	"Rename": "ਨਾਮ ਬਦਲੋ",
+	"Reorder Models": "",
 	"Repeat Last N": "ਆਖਰੀ N ਨੂੰ ਦੁਹਰਾਓ",
 	"Request Mode": "ਬੇਨਤੀ ਮੋਡ",
 	"Reranking Model": "ਮਾਡਲ ਮੁੜ ਰੈਂਕਿੰਗ",

+ 6 - 0
src/lib/i18n/locales/pl-PL/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Równoczesne żądania",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Potwierdź hasło",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Domyślny (SentenceTransformers)",
 	"Default Model": "Model domyślny",
 	"Default model updated": "Domyślny model zaktualizowany",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Domyślne sugestie promptów",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Nie udało się utworzyć klucza API.",
 	"Failed to read clipboard contents": "Nie udało się odczytać zawartości schowka",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "Luty",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Zawartość pliku modelu",
 	"Models": "Modele",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Więcej",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Nie znaleziono rezultatów",
 	"No search query generated": "Nie wygenerowano zapytania wyszukiwania",
 	"No source available": "Źródło nie dostępne",
@@ -699,6 +704,7 @@
 	"Remove": "Usuń",
 	"Remove Model": "Usuń model",
 	"Rename": "ZMień nazwę",
+	"Reorder Models": "",
 	"Repeat Last N": "Powtórz ostatnie N",
 	"Request Mode": "Tryb żądania",
 	"Reranking Model": "Zmiana rankingu modelu",

+ 6 - 0
src/lib/i18n/locales/pt-BR/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Conclusões",
 	"Concurrent Requests": "Solicitações Concomitantes",
 	"Configure": "Configurar",
+	"Configure Models": "",
 	"Confirm": "Confirmar",
 	"Confirm Password": "Confirmar Senha",
 	"Confirm your action": "Confirme sua ação",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Padrão (SentenceTransformers)",
 	"Default Model": "Modelo Padrão",
 	"Default model updated": "Modelo padrão atualizado",
+	"Default Models": "",
 	"Default permissions": "Permissões padrão",
 	"Default permissions updated successfully": "Permissões padrão atualizadas com sucesso",
 	"Default Prompt Suggestions": "Sugestões de Prompt Padrão",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Falha ao adicionar arquivo.",
 	"Failed to create API Key.": "Falha ao criar a Chave API.",
 	"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Falha ao atualizar as configurações",
 	"Failed to upload file.": "Falha ao carregar o arquivo.",
 	"February": "Fevereiro",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Conteúdo do Arquivo do Modelo",
 	"Models": "Modelos",
 	"Models Access": "Acesso aos Modelos",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "Chave de API Mojeel Search",
 	"more": "mais",
 	"More": "Mais",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Nenhum conhecimento encontrado",
 	"No model IDs": "Nenhum ID de modelo",
 	"No models found": "Nenhum modelo encontrado",
+	"No models selected": "",
 	"No results found": "Nenhum resultado encontrado",
 	"No search query generated": "Nenhuma consulta de pesquisa gerada",
 	"No source available": "Nenhuma fonte disponível",
@@ -699,6 +704,7 @@
 	"Remove": "Remover",
 	"Remove Model": "Remover Modelo",
 	"Rename": "Renomear",
+	"Reorder Models": "",
 	"Repeat Last N": "Repetir Último N",
 	"Request Mode": "Modo de Solicitação",
 	"Reranking Model": "Modelo de Reclassificação",

+ 6 - 0
src/lib/i18n/locales/pt-PT/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Solicitações simultâneas",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Confirmar Senha",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Padrão (SentenceTransformers)",
 	"Default Model": "Modelo padrão",
 	"Default model updated": "Modelo padrão atualizado",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Sugestões de Prompt Padrão",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Falha ao criar a Chave da API.",
 	"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Falha ao atualizar as definições",
 	"Failed to upload file.": "",
 	"February": "Fevereiro",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Conteúdo do Ficheiro do Modelo",
 	"Models": "Modelos",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Mais",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Não foram encontrados resultados",
 	"No search query generated": "Não foi gerada nenhuma consulta de pesquisa",
 	"No source available": "Nenhuma fonte disponível",
@@ -699,6 +704,7 @@
 	"Remove": "Remover",
 	"Remove Model": "Remover Modelo",
 	"Rename": "Renomear",
+	"Reorder Models": "",
 	"Repeat Last N": "Repetir Últimos N",
 	"Request Mode": "Modo de Pedido",
 	"Reranking Model": "Modelo de Reranking",

+ 6 - 0
src/lib/i18n/locales/ro-RO/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Completări",
 	"Concurrent Requests": "Cereri Concurente",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Confirmă",
 	"Confirm Password": "Confirmă Parola",
 	"Confirm your action": "Confirmă acțiunea ta",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Implicit (SentenceTransformers)",
 	"Default Model": "Model Implicit",
 	"Default model updated": "Modelul implicit a fost actualizat",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Sugestii de Prompt Implicite",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Eșec la adăugarea fișierului.",
 	"Failed to create API Key.": "Crearea cheii API a eșuat.",
 	"Failed to read clipboard contents": "Citirea conținutului clipboard-ului a eșuat",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Actualizarea setărilor a eșuat",
 	"Failed to upload file.": "Încărcarea fișierului a eșuat.",
 	"February": "Februarie",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Conținutul Fișierului Model",
 	"Models": "Modele",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "mai mult",
 	"More": "Mai multe",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Nu au fost găsite informații.",
 	"No model IDs": "",
 	"No models found": "Nu s-au găsit modele",
+	"No models selected": "",
 	"No results found": "Nu au fost găsite rezultate",
 	"No search query generated": "Nu a fost generată nicio interogare de căutare",
 	"No source available": "Nicio sursă disponibilă",
@@ -699,6 +704,7 @@
 	"Remove": "Înlătură",
 	"Remove Model": "Înlătură Modelul",
 	"Rename": "Redenumește",
+	"Reorder Models": "",
 	"Repeat Last N": "Repetă Ultimele N",
 	"Request Mode": "Mod de Cerere",
 	"Reranking Model": "Model de Rearanjare",

+ 6 - 0
src/lib/i18n/locales/ru-RU/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Одновременные запросы",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Подтвердить",
 	"Confirm Password": "Подтвердите пароль",
 	"Confirm your action": "Подтвердите свое действие",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "По умолчанию (SentenceTransformers)",
 	"Default Model": "Модель по умолчанию",
 	"Default model updated": "Модель по умолчанию обновлена",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Предложения промптов по умолчанию",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Не удалось создать ключ API.",
 	"Failed to read clipboard contents": "Не удалось прочитать содержимое буфера обмена",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Не удалось обновить настройки",
 	"Failed to upload file.": "",
 	"February": "Февраль",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Содержимое файла модели",
 	"Models": "Модели",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Больше",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Результатов не найдено",
 	"No search query generated": "Поисковый запрос не сгенерирован",
 	"No source available": "Нет доступных источников",
@@ -699,6 +704,7 @@
 	"Remove": "Удалить",
 	"Remove Model": "Удалить модель",
 	"Rename": "Переименовать",
+	"Reorder Models": "",
 	"Repeat Last N": "Повторить последние N",
 	"Request Mode": "Режим запроса",
 	"Reranking Model": "Модель реранжирования",

+ 6 - 0
src/lib/i18n/locales/sr-RS/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Упоредни захтеви",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Потврди лозинку",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Подразумевано (SentenceTransformers)",
 	"Default Model": "Подразумевани модел",
 	"Default model updated": "Подразумевани модел ажуриран",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Подразумевани предлози упита",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Неуспешно стварање API кључа.",
 	"Failed to read clipboard contents": "Неуспешно читање садржаја оставе",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "Фебруар",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Садржај модел-датотеке",
 	"Models": "Модели",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Више",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Нема резултата",
 	"No search query generated": "Није генерисан упит за претрагу",
 	"No source available": "Нема доступног извора",
@@ -699,6 +704,7 @@
 	"Remove": "Уклони",
 	"Remove Model": "Уклони модел",
 	"Rename": "Преименуј",
+	"Reorder Models": "",
 	"Repeat Last N": "Понови последњих N",
 	"Request Mode": "Режим захтева",
 	"Reranking Model": "Модел поновног рангирања",

+ 6 - 0
src/lib/i18n/locales/sv-SE/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Parallella anrop",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "Bekräfta lösenord",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
 	"Default Model": "Standardmodell",
 	"Default model updated": "Standardmodell uppdaterad",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Standardinstruktionsförslag",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Misslyckades med att skapa API-nyckel.",
 	"Failed to read clipboard contents": "Misslyckades med att läsa urklippsinnehåll",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Misslyckades med att uppdatera inställningarna",
 	"Failed to upload file.": "",
 	"February": "februari",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Modelfilens innehåll",
 	"Models": "Modeller",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Mer",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Inga resultat hittades",
 	"No search query generated": "Ingen sökfråga genererad",
 	"No source available": "Ingen tillgänglig källa",
@@ -699,6 +704,7 @@
 	"Remove": "Ta bort",
 	"Remove Model": "Ta bort modell",
 	"Rename": "Byt namn",
+	"Reorder Models": "",
 	"Repeat Last N": "Upprepa senaste N",
 	"Request Mode": "Frågeläge",
 	"Reranking Model": "Reranking modell",

+ 6 - 0
src/lib/i18n/locales/th-TH/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "คำขอพร้อมกัน",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "ยืนยัน",
 	"Confirm Password": "ยืนยันรหัสผ่าน",
 	"Confirm your action": "ยืนยันการดำเนินการของคุณ",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "ค่าเริ่มต้น (SentenceTransformers)",
 	"Default Model": "โมเดลค่าเริ่มต้น",
 	"Default model updated": "อัปเดตโมเดลค่าเริ่มต้นแล้ว",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "คำแนะนำพรอมต์ค่าเริ่มต้น",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "สร้างคีย์ API ล้มเหลว",
 	"Failed to read clipboard contents": "อ่านเนื้อหาคลิปบอร์ดล้มเหลว",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "อัปเดตการตั้งค่าล้มเหลว",
 	"Failed to upload file.": "",
 	"February": "กุมภาพันธ์",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "เนื้อหาของไฟล์โมเดล",
 	"Models": "โมเดล",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "เพิ่มเติม",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "ไม่มีผลลัพธ์",
 	"No search query generated": "ไม่มีการสร้างคำค้นหา",
 	"No source available": "ไม่มีแหล่งข้อมูล",
@@ -699,6 +704,7 @@
 	"Remove": "ลบ",
 	"Remove Model": "ลบโมเดล",
 	"Rename": "เปลี่ยนชื่อ",
+	"Reorder Models": "",
 	"Repeat Last N": "ทำซ้ำครั้งล่าสุด N",
 	"Request Mode": "โหมดคำขอ",
 	"Reranking Model": "จัดอันดับใหม่โมเดล",

+ 6 - 0
src/lib/i18n/locales/tk-TW/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "",
 	"Confirm Password": "",
 	"Confirm your action": "",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "",
 	"Default Model": "",
 	"Default model updated": "",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "",
 	"Failed to upload file.": "",
 	"February": "",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "",
 	"Models": "",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "",
 	"No search query generated": "",
 	"No source available": "",
@@ -699,6 +704,7 @@
 	"Remove": "",
 	"Remove Model": "",
 	"Rename": "",
+	"Reorder Models": "",
 	"Repeat Last N": "",
 	"Request Mode": "",
 	"Reranking Model": "",

+ 6 - 0
src/lib/i18n/locales/tr-TR/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Eşzamanlı İstekler",
 	"Configure": "Yapılandırma",
+	"Configure Models": "",
 	"Confirm": "Onayla",
 	"Confirm Password": "Parolayı Onayla",
 	"Confirm your action": "İşleminizi onaylayın",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Varsayılan (SentenceTransformers)",
 	"Default Model": "Varsayılan Model",
 	"Default model updated": "Varsayılan model güncellendi",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Varsayılan Prompt Önerileri",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Dosya eklenemedi.",
 	"Failed to create API Key.": "API Anahtarı oluşturulamadı.",
 	"Failed to read clipboard contents": "Pano içeriği okunamadı",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Ayarlar güncellenemedi",
 	"Failed to upload file.": "",
 	"February": "Şubat",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Model Dosyası İçeriği",
 	"Models": "Modeller",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Daha Fazla",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Bilgi bulunamadı",
 	"No model IDs": "",
 	"No models found": "Model bulunamadı",
+	"No models selected": "",
 	"No results found": "Sonuç bulunamadı",
 	"No search query generated": "Hiç arama sorgusu oluşturulmadı",
 	"No source available": "Kaynak mevcut değil",
@@ -699,6 +704,7 @@
 	"Remove": "Kaldır",
 	"Remove Model": "Modeli Kaldır",
 	"Rename": "Yeniden Adlandır",
+	"Reorder Models": "",
 	"Repeat Last N": "Son N'yi Tekrar Et",
 	"Request Mode": "İstek Modu",
 	"Reranking Model": "Yeniden Sıralama Modeli",

+ 6 - 0
src/lib/i18n/locales/uk-UA/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "Завершення",
 	"Concurrent Requests": "Одночасні запити",
 	"Configure": "Налаштувати",
+	"Configure Models": "",
 	"Confirm": "Підтвердити",
 	"Confirm Password": "Підтвердіть пароль",
 	"Confirm your action": "Підтвердіть свою дію",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "За замовчуванням (SentenceTransformers)",
 	"Default Model": "Модель за замовчуванням",
 	"Default model updated": "Модель за замовчуванням оновлено",
+	"Default Models": "",
 	"Default permissions": "Дозволи за замовчуванням",
 	"Default permissions updated successfully": "Дозволи за замовчуванням успішно оновлено",
 	"Default Prompt Suggestions": "Пропозиції промтів замовчуванням",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "Не вдалося додати файл.",
 	"Failed to create API Key.": "Не вдалося створити API ключ.",
 	"Failed to read clipboard contents": "Не вдалося прочитати вміст буфера обміну",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Не вдалося оновити налаштування",
 	"Failed to upload file.": "Не вдалося завантажити файл.",
 	"February": "Лютий",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Вміст файлу моделі",
 	"Models": "Моделі",
 	"Models Access": "Доступ до моделей",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "API ключ для пошуку Mojeek",
 	"more": "більше",
 	"More": "Більше",
@@ -589,6 +593,7 @@
 	"No knowledge found": "Знання не знайдено.",
 	"No model IDs": "Немає ID моделей",
 	"No models found": "Моделей не знайдено",
+	"No models selected": "",
 	"No results found": "Не знайдено жодного результату",
 	"No search query generated": "Пошуковий запит не сформовано",
 	"No source available": "Джерело не доступне",
@@ -699,6 +704,7 @@
 	"Remove": "Видалити",
 	"Remove Model": "Видалити модель",
 	"Rename": "Перейменувати",
+	"Reorder Models": "",
 	"Repeat Last N": "Повторити останні N",
 	"Request Mode": "Режим запиту",
 	"Reranking Model": "Модель переранжування",

+ 6 - 0
src/lib/i18n/locales/ur-PK/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "تکمیل",
 	"Concurrent Requests": "ہم وقت درخواستیں",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "تصدیق کریں",
 	"Confirm Password": "پاس ورڈ کی توثیق کریں",
 	"Confirm your action": "اپنی کارروائی کی تصدیق کریں",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "ڈیفالٹ (سینٹینس ٹرانسفارمرز)",
 	"Default Model": "ڈیفالٹ ماڈل",
 	"Default model updated": "ڈیفالٹ ماڈل اپ ڈیٹ ہو گیا",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "ڈیفالٹ پرامپٹ تجاویز",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "فائل شامل کرنے میں ناکام",
 	"Failed to create API Key.": "API کلید بنانے میں ناکام",
 	"Failed to read clipboard contents": "کلپ بورڈ مواد کو پڑھنے میں ناکام",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "ترتیبات کی تازہ کاری ناکام رہی",
 	"Failed to upload file.": "فائل اپلوڈ کرنے میں ناکامی ہوئی",
 	"February": "فروری",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "ماڈل فائل مواد",
 	"Models": "ماڈلز",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "مزید",
 	"More": "مزید",
@@ -589,6 +593,7 @@
 	"No knowledge found": "کوئی معلومات نہیں ملی",
 	"No model IDs": "",
 	"No models found": "کوئی ماڈل نہیں ملا",
+	"No models selected": "",
 	"No results found": "کوئی نتائج نہیں ملے",
 	"No search query generated": "کوئی تلاش کی درخواست نہیں بنائی گئی",
 	"No source available": "ماخذ دستیاب نہیں ہے",
@@ -699,6 +704,7 @@
 	"Remove": "ہٹا دیں",
 	"Remove Model": "ماڈل ہٹائیں",
 	"Rename": "تبدیل نام کریں",
+	"Reorder Models": "",
 	"Repeat Last N": "آخری این (N)",
 	"Request Mode": "درخواست کا موڈ",
 	"Reranking Model": "دوبارہ درجہ بندی کا ماڈل",

+ 6 - 0
src/lib/i18n/locales/vi-VN/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "Các truy vấn đồng thời",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "Xác nhận",
 	"Confirm Password": "Xác nhận Mật khẩu",
 	"Confirm your action": "Xác nhận hành động của bạn",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "Mặc định (SentenceTransformers)",
 	"Default Model": "Model mặc định",
 	"Default model updated": "Mô hình mặc định đã được cập nhật",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "Đề xuất prompt mặc định",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "",
 	"Failed to create API Key.": "Lỗi khởi tạo API Key",
 	"Failed to read clipboard contents": "Không thể đọc nội dung clipboard",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "Lỗi khi cập nhật các cài đặt",
 	"Failed to upload file.": "",
 	"February": "Tháng 2",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "Nội dung Tệp Mô hình",
 	"Models": "Mô hình",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "Thêm",
@@ -589,6 +593,7 @@
 	"No knowledge found": "",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "Không tìm thấy kết quả",
 	"No search query generated": "Không có truy vấn tìm kiếm nào được tạo ra",
 	"No source available": "Không có nguồn",
@@ -699,6 +704,7 @@
 	"Remove": "Xóa",
 	"Remove Model": "Xóa model",
 	"Rename": "Đổi tên",
+	"Reorder Models": "",
 	"Repeat Last N": "Repeat Last N",
 	"Request Mode": "Request Mode",
 	"Reranking Model": "Reranking Model",

+ 6 - 0
src/lib/i18n/locales/zh-CN/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "续写",
 	"Concurrent Requests": "并发请求",
 	"Configure": "配置",
+	"Configure Models": "",
 	"Confirm": "确认",
 	"Confirm Password": "确认密码",
 	"Confirm your action": "确定吗?",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "默认(SentenceTransformers)",
 	"Default Model": "默认模型",
 	"Default model updated": "默认模型已更新",
+	"Default Models": "",
 	"Default permissions": "默认权限",
 	"Default permissions updated successfully": "默认权限更新成功",
 	"Default Prompt Suggestions": "默认提示词建议",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "添加文件失败。",
 	"Failed to create API Key.": "无法创建 API 密钥。",
 	"Failed to read clipboard contents": "无法读取剪贴板内容",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "无法更新设置",
 	"Failed to upload file.": "上传文件失败",
 	"February": "二月",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "模型文件内容",
 	"Models": "模型",
 	"Models Access": "访问模型列表",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "更多",
 	"More": "更多",
@@ -589,6 +593,7 @@
 	"No knowledge found": "未找到知识",
 	"No model IDs": "没有模型 ID",
 	"No models found": "未找到任何模型",
+	"No models selected": "",
 	"No results found": "未找到结果",
 	"No search query generated": "未生成搜索查询",
 	"No source available": "没有可用来源",
@@ -699,6 +704,7 @@
 	"Remove": "移除",
 	"Remove Model": "移除模型",
 	"Rename": "重命名",
+	"Reorder Models": "",
 	"Repeat Last N": "重复最后 N 次",
 	"Request Mode": "请求模式",
 	"Reranking Model": "重排模型",

+ 6 - 0
src/lib/i18n/locales/zh-TW/translation.json

@@ -167,6 +167,7 @@
 	"Completions": "",
 	"Concurrent Requests": "平行請求",
 	"Configure": "",
+	"Configure Models": "",
 	"Confirm": "確認",
 	"Confirm Password": "確認密碼",
 	"Confirm your action": "確認您的操作",
@@ -215,6 +216,7 @@
 	"Default (SentenceTransformers)": "預設 (SentenceTransformers)",
 	"Default Model": "預設模型",
 	"Default model updated": "預設模型已更新",
+	"Default Models": "",
 	"Default permissions": "",
 	"Default permissions updated successfully": "",
 	"Default Prompt Suggestions": "預設提示詞建議",
@@ -383,6 +385,7 @@
 	"Failed to add file.": "無法新增檔案。",
 	"Failed to create API Key.": "無法建立 API 金鑰。",
 	"Failed to read clipboard contents": "無法讀取剪貼簿內容",
+	"Failed to save models configuration": "",
 	"Failed to update settings": "無法更新設定",
 	"Failed to upload file.": "無法上傳檔案。",
 	"February": "2 月",
@@ -570,6 +573,7 @@
 	"Modelfile Content": "模型檔案內容",
 	"Models": "模型",
 	"Models Access": "",
+	"Models configuration saved successfully": "",
 	"Mojeek Search API Key": "",
 	"more": "",
 	"More": "更多",
@@ -589,6 +593,7 @@
 	"No knowledge found": "找不到知識",
 	"No model IDs": "",
 	"No models found": "",
+	"No models selected": "",
 	"No results found": "找不到任何結果",
 	"No search query generated": "未產生搜尋查詢",
 	"No source available": "沒有可用的來源",
@@ -699,6 +704,7 @@
 	"Remove": "移除",
 	"Remove Model": "移除模型",
 	"Rename": "重新命名",
+	"Reorder Models": "",
 	"Repeat Last N": "重複最後 N 個",
 	"Request Mode": "請求模式",
 	"Reranking Model": "重新排序模型",

+ 8 - 1
src/lib/utils/index.ts

@@ -8,6 +8,10 @@ import { TTS_RESPONSE_SPLIT } from '$lib/types';
 // Helper functions
 //////////////////////////
 
+function escapeRegExp(string: string): string {
+	return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
 export const replaceTokens = (content, sourceIds, char, user) => {
 	const charToken = /{{char}}/gi;
 	const userToken = /{{user}}/gi;
@@ -39,8 +43,11 @@ export const replaceTokens = (content, sourceIds, char, user) => {
 	// Remove sourceIds from the content and replace them with <source_id>...</source_id>
 	if (Array.isArray(sourceIds)) {
 		sourceIds.forEach((sourceId) => {
+			// Escape special characters in the sourceId
+			const escapedSourceId = escapeRegExp(sourceId);
+
 			// Create a token based on the exact `[sourceId]` string
-			const sourceToken = `\\[${sourceId}\\]`; // Escape special characters for RegExp
+			const sourceToken = `\\[${escapedSourceId}\\]`; // Escape special characters for RegExp
 			const sourceRegex = new RegExp(sourceToken, 'g'); // Match all occurrences of [sourceId]
 
 			content = content.replace(sourceRegex, `<source_id data="${sourceId}" />`);