Преглед изворни кода

Merge pull request #2505 from open-webui/dev-models

feat: openai api compatible model presets (profiles/modelfiles)
Timothy Jaeryang Baek пре 11 месеци
родитељ
комит
67a5020cdc
88 измењених фајлова са 3354 додато и 2503 уклоњено
  1. 11 2
      backend/apps/litellm/main.py
  2. 150 26
      backend/apps/ollama/main.py
  3. 89 30
      backend/apps/openai/main.py
  4. 12 0
      backend/apps/web/internal/db.py
  5. 61 0
      backend/apps/web/internal/migrations/009_add_models.py
  6. 130 0
      backend/apps/web/internal/migrations/010_migrate_modelfiles_to_models.py
  7. 5 3
      backend/apps/web/main.py
  8. 8 0
      backend/apps/web/models/modelfiles.py
  9. 179 0
      backend/apps/web/models/models.py
  10. 0 124
      backend/apps/web/routers/modelfiles.py
  11. 108 0
      backend/apps/web/routers/models.py
  12. 2 0
      backend/constants.py
  13. 104 6
      backend/main.py
  14. 74 0
      backend/utils/misc.py
  15. 10 0
      backend/utils/models.py
  16. 123 0
      src/lib/apis/index.ts
  17. 2 1
      src/lib/apis/litellm/index.ts
  18. 17 32
      src/lib/apis/models/index.ts
  19. 6 1
      src/lib/apis/openai/index.ts
  20. 104 106
      src/lib/components/chat/Chat.svelte
  21. 60 13
      src/lib/components/chat/MessageInput.svelte
  22. 2 8
      src/lib/components/chat/Messages.svelte
  23. 54 53
      src/lib/components/chat/Messages/CodeBlock.svelte
  24. 0 3
      src/lib/components/chat/Messages/CompareMessages.svelte
  25. 36 37
      src/lib/components/chat/Messages/Placeholder.svelte
  26. 8 10
      src/lib/components/chat/Messages/ResponseMessage.svelte
  27. 4 9
      src/lib/components/chat/Messages/UserMessage.svelte
  28. 2 0
      src/lib/components/chat/Messages/test.json
  29. 5 7
      src/lib/components/chat/ModelSelector.svelte
  30. 43 18
      src/lib/components/chat/ModelSelector/Selector.svelte
  31. 0 155
      src/lib/components/chat/Settings/Advanced.svelte
  32. 177 97
      src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
  33. 28 27
      src/lib/components/chat/Settings/General.svelte
  34. 37 287
      src/lib/components/chat/Settings/Models.svelte
  35. 1 1
      src/lib/components/chat/SettingsModal.svelte
  36. 2 4
      src/lib/components/chat/ShareChatModal.svelte
  37. 1 0
      src/lib/components/common/Checkbox.svelte
  38. 2 1
      src/lib/components/common/Tooltip.svelte
  39. 0 1
      src/lib/components/layout/Navbar.svelte
  40. 73 92
      src/lib/components/workspace/Models.svelte
  41. 5 7
      src/lib/components/workspace/Playground.svelte
  42. 1 1
      src/lib/constants.ts
  43. 14 0
      src/lib/i18n/locales/ar-BH/translation.json
  44. 14 0
      src/lib/i18n/locales/bg-BG/translation.json
  45. 14 0
      src/lib/i18n/locales/bn-BD/translation.json
  46. 14 0
      src/lib/i18n/locales/ca-ES/translation.json
  47. 14 0
      src/lib/i18n/locales/de-DE/translation.json
  48. 14 0
      src/lib/i18n/locales/dg-DG/translation.json
  49. 14 0
      src/lib/i18n/locales/en-GB/translation.json
  50. 14 0
      src/lib/i18n/locales/en-US/translation.json
  51. 14 0
      src/lib/i18n/locales/es-ES/translation.json
  52. 14 0
      src/lib/i18n/locales/fa-IR/translation.json
  53. 14 0
      src/lib/i18n/locales/fi-FI/translation.json
  54. 14 0
      src/lib/i18n/locales/fr-CA/translation.json
  55. 14 0
      src/lib/i18n/locales/fr-FR/translation.json
  56. 14 0
      src/lib/i18n/locales/he-IL/translation.json
  57. 14 0
      src/lib/i18n/locales/hi-IN/translation.json
  58. 14 0
      src/lib/i18n/locales/hr-HR/translation.json
  59. 14 0
      src/lib/i18n/locales/it-IT/translation.json
  60. 14 0
      src/lib/i18n/locales/ja-JP/translation.json
  61. 14 0
      src/lib/i18n/locales/ka-GE/translation.json
  62. 14 0
      src/lib/i18n/locales/ko-KR/translation.json
  63. 14 0
      src/lib/i18n/locales/nl-NL/translation.json
  64. 14 0
      src/lib/i18n/locales/pa-IN/translation.json
  65. 14 0
      src/lib/i18n/locales/pl-PL/translation.json
  66. 14 0
      src/lib/i18n/locales/pt-BR/translation.json
  67. 14 0
      src/lib/i18n/locales/pt-PT/translation.json
  68. 14 0
      src/lib/i18n/locales/ru-RU/translation.json
  69. 14 0
      src/lib/i18n/locales/sr-RS/translation.json
  70. 14 0
      src/lib/i18n/locales/sv-SE/translation.json
  71. 14 0
      src/lib/i18n/locales/tr-TR/translation.json
  72. 14 0
      src/lib/i18n/locales/uk-UA/translation.json
  73. 14 0
      src/lib/i18n/locales/vi-VN/translation.json
  74. 14 0
      src/lib/i18n/locales/zh-CN/translation.json
  75. 14 0
      src/lib/i18n/locales/zh-TW/translation.json
  76. 11 9
      src/lib/stores/index.ts
  77. 0 24
      src/lib/utils/index.ts
  78. 3 28
      src/routes/(app)/+layout.svelte
  79. 2 2
      src/routes/(app)/workspace/+layout.svelte
  80. 1 1
      src/routes/(app)/workspace/+page.svelte
  81. 0 5
      src/routes/(app)/workspace/modelfiles/+page.svelte
  82. 0 721
      src/routes/(app)/workspace/modelfiles/create/+page.svelte
  83. 0 507
      src/routes/(app)/workspace/modelfiles/edit/+page.svelte
  84. 5 0
      src/routes/(app)/workspace/models/+page.svelte
  85. 576 0
      src/routes/(app)/workspace/models/create/+page.svelte
  86. 555 0
      src/routes/(app)/workspace/models/edit/+page.svelte
  87. 0 27
      src/routes/modelfiles/create/+page.svelte
  88. 3 17
      src/routes/s/[id]/+page.svelte

+ 11 - 2
backend/apps/litellm/main.py

@@ -18,8 +18,9 @@ import requests
 from pydantic import BaseModel, ConfigDict
 from pydantic import BaseModel, ConfigDict
 from typing import Optional, List
 from typing import Optional, List
 
 
+from apps.web.models.models import Models
 from utils.utils import get_verified_user, get_current_user, get_admin_user
 from utils.utils import get_verified_user, get_current_user, get_admin_user
-from config import SRC_LOG_LEVELS, ENV
+from config import SRC_LOG_LEVELS
 from constants import MESSAGES
 from constants import MESSAGES
 
 
 import os
 import os
@@ -77,7 +78,7 @@ with open(LITELLM_CONFIG_DIR, "r") as file:
 
 
 app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER.value
 app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER.value
 app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST.value
 app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST.value
-
+app.state.MODEL_CONFIG = Models.get_all_models()
 
 
 app.state.ENABLE = ENABLE_LITELLM
 app.state.ENABLE = ENABLE_LITELLM
 app.state.CONFIG = litellm_config
 app.state.CONFIG = litellm_config
@@ -261,6 +262,14 @@ async def get_models(user=Depends(get_current_user)):
                         "object": "model",
                         "object": "model",
                         "created": int(time.time()),
                         "created": int(time.time()),
                         "owned_by": "openai",
                         "owned_by": "openai",
+                        "custom_info": next(
+                            (
+                                item
+                                for item in app.state.MODEL_CONFIG
+                                if item.id == model["model_name"]
+                            ),
+                            None,
+                        ),
                     }
                     }
                     for model in app.state.CONFIG["model_list"]
                     for model in app.state.CONFIG["model_list"]
                 ],
                 ],

+ 150 - 26
backend/apps/ollama/main.py

@@ -29,7 +29,7 @@ import time
 from urllib.parse import urlparse
 from urllib.parse import urlparse
 from typing import Optional, List, Union
 from typing import Optional, List, Union
 
 
-
+from apps.web.models.models import Models
 from apps.web.models.users import Users
 from apps.web.models.users import Users
 from constants import ERROR_MESSAGES
 from constants import ERROR_MESSAGES
 from utils.utils import (
 from utils.utils import (
@@ -39,6 +39,8 @@ from utils.utils import (
     get_admin_user,
     get_admin_user,
 )
 )
 
 
+from utils.models import get_model_id_from_custom_model_id
+
 
 
 from config import (
 from config import (
     SRC_LOG_LEVELS,
     SRC_LOG_LEVELS,
@@ -68,7 +70,6 @@ app.state.config = AppConfig()
 app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 
 
-
 app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
 app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
 app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
 app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
 app.state.MODELS = {}
 app.state.MODELS = {}
@@ -875,14 +876,93 @@ async def generate_chat_completion(
     user=Depends(get_verified_user),
     user=Depends(get_verified_user),
 ):
 ):
 
 
-    if url_idx == None:
-        model = form_data.model
+    log.debug(
+        "form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(
+            form_data.model_dump_json(exclude_none=True).encode()
+        )
+    )
 
 
-        if ":" not in model:
-            model = f"{model}:latest"
+    payload = {
+        **form_data.model_dump(exclude_none=True),
+    }
 
 
-        if model in app.state.MODELS:
-            url_idx = random.choice(app.state.MODELS[model]["urls"])
+    model_id = form_data.model
+    model_info = Models.get_model_by_id(model_id)
+
+    if model_info:
+        print(model_info)
+        if model_info.base_model_id:
+            payload["model"] = model_info.base_model_id
+
+        model_info.params = model_info.params.model_dump()
+
+        if model_info.params:
+            payload["options"] = {}
+
+            payload["options"]["mirostat"] = model_info.params.get("mirostat", None)
+            payload["options"]["mirostat_eta"] = model_info.params.get(
+                "mirostat_eta", None
+            )
+            payload["options"]["mirostat_tau"] = model_info.params.get(
+                "mirostat_tau", None
+            )
+            payload["options"]["num_ctx"] = model_info.params.get("num_ctx", None)
+
+            payload["options"]["repeat_last_n"] = model_info.params.get(
+                "repeat_last_n", None
+            )
+            payload["options"]["repeat_penalty"] = model_info.params.get(
+                "frequency_penalty", None
+            )
+
+            payload["options"]["temperature"] = model_info.params.get(
+                "temperature", None
+            )
+            payload["options"]["seed"] = model_info.params.get("seed", None)
+
+            payload["options"]["stop"] = (
+                [
+                    bytes(stop, "utf-8").decode("unicode_escape")
+                    for stop in model_info.params["stop"]
+                ]
+                if model_info.params.get("stop", None)
+                else None
+            )
+
+            payload["options"]["tfs_z"] = model_info.params.get("tfs_z", None)
+
+            payload["options"]["num_predict"] = model_info.params.get(
+                "max_tokens", None
+            )
+            payload["options"]["top_k"] = model_info.params.get("top_k", None)
+
+            payload["options"]["top_p"] = model_info.params.get("top_p", None)
+
+        if model_info.params.get("system", None):
+            # Check if the payload already has a system message
+            # If not, add a system message to the payload
+            if payload.get("messages"):
+                for message in payload["messages"]:
+                    if message.get("role") == "system":
+                        message["content"] = (
+                            model_info.params.get("system", None) + message["content"]
+                        )
+                        break
+                else:
+                    payload["messages"].insert(
+                        0,
+                        {
+                            "role": "system",
+                            "content": model_info.params.get("system", None),
+                        },
+                    )
+
+    if url_idx == None:
+        if ":" not in payload["model"]:
+            payload["model"] = f"{payload['model']}:latest"
+
+        if payload["model"] in app.state.MODELS:
+            url_idx = random.choice(app.state.MODELS[payload["model"]]["urls"])
         else:
         else:
             raise HTTPException(
             raise HTTPException(
                 status_code=400,
                 status_code=400,
@@ -892,16 +972,12 @@ async def generate_chat_completion(
     url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
     log.info(f"url: {url}")
 
 
-    r = None
+    print(payload)
 
 
-    log.debug(
-        "form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(
-            form_data.model_dump_json(exclude_none=True).encode()
-        )
-    )
+    r = None
 
 
     def get_request():
     def get_request():
-        nonlocal form_data
+        nonlocal payload
         nonlocal r
         nonlocal r
 
 
         request_id = str(uuid.uuid4())
         request_id = str(uuid.uuid4())
@@ -910,7 +986,7 @@ async def generate_chat_completion(
 
 
             def stream_content():
             def stream_content():
                 try:
                 try:
-                    if form_data.stream:
+                    if payload.get("stream", None):
                         yield json.dumps({"id": request_id, "done": False}) + "\n"
                         yield json.dumps({"id": request_id, "done": False}) + "\n"
 
 
                     for chunk in r.iter_content(chunk_size=8192):
                     for chunk in r.iter_content(chunk_size=8192):
@@ -928,7 +1004,7 @@ async def generate_chat_completion(
             r = requests.request(
             r = requests.request(
                 method="POST",
                 method="POST",
                 url=f"{url}/api/chat",
                 url=f"{url}/api/chat",
-                data=form_data.model_dump_json(exclude_none=True).encode(),
+                data=json.dumps(payload),
                 stream=True,
                 stream=True,
             )
             )
 
 
@@ -984,14 +1060,62 @@ async def generate_openai_chat_completion(
     user=Depends(get_verified_user),
     user=Depends(get_verified_user),
 ):
 ):
 
 
-    if url_idx == None:
-        model = form_data.model
+    payload = {
+        **form_data.model_dump(exclude_none=True),
+    }
 
 
-        if ":" not in model:
-            model = f"{model}:latest"
+    model_id = form_data.model
+    model_info = Models.get_model_by_id(model_id)
 
 
-        if model in app.state.MODELS:
-            url_idx = random.choice(app.state.MODELS[model]["urls"])
+    if model_info:
+        print(model_info)
+        if model_info.base_model_id:
+            payload["model"] = model_info.base_model_id
+
+        model_info.params = model_info.params.model_dump()
+
+        if model_info.params:
+            payload["temperature"] = model_info.params.get("temperature", None)
+            payload["top_p"] = model_info.params.get("top_p", None)
+            payload["max_tokens"] = model_info.params.get("max_tokens", None)
+            payload["frequency_penalty"] = model_info.params.get(
+                "frequency_penalty", None
+            )
+            payload["seed"] = model_info.params.get("seed", None)
+            payload["stop"] = (
+                [
+                    bytes(stop, "utf-8").decode("unicode_escape")
+                    for stop in model_info.params["stop"]
+                ]
+                if model_info.params.get("stop", None)
+                else None
+            )
+
+        if model_info.params.get("system", None):
+            # Check if the payload already has a system message
+            # If not, add a system message to the payload
+            if payload.get("messages"):
+                for message in payload["messages"]:
+                    if message.get("role") == "system":
+                        message["content"] = (
+                            model_info.params.get("system", None) + message["content"]
+                        )
+                        break
+                else:
+                    payload["messages"].insert(
+                        0,
+                        {
+                            "role": "system",
+                            "content": model_info.params.get("system", None),
+                        },
+                    )
+
+    if url_idx == None:
+        if ":" not in payload["model"]:
+            payload["model"] = f"{payload['model']}:latest"
+
+        if payload["model"] in app.state.MODELS:
+            url_idx = random.choice(app.state.MODELS[payload["model"]]["urls"])
         else:
         else:
             raise HTTPException(
             raise HTTPException(
                 status_code=400,
                 status_code=400,
@@ -1004,7 +1128,7 @@ async def generate_openai_chat_completion(
     r = None
     r = None
 
 
     def get_request():
     def get_request():
-        nonlocal form_data
+        nonlocal payload
         nonlocal r
         nonlocal r
 
 
         request_id = str(uuid.uuid4())
         request_id = str(uuid.uuid4())
@@ -1013,7 +1137,7 @@ async def generate_openai_chat_completion(
 
 
             def stream_content():
             def stream_content():
                 try:
                 try:
-                    if form_data.stream:
+                    if payload.get("stream"):
                         yield json.dumps(
                         yield json.dumps(
                             {"request_id": request_id, "done": False}
                             {"request_id": request_id, "done": False}
                         ) + "\n"
                         ) + "\n"
@@ -1033,7 +1157,7 @@ async def generate_openai_chat_completion(
             r = requests.request(
             r = requests.request(
                 method="POST",
                 method="POST",
                 url=f"{url}/v1/chat/completions",
                 url=f"{url}/v1/chat/completions",
-                data=form_data.model_dump_json(exclude_none=True).encode(),
+                data=json.dumps(payload),
                 stream=True,
                 stream=True,
             )
             )
 
 

+ 89 - 30
backend/apps/openai/main.py

@@ -10,7 +10,7 @@ import logging
 
 
 from pydantic import BaseModel
 from pydantic import BaseModel
 
 
-
+from apps.web.models.models import Models
 from apps.web.models.users import Users
 from apps.web.models.users import Users
 from constants import ERROR_MESSAGES
 from constants import ERROR_MESSAGES
 from utils.utils import (
 from utils.utils import (
@@ -53,7 +53,6 @@ app.state.config = AppConfig()
 app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 
 
-
 app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
 app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
 app.state.config.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
 app.state.config.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
 app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS
 app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS
@@ -206,7 +205,13 @@ def merge_models_lists(model_lists):
         if models is not None and "error" not in models:
         if models is not None and "error" not in models:
             merged_list.extend(
             merged_list.extend(
                 [
                 [
-                    {**model, "urlIdx": idx}
+                    {
+                        **model,
+                        "name": model.get("name", model["id"]),
+                        "owned_by": "openai",
+                        "openai": model,
+                        "urlIdx": idx,
+                    }
                     for model in models
                     for model in models
                     if "api.openai.com"
                     if "api.openai.com"
                     not in app.state.config.OPENAI_API_BASE_URLS[idx]
                     not in app.state.config.OPENAI_API_BASE_URLS[idx]
@@ -252,7 +257,7 @@ async def get_all_models():
         log.info(f"models: {models}")
         log.info(f"models: {models}")
         app.state.MODELS = {model["id"]: model for model in models["data"]}
         app.state.MODELS = {model["id"]: model for model in models["data"]}
 
 
-        return models
+    return models
 
 
 
 
 @app.get("/models")
 @app.get("/models")
@@ -310,39 +315,93 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
     body = await request.body()
     body = await request.body()
     # TODO: Remove below after gpt-4-vision fix from Open AI
     # TODO: Remove below after gpt-4-vision fix from Open AI
     # Try to decode the body of the request from bytes to a UTF-8 string (Require add max_token to fix gpt-4-vision)
     # Try to decode the body of the request from bytes to a UTF-8 string (Require add max_token to fix gpt-4-vision)
+
+    payload = None
+
     try:
     try:
-        body = body.decode("utf-8")
-        body = json.loads(body)
+        if "chat/completions" in path:
+            body = body.decode("utf-8")
+            body = json.loads(body)
 
 
-        model = app.state.MODELS[body.get("model")]
+            payload = {**body}
 
 
-        idx = model["urlIdx"]
+            model_id = body.get("model")
+            model_info = Models.get_model_by_id(model_id)
 
 
-        if "pipeline" in model and model.get("pipeline"):
-            body["user"] = {"name": user.name, "id": user.id}
-            body["title"] = (
-                True if body["stream"] == False and body["max_tokens"] == 50 else False
-            )
+            if model_info:
+                print(model_info)
+                if model_info.base_model_id:
+                    payload["model"] = model_info.base_model_id
+
+                model_info.params = model_info.params.model_dump()
+
+                if model_info.params:
+                    payload["temperature"] = model_info.params.get("temperature", None)
+                    payload["top_p"] = model_info.params.get("top_p", None)
+                    payload["max_tokens"] = model_info.params.get("max_tokens", None)
+                    payload["frequency_penalty"] = model_info.params.get(
+                        "frequency_penalty", None
+                    )
+                    payload["seed"] = model_info.params.get("seed", None)
+                    payload["stop"] = (
+                        [
+                            bytes(stop, "utf-8").decode("unicode_escape")
+                            for stop in model_info.params["stop"]
+                        ]
+                        if model_info.params.get("stop", None)
+                        else None
+                    )
+
+                if model_info.params.get("system", None):
+                    # Check if the payload already has a system message
+                    # If not, add a system message to the payload
+                    if payload.get("messages"):
+                        for message in payload["messages"]:
+                            if message.get("role") == "system":
+                                message["content"] = (
+                                    model_info.params.get("system", None)
+                                    + message["content"]
+                                )
+                                break
+                        else:
+                            payload["messages"].insert(
+                                0,
+                                {
+                                    "role": "system",
+                                    "content": model_info.params.get("system", None),
+                                },
+                            )
+            else:
+                pass
+
+            print(app.state.MODELS)
+            model = app.state.MODELS[payload.get("model")]
+
+            idx = model["urlIdx"]
+
+            if "pipeline" in model and model.get("pipeline"):
+                payload["user"] = {"name": user.name, "id": user.id}
+                payload["title"] = (
+                    True
+                    if payload["stream"] == False and payload["max_tokens"] == 50
+                    else False
+                )
+
+            # Check if the model is "gpt-4-vision-preview" and set "max_tokens" to 4000
+            # This is a workaround until OpenAI fixes the issue with this model
+            if payload.get("model") == "gpt-4-vision-preview":
+                if "max_tokens" not in payload:
+                    payload["max_tokens"] = 4000
+                log.debug("Modified payload:", payload)
+
+            # Convert the modified body back to JSON
+            payload = json.dumps(payload)
 
 
-        # Check if the model is "gpt-4-vision-preview" and set "max_tokens" to 4000
-        # This is a workaround until OpenAI fixes the issue with this model
-        if body.get("model") == "gpt-4-vision-preview":
-            if "max_tokens" not in body:
-                body["max_tokens"] = 4000
-            log.debug("Modified body_dict:", body)
-
-        # Fix for ChatGPT calls failing because the num_ctx key is in body
-        if "num_ctx" in body:
-            # If 'num_ctx' is in the dictionary, delete it
-            # Leaving it there generates an error with the
-            # OpenAI API (Feb 2024)
-            del body["num_ctx"]
-
-        # Convert the modified body back to JSON
-        body = json.dumps(body)
     except json.JSONDecodeError as e:
     except json.JSONDecodeError as e:
         log.error("Error loading request body into a dictionary:", e)
         log.error("Error loading request body into a dictionary:", e)
 
 
+    print(payload)
+
     url = app.state.config.OPENAI_API_BASE_URLS[idx]
     url = app.state.config.OPENAI_API_BASE_URLS[idx]
     key = app.state.config.OPENAI_API_KEYS[idx]
     key = app.state.config.OPENAI_API_KEYS[idx]
 
 
@@ -361,7 +420,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
         r = requests.request(
         r = requests.request(
             method=request.method,
             method=request.method,
             url=target_url,
             url=target_url,
-            data=body,
+            data=payload if payload else body,
             headers=headers,
             headers=headers,
             stream=True,
             stream=True,
         )
         )

+ 12 - 0
backend/apps/web/internal/db.py

@@ -1,3 +1,5 @@
+import json
+
 from peewee import *
 from peewee import *
 from peewee_migrate import Router
 from peewee_migrate import Router
 from playhouse.db_url import connect
 from playhouse.db_url import connect
@@ -8,6 +10,16 @@ import logging
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 log.setLevel(SRC_LOG_LEVELS["DB"])
 log.setLevel(SRC_LOG_LEVELS["DB"])
 
 
+
+class JSONField(TextField):
+    def db_value(self, value):
+        return json.dumps(value)
+
+    def python_value(self, value):
+        if value is not None:
+            return json.loads(value)
+
+
 # Check if the file exists
 # Check if the file exists
 if os.path.exists(f"{DATA_DIR}/ollama.db"):
 if os.path.exists(f"{DATA_DIR}/ollama.db"):
     # Rename the file
     # Rename the file

+ 61 - 0
backend/apps/web/internal/migrations/009_add_models.py

@@ -0,0 +1,61 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    @migrator.create_model
+    class Model(pw.Model):
+        id = pw.TextField(unique=True)
+        user_id = pw.TextField()
+        base_model_id = pw.TextField(null=True)
+
+        name = pw.TextField()
+
+        meta = pw.TextField()
+        params = pw.TextField()
+
+        created_at = pw.BigIntegerField(null=False)
+        updated_at = pw.BigIntegerField(null=False)
+
+        class Meta:
+            table_name = "model"
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_model("model")

+ 130 - 0
backend/apps/web/internal/migrations/010_migrate_modelfiles_to_models.py

@@ -0,0 +1,130 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+import json
+
+from utils.misc import parse_ollama_modelfile
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    # Fetch data from 'modelfile' table and insert into 'model' table
+    migrate_modelfile_to_model(migrator, database)
+    # Drop the 'modelfile' table
+    migrator.remove_model("modelfile")
+
+
+def migrate_modelfile_to_model(migrator: Migrator, database: pw.Database):
+    ModelFile = migrator.orm["modelfile"]
+    Model = migrator.orm["model"]
+
+    modelfiles = ModelFile.select()
+
+    for modelfile in modelfiles:
+        # Extract and transform data in Python
+
+        modelfile.modelfile = json.loads(modelfile.modelfile)
+        meta = json.dumps(
+            {
+                "description": modelfile.modelfile.get("desc"),
+                "profile_image_url": modelfile.modelfile.get("imageUrl"),
+                "ollama": {"modelfile": modelfile.modelfile.get("content")},
+                "suggestion_prompts": modelfile.modelfile.get("suggestionPrompts"),
+                "categories": modelfile.modelfile.get("categories"),
+                "user": {**modelfile.modelfile.get("user", {}), "community": True},
+            }
+        )
+
+        info = parse_ollama_modelfile(modelfile.modelfile.get("content"))
+
+        # Insert the processed data into the 'model' table
+        Model.create(
+            id=f"ollama-{modelfile.tag_name}",
+            user_id=modelfile.user_id,
+            base_model_id=info.get("base_model_id"),
+            name=modelfile.modelfile.get("title"),
+            meta=meta,
+            params=json.dumps(info.get("params", {})),
+            created_at=modelfile.timestamp,
+            updated_at=modelfile.timestamp,
+        )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    recreate_modelfile_table(migrator, database)
+    move_data_back_to_modelfile(migrator, database)
+    migrator.remove_model("model")
+
+
+def recreate_modelfile_table(migrator: Migrator, database: pw.Database):
+    query = """
+    CREATE TABLE IF NOT EXISTS modelfile (
+        user_id TEXT,
+        tag_name TEXT,
+        modelfile JSON,
+        timestamp BIGINT
+    )
+    """
+    migrator.sql(query)
+
+
+def move_data_back_to_modelfile(migrator: Migrator, database: pw.Database):
+    Model = migrator.orm["model"]
+    Modelfile = migrator.orm["modelfile"]
+
+    models = Model.select()
+
+    for model in models:
+        # Extract and transform data in Python
+        meta = json.loads(model.meta)
+
+        modelfile_data = {
+            "title": model.name,
+            "desc": meta.get("description"),
+            "imageUrl": meta.get("profile_image_url"),
+            "content": meta.get("ollama", {}).get("modelfile"),
+            "suggestionPrompts": meta.get("suggestion_prompts"),
+            "categories": meta.get("categories"),
+            "user": {k: v for k, v in meta.get("user", {}).items() if k != "community"},
+        }
+
+        # Insert the processed data back into the 'modelfile' table
+        Modelfile.create(
+            user_id=model.user_id,
+            tag_name=model.id,
+            modelfile=modelfile_data,
+            timestamp=model.created_at,
+        )

+ 5 - 3
backend/apps/web/main.py

@@ -6,7 +6,7 @@ from apps.web.routers import (
     users,
     users,
     chats,
     chats,
     documents,
     documents,
-    modelfiles,
+    models,
     prompts,
     prompts,
     configs,
     configs,
     memories,
     memories,
@@ -40,6 +40,9 @@ app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
 app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
 app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
 app.state.config.USER_PERMISSIONS = USER_PERMISSIONS
 app.state.config.USER_PERMISSIONS = USER_PERMISSIONS
 app.state.config.WEBHOOK_URL = WEBHOOK_URL
 app.state.config.WEBHOOK_URL = WEBHOOK_URL
+
+
+app.state.MODELS = {}
 app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 
 
 
 
@@ -56,11 +59,10 @@ app.include_router(users.router, prefix="/users", tags=["users"])
 app.include_router(chats.router, prefix="/chats", tags=["chats"])
 app.include_router(chats.router, prefix="/chats", tags=["chats"])
 
 
 app.include_router(documents.router, prefix="/documents", tags=["documents"])
 app.include_router(documents.router, prefix="/documents", tags=["documents"])
-app.include_router(modelfiles.router, prefix="/modelfiles", tags=["modelfiles"])
+app.include_router(models.router, prefix="/models", tags=["models"])
 app.include_router(prompts.router, prefix="/prompts", tags=["prompts"])
 app.include_router(prompts.router, prefix="/prompts", tags=["prompts"])
 app.include_router(memories.router, prefix="/memories", tags=["memories"])
 app.include_router(memories.router, prefix="/memories", tags=["memories"])
 
 
-
 app.include_router(configs.router, prefix="/configs", tags=["configs"])
 app.include_router(configs.router, prefix="/configs", tags=["configs"])
 app.include_router(utils.router, prefix="/utils", tags=["utils"])
 app.include_router(utils.router, prefix="/utils", tags=["utils"])
 
 

+ 8 - 0
backend/apps/web/models/modelfiles.py

@@ -1,3 +1,11 @@
+################################################################################
+#                              DEPRECATION NOTICE                              #
+#                                                                              #
+# This file has been deprecated since version 0.2.0.                           #
+#                                                                              #
+################################################################################
+
+
 from pydantic import BaseModel
 from pydantic import BaseModel
 from peewee import *
 from peewee import *
 from playhouse.shortcuts import model_to_dict
 from playhouse.shortcuts import model_to_dict

+ 179 - 0
backend/apps/web/models/models.py

@@ -0,0 +1,179 @@
+import json
+import logging
+from typing import Optional
+
+import peewee as pw
+from peewee import *
+
+from playhouse.shortcuts import model_to_dict
+from pydantic import BaseModel, ConfigDict
+
+from apps.web.internal.db import DB, JSONField
+
+from typing import List, Union, Optional
+from config import SRC_LOG_LEVELS
+
+import time
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+
+####################
+# Models DB Schema
+####################
+
+
+# ModelParams is a model for the data stored in the params field of the Model table
+class ModelParams(BaseModel):
+    model_config = ConfigDict(extra="allow")
+    pass
+
+
+# ModelMeta is a model for the data stored in the meta field of the Model table
+class ModelMeta(BaseModel):
+    profile_image_url: Optional[str] = "/favicon.png"
+
+    description: Optional[str] = None
+    """
+        User-facing description of the model.
+    """
+
+    capabilities: Optional[dict] = None
+
+    model_config = ConfigDict(extra="allow")
+
+    pass
+
+
+class Model(pw.Model):
+    id = pw.TextField(unique=True)
+    """
+        The model's id as used in the API. If set to an existing model, it will override the model.
+    """
+    user_id = pw.TextField()
+
+    base_model_id = pw.TextField(null=True)
+    """
+        An optional pointer to the actual model that should be used when proxying requests.
+    """
+
+    name = pw.TextField()
+    """
+        The human-readable display name of the model.
+    """
+
+    params = JSONField()
+    """
+        Holds a JSON encoded blob of parameters, see `ModelParams`.
+    """
+
+    meta = JSONField()
+    """
+        Holds a JSON encoded blob of metadata, see `ModelMeta`.
+    """
+
+    updated_at = BigIntegerField()
+    created_at = BigIntegerField()
+
+    class Meta:
+        database = DB
+
+
+class ModelModel(BaseModel):
+    id: str
+    user_id: str
+    base_model_id: Optional[str] = None
+
+    name: str
+    params: ModelParams
+    meta: ModelMeta
+
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+
+####################
+# Forms
+####################
+
+
+class ModelResponse(BaseModel):
+    id: str
+    name: str
+    meta: ModelMeta
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+
+class ModelForm(BaseModel):
+    id: str
+    base_model_id: Optional[str] = None
+    name: str
+    meta: ModelMeta
+    params: ModelParams
+
+
+class ModelsTable:
+    def __init__(
+        self,
+        db: pw.SqliteDatabase | pw.PostgresqlDatabase,
+    ):
+        self.db = db
+        self.db.create_tables([Model])
+
+    def insert_new_model(
+        self, form_data: ModelForm, user_id: str
+    ) -> Optional[ModelModel]:
+        model = ModelModel(
+            **{
+                **form_data.model_dump(),
+                "user_id": user_id,
+                "created_at": int(time.time()),
+                "updated_at": int(time.time()),
+            }
+        )
+        try:
+            result = Model.create(**model.model_dump())
+
+            if result:
+                return model
+            else:
+                return None
+        except Exception as e:
+            print(e)
+            return None
+
+    def get_all_models(self) -> List[ModelModel]:
+        return [ModelModel(**model_to_dict(model)) for model in Model.select()]
+
+    def get_model_by_id(self, id: str) -> Optional[ModelModel]:
+        try:
+            model = Model.get(Model.id == id)
+            return ModelModel(**model_to_dict(model))
+        except:
+            return None
+
+    def update_model_by_id(self, id: str, model: ModelForm) -> Optional[ModelModel]:
+        try:
+            # update only the fields that are present in the model
+            query = Model.update(**model.model_dump()).where(Model.id == id)
+            query.execute()
+
+            model = Model.get(Model.id == id)
+            return ModelModel(**model_to_dict(model))
+        except Exception as e:
+            print(e)
+
+            return None
+
+    def delete_model_by_id(self, id: str) -> bool:
+        try:
+            query = Model.delete().where(Model.id == id)
+            query.execute()
+            return True
+        except:
+            return False
+
+
+Models = ModelsTable(DB)

+ 0 - 124
backend/apps/web/routers/modelfiles.py

@@ -1,124 +0,0 @@
-from fastapi import Depends, FastAPI, HTTPException, status
-from datetime import datetime, timedelta
-from typing import List, Union, Optional
-
-from fastapi import APIRouter
-from pydantic import BaseModel
-import json
-from apps.web.models.modelfiles import (
-    Modelfiles,
-    ModelfileForm,
-    ModelfileTagNameForm,
-    ModelfileUpdateForm,
-    ModelfileResponse,
-)
-
-from utils.utils import get_current_user, get_admin_user
-from constants import ERROR_MESSAGES
-
-router = APIRouter()
-
-############################
-# GetModelfiles
-############################
-
-
-@router.get("/", response_model=List[ModelfileResponse])
-async def get_modelfiles(
-    skip: int = 0, limit: int = 50, user=Depends(get_current_user)
-):
-    return Modelfiles.get_modelfiles(skip, limit)
-
-
-############################
-# CreateNewModelfile
-############################
-
-
-@router.post("/create", response_model=Optional[ModelfileResponse])
-async def create_new_modelfile(form_data: ModelfileForm, user=Depends(get_admin_user)):
-    modelfile = Modelfiles.insert_new_modelfile(user.id, form_data)
-
-    if modelfile:
-        return ModelfileResponse(
-            **{
-                **modelfile.model_dump(),
-                "modelfile": json.loads(modelfile.modelfile),
-            }
-        )
-    else:
-        raise HTTPException(
-            status_code=status.HTTP_401_UNAUTHORIZED,
-            detail=ERROR_MESSAGES.DEFAULT(),
-        )
-
-
-############################
-# GetModelfileByTagName
-############################
-
-
-@router.post("/", response_model=Optional[ModelfileResponse])
-async def get_modelfile_by_tag_name(
-    form_data: ModelfileTagNameForm, user=Depends(get_current_user)
-):
-    modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name)
-
-    if modelfile:
-        return ModelfileResponse(
-            **{
-                **modelfile.model_dump(),
-                "modelfile": json.loads(modelfile.modelfile),
-            }
-        )
-    else:
-        raise HTTPException(
-            status_code=status.HTTP_401_UNAUTHORIZED,
-            detail=ERROR_MESSAGES.NOT_FOUND,
-        )
-
-
-############################
-# UpdateModelfileByTagName
-############################
-
-
-@router.post("/update", response_model=Optional[ModelfileResponse])
-async def update_modelfile_by_tag_name(
-    form_data: ModelfileUpdateForm, user=Depends(get_admin_user)
-):
-    modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name)
-    if modelfile:
-        updated_modelfile = {
-            **json.loads(modelfile.modelfile),
-            **form_data.modelfile,
-        }
-
-        modelfile = Modelfiles.update_modelfile_by_tag_name(
-            form_data.tag_name, updated_modelfile
-        )
-
-        return ModelfileResponse(
-            **{
-                **modelfile.model_dump(),
-                "modelfile": json.loads(modelfile.modelfile),
-            }
-        )
-    else:
-        raise HTTPException(
-            status_code=status.HTTP_401_UNAUTHORIZED,
-            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
-        )
-
-
-############################
-# DeleteModelfileByTagName
-############################
-
-
-@router.delete("/delete", response_model=bool)
-async def delete_modelfile_by_tag_name(
-    form_data: ModelfileTagNameForm, user=Depends(get_admin_user)
-):
-    result = Modelfiles.delete_modelfile_by_tag_name(form_data.tag_name)
-    return result

+ 108 - 0
backend/apps/web/routers/models.py

@@ -0,0 +1,108 @@
+from fastapi import Depends, FastAPI, HTTPException, status, Request
+from datetime import datetime, timedelta
+from typing import List, Union, Optional
+
+from fastapi import APIRouter
+from pydantic import BaseModel
+import json
+from apps.web.models.models import Models, ModelModel, ModelForm, ModelResponse
+
+from utils.utils import get_verified_user, get_admin_user
+from constants import ERROR_MESSAGES
+
+router = APIRouter()
+
+###########################
+# getModels
+###########################
+
+
+@router.get("/", response_model=List[ModelResponse])
+async def get_models(user=Depends(get_verified_user)):
+    return Models.get_all_models()
+
+
+############################
+# AddNewModel
+############################
+
+
+@router.post("/add", response_model=Optional[ModelModel])
+async def add_new_model(
+    request: Request, form_data: ModelForm, user=Depends(get_admin_user)
+):
+    if form_data.id in request.app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.MODEL_ID_TAKEN,
+        )
+    else:
+        model = Models.insert_new_model(form_data, user.id)
+
+        if model:
+            return model
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.DEFAULT(),
+            )
+
+
+############################
+# GetModelById
+############################
+
+
+@router.get("/{id}", response_model=Optional[ModelModel])
+async def get_model_by_id(id: str, user=Depends(get_verified_user)):
+    model = Models.get_model_by_id(id)
+
+    if model:
+        return model
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateModelById
+############################
+
+
+@router.post("/{id}/update", response_model=Optional[ModelModel])
+async def update_model_by_id(
+    request: Request, id: str, form_data: ModelForm, user=Depends(get_admin_user)
+):
+    model = Models.get_model_by_id(id)
+    if model:
+        model = Models.update_model_by_id(id, form_data)
+        return model
+    else:
+        if form_data.id in request.app.state.MODELS:
+            model = Models.insert_new_model(form_data, user.id)
+            print(model)
+            if model:
+                return model
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_401_UNAUTHORIZED,
+                    detail=ERROR_MESSAGES.DEFAULT(),
+                )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.DEFAULT(),
+            )
+
+
+############################
+# DeleteModelById
+############################
+
+
+@router.delete("/{id}/delete", response_model=bool)
+async def delete_model_by_id(id: str, user=Depends(get_admin_user)):
+    result = Models.delete_model_by_id(id)
+    return result

+ 2 - 0
backend/constants.py

@@ -32,6 +32,8 @@ class ERROR_MESSAGES(str, Enum):
     COMMAND_TAKEN = "Uh-oh! This command is already registered. Please choose another command string."
     COMMAND_TAKEN = "Uh-oh! This command is already registered. Please choose another command string."
     FILE_EXISTS = "Uh-oh! This file is already registered. Please choose another file."
     FILE_EXISTS = "Uh-oh! This file is already registered. Please choose another file."
 
 
+    MODEL_ID_TAKEN = "Uh-oh! This model id is already registered. Please choose another model id string."
+
     NAME_TAG_TAKEN = "Uh-oh! This name tag is already registered. Please choose another name tag string."
     NAME_TAG_TAKEN = "Uh-oh! This name tag is already registered. Please choose another name tag string."
     INVALID_TOKEN = (
     INVALID_TOKEN = (
         "Your session has expired or the token is invalid. Please sign in again."
         "Your session has expired or the token is invalid. Please sign in again."

+ 104 - 6
backend/main.py

@@ -19,8 +19,8 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
 from starlette.middleware.base import BaseHTTPMiddleware
 from starlette.middleware.base import BaseHTTPMiddleware
 from starlette.responses import StreamingResponse, Response
 from starlette.responses import StreamingResponse, Response
 
 
-from apps.ollama.main import app as ollama_app
-from apps.openai.main import app as openai_app
+from apps.ollama.main import app as ollama_app, get_all_models as get_ollama_models
+from apps.openai.main import app as openai_app, get_all_models as get_openai_models
 
 
 from apps.litellm.main import (
 from apps.litellm.main import (
     app as litellm_app,
     app as litellm_app,
@@ -36,10 +36,10 @@ from apps.web.main import app as webui_app
 
 
 import asyncio
 import asyncio
 from pydantic import BaseModel
 from pydantic import BaseModel
-from typing import List
+from typing import List, Optional
 
 
-
-from utils.utils import get_admin_user
+from apps.web.models.models import Models, ModelModel
+from utils.utils import get_admin_user, get_verified_user
 from apps.rag.utils import rag_messages
 from apps.rag.utils import rag_messages
 
 
 from config import (
 from config import (
@@ -53,6 +53,8 @@ from config import (
     FRONTEND_BUILD_DIR,
     FRONTEND_BUILD_DIR,
     CACHE_DIR,
     CACHE_DIR,
     STATIC_DIR,
     STATIC_DIR,
+    ENABLE_OPENAI_API,
+    ENABLE_OLLAMA_API,
     ENABLE_LITELLM,
     ENABLE_LITELLM,
     ENABLE_MODEL_FILTER,
     ENABLE_MODEL_FILTER,
     MODEL_FILTER_LIST,
     MODEL_FILTER_LIST,
@@ -110,11 +112,19 @@ app = FastAPI(
 )
 )
 
 
 app.state.config = AppConfig()
 app.state.config = AppConfig()
+
+app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
+app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
+
 app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 
 
+
 app.state.config.WEBHOOK_URL = WEBHOOK_URL
 app.state.config.WEBHOOK_URL = WEBHOOK_URL
 
 
+
+app.state.MODELS = {}
+
 origins = ["*"]
 origins = ["*"]
 
 
 
 
@@ -231,6 +241,11 @@ app.add_middleware(
 
 
 @app.middleware("http")
 @app.middleware("http")
 async def check_url(request: Request, call_next):
 async def check_url(request: Request, call_next):
+    if len(app.state.MODELS) == 0:
+        await get_all_models()
+    else:
+        pass
+
     start_time = int(time.time())
     start_time = int(time.time())
     response = await call_next(request)
     response = await call_next(request)
     process_time = int(time.time()) - start_time
     process_time = int(time.time()) - start_time
@@ -247,9 +262,11 @@ async def update_embedding_function(request: Request, call_next):
     return response
     return response
 
 
 
 
+# TODO: Deprecate LiteLLM
 app.mount("/litellm/api", litellm_app)
 app.mount("/litellm/api", litellm_app)
+
 app.mount("/ollama", ollama_app)
 app.mount("/ollama", ollama_app)
-app.mount("/openai/api", openai_app)
+app.mount("/openai", openai_app)
 
 
 app.mount("/images/api/v1", images_app)
 app.mount("/images/api/v1", images_app)
 app.mount("/audio/api/v1", audio_app)
 app.mount("/audio/api/v1", audio_app)
@@ -260,6 +277,87 @@ app.mount("/api/v1", webui_app)
 webui_app.state.EMBEDDING_FUNCTION = rag_app.state.EMBEDDING_FUNCTION
 webui_app.state.EMBEDDING_FUNCTION = rag_app.state.EMBEDDING_FUNCTION
 
 
 
 
+async def get_all_models():
+    openai_models = []
+    ollama_models = []
+
+    if app.state.config.ENABLE_OPENAI_API:
+        openai_models = await get_openai_models()
+
+        openai_models = openai_models["data"]
+
+    if app.state.config.ENABLE_OLLAMA_API:
+        ollama_models = await get_ollama_models()
+
+        ollama_models = [
+            {
+                "id": model["model"],
+                "name": model["name"],
+                "object": "model",
+                "created": int(time.time()),
+                "owned_by": "ollama",
+                "ollama": model,
+            }
+            for model in ollama_models["models"]
+        ]
+
+    models = openai_models + ollama_models
+    custom_models = Models.get_all_models()
+
+    for custom_model in custom_models:
+        if custom_model.base_model_id == None:
+            for model in models:
+                if (
+                    custom_model.id == model["id"]
+                    or custom_model.id == model["id"].split(":")[0]
+                ):
+                    model["name"] = custom_model.name
+                    model["info"] = custom_model.model_dump()
+        else:
+            owned_by = "openai"
+            for model in models:
+                if (
+                    custom_model.base_model_id == model["id"]
+                    or custom_model.base_model_id == model["id"].split(":")[0]
+                ):
+                    owned_by = model["owned_by"]
+                    break
+
+            models.append(
+                {
+                    "id": custom_model.id,
+                    "name": custom_model.name,
+                    "object": "model",
+                    "created": custom_model.created_at,
+                    "owned_by": owned_by,
+                    "info": custom_model.model_dump(),
+                    "preset": True,
+                }
+            )
+
+    app.state.MODELS = {model["id"]: model for model in models}
+
+    webui_app.state.MODELS = app.state.MODELS
+
+    return models
+
+
+@app.get("/api/models")
+async def get_models(user=Depends(get_verified_user)):
+    models = await get_all_models()
+    if app.state.config.ENABLE_MODEL_FILTER:
+        if user.role == "user":
+            models = list(
+                filter(
+                    lambda model: model["id"] in app.state.config.MODEL_FILTER_LIST,
+                    models,
+                )
+            )
+            return {"data": models}
+
+    return {"data": models}
+
+
 @app.get("/api/config")
 @app.get("/api/config")
 async def get_app_config():
 async def get_app_config():
     # Checking and Handling the Absence of 'ui' in CONFIG_DATA
     # Checking and Handling the Absence of 'ui' in CONFIG_DATA

+ 74 - 0
backend/utils/misc.py

@@ -1,5 +1,6 @@
 from pathlib import Path
 from pathlib import Path
 import hashlib
 import hashlib
+import json
 import re
 import re
 from datetime import timedelta
 from datetime import timedelta
 from typing import Optional
 from typing import Optional
@@ -110,3 +111,76 @@ def parse_duration(duration: str) -> Optional[timedelta]:
             total_duration += timedelta(weeks=number)
             total_duration += timedelta(weeks=number)
 
 
     return total_duration
     return total_duration
+
+
+def parse_ollama_modelfile(model_text):
+    parameters_meta = {
+        "mirostat": int,
+        "mirostat_eta": float,
+        "mirostat_tau": float,
+        "num_ctx": int,
+        "repeat_last_n": int,
+        "repeat_penalty": float,
+        "temperature": float,
+        "seed": int,
+        "stop": str,
+        "tfs_z": float,
+        "num_predict": int,
+        "top_k": int,
+        "top_p": float,
+    }
+
+    data = {"base_model_id": None, "params": {}}
+
+    # Parse base model
+    base_model_match = re.search(
+        r"^FROM\s+(\w+)", model_text, re.MULTILINE | re.IGNORECASE
+    )
+    if base_model_match:
+        data["base_model_id"] = base_model_match.group(1)
+
+    # Parse template
+    template_match = re.search(
+        r'TEMPLATE\s+"""(.+?)"""', model_text, re.DOTALL | re.IGNORECASE
+    )
+    if template_match:
+        data["params"] = {"template": template_match.group(1).strip()}
+
+    # Parse stops
+    stops = re.findall(r'PARAMETER stop "(.*?)"', model_text, re.IGNORECASE)
+    if stops:
+        data["params"]["stop"] = stops
+
+    # Parse other parameters from the provided list
+    for param, param_type in parameters_meta.items():
+        param_match = re.search(rf"PARAMETER {param} (.+)", model_text, re.IGNORECASE)
+        if param_match:
+            value = param_match.group(1)
+            if param_type == int:
+                value = int(value)
+            elif param_type == float:
+                value = float(value)
+            data["params"][param] = value
+
+    # Parse adapter
+    adapter_match = re.search(r"ADAPTER (.+)", model_text, re.IGNORECASE)
+    if adapter_match:
+        data["params"]["adapter"] = adapter_match.group(1)
+
+    # Parse system description
+    system_desc_match = re.search(
+        r'SYSTEM\s+"""(.+?)"""', model_text, re.DOTALL | re.IGNORECASE
+    )
+    if system_desc_match:
+        data["params"]["system"] = system_desc_match.group(1).strip()
+
+    # Parse messages
+    messages = []
+    message_matches = re.findall(r"MESSAGE (\w+) (.+)", model_text, re.IGNORECASE)
+    for role, content in message_matches:
+        messages.append({"role": role, "content": content})
+
+    if messages:
+        data["params"]["messages"] = messages
+
+    return data

+ 10 - 0
backend/utils/models.py

@@ -0,0 +1,10 @@
+from apps.web.models.models import Models, ModelModel, ModelForm, ModelResponse
+
+
+def get_model_id_from_custom_model_id(id: str):
+    model = Models.get_model_by_id(id)
+
+    if model:
+        return model.id
+    else:
+        return id

+ 123 - 0
src/lib/apis/index.ts

@@ -1,5 +1,54 @@
 import { WEBUI_BASE_URL } from '$lib/constants';
 import { WEBUI_BASE_URL } from '$lib/constants';
 
 
+export const getModels = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/models`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	let models = res?.data ?? [];
+
+	models = models
+		.filter((models) => models)
+		.sort((a, b) => {
+			// Compare case-insensitively
+			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 < b) return -1;
+			if (a > b) return 1;
+
+			return 0; // They are equal
+		});
+
+	console.log(models);
+	return models;
+};
+
 export const getBackendConfig = async () => {
 export const getBackendConfig = async () => {
 	let error = null;
 	let error = null;
 
 
@@ -196,3 +245,77 @@ export const updateWebhookUrl = async (token: string, url: string) => {
 
 
 	return res.url;
 	return res.url;
 };
 };
+
+export const getModelConfig = async (token: string): Promise<GlobalModelConfig> => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config/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;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.models;
+};
+
+export interface ModelConfig {
+	id: string;
+	name: string;
+	meta: ModelMeta;
+	base_model_id?: string;
+	params: ModelParams;
+}
+
+export interface ModelMeta {
+	description?: string;
+	capabilities?: object;
+}
+
+export interface ModelParams {}
+
+export type GlobalModelConfig = ModelConfig[];
+
+export const updateModelConfig = async (token: string, config: GlobalModelConfig) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config/models`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			models: config
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};

+ 2 - 1
src/lib/apis/litellm/index.ts

@@ -33,7 +33,8 @@ export const getLiteLLMModels = async (token: string = '') => {
 					id: model.id,
 					id: model.id,
 					name: model.name ?? model.id,
 					name: model.name ?? model.id,
 					external: true,
 					external: true,
-					source: 'LiteLLM'
+					source: 'LiteLLM',
+					custom_info: model.custom_info
 				}))
 				}))
 				.sort((a, b) => {
 				.sort((a, b) => {
 					return a.name.localeCompare(b.name);
 					return a.name.localeCompare(b.name);

+ 17 - 32
src/lib/apis/modelfiles/index.ts → src/lib/apis/models/index.ts

@@ -1,18 +1,16 @@
 import { WEBUI_API_BASE_URL } from '$lib/constants';
 import { WEBUI_API_BASE_URL } from '$lib/constants';
 
 
-export const createNewModelfile = async (token: string, modelfile: object) => {
+export const addNewModel = async (token: string, model: object) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/create`, {
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models/add`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
 			authorization: `Bearer ${token}`
 			authorization: `Bearer ${token}`
 		},
 		},
-		body: JSON.stringify({
-			modelfile: modelfile
-		})
+		body: JSON.stringify(model)
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
@@ -31,10 +29,10 @@ export const createNewModelfile = async (token: string, modelfile: object) => {
 	return res;
 	return res;
 };
 };
 
 
-export const getModelfiles = async (token: string = '') => {
+export const getModelInfos = async (token: string = '') => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/`, {
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models/`, {
 		method: 'GET',
 		method: 'GET',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
@@ -59,22 +57,19 @@ export const getModelfiles = async (token: string = '') => {
 		throw error;
 		throw error;
 	}
 	}
 
 
-	return res.map((modelfile) => modelfile.modelfile);
+	return res;
 };
 };
 
 
-export const getModelfileByTagName = async (token: string, tagName: string) => {
+export const getModelById = async (token: string, id: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/`, {
-		method: 'POST',
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models/${id}`, {
+		method: 'GET',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
 			authorization: `Bearer ${token}`
 			authorization: `Bearer ${token}`
-		},
-		body: JSON.stringify({
-			tag_name: tagName
-		})
+		}
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
@@ -94,27 +89,20 @@ export const getModelfileByTagName = async (token: string, tagName: string) => {
 		throw error;
 		throw error;
 	}
 	}
 
 
-	return res.modelfile;
+	return res;
 };
 };
 
 
-export const updateModelfileByTagName = async (
-	token: string,
-	tagName: string,
-	modelfile: object
-) => {
+export const updateModelById = async (token: string, id: string, model: object) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/update`, {
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models/${id}/update`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
 			authorization: `Bearer ${token}`
 			authorization: `Bearer ${token}`
 		},
 		},
-		body: JSON.stringify({
-			tag_name: tagName,
-			modelfile: modelfile
-		})
+		body: JSON.stringify(model)
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
@@ -137,19 +125,16 @@ export const updateModelfileByTagName = async (
 	return res;
 	return res;
 };
 };
 
 
-export const deleteModelfileByTagName = async (token: string, tagName: string) => {
+export const deleteModelById = async (token: string, id: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/delete`, {
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models/${id}/delete`, {
 		method: 'DELETE',
 		method: 'DELETE',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
 			authorization: `Bearer ${token}`
 			authorization: `Bearer ${token}`
-		},
-		body: JSON.stringify({
-			tag_name: tagName
-		})
+		}
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();

+ 6 - 1
src/lib/apis/openai/index.ts

@@ -230,7 +230,12 @@ export const getOpenAIModels = async (token: string = '') => {
 
 
 	return models
 	return models
 		? models
 		? models
-				.map((model) => ({ id: model.id, name: model.name ?? model.id, external: true }))
+				.map((model) => ({
+					id: model.id,
+					name: model.name ?? model.id,
+					external: true,
+					custom_info: model.custom_info
+				}))
 				.sort((a, b) => {
 				.sort((a, b) => {
 					return a.name.localeCompare(b.name);
 					return a.name.localeCompare(b.name);
 				})
 				})

+ 104 - 106
src/lib/components/chat/Chat.svelte

@@ -10,7 +10,7 @@
 		chatId,
 		chatId,
 		chats,
 		chats,
 		config,
 		config,
-		modelfiles,
+		type Model,
 		models,
 		models,
 		settings,
 		settings,
 		showSidebar,
 		showSidebar,
@@ -60,25 +60,7 @@
 	let showModelSelector = true;
 	let showModelSelector = true;
 
 
 	let selectedModels = [''];
 	let selectedModels = [''];
-	let atSelectedModel = '';
-
-	let selectedModelfile = null;
-	$: selectedModelfile =
-		selectedModels.length === 1 &&
-		$modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0]).length > 0
-			? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
-			: null;
-
-	let selectedModelfiles = {};
-	$: selectedModelfiles = selectedModels.reduce((a, tagName, i, arr) => {
-		const modelfile =
-			$modelfiles.filter((modelfile) => modelfile.tagName === tagName)?.at(0) ?? undefined;
-
-		return {
-			...a,
-			...(modelfile && { [tagName]: modelfile })
-		};
-	}, {});
+	let atSelectedModel: Model | undefined;
 
 
 	let chat = null;
 	let chat = null;
 	let tags = [];
 	let tags = [];
@@ -164,6 +146,7 @@
 
 
 		if ($page.url.searchParams.get('q')) {
 		if ($page.url.searchParams.get('q')) {
 			prompt = $page.url.searchParams.get('q') ?? '';
 			prompt = $page.url.searchParams.get('q') ?? '';
+
 			if (prompt) {
 			if (prompt) {
 				await tick();
 				await tick();
 				submitPrompt(prompt);
 				submitPrompt(prompt);
@@ -211,7 +194,7 @@
 				await settings.set({
 				await settings.set({
 					..._settings,
 					..._settings,
 					system: chatContent.system ?? _settings.system,
 					system: chatContent.system ?? _settings.system,
-					options: chatContent.options ?? _settings.options
+					params: chatContent.options ?? _settings.params
 				});
 				});
 				autoScroll = true;
 				autoScroll = true;
 				await tick();
 				await tick();
@@ -300,7 +283,7 @@
 						models: selectedModels,
 						models: selectedModels,
 						system: $settings.system ?? undefined,
 						system: $settings.system ?? undefined,
 						options: {
 						options: {
-							...($settings.options ?? {})
+							...($settings.params ?? {})
 						},
 						},
 						messages: messages,
 						messages: messages,
 						history: history,
 						history: history,
@@ -317,6 +300,7 @@
 
 
 			// Reset chat input textarea
 			// Reset chat input textarea
 			prompt = '';
 			prompt = '';
+			document.getElementById('chat-textarea').style.height = '';
 			files = [];
 			files = [];
 
 
 			// Send prompt
 			// Send prompt
@@ -328,75 +312,92 @@
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 
 
 		await Promise.all(
 		await Promise.all(
-			(modelId ? [modelId] : atSelectedModel !== '' ? [atSelectedModel.id] : selectedModels).map(
-				async (modelId) => {
-					console.log('modelId', modelId);
-					const model = $models.filter((m) => m.id === modelId).at(0);
-
-					if (model) {
-						// Create response message
-						let responseMessageId = uuidv4();
-						let responseMessage = {
-							parentId: parentId,
-							id: responseMessageId,
-							childrenIds: [],
-							role: 'assistant',
-							content: '',
-							model: model.id,
-							userContext: null,
-							timestamp: Math.floor(Date.now() / 1000) // Unix epoch
-						};
-
-						// Add message to history and Set currentId to messageId
-						history.messages[responseMessageId] = responseMessage;
-						history.currentId = responseMessageId;
-
-						// Append messageId to childrenIds of parent message
-						if (parentId !== null) {
-							history.messages[parentId].childrenIds = [
-								...history.messages[parentId].childrenIds,
-								responseMessageId
-							];
-						}
+			(modelId
+				? [modelId]
+				: atSelectedModel !== undefined
+				? [atSelectedModel.id]
+				: selectedModels
+			).map(async (modelId) => {
+				console.log('modelId', modelId);
+				const model = $models.filter((m) => m.id === modelId).at(0);
+
+				if (model) {
+					// If there are image files, check if model is vision capable
+					const hasImages = messages.some((message) =>
+						message.files?.some((file) => file.type === 'image')
+					);
 
 
-						await tick();
+					if (hasImages && !(model.info?.meta?.capabilities?.vision ?? true)) {
+						toast.error(
+							$i18n.t('Model {{modelName}} is not vision capable', {
+								modelName: model.name ?? model.id
+							})
+						);
+					}
 
 
-						let userContext = null;
-						if ($settings?.memory ?? false) {
-							if (userContext === null) {
-								const res = await queryMemory(localStorage.token, prompt).catch((error) => {
-									toast.error(error);
-									return null;
-								});
-
-								if (res) {
-									if (res.documents[0].length > 0) {
-										userContext = res.documents.reduce((acc, doc, index) => {
-											const createdAtTimestamp = res.metadatas[index][0].created_at;
-											const createdAtDate = new Date(createdAtTimestamp * 1000)
-												.toISOString()
-												.split('T')[0];
-											acc.push(`${index + 1}. [${createdAtDate}]. ${doc[0]}`);
-											return acc;
-										}, []);
-									}
+					// Create response message
+					let responseMessageId = uuidv4();
+					let responseMessage = {
+						parentId: parentId,
+						id: responseMessageId,
+						childrenIds: [],
+						role: 'assistant',
+						content: '',
+						model: model.id,
+						modelName: model.name ?? model.id,
+						userContext: null,
+						timestamp: Math.floor(Date.now() / 1000) // Unix epoch
+					};
+
+					// Add message to history and Set currentId to messageId
+					history.messages[responseMessageId] = responseMessage;
+					history.currentId = responseMessageId;
+
+					// Append messageId to childrenIds of parent message
+					if (parentId !== null) {
+						history.messages[parentId].childrenIds = [
+							...history.messages[parentId].childrenIds,
+							responseMessageId
+						];
+					}
 
 
-									console.log(userContext);
+					await tick();
+
+					let userContext = null;
+					if ($settings?.memory ?? false) {
+						if (userContext === null) {
+							const res = await queryMemory(localStorage.token, prompt).catch((error) => {
+								toast.error(error);
+								return null;
+							});
+
+							if (res) {
+								if (res.documents[0].length > 0) {
+									userContext = res.documents.reduce((acc, doc, index) => {
+										const createdAtTimestamp = res.metadatas[index][0].created_at;
+										const createdAtDate = new Date(createdAtTimestamp * 1000)
+											.toISOString()
+											.split('T')[0];
+										acc.push(`${index + 1}. [${createdAtDate}]. ${doc[0]}`);
+										return acc;
+									}, []);
 								}
 								}
+
+								console.log(userContext);
 							}
 							}
 						}
 						}
-						responseMessage.userContext = userContext;
+					}
+					responseMessage.userContext = userContext;
 
 
-						if (model?.external) {
-							await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
-						} else if (model) {
-							await sendPromptOllama(model, prompt, responseMessageId, _chatId);
-						}
-					} else {
-						toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
+					if (model?.owned_by === 'openai') {
+						await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
+					} else if (model) {
+						await sendPromptOllama(model, prompt, responseMessageId, _chatId);
 					}
 					}
+				} else {
+					toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
 				}
 				}
-			)
+			})
 		);
 		);
 
 
 		await chats.set(await getChatList(localStorage.token));
 		await chats.set(await getChatList(localStorage.token));
@@ -430,7 +431,7 @@
 				// Prepare the base message object
 				// Prepare the base message object
 				const baseMessage = {
 				const baseMessage = {
 					role: message.role,
 					role: message.role,
-					content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
+					content: message.content
 				};
 				};
 
 
 				// Extract and format image URLs if any exist
 				// Extract and format image URLs if any exist
@@ -442,7 +443,6 @@
 				if (imageUrls && imageUrls.length > 0 && message.role === 'user') {
 				if (imageUrls && imageUrls.length > 0 && message.role === 'user') {
 					baseMessage.images = imageUrls;
 					baseMessage.images = imageUrls;
 				}
 				}
-
 				return baseMessage;
 				return baseMessage;
 			});
 			});
 
 
@@ -473,13 +473,15 @@
 			model: model,
 			model: model,
 			messages: messagesBody,
 			messages: messagesBody,
 			options: {
 			options: {
-				...($settings.options ?? {}),
+				...($settings.params ?? {}),
 				stop:
 				stop:
-					$settings?.options?.stop ?? undefined
-						? $settings.options.stop.map((str) =>
+					$settings?.params?.stop ?? undefined
+						? $settings.params.stop.map((str) =>
 								decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
 								decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
 						  )
 						  )
-						: undefined
+						: undefined,
+				num_predict: $settings?.params?.max_tokens ?? undefined,
+				repeat_penalty: $settings?.params?.frequency_penalty ?? undefined
 			},
 			},
 			format: $settings.requestFormat ?? undefined,
 			format: $settings.requestFormat ?? undefined,
 			keep_alive: $settings.keepAlive ?? undefined,
 			keep_alive: $settings.keepAlive ?? undefined,
@@ -605,7 +607,8 @@
 				if ($settings.saveChatHistory ?? true) {
 				if ($settings.saveChatHistory ?? true) {
 					chat = await updateChatById(localStorage.token, _chatId, {
 					chat = await updateChatById(localStorage.token, _chatId, {
 						messages: messages,
 						messages: messages,
-						history: history
+						history: history,
+						models: selectedModels
 					});
 					});
 					await chats.set(await getChatList(localStorage.token));
 					await chats.set(await getChatList(localStorage.token));
 				}
 				}
@@ -716,18 +719,17 @@
 												: message?.raContent ?? message.content
 												: message?.raContent ?? message.content
 								  })
 								  })
 						})),
 						})),
-					seed: $settings?.options?.seed ?? undefined,
+					seed: $settings?.params?.seed ?? undefined,
 					stop:
 					stop:
-						$settings?.options?.stop ?? undefined
-							? $settings.options.stop.map((str) =>
+						$settings?.params?.stop ?? undefined
+							? $settings.params.stop.map((str) =>
 									decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
 									decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
 							  )
 							  )
 							: undefined,
 							: undefined,
-					temperature: $settings?.options?.temperature ?? undefined,
-					top_p: $settings?.options?.top_p ?? undefined,
-					num_ctx: $settings?.options?.num_ctx ?? undefined,
-					frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
-					max_tokens: $settings?.options?.num_predict ?? undefined,
+					temperature: $settings?.params?.temperature ?? undefined,
+					top_p: $settings?.params?.top_p ?? undefined,
+					frequency_penalty: $settings?.params?.frequency_penalty ?? undefined,
+					max_tokens: $settings?.params?.max_tokens ?? undefined,
 					docs: docs.length > 0 ? docs : undefined,
 					docs: docs.length > 0 ? docs : undefined,
 					citations: docs.length > 0
 					citations: docs.length > 0
 				},
 				},
@@ -797,6 +799,7 @@
 				if ($chatId == _chatId) {
 				if ($chatId == _chatId) {
 					if ($settings.saveChatHistory ?? true) {
 					if ($settings.saveChatHistory ?? true) {
 						chat = await updateChatById(localStorage.token, _chatId, {
 						chat = await updateChatById(localStorage.token, _chatId, {
+							models: selectedModels,
 							messages: messages,
 							messages: messages,
 							history: history
 							history: history
 						});
 						});
@@ -935,10 +938,8 @@
 					) + ' {{prompt}}',
 					) + ' {{prompt}}',
 				titleModelId,
 				titleModelId,
 				userPrompt,
 				userPrompt,
-				titleModel?.external ?? false
-					? titleModel?.source?.toLowerCase() === 'litellm'
-						? `${LITELLM_API_BASE_URL}/v1`
-						: `${OPENAI_API_BASE_URL}`
+				titleModel?.owned_by === 'openai' ?? false
+					? `${OPENAI_API_BASE_URL}`
 					: `${OLLAMA_API_BASE_URL}/v1`
 					: `${OLLAMA_API_BASE_URL}/v1`
 			);
 			);
 
 
@@ -1025,16 +1026,12 @@
 					<Messages
 					<Messages
 						chatId={$chatId}
 						chatId={$chatId}
 						{selectedModels}
 						{selectedModels}
-						{selectedModelfiles}
 						{processing}
 						{processing}
 						bind:history
 						bind:history
 						bind:messages
 						bind:messages
 						bind:autoScroll
 						bind:autoScroll
 						bind:prompt
 						bind:prompt
 						bottomPadding={files.length > 0}
 						bottomPadding={files.length > 0}
-						suggestionPrompts={chatIdProp
-							? []
-							: selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
 						{sendPrompt}
 						{sendPrompt}
 						{continueGeneration}
 						{continueGeneration}
 						{regenerateResponse}
 						{regenerateResponse}
@@ -1048,7 +1045,8 @@
 		bind:files
 		bind:files
 		bind:prompt
 		bind:prompt
 		bind:autoScroll
 		bind:autoScroll
-		bind:selectedModel={atSelectedModel}
+		bind:atSelectedModel
+		{selectedModels}
 		{messages}
 		{messages}
 		{submitPrompt}
 		{submitPrompt}
 		{stopResponse}
 		{stopResponse}

+ 60 - 13
src/lib/components/chat/MessageInput.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 	import { onMount, tick, getContext } from 'svelte';
 	import { onMount, tick, getContext } from 'svelte';
-	import { mobile, modelfiles, settings, showSidebar } from '$lib/stores';
+	import { type Model, mobile, settings, showSidebar, models } from '$lib/stores';
 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
 
 
 	import {
 	import {
@@ -27,7 +27,9 @@
 	export let stopResponse: Function;
 	export let stopResponse: Function;
 
 
 	export let autoScroll = true;
 	export let autoScroll = true;
-	export let selectedModel = '';
+
+	export let atSelectedModel: Model | undefined;
+	export let selectedModels: [''];
 
 
 	let chatTextAreaElement: HTMLTextAreaElement;
 	let chatTextAreaElement: HTMLTextAreaElement;
 	let filesInputElement;
 	let filesInputElement;
@@ -52,6 +54,11 @@
 
 
 	let speechRecognition;
 	let speechRecognition;
 
 
+	let visionCapableModels = [];
+	$: visionCapableModels = [...(atSelectedModel ? [atSelectedModel] : selectedModels)].filter(
+		(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.vision ?? true
+	);
+
 	$: if (prompt) {
 	$: if (prompt) {
 		if (chatTextAreaElement) {
 		if (chatTextAreaElement) {
 			chatTextAreaElement.style.height = '';
 			chatTextAreaElement.style.height = '';
@@ -358,6 +365,10 @@
 					inputFiles.forEach((file) => {
 					inputFiles.forEach((file) => {
 						console.log(file, file.name.split('.').at(-1));
 						console.log(file, file.name.split('.').at(-1));
 						if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
 						if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
+							if (visionCapableModels.length === 0) {
+								toast.error($i18n.t('Selected model(s) do not support image inputs'));
+								return;
+							}
 							let reader = new FileReader();
 							let reader = new FileReader();
 							reader.onload = (event) => {
 							reader.onload = (event) => {
 								files = [
 								files = [
@@ -429,8 +440,8 @@
 
 
 <div class="fixed bottom-0 {$showSidebar ? 'left-0 md:left-[260px]' : 'left-0'} right-0">
 <div class="fixed bottom-0 {$showSidebar ? 'left-0 md:left-[260px]' : 'left-0'} right-0">
 	<div class="w-full">
 	<div class="w-full">
-		<div class="px-2.5 md:px-16 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
-			<div class="flex flex-col max-w-5xl w-full">
+		<div class=" -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
+			<div class="flex flex-col max-w-6xl px-2.5 md:px-6 w-full">
 				<div class="relative">
 				<div class="relative">
 					{#if autoScroll === false && messages.length > 0}
 					{#if autoScroll === false && messages.length > 0}
 						<div class=" absolute -top-12 left-0 right-0 flex justify-center z-30">
 						<div class=" absolute -top-12 left-0 right-0 flex justify-center z-30">
@@ -494,12 +505,12 @@
 						bind:chatInputPlaceholder
 						bind:chatInputPlaceholder
 						{messages}
 						{messages}
 						on:select={(e) => {
 						on:select={(e) => {
-							selectedModel = e.detail;
+							atSelectedModel = e.detail;
 							chatTextAreaElement?.focus();
 							chatTextAreaElement?.focus();
 						}}
 						}}
 					/>
 					/>
 
 
-					{#if selectedModel !== ''}
+					{#if atSelectedModel !== undefined}
 						<div
 						<div
 							class="px-3 py-2.5 text-left w-full flex justify-between items-center absolute bottom-0 left-0 right-0 bg-gradient-to-t from-50% from-white dark:from-gray-900"
 							class="px-3 py-2.5 text-left w-full flex justify-between items-center absolute bottom-0 left-0 right-0 bg-gradient-to-t from-50% from-white dark:from-gray-900"
 						>
 						>
@@ -508,21 +519,21 @@
 									crossorigin="anonymous"
 									crossorigin="anonymous"
 									alt="model profile"
 									alt="model profile"
 									class="size-5 max-w-[28px] object-cover rounded-full"
 									class="size-5 max-w-[28px] object-cover rounded-full"
-									src={$modelfiles.find((modelfile) => modelfile.tagName === selectedModel.id)
-										?.imageUrl ??
+									src={$models.find((model) => model.id === atSelectedModel.id)?.info?.meta
+										?.profile_image_url ??
 										($i18n.language === 'dg-DG'
 										($i18n.language === 'dg-DG'
 											? `/doge.png`
 											? `/doge.png`
 											: `${WEBUI_BASE_URL}/static/favicon.png`)}
 											: `${WEBUI_BASE_URL}/static/favicon.png`)}
 								/>
 								/>
 								<div>
 								<div>
-									Talking to <span class=" font-medium">{selectedModel.name} </span>
+									Talking to <span class=" font-medium">{atSelectedModel.name}</span>
 								</div>
 								</div>
 							</div>
 							</div>
 							<div>
 							<div>
 								<button
 								<button
 									class="flex items-center"
 									class="flex items-center"
 									on:click={() => {
 									on:click={() => {
-										selectedModel = '';
+										atSelectedModel = undefined;
 									}}
 									}}
 								>
 								>
 									<XMark />
 									<XMark />
@@ -535,7 +546,7 @@
 		</div>
 		</div>
 
 
 		<div class="bg-white dark:bg-gray-900">
 		<div class="bg-white dark:bg-gray-900">
-			<div class="max-w-6xl px-2.5 md:px-16 mx-auto inset-x-0">
+			<div class="max-w-6xl px-2.5 md:px-6 mx-auto inset-x-0">
 				<div class=" pb-2">
 				<div class=" pb-2">
 					<input
 					<input
 						bind:this={filesInputElement}
 						bind:this={filesInputElement}
@@ -550,6 +561,12 @@
 									if (
 									if (
 										['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])
 										['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])
 									) {
 									) {
+										if (visionCapableModels.length === 0) {
+											toast.error($i18n.t('Selected model(s) do not support image inputs'));
+											inputFiles = null;
+											filesInputElement.value = '';
+											return;
+										}
 										let reader = new FileReader();
 										let reader = new FileReader();
 										reader.onload = (event) => {
 										reader.onload = (event) => {
 											files = [
 											files = [
@@ -589,6 +606,7 @@
 						dir={$settings?.chatDirection ?? 'LTR'}
 						dir={$settings?.chatDirection ?? 'LTR'}
 						class=" flex flex-col relative w-full rounded-3xl px-1.5 bg-gray-50 dark:bg-gray-850 dark:text-gray-100"
 						class=" flex flex-col relative w-full rounded-3xl px-1.5 bg-gray-50 dark:bg-gray-850 dark:text-gray-100"
 						on:submit|preventDefault={() => {
 						on:submit|preventDefault={() => {
+							// check if selectedModels support image input
 							submitPrompt(prompt, user);
 							submitPrompt(prompt, user);
 						}}
 						}}
 					>
 					>
@@ -597,7 +615,36 @@
 								{#each files as file, fileIdx}
 								{#each files as file, fileIdx}
 									<div class=" relative group">
 									<div class=" relative group">
 										{#if file.type === 'image'}
 										{#if file.type === 'image'}
-											<img src={file.url} alt="input" class=" h-16 w-16 rounded-xl object-cover" />
+											<div class="relative">
+												<img
+													src={file.url}
+													alt="input"
+													class=" h-16 w-16 rounded-xl object-cover"
+												/>
+												{#if atSelectedModel ? visionCapableModels.length === 0 : selectedModels.length !== visionCapableModels.length}
+													<Tooltip
+														className=" absolute top-1 left-1"
+														content={$i18n.t('{{ models }}', {
+															models: [...(atSelectedModel ? [atSelectedModel] : selectedModels)]
+																.filter((id) => !visionCapableModels.includes(id))
+																.join(', ')
+														})}
+													>
+														<svg
+															xmlns="http://www.w3.org/2000/svg"
+															viewBox="0 0 24 24"
+															fill="currentColor"
+															class="size-4 fill-yellow-300"
+														>
+															<path
+																fill-rule="evenodd"
+																d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
+																clip-rule="evenodd"
+															/>
+														</svg>
+													</Tooltip>
+												{/if}
+											</div>
 										{:else if file.type === 'doc'}
 										{:else if file.type === 'doc'}
 											<div
 											<div
 												class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
 												class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
@@ -883,7 +930,7 @@
 
 
 									if (e.key === 'Escape') {
 									if (e.key === 'Escape') {
 										console.log('Escape');
 										console.log('Escape');
-										selectedModel = '';
+										atSelectedModel = undefined;
 									}
 									}
 								}}
 								}}
 								rows="1"
 								rows="1"

+ 2 - 8
src/lib/components/chat/Messages.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { v4 as uuidv4 } from 'uuid';
 	import { v4 as uuidv4 } from 'uuid';
 
 
-	import { chats, config, modelfiles, settings, user as _user, mobile } from '$lib/stores';
+	import { chats, config, settings, user as _user, mobile } from '$lib/stores';
 	import { tick, getContext } from 'svelte';
 	import { tick, getContext } from 'svelte';
 
 
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
@@ -26,7 +26,6 @@
 
 
 	export let user = $_user;
 	export let user = $_user;
 	export let prompt;
 	export let prompt;
-	export let suggestionPrompts = [];
 	export let processing = '';
 	export let processing = '';
 	export let bottomPadding = false;
 	export let bottomPadding = false;
 	export let autoScroll;
 	export let autoScroll;
@@ -34,7 +33,6 @@
 	export let messages = [];
 	export let messages = [];
 
 
 	export let selectedModels;
 	export let selectedModels;
-	export let selectedModelfiles = [];
 
 
 	$: if (autoScroll && bottomPadding) {
 	$: if (autoScroll && bottomPadding) {
 		(async () => {
 		(async () => {
@@ -247,9 +245,7 @@
 <div class="h-full flex mb-16">
 <div class="h-full flex mb-16">
 	{#if messages.length == 0}
 	{#if messages.length == 0}
 		<Placeholder
 		<Placeholder
-			models={selectedModels}
-			modelfiles={selectedModelfiles}
-			{suggestionPrompts}
+			modelIds={selectedModels}
 			submitPrompt={async (p) => {
 			submitPrompt={async (p) => {
 				let text = p;
 				let text = p;
 
 
@@ -316,7 +312,6 @@
 								{#key message.id}
 								{#key message.id}
 									<ResponseMessage
 									<ResponseMessage
 										{message}
 										{message}
-										modelfiles={selectedModelfiles}
 										siblings={history.messages[message.parentId]?.childrenIds ?? []}
 										siblings={history.messages[message.parentId]?.childrenIds ?? []}
 										isLastMessage={messageIdx + 1 === messages.length}
 										isLastMessage={messageIdx + 1 === messages.length}
 										{readOnly}
 										{readOnly}
@@ -348,7 +343,6 @@
 										{chatId}
 										{chatId}
 										parentMessage={history.messages[message.parentId]}
 										parentMessage={history.messages[message.parentId]}
 										{messageIdx}
 										{messageIdx}
-										{selectedModelfiles}
 										{updateChatMessages}
 										{updateChatMessages}
 										{confirmEditResponseMessage}
 										{confirmEditResponseMessage}
 										{rateMessage}
 										{rateMessage}

+ 54 - 53
src/lib/components/chat/Messages/CodeBlock.svelte

@@ -4,7 +4,7 @@
 	import hljs from 'highlight.js';
 	import hljs from 'highlight.js';
 	import 'highlight.js/styles/github-dark.min.css';
 	import 'highlight.js/styles/github-dark.min.css';
 	import { loadPyodide } from 'pyodide';
 	import { loadPyodide } from 'pyodide';
-	import { tick } from 'svelte';
+	import { onMount, tick } from 'svelte';
 	import PyodideWorker from '$lib/workers/pyodide.worker?worker';
 	import PyodideWorker from '$lib/workers/pyodide.worker?worker';
 
 
 	export let id = '';
 	export let id = '';
@@ -12,6 +12,7 @@
 	export let lang = '';
 	export let lang = '';
 	export let code = '';
 	export let code = '';
 
 
+	let highlightedCode = null;
 	let executing = false;
 	let executing = false;
 
 
 	let stdout = null;
 	let stdout = null;
@@ -202,60 +203,60 @@ __builtins__.input = input`);
 		};
 		};
 	};
 	};
 
 
-	$: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
+	$: if (code) {
+		highlightedCode = hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value || code;
+	}
 </script>
 </script>
 
 
-{#if code}
-	<div class="mb-4" dir="ltr">
-		<div
-			class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
-		>
-			<div class="p-1">{@html lang}</div>
-
-			<div class="flex items-center">
-				{#if lang === 'python' || (lang === '' && checkPythonCode(code))}
-					{#if executing}
-						<div class="copy-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
-					{:else}
-						<button
-							class="copy-code-button bg-none border-none p-1"
-							on:click={() => {
-								executePython(code);
-							}}>Run</button
-						>
-					{/if}
+<div class="mb-4" dir="ltr">
+	<div
+		class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
+	>
+		<div class="p-1">{@html lang}</div>
+
+		<div class="flex items-center">
+			{#if lang === 'python' || (lang === '' && checkPythonCode(code))}
+				{#if executing}
+					<div class="copy-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
+				{:else}
+					<button
+						class="copy-code-button bg-none border-none p-1"
+						on:click={() => {
+							executePython(code);
+						}}>Run</button
+					>
 				{/if}
 				{/if}
-				<button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
-					>{copied ? 'Copied' : 'Copy Code'}</button
-				>
-			</div>
+			{/if}
+			<button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
+				>{copied ? 'Copied' : 'Copy Code'}</button
+			>
 		</div>
 		</div>
-
-		<pre
-			class=" hljs p-4 px-5 overflow-x-auto"
-			style="border-top-left-radius: 0px; border-top-right-radius: 0px; {(executing ||
-				stdout ||
-				stderr ||
-				result) &&
-				'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
-				class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
-			></pre>
-
-		<div
-			id="plt-canvas-{id}"
-			class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
-		/>
-
-		{#if executing}
-			<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
-				<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
-				<div class="text-sm">Running...</div>
-			</div>
-		{:else if stdout || stderr || result}
-			<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
-				<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
-				<div class="text-sm">{stdout || stderr || result}</div>
-			</div>
-		{/if}
 	</div>
 	</div>
-{/if}
+
+	<pre
+		class=" hljs p-4 px-5 overflow-x-auto"
+		style="border-top-left-radius: 0px; border-top-right-radius: 0px; {(executing ||
+			stdout ||
+			stderr ||
+			result) &&
+			'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
+			class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
+		></pre>
+
+	<div
+		id="plt-canvas-{id}"
+		class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
+	/>
+
+	{#if executing}
+		<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
+			<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
+			<div class="text-sm">Running...</div>
+		</div>
+	{:else if stdout || stderr || result}
+		<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
+			<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
+			<div class="text-sm">{stdout || stderr || result}</div>
+		</div>
+	{/if}
+</div>

+ 0 - 3
src/lib/components/chat/Messages/CompareMessages.svelte

@@ -13,8 +13,6 @@
 
 
 	export let parentMessage;
 	export let parentMessage;
 
 
-	export let selectedModelfiles;
-
 	export let updateChatMessages: Function;
 	export let updateChatMessages: Function;
 	export let confirmEditResponseMessage: Function;
 	export let confirmEditResponseMessage: Function;
 	export let rateMessage: Function;
 	export let rateMessage: Function;
@@ -130,7 +128,6 @@
 				>
 				>
 					<ResponseMessage
 					<ResponseMessage
 						message={groupedMessages[model].messages[groupedMessagesIdx[model]]}
 						message={groupedMessages[model].messages[groupedMessagesIdx[model]]}
-						modelfiles={selectedModelfiles}
 						siblings={groupedMessages[model].messages.map((m) => m.id)}
 						siblings={groupedMessages[model].messages.map((m) => m.id)}
 						isLastMessage={true}
 						isLastMessage={true}
 						{updateChatMessages}
 						{updateChatMessages}

+ 36 - 37
src/lib/components/chat/Messages/Placeholder.svelte

@@ -1,6 +1,6 @@
 <script lang="ts">
 <script lang="ts">
 	import { WEBUI_BASE_URL } from '$lib/constants';
 	import { WEBUI_BASE_URL } from '$lib/constants';
-	import { user } from '$lib/stores';
+	import { config, user, models as _models } from '$lib/stores';
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 
 
 	import { blur, fade } from 'svelte/transition';
 	import { blur, fade } from 'svelte/transition';
@@ -9,23 +9,20 @@
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
+	export let modelIds = [];
 	export let models = [];
 	export let models = [];
-	export let modelfiles = [];
 
 
 	export let submitPrompt;
 	export let submitPrompt;
-	export let suggestionPrompts;
 
 
 	let mounted = false;
 	let mounted = false;
-	let modelfile = null;
 	let selectedModelIdx = 0;
 	let selectedModelIdx = 0;
 
 
-	$: modelfile =
-		models[selectedModelIdx] in modelfiles ? modelfiles[models[selectedModelIdx]] : null;
-
-	$: if (models.length > 0) {
+	$: if (modelIds.length > 0) {
 		selectedModelIdx = models.length - 1;
 		selectedModelIdx = models.length - 1;
 	}
 	}
 
 
+	$: models = modelIds.map((id) => $_models.find((m) => m.id === id));
+
 	onMount(() => {
 	onMount(() => {
 		mounted = true;
 		mounted = true;
 	});
 	});
@@ -41,25 +38,14 @@
 							selectedModelIdx = modelIdx;
 							selectedModelIdx = modelIdx;
 						}}
 						}}
 					>
 					>
-						{#if model in modelfiles}
-							<img
-								crossorigin="anonymous"
-								src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
-								alt="modelfile"
-								class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
-								draggable="false"
-							/>
-						{:else}
-							<img
-								crossorigin="anonymous"
-								src={$i18n.language === 'dg-DG'
-									? `/doge.png`
-									: `${WEBUI_BASE_URL}/static/favicon.png`}
-								class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
-								alt="logo"
-								draggable="false"
-							/>
-						{/if}
+						<img
+							crossorigin="anonymous"
+							src={model?.info?.meta?.profile_image_url ??
+								($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
+							class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
+							alt="logo"
+							draggable="false"
+						/>
 					</button>
 					</button>
 				{/each}
 				{/each}
 			</div>
 			</div>
@@ -70,23 +56,32 @@
 		>
 		>
 			<div>
 			<div>
 				<div class=" capitalize line-clamp-1" in:fade={{ duration: 200 }}>
 				<div class=" capitalize line-clamp-1" in:fade={{ duration: 200 }}>
-					{#if modelfile}
-						{modelfile.title}
+					{#if models[selectedModelIdx]?.info}
+						{models[selectedModelIdx]?.info?.name}
 					{:else}
 					{:else}
 						{$i18n.t('Hello, {{name}}', { name: $user.name })}
 						{$i18n.t('Hello, {{name}}', { name: $user.name })}
 					{/if}
 					{/if}
 				</div>
 				</div>
 
 
 				<div in:fade={{ duration: 200, delay: 200 }}>
 				<div in:fade={{ duration: 200, delay: 200 }}>
-					{#if modelfile}
-						<div class="mt-0.5 text-base font-normal text-gray-500 dark:text-gray-400">
-							{modelfile.desc}
+					{#if models[selectedModelIdx]?.info}
+						<div class="mt-0.5 text-base font-normal text-gray-500 dark:text-gray-400 line-clamp-3">
+							{models[selectedModelIdx]?.info?.meta?.description}
 						</div>
 						</div>
-						{#if modelfile.user}
+						{#if models[selectedModelIdx]?.info?.meta?.user}
 							<div class="mt-0.5 text-sm font-normal text-gray-400 dark:text-gray-500">
 							<div class="mt-0.5 text-sm font-normal text-gray-400 dark:text-gray-500">
-								By <a href="https://openwebui.com/m/{modelfile.user.username}"
-									>{modelfile.user.name ? modelfile.user.name : `@${modelfile.user.username}`}</a
-								>
+								By
+								{#if models[selectedModelIdx]?.info?.meta?.user.community}
+									<a
+										href="https://openwebui.com/m/{models[selectedModelIdx]?.info?.meta?.user
+											.username}"
+										>{models[selectedModelIdx]?.info?.meta?.user.name
+											? models[selectedModelIdx]?.info?.meta?.user.name
+											: `@${models[selectedModelIdx]?.info?.meta?.user.username}`}</a
+									>
+								{:else}
+									{models[selectedModelIdx]?.info?.meta?.user.name}
+								{/if}
 							</div>
 							</div>
 						{/if}
 						{/if}
 					{:else}
 					{:else}
@@ -99,7 +94,11 @@
 		</div>
 		</div>
 
 
 		<div class=" w-full" in:fade={{ duration: 200, delay: 300 }}>
 		<div class=" w-full" in:fade={{ duration: 200, delay: 300 }}>
-			<Suggestions {suggestionPrompts} {submitPrompt} />
+			<Suggestions
+				suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
+					$config.default_prompt_suggestions}
+				{submitPrompt}
+			/>
 		</div>
 		</div>
 	</div>
 	</div>
 {/key}
 {/key}

+ 8 - 10
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -14,7 +14,7 @@
 
 
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();
 
 
-	import { config, settings } from '$lib/stores';
+	import { config, models, settings } from '$lib/stores';
 	import { synthesizeOpenAISpeech } from '$lib/apis/audio';
 	import { synthesizeOpenAISpeech } from '$lib/apis/audio';
 	import { imageGenerations } from '$lib/apis/images';
 	import { imageGenerations } from '$lib/apis/images';
 	import {
 	import {
@@ -34,7 +34,6 @@
 	import RateComment from './RateComment.svelte';
 	import RateComment from './RateComment.svelte';
 	import CitationsModal from '$lib/components/chat/Messages/CitationsModal.svelte';
 	import CitationsModal from '$lib/components/chat/Messages/CitationsModal.svelte';
 
 
-	export let modelfiles = [];
 	export let message;
 	export let message;
 	export let siblings;
 	export let siblings;
 
 
@@ -52,6 +51,9 @@
 	export let continueGeneration: Function;
 	export let continueGeneration: Function;
 	export let regenerateResponse: Function;
 	export let regenerateResponse: Function;
 
 
+	let model = null;
+	$: model = $models.find((m) => m.id === message.model);
+
 	let edit = false;
 	let edit = false;
 	let editedContent = '';
 	let editedContent = '';
 	let editTextAreaElement: HTMLTextAreaElement;
 	let editTextAreaElement: HTMLTextAreaElement;
@@ -338,17 +340,13 @@
 		dir={$settings.chatDirection}
 		dir={$settings.chatDirection}
 	>
 	>
 		<ProfileImage
 		<ProfileImage
-			src={modelfiles[message.model]?.imageUrl ??
+			src={model?.info?.meta?.profile_image_url ??
 				($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
 				($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
 		/>
 		/>
 
 
 		<div class="w-full overflow-hidden pl-1">
 		<div class="w-full overflow-hidden pl-1">
 			<Name>
 			<Name>
-				{#if message.model in modelfiles}
-					{modelfiles[message.model]?.title}
-				{:else}
-					{message.model ? ` ${message.model}` : ''}
-				{/if}
+				{model?.name ?? message.model}
 
 
 				{#if message.timestamp}
 				{#if message.timestamp}
 					<span
 					<span
@@ -442,8 +440,8 @@
 									{#if token.type === 'code'}
 									{#if token.type === 'code'}
 										<CodeBlock
 										<CodeBlock
 											id={`${message.id}-${tokenIdx}`}
 											id={`${message.id}-${tokenIdx}`}
-											lang={token.lang}
-											code={revertSanitizedResponseContent(token.text)}
+											lang={token?.lang ?? ''}
+											code={revertSanitizedResponseContent(token?.text ?? '')}
 										/>
 										/>
 									{:else}
 									{:else}
 										{@html marked.parse(token.raw, {
 										{@html marked.parse(token.raw, {

+ 4 - 9
src/lib/components/chat/Messages/UserMessage.svelte

@@ -4,7 +4,7 @@
 	import { tick, createEventDispatcher, getContext } from 'svelte';
 	import { tick, createEventDispatcher, getContext } from 'svelte';
 	import Name from './Name.svelte';
 	import Name from './Name.svelte';
 	import ProfileImage from './ProfileImage.svelte';
 	import ProfileImage from './ProfileImage.svelte';
-	import { modelfiles, settings } from '$lib/stores';
+	import { models, settings } from '$lib/stores';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 
 
 	import { user as _user } from '$lib/stores';
 	import { user as _user } from '$lib/stores';
@@ -60,8 +60,7 @@
 	{#if !($settings?.chatBubble ?? true)}
 	{#if !($settings?.chatBubble ?? true)}
 		<ProfileImage
 		<ProfileImage
 			src={message.user
 			src={message.user
-				? $modelfiles.find((modelfile) => modelfile.tagName === message.user)?.imageUrl ??
-				  '/user.png'
+				? $models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ?? '/user.png'
 				: user?.profile_image_url ?? '/user.png'}
 				: user?.profile_image_url ?? '/user.png'}
 		/>
 		/>
 	{/if}
 	{/if}
@@ -70,12 +69,8 @@
 			<div>
 			<div>
 				<Name>
 				<Name>
 					{#if message.user}
 					{#if message.user}
-						{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
-							{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
-						{:else}
-							{$i18n.t('You')}
-							<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
-						{/if}
+						{$i18n.t('You')}
+						<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
 					{:else if $settings.showUsername || $_user.name !== user.name}
 					{:else if $settings.showUsername || $_user.name !== user.name}
 						{user.name}
 						{user.name}
 					{:else}
 					{:else}

Разлика између датотеке није приказан због своје велике величине
+ 2 - 0
src/lib/components/chat/Messages/test.json


+ 5 - 7
src/lib/components/chat/ModelSelector.svelte

@@ -45,13 +45,11 @@
 				<div class="mr-1 max-w-full">
 				<div class="mr-1 max-w-full">
 					<Selector
 					<Selector
 						placeholder={$i18n.t('Select a model')}
 						placeholder={$i18n.t('Select a model')}
-						items={$models
-							.filter((model) => model.name !== 'hr')
-							.map((model) => ({
-								value: model.id,
-								label: model.name,
-								info: model
-							}))}
+						items={$models.map((model) => ({
+							value: model.id,
+							label: model.name,
+							model: model
+						}))}
 						bind:value={selectedModel}
 						bind:value={selectedModel}
 					/>
 					/>
 				</div>
 				</div>

+ 43 - 18
src/lib/components/chat/ModelSelector/Selector.svelte

@@ -12,7 +12,9 @@
 
 
 	import { user, MODEL_DOWNLOAD_POOL, models, mobile } from '$lib/stores';
 	import { user, MODEL_DOWNLOAD_POOL, models, mobile } from '$lib/stores';
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
-	import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
+	import { capitalizeFirstLetter, sanitizeResponseContent, splitStream } from '$lib/utils';
+	import { getModels } from '$lib/apis';
+
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
@@ -23,7 +25,12 @@
 	export let searchEnabled = true;
 	export let searchEnabled = true;
 	export let searchPlaceholder = $i18n.t('Search a model');
 	export let searchPlaceholder = $i18n.t('Search a model');
 
 
-	export let items = [{ value: 'mango', label: 'Mango' }];
+	export let items: {
+		label: string;
+		value: string;
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
+		[key: string]: any;
+	} = [];
 
 
 	export let className = 'w-[30rem]';
 	export let className = 'w-[30rem]';
 
 
@@ -239,19 +246,37 @@
 						}}
 						}}
 					>
 					>
 						<div class="flex items-center gap-2">
 						<div class="flex items-center gap-2">
-							<div class="line-clamp-1">
-								{item.label}
-
-								<span class=" text-xs font-medium text-gray-600 dark:text-gray-400"
-									>{item.info?.details?.parameter_size ?? ''}</span
-								>
+							<div class="flex items-center">
+								<div class="line-clamp-1">
+									{item.label}
+								</div>
+								{#if item.model.owned_by === 'ollama' && (item.model.ollama?.details?.parameter_size ?? '') !== ''}
+									<div class="flex ml-1 items-center">
+										<Tooltip
+											content={`${
+												item.model.ollama?.details?.quantization_level
+													? item.model.ollama?.details?.quantization_level + ' '
+													: ''
+											}${
+												item.model.ollama?.size
+													? `(${(item.model.ollama?.size / 1024 ** 3).toFixed(1)}GB)`
+													: ''
+											}`}
+											className="self-end"
+										>
+											<span class=" text-xs font-medium text-gray-600 dark:text-gray-400"
+												>{item.model.ollama?.details?.parameter_size ?? ''}</span
+											>
+										</Tooltip>
+									</div>
+								{/if}
 							</div>
 							</div>
 
 
 							<!-- {JSON.stringify(item.info)} -->
 							<!-- {JSON.stringify(item.info)} -->
 
 
-							{#if item.info.external}
-								<Tooltip content={item.info?.source ?? 'External'}>
-									<div class=" mr-2">
+							{#if item.model.owned_by === 'openai'}
+								<Tooltip content={`${'External'}`}>
+									<div class="">
 										<svg
 										<svg
 											xmlns="http://www.w3.org/2000/svg"
 											xmlns="http://www.w3.org/2000/svg"
 											viewBox="0 0 16 16"
 											viewBox="0 0 16 16"
@@ -271,15 +296,15 @@
 										</svg>
 										</svg>
 									</div>
 									</div>
 								</Tooltip>
 								</Tooltip>
-							{:else}
+							{/if}
+
+							{#if item.model?.info?.meta?.description}
 								<Tooltip
 								<Tooltip
-									content={`${
-										item.info?.details?.quantization_level
-											? item.info?.details?.quantization_level + ' '
-											: ''
-									}${item.info.size ? `(${(item.info.size / 1024 ** 3).toFixed(1)}GB)` : ''}`}
+									content={`${sanitizeResponseContent(
+										item.model?.info?.meta?.description
+									).replaceAll('\n', '<br>')}`}
 								>
 								>
-									<div class=" mr-2">
+									<div class="">
 										<svg
 										<svg
 											xmlns="http://www.w3.org/2000/svg"
 											xmlns="http://www.w3.org/2000/svg"
 											fill="none"
 											fill="none"

+ 0 - 155
src/lib/components/chat/Settings/Advanced.svelte

@@ -1,155 +0,0 @@
-<script lang="ts">
-	import { createEventDispatcher, onMount, getContext } from 'svelte';
-	import AdvancedParams from './Advanced/AdvancedParams.svelte';
-
-	const i18n = getContext('i18n');
-	const dispatch = createEventDispatcher();
-
-	export let saveSettings: Function;
-
-	// Advanced
-	let requestFormat = '';
-	let keepAlive = null;
-
-	let options = {
-		// Advanced
-		seed: 0,
-		temperature: '',
-		repeat_penalty: '',
-		repeat_last_n: '',
-		mirostat: '',
-		mirostat_eta: '',
-		mirostat_tau: '',
-		top_k: '',
-		top_p: '',
-		stop: '',
-		tfs_z: '',
-		num_ctx: '',
-		num_predict: ''
-	};
-
-	const toggleRequestFormat = async () => {
-		if (requestFormat === '') {
-			requestFormat = 'json';
-		} else {
-			requestFormat = '';
-		}
-
-		saveSettings({ requestFormat: requestFormat !== '' ? requestFormat : undefined });
-	};
-
-	onMount(() => {
-		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
-
-		requestFormat = settings.requestFormat ?? '';
-		keepAlive = settings.keepAlive ?? null;
-
-		options.seed = settings.seed ?? 0;
-		options.temperature = settings.temperature ?? '';
-		options.repeat_penalty = settings.repeat_penalty ?? '';
-		options.top_k = settings.top_k ?? '';
-		options.top_p = settings.top_p ?? '';
-		options.num_ctx = settings.num_ctx ?? '';
-		options = { ...options, ...settings.options };
-		options.stop = (settings?.options?.stop ?? []).join(',');
-	});
-</script>
-
-<div class="flex flex-col h-full justify-between text-sm">
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
-		<div class=" text-sm font-medium">{$i18n.t('Parameters')}</div>
-
-		<AdvancedParams bind:options />
-		<hr class=" dark:border-gray-700" />
-
-		<div class=" py-1 w-full justify-between">
-			<div class="flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
-
-				<button
-					class="p-1 px-3 text-xs flex rounded transition"
-					type="button"
-					on:click={() => {
-						keepAlive = keepAlive === null ? '5m' : null;
-					}}
-				>
-					{#if keepAlive === null}
-						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
-					{:else}
-						<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
-					{/if}
-				</button>
-			</div>
-
-			{#if keepAlive !== null}
-				<div class="flex mt-1 space-x-2">
-					<input
-						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-						type="text"
-						placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
-						bind:value={keepAlive}
-					/>
-				</div>
-			{/if}
-		</div>
-
-		<div>
-			<div class=" py-1 flex w-full justify-between">
-				<div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</div>
-
-				<button
-					class="p-1 px-3 text-xs flex rounded transition"
-					on:click={() => {
-						toggleRequestFormat();
-					}}
-				>
-					{#if requestFormat === ''}
-						<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
-					{:else if requestFormat === 'json'}
-						<!-- <svg
-                            xmlns="http://www.w3.org/2000/svg"
-                            viewBox="0 0 20 20"
-                            fill="currentColor"
-                            class="w-4 h-4 self-center"
-                        >
-                            <path
-                                d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
-                            />
-                        </svg> -->
-						<span class="ml-2 self-center">{$i18n.t('JSON')}</span>
-					{/if}
-				</button>
-			</div>
-		</div>
-	</div>
-
-	<div class="flex justify-end pt-3 text-sm font-medium">
-		<button
-			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
-			on:click={() => {
-				saveSettings({
-					options: {
-						seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined,
-						stop: options.stop !== '' ? options.stop.split(',').filter((e) => e) : undefined,
-						temperature: options.temperature !== '' ? options.temperature : undefined,
-						repeat_penalty: options.repeat_penalty !== '' ? options.repeat_penalty : undefined,
-						repeat_last_n: options.repeat_last_n !== '' ? options.repeat_last_n : undefined,
-						mirostat: options.mirostat !== '' ? options.mirostat : undefined,
-						mirostat_eta: options.mirostat_eta !== '' ? options.mirostat_eta : undefined,
-						mirostat_tau: options.mirostat_tau !== '' ? options.mirostat_tau : undefined,
-						top_k: options.top_k !== '' ? options.top_k : undefined,
-						top_p: options.top_p !== '' ? options.top_p : undefined,
-						tfs_z: options.tfs_z !== '' ? options.tfs_z : undefined,
-						num_ctx: options.num_ctx !== '' ? options.num_ctx : undefined,
-						num_predict: options.num_predict !== '' ? options.num_predict : undefined
-					},
-					keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
-				});
-
-				dispatch('save');
-			}}
-		>
-			{$i18n.t('Save')}
-		</button>
-	</div>
-</div>

+ 177 - 97
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte

@@ -1,14 +1,16 @@
 <script lang="ts">
 <script lang="ts">
-	import { getContext } from 'svelte';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
-	export let options = {
+	export let params = {
 		// Advanced
 		// Advanced
 		seed: 0,
 		seed: 0,
-		stop: '',
+		stop: null,
 		temperature: '',
 		temperature: '',
-		repeat_penalty: '',
+		frequency_penalty: '',
 		repeat_last_n: '',
 		repeat_last_n: '',
 		mirostat: '',
 		mirostat: '',
 		mirostat_eta: '',
 		mirostat_eta: '',
@@ -17,40 +19,86 @@
 		top_p: '',
 		top_p: '',
 		tfs_z: '',
 		tfs_z: '',
 		num_ctx: '',
 		num_ctx: '',
-		num_predict: ''
+		max_tokens: '',
+		template: null
 	};
 	};
+
+	let customFieldName = '';
+	let customFieldValue = '';
+
+	$: if (params) {
+		dispatch('change', params);
+	}
 </script>
 </script>
 
 
-<div class=" space-y-3 text-xs">
-	<div>
-		<div class=" py-0.5 flex w-full justify-between">
-			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
-			<div class=" flex-1 self-center">
-				<input
-					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-					type="number"
-					placeholder="Enter Seed"
-					bind:value={options.seed}
-					autocomplete="off"
-					min="0"
-				/>
-			</div>
+<div class=" space-y-1 text-xs">
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Seed')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition"
+				type="button"
+				on:click={() => {
+					params.seed = (params?.seed ?? null) === null ? 0 : null;
+				}}
+			>
+				{#if (params?.seed ?? null) === null}
+					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+				{:else}
+					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+				{/if}
+			</button>
 		</div>
 		</div>
-	</div>
 
 
-	<div>
-		<div class=" py-0.5 flex w-full justify-between">
-			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
-			<div class=" flex-1 self-center">
-				<input
-					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-					type="text"
-					placeholder={$i18n.t('Enter stop sequence')}
-					bind:value={options.stop}
-					autocomplete="off"
-				/>
+		{#if (params?.seed ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+						type="number"
+						placeholder="Enter Seed"
+						bind:value={params.seed}
+						autocomplete="off"
+						min="0"
+					/>
+				</div>
 			</div>
 			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Stop Sequence')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition"
+				type="button"
+				on:click={() => {
+					params.stop = (params?.stop ?? null) === null ? '' : null;
+				}}
+			>
+				{#if (params?.stop ?? null) === null}
+					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+				{:else}
+					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+				{/if}
+			</button>
 		</div>
 		</div>
+
+		{#if (params?.stop ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+						type="text"
+						placeholder={$i18n.t('Enter stop sequence')}
+						bind:value={params.stop}
+						autocomplete="off"
+					/>
+				</div>
+			</div>
+		{/if}
 	</div>
 	</div>
 
 
 	<div class=" py-0.5 w-full justify-between">
 	<div class=" py-0.5 w-full justify-between">
@@ -61,10 +109,10 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.temperature = options.temperature === '' ? 0.8 : '';
+					params.temperature = (params?.temperature ?? '') === '' ? 0.8 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.temperature === ''}
+				{#if (params?.temperature ?? '') === ''}
 					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 				{:else}
 				{:else}
 					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
 					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
@@ -72,7 +120,7 @@
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.temperature !== ''}
+		{#if (params?.temperature ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -81,13 +129,13 @@
 						min="0"
 						min="0"
 						max="1"
 						max="1"
 						step="0.05"
 						step="0.05"
-						bind:value={options.temperature}
+						bind:value={params.temperature}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.temperature}
+						bind:value={params.temperature}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -107,18 +155,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.mirostat = options.mirostat === '' ? 0 : '';
+					params.mirostat = (params?.mirostat ?? '') === '' ? 0 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.mirostat === ''}
+				{#if (params?.mirostat ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.mirostat !== ''}
+		{#if (params?.mirostat ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -127,13 +175,13 @@
 						min="0"
 						min="0"
 						max="2"
 						max="2"
 						step="1"
 						step="1"
-						bind:value={options.mirostat}
+						bind:value={params.mirostat}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.mirostat}
+						bind:value={params.mirostat}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -153,18 +201,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.mirostat_eta = options.mirostat_eta === '' ? 0.1 : '';
+					params.mirostat_eta = (params?.mirostat_eta ?? '') === '' ? 0.1 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.mirostat_eta === ''}
+				{#if (params?.mirostat_eta ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.mirostat_eta !== ''}
+		{#if (params?.mirostat_eta ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -173,13 +221,13 @@
 						min="0"
 						min="0"
 						max="1"
 						max="1"
 						step="0.05"
 						step="0.05"
-						bind:value={options.mirostat_eta}
+						bind:value={params.mirostat_eta}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.mirostat_eta}
+						bind:value={params.mirostat_eta}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -199,10 +247,10 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.mirostat_tau = options.mirostat_tau === '' ? 5.0 : '';
+					params.mirostat_tau = (params?.mirostat_tau ?? '') === '' ? 5.0 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.mirostat_tau === ''}
+				{#if (params?.mirostat_tau ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
 					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
@@ -210,7 +258,7 @@
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.mirostat_tau !== ''}
+		{#if (params?.mirostat_tau ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -219,13 +267,13 @@
 						min="0"
 						min="0"
 						max="10"
 						max="10"
 						step="0.5"
 						step="0.5"
-						bind:value={options.mirostat_tau}
+						bind:value={params.mirostat_tau}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.mirostat_tau}
+						bind:value={params.mirostat_tau}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -245,18 +293,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.top_k = options.top_k === '' ? 40 : '';
+					params.top_k = (params?.top_k ?? '') === '' ? 40 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.top_k === ''}
+				{#if (params?.top_k ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.top_k !== ''}
+		{#if (params?.top_k ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -265,13 +313,13 @@
 						min="0"
 						min="0"
 						max="100"
 						max="100"
 						step="0.5"
 						step="0.5"
-						bind:value={options.top_k}
+						bind:value={params.top_k}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.top_k}
+						bind:value={params.top_k}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -291,18 +339,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.top_p = options.top_p === '' ? 0.9 : '';
+					params.top_p = (params?.top_p ?? '') === '' ? 0.9 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.top_p === ''}
+				{#if (params?.top_p ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.top_p !== ''}
+		{#if (params?.top_p ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -311,13 +359,13 @@
 						min="0"
 						min="0"
 						max="1"
 						max="1"
 						step="0.05"
 						step="0.05"
-						bind:value={options.top_p}
+						bind:value={params.top_p}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.top_p}
+						bind:value={params.top_p}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -331,24 +379,24 @@
 
 
 	<div class=" py-0.5 w-full justify-between">
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">{$i18n.t('Repeat Penalty')}</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Frequencey Penalty')}</div>
 
 
 			<button
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.repeat_penalty = options.repeat_penalty === '' ? 1.1 : '';
+					params.frequency_penalty = (params?.frequency_penalty ?? '') === '' ? 1.1 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.repeat_penalty === ''}
+				{#if (params?.frequency_penalty ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.repeat_penalty !== ''}
+		{#if (params?.frequency_penalty ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -357,13 +405,13 @@
 						min="0"
 						min="0"
 						max="2"
 						max="2"
 						step="0.05"
 						step="0.05"
-						bind:value={options.repeat_penalty}
+						bind:value={params.frequency_penalty}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.repeat_penalty}
+						bind:value={params.frequency_penalty}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -383,18 +431,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.repeat_last_n = options.repeat_last_n === '' ? 64 : '';
+					params.repeat_last_n = (params?.repeat_last_n ?? '') === '' ? 64 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.repeat_last_n === ''}
+				{#if (params?.repeat_last_n ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.repeat_last_n !== ''}
+		{#if (params?.repeat_last_n ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -403,13 +451,13 @@
 						min="-1"
 						min="-1"
 						max="128"
 						max="128"
 						step="1"
 						step="1"
-						bind:value={options.repeat_last_n}
+						bind:value={params.repeat_last_n}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.repeat_last_n}
+						bind:value={params.repeat_last_n}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="-1"
 						min="-1"
@@ -429,18 +477,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.tfs_z = options.tfs_z === '' ? 1 : '';
+					params.tfs_z = (params?.tfs_z ?? '') === '' ? 1 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.tfs_z === ''}
+				{#if (params?.tfs_z ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.tfs_z !== ''}
+		{#if (params?.tfs_z ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -449,13 +497,13 @@
 						min="0"
 						min="0"
 						max="2"
 						max="2"
 						step="0.05"
 						step="0.05"
-						bind:value={options.tfs_z}
+						bind:value={params.tfs_z}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.tfs_z}
+						bind:value={params.tfs_z}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -475,18 +523,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.num_ctx = options.num_ctx === '' ? 2048 : '';
+					params.num_ctx = (params?.num_ctx ?? '') === '' ? 2048 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.num_ctx === ''}
+				{#if (params?.num_ctx ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.num_ctx !== ''}
+		{#if (params?.num_ctx ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -495,13 +543,13 @@
 						min="-1"
 						min="-1"
 						max="10240000"
 						max="10240000"
 						step="1"
 						step="1"
-						bind:value={options.num_ctx}
+						bind:value={params.num_ctx}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div class="">
 				<div class="">
 					<input
 					<input
-						bind:value={options.num_ctx}
+						bind:value={params.num_ctx}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="-1"
 						min="-1"
@@ -513,24 +561,24 @@
 	</div>
 	</div>
 	<div class=" py-0.5 w-full justify-between">
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens')}</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens (num_predict)')}</div>
 
 
 			<button
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.num_predict = options.num_predict === '' ? 128 : '';
+					params.max_tokens = (params?.max_tokens ?? '') === '' ? 128 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.num_predict === ''}
+				{#if (params?.max_tokens ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.num_predict !== ''}
+		{#if (params?.max_tokens ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -539,13 +587,13 @@
 						min="-2"
 						min="-2"
 						max="16000"
 						max="16000"
 						step="1"
 						step="1"
-						bind:value={options.num_predict}
+						bind:value={params.max_tokens}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div class="">
 				<div class="">
 					<input
 					<input
-						bind:value={options.num_predict}
+						bind:value={params.max_tokens}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="-2"
 						min="-2"
@@ -556,4 +604,36 @@
 			</div>
 			</div>
 		{/if}
 		{/if}
 	</div>
 	</div>
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Template')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition"
+				type="button"
+				on:click={() => {
+					params.template = (params?.template ?? null) === null ? '' : null;
+				}}
+			>
+				{#if (params?.template ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.template ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<textarea
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
+						placeholder="Write your model template content here"
+						rows="4"
+						bind:value={params.template}
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
 </div>
 </div>

+ 28 - 27
src/lib/components/chat/Settings/General.svelte

@@ -41,21 +41,21 @@
 	let requestFormat = '';
 	let requestFormat = '';
 	let keepAlive = null;
 	let keepAlive = null;
 
 
-	let options = {
+	let params = {
 		// Advanced
 		// Advanced
 		seed: 0,
 		seed: 0,
 		temperature: '',
 		temperature: '',
-		repeat_penalty: '',
+		frequency_penalty: '',
 		repeat_last_n: '',
 		repeat_last_n: '',
 		mirostat: '',
 		mirostat: '',
 		mirostat_eta: '',
 		mirostat_eta: '',
 		mirostat_tau: '',
 		mirostat_tau: '',
 		top_k: '',
 		top_k: '',
 		top_p: '',
 		top_p: '',
-		stop: '',
+		stop: null,
 		tfs_z: '',
 		tfs_z: '',
 		num_ctx: '',
 		num_ctx: '',
-		num_predict: ''
+		max_tokens: ''
 	};
 	};
 
 
 	const toggleRequestFormat = async () => {
 	const toggleRequestFormat = async () => {
@@ -80,14 +80,14 @@
 		requestFormat = settings.requestFormat ?? '';
 		requestFormat = settings.requestFormat ?? '';
 		keepAlive = settings.keepAlive ?? null;
 		keepAlive = settings.keepAlive ?? null;
 
 
-		options.seed = settings.seed ?? 0;
-		options.temperature = settings.temperature ?? '';
-		options.repeat_penalty = settings.repeat_penalty ?? '';
-		options.top_k = settings.top_k ?? '';
-		options.top_p = settings.top_p ?? '';
-		options.num_ctx = settings.num_ctx ?? '';
-		options = { ...options, ...settings.options };
-		options.stop = (settings?.options?.stop ?? []).join(',');
+		params.seed = settings.seed ?? 0;
+		params.temperature = settings.temperature ?? '';
+		params.frequency_penalty = settings.frequency_penalty ?? '';
+		params.top_k = settings.top_k ?? '';
+		params.top_p = settings.top_p ?? '';
+		params.num_ctx = settings.num_ctx ?? '';
+		params = { ...params, ...settings.params };
+		params.stop = settings?.params?.stop ? (settings?.params?.stop ?? []).join(',') : null;
 	});
 	});
 
 
 	const applyTheme = (_theme: string) => {
 	const applyTheme = (_theme: string) => {
@@ -228,7 +228,7 @@
 			</div>
 			</div>
 
 
 			{#if showAdvanced}
 			{#if showAdvanced}
-				<AdvancedParams bind:options />
+				<AdvancedParams bind:params />
 				<hr class=" dark:border-gray-700" />
 				<hr class=" dark:border-gray-700" />
 
 
 				<div class=" py-1 w-full justify-between">
 				<div class=" py-1 w-full justify-between">
@@ -300,20 +300,21 @@
 			on:click={() => {
 			on:click={() => {
 				saveSettings({
 				saveSettings({
 					system: system !== '' ? system : undefined,
 					system: system !== '' ? system : undefined,
-					options: {
-						seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined,
-						stop: options.stop !== '' ? options.stop.split(',').filter((e) => e) : undefined,
-						temperature: options.temperature !== '' ? options.temperature : undefined,
-						repeat_penalty: options.repeat_penalty !== '' ? options.repeat_penalty : undefined,
-						repeat_last_n: options.repeat_last_n !== '' ? options.repeat_last_n : undefined,
-						mirostat: options.mirostat !== '' ? options.mirostat : undefined,
-						mirostat_eta: options.mirostat_eta !== '' ? options.mirostat_eta : undefined,
-						mirostat_tau: options.mirostat_tau !== '' ? options.mirostat_tau : undefined,
-						top_k: options.top_k !== '' ? options.top_k : undefined,
-						top_p: options.top_p !== '' ? options.top_p : undefined,
-						tfs_z: options.tfs_z !== '' ? options.tfs_z : undefined,
-						num_ctx: options.num_ctx !== '' ? options.num_ctx : undefined,
-						num_predict: options.num_predict !== '' ? options.num_predict : undefined
+					params: {
+						seed: (params.seed !== 0 ? params.seed : undefined) ?? undefined,
+						stop: params.stop !== null ? params.stop.split(',').filter((e) => e) : undefined,
+						temperature: params.temperature !== '' ? params.temperature : undefined,
+						frequency_penalty:
+							params.frequency_penalty !== '' ? params.frequency_penalty : undefined,
+						repeat_last_n: params.repeat_last_n !== '' ? params.repeat_last_n : undefined,
+						mirostat: params.mirostat !== '' ? params.mirostat : undefined,
+						mirostat_eta: params.mirostat_eta !== '' ? params.mirostat_eta : undefined,
+						mirostat_tau: params.mirostat_tau !== '' ? params.mirostat_tau : undefined,
+						top_k: params.top_k !== '' ? params.top_k : undefined,
+						top_p: params.top_p !== '' ? params.top_p : undefined,
+						tfs_z: params.tfs_z !== '' ? params.tfs_z : undefined,
+						num_ctx: params.num_ctx !== '' ? params.num_ctx : undefined,
+						max_tokens: params.max_tokens !== '' ? params.max_tokens : undefined
 					},
 					},
 					keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
 					keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
 				});
 				});

+ 37 - 287
src/lib/components/chat/Settings/Models.svelte

@@ -1,5 +1,4 @@
 <script lang="ts">
 <script lang="ts">
-	import queue from 'async/queue';
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 
 
 	import {
 	import {
@@ -12,32 +11,19 @@
 		cancelOllamaRequest,
 		cancelOllamaRequest,
 		uploadModel
 		uploadModel
 	} from '$lib/apis/ollama';
 	} from '$lib/apis/ollama';
+
 	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
 	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
-	import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user } from '$lib/stores';
+	import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config } from '$lib/stores';
 	import { splitStream } from '$lib/utils';
 	import { splitStream } from '$lib/utils';
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
-	import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm';
+
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
 	export let getModels: Function;
 	export let getModels: Function;
 
 
-	let showLiteLLM = false;
-	let showLiteLLMParams = false;
 	let modelUploadInputElement: HTMLInputElement;
 	let modelUploadInputElement: HTMLInputElement;
-	let liteLLMModelInfo = [];
-
-	let liteLLMModel = '';
-	let liteLLMModelName = '';
-	let liteLLMAPIBase = '';
-	let liteLLMAPIKey = '';
-	let liteLLMRPM = '';
-	let liteLLMMaxTokens = '';
-
-	let deleteLiteLLMModelName = '';
-
-	$: liteLLMModelName = liteLLMModel;
 
 
 	// Models
 	// Models
 
 
@@ -439,71 +425,22 @@
 		}
 		}
 	};
 	};
 
 
-	const addLiteLLMModelHandler = async () => {
-		if (!liteLLMModelInfo.find((info) => info.model_name === liteLLMModelName)) {
-			const res = await addLiteLLMModel(localStorage.token, {
-				name: liteLLMModelName,
-				model: liteLLMModel,
-				api_base: liteLLMAPIBase,
-				api_key: liteLLMAPIKey,
-				rpm: liteLLMRPM,
-				max_tokens: liteLLMMaxTokens
-			}).catch((error) => {
-				toast.error(error);
-				return null;
-			});
-
-			if (res) {
-				if (res.message) {
-					toast.success(res.message);
-				}
-			}
-		} else {
-			toast.error($i18n.t(`Model {{modelName}} already exists.`, { modelName: liteLLMModelName }));
-		}
-
-		liteLLMModelName = '';
-		liteLLMModel = '';
-		liteLLMAPIBase = '';
-		liteLLMAPIKey = '';
-		liteLLMRPM = '';
-		liteLLMMaxTokens = '';
-
-		liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
-		models.set(await getModels());
-	};
-
-	const deleteLiteLLMModelHandler = async () => {
-		const res = await deleteLiteLLMModel(localStorage.token, deleteLiteLLMModelName).catch(
-			(error) => {
-				toast.error(error);
-				return null;
-			}
-		);
-
-		if (res) {
-			if (res.message) {
-				toast.success(res.message);
-			}
-		}
-
-		deleteLiteLLMModelName = '';
-		liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
-		models.set(await getModels());
-	};
-
 	onMount(async () => {
 	onMount(async () => {
-		OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
-			toast.error(error);
-			return [];
-		});
-
-		if (OLLAMA_URLS.length > 0) {
-			selectedOllamaUrlIdx = 0;
-		}
+		await Promise.all([
+			(async () => {
+				OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
+					toast.error(error);
+					return [];
+				});
 
 
-		ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
-		liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
+				if (OLLAMA_URLS.length > 0) {
+					selectedOllamaUrlIdx = 0;
+				}
+			})(),
+			(async () => {
+				ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
+			})()
+		]);
 	});
 	});
 </script>
 </script>
 
 
@@ -587,24 +524,28 @@
 											viewBox="0 0 24 24"
 											viewBox="0 0 24 24"
 											fill="currentColor"
 											fill="currentColor"
 											xmlns="http://www.w3.org/2000/svg"
 											xmlns="http://www.w3.org/2000/svg"
-											><style>
+										>
+											<style>
 												.spinner_ajPY {
 												.spinner_ajPY {
 													transform-origin: center;
 													transform-origin: center;
 													animation: spinner_AtaB 0.75s infinite linear;
 													animation: spinner_AtaB 0.75s infinite linear;
 												}
 												}
+
 												@keyframes spinner_AtaB {
 												@keyframes spinner_AtaB {
 													100% {
 													100% {
 														transform: rotate(360deg);
 														transform: rotate(360deg);
 													}
 													}
 												}
 												}
-											</style><path
+											</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"
 												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"
 												opacity=".25"
-											/><path
+											/>
+											<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"
 												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"
 												class="spinner_ajPY"
-											/></svg
-										>
+											/>
+										</svg>
 									</div>
 									</div>
 								{:else}
 								{:else}
 									<svg
 									<svg
@@ -833,24 +774,28 @@
 													viewBox="0 0 24 24"
 													viewBox="0 0 24 24"
 													fill="currentColor"
 													fill="currentColor"
 													xmlns="http://www.w3.org/2000/svg"
 													xmlns="http://www.w3.org/2000/svg"
-													><style>
+												>
+													<style>
 														.spinner_ajPY {
 														.spinner_ajPY {
 															transform-origin: center;
 															transform-origin: center;
 															animation: spinner_AtaB 0.75s infinite linear;
 															animation: spinner_AtaB 0.75s infinite linear;
 														}
 														}
+
 														@keyframes spinner_AtaB {
 														@keyframes spinner_AtaB {
 															100% {
 															100% {
 																transform: rotate(360deg);
 																transform: rotate(360deg);
 															}
 															}
 														}
 														}
-													</style><path
+													</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"
 														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"
 														opacity=".25"
-													/><path
+													/>
+													<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"
 														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"
 														class="spinner_ajPY"
-													/></svg
-												>
+													/>
+												</svg>
 											</div>
 											</div>
 										{:else}
 										{:else}
 											<svg
 											<svg
@@ -929,203 +874,8 @@
 					{/if}
 					{/if}
 				</div>
 				</div>
 			</div>
 			</div>
-			<hr class=" dark:border-gray-700 my-2" />
+		{:else}
+			<div>Ollama Not Detected</div>
 		{/if}
 		{/if}
-
-		<div class=" space-y-3">
-			<div class="mt-2 space-y-3 pr-1.5">
-				<div>
-					<div class="mb-2">
-						<div class="flex justify-between items-center text-xs">
-							<div class=" text-sm font-medium">{$i18n.t('Manage LiteLLM Models')}</div>
-							<button
-								class=" text-xs font-medium text-gray-500"
-								type="button"
-								on:click={() => {
-									showLiteLLM = !showLiteLLM;
-								}}>{showLiteLLM ? $i18n.t('Hide') : $i18n.t('Show')}</button
-							>
-						</div>
-					</div>
-
-					{#if showLiteLLM}
-						<div>
-							<div class="flex justify-between items-center text-xs">
-								<div class=" text-sm font-medium">{$i18n.t('Add a model')}</div>
-								<button
-									class=" text-xs font-medium text-gray-500"
-									type="button"
-									on:click={() => {
-										showLiteLLMParams = !showLiteLLMParams;
-									}}
-									>{showLiteLLMParams
-										? $i18n.t('Hide Additional Params')
-										: $i18n.t('Show Additional Params')}</button
-								>
-							</div>
-						</div>
-
-						<div class="my-2 space-y-2">
-							<div class="flex w-full mb-1.5">
-								<div class="flex-1 mr-2">
-									<input
-										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-										placeholder={$i18n.t('Enter LiteLLM Model (litellm_params.model)')}
-										bind:value={liteLLMModel}
-										autocomplete="off"
-									/>
-								</div>
-
-								<button
-									class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
-									on:click={() => {
-										addLiteLLMModelHandler();
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 16 16"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
-										/>
-									</svg>
-								</button>
-							</div>
-
-							{#if showLiteLLMParams}
-								<div>
-									<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Model Name')}</div>
-									<div class="flex w-full">
-										<div class="flex-1">
-											<input
-												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-												placeholder="Enter Model Name (model_name)"
-												bind:value={liteLLMModelName}
-												autocomplete="off"
-											/>
-										</div>
-									</div>
-								</div>
-
-								<div>
-									<div class=" mb-1.5 text-sm font-medium">{$i18n.t('API Base URL')}</div>
-									<div class="flex w-full">
-										<div class="flex-1">
-											<input
-												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-												placeholder={$i18n.t(
-													'Enter LiteLLM API Base URL (litellm_params.api_base)'
-												)}
-												bind:value={liteLLMAPIBase}
-												autocomplete="off"
-											/>
-										</div>
-									</div>
-								</div>
-
-								<div>
-									<div class=" mb-1.5 text-sm font-medium">{$i18n.t('API Key')}</div>
-									<div class="flex w-full">
-										<div class="flex-1">
-											<input
-												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-												placeholder={$i18n.t('Enter LiteLLM API Key (litellm_params.api_key)')}
-												bind:value={liteLLMAPIKey}
-												autocomplete="off"
-											/>
-										</div>
-									</div>
-								</div>
-
-								<div>
-									<div class="mb-1.5 text-sm font-medium">{$i18n.t('API RPM')}</div>
-									<div class="flex w-full">
-										<div class="flex-1">
-											<input
-												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-												placeholder={$i18n.t('Enter LiteLLM API RPM (litellm_params.rpm)')}
-												bind:value={liteLLMRPM}
-												autocomplete="off"
-											/>
-										</div>
-									</div>
-								</div>
-
-								<div>
-									<div class="mb-1.5 text-sm font-medium">{$i18n.t('Max Tokens')}</div>
-									<div class="flex w-full">
-										<div class="flex-1">
-											<input
-												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-												placeholder={$i18n.t('Enter Max Tokens (litellm_params.max_tokens)')}
-												bind:value={liteLLMMaxTokens}
-												type="number"
-												min="1"
-												autocomplete="off"
-											/>
-										</div>
-									</div>
-								</div>
-							{/if}
-						</div>
-
-						<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
-							{$i18n.t('Not sure what to add?')}
-							<a
-								class=" text-gray-300 font-medium underline"
-								href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
-								target="_blank"
-							>
-								{$i18n.t('Click here for help.')}
-							</a>
-						</div>
-
-						<div>
-							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Delete a model')}</div>
-							<div class="flex w-full">
-								<div class="flex-1 mr-2">
-									<select
-										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-										bind:value={deleteLiteLLMModelName}
-										placeholder={$i18n.t('Select a model')}
-									>
-										{#if !deleteLiteLLMModelName}
-											<option value="" disabled selected>{$i18n.t('Select a model')}</option>
-										{/if}
-										{#each liteLLMModelInfo as model}
-											<option value={model.model_name} class="bg-gray-100 dark:bg-gray-700"
-												>{model.model_name}</option
-											>
-										{/each}
-									</select>
-								</div>
-								<button
-									class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
-									on:click={() => {
-										deleteLiteLLMModelHandler();
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 16 16"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</button>
-							</div>
-						</div>
-					{/if}
-				</div>
-			</div>
-		</div>
 	</div>
 	</div>
 </div>
 </div>

+ 1 - 1
src/lib/components/chat/SettingsModal.svelte

@@ -3,7 +3,7 @@
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 	import { models, settings, user } from '$lib/stores';
 	import { models, settings, user } from '$lib/stores';
 
 
-	import { getModels as _getModels } from '$lib/utils';
+	import { getModels as _getModels } from '$lib/apis';
 
 
 	import Modal from '../common/Modal.svelte';
 	import Modal from '../common/Modal.svelte';
 	import Account from './Settings/Account.svelte';
 	import Account from './Settings/Account.svelte';

+ 2 - 4
src/lib/components/chat/ShareChatModal.svelte

@@ -1,9 +1,9 @@
 <script lang="ts">
 <script lang="ts">
 	import { getContext, onMount } from 'svelte';
 	import { getContext, onMount } from 'svelte';
+	import { models } from '$lib/stores';
 
 
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 	import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
 	import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
-	import { modelfiles } from '$lib/stores';
 	import { copyToClipboard } from '$lib/utils';
 	import { copyToClipboard } from '$lib/utils';
 
 
 	import Modal from '../common/Modal.svelte';
 	import Modal from '../common/Modal.svelte';
@@ -43,9 +43,7 @@
 					tab.postMessage(
 					tab.postMessage(
 						JSON.stringify({
 						JSON.stringify({
 							chat: _chat,
 							chat: _chat,
-							modelfiles: $modelfiles.filter((modelfile) =>
-								_chat.models.includes(modelfile.tagName)
-							)
+							models: $models.filter((m) => _chat.models.includes(m.id))
 						}),
 						}),
 						'*'
 						'*'
 					);
 					);

+ 1 - 0
src/lib/components/common/Checkbox.svelte

@@ -29,6 +29,7 @@
 			dispatch('change', _state);
 			dispatch('change', _state);
 		}
 		}
 	}}
 	}}
+	type="button"
 >
 >
 	<div class="top-0 left-0 absolute w-full flex justify-center">
 	<div class="top-0 left-0 absolute w-full flex justify-center">
 		{#if _state === 'checked'}
 		{#if _state === 'checked'}

+ 2 - 1
src/lib/components/common/Tooltip.svelte

@@ -5,6 +5,7 @@
 	export let placement = 'top';
 	export let placement = 'top';
 	export let content = `I'm a tooltip!`;
 	export let content = `I'm a tooltip!`;
 	export let touch = true;
 	export let touch = true;
+	export let className = 'flex';
 
 
 	let tooltipElement;
 	let tooltipElement;
 	let tooltipInstance;
 	let tooltipInstance;
@@ -29,6 +30,6 @@
 	});
 	});
 </script>
 </script>
 
 
-<div bind:this={tooltipElement} aria-label={content} class="flex">
+<div bind:this={tooltipElement} aria-label={content} class={className}>
 	<slot />
 	<slot />
 </div>
 </div>

+ 0 - 1
src/lib/components/layout/Navbar.svelte

@@ -6,7 +6,6 @@
 		WEBUI_NAME,
 		WEBUI_NAME,
 		chatId,
 		chatId,
 		mobile,
 		mobile,
-		modelfiles,
 		settings,
 		settings,
 		showArchivedChats,
 		showArchivedChats,
 		showSettings,
 		showSettings,

+ 73 - 92
src/lib/components/workspace/Modelfiles.svelte → src/lib/components/workspace/Models.svelte

@@ -5,67 +5,82 @@
 
 
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 
 
-	import { WEBUI_NAME, modelfiles, settings, user } from '$lib/stores';
-	import { createModel, deleteModel } from '$lib/apis/ollama';
-	import {
-		createNewModelfile,
-		deleteModelfileByTagName,
-		getModelfiles
-	} from '$lib/apis/modelfiles';
+	import { WEBUI_NAME, modelfiles, models, settings, user } from '$lib/stores';
+	import { addNewModel, deleteModelById, getModelInfos } from '$lib/apis/models';
+
+	import { deleteModel } from '$lib/apis/ollama';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 
 
+	import { getModels } from '$lib/apis';
+
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
 	let localModelfiles = [];
 	let localModelfiles = [];
-	let importFiles;
-	let modelfilesImportInputElement: HTMLInputElement;
-	const deleteModelHandler = async (tagName) => {
-		let success = null;
 
 
-		success = await deleteModel(localStorage.token, tagName).catch((err) => {
-			toast.error(err);
+	let importFiles;
+	let modelsImportInputElement: HTMLInputElement;
+
+	const deleteModelHandler = async (model) => {
+		console.log(model.info);
+		if (!model?.info) {
+			toast.error(
+				$i18n.t('{{ owner }}: You cannot delete a base model', {
+					owner: model.owned_by.toUpperCase()
+				})
+			);
 			return null;
 			return null;
-		});
+		}
 
 
-		if (success) {
-			toast.success($i18n.t(`Deleted {{tagName}}`, { tagName }));
+		const res = await deleteModelById(localStorage.token, model.id);
+
+		if (res) {
+			toast.success($i18n.t(`Deleted {{name}}`, { name: model.id }));
 		}
 		}
 
 
-		return success;
+		await models.set(await getModels(localStorage.token));
 	};
 	};
 
 
-	const deleteModelfile = async (tagName) => {
-		await deleteModelHandler(tagName);
-		await deleteModelfileByTagName(localStorage.token, tagName);
-		await modelfiles.set(await getModelfiles(localStorage.token));
+	const cloneModelHandler = async (model) => {
+		if ((model?.info?.base_model_id ?? null) === null) {
+			toast.error($i18n.t('You cannot clone a base model'));
+			return;
+		} else {
+			sessionStorage.model = JSON.stringify({
+				...model,
+				id: `${model.id}-clone`,
+				name: `${model.name} (Clone)`
+			});
+			goto('/workspace/models/create');
+		}
 	};
 	};
 
 
-	const shareModelfile = async (modelfile) => {
+	const shareModelHandler = async (model) => {
 		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
 		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
 
 
 		const url = 'https://openwebui.com';
 		const url = 'https://openwebui.com';
 
 
-		const tab = await window.open(`${url}/modelfiles/create`, '_blank');
+		const tab = await window.open(`${url}/models/create`, '_blank');
 		window.addEventListener(
 		window.addEventListener(
 			'message',
 			'message',
 			(event) => {
 			(event) => {
 				if (event.origin !== url) return;
 				if (event.origin !== url) return;
 				if (event.data === 'loaded') {
 				if (event.data === 'loaded') {
-					tab.postMessage(JSON.stringify(modelfile), '*');
+					tab.postMessage(JSON.stringify(model), '*');
 				}
 				}
 			},
 			},
 			false
 			false
 		);
 		);
 	};
 	};
 
 
-	const saveModelfiles = async (modelfiles) => {
-		let blob = new Blob([JSON.stringify(modelfiles)], {
+	const downloadModels = async (models) => {
+		let blob = new Blob([JSON.stringify(models)], {
 			type: 'application/json'
 			type: 'application/json'
 		});
 		});
-		saveAs(blob, `modelfiles-export-${Date.now()}.json`);
+		saveAs(blob, `models-export-${Date.now()}.json`);
 	};
 	};
 
 
 	onMount(() => {
 	onMount(() => {
+		// Legacy code to sync localModelfiles with models
 		localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
 		localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
 
 
 		if (localModelfiles) {
 		if (localModelfiles) {
@@ -76,13 +91,13 @@
 
 
 <svelte:head>
 <svelte:head>
 	<title>
 	<title>
-		{$i18n.t('Modelfiles')} | {$WEBUI_NAME}
+		{$i18n.t('Models')} | {$WEBUI_NAME}
 	</title>
 	</title>
 </svelte:head>
 </svelte:head>
 
 
-<div class=" text-lg font-semibold mb-3">{$i18n.t('Modelfiles')}</div>
+<div class=" text-lg font-semibold mb-3">{$i18n.t('Models')}</div>
 
 
-<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2" href="/workspace/modelfiles/create">
+<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2" href="/workspace/models/create">
 	<div class=" self-center w-10">
 	<div class=" self-center w-10">
 		<div
 		<div
 			class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
 			class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
@@ -98,26 +113,26 @@
 	</div>
 	</div>
 
 
 	<div class=" self-center">
 	<div class=" self-center">
-		<div class=" font-bold">{$i18n.t('Create a modelfile')}</div>
-		<div class=" text-sm">{$i18n.t('Customize Ollama models for a specific purpose')}</div>
+		<div class=" font-bold">{$i18n.t('Create a model')}</div>
+		<div class=" text-sm">{$i18n.t('Customize models for a specific purpose')}</div>
 	</div>
 	</div>
 </a>
 </a>
 
 
 <hr class=" dark:border-gray-850" />
 <hr class=" dark:border-gray-850" />
 
 
 <div class=" my-2 mb-5">
 <div class=" my-2 mb-5">
-	{#each $modelfiles as modelfile}
+	{#each $models as model}
 		<div
 		<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"
 			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
 		>
 		>
 			<a
 			<a
 				class=" flex flex-1 space-x-4 cursor-pointer w-full"
 				class=" flex flex-1 space-x-4 cursor-pointer w-full"
-				href={`/?models=${encodeURIComponent(modelfile.tagName)}`}
+				href={`/?models=${encodeURIComponent(model.id)}`}
 			>
 			>
 				<div class=" self-center w-10">
 				<div class=" self-center w-10">
 					<div class=" rounded-full bg-stone-700">
 					<div class=" rounded-full bg-stone-700">
 						<img
 						<img
-							src={modelfile.imageUrl ?? '/user.png'}
+							src={model?.info?.meta?.profile_image_url ?? '/favicon.png'}
 							alt="modelfile profile"
 							alt="modelfile profile"
 							class=" rounded-full w-full h-auto object-cover"
 							class=" rounded-full w-full h-auto object-cover"
 						/>
 						/>
@@ -125,9 +140,9 @@
 				</div>
 				</div>
 
 
 				<div class=" flex-1 self-center">
 				<div class=" flex-1 self-center">
-					<div class=" font-bold capitalize">{modelfile.title}</div>
+					<div class=" font-bold line-clamp-1">{model.name}</div>
 					<div class=" text-sm overflow-hidden text-ellipsis line-clamp-1">
 					<div class=" text-sm overflow-hidden text-ellipsis line-clamp-1">
-						{modelfile.desc}
+						{!!model?.info?.meta?.description ? model?.info?.meta?.description : model.id}
 					</div>
 					</div>
 				</div>
 				</div>
 			</a>
 			</a>
@@ -135,7 +150,7 @@
 				<a
 				<a
 					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 					type="button"
 					type="button"
-					href={`/workspace/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
+					href={`/workspace/models/edit?id=${encodeURIComponent(model.id)}`}
 				>
 				>
 					<svg
 					<svg
 						xmlns="http://www.w3.org/2000/svg"
 						xmlns="http://www.w3.org/2000/svg"
@@ -157,9 +172,7 @@
 					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 					type="button"
 					type="button"
 					on:click={() => {
 					on:click={() => {
-						// console.log(modelfile);
-						sessionStorage.modelfile = JSON.stringify(modelfile);
-						goto('/workspace/modelfiles/create');
+						cloneModelHandler(model);
 					}}
 					}}
 				>
 				>
 					<svg
 					<svg
@@ -182,7 +195,7 @@
 					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 					type="button"
 					type="button"
 					on:click={() => {
 					on:click={() => {
-						shareModelfile(modelfile);
+						shareModelHandler(model);
 					}}
 					}}
 				>
 				>
 					<svg
 					<svg
@@ -205,7 +218,7 @@
 					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 					type="button"
 					type="button"
 					on:click={() => {
 					on:click={() => {
-						deleteModelfile(modelfile.tagName);
+						deleteModelHandler(model);
 					}}
 					}}
 				>
 				>
 					<svg
 					<svg
@@ -231,8 +244,8 @@
 <div class=" flex justify-end w-full mb-3">
 <div class=" flex justify-end w-full mb-3">
 	<div class="flex space-x-1">
 	<div class="flex space-x-1">
 		<input
 		<input
-			id="modelfiles-import-input"
-			bind:this={modelfilesImportInputElement}
+			id="models-import-input"
+			bind:this={modelsImportInputElement}
 			bind:files={importFiles}
 			bind:files={importFiles}
 			type="file"
 			type="file"
 			accept=".json"
 			accept=".json"
@@ -242,16 +255,18 @@
 
 
 				let reader = new FileReader();
 				let reader = new FileReader();
 				reader.onload = async (event) => {
 				reader.onload = async (event) => {
-					let savedModelfiles = JSON.parse(event.target.result);
-					console.log(savedModelfiles);
+					let savedModels = JSON.parse(event.target.result);
+					console.log(savedModels);
 
 
-					for (const modelfile of savedModelfiles) {
-						await createNewModelfile(localStorage.token, modelfile).catch((error) => {
-							return null;
-						});
+					for (const model of savedModels) {
+						if (model?.info ?? false) {
+							await addNewModel(localStorage.token, model.info).catch((error) => {
+								return null;
+							});
+						}
 					}
 					}
 
 
-					await modelfiles.set(await getModelfiles(localStorage.token));
+					await models.set(await getModels(localStorage.token));
 				};
 				};
 
 
 				reader.readAsText(importFiles[0]);
 				reader.readAsText(importFiles[0]);
@@ -261,10 +276,10 @@
 		<button
 		<button
 			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
 			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
 			on:click={() => {
 			on:click={() => {
-				modelfilesImportInputElement.click();
+				modelsImportInputElement.click();
 			}}
 			}}
 		>
 		>
-			<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</div>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Import Models')}</div>
 
 
 			<div class=" self-center">
 			<div class=" self-center">
 				<svg
 				<svg
@@ -285,10 +300,10 @@
 		<button
 		<button
 			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
 			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
 			on:click={async () => {
 			on:click={async () => {
-				saveModelfiles($modelfiles);
+				downloadModels($models);
 			}}
 			}}
 		>
 		>
-			<div class=" self-center mr-2 font-medium">{$i18n.t('Export Modelfiles')}</div>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Export Models')}</div>
 
 
 			<div class=" self-center">
 			<div class=" self-center">
 				<svg
 				<svg
@@ -314,47 +329,13 @@
 			</div>
 			</div>
 
 
 			<div class="flex space-x-1">
 			<div class="flex space-x-1">
-				<button
-					class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
-					on:click={async () => {
-						for (const modelfile of localModelfiles) {
-							await createNewModelfile(localStorage.token, modelfile).catch((error) => {
-								return null;
-							});
-						}
-
-						saveModelfiles(localModelfiles);
-						localStorage.removeItem('modelfiles');
-						localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
-						await modelfiles.set(await getModelfiles(localStorage.token));
-					}}
-				>
-					<div class=" self-center mr-2 font-medium">{$i18n.t('Sync All')}</div>
-
-					<div class=" self-center">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 16 16"
-							fill="currentColor"
-							class="w-3.5 h-3.5"
-						>
-							<path
-								fill-rule="evenodd"
-								d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
-								clip-rule="evenodd"
-							/>
-						</svg>
-					</div>
-				</button>
-
 				<button
 				<button
 					class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
 					class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
 					on:click={async () => {
 					on:click={async () => {
-						saveModelfiles(localModelfiles);
+						downloadModels(localModelfiles);
 
 
 						localStorage.removeItem('modelfiles');
 						localStorage.removeItem('modelfiles');
 						localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
 						localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
-						await modelfiles.set(await getModelfiles(localStorage.token));
 					}}
 					}}
 				>
 				>
 					<div class=" self-center">
 					<div class=" self-center">
@@ -402,7 +383,7 @@
 		</div>
 		</div>
 
 
 		<div class=" self-center">
 		<div class=" self-center">
-			<div class=" font-bold">{$i18n.t('Discover a modelfile')}</div>
+			<div class=" font-bold">{$i18n.t('Discover a model')}</div>
 			<div class=" text-sm">{$i18n.t('Discover, download, and explore model presets')}</div>
 			<div class=" text-sm">{$i18n.t('Discover, download, and explore model presets')}</div>
 		</div>
 		</div>
 	</a>
 	</a>

+ 5 - 7
src/lib/components/workspace/Playground.svelte

@@ -321,13 +321,11 @@
 							<div class="max-w-full">
 							<div class="max-w-full">
 								<Selector
 								<Selector
 									placeholder={$i18n.t('Select a model')}
 									placeholder={$i18n.t('Select a model')}
-									items={$models
-										.filter((model) => model.name !== 'hr')
-										.map((model) => ({
-											value: model.id,
-											label: model.name,
-											info: model
-										}))}
+									items={$models.map((model) => ({
+										value: model.id,
+										label: model.name,
+										model: model
+									}))}
 									bind:value={selectedModelId}
 									bind:value={selectedModelId}
 								/>
 								/>
 							</div>
 							</div>

+ 1 - 1
src/lib/constants.ts

@@ -8,7 +8,7 @@ export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;
 
 
 export const LITELLM_API_BASE_URL = `${WEBUI_BASE_URL}/litellm/api`;
 export const LITELLM_API_BASE_URL = `${WEBUI_BASE_URL}/litellm/api`;
 export const OLLAMA_API_BASE_URL = `${WEBUI_BASE_URL}/ollama`;
 export const OLLAMA_API_BASE_URL = `${WEBUI_BASE_URL}/ollama`;
-export const OPENAI_API_BASE_URL = `${WEBUI_BASE_URL}/openai/api`;
+export const OPENAI_API_BASE_URL = `${WEBUI_BASE_URL}/openai`;
 export const AUDIO_API_BASE_URL = `${WEBUI_BASE_URL}/audio/api/v1`;
 export const AUDIO_API_BASE_URL = `${WEBUI_BASE_URL}/audio/api/v1`;
 export const IMAGES_API_BASE_URL = `${WEBUI_BASE_URL}/images/api/v1`;
 export const IMAGES_API_BASE_URL = `${WEBUI_BASE_URL}/images/api/v1`;
 export const RAG_API_BASE_URL = `${WEBUI_BASE_URL}/rag/api/v1`;
 export const RAG_API_BASE_URL = `${WEBUI_BASE_URL}/rag/api/v1`;

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} ...يفكر",
 	"{{modelName}} is thinking...": "{{modelName}} ...يفكر",
 	"{{user}}'s Chats": "دردشات {{user}}",
 	"{{user}}'s Chats": "دردشات {{user}}",
 	"{{webUIName}} Backend Required": "{{webUIName}} مطلوب",
 	"{{webUIName}} Backend Required": "{{webUIName}} مطلوب",
+	"A selected model does not support image input": "",
 	"a user": "مستخدم",
 	"a user": "مستخدم",
 	"About": "عن",
 	"About": "عن",
 	"Account": "الحساب",
 	"Account": "الحساب",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "التعليمات المتقدمة",
 	"Advanced Parameters": "التعليمات المتقدمة",
 	"all": "الكل",
 	"all": "الكل",
 	"All Documents": "جميع الملفات",
 	"All Documents": "جميع الملفات",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "جميع المستخدمين",
 	"All Users": "جميع المستخدمين",
 	"Allow": "يسمح",
 	"Allow": "يسمح",
 	"Allow Chat Deletion": "يستطيع حذف المحادثات",
 	"Allow Chat Deletion": "يستطيع حذف المحادثات",
@@ -115,6 +117,7 @@
 	"Created at": "أنشئت في",
 	"Created at": "أنشئت في",
 	"Created At": "أنشئت من",
 	"Created At": "أنشئت من",
 	"Current Model": "الموديل المختار",
 	"Current Model": "الموديل المختار",
+	"Current Models": "",
 	"Current Password": "كلمة السر الحالية",
 	"Current Password": "كلمة السر الحالية",
 	"Custom": "مخصص",
 	"Custom": "مخصص",
 	"Customize Ollama models for a specific purpose": "تخصيص الموديل Ollama لغرض محدد",
 	"Customize Ollama models for a specific purpose": "تخصيص الموديل Ollama لغرض محدد",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "أدخل LiteLLM API RPM (litllm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "أدخل LiteLLM API RPM (litllm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "أدخل LiteLLM الموديل (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "أدخل LiteLLM الموديل (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "أدخل أكثر Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "أدخل أكثر Tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
 	"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
 	"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
 	"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
 	"Enter Score": "أدخل النتيجة",
 	"Enter Score": "أدخل النتيجة",
@@ -235,6 +239,7 @@
 	"Input commands": "إدخال الأوامر",
 	"Input commands": "إدخال الأوامر",
 	"Interface": "واجهه المستخدم",
 	"Interface": "واجهه المستخدم",
 	"Invalid Tag": "تاق غير صالحة",
 	"Invalid Tag": "تاق غير صالحة",
+	"Is Model Vision Capable": "",
 	"January": "يناير",
 	"January": "يناير",
 	"join our Discord for help.": "انضم إلى Discord للحصول على المساعدة.",
 	"join our Discord for help.": "انضم إلى Discord للحصول على المساعدة.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ",
 	"Made by OpenWebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ",
 	"Make sure to enclose them with": "تأكد من إرفاقها",
 	"Make sure to enclose them with": "تأكد من إرفاقها",
 	"Manage LiteLLM Models": "LiteLLM إدارة نماذج ",
 	"Manage LiteLLM Models": "LiteLLM إدارة نماذج ",
+	"Manage Model Information": "",
 	"Manage Models": "إدارة النماذج",
 	"Manage Models": "إدارة النماذج",
 	"Manage Ollama Models": "Ollama إدارة موديلات ",
 	"Manage Ollama Models": "Ollama إدارة موديلات ",
 	"March": "مارس",
 	"March": "مارس",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل",
 	"Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل",
 	"Model {{modelId}} not found": "لم يتم العثور على النموذج {{modelId}}.",
 	"Model {{modelId}} not found": "لم يتم العثور على النموذج {{modelId}}.",
 	"Model {{modelName}} already exists.": "موجود {{modelName}} موديل بالفعل",
 	"Model {{modelName}} already exists.": "موجود {{modelName}} موديل بالفعل",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "تم اكتشاف مسار نظام الملفات النموذجي. الاسم المختصر للنموذج مطلوب للتحديث، ولا يمكن الاستمرار.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "تم اكتشاف مسار نظام الملفات النموذجي. الاسم المختصر للنموذج مطلوب للتحديث، ولا يمكن الاستمرار.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "أسم الموديل",
 	"Model Name": "أسم الموديل",
 	"Model not selected": "لم تختار موديل",
 	"Model not selected": "لم تختار موديل",
 	"Model Tag Name": "أسم التاق للموديل",
 	"Model Tag Name": "أسم التاق للموديل",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "قم بتسمية ملف النموذج الخاص بك",
 	"Name your modelfile": "قم بتسمية ملف النموذج الخاص بك",
 	"New Chat": "دردشة جديدة",
 	"New Chat": "دردشة جديدة",
 	"New Password": "كلمة المرور الجديدة",
 	"New Password": "كلمة المرور الجديدة",
+	"No": "",
 	"No results found": "لا توجد نتايج",
 	"No results found": "لا توجد نتايج",
 	"No source available": "لا يوجد مصدر متاح",
 	"No source available": "لا يوجد مصدر متاح",
 	"Not factually correct": "ليس صحيحا من حيث الواقع",
 	"Not factually correct": "ليس صحيحا من حيث الواقع",
@@ -385,6 +397,7 @@
 	"Select a model": "أختار الموديل",
 	"Select a model": "أختار الموديل",
 	"Select an Ollama instance": "أختار سيرفر ",
 	"Select an Ollama instance": "أختار سيرفر ",
 	"Select model": " أختار موديل",
 	"Select model": " أختار موديل",
+	"Selected models do not support image inputs": "",
 	"Send": "تم",
 	"Send": "تم",
 	"Send a Message": "يُرجى إدخال طلبك هنا",
 	"Send a Message": "يُرجى إدخال طلبك هنا",
 	"Send message": "يُرجى إدخال طلبك هنا.",
 	"Send message": "يُرجى إدخال طلبك هنا.",
@@ -492,6 +505,7 @@
 	"Workspace": "مساحة العمل",
 	"Workspace": "مساحة العمل",
 	"Write a prompt suggestion (e.g. Who are you?)": "اكتب اقتراحًا سريعًا (على سبيل المثال، من أنت؟)",
 	"Write a prompt suggestion (e.g. Who are you?)": "اكتب اقتراحًا سريعًا (على سبيل المثال، من أنت؟)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
+	"Yes": "",
 	"Yesterday": "أمس",
 	"Yesterday": "أمس",
 	"You": "انت",
 	"You": "انت",
 	"You have no archived conversations.": "لا تملك محادثات محفوظه",
 	"You have no archived conversations.": "لا تملك محادثات محفوظه",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} мисли ...",
 	"{{modelName}} is thinking...": "{{modelName}} мисли ...",
 	"{{user}}'s Chats": "{{user}}'s чатове",
 	"{{user}}'s Chats": "{{user}}'s чатове",
 	"{{webUIName}} Backend Required": "{{webUIName}} Изисква се Бекенд",
 	"{{webUIName}} Backend Required": "{{webUIName}} Изисква се Бекенд",
+	"A selected model does not support image input": "",
 	"a user": "потребител",
 	"a user": "потребител",
 	"About": "Относно",
 	"About": "Относно",
 	"Account": "Акаунт",
 	"Account": "Акаунт",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Разширени Параметри",
 	"Advanced Parameters": "Разширени Параметри",
 	"all": "всички",
 	"all": "всички",
 	"All Documents": "Всички Документи",
 	"All Documents": "Всички Документи",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Всички Потребители",
 	"All Users": "Всички Потребители",
 	"Allow": "Позволи",
 	"Allow": "Позволи",
 	"Allow Chat Deletion": "Позволи Изтриване на Чат",
 	"Allow Chat Deletion": "Позволи Изтриване на Чат",
@@ -115,6 +117,7 @@
 	"Created at": "Създадено на",
 	"Created at": "Създадено на",
 	"Created At": "Създадено на",
 	"Created At": "Създадено на",
 	"Current Model": "Текущ модел",
 	"Current Model": "Текущ модел",
+	"Current Models": "",
 	"Current Password": "Текуща Парола",
 	"Current Password": "Текуща Парола",
 	"Custom": "Персонализиран",
 	"Custom": "Персонализиран",
 	"Customize Ollama models for a specific purpose": "Персонализиране на Ollama моделите за конкретна цел",
 	"Customize Ollama models for a specific purpose": "Персонализиране на Ollama моделите за конкретна цел",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Въведете LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Въведете LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Въведете LiteLLM Model (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Въведете LiteLLM Model (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Въведете Max Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Въведете Max Tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Въведете таг на модел (напр. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Въведете таг на модел (напр. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Въведете брой стъпки (напр. 50)",
 	"Enter Number of Steps (e.g. 50)": "Въведете брой стъпки (напр. 50)",
 	"Enter Score": "Въведете оценка",
 	"Enter Score": "Въведете оценка",
@@ -235,6 +239,7 @@
 	"Input commands": "Въведете команди",
 	"Input commands": "Въведете команди",
 	"Interface": "Интерфейс",
 	"Interface": "Интерфейс",
 	"Invalid Tag": "Невалиден тег",
 	"Invalid Tag": "Невалиден тег",
+	"Is Model Vision Capable": "",
 	"January": "Януари",
 	"January": "Януари",
 	"join our Discord for help.": "свържете се с нашия Discord за помощ.",
 	"join our Discord for help.": "свържете се с нашия Discord за помощ.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Направено от OpenWebUI общността",
 	"Made by OpenWebUI Community": "Направено от OpenWebUI общността",
 	"Make sure to enclose them with": "Уверете се, че са заключени с",
 	"Make sure to enclose them with": "Уверете се, че са заключени с",
 	"Manage LiteLLM Models": "Управление на LiteLLM Моделите",
 	"Manage LiteLLM Models": "Управление на LiteLLM Моделите",
+	"Manage Model Information": "",
 	"Manage Models": "Управление на Моделите",
 	"Manage Models": "Управление на Моделите",
 	"Manage Ollama Models": "Управление на Ollama Моделите",
 	"Manage Ollama Models": "Управление на Ollama Моделите",
 	"March": "Март",
 	"March": "Март",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.",
 	"Model {{modelId}} not found": "Моделът {{modelId}} не е намерен",
 	"Model {{modelId}} not found": "Моделът {{modelId}} не е намерен",
 	"Model {{modelName}} already exists.": "Моделът {{modelName}} вече съществува.",
 	"Model {{modelName}} already exists.": "Моделът {{modelName}} вече съществува.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Открит е път до файловата система на модела. За актуализацията се изисква съкратено име на модела, не може да продължи.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Открит е път до файловата система на модела. За актуализацията се изисква съкратено име на модела, не може да продължи.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Име на модел",
 	"Model Name": "Име на модел",
 	"Model not selected": "Не е избран модел",
 	"Model not selected": "Не е избран модел",
 	"Model Tag Name": "Име на таг на модел",
 	"Model Tag Name": "Име на таг на модел",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Име на модфайла",
 	"Name your modelfile": "Име на модфайла",
 	"New Chat": "Нов чат",
 	"New Chat": "Нов чат",
 	"New Password": "Нова парола",
 	"New Password": "Нова парола",
+	"No": "",
 	"No results found": "Няма намерени резултати",
 	"No results found": "Няма намерени резултати",
 	"No source available": "Няма наличен източник",
 	"No source available": "Няма наличен източник",
 	"Not factually correct": "Не е фактологически правилно",
 	"Not factually correct": "Не е фактологически правилно",
@@ -385,6 +397,7 @@
 	"Select a model": "Изберете модел",
 	"Select a model": "Изберете модел",
 	"Select an Ollama instance": "Изберете Ollama инстанция",
 	"Select an Ollama instance": "Изберете Ollama инстанция",
 	"Select model": "Изберете модел",
 	"Select model": "Изберете модел",
+	"Selected models do not support image inputs": "",
 	"Send": "Изпрати",
 	"Send": "Изпрати",
 	"Send a Message": "Изпращане на Съобщение",
 	"Send a Message": "Изпращане на Съобщение",
 	"Send message": "Изпращане на съобщение",
 	"Send message": "Изпращане на съобщение",
@@ -492,6 +505,7 @@
 	"Workspace": "Работно пространство",
 	"Workspace": "Работно пространство",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напиши предложение за промпт (напр. Кой сте вие?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напиши предложение за промпт (напр. Кой сте вие?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напиши описание в 50 знака, което описва [тема или ключова дума].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напиши описание в 50 знака, което описва [тема или ключова дума].",
+	"Yes": "",
 	"Yesterday": "вчера",
 	"Yesterday": "вчера",
 	"You": "вие",
 	"You": "вие",
 	"You have no archived conversations.": "Нямате архивирани разговори.",
 	"You have no archived conversations.": "Нямате архивирани разговори.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} চিন্তা করছে...",
 	"{{modelName}} is thinking...": "{{modelName}} চিন্তা করছে...",
 	"{{user}}'s Chats": "{{user}}র চ্যাটস",
 	"{{user}}'s Chats": "{{user}}র চ্যাটস",
 	"{{webUIName}} Backend Required": "{{webUIName}} ব্যাকএন্ড আবশ্যক",
 	"{{webUIName}} Backend Required": "{{webUIName}} ব্যাকএন্ড আবশ্যক",
+	"A selected model does not support image input": "",
 	"a user": "একজন ব্যাবহারকারী",
 	"a user": "একজন ব্যাবহারকারী",
 	"About": "সম্পর্কে",
 	"About": "সম্পর্কে",
 	"Account": "একাউন্ট",
 	"Account": "একাউন্ট",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "এডভান্সড প্যারামিটার্স",
 	"Advanced Parameters": "এডভান্সড প্যারামিটার্স",
 	"all": "সব",
 	"all": "সব",
 	"All Documents": "সব ডকুমেন্ট",
 	"All Documents": "সব ডকুমেন্ট",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "সব ইউজার",
 	"All Users": "সব ইউজার",
 	"Allow": "অনুমোদন",
 	"Allow": "অনুমোদন",
 	"Allow Chat Deletion": "চ্যাট ডিলিট করতে দিন",
 	"Allow Chat Deletion": "চ্যাট ডিলিট করতে দিন",
@@ -115,6 +117,7 @@
 	"Created at": "নির্মানকাল",
 	"Created at": "নির্মানকাল",
 	"Created At": "নির্মানকাল",
 	"Created At": "নির্মানকাল",
 	"Current Model": "বর্তমান মডেল",
 	"Current Model": "বর্তমান মডেল",
+	"Current Models": "",
 	"Current Password": "বর্তমান পাসওয়ার্ড",
 	"Current Password": "বর্তমান পাসওয়ার্ড",
 	"Custom": "কাস্টম",
 	"Custom": "কাস্টম",
 	"Customize Ollama models for a specific purpose": "নির্দিষ্ট উদ্দেশ্যে Ollama মডেল পরিবর্তন করুন",
 	"Customize Ollama models for a specific purpose": "নির্দিষ্ট উদ্দেশ্যে Ollama মডেল পরিবর্তন করুন",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM এপিআই RPM দিন (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM এপিআই RPM দিন (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM মডেল দিন (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM মডেল দিন (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "সর্বোচ্চ টোকেন সংখ্যা দিন (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "সর্বোচ্চ টোকেন সংখ্যা দিন (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "মডেল ট্যাগ লিখুন (e.g. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "মডেল ট্যাগ লিখুন (e.g. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "ধাপের সংখ্যা দিন (যেমন: 50)",
 	"Enter Number of Steps (e.g. 50)": "ধাপের সংখ্যা দিন (যেমন: 50)",
 	"Enter Score": "স্কোর দিন",
 	"Enter Score": "স্কোর দিন",
@@ -235,6 +239,7 @@
 	"Input commands": "ইনপুট কমান্ডস",
 	"Input commands": "ইনপুট কমান্ডস",
 	"Interface": "ইন্টারফেস",
 	"Interface": "ইন্টারফেস",
 	"Invalid Tag": "অবৈধ ট্যাগ",
 	"Invalid Tag": "অবৈধ ট্যাগ",
+	"Is Model Vision Capable": "",
 	"January": "জানুয়ারী",
 	"January": "জানুয়ারী",
 	"join our Discord for help.": "সাহায্যের জন্য আমাদের Discord-এ যুক্ত হোন",
 	"join our Discord for help.": "সাহায্যের জন্য আমাদের Discord-এ যুক্ত হোন",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "OpenWebUI কমিউনিটিকর্তৃক নির্মিত",
 	"Made by OpenWebUI Community": "OpenWebUI কমিউনিটিকর্তৃক নির্মিত",
 	"Make sure to enclose them with": "এটা দিয়ে বন্ধনী দিতে ভুলবেন না",
 	"Make sure to enclose them with": "এটা দিয়ে বন্ধনী দিতে ভুলবেন না",
 	"Manage LiteLLM Models": "LiteLLM মডেল ব্যবস্থাপনা করুন",
 	"Manage LiteLLM Models": "LiteLLM মডেল ব্যবস্থাপনা করুন",
+	"Manage Model Information": "",
 	"Manage Models": "মডেলসমূহ ব্যবস্থাপনা করুন",
 	"Manage Models": "মডেলসমূহ ব্যবস্থাপনা করুন",
 	"Manage Ollama Models": "Ollama মডেলসূহ ব্যবস্থাপনা করুন",
 	"Manage Ollama Models": "Ollama মডেলসূহ ব্যবস্থাপনা করুন",
 	"March": "মার্চ",
 	"March": "মার্চ",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "{{modelTag}} ডাউনলোডের জন্য আগে থেকেই অপেক্ষমান আছে।",
 	"Model '{{modelTag}}' is already in queue for downloading.": "{{modelTag}} ডাউনলোডের জন্য আগে থেকেই অপেক্ষমান আছে।",
 	"Model {{modelId}} not found": "{{modelId}} মডেল পাওয়া যায়নি",
 	"Model {{modelId}} not found": "{{modelId}} মডেল পাওয়া যায়নি",
 	"Model {{modelName}} already exists.": "{{modelName}} মডেল আগে থেকেই আছে",
 	"Model {{modelName}} already exists.": "{{modelName}} মডেল আগে থেকেই আছে",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "মডেল ফাইলসিস্টেম পাথ পাওয়া গেছে। আপডেটের জন্য মডেলের শর্টনেম আবশ্যক, এগিয়ে যাওয়া যাচ্ছে না।",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "মডেল ফাইলসিস্টেম পাথ পাওয়া গেছে। আপডেটের জন্য মডেলের শর্টনেম আবশ্যক, এগিয়ে যাওয়া যাচ্ছে না।",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "মডেলের নাম",
 	"Model Name": "মডেলের নাম",
 	"Model not selected": "মডেল নির্বাচন করা হয়নি",
 	"Model not selected": "মডেল নির্বাচন করা হয়নি",
 	"Model Tag Name": "মডেলের ট্যাগ নাম",
 	"Model Tag Name": "মডেলের ট্যাগ নাম",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "আপনার মডেলফাইলের নাম দিন",
 	"Name your modelfile": "আপনার মডেলফাইলের নাম দিন",
 	"New Chat": "নতুন চ্যাট",
 	"New Chat": "নতুন চ্যাট",
 	"New Password": "নতুন পাসওয়ার্ড",
 	"New Password": "নতুন পাসওয়ার্ড",
+	"No": "",
 	"No results found": "কোন ফলাফল পাওয়া যায়নি",
 	"No results found": "কোন ফলাফল পাওয়া যায়নি",
 	"No source available": "কোন উৎস পাওয়া যায়নি",
 	"No source available": "কোন উৎস পাওয়া যায়নি",
 	"Not factually correct": "তথ্যগত দিক থেকে সঠিক নয়",
 	"Not factually correct": "তথ্যগত দিক থেকে সঠিক নয়",
@@ -385,6 +397,7 @@
 	"Select a model": "একটি মডেল নির্বাচন করুন",
 	"Select a model": "একটি মডেল নির্বাচন করুন",
 	"Select an Ollama instance": "একটি Ollama ইন্সট্যান্স নির্বাচন করুন",
 	"Select an Ollama instance": "একটি Ollama ইন্সট্যান্স নির্বাচন করুন",
 	"Select model": "মডেল নির্বাচন করুন",
 	"Select model": "মডেল নির্বাচন করুন",
+	"Selected models do not support image inputs": "",
 	"Send": "পাঠান",
 	"Send": "পাঠান",
 	"Send a Message": "একটি মেসেজ পাঠান",
 	"Send a Message": "একটি মেসেজ পাঠান",
 	"Send message": "মেসেজ পাঠান",
 	"Send message": "মেসেজ পাঠান",
@@ -492,6 +505,7 @@
 	"Workspace": "ওয়ার্কস্পেস",
 	"Workspace": "ওয়ার্কস্পেস",
 	"Write a prompt suggestion (e.g. Who are you?)": "একটি প্রম্পট সাজেশন লিখুন (যেমন Who are you?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "একটি প্রম্পট সাজেশন লিখুন (যেমন Who are you?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "৫০ শব্দের মধ্যে [topic or keyword] এর একটি সারসংক্ষেপ লিখুন।",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "৫০ শব্দের মধ্যে [topic or keyword] এর একটি সারসংক্ষেপ লিখুন।",
+	"Yes": "",
 	"Yesterday": "আগামী",
 	"Yesterday": "আগামী",
 	"You": "আপনি",
 	"You": "আপনি",
 	"You have no archived conversations.": "আপনার কোনও আর্কাইভ করা কথোপকথন নেই।",
 	"You have no archived conversations.": "আপনার কোনও আর্কাইভ করা কথোপকথন নেই।",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} està pensant...",
 	"{{modelName}} is thinking...": "{{modelName}} està pensant...",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "Es requereix Backend de {{webUIName}}",
 	"{{webUIName}} Backend Required": "Es requereix Backend de {{webUIName}}",
+	"A selected model does not support image input": "",
 	"a user": "un usuari",
 	"a user": "un usuari",
 	"About": "Sobre",
 	"About": "Sobre",
 	"Account": "Compte",
 	"Account": "Compte",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Paràmetres Avançats",
 	"Advanced Parameters": "Paràmetres Avançats",
 	"all": "tots",
 	"all": "tots",
 	"All Documents": "Tots els Documents",
 	"All Documents": "Tots els Documents",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Tots els Usuaris",
 	"All Users": "Tots els Usuaris",
 	"Allow": "Permet",
 	"Allow": "Permet",
 	"Allow Chat Deletion": "Permet la Supressió del Xat",
 	"Allow Chat Deletion": "Permet la Supressió del Xat",
@@ -115,6 +117,7 @@
 	"Created at": "Creat el",
 	"Created at": "Creat el",
 	"Created At": "Creat el",
 	"Created At": "Creat el",
 	"Current Model": "Model Actual",
 	"Current Model": "Model Actual",
+	"Current Models": "",
 	"Current Password": "Contrasenya Actual",
 	"Current Password": "Contrasenya Actual",
 	"Custom": "Personalitzat",
 	"Custom": "Personalitzat",
 	"Customize Ollama models for a specific purpose": "Personalitza els models Ollama per a un propòsit específic",
 	"Customize Ollama models for a specific purpose": "Personalitza els models Ollama per a un propòsit específic",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Introdueix RPM de LiteLLM API (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Introdueix RPM de LiteLLM API (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Introdueix el Model de LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Introdueix el Model de LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Introdueix el Màxim de Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Introdueix el Màxim de Tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Introdueix l'etiqueta del model (p. ex. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Introdueix l'etiqueta del model (p. ex. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Introdueix el Nombre de Passos (p. ex. 50)",
 	"Enter Number of Steps (e.g. 50)": "Introdueix el Nombre de Passos (p. ex. 50)",
 	"Enter Score": "Introdueix el Puntuació",
 	"Enter Score": "Introdueix el Puntuació",
@@ -235,6 +239,7 @@
 	"Input commands": "Entra ordres",
 	"Input commands": "Entra ordres",
 	"Interface": "Interfície",
 	"Interface": "Interfície",
 	"Invalid Tag": "Etiqueta Inválida",
 	"Invalid Tag": "Etiqueta Inválida",
+	"Is Model Vision Capable": "",
 	"January": "Gener",
 	"January": "Gener",
 	"join our Discord for help.": "uneix-te al nostre Discord per ajuda.",
 	"join our Discord for help.": "uneix-te al nostre Discord per ajuda.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Creat per la Comunitat OpenWebUI",
 	"Made by OpenWebUI Community": "Creat per la Comunitat OpenWebUI",
 	"Make sure to enclose them with": "Assegura't d'envoltar-los amb",
 	"Make sure to enclose them with": "Assegura't d'envoltar-los amb",
 	"Manage LiteLLM Models": "Gestiona Models LiteLLM",
 	"Manage LiteLLM Models": "Gestiona Models LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Gestiona Models",
 	"Manage Models": "Gestiona Models",
 	"Manage Ollama Models": "Gestiona Models Ollama",
 	"Manage Ollama Models": "Gestiona Models Ollama",
 	"March": "Març",
 	"March": "Març",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "El model '{{modelTag}}' ja està en cua per ser descarregat.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "El model '{{modelTag}}' ja està en cua per ser descarregat.",
 	"Model {{modelId}} not found": "Model {{modelId}} no trobat",
 	"Model {{modelId}} not found": "Model {{modelId}} no trobat",
 	"Model {{modelName}} already exists.": "El model {{modelName}} ja existeix.",
 	"Model {{modelName}} already exists.": "El model {{modelName}} ja existeix.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "S'ha detectat el camí del sistema de fitxers del model. És necessari un nom curt del model per a actualitzar, no es pot continuar.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "S'ha detectat el camí del sistema de fitxers del model. És necessari un nom curt del model per a actualitzar, no es pot continuar.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nom del Model",
 	"Model Name": "Nom del Model",
 	"Model not selected": "Model no seleccionat",
 	"Model not selected": "Model no seleccionat",
 	"Model Tag Name": "Nom de l'Etiqueta del Model",
 	"Model Tag Name": "Nom de l'Etiqueta del Model",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nomena el teu fitxer de model",
 	"Name your modelfile": "Nomena el teu fitxer de model",
 	"New Chat": "Xat Nou",
 	"New Chat": "Xat Nou",
 	"New Password": "Nova Contrasenya",
 	"New Password": "Nova Contrasenya",
+	"No": "",
 	"No results found": "No s'han trobat resultats",
 	"No results found": "No s'han trobat resultats",
 	"No source available": "Sense font disponible",
 	"No source available": "Sense font disponible",
 	"Not factually correct": "No està clarament correcte",
 	"Not factually correct": "No està clarament correcte",
@@ -385,6 +397,7 @@
 	"Select a model": "Selecciona un model",
 	"Select a model": "Selecciona un model",
 	"Select an Ollama instance": "Selecciona una instància d'Ollama",
 	"Select an Ollama instance": "Selecciona una instància d'Ollama",
 	"Select model": "Selecciona un model",
 	"Select model": "Selecciona un model",
+	"Selected models do not support image inputs": "",
 	"Send": "Envia",
 	"Send": "Envia",
 	"Send a Message": "Envia un Missatge",
 	"Send a Message": "Envia un Missatge",
 	"Send message": "Envia missatge",
 	"Send message": "Envia missatge",
@@ -492,6 +505,7 @@
 	"Workspace": "Treball",
 	"Workspace": "Treball",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escriu una suggerència de prompt (p. ex. Qui ets tu?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escriu una suggerència de prompt (p. ex. Qui ets tu?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
+	"Yes": "",
 	"Yesterday": "Ayer",
 	"Yesterday": "Ayer",
 	"You": "Tu",
 	"You": "Tu",
 	"You have no archived conversations.": "No tens converses arxivades.",
 	"You have no archived conversations.": "No tens converses arxivades.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} denkt nach...",
 	"{{modelName}} is thinking...": "{{modelName}} denkt nach...",
 	"{{user}}'s Chats": "{{user}}s Chats",
 	"{{user}}'s Chats": "{{user}}s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich",
 	"{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich",
+	"A selected model does not support image input": "",
 	"a user": "ein Benutzer",
 	"a user": "ein Benutzer",
 	"About": "Über",
 	"About": "Über",
 	"Account": "Account",
 	"Account": "Account",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Erweiterte Parameter",
 	"Advanced Parameters": "Erweiterte Parameter",
 	"all": "Alle",
 	"all": "Alle",
 	"All Documents": "Alle Dokumente",
 	"All Documents": "Alle Dokumente",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Alle Benutzer",
 	"All Users": "Alle Benutzer",
 	"Allow": "Erlauben",
 	"Allow": "Erlauben",
 	"Allow Chat Deletion": "Chat Löschung erlauben",
 	"Allow Chat Deletion": "Chat Löschung erlauben",
@@ -115,6 +117,7 @@
 	"Created at": "Erstellt am",
 	"Created at": "Erstellt am",
 	"Created At": "Erstellt am",
 	"Created At": "Erstellt am",
 	"Current Model": "Aktuelles Modell",
 	"Current Model": "Aktuelles Modell",
+	"Current Models": "",
 	"Current Password": "Aktuelles Passwort",
 	"Current Password": "Aktuelles Passwort",
 	"Custom": "Benutzerdefiniert",
 	"Custom": "Benutzerdefiniert",
 	"Customize Ollama models for a specific purpose": "Ollama-Modelle für einen bestimmten Zweck anpassen",
 	"Customize Ollama models for a specific purpose": "Ollama-Modelle für einen bestimmten Zweck anpassen",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Gib die LiteLLM API RPM ein (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Gib die LiteLLM API RPM ein (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Gib das LiteLLM Model ein (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Gib das LiteLLM Model ein (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Gib die maximalen Token ein (litellm_params.max_tokens) an",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Gib die maximalen Token ein (litellm_params.max_tokens) an",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Gib den Model-Tag ein",
 	"Enter model tag (e.g. {{modelTag}})": "Gib den Model-Tag ein",
 	"Enter Number of Steps (e.g. 50)": "Gib die Anzahl an Schritten ein (z.B. 50)",
 	"Enter Number of Steps (e.g. 50)": "Gib die Anzahl an Schritten ein (z.B. 50)",
 	"Enter Score": "Score eingeben",
 	"Enter Score": "Score eingeben",
@@ -235,6 +239,7 @@
 	"Input commands": "Eingabebefehle",
 	"Input commands": "Eingabebefehle",
 	"Interface": "Benutzeroberfläche",
 	"Interface": "Benutzeroberfläche",
 	"Invalid Tag": "Ungültiger Tag",
 	"Invalid Tag": "Ungültiger Tag",
+	"Is Model Vision Capable": "",
 	"January": "Januar",
 	"January": "Januar",
 	"join our Discord for help.": "Trete unserem Discord bei, um Hilfe zu erhalten.",
 	"join our Discord for help.": "Trete unserem Discord bei, um Hilfe zu erhalten.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Von der OpenWebUI-Community",
 	"Made by OpenWebUI Community": "Von der OpenWebUI-Community",
 	"Make sure to enclose them with": "Formatiere deine Variablen mit:",
 	"Make sure to enclose them with": "Formatiere deine Variablen mit:",
 	"Manage LiteLLM Models": "LiteLLM-Modelle verwalten",
 	"Manage LiteLLM Models": "LiteLLM-Modelle verwalten",
+	"Manage Model Information": "",
 	"Manage Models": "Modelle verwalten",
 	"Manage Models": "Modelle verwalten",
 	"Manage Ollama Models": "Ollama-Modelle verwalten",
 	"Manage Ollama Models": "Ollama-Modelle verwalten",
 	"March": "März",
 	"March": "März",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.",
 	"Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden",
 	"Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden",
 	"Model {{modelName}} already exists.": "Modell {{modelName}} existiert bereits.",
 	"Model {{modelName}} already exists.": "Modell {{modelName}} existiert bereits.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell-Dateisystempfad erkannt. Modellkurzname ist für das Update erforderlich, Fortsetzung nicht möglich.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell-Dateisystempfad erkannt. Modellkurzname ist für das Update erforderlich, Fortsetzung nicht möglich.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Modellname",
 	"Model Name": "Modellname",
 	"Model not selected": "Modell nicht ausgewählt",
 	"Model not selected": "Modell nicht ausgewählt",
 	"Model Tag Name": "Modell-Tag-Name",
 	"Model Tag Name": "Modell-Tag-Name",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Benenne dein modelfile",
 	"Name your modelfile": "Benenne dein modelfile",
 	"New Chat": "Neuer Chat",
 	"New Chat": "Neuer Chat",
 	"New Password": "Neues Passwort",
 	"New Password": "Neues Passwort",
+	"No": "",
 	"No results found": "Keine Ergebnisse gefunden",
 	"No results found": "Keine Ergebnisse gefunden",
 	"No source available": "Keine Quelle verfügbar.",
 	"No source available": "Keine Quelle verfügbar.",
 	"Not factually correct": "Nicht sachlich korrekt.",
 	"Not factually correct": "Nicht sachlich korrekt.",
@@ -385,6 +397,7 @@
 	"Select a model": "Ein Modell auswählen",
 	"Select a model": "Ein Modell auswählen",
 	"Select an Ollama instance": "Eine Ollama Instanz auswählen",
 	"Select an Ollama instance": "Eine Ollama Instanz auswählen",
 	"Select model": "Modell auswählen",
 	"Select model": "Modell auswählen",
+	"Selected models do not support image inputs": "",
 	"Send": "Senden",
 	"Send": "Senden",
 	"Send a Message": "Eine Nachricht senden",
 	"Send a Message": "Eine Nachricht senden",
 	"Send message": "Nachricht senden",
 	"Send message": "Nachricht senden",
@@ -492,6 +505,7 @@
 	"Workspace": "Arbeitsbereich",
 	"Workspace": "Arbeitsbereich",
 	"Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z.B. Wer bist du?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z.B. Wer bist du?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.",
+	"Yes": "",
 	"Yesterday": "Gestern",
 	"Yesterday": "Gestern",
 	"You": "Du",
 	"You": "Du",
 	"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",
 	"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} is thinkin'...",
 	"{{modelName}} is thinking...": "{{modelName}} is thinkin'...",
 	"{{user}}'s Chats": "",
 	"{{user}}'s Chats": "",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Much Required",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Much Required",
+	"A selected model does not support image input": "",
 	"a user": "such user",
 	"a user": "such user",
 	"About": "Much About",
 	"About": "Much About",
 	"Account": "Account",
 	"Account": "Account",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Advanced Parameters",
 	"Advanced Parameters": "Advanced Parameters",
 	"all": "all",
 	"all": "all",
 	"All Documents": "",
 	"All Documents": "",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "All Users",
 	"All Users": "All Users",
 	"Allow": "Allow",
 	"Allow": "Allow",
 	"Allow Chat Deletion": "Allow Delete Chats",
 	"Allow Chat Deletion": "Allow Delete Chats",
@@ -115,6 +117,7 @@
 	"Created at": "Created at",
 	"Created at": "Created at",
 	"Created At": "",
 	"Created At": "",
 	"Current Model": "Current Model",
 	"Current Model": "Current Model",
+	"Current Models": "",
 	"Current Password": "Current Password",
 	"Current Password": "Current Password",
 	"Custom": "Custom",
 	"Custom": "Custom",
 	"Customize Ollama models for a specific purpose": "Customize Ollama models for purpose",
 	"Customize Ollama models for a specific purpose": "Customize Ollama models for purpose",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Enter RPM of LiteLLM API (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Enter RPM of LiteLLM API (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Enter Model of LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Enter Model of LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Enter Maximum Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Enter Maximum Tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Enter model doge tag (e.g. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Enter model doge tag (e.g. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Enter Number of Steps (e.g. 50)",
 	"Enter Number of Steps (e.g. 50)": "Enter Number of Steps (e.g. 50)",
 	"Enter Score": "",
 	"Enter Score": "",
@@ -235,6 +239,7 @@
 	"Input commands": "Input commands",
 	"Input commands": "Input commands",
 	"Interface": "Interface",
 	"Interface": "Interface",
 	"Invalid Tag": "",
 	"Invalid Tag": "",
+	"Is Model Vision Capable": "",
 	"January": "",
 	"January": "",
 	"join our Discord for help.": "join our Discord for help.",
 	"join our Discord for help.": "join our Discord for help.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Made by OpenWebUI Community",
 	"Made by OpenWebUI Community": "Made by OpenWebUI Community",
 	"Make sure to enclose them with": "Make sure to enclose them with",
 	"Make sure to enclose them with": "Make sure to enclose them with",
 	"Manage LiteLLM Models": "Manage LiteLLM Models",
 	"Manage LiteLLM Models": "Manage LiteLLM Models",
+	"Manage Model Information": "",
 	"Manage Models": "Manage Wowdels",
 	"Manage Models": "Manage Wowdels",
 	"Manage Ollama Models": "Manage Ollama Wowdels",
 	"Manage Ollama Models": "Manage Ollama Wowdels",
 	"March": "",
 	"March": "",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' is already in queue for downloading.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' is already in queue for downloading.",
 	"Model {{modelId}} not found": "Model {{modelId}} not found",
 	"Model {{modelId}} not found": "Model {{modelId}} not found",
 	"Model {{modelName}} already exists.": "Model {{modelName}} already exists.",
 	"Model {{modelName}} already exists.": "Model {{modelName}} already exists.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model filesystem bark detected. Model shortname is required for update, cannot continue.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model filesystem bark detected. Model shortname is required for update, cannot continue.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Wowdel Name",
 	"Model Name": "Wowdel Name",
 	"Model not selected": "Model not selected",
 	"Model not selected": "Model not selected",
 	"Model Tag Name": "Wowdel Tag Name",
 	"Model Tag Name": "Wowdel Tag Name",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Name your modelfile",
 	"Name your modelfile": "Name your modelfile",
 	"New Chat": "New Bark",
 	"New Chat": "New Bark",
 	"New Password": "New Barkword",
 	"New Password": "New Barkword",
+	"No": "",
 	"No results found": "",
 	"No results found": "",
 	"No source available": "No source available",
 	"No source available": "No source available",
 	"Not factually correct": "",
 	"Not factually correct": "",
@@ -385,6 +397,7 @@
 	"Select a model": "Select a model much choice",
 	"Select a model": "Select a model much choice",
 	"Select an Ollama instance": "Select an Ollama instance very choose",
 	"Select an Ollama instance": "Select an Ollama instance very choose",
 	"Select model": "Select model much choice",
 	"Select model": "Select model much choice",
+	"Selected models do not support image inputs": "",
 	"Send": "",
 	"Send": "",
 	"Send a Message": "Send a Message much message",
 	"Send a Message": "Send a Message much message",
 	"Send message": "Send message very send",
 	"Send message": "Send message very send",
@@ -492,6 +505,7 @@
 	"Workspace": "",
 	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Write a prompt suggestion (e.g. Who are you?) much suggest",
 	"Write a prompt suggestion (e.g. Who are you?)": "Write a prompt suggestion (e.g. Who are you?) much suggest",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Write a summary in 50 words that summarizes [topic or keyword]. Much summarize.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Write a summary in 50 words that summarizes [topic or keyword]. Much summarize.",
+	"Yes": "",
 	"Yesterday": "",
 	"Yesterday": "",
 	"You": "",
 	"You": "",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "",
 	"{{modelName}} is thinking...": "",
 	"{{user}}'s Chats": "",
 	"{{user}}'s Chats": "",
 	"{{webUIName}} Backend Required": "",
 	"{{webUIName}} Backend Required": "",
+	"A selected model does not support image input": "",
 	"a user": "",
 	"a user": "",
 	"About": "",
 	"About": "",
 	"Account": "",
 	"Account": "",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "",
 	"Advanced Parameters": "",
 	"all": "",
 	"all": "",
 	"All Documents": "",
 	"All Documents": "",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "",
 	"All Users": "",
 	"Allow": "",
 	"Allow": "",
 	"Allow Chat Deletion": "",
 	"Allow Chat Deletion": "",
@@ -115,6 +117,7 @@
 	"Created at": "",
 	"Created at": "",
 	"Created At": "",
 	"Created At": "",
 	"Current Model": "",
 	"Current Model": "",
+	"Current Models": "",
 	"Current Password": "",
 	"Current Password": "",
 	"Custom": "",
 	"Custom": "",
 	"Customize Ollama models for a specific purpose": "",
 	"Customize Ollama models for a specific purpose": "",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "",
 	"Enter LiteLLM Model (litellm_params.model)": "",
 	"Enter LiteLLM Model (litellm_params.model)": "",
 	"Enter Max Tokens (litellm_params.max_tokens)": "",
 	"Enter Max Tokens (litellm_params.max_tokens)": "",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "",
 	"Enter model tag (e.g. {{modelTag}})": "",
 	"Enter Number of Steps (e.g. 50)": "",
 	"Enter Number of Steps (e.g. 50)": "",
 	"Enter Score": "",
 	"Enter Score": "",
@@ -235,6 +239,7 @@
 	"Input commands": "",
 	"Input commands": "",
 	"Interface": "",
 	"Interface": "",
 	"Invalid Tag": "",
 	"Invalid Tag": "",
+	"Is Model Vision Capable": "",
 	"January": "",
 	"January": "",
 	"join our Discord for help.": "",
 	"join our Discord for help.": "",
 	"JSON": "",
 	"JSON": "",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "",
 	"Made by OpenWebUI Community": "",
 	"Make sure to enclose them with": "",
 	"Make sure to enclose them with": "",
 	"Manage LiteLLM Models": "",
 	"Manage LiteLLM Models": "",
+	"Manage Model Information": "",
 	"Manage Models": "",
 	"Manage Models": "",
 	"Manage Ollama Models": "",
 	"Manage Ollama Models": "",
 	"March": "",
 	"March": "",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "",
 	"Model '{{modelTag}}' is already in queue for downloading.": "",
 	"Model {{modelId}} not found": "",
 	"Model {{modelId}} not found": "",
 	"Model {{modelName}} already exists.": "",
 	"Model {{modelName}} already exists.": "",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "",
 	"Model Name": "",
 	"Model not selected": "",
 	"Model not selected": "",
 	"Model Tag Name": "",
 	"Model Tag Name": "",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "",
 	"Name your modelfile": "",
 	"New Chat": "",
 	"New Chat": "",
 	"New Password": "",
 	"New Password": "",
+	"No": "",
 	"No results found": "",
 	"No results found": "",
 	"No source available": "",
 	"No source available": "",
 	"Not factually correct": "",
 	"Not factually correct": "",
@@ -385,6 +397,7 @@
 	"Select a model": "",
 	"Select a model": "",
 	"Select an Ollama instance": "",
 	"Select an Ollama instance": "",
 	"Select model": "",
 	"Select model": "",
+	"Selected models do not support image inputs": "",
 	"Send": "",
 	"Send": "",
 	"Send a Message": "",
 	"Send a Message": "",
 	"Send message": "",
 	"Send message": "",
@@ -492,6 +505,7 @@
 	"Workspace": "",
 	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
+	"Yes": "",
 	"Yesterday": "",
 	"Yesterday": "",
 	"You": "",
 	"You": "",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "",
 	"{{modelName}} is thinking...": "",
 	"{{user}}'s Chats": "",
 	"{{user}}'s Chats": "",
 	"{{webUIName}} Backend Required": "",
 	"{{webUIName}} Backend Required": "",
+	"A selected model does not support image input": "",
 	"a user": "",
 	"a user": "",
 	"About": "",
 	"About": "",
 	"Account": "",
 	"Account": "",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "",
 	"Advanced Parameters": "",
 	"all": "",
 	"all": "",
 	"All Documents": "",
 	"All Documents": "",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "",
 	"All Users": "",
 	"Allow": "",
 	"Allow": "",
 	"Allow Chat Deletion": "",
 	"Allow Chat Deletion": "",
@@ -115,6 +117,7 @@
 	"Created at": "",
 	"Created at": "",
 	"Created At": "",
 	"Created At": "",
 	"Current Model": "",
 	"Current Model": "",
+	"Current Models": "",
 	"Current Password": "",
 	"Current Password": "",
 	"Custom": "",
 	"Custom": "",
 	"Customize Ollama models for a specific purpose": "",
 	"Customize Ollama models for a specific purpose": "",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "",
 	"Enter LiteLLM Model (litellm_params.model)": "",
 	"Enter LiteLLM Model (litellm_params.model)": "",
 	"Enter Max Tokens (litellm_params.max_tokens)": "",
 	"Enter Max Tokens (litellm_params.max_tokens)": "",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "",
 	"Enter model tag (e.g. {{modelTag}})": "",
 	"Enter Number of Steps (e.g. 50)": "",
 	"Enter Number of Steps (e.g. 50)": "",
 	"Enter Score": "",
 	"Enter Score": "",
@@ -235,6 +239,7 @@
 	"Input commands": "",
 	"Input commands": "",
 	"Interface": "",
 	"Interface": "",
 	"Invalid Tag": "",
 	"Invalid Tag": "",
+	"Is Model Vision Capable": "",
 	"January": "",
 	"January": "",
 	"join our Discord for help.": "",
 	"join our Discord for help.": "",
 	"JSON": "",
 	"JSON": "",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "",
 	"Made by OpenWebUI Community": "",
 	"Make sure to enclose them with": "",
 	"Make sure to enclose them with": "",
 	"Manage LiteLLM Models": "",
 	"Manage LiteLLM Models": "",
+	"Manage Model Information": "",
 	"Manage Models": "",
 	"Manage Models": "",
 	"Manage Ollama Models": "",
 	"Manage Ollama Models": "",
 	"March": "",
 	"March": "",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "",
 	"Model '{{modelTag}}' is already in queue for downloading.": "",
 	"Model {{modelId}} not found": "",
 	"Model {{modelId}} not found": "",
 	"Model {{modelName}} already exists.": "",
 	"Model {{modelName}} already exists.": "",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "",
 	"Model Name": "",
 	"Model not selected": "",
 	"Model not selected": "",
 	"Model Tag Name": "",
 	"Model Tag Name": "",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "",
 	"Name your modelfile": "",
 	"New Chat": "",
 	"New Chat": "",
 	"New Password": "",
 	"New Password": "",
+	"No": "",
 	"No results found": "",
 	"No results found": "",
 	"No source available": "",
 	"No source available": "",
 	"Not factually correct": "",
 	"Not factually correct": "",
@@ -385,6 +397,7 @@
 	"Select a model": "",
 	"Select a model": "",
 	"Select an Ollama instance": "",
 	"Select an Ollama instance": "",
 	"Select model": "",
 	"Select model": "",
+	"Selected models do not support image inputs": "",
 	"Send": "",
 	"Send": "",
 	"Send a Message": "",
 	"Send a Message": "",
 	"Send message": "",
 	"Send message": "",
@@ -492,6 +505,7 @@
 	"Workspace": "",
 	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
+	"Yes": "",
 	"Yesterday": "",
 	"Yesterday": "",
 	"You": "",
 	"You": "",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} está pensando...",
 	"{{modelName}} is thinking...": "{{modelName}} está pensando...",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Servidor Requerido",
 	"{{webUIName}} Backend Required": "{{webUIName}} Servidor Requerido",
+	"A selected model does not support image input": "",
 	"a user": "un usuario",
 	"a user": "un usuario",
 	"About": "Sobre nosotros",
 	"About": "Sobre nosotros",
 	"Account": "Cuenta",
 	"Account": "Cuenta",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Parámetros Avanzados",
 	"Advanced Parameters": "Parámetros Avanzados",
 	"all": "todo",
 	"all": "todo",
 	"All Documents": "Todos los Documentos",
 	"All Documents": "Todos los Documentos",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Todos los Usuarios",
 	"All Users": "Todos los Usuarios",
 	"Allow": "Permitir",
 	"Allow": "Permitir",
 	"Allow Chat Deletion": "Permitir Borrar Chats",
 	"Allow Chat Deletion": "Permitir Borrar Chats",
@@ -115,6 +117,7 @@
 	"Created at": "Creado en",
 	"Created at": "Creado en",
 	"Created At": "Creado en",
 	"Created At": "Creado en",
 	"Current Model": "Modelo Actual",
 	"Current Model": "Modelo Actual",
+	"Current Models": "",
 	"Current Password": "Contraseña Actual",
 	"Current Password": "Contraseña Actual",
 	"Custom": "Personalizado",
 	"Custom": "Personalizado",
 	"Customize Ollama models for a specific purpose": "Personaliza los modelos de Ollama para un propósito específico",
 	"Customize Ollama models for a specific purpose": "Personaliza los modelos de Ollama para un propósito específico",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Ingrese el RPM de la API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Ingrese el RPM de la API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Ingrese el modelo LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Ingrese el modelo LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Ingrese tokens máximos (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Ingrese tokens máximos (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Ingrese la etiqueta del modelo (p.ej. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Ingrese la etiqueta del modelo (p.ej. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Ingrese el número de pasos (p.ej., 50)",
 	"Enter Number of Steps (e.g. 50)": "Ingrese el número de pasos (p.ej., 50)",
 	"Enter Score": "Ingrese la puntuación",
 	"Enter Score": "Ingrese la puntuación",
@@ -235,6 +239,7 @@
 	"Input commands": "Ingresar comandos",
 	"Input commands": "Ingresar comandos",
 	"Interface": "Interfaz",
 	"Interface": "Interfaz",
 	"Invalid Tag": "Etiqueta Inválida",
 	"Invalid Tag": "Etiqueta Inválida",
+	"Is Model Vision Capable": "",
 	"January": "Enero",
 	"January": "Enero",
 	"join our Discord for help.": "Únase a nuestro Discord para obtener ayuda.",
 	"join our Discord for help.": "Únase a nuestro Discord para obtener ayuda.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Hecho por la comunidad de OpenWebUI",
 	"Made by OpenWebUI Community": "Hecho por la comunidad de OpenWebUI",
 	"Make sure to enclose them with": "Asegúrese de adjuntarlos con",
 	"Make sure to enclose them with": "Asegúrese de adjuntarlos con",
 	"Manage LiteLLM Models": "Administrar Modelos LiteLLM",
 	"Manage LiteLLM Models": "Administrar Modelos LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Administrar Modelos",
 	"Manage Models": "Administrar Modelos",
 	"Manage Ollama Models": "Administrar Modelos Ollama",
 	"Manage Ollama Models": "Administrar Modelos Ollama",
 	"March": "Marzo",
 	"March": "Marzo",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "El modelo '{{modelTag}}' ya está en cola para descargar.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "El modelo '{{modelTag}}' ya está en cola para descargar.",
 	"Model {{modelId}} not found": "El modelo {{modelId}} no fue encontrado",
 	"Model {{modelId}} not found": "El modelo {{modelId}} no fue encontrado",
 	"Model {{modelName}} already exists.": "El modelo {{modelName}} ya existe.",
 	"Model {{modelName}} already exists.": "El modelo {{modelName}} ya existe.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Se detectó la ruta del sistema de archivos del modelo. Se requiere el nombre corto del modelo para la actualización, no se puede continuar.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Se detectó la ruta del sistema de archivos del modelo. Se requiere el nombre corto del modelo para la actualización, no se puede continuar.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nombre del modelo",
 	"Model Name": "Nombre del modelo",
 	"Model not selected": "Modelo no seleccionado",
 	"Model not selected": "Modelo no seleccionado",
 	"Model Tag Name": "Nombre de la etiqueta del modelo",
 	"Model Tag Name": "Nombre de la etiqueta del modelo",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nombra tu modelfile",
 	"Name your modelfile": "Nombra tu modelfile",
 	"New Chat": "Nuevo Chat",
 	"New Chat": "Nuevo Chat",
 	"New Password": "Nueva Contraseña",
 	"New Password": "Nueva Contraseña",
+	"No": "",
 	"No results found": "No se han encontrado resultados",
 	"No results found": "No se han encontrado resultados",
 	"No source available": "No hay fuente disponible",
 	"No source available": "No hay fuente disponible",
 	"Not factually correct": "No es correcto en todos los aspectos",
 	"Not factually correct": "No es correcto en todos los aspectos",
@@ -385,6 +397,7 @@
 	"Select a model": "Selecciona un modelo",
 	"Select a model": "Selecciona un modelo",
 	"Select an Ollama instance": "Seleccione una instancia de Ollama",
 	"Select an Ollama instance": "Seleccione una instancia de Ollama",
 	"Select model": "Selecciona un modelo",
 	"Select model": "Selecciona un modelo",
+	"Selected models do not support image inputs": "",
 	"Send": "Enviar",
 	"Send": "Enviar",
 	"Send a Message": "Enviar un Mensaje",
 	"Send a Message": "Enviar un Mensaje",
 	"Send message": "Enviar Mensaje",
 	"Send message": "Enviar Mensaje",
@@ -492,6 +505,7 @@
 	"Workspace": "Espacio de trabajo",
 	"Workspace": "Espacio de trabajo",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escribe una sugerencia para un prompt (por ejemplo, ¿quién eres?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escribe una sugerencia para un prompt (por ejemplo, ¿quién eres?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escribe un resumen en 50 palabras que resuma [tema o palabra clave].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escribe un resumen en 50 palabras que resuma [tema o palabra clave].",
+	"Yes": "",
 	"Yesterday": "Ayer",
 	"Yesterday": "Ayer",
 	"You": "Usted",
 	"You": "Usted",
 	"You have no archived conversations.": "No tiene conversaciones archivadas.",
 	"You have no archived conversations.": "No tiene conversaciones archivadas.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} در حال فکر کردن است...",
 	"{{modelName}} is thinking...": "{{modelName}} در حال فکر کردن است...",
 	"{{user}}'s Chats": "{{user}} چت ها",
 	"{{user}}'s Chats": "{{user}} چت ها",
 	"{{webUIName}} Backend Required": "بکند {{webUIName}} نیاز است.",
 	"{{webUIName}} Backend Required": "بکند {{webUIName}} نیاز است.",
+	"A selected model does not support image input": "",
 	"a user": "یک کاربر",
 	"a user": "یک کاربر",
 	"About": "درباره",
 	"About": "درباره",
 	"Account": "حساب کاربری",
 	"Account": "حساب کاربری",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "پارامترهای پیشرفته",
 	"Advanced Parameters": "پارامترهای پیشرفته",
 	"all": "همه",
 	"all": "همه",
 	"All Documents": "تمام سند ها",
 	"All Documents": "تمام سند ها",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "همه کاربران",
 	"All Users": "همه کاربران",
 	"Allow": "اجازه دادن",
 	"Allow": "اجازه دادن",
 	"Allow Chat Deletion": "اجازه حذف گپ",
 	"Allow Chat Deletion": "اجازه حذف گپ",
@@ -115,6 +117,7 @@
 	"Created at": "ایجاد شده در",
 	"Created at": "ایجاد شده در",
 	"Created At": "ایجاد شده در",
 	"Created At": "ایجاد شده در",
 	"Current Model": "مدل فعلی",
 	"Current Model": "مدل فعلی",
+	"Current Models": "",
 	"Current Password": "رمز عبور فعلی",
 	"Current Password": "رمز عبور فعلی",
 	"Custom": "دلخواه",
 	"Custom": "دلخواه",
 	"Customize Ollama models for a specific purpose": "مدل های اولاما را برای یک هدف خاص سفارشی کنید",
 	"Customize Ollama models for a specific purpose": "مدل های اولاما را برای یک هدف خاص سفارشی کنید",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "RPM API مربوط به LiteLLM را وارد کنید (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "RPM API مربوط به LiteLLM را وارد کنید (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "مدل مربوط به LiteLLM را وارد کنید (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "مدل مربوط به LiteLLM را وارد کنید (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "حداکثر تعداد توکن را وارد کنید (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "حداکثر تعداد توکن را وارد کنید (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "تگ مدل را وارد کنید (مثلا {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "تگ مدل را وارد کنید (مثلا {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "تعداد گام ها را وارد کنید (مثال: 50)",
 	"Enter Number of Steps (e.g. 50)": "تعداد گام ها را وارد کنید (مثال: 50)",
 	"Enter Score": "امتیاز را وارد کنید",
 	"Enter Score": "امتیاز را وارد کنید",
@@ -235,6 +239,7 @@
 	"Input commands": "ورودی دستورات",
 	"Input commands": "ورودی دستورات",
 	"Interface": "رابط",
 	"Interface": "رابط",
 	"Invalid Tag": "تگ نامعتبر",
 	"Invalid Tag": "تگ نامعتبر",
+	"Is Model Vision Capable": "",
 	"January": "ژانویه",
 	"January": "ژانویه",
 	"join our Discord for help.": "برای کمک به دیسکورد ما بپیوندید.",
 	"join our Discord for help.": "برای کمک به دیسکورد ما بپیوندید.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "ساخته شده توسط OpenWebUI Community",
 	"Made by OpenWebUI Community": "ساخته شده توسط OpenWebUI Community",
 	"Make sure to enclose them with": "مطمئن شوید که آنها را با این محصور کنید:",
 	"Make sure to enclose them with": "مطمئن شوید که آنها را با این محصور کنید:",
 	"Manage LiteLLM Models": "Manage LiteLLM Models",
 	"Manage LiteLLM Models": "Manage LiteLLM Models",
+	"Manage Model Information": "",
 	"Manage Models": "مدیریت مدل\u200cها",
 	"Manage Models": "مدیریت مدل\u200cها",
 	"Manage Ollama Models": "مدیریت مدل\u200cهای اولاما",
 	"Manage Ollama Models": "مدیریت مدل\u200cهای اولاما",
 	"March": "مارچ",
 	"March": "مارچ",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "مدل '{{modelTag}}' در حال حاضر در صف برای دانلود است.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "مدل '{{modelTag}}' در حال حاضر در صف برای دانلود است.",
 	"Model {{modelId}} not found": "مدل {{modelId}} یافت نشد",
 	"Model {{modelId}} not found": "مدل {{modelId}} یافت نشد",
 	"Model {{modelName}} already exists.": "مدل {{modelName}} در حال حاضر وجود دارد.",
 	"Model {{modelName}} already exists.": "مدل {{modelName}} در حال حاضر وجود دارد.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "مسیر فایل سیستم مدل یافت شد. برای بروزرسانی نیاز است نام کوتاه مدل وجود داشته باشد.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "مسیر فایل سیستم مدل یافت شد. برای بروزرسانی نیاز است نام کوتاه مدل وجود داشته باشد.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "نام مدل",
 	"Model Name": "نام مدل",
 	"Model not selected": "مدل انتخاب نشده",
 	"Model not selected": "مدل انتخاب نشده",
 	"Model Tag Name": "نام تگ مدل",
 	"Model Tag Name": "نام تگ مدل",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "فایل مدل را نام\u200cگذاری کنید",
 	"Name your modelfile": "فایل مدل را نام\u200cگذاری کنید",
 	"New Chat": "گپ جدید",
 	"New Chat": "گپ جدید",
 	"New Password": "رمز عبور جدید",
 	"New Password": "رمز عبور جدید",
+	"No": "",
 	"No results found": "نتیجه\u200cای یافت نشد",
 	"No results found": "نتیجه\u200cای یافت نشد",
 	"No source available": "منبعی در دسترس نیست",
 	"No source available": "منبعی در دسترس نیست",
 	"Not factually correct": "اشتباهی فکری نیست",
 	"Not factually correct": "اشتباهی فکری نیست",
@@ -385,6 +397,7 @@
 	"Select a model": "انتخاب یک مدل",
 	"Select a model": "انتخاب یک مدل",
 	"Select an Ollama instance": "انتخاب یک نمونه از اولاما",
 	"Select an Ollama instance": "انتخاب یک نمونه از اولاما",
 	"Select model": "انتخاب یک مدل",
 	"Select model": "انتخاب یک مدل",
+	"Selected models do not support image inputs": "",
 	"Send": "ارسال",
 	"Send": "ارسال",
 	"Send a Message": "ارسال یک پیام",
 	"Send a Message": "ارسال یک پیام",
 	"Send message": "ارسال پیام",
 	"Send message": "ارسال پیام",
@@ -492,6 +505,7 @@
 	"Workspace": "محیط کار",
 	"Workspace": "محیط کار",
 	"Write a prompt suggestion (e.g. Who are you?)": "یک پیشنهاد پرامپت بنویسید (مثلاً شما کی هستید؟)",
 	"Write a prompt suggestion (e.g. Who are you?)": "یک پیشنهاد پرامپت بنویسید (مثلاً شما کی هستید؟)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "خلاصه ای در 50 کلمه بنویسید که [موضوع یا کلمه کلیدی] را خلاصه کند.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "خلاصه ای در 50 کلمه بنویسید که [موضوع یا کلمه کلیدی] را خلاصه کند.",
+	"Yes": "",
 	"Yesterday": "دیروز",
 	"Yesterday": "دیروز",
 	"You": "شما",
 	"You": "شما",
 	"You have no archived conversations.": "شما هیچ گفتگوی ذخیره شده ندارید.",
 	"You have no archived conversations.": "شما هیچ گفتگوی ذخیره شده ندارید.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} miettii...",
 	"{{modelName}} is thinking...": "{{modelName}} miettii...",
 	"{{user}}'s Chats": "{{user}}:n keskustelut",
 	"{{user}}'s Chats": "{{user}}:n keskustelut",
 	"{{webUIName}} Backend Required": "{{webUIName}} backend vaaditaan",
 	"{{webUIName}} Backend Required": "{{webUIName}} backend vaaditaan",
+	"A selected model does not support image input": "",
 	"a user": "käyttäjä",
 	"a user": "käyttäjä",
 	"About": "Tietoja",
 	"About": "Tietoja",
 	"Account": "Tili",
 	"Account": "Tili",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Edistyneet parametrit",
 	"Advanced Parameters": "Edistyneet parametrit",
 	"all": "kaikki",
 	"all": "kaikki",
 	"All Documents": "Kaikki asiakirjat",
 	"All Documents": "Kaikki asiakirjat",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Kaikki käyttäjät",
 	"All Users": "Kaikki käyttäjät",
 	"Allow": "Salli",
 	"Allow": "Salli",
 	"Allow Chat Deletion": "Salli keskustelujen poisto",
 	"Allow Chat Deletion": "Salli keskustelujen poisto",
@@ -115,6 +117,7 @@
 	"Created at": "Luotu",
 	"Created at": "Luotu",
 	"Created At": "Luotu",
 	"Created At": "Luotu",
 	"Current Model": "Nykyinen malli",
 	"Current Model": "Nykyinen malli",
+	"Current Models": "",
 	"Current Password": "Nykyinen salasana",
 	"Current Password": "Nykyinen salasana",
 	"Custom": "Mukautettu",
 	"Custom": "Mukautettu",
 	"Customize Ollama models for a specific purpose": "Mukauta Ollama-malleja tiettyyn tarkoitukseen",
 	"Customize Ollama models for a specific purpose": "Mukauta Ollama-malleja tiettyyn tarkoitukseen",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Syötä LiteLLM-APIn RPM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Syötä LiteLLM-APIn RPM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Syötä LiteLLM-malli (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Syötä LiteLLM-malli (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Syötä maksimitokenit (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Syötä maksimitokenit (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Syötä mallitagi (esim. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Syötä mallitagi (esim. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Syötä askelien määrä (esim. 50)",
 	"Enter Number of Steps (e.g. 50)": "Syötä askelien määrä (esim. 50)",
 	"Enter Score": "Syötä pisteet",
 	"Enter Score": "Syötä pisteet",
@@ -235,6 +239,7 @@
 	"Input commands": "Syötä komennot",
 	"Input commands": "Syötä komennot",
 	"Interface": "Käyttöliittymä",
 	"Interface": "Käyttöliittymä",
 	"Invalid Tag": "Virheellinen tagi",
 	"Invalid Tag": "Virheellinen tagi",
+	"Is Model Vision Capable": "",
 	"January": "tammikuu",
 	"January": "tammikuu",
 	"join our Discord for help.": "liity Discordiimme saadaksesi apua.",
 	"join our Discord for help.": "liity Discordiimme saadaksesi apua.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Tehnyt OpenWebUI-yhteisö",
 	"Made by OpenWebUI Community": "Tehnyt OpenWebUI-yhteisö",
 	"Make sure to enclose them with": "Varmista, että suljet ne",
 	"Make sure to enclose them with": "Varmista, että suljet ne",
 	"Manage LiteLLM Models": "Hallitse LiteLLM-malleja",
 	"Manage LiteLLM Models": "Hallitse LiteLLM-malleja",
+	"Manage Model Information": "",
 	"Manage Models": "Hallitse malleja",
 	"Manage Models": "Hallitse malleja",
 	"Manage Ollama Models": "Hallitse Ollama-malleja",
 	"Manage Ollama Models": "Hallitse Ollama-malleja",
 	"March": "maaliskuu",
 	"March": "maaliskuu",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Malli '{{modelTag}}' on jo jonossa ladattavaksi.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Malli '{{modelTag}}' on jo jonossa ladattavaksi.",
 	"Model {{modelId}} not found": "Mallia {{modelId}} ei löytynyt",
 	"Model {{modelId}} not found": "Mallia {{modelId}} ei löytynyt",
 	"Model {{modelName}} already exists.": "Malli {{modelName}} on jo olemassa.",
 	"Model {{modelName}} already exists.": "Malli {{modelName}} on jo olemassa.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Mallin tiedostojärjestelmäpolku havaittu. Mallin lyhytnimi vaaditaan päivitykseen, ei voi jatkaa.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Mallin tiedostojärjestelmäpolku havaittu. Mallin lyhytnimi vaaditaan päivitykseen, ei voi jatkaa.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Mallin nimi",
 	"Model Name": "Mallin nimi",
 	"Model not selected": "Mallia ei valittu",
 	"Model not selected": "Mallia ei valittu",
 	"Model Tag Name": "Mallitagin nimi",
 	"Model Tag Name": "Mallitagin nimi",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nimeä mallitiedostosi",
 	"Name your modelfile": "Nimeä mallitiedostosi",
 	"New Chat": "Uusi keskustelu",
 	"New Chat": "Uusi keskustelu",
 	"New Password": "Uusi salasana",
 	"New Password": "Uusi salasana",
+	"No": "",
 	"No results found": "Ei tuloksia",
 	"No results found": "Ei tuloksia",
 	"No source available": "Ei lähdettä saatavilla",
 	"No source available": "Ei lähdettä saatavilla",
 	"Not factually correct": "Ei faktisesti oikein",
 	"Not factually correct": "Ei faktisesti oikein",
@@ -385,6 +397,7 @@
 	"Select a model": "Valitse malli",
 	"Select a model": "Valitse malli",
 	"Select an Ollama instance": "Valitse Ollama-instanssi",
 	"Select an Ollama instance": "Valitse Ollama-instanssi",
 	"Select model": "Valitse malli",
 	"Select model": "Valitse malli",
+	"Selected models do not support image inputs": "",
 	"Send": "Lähetä",
 	"Send": "Lähetä",
 	"Send a Message": "Lähetä viesti",
 	"Send a Message": "Lähetä viesti",
 	"Send message": "Lähetä viesti",
 	"Send message": "Lähetä viesti",
@@ -492,6 +505,7 @@
 	"Workspace": "Työtilat",
 	"Workspace": "Työtilat",
 	"Write a prompt suggestion (e.g. Who are you?)": "Kirjoita ehdotettu kehote (esim. Kuka olet?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Kirjoita ehdotettu kehote (esim. Kuka olet?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Kirjoita 50 sanan yhteenveto, joka tiivistää [aihe tai avainsana].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Kirjoita 50 sanan yhteenveto, joka tiivistää [aihe tai avainsana].",
+	"Yes": "",
 	"Yesterday": "Eilen",
 	"Yesterday": "Eilen",
 	"You": "Sinä",
 	"You": "Sinä",
 	"You have no archived conversations.": "Sinulla ei ole arkistoituja keskusteluja.",
 	"You have no archived conversations.": "Sinulla ei ole arkistoituja keskusteluja.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} réfléchit...",
 	"{{modelName}} is thinking...": "{{modelName}} réfléchit...",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
+	"A selected model does not support image input": "",
 	"a user": "un utilisateur",
 	"a user": "un utilisateur",
 	"About": "À propos",
 	"About": "À propos",
 	"Account": "Compte",
 	"Account": "Compte",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Paramètres avancés",
 	"Advanced Parameters": "Paramètres avancés",
 	"all": "tous",
 	"all": "tous",
 	"All Documents": "Tous les documents",
 	"All Documents": "Tous les documents",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Tous les utilisateurs",
 	"All Users": "Tous les utilisateurs",
 	"Allow": "Autoriser",
 	"Allow": "Autoriser",
 	"Allow Chat Deletion": "Autoriser la suppression des discussions",
 	"Allow Chat Deletion": "Autoriser la suppression des discussions",
@@ -115,6 +117,7 @@
 	"Created at": "Créé le",
 	"Created at": "Créé le",
 	"Created At": "Créé le",
 	"Created At": "Créé le",
 	"Current Model": "Modèle actuel",
 	"Current Model": "Modèle actuel",
+	"Current Models": "",
 	"Current Password": "Mot de passe actuel",
 	"Current Password": "Mot de passe actuel",
 	"Custom": "Personnalisé",
 	"Custom": "Personnalisé",
 	"Customize Ollama models for a specific purpose": "Personnaliser les modèles Ollama pour un objectif spécifique",
 	"Customize Ollama models for a specific purpose": "Personnaliser les modèles Ollama pour un objectif spécifique",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Entrez le RPM de l'API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Entrez le RPM de l'API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Entrez le modèle LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Entrez le modèle LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Entrez le nombre max de tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Entrez le nombre max de tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Entrez le tag du modèle (p. ex. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Entrez le tag du modèle (p. ex. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Entrez le nombre d'étapes (p. ex. 50)",
 	"Enter Number of Steps (e.g. 50)": "Entrez le nombre d'étapes (p. ex. 50)",
 	"Enter Score": "Entrez le score",
 	"Enter Score": "Entrez le score",
@@ -235,6 +239,7 @@
 	"Input commands": "Entrez des commandes d'entrée",
 	"Input commands": "Entrez des commandes d'entrée",
 	"Interface": "Interface",
 	"Interface": "Interface",
 	"Invalid Tag": "Tag invalide",
 	"Invalid Tag": "Tag invalide",
+	"Is Model Vision Capable": "",
 	"January": "Janvier",
 	"January": "Janvier",
 	"join our Discord for help.": "rejoignez notre Discord pour obtenir de l'aide.",
 	"join our Discord for help.": "rejoignez notre Discord pour obtenir de l'aide.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
 	"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
 	"Make sure to enclose them with": "Assurez-vous de les entourer avec",
 	"Make sure to enclose them with": "Assurez-vous de les entourer avec",
 	"Manage LiteLLM Models": "Gérer les modèles LiteLLM",
 	"Manage LiteLLM Models": "Gérer les modèles LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Gérer les modèles",
 	"Manage Models": "Gérer les modèles",
 	"Manage Ollama Models": "Gérer les modèles Ollama",
 	"Manage Ollama Models": "Gérer les modèles Ollama",
 	"March": "Mars",
 	"March": "Mars",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.",
 	"Model {{modelId}} not found": "Modèle {{modelId}} non trouvé",
 	"Model {{modelId}} not found": "Modèle {{modelId}} non trouvé",
 	"Model {{modelName}} already exists.": "Le modèle {{modelName}} existe déjà.",
 	"Model {{modelName}} already exists.": "Le modèle {{modelName}} existe déjà.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Le chemin du système de fichiers du modèle a été détecté. Le nom court du modèle est nécessaire pour la mise à jour, impossible de continuer.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Le chemin du système de fichiers du modèle a été détecté. Le nom court du modèle est nécessaire pour la mise à jour, impossible de continuer.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nom du modèle",
 	"Model Name": "Nom du modèle",
 	"Model not selected": "Modèle non sélectionné",
 	"Model not selected": "Modèle non sélectionné",
 	"Model Tag Name": "Nom de tag du modèle",
 	"Model Tag Name": "Nom de tag du modèle",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nommez votre fichier de modèle",
 	"Name your modelfile": "Nommez votre fichier de modèle",
 	"New Chat": "Nouvelle discussion",
 	"New Chat": "Nouvelle discussion",
 	"New Password": "Nouveau mot de passe",
 	"New Password": "Nouveau mot de passe",
+	"No": "",
 	"No results found": "Aucun résultat trouvé",
 	"No results found": "Aucun résultat trouvé",
 	"No source available": "Aucune source disponible",
 	"No source available": "Aucune source disponible",
 	"Not factually correct": "Non, pas exactement correct",
 	"Not factually correct": "Non, pas exactement correct",
@@ -385,6 +397,7 @@
 	"Select a model": "Sélectionnez un modèle",
 	"Select a model": "Sélectionnez un modèle",
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select model": "Sélectionnez un modèle",
 	"Select model": "Sélectionnez un modèle",
+	"Selected models do not support image inputs": "",
 	"Send": "Envoyer",
 	"Send": "Envoyer",
 	"Send a Message": "Envoyer un message",
 	"Send a Message": "Envoyer un message",
 	"Send message": "Envoyer un message",
 	"Send message": "Envoyer un message",
@@ -492,6 +505,7 @@
 	"Workspace": "Espace de travail",
 	"Workspace": "Espace de travail",
 	"Write a prompt suggestion (e.g. Who are you?)": "Rédigez une suggestion de prompt (p. ex. Qui êtes-vous ?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Rédigez une suggestion de prompt (p. ex. Qui êtes-vous ?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Rédigez un résumé en 50 mots qui résume [sujet ou mot-clé].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Rédigez un résumé en 50 mots qui résume [sujet ou mot-clé].",
+	"Yes": "",
 	"Yesterday": "hier",
 	"Yesterday": "hier",
 	"You": "Vous",
 	"You": "Vous",
 	"You have no archived conversations.": "Vous n'avez aucune conversation archivée.",
 	"You have no archived conversations.": "Vous n'avez aucune conversation archivée.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} réfléchit...",
 	"{{modelName}} is thinking...": "{{modelName}} réfléchit...",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
+	"A selected model does not support image input": "",
 	"a user": "un utilisateur",
 	"a user": "un utilisateur",
 	"About": "À propos",
 	"About": "À propos",
 	"Account": "Compte",
 	"Account": "Compte",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Paramètres avancés",
 	"Advanced Parameters": "Paramètres avancés",
 	"all": "tous",
 	"all": "tous",
 	"All Documents": "Tous les documents",
 	"All Documents": "Tous les documents",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Tous les utilisateurs",
 	"All Users": "Tous les utilisateurs",
 	"Allow": "Autoriser",
 	"Allow": "Autoriser",
 	"Allow Chat Deletion": "Autoriser la suppression du chat",
 	"Allow Chat Deletion": "Autoriser la suppression du chat",
@@ -115,6 +117,7 @@
 	"Created at": "Créé le",
 	"Created at": "Créé le",
 	"Created At": "Créé le",
 	"Created At": "Créé le",
 	"Current Model": "Modèle actuel",
 	"Current Model": "Modèle actuel",
+	"Current Models": "",
 	"Current Password": "Mot de passe actuel",
 	"Current Password": "Mot de passe actuel",
 	"Custom": "Personnalisé",
 	"Custom": "Personnalisé",
 	"Customize Ollama models for a specific purpose": "Personnaliser les modèles Ollama pour un objectif spécifique",
 	"Customize Ollama models for a specific purpose": "Personnaliser les modèles Ollama pour un objectif spécifique",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Entrez le RPM de l'API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Entrez le RPM de l'API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Entrez le modèle LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Entrez le modèle LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Entrez le nombre max de tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Entrez le nombre max de tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Entrez le tag du modèle (p. ex. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Entrez le tag du modèle (p. ex. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Entrez le nombre d'étapes (p. ex. 50)",
 	"Enter Number of Steps (e.g. 50)": "Entrez le nombre d'étapes (p. ex. 50)",
 	"Enter Score": "Entrez le score",
 	"Enter Score": "Entrez le score",
@@ -235,6 +239,7 @@
 	"Input commands": "Entrez les commandes d'entrée",
 	"Input commands": "Entrez les commandes d'entrée",
 	"Interface": "Interface",
 	"Interface": "Interface",
 	"Invalid Tag": "Tag invalide",
 	"Invalid Tag": "Tag invalide",
+	"Is Model Vision Capable": "",
 	"January": "Janvier",
 	"January": "Janvier",
 	"join our Discord for help.": "rejoignez notre Discord pour obtenir de l'aide.",
 	"join our Discord for help.": "rejoignez notre Discord pour obtenir de l'aide.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
 	"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
 	"Make sure to enclose them with": "Assurez-vous de les entourer avec",
 	"Make sure to enclose them with": "Assurez-vous de les entourer avec",
 	"Manage LiteLLM Models": "Gérer les modèles LiteLLM",
 	"Manage LiteLLM Models": "Gérer les modèles LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Gérer les modèles",
 	"Manage Models": "Gérer les modèles",
 	"Manage Ollama Models": "Gérer les modèles Ollama",
 	"Manage Ollama Models": "Gérer les modèles Ollama",
 	"March": "Mars",
 	"March": "Mars",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.",
 	"Model {{modelId}} not found": "Modèle {{modelId}} non trouvé",
 	"Model {{modelId}} not found": "Modèle {{modelId}} non trouvé",
 	"Model {{modelName}} already exists.": "Le modèle {{modelName}} existe déjà.",
 	"Model {{modelName}} already exists.": "Le modèle {{modelName}} existe déjà.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Le chemin du système de fichiers du modèle a été détecté. Le nom court du modèle est nécessaire pour la mise à jour, impossible de continuer.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Le chemin du système de fichiers du modèle a été détecté. Le nom court du modèle est nécessaire pour la mise à jour, impossible de continuer.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nom du modèle",
 	"Model Name": "Nom du modèle",
 	"Model not selected": "Modèle non sélectionné",
 	"Model not selected": "Modèle non sélectionné",
 	"Model Tag Name": "Nom de tag du modèle",
 	"Model Tag Name": "Nom de tag du modèle",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nommez votre fichier de modèle",
 	"Name your modelfile": "Nommez votre fichier de modèle",
 	"New Chat": "Nouveau chat",
 	"New Chat": "Nouveau chat",
 	"New Password": "Nouveau mot de passe",
 	"New Password": "Nouveau mot de passe",
+	"No": "",
 	"No results found": "Aucun résultat trouvé",
 	"No results found": "Aucun résultat trouvé",
 	"No source available": "Aucune source disponible",
 	"No source available": "Aucune source disponible",
 	"Not factually correct": "Non, pas exactement correct",
 	"Not factually correct": "Non, pas exactement correct",
@@ -385,6 +397,7 @@
 	"Select a model": "Sélectionner un modèle",
 	"Select a model": "Sélectionner un modèle",
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select model": "Sélectionner un modèle",
 	"Select model": "Sélectionner un modèle",
+	"Selected models do not support image inputs": "",
 	"Send": "Envoyer",
 	"Send": "Envoyer",
 	"Send a Message": "Envoyer un message",
 	"Send a Message": "Envoyer un message",
 	"Send message": "Envoyer un message",
 	"Send message": "Envoyer un message",
@@ -492,6 +505,7 @@
 	"Workspace": "Espace de travail",
 	"Workspace": "Espace de travail",
 	"Write a prompt suggestion (e.g. Who are you?)": "Écrivez un prompt (e.x. Qui est-tu ?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Écrivez un prompt (e.x. Qui est-tu ?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Ecrivez un résumé en 50 mots [sujet ou mot-clé]",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Ecrivez un résumé en 50 mots [sujet ou mot-clé]",
+	"Yes": "",
 	"Yesterday": "hier",
 	"Yesterday": "hier",
 	"You": "Vous",
 	"You": "Vous",
 	"You have no archived conversations.": "Vous n'avez aucune conversation archivée.",
 	"You have no archived conversations.": "Vous n'avez aucune conversation archivée.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} חושב...",
 	"{{modelName}} is thinking...": "{{modelName}} חושב...",
 	"{{user}}'s Chats": "צ'אטים של {{user}}",
 	"{{user}}'s Chats": "צ'אטים של {{user}}",
 	"{{webUIName}} Backend Required": "נדרש Backend של {{webUIName}}",
 	"{{webUIName}} Backend Required": "נדרש Backend של {{webUIName}}",
+	"A selected model does not support image input": "",
 	"a user": "משתמש",
 	"a user": "משתמש",
 	"About": "אודות",
 	"About": "אודות",
 	"Account": "חשבון",
 	"Account": "חשבון",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "פרמטרים מתקדמים",
 	"Advanced Parameters": "פרמטרים מתקדמים",
 	"all": "הכל",
 	"all": "הכל",
 	"All Documents": "כל המסמכים",
 	"All Documents": "כל המסמכים",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "כל המשתמשים",
 	"All Users": "כל המשתמשים",
 	"Allow": "אפשר",
 	"Allow": "אפשר",
 	"Allow Chat Deletion": "אפשר מחיקת צ'אט",
 	"Allow Chat Deletion": "אפשר מחיקת צ'אט",
@@ -115,6 +117,7 @@
 	"Created at": "נוצר ב",
 	"Created at": "נוצר ב",
 	"Created At": "נוצר ב",
 	"Created At": "נוצר ב",
 	"Current Model": "המודל הנוכחי",
 	"Current Model": "המודל הנוכחי",
+	"Current Models": "",
 	"Current Password": "הסיסמה הנוכחית",
 	"Current Password": "הסיסמה הנוכחית",
 	"Custom": "מותאם אישית",
 	"Custom": "מותאם אישית",
 	"Customize Ollama models for a specific purpose": "התאמה אישית של מודלים של Ollama למטרה מסוימת",
 	"Customize Ollama models for a specific purpose": "התאמה אישית של מודלים של Ollama למטרה מסוימת",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "הזן RPM של API של LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "הזן RPM של API של LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "הזן מודל LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "הזן מודל LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "הזן מספר מקסימלי של טוקנים (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "הזן מספר מקסימלי של טוקנים (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "הזן תג מודל (למשל {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "הזן תג מודל (למשל {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "הזן מספר שלבים (למשל 50)",
 	"Enter Number of Steps (e.g. 50)": "הזן מספר שלבים (למשל 50)",
 	"Enter Score": "הזן ציון",
 	"Enter Score": "הזן ציון",
@@ -235,6 +239,7 @@
 	"Input commands": "פקודות קלט",
 	"Input commands": "פקודות קלט",
 	"Interface": "ממשק",
 	"Interface": "ממשק",
 	"Invalid Tag": "תג לא חוקי",
 	"Invalid Tag": "תג לא חוקי",
+	"Is Model Vision Capable": "",
 	"January": "ינואר",
 	"January": "ינואר",
 	"join our Discord for help.": "הצטרף ל-Discord שלנו לעזרה.",
 	"join our Discord for help.": "הצטרף ל-Discord שלנו לעזרה.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "נוצר על ידי קהילת OpenWebUI",
 	"Made by OpenWebUI Community": "נוצר על ידי קהילת OpenWebUI",
 	"Make sure to enclose them with": "ודא להקיף אותם עם",
 	"Make sure to enclose them with": "ודא להקיף אותם עם",
 	"Manage LiteLLM Models": "נהל מודלים של LiteLLM",
 	"Manage LiteLLM Models": "נהל מודלים של LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "נהל מודלים",
 	"Manage Models": "נהל מודלים",
 	"Manage Ollama Models": "נהל מודלים של Ollama",
 	"Manage Ollama Models": "נהל מודלים של Ollama",
 	"March": "מרץ",
 	"March": "מרץ",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "המודל '{{modelTag}}' כבר בתור להורדה.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "המודל '{{modelTag}}' כבר בתור להורדה.",
 	"Model {{modelId}} not found": "המודל {{modelId}} לא נמצא",
 	"Model {{modelId}} not found": "המודל {{modelId}} לא נמצא",
 	"Model {{modelName}} already exists.": "המודל {{modelName}} כבר קיים.",
 	"Model {{modelName}} already exists.": "המודל {{modelName}} כבר קיים.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "נתיב מערכת הקבצים של המודל זוהה. נדרש שם קצר של המודל לעדכון, לא ניתן להמשיך.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "נתיב מערכת הקבצים של המודל זוהה. נדרש שם קצר של המודל לעדכון, לא ניתן להמשיך.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "שם המודל",
 	"Model Name": "שם המודל",
 	"Model not selected": "לא נבחר מודל",
 	"Model not selected": "לא נבחר מודל",
 	"Model Tag Name": "שם תג המודל",
 	"Model Tag Name": "שם תג המודל",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "תן שם לקובץ המודל שלך",
 	"Name your modelfile": "תן שם לקובץ המודל שלך",
 	"New Chat": "צ'אט חדש",
 	"New Chat": "צ'אט חדש",
 	"New Password": "סיסמה חדשה",
 	"New Password": "סיסמה חדשה",
+	"No": "",
 	"No results found": "לא נמצאו תוצאות",
 	"No results found": "לא נמצאו תוצאות",
 	"No source available": "אין מקור זמין",
 	"No source available": "אין מקור זמין",
 	"Not factually correct": "לא נכון מבחינה עובדתית",
 	"Not factually correct": "לא נכון מבחינה עובדתית",
@@ -385,6 +397,7 @@
 	"Select a model": "בחר מודל",
 	"Select a model": "בחר מודל",
 	"Select an Ollama instance": "בחר מופע של Ollama",
 	"Select an Ollama instance": "בחר מופע של Ollama",
 	"Select model": "בחר מודל",
 	"Select model": "בחר מודל",
+	"Selected models do not support image inputs": "",
 	"Send": "שלח",
 	"Send": "שלח",
 	"Send a Message": "שלח הודעה",
 	"Send a Message": "שלח הודעה",
 	"Send message": "שלח הודעה",
 	"Send message": "שלח הודעה",
@@ -492,6 +505,7 @@
 	"Workspace": "סביבה",
 	"Workspace": "סביבה",
 	"Write a prompt suggestion (e.g. Who are you?)": "כתוב הצעה מהירה (למשל, מי אתה?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "כתוב הצעה מהירה (למשל, מי אתה?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "כתוב סיכום ב-50 מילים שמסכם [נושא או מילת מפתח].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "כתוב סיכום ב-50 מילים שמסכם [נושא או מילת מפתח].",
+	"Yes": "",
 	"Yesterday": "אתמול",
 	"Yesterday": "אתמול",
 	"You": "אתה",
 	"You": "אתה",
 	"You have no archived conversations.": "אין לך שיחות בארכיון.",
 	"You have no archived conversations.": "אין לך שיחות בארכיון.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} सोच रहा है...",
 	"{{modelName}} is thinking...": "{{modelName}} सोच रहा है...",
 	"{{user}}'s Chats": "{{user}} की चैट",
 	"{{user}}'s Chats": "{{user}} की चैट",
 	"{{webUIName}} Backend Required": "{{webUIName}} बैकएंड आवश्यक",
 	"{{webUIName}} Backend Required": "{{webUIName}} बैकएंड आवश्यक",
+	"A selected model does not support image input": "",
 	"a user": "एक उपयोगकर्ता",
 	"a user": "एक उपयोगकर्ता",
 	"About": "हमारे बारे में",
 	"About": "हमारे बारे में",
 	"Account": "खाता",
 	"Account": "खाता",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "उन्नत पैरामीटर",
 	"Advanced Parameters": "उन्नत पैरामीटर",
 	"all": "सभी",
 	"all": "सभी",
 	"All Documents": "सभी डॉक्यूमेंट्स",
 	"All Documents": "सभी डॉक्यूमेंट्स",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "सभी उपयोगकर्ता",
 	"All Users": "सभी उपयोगकर्ता",
 	"Allow": "अनुमति दें",
 	"Allow": "अनुमति दें",
 	"Allow Chat Deletion": "चैट हटाने की अनुमति दें",
 	"Allow Chat Deletion": "चैट हटाने की अनुमति दें",
@@ -115,6 +117,7 @@
 	"Created at": "किस समय बनाया गया",
 	"Created at": "किस समय बनाया गया",
 	"Created At": "किस समय बनाया गया",
 	"Created At": "किस समय बनाया गया",
 	"Current Model": "वर्तमान मॉडल",
 	"Current Model": "वर्तमान मॉडल",
+	"Current Models": "",
 	"Current Password": "वर्तमान पासवर्ड",
 	"Current Password": "वर्तमान पासवर्ड",
 	"Custom": "कस्टम संस्करण",
 	"Custom": "कस्टम संस्करण",
 	"Customize Ollama models for a specific purpose": "किसी विशिष्ट उद्देश्य के लिए ओलामा मॉडल को अनुकूलित करें",
 	"Customize Ollama models for a specific purpose": "किसी विशिष्ट उद्देश्य के लिए ओलामा मॉडल को अनुकूलित करें",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM दर्ज करें (litellm_params.rpm) ",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM दर्ज करें (litellm_params.rpm) ",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM Model दर्ज करें (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM Model दर्ज करें (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Max Tokens दर्ज करें (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Max Tokens दर्ज करें (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Model tag दर्ज करें (उदा. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Model tag दर्ज करें (उदा. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "चरणों की संख्या दर्ज करें (उदा. 50)",
 	"Enter Number of Steps (e.g. 50)": "चरणों की संख्या दर्ज करें (उदा. 50)",
 	"Enter Score": "स्कोर दर्ज करें",
 	"Enter Score": "स्कोर दर्ज करें",
@@ -235,6 +239,7 @@
 	"Input commands": "इनपुट क命",
 	"Input commands": "इनपुट क命",
 	"Interface": "इंटरफेस",
 	"Interface": "इंटरफेस",
 	"Invalid Tag": "अवैध टैग",
 	"Invalid Tag": "अवैध टैग",
+	"Is Model Vision Capable": "",
 	"January": "जनवरी",
 	"January": "जनवरी",
 	"join our Discord for help.": "मदद के लिए हमारे डिस्कोर्ड में शामिल हों।",
 	"join our Discord for help.": "मदद के लिए हमारे डिस्कोर्ड में शामिल हों।",
 	"JSON": "ज्ञान प्रकार",
 	"JSON": "ज्ञान प्रकार",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "OpenWebUI समुदाय द्वारा निर्मित",
 	"Made by OpenWebUI Community": "OpenWebUI समुदाय द्वारा निर्मित",
 	"Make sure to enclose them with": "उन्हें संलग्न करना सुनिश्चित करें",
 	"Make sure to enclose them with": "उन्हें संलग्न करना सुनिश्चित करें",
 	"Manage LiteLLM Models": "LiteLLM मॉडल प्रबंधित करें",
 	"Manage LiteLLM Models": "LiteLLM मॉडल प्रबंधित करें",
+	"Manage Model Information": "",
 	"Manage Models": "मॉडल प्रबंधित करें",
 	"Manage Models": "मॉडल प्रबंधित करें",
 	"Manage Ollama Models": "Ollama मॉडल प्रबंधित करें",
 	"Manage Ollama Models": "Ollama मॉडल प्रबंधित करें",
 	"March": "मार्च",
 	"March": "मार्च",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "मॉडल '{{modelTag}}' पहले से ही डाउनलोड करने के लिए कतार में है।",
 	"Model '{{modelTag}}' is already in queue for downloading.": "मॉडल '{{modelTag}}' पहले से ही डाउनलोड करने के लिए कतार में है।",
 	"Model {{modelId}} not found": "मॉडल {{modelId}} नहीं मिला",
 	"Model {{modelId}} not found": "मॉडल {{modelId}} नहीं मिला",
 	"Model {{modelName}} already exists.": "मॉडल {{modelName}} पहले से मौजूद है।",
 	"Model {{modelName}} already exists.": "मॉडल {{modelName}} पहले से मौजूद है।",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "मॉडल फ़ाइल सिस्टम पथ का पता चला. अद्यतन के लिए मॉडल संक्षिप्त नाम आवश्यक है, जारी नहीं रखा जा सकता।",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "मॉडल फ़ाइल सिस्टम पथ का पता चला. अद्यतन के लिए मॉडल संक्षिप्त नाम आवश्यक है, जारी नहीं रखा जा सकता।",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "मॉडल नाम",
 	"Model Name": "मॉडल नाम",
 	"Model not selected": "मॉडल चयनित नहीं है",
 	"Model not selected": "मॉडल चयनित नहीं है",
 	"Model Tag Name": "मॉडल टैग नाम",
 	"Model Tag Name": "मॉडल टैग नाम",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "अपनी मॉडलफ़ाइल को नाम दें",
 	"Name your modelfile": "अपनी मॉडलफ़ाइल को नाम दें",
 	"New Chat": "नई चैट",
 	"New Chat": "नई चैट",
 	"New Password": "नया पासवर्ड",
 	"New Password": "नया पासवर्ड",
+	"No": "",
 	"No results found": "कोई परिणाम नहीं मिला",
 	"No results found": "कोई परिणाम नहीं मिला",
 	"No source available": "कोई स्रोत उपलब्ध नहीं है",
 	"No source available": "कोई स्रोत उपलब्ध नहीं है",
 	"Not factually correct": "तथ्यात्मक रूप से सही नहीं है",
 	"Not factually correct": "तथ्यात्मक रूप से सही नहीं है",
@@ -385,6 +397,7 @@
 	"Select a model": "एक मॉडल चुनें",
 	"Select a model": "एक मॉडल चुनें",
 	"Select an Ollama instance": "एक Ollama Instance चुनें",
 	"Select an Ollama instance": "एक Ollama Instance चुनें",
 	"Select model": "मॉडल चुनें",
 	"Select model": "मॉडल चुनें",
+	"Selected models do not support image inputs": "",
 	"Send": "भेज",
 	"Send": "भेज",
 	"Send a Message": "एक संदेश भेजो",
 	"Send a Message": "एक संदेश भेजो",
 	"Send message": "मेसेज भेजें",
 	"Send message": "मेसेज भेजें",
@@ -492,6 +505,7 @@
 	"Workspace": "वर्कस्पेस",
 	"Workspace": "वर्कस्पेस",
 	"Write a prompt suggestion (e.g. Who are you?)": "एक त्वरित सुझाव लिखें (जैसे कि आप कौन हैं?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "एक त्वरित सुझाव लिखें (जैसे कि आप कौन हैं?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "50 शब्दों में एक सारांश लिखें जो [विषय या कीवर्ड] का सारांश प्रस्तुत करता हो।",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "50 शब्दों में एक सारांश लिखें जो [विषय या कीवर्ड] का सारांश प्रस्तुत करता हो।",
+	"Yes": "",
 	"Yesterday": "कल",
 	"Yesterday": "कल",
 	"You": "आप",
 	"You": "आप",
 	"You have no archived conversations.": "आपको कोई अंकित चैट नहीं है।",
 	"You have no archived conversations.": "आपको कोई अंकित चैट नहीं है।",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} razmišlja...",
 	"{{modelName}} is thinking...": "{{modelName}} razmišlja...",
 	"{{user}}'s Chats": "Razgovori korisnika {{user}}",
 	"{{user}}'s Chats": "Razgovori korisnika {{user}}",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend je potreban",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend je potreban",
+	"A selected model does not support image input": "",
 	"a user": "korisnik",
 	"a user": "korisnik",
 	"About": "O aplikaciji",
 	"About": "O aplikaciji",
 	"Account": "Račun",
 	"Account": "Račun",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Napredni parametri",
 	"Advanced Parameters": "Napredni parametri",
 	"all": "sve",
 	"all": "sve",
 	"All Documents": "Svi dokumenti",
 	"All Documents": "Svi dokumenti",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Svi korisnici",
 	"All Users": "Svi korisnici",
 	"Allow": "Dopusti",
 	"Allow": "Dopusti",
 	"Allow Chat Deletion": "Dopusti brisanje razgovora",
 	"Allow Chat Deletion": "Dopusti brisanje razgovora",
@@ -115,6 +117,7 @@
 	"Created at": "Stvoreno",
 	"Created at": "Stvoreno",
 	"Created At": "Stvoreno",
 	"Created At": "Stvoreno",
 	"Current Model": "Trenutni model",
 	"Current Model": "Trenutni model",
+	"Current Models": "",
 	"Current Password": "Trenutna lozinka",
 	"Current Password": "Trenutna lozinka",
 	"Custom": "Prilagođeno",
 	"Custom": "Prilagođeno",
 	"Customize Ollama models for a specific purpose": "Prilagodite Ollama modele za specifičnu svrhu",
 	"Customize Ollama models for a specific purpose": "Prilagodite Ollama modele za specifičnu svrhu",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Unesite LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Unesite LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Unesite LiteLLM model (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Unesite LiteLLM model (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Unesite maksimalan broj tokena (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Unesite maksimalan broj tokena (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Unesite oznaku modela (npr. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Unesite oznaku modela (npr. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Unesite broj koraka (npr. 50)",
 	"Enter Number of Steps (e.g. 50)": "Unesite broj koraka (npr. 50)",
 	"Enter Score": "Unesite ocjenu",
 	"Enter Score": "Unesite ocjenu",
@@ -235,6 +239,7 @@
 	"Input commands": "Unos naredbi",
 	"Input commands": "Unos naredbi",
 	"Interface": "Sučelje",
 	"Interface": "Sučelje",
 	"Invalid Tag": "Nevažeća oznaka",
 	"Invalid Tag": "Nevažeća oznaka",
+	"Is Model Vision Capable": "",
 	"January": "Siječanj",
 	"January": "Siječanj",
 	"join our Discord for help.": "pridružite se našem Discordu za pomoć.",
 	"join our Discord for help.": "pridružite se našem Discordu za pomoć.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Izradio OpenWebUI Community",
 	"Made by OpenWebUI Community": "Izradio OpenWebUI Community",
 	"Make sure to enclose them with": "Provjerite da ih zatvorite s",
 	"Make sure to enclose them with": "Provjerite da ih zatvorite s",
 	"Manage LiteLLM Models": "Upravljajte LiteLLM modelima",
 	"Manage LiteLLM Models": "Upravljajte LiteLLM modelima",
+	"Manage Model Information": "",
 	"Manage Models": "Upravljanje modelima",
 	"Manage Models": "Upravljanje modelima",
 	"Manage Ollama Models": "Upravljanje Ollama modelima",
 	"Manage Ollama Models": "Upravljanje Ollama modelima",
 	"March": "Ožujak",
 	"March": "Ožujak",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je već u redu za preuzimanje.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je već u redu za preuzimanje.",
 	"Model {{modelId}} not found": "Model {{modelId}} nije pronađen",
 	"Model {{modelId}} not found": "Model {{modelId}} nije pronađen",
 	"Model {{modelName}} already exists.": "Model {{modelName}} već postoji.",
 	"Model {{modelName}} already exists.": "Model {{modelName}} već postoji.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Otkriven put datotečnog sustava modela. Kratko ime modela je potrebno za ažuriranje, nije moguće nastaviti.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Otkriven put datotečnog sustava modela. Kratko ime modela je potrebno za ažuriranje, nije moguće nastaviti.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Naziv modela",
 	"Model Name": "Naziv modela",
 	"Model not selected": "Model nije odabran",
 	"Model not selected": "Model nije odabran",
 	"Model Tag Name": "Naziv oznake modela",
 	"Model Tag Name": "Naziv oznake modela",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nazovite svoju datoteku modela",
 	"Name your modelfile": "Nazovite svoju datoteku modela",
 	"New Chat": "Novi razgovor",
 	"New Chat": "Novi razgovor",
 	"New Password": "Nova lozinka",
 	"New Password": "Nova lozinka",
+	"No": "",
 	"No results found": "Nema rezultata",
 	"No results found": "Nema rezultata",
 	"No source available": "Nema dostupnog izvora",
 	"No source available": "Nema dostupnog izvora",
 	"Not factually correct": "Nije činjenično točno",
 	"Not factually correct": "Nije činjenično točno",
@@ -385,6 +397,7 @@
 	"Select a model": "Odaberite model",
 	"Select a model": "Odaberite model",
 	"Select an Ollama instance": "Odaberite Ollama instancu",
 	"Select an Ollama instance": "Odaberite Ollama instancu",
 	"Select model": "Odaberite model",
 	"Select model": "Odaberite model",
+	"Selected models do not support image inputs": "",
 	"Send": "Pošalji",
 	"Send": "Pošalji",
 	"Send a Message": "Pošaljite poruku",
 	"Send a Message": "Pošaljite poruku",
 	"Send message": "Pošalji poruku",
 	"Send message": "Pošalji poruku",
@@ -492,6 +505,7 @@
 	"Workspace": "Radna ploča",
 	"Workspace": "Radna ploča",
 	"Write a prompt suggestion (e.g. Who are you?)": "Napišite prijedlog prompta (npr. Tko si ti?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Napišite prijedlog prompta (npr. Tko si ti?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Napišite sažetak u 50 riječi koji sažima [temu ili ključnu riječ].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Napišite sažetak u 50 riječi koji sažima [temu ili ključnu riječ].",
+	"Yes": "",
 	"Yesterday": "Jučer",
 	"Yesterday": "Jučer",
 	"You": "Vi",
 	"You": "Vi",
 	"You have no archived conversations.": "Nemate arhiviranih razgovora.",
 	"You have no archived conversations.": "Nemate arhiviranih razgovora.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} sta pensando...",
 	"{{modelName}} is thinking...": "{{modelName}} sta pensando...",
 	"{{user}}'s Chats": "{{user}} Chat",
 	"{{user}}'s Chats": "{{user}} Chat",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
+	"A selected model does not support image input": "",
 	"a user": "un utente",
 	"a user": "un utente",
 	"About": "Informazioni",
 	"About": "Informazioni",
 	"Account": "Account",
 	"Account": "Account",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Parametri avanzati",
 	"Advanced Parameters": "Parametri avanzati",
 	"all": "tutti",
 	"all": "tutti",
 	"All Documents": "Tutti i documenti",
 	"All Documents": "Tutti i documenti",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Tutti gli utenti",
 	"All Users": "Tutti gli utenti",
 	"Allow": "Consenti",
 	"Allow": "Consenti",
 	"Allow Chat Deletion": "Consenti l'eliminazione della chat",
 	"Allow Chat Deletion": "Consenti l'eliminazione della chat",
@@ -115,6 +117,7 @@
 	"Created at": "Creato il",
 	"Created at": "Creato il",
 	"Created At": "Creato il",
 	"Created At": "Creato il",
 	"Current Model": "Modello corrente",
 	"Current Model": "Modello corrente",
+	"Current Models": "",
 	"Current Password": "Password corrente",
 	"Current Password": "Password corrente",
 	"Custom": "Personalizzato",
 	"Custom": "Personalizzato",
 	"Customize Ollama models for a specific purpose": "Personalizza i modelli Ollama per uno scopo specifico",
 	"Customize Ollama models for a specific purpose": "Personalizza i modelli Ollama per uno scopo specifico",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Inserisci LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Inserisci LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Inserisci il modello LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Inserisci il modello LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Inserisci Max Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Inserisci Max Tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Inserisci il tag del modello (ad esempio {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Inserisci il tag del modello (ad esempio {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Inserisci il numero di passaggi (ad esempio 50)",
 	"Enter Number of Steps (e.g. 50)": "Inserisci il numero di passaggi (ad esempio 50)",
 	"Enter Score": "Inserisci il punteggio",
 	"Enter Score": "Inserisci il punteggio",
@@ -235,6 +239,7 @@
 	"Input commands": "Comandi di input",
 	"Input commands": "Comandi di input",
 	"Interface": "Interfaccia",
 	"Interface": "Interfaccia",
 	"Invalid Tag": "Tag non valido",
 	"Invalid Tag": "Tag non valido",
+	"Is Model Vision Capable": "",
 	"January": "Gennaio",
 	"January": "Gennaio",
 	"join our Discord for help.": "unisciti al nostro Discord per ricevere aiuto.",
 	"join our Discord for help.": "unisciti al nostro Discord per ricevere aiuto.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Realizzato dalla comunità OpenWebUI",
 	"Made by OpenWebUI Community": "Realizzato dalla comunità OpenWebUI",
 	"Make sure to enclose them with": "Assicurati di racchiuderli con",
 	"Make sure to enclose them with": "Assicurati di racchiuderli con",
 	"Manage LiteLLM Models": "Gestisci modelli LiteLLM",
 	"Manage LiteLLM Models": "Gestisci modelli LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Gestisci modelli",
 	"Manage Models": "Gestisci modelli",
 	"Manage Ollama Models": "Gestisci modelli Ollama",
 	"Manage Ollama Models": "Gestisci modelli Ollama",
 	"March": "Marzo",
 	"March": "Marzo",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Il modello '{{modelTag}}' è già in coda per il download.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Il modello '{{modelTag}}' è già in coda per il download.",
 	"Model {{modelId}} not found": "Modello {{modelId}} non trovato",
 	"Model {{modelId}} not found": "Modello {{modelId}} non trovato",
 	"Model {{modelName}} already exists.": "Il modello {{modelName}} esiste già.",
 	"Model {{modelName}} already exists.": "Il modello {{modelName}} esiste già.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Percorso del filesystem del modello rilevato. Il nome breve del modello è richiesto per l'aggiornamento, impossibile continuare.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Percorso del filesystem del modello rilevato. Il nome breve del modello è richiesto per l'aggiornamento, impossibile continuare.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nome modello",
 	"Model Name": "Nome modello",
 	"Model not selected": "Modello non selezionato",
 	"Model not selected": "Modello non selezionato",
 	"Model Tag Name": "Nome tag del modello",
 	"Model Tag Name": "Nome tag del modello",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Assegna un nome al tuo file modello",
 	"Name your modelfile": "Assegna un nome al tuo file modello",
 	"New Chat": "Nuova chat",
 	"New Chat": "Nuova chat",
 	"New Password": "Nuova password",
 	"New Password": "Nuova password",
+	"No": "",
 	"No results found": "Nessun risultato trovato",
 	"No results found": "Nessun risultato trovato",
 	"No source available": "Nessuna fonte disponibile",
 	"No source available": "Nessuna fonte disponibile",
 	"Not factually correct": "Non corretto dal punto di vista fattuale",
 	"Not factually correct": "Non corretto dal punto di vista fattuale",
@@ -385,6 +397,7 @@
 	"Select a model": "Seleziona un modello",
 	"Select a model": "Seleziona un modello",
 	"Select an Ollama instance": "Seleziona un'istanza Ollama",
 	"Select an Ollama instance": "Seleziona un'istanza Ollama",
 	"Select model": "Seleziona modello",
 	"Select model": "Seleziona modello",
+	"Selected models do not support image inputs": "",
 	"Send": "Invia",
 	"Send": "Invia",
 	"Send a Message": "Invia un messaggio",
 	"Send a Message": "Invia un messaggio",
 	"Send message": "Invia messaggio",
 	"Send message": "Invia messaggio",
@@ -492,6 +505,7 @@
 	"Workspace": "Area di lavoro",
 	"Workspace": "Area di lavoro",
 	"Write a prompt suggestion (e.g. Who are you?)": "Scrivi un suggerimento per il prompt (ad esempio Chi sei?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Scrivi un suggerimento per il prompt (ad esempio Chi sei?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Scrivi un riassunto in 50 parole che riassume [argomento o parola chiave].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Scrivi un riassunto in 50 parole che riassume [argomento o parola chiave].",
+	"Yes": "",
 	"Yesterday": "Ieri",
 	"Yesterday": "Ieri",
 	"You": "Tu",
 	"You": "Tu",
 	"You have no archived conversations.": "Non hai conversazioni archiviate.",
 	"You have no archived conversations.": "Non hai conversazioni archiviate.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} は思考中です...",
 	"{{modelName}} is thinking...": "{{modelName}} は思考中です...",
 	"{{user}}'s Chats": "{{user}} のチャット",
 	"{{user}}'s Chats": "{{user}} のチャット",
 	"{{webUIName}} Backend Required": "{{webUIName}} バックエンドが必要です",
 	"{{webUIName}} Backend Required": "{{webUIName}} バックエンドが必要です",
+	"A selected model does not support image input": "",
 	"a user": "ユーザー",
 	"a user": "ユーザー",
 	"About": "概要",
 	"About": "概要",
 	"Account": "アカウント",
 	"Account": "アカウント",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "詳細パラメーター",
 	"Advanced Parameters": "詳細パラメーター",
 	"all": "すべて",
 	"all": "すべて",
 	"All Documents": "全てのドキュメント",
 	"All Documents": "全てのドキュメント",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "すべてのユーザー",
 	"All Users": "すべてのユーザー",
 	"Allow": "許可",
 	"Allow": "許可",
 	"Allow Chat Deletion": "チャットの削除を許可",
 	"Allow Chat Deletion": "チャットの削除を許可",
@@ -115,6 +117,7 @@
 	"Created at": "作成日時",
 	"Created at": "作成日時",
 	"Created At": "作成日時",
 	"Created At": "作成日時",
 	"Current Model": "現在のモデル",
 	"Current Model": "現在のモデル",
+	"Current Models": "",
 	"Current Password": "現在のパスワード",
 	"Current Password": "現在のパスワード",
 	"Custom": "カスタム",
 	"Custom": "カスタム",
 	"Customize Ollama models for a specific purpose": "特定の目的に合わせて Ollama モデルをカスタマイズ",
 	"Customize Ollama models for a specific purpose": "特定の目的に合わせて Ollama モデルをカスタマイズ",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM を入力してください (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM を入力してください (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM モデルを入力してください (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM モデルを入力してください (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "最大トークン数を入力してください (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "最大トークン数を入力してください (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "モデルタグを入力してください (例: {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "モデルタグを入力してください (例: {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "ステップ数を入力してください (例: 50)",
 	"Enter Number of Steps (e.g. 50)": "ステップ数を入力してください (例: 50)",
 	"Enter Score": "スコアを入力してください",
 	"Enter Score": "スコアを入力してください",
@@ -235,6 +239,7 @@
 	"Input commands": "入力コマンド",
 	"Input commands": "入力コマンド",
 	"Interface": "インターフェース",
 	"Interface": "インターフェース",
 	"Invalid Tag": "無効なタグ",
 	"Invalid Tag": "無効なタグ",
+	"Is Model Vision Capable": "",
 	"January": "1月",
 	"January": "1月",
 	"join our Discord for help.": "ヘルプについては、Discord に参加してください。",
 	"join our Discord for help.": "ヘルプについては、Discord に参加してください。",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "OpenWebUI コミュニティによって作成",
 	"Made by OpenWebUI Community": "OpenWebUI コミュニティによって作成",
 	"Make sure to enclose them with": "必ず次で囲んでください",
 	"Make sure to enclose them with": "必ず次で囲んでください",
 	"Manage LiteLLM Models": "LiteLLM モデルを管理",
 	"Manage LiteLLM Models": "LiteLLM モデルを管理",
+	"Manage Model Information": "",
 	"Manage Models": "モデルを管理",
 	"Manage Models": "モデルを管理",
 	"Manage Ollama Models": "Ollama モデルを管理",
 	"Manage Ollama Models": "Ollama モデルを管理",
 	"March": "3月",
 	"March": "3月",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "モデル '{{modelTag}}' はすでにダウンロード待ち行列に入っています。",
 	"Model '{{modelTag}}' is already in queue for downloading.": "モデル '{{modelTag}}' はすでにダウンロード待ち行列に入っています。",
 	"Model {{modelId}} not found": "モデル {{modelId}} が見つかりません",
 	"Model {{modelId}} not found": "モデル {{modelId}} が見つかりません",
 	"Model {{modelName}} already exists.": "モデル {{modelName}} はすでに存在します。",
 	"Model {{modelName}} already exists.": "モデル {{modelName}} はすでに存在します。",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "モデルファイルシステムパスが検出されました。モデルの短縮名が必要です。更新できません。",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "モデルファイルシステムパスが検出されました。モデルの短縮名が必要です。更新できません。",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "モデル名",
 	"Model Name": "モデル名",
 	"Model not selected": "モデルが選択されていません",
 	"Model not selected": "モデルが選択されていません",
 	"Model Tag Name": "モデルタグ名",
 	"Model Tag Name": "モデルタグ名",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "モデルファイルに名前を付ける",
 	"Name your modelfile": "モデルファイルに名前を付ける",
 	"New Chat": "新しいチャット",
 	"New Chat": "新しいチャット",
 	"New Password": "新しいパスワード",
 	"New Password": "新しいパスワード",
+	"No": "",
 	"No results found": "結果が見つかりません",
 	"No results found": "結果が見つかりません",
 	"No source available": "使用可能なソースがありません",
 	"No source available": "使用可能なソースがありません",
 	"Not factually correct": "実事上正しくない",
 	"Not factually correct": "実事上正しくない",
@@ -385,6 +397,7 @@
 	"Select a model": "モデルを選択",
 	"Select a model": "モデルを選択",
 	"Select an Ollama instance": "Ollama インスタンスを選択",
 	"Select an Ollama instance": "Ollama インスタンスを選択",
 	"Select model": "モデルを選択",
 	"Select model": "モデルを選択",
+	"Selected models do not support image inputs": "",
 	"Send": "送信",
 	"Send": "送信",
 	"Send a Message": "メッセージを送信",
 	"Send a Message": "メッセージを送信",
 	"Send message": "メッセージを送信",
 	"Send message": "メッセージを送信",
@@ -492,6 +505,7 @@
 	"Workspace": "ワークスペース",
 	"Workspace": "ワークスペース",
 	"Write a prompt suggestion (e.g. Who are you?)": "プロンプトの提案を書いてください (例: あなたは誰ですか?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "プロンプトの提案を書いてください (例: あなたは誰ですか?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[トピックまたはキーワード] を要約する 50 語の概要を書いてください。",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[トピックまたはキーワード] を要約する 50 語の概要を書いてください。",
+	"Yes": "",
 	"Yesterday": "昨日",
 	"Yesterday": "昨日",
 	"You": "あなた",
 	"You": "あなた",
 	"You have no archived conversations.": "これまでにアーカイブされた会話はありません。",
 	"You have no archived conversations.": "これまでにアーカイブされた会話はありません。",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} ფიქრობს...",
 	"{{modelName}} is thinking...": "{{modelName}} ფიქრობს...",
 	"{{user}}'s Chats": "{{user}}-ის ჩათები",
 	"{{user}}'s Chats": "{{user}}-ის ჩათები",
 	"{{webUIName}} Backend Required": "{{webUIName}} საჭიროა ბექენდი",
 	"{{webUIName}} Backend Required": "{{webUIName}} საჭიროა ბექენდი",
+	"A selected model does not support image input": "",
 	"a user": "მომხმარებელი",
 	"a user": "მომხმარებელი",
 	"About": "შესახებ",
 	"About": "შესახებ",
 	"Account": "ანგარიში",
 	"Account": "ანგარიში",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "დამატებითი პარამეტრები",
 	"Advanced Parameters": "დამატებითი პარამეტრები",
 	"all": "ყველა",
 	"all": "ყველა",
 	"All Documents": "ყველა დოკუმენტი",
 	"All Documents": "ყველა დოკუმენტი",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "ყველა მომხმარებელი",
 	"All Users": "ყველა მომხმარებელი",
 	"Allow": "ნების დართვა",
 	"Allow": "ნების დართვა",
 	"Allow Chat Deletion": "მიმოწერის წაშლის დაშვება",
 	"Allow Chat Deletion": "მიმოწერის წაშლის დაშვება",
@@ -115,6 +117,7 @@
 	"Created at": "შექმნილია",
 	"Created at": "შექმნილია",
 	"Created At": "შექმნილია",
 	"Created At": "შექმნილია",
 	"Current Model": "მიმდინარე მოდელი",
 	"Current Model": "მიმდინარე მოდელი",
+	"Current Models": "",
 	"Current Password": "მიმდინარე პაროლი",
 	"Current Password": "მიმდინარე პაროლი",
 	"Custom": "საკუთარი",
 	"Custom": "საკუთარი",
 	"Customize Ollama models for a specific purpose": "Ollama მოდელების დამუშავება სპეციფიური დანიშნულებისთვის",
 	"Customize Ollama models for a specific purpose": "Ollama მოდელების დამუშავება სპეციფიური დანიშნულებისთვის",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "შეიყვანეთ LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "შეიყვანეთ LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "შეიყვანეთ LiteLLM მოდელი (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "შეიყვანეთ LiteLLM მოდელი (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "შეიყვანეთ მაქსიმალური ტოკენები (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "შეიყვანეთ მაქსიმალური ტოკენები (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "შეიყვანეთ მოდელის ტეგი (მაგ. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "შეიყვანეთ მოდელის ტეგი (მაგ. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "შეიყვანეთ ნაბიჯების რაოდენობა (მაგ. 50)",
 	"Enter Number of Steps (e.g. 50)": "შეიყვანეთ ნაბიჯების რაოდენობა (მაგ. 50)",
 	"Enter Score": "შეიყვანეთ ქულა",
 	"Enter Score": "შეიყვანეთ ქულა",
@@ -235,6 +239,7 @@
 	"Input commands": "შეყვანით ბრძანებებს",
 	"Input commands": "შეყვანით ბრძანებებს",
 	"Interface": "ინტერფეისი",
 	"Interface": "ინტერფეისი",
 	"Invalid Tag": "არასწორი ტეგი",
 	"Invalid Tag": "არასწორი ტეგი",
+	"Is Model Vision Capable": "",
 	"January": "იანვარი",
 	"January": "იანვარი",
 	"join our Discord for help.": "შეუერთდით ჩვენს Discord-ს დახმარებისთვის",
 	"join our Discord for help.": "შეუერთდით ჩვენს Discord-ს დახმარებისთვის",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "დამზადებულია OpenWebUI საზოგადოების მიერ",
 	"Made by OpenWebUI Community": "დამზადებულია OpenWebUI საზოგადოების მიერ",
 	"Make sure to enclose them with": "დარწმუნდით, რომ დაურთეთ ისინი",
 	"Make sure to enclose them with": "დარწმუნდით, რომ დაურთეთ ისინი",
 	"Manage LiteLLM Models": "LiteLLM მოდელების მართვა",
 	"Manage LiteLLM Models": "LiteLLM მოდელების მართვა",
+	"Manage Model Information": "",
 	"Manage Models": "მოდელების მართვა",
 	"Manage Models": "მოდელების მართვა",
 	"Manage Ollama Models": "Ollama მოდელების მართვა",
 	"Manage Ollama Models": "Ollama მოდელების მართვა",
 	"March": "მარტივი",
 	"March": "მარტივი",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "მოდელი „{{modelTag}}“ უკვე ჩამოტვირთვის რიგშია.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "მოდელი „{{modelTag}}“ უკვე ჩამოტვირთვის რიგშია.",
 	"Model {{modelId}} not found": "მოდელი {{modelId}} ვერ მოიძებნა",
 	"Model {{modelId}} not found": "მოდელი {{modelId}} ვერ მოიძებნა",
 	"Model {{modelName}} already exists.": "მოდელი {{modelName}} უკვე არსებობს.",
 	"Model {{modelName}} already exists.": "მოდელი {{modelName}} უკვე არსებობს.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "აღმოჩენილია მოდელის ფაილური სისტემის გზა. განახლებისთვის საჭიროა მოდელის მოკლე სახელი, გაგრძელება შეუძლებელია.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "აღმოჩენილია მოდელის ფაილური სისტემის გზა. განახლებისთვის საჭიროა მოდელის მოკლე სახელი, გაგრძელება შეუძლებელია.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "მოდელის სახელი",
 	"Model Name": "მოდელის სახელი",
 	"Model not selected": "მოდელი არ არის არჩეული",
 	"Model not selected": "მოდელი არ არის არჩეული",
 	"Model Tag Name": "მოდელის ტეგის სახელი",
 	"Model Tag Name": "მოდელის ტეგის სახელი",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "თქვენი მოდელური ფაილის სახელი",
 	"Name your modelfile": "თქვენი მოდელური ფაილის სახელი",
 	"New Chat": "ახალი მიმოწერა",
 	"New Chat": "ახალი მიმოწერა",
 	"New Password": "ახალი პაროლი",
 	"New Password": "ახალი პაროლი",
+	"No": "",
 	"No results found": "ჩვენ ვერ პოულობით ნაპოვნი ჩაწერები",
 	"No results found": "ჩვენ ვერ პოულობით ნაპოვნი ჩაწერები",
 	"No source available": "წყარო არ არის ხელმისაწვდომი",
 	"No source available": "წყარო არ არის ხელმისაწვდომი",
 	"Not factually correct": "არ ვეთანხმები პირდაპირ ვერც ვეთანხმები",
 	"Not factually correct": "არ ვეთანხმები პირდაპირ ვერც ვეთანხმები",
@@ -385,6 +397,7 @@
 	"Select a model": "მოდელის არჩევა",
 	"Select a model": "მოდელის არჩევა",
 	"Select an Ollama instance": "Ollama ინსტანსის არჩევა",
 	"Select an Ollama instance": "Ollama ინსტანსის არჩევა",
 	"Select model": "მოდელის არჩევა",
 	"Select model": "მოდელის არჩევა",
+	"Selected models do not support image inputs": "",
 	"Send": "გაგზავნა",
 	"Send": "გაგზავნა",
 	"Send a Message": "შეტყობინების გაგზავნა",
 	"Send a Message": "შეტყობინების გაგზავნა",
 	"Send message": "შეტყობინების გაგზავნა",
 	"Send message": "შეტყობინების გაგზავნა",
@@ -492,6 +505,7 @@
 	"Workspace": "ვულერი",
 	"Workspace": "ვულერი",
 	"Write a prompt suggestion (e.g. Who are you?)": "დაწერეთ მოკლე წინადადება (მაგ. ვინ ხარ?",
 	"Write a prompt suggestion (e.g. Who are you?)": "დაწერეთ მოკლე წინადადება (მაგ. ვინ ხარ?",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "დაწერეთ რეზიუმე 50 სიტყვით, რომელიც აჯამებს [თემას ან საკვანძო სიტყვას].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "დაწერეთ რეზიუმე 50 სიტყვით, რომელიც აჯამებს [თემას ან საკვანძო სიტყვას].",
+	"Yes": "",
 	"Yesterday": "აღდგენა",
 	"Yesterday": "აღდგენა",
 	"You": "ჩემი",
 	"You": "ჩემი",
 	"You have no archived conversations.": "არ ხართ არქივირებული განხილვები.",
 	"You have no archived conversations.": "არ ხართ არქივირებული განხილვები.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} 이(가) 생각중입니다....",
 	"{{modelName}} is thinking...": "{{modelName}} 이(가) 생각중입니다....",
 	"{{user}}'s Chats": "{{user}}의 채팅",
 	"{{user}}'s Chats": "{{user}}의 채팅",
 	"{{webUIName}} Backend Required": "{{webUIName}} 백엔드가 필요합니다.",
 	"{{webUIName}} Backend Required": "{{webUIName}} 백엔드가 필요합니다.",
+	"A selected model does not support image input": "",
 	"a user": "사용자",
 	"a user": "사용자",
 	"About": "소개",
 	"About": "소개",
 	"Account": "계정",
 	"Account": "계정",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "고급 매개변수",
 	"Advanced Parameters": "고급 매개변수",
 	"all": "모두",
 	"all": "모두",
 	"All Documents": "모든 문서",
 	"All Documents": "모든 문서",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "모든 사용자",
 	"All Users": "모든 사용자",
 	"Allow": "허용",
 	"Allow": "허용",
 	"Allow Chat Deletion": "채팅 삭제 허용",
 	"Allow Chat Deletion": "채팅 삭제 허용",
@@ -115,6 +117,7 @@
 	"Created at": "생성일",
 	"Created at": "생성일",
 	"Created At": "생성일",
 	"Created At": "생성일",
 	"Current Model": "현재 모델",
 	"Current Model": "현재 모델",
+	"Current Models": "",
 	"Current Password": "현재 비밀번호",
 	"Current Password": "현재 비밀번호",
 	"Custom": "사용자 정의",
 	"Custom": "사용자 정의",
 	"Customize Ollama models for a specific purpose": "특정 목적으로 Ollama 모델 사용자 정의",
 	"Customize Ollama models for a specific purpose": "특정 목적으로 Ollama 모델 사용자 정의",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM 입력(litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM 입력(litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM 모델 입력(litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM 모델 입력(litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "최대 토큰 수 입력(litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "최대 토큰 수 입력(litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "모델 태그 입력(예: {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "모델 태그 입력(예: {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "단계 수 입력(예: 50)",
 	"Enter Number of Steps (e.g. 50)": "단계 수 입력(예: 50)",
 	"Enter Score": "점수 입력",
 	"Enter Score": "점수 입력",
@@ -235,6 +239,7 @@
 	"Input commands": "입력 명령",
 	"Input commands": "입력 명령",
 	"Interface": "인터페이스",
 	"Interface": "인터페이스",
 	"Invalid Tag": "잘못된 태그",
 	"Invalid Tag": "잘못된 태그",
+	"Is Model Vision Capable": "",
 	"January": "1월",
 	"January": "1월",
 	"join our Discord for help.": "도움말을 보려면 Discord에 가입하세요.",
 	"join our Discord for help.": "도움말을 보려면 Discord에 가입하세요.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "OpenWebUI 커뮤니티에서 제작",
 	"Made by OpenWebUI Community": "OpenWebUI 커뮤니티에서 제작",
 	"Make sure to enclose them with": "다음으로 묶는 것을 잊지 마세요:",
 	"Make sure to enclose them with": "다음으로 묶는 것을 잊지 마세요:",
 	"Manage LiteLLM Models": "LiteLLM 모델 관리",
 	"Manage LiteLLM Models": "LiteLLM 모델 관리",
+	"Manage Model Information": "",
 	"Manage Models": "모델 관리",
 	"Manage Models": "모델 관리",
 	"Manage Ollama Models": "Ollama 모델 관리",
 	"Manage Ollama Models": "Ollama 모델 관리",
 	"March": "3월",
 	"March": "3월",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "모델 '{{modelTag}}'이(가) 이미 다운로드 대기열에 있습니다.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "모델 '{{modelTag}}'이(가) 이미 다운로드 대기열에 있습니다.",
 	"Model {{modelId}} not found": "모델 {{modelId}}를 찾을 수 없습니다.",
 	"Model {{modelId}} not found": "모델 {{modelId}}를 찾을 수 없습니다.",
 	"Model {{modelName}} already exists.": "모델 {{modelName}}이(가) 이미 존재합니다.",
 	"Model {{modelName}} already exists.": "모델 {{modelName}}이(가) 이미 존재합니다.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "모델 파일 시스템 경로가 감지되었습니다. 업데이트하려면 모델 단축 이름이 필요하며 계속할 수 없습니다.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "모델 파일 시스템 경로가 감지되었습니다. 업데이트하려면 모델 단축 이름이 필요하며 계속할 수 없습니다.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "모델 이름",
 	"Model Name": "모델 이름",
 	"Model not selected": "모델이 선택되지 않았습니다.",
 	"Model not selected": "모델이 선택되지 않았습니다.",
 	"Model Tag Name": "모델 태그 이름",
 	"Model Tag Name": "모델 태그 이름",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "모델파일 이름 지정",
 	"Name your modelfile": "모델파일 이름 지정",
 	"New Chat": "새 채팅",
 	"New Chat": "새 채팅",
 	"New Password": "새 비밀번호",
 	"New Password": "새 비밀번호",
+	"No": "",
 	"No results found": "결과 없음",
 	"No results found": "결과 없음",
 	"No source available": "사용 가능한 소스 없음",
 	"No source available": "사용 가능한 소스 없음",
 	"Not factually correct": "사실상 맞지 않음",
 	"Not factually correct": "사실상 맞지 않음",
@@ -385,6 +397,7 @@
 	"Select a model": "모델 선택",
 	"Select a model": "모델 선택",
 	"Select an Ollama instance": "Ollama 인스턴스 선택",
 	"Select an Ollama instance": "Ollama 인스턴스 선택",
 	"Select model": "모델 선택",
 	"Select model": "모델 선택",
+	"Selected models do not support image inputs": "",
 	"Send": "보내기",
 	"Send": "보내기",
 	"Send a Message": "메시지 보내기",
 	"Send a Message": "메시지 보내기",
 	"Send message": "메시지 보내기",
 	"Send message": "메시지 보내기",
@@ -492,6 +505,7 @@
 	"Workspace": "워크스페이스",
 	"Workspace": "워크스페이스",
 	"Write a prompt suggestion (e.g. Who are you?)": "프롬프트 제안 작성 (예: 당신은 누구인가요?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "프롬프트 제안 작성 (예: 당신은 누구인가요?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[주제 또는 키워드]에 대한 50단어 요약문 작성.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[주제 또는 키워드]에 대한 50단어 요약문 작성.",
+	"Yes": "",
 	"Yesterday": "어제",
 	"Yesterday": "어제",
 	"You": "당신",
 	"You": "당신",
 	"You have no archived conversations.": "채팅을 아카이브한 적이 없습니다.",
 	"You have no archived conversations.": "채팅을 아카이브한 적이 없습니다.",

+ 14 - 0
src/lib/i18n/locales/nl-NL/translation.json

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} is aan het denken...",
 	"{{modelName}} is thinking...": "{{modelName}} is aan het denken...",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht",
+	"A selected model does not support image input": "",
 	"a user": "een gebruiker",
 	"a user": "een gebruiker",
 	"About": "Over",
 	"About": "Over",
 	"Account": "Account",
 	"Account": "Account",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Geavanceerde Parameters",
 	"Advanced Parameters": "Geavanceerde Parameters",
 	"all": "alle",
 	"all": "alle",
 	"All Documents": "Alle Documenten",
 	"All Documents": "Alle Documenten",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Alle Gebruikers",
 	"All Users": "Alle Gebruikers",
 	"Allow": "Toestaan",
 	"Allow": "Toestaan",
 	"Allow Chat Deletion": "Sta Chat Verwijdering toe",
 	"Allow Chat Deletion": "Sta Chat Verwijdering toe",
@@ -115,6 +117,7 @@
 	"Created at": "Gemaakt op",
 	"Created at": "Gemaakt op",
 	"Created At": "Gemaakt op",
 	"Created At": "Gemaakt op",
 	"Current Model": "Huidig Model",
 	"Current Model": "Huidig Model",
+	"Current Models": "",
 	"Current Password": "Huidig Wachtwoord",
 	"Current Password": "Huidig Wachtwoord",
 	"Custom": "Aangepast",
 	"Custom": "Aangepast",
 	"Customize Ollama models for a specific purpose": "Pas Ollama modellen aan voor een specifiek doel",
 	"Customize Ollama models for a specific purpose": "Pas Ollama modellen aan voor een specifiek doel",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Voeg LiteLLM API RPM toe (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Voeg LiteLLM API RPM toe (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Voeg LiteLLM Model toe (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Voeg LiteLLM Model toe (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Voeg maximum aantal tokens toe (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Voeg maximum aantal tokens toe (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Voeg model tag toe (Bijv. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Voeg model tag toe (Bijv. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Voeg aantal stappen toe (Bijv. 50)",
 	"Enter Number of Steps (e.g. 50)": "Voeg aantal stappen toe (Bijv. 50)",
 	"Enter Score": "Voeg score toe",
 	"Enter Score": "Voeg score toe",
@@ -235,6 +239,7 @@
 	"Input commands": "Voer commando's in",
 	"Input commands": "Voer commando's in",
 	"Interface": "Interface",
 	"Interface": "Interface",
 	"Invalid Tag": "Ongeldige Tag",
 	"Invalid Tag": "Ongeldige Tag",
+	"Is Model Vision Capable": "",
 	"January": "Januari",
 	"January": "Januari",
 	"join our Discord for help.": "join onze Discord voor hulp.",
 	"join our Discord for help.": "join onze Discord voor hulp.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Gemaakt door OpenWebUI Community",
 	"Made by OpenWebUI Community": "Gemaakt door OpenWebUI Community",
 	"Make sure to enclose them with": "Zorg ervoor dat je ze omringt met",
 	"Make sure to enclose them with": "Zorg ervoor dat je ze omringt met",
 	"Manage LiteLLM Models": "Beheer LiteLLM Modellen",
 	"Manage LiteLLM Models": "Beheer LiteLLM Modellen",
+	"Manage Model Information": "",
 	"Manage Models": "Beheer Modellen",
 	"Manage Models": "Beheer Modellen",
 	"Manage Ollama Models": "Beheer Ollama Modellen",
 	"Manage Ollama Models": "Beheer Ollama Modellen",
 	"March": "Maart",
 	"March": "Maart",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' staat al in de wachtrij voor downloaden.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' staat al in de wachtrij voor downloaden.",
 	"Model {{modelId}} not found": "Model {{modelId}} niet gevonden",
 	"Model {{modelId}} not found": "Model {{modelId}} niet gevonden",
 	"Model {{modelName}} already exists.": "Model {{modelName}} bestaat al.",
 	"Model {{modelName}} already exists.": "Model {{modelName}} bestaat al.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model filesystem path gedetecteerd. Model shortname is vereist voor update, kan niet doorgaan.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model filesystem path gedetecteerd. Model shortname is vereist voor update, kan niet doorgaan.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Model Naam",
 	"Model Name": "Model Naam",
 	"Model not selected": "Model niet geselecteerd",
 	"Model not selected": "Model niet geselecteerd",
 	"Model Tag Name": "Model Tag Naam",
 	"Model Tag Name": "Model Tag Naam",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Benoem je modelfile",
 	"Name your modelfile": "Benoem je modelfile",
 	"New Chat": "Nieuwe Chat",
 	"New Chat": "Nieuwe Chat",
 	"New Password": "Nieuw Wachtwoord",
 	"New Password": "Nieuw Wachtwoord",
+	"No": "",
 	"No results found": "Geen resultaten gevonden",
 	"No results found": "Geen resultaten gevonden",
 	"No source available": "Geen bron beschikbaar",
 	"No source available": "Geen bron beschikbaar",
 	"Not factually correct": "Feitelijk niet juist",
 	"Not factually correct": "Feitelijk niet juist",
@@ -385,6 +397,7 @@
 	"Select a model": "Selecteer een model",
 	"Select a model": "Selecteer een model",
 	"Select an Ollama instance": "Selecteer een Ollama instantie",
 	"Select an Ollama instance": "Selecteer een Ollama instantie",
 	"Select model": "Selecteer een model",
 	"Select model": "Selecteer een model",
+	"Selected models do not support image inputs": "",
 	"Send": "Verzenden",
 	"Send": "Verzenden",
 	"Send a Message": "Stuur een Bericht",
 	"Send a Message": "Stuur een Bericht",
 	"Send message": "Stuur bericht",
 	"Send message": "Stuur bericht",
@@ -492,6 +505,7 @@
 	"Workspace": "Werkruimte",
 	"Workspace": "Werkruimte",
 	"Write a prompt suggestion (e.g. Who are you?)": "Schrijf een prompt suggestie (bijv. Wie ben je?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Schrijf een prompt suggestie (bijv. Wie ben je?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Schrijf een samenvatting in 50 woorden die [onderwerp of trefwoord] samenvat.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Schrijf een samenvatting in 50 woorden die [onderwerp of trefwoord] samenvat.",
+	"Yes": "",
 	"Yesterday": "gisteren",
 	"Yesterday": "gisteren",
 	"You": "U",
 	"You": "U",
 	"You have no archived conversations.": "U heeft geen gearchiveerde gesprekken.",
 	"You have no archived conversations.": "U heeft geen gearchiveerde gesprekken.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} ਸੋਚ ਰਿਹਾ ਹੈ...",
 	"{{modelName}} is thinking...": "{{modelName}} ਸੋਚ ਰਿਹਾ ਹੈ...",
 	"{{user}}'s Chats": "{{user}} ਦੀਆਂ ਗੱਲਾਂ",
 	"{{user}}'s Chats": "{{user}} ਦੀਆਂ ਗੱਲਾਂ",
 	"{{webUIName}} Backend Required": "{{webUIName}} ਬੈਕਐਂਡ ਲੋੜੀਂਦਾ ਹੈ",
 	"{{webUIName}} Backend Required": "{{webUIName}} ਬੈਕਐਂਡ ਲੋੜੀਂਦਾ ਹੈ",
+	"A selected model does not support image input": "",
 	"a user": "ਇੱਕ ਉਪਭੋਗਤਾ",
 	"a user": "ਇੱਕ ਉਪਭੋਗਤਾ",
 	"About": "ਬਾਰੇ",
 	"About": "ਬਾਰੇ",
 	"Account": "ਖਾਤਾ",
 	"Account": "ਖਾਤਾ",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "ਉੱਚ ਸਤਰ ਦੇ ਪੈਰਾਮੀਟਰ",
 	"Advanced Parameters": "ਉੱਚ ਸਤਰ ਦੇ ਪੈਰਾਮੀਟਰ",
 	"all": "ਸਾਰੇ",
 	"all": "ਸਾਰੇ",
 	"All Documents": "ਸਾਰੇ ਡਾਕੂਮੈਂਟ",
 	"All Documents": "ਸਾਰੇ ਡਾਕੂਮੈਂਟ",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "ਸਾਰੇ ਉਪਭੋਗਤਾ",
 	"All Users": "ਸਾਰੇ ਉਪਭੋਗਤਾ",
 	"Allow": "ਅਨੁਮਤੀ",
 	"Allow": "ਅਨੁਮਤੀ",
 	"Allow Chat Deletion": "ਗੱਲਬਾਤ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ",
 	"Allow Chat Deletion": "ਗੱਲਬਾਤ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ",
@@ -115,6 +117,7 @@
 	"Created at": "ਤੇ ਬਣਾਇਆ ਗਿਆ",
 	"Created at": "ਤੇ ਬਣਾਇਆ ਗਿਆ",
 	"Created At": "ਤੇ ਬਣਾਇਆ ਗਿਆ",
 	"Created At": "ਤੇ ਬਣਾਇਆ ਗਿਆ",
 	"Current Model": "ਮੌਜੂਦਾ ਮਾਡਲ",
 	"Current Model": "ਮੌਜੂਦਾ ਮਾਡਲ",
+	"Current Models": "",
 	"Current Password": "ਮੌਜੂਦਾ ਪਾਸਵਰਡ",
 	"Current Password": "ਮੌਜੂਦਾ ਪਾਸਵਰਡ",
 	"Custom": "ਕਸਟਮ",
 	"Custom": "ਕਸਟਮ",
 	"Customize Ollama models for a specific purpose": "ਇੱਕ ਖਾਸ ਉਦੇਸ਼ ਲਈ ਓਲਾਮਾ ਮਾਡਲਾਂ ਨੂੰ ਕਸਟਮਾਈਜ਼ ਕਰੋ",
 	"Customize Ollama models for a specific purpose": "ਇੱਕ ਖਾਸ ਉਦੇਸ਼ ਲਈ ਓਲਾਮਾ ਮਾਡਲਾਂ ਨੂੰ ਕਸਟਮਾਈਜ਼ ਕਰੋ",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM (litellm_params.rpm) ਦਰਜ ਕਰੋ",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM (litellm_params.rpm) ਦਰਜ ਕਰੋ",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM ਮਾਡਲ (litellm_params.model) ਦਰਜ ਕਰੋ",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM ਮਾਡਲ (litellm_params.model) ਦਰਜ ਕਰੋ",
 	"Enter Max Tokens (litellm_params.max_tokens)": "ਅਧਿਕਤਮ ਟੋਕਨ ਦਰਜ ਕਰੋ (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "ਅਧਿਕਤਮ ਟੋਕਨ ਦਰਜ ਕਰੋ (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "ਮਾਡਲ ਟੈਗ ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "ਮਾਡਲ ਟੈਗ ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "ਕਦਮਾਂ ਦੀ ਗਿਣਤੀ ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ 50)",
 	"Enter Number of Steps (e.g. 50)": "ਕਦਮਾਂ ਦੀ ਗਿਣਤੀ ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ 50)",
 	"Enter Score": "ਸਕੋਰ ਦਰਜ ਕਰੋ",
 	"Enter Score": "ਸਕੋਰ ਦਰਜ ਕਰੋ",
@@ -235,6 +239,7 @@
 	"Input commands": "ਇਨਪੁਟ ਕਮਾਂਡਾਂ",
 	"Input commands": "ਇਨਪੁਟ ਕਮਾਂਡਾਂ",
 	"Interface": "ਇੰਟਰਫੇਸ",
 	"Interface": "ਇੰਟਰਫੇਸ",
 	"Invalid Tag": "ਗਲਤ ਟੈਗ",
 	"Invalid Tag": "ਗਲਤ ਟੈਗ",
+	"Is Model Vision Capable": "",
 	"January": "ਜਨਵਰੀ",
 	"January": "ਜਨਵਰੀ",
 	"join our Discord for help.": "ਮਦਦ ਲਈ ਸਾਡੇ ਡਿਸਕੋਰਡ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ।",
 	"join our Discord for help.": "ਮਦਦ ਲਈ ਸਾਡੇ ਡਿਸਕੋਰਡ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ।",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "ਓਪਨਵੈਬਯੂਆਈ ਕਮਿਊਨਿਟੀ ਦੁਆਰਾ ਬਣਾਇਆ ਗਿਆ",
 	"Made by OpenWebUI Community": "ਓਪਨਵੈਬਯੂਆਈ ਕਮਿਊਨਿਟੀ ਦੁਆਰਾ ਬਣਾਇਆ ਗਿਆ",
 	"Make sure to enclose them with": "ਸੁਨਿਸ਼ਚਿਤ ਕਰੋ ਕਿ ਉਨ੍ਹਾਂ ਨੂੰ ਘੇਰੋ",
 	"Make sure to enclose them with": "ਸੁਨਿਸ਼ਚਿਤ ਕਰੋ ਕਿ ਉਨ੍ਹਾਂ ਨੂੰ ਘੇਰੋ",
 	"Manage LiteLLM Models": "LiteLLM ਮਾਡਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
 	"Manage LiteLLM Models": "LiteLLM ਮਾਡਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
+	"Manage Model Information": "",
 	"Manage Models": "ਮਾਡਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
 	"Manage Models": "ਮਾਡਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
 	"Manage Ollama Models": "ਓਲਾਮਾ ਮਾਡਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
 	"Manage Ollama Models": "ਓਲਾਮਾ ਮਾਡਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
 	"March": "ਮਾਰਚ",
 	"March": "ਮਾਰਚ",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "ਮਾਡਲ '{{modelTag}}' ਪਹਿਲਾਂ ਹੀ ਡਾਊਨਲੋਡ ਲਈ ਕਤਾਰ ਵਿੱਚ ਹੈ।",
 	"Model '{{modelTag}}' is already in queue for downloading.": "ਮਾਡਲ '{{modelTag}}' ਪਹਿਲਾਂ ਹੀ ਡਾਊਨਲੋਡ ਲਈ ਕਤਾਰ ਵਿੱਚ ਹੈ।",
 	"Model {{modelId}} not found": "ਮਾਡਲ {{modelId}} ਨਹੀਂ ਮਿਲਿਆ",
 	"Model {{modelId}} not found": "ਮਾਡਲ {{modelId}} ਨਹੀਂ ਮਿਲਿਆ",
 	"Model {{modelName}} already exists.": "ਮਾਡਲ {{modelName}} ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।",
 	"Model {{modelName}} already exists.": "ਮਾਡਲ {{modelName}} ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "ਮਾਡਲ ਫਾਈਲਸਿਸਟਮ ਪੱਥ ਪਾਇਆ ਗਿਆ। ਅੱਪਡੇਟ ਲਈ ਮਾਡਲ ਸ਼ੌਰਟਨੇਮ ਦੀ ਲੋੜ ਹੈ, ਜਾਰੀ ਨਹੀਂ ਰੱਖ ਸਕਦੇ।",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "ਮਾਡਲ ਫਾਈਲਸਿਸਟਮ ਪੱਥ ਪਾਇਆ ਗਿਆ। ਅੱਪਡੇਟ ਲਈ ਮਾਡਲ ਸ਼ੌਰਟਨੇਮ ਦੀ ਲੋੜ ਹੈ, ਜਾਰੀ ਨਹੀਂ ਰੱਖ ਸਕਦੇ।",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "ਮਾਡਲ ਨਾਮ",
 	"Model Name": "ਮਾਡਲ ਨਾਮ",
 	"Model not selected": "ਮਾਡਲ ਚੁਣਿਆ ਨਹੀਂ ਗਿਆ",
 	"Model not selected": "ਮਾਡਲ ਚੁਣਿਆ ਨਹੀਂ ਗਿਆ",
 	"Model Tag Name": "ਮਾਡਲ ਟੈਗ ਨਾਮ",
 	"Model Tag Name": "ਮਾਡਲ ਟੈਗ ਨਾਮ",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "ਆਪਣੀ ਮਾਡਲਫਾਈਲ ਦਾ ਨਾਮ ਰੱਖੋ",
 	"Name your modelfile": "ਆਪਣੀ ਮਾਡਲਫਾਈਲ ਦਾ ਨਾਮ ਰੱਖੋ",
 	"New Chat": "ਨਵੀਂ ਗੱਲਬਾਤ",
 	"New Chat": "ਨਵੀਂ ਗੱਲਬਾਤ",
 	"New Password": "ਨਵਾਂ ਪਾਸਵਰਡ",
 	"New Password": "ਨਵਾਂ ਪਾਸਵਰਡ",
+	"No": "",
 	"No results found": "ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਮਿਲੇ",
 	"No results found": "ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਮਿਲੇ",
 	"No source available": "ਕੋਈ ਸਰੋਤ ਉਪਲਬਧ ਨਹੀਂ",
 	"No source available": "ਕੋਈ ਸਰੋਤ ਉਪਲਬਧ ਨਹੀਂ",
 	"Not factually correct": "ਤੱਥਕ ਰੂਪ ਵਿੱਚ ਸਹੀ ਨਹੀਂ",
 	"Not factually correct": "ਤੱਥਕ ਰੂਪ ਵਿੱਚ ਸਹੀ ਨਹੀਂ",
@@ -385,6 +397,7 @@
 	"Select a model": "ਇੱਕ ਮਾਡਲ ਚੁਣੋ",
 	"Select a model": "ਇੱਕ ਮਾਡਲ ਚੁਣੋ",
 	"Select an Ollama instance": "ਇੱਕ ਓਲਾਮਾ ਇੰਸਟੈਂਸ ਚੁਣੋ",
 	"Select an Ollama instance": "ਇੱਕ ਓਲਾਮਾ ਇੰਸਟੈਂਸ ਚੁਣੋ",
 	"Select model": "ਮਾਡਲ ਚੁਣੋ",
 	"Select model": "ਮਾਡਲ ਚੁਣੋ",
+	"Selected models do not support image inputs": "",
 	"Send": "ਭੇਜੋ",
 	"Send": "ਭੇਜੋ",
 	"Send a Message": "ਇੱਕ ਸੁਨੇਹਾ ਭੇਜੋ",
 	"Send a Message": "ਇੱਕ ਸੁਨੇਹਾ ਭੇਜੋ",
 	"Send message": "ਸੁਨੇਹਾ ਭੇਜੋ",
 	"Send message": "ਸੁਨੇਹਾ ਭੇਜੋ",
@@ -492,6 +505,7 @@
 	"Workspace": "ਕਾਰਜਸਥਲ",
 	"Workspace": "ਕਾਰਜਸਥਲ",
 	"Write a prompt suggestion (e.g. Who are you?)": "ਇੱਕ ਪ੍ਰੰਪਟ ਸੁਝਾਅ ਲਿਖੋ (ਉਦਾਹਰਣ ਲਈ ਤੁਸੀਂ ਕੌਣ ਹੋ?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "ਇੱਕ ਪ੍ਰੰਪਟ ਸੁਝਾਅ ਲਿਖੋ (ਉਦਾਹਰਣ ਲਈ ਤੁਸੀਂ ਕੌਣ ਹੋ?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "50 ਸ਼ਬਦਾਂ ਵਿੱਚ ਇੱਕ ਸੰਖੇਪ ਲਿਖੋ ਜੋ [ਵਿਸ਼ਾ ਜਾਂ ਕੁੰਜੀ ਸ਼ਬਦ] ਨੂੰ ਸੰਖੇਪ ਕਰਦਾ ਹੈ।",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "50 ਸ਼ਬਦਾਂ ਵਿੱਚ ਇੱਕ ਸੰਖੇਪ ਲਿਖੋ ਜੋ [ਵਿਸ਼ਾ ਜਾਂ ਕੁੰਜੀ ਸ਼ਬਦ] ਨੂੰ ਸੰਖੇਪ ਕਰਦਾ ਹੈ।",
+	"Yes": "",
 	"Yesterday": "ਕੱਲ੍ਹ",
 	"Yesterday": "ਕੱਲ੍ਹ",
 	"You": "ਤੁਸੀਂ",
 	"You": "ਤੁਸੀਂ",
 	"You have no archived conversations.": "ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਆਰਕਾਈਵ ਕੀਤੀਆਂ ਗੱਲਾਂ ਨਹੀਂ ਹਨ।",
 	"You have no archived conversations.": "ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਆਰਕਾਈਵ ਕੀਤੀਆਂ ਗੱਲਾਂ ਨਹੀਂ ਹਨ।",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} myśli...",
 	"{{modelName}} is thinking...": "{{modelName}} myśli...",
 	"{{user}}'s Chats": "{{user}} - czaty",
 	"{{user}}'s Chats": "{{user}} - czaty",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
+	"A selected model does not support image input": "",
 	"a user": "użytkownik",
 	"a user": "użytkownik",
 	"About": "O nas",
 	"About": "O nas",
 	"Account": "Konto",
 	"Account": "Konto",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Zaawansowane parametry",
 	"Advanced Parameters": "Zaawansowane parametry",
 	"all": "wszyscy",
 	"all": "wszyscy",
 	"All Documents": "Wszystkie dokumenty",
 	"All Documents": "Wszystkie dokumenty",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Wszyscy użytkownicy",
 	"All Users": "Wszyscy użytkownicy",
 	"Allow": "Pozwól",
 	"Allow": "Pozwól",
 	"Allow Chat Deletion": "Pozwól na usuwanie czatu",
 	"Allow Chat Deletion": "Pozwól na usuwanie czatu",
@@ -115,6 +117,7 @@
 	"Created at": "Utworzono o",
 	"Created at": "Utworzono o",
 	"Created At": "Utworzono o",
 	"Created At": "Utworzono o",
 	"Current Model": "Bieżący model",
 	"Current Model": "Bieżący model",
+	"Current Models": "",
 	"Current Password": "Bieżące hasło",
 	"Current Password": "Bieżące hasło",
 	"Custom": "Niestandardowy",
 	"Custom": "Niestandardowy",
 	"Customize Ollama models for a specific purpose": "Dostosuj modele Ollama do określonego celu",
 	"Customize Ollama models for a specific purpose": "Dostosuj modele Ollama do określonego celu",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Wprowadź API LiteLLM RPM(litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Wprowadź API LiteLLM RPM(litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Wprowadź model LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Wprowadź model LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Wprowadź maksymalną liczbę tokenów (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Wprowadź maksymalną liczbę tokenów (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Wprowadź tag modelu (np. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Wprowadź tag modelu (np. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Wprowadź liczbę kroków (np. 50)",
 	"Enter Number of Steps (e.g. 50)": "Wprowadź liczbę kroków (np. 50)",
 	"Enter Score": "Wprowadź wynik",
 	"Enter Score": "Wprowadź wynik",
@@ -235,6 +239,7 @@
 	"Input commands": "Wprowadź komendy",
 	"Input commands": "Wprowadź komendy",
 	"Interface": "Interfejs",
 	"Interface": "Interfejs",
 	"Invalid Tag": "Nieprawidłowy tag",
 	"Invalid Tag": "Nieprawidłowy tag",
+	"Is Model Vision Capable": "",
 	"January": "Styczeń",
 	"January": "Styczeń",
 	"join our Discord for help.": "Dołącz do naszego Discorda po pomoc.",
 	"join our Discord for help.": "Dołącz do naszego Discorda po pomoc.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Stworzone przez społeczność OpenWebUI",
 	"Made by OpenWebUI Community": "Stworzone przez społeczność OpenWebUI",
 	"Make sure to enclose them with": "Upewnij się, że są one zamknięte w",
 	"Make sure to enclose them with": "Upewnij się, że są one zamknięte w",
 	"Manage LiteLLM Models": "Zarządzaj modelami LiteLLM",
 	"Manage LiteLLM Models": "Zarządzaj modelami LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Zarządzaj modelami",
 	"Manage Models": "Zarządzaj modelami",
 	"Manage Ollama Models": "Zarządzaj modelami Ollama",
 	"Manage Ollama Models": "Zarządzaj modelami Ollama",
 	"March": "Marzec",
 	"March": "Marzec",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' jest już w kolejce do pobrania.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' jest już w kolejce do pobrania.",
 	"Model {{modelId}} not found": "Model {{modelId}} nie został znaleziony",
 	"Model {{modelId}} not found": "Model {{modelId}} nie został znaleziony",
 	"Model {{modelName}} already exists.": "Model {{modelName}} już istnieje.",
 	"Model {{modelName}} already exists.": "Model {{modelName}} już istnieje.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Wykryto ścieżkę systemu plików modelu. Wymagana jest krótka nazwa modelu do aktualizacji, nie można kontynuować.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Wykryto ścieżkę systemu plików modelu. Wymagana jest krótka nazwa modelu do aktualizacji, nie można kontynuować.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nazwa modelu",
 	"Model Name": "Nazwa modelu",
 	"Model not selected": "Model nie został wybrany",
 	"Model not selected": "Model nie został wybrany",
 	"Model Tag Name": "Nazwa tagu modelu",
 	"Model Tag Name": "Nazwa tagu modelu",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
 	"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
 	"New Chat": "Nowy czat",
 	"New Chat": "Nowy czat",
 	"New Password": "Nowe hasło",
 	"New Password": "Nowe hasło",
+	"No": "",
 	"No results found": "Nie znaleziono rezultatów",
 	"No results found": "Nie znaleziono rezultatów",
 	"No source available": "Źródło nie dostępne",
 	"No source available": "Źródło nie dostępne",
 	"Not factually correct": "Nie zgodne z faktami",
 	"Not factually correct": "Nie zgodne z faktami",
@@ -385,6 +397,7 @@
 	"Select a model": "Wybierz model",
 	"Select a model": "Wybierz model",
 	"Select an Ollama instance": "Wybierz instancję Ollama",
 	"Select an Ollama instance": "Wybierz instancję Ollama",
 	"Select model": "Wybierz model",
 	"Select model": "Wybierz model",
+	"Selected models do not support image inputs": "",
 	"Send": "Wyślij",
 	"Send": "Wyślij",
 	"Send a Message": "Wyślij Wiadomość",
 	"Send a Message": "Wyślij Wiadomość",
 	"Send message": "Wyślij wiadomość",
 	"Send message": "Wyślij wiadomość",
@@ -492,6 +505,7 @@
 	"Workspace": "Obszar roboczy",
 	"Workspace": "Obszar roboczy",
 	"Write a prompt suggestion (e.g. Who are you?)": "Napisz sugestię do polecenia (np. Kim jesteś?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Napisz sugestię do polecenia (np. Kim jesteś?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Napisz podsumowanie w 50 słowach, które podsumowuje [temat lub słowo kluczowe].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Napisz podsumowanie w 50 słowach, które podsumowuje [temat lub słowo kluczowe].",
+	"Yes": "",
 	"Yesterday": "Wczoraj",
 	"Yesterday": "Wczoraj",
 	"You": "Ty",
 	"You": "Ty",
 	"You have no archived conversations.": "Nie masz zarchiwizowanych rozmów.",
 	"You have no archived conversations.": "Nie masz zarchiwizowanych rozmów.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} está pensando...",
 	"{{modelName}} is thinking...": "{{modelName}} está pensando...",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
+	"A selected model does not support image input": "",
 	"a user": "um usuário",
 	"a user": "um usuário",
 	"About": "Sobre",
 	"About": "Sobre",
 	"Account": "Conta",
 	"Account": "Conta",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Parâmetros Avançados",
 	"Advanced Parameters": "Parâmetros Avançados",
 	"all": "todos",
 	"all": "todos",
 	"All Documents": "Todos os Documentos",
 	"All Documents": "Todos os Documentos",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Todos os Usuários",
 	"All Users": "Todos os Usuários",
 	"Allow": "Permitir",
 	"Allow": "Permitir",
 	"Allow Chat Deletion": "Permitir Exclusão de Bate-papo",
 	"Allow Chat Deletion": "Permitir Exclusão de Bate-papo",
@@ -115,6 +117,7 @@
 	"Created at": "Criado em",
 	"Created at": "Criado em",
 	"Created At": "Criado em",
 	"Created At": "Criado em",
 	"Current Model": "Modelo Atual",
 	"Current Model": "Modelo Atual",
+	"Current Models": "",
 	"Current Password": "Senha Atual",
 	"Current Password": "Senha Atual",
 	"Custom": "Personalizado",
 	"Custom": "Personalizado",
 	"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
 	"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Digite o RPM da API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Digite o RPM da API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Digite o Modelo LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Digite o Modelo LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Digite o Máximo de Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Digite o Máximo de Tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Digite a tag do modelo (por exemplo, {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Digite a tag do modelo (por exemplo, {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Digite o Número de Etapas (por exemplo, 50)",
 	"Enter Number of Steps (e.g. 50)": "Digite o Número de Etapas (por exemplo, 50)",
 	"Enter Score": "Digite a Pontuação",
 	"Enter Score": "Digite a Pontuação",
@@ -235,6 +239,7 @@
 	"Input commands": "Comandos de entrada",
 	"Input commands": "Comandos de entrada",
 	"Interface": "Interface",
 	"Interface": "Interface",
 	"Invalid Tag": "Etiqueta Inválida",
 	"Invalid Tag": "Etiqueta Inválida",
+	"Is Model Vision Capable": "",
 	"January": "Janeiro",
 	"January": "Janeiro",
 	"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.",
 	"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
 	"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
 	"Make sure to enclose them with": "Certifique-se de colocá-los entre",
 	"Make sure to enclose them with": "Certifique-se de colocá-los entre",
 	"Manage LiteLLM Models": "Gerenciar Modelos LiteLLM",
 	"Manage LiteLLM Models": "Gerenciar Modelos LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Gerenciar Modelos",
 	"Manage Models": "Gerenciar Modelos",
 	"Manage Ollama Models": "Gerenciar Modelos Ollama",
 	"Manage Ollama Models": "Gerenciar Modelos Ollama",
 	"March": "Março",
 	"March": "Março",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para download.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para download.",
 	"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
 	"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
 	"Model {{modelName}} already exists.": "O modelo {{modelName}} já existe.",
 	"Model {{modelName}} already exists.": "O modelo {{modelName}} já existe.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Otkrivena putanja datoteke modela. Skraćeno ime modela je potrebno za ažuriranje, ne može se nastaviti.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Otkrivena putanja datoteke modela. Skraćeno ime modela je potrebno za ažuriranje, ne može se nastaviti.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nome do Modelo",
 	"Model Name": "Nome do Modelo",
 	"Model not selected": "Modelo não selecionado",
 	"Model not selected": "Modelo não selecionado",
 	"Model Tag Name": "Nome da Tag do Modelo",
 	"Model Tag Name": "Nome da Tag do Modelo",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
 	"New Chat": "Novo Bate-papo",
 	"New Chat": "Novo Bate-papo",
 	"New Password": "Nova Senha",
 	"New Password": "Nova Senha",
+	"No": "",
 	"No results found": "Nenhum resultado encontrado",
 	"No results found": "Nenhum resultado encontrado",
 	"No source available": "Nenhuma fonte disponível",
 	"No source available": "Nenhuma fonte disponível",
 	"Not factually correct": "Não é correto em termos factuais",
 	"Not factually correct": "Não é correto em termos factuais",
@@ -385,6 +397,7 @@
 	"Select a model": "Selecione um modelo",
 	"Select a model": "Selecione um modelo",
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select model": "Selecione um modelo",
 	"Select model": "Selecione um modelo",
+	"Selected models do not support image inputs": "",
 	"Send": "Enviar",
 	"Send": "Enviar",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send message": "Enviar mensagem",
 	"Send message": "Enviar mensagem",
@@ -492,6 +505,7 @@
 	"Workspace": "Espaço de trabalho",
 	"Workspace": "Espaço de trabalho",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
+	"Yes": "",
 	"Yesterday": "Ontem",
 	"Yesterday": "Ontem",
 	"You": "Você",
 	"You": "Você",
 	"You have no archived conversations.": "Você não tem conversas arquivadas.",
 	"You have no archived conversations.": "Você não tem conversas arquivadas.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} está pensando...",
 	"{{modelName}} is thinking...": "{{modelName}} está pensando...",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
+	"A selected model does not support image input": "",
 	"a user": "um usuário",
 	"a user": "um usuário",
 	"About": "Sobre",
 	"About": "Sobre",
 	"Account": "Conta",
 	"Account": "Conta",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Parâmetros Avançados",
 	"Advanced Parameters": "Parâmetros Avançados",
 	"all": "todos",
 	"all": "todos",
 	"All Documents": "Todos os Documentos",
 	"All Documents": "Todos os Documentos",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Todos os Usuários",
 	"All Users": "Todos os Usuários",
 	"Allow": "Permitir",
 	"Allow": "Permitir",
 	"Allow Chat Deletion": "Permitir Exclusão de Bate-papo",
 	"Allow Chat Deletion": "Permitir Exclusão de Bate-papo",
@@ -115,6 +117,7 @@
 	"Created at": "Criado em",
 	"Created at": "Criado em",
 	"Created At": "Criado em",
 	"Created At": "Criado em",
 	"Current Model": "Modelo Atual",
 	"Current Model": "Modelo Atual",
+	"Current Models": "",
 	"Current Password": "Senha Atual",
 	"Current Password": "Senha Atual",
 	"Custom": "Personalizado",
 	"Custom": "Personalizado",
 	"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
 	"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Digite o RPM da API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Digite o RPM da API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Digite o Modelo LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Digite o Modelo LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Digite o Máximo de Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Digite o Máximo de Tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Digite a tag do modelo (por exemplo, {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Digite a tag do modelo (por exemplo, {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Digite o Número de Etapas (por exemplo, 50)",
 	"Enter Number of Steps (e.g. 50)": "Digite o Número de Etapas (por exemplo, 50)",
 	"Enter Score": "Digite a Pontuação",
 	"Enter Score": "Digite a Pontuação",
@@ -235,6 +239,7 @@
 	"Input commands": "Comandos de entrada",
 	"Input commands": "Comandos de entrada",
 	"Interface": "Interface",
 	"Interface": "Interface",
 	"Invalid Tag": "Etiqueta Inválida",
 	"Invalid Tag": "Etiqueta Inválida",
+	"Is Model Vision Capable": "",
 	"January": "Janeiro",
 	"January": "Janeiro",
 	"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.",
 	"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
 	"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
 	"Make sure to enclose them with": "Certifique-se de colocá-los entre",
 	"Make sure to enclose them with": "Certifique-se de colocá-los entre",
 	"Manage LiteLLM Models": "Gerenciar Modelos LiteLLM",
 	"Manage LiteLLM Models": "Gerenciar Modelos LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Gerenciar Modelos",
 	"Manage Models": "Gerenciar Modelos",
 	"Manage Ollama Models": "Gerenciar Modelos Ollama",
 	"Manage Ollama Models": "Gerenciar Modelos Ollama",
 	"March": "Março",
 	"March": "Março",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para download.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para download.",
 	"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
 	"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
 	"Model {{modelName}} already exists.": "O modelo {{modelName}} já existe.",
 	"Model {{modelName}} already exists.": "O modelo {{modelName}} já existe.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Caminho do sistema de arquivos do modelo detectado. É necessário o nome curto do modelo para atualização, não é possível continuar.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Caminho do sistema de arquivos do modelo detectado. É necessário o nome curto do modelo para atualização, não é possível continuar.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nome do Modelo",
 	"Model Name": "Nome do Modelo",
 	"Model not selected": "Modelo não selecionado",
 	"Model not selected": "Modelo não selecionado",
 	"Model Tag Name": "Nome da Tag do Modelo",
 	"Model Tag Name": "Nome da Tag do Modelo",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
 	"New Chat": "Novo Bate-papo",
 	"New Chat": "Novo Bate-papo",
 	"New Password": "Nova Senha",
 	"New Password": "Nova Senha",
+	"No": "",
 	"No results found": "Nenhum resultado encontrado",
 	"No results found": "Nenhum resultado encontrado",
 	"No source available": "Nenhuma fonte disponível",
 	"No source available": "Nenhuma fonte disponível",
 	"Not factually correct": "Não é correto em termos factuais",
 	"Not factually correct": "Não é correto em termos factuais",
@@ -385,6 +397,7 @@
 	"Select a model": "Selecione um modelo",
 	"Select a model": "Selecione um modelo",
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select model": "Selecione um modelo",
 	"Select model": "Selecione um modelo",
+	"Selected models do not support image inputs": "",
 	"Send": "Enviar",
 	"Send": "Enviar",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send message": "Enviar mensagem",
 	"Send message": "Enviar mensagem",
@@ -492,6 +505,7 @@
 	"Workspace": "Espaço de Trabalho",
 	"Workspace": "Espaço de Trabalho",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
+	"Yes": "",
 	"Yesterday": "Ontem",
 	"Yesterday": "Ontem",
 	"You": "Você",
 	"You": "Você",
 	"You have no archived conversations.": "Você não tem bate-papos arquivados.",
 	"You have no archived conversations.": "Você não tem bate-papos arquivados.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} думает...",
 	"{{modelName}} is thinking...": "{{modelName}} думает...",
 	"{{user}}'s Chats": "{{user}} чаты",
 	"{{user}}'s Chats": "{{user}} чаты",
 	"{{webUIName}} Backend Required": "{{webUIName}} бэкенд требуемый",
 	"{{webUIName}} Backend Required": "{{webUIName}} бэкенд требуемый",
+	"A selected model does not support image input": "",
 	"a user": "пользователь",
 	"a user": "пользователь",
 	"About": "Об",
 	"About": "Об",
 	"Account": "Аккаунт",
 	"Account": "Аккаунт",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Расширенные Параметры",
 	"Advanced Parameters": "Расширенные Параметры",
 	"all": "всё",
 	"all": "всё",
 	"All Documents": "Все документы",
 	"All Documents": "Все документы",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Все пользователи",
 	"All Users": "Все пользователи",
 	"Allow": "Разрешить",
 	"Allow": "Разрешить",
 	"Allow Chat Deletion": "Дозволять удаление чат",
 	"Allow Chat Deletion": "Дозволять удаление чат",
@@ -115,6 +117,7 @@
 	"Created at": "Создано в",
 	"Created at": "Создано в",
 	"Created At": "Создано в",
 	"Created At": "Создано в",
 	"Current Model": "Текущая модель",
 	"Current Model": "Текущая модель",
+	"Current Models": "",
 	"Current Password": "Текущий пароль",
 	"Current Password": "Текущий пароль",
 	"Custom": "Пользовательский",
 	"Custom": "Пользовательский",
 	"Customize Ollama models for a specific purpose": "Настроить модели Ollama для конкретной цели",
 	"Customize Ollama models for a specific purpose": "Настроить модели Ollama для конкретной цели",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Введите RPM API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Введите RPM API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Введите модель LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Введите модель LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Введите максимальное количество токенов (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Введите максимальное количество токенов (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Введите тег модели (например, {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Введите тег модели (например, {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Введите количество шагов (например, 50)",
 	"Enter Number of Steps (e.g. 50)": "Введите количество шагов (например, 50)",
 	"Enter Score": "Введите оценку",
 	"Enter Score": "Введите оценку",
@@ -235,6 +239,7 @@
 	"Input commands": "Введите команды",
 	"Input commands": "Введите команды",
 	"Interface": "Интерфейс",
 	"Interface": "Интерфейс",
 	"Invalid Tag": "Недопустимый тег",
 	"Invalid Tag": "Недопустимый тег",
+	"Is Model Vision Capable": "",
 	"January": "Январь",
 	"January": "Январь",
 	"join our Discord for help.": "присоединяйтесь к нашему Discord для помощи.",
 	"join our Discord for help.": "присоединяйтесь к нашему Discord для помощи.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Сделано сообществом OpenWebUI",
 	"Made by OpenWebUI Community": "Сделано сообществом OpenWebUI",
 	"Make sure to enclose them with": "Убедитесь, что они заключены в",
 	"Make sure to enclose them with": "Убедитесь, что они заключены в",
 	"Manage LiteLLM Models": "Управление моделями LiteLLM",
 	"Manage LiteLLM Models": "Управление моделями LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Управление моделями",
 	"Manage Models": "Управление моделями",
 	"Manage Ollama Models": "Управление моделями Ollama",
 	"Manage Ollama Models": "Управление моделями Ollama",
 	"March": "Март",
 	"March": "Март",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' уже находится в очереди на загрузку.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' уже находится в очереди на загрузку.",
 	"Model {{modelId}} not found": "Модель {{modelId}} не найдена",
 	"Model {{modelId}} not found": "Модель {{modelId}} не найдена",
 	"Model {{modelName}} already exists.": "Модель {{modelName}} уже существует.",
 	"Model {{modelName}} already exists.": "Модель {{modelName}} уже существует.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Модель файловой системы обнаружена. Требуется имя тега модели для обновления, не удается продолжить.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Модель файловой системы обнаружена. Требуется имя тега модели для обновления, не удается продолжить.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Имя модели",
 	"Model Name": "Имя модели",
 	"Model not selected": "Модель не выбрана",
 	"Model not selected": "Модель не выбрана",
 	"Model Tag Name": "Имя тега модели",
 	"Model Tag Name": "Имя тега модели",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Назовите свой файл модели",
 	"Name your modelfile": "Назовите свой файл модели",
 	"New Chat": "Новый чат",
 	"New Chat": "Новый чат",
 	"New Password": "Новый пароль",
 	"New Password": "Новый пароль",
+	"No": "",
 	"No results found": "Результатов не найдено",
 	"No results found": "Результатов не найдено",
 	"No source available": "Нет доступных источников",
 	"No source available": "Нет доступных источников",
 	"Not factually correct": "Не фактически правильно",
 	"Not factually correct": "Не фактически правильно",
@@ -385,6 +397,7 @@
 	"Select a model": "Выберите модель",
 	"Select a model": "Выберите модель",
 	"Select an Ollama instance": "Выберите экземпляр Ollama",
 	"Select an Ollama instance": "Выберите экземпляр Ollama",
 	"Select model": "Выберите модель",
 	"Select model": "Выберите модель",
+	"Selected models do not support image inputs": "",
 	"Send": "Отправить",
 	"Send": "Отправить",
 	"Send a Message": "Отправить сообщение",
 	"Send a Message": "Отправить сообщение",
 	"Send message": "Отправить сообщение",
 	"Send message": "Отправить сообщение",
@@ -492,6 +505,7 @@
 	"Workspace": "Рабочая область",
 	"Workspace": "Рабочая область",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предложение промпта (например, Кто вы?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предложение промпта (например, Кто вы?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите резюме в 50 словах, которое кратко описывает [тему или ключевое слово].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите резюме в 50 словах, которое кратко описывает [тему или ключевое слово].",
+	"Yes": "",
 	"Yesterday": "Вчера",
 	"Yesterday": "Вчера",
 	"You": "Вы",
 	"You": "Вы",
 	"You have no archived conversations.": "У вас нет архивированных бесед.",
 	"You have no archived conversations.": "У вас нет архивированных бесед.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} размишља...",
 	"{{modelName}} is thinking...": "{{modelName}} размишља...",
 	"{{user}}'s Chats": "Ћаскања корисника {{user}}",
 	"{{user}}'s Chats": "Ћаскања корисника {{user}}",
 	"{{webUIName}} Backend Required": "Захтева се {{webUIName}} позадинац",
 	"{{webUIName}} Backend Required": "Захтева се {{webUIName}} позадинац",
+	"A selected model does not support image input": "",
 	"a user": "корисник",
 	"a user": "корисник",
 	"About": "О нама",
 	"About": "О нама",
 	"Account": "Налог",
 	"Account": "Налог",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Напредни параметри",
 	"Advanced Parameters": "Напредни параметри",
 	"all": "сви",
 	"all": "сви",
 	"All Documents": "Сви документи",
 	"All Documents": "Сви документи",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Сви корисници",
 	"All Users": "Сви корисници",
 	"Allow": "Дозволи",
 	"Allow": "Дозволи",
 	"Allow Chat Deletion": "Дозволи брисање ћаскања",
 	"Allow Chat Deletion": "Дозволи брисање ћаскања",
@@ -115,6 +117,7 @@
 	"Created at": "Направљено у",
 	"Created at": "Направљено у",
 	"Created At": "Направљено у",
 	"Created At": "Направљено у",
 	"Current Model": "Тренутни модел",
 	"Current Model": "Тренутни модел",
+	"Current Models": "",
 	"Current Password": "Тренутна лозинка",
 	"Current Password": "Тренутна лозинка",
 	"Custom": "Прилагођено",
 	"Custom": "Прилагођено",
 	"Customize Ollama models for a specific purpose": "Прилагоди Ollama моделе за специфичну намену",
 	"Customize Ollama models for a specific purpose": "Прилагоди Ollama моделе за специфичну намену",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Унесите LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Унесите LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Унесите LiteLLM модел (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Унесите LiteLLM модел (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Унесите највећи број жетона (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Унесите највећи број жетона (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Унесите ознаку модела (нпр. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Унесите ознаку модела (нпр. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Унесите број корака (нпр. 50)",
 	"Enter Number of Steps (e.g. 50)": "Унесите број корака (нпр. 50)",
 	"Enter Score": "Унесите резултат",
 	"Enter Score": "Унесите резултат",
@@ -235,6 +239,7 @@
 	"Input commands": "Унеси наредбе",
 	"Input commands": "Унеси наредбе",
 	"Interface": "Изглед",
 	"Interface": "Изглед",
 	"Invalid Tag": "Неисправна ознака",
 	"Invalid Tag": "Неисправна ознака",
+	"Is Model Vision Capable": "",
 	"January": "Јануар",
 	"January": "Јануар",
 	"join our Discord for help.": "придружите се нашем Дискорду за помоћ.",
 	"join our Discord for help.": "придружите се нашем Дискорду за помоћ.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Израдила OpenWebUI заједница",
 	"Made by OpenWebUI Community": "Израдила OpenWebUI заједница",
 	"Make sure to enclose them with": "Уверите се да их затворите са",
 	"Make sure to enclose them with": "Уверите се да их затворите са",
 	"Manage LiteLLM Models": "Управљај LiteLLM моделима",
 	"Manage LiteLLM Models": "Управљај LiteLLM моделима",
+	"Manage Model Information": "",
 	"Manage Models": "Управљај моделима",
 	"Manage Models": "Управљај моделима",
 	"Manage Ollama Models": "Управљај Ollama моделима",
 	"Manage Ollama Models": "Управљај Ollama моделима",
 	"March": "Март",
 	"March": "Март",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Модел „{{modelTag}}“ је већ у реду за преузимање.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Модел „{{modelTag}}“ је већ у реду за преузимање.",
 	"Model {{modelId}} not found": "Модел {{modelId}} није пронађен",
 	"Model {{modelId}} not found": "Модел {{modelId}} није пронађен",
 	"Model {{modelName}} already exists.": "Модел {{modelName}} већ постоји.",
 	"Model {{modelName}} already exists.": "Модел {{modelName}} већ постоји.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Откривена путања система датотека модела. За ажурирање је потребан кратак назив модела, не може се наставити.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Откривена путања система датотека модела. За ажурирање је потребан кратак назив модела, не може се наставити.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Назив модела",
 	"Model Name": "Назив модела",
 	"Model not selected": "Модел није изабран",
 	"Model not selected": "Модел није изабран",
 	"Model Tag Name": "Назив ознаке модела",
 	"Model Tag Name": "Назив ознаке модела",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Назовите вашу модел-датотеку",
 	"Name your modelfile": "Назовите вашу модел-датотеку",
 	"New Chat": "Ново ћаскање",
 	"New Chat": "Ново ћаскање",
 	"New Password": "Нова лозинка",
 	"New Password": "Нова лозинка",
+	"No": "",
 	"No results found": "Нема резултата",
 	"No results found": "Нема резултата",
 	"No source available": "Нема доступног извора",
 	"No source available": "Нема доступног извора",
 	"Not factually correct": "Није чињенично тачно",
 	"Not factually correct": "Није чињенично тачно",
@@ -385,6 +397,7 @@
 	"Select a model": "Изабери модел",
 	"Select a model": "Изабери модел",
 	"Select an Ollama instance": "Изабери Ollama инстанцу",
 	"Select an Ollama instance": "Изабери Ollama инстанцу",
 	"Select model": "Изабери модел",
 	"Select model": "Изабери модел",
+	"Selected models do not support image inputs": "",
 	"Send": "Пошаљи",
 	"Send": "Пошаљи",
 	"Send a Message": "Пошаљи поруку",
 	"Send a Message": "Пошаљи поруку",
 	"Send message": "Пошаљи поруку",
 	"Send message": "Пошаљи поруку",
@@ -492,6 +505,7 @@
 	"Workspace": "Радни простор",
 	"Workspace": "Радни простор",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предлог упита (нпр. „ко си ти?“)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предлог упита (нпр. „ко си ти?“)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите сажетак у 50 речи који резимира [тему или кључну реч].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите сажетак у 50 речи који резимира [тему или кључну реч].",
+	"Yes": "",
 	"Yesterday": "Јуче",
 	"Yesterday": "Јуче",
 	"You": "Ти",
 	"You": "Ти",
 	"You have no archived conversations.": "Немате архивиране разговоре.",
 	"You have no archived conversations.": "Немате архивиране разговоре.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} tänker...",
 	"{{modelName}} is thinking...": "{{modelName}} tänker...",
 	"{{user}}'s Chats": "{{user}}s Chats",
 	"{{user}}'s Chats": "{{user}}s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend krävs",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend krävs",
+	"A selected model does not support image input": "",
 	"a user": "en användare",
 	"a user": "en användare",
 	"About": "Om",
 	"About": "Om",
 	"Account": "Konto",
 	"Account": "Konto",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Avancerade parametrar",
 	"Advanced Parameters": "Avancerade parametrar",
 	"all": "alla",
 	"all": "alla",
 	"All Documents": "Alla dokument",
 	"All Documents": "Alla dokument",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Alla användare",
 	"All Users": "Alla användare",
 	"Allow": "Tillåt",
 	"Allow": "Tillåt",
 	"Allow Chat Deletion": "Tillåt chattborttagning",
 	"Allow Chat Deletion": "Tillåt chattborttagning",
@@ -115,6 +117,7 @@
 	"Created at": "Skapad",
 	"Created at": "Skapad",
 	"Created At": "Skapad",
 	"Created At": "Skapad",
 	"Current Model": "Aktuell modell",
 	"Current Model": "Aktuell modell",
+	"Current Models": "",
 	"Current Password": "Nuvarande lösenord",
 	"Current Password": "Nuvarande lösenord",
 	"Custom": "Anpassad",
 	"Custom": "Anpassad",
 	"Customize Ollama models for a specific purpose": "Anpassa Ollama-modeller för ett specifikt ändamål",
 	"Customize Ollama models for a specific purpose": "Anpassa Ollama-modeller för ett specifikt ändamål",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Ange LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Ange LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Ange LiteLLM-modell (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Ange LiteLLM-modell (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Ange max antal tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Ange max antal tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Ange modelltagg (t.ex. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Ange modelltagg (t.ex. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Ange antal steg (t.ex. 50)",
 	"Enter Number of Steps (e.g. 50)": "Ange antal steg (t.ex. 50)",
 	"Enter Score": "Ange poäng",
 	"Enter Score": "Ange poäng",
@@ -235,6 +239,7 @@
 	"Input commands": "Indatakommandon",
 	"Input commands": "Indatakommandon",
 	"Interface": "Gränssnitt",
 	"Interface": "Gränssnitt",
 	"Invalid Tag": "Ogiltig tagg",
 	"Invalid Tag": "Ogiltig tagg",
+	"Is Model Vision Capable": "",
 	"January": "januar",
 	"January": "januar",
 	"join our Discord for help.": "gå med i vår Discord för hjälp.",
 	"join our Discord for help.": "gå med i vår Discord för hjälp.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Skapad av OpenWebUI Community",
 	"Made by OpenWebUI Community": "Skapad av OpenWebUI Community",
 	"Make sure to enclose them with": "Se till att bifoga dem med",
 	"Make sure to enclose them with": "Se till att bifoga dem med",
 	"Manage LiteLLM Models": "Hantera LiteLLM-modeller",
 	"Manage LiteLLM Models": "Hantera LiteLLM-modeller",
+	"Manage Model Information": "",
 	"Manage Models": "Hantera modeller",
 	"Manage Models": "Hantera modeller",
 	"Manage Ollama Models": "Hantera Ollama-modeller",
 	"Manage Ollama Models": "Hantera Ollama-modeller",
 	"March": "mars",
 	"March": "mars",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Modellen '{{modelTag}}' är redan i kö för nedladdning.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Modellen '{{modelTag}}' är redan i kö för nedladdning.",
 	"Model {{modelId}} not found": "Modell {{modelId}} hittades inte",
 	"Model {{modelId}} not found": "Modell {{modelId}} hittades inte",
 	"Model {{modelName}} already exists.": "Modellen {{modelName}} finns redan.",
 	"Model {{modelName}} already exists.": "Modellen {{modelName}} finns redan.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modellens filsystemväg upptäckt. Modellens kortnamn krävs för uppdatering, kan inte fortsätta.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modellens filsystemväg upptäckt. Modellens kortnamn krävs för uppdatering, kan inte fortsätta.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Modellnamn",
 	"Model Name": "Modellnamn",
 	"Model not selected": "Modell inte vald",
 	"Model not selected": "Modell inte vald",
 	"Model Tag Name": "Modelltaggnamn",
 	"Model Tag Name": "Modelltaggnamn",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Namnge din modelfil",
 	"Name your modelfile": "Namnge din modelfil",
 	"New Chat": "Ny chatt",
 	"New Chat": "Ny chatt",
 	"New Password": "Nytt lösenord",
 	"New Password": "Nytt lösenord",
+	"No": "",
 	"No results found": "Inga resultat hittades",
 	"No results found": "Inga resultat hittades",
 	"No source available": "Ingen tilgjengelig kilde",
 	"No source available": "Ingen tilgjengelig kilde",
 	"Not factually correct": "Inte faktiskt korrekt",
 	"Not factually correct": "Inte faktiskt korrekt",
@@ -385,6 +397,7 @@
 	"Select a model": "Välj en modell",
 	"Select a model": "Välj en modell",
 	"Select an Ollama instance": "Välj en Ollama-instans",
 	"Select an Ollama instance": "Välj en Ollama-instans",
 	"Select model": "Välj en modell",
 	"Select model": "Välj en modell",
+	"Selected models do not support image inputs": "",
 	"Send": "Skicka",
 	"Send": "Skicka",
 	"Send a Message": "Skicka ett meddelande",
 	"Send a Message": "Skicka ett meddelande",
 	"Send message": "Skicka meddelande",
 	"Send message": "Skicka meddelande",
@@ -492,6 +505,7 @@
 	"Workspace": "arbetsyta",
 	"Workspace": "arbetsyta",
 	"Write a prompt suggestion (e.g. Who are you?)": "Skriv ett förslag (t.ex. Vem är du?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Skriv ett förslag (t.ex. Vem är du?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Skriv en sammanfattning på 50 ord som sammanfattar [ämne eller nyckelord].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Skriv en sammanfattning på 50 ord som sammanfattar [ämne eller nyckelord].",
+	"Yes": "",
 	"Yesterday": "Igenom",
 	"Yesterday": "Igenom",
 	"You": "du",
 	"You": "du",
 	"You have no archived conversations.": "Du har inga arkiverade konversationer.",
 	"You have no archived conversations.": "Du har inga arkiverade konversationer.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} düşünüyor...",
 	"{{modelName}} is thinking...": "{{modelName}} düşünüyor...",
 	"{{user}}'s Chats": "{{user}} Sohbetleri",
 	"{{user}}'s Chats": "{{user}} Sohbetleri",
 	"{{webUIName}} Backend Required": "{{webUIName}} Arkayüz Gerekli",
 	"{{webUIName}} Backend Required": "{{webUIName}} Arkayüz Gerekli",
+	"A selected model does not support image input": "",
 	"a user": "bir kullanıcı",
 	"a user": "bir kullanıcı",
 	"About": "Hakkında",
 	"About": "Hakkında",
 	"Account": "Hesap",
 	"Account": "Hesap",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Gelişmiş Parametreler",
 	"Advanced Parameters": "Gelişmiş Parametreler",
 	"all": "tümü",
 	"all": "tümü",
 	"All Documents": "Tüm Belgeler",
 	"All Documents": "Tüm Belgeler",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Tüm Kullanıcılar",
 	"All Users": "Tüm Kullanıcılar",
 	"Allow": "İzin ver",
 	"Allow": "İzin ver",
 	"Allow Chat Deletion": "Sohbet Silmeye İzin Ver",
 	"Allow Chat Deletion": "Sohbet Silmeye İzin Ver",
@@ -115,6 +117,7 @@
 	"Created at": "Oluşturulma tarihi",
 	"Created at": "Oluşturulma tarihi",
 	"Created At": "Şu Tarihte Oluşturuldu:",
 	"Created At": "Şu Tarihte Oluşturuldu:",
 	"Current Model": "Mevcut Model",
 	"Current Model": "Mevcut Model",
+	"Current Models": "",
 	"Current Password": "Mevcut Parola",
 	"Current Password": "Mevcut Parola",
 	"Custom": "Özel",
 	"Custom": "Özel",
 	"Customize Ollama models for a specific purpose": "Ollama modellerini belirli bir amaç için özelleştirin",
 	"Customize Ollama models for a specific purpose": "Ollama modellerini belirli bir amaç için özelleştirin",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM'ini Girin (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM'ini Girin (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM Modelini Girin (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM Modelini Girin (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Maksimum Token Sayısını Girin (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Maksimum Token Sayısını Girin (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Model etiketini girin (örn. {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Model etiketini girin (örn. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Adım Sayısını Girin (örn. 50)",
 	"Enter Number of Steps (e.g. 50)": "Adım Sayısını Girin (örn. 50)",
 	"Enter Score": "Skoru Girin",
 	"Enter Score": "Skoru Girin",
@@ -235,6 +239,7 @@
 	"Input commands": "Giriş komutları",
 	"Input commands": "Giriş komutları",
 	"Interface": "Arayüz",
 	"Interface": "Arayüz",
 	"Invalid Tag": "Geçersiz etiket",
 	"Invalid Tag": "Geçersiz etiket",
+	"Is Model Vision Capable": "",
 	"January": "Ocak",
 	"January": "Ocak",
 	"join our Discord for help.": "yardım için Discord'umuza katılın.",
 	"join our Discord for help.": "yardım için Discord'umuza katılın.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "OpenWebUI Topluluğu tarafından yapılmıştır",
 	"Made by OpenWebUI Community": "OpenWebUI Topluluğu tarafından yapılmıştır",
 	"Make sure to enclose them with": "Değişkenlerinizi şu şekilde biçimlendirin:",
 	"Make sure to enclose them with": "Değişkenlerinizi şu şekilde biçimlendirin:",
 	"Manage LiteLLM Models": "LiteLLM Modellerini Yönet",
 	"Manage LiteLLM Models": "LiteLLM Modellerini Yönet",
+	"Manage Model Information": "",
 	"Manage Models": "Modelleri Yönet",
 	"Manage Models": "Modelleri Yönet",
 	"Manage Ollama Models": "Ollama Modellerini Yönet",
 	"Manage Ollama Models": "Ollama Modellerini Yönet",
 	"March": "Mart",
 	"March": "Mart",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' zaten indirme sırasında.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' zaten indirme sırasında.",
 	"Model {{modelId}} not found": "{{modelId}} bulunamadı",
 	"Model {{modelId}} not found": "{{modelId}} bulunamadı",
 	"Model {{modelName}} already exists.": "{{modelName}} zaten mevcut.",
 	"Model {{modelName}} already exists.": "{{modelName}} zaten mevcut.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model dosya sistemi yolu algılandı. Güncelleme için model kısa adı gerekli, devam edilemiyor.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model dosya sistemi yolu algılandı. Güncelleme için model kısa adı gerekli, devam edilemiyor.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Model Adı",
 	"Model Name": "Model Adı",
 	"Model not selected": "Model seçilmedi",
 	"Model not selected": "Model seçilmedi",
 	"Model Tag Name": "Model Etiket Adı",
 	"Model Tag Name": "Model Etiket Adı",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Model dosyanıza ad verin",
 	"Name your modelfile": "Model dosyanıza ad verin",
 	"New Chat": "Yeni Sohbet",
 	"New Chat": "Yeni Sohbet",
 	"New Password": "Yeni Parola",
 	"New Password": "Yeni Parola",
+	"No": "",
 	"No results found": "Sonuç bulunamadı",
 	"No results found": "Sonuç bulunamadı",
 	"No source available": "Kaynak mevcut değil",
 	"No source available": "Kaynak mevcut değil",
 	"Not factually correct": "Gerçeklere göre doğru değil",
 	"Not factually correct": "Gerçeklere göre doğru değil",
@@ -385,6 +397,7 @@
 	"Select a model": "Bir model seç",
 	"Select a model": "Bir model seç",
 	"Select an Ollama instance": "Bir Ollama örneği seçin",
 	"Select an Ollama instance": "Bir Ollama örneği seçin",
 	"Select model": "Model seç",
 	"Select model": "Model seç",
+	"Selected models do not support image inputs": "",
 	"Send": "Gönder",
 	"Send": "Gönder",
 	"Send a Message": "Bir Mesaj Gönder",
 	"Send a Message": "Bir Mesaj Gönder",
 	"Send message": "Mesaj gönder",
 	"Send message": "Mesaj gönder",
@@ -492,6 +505,7 @@
 	"Workspace": "Çalışma Alanı",
 	"Workspace": "Çalışma Alanı",
 	"Write a prompt suggestion (e.g. Who are you?)": "Bir prompt önerisi yazın (örn. Sen kimsin?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Bir prompt önerisi yazın (örn. Sen kimsin?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.",
+	"Yes": "",
 	"Yesterday": "Dün",
 	"Yesterday": "Dün",
 	"You": "Sen",
 	"You": "Sen",
 	"You have no archived conversations.": "Arşivlenmiş sohbetleriniz yok.",
 	"You have no archived conversations.": "Arşivlenmiş sohbetleriniz yok.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} думає...",
 	"{{modelName}} is thinking...": "{{modelName}} думає...",
 	"{{user}}'s Chats": "Чати {{user}}а",
 	"{{user}}'s Chats": "Чати {{user}}а",
 	"{{webUIName}} Backend Required": "Необхідно підключення бекенду {{webUIName}}",
 	"{{webUIName}} Backend Required": "Необхідно підключення бекенду {{webUIName}}",
+	"A selected model does not support image input": "",
 	"a user": "користувача",
 	"a user": "користувача",
 	"About": "Про програму",
 	"About": "Про програму",
 	"Account": "Обліковий запис",
 	"Account": "Обліковий запис",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Розширені параметри",
 	"Advanced Parameters": "Розширені параметри",
 	"all": "всі",
 	"all": "всі",
 	"All Documents": "Усі документи",
 	"All Documents": "Усі документи",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Всі користувачі",
 	"All Users": "Всі користувачі",
 	"Allow": "Дозволити",
 	"Allow": "Дозволити",
 	"Allow Chat Deletion": "Дозволити видалення чату",
 	"Allow Chat Deletion": "Дозволити видалення чату",
@@ -115,6 +117,7 @@
 	"Created at": "Створено у",
 	"Created at": "Створено у",
 	"Created At": "Створено у",
 	"Created At": "Створено у",
 	"Current Model": "Поточна модель",
 	"Current Model": "Поточна модель",
+	"Current Models": "",
 	"Current Password": "Поточний пароль",
 	"Current Password": "Поточний пароль",
 	"Custom": "Налаштувати",
 	"Custom": "Налаштувати",
 	"Customize Ollama models for a specific purpose": "Налаштувати моделі Ollama для конкретної мети",
 	"Customize Ollama models for a specific purpose": "Налаштувати моделі Ollama для конкретної мети",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Введіть RPM API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Введіть RPM API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Введіть модель LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Введіть модель LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Введіть максимальну кількість токенів (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Введіть максимальну кількість токенів (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Введіть тег моделі (напр., {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Введіть тег моделі (напр., {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Введіть кількість кроків (напр., 50)",
 	"Enter Number of Steps (e.g. 50)": "Введіть кількість кроків (напр., 50)",
 	"Enter Score": "Введіть бал",
 	"Enter Score": "Введіть бал",
@@ -235,6 +239,7 @@
 	"Input commands": "Команди вводу",
 	"Input commands": "Команди вводу",
 	"Interface": "Інтерфейс",
 	"Interface": "Інтерфейс",
 	"Invalid Tag": "Недійсний тег",
 	"Invalid Tag": "Недійсний тег",
+	"Is Model Vision Capable": "",
 	"January": "Січень",
 	"January": "Січень",
 	"join our Discord for help.": "приєднуйтеся до нашого Discord для допомоги.",
 	"join our Discord for help.": "приєднуйтеся до нашого Discord для допомоги.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Зроблено спільнотою OpenWebUI",
 	"Made by OpenWebUI Community": "Зроблено спільнотою OpenWebUI",
 	"Make sure to enclose them with": "Переконайтеся, що вони закриті",
 	"Make sure to enclose them with": "Переконайтеся, що вони закриті",
 	"Manage LiteLLM Models": "Керування моделями LiteLLM",
 	"Manage LiteLLM Models": "Керування моделями LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Керування моделями",
 	"Manage Models": "Керування моделями",
 	"Manage Ollama Models": "Керування моделями Ollama",
 	"Manage Ollama Models": "Керування моделями Ollama",
 	"March": "Березень",
 	"March": "Березень",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' вже знаходиться в черзі на завантаження.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' вже знаходиться в черзі на завантаження.",
 	"Model {{modelId}} not found": "Модель {{modelId}} не знайдено",
 	"Model {{modelId}} not found": "Модель {{modelId}} не знайдено",
 	"Model {{modelName}} already exists.": "Модель {{modelName}} вже існує.",
 	"Model {{modelName}} already exists.": "Модель {{modelName}} вже існує.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Виявлено шлях до файлової системи моделі. Для оновлення потрібно вказати коротке ім'я моделі, не вдасться продовжити.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Виявлено шлях до файлової системи моделі. Для оновлення потрібно вказати коротке ім'я моделі, не вдасться продовжити.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Назва моделі",
 	"Model Name": "Назва моделі",
 	"Model not selected": "Модель не вибрана",
 	"Model not selected": "Модель не вибрана",
 	"Model Tag Name": "Ім'я тегу моделі",
 	"Model Tag Name": "Ім'я тегу моделі",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Назвіть свій файл моделі",
 	"Name your modelfile": "Назвіть свій файл моделі",
 	"New Chat": "Новий чат",
 	"New Chat": "Новий чат",
 	"New Password": "Новий пароль",
 	"New Password": "Новий пароль",
+	"No": "",
 	"No results found": "Не знайдено жодного результату",
 	"No results found": "Не знайдено жодного результату",
 	"No source available": "Джерело не доступне",
 	"No source available": "Джерело не доступне",
 	"Not factually correct": "Не відповідає дійсності",
 	"Not factually correct": "Не відповідає дійсності",
@@ -385,6 +397,7 @@
 	"Select a model": "Виберіть модель",
 	"Select a model": "Виберіть модель",
 	"Select an Ollama instance": "Виберіть екземпляр Ollama",
 	"Select an Ollama instance": "Виберіть екземпляр Ollama",
 	"Select model": "Вибрати модель",
 	"Select model": "Вибрати модель",
+	"Selected models do not support image inputs": "",
 	"Send": "Надіслати",
 	"Send": "Надіслати",
 	"Send a Message": "Надіслати повідомлення",
 	"Send a Message": "Надіслати повідомлення",
 	"Send message": "Надіслати повідомлення",
 	"Send message": "Надіслати повідомлення",
@@ -492,6 +505,7 @@
 	"Workspace": "Робочий простір",
 	"Workspace": "Робочий простір",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишіть промт (напр., Хто ти?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишіть промт (напр., Хто ти?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].",
+	"Yes": "",
 	"Yesterday": "Вчора",
 	"Yesterday": "Вчора",
 	"You": "Ви",
 	"You": "Ви",
 	"You have no archived conversations.": "У вас немає архівованих розмов.",
 	"You have no archived conversations.": "У вас немає архівованих розмов.",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} đang suy nghĩ...",
 	"{{modelName}} is thinking...": "{{modelName}} đang suy nghĩ...",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend",
 	"{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend",
+	"A selected model does not support image input": "",
 	"a user": "người sử dụng",
 	"a user": "người sử dụng",
 	"About": "Giới thiệu",
 	"About": "Giới thiệu",
 	"Account": "Tài khoản",
 	"Account": "Tài khoản",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "Các tham số Nâng cao",
 	"Advanced Parameters": "Các tham số Nâng cao",
 	"all": "tất cả",
 	"all": "tất cả",
 	"All Documents": "Tất cả tài liệu",
 	"All Documents": "Tất cả tài liệu",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "Danh sách người sử dụng",
 	"All Users": "Danh sách người sử dụng",
 	"Allow": "Cho phép",
 	"Allow": "Cho phép",
 	"Allow Chat Deletion": "Cho phép Xóa nội dung chat",
 	"Allow Chat Deletion": "Cho phép Xóa nội dung chat",
@@ -115,6 +117,7 @@
 	"Created at": "Được tạo vào lúc",
 	"Created at": "Được tạo vào lúc",
 	"Created At": "Tạo lúc",
 	"Created At": "Tạo lúc",
 	"Current Model": "Mô hình hiện tại",
 	"Current Model": "Mô hình hiện tại",
+	"Current Models": "",
 	"Current Password": "Mật khẩu hiện tại",
 	"Current Password": "Mật khẩu hiện tại",
 	"Custom": "Tùy chỉnh",
 	"Custom": "Tùy chỉnh",
 	"Customize Ollama models for a specific purpose": "Tùy chỉnh các mô hình dựa trên Ollama cho một mục đích cụ thể",
 	"Customize Ollama models for a specific purpose": "Tùy chỉnh các mô hình dựa trên Ollama cho một mục đích cụ thể",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Nhập RPM API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Nhập RPM API LiteLLM (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "Nhập Mô hình LiteLLM (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "Nhập Mô hình LiteLLM (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Nhập Số Token Tối đa (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Nhập Số Token Tối đa (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "Nhập thẻ mô hình (vd: {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Nhập thẻ mô hình (vd: {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Nhập số Steps (vd: 50)",
 	"Enter Number of Steps (e.g. 50)": "Nhập số Steps (vd: 50)",
 	"Enter Score": "Nhập Score",
 	"Enter Score": "Nhập Score",
@@ -235,6 +239,7 @@
 	"Input commands": "Nhập các câu lệnh",
 	"Input commands": "Nhập các câu lệnh",
 	"Interface": "Giao diện",
 	"Interface": "Giao diện",
 	"Invalid Tag": "Tag không hợp lệ",
 	"Invalid Tag": "Tag không hợp lệ",
+	"Is Model Vision Capable": "",
 	"January": "Tháng 1",
 	"January": "Tháng 1",
 	"join our Discord for help.": "tham gia Discord của chúng tôi để được trợ giúp.",
 	"join our Discord for help.": "tham gia Discord của chúng tôi để được trợ giúp.",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "Được tạo bởi Cộng đồng OpenWebUI",
 	"Made by OpenWebUI Community": "Được tạo bởi Cộng đồng OpenWebUI",
 	"Make sure to enclose them with": "Hãy chắc chắn bao quanh chúng bằng",
 	"Make sure to enclose them with": "Hãy chắc chắn bao quanh chúng bằng",
 	"Manage LiteLLM Models": "Quản lý mô hình với LiteLLM",
 	"Manage LiteLLM Models": "Quản lý mô hình với LiteLLM",
+	"Manage Model Information": "",
 	"Manage Models": "Quản lý mô hình",
 	"Manage Models": "Quản lý mô hình",
 	"Manage Ollama Models": "Quản lý mô hình với Ollama",
 	"Manage Ollama Models": "Quản lý mô hình với Ollama",
 	"March": "Tháng 3",
 	"March": "Tháng 3",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "Mô hình '{{modelTag}}' đã có trong hàng đợi để tải xuống.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Mô hình '{{modelTag}}' đã có trong hàng đợi để tải xuống.",
 	"Model {{modelId}} not found": "Không tìm thấy Mô hình {{modelId}}",
 	"Model {{modelId}} not found": "Không tìm thấy Mô hình {{modelId}}",
 	"Model {{modelName}} already exists.": "Mô hình {{modelName}} đã tồn tại.",
 	"Model {{modelName}} already exists.": "Mô hình {{modelName}} đã tồn tại.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Đường dẫn hệ thống tệp mô hình được phát hiện. Tên viết tắt mô hình là bắt buộc để cập nhật, không thể tiếp tục.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Đường dẫn hệ thống tệp mô hình được phát hiện. Tên viết tắt mô hình là bắt buộc để cập nhật, không thể tiếp tục.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Tên Mô hình",
 	"Model Name": "Tên Mô hình",
 	"Model not selected": "Chưa chọn Mô hình",
 	"Model not selected": "Chưa chọn Mô hình",
 	"Model Tag Name": "Tên thẻ Mô hình",
 	"Model Tag Name": "Tên thẻ Mô hình",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "Đặt tên cho tệp mô hình của bạn",
 	"Name your modelfile": "Đặt tên cho tệp mô hình của bạn",
 	"New Chat": "Tạo cuộc trò chuyện mới",
 	"New Chat": "Tạo cuộc trò chuyện mới",
 	"New Password": "Mật khẩu mới",
 	"New Password": "Mật khẩu mới",
+	"No": "",
 	"No results found": "Không tìm thấy kết quả",
 	"No results found": "Không tìm thấy kết quả",
 	"No source available": "Không có nguồn",
 	"No source available": "Không có nguồn",
 	"Not factually correct": "Không chính xác so với thực tế",
 	"Not factually correct": "Không chính xác so với thực tế",
@@ -385,6 +397,7 @@
 	"Select a model": "Chọn mô hình",
 	"Select a model": "Chọn mô hình",
 	"Select an Ollama instance": "Chọn một thực thể Ollama",
 	"Select an Ollama instance": "Chọn một thực thể Ollama",
 	"Select model": "Chọn model",
 	"Select model": "Chọn model",
+	"Selected models do not support image inputs": "",
 	"Send": "Gửi",
 	"Send": "Gửi",
 	"Send a Message": "Gửi yêu cầu",
 	"Send a Message": "Gửi yêu cầu",
 	"Send message": "Gửi yêu cầu",
 	"Send message": "Gửi yêu cầu",
@@ -492,6 +505,7 @@
 	"Workspace": "Workspace",
 	"Workspace": "Workspace",
 	"Write a prompt suggestion (e.g. Who are you?)": "Hãy viết một prompt (vd: Bạn là ai?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Hãy viết một prompt (vd: Bạn là ai?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].",
+	"Yes": "",
 	"Yesterday": "Hôm qua",
 	"Yesterday": "Hôm qua",
 	"You": "Bạn",
 	"You": "Bạn",
 	"You have no archived conversations.": "Bạn chưa lưu trữ một nội dung chat nào",
 	"You have no archived conversations.": "Bạn chưa lưu trữ một nội dung chat nào",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} 正在思考...",
 	"{{modelName}} is thinking...": "{{modelName}} 正在思考...",
 	"{{user}}'s Chats": "{{user}} 的聊天记录",
 	"{{user}}'s Chats": "{{user}} 的聊天记录",
 	"{{webUIName}} Backend Required": "需要 {{webUIName}} 后端",
 	"{{webUIName}} Backend Required": "需要 {{webUIName}} 后端",
+	"A selected model does not support image input": "",
 	"a user": "用户",
 	"a user": "用户",
 	"About": "关于",
 	"About": "关于",
 	"Account": "账户",
 	"Account": "账户",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "高级参数",
 	"Advanced Parameters": "高级参数",
 	"all": "所有",
 	"all": "所有",
 	"All Documents": "所有文档",
 	"All Documents": "所有文档",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "所有用户",
 	"All Users": "所有用户",
 	"Allow": "允许",
 	"Allow": "允许",
 	"Allow Chat Deletion": "允许删除聊天记录",
 	"Allow Chat Deletion": "允许删除聊天记录",
@@ -115,6 +117,7 @@
 	"Created at": "创建于",
 	"Created at": "创建于",
 	"Created At": "创建于",
 	"Created At": "创建于",
 	"Current Model": "当前模型",
 	"Current Model": "当前模型",
+	"Current Models": "",
 	"Current Password": "当前密码",
 	"Current Password": "当前密码",
 	"Custom": "自定义",
 	"Custom": "自定义",
 	"Customize Ollama models for a specific purpose": "定制特定用途的 Ollama 模型",
 	"Customize Ollama models for a specific purpose": "定制特定用途的 Ollama 模型",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "输入 LiteLLM API 速率限制 (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "输入 LiteLLM API 速率限制 (litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "输入 LiteLLM 模型 (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "输入 LiteLLM 模型 (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "输入模型的 Max Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "输入模型的 Max Tokens (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "输入模型标签 (例如{{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "输入模型标签 (例如{{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "输入步数 (例如 50)",
 	"Enter Number of Steps (e.g. 50)": "输入步数 (例如 50)",
 	"Enter Score": "输入分",
 	"Enter Score": "输入分",
@@ -235,6 +239,7 @@
 	"Input commands": "输入命令",
 	"Input commands": "输入命令",
 	"Interface": "界面",
 	"Interface": "界面",
 	"Invalid Tag": "无效标签",
 	"Invalid Tag": "无效标签",
+	"Is Model Vision Capable": "",
 	"January": "一月",
 	"January": "一月",
 	"join our Discord for help.": "加入我们的 Discord 寻求帮助。",
 	"join our Discord for help.": "加入我们的 Discord 寻求帮助。",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "由 OpenWebUI 社区制作",
 	"Made by OpenWebUI Community": "由 OpenWebUI 社区制作",
 	"Make sure to enclose them with": "确保将它们包含在内",
 	"Make sure to enclose them with": "确保将它们包含在内",
 	"Manage LiteLLM Models": "管理 LiteLLM 模型",
 	"Manage LiteLLM Models": "管理 LiteLLM 模型",
+	"Manage Model Information": "",
 	"Manage Models": "管理模型",
 	"Manage Models": "管理模型",
 	"Manage Ollama Models": "管理 Ollama 模型",
 	"Manage Ollama Models": "管理 Ollama 模型",
 	"March": "三月",
 	"March": "三月",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "模型'{{modelTag}}'已在下载队列中。",
 	"Model '{{modelTag}}' is already in queue for downloading.": "模型'{{modelTag}}'已在下载队列中。",
 	"Model {{modelId}} not found": "未找到模型{{modelId}}",
 	"Model {{modelId}} not found": "未找到模型{{modelId}}",
 	"Model {{modelName}} already exists.": "模型{{modelName}}已存在。",
 	"Model {{modelName}} already exists.": "模型{{modelName}}已存在。",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "检测到模型文件系统路径。模型简名是更新所必需的,无法继续。",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "检测到模型文件系统路径。模型简名是更新所必需的,无法继续。",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "模型名称",
 	"Model Name": "模型名称",
 	"Model not selected": "未选择模型",
 	"Model not selected": "未选择模型",
 	"Model Tag Name": "模型标签名称",
 	"Model Tag Name": "模型标签名称",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "命名你的模型文件",
 	"Name your modelfile": "命名你的模型文件",
 	"New Chat": "新聊天",
 	"New Chat": "新聊天",
 	"New Password": "新密码",
 	"New Password": "新密码",
+	"No": "",
 	"No results found": "未找到结果",
 	"No results found": "未找到结果",
 	"No source available": "没有可用来源",
 	"No source available": "没有可用来源",
 	"Not factually correct": "与事实不符",
 	"Not factually correct": "与事实不符",
@@ -385,6 +397,7 @@
 	"Select a model": "选择一个模型",
 	"Select a model": "选择一个模型",
 	"Select an Ollama instance": "选择一个 Ollama 实例",
 	"Select an Ollama instance": "选择一个 Ollama 实例",
 	"Select model": "选择模型",
 	"Select model": "选择模型",
+	"Selected models do not support image inputs": "",
 	"Send": "发送",
 	"Send": "发送",
 	"Send a Message": "发送消息",
 	"Send a Message": "发送消息",
 	"Send message": "发送消息",
 	"Send message": "发送消息",
@@ -492,6 +505,7 @@
 	"Workspace": "工作空间",
 	"Workspace": "工作空间",
 	"Write a prompt suggestion (e.g. Who are you?)": "写一个提示建议(例如:你是谁?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "写一个提示建议(例如:你是谁?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "用 50 个字写一个总结 [主题或关键词]。",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "用 50 个字写一个总结 [主题或关键词]。",
+	"Yes": "",
 	"Yesterday": "昨天",
 	"Yesterday": "昨天",
 	"You": "你",
 	"You": "你",
 	"You have no archived conversations.": "你没有存档的对话。",
 	"You have no archived conversations.": "你没有存档的对话。",

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

@@ -6,6 +6,7 @@
 	"{{modelName}} is thinking...": "{{modelName}} 正在思考...",
 	"{{modelName}} is thinking...": "{{modelName}} 正在思考...",
 	"{{user}}'s Chats": "{{user}} 的聊天",
 	"{{user}}'s Chats": "{{user}} 的聊天",
 	"{{webUIName}} Backend Required": "需要 {{webUIName}} 後台",
 	"{{webUIName}} Backend Required": "需要 {{webUIName}} 後台",
+	"A selected model does not support image input": "",
 	"a user": "使用者",
 	"a user": "使用者",
 	"About": "關於",
 	"About": "關於",
 	"Account": "帳號",
 	"Account": "帳號",
@@ -31,6 +32,7 @@
 	"Advanced Parameters": "進階參數",
 	"Advanced Parameters": "進階參數",
 	"all": "所有",
 	"all": "所有",
 	"All Documents": "所有文件",
 	"All Documents": "所有文件",
+	"All selected models do not support image input, removed images": "",
 	"All Users": "所有使用者",
 	"All Users": "所有使用者",
 	"Allow": "允許",
 	"Allow": "允許",
 	"Allow Chat Deletion": "允許刪除聊天紀錄",
 	"Allow Chat Deletion": "允許刪除聊天紀錄",
@@ -115,6 +117,7 @@
 	"Created at": "建立於",
 	"Created at": "建立於",
 	"Created At": "建立於",
 	"Created At": "建立於",
 	"Current Model": "目前模型",
 	"Current Model": "目前模型",
+	"Current Models": "",
 	"Current Password": "目前密碼",
 	"Current Password": "目前密碼",
 	"Custom": "自訂",
 	"Custom": "自訂",
 	"Customize Ollama models for a specific purpose": "定制特定用途的 Ollama 模型",
 	"Customize Ollama models for a specific purpose": "定制特定用途的 Ollama 模型",
@@ -181,6 +184,7 @@
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "輸入 LiteLLM API RPM(litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "輸入 LiteLLM API RPM(litellm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "輸入 LiteLLM 模型(litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "輸入 LiteLLM 模型(litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "輸入最大 Token 數(litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "輸入最大 Token 數(litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "輸入模型標籤(例如 {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "輸入模型標籤(例如 {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "輸入步數(例如 50)",
 	"Enter Number of Steps (e.g. 50)": "輸入步數(例如 50)",
 	"Enter Score": "輸入分數",
 	"Enter Score": "輸入分數",
@@ -235,6 +239,7 @@
 	"Input commands": "輸入命令",
 	"Input commands": "輸入命令",
 	"Interface": "介面",
 	"Interface": "介面",
 	"Invalid Tag": "無效標籤",
 	"Invalid Tag": "無效標籤",
+	"Is Model Vision Capable": "",
 	"January": "1月",
 	"January": "1月",
 	"join our Discord for help.": "加入我們的 Discord 尋找幫助。",
 	"join our Discord for help.": "加入我們的 Discord 尋找幫助。",
 	"JSON": "JSON",
 	"JSON": "JSON",
@@ -253,6 +258,7 @@
 	"Made by OpenWebUI Community": "由 OpenWebUI 社區製作",
 	"Made by OpenWebUI Community": "由 OpenWebUI 社區製作",
 	"Make sure to enclose them with": "請確保變數有被以下符號框住:",
 	"Make sure to enclose them with": "請確保變數有被以下符號框住:",
 	"Manage LiteLLM Models": "管理 LiteLLM 模型",
 	"Manage LiteLLM Models": "管理 LiteLLM 模型",
+	"Manage Model Information": "",
 	"Manage Models": "管理模組",
 	"Manage Models": "管理模組",
 	"Manage Ollama Models": "管理 Ollama 模型",
 	"Manage Ollama Models": "管理 Ollama 模型",
 	"March": "3月",
 	"March": "3月",
@@ -272,7 +278,12 @@
 	"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' 模型已經在下載佇列中。",
 	"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' 模型已經在下載佇列中。",
 	"Model {{modelId}} not found": "找不到 {{modelId}} 模型",
 	"Model {{modelId}} not found": "找不到 {{modelId}} 模型",
 	"Model {{modelName}} already exists.": "模型 {{modelName}} 已存在。",
 	"Model {{modelName}} already exists.": "模型 {{modelName}} 已存在。",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "模型文件系統路徑已檢測。需要更新模型短名,無法繼續。",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "模型文件系統路徑已檢測。需要更新模型短名,無法繼續。",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "模型名稱",
 	"Model Name": "模型名稱",
 	"Model not selected": "未選擇模型",
 	"Model not selected": "未選擇模型",
 	"Model Tag Name": "模型標籤",
 	"Model Tag Name": "模型標籤",
@@ -289,6 +300,7 @@
 	"Name your modelfile": "命名你的 Modelfile",
 	"Name your modelfile": "命名你的 Modelfile",
 	"New Chat": "新增聊天",
 	"New Chat": "新增聊天",
 	"New Password": "新密碼",
 	"New Password": "新密碼",
+	"No": "",
 	"No results found": "沒有找到結果",
 	"No results found": "沒有找到結果",
 	"No source available": "沒有可用的來源",
 	"No source available": "沒有可用的來源",
 	"Not factually correct": "與真實資訊不相符",
 	"Not factually correct": "與真實資訊不相符",
@@ -385,6 +397,7 @@
 	"Select a model": "選擇一個模型",
 	"Select a model": "選擇一個模型",
 	"Select an Ollama instance": "選擇 Ollama 實例",
 	"Select an Ollama instance": "選擇 Ollama 實例",
 	"Select model": "選擇模型",
 	"Select model": "選擇模型",
+	"Selected models do not support image inputs": "",
 	"Send": "傳送",
 	"Send": "傳送",
 	"Send a Message": "傳送訊息",
 	"Send a Message": "傳送訊息",
 	"Send message": "傳送訊息",
 	"Send message": "傳送訊息",
@@ -492,6 +505,7 @@
 	"Workspace": "工作區",
 	"Workspace": "工作區",
 	"Write a prompt suggestion (e.g. Who are you?)": "寫一個提示詞建議(例如:你是誰?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "寫一個提示詞建議(例如:你是誰?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "寫一個 50 字的摘要來概括 [主題或關鍵詞]。",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "寫一個 50 字的摘要來概括 [主題或關鍵詞]。",
+	"Yes": "",
 	"Yesterday": "昨天",
 	"Yesterday": "昨天",
 	"You": "你",
 	"You": "你",
 	"You have no archived conversations.": "你沒有任何已封存的對話",
 	"You have no archived conversations.": "你沒有任何已封存的對話",

+ 11 - 9
src/lib/stores/index.ts

@@ -1,5 +1,6 @@
 import { APP_NAME } from '$lib/constants';
 import { APP_NAME } from '$lib/constants';
 import { type Writable, writable } from 'svelte/store';
 import { type Writable, writable } from 'svelte/store';
+import type { GlobalModelConfig, ModelConfig } from '$lib/apis';
 
 
 // Backend
 // Backend
 export const WEBUI_NAME = writable(APP_NAME);
 export const WEBUI_NAME = writable(APP_NAME);
@@ -42,27 +43,27 @@ export const showSettings = writable(false);
 export const showArchivedChats = writable(false);
 export const showArchivedChats = writable(false);
 export const showChangelog = writable(false);
 export const showChangelog = writable(false);
 
 
-type Model = OpenAIModel | OllamaModel;
+export type Model = OpenAIModel | OllamaModel;
 
 
-type OpenAIModel = {
+type BaseModel = {
 	id: string;
 	id: string;
 	name: string;
 	name: string;
-	external: boolean;
-	source?: string;
+	info?: ModelConfig;
 };
 };
 
 
-type OllamaModel = {
-	id: string;
-	name: string;
+export interface OpenAIModel extends BaseModel {
+	external: boolean;
+	source?: string;
+}
 
 
-	// Ollama specific fields
+export interface OllamaModel extends BaseModel {
 	details: OllamaModelDetails;
 	details: OllamaModelDetails;
 	size: number;
 	size: number;
 	description: string;
 	description: string;
 	model: string;
 	model: string;
 	modified_at: string;
 	modified_at: string;
 	digest: string;
 	digest: string;
-};
+}
 
 
 type OllamaModelDetails = {
 type OllamaModelDetails = {
 	parent_model: string;
 	parent_model: string;
@@ -133,6 +134,7 @@ type Config = {
 	default_models?: string[];
 	default_models?: string[];
 	default_prompt_suggestions?: PromptSuggestion[];
 	default_prompt_suggestions?: PromptSuggestion[];
 	trusted_header_auth?: boolean;
 	trusted_header_auth?: boolean;
+	model_config?: GlobalModelConfig;
 };
 };
 
 
 type PromptSuggestion = {
 type PromptSuggestion = {

+ 0 - 24
src/lib/utils/index.ts

@@ -1,29 +1,5 @@
 import { v4 as uuidv4 } from 'uuid';
 import { v4 as uuidv4 } from 'uuid';
 import sha256 from 'js-sha256';
 import sha256 from 'js-sha256';
-import { getOllamaModels } from '$lib/apis/ollama';
-import { getOpenAIModels } from '$lib/apis/openai';
-import { getLiteLLMModels } from '$lib/apis/litellm';
-
-export const getModels = async (token: string) => {
-	let models = await Promise.all([
-		getOllamaModels(token).catch((error) => {
-			console.log(error);
-			return null;
-		}),
-		getOpenAIModels(token).catch((error) => {
-			console.log(error);
-			return null;
-		}),
-		getLiteLLMModels(token).catch((error) => {
-			console.log(error);
-			return null;
-		})
-	]);
-
-	models = models.filter((models) => models).reduce((a, e, i, arr) => a.concat(e), []);
-
-	return models;
-};
 
 
 //////////////////////////
 //////////////////////////
 // Helper functions
 // Helper functions

+ 3 - 28
src/routes/(app)/+layout.svelte

@@ -7,9 +7,8 @@
 
 
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 
 
-	import { getModels as _getModels } from '$lib/utils';
+	import { getModels as _getModels } from '$lib/apis';
 	import { getOllamaVersion } from '$lib/apis/ollama';
 	import { getOllamaVersion } from '$lib/apis/ollama';
-	import { getModelfiles } from '$lib/apis/modelfiles';
 	import { getPrompts } from '$lib/apis/prompts';
 	import { getPrompts } from '$lib/apis/prompts';
 
 
 	import { getDocs } from '$lib/apis/documents';
 	import { getDocs } from '$lib/apis/documents';
@@ -20,7 +19,6 @@
 		showSettings,
 		showSettings,
 		settings,
 		settings,
 		models,
 		models,
-		modelfiles,
 		prompts,
 		prompts,
 		documents,
 		documents,
 		tags,
 		tags,
@@ -50,21 +48,6 @@
 		return _getModels(localStorage.token);
 		return _getModels(localStorage.token);
 	};
 	};
 
 
-	const setOllamaVersion = async (version: string = '') => {
-		if (version === '') {
-			version = await getOllamaVersion(localStorage.token).catch((error) => {
-				return '';
-			});
-		}
-
-		ollamaVersion = version;
-
-		console.log(ollamaVersion);
-		if (compareVersion(REQUIRED_OLLAMA_VERSION, ollamaVersion)) {
-			toast.error(`Ollama Version: ${ollamaVersion !== '' ? ollamaVersion : 'Not Detected'}`);
-		}
-	};
-
 	onMount(async () => {
 	onMount(async () => {
 		if ($user === undefined) {
 		if ($user === undefined) {
 			await goto('/auth');
 			await goto('/auth');
@@ -93,9 +76,6 @@
 				(async () => {
 				(async () => {
 					models.set(await getModels());
 					models.set(await getModels());
 				})(),
 				})(),
-				(async () => {
-					modelfiles.set(await getModelfiles(localStorage.token));
-				})(),
 				(async () => {
 				(async () => {
 					prompts.set(await getPrompts(localStorage.token));
 					prompts.set(await getPrompts(localStorage.token));
 				})(),
 				})(),
@@ -107,11 +87,6 @@
 				})()
 				})()
 			]);
 			]);
 
 
-			modelfiles.subscribe(async () => {
-				// should fetch models
-				models.set(await getModels());
-			});
-
 			document.addEventListener('keydown', function (event) {
 			document.addEventListener('keydown', function (event) {
 				const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
 				const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
 				// Check if the Shift key is pressed
 				// Check if the Shift key is pressed
@@ -188,12 +163,12 @@
 	});
 	});
 </script>
 </script>
 
 
-<div class=" hidden lg:flex fixed bottom-0 right-0 px-3 py-3 z-10">
+<div class=" hidden lg:flex fixed bottom-0 right-0 px-2 py-2 z-10">
 	<Tooltip content={$i18n.t('Help')} placement="left">
 	<Tooltip content={$i18n.t('Help')} placement="left">
 		<button
 		<button
 			id="show-shortcuts-button"
 			id="show-shortcuts-button"
 			bind:this={showShortcutsButtonElement}
 			bind:this={showShortcutsButtonElement}
-			class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full"
+			class="text-gray-600 dark:text-gray-300 bg-gray-300/20 size-5 flex items-center justify-center text-[0.7rem] rounded-full"
 			on:click={() => {
 			on:click={() => {
 				showShortcuts = !showShortcuts;
 				showShortcuts = !showShortcuts;
 			}}
 			}}

+ 2 - 2
src/routes/(app)/workspace/+layout.svelte

@@ -39,10 +39,10 @@
 			class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-xl bg-transparent/10 p-1"
 			class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-xl bg-transparent/10 p-1"
 		>
 		>
 			<a
 			<a
-				class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/modelfiles')
+				class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/models')
 					? 'bg-gray-50 dark:bg-gray-850'
 					? 'bg-gray-50 dark:bg-gray-850'
 					: ''} transition"
 					: ''} transition"
-				href="/workspace/modelfiles">{$i18n.t('Modelfiles')}</a
+				href="/workspace/models">{$i18n.t('Models')}</a
 			>
 			>
 
 
 			<a
 			<a

+ 1 - 1
src/routes/(app)/workspace/+page.svelte

@@ -3,6 +3,6 @@
 	import { onMount } from 'svelte';
 	import { onMount } from 'svelte';
 
 
 	onMount(() => {
 	onMount(() => {
-		goto('/workspace/modelfiles');
+		goto('/workspace/models');
 	});
 	});
 </script>
 </script>

+ 0 - 5
src/routes/(app)/workspace/modelfiles/+page.svelte

@@ -1,5 +0,0 @@
-<script>
-	import Modelfiles from '$lib/components/workspace/Modelfiles.svelte';
-</script>
-
-<Modelfiles />

+ 0 - 721
src/routes/(app)/workspace/modelfiles/create/+page.svelte

@@ -1,721 +0,0 @@
-<script>
-	import { v4 as uuidv4 } from 'uuid';
-	import { toast } from 'svelte-sonner';
-	import { goto } from '$app/navigation';
-	import { settings, user, config, modelfiles, models } from '$lib/stores';
-
-	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
-	import { splitStream } from '$lib/utils';
-	import { onMount, tick, getContext } from 'svelte';
-	import { createModel } from '$lib/apis/ollama';
-	import { createNewModelfile, getModelfileByTagName, getModelfiles } from '$lib/apis/modelfiles';
-
-	const i18n = getContext('i18n');
-
-	let loading = false;
-
-	let filesInputElement;
-	let inputFiles;
-	let imageUrl = null;
-	let digest = '';
-	let pullProgress = null;
-	let success = false;
-
-	// ///////////
-	// Modelfile
-	// ///////////
-
-	let title = '';
-	let tagName = '';
-	let desc = '';
-
-	let raw = true;
-	let advanced = false;
-
-	// Raw Mode
-	let content = '';
-
-	// Builder Mode
-	let model = '';
-	let system = '';
-	let template = '';
-	let options = {
-		// Advanced
-		seed: 0,
-		stop: '',
-		temperature: '',
-		repeat_penalty: '',
-		repeat_last_n: '',
-		mirostat: '',
-		mirostat_eta: '',
-		mirostat_tau: '',
-		top_k: '',
-		top_p: '',
-		tfs_z: '',
-		num_ctx: '',
-		num_predict: ''
-	};
-
-	let modelfileCreator = null;
-
-	$: tagName = title !== '' ? `${title.replace(/\s+/g, '-').toLowerCase()}:latest` : '';
-
-	$: if (!raw) {
-		content = `FROM ${model}
-${template !== '' ? `TEMPLATE """${template}"""` : ''}
-${options.seed !== 0 ? `PARAMETER seed ${options.seed}` : ''}
-${options.stop !== '' ? `PARAMETER stop ${options.stop}` : ''}
-${options.temperature !== '' ? `PARAMETER temperature ${options.temperature}` : ''}
-${options.repeat_penalty !== '' ? `PARAMETER repeat_penalty ${options.repeat_penalty}` : ''}
-${options.repeat_last_n !== '' ? `PARAMETER repeat_last_n ${options.repeat_last_n}` : ''}
-${options.mirostat !== '' ? `PARAMETER mirostat ${options.mirostat}` : ''}
-${options.mirostat_eta !== '' ? `PARAMETER mirostat_eta ${options.mirostat_eta}` : ''}
-${options.mirostat_tau !== '' ? `PARAMETER mirostat_tau ${options.mirostat_tau}` : ''}
-${options.top_k !== '' ? `PARAMETER top_k ${options.top_k}` : ''}
-${options.top_p !== '' ? `PARAMETER top_p ${options.top_p}` : ''}
-${options.tfs_z !== '' ? `PARAMETER tfs_z ${options.tfs_z}` : ''}
-${options.num_ctx !== '' ? `PARAMETER num_ctx ${options.num_ctx}` : ''}
-${options.num_predict !== '' ? `PARAMETER num_predict ${options.num_predict}` : ''}
-SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
-	}
-
-	let suggestions = [
-		{
-			content: ''
-		}
-	];
-
-	let categories = {
-		character: false,
-		assistant: false,
-		writing: false,
-		productivity: false,
-		programming: false,
-		'data analysis': false,
-		lifestyle: false,
-		education: false,
-		business: false
-	};
-
-	const saveModelfile = async (modelfile) => {
-		await createNewModelfile(localStorage.token, modelfile);
-		await modelfiles.set(await getModelfiles(localStorage.token));
-	};
-
-	const submitHandler = async () => {
-		loading = true;
-
-		if (Object.keys(categories).filter((category) => categories[category]).length == 0) {
-			toast.error(
-				'Uh-oh! It looks like you missed selecting a category. Please choose one to complete your modelfile.'
-			);
-			loading = false;
-			success = false;
-			return success;
-		}
-
-		if (
-			$models.map((model) => model.name).includes(tagName) ||
-			(await getModelfileByTagName(localStorage.token, tagName).catch(() => false))
-		) {
-			toast.error(
-				`Uh-oh! It looks like you already have a model named '${tagName}'. Please choose a different name to complete your modelfile.`
-			);
-			loading = false;
-			success = false;
-			return success;
-		}
-
-		if (
-			title !== '' &&
-			desc !== '' &&
-			content !== '' &&
-			Object.keys(categories).filter((category) => categories[category]).length > 0 &&
-			!$models.includes(tagName)
-		) {
-			const res = await createModel(localStorage.token, tagName, content);
-
-			if (res) {
-				const reader = res.body
-					.pipeThrough(new TextDecoderStream())
-					.pipeThrough(splitStream('\n'))
-					.getReader();
-
-				while (true) {
-					const { value, done } = await reader.read();
-					if (done) break;
-
-					try {
-						let lines = value.split('\n');
-
-						for (const line of lines) {
-							if (line !== '') {
-								console.log(line);
-								let data = JSON.parse(line);
-								console.log(data);
-
-								if (data.error) {
-									throw data.error;
-								}
-								if (data.detail) {
-									throw data.detail;
-								}
-
-								if (data.status) {
-									if (
-										!data.digest &&
-										!data.status.includes('writing') &&
-										!data.status.includes('sha256')
-									) {
-										toast.success(data.status);
-
-										if (data.status === 'success') {
-											success = true;
-										}
-									} else {
-										if (data.digest) {
-											digest = data.digest;
-
-											if (data.completed) {
-												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
-											} else {
-												pullProgress = 100;
-											}
-										}
-									}
-								}
-							}
-						}
-					} catch (error) {
-						console.log(error);
-						toast.error(error);
-					}
-				}
-			}
-
-			if (success) {
-				await saveModelfile({
-					tagName: tagName,
-					imageUrl: imageUrl,
-					title: title,
-					desc: desc,
-					content: content,
-					suggestionPrompts: suggestions.filter((prompt) => prompt.content !== ''),
-					categories: Object.keys(categories).filter((category) => categories[category]),
-					user: modelfileCreator !== null ? modelfileCreator : undefined
-				});
-				await goto('/workspace/modelfiles');
-			}
-		}
-		loading = false;
-		success = false;
-	};
-
-	onMount(async () => {
-		window.addEventListener('message', async (event) => {
-			if (
-				![
-					'https://ollamahub.com',
-					'https://www.ollamahub.com',
-					'https://openwebui.com',
-					'https://www.openwebui.com',
-					'http://localhost:5173'
-				].includes(event.origin)
-			)
-				return;
-			const modelfile = JSON.parse(event.data);
-			console.log(modelfile);
-
-			imageUrl = modelfile.imageUrl;
-			title = modelfile.title;
-			await tick();
-			tagName = `${modelfile.user.username === 'hub' ? '' : `hub/`}${modelfile.user.username}/${
-				modelfile.tagName
-			}`;
-			desc = modelfile.desc;
-			content = modelfile.content;
-			suggestions =
-				modelfile.suggestionPrompts.length != 0
-					? modelfile.suggestionPrompts
-					: [
-							{
-								content: ''
-							}
-					  ];
-
-			modelfileCreator = {
-				username: modelfile.user.username,
-				name: modelfile.user.name
-			};
-			for (const category of modelfile.categories) {
-				categories[category.toLowerCase()] = true;
-			}
-		});
-
-		if (window.opener ?? false) {
-			window.opener.postMessage('loaded', '*');
-		}
-
-		if (sessionStorage.modelfile) {
-			const modelfile = JSON.parse(sessionStorage.modelfile);
-			console.log(modelfile);
-			imageUrl = modelfile.imageUrl;
-			title = modelfile.title;
-			await tick();
-			tagName = modelfile.tagName;
-			desc = modelfile.desc;
-			content = modelfile.content;
-			suggestions =
-				modelfile.suggestionPrompts.length != 0
-					? modelfile.suggestionPrompts
-					: [
-							{
-								content: ''
-							}
-					  ];
-
-			for (const category of modelfile.categories) {
-				categories[category.toLowerCase()] = true;
-			}
-
-			sessionStorage.removeItem('modelfile');
-		}
-	});
-</script>
-
-<div class="w-full max-h-full">
-	<input
-		bind:this={filesInputElement}
-		bind:files={inputFiles}
-		type="file"
-		hidden
-		accept="image/*"
-		on:change={() => {
-			let reader = new FileReader();
-			reader.onload = (event) => {
-				let originalImageUrl = `${event.target.result}`;
-
-				const img = new Image();
-				img.src = originalImageUrl;
-
-				img.onload = function () {
-					const canvas = document.createElement('canvas');
-					const ctx = canvas.getContext('2d');
-
-					// Calculate the aspect ratio of the image
-					const aspectRatio = img.width / img.height;
-
-					// Calculate the new width and height to fit within 100x100
-					let newWidth, newHeight;
-					if (aspectRatio > 1) {
-						newWidth = 100 * aspectRatio;
-						newHeight = 100;
-					} else {
-						newWidth = 100;
-						newHeight = 100 / aspectRatio;
-					}
-
-					// Set the canvas size
-					canvas.width = 100;
-					canvas.height = 100;
-
-					// Calculate the position to center the image
-					const offsetX = (100 - newWidth) / 2;
-					const offsetY = (100 - newHeight) / 2;
-
-					// Draw the image on the canvas
-					ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
-
-					// Get the base64 representation of the compressed image
-					const compressedSrc = canvas.toDataURL('image/jpeg');
-
-					// Display the compressed image
-					imageUrl = compressedSrc;
-
-					inputFiles = null;
-				};
-			};
-
-			if (
-				inputFiles &&
-				inputFiles.length > 0 &&
-				['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
-			) {
-				reader.readAsDataURL(inputFiles[0]);
-			} else {
-				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
-				inputFiles = null;
-			}
-		}}
-	/>
-
-	<button
-		class="flex space-x-1"
-		on:click={() => {
-			history.back();
-		}}
-	>
-		<div class=" self-center">
-			<svg
-				xmlns="http://www.w3.org/2000/svg"
-				viewBox="0 0 20 20"
-				fill="currentColor"
-				class="w-4 h-4"
-			>
-				<path
-					fill-rule="evenodd"
-					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
-					clip-rule="evenodd"
-				/>
-			</svg>
-		</div>
-		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
-	</button>
-	<!-- <hr class="my-3 dark:border-gray-700" /> -->
-
-	<form
-		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
-		on:submit|preventDefault={() => {
-			submitHandler();
-		}}
-	>
-		<div class="flex justify-center my-4">
-			<div class="self-center">
-				<button
-					class=" {imageUrl
-						? ''
-						: 'p-6'} rounded-full dark:bg-gray-700 border border-dashed border-gray-200"
-					type="button"
-					on:click={() => {
-						filesInputElement.click();
-					}}
-				>
-					{#if imageUrl}
-						<img
-							src={imageUrl}
-							alt="modelfile profile"
-							class=" rounded-full w-20 h-20 object-cover"
-						/>
-					{:else}
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 24 24"
-							fill="currentColor"
-							class="w-8"
-						>
-							<path
-								fill-rule="evenodd"
-								d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
-								clip-rule="evenodd"
-							/>
-						</svg>
-					{/if}
-				</button>
-			</div>
-		</div>
-
-		<div class="my-2 flex space-x-2">
-			<div class="flex-1">
-				<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
-
-				<div>
-					<input
-						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-						placeholder={$i18n.t('Name your modelfile')}
-						bind:value={title}
-						required
-					/>
-				</div>
-			</div>
-
-			<div class="flex-1">
-				<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
-
-				<div>
-					<input
-						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-						placeholder={$i18n.t('Add a model tag name')}
-						bind:value={tagName}
-						required
-					/>
-				</div>
-			</div>
-		</div>
-
-		<div class="my-2">
-			<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
-
-			<div>
-				<input
-					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-					placeholder={$i18n.t('Add a short description about what this modelfile does')}
-					bind:value={desc}
-					required
-				/>
-			</div>
-		</div>
-
-		<div class="my-2">
-			<div class="flex w-full justify-between">
-				<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
-
-				<button
-					class="p-1 px-3 text-xs flex rounded transition"
-					type="button"
-					on:click={() => {
-						raw = !raw;
-					}}
-				>
-					{#if raw}
-						<span class="ml-2 self-center"> {$i18n.t('Raw Format')} </span>
-					{:else}
-						<span class="ml-2 self-center"> {$i18n.t('Builder Mode')} </span>
-					{/if}
-				</button>
-			</div>
-
-			<!-- <div class=" text-sm font-semibold mb-2"></div> -->
-
-			{#if raw}
-				<div class="mt-2">
-					<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
-
-					<div>
-						<textarea
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-							placeholder={`FROM llama2\nPARAMETER temperature 1\nSYSTEM """\nYou are Mario from Super Mario Bros, acting as an assistant.\n"""`}
-							rows="6"
-							bind:value={content}
-							required
-						/>
-					</div>
-
-					<div class="text-xs text-gray-400 dark:text-gray-500">
-						{$i18n.t('Not sure what to write? Switch to')}
-						<button
-							class="text-gray-500 dark:text-gray-300 font-medium cursor-pointer"
-							type="button"
-							on:click={() => {
-								raw = !raw;
-							}}>{$i18n.t('Builder Mode')}</button
-						>
-						or
-						<a
-							class=" text-gray-500 dark:text-gray-300 font-medium"
-							href="https://openwebui.com"
-							target="_blank"
-						>
-							{$i18n.t('Click here to check other modelfiles.')}
-						</a>
-					</div>
-				</div>
-			{:else}
-				<div class="my-2">
-					<div class=" text-xs font-semibold mb-2">{$i18n.t('From (Base Model)')}*</div>
-
-					<div>
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-							placeholder="Write a modelfile base model name (e.g. llama2, mistral)"
-							bind:value={model}
-							required
-						/>
-					</div>
-
-					<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
-						{$i18n.t('To access the available model names for downloading,')}
-						<a
-							class=" text-gray-500 dark:text-gray-300 font-medium"
-							href="https://ollama.com/library"
-							target="_blank">{$i18n.t('click here.')}</a
-						>
-					</div>
-				</div>
-
-				<div class="my-1">
-					<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
-
-					<div>
-						<textarea
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
-							placeholder={`Write your modelfile system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
-							rows="4"
-							bind:value={system}
-						/>
-					</div>
-				</div>
-
-				<div class="flex w-full justify-between">
-					<div class=" self-center text-sm font-semibold">
-						{$i18n.t('Modelfile Advanced Settings')}
-					</div>
-
-					<button
-						class="p-1 px-3 text-xs flex rounded transition"
-						type="button"
-						on:click={() => {
-							advanced = !advanced;
-						}}
-					>
-						{#if advanced}
-							<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
-						{:else}
-							<span class="ml-2 self-center">{$i18n.t('Default')}</span>
-						{/if}
-					</button>
-				</div>
-
-				{#if advanced}
-					<div class="my-2">
-						<div class=" text-xs font-semibold mb-2">{$i18n.t('Template')}</div>
-
-						<div>
-							<textarea
-								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
-								placeholder="Write your modelfile template content here"
-								rows="4"
-								bind:value={template}
-							/>
-						</div>
-					</div>
-
-					<div class="my-2">
-						<div class=" text-xs font-semibold mb-2">{$i18n.t('Parameters')}</div>
-
-						<div>
-							<AdvancedParams bind:options />
-						</div>
-					</div>
-				{/if}
-			{/if}
-		</div>
-
-		<div class="my-2">
-			<div class="flex w-full justify-between mb-2">
-				<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
-
-				<button
-					class="p-1 px-3 text-xs flex rounded transition"
-					type="button"
-					on:click={() => {
-						if (suggestions.length === 0 || suggestions.at(-1).content !== '') {
-							suggestions = [...suggestions, { content: '' }];
-						}
-					}}
-				>
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 20 20"
-						fill="currentColor"
-						class="w-4 h-4"
-					>
-						<path
-							d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
-						/>
-					</svg>
-				</button>
-			</div>
-			<div class="flex flex-col space-y-1">
-				{#each suggestions as prompt, promptIdx}
-					<div class=" flex border dark:border-gray-600 rounded-lg">
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
-							placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
-							bind:value={prompt.content}
-						/>
-
-						<button
-							class="px-2"
-							type="button"
-							on:click={() => {
-								suggestions.splice(promptIdx, 1);
-								suggestions = suggestions;
-							}}
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 20 20"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<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>
-				{/each}
-			</div>
-		</div>
-
-		<div class="my-2">
-			<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
-
-			<div class="grid grid-cols-4">
-				{#each Object.keys(categories) as category}
-					<div class="flex space-x-2 text-sm">
-						<input type="checkbox" bind:checked={categories[category]} />
-						<div class="capitalize">{category}</div>
-					</div>
-				{/each}
-			</div>
-		</div>
-
-		{#if pullProgress !== null}
-			<div class="my-2">
-				<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
-				<div class="w-full rounded-full dark:bg-gray-800">
-					<div
-						class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
-						style="width: {Math.max(15, pullProgress ?? 0)}%"
-					>
-						{pullProgress ?? 0}%
-					</div>
-				</div>
-				<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-					{digest}
-				</div>
-			</div>
-		{/if}
-
-		<div class="my-2 flex justify-end">
-			<button
-				class=" text-sm px-3 py-2 transition rounded-xl {loading
-					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
-					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
-				type="submit"
-				disabled={loading}
-			>
-				<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
-
-				{#if loading}
-					<div class="ml-1.5 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>
-</div>

+ 0 - 507
src/routes/(app)/workspace/modelfiles/edit/+page.svelte

@@ -1,507 +0,0 @@
-<script>
-	import { v4 as uuidv4 } from 'uuid';
-	import { toast } from 'svelte-sonner';
-	import { goto } from '$app/navigation';
-
-	import { onMount, getContext } from 'svelte';
-	import { page } from '$app/stores';
-
-	import { settings, user, config, modelfiles } from '$lib/stores';
-	import { splitStream } from '$lib/utils';
-
-	import { createModel } from '$lib/apis/ollama';
-	import { getModelfiles, updateModelfileByTagName } from '$lib/apis/modelfiles';
-
-	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
-
-	const i18n = getContext('i18n');
-
-	let loading = false;
-
-	let filesInputElement;
-	let inputFiles;
-	let imageUrl = null;
-	let digest = '';
-	let pullProgress = null;
-	let success = false;
-
-	let modelfile = null;
-	// ///////////
-	// Modelfile
-	// ///////////
-
-	let title = '';
-	let tagName = '';
-	let desc = '';
-
-	// Raw Mode
-	let content = '';
-
-	let suggestions = [
-		{
-			content: ''
-		}
-	];
-
-	let categories = {
-		character: false,
-		assistant: false,
-		writing: false,
-		productivity: false,
-		programming: false,
-		'data analysis': false,
-		lifestyle: false,
-		education: false,
-		business: false
-	};
-
-	onMount(() => {
-		tagName = $page.url.searchParams.get('tag');
-
-		if (tagName) {
-			modelfile = $modelfiles.filter((modelfile) => modelfile.tagName === tagName)[0];
-
-			console.log(modelfile);
-
-			imageUrl = modelfile.imageUrl;
-			title = modelfile.title;
-			desc = modelfile.desc;
-			content = modelfile.content;
-			suggestions =
-				modelfile.suggestionPrompts.length != 0
-					? modelfile.suggestionPrompts
-					: [
-							{
-								content: ''
-							}
-					  ];
-
-			for (const category of modelfile.categories) {
-				categories[category.toLowerCase()] = true;
-			}
-		} else {
-			goto('/workspace/modelfiles');
-		}
-	});
-
-	const updateModelfile = async (modelfile) => {
-		await updateModelfileByTagName(localStorage.token, modelfile.tagName, modelfile);
-		await modelfiles.set(await getModelfiles(localStorage.token));
-	};
-
-	const updateHandler = async () => {
-		loading = true;
-
-		if (Object.keys(categories).filter((category) => categories[category]).length == 0) {
-			toast.error(
-				'Uh-oh! It looks like you missed selecting a category. Please choose one to complete your modelfile.'
-			);
-		}
-
-		if (
-			title !== '' &&
-			desc !== '' &&
-			content !== '' &&
-			Object.keys(categories).filter((category) => categories[category]).length > 0
-		) {
-			const res = await createModel(localStorage.token, tagName, content);
-
-			if (res) {
-				const reader = res.body
-					.pipeThrough(new TextDecoderStream())
-					.pipeThrough(splitStream('\n'))
-					.getReader();
-
-				while (true) {
-					const { value, done } = await reader.read();
-					if (done) break;
-
-					try {
-						let lines = value.split('\n');
-
-						for (const line of lines) {
-							if (line !== '') {
-								console.log(line);
-								let data = JSON.parse(line);
-								console.log(data);
-
-								if (data.error) {
-									throw data.error;
-								}
-								if (data.detail) {
-									throw data.detail;
-								}
-
-								if (data.status) {
-									if (
-										!data.digest &&
-										!data.status.includes('writing') &&
-										!data.status.includes('sha256')
-									) {
-										toast.success(data.status);
-
-										if (data.status === 'success') {
-											success = true;
-										}
-									} else {
-										if (data.digest) {
-											digest = data.digest;
-
-											if (data.completed) {
-												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
-											} else {
-												pullProgress = 100;
-											}
-										}
-									}
-								}
-							}
-						}
-					} catch (error) {
-						console.log(error);
-						toast.error(error);
-					}
-				}
-			}
-
-			if (success) {
-				await updateModelfile({
-					tagName: tagName,
-					imageUrl: imageUrl,
-					title: title,
-					desc: desc,
-					content: content,
-					suggestionPrompts: suggestions.filter((prompt) => prompt.content !== ''),
-					categories: Object.keys(categories).filter((category) => categories[category])
-				});
-				await goto('/workspace/modelfiles');
-			}
-		}
-		loading = false;
-		success = false;
-	};
-</script>
-
-<div class="w-full max-h-full">
-	<input
-		bind:this={filesInputElement}
-		bind:files={inputFiles}
-		type="file"
-		hidden
-		accept="image/*"
-		on:change={() => {
-			let reader = new FileReader();
-			reader.onload = (event) => {
-				let originalImageUrl = `${event.target.result}`;
-
-				const img = new Image();
-				img.src = originalImageUrl;
-
-				img.onload = function () {
-					const canvas = document.createElement('canvas');
-					const ctx = canvas.getContext('2d');
-
-					// Calculate the aspect ratio of the image
-					const aspectRatio = img.width / img.height;
-
-					// Calculate the new width and height to fit within 100x100
-					let newWidth, newHeight;
-					if (aspectRatio > 1) {
-						newWidth = 100 * aspectRatio;
-						newHeight = 100;
-					} else {
-						newWidth = 100;
-						newHeight = 100 / aspectRatio;
-					}
-
-					// Set the canvas size
-					canvas.width = 100;
-					canvas.height = 100;
-
-					// Calculate the position to center the image
-					const offsetX = (100 - newWidth) / 2;
-					const offsetY = (100 - newHeight) / 2;
-
-					// Draw the image on the canvas
-					ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
-
-					// Get the base64 representation of the compressed image
-					const compressedSrc = canvas.toDataURL('image/jpeg');
-
-					// Display the compressed image
-					imageUrl = compressedSrc;
-
-					inputFiles = null;
-				};
-			};
-
-			if (
-				inputFiles &&
-				inputFiles.length > 0 &&
-				['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
-			) {
-				reader.readAsDataURL(inputFiles[0]);
-			} else {
-				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
-				inputFiles = null;
-			}
-		}}
-	/>
-
-	<button
-		class="flex space-x-1"
-		on:click={() => {
-			history.back();
-		}}
-	>
-		<div class=" self-center">
-			<svg
-				xmlns="http://www.w3.org/2000/svg"
-				viewBox="0 0 20 20"
-				fill="currentColor"
-				class="w-4 h-4"
-			>
-				<path
-					fill-rule="evenodd"
-					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
-					clip-rule="evenodd"
-				/>
-			</svg>
-		</div>
-		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
-	</button>
-	<form
-		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
-		on:submit|preventDefault={() => {
-			updateHandler();
-		}}
-	>
-		<div class="flex justify-center my-4">
-			<div class="self-center">
-				<button
-					class=" {imageUrl
-						? ''
-						: 'p-6'} rounded-full dark:bg-gray-700 border border-dashed border-gray-200"
-					type="button"
-					on:click={() => {
-						filesInputElement.click();
-					}}
-				>
-					{#if imageUrl}
-						<img
-							src={imageUrl}
-							alt="modelfile profile"
-							class=" rounded-full w-20 h-20 object-cover"
-						/>
-					{:else}
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 24 24"
-							fill="currentColor"
-							class="w-8"
-						>
-							<path
-								fill-rule="evenodd"
-								d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
-								clip-rule="evenodd"
-							/>
-						</svg>
-					{/if}
-				</button>
-			</div>
-		</div>
-
-		<div class="my-2 flex space-x-2">
-			<div class="flex-1">
-				<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
-
-				<div>
-					<input
-						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-						placeholder={$i18n.t('Name your modelfile')}
-						bind:value={title}
-						required
-					/>
-				</div>
-			</div>
-
-			<div class="flex-1">
-				<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
-
-				<div>
-					<input
-						class="px-3 py-1.5 text-sm w-full bg-transparent disabled:text-gray-500 border dark:border-gray-600 outline-none rounded-lg"
-						placeholder={$i18n.t('Add a model tag name')}
-						value={tagName}
-						disabled
-						required
-					/>
-				</div>
-			</div>
-		</div>
-
-		<div class="my-2">
-			<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
-
-			<div>
-				<input
-					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-					placeholder={$i18n.t('Add a short description about what this modelfile does')}
-					bind:value={desc}
-					required
-				/>
-			</div>
-		</div>
-
-		<div class="my-2">
-			<div class="flex w-full justify-between">
-				<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
-			</div>
-
-			<!-- <div class=" text-sm font-semibold mb-2"></div> -->
-
-			<div class="mt-2">
-				<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
-
-				<div>
-					<textarea
-						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-						placeholder={`FROM llama2\nPARAMETER temperature 1\nSYSTEM """\nYou are Mario from Super Mario Bros, acting as an assistant.\n"""`}
-						rows="6"
-						bind:value={content}
-						required
-					/>
-				</div>
-			</div>
-		</div>
-
-		<div class="my-2">
-			<div class="flex w-full justify-between mb-2">
-				<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
-
-				<button
-					class="p-1 px-3 text-xs flex rounded transition"
-					type="button"
-					on:click={() => {
-						if (suggestions.length === 0 || suggestions.at(-1).content !== '') {
-							suggestions = [...suggestions, { content: '' }];
-						}
-					}}
-				>
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 20 20"
-						fill="currentColor"
-						class="w-4 h-4"
-					>
-						<path
-							d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
-						/>
-					</svg>
-				</button>
-			</div>
-			<div class="flex flex-col space-y-1">
-				{#each suggestions as prompt, promptIdx}
-					<div class=" flex border dark:border-gray-600 rounded-lg">
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
-							placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
-							bind:value={prompt.content}
-						/>
-
-						<button
-							class="px-2"
-							type="button"
-							on:click={() => {
-								suggestions.splice(promptIdx, 1);
-								suggestions = suggestions;
-							}}
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 20 20"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<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>
-				{/each}
-			</div>
-		</div>
-
-		<div class="my-2">
-			<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
-
-			<div class="grid grid-cols-4">
-				{#each Object.keys(categories) as category}
-					<div class="flex space-x-2 text-sm">
-						<input type="checkbox" bind:checked={categories[category]} />
-
-						<div class=" capitalize">{category}</div>
-					</div>
-				{/each}
-			</div>
-		</div>
-
-		{#if pullProgress !== null}
-			<div class="my-2">
-				<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
-				<div class="w-full rounded-full dark:bg-gray-800">
-					<div
-						class="dark:bg-gray-600 text-xs font-medium text-blue-100 text-center p-0.5 leading-none rounded-full"
-						style="width: {Math.max(15, pullProgress ?? 0)}%"
-					>
-						{pullProgress ?? 0}%
-					</div>
-				</div>
-				<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-					{digest}
-				</div>
-			</div>
-		{/if}
-
-		<div class="my-2 flex justify-end">
-			<button
-				class=" text-sm px-3 py-2 transition rounded-xl {loading
-					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
-					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
-				type="submit"
-				disabled={loading}
-			>
-				<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
-
-				{#if loading}
-					<div class="ml-1.5 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>
-</div>

+ 5 - 0
src/routes/(app)/workspace/models/+page.svelte

@@ -0,0 +1,5 @@
+<script>
+	import Models from '$lib/components/workspace/Models.svelte';
+</script>
+
+<Models />

+ 576 - 0
src/routes/(app)/workspace/models/create/+page.svelte

@@ -0,0 +1,576 @@
+<script>
+	import { v4 as uuidv4 } from 'uuid';
+	import { toast } from 'svelte-sonner';
+	import { goto } from '$app/navigation';
+	import { settings, user, config, models } from '$lib/stores';
+
+	import { onMount, tick, getContext } from 'svelte';
+	import { addNewModel, getModelById, getModelInfos } from '$lib/apis/models';
+	import { getModels } from '$lib/apis';
+
+	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+
+	const i18n = getContext('i18n');
+
+	let filesInputElement;
+	let inputFiles;
+
+	let showAdvanced = false;
+	let showPreview = false;
+
+	let loading = false;
+	let success = false;
+
+	// ///////////
+	// Model
+	// ///////////
+
+	let id = '';
+	let name = '';
+
+	let params = {};
+	let capabilities = {
+		vision: true
+	};
+
+	let info = {
+		id: '',
+		base_model_id: null,
+		name: '',
+		meta: {
+			profile_image_url: null,
+			description: '',
+			suggestion_prompts: [
+				{
+					content: ''
+				}
+			]
+		},
+		params: {
+			system: ''
+		}
+	};
+
+	$: if (name) {
+		id = name.replace(/\s+/g, '-').toLowerCase();
+	}
+
+	const submitHandler = async () => {
+		loading = true;
+
+		info.id = id;
+		info.name = name;
+		info.meta.capabilities = capabilities;
+		info.params.stop = params.stop !== null ? params.stop.split(',').filter((s) => s.trim()) : null;
+
+		if ($models.find((m) => m.id === info.id)) {
+			toast.error(
+				`Error: A model with the ID '${info.id}' already exists. Please select a different ID to proceed.`
+			);
+			loading = false;
+			success = false;
+			return success;
+		}
+
+		if (info) {
+			const res = await addNewModel(localStorage.token, {
+				...info,
+				meta: {
+					...info.meta,
+					profile_image_url: info.meta.profile_image_url ?? '/favicon.png',
+					suggestion_prompts: info.meta.suggestion_prompts
+						? info.meta.suggestion_prompts.filter((prompt) => prompt.content !== '')
+						: null
+				},
+				params: { ...info.params, ...params }
+			});
+
+			if (res) {
+				toast.success('Model created successfully!');
+				await goto('/workspace/models');
+				await models.set(await getModels(localStorage.token));
+			}
+		}
+
+		loading = false;
+		success = false;
+	};
+
+	const initModel = async (model) => {
+		name = model.name;
+		await tick();
+
+		id = model.id;
+
+		params = { ...params, ...model?.info?.params };
+		params.stop = params?.stop ? (params?.stop ?? []).join(',') : null;
+
+		capabilities = { ...capabilities, ...(model?.info?.meta?.capabilities ?? {}) };
+
+		info = {
+			...info,
+			...model.info
+		};
+	};
+
+	onMount(async () => {
+		window.addEventListener('message', async (event) => {
+			if (
+				![
+					'https://ollamahub.com',
+					'https://www.ollamahub.com',
+					'https://openwebui.com',
+					'https://www.openwebui.com',
+					'http://localhost:5173'
+				].includes(event.origin)
+			)
+				return;
+
+			const model = JSON.parse(event.data);
+			console.log(model);
+
+			initModel(model);
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+
+		if (sessionStorage.model) {
+			const model = JSON.parse(sessionStorage.model);
+			sessionStorage.removeItem('model');
+
+			console.log(model);
+			initModel(model);
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<input
+		bind:this={filesInputElement}
+		bind:files={inputFiles}
+		type="file"
+		hidden
+		accept="image/*"
+		on:change={() => {
+			let reader = new FileReader();
+			reader.onload = (event) => {
+				let originalImageUrl = `${event.target.result}`;
+
+				const img = new Image();
+				img.src = originalImageUrl;
+
+				img.onload = function () {
+					const canvas = document.createElement('canvas');
+					const ctx = canvas.getContext('2d');
+
+					// Calculate the aspect ratio of the image
+					const aspectRatio = img.width / img.height;
+
+					// Calculate the new width and height to fit within 100x100
+					let newWidth, newHeight;
+					if (aspectRatio > 1) {
+						newWidth = 100 * aspectRatio;
+						newHeight = 100;
+					} else {
+						newWidth = 100;
+						newHeight = 100 / aspectRatio;
+					}
+
+					// Set the canvas size
+					canvas.width = 100;
+					canvas.height = 100;
+
+					// Calculate the position to center the image
+					const offsetX = (100 - newWidth) / 2;
+					const offsetY = (100 - newHeight) / 2;
+
+					// Draw the image on the canvas
+					ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
+
+					// Get the base64 representation of the compressed image
+					const compressedSrc = canvas.toDataURL('image/jpeg');
+
+					// Display the compressed image
+					info.meta.profile_image_url = compressedSrc;
+
+					inputFiles = null;
+				};
+			};
+
+			if (
+				inputFiles &&
+				inputFiles.length > 0 &&
+				['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
+			) {
+				reader.readAsDataURL(inputFiles[0]);
+			} else {
+				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
+				inputFiles = null;
+			}
+		}}
+	/>
+
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			history.back();
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+	<!-- <hr class="my-3 dark:border-gray-700" /> -->
+
+	<form
+		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
+		on:submit|preventDefault={() => {
+			submitHandler();
+		}}
+	>
+		<div class="flex justify-center my-4">
+			<div class="self-center">
+				<button
+					class=" {info.meta.profile_image_url
+						? ''
+						: 'p-6'} rounded-full dark:bg-gray-700 border border-dashed border-gray-200"
+					type="button"
+					on:click={() => {
+						filesInputElement.click();
+					}}
+				>
+					{#if info.meta.profile_image_url}
+						<img
+							src={info.meta.profile_image_url}
+							alt="modelfile profile"
+							class=" rounded-full w-20 h-20 object-cover"
+						/>
+					{:else}
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							class="size-8"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					{/if}
+				</button>
+			</div>
+		</div>
+
+		<div class="my-2 flex space-x-2">
+			<div class="flex-1">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Name your model')}
+						bind:value={name}
+						required
+					/>
+				</div>
+			</div>
+
+			<div class="flex-1">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Model ID')}*</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Add a model id')}
+						bind:value={id}
+						required
+					/>
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Base Model (From)')}</div>
+
+			<div>
+				<select
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder="Select a base model (e.g. llama3, gpt-4o)"
+					bind:value={info.base_model_id}
+					required
+				>
+					<option value={null} class=" placeholder:text-gray-500"
+						>{$i18n.t('Select a base model')}</option
+					>
+					{#each $models.filter((m) => !m?.preset) as model}
+						<option value={model.id}>{model.name}</option>
+					{/each}
+				</select>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}</div>
+
+			<div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder={$i18n.t('Add a short description about what this model does')}
+					bind:value={info.meta.description}
+				/>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Model Params')}</div>
+			</div>
+
+			<div class="mt-2">
+				<div class="my-1">
+					<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
+					<div>
+						<textarea
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
+							placeholder={`Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
+							rows="4"
+							bind:value={info.params.system}
+						/>
+					</div>
+				</div>
+
+				<div class="flex w-full justify-between">
+					<div class=" self-center text-xs font-semibold">
+						{$i18n.t('Advanced Params')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							showAdvanced = !showAdvanced;
+						}}
+					>
+						{#if showAdvanced}
+							<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if showAdvanced}
+					<div class="my-2">
+						<AdvancedParams
+							bind:params
+							on:change={(e) => {
+								info.params = { ...info.params, ...params };
+							}}
+						/>
+					</div>
+				{/if}
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between items-center">
+				<div class="flex w-full justify-between items-center">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
+
+					<button
+						class="p-1 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							if (info.meta.suggestion_prompts === null) {
+								info.meta.suggestion_prompts = [{ content: '' }];
+							} else {
+								info.meta.suggestion_prompts = null;
+							}
+						}}
+					>
+						{#if info.meta.suggestion_prompts === null}
+							<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if info.meta.suggestion_prompts !== null}
+					<button
+						class="p-1 px-2 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							if (
+								info.meta.suggestion_prompts.length === 0 ||
+								info.meta.suggestion_prompts.at(-1).content !== ''
+							) {
+								info.meta.suggestion_prompts = [...info.meta.suggestion_prompts, { content: '' }];
+							}
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+							/>
+						</svg>
+					</button>
+				{/if}
+			</div>
+
+			{#if info.meta.suggestion_prompts}
+				<div class="flex flex-col space-y-1 mt-2">
+					{#if info.meta.suggestion_prompts.length > 0}
+						{#each info.meta.suggestion_prompts as prompt, promptIdx}
+							<div class=" flex border dark:border-gray-600 rounded-lg">
+								<input
+									class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
+									placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
+									bind:value={prompt.content}
+								/>
+
+								<button
+									class="px-2"
+									type="button"
+									on:click={() => {
+										info.meta.suggestion_prompts.splice(promptIdx, 1);
+										info.meta.suggestion_prompts = info.meta.suggestion_prompts;
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 20 20"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<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>
+						{/each}
+					{:else}
+						<div class="text-xs text-center">No suggestion prompts</div>
+					{/if}
+				</div>
+			{/if}
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Capabilities')}</div>
+			</div>
+			<div class="flex flex-col">
+				{#each Object.keys(capabilities) as capability}
+					<div class=" flex items-center gap-2">
+						<Checkbox
+							state={capabilities[capability] ? 'checked' : 'unchecked'}
+							on:change={(e) => {
+								capabilities[capability] = e.detail === 'checked';
+							}}
+						/>
+
+						<div class=" py-1.5 text-sm w-full capitalize">
+							{$i18n.t(capability)}
+						</div>
+					</div>
+				{/each}
+			</div>
+		</div>
+
+		<div class="my-2 text-gray-500">
+			<div class="flex w-full justify-between mb-2">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('JSON Preview')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					type="button"
+					on:click={() => {
+						showPreview = !showPreview;
+					}}
+				>
+					{#if showPreview}
+						<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+					{/if}
+				</button>
+			</div>
+
+			{#if showPreview}
+				<div>
+					<textarea
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						rows="10"
+						value={JSON.stringify(info, null, 2)}
+						disabled
+						readonly
+					/>
+				</div>
+			{/if}
+		</div>
+
+		<div class="my-2 flex justify-end mb-20">
+			<button
+				class=" text-sm px-3 py-2 transition rounded-xl {loading
+					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+				type="submit"
+				disabled={loading}
+			>
+				<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
+
+				{#if loading}
+					<div class="ml-1.5 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>
+</div>

+ 555 - 0
src/routes/(app)/workspace/models/edit/+page.svelte

@@ -0,0 +1,555 @@
+<script>
+	import { v4 as uuidv4 } from 'uuid';
+	import { toast } from 'svelte-sonner';
+	import { goto } from '$app/navigation';
+
+	import { onMount, getContext } from 'svelte';
+	import { page } from '$app/stores';
+	import { settings, user, config, models } from '$lib/stores';
+	import { splitStream } from '$lib/utils';
+
+	import { getModelInfos, updateModelById } from '$lib/apis/models';
+
+	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
+	import { getModels } from '$lib/apis';
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loading = false;
+	let success = false;
+
+	let filesInputElement;
+	let inputFiles;
+
+	let digest = '';
+	let pullProgress = null;
+
+	let showAdvanced = false;
+	let showPreview = false;
+
+	// ///////////
+	// model
+	// ///////////
+
+	let model = null;
+
+	let id = '';
+	let name = '';
+
+	let info = {
+		id: '',
+		base_model_id: null,
+		name: '',
+		meta: {
+			profile_image_url: '/favicon.png',
+			description: '',
+			suggestion_prompts: null
+		},
+		params: {
+			system: ''
+		}
+	};
+
+	let params = {};
+
+	let capabilities = {
+		vision: true
+	};
+
+	const updateHandler = async () => {
+		loading = true;
+
+		info.id = id;
+		info.name = name;
+		info.meta.capabilities = capabilities;
+		info.params.stop = params.stop !== null ? params.stop.split(',').filter((s) => s.trim()) : null;
+
+		const res = await updateModelById(localStorage.token, info.id, info);
+
+		if (res) {
+			toast.success('Model updated successfully');
+			await goto('/workspace/models');
+			await models.set(await getModels(localStorage.token));
+		}
+
+		loading = false;
+		success = false;
+	};
+
+	onMount(() => {
+		const _id = $page.url.searchParams.get('id');
+
+		if (_id) {
+			model = $models.find((m) => m.id === _id);
+			if (model) {
+				id = model.id;
+				name = model.name;
+
+				info = {
+					...info,
+					...JSON.parse(
+						JSON.stringify(
+							model?.info
+								? model?.info
+								: {
+										id: model.id,
+										name: model.name
+								  }
+						)
+					)
+				};
+
+				if (model.preset && model.owned_by === 'ollama' && !info.base_model_id.includes(':')) {
+					info.base_model_id = `${info.base_model_id}:latest`;
+				}
+
+				params = { ...params, ...model?.info?.params };
+				params.stop = params?.stop ? (params?.stop ?? []).join(',') : null;
+
+				if (model?.info?.meta?.capabilities) {
+					capabilities = { ...capabilities, ...model?.info?.meta?.capabilities };
+				}
+				console.log(model);
+			} else {
+				goto('/workspace/models');
+			}
+		} else {
+			goto('/workspace/models');
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<input
+		bind:this={filesInputElement}
+		bind:files={inputFiles}
+		type="file"
+		hidden
+		accept="image/*"
+		on:change={() => {
+			let reader = new FileReader();
+			reader.onload = (event) => {
+				let originalImageUrl = `${event.target.result}`;
+
+				const img = new Image();
+				img.src = originalImageUrl;
+
+				img.onload = function () {
+					const canvas = document.createElement('canvas');
+					const ctx = canvas.getContext('2d');
+
+					// Calculate the aspect ratio of the image
+					const aspectRatio = img.width / img.height;
+
+					// Calculate the new width and height to fit within 100x100
+					let newWidth, newHeight;
+					if (aspectRatio > 1) {
+						newWidth = 100 * aspectRatio;
+						newHeight = 100;
+					} else {
+						newWidth = 100;
+						newHeight = 100 / aspectRatio;
+					}
+
+					// Set the canvas size
+					canvas.width = 100;
+					canvas.height = 100;
+
+					// Calculate the position to center the image
+					const offsetX = (100 - newWidth) / 2;
+					const offsetY = (100 - newHeight) / 2;
+
+					// Draw the image on the canvas
+					ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
+
+					// Get the base64 representation of the compressed image
+					const compressedSrc = canvas.toDataURL('image/jpeg');
+
+					// Display the compressed image
+					info.meta.profile_image_url = compressedSrc;
+
+					inputFiles = null;
+				};
+			};
+
+			if (
+				inputFiles &&
+				inputFiles.length > 0 &&
+				['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
+			) {
+				reader.readAsDataURL(inputFiles[0]);
+			} else {
+				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
+				inputFiles = null;
+			}
+		}}
+	/>
+
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			history.back();
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+
+	{#if model}
+		<form
+			class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
+			on:submit|preventDefault={() => {
+				updateHandler();
+			}}
+		>
+			<div class="flex justify-center my-4">
+				<div class="self-center">
+					<button
+						class=" {info?.meta?.profile_image_url
+							? ''
+							: 'p-6'} rounded-full dark:bg-gray-700 border border-dashed border-gray-200"
+						type="button"
+						on:click={() => {
+							filesInputElement.click();
+						}}
+					>
+						{#if info?.meta?.profile_image_url}
+							<img
+								src={info?.meta?.profile_image_url}
+								alt="modelfile profile"
+								class=" rounded-full w-20 h-20 object-cover"
+							/>
+						{:else}
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 24 24"
+								fill="currentColor"
+								class="w-8"
+							>
+								<path
+									fill-rule="evenodd"
+									d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div class="my-2 flex space-x-2">
+				<div class="flex-1">
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
+
+					<div>
+						<input
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							placeholder={$i18n.t('Name your model')}
+							bind:value={name}
+							required
+						/>
+					</div>
+				</div>
+
+				<div class="flex-1">
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Model ID')}*</div>
+
+					<div>
+						<input
+							class="px-3 py-1.5 text-sm w-full bg-transparent disabled:text-gray-500 border dark:border-gray-600 outline-none rounded-lg"
+							placeholder={$i18n.t('Add a model id')}
+							value={id}
+							disabled
+							required
+						/>
+					</div>
+				</div>
+			</div>
+
+			{#if model.preset}
+				<div class="my-2">
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Base Model (From)')}</div>
+
+					<div>
+						<select
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							placeholder="Select a base model (e.g. llama3, gpt-4o)"
+							bind:value={info.base_model_id}
+							required
+						>
+							<option value={null} class=" placeholder:text-gray-500"
+								>{$i18n.t('Select a base model')}</option
+							>
+							{#each $models.filter((m) => m.id !== model.id && !m?.preset) as model}
+								<option value={model.id}>{model.name}</option>
+							{/each}
+						</select>
+					</div>
+				</div>
+			{/if}
+
+			<div class="my-2">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Add a short description about what this model does')}
+						bind:value={info.meta.description}
+					/>
+				</div>
+			</div>
+
+			<div class="my-2">
+				<div class="flex w-full justify-between">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Model Params')}</div>
+				</div>
+
+				<!-- <div class=" text-sm font-semibold mb-2"></div> -->
+
+				<div class="mt-2">
+					<div class="my-1">
+						<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
+						<div>
+							<textarea
+								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
+								placeholder={`Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
+								rows="4"
+								bind:value={info.params.system}
+							/>
+						</div>
+					</div>
+
+					<div class="flex w-full justify-between">
+						<div class=" self-center text-xs font-semibold">
+							{$i18n.t('Advanced Params')}
+						</div>
+
+						<button
+							class="p-1 px-3 text-xs flex rounded transition"
+							type="button"
+							on:click={() => {
+								showAdvanced = !showAdvanced;
+							}}
+						>
+							{#if showAdvanced}
+								<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+							{:else}
+								<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+							{/if}
+						</button>
+					</div>
+
+					{#if showAdvanced}
+						<div class="my-2">
+							<AdvancedParams
+								bind:params
+								on:change={(e) => {
+									info.params = { ...info.params, ...params };
+								}}
+							/>
+						</div>
+					{/if}
+				</div>
+			</div>
+
+			<div class="my-2">
+				<div class="flex w-full justify-between items-center">
+					<div class="flex w-full justify-between items-center">
+						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
+
+						<button
+							class="p-1 text-xs flex rounded transition"
+							type="button"
+							on:click={() => {
+								if (info.meta.suggestion_prompts === null) {
+									info.meta.suggestion_prompts = [{ content: '' }];
+								} else {
+									info.meta.suggestion_prompts = null;
+								}
+							}}
+						>
+							{#if info.meta.suggestion_prompts === null}
+								<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+							{:else}
+								<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+							{/if}
+						</button>
+					</div>
+
+					{#if info.meta.suggestion_prompts !== null}
+						<button
+							class="p-1 px-2 text-xs flex rounded transition"
+							type="button"
+							on:click={() => {
+								if (
+									info.meta.suggestion_prompts.length === 0 ||
+									info.meta.suggestion_prompts.at(-1).content !== ''
+								) {
+									info.meta.suggestion_prompts = [...info.meta.suggestion_prompts, { content: '' }];
+								}
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+								/>
+							</svg>
+						</button>
+					{/if}
+				</div>
+
+				{#if info.meta.suggestion_prompts}
+					<div class="flex flex-col space-y-1 mt-2">
+						{#if info.meta.suggestion_prompts.length > 0}
+							{#each info.meta.suggestion_prompts as prompt, promptIdx}
+								<div class=" flex border dark:border-gray-600 rounded-lg">
+									<input
+										class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
+										placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
+										bind:value={prompt.content}
+									/>
+
+									<button
+										class="px-2"
+										type="button"
+										on:click={() => {
+											info.meta.suggestion_prompts.splice(promptIdx, 1);
+											info.meta.suggestion_prompts = info.meta.suggestion_prompts;
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 20 20"
+											fill="currentColor"
+											class="w-4 h-4"
+										>
+											<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>
+							{/each}
+						{:else}
+							<div class="text-xs text-center">No suggestion prompts</div>
+						{/if}
+					</div>
+				{/if}
+			</div>
+
+			<div class="my-2">
+				<div class="flex w-full justify-between">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Capabilities')}</div>
+				</div>
+				<div class="flex flex-col">
+					{#each Object.keys(capabilities) as capability}
+						<div class=" flex items-center gap-2">
+							<Checkbox
+								state={capabilities[capability] ? 'checked' : 'unchecked'}
+								on:change={(e) => {
+									capabilities[capability] = e.detail === 'checked';
+								}}
+							/>
+
+							<div class=" py-1.5 text-sm w-full capitalize">
+								{$i18n.t(capability)}
+							</div>
+						</div>
+					{/each}
+				</div>
+			</div>
+
+			<div class="my-2 text-gray-500">
+				<div class="flex w-full justify-between mb-2">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('JSON Preview')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							showPreview = !showPreview;
+						}}
+					>
+						{#if showPreview}
+							<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if showPreview}
+					<div>
+						<textarea
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							rows="10"
+							value={JSON.stringify(info, null, 2)}
+							disabled
+							readonly
+						/>
+					</div>
+				{/if}
+			</div>
+
+			<div class="my-2 flex justify-end mb-20">
+				<button
+					class=" text-sm px-3 py-2 transition rounded-xl {loading
+						? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+						: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+					type="submit"
+					disabled={loading}
+				>
+					<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
+
+					{#if loading}
+						<div class="ml-1.5 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>
+	{/if}
+</div>

+ 0 - 27
src/routes/modelfiles/create/+page.svelte

@@ -1,27 +0,0 @@
-<script lang="ts">
-	import { goto } from '$app/navigation';
-	import { onMount } from 'svelte';
-
-	onMount(async () => {
-		window.addEventListener('message', async (event) => {
-			if (
-				![
-					'https://ollamahub.com',
-					'https://www.ollamahub.com',
-					'https://openwebui.com',
-					'https://www.openwebui.com',
-					'http://localhost:5173'
-				].includes(event.origin)
-			)
-				return;
-			const modelfile = JSON.parse(event.data);
-			sessionStorage.modelfile = JSON.stringify(modelfile);
-
-			goto('/workspace/modelfiles/create');
-		});
-
-		if (window.opener ?? false) {
-			window.opener.postMessage('loaded', '*');
-		}
-	});
-</script>

+ 3 - 17
src/routes/s/[id]/+page.svelte

@@ -5,7 +5,7 @@
 
 
 	import dayjs from 'dayjs';
 	import dayjs from 'dayjs';
 
 
-	import { modelfiles, settings, chatId, WEBUI_NAME } from '$lib/stores';
+	import { settings, chatId, WEBUI_NAME, models } from '$lib/stores';
 	import { convertMessagesToHistory } from '$lib/utils';
 	import { convertMessagesToHistory } from '$lib/utils';
 
 
 	import { getChatByShareId } from '$lib/apis/chats';
 	import { getChatByShareId } from '$lib/apis/chats';
@@ -14,6 +14,7 @@
 	import Navbar from '$lib/components/layout/Navbar.svelte';
 	import Navbar from '$lib/components/layout/Navbar.svelte';
 	import { getUserById } from '$lib/apis/users';
 	import { getUserById } from '$lib/apis/users';
 	import { error } from '@sveltejs/kit';
 	import { error } from '@sveltejs/kit';
+	import { getModels } from '$lib/apis';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
@@ -27,17 +28,6 @@
 	let showModelSelector = false;
 	let showModelSelector = false;
 	let selectedModels = [''];
 	let selectedModels = [''];
 
 
-	let selectedModelfiles = {};
-	$: selectedModelfiles = selectedModels.reduce((a, tagName, i, arr) => {
-		const modelfile =
-			$modelfiles.filter((modelfile) => modelfile.tagName === tagName)?.at(0) ?? undefined;
-
-		return {
-			...a,
-			...(modelfile && { [tagName]: modelfile })
-		};
-	}, {});
-
 	let chat = null;
 	let chat = null;
 	let user = null;
 	let user = null;
 
 
@@ -69,10 +59,6 @@
 			if (await loadSharedChat()) {
 			if (await loadSharedChat()) {
 				await tick();
 				await tick();
 				loaded = true;
 				loaded = true;
-
-				window.setTimeout(() => scrollToBottom(), 0);
-				const chatInput = document.getElementById('chat-textarea');
-				chatInput?.focus();
 			} else {
 			} else {
 				await goto('/');
 				await goto('/');
 			}
 			}
@@ -84,6 +70,7 @@
 	//////////////////////////
 	//////////////////////////
 
 
 	const loadSharedChat = async () => {
 	const loadSharedChat = async () => {
+		await models.set(await getModels(localStorage.token));
 		await chatId.set($page.params.id);
 		await chatId.set($page.params.id);
 		chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => {
 		chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => {
 			await goto('/');
 			await goto('/');
@@ -168,7 +155,6 @@
 							chatId={$chatId}
 							chatId={$chatId}
 							readOnly={true}
 							readOnly={true}
 							{selectedModels}
 							{selectedModels}
-							{selectedModelfiles}
 							{processing}
 							{processing}
 							bind:history
 							bind:history
 							bind:messages
 							bind:messages

Неке датотеке нису приказане због велике количине промена