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

feat: openai api compatible model presets (profiles/modelfiles)
This commit is contained in:
Timothy Jaeryang Baek 2024-05-24 23:47:25 -10:00 committed by GitHub
commit 67a5020cdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
88 changed files with 3403 additions and 2526 deletions

View File

@ -18,8 +18,9 @@ import requests
from pydantic import BaseModel, ConfigDict
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 config import SRC_LOG_LEVELS, ENV
from config import SRC_LOG_LEVELS
from constants import MESSAGES
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.MODEL_FILTER_LIST = MODEL_FILTER_LIST.value
app.state.MODEL_CONFIG = Models.get_all_models()
app.state.ENABLE = ENABLE_LITELLM
app.state.CONFIG = litellm_config
@ -261,6 +262,14 @@ async def get_models(user=Depends(get_current_user)):
"object": "model",
"created": int(time.time()),
"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"]
],

View File

@ -29,7 +29,7 @@ import time
from urllib.parse import urlparse
from typing import Optional, List, Union
from apps.web.models.models import Models
from apps.web.models.users import Users
from constants import ERROR_MESSAGES
from utils.utils import (
@ -39,6 +39,8 @@ from utils.utils import (
get_admin_user,
)
from utils.models import get_model_id_from_custom_model_id
from config import (
SRC_LOG_LEVELS,
@ -68,7 +70,6 @@ app.state.config = AppConfig()
app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
app.state.MODELS = {}
@ -875,14 +876,93 @@ async def generate_chat_completion(
user=Depends(get_verified_user),
):
log.debug(
"form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(
form_data.model_dump_json(exclude_none=True).encode()
)
)
payload = {
**form_data.model_dump(exclude_none=True),
}
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:
model = form_data.model
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
if ":" not in model:
model = f"{model}:latest"
if model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[model]["urls"])
if payload["model"] in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[payload["model"]]["urls"])
else:
raise HTTPException(
status_code=400,
@ -892,16 +972,12 @@ async def generate_chat_completion(
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
print(payload)
r = None
log.debug(
"form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(
form_data.model_dump_json(exclude_none=True).encode()
)
)
def get_request():
nonlocal form_data
nonlocal payload
nonlocal r
request_id = str(uuid.uuid4())
@ -910,7 +986,7 @@ async def generate_chat_completion(
def stream_content():
try:
if form_data.stream:
if payload.get("stream", None):
yield json.dumps({"id": request_id, "done": False}) + "\n"
for chunk in r.iter_content(chunk_size=8192):
@ -928,7 +1004,7 @@ async def generate_chat_completion(
r = requests.request(
method="POST",
url=f"{url}/api/chat",
data=form_data.model_dump_json(exclude_none=True).encode(),
data=json.dumps(payload),
stream=True,
)
@ -984,14 +1060,62 @@ async def generate_openai_chat_completion(
user=Depends(get_verified_user),
):
payload = {
**form_data.model_dump(exclude_none=True),
}
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["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:
model = form_data.model
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
if ":" not in model:
model = f"{model}:latest"
if model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[model]["urls"])
if payload["model"] in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[payload["model"]]["urls"])
else:
raise HTTPException(
status_code=400,
@ -1004,7 +1128,7 @@ async def generate_openai_chat_completion(
r = None
def get_request():
nonlocal form_data
nonlocal payload
nonlocal r
request_id = str(uuid.uuid4())
@ -1013,7 +1137,7 @@ async def generate_openai_chat_completion(
def stream_content():
try:
if form_data.stream:
if payload.get("stream"):
yield json.dumps(
{"request_id": request_id, "done": False}
) + "\n"
@ -1033,7 +1157,7 @@ async def generate_openai_chat_completion(
r = requests.request(
method="POST",
url=f"{url}/v1/chat/completions",
data=form_data.model_dump_json(exclude_none=True).encode(),
data=json.dumps(payload),
stream=True,
)

View File

@ -10,7 +10,7 @@ import logging
from pydantic import BaseModel
from apps.web.models.models import Models
from apps.web.models.users import Users
from constants import ERROR_MESSAGES
from utils.utils import (
@ -53,7 +53,6 @@ app.state.config = AppConfig()
app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
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_KEYS = OPENAI_API_KEYS
@ -206,7 +205,13 @@ def merge_models_lists(model_lists):
if models is not None and "error" not in models:
merged_list.extend(
[
{**model, "urlIdx": idx}
{
**model,
"name": model.get("name", model["id"]),
"owned_by": "openai",
"openai": model,
"urlIdx": idx,
}
for model in models
if "api.openai.com"
not in app.state.config.OPENAI_API_BASE_URLS[idx]
@ -252,7 +257,7 @@ async def get_all_models():
log.info(f"models: {models}")
app.state.MODELS = {model["id"]: model for model in models["data"]}
return models
return models
@app.get("/models")
@ -310,39 +315,93 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
body = await request.body()
# 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)
payload = None
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
# 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)
model_info.params = model_info.params.model_dump()
# 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"]
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)
# Convert the modified body back to JSON
body = json.dumps(body)
except json.JSONDecodeError as e:
log.error("Error loading request body into a dictionary:", e)
print(payload)
url = app.state.config.OPENAI_API_BASE_URLS[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(
method=request.method,
url=target_url,
data=body,
data=payload if payload else body,
headers=headers,
stream=True,
)

View File

@ -1,3 +1,5 @@
import json
from peewee import *
from peewee_migrate import Router
from playhouse.db_url import connect
@ -8,6 +10,16 @@ import logging
log = logging.getLogger(__name__)
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
if os.path.exists(f"{DATA_DIR}/ollama.db"):
# Rename the file

View File

@ -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")

View File

@ -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,
)

View File

@ -6,7 +6,7 @@ from apps.web.routers import (
users,
chats,
documents,
modelfiles,
models,
prompts,
configs,
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.USER_PERMISSIONS = USER_PERMISSIONS
app.state.config.WEBHOOK_URL = WEBHOOK_URL
app.state.MODELS = {}
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(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(memories.router, prefix="/memories", tags=["memories"])
app.include_router(configs.router, prefix="/configs", tags=["configs"])
app.include_router(utils.router, prefix="/utils", tags=["utils"])

View File

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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -32,6 +32,8 @@ class ERROR_MESSAGES(str, Enum):
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."
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."
INVALID_TOKEN = (
"Your session has expired or the token is invalid. Please sign in again."

View File

@ -19,8 +19,8 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.middleware.base import BaseHTTPMiddleware
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 (
app as litellm_app,
@ -36,10 +36,10 @@ from apps.web.main import app as webui_app
import asyncio
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 config import (
@ -53,6 +53,8 @@ from config import (
FRONTEND_BUILD_DIR,
CACHE_DIR,
STATIC_DIR,
ENABLE_OPENAI_API,
ENABLE_OLLAMA_API,
ENABLE_LITELLM,
ENABLE_MODEL_FILTER,
MODEL_FILTER_LIST,
@ -110,11 +112,19 @@ app = FastAPI(
)
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.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.config.WEBHOOK_URL = WEBHOOK_URL
app.state.MODELS = {}
origins = ["*"]
@ -231,6 +241,11 @@ app.add_middleware(
@app.middleware("http")
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())
response = await call_next(request)
process_time = int(time.time()) - start_time
@ -247,9 +262,11 @@ async def update_embedding_function(request: Request, call_next):
return response
# TODO: Deprecate LiteLLM
app.mount("/litellm/api", litellm_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("/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
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")
async def get_app_config():
# Checking and Handling the Absence of 'ui' in CONFIG_DATA

View File

@ -1,5 +1,6 @@
from pathlib import Path
import hashlib
import json
import re
from datetime import timedelta
from typing import Optional
@ -110,3 +111,76 @@ def parse_duration(duration: str) -> Optional[timedelta]:
total_duration += timedelta(weeks=number)
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
backend/utils/models.py Normal file
View File

@ -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

View File

@ -1,5 +1,54 @@
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 () => {
let error = null;
@ -196,3 +245,77 @@ export const updateWebhookUrl = async (token: string, url: string) => {
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;
};

View File

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

View File

@ -1,18 +1,16 @@
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;
const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/create`, {
const res = await fetch(`${WEBUI_API_BASE_URL}/models/add`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
modelfile: modelfile
})
body: JSON.stringify(model)
})
.then(async (res) => {
if (!res.ok) throw await res.json();
@ -31,10 +29,10 @@ export const createNewModelfile = async (token: string, modelfile: object) => {
return res;
};
export const getModelfiles = async (token: string = '') => {
export const getModelInfos = async (token: string = '') => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/`, {
const res = await fetch(`${WEBUI_API_BASE_URL}/models/`, {
method: 'GET',
headers: {
Accept: 'application/json',
@ -59,62 +57,19 @@ export const getModelfiles = async (token: string = '') => {
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;
const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/`, {
method: 'POST',
const res = await fetch(`${WEBUI_API_BASE_URL}/models/${id}`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
tag_name: tagName
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res.modelfile;
};
export const updateModelfileByTagName = async (
token: string,
tagName: string,
modelfile: object
) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
tag_name: tagName,
modelfile: modelfile
})
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
@ -137,19 +92,49 @@ export const updateModelfileByTagName = async (
return res;
};
export const deleteModelfileByTagName = async (token: string, tagName: string) => {
export const updateModelById = async (token: string, id: string, model: object) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/modelfiles/delete`, {
const res = await fetch(`${WEBUI_API_BASE_URL}/models/${id}/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify(model)
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const deleteModelById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/models/${id}/delete`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
tag_name: tagName
})
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();

View File

@ -230,7 +230,12 @@ export const getOpenAIModels = async (token: string = '') => {
return 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) => {
return a.name.localeCompare(b.name);
})

View File

@ -10,7 +10,7 @@
chatId,
chats,
config,
modelfiles,
type Model,
models,
settings,
showSidebar,
@ -60,25 +60,7 @@
let showModelSelector = true;
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 tags = [];
@ -164,6 +146,7 @@
if ($page.url.searchParams.get('q')) {
prompt = $page.url.searchParams.get('q') ?? '';
if (prompt) {
await tick();
submitPrompt(prompt);
@ -211,7 +194,7 @@
await settings.set({
..._settings,
system: chatContent.system ?? _settings.system,
options: chatContent.options ?? _settings.options
params: chatContent.options ?? _settings.params
});
autoScroll = true;
await tick();
@ -300,7 +283,7 @@
models: selectedModels,
system: $settings.system ?? undefined,
options: {
...($settings.options ?? {})
...($settings.params ?? {})
},
messages: messages,
history: history,
@ -317,6 +300,7 @@
// Reset chat input textarea
prompt = '';
document.getElementById('chat-textarea').style.height = '';
files = [];
// Send prompt
@ -328,75 +312,92 @@
const _chatId = JSON.parse(JSON.stringify($chatId));
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);
(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) {
// 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
};
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')
);
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
if (hasImages && !(model.info?.meta?.capabilities?.vision ?? true)) {
toast.error(
$i18n.t('Model {{modelName}} is not vision capable', {
modelName: model.name ?? model.id
})
);
}
// Append messageId to childrenIds of parent message
if (parentId !== null) {
history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds,
responseMessageId
];
}
// 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
};
await tick();
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
let userContext = null;
if ($settings?.memory ?? false) {
if (userContext === null) {
const res = await queryMemory(localStorage.token, prompt).catch((error) => {
toast.error(error);
return null;
});
// Append messageId to childrenIds of parent message
if (parentId !== null) {
history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds,
responseMessageId
];
}
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;
}, []);
}
await tick();
console.log(userContext);
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;
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 }));
}
responseMessage.userContext = userContext;
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));
@ -430,7 +431,7 @@
// Prepare the base message object
const baseMessage = {
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
@ -442,7 +443,6 @@
if (imageUrls && imageUrls.length > 0 && message.role === 'user') {
baseMessage.images = imageUrls;
}
return baseMessage;
});
@ -473,13 +473,15 @@
model: model,
messages: messagesBody,
options: {
...($settings.options ?? {}),
...($settings.params ?? {}),
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, '\\"') + '"'))
)
: undefined
: undefined,
num_predict: $settings?.params?.max_tokens ?? undefined,
repeat_penalty: $settings?.params?.frequency_penalty ?? undefined
},
format: $settings.requestFormat ?? undefined,
keep_alive: $settings.keepAlive ?? undefined,
@ -605,7 +607,8 @@
if ($settings.saveChatHistory ?? true) {
chat = await updateChatById(localStorage.token, _chatId, {
messages: messages,
history: history
history: history,
models: selectedModels
});
await chats.set(await getChatList(localStorage.token));
}
@ -716,18 +719,17 @@
: message?.raContent ?? message.content
})
})),
seed: $settings?.options?.seed ?? undefined,
seed: $settings?.params?.seed ?? undefined,
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, '\\"') + '"'))
)
: 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,
citations: docs.length > 0
},
@ -797,6 +799,7 @@
if ($chatId == _chatId) {
if ($settings.saveChatHistory ?? true) {
chat = await updateChatById(localStorage.token, _chatId, {
models: selectedModels,
messages: messages,
history: history
});
@ -935,10 +938,8 @@
) + ' {{prompt}}',
titleModelId,
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`
);
@ -1025,16 +1026,12 @@
<Messages
chatId={$chatId}
{selectedModels}
{selectedModelfiles}
{processing}
bind:history
bind:messages
bind:autoScroll
bind:prompt
bottomPadding={files.length > 0}
suggestionPrompts={chatIdProp
? []
: selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
{sendPrompt}
{continueGeneration}
{regenerateResponse}
@ -1048,7 +1045,8 @@
bind:files
bind:prompt
bind:autoScroll
bind:selectedModel={atSelectedModel}
bind:atSelectedModel
{selectedModels}
{messages}
{submitPrompt}
{stopResponse}

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
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 {
@ -27,7 +27,9 @@
export let stopResponse: Function;
export let autoScroll = true;
export let selectedModel = '';
export let atSelectedModel: Model | undefined;
export let selectedModels: [''];
let chatTextAreaElement: HTMLTextAreaElement;
let filesInputElement;
@ -52,6 +54,11 @@
let speechRecognition;
let visionCapableModels = [];
$: visionCapableModels = [...(atSelectedModel ? [atSelectedModel] : selectedModels)].filter(
(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.vision ?? true
);
$: if (prompt) {
if (chatTextAreaElement) {
chatTextAreaElement.style.height = '';
@ -358,6 +365,10 @@
inputFiles.forEach((file) => {
console.log(file, file.name.split('.').at(-1));
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();
reader.onload = (event) => {
files = [
@ -429,8 +440,8 @@
<div class="fixed bottom-0 {$showSidebar ? 'left-0 md:left-[260px]' : 'left-0'} right-0">
<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">
{#if autoScroll === false && messages.length > 0}
<div class=" absolute -top-12 left-0 right-0 flex justify-center z-30">
@ -494,12 +505,12 @@
bind:chatInputPlaceholder
{messages}
on:select={(e) => {
selectedModel = e.detail;
atSelectedModel = e.detail;
chatTextAreaElement?.focus();
}}
/>
{#if selectedModel !== ''}
{#if atSelectedModel !== undefined}
<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"
>
@ -508,21 +519,21 @@
crossorigin="anonymous"
alt="model profile"
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'
? `/doge.png`
: `${WEBUI_BASE_URL}/static/favicon.png`)}
/>
<div>
Talking to <span class=" font-medium">{selectedModel.name} </span>
Talking to <span class=" font-medium">{atSelectedModel.name}</span>
</div>
</div>
<div>
<button
class="flex items-center"
on:click={() => {
selectedModel = '';
atSelectedModel = undefined;
}}
>
<XMark />
@ -535,7 +546,7 @@
</div>
<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">
<input
bind:this={filesInputElement}
@ -550,6 +561,12 @@
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'));
inputFiles = null;
filesInputElement.value = '';
return;
}
let reader = new FileReader();
reader.onload = (event) => {
files = [
@ -589,6 +606,7 @@
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"
on:submit|preventDefault={() => {
// check if selectedModels support image input
submitPrompt(prompt, user);
}}
>
@ -597,7 +615,36 @@
{#each files as file, fileIdx}
<div class=" relative group">
{#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'}
<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"
@ -883,7 +930,7 @@
if (e.key === 'Escape') {
console.log('Escape');
selectedModel = '';
atSelectedModel = undefined;
}
}}
rows="1"

View File

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

View File

@ -4,7 +4,7 @@
import hljs from 'highlight.js';
import 'highlight.js/styles/github-dark.min.css';
import { loadPyodide } from 'pyodide';
import { tick } from 'svelte';
import { onMount, tick } from 'svelte';
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
export let id = '';
@ -12,6 +12,7 @@
export let lang = '';
export let code = '';
let highlightedCode = null;
let executing = false;
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>
{#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="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="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}
<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>
<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>
{/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>

View File

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

View File

@ -1,6 +1,6 @@
<script lang="ts">
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 { blur, fade } from 'svelte/transition';
@ -9,23 +9,20 @@
const i18n = getContext('i18n');
export let modelIds = [];
export let models = [];
export let modelfiles = [];
export let submitPrompt;
export let suggestionPrompts;
let mounted = false;
let modelfile = null;
let selectedModelIdx = 0;
$: modelfile =
models[selectedModelIdx] in modelfiles ? modelfiles[models[selectedModelIdx]] : null;
$: if (models.length > 0) {
$: if (modelIds.length > 0) {
selectedModelIdx = models.length - 1;
}
$: models = modelIds.map((id) => $_models.find((m) => m.id === id));
onMount(() => {
mounted = true;
});
@ -41,25 +38,14 @@
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>
{/each}
</div>
@ -70,23 +56,32 @@
>
<div>
<div class=" capitalize line-clamp-1" in:fade={{ duration: 200 }}>
{#if modelfile}
{modelfile.title}
{#if models[selectedModelIdx]?.info}
{models[selectedModelIdx]?.info?.name}
{:else}
{$i18n.t('Hello, {{name}}', { name: $user.name })}
{/if}
</div>
<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>
{#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">
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>
{/if}
{:else}
@ -99,7 +94,11 @@
</div>
<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>
{/key}

View File

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

View File

@ -4,7 +4,7 @@
import { tick, createEventDispatcher, getContext } from 'svelte';
import Name from './Name.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 { user as _user } from '$lib/stores';
@ -60,8 +60,7 @@
{#if !($settings?.chatBubble ?? true)}
<ProfileImage
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'}
/>
{/if}
@ -70,12 +69,8 @@
<div>
<Name>
{#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}
{user.name}
{:else}

File diff suppressed because one or more lines are too long

View File

@ -45,13 +45,11 @@
<div class="mr-1 max-w-full">
<Selector
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}
/>
</div>

View File

@ -12,7 +12,9 @@
import { user, MODEL_DOWNLOAD_POOL, models, mobile } from '$lib/stores';
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';
const i18n = getContext('i18n');
@ -23,7 +25,12 @@
export let searchEnabled = true;
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]';
@ -239,19 +246,37 @@
}}
>
<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>
<!-- {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
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@ -271,15 +296,15 @@
</svg>
</div>
</Tooltip>
{:else}
{/if}
{#if item.model?.info?.meta?.description}
<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
xmlns="http://www.w3.org/2000/svg"
fill="none"

View File

@ -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>

View File

@ -1,14 +1,16 @@
<script lang="ts">
import { getContext } from 'svelte';
import { getContext, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
export let options = {
export let params = {
// Advanced
seed: 0,
stop: '',
stop: null,
temperature: '',
repeat_penalty: '',
frequency_penalty: '',
repeat_last_n: '',
mirostat: '',
mirostat_eta: '',
@ -17,40 +19,86 @@
top_p: '',
tfs_z: '',
num_ctx: '',
num_predict: ''
max_tokens: '',
template: null
};
let customFieldName = '';
let customFieldValue = '';
$: if (params) {
dispatch('change', params);
}
</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>
{#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>
{/if}
</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"
/>
</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>
{#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 class=" py-0.5 w-full justify-between">
@ -61,10 +109,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
@ -72,7 +120,7 @@
</button>
</div>
{#if options.temperature !== ''}
{#if (params?.temperature ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -81,13 +129,13 @@
min="0"
max="1"
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"
/>
</div>
<div>
<input
bind:value={options.temperature}
bind:value={params.temperature}
type="number"
class=" bg-transparent text-center w-14"
min="0"
@ -107,18 +155,18 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.mirostat !== ''}
{#if (params?.mirostat ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -127,13 +175,13 @@
min="0"
max="2"
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"
/>
</div>
<div>
<input
bind:value={options.mirostat}
bind:value={params.mirostat}
type="number"
class=" bg-transparent text-center w-14"
min="0"
@ -153,18 +201,18 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.mirostat_eta !== ''}
{#if (params?.mirostat_eta ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -173,13 +221,13 @@
min="0"
max="1"
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"
/>
</div>
<div>
<input
bind:value={options.mirostat_eta}
bind:value={params.mirostat_eta}
type="number"
class=" bg-transparent text-center w-14"
min="0"
@ -199,10 +247,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
@ -210,7 +258,7 @@
</button>
</div>
{#if options.mirostat_tau !== ''}
{#if (params?.mirostat_tau ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -219,13 +267,13 @@
min="0"
max="10"
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"
/>
</div>
<div>
<input
bind:value={options.mirostat_tau}
bind:value={params.mirostat_tau}
type="number"
class=" bg-transparent text-center w-14"
min="0"
@ -245,18 +293,18 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.top_k !== ''}
{#if (params?.top_k ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -265,13 +313,13 @@
min="0"
max="100"
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"
/>
</div>
<div>
<input
bind:value={options.top_k}
bind:value={params.top_k}
type="number"
class=" bg-transparent text-center w-14"
min="0"
@ -291,18 +339,18 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.top_p !== ''}
{#if (params?.top_p ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -311,13 +359,13 @@
min="0"
max="1"
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"
/>
</div>
<div>
<input
bind:value={options.top_p}
bind:value={params.top_p}
type="number"
class=" bg-transparent text-center w-14"
min="0"
@ -331,24 +379,24 @@
<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('Repeat Penalty')}</div>
<div class=" self-center text-xs font-medium">{$i18n.t('Frequencey Penalty')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.repeat_penalty !== ''}
{#if (params?.frequency_penalty ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -357,13 +405,13 @@
min="0"
max="2"
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"
/>
</div>
<div>
<input
bind:value={options.repeat_penalty}
bind:value={params.frequency_penalty}
type="number"
class=" bg-transparent text-center w-14"
min="0"
@ -383,18 +431,18 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.repeat_last_n !== ''}
{#if (params?.repeat_last_n ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -403,13 +451,13 @@
min="-1"
max="128"
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"
/>
</div>
<div>
<input
bind:value={options.repeat_last_n}
bind:value={params.repeat_last_n}
type="number"
class=" bg-transparent text-center w-14"
min="-1"
@ -429,18 +477,18 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.tfs_z !== ''}
{#if (params?.tfs_z ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -449,13 +497,13 @@
min="0"
max="2"
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"
/>
</div>
<div>
<input
bind:value={options.tfs_z}
bind:value={params.tfs_z}
type="number"
class=" bg-transparent text-center w-14"
min="0"
@ -475,18 +523,18 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.num_ctx !== ''}
{#if (params?.num_ctx ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -495,13 +543,13 @@
min="-1"
max="10240000"
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"
/>
</div>
<div class="">
<input
bind:value={options.num_ctx}
bind:value={params.num_ctx}
type="number"
class=" bg-transparent text-center w-14"
min="-1"
@ -513,24 +561,24 @@
</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('Max Tokens')}</div>
<div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens (num_predict)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
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>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if options.num_predict !== ''}
{#if (params?.max_tokens ?? '') !== ''}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
@ -539,13 +587,13 @@
min="-2"
max="16000"
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"
/>
</div>
<div class="">
<input
bind:value={options.num_predict}
bind:value={params.max_tokens}
type="number"
class=" bg-transparent text-center w-14"
min="-2"
@ -556,4 +604,36 @@
</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('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>

View File

@ -41,21 +41,21 @@
let requestFormat = '';
let keepAlive = null;
let options = {
let params = {
// Advanced
seed: 0,
temperature: '',
repeat_penalty: '',
frequency_penalty: '',
repeat_last_n: '',
mirostat: '',
mirostat_eta: '',
mirostat_tau: '',
top_k: '',
top_p: '',
stop: '',
stop: null,
tfs_z: '',
num_ctx: '',
num_predict: ''
max_tokens: ''
};
const toggleRequestFormat = async () => {
@ -80,14 +80,14 @@
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(',');
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) => {
@ -228,7 +228,7 @@
</div>
{#if showAdvanced}
<AdvancedParams bind:options />
<AdvancedParams bind:params />
<hr class=" dark:border-gray-700" />
<div class=" py-1 w-full justify-between">
@ -300,20 +300,21 @@
on:click={() => {
saveSettings({
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
});

View File

@ -1,5 +1,4 @@
<script lang="ts">
import queue from 'async/queue';
import { toast } from 'svelte-sonner';
import {
@ -12,32 +11,19 @@
cancelOllamaRequest,
uploadModel
} from '$lib/apis/ollama';
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 { onMount, getContext } from 'svelte';
import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
export let getModels: Function;
let showLiteLLM = false;
let showLiteLLMParams = false;
let modelUploadInputElement: HTMLInputElement;
let liteLLMModelInfo = [];
let liteLLMModel = '';
let liteLLMModelName = '';
let liteLLMAPIBase = '';
let liteLLMAPIKey = '';
let liteLLMRPM = '';
let liteLLMMaxTokens = '';
let deleteLiteLLMModelName = '';
$: liteLLMModelName = liteLLMModel;
// 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 () => {
OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
toast.error(error);
return [];
});
await Promise.all([
(async () => {
OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
toast.error(error);
return [];
});
if (OLLAMA_URLS.length > 0) {
selectedOllamaUrlIdx = 0;
}
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>
@ -587,24 +524,28 @@
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
>
<style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
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"
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"
class="spinner_ajPY"
/></svg
>
/>
</svg>
</div>
{:else}
<svg
@ -833,24 +774,28 @@
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
>
<style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
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"
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"
class="spinner_ajPY"
/></svg
>
/>
</svg>
</div>
{:else}
<svg
@ -929,203 +874,8 @@
{/if}
</div>
</div>
<hr class=" dark:border-gray-700 my-2" />
{:else}
<div>Ollama Not Detected</div>
{/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>

View File

@ -3,7 +3,7 @@
import { toast } from 'svelte-sonner';
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 Account from './Settings/Account.svelte';

View File

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

View File

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

View File

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

View File

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

View File

@ -5,67 +5,82 @@
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 { getModels } from '$lib/apis';
const i18n = getContext('i18n');
let localModelfiles = [];
let importFiles;
let modelfilesImportInputElement: HTMLInputElement;
const deleteModelHandler = async (tagName) => {
let success = null;
let modelsImportInputElement: HTMLInputElement;
success = await deleteModel(localStorage.token, tagName).catch((err) => {
toast.error(err);
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;
});
if (success) {
toast.success($i18n.t(`Deleted {{tagName}}`, { tagName }));
}
return success;
const res = await deleteModelById(localStorage.token, model.id);
if (res) {
toast.success($i18n.t(`Deleted {{name}}`, { name: model.id }));
}
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'));
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(
'message',
(event) => {
if (event.origin !== url) return;
if (event.data === 'loaded') {
tab.postMessage(JSON.stringify(modelfile), '*');
tab.postMessage(JSON.stringify(model), '*');
}
},
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'
});
saveAs(blob, `modelfiles-export-${Date.now()}.json`);
saveAs(blob, `models-export-${Date.now()}.json`);
};
onMount(() => {
// Legacy code to sync localModelfiles with models
localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
if (localModelfiles) {
@ -76,13 +91,13 @@
<svelte:head>
<title>
{$i18n.t('Modelfiles')} | {$WEBUI_NAME}
{$i18n.t('Models')} | {$WEBUI_NAME}
</title>
</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="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 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>
</a>
<hr class=" dark:border-gray-850" />
<div class=" my-2 mb-5">
{#each $modelfiles as modelfile}
{#each $models as model}
<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"
>
<a
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=" rounded-full bg-stone-700">
<img
src={modelfile.imageUrl ?? '/user.png'}
src={model?.info?.meta?.profile_image_url ?? '/favicon.png'}
alt="modelfile profile"
class=" rounded-full w-full h-auto object-cover"
/>
@ -125,9 +140,9 @@
</div>
<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">
{modelfile.desc}
{!!model?.info?.meta?.description ? model?.info?.meta?.description : model.id}
</div>
</div>
</a>
@ -135,7 +150,7 @@
<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"
type="button"
href={`/workspace/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
href={`/workspace/models/edit?id=${encodeURIComponent(model.id)}`}
>
<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"
type="button"
on:click={() => {
// console.log(modelfile);
sessionStorage.modelfile = JSON.stringify(modelfile);
goto('/workspace/modelfiles/create');
cloneModelHandler(model);
}}
>
<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"
type="button"
on:click={() => {
shareModelfile(modelfile);
shareModelHandler(model);
}}
>
<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"
type="button"
on:click={() => {
deleteModelfile(modelfile.tagName);
deleteModelHandler(model);
}}
>
<svg
@ -231,8 +244,8 @@
<div class=" flex justify-end w-full mb-3">
<div class="flex space-x-1">
<input
id="modelfiles-import-input"
bind:this={modelfilesImportInputElement}
id="models-import-input"
bind:this={modelsImportInputElement}
bind:files={importFiles}
type="file"
accept=".json"
@ -242,16 +255,18 @@
let reader = new FileReader();
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]);
@ -261,10 +276,10 @@
<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"
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">
<svg
@ -285,10 +300,10 @@
<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"
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">
<svg
@ -314,47 +329,13 @@
</div>
<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
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
saveModelfiles(localModelfiles);
downloadModels(localModelfiles);
localStorage.removeItem('modelfiles');
localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
await modelfiles.set(await getModelfiles(localStorage.token));
}}
>
<div class=" self-center">
@ -402,7 +383,7 @@
</div>
<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>
</a>

View File

@ -321,13 +321,11 @@
<div class="max-w-full">
<Selector
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}
/>
</div>

View File

@ -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 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 IMAGES_API_BASE_URL = `${WEBUI_BASE_URL}/images/api/v1`;
export const RAG_API_BASE_URL = `${WEBUI_BASE_URL}/rag/api/v1`;

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} està pensant...",
"{{user}}'s Chats": "{{user}}'s Chats",
"{{webUIName}} Backend Required": "Es requereix Backend de {{webUIName}}",
"A selected model does not support image input": "",
"a user": "un usuari",
"About": "Sobre",
"Account": "Compte",
@ -31,6 +32,7 @@
"Advanced Parameters": "Paràmetres Avançats",
"all": "tots",
"All Documents": "Tots els Documents",
"All selected models do not support image input, removed images": "",
"All Users": "Tots els Usuaris",
"Allow": "Permet",
"Allow Chat Deletion": "Permet la Supressió del Xat",
@ -115,6 +117,7 @@
"Created at": "Creat el",
"Created At": "Creat el",
"Current Model": "Model Actual",
"Current Models": "",
"Current Password": "Contrasenya Actual",
"Custom": "Personalitzat",
"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 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 Model Display Name": "",
"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 Score": "Introdueix el Puntuació",
@ -235,6 +239,7 @@
"Input commands": "Entra ordres",
"Interface": "Interfície",
"Invalid Tag": "Etiqueta Inválida",
"Is Model Vision Capable": "",
"January": "Gener",
"join our Discord for help.": "uneix-te al nostre Discord per ajuda.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Creat per la Comunitat OpenWebUI",
"Make sure to enclose them with": "Assegura't d'envoltar-los amb",
"Manage LiteLLM Models": "Gestiona Models LiteLLM",
"Manage Model Information": "",
"Manage Models": "Gestiona Models",
"Manage Ollama Models": "Gestiona Models Ollama",
"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 {{modelId}} not found": "Model {{modelId}} no trobat",
"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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Nom del Model",
"Model not selected": "Model no seleccionat",
"Model Tag Name": "Nom de l'Etiqueta del Model",
@ -289,6 +300,7 @@
"Name your modelfile": "Nomena el teu fitxer de model",
"New Chat": "Xat Nou",
"New Password": "Nova Contrasenya",
"No": "",
"No results found": "No s'han trobat resultats",
"No source available": "Sense font disponible",
"Not factually correct": "No està clarament correcte",
@ -385,6 +397,7 @@
"Select a model": "Selecciona un model",
"Select an Ollama instance": "Selecciona una instància d'Ollama",
"Select model": "Selecciona un model",
"Selected models do not support image inputs": "",
"Send": "Envia",
"Send a Message": "Envia un Missatge",
"Send message": "Envia missatge",
@ -492,6 +505,7 @@
"Workspace": "Treball",
"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].",
"Yes": "",
"Yesterday": "Ayer",
"You": "Tu",
"You have no archived conversations.": "No tens converses arxivades.",

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} denkt nach...",
"{{user}}'s Chats": "{{user}}s Chats",
"{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich",
"A selected model does not support image input": "",
"a user": "ein Benutzer",
"About": "Über",
"Account": "Account",
@ -31,6 +32,7 @@
"Advanced Parameters": "Erweiterte Parameter",
"all": "Alle",
"All Documents": "Alle Dokumente",
"All selected models do not support image input, removed images": "",
"All Users": "Alle Benutzer",
"Allow": "Erlauben",
"Allow Chat Deletion": "Chat Löschung erlauben",
@ -115,6 +117,7 @@
"Created at": "Erstellt am",
"Created At": "Erstellt am",
"Current Model": "Aktuelles Modell",
"Current Models": "",
"Current Password": "Aktuelles Passwort",
"Custom": "Benutzerdefiniert",
"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 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 Model Display Name": "",
"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 Score": "Score eingeben",
@ -235,6 +239,7 @@
"Input commands": "Eingabebefehle",
"Interface": "Benutzeroberfläche",
"Invalid Tag": "Ungültiger Tag",
"Is Model Vision Capable": "",
"January": "Januar",
"join our Discord for help.": "Trete unserem Discord bei, um Hilfe zu erhalten.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Von der OpenWebUI-Community",
"Make sure to enclose them with": "Formatiere deine Variablen mit:",
"Manage LiteLLM Models": "LiteLLM-Modelle verwalten",
"Manage Model Information": "",
"Manage Models": "Modelle verwalten",
"Manage Ollama Models": "Ollama-Modelle verwalten",
"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 {{modelId}} not found": "Modell {{modelId}} nicht gefunden",
"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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Modellname",
"Model not selected": "Modell nicht ausgewählt",
"Model Tag Name": "Modell-Tag-Name",
@ -289,6 +300,7 @@
"Name your modelfile": "Benenne dein modelfile",
"New Chat": "Neuer Chat",
"New Password": "Neues Passwort",
"No": "",
"No results found": "Keine Ergebnisse gefunden",
"No source available": "Keine Quelle verfügbar.",
"Not factually correct": "Nicht sachlich korrekt.",
@ -385,6 +397,7 @@
"Select a model": "Ein Modell auswählen",
"Select an Ollama instance": "Eine Ollama Instanz auswählen",
"Select model": "Modell auswählen",
"Selected models do not support image inputs": "",
"Send": "Senden",
"Send a Message": "Eine Nachricht senden",
"Send message": "Nachricht senden",
@ -492,6 +505,7 @@
"Workspace": "Arbeitsbereich",
"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.",
"Yes": "",
"Yesterday": "Gestern",
"You": "Du",
"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} is thinkin'...",
"{{user}}'s Chats": "",
"{{webUIName}} Backend Required": "{{webUIName}} Backend Much Required",
"A selected model does not support image input": "",
"a user": "such user",
"About": "Much About",
"Account": "Account",
@ -31,6 +32,7 @@
"Advanced Parameters": "Advanced Parameters",
"all": "all",
"All Documents": "",
"All selected models do not support image input, removed images": "",
"All Users": "All Users",
"Allow": "Allow",
"Allow Chat Deletion": "Allow Delete Chats",
@ -115,6 +117,7 @@
"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 purpose",
@ -181,6 +184,7 @@
"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 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 Number of Steps (e.g. 50)": "Enter Number of Steps (e.g. 50)",
"Enter Score": "",
@ -235,6 +239,7 @@
"Input commands": "Input commands",
"Interface": "Interface",
"Invalid Tag": "",
"Is Model Vision Capable": "",
"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 Wowdels",
"Manage Ollama Models": "Manage Ollama Wowdels",
"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 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 not selected": "Model not selected",
"Model Tag Name": "Wowdel Tag Name",
@ -289,6 +300,7 @@
"Name your modelfile": "Name your modelfile",
"New Chat": "New Bark",
"New Password": "New Barkword",
"No": "",
"No results found": "",
"No source available": "No source available",
"Not factually correct": "",
@ -385,6 +397,7 @@
"Select a model": "Select a model much choice",
"Select an Ollama instance": "Select an Ollama instance very choose",
"Select model": "Select model much choice",
"Selected models do not support image inputs": "",
"Send": "",
"Send a Message": "Send a Message much message",
"Send message": "Send message very send",
@ -492,6 +505,7 @@
"Workspace": "",
"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.",
"Yes": "",
"Yesterday": "",
"You": "",
"You have no archived conversations.": "",

View File

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

View File

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

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} está pensando...",
"{{user}}'s Chats": "{{user}}'s Chats",
"{{webUIName}} Backend Required": "{{webUIName}} Servidor Requerido",
"A selected model does not support image input": "",
"a user": "un usuario",
"About": "Sobre nosotros",
"Account": "Cuenta",
@ -31,6 +32,7 @@
"Advanced Parameters": "Parámetros Avanzados",
"all": "todo",
"All Documents": "Todos los Documentos",
"All selected models do not support image input, removed images": "",
"All Users": "Todos los Usuarios",
"Allow": "Permitir",
"Allow Chat Deletion": "Permitir Borrar Chats",
@ -115,6 +117,7 @@
"Created at": "Creado en",
"Created At": "Creado en",
"Current Model": "Modelo Actual",
"Current Models": "",
"Current Password": "Contraseña Actual",
"Custom": "Personalizado",
"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 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 Model Display Name": "",
"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 Score": "Ingrese la puntuación",
@ -235,6 +239,7 @@
"Input commands": "Ingresar comandos",
"Interface": "Interfaz",
"Invalid Tag": "Etiqueta Inválida",
"Is Model Vision Capable": "",
"January": "Enero",
"join our Discord for help.": "Únase a nuestro Discord para obtener ayuda.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Hecho por la comunidad de OpenWebUI",
"Make sure to enclose them with": "Asegúrese de adjuntarlos con",
"Manage LiteLLM Models": "Administrar Modelos LiteLLM",
"Manage Model Information": "",
"Manage Models": "Administrar Modelos",
"Manage Ollama Models": "Administrar Modelos Ollama",
"March": "Marzo",
@ -272,7 +278,12 @@
"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 {{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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Nombre del modelo",
"Model not selected": "Modelo no seleccionado",
"Model Tag Name": "Nombre de la etiqueta del modelo",
@ -289,6 +300,7 @@
"Name your modelfile": "Nombra tu modelfile",
"New Chat": "Nuevo Chat",
"New Password": "Nueva Contraseña",
"No": "",
"No results found": "No se han encontrado resultados",
"No source available": "No hay fuente disponible",
"Not factually correct": "No es correcto en todos los aspectos",
@ -385,6 +397,7 @@
"Select a model": "Selecciona un modelo",
"Select an Ollama instance": "Seleccione una instancia de Ollama",
"Select model": "Selecciona un modelo",
"Selected models do not support image inputs": "",
"Send": "Enviar",
"Send a Message": "Enviar un Mensaje",
"Send message": "Enviar Mensaje",
@ -492,6 +505,7 @@
"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 summary in 50 words that summarizes [topic or keyword].": "Escribe un resumen en 50 palabras que resuma [tema o palabra clave].",
"Yes": "",
"Yesterday": "Ayer",
"You": "Usted",
"You have no archived conversations.": "No tiene conversaciones archivadas.",

View File

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

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} miettii...",
"{{user}}'s Chats": "{{user}}:n keskustelut",
"{{webUIName}} Backend Required": "{{webUIName}} backend vaaditaan",
"A selected model does not support image input": "",
"a user": "käyttäjä",
"About": "Tietoja",
"Account": "Tili",
@ -31,6 +32,7 @@
"Advanced Parameters": "Edistyneet parametrit",
"all": "kaikki",
"All Documents": "Kaikki asiakirjat",
"All selected models do not support image input, removed images": "",
"All Users": "Kaikki käyttäjät",
"Allow": "Salli",
"Allow Chat Deletion": "Salli keskustelujen poisto",
@ -115,6 +117,7 @@
"Created at": "Luotu",
"Created At": "Luotu",
"Current Model": "Nykyinen malli",
"Current Models": "",
"Current Password": "Nykyinen salasana",
"Custom": "Mukautettu",
"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 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 Model Display Name": "",
"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 Score": "Syötä pisteet",
@ -235,6 +239,7 @@
"Input commands": "Syötä komennot",
"Interface": "Käyttöliittymä",
"Invalid Tag": "Virheellinen tagi",
"Is Model Vision Capable": "",
"January": "tammikuu",
"join our Discord for help.": "liity Discordiimme saadaksesi apua.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Tehnyt OpenWebUI-yhteisö",
"Make sure to enclose them with": "Varmista, että suljet ne",
"Manage LiteLLM Models": "Hallitse LiteLLM-malleja",
"Manage Model Information": "",
"Manage Models": "Hallitse malleja",
"Manage Ollama Models": "Hallitse Ollama-malleja",
"March": "maaliskuu",
@ -272,7 +278,12 @@
"Model '{{modelTag}}' is already in queue for downloading.": "Malli '{{modelTag}}' on jo jonossa ladattavaksi.",
"Model {{modelId}} not found": "Mallia {{modelId}} ei löytynyt",
"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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Mallin nimi",
"Model not selected": "Mallia ei valittu",
"Model Tag Name": "Mallitagin nimi",
@ -289,6 +300,7 @@
"Name your modelfile": "Nimeä mallitiedostosi",
"New Chat": "Uusi keskustelu",
"New Password": "Uusi salasana",
"No": "",
"No results found": "Ei tuloksia",
"No source available": "Ei lähdettä saatavilla",
"Not factually correct": "Ei faktisesti oikein",
@ -385,6 +397,7 @@
"Select a model": "Valitse malli",
"Select an Ollama instance": "Valitse Ollama-instanssi",
"Select model": "Valitse malli",
"Selected models do not support image inputs": "",
"Send": "Lähetä",
"Send a Message": "Lähetä viesti",
"Send message": "Lähetä viesti",
@ -492,6 +505,7 @@
"Workspace": "Työtilat",
"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].",
"Yes": "",
"Yesterday": "Eilen",
"You": "Sinä",
"You have no archived conversations.": "Sinulla ei ole arkistoituja keskusteluja.",

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} réfléchit...",
"{{user}}'s Chats": "{{user}}'s Chats",
"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
"A selected model does not support image input": "",
"a user": "un utilisateur",
"About": "À propos",
"Account": "Compte",
@ -31,6 +32,7 @@
"Advanced Parameters": "Paramètres avancés",
"all": "tous",
"All Documents": "Tous les documents",
"All selected models do not support image input, removed images": "",
"All Users": "Tous les utilisateurs",
"Allow": "Autoriser",
"Allow Chat Deletion": "Autoriser la suppression des discussions",
@ -115,6 +117,7 @@
"Created at": "Créé le",
"Created At": "Créé le",
"Current Model": "Modèle actuel",
"Current Models": "",
"Current Password": "Mot de passe actuel",
"Custom": "Personnalisé",
"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 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 Model Display Name": "",
"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 Score": "Entrez le score",
@ -235,6 +239,7 @@
"Input commands": "Entrez des commandes d'entrée",
"Interface": "Interface",
"Invalid Tag": "Tag invalide",
"Is Model Vision Capable": "",
"January": "Janvier",
"join our Discord for help.": "rejoignez notre Discord pour obtenir de l'aide.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
"Make sure to enclose them with": "Assurez-vous de les entourer avec",
"Manage LiteLLM Models": "Gérer les modèles LiteLLM",
"Manage Model Information": "",
"Manage Models": "Gérer les modèles",
"Manage Ollama Models": "Gérer les modèles Ollama",
"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 {{modelId}} not found": "Modèle {{modelId}} non trouvé",
"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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Nom du modèle",
"Model not selected": "Modèle non sélectionné",
"Model Tag Name": "Nom de tag du modèle",
@ -289,6 +300,7 @@
"Name your modelfile": "Nommez votre fichier de modèle",
"New Chat": "Nouvelle discussion",
"New Password": "Nouveau mot de passe",
"No": "",
"No results found": "Aucun résultat trouvé",
"No source available": "Aucune source disponible",
"Not factually correct": "Non, pas exactement correct",
@ -385,6 +397,7 @@
"Select a model": "Sélectionnez un modèle",
"Select an Ollama instance": "Sélectionner une instance Ollama",
"Select model": "Sélectionnez un modèle",
"Selected models do not support image inputs": "",
"Send": "Envoyer",
"Send a Message": "Envoyer un message",
"Send message": "Envoyer un message",
@ -492,6 +505,7 @@
"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 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",
"You": "Vous",
"You have no archived conversations.": "Vous n'avez aucune conversation archivée.",

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} réfléchit...",
"{{user}}'s Chats": "{{user}}'s Chats",
"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
"A selected model does not support image input": "",
"a user": "un utilisateur",
"About": "À propos",
"Account": "Compte",
@ -31,6 +32,7 @@
"Advanced Parameters": "Paramètres avancés",
"all": "tous",
"All Documents": "Tous les documents",
"All selected models do not support image input, removed images": "",
"All Users": "Tous les utilisateurs",
"Allow": "Autoriser",
"Allow Chat Deletion": "Autoriser la suppression du chat",
@ -115,6 +117,7 @@
"Created at": "Créé le",
"Created At": "Créé le",
"Current Model": "Modèle actuel",
"Current Models": "",
"Current Password": "Mot de passe actuel",
"Custom": "Personnalisé",
"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 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 Model Display Name": "",
"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 Score": "Entrez le score",
@ -235,6 +239,7 @@
"Input commands": "Entrez les commandes d'entrée",
"Interface": "Interface",
"Invalid Tag": "Tag invalide",
"Is Model Vision Capable": "",
"January": "Janvier",
"join our Discord for help.": "rejoignez notre Discord pour obtenir de l'aide.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
"Make sure to enclose them with": "Assurez-vous de les entourer avec",
"Manage LiteLLM Models": "Gérer les modèles LiteLLM",
"Manage Model Information": "",
"Manage Models": "Gérer les modèles",
"Manage Ollama Models": "Gérer les modèles Ollama",
"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 {{modelId}} not found": "Modèle {{modelId}} non trouvé",
"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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Nom du modèle",
"Model not selected": "Modèle non sélectionné",
"Model Tag Name": "Nom de tag du modèle",
@ -289,6 +300,7 @@
"Name your modelfile": "Nommez votre fichier de modèle",
"New Chat": "Nouveau chat",
"New Password": "Nouveau mot de passe",
"No": "",
"No results found": "Aucun résultat trouvé",
"No source available": "Aucune source disponible",
"Not factually correct": "Non, pas exactement correct",
@ -385,6 +397,7 @@
"Select a model": "Sélectionner un modèle",
"Select an Ollama instance": "Sélectionner une instance Ollama",
"Select model": "Sélectionner un modèle",
"Selected models do not support image inputs": "",
"Send": "Envoyer",
"Send a Message": "Envoyer un message",
"Send message": "Envoyer un message",
@ -492,6 +505,7 @@
"Workspace": "Espace de travail",
"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é]",
"Yes": "",
"Yesterday": "hier",
"You": "Vous",
"You have no archived conversations.": "Vous n'avez aucune conversation archivée.",

View File

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

View File

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

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} razmišlja...",
"{{user}}'s Chats": "Razgovori korisnika {{user}}",
"{{webUIName}} Backend Required": "{{webUIName}} Backend je potreban",
"A selected model does not support image input": "",
"a user": "korisnik",
"About": "O aplikaciji",
"Account": "Račun",
@ -31,6 +32,7 @@
"Advanced Parameters": "Napredni parametri",
"all": "sve",
"All Documents": "Svi dokumenti",
"All selected models do not support image input, removed images": "",
"All Users": "Svi korisnici",
"Allow": "Dopusti",
"Allow Chat Deletion": "Dopusti brisanje razgovora",
@ -115,6 +117,7 @@
"Created at": "Stvoreno",
"Created At": "Stvoreno",
"Current Model": "Trenutni model",
"Current Models": "",
"Current Password": "Trenutna lozinka",
"Custom": "Prilagođeno",
"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 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 Model Display Name": "",
"Enter model tag (e.g. {{modelTag}})": "Unesite oznaku modela (npr. {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "Unesite broj koraka (npr. 50)",
"Enter Score": "Unesite ocjenu",
@ -235,6 +239,7 @@
"Input commands": "Unos naredbi",
"Interface": "Sučelje",
"Invalid Tag": "Nevažeća oznaka",
"Is Model Vision Capable": "",
"January": "Siječanj",
"join our Discord for help.": "pridružite se našem Discordu za pomoć.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Izradio OpenWebUI Community",
"Make sure to enclose them with": "Provjerite da ih zatvorite s",
"Manage LiteLLM Models": "Upravljajte LiteLLM modelima",
"Manage Model Information": "",
"Manage Models": "Upravljanje modelima",
"Manage Ollama Models": "Upravljanje Ollama modelima",
"March": "Ožujak",
@ -272,7 +278,12 @@
"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 {{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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Naziv modela",
"Model not selected": "Model nije odabran",
"Model Tag Name": "Naziv oznake modela",
@ -289,6 +300,7 @@
"Name your modelfile": "Nazovite svoju datoteku modela",
"New Chat": "Novi razgovor",
"New Password": "Nova lozinka",
"No": "",
"No results found": "Nema rezultata",
"No source available": "Nema dostupnog izvora",
"Not factually correct": "Nije činjenično točno",
@ -385,6 +397,7 @@
"Select a model": "Odaberite model",
"Select an Ollama instance": "Odaberite Ollama instancu",
"Select model": "Odaberite model",
"Selected models do not support image inputs": "",
"Send": "Pošalji",
"Send a Message": "Pošaljite poruku",
"Send message": "Pošalji poruku",
@ -492,6 +505,7 @@
"Workspace": "Radna ploča",
"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č].",
"Yes": "",
"Yesterday": "Jučer",
"You": "Vi",
"You have no archived conversations.": "Nemate arhiviranih razgovora.",

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} sta pensando...",
"{{user}}'s Chats": "{{user}} Chat",
"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
"A selected model does not support image input": "",
"a user": "un utente",
"About": "Informazioni",
"Account": "Account",
@ -31,6 +32,7 @@
"Advanced Parameters": "Parametri avanzati",
"all": "tutti",
"All Documents": "Tutti i documenti",
"All selected models do not support image input, removed images": "",
"All Users": "Tutti gli utenti",
"Allow": "Consenti",
"Allow Chat Deletion": "Consenti l'eliminazione della chat",
@ -115,6 +117,7 @@
"Created at": "Creato il",
"Created At": "Creato il",
"Current Model": "Modello corrente",
"Current Models": "",
"Current Password": "Password corrente",
"Custom": "Personalizzato",
"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 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 Model Display Name": "",
"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 Score": "Inserisci il punteggio",
@ -235,6 +239,7 @@
"Input commands": "Comandi di input",
"Interface": "Interfaccia",
"Invalid Tag": "Tag non valido",
"Is Model Vision Capable": "",
"January": "Gennaio",
"join our Discord for help.": "unisciti al nostro Discord per ricevere aiuto.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Realizzato dalla comunità OpenWebUI",
"Make sure to enclose them with": "Assicurati di racchiuderli con",
"Manage LiteLLM Models": "Gestisci modelli LiteLLM",
"Manage Model Information": "",
"Manage Models": "Gestisci modelli",
"Manage Ollama Models": "Gestisci modelli Ollama",
"March": "Marzo",
@ -272,7 +278,12 @@
"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 {{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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Nome modello",
"Model not selected": "Modello non selezionato",
"Model Tag Name": "Nome tag del modello",
@ -289,6 +300,7 @@
"Name your modelfile": "Assegna un nome al tuo file modello",
"New Chat": "Nuova chat",
"New Password": "Nuova password",
"No": "",
"No results found": "Nessun risultato trovato",
"No source available": "Nessuna fonte disponibile",
"Not factually correct": "Non corretto dal punto di vista fattuale",
@ -385,6 +397,7 @@
"Select a model": "Seleziona un modello",
"Select an Ollama instance": "Seleziona un'istanza Ollama",
"Select model": "Seleziona modello",
"Selected models do not support image inputs": "",
"Send": "Invia",
"Send a Message": "Invia un messaggio",
"Send message": "Invia messaggio",
@ -492,6 +505,7 @@
"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 summary in 50 words that summarizes [topic or keyword].": "Scrivi un riassunto in 50 parole che riassume [argomento o parola chiave].",
"Yes": "",
"Yesterday": "Ieri",
"You": "Tu",
"You have no archived conversations.": "Non hai conversazioni archiviate.",

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} is aan het denken...",
"{{user}}'s Chats": "{{user}}'s Chats",
"{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht",
"A selected model does not support image input": "",
"a user": "een gebruiker",
"About": "Over",
"Account": "Account",
@ -31,6 +32,7 @@
"Advanced Parameters": "Geavanceerde Parameters",
"all": "alle",
"All Documents": "Alle Documenten",
"All selected models do not support image input, removed images": "",
"All Users": "Alle Gebruikers",
"Allow": "Toestaan",
"Allow Chat Deletion": "Sta Chat Verwijdering toe",
@ -115,6 +117,7 @@
"Created at": "Gemaakt op",
"Created At": "Gemaakt op",
"Current Model": "Huidig Model",
"Current Models": "",
"Current Password": "Huidig Wachtwoord",
"Custom": "Aangepast",
"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 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 Model Display Name": "",
"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 Score": "Voeg score toe",
@ -235,6 +239,7 @@
"Input commands": "Voer commando's in",
"Interface": "Interface",
"Invalid Tag": "Ongeldige Tag",
"Is Model Vision Capable": "",
"January": "Januari",
"join our Discord for help.": "join onze Discord voor hulp.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Gemaakt door OpenWebUI Community",
"Make sure to enclose them with": "Zorg ervoor dat je ze omringt met",
"Manage LiteLLM Models": "Beheer LiteLLM Modellen",
"Manage Model Information": "",
"Manage Models": "Beheer Modellen",
"Manage Ollama Models": "Beheer Ollama Modellen",
"March": "Maart",
@ -272,7 +278,12 @@
"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 {{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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Model Naam",
"Model not selected": "Model niet geselecteerd",
"Model Tag Name": "Model Tag Naam",
@ -289,6 +300,7 @@
"Name your modelfile": "Benoem je modelfile",
"New Chat": "Nieuwe Chat",
"New Password": "Nieuw Wachtwoord",
"No": "",
"No results found": "Geen resultaten gevonden",
"No source available": "Geen bron beschikbaar",
"Not factually correct": "Feitelijk niet juist",
@ -385,6 +397,7 @@
"Select a model": "Selecteer een model",
"Select an Ollama instance": "Selecteer een Ollama instantie",
"Select model": "Selecteer een model",
"Selected models do not support image inputs": "",
"Send": "Verzenden",
"Send a Message": "Stuur een Bericht",
"Send message": "Stuur bericht",
@ -492,6 +505,7 @@
"Workspace": "Werkruimte",
"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.",
"Yes": "",
"Yesterday": "gisteren",
"You": "U",
"You have no archived conversations.": "U heeft geen gearchiveerde gesprekken.",

View File

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

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} myśli...",
"{{user}}'s Chats": "{{user}} - czaty",
"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
"A selected model does not support image input": "",
"a user": "użytkownik",
"About": "O nas",
"Account": "Konto",
@ -31,6 +32,7 @@
"Advanced Parameters": "Zaawansowane parametry",
"all": "wszyscy",
"All Documents": "Wszystkie dokumenty",
"All selected models do not support image input, removed images": "",
"All Users": "Wszyscy użytkownicy",
"Allow": "Pozwól",
"Allow Chat Deletion": "Pozwól na usuwanie czatu",
@ -115,6 +117,7 @@
"Created at": "Utworzono o",
"Created At": "Utworzono o",
"Current Model": "Bieżący model",
"Current Models": "",
"Current Password": "Bieżące hasło",
"Custom": "Niestandardowy",
"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 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 Model Display Name": "",
"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 Score": "Wprowadź wynik",
@ -235,6 +239,7 @@
"Input commands": "Wprowadź komendy",
"Interface": "Interfejs",
"Invalid Tag": "Nieprawidłowy tag",
"Is Model Vision Capable": "",
"January": "Styczeń",
"join our Discord for help.": "Dołącz do naszego Discorda po pomoc.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Stworzone przez społeczność OpenWebUI",
"Make sure to enclose them with": "Upewnij się, że są one zamknięte w",
"Manage LiteLLM Models": "Zarządzaj modelami LiteLLM",
"Manage Model Information": "",
"Manage Models": "Zarządzaj modelami",
"Manage Ollama Models": "Zarządzaj modelami Ollama",
"March": "Marzec",
@ -272,7 +278,12 @@
"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 {{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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Nazwa modelu",
"Model not selected": "Model nie został wybrany",
"Model Tag Name": "Nazwa tagu modelu",
@ -289,6 +300,7 @@
"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
"New Chat": "Nowy czat",
"New Password": "Nowe hasło",
"No": "",
"No results found": "Nie znaleziono rezultatów",
"No source available": "Źródło nie dostępne",
"Not factually correct": "Nie zgodne z faktami",
@ -385,6 +397,7 @@
"Select a model": "Wybierz model",
"Select an Ollama instance": "Wybierz instancję Ollama",
"Select model": "Wybierz model",
"Selected models do not support image inputs": "",
"Send": "Wyślij",
"Send a Message": "Wyślij Wiadomość",
"Send message": "Wyślij wiadomość",
@ -492,6 +505,7 @@
"Workspace": "Obszar roboczy",
"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].",
"Yes": "",
"Yesterday": "Wczoraj",
"You": "Ty",
"You have no archived conversations.": "Nie masz zarchiwizowanych rozmów.",

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} está pensando...",
"{{user}}'s Chats": "{{user}}'s Chats",
"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
"A selected model does not support image input": "",
"a user": "um usuário",
"About": "Sobre",
"Account": "Conta",
@ -31,6 +32,7 @@
"Advanced Parameters": "Parâmetros Avançados",
"all": "todos",
"All Documents": "Todos os Documentos",
"All selected models do not support image input, removed images": "",
"All Users": "Todos os Usuários",
"Allow": "Permitir",
"Allow Chat Deletion": "Permitir Exclusão de Bate-papo",
@ -115,6 +117,7 @@
"Created at": "Criado em",
"Created At": "Criado em",
"Current Model": "Modelo Atual",
"Current Models": "",
"Current Password": "Senha Atual",
"Custom": "Personalizado",
"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 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 Model Display Name": "",
"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 Score": "Digite a Pontuação",
@ -235,6 +239,7 @@
"Input commands": "Comandos de entrada",
"Interface": "Interface",
"Invalid Tag": "Etiqueta Inválida",
"Is Model Vision Capable": "",
"January": "Janeiro",
"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
"Make sure to enclose them with": "Certifique-se de colocá-los entre",
"Manage LiteLLM Models": "Gerenciar Modelos LiteLLM",
"Manage Model Information": "",
"Manage Models": "Gerenciar Modelos",
"Manage Ollama Models": "Gerenciar Modelos Ollama",
"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 {{modelId}} not found": "Modelo {{modelId}} não encontrado",
"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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Nome do Modelo",
"Model not selected": "Modelo não selecionado",
"Model Tag Name": "Nome da Tag do Modelo",
@ -289,6 +300,7 @@
"Name your modelfile": "Nomeie seu arquivo de modelo",
"New Chat": "Novo Bate-papo",
"New Password": "Nova Senha",
"No": "",
"No results found": "Nenhum resultado encontrado",
"No source available": "Nenhuma fonte disponível",
"Not factually correct": "Não é correto em termos factuais",
@ -385,6 +397,7 @@
"Select a model": "Selecione um modelo",
"Select an Ollama instance": "Selecione uma instância Ollama",
"Select model": "Selecione um modelo",
"Selected models do not support image inputs": "",
"Send": "Enviar",
"Send a Message": "Enviar uma Mensagem",
"Send message": "Enviar mensagem",
@ -492,6 +505,7 @@
"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 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",
"You": "Você",
"You have no archived conversations.": "Você não tem conversas arquivadas.",

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} está pensando...",
"{{user}}'s Chats": "{{user}}'s Chats",
"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
"A selected model does not support image input": "",
"a user": "um usuário",
"About": "Sobre",
"Account": "Conta",
@ -31,6 +32,7 @@
"Advanced Parameters": "Parâmetros Avançados",
"all": "todos",
"All Documents": "Todos os Documentos",
"All selected models do not support image input, removed images": "",
"All Users": "Todos os Usuários",
"Allow": "Permitir",
"Allow Chat Deletion": "Permitir Exclusão de Bate-papo",
@ -115,6 +117,7 @@
"Created at": "Criado em",
"Created At": "Criado em",
"Current Model": "Modelo Atual",
"Current Models": "",
"Current Password": "Senha Atual",
"Custom": "Personalizado",
"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 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 Model Display Name": "",
"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 Score": "Digite a Pontuação",
@ -235,6 +239,7 @@
"Input commands": "Comandos de entrada",
"Interface": "Interface",
"Invalid Tag": "Etiqueta Inválida",
"Is Model Vision Capable": "",
"January": "Janeiro",
"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
"Make sure to enclose them with": "Certifique-se de colocá-los entre",
"Manage LiteLLM Models": "Gerenciar Modelos LiteLLM",
"Manage Model Information": "",
"Manage Models": "Gerenciar Modelos",
"Manage Ollama Models": "Gerenciar Modelos Ollama",
"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 {{modelId}} not found": "Modelo {{modelId}} não encontrado",
"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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Nome do Modelo",
"Model not selected": "Modelo não selecionado",
"Model Tag Name": "Nome da Tag do Modelo",
@ -289,6 +300,7 @@
"Name your modelfile": "Nomeie seu arquivo de modelo",
"New Chat": "Novo Bate-papo",
"New Password": "Nova Senha",
"No": "",
"No results found": "Nenhum resultado encontrado",
"No source available": "Nenhuma fonte disponível",
"Not factually correct": "Não é correto em termos factuais",
@ -385,6 +397,7 @@
"Select a model": "Selecione um modelo",
"Select an Ollama instance": "Selecione uma instância Ollama",
"Select model": "Selecione um modelo",
"Selected models do not support image inputs": "",
"Send": "Enviar",
"Send a Message": "Enviar uma Mensagem",
"Send message": "Enviar mensagem",
@ -492,6 +505,7 @@
"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 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",
"You": "Você",
"You have no archived conversations.": "Você não tem bate-papos arquivados.",

View File

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

View File

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

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} tänker...",
"{{user}}'s Chats": "{{user}}s Chats",
"{{webUIName}} Backend Required": "{{webUIName}} Backend krävs",
"A selected model does not support image input": "",
"a user": "en användare",
"About": "Om",
"Account": "Konto",
@ -31,6 +32,7 @@
"Advanced Parameters": "Avancerade parametrar",
"all": "alla",
"All Documents": "Alla dokument",
"All selected models do not support image input, removed images": "",
"All Users": "Alla användare",
"Allow": "Tillåt",
"Allow Chat Deletion": "Tillåt chattborttagning",
@ -115,6 +117,7 @@
"Created at": "Skapad",
"Created At": "Skapad",
"Current Model": "Aktuell modell",
"Current Models": "",
"Current Password": "Nuvarande lösenord",
"Custom": "Anpassad",
"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 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 Model Display Name": "",
"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 Score": "Ange poäng",
@ -235,6 +239,7 @@
"Input commands": "Indatakommandon",
"Interface": "Gränssnitt",
"Invalid Tag": "Ogiltig tagg",
"Is Model Vision Capable": "",
"January": "januar",
"join our Discord for help.": "gå med i vår Discord för hjälp.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"Made by OpenWebUI Community": "Skapad av OpenWebUI Community",
"Make sure to enclose them with": "Se till att bifoga dem med",
"Manage LiteLLM Models": "Hantera LiteLLM-modeller",
"Manage Model Information": "",
"Manage Models": "Hantera modeller",
"Manage Ollama Models": "Hantera Ollama-modeller",
"March": "mars",
@ -272,7 +278,12 @@
"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 {{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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Modellnamn",
"Model not selected": "Modell inte vald",
"Model Tag Name": "Modelltaggnamn",
@ -289,6 +300,7 @@
"Name your modelfile": "Namnge din modelfil",
"New Chat": "Ny chatt",
"New Password": "Nytt lösenord",
"No": "",
"No results found": "Inga resultat hittades",
"No source available": "Ingen tilgjengelig kilde",
"Not factually correct": "Inte faktiskt korrekt",
@ -385,6 +397,7 @@
"Select a model": "Välj en modell",
"Select an Ollama instance": "Välj en Ollama-instans",
"Select model": "Välj en modell",
"Selected models do not support image inputs": "",
"Send": "Skicka",
"Send a Message": "Skicka ett meddelande",
"Send message": "Skicka meddelande",
@ -492,6 +505,7 @@
"Workspace": "arbetsyta",
"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].",
"Yes": "",
"Yesterday": "Igenom",
"You": "du",
"You have no archived conversations.": "Du har inga arkiverade konversationer.",

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} düşünüyor...",
"{{user}}'s Chats": "{{user}} Sohbetleri",
"{{webUIName}} Backend Required": "{{webUIName}} Arkayüz Gerekli",
"A selected model does not support image input": "",
"a user": "bir kullanıcı",
"About": "Hakkında",
"Account": "Hesap",
@ -31,6 +32,7 @@
"Advanced Parameters": "Gelişmiş Parametreler",
"all": "tümü",
"All Documents": "Tüm Belgeler",
"All selected models do not support image input, removed images": "",
"All Users": "Tüm Kullanıcılar",
"Allow": "İzin ver",
"Allow Chat Deletion": "Sohbet Silmeye İzin Ver",
@ -115,6 +117,7 @@
"Created at": "Oluşturulma tarihi",
"Created At": "Şu Tarihte Oluşturuldu:",
"Current Model": "Mevcut Model",
"Current Models": "",
"Current Password": "Mevcut Parola",
"Custom": "Özel",
"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 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 Model Display Name": "",
"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 Score": "Skoru Girin",
@ -235,6 +239,7 @@
"Input commands": "Giriş komutları",
"Interface": "Arayüz",
"Invalid Tag": "Geçersiz etiket",
"Is Model Vision Capable": "",
"January": "Ocak",
"join our Discord for help.": "yardım için Discord'umuza katılın.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"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:",
"Manage LiteLLM Models": "LiteLLM Modellerini Yönet",
"Manage Model Information": "",
"Manage Models": "Modelleri Yönet",
"Manage Ollama Models": "Ollama Modellerini Yönet",
"March": "Mart",
@ -272,7 +278,12 @@
"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' zaten indirme sırasında.",
"Model {{modelId}} not found": "{{modelId}} bulunamadı",
"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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Model Adı",
"Model not selected": "Model seçilmedi",
"Model Tag Name": "Model Etiket Adı",
@ -289,6 +300,7 @@
"Name your modelfile": "Model dosyanıza ad verin",
"New Chat": "Yeni Sohbet",
"New Password": "Yeni Parola",
"No": "",
"No results found": "Sonuç bulunamadı",
"No source available": "Kaynak mevcut değil",
"Not factually correct": "Gerçeklere göre doğru değil",
@ -385,6 +397,7 @@
"Select a model": "Bir model seç",
"Select an Ollama instance": "Bir Ollama örneği seçin",
"Select model": "Model seç",
"Selected models do not support image inputs": "",
"Send": "Gönder",
"Send a Message": "Bir Mesaj Gönder",
"Send message": "Mesaj gönder",
@ -492,6 +505,7 @@
"Workspace": "Çalışma Alanı",
"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.",
"Yes": "",
"Yesterday": "Dün",
"You": "Sen",
"You have no archived conversations.": "Arşivlenmiş sohbetleriniz yok.",

View File

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

View File

@ -6,6 +6,7 @@
"{{modelName}} is thinking...": "{{modelName}} đang suy nghĩ...",
"{{user}}'s Chats": "{{user}}'s Chats",
"{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend",
"A selected model does not support image input": "",
"a user": "người sử dụng",
"About": "Giới thiệu",
"Account": "Tài khoản",
@ -31,6 +32,7 @@
"Advanced Parameters": "Các tham số Nâng cao",
"all": "tất cả",
"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",
"Allow": "Cho phép",
"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": "Tạo lúc",
"Current Model": "Mô hình hiện tại",
"Current Models": "",
"Current Password": "Mật khẩu hiện tại",
"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ể",
@ -181,6 +184,7 @@
"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 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 Number of Steps (e.g. 50)": "Nhập số Steps (vd: 50)",
"Enter Score": "Nhập Score",
@ -235,6 +239,7 @@
"Input commands": "Nhập các câu lệnh",
"Interface": "Giao diện",
"Invalid Tag": "Tag không hợp lệ",
"Is Model Vision Capable": "",
"January": "Tháng 1",
"join our Discord for help.": "tham gia Discord của chúng tôi để được trợ giúp.",
"JSON": "JSON",
@ -253,6 +258,7 @@
"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",
"Manage LiteLLM Models": "Quản lý mô hình với LiteLLM",
"Manage Model Information": "",
"Manage Models": "Quản lý mô hình",
"Manage Ollama Models": "Quản lý mô hình với Ollama",
"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 {{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}} 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 info for {{modelName}} added successfully": "",
"Model info for {{modelName}} deleted successfully": "",
"Model Name": "Tên Mô hình",
"Model not selected": "Chưa chọn 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",
"New Chat": "Tạo cuộc trò chuyện mới",
"New Password": "Mật khẩu mới",
"No": "",
"No results found": "Không tìm thấy kết quả",
"No source available": "Không có nguồn",
"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 an Ollama instance": "Chọn một thực thể Ollama",
"Select model": "Chọn model",
"Selected models do not support image inputs": "",
"Send": "Gửi",
"Send a Message": "Gửi yêu cầu",
"Send message": "Gửi yêu cầu",
@ -492,6 +505,7 @@
"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 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",
"You": "Bạn",
"You have no archived conversations.": "Bạn chưa lưu trữ một nội dung chat nào",

View File

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

View File

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

View File

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

View File

@ -1,29 +1,5 @@
import { v4 as uuidv4 } from 'uuid';
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

View File

@ -7,9 +7,8 @@
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 { getModelfiles } from '$lib/apis/modelfiles';
import { getPrompts } from '$lib/apis/prompts';
import { getDocs } from '$lib/apis/documents';
@ -20,7 +19,6 @@
showSettings,
settings,
models,
modelfiles,
prompts,
documents,
tags,
@ -50,21 +48,6 @@
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 () => {
if ($user === undefined) {
await goto('/auth');
@ -93,9 +76,6 @@
(async () => {
models.set(await getModels());
})(),
(async () => {
modelfiles.set(await getModelfiles(localStorage.token));
})(),
(async () => {
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) {
const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
// Check if the Shift key is pressed
@ -188,12 +163,12 @@
});
</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">
<button
id="show-shortcuts-button"
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={() => {
showShortcuts = !showShortcuts;
}}

View File

@ -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"
>
<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'
: ''} transition"
href="/workspace/modelfiles">{$i18n.t('Modelfiles')}</a
href="/workspace/models">{$i18n.t('Models')}</a
>
<a

View File

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

View File

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

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -5,7 +5,7 @@
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 { getChatByShareId } from '$lib/apis/chats';
@ -14,6 +14,7 @@
import Navbar from '$lib/components/layout/Navbar.svelte';
import { getUserById } from '$lib/apis/users';
import { error } from '@sveltejs/kit';
import { getModels } from '$lib/apis';
const i18n = getContext('i18n');
@ -27,17 +28,6 @@
let showModelSelector = false;
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 user = null;
@ -69,10 +59,6 @@
if (await loadSharedChat()) {
await tick();
loaded = true;
window.setTimeout(() => scrollToBottom(), 0);
const chatInput = document.getElementById('chat-textarea');
chatInput?.focus();
} else {
await goto('/');
}
@ -84,6 +70,7 @@
//////////////////////////
const loadSharedChat = async () => {
await models.set(await getModels(localStorage.token));
await chatId.set($page.params.id);
chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => {
await goto('/');
@ -168,7 +155,6 @@
chatId={$chatId}
readOnly={true}
{selectedModels}
{selectedModelfiles}
{processing}
bind:history
bind:messages