mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-15 05:45:19 +00:00
commit
c869652ef4
86 changed files with 2471 additions and 1376 deletions
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -8,36 +8,43 @@ assignees: ''
|
|||
|
||||
# Bug Report
|
||||
|
||||
## Description
|
||||
## Installation Method
|
||||
|
||||
**Bug Summary:**
|
||||
[Provide a brief but clear summary of the bug]
|
||||
|
||||
**Steps to Reproduce:**
|
||||
[Outline the steps to reproduce the bug. Be as detailed as possible.]
|
||||
|
||||
**Expected Behavior:**
|
||||
[Describe what you expected to happen.]
|
||||
|
||||
**Actual Behavior:**
|
||||
[Describe what actually happened.]
|
||||
[Describe the method you used to install the project, e.g., git clone, Docker, pip, etc.]
|
||||
|
||||
## Environment
|
||||
|
||||
- **Open WebUI Version:** [e.g., 0.1.120]
|
||||
- **Ollama (if applicable):** [e.g., 0.1.30, 0.1.32-rc1]
|
||||
- **Open WebUI Version:** [e.g., v0.3.11]
|
||||
- **Ollama (if applicable):** [e.g., v0.2.0, v0.1.32-rc1]
|
||||
|
||||
- **Operating System:** [e.g., Windows 10, macOS Big Sur, Ubuntu 20.04]
|
||||
- **Browser (if applicable):** [e.g., Chrome 100.0, Firefox 98.0]
|
||||
|
||||
## Reproduction Details
|
||||
|
||||
**Confirmation:**
|
||||
|
||||
- [ ] I have read and followed all the instructions provided in the README.md.
|
||||
- [ ] I am on the latest version of both Open WebUI and Ollama.
|
||||
- [ ] I have included the browser console logs.
|
||||
- [ ] I have included the Docker container logs.
|
||||
- [ ] I have provided the exact steps to reproduce the bug in the "Steps to Reproduce" section below.
|
||||
|
||||
## Expected Behavior:
|
||||
|
||||
[Describe what you expected to happen.]
|
||||
|
||||
## Actual Behavior:
|
||||
|
||||
[Describe what actually happened.]
|
||||
|
||||
## Description
|
||||
|
||||
**Bug Summary:**
|
||||
[Provide a brief but clear summary of the bug]
|
||||
|
||||
## Reproduction Details
|
||||
|
||||
**Steps to Reproduce:**
|
||||
[Outline the steps to reproduce the bug. Be as detailed as possible.]
|
||||
|
||||
## Logs and Screenshots
|
||||
|
||||
|
|
@ -47,13 +54,9 @@ assignees: ''
|
|||
**Docker Container Logs:**
|
||||
[Include relevant Docker container logs, if applicable]
|
||||
|
||||
**Screenshots (if applicable):**
|
||||
**Screenshots/Screen Recordings (if applicable):**
|
||||
[Attach any relevant screenshots to help illustrate the issue]
|
||||
|
||||
## Installation Method
|
||||
|
||||
[Describe the method you used to install the project, e.g., manual installation, Docker, package manager, etc.]
|
||||
|
||||
## Additional Information
|
||||
|
||||
[Include any additional details that may help in understanding and reproducing the issue. This could include specific configurations, error messages, or anything else relevant to the bug.]
|
||||
|
|
|
|||
8
.github/workflows/integration-test.yml
vendored
8
.github/workflows/integration-test.yml
vendored
|
|
@ -26,6 +26,10 @@ jobs:
|
|||
--file docker-compose.a1111-test.yaml \
|
||||
up --detach --build
|
||||
|
||||
- name: Delete Docker build cache
|
||||
run: |
|
||||
docker builder prune --all --force
|
||||
|
||||
- name: Wait for Ollama to be up
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
|
|
@ -35,10 +39,6 @@ jobs:
|
|||
done
|
||||
echo "Service is up!"
|
||||
|
||||
- name: Delete Docker build cache
|
||||
run: |
|
||||
docker builder prune --all --force
|
||||
|
||||
- name: Preload Ollama model
|
||||
run: |
|
||||
docker exec ollama ollama pull qwen:0.5b-chat-v1.5-q2_K
|
||||
|
|
|
|||
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.3.12] - 2024-08-07
|
||||
|
||||
### Added
|
||||
|
||||
- **🔄 Sidebar Infinite Scroll**: Added an infinite scroll feature in the sidebar for more efficient chat navigation, reducing load times and enhancing user experience.
|
||||
- **🚀 Enhanced Markdown Rendering**: Support for rendering all code blocks and making images clickable for preview; codespan styling is also enhanced to improve readability and user interaction.
|
||||
- **🔒 Admin Shared Chat Visibility**: Admins no longer have default visibility over shared chats when ENABLE_ADMIN_CHAT_ACCESS is set to false, tightening security and privacy settings for users.
|
||||
- **🌍 Language Updates**: Added Malay (Bahasa Malaysia) translation and updated Catalan and Traditional Chinese translations to improve accessibility for more users.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **📊 Markdown Rendering Issues**: Resolved issues with markdown rendering to ensure consistent and correct display across components.
|
||||
- **🛠️ Styling Issues**: Multiple fixes applied to styling throughout the application, improving the overall visual experience and interface consistency.
|
||||
- **🗃️ Modal Handling**: Fixed an issue where modals were not closing correctly in various model chat scenarios, enhancing usability and interface reliability.
|
||||
- **📄 Missing OpenAI Usage Information**: Resolved issues where usage statistics for OpenAI services were not being correctly displayed, ensuring users have access to crucial data for managing and monitoring their API consumption.
|
||||
- **🔧 Non-Streaming Support for Functions Plugin**: Fixed a functionality issue with the Functions plugin where non-streaming operations were not functioning as intended, restoring full capabilities for async and sync integration within the platform.
|
||||
- **🔄 Environment Variable Type Correction (COMFYUI_FLUX_FP8_CLIP)**: Corrected the data type of the 'COMFYUI_FLUX_FP8_CLIP' environment variable from string to boolean, ensuring environment settings apply correctly and enhance configuration management.
|
||||
|
||||
### Changed
|
||||
|
||||
- **🔧 Backend Dependency Updates**: Updated several backend dependencies such as boto3, pypdf, python-pptx, validators, and black, ensuring up-to-date security and performance optimizations.
|
||||
|
||||
## [0.3.11] - 2024-08-02
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from fastapi import FastAPI, Request, Response, HTTPException, Depends
|
||||
from fastapi import FastAPI, Request, HTTPException, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
|
||||
from fastapi.responses import StreamingResponse, FileResponse
|
||||
|
||||
import requests
|
||||
import aiohttp
|
||||
|
|
@ -12,16 +12,12 @@ from pydantic import BaseModel
|
|||
from starlette.background import BackgroundTask
|
||||
|
||||
from apps.webui.models.models import Models
|
||||
from apps.webui.models.users import Users
|
||||
from constants import ERROR_MESSAGES
|
||||
from utils.utils import (
|
||||
decode_token,
|
||||
get_verified_user,
|
||||
get_verified_user,
|
||||
get_admin_user,
|
||||
)
|
||||
from utils.task import prompt_template
|
||||
from utils.misc import add_or_update_system_message
|
||||
from utils.misc import apply_model_params_to_body, apply_model_system_prompt_to_body
|
||||
|
||||
from config import (
|
||||
SRC_LOG_LEVELS,
|
||||
|
|
@ -34,7 +30,7 @@ from config import (
|
|||
MODEL_FILTER_LIST,
|
||||
AppConfig,
|
||||
)
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Literal, overload
|
||||
|
||||
|
||||
import hashlib
|
||||
|
|
@ -69,8 +65,6 @@ app.state.MODELS = {}
|
|||
async def check_url(request: Request, call_next):
|
||||
if len(app.state.MODELS) == 0:
|
||||
await get_all_models()
|
||||
else:
|
||||
pass
|
||||
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
|
@ -175,7 +169,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
|
|||
res = r.json()
|
||||
if "error" in res:
|
||||
error_detail = f"External: {res['error']}"
|
||||
except:
|
||||
except Exception:
|
||||
error_detail = f"External: {e}"
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -234,35 +228,28 @@ def merge_models_lists(model_lists):
|
|||
return merged_list
|
||||
|
||||
|
||||
async def get_all_models(raw: bool = False):
|
||||
log.info("get_all_models()")
|
||||
def is_openai_api_disabled():
|
||||
api_keys = app.state.config.OPENAI_API_KEYS
|
||||
no_keys = len(api_keys) == 1 and api_keys[0] == ""
|
||||
return no_keys or not app.state.config.ENABLE_OPENAI_API
|
||||
|
||||
|
||||
async def get_all_models_raw() -> list:
|
||||
if is_openai_api_disabled():
|
||||
return []
|
||||
|
||||
if (
|
||||
len(app.state.config.OPENAI_API_KEYS) == 1
|
||||
and app.state.config.OPENAI_API_KEYS[0] == ""
|
||||
) or not app.state.config.ENABLE_OPENAI_API:
|
||||
models = {"data": []}
|
||||
else:
|
||||
# Check if API KEYS length is same than API URLS length
|
||||
if len(app.state.config.OPENAI_API_KEYS) != len(
|
||||
app.state.config.OPENAI_API_BASE_URLS
|
||||
):
|
||||
num_urls = len(app.state.config.OPENAI_API_BASE_URLS)
|
||||
num_keys = len(app.state.config.OPENAI_API_KEYS)
|
||||
|
||||
if num_keys != num_urls:
|
||||
# if there are more keys than urls, remove the extra keys
|
||||
if len(app.state.config.OPENAI_API_KEYS) > len(
|
||||
app.state.config.OPENAI_API_BASE_URLS
|
||||
):
|
||||
app.state.config.OPENAI_API_KEYS = app.state.config.OPENAI_API_KEYS[
|
||||
: len(app.state.config.OPENAI_API_BASE_URLS)
|
||||
]
|
||||
if num_keys > num_urls:
|
||||
new_keys = app.state.config.OPENAI_API_KEYS[:num_urls]
|
||||
app.state.config.OPENAI_API_KEYS = new_keys
|
||||
# if there are more urls than keys, add empty keys
|
||||
else:
|
||||
app.state.config.OPENAI_API_KEYS += [
|
||||
""
|
||||
for _ in range(
|
||||
len(app.state.config.OPENAI_API_BASE_URLS)
|
||||
- len(app.state.config.OPENAI_API_KEYS)
|
||||
)
|
||||
]
|
||||
app.state.config.OPENAI_API_KEYS += [""] * (num_urls - num_keys)
|
||||
|
||||
tasks = [
|
||||
fetch_url(f"{url}/models", app.state.config.OPENAI_API_KEYS[idx])
|
||||
|
|
@ -272,23 +259,34 @@ async def get_all_models(raw: bool = False):
|
|||
responses = await asyncio.gather(*tasks)
|
||||
log.debug(f"get_all_models:responses() {responses}")
|
||||
|
||||
return responses
|
||||
|
||||
|
||||
@overload
|
||||
async def get_all_models(raw: Literal[True]) -> list: ...
|
||||
|
||||
|
||||
@overload
|
||||
async def get_all_models(raw: Literal[False] = False) -> dict[str, list]: ...
|
||||
|
||||
|
||||
async def get_all_models(raw=False) -> dict[str, list] | list:
|
||||
log.info("get_all_models()")
|
||||
if is_openai_api_disabled():
|
||||
return [] if raw else {"data": []}
|
||||
|
||||
responses = await get_all_models_raw()
|
||||
if raw:
|
||||
return responses
|
||||
|
||||
models = {
|
||||
"data": merge_models_lists(
|
||||
list(
|
||||
map(
|
||||
lambda response: (
|
||||
response["data"]
|
||||
if (response and "data" in response)
|
||||
else (response if isinstance(response, list) else None)
|
||||
),
|
||||
responses,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
def extract_data(response):
|
||||
if response and "data" in response:
|
||||
return response["data"]
|
||||
if isinstance(response, list):
|
||||
return response
|
||||
return None
|
||||
|
||||
models = {"data": merge_models_lists(map(extract_data, responses))}
|
||||
|
||||
log.debug(f"models: {models}")
|
||||
app.state.MODELS = {model["id"]: model for model in models["data"]}
|
||||
|
|
@ -299,7 +297,7 @@ async def get_all_models(raw: bool = False):
|
|||
@app.get("/models")
|
||||
@app.get("/models/{url_idx}")
|
||||
async def get_models(url_idx: Optional[int] = None, user=Depends(get_verified_user)):
|
||||
if url_idx == None:
|
||||
if url_idx is None:
|
||||
models = await get_all_models()
|
||||
if app.state.config.ENABLE_MODEL_FILTER:
|
||||
if user.role == "user":
|
||||
|
|
@ -340,7 +338,7 @@ async def get_models(url_idx: Optional[int] = None, user=Depends(get_verified_us
|
|||
res = r.json()
|
||||
if "error" in res:
|
||||
error_detail = f"External: {res['error']}"
|
||||
except:
|
||||
except Exception:
|
||||
error_detail = f"External: {e}"
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -358,8 +356,7 @@ async def generate_chat_completion(
|
|||
):
|
||||
idx = 0
|
||||
payload = {**form_data}
|
||||
if "metadata" in payload:
|
||||
del payload["metadata"]
|
||||
payload.pop("metadata")
|
||||
|
||||
model_id = form_data.get("model")
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
|
|
@ -368,70 +365,9 @@ async def generate_chat_completion(
|
|||
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:
|
||||
if (
|
||||
model_info.params.get("temperature", None) is not None
|
||||
and payload.get("temperature") is None
|
||||
):
|
||||
payload["temperature"] = float(model_info.params.get("temperature"))
|
||||
|
||||
if model_info.params.get("top_p", None) and payload.get("top_p") is None:
|
||||
payload["top_p"] = int(model_info.params.get("top_p", None))
|
||||
|
||||
if (
|
||||
model_info.params.get("max_tokens", None)
|
||||
and payload.get("max_tokens") is None
|
||||
):
|
||||
payload["max_tokens"] = int(model_info.params.get("max_tokens", None))
|
||||
|
||||
if (
|
||||
model_info.params.get("frequency_penalty", None)
|
||||
and payload.get("frequency_penalty") is None
|
||||
):
|
||||
payload["frequency_penalty"] = int(
|
||||
model_info.params.get("frequency_penalty", None)
|
||||
)
|
||||
|
||||
if (
|
||||
model_info.params.get("seed", None) is not None
|
||||
and payload.get("seed") is None
|
||||
):
|
||||
payload["seed"] = model_info.params.get("seed", None)
|
||||
|
||||
if model_info.params.get("stop", None) and payload.get("stop") is 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
|
||||
)
|
||||
|
||||
system = model_info.params.get("system", None)
|
||||
if system:
|
||||
system = prompt_template(
|
||||
system,
|
||||
**(
|
||||
{
|
||||
"user_name": user.name,
|
||||
"user_location": (
|
||||
user.info.get("location") if user.info else None
|
||||
),
|
||||
}
|
||||
if user
|
||||
else {}
|
||||
),
|
||||
)
|
||||
if payload.get("messages"):
|
||||
payload["messages"] = add_or_update_system_message(
|
||||
system, payload["messages"]
|
||||
)
|
||||
|
||||
else:
|
||||
pass
|
||||
params = model_info.params.model_dump()
|
||||
payload = apply_model_params_to_body(params, payload)
|
||||
payload = apply_model_system_prompt_to_body(params, payload, user)
|
||||
|
||||
model = app.state.MODELS[payload.get("model")]
|
||||
idx = model["urlIdx"]
|
||||
|
|
@ -444,13 +380,6 @@ async def generate_chat_completion(
|
|||
"role": user.role,
|
||||
}
|
||||
|
||||
# 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)
|
||||
|
||||
|
|
@ -506,7 +435,7 @@ async def generate_chat_completion(
|
|||
print(res)
|
||||
if "error" in res:
|
||||
error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}"
|
||||
except:
|
||||
except Exception:
|
||||
error_detail = f"External: {e}"
|
||||
raise HTTPException(status_code=r.status if r else 500, detail=error_detail)
|
||||
finally:
|
||||
|
|
@ -569,7 +498,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
|
|||
print(res)
|
||||
if "error" in res:
|
||||
error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}"
|
||||
except:
|
||||
except Exception:
|
||||
error_detail = f"External: {e}"
|
||||
raise HTTPException(status_code=r.status if r else 500, detail=error_detail)
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -44,14 +44,17 @@ async def user_join(sid, data):
|
|||
print("user-join", sid, data)
|
||||
|
||||
auth = data["auth"] if "auth" in data else None
|
||||
if not auth or "token" not in auth:
|
||||
return
|
||||
|
||||
if auth and "token" in auth:
|
||||
data = decode_token(auth["token"])
|
||||
if data is None or "id" not in data:
|
||||
return
|
||||
|
||||
if data is not None and "id" in data:
|
||||
user = Users.get_user_by_id(data["id"])
|
||||
if not user:
|
||||
return
|
||||
|
||||
if user:
|
||||
SESSION_POOL[sid] = user.id
|
||||
if user.id in USER_POOL:
|
||||
USER_POOL[user.id].append(sid)
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ from apps.webui.utils import load_function_module_by_id
|
|||
from utils.misc import (
|
||||
openai_chat_chunk_message_template,
|
||||
openai_chat_completion_message_template,
|
||||
add_or_update_system_message,
|
||||
apply_model_params_to_body,
|
||||
apply_model_system_prompt_to_body,
|
||||
)
|
||||
from utils.task import prompt_template
|
||||
|
||||
|
||||
from config import (
|
||||
|
|
@ -269,47 +269,6 @@ def get_function_params(function_module, form_data, user, extra_params={}):
|
|||
return params
|
||||
|
||||
|
||||
# inplace function: form_data is modified
|
||||
def apply_model_params_to_body(params: dict, form_data: dict) -> dict:
|
||||
if not params:
|
||||
return form_data
|
||||
|
||||
mappings = {
|
||||
"temperature": float,
|
||||
"top_p": int,
|
||||
"max_tokens": int,
|
||||
"frequency_penalty": int,
|
||||
"seed": lambda x: x,
|
||||
"stop": lambda x: [bytes(s, "utf-8").decode("unicode_escape") for s in x],
|
||||
}
|
||||
|
||||
for key, cast_func in mappings.items():
|
||||
if (value := params.get(key)) is not None:
|
||||
form_data[key] = cast_func(value)
|
||||
|
||||
return form_data
|
||||
|
||||
|
||||
# inplace function: form_data is modified
|
||||
def apply_model_system_prompt_to_body(params: dict, form_data: dict, user) -> dict:
|
||||
system = params.get("system", None)
|
||||
if not system:
|
||||
return form_data
|
||||
|
||||
if user:
|
||||
template_params = {
|
||||
"user_name": user.name,
|
||||
"user_location": user.info.get("location") if user.info else None,
|
||||
}
|
||||
else:
|
||||
template_params = {}
|
||||
system = prompt_template(system, **template_params)
|
||||
form_data["messages"] = add_or_update_system_message(
|
||||
system, form_data.get("messages", [])
|
||||
)
|
||||
return form_data
|
||||
|
||||
|
||||
async def generate_function_chat_completion(form_data, user):
|
||||
model_id = form_data.get("model")
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ class ChatTable:
|
|||
user_id: str,
|
||||
include_archived: bool = False,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
limit: int = -1,
|
||||
) -> List[ChatTitleIdResponse]:
|
||||
with get_db() as db:
|
||||
query = db.query(Chat).filter_by(user_id=user_id)
|
||||
|
|
@ -260,9 +260,10 @@ class ChatTable:
|
|||
all_chats = (
|
||||
query.order_by(Chat.updated_at.desc())
|
||||
# limit cols
|
||||
.with_entities(
|
||||
Chat.id, Chat.title, Chat.updated_at, Chat.created_at
|
||||
).all()
|
||||
.with_entities(Chat.id, Chat.title, Chat.updated_at, Chat.created_at)
|
||||
.limit(limit)
|
||||
.offset(skip)
|
||||
.all()
|
||||
)
|
||||
# result has to be destrctured from sqlalchemy `row` and mapped to a dict since the `ChatModel`is not the returned dataclass.
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from apps.webui.models.tags import (
|
|||
|
||||
from constants import ERROR_MESSAGES
|
||||
|
||||
from config import SRC_LOG_LEVELS, ENABLE_ADMIN_EXPORT
|
||||
from config import SRC_LOG_LEVELS, ENABLE_ADMIN_EXPORT, ENABLE_ADMIN_CHAT_ACCESS
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(SRC_LOG_LEVELS["MODELS"])
|
||||
|
|
@ -43,9 +43,15 @@ router = APIRouter()
|
|||
@router.get("/", response_model=List[ChatTitleIdResponse])
|
||||
@router.get("/list", response_model=List[ChatTitleIdResponse])
|
||||
async def get_session_user_chat_list(
|
||||
user=Depends(get_verified_user), skip: int = 0, limit: int = 50
|
||||
user=Depends(get_verified_user), page: Optional[int] = None
|
||||
):
|
||||
if page is not None:
|
||||
limit = 60
|
||||
skip = (page - 1) * limit
|
||||
|
||||
return Chats.get_chat_title_id_list_by_user_id(user.id, skip=skip, limit=limit)
|
||||
else:
|
||||
return Chats.get_chat_title_id_list_by_user_id(user.id)
|
||||
|
||||
|
||||
############################
|
||||
|
|
@ -81,6 +87,11 @@ async def get_user_chat_list_by_user_id(
|
|||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
):
|
||||
if not ENABLE_ADMIN_CHAT_ACCESS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
return Chats.get_chat_list_by_user_id(
|
||||
user_id, include_archived=True, skip=skip, limit=limit
|
||||
)
|
||||
|
|
@ -181,9 +192,9 @@ async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user)):
|
|||
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if user.role == "user":
|
||||
if user.role == "user" or (user.role == "admin" and not ENABLE_ADMIN_CHAT_ACCESS):
|
||||
chat = Chats.get_chat_by_share_id(share_id)
|
||||
elif user.role == "admin":
|
||||
elif user.role == "admin" and ENABLE_ADMIN_CHAT_ACCESS:
|
||||
chat = Chats.get_chat_by_id(share_id)
|
||||
|
||||
if chat:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
from fastapi import Depends, FastAPI, HTTPException, status, Request
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Union, Optional
|
||||
from fastapi import Depends, HTTPException, status, Request
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
import json
|
||||
|
||||
from apps.webui.models.users import Users
|
||||
from apps.webui.models.tools import Tools, ToolForm, ToolModel, ToolResponse
|
||||
from apps.webui.utils import load_toolkit_module_by_id
|
||||
|
||||
|
|
@ -14,7 +10,6 @@ from utils.utils import get_admin_user, get_verified_user
|
|||
from utils.tools import get_tools_specs
|
||||
from constants import ERROR_MESSAGES
|
||||
|
||||
from importlib import util
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -69,7 +64,7 @@ async def create_new_toolkit(
|
|||
form_data.id = form_data.id.lower()
|
||||
|
||||
toolkit = Tools.get_tool_by_id(form_data.id)
|
||||
if toolkit == None:
|
||||
if toolkit is None:
|
||||
toolkit_path = os.path.join(TOOLS_DIR, f"{form_data.id}.py")
|
||||
try:
|
||||
with open(toolkit_path, "w") as tool_file:
|
||||
|
|
@ -98,7 +93,7 @@ async def create_new_toolkit(
|
|||
print(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
|
@ -170,7 +165,7 @@ async def update_toolkit_by_id(
|
|||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -210,7 +205,7 @@ async def get_toolkit_valves_by_id(id: str, user=Depends(get_admin_user)):
|
|||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
|
@ -233,7 +228,7 @@ async def get_toolkit_valves_spec_by_id(
|
|||
if id in request.app.state.TOOLS:
|
||||
toolkit_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
toolkit_module, frontmatter = load_toolkit_module_by_id(id)
|
||||
toolkit_module, _ = load_toolkit_module_by_id(id)
|
||||
request.app.state.TOOLS[id] = toolkit_module
|
||||
|
||||
if hasattr(toolkit_module, "Valves"):
|
||||
|
|
@ -261,7 +256,7 @@ async def update_toolkit_valves_by_id(
|
|||
if id in request.app.state.TOOLS:
|
||||
toolkit_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
toolkit_module, frontmatter = load_toolkit_module_by_id(id)
|
||||
toolkit_module, _ = load_toolkit_module_by_id(id)
|
||||
request.app.state.TOOLS[id] = toolkit_module
|
||||
|
||||
if hasattr(toolkit_module, "Valves"):
|
||||
|
|
@ -276,7 +271,7 @@ async def update_toolkit_valves_by_id(
|
|||
print(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
|
@ -306,7 +301,7 @@ async def get_toolkit_user_valves_by_id(id: str, user=Depends(get_verified_user)
|
|||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
|
@ -324,7 +319,7 @@ async def get_toolkit_user_valves_spec_by_id(
|
|||
if id in request.app.state.TOOLS:
|
||||
toolkit_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
toolkit_module, frontmatter = load_toolkit_module_by_id(id)
|
||||
toolkit_module, _ = load_toolkit_module_by_id(id)
|
||||
request.app.state.TOOLS[id] = toolkit_module
|
||||
|
||||
if hasattr(toolkit_module, "UserValves"):
|
||||
|
|
@ -348,7 +343,7 @@ async def update_toolkit_user_valves_by_id(
|
|||
if id in request.app.state.TOOLS:
|
||||
toolkit_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
toolkit_module, frontmatter = load_toolkit_module_by_id(id)
|
||||
toolkit_module, _ = load_toolkit_module_by_id(id)
|
||||
request.app.state.TOOLS[id] = toolkit_module
|
||||
|
||||
if hasattr(toolkit_module, "UserValves"):
|
||||
|
|
@ -365,7 +360,7 @@ async def update_toolkit_user_valves_by_id(
|
|||
print(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -824,6 +824,10 @@ WEBHOOK_URL = PersistentConfig(
|
|||
|
||||
ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true"
|
||||
|
||||
ENABLE_ADMIN_CHAT_ACCESS = (
|
||||
os.environ.get("ENABLE_ADMIN_CHAT_ACCESS", "True").lower() == "true"
|
||||
)
|
||||
|
||||
ENABLE_COMMUNITY_SHARING = PersistentConfig(
|
||||
"ENABLE_COMMUNITY_SHARING",
|
||||
"ui.enable_community_sharing",
|
||||
|
|
@ -1317,7 +1321,7 @@ COMFYUI_FLUX_WEIGHT_DTYPE = PersistentConfig(
|
|||
COMFYUI_FLUX_FP8_CLIP = PersistentConfig(
|
||||
"COMFYUI_FLUX_FP8_CLIP",
|
||||
"image_generation.comfyui.flux_fp8_clip",
|
||||
os.getenv("COMFYUI_FLUX_FP8_CLIP", ""),
|
||||
os.environ.get("COMFYUI_FLUX_FP8_CLIP", "").lower() == "true",
|
||||
)
|
||||
|
||||
IMAGES_OPENAI_API_BASE_URL = PersistentConfig(
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ from config import (
|
|||
WEBUI_SECRET_KEY,
|
||||
WEBUI_SESSION_COOKIE_SAME_SITE,
|
||||
WEBUI_SESSION_COOKIE_SECURE,
|
||||
ENABLE_ADMIN_CHAT_ACCESS,
|
||||
AppConfig,
|
||||
)
|
||||
|
||||
|
|
@ -957,7 +958,7 @@ async def get_all_models():
|
|||
|
||||
custom_models = Models.get_all_models()
|
||||
for custom_model in custom_models:
|
||||
if custom_model.base_model_id == None:
|
||||
if custom_model.base_model_id is None:
|
||||
for model in models:
|
||||
if (
|
||||
custom_model.id == model["id"]
|
||||
|
|
@ -1662,7 +1663,7 @@ async def get_pipelines_list(user=Depends(get_admin_user)):
|
|||
urlIdxs = [
|
||||
idx
|
||||
for idx, response in enumerate(responses)
|
||||
if response != None and "pipelines" in response
|
||||
if response is not None and "pipelines" in response
|
||||
]
|
||||
|
||||
return {
|
||||
|
|
@ -1723,7 +1724,7 @@ async def upload_pipeline(
|
|||
res = r.json()
|
||||
if "detail" in res:
|
||||
detail = res["detail"]
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -1769,7 +1770,7 @@ async def add_pipeline(form_data: AddPipelineForm, user=Depends(get_admin_user))
|
|||
res = r.json()
|
||||
if "detail" in res:
|
||||
detail = res["detail"]
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -1811,7 +1812,7 @@ async def delete_pipeline(form_data: DeletePipelineForm, user=Depends(get_admin_
|
|||
res = r.json()
|
||||
if "detail" in res:
|
||||
detail = res["detail"]
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -1844,7 +1845,7 @@ async def get_pipelines(urlIdx: Optional[int] = None, user=Depends(get_admin_use
|
|||
res = r.json()
|
||||
if "detail" in res:
|
||||
detail = res["detail"]
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -1859,7 +1860,6 @@ async def get_pipeline_valves(
|
|||
pipeline_id: str,
|
||||
user=Depends(get_admin_user),
|
||||
):
|
||||
models = await get_all_models()
|
||||
r = None
|
||||
try:
|
||||
url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
|
||||
|
|
@ -1898,8 +1898,6 @@ async def get_pipeline_valves_spec(
|
|||
pipeline_id: str,
|
||||
user=Depends(get_admin_user),
|
||||
):
|
||||
models = await get_all_models()
|
||||
|
||||
r = None
|
||||
try:
|
||||
url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
|
||||
|
|
@ -1922,7 +1920,7 @@ async def get_pipeline_valves_spec(
|
|||
res = r.json()
|
||||
if "detail" in res:
|
||||
detail = res["detail"]
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -1938,8 +1936,6 @@ async def update_pipeline_valves(
|
|||
form_data: dict,
|
||||
user=Depends(get_admin_user),
|
||||
):
|
||||
models = await get_all_models()
|
||||
|
||||
r = None
|
||||
try:
|
||||
url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
|
||||
|
|
@ -1967,7 +1963,7 @@ async def update_pipeline_valves(
|
|||
res = r.json()
|
||||
if "detail" in res:
|
||||
detail = res["detail"]
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -2001,6 +1997,7 @@ async def get_app_config():
|
|||
"enable_image_generation": images_app.state.config.ENABLED,
|
||||
"enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING,
|
||||
"enable_admin_export": ENABLE_ADMIN_EXPORT,
|
||||
"enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
|
||||
},
|
||||
"audio": {
|
||||
"tts": {
|
||||
|
|
@ -2068,7 +2065,7 @@ async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)):
|
|||
|
||||
|
||||
@app.get("/api/version")
|
||||
async def get_app_config():
|
||||
async def get_app_version():
|
||||
return {
|
||||
"version": VERSION,
|
||||
}
|
||||
|
|
@ -2091,7 +2088,7 @@ async def get_app_latest_release_version():
|
|||
latest_version = data["tag_name"]
|
||||
|
||||
return {"current": VERSION, "latest": latest_version[1:]}
|
||||
except aiohttp.ClientError as e:
|
||||
except aiohttp.ClientError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=ERROR_MESSAGES.RATE_LIMIT_EXCEEDED,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ bcrypt==4.1.3
|
|||
|
||||
pymongo
|
||||
redis
|
||||
boto3==1.34.110
|
||||
boto3==1.34.153
|
||||
|
||||
argon2-cffi==23.1.0
|
||||
APScheduler==3.10.4
|
||||
|
|
@ -41,9 +41,9 @@ langchain-chroma==0.1.2
|
|||
fake-useragent==1.5.1
|
||||
chromadb==0.5.4
|
||||
sentence-transformers==3.0.1
|
||||
pypdf==4.2.0
|
||||
pypdf==4.3.1
|
||||
docx2txt==0.8
|
||||
python-pptx==0.6.23
|
||||
python-pptx==1.0.0
|
||||
unstructured==0.15.0
|
||||
Markdown==3.6
|
||||
pypandoc==1.13
|
||||
|
|
@ -51,7 +51,7 @@ pandas==2.2.2
|
|||
openpyxl==3.1.5
|
||||
pyxlsb==1.0.10
|
||||
xlrd==2.0.1
|
||||
validators==0.28.1
|
||||
validators==0.33.0
|
||||
psutil
|
||||
|
||||
opencv-python-headless==4.10.0.84
|
||||
|
|
@ -65,7 +65,7 @@ faster-whisper==1.0.2
|
|||
PyJWT[crypto]==2.8.0
|
||||
authlib==1.3.1
|
||||
|
||||
black==24.4.2
|
||||
black==24.8.0
|
||||
langfuse==2.39.2
|
||||
youtube-transcript-api==0.6.2
|
||||
pytube==15.0.0
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from typing import Optional, List, Tuple
|
|||
import uuid
|
||||
import time
|
||||
|
||||
from utils.task import prompt_template
|
||||
|
||||
|
||||
def get_last_user_message_item(messages: List[dict]) -> Optional[dict]:
|
||||
for message in reversed(messages):
|
||||
|
|
@ -97,18 +99,60 @@ def openai_chat_message_template(model: str):
|
|||
}
|
||||
|
||||
|
||||
def openai_chat_chunk_message_template(model: str, message: str):
|
||||
def openai_chat_chunk_message_template(model: str, message: str) -> dict:
|
||||
template = openai_chat_message_template(model)
|
||||
template["object"] = "chat.completion.chunk"
|
||||
template["choices"][0]["delta"] = {"content": message}
|
||||
return template
|
||||
|
||||
|
||||
def openai_chat_completion_message_template(model: str, message: str):
|
||||
def openai_chat_completion_message_template(model: str, message: str) -> dict:
|
||||
template = openai_chat_message_template(model)
|
||||
template["object"] = "chat.completion"
|
||||
template["choices"][0]["message"] = {"content": message, "role": "assistant"}
|
||||
template["choices"][0]["finish_reason"] = "stop"
|
||||
return template
|
||||
|
||||
|
||||
# inplace function: form_data is modified
|
||||
def apply_model_system_prompt_to_body(params: dict, form_data: dict, user) -> dict:
|
||||
system = params.get("system", None)
|
||||
if not system:
|
||||
return form_data
|
||||
|
||||
if user:
|
||||
template_params = {
|
||||
"user_name": user.name,
|
||||
"user_location": user.info.get("location") if user.info else None,
|
||||
}
|
||||
else:
|
||||
template_params = {}
|
||||
system = prompt_template(system, **template_params)
|
||||
form_data["messages"] = add_or_update_system_message(
|
||||
system, form_data.get("messages", [])
|
||||
)
|
||||
return form_data
|
||||
|
||||
|
||||
# inplace function: form_data is modified
|
||||
def apply_model_params_to_body(params: dict, form_data: dict) -> dict:
|
||||
if not params:
|
||||
return form_data
|
||||
|
||||
mappings = {
|
||||
"temperature": float,
|
||||
"top_p": int,
|
||||
"max_tokens": int,
|
||||
"frequency_penalty": int,
|
||||
"seed": lambda x: x,
|
||||
"stop": lambda x: [bytes(s, "utf-8").decode("unicode_escape") for s in x],
|
||||
}
|
||||
|
||||
for key, cast_func in mappings.items():
|
||||
if (value := params.get(key)) is not None:
|
||||
form_data[key] = cast_func(value)
|
||||
|
||||
return form_data
|
||||
|
||||
|
||||
def get_gravatar_url(email):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from typing import Optional
|
|||
|
||||
|
||||
def prompt_template(
|
||||
template: str, user_name: str = None, user_location: str = None
|
||||
template: str, user_name: Optional[str] = None, user_location: Optional[str] = None
|
||||
) -> str:
|
||||
# Get the current date
|
||||
current_date = datetime.now()
|
||||
|
|
@ -83,7 +83,6 @@ def title_generation_template(
|
|||
def search_query_generation_template(
|
||||
template: str, prompt: str, user: Optional[dict] = None
|
||||
) -> str:
|
||||
|
||||
def replacement_function(match):
|
||||
full_match = match.group(0)
|
||||
start_length = match.group(1)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from fastapi import HTTPException, status, Depends, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from apps.webui.models.users import Users
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import Union, Optional
|
||||
from constants import ERROR_MESSAGES
|
||||
from passlib.context import CryptContext
|
||||
from datetime import datetime, timedelta
|
||||
import requests
|
||||
import jwt
|
||||
import uuid
|
||||
import logging
|
||||
|
|
@ -54,7 +51,7 @@ def decode_token(token: str) -> Optional[dict]:
|
|||
try:
|
||||
decoded = jwt.decode(token, SESSION_SECRET, algorithms=[ALGORITHM])
|
||||
return decoded
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -71,7 +68,7 @@ def get_http_authorization_cred(auth_header: str):
|
|||
try:
|
||||
scheme, credentials = auth_header.split(" ")
|
||||
return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
|
||||
except:
|
||||
except Exception:
|
||||
raise ValueError(ERROR_MESSAGES.INVALID_TOKEN)
|
||||
|
||||
|
||||
|
|
@ -96,7 +93,7 @@ def get_current_user(
|
|||
|
||||
# auth by jwt token
|
||||
data = decode_token(token)
|
||||
if data != None and "id" in data:
|
||||
if data is not None and "id" in data:
|
||||
user = Users.get_user_by_id(data["id"])
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -11,10 +11,26 @@ Our primary goal is to ensure the protection and confidentiality of sensitive da
|
|||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security issue within our system, please notify us immediately via a pull request or contact us on discord.
|
||||
We appreciate the community's interest in identifying potential vulnerabilities. However, effective immediately, we will **not** accept low-effort vulnerability reports. To ensure that submissions are constructive and actionable, please adhere to the following guidelines:
|
||||
|
||||
1. **No Vague Reports**: Submissions such as "I found a vulnerability" without any details will be treated as spam and will not be accepted.
|
||||
|
||||
2. **In-Depth Understanding Required**: Reports must reflect a clear understanding of the codebase and provide specific details about the vulnerability, including the affected components and potential impacts.
|
||||
|
||||
3. **Proof of Concept (PoC) is Mandatory**: Each submission must include a well-documented proof of concept (PoC) that demonstrates the vulnerability. If confidentiality is a concern, reporters are encouraged to create a private fork of the repository and share access with the maintainers. Reports lacking valid evidence will be disregarded.
|
||||
|
||||
4. **Required Patch Submission**: Along with the PoC, reporters must provide a patch or actionable steps to remediate the identified vulnerability. This helps us evaluate and implement fixes rapidly.
|
||||
|
||||
5. **Streamlined Merging Process**: When vulnerability reports meet the above criteria, we can consider them for immediate merging, similar to regular pull requests. Well-structured and thorough submissions will expedite the process of enhancing our security.
|
||||
|
||||
Submissions that do not meet these criteria will be closed, and repeat offenders may face a ban from future submissions. We aim to create a respectful and constructive reporting environment, where high-quality submissions foster better security for everyone.
|
||||
|
||||
## Product Security
|
||||
|
||||
We regularly audit our internal processes and system's architecture for vulnerabilities using a combination of automated and manual testing techniques.
|
||||
We regularly audit our internal processes and system architecture for vulnerabilities using a combination of automated and manual testing techniques. We are also planning to implement SAST and SCA scans in our project soon.
|
||||
|
||||
We are planning on implementing SAST and SCA scans in our project soon.
|
||||
For immediate concerns or detailed reports that meet our guidelines, please create an issue in our [issue tracker](/open-webui/open-webui/issues) or contact us on [Discord](https://discord.gg/5rJgQTnV4s).
|
||||
|
||||
---
|
||||
|
||||
_Last updated on **2024-08-06**._
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.3.11",
|
||||
"version": "0.3.12",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-webui",
|
||||
"version": "0.3.11",
|
||||
"version": "0.3.12",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-python": "^6.1.6",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.3.11",
|
||||
"version": "0.3.12",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run pyodide:fetch && vite dev --host",
|
||||
|
|
|
|||
|
|
@ -31,14 +31,14 @@ dependencies = [
|
|||
|
||||
"pymongo",
|
||||
"redis",
|
||||
"boto3==1.34.110",
|
||||
"boto3==1.34.153",
|
||||
|
||||
"argon2-cffi==23.1.0",
|
||||
"APScheduler==3.10.4",
|
||||
|
||||
"openai",
|
||||
"anthropic",
|
||||
"google-generativeai==0.5.4",
|
||||
"google-generativeai==0.7.2",
|
||||
"tiktoken",
|
||||
|
||||
"langchain==0.2.11",
|
||||
|
|
@ -48,9 +48,9 @@ dependencies = [
|
|||
"fake-useragent==1.5.1",
|
||||
"chromadb==0.5.4",
|
||||
"sentence-transformers==3.0.1",
|
||||
"pypdf==4.2.0",
|
||||
"pypdf==4.3.1",
|
||||
"docx2txt==0.8",
|
||||
"python-pptx==0.6.23",
|
||||
"python-pptx==1.0.0",
|
||||
"unstructured==0.15.0",
|
||||
"Markdown==3.6",
|
||||
"pypandoc==1.13",
|
||||
|
|
@ -58,7 +58,7 @@ dependencies = [
|
|||
"openpyxl==3.1.5",
|
||||
"pyxlsb==1.0.10",
|
||||
"xlrd==2.0.1",
|
||||
"validators==0.28.1",
|
||||
"validators==0.33.0",
|
||||
"psutil",
|
||||
|
||||
"opencv-python-headless==4.10.0.84",
|
||||
|
|
@ -72,7 +72,7 @@ dependencies = [
|
|||
"PyJWT[crypto]==2.8.0",
|
||||
"authlib==1.3.1",
|
||||
|
||||
"black==24.4.2",
|
||||
"black==24.8.0",
|
||||
"langfuse==2.39.2",
|
||||
"youtube-transcript-api==0.6.2",
|
||||
"pytube==15.0.0",
|
||||
|
|
|
|||
|
|
@ -57,13 +57,13 @@ beautifulsoup4==4.12.3
|
|||
# via unstructured
|
||||
bidict==0.23.1
|
||||
# via python-socketio
|
||||
black==24.4.2
|
||||
black==24.8.0
|
||||
# via open-webui
|
||||
blinker==1.8.2
|
||||
# via flask
|
||||
boto3==1.34.110
|
||||
boto3==1.34.153
|
||||
# via open-webui
|
||||
botocore==1.34.110
|
||||
botocore==1.34.155
|
||||
# via boto3
|
||||
# via s3transfer
|
||||
build==1.2.1
|
||||
|
|
@ -179,7 +179,7 @@ frozenlist==1.4.1
|
|||
fsspec==2024.3.1
|
||||
# via huggingface-hub
|
||||
# via torch
|
||||
google-ai-generativelanguage==0.6.4
|
||||
google-ai-generativelanguage==0.6.6
|
||||
# via google-generativeai
|
||||
google-api-core==2.19.0
|
||||
# via google-ai-generativelanguage
|
||||
|
|
@ -196,7 +196,7 @@ google-auth==2.29.0
|
|||
# via kubernetes
|
||||
google-auth-httplib2==0.2.0
|
||||
# via google-api-python-client
|
||||
google-generativeai==0.5.4
|
||||
google-generativeai==0.7.2
|
||||
# via open-webui
|
||||
googleapis-common-protos==1.63.0
|
||||
# via google-api-core
|
||||
|
|
@ -502,7 +502,7 @@ pypandoc==1.13
|
|||
pyparsing==2.4.7
|
||||
# via httplib2
|
||||
# via oletools
|
||||
pypdf==4.2.0
|
||||
pypdf==4.3.1
|
||||
# via open-webui
|
||||
# via unstructured-client
|
||||
pypika==0.48.9
|
||||
|
|
@ -533,7 +533,7 @@ python-magic==0.4.27
|
|||
python-multipart==0.0.9
|
||||
# via fastapi
|
||||
# via open-webui
|
||||
python-pptx==0.6.23
|
||||
python-pptx==1.0.0
|
||||
# via open-webui
|
||||
python-socketio==5.11.3
|
||||
# via open-webui
|
||||
|
|
@ -684,6 +684,7 @@ typing-extensions==4.11.0
|
|||
# via opentelemetry-sdk
|
||||
# via pydantic
|
||||
# via pydantic-core
|
||||
# via python-pptx
|
||||
# via sqlalchemy
|
||||
# via torch
|
||||
# via typer
|
||||
|
|
@ -718,7 +719,7 @@ uvicorn==0.22.0
|
|||
# via open-webui
|
||||
uvloop==0.19.0
|
||||
# via uvicorn
|
||||
validators==0.28.1
|
||||
validators==0.33.0
|
||||
# via open-webui
|
||||
watchfiles==0.21.0
|
||||
# via uvicorn
|
||||
|
|
|
|||
|
|
@ -57,13 +57,13 @@ beautifulsoup4==4.12.3
|
|||
# via unstructured
|
||||
bidict==0.23.1
|
||||
# via python-socketio
|
||||
black==24.4.2
|
||||
black==24.8.0
|
||||
# via open-webui
|
||||
blinker==1.8.2
|
||||
# via flask
|
||||
boto3==1.34.110
|
||||
boto3==1.34.153
|
||||
# via open-webui
|
||||
botocore==1.34.110
|
||||
botocore==1.34.155
|
||||
# via boto3
|
||||
# via s3transfer
|
||||
build==1.2.1
|
||||
|
|
@ -179,7 +179,7 @@ frozenlist==1.4.1
|
|||
fsspec==2024.3.1
|
||||
# via huggingface-hub
|
||||
# via torch
|
||||
google-ai-generativelanguage==0.6.4
|
||||
google-ai-generativelanguage==0.6.6
|
||||
# via google-generativeai
|
||||
google-api-core==2.19.0
|
||||
# via google-ai-generativelanguage
|
||||
|
|
@ -196,7 +196,7 @@ google-auth==2.29.0
|
|||
# via kubernetes
|
||||
google-auth-httplib2==0.2.0
|
||||
# via google-api-python-client
|
||||
google-generativeai==0.5.4
|
||||
google-generativeai==0.7.2
|
||||
# via open-webui
|
||||
googleapis-common-protos==1.63.0
|
||||
# via google-api-core
|
||||
|
|
@ -502,7 +502,7 @@ pypandoc==1.13
|
|||
pyparsing==2.4.7
|
||||
# via httplib2
|
||||
# via oletools
|
||||
pypdf==4.2.0
|
||||
pypdf==4.3.1
|
||||
# via open-webui
|
||||
# via unstructured-client
|
||||
pypika==0.48.9
|
||||
|
|
@ -533,7 +533,7 @@ python-magic==0.4.27
|
|||
python-multipart==0.0.9
|
||||
# via fastapi
|
||||
# via open-webui
|
||||
python-pptx==0.6.23
|
||||
python-pptx==1.0.0
|
||||
# via open-webui
|
||||
python-socketio==5.11.3
|
||||
# via open-webui
|
||||
|
|
@ -684,6 +684,7 @@ typing-extensions==4.11.0
|
|||
# via opentelemetry-sdk
|
||||
# via pydantic
|
||||
# via pydantic-core
|
||||
# via python-pptx
|
||||
# via sqlalchemy
|
||||
# via torch
|
||||
# via typer
|
||||
|
|
@ -718,7 +719,7 @@ uvicorn==0.22.0
|
|||
# via open-webui
|
||||
uvloop==0.19.0
|
||||
# via uvicorn
|
||||
validators==0.28.1
|
||||
validators==0.33.0
|
||||
# via open-webui
|
||||
watchfiles==0.21.0
|
||||
# via uvicorn
|
||||
|
|
|
|||
|
|
@ -158,3 +158,12 @@ input[type='number'] {
|
|||
.password {
|
||||
-webkit-text-security: disc;
|
||||
}
|
||||
|
||||
.codespan {
|
||||
color: #eb5757;
|
||||
border-width: 0px;
|
||||
padding: 3px 8px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 600;
|
||||
@apply rounded-md dark:bg-gray-800 bg-gray-100 mx-0.5;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,10 +32,15 @@ export const createNewChat = async (token: string, chat: object) => {
|
|||
return res;
|
||||
};
|
||||
|
||||
export const getChatList = async (token: string = '') => {
|
||||
export const getChatList = async (token: string = '', page: number | null = null) => {
|
||||
let error = null;
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/`, {
|
||||
if (page !== null) {
|
||||
searchParams.append('page', `${page}`);
|
||||
}
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/?${searchParams.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@
|
|||
user,
|
||||
socket,
|
||||
showCallOverlay,
|
||||
tools
|
||||
tools,
|
||||
currentChatPage
|
||||
} from '$lib/stores';
|
||||
import {
|
||||
convertMessagesToHistory,
|
||||
|
|
@ -421,7 +422,9 @@
|
|||
params: params,
|
||||
files: chatFiles
|
||||
});
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -467,7 +470,9 @@
|
|||
params: params,
|
||||
files: chatFiles
|
||||
});
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -627,7 +632,9 @@
|
|||
tags: [],
|
||||
timestamp: Date.now()
|
||||
});
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
await chatId.set(chat.id);
|
||||
} else {
|
||||
await chatId.set('local');
|
||||
|
|
@ -703,7 +710,9 @@
|
|||
})
|
||||
);
|
||||
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
|
||||
return _responses;
|
||||
};
|
||||
|
||||
|
|
@ -803,8 +812,8 @@
|
|||
...(params ?? $settings.params ?? {}),
|
||||
stop:
|
||||
params?.stop ?? $settings?.params?.stop ?? undefined
|
||||
? (params?.stop ?? $settings.params.stop).map((str) =>
|
||||
decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
|
||||
? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
|
||||
(str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
|
||||
)
|
||||
: undefined,
|
||||
num_predict: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined,
|
||||
|
|
@ -949,7 +958,9 @@
|
|||
params: params,
|
||||
files: chatFiles
|
||||
});
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1103,8 +1114,8 @@
|
|||
seed: params?.seed ?? $settings?.params?.seed ?? undefined,
|
||||
stop:
|
||||
params?.stop ?? $settings?.params?.stop ?? undefined
|
||||
? (params?.stop ?? $settings.params.stop).map((str) =>
|
||||
decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
|
||||
? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
|
||||
(str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
|
||||
)
|
||||
: undefined,
|
||||
temperature: params?.temperature ?? $settings?.params?.temperature ?? undefined,
|
||||
|
|
@ -1128,7 +1139,6 @@
|
|||
|
||||
if (res && res.ok && res.body) {
|
||||
const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
|
||||
let lastUsage = null;
|
||||
|
||||
for await (const update of textStream) {
|
||||
const { value, done, citations, error, usage } = update;
|
||||
|
|
@ -1154,7 +1164,7 @@
|
|||
}
|
||||
|
||||
if (usage) {
|
||||
lastUsage = usage;
|
||||
responseMessage.info = { ...usage, openai: true };
|
||||
}
|
||||
|
||||
if (citations) {
|
||||
|
|
@ -1208,10 +1218,6 @@
|
|||
document.getElementById(`speak-button-${responseMessage.id}`)?.click();
|
||||
}
|
||||
|
||||
if (lastUsage) {
|
||||
responseMessage.info = { ...lastUsage, openai: true };
|
||||
}
|
||||
|
||||
if ($chatId == _chatId) {
|
||||
if ($settings.saveChatHistory ?? true) {
|
||||
chat = await updateChatById(localStorage.token, _chatId, {
|
||||
|
|
@ -1221,7 +1227,9 @@
|
|||
params: params,
|
||||
files: chatFiles
|
||||
});
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1386,7 +1394,9 @@
|
|||
|
||||
if ($settings.saveChatHistory ?? true) {
|
||||
chat = await updateChatById(localStorage.token, _chatId, { title: _title });
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -384,7 +384,7 @@
|
|||
|
||||
{#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"
|
||||
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 z-50"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-sm dark:text-gray-500">
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -159,7 +159,12 @@
|
|||
}}
|
||||
on:focus={() => {}}
|
||||
>
|
||||
<div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
|
||||
<div class="flex font-medium text-black dark:text-gray-100 line-clamp-1">
|
||||
<img
|
||||
src={model?.info?.meta?.profile_image_url ?? '/static/favicon.png'}
|
||||
alt={model?.name ?? model.id}
|
||||
class="rounded-full size-6 items-center mr-2"
|
||||
/>
|
||||
{model.name}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { chats, config, settings, user as _user, mobile } from '$lib/stores';
|
||||
import { chats, config, settings, user as _user, mobile, currentChatPage } from '$lib/stores';
|
||||
import { tick, getContext, onMount } from 'svelte';
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
|
@ -90,7 +90,8 @@
|
|||
history: history
|
||||
});
|
||||
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
};
|
||||
|
||||
const confirmEditResponseMessage = async (messageId, content) => {
|
||||
|
|
@ -146,12 +147,14 @@
|
|||
|
||||
await tick();
|
||||
|
||||
if ($settings?.scrollOnBranchChange ?? true) {
|
||||
const element = document.getElementById('messages-container');
|
||||
autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
const showNextMessage = async (message) => {
|
||||
|
|
@ -195,12 +198,14 @@
|
|||
|
||||
await tick();
|
||||
|
||||
if ($settings?.scrollOnBranchChange ?? true) {
|
||||
const element = document.getElementById('messages-container');
|
||||
autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteMessageHandler = async (messageId) => {
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ __builtins__.input = input`);
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="mb-4" dir="ltr">
|
||||
<div class="my-2" 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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -118,19 +118,18 @@
|
|||
currentMessageId = message.id;
|
||||
let messageId = message.id;
|
||||
console.log(messageId);
|
||||
|
||||
//
|
||||
let messageChildrenIds = history.messages[messageId].childrenIds;
|
||||
while (messageChildrenIds.length !== 0) {
|
||||
messageId = messageChildrenIds.at(-1);
|
||||
messageChildrenIds = history.messages[messageId].childrenIds;
|
||||
}
|
||||
|
||||
history.currentId = messageId;
|
||||
dispatch('change');
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#key history.currentId}
|
||||
<ResponseMessage
|
||||
message={groupedMessages[model].messages[groupedMessagesIdx[model]]}
|
||||
siblings={groupedMessages[model].messages.map((m) => m.id)}
|
||||
|
|
@ -159,6 +158,7 @@
|
|||
});
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
|
|
|||
30
src/lib/components/chat/Messages/HTMLRenderer.svelte
Normal file
30
src/lib/components/chat/Messages/HTMLRenderer.svelte
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import Image from '$lib/components/common/Image.svelte';
|
||||
import CodeBlock from './CodeBlock.svelte';
|
||||
|
||||
/* The html content of the tag */
|
||||
export let html; //: string;
|
||||
let parsedHTML = [html];
|
||||
|
||||
export let images;
|
||||
export let codes;
|
||||
|
||||
// all images are in {{IMAGE_0}}, {{IMAGE_1}}.... format
|
||||
// all codes are in {{CODE_0}}, {{CODE_1}}.... format
|
||||
|
||||
const rules = [];
|
||||
rules.forEach((rule) => {
|
||||
parsedHTML = parsedHTML.map((substr) => substr.split(rule.regex)).flat();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#each parsedHTML as part}
|
||||
{@const match = rules.find((rule) => rule.regex.test(part))}
|
||||
{#if match}
|
||||
<svelte:component this={match.component} {...match.props}>
|
||||
{@html part}
|
||||
</svelte:component>
|
||||
{:else}
|
||||
{@html part}
|
||||
{/if}
|
||||
{/each}
|
||||
38
src/lib/components/chat/Messages/MarkdownInlineTokens.svelte
Normal file
38
src/lib/components/chat/Messages/MarkdownInlineTokens.svelte
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
import type { Token } from 'marked';
|
||||
import { unescapeHtml } from '$lib/utils';
|
||||
import Image from '$lib/components/common/Image.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let tokens: Token[];
|
||||
</script>
|
||||
|
||||
{#each tokens as token}
|
||||
{#if token.type === 'escape'}
|
||||
{unescapeHtml(token.text)}
|
||||
{:else if token.type === 'html'}
|
||||
{@html token.text}
|
||||
{:else if token.type === 'link'}
|
||||
<a href={token.href} target="_blank" rel="nofollow" title={token.title}>{token.text}</a>
|
||||
{:else if token.type === 'image'}
|
||||
<Image src={token.href} alt={token.text} />
|
||||
{:else if token.type === 'strong'}
|
||||
<strong>
|
||||
<svelte:self id={`${id}-strong`} tokens={token.tokens} />
|
||||
</strong>
|
||||
{:else if token.type === 'em'}
|
||||
<em>
|
||||
<svelte:self id={`${id}-em`} tokens={token.tokens} />
|
||||
</em>
|
||||
{:else if token.type === 'codespan'}
|
||||
<code class="codespan">{unescapeHtml(token.text.replaceAll('&', '&'))}</code>
|
||||
{:else if token.type === 'br'}
|
||||
<br />
|
||||
{:else if token.type === 'del'}
|
||||
<del>
|
||||
<svelte:self id={`${id}-del`} tokens={token.tokens} />
|
||||
</del>
|
||||
{:else if token.type === 'text'}
|
||||
{unescapeHtml(token.text)}
|
||||
{/if}
|
||||
{/each}
|
||||
137
src/lib/components/chat/Messages/MarkdownTokens.svelte
Normal file
137
src/lib/components/chat/Messages/MarkdownTokens.svelte
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<script lang="ts">
|
||||
import { marked } from 'marked';
|
||||
import type { Token } from 'marked';
|
||||
import { revertSanitizedResponseContent, unescapeHtml } from '$lib/utils';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import Image from '$lib/components/common/Image.svelte';
|
||||
import CodeBlock from '$lib/components/chat/Messages/CodeBlock.svelte';
|
||||
|
||||
import MarkdownInlineTokens from '$lib/components/chat/Messages/MarkdownInlineTokens.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let tokens: Token[];
|
||||
export let top = true;
|
||||
|
||||
let containerElement;
|
||||
|
||||
const headerComponent = (depth: number) => {
|
||||
return 'h' + depth;
|
||||
};
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
// For code blocks with simple backticks
|
||||
renderer.codespan = (code) => {
|
||||
return `<code class="codespan">${code.replaceAll('&', '&')}</code>`;
|
||||
};
|
||||
|
||||
let codes = [];
|
||||
renderer.code = (code, lang) => {
|
||||
codes.push({
|
||||
code: code,
|
||||
lang: lang
|
||||
});
|
||||
codes = codes;
|
||||
const codeId = `${id}-${codes.length}`;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const codeElement = document.getElementById(`code-${codeId}`);
|
||||
if (codeElement) {
|
||||
clearInterval(interval);
|
||||
// If the code is already loaded, don't load it again
|
||||
if (codeElement.innerHTML) {
|
||||
return;
|
||||
}
|
||||
|
||||
new CodeBlock({
|
||||
target: codeElement,
|
||||
props: {
|
||||
id: `${id}-${codes.length}`,
|
||||
lang: lang,
|
||||
code: revertSanitizedResponseContent(code)
|
||||
},
|
||||
hydrate: true,
|
||||
$$inline: true
|
||||
});
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return `<div id="code-${id}-${codes.length}"></div>`;
|
||||
};
|
||||
|
||||
let images = [];
|
||||
renderer.image = (href, title, text) => {
|
||||
images.push({
|
||||
href: href,
|
||||
title: title,
|
||||
text: text
|
||||
});
|
||||
images = images;
|
||||
|
||||
const imageId = `${id}-${images.length}`;
|
||||
const interval = setInterval(() => {
|
||||
const imageElement = document.getElementById(`image-${imageId}`);
|
||||
if (imageElement) {
|
||||
clearInterval(interval);
|
||||
|
||||
// If the image is already loaded, don't load it again
|
||||
if (imageElement.innerHTML) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('image', href, text);
|
||||
new Image({
|
||||
target: imageElement,
|
||||
props: {
|
||||
src: href,
|
||||
alt: text
|
||||
},
|
||||
$$inline: true
|
||||
});
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return `<div id="image-${id}-${images.length}"></div>`;
|
||||
};
|
||||
|
||||
// Open all links in a new tab/window (from https://github.com/markedjs/marked/issues/655#issuecomment-383226346)
|
||||
const origLinkRenderer = renderer.link;
|
||||
renderer.link = (href, title, text) => {
|
||||
const html = origLinkRenderer.call(renderer, href, title, text);
|
||||
return html.replace(/^<a /, '<a target="_blank" rel="nofollow" ');
|
||||
};
|
||||
|
||||
const { extensions, ...defaults } = marked.getDefaults() as marked.MarkedOptions & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
extensions: any;
|
||||
};
|
||||
|
||||
$: if (tokens) {
|
||||
images = [];
|
||||
codes = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={containerElement} class="flex flex-col">
|
||||
{#each tokens as token, tokenIdx (`${id}-${tokenIdx}`)}
|
||||
{#if token.type === 'code'}
|
||||
{#if token.lang === 'mermaid'}
|
||||
<pre class="mermaid">{revertSanitizedResponseContent(token.text)}</pre>
|
||||
{:else}
|
||||
<CodeBlock
|
||||
id={`${id}-${tokenIdx}`}
|
||||
lang={token?.lang ?? ''}
|
||||
code={revertSanitizedResponseContent(token?.text ?? '')}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
{@html marked.parse(token.raw, {
|
||||
...defaults,
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
renderer
|
||||
})}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import WebSearchResults from './ResponseMessage/WebSearchResults.svelte';
|
||||
import Sparkles from '$lib/components/icons/Sparkles.svelte';
|
||||
import MarkdownTokens from './MarkdownTokens.svelte';
|
||||
|
||||
export let message;
|
||||
export let siblings;
|
||||
|
|
@ -55,7 +56,6 @@
|
|||
export let copyToClipboard: Function;
|
||||
export let continueGeneration: Function;
|
||||
export let regenerateResponse: Function;
|
||||
export let chatActionHandler: Function;
|
||||
|
||||
let model = null;
|
||||
$: model = $models.find((m) => m.id === message.model);
|
||||
|
|
@ -77,28 +77,16 @@
|
|||
|
||||
let selectedCitation = null;
|
||||
|
||||
$: tokens = marked.lexer(
|
||||
let tokens;
|
||||
|
||||
$: (async () => {
|
||||
if (message?.content) {
|
||||
tokens = marked.lexer(
|
||||
replaceTokens(sanitizeResponseContent(message?.content), model?.name, $user?.name)
|
||||
);
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
// For code blocks with simple backticks
|
||||
renderer.codespan = (code) => {
|
||||
return `<code>${code.replaceAll('&', '&')}</code>`;
|
||||
};
|
||||
|
||||
// Open all links in a new tab/window (from https://github.com/markedjs/marked/issues/655#issuecomment-383226346)
|
||||
const origLinkRenderer = renderer.link;
|
||||
renderer.link = (href, title, text) => {
|
||||
const html = origLinkRenderer.call(renderer, href, title, text);
|
||||
return html.replace(/^<a /, '<a target="_blank" rel="nofollow" ');
|
||||
};
|
||||
|
||||
const { extensions, ...defaults } = marked.getDefaults() as marked.MarkedOptions & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
extensions: any;
|
||||
};
|
||||
// console.log(message?.content, tokens);
|
||||
}
|
||||
})();
|
||||
|
||||
$: if (message) {
|
||||
renderStyling();
|
||||
|
|
@ -418,6 +406,7 @@
|
|||
{/if}
|
||||
</Name>
|
||||
|
||||
<div>
|
||||
{#if (message?.files ?? []).filter((f) => f.type === 'image').length > 0}
|
||||
<div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap">
|
||||
{#each message.files as file}
|
||||
|
|
@ -431,7 +420,7 @@
|
|||
{/if}
|
||||
|
||||
<div
|
||||
class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-headings:-mb-4 prose-p:m-0 prose-p:-mb-6 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-8 prose-ol:p-0 prose-li:-mb-4 whitespace-pre-line"
|
||||
class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-2 prose-ol:-my-2 prose-li:-my-3 whitespace-pre-line"
|
||||
>
|
||||
<div>
|
||||
{#if (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length > 0}
|
||||
|
|
@ -511,32 +500,15 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
<div class="w-full flex flex-col">
|
||||
{#if message.content === '' && !message.error}
|
||||
<Skeleton />
|
||||
{:else if message.content && message.error !== true}
|
||||
<!-- always show message contents even if there's an error -->
|
||||
<!-- unless message.error === true which is legacy error handling, where the error message is stored in message.content -->
|
||||
{#each tokens as token, tokenIdx}
|
||||
{#if token.type === 'code'}
|
||||
{#if token.lang === 'mermaid'}
|
||||
<pre class="mermaid">{revertSanitizedResponseContent(token.text)}</pre>
|
||||
{:else}
|
||||
<CodeBlock
|
||||
id={`${message.id}-${tokenIdx}`}
|
||||
lang={token?.lang ?? ''}
|
||||
code={revertSanitizedResponseContent(token?.text ?? '')}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
{@html marked.parse(token.raw, {
|
||||
...defaults,
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
renderer
|
||||
})}
|
||||
{/if}
|
||||
{/each}
|
||||
{#key message.id}
|
||||
<MarkdownTokens id={message.id} {tokens} />
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
{#if message.error}
|
||||
|
|
@ -611,7 +583,12 @@
|
|||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !edit}
|
||||
{#if message.done || siblings.length > 1}
|
||||
<div
|
||||
class=" flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500"
|
||||
|
|
@ -765,12 +742,7 @@
|
|||
cx="12"
|
||||
cy="12"
|
||||
r="3"
|
||||
/><circle
|
||||
class="spinner_S1WN spinner_JApP"
|
||||
cx="20"
|
||||
cy="12"
|
||||
r="3"
|
||||
/></svg
|
||||
/><circle class="spinner_S1WN spinner_JApP" cx="20" cy="12" r="3" /></svg
|
||||
>
|
||||
{:else if speaking}
|
||||
<svg
|
||||
|
|
@ -846,12 +818,7 @@
|
|||
cx="12"
|
||||
cy="12"
|
||||
r="3"
|
||||
/><circle
|
||||
class="spinner_S1WN spinner_JApP"
|
||||
cx="20"
|
||||
cy="12"
|
||||
r="3"
|
||||
/></svg
|
||||
/><circle class="spinner_S1WN spinner_JApP" cx="20" cy="12" r="3" /></svg
|
||||
>
|
||||
{:else}
|
||||
<svg
|
||||
|
|
@ -1075,12 +1042,10 @@
|
|||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import { chats, user, settings } from '$lib/stores';
|
||||
import { chats, user, settings, scrollPaginationEnabled, currentChatPage } from '$lib/stores';
|
||||
|
||||
import {
|
||||
archiveAllChats,
|
||||
|
|
@ -62,7 +62,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
scrollPaginationEnabled.set(true);
|
||||
};
|
||||
|
||||
const exportChats = async () => {
|
||||
|
|
@ -77,7 +79,10 @@
|
|||
await archiveAllChats(localStorage.token).catch((error) => {
|
||||
toast.error(error);
|
||||
});
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
scrollPaginationEnabled.set(true);
|
||||
};
|
||||
|
||||
const deleteAllChatsHandler = async () => {
|
||||
|
|
@ -85,7 +90,10 @@
|
|||
await deleteAllChats(localStorage.token).catch((error) => {
|
||||
toast.error(error);
|
||||
});
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
scrollPaginationEnabled.set(true);
|
||||
};
|
||||
|
||||
const toggleSaveChatHistory = async () => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
let responseAutoCopy = false;
|
||||
let widescreenMode = false;
|
||||
let splitLargeChunks = false;
|
||||
let scrollOnBranchChange = true;
|
||||
let userLocation = false;
|
||||
|
||||
// Interface
|
||||
|
|
@ -39,6 +40,11 @@
|
|||
saveSettings({ splitLargeChunks: splitLargeChunks });
|
||||
};
|
||||
|
||||
const togglesScrollOnBranchChange = async () => {
|
||||
scrollOnBranchChange = !scrollOnBranchChange;
|
||||
saveSettings({ scrollOnBranchChange: scrollOnBranchChange });
|
||||
};
|
||||
|
||||
const togglewidescreenMode = async () => {
|
||||
widescreenMode = !widescreenMode;
|
||||
saveSettings({ widescreenMode: widescreenMode });
|
||||
|
|
@ -141,6 +147,7 @@
|
|||
chatBubble = $settings.chatBubble ?? true;
|
||||
widescreenMode = $settings.widescreenMode ?? false;
|
||||
splitLargeChunks = $settings.splitLargeChunks ?? false;
|
||||
scrollOnBranchChange = $settings.scrollOnBranchChange ?? true;
|
||||
chatDirection = $settings.chatDirection ?? 'LTR';
|
||||
userLocation = $settings.userLocation ?? false;
|
||||
|
||||
|
|
@ -318,6 +325,28 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Scroll to bottom when switching between branches')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
togglesScrollOnBranchChange();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if scrollOnBranchChange === true}
|
||||
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,13 @@
|
|||
getTagsById,
|
||||
updateChatById
|
||||
} from '$lib/apis/chats';
|
||||
import { tags as _tags, chats, pinnedChats } from '$lib/stores';
|
||||
import {
|
||||
tags as _tags,
|
||||
chats,
|
||||
pinnedChats,
|
||||
currentChatPage,
|
||||
scrollPaginationEnabled
|
||||
} from '$lib/stores';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
|
@ -46,11 +52,7 @@
|
|||
tags: tags
|
||||
});
|
||||
|
||||
console.log($_tags);
|
||||
await _tags.set(await getAllChatTags(localStorage.token));
|
||||
|
||||
console.log($_tags);
|
||||
|
||||
if ($_tags.map((t) => t.name).includes(tagName)) {
|
||||
if (tagName === 'pinned') {
|
||||
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
|
||||
|
|
@ -62,8 +64,11 @@
|
|||
dispatch('close');
|
||||
}
|
||||
} else {
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
// if the tag we deleted is no longer a valid tag, return to main chat list view
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
|
||||
await scrollPaginationEnabled.set(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,19 +5,21 @@
|
|||
export let src = '';
|
||||
export let alt = '';
|
||||
|
||||
let _src = '';
|
||||
export let className = '';
|
||||
|
||||
let _src = '';
|
||||
$: _src = src.startsWith('/') ? `${WEBUI_BASE_URL}${src}` : src;
|
||||
|
||||
let showImagePreview = false;
|
||||
</script>
|
||||
|
||||
<ImagePreview bind:show={showImagePreview} src={_src} {alt} />
|
||||
<button
|
||||
class={className}
|
||||
on:click={() => {
|
||||
console.log('image preview');
|
||||
showImagePreview = true;
|
||||
}}
|
||||
>
|
||||
<img src={_src} {alt} class=" max-h-96 rounded-lg" draggable="false" data-cy="image" />
|
||||
<img src={_src} {alt} class=" rounded-lg cursor-pointer" draggable="false" data-cy="image" />
|
||||
</button>
|
||||
|
||||
<ImagePreview bind:show={showImagePreview} src={_src} {alt} />
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
let mounted = false;
|
||||
|
||||
let previewElement = null;
|
||||
|
||||
const downloadImage = (url, filename) => {
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
|
|
@ -34,24 +36,25 @@
|
|||
mounted = true;
|
||||
});
|
||||
|
||||
$: if (mounted) {
|
||||
if (show) {
|
||||
$: if (show && previewElement) {
|
||||
document.body.appendChild(previewElement);
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
} else if (previewElement) {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
document.body.removeChild(previewElement);
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="fixed top-0 right-0 left-0 bottom-0 bg-black text-white w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain"
|
||||
bind:this={previewElement}
|
||||
class="modal fixed top-0 right-0 left-0 bottom-0 bg-black text-white w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
|
||||
>
|
||||
<div class=" absolute left-0 w-full flex justify-between">
|
||||
<div class=" absolute left-0 w-full flex justify-between select-none">
|
||||
<div>
|
||||
<button
|
||||
class=" p-5"
|
||||
|
|
@ -95,6 +98,6 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<img {src} {alt} class=" mx-auto h-full object-scale-down" />
|
||||
<img {src} {alt} class=" mx-auto h-full object-scale-down select-none" draggable="false" />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
30
src/lib/components/common/Loader.svelte
Normal file
30
src/lib/components/common/Loader.svelte
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let loaderElement: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
dispatch('visible');
|
||||
// observer.unobserve(loaderElement); // Stop observing until content is loaded
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
root: null, // viewport
|
||||
rootMargin: '0px',
|
||||
threshold: 0.1 // When 10% of the loader is visible
|
||||
}
|
||||
);
|
||||
|
||||
observer.observe(loaderElement);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={loaderElement}>
|
||||
<slot />
|
||||
</div>
|
||||
|
|
@ -11,7 +11,9 @@
|
|||
showSidebar,
|
||||
mobile,
|
||||
showArchivedChats,
|
||||
pinnedChats
|
||||
pinnedChats,
|
||||
scrollPaginationEnabled,
|
||||
currentChatPage
|
||||
} from '$lib/stores';
|
||||
import { onMount, getContext, tick } from 'svelte';
|
||||
|
||||
|
|
@ -34,6 +36,8 @@
|
|||
import UserMenu from './Sidebar/UserMenu.svelte';
|
||||
import ChatItem from './Sidebar/ChatItem.svelte';
|
||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import Loader from '../common/Loader.svelte';
|
||||
|
||||
const BREAKPOINT = 768;
|
||||
|
||||
|
|
@ -50,6 +54,10 @@
|
|||
|
||||
let filteredChatList = [];
|
||||
|
||||
// Pagination variables
|
||||
let chatListLoading = false;
|
||||
let allChatsLoaded = false;
|
||||
|
||||
$: filteredChatList = $chats.filter((chat) => {
|
||||
if (search === '') {
|
||||
return true;
|
||||
|
|
@ -70,6 +78,29 @@
|
|||
}
|
||||
});
|
||||
|
||||
const enablePagination = async () => {
|
||||
// Reset pagination variables
|
||||
currentChatPage.set(1);
|
||||
allChatsLoaded = false;
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
|
||||
// Enable pagination
|
||||
scrollPaginationEnabled.set(true);
|
||||
};
|
||||
|
||||
const loadMoreChats = async () => {
|
||||
chatListLoading = true;
|
||||
|
||||
currentChatPage.set($currentChatPage + 1);
|
||||
const newChatList = await getChatList(localStorage.token, $currentChatPage);
|
||||
|
||||
// once the bottom of the list has been reached (no results) there is no need to continue querying
|
||||
allChatsLoaded = newChatList.length === 0;
|
||||
await chats.set([...$chats, ...newChatList]);
|
||||
|
||||
chatListLoading = false;
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
mobile.subscribe((e) => {
|
||||
if ($showSidebar && e) {
|
||||
|
|
@ -82,9 +113,8 @@
|
|||
});
|
||||
|
||||
showSidebar.set(window.innerWidth > BREAKPOINT);
|
||||
|
||||
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
await enablePagination();
|
||||
|
||||
let touchstart;
|
||||
let touchend;
|
||||
|
|
@ -185,7 +215,11 @@
|
|||
await tick();
|
||||
goto('/');
|
||||
}
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
allChatsLoaded = false;
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
|
||||
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
|
||||
}
|
||||
};
|
||||
|
|
@ -410,7 +444,10 @@
|
|||
class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm bg-transparent dark:text-gray-300 outline-none"
|
||||
placeholder={$i18n.t('Search')}
|
||||
bind:value={search}
|
||||
on:focus={() => {
|
||||
on:focus={async () => {
|
||||
// TODO: migrate backend for more scalable search mechanism
|
||||
scrollPaginationEnabled.set(false);
|
||||
await chats.set(await getChatList(localStorage.token)); // when searching, load all chats
|
||||
enrichChatsWithContent($chats);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -422,7 +459,7 @@
|
|||
<button
|
||||
class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
|
||||
on:click={async () => {
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
await enablePagination();
|
||||
}}
|
||||
>
|
||||
{$i18n.t('all')}
|
||||
|
|
@ -431,12 +468,17 @@
|
|||
<button
|
||||
class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
|
||||
on:click={async () => {
|
||||
scrollPaginationEnabled.set(false);
|
||||
let chatIds = await getChatListByTagName(localStorage.token, tag.name);
|
||||
if (chatIds.length === 0) {
|
||||
await tags.set(await getAllChatTags(localStorage.token));
|
||||
chatIds = await getChatList(localStorage.token);
|
||||
|
||||
// if the tag we deleted is no longer a valid tag, return to main chat list view
|
||||
await enablePagination();
|
||||
}
|
||||
await chats.set(chatIds);
|
||||
|
||||
chatListLoading = false;
|
||||
}}
|
||||
>
|
||||
{tag.name}
|
||||
|
|
@ -527,6 +569,21 @@
|
|||
}}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#if $scrollPaginationEnabled && !allChatsLoaded}
|
||||
<Loader
|
||||
on:visible={(e) => {
|
||||
if (!chatListLoading) {
|
||||
loadMoreChats();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2">
|
||||
<Spinner className=" size-4" />
|
||||
<div class=" ">Loading...</div>
|
||||
</div>
|
||||
</Loader>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
getChatListByTagName,
|
||||
updateChatById
|
||||
} from '$lib/apis/chats';
|
||||
import { chatId, chats, mobile, pinnedChats, showSidebar } from '$lib/stores';
|
||||
import { chatId, chats, mobile, pinnedChats, showSidebar, currentChatPage } from '$lib/stores';
|
||||
|
||||
import ChatMenu from './ChatMenu.svelte';
|
||||
import ShareChatModal from '$lib/components/chat/ShareChatModal.svelte';
|
||||
|
|
@ -40,7 +40,9 @@
|
|||
await updateChatById(localStorage.token, id, {
|
||||
title: _title
|
||||
});
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
|
||||
}
|
||||
};
|
||||
|
|
@ -53,14 +55,18 @@
|
|||
|
||||
if (res) {
|
||||
goto(`/c/${res.id}`);
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
|
||||
}
|
||||
};
|
||||
|
||||
const archiveChatHandler = async (id) => {
|
||||
await archiveChatById(localStorage.token, id);
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned'));
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "مسح",
|
||||
"Scan complete!": "تم المسح",
|
||||
"Scan for documents from {{path}}": "{{path}} مسح على الملفات من",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "البحث",
|
||||
"Search a model": "البحث عن موديل",
|
||||
"Search Chats": "البحث في الدردشات",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Сканиране",
|
||||
"Scan complete!": "Сканиране завършено!",
|
||||
"Scan for documents from {{path}}": "Сканиране за документи в {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Търси",
|
||||
"Search a model": "Търси модел",
|
||||
"Search Chats": "Търсене на чатове",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "স্ক্যান",
|
||||
"Scan complete!": "স্ক্যান সম্পন্ন হয়েছে!",
|
||||
"Scan for documents from {{path}}": "ডকুমেন্টসমূহের জন্য {{path}} স্ক্যান করুন",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "অনুসন্ধান",
|
||||
"Search a model": "মডেল অনুসন্ধান করুন",
|
||||
"Search Chats": "চ্যাট অনুসন্ধান করুন",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"Account": "Compte",
|
||||
"Account Activation Pending": "Activació del compte pendent",
|
||||
"Accurate information": "Informació precisa",
|
||||
"Actions": "",
|
||||
"Actions": "Accions",
|
||||
"Active Users": "Usuaris actius",
|
||||
"Add": "Afegir",
|
||||
"Add a model id": "Afegeix un identificador de model",
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
"Add Memory": "Afegir memòria",
|
||||
"Add message": "Afegir un missatge",
|
||||
"Add Model": "Afegir un model",
|
||||
"Add Tag": "",
|
||||
"Add Tag": "Afegir etiqueta",
|
||||
"Add Tags": "Afegir etiquetes",
|
||||
"Add User": "Afegir un usuari",
|
||||
"Adjusting these settings will apply changes universally to all users.": "Si ajustes aquesta preferència, els canvis s'aplicaran de manera universal a tots els usuaris.",
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
"Delete chat": "Eliminar xat",
|
||||
"Delete Chat": "Eliminar xat",
|
||||
"Delete chat?": "Eliminar el xat?",
|
||||
"Delete Doc": "",
|
||||
"Delete Doc": "Eliminar document",
|
||||
"Delete function?": "Eliminar funció?",
|
||||
"Delete prompt?": "Eliminar indicació?",
|
||||
"delete this link": "Eliminar aquest enllaç",
|
||||
|
|
@ -214,7 +214,7 @@
|
|||
"Edit Doc": "Editar el document",
|
||||
"Edit Memory": "Editar la memòria",
|
||||
"Edit User": "Editar l'usuari",
|
||||
"ElevenLabs": "",
|
||||
"ElevenLabs": "ElevenLabs",
|
||||
"Email": "Correu electrònic",
|
||||
"Embedding Batch Size": "Mida del lot d'incrustació",
|
||||
"Embedding Model": "Model d'incrustació",
|
||||
|
|
@ -278,7 +278,7 @@
|
|||
"File": "Arxiu",
|
||||
"File Mode": "Mode d'arxiu",
|
||||
"File not found.": "No s'ha trobat l'arxiu.",
|
||||
"Files": "",
|
||||
"Files": "Arxius",
|
||||
"Filter is now globally disabled": "El filtre ha estat desactivat globalment",
|
||||
"Filter is now globally enabled": "El filtre ha estat activat globalment",
|
||||
"Filters": "Filtres",
|
||||
|
|
@ -375,7 +375,7 @@
|
|||
"Memory deleted successfully": "Memòria eliminada correctament",
|
||||
"Memory updated successfully": "Memòria actualitzada correctament",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Els missatges enviats després de crear el teu enllaç no es compartiran. Els usuaris amb l'URL podran veure el xat compartit.",
|
||||
"Min P": "",
|
||||
"Min P": "Min P",
|
||||
"Minimum Score": "Puntuació mínima",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Eta de Mirostat",
|
||||
|
|
@ -504,11 +504,12 @@
|
|||
"Save": "Desar",
|
||||
"Save & Create": "Desar i crear",
|
||||
"Save & Update": "Desar i actualitzar",
|
||||
"Save Tag": "",
|
||||
"Save Tag": "Desar l'etiqueta",
|
||||
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Desar els registres de xat directament a l'emmagatzematge del teu navegador ja no està suportat. Si us plau, descarregr i elimina els registres de xat fent clic al botó de sota. No et preocupis, pots tornar a importar fàcilment els teus registres de xat al backend a través de",
|
||||
"Scan": "Escanejar",
|
||||
"Scan complete!": "Escaneigr completat!",
|
||||
"Scan for documents from {{path}}": "Escanejar documents des de {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Cercar",
|
||||
"Search a model": "Cercar un model",
|
||||
"Search Chats": "Cercar xats",
|
||||
|
|
@ -623,7 +624,7 @@
|
|||
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Per accedir a la WebUI, poseu-vos en contacte amb l'administrador. Els administradors poden gestionar els estats dels usuaris des del tauler d'administració.",
|
||||
"To add documents here, upload them to the \"Documents\" workspace first.": "Per afegir documents aquí, puja-ls primer a l'espai de treball \"Documents\".",
|
||||
"to chat input.": "a l'entrada del xat.",
|
||||
"To select actions here, add them to the \"Functions\" workspace first.": "",
|
||||
"To select actions here, add them to the \"Functions\" workspace first.": "Per seleccionar accions aquí, afegeix-los primer a l'espai de treball \"Funcions\".",
|
||||
"To select filters here, add them to the \"Functions\" workspace first.": "Per seleccionar filtres aquí, afegeix-los primer a l'espai de treball \"Funcions\".",
|
||||
"To select toolkits here, add them to the \"Tools\" workspace first.": "Per seleccionar kits d'eines aquí, afegeix-los primer a l'espai de treball \"Eines\".",
|
||||
"Today": "Avui",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Aron ma-scan",
|
||||
"Scan complete!": "Nakompleto ang pag-scan!",
|
||||
"Scan for documents from {{path}}": "I-scan ang mga dokumento gikan sa {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Pagpanukiduki",
|
||||
"Search a model": "",
|
||||
"Search Chats": "",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Scannen",
|
||||
"Scan complete!": "Scan abgeschlossen!",
|
||||
"Scan for documents from {{path}}": "Dokumente im {{path}} scannen",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Suchen",
|
||||
"Search a model": "Modell suchen",
|
||||
"Search Chats": "Unterhaltungen durchsuchen...",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Scan much scan",
|
||||
"Scan complete!": "Scan complete! Very wow!",
|
||||
"Scan for documents from {{path}}": "Scan for documents from {{path}} wow",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Search very search",
|
||||
"Search a model": "",
|
||||
"Search Chats": "",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "",
|
||||
"Scan complete!": "",
|
||||
"Scan for documents from {{path}}": "",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "",
|
||||
"Search a model": "",
|
||||
"Search Chats": "",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "",
|
||||
"Scan complete!": "",
|
||||
"Scan for documents from {{path}}": "",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "",
|
||||
"Search a model": "",
|
||||
"Search Chats": "",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Escanear",
|
||||
"Scan complete!": "¡Escaneo completado!",
|
||||
"Scan for documents from {{path}}": "Escanear en busca de documentos desde {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Buscar",
|
||||
"Search a model": "Buscar un modelo",
|
||||
"Search Chats": "Chats de búsqueda",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "اسکن",
|
||||
"Scan complete!": "اسکن کامل شد!",
|
||||
"Scan for documents from {{path}}": "اسکن اسناد از {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "جستجو",
|
||||
"Search a model": "جستجوی مدل",
|
||||
"Search Chats": "جستجو گپ ها",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Skannaa",
|
||||
"Scan complete!": "Skannaus valmis!",
|
||||
"Scan for documents from {{path}}": "Skannaa asiakirjoja polusta {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Haku",
|
||||
"Search a model": "Hae mallia",
|
||||
"Search Chats": "Etsi chatteja",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Scanner",
|
||||
"Scan complete!": "Scan terminé !",
|
||||
"Scan for documents from {{path}}": "Scanner des documents depuis {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Recherche",
|
||||
"Search a model": "Rechercher un modèle",
|
||||
"Search Chats": "Rechercher des conversations",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Scanner",
|
||||
"Scan complete!": "Scan terminé !",
|
||||
"Scan for documents from {{path}}": "Scanner des documents depuis {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Recherche",
|
||||
"Search a model": "Rechercher un modèle",
|
||||
"Search Chats": "Rechercher des conversations",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "סרוק",
|
||||
"Scan complete!": "הסריקה הושלמה!",
|
||||
"Scan for documents from {{path}}": "סרוק מסמכים מ-{{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "חפש",
|
||||
"Search a model": "חפש מודל",
|
||||
"Search Chats": "חיפוש צ'אטים",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "स्कैन",
|
||||
"Scan complete!": "स्कैन पूरा हुआ!",
|
||||
"Scan for documents from {{path}}": "{{path}} से दस्तावेज़ों को स्कैन करें",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "खोजें",
|
||||
"Search a model": "एक मॉडल खोजें",
|
||||
"Search Chats": "चैट खोजें",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Skeniraj",
|
||||
"Scan complete!": "Skeniranje dovršeno!",
|
||||
"Scan for documents from {{path}}": "Skeniraj dokumente s {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Pretraga",
|
||||
"Search a model": "Pretraži model",
|
||||
"Search Chats": "Pretraži razgovore",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Pindai",
|
||||
"Scan complete!": "Pindai selesai!",
|
||||
"Scan for documents from {{path}}": "Memindai dokumen dari {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Cari",
|
||||
"Search a model": "Mencari model",
|
||||
"Search Chats": "Cari Obrolan",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Scansione",
|
||||
"Scan complete!": "Scansione completata!",
|
||||
"Scan for documents from {{path}}": "Cerca documenti da {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Cerca",
|
||||
"Search a model": "Cerca un modello",
|
||||
"Search Chats": "Cerca nelle chat",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "スキャン",
|
||||
"Scan complete!": "スキャン完了!",
|
||||
"Scan for documents from {{path}}": "{{path}} からドキュメントをスキャン",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "検索",
|
||||
"Search a model": "モデルを検索",
|
||||
"Search Chats": "チャットの検索",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "სკანირება",
|
||||
"Scan complete!": "სკანირება დასრულდა!",
|
||||
"Scan for documents from {{path}}": "დოკუმენტების სკანირება {{ path}}-დან",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "ძიება",
|
||||
"Search a model": "მოდელის ძიება",
|
||||
"Search Chats": "ჩატების ძებნა",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "스캔",
|
||||
"Scan complete!": "스캔 완료!",
|
||||
"Scan for documents from {{path}}": "{{path}} 에서 문서 스캔",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "검색",
|
||||
"Search a model": "모델 검색",
|
||||
"Search Chats": "채팅 검색",
|
||||
|
|
|
|||
|
|
@ -87,6 +87,10 @@
|
|||
"code": "lt-LT",
|
||||
"title": "Lithuanian (Lietuvių)"
|
||||
},
|
||||
{
|
||||
"code": "ms-MY",
|
||||
"title": "Malay (Bahasa Malaysia)"
|
||||
},
|
||||
{
|
||||
"code": "nb-NO",
|
||||
"title": "Norwegian Bokmål (Norway)"
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Skenuoti",
|
||||
"Scan complete!": "Skenavimas baigtas!",
|
||||
"Scan for documents from {{path}}": "Skenuoti dokumentus iš {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Ieškoti",
|
||||
"Search a model": "Ieškoti modelio",
|
||||
"Search Chats": "",
|
||||
|
|
|
|||
715
src/lib/i18n/locales/ms-MY/translation.json
Normal file
715
src/lib/i18n/locales/ms-MY/translation.json
Normal file
|
|
@ -0,0 +1,715 @@
|
|||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' untuk tiada tempoh luput.",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "(contoh `sh webui.sh --api --api-auth username_password`)",
|
||||
"(e.g. `sh webui.sh --api`)": "(contoh `sh webui.sh --api`)",
|
||||
"(latest)": "(terkini)",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{ owner }}: You cannot delete a base model": "{{ owner }}: Anda tidak boleh memadamkan model asas",
|
||||
"{{modelName}} is thinking...": "{{modelName}} sedang berfikir...",
|
||||
"{{user}}'s Chats": "Perbualan {{user}}",
|
||||
"{{webUIName}} Backend Required": "{{webUIName}} Backend diperlukan",
|
||||
"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model tugas digunakan semasa melaksanakan tugas seperti menjana tajuk untuk perbualan dan pertanyaan carian web.",
|
||||
"a user": "seorang pengguna",
|
||||
"About": "Mengenai",
|
||||
"Account": "Akaun",
|
||||
"Account Activation Pending": "Pengaktifan Akaun belum selesai",
|
||||
"Accurate information": "Informasi tepat",
|
||||
"Actions": "Tindakan",
|
||||
"Active Users": "Pengguna Aktif",
|
||||
"Add": "Tambah",
|
||||
"Add a model id": "Tambah id model",
|
||||
"Add a short description about what this model does": "Tambah penerangan ringkas tentang apa yang model ini boleh lakukan",
|
||||
"Add a short title for this prompt": "Tambah tajuk pendek untuk arahan ini",
|
||||
"Add a tag": "Tambah tag",
|
||||
"Add custom prompt": "Tambah arahan khusus",
|
||||
"Add Docs": "Tambah Dokumen",
|
||||
"Add Files": "Tambah Fail",
|
||||
"Add Memory": "Tambah Memori",
|
||||
"Add message": "Tambah Mesej",
|
||||
"Add Model": "Tambah Model",
|
||||
"Add Tag": "Tambah Tag",
|
||||
"Add Tags": "Tambah Tag",
|
||||
"Add User": "Tambah Pengguna",
|
||||
"Adjusting these settings will apply changes universally to all users.": "Melaraskan tetapan ini akan menggunakan perubahan secara universal kepada semua pengguna.",
|
||||
"admin": "pentadbir",
|
||||
"Admin": "Pentadbir",
|
||||
"Admin Panel": "Panel Pentadbir",
|
||||
"Admin Settings": "Tetapan Pentadbir",
|
||||
"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Pentadbir mempunyai akses kepada semua alat pada setiap masa; pengguna memerlukan alat yang ditetapkan mengikut model dalam ruang kerja.",
|
||||
"Advanced Parameters": "Parameter Lanjutan",
|
||||
"Advanced Params": "Parameter Lanjutan",
|
||||
"all": "semua",
|
||||
"All Documents": "Semua Dokumen",
|
||||
"All Users": "Semua Pengguna",
|
||||
"Allow": "Benarkan",
|
||||
"Allow Chat Deletion": "Benarkan Penghapusan Perbualan",
|
||||
"Allow non-local voices": "Benarkan suara bukan tempatan ",
|
||||
"Allow User Location": "Benarkan Lokasi Pengguna",
|
||||
"Allow Voice Interruption in Call": "Benarkan gangguan suara dalam panggilan",
|
||||
"alphanumeric characters and hyphens": "aksara alfanumerik dan tanda sempang",
|
||||
"Already have an account?": "Telah mempunyai akaun?",
|
||||
"an assistant": "seorang pembantu",
|
||||
"and": "dan",
|
||||
"and create a new shared link.": "dan cipta pautan kongsi baharu",
|
||||
"API Base URL": "URL Asas API",
|
||||
"API Key": "Kunci API",
|
||||
"API Key created.": "Kunci API dicipta",
|
||||
"API keys": "Kekunci API",
|
||||
"April": "April",
|
||||
"Archive": "Arkib",
|
||||
"Archive All Chats": "Arkibkan Semua Perbualan",
|
||||
"Archived Chats": "Perbualan yang diarkibkan",
|
||||
"are allowed - Activate this command by typing": "adalah dibenarkan - Aktifkan arahan in dengan menaip",
|
||||
"Are you sure?": "Adakah anda pasti",
|
||||
"Attach file": "Kepilkan Fail",
|
||||
"Attention to detail": "Perincian",
|
||||
"Audio": "Audio",
|
||||
"Audio settings updated successfully": "Tetapan audio berjaya dikemas kini",
|
||||
"August": "Ogos",
|
||||
"Auto-playback response": "Main semula respons secara automatik",
|
||||
"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api Auth String",
|
||||
"AUTOMATIC1111 Base URL": "URL Asas AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "URL Asas AUTOMATIC1111 diperlukan.",
|
||||
"available!": "tersedia!",
|
||||
"Back": "Kembali",
|
||||
"Bad Response": "Maklumbalas Salah",
|
||||
"Banners": "Sepanduk",
|
||||
"Base Model (From)": "Model Asas (Dari)",
|
||||
"Batch Size (num_batch)": "Saiz Kumpulan (num_batch)",
|
||||
"before": "sebelum,",
|
||||
"Being lazy": "Menjadi Malas",
|
||||
"Brave Search API Key": "Kunci API Carian Brave",
|
||||
"Bypass SSL verification for Websites": "Pintas pengesahan SSL untuk Laman Web",
|
||||
"Call": "Hubungi",
|
||||
"Call feature is not supported when using Web STT engine": "Ciri panggilan tidak disokong apabila menggunakan enjin Web STT",
|
||||
"Camera": "Kamera",
|
||||
"Cancel": "Batal",
|
||||
"Capabilities": "Keupayaan",
|
||||
"Change Password": "Tukar Kata Laluan",
|
||||
"Chat": "Perbualan",
|
||||
"Chat Background Image": "Imej Latar Belakang Perbualan",
|
||||
"Chat Bubble UI": "Antaramuka Buih Perbualan",
|
||||
"Chat Controls": "Kawalan Perbualan",
|
||||
"Chat direction": "Arah Perbualan",
|
||||
"Chat History": "Sejarah Perbualan",
|
||||
"Chat History is off for this browser.": "Sejarah perbualan dimatikan untuk pelayan web ini",
|
||||
"Chats": "Perbualan",
|
||||
"Check Again": "Semak Kembali",
|
||||
"Check for updates": "Semak kemas kini",
|
||||
"Checking for updates...": "Kemas kini sedang disemak",
|
||||
"Choose a model before saving...": "Pilih model sebelum menyimpan",
|
||||
"Chunk Overlap": "Tindihan 'Çhunk'",
|
||||
"Chunk Params": "Parameter 'Çhunk'",
|
||||
"Chunk Size": "Saiz 'Chunk'",
|
||||
"Citation": "Petikan",
|
||||
"Clear memory": "Kosongkan memori",
|
||||
"Click here for help.": "Klik disini untuk mendapatkan bantuan",
|
||||
"Click here to": "Klik disini untuk",
|
||||
"Click here to download user import template file.": "Klik disini untuk memuat turun fail templat import pengguna",
|
||||
"Click here to select": "Klik disini untuk memilih",
|
||||
"Click here to select a csv file.": "Klik disini untuk memilih fail csv",
|
||||
"Click here to select a py file.": "Klik disini untuk memilih fail py",
|
||||
"Click here to select documents.": "Klik disini untuk memilih dokumen",
|
||||
"click here.": "klik disini.",
|
||||
"Click on the user role button to change a user's role.": "Klik pada butang peranan pengguna untuk menukar peranan pengguna",
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Kebenaran untuk menulis di papan klip ditolak. Sila semak tetapan pelayan web anda untuk memberikan akses yang diperlukan",
|
||||
"Clone": "Klon",
|
||||
"Close": "Tutup",
|
||||
"Code formatted successfully": "Kod berjaya diformatkan",
|
||||
"Collection": "Koleksi",
|
||||
"ComfyUI": "ComfyUI",
|
||||
"ComfyUI Base URL": "URL asas ComfyUI",
|
||||
"ComfyUI Base URL is required.": "URL asas ComfyUI diperlukan",
|
||||
"Command": "Arahan",
|
||||
"Concurrent Requests": "Permintaan Serentak",
|
||||
"Confirm": "Sahkan",
|
||||
"Confirm Password": "Sahkan kata laluan",
|
||||
"Confirm your action": "Sahkan tindakan anda",
|
||||
"Connections": "Sambungan",
|
||||
"Contact Admin for WebUI Access": "Hubungi admin untuk akses WebUI",
|
||||
"Content": "Kandungan",
|
||||
"Content Extraction": "Pengekstrakan Kandungan",
|
||||
"Context Length": "Panjang Konteks",
|
||||
"Continue Response": "Teruskan Respons",
|
||||
"Continue with {{provider}}": "Teruskan dengan {{provider}}",
|
||||
"Controls": "Kawalan",
|
||||
"Copied shared chat URL to clipboard!": "Menyalin URL sembang kongsi ke papan klip",
|
||||
"Copy": "Salin",
|
||||
"Copy last code block": "Salin Blok Kod Terakhir",
|
||||
"Copy last response": "Salin Respons Terakhir",
|
||||
"Copy Link": "Salin Pautan",
|
||||
"Copying to clipboard was successful!": "Menyalin ke papan klip berjaya!",
|
||||
"Create a model": "Cipta model",
|
||||
"Create Account": "Cipta Akaun",
|
||||
"Create new key": "Cipta kekunci baharu",
|
||||
"Create new secret key": "Cipta kekunci rahsia baharu",
|
||||
"Created at": "Dicipta di",
|
||||
"Created At": "Dicipta Di",
|
||||
"Created by": "Dicipta oleh",
|
||||
"CSV Import": "Import CSV",
|
||||
"Current Model": "Model Semasa",
|
||||
"Current Password": "Kata laluan semasa",
|
||||
"Custom": "Tersuai",
|
||||
"Customize models for a specific purpose": "Sesuaikan model untuk tujuan tertentu",
|
||||
"Dark": "Gelap",
|
||||
"Dashboard": "Papan Pemuka",
|
||||
"Database": "Pangkalan Data",
|
||||
"December": "Disember",
|
||||
"Default": "Lalai",
|
||||
"Default (Automatic1111)": "Lalai (Automatic1111)",
|
||||
"Default (SentenceTransformers)": "Lalai (SentenceTransformers)",
|
||||
"Default Model": "Model Lalai",
|
||||
"Default model updated": "Model lalai dikemas kini",
|
||||
"Default Prompt Suggestions": "Cadangan Gesaan Lalai",
|
||||
"Default User Role": "Peranan Pengguna Lalai",
|
||||
"delete": "padam",
|
||||
"Delete": "Padam",
|
||||
"Delete a model": "Padam Model",
|
||||
"Delete All Chats": "Padam Semua Perbualan",
|
||||
"Delete chat": "Padam perbualan",
|
||||
"Delete Chat": "Padam Perbualan",
|
||||
"Delete chat?": "Padam perbualan?",
|
||||
"Delete Doc": "Padam Dokumen",
|
||||
"Delete function?": "Padam fungsi?",
|
||||
"Delete prompt?": "Padam Gesaan?",
|
||||
"delete this link": "Padam pautan ini?",
|
||||
"Delete tool?": "Padam alat?",
|
||||
"Delete User": "Padam Pengguna",
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} dipadam",
|
||||
"Deleted {{name}}": "{{name}} dipadam",
|
||||
"Description": "Penerangan",
|
||||
"Didn't fully follow instructions": "Tidak mengikut arahan sepenuhnya",
|
||||
"Disabled": "Dilumpuhkan",
|
||||
"Discover a function": "Temui fungsi",
|
||||
"Discover a model": "Temui model",
|
||||
"Discover a prompt": "Temui gesaan",
|
||||
"Discover a tool": "Temui alat",
|
||||
"Discover, download, and explore custom functions": "Temui, muat turun dan teroka fungsi tersuai",
|
||||
"Discover, download, and explore custom prompts": "Temui, muat turun dan teroka gesaan tersuai",
|
||||
"Discover, download, and explore custom tools": "Temui, muat turun dan teroka alat tersuai",
|
||||
"Discover, download, and explore model presets": "Temui, muat turun dan teroka model pratetap",
|
||||
"Dismissible": "Diketepikan",
|
||||
"Display Emoji in Call": "Paparkan Emoji dalam Panggilan",
|
||||
"Display the username instead of You in the Chat": "aparkan nama pengguna dan bukannya 'Anda' dalam Sembang",
|
||||
"Do not install functions from sources you do not fully trust.": "Jangan pasang fungsi daripada sumber yang anda tidak percayai sepenuhnya.",
|
||||
"Do not install tools from sources you do not fully trust.": "Jangan pasang alat daripada sumber yang anda tidak percaya sepenuhnya.",
|
||||
"Document": "Dokumen",
|
||||
"Document Settings": "Tetapan Dokumen",
|
||||
"Documentation": "Dokumentasi",
|
||||
"Documents": "Dokumen",
|
||||
"does not make any external connections, and your data stays securely on your locally hosted server.": "tidak membuat sebarang sambungan luaran, dan data anda kekal selamat pada pelayan yang dihoskan ditempat anda",
|
||||
"Don't Allow": "Tidak Dibenarkan",
|
||||
"Don't have an account?": "Anda tidak mempunyai akaun?",
|
||||
"don't install random functions from sources you don't trust.": "jangan pasang mana-mana fungsi daripada sumber yang anda tidak percayai.",
|
||||
"don't install random tools from sources you don't trust.": "jangan pasang mana-mana alat daripada sumber yang anda tidak percayai.",
|
||||
"Don't like the style": "Tidak suka gaya ini",
|
||||
"Done": "Selesai",
|
||||
"Download": "Muat Turun",
|
||||
"Download canceled": "Muat Turun dibatalkan",
|
||||
"Download Database": "Muat turun Pangkalan Data",
|
||||
"Drop any files here to add to the conversation": "Letakkan mana-mana fail di sini untuk ditambahkan pada perbualan",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "cth '30s','10m'. Unit masa yang sah ialah 's', 'm', 'h'.",
|
||||
"Edit": "Edit",
|
||||
"Edit Doc": "Edit Dokumen",
|
||||
"Edit Memory": "Edit Memori",
|
||||
"Edit User": "Edit Penggunal",
|
||||
"ElevenLabs": "ElevenLabs",
|
||||
"Email": "E-mel",
|
||||
"Embedding Batch Size": "Membenamkan Saiz Kelompok",
|
||||
"Embedding Model": "Model Benamkan",
|
||||
"Embedding Model Engine": "Enjin Model Benamkan",
|
||||
"Embedding model set to \"{{embedding_model}}\"": "Model Benamkan ditetapkan kepada \"{{embedding_model}}\"",
|
||||
"Enable Chat History": "Benarkan Sejarah Perbualan",
|
||||
"Enable Community Sharing": "Benarkan Perkongsian Komunity",
|
||||
"Enable New Sign Ups": "Benarkan Pendaftaran Baharu",
|
||||
"Enable Web Search": "Benarkan Carian Web",
|
||||
"Enabled": "Dibenarkan",
|
||||
"Engine": "Enjin",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "astikan fail CSV anda mengandungi 4 lajur dalam susunan ini: Nama, E-mel, Kata Laluan, Peranan.",
|
||||
"Enter {{role}} message here": "Masukkan mesej {{role}} di sini",
|
||||
"Enter a detail about yourself for your LLMs to recall": "Masukkan butiran tentang diri anda untuk diingati oleh LLM anda",
|
||||
"Enter api auth string (e.g. username:password)": "Masukkan kekunci auth api ( cth nama pengguna:kata laluan )",
|
||||
"Enter Brave Search API Key": "Masukkan Kekunci API carian Brave",
|
||||
"Enter Chunk Overlap": "Masukkan Tindihan 'Chunk'",
|
||||
"Enter Chunk Size": "Masukkan Saiz 'Chunk'",
|
||||
"Enter Github Raw URL": "Masukkan URL 'Github Raw'",
|
||||
"Enter Google PSE API Key": "Masukkan kunci API Google PSE",
|
||||
"Enter Google PSE Engine Id": "Masukkan Id Enjin Google PSE",
|
||||
"Enter Image Size (e.g. 512x512)": "Masukkan Saiz Imej (cth 512x512)",
|
||||
"Enter language codes": "Masukkan kod bahasa",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Masukkan tag model (cth {{ modelTag }})",
|
||||
"Enter Number of Steps (e.g. 50)": "Masukkan Bilangan Langkah (cth 50)",
|
||||
"Enter Score": "Masukkan Skor",
|
||||
"Enter Searxng Query URL": "Masukkan URL 'Searxng Query'",
|
||||
"Enter Serper API Key": "Masukkan Kunci API Serper",
|
||||
"Enter Serply API Key": "Masukkan Kunci API Serply",
|
||||
"Enter Serpstack API Key": "Masukkan Kunci API Serpstack",
|
||||
"Enter stop sequence": "Masukkan urutan hentian",
|
||||
"Enter system prompt": "Masukkan gesaan sistem",
|
||||
"Enter Tavily API Key": "Masukkan Kunci API Tavily",
|
||||
"Enter Tika Server URL": "Masukkan URL Pelayan Tika",
|
||||
"Enter Top K": "Masukkan 'Top K'",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Masukkan URL (cth http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "Masukkan URL (cth http://localhost:11434)",
|
||||
"Enter Your Email": "Masukkan E-mel Anda",
|
||||
"Enter Your Full Name": "Masukkan Nama Penuh Anda",
|
||||
"Enter your message": "Masukkan mesej anda",
|
||||
"Enter Your Password": "Masukkan Kata Laluan Anda",
|
||||
"Enter Your Role": "Masukkan Peranan Anda",
|
||||
"Error": "Ralat",
|
||||
"Experimental": "Percubaan",
|
||||
"Export": "Eksport",
|
||||
"Export All Chats (All Users)": "Eksport Semua Perbualan (Semua Pengguna)",
|
||||
"Export chat (.json)": "Eksport perbualan (.json)",
|
||||
"Export Chats": "Eksport Perbualan",
|
||||
"Export Documents Mapping": "Eksport Pemetaan Dokumen",
|
||||
"Export Functions": "Eksport 'Fungsi'",
|
||||
"Export LiteLLM config.yaml": "Eksport LiteLLM config.yaml",
|
||||
"Export Models": "Eksport Model",
|
||||
"Export Prompts": "Eksport Gesaan",
|
||||
"Export Tools": "Eksport Alat",
|
||||
"External Models": "Model Luaran",
|
||||
"Failed to create API Key.": "Gagal mencipta kekunci API",
|
||||
"Failed to read clipboard contents": "Gagal membaca konten papan klip",
|
||||
"Failed to update settings": "Gagal mengemaskini tetapan",
|
||||
"February": "Febuari",
|
||||
"Feel free to add specific details": "Jangan ragu untuk menambah butiran khusus",
|
||||
"File": "Fail",
|
||||
"File Mode": "Mod Fail",
|
||||
"File not found.": "Fail tidak dijumpai",
|
||||
"Files": "Fail-Fail",
|
||||
"Filter is now globally disabled": "Tapisan kini dilumpuhkan secara global",
|
||||
"Filter is now globally enabled": "Tapisan kini dibenarkan secara global",
|
||||
"Filters": "Tapisan",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Peniruan cap jari dikesan, tidak dapat menggunakan nama pendek sebagai avatar. Lalai kepada imej profail asal",
|
||||
"Fluidly stream large external response chunks": "Strim 'chunks' respons luaran yang besar dengan lancar",
|
||||
"Focus chat input": "Fokus kepada input perbualan",
|
||||
"Followed instructions perfectly": "Mengikut arahan dengan sempurna",
|
||||
"Form": "Borang",
|
||||
"Format your variables using square brackets like this:": "Formatkan pembolehubah anda menggunakan kurungan segi empat sama seperti ini:",
|
||||
"Frequency Penalty": "Penalti Kekerapan",
|
||||
"Function created successfully": "Fungsi berjaya dibuat",
|
||||
"Function deleted successfully": "Fungsi berjaya dipadamkan",
|
||||
"Function Description (e.g. A filter to remove profanity from text)": "Perihalan Fungsi (cth. Tapisan untuk membuang kata-kata kotor daripada teks)",
|
||||
"Function ID (e.g. my_filter)": "ID Fungsi (cth. my_filter)",
|
||||
"Function is now globally disabled": "Fungsi kini dilumpuhkan secara global",
|
||||
"Function is now globally enabled": "Fungsi kini dibenarkan secara global",
|
||||
"Function Name (e.g. My Filter)": "Nama Fungsi (cth. 'My Filter')",
|
||||
"Function updated successfully": "Fungsi berjaya dikemaskini",
|
||||
"Functions": "Fungsi",
|
||||
"Functions allow arbitrary code execution": "Fungsi membenarkan pelaksanaan kod sewenang-wenangnya",
|
||||
"Functions allow arbitrary code execution.": "Fungsi membenarkan pelaksanaan kod sewenang-wenangnya.",
|
||||
"Functions imported successfully": "Fungsi berjaya diimport",
|
||||
"General": "Umum",
|
||||
"General Settings": "Tetapan Umum",
|
||||
"Generate Image": "Jana Imej",
|
||||
"Generating search query": "Jana pertanyaan carian",
|
||||
"Generation Info": "Maklumat Penjanaan",
|
||||
"Get up and running with": "Bangun dan berlari dengan",
|
||||
"Global": "Global",
|
||||
"Good Response": "Respons Baik",
|
||||
"Google PSE API Key": "Kunci API Google PSE",
|
||||
"Google PSE Engine Id": "ID Enjin Google PSE",
|
||||
"h:mm a": "h:mm a",
|
||||
"has no conversations.": "tidak mempunyai perbualan.",
|
||||
"Hello, {{name}}": "Hello, {{name}}",
|
||||
"Help": "Bantuan",
|
||||
"Hide": "Sembunyi",
|
||||
"Hide Model": "Sembunyikan Model",
|
||||
"How can I help you today?": "Bagaimana saya boleh membantu anda hari ini?",
|
||||
"Hybrid Search": "Carian Hibrid",
|
||||
"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Saya mengakui bahawa saya telah membaca dan saya memahami implikasi tindakan saya. Saya sedar tentang risiko yang berkaitan dengan melaksanakan kod sewenang-wenangnya dan saya telah mengesahkan kebolehpercayaan sumber tersebut.",
|
||||
"Image Generation (Experimental)": "Penjanaan Imej (Percubaan)",
|
||||
"Image Generation Engine": "Enjin Penjanaan Imej",
|
||||
"Image Settings": "Tetapan Imej",
|
||||
"Images": "Imej",
|
||||
"Import Chats": "Import Perbualan",
|
||||
"Import Documents Mapping": "Import Pemetaan Dokumen",
|
||||
"Import Functions": "Import Fungsi",
|
||||
"Import Models": "Import Model",
|
||||
"Import Prompts": "Import Gesaan",
|
||||
"Import Tools": "Import Alat",
|
||||
"Include `--api-auth` flag when running stable-diffusion-webui": "Sertakan bendera `-- api -auth` semasa menjalankan stable-diffusion-webui ",
|
||||
"Include `--api` flag when running stable-diffusion-webui": "Sertakan bendera `-- api ` semasa menjalankan stable-diffusion-webui",
|
||||
"Info": "Maklumat",
|
||||
"Input commands": "Masukkan Arahan",
|
||||
"Install from Github URL": "Pasang daripada URL Github",
|
||||
"Instant Auto-Send After Voice Transcription": "Hantar Secara Automatik Dengan Segera Selepas Transkripsi Suara",
|
||||
"Interface": "Ataramuka",
|
||||
"Invalid Tag": "Tag tidak sah",
|
||||
"January": "Januari",
|
||||
"join our Discord for help.": "sertai Discord kami untuk mendapatkan bantuan.",
|
||||
"JSON": "JSON",
|
||||
"JSON Preview": "Pratonton JSON",
|
||||
"July": "Julai",
|
||||
"June": "Jun",
|
||||
"JWT Expiration": "Tempoh Tamat JWT",
|
||||
"JWT Token": "Token JWT",
|
||||
"Keep Alive": "Kekalkan Hidup",
|
||||
"Keyboard shortcuts": "Pintasan papan kekunci",
|
||||
"Knowledge": "Pengetahuan",
|
||||
"Language": "Bahasa",
|
||||
"large language models, locally.": "model bahasa besar, tempatan.",
|
||||
"Last Active": "Dilihat aktif terakhir pada",
|
||||
"Last Modified": "Kemaskini terakhir pada",
|
||||
"Light": "Cerah",
|
||||
"Listening...": "Mendengar...",
|
||||
"LLMs can make mistakes. Verify important information.": "LLM boleh membuat kesilapan. Sahkan maklumat penting",
|
||||
"Local Models": "Model Tempatan",
|
||||
"LTR": "LTR",
|
||||
"Made by OpenWebUI Community": "Dicipta oleh Komunity OpenWebUI",
|
||||
"Make sure to enclose them with": "Pastikan untuk melampirkannya dengan",
|
||||
"Manage": "Mengurus",
|
||||
"Manage Models": "Urus Model",
|
||||
"Manage Ollama Models": "Urus Model Ollama",
|
||||
"Manage Pipelines": "Urus 'Pipelines'",
|
||||
"March": "Mac",
|
||||
"Max Tokens (num_predict)": "Token Maksimum ( num_predict )",
|
||||
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maksimum 3 model boleh dimuat turun serentak. Sila cuba sebentar lagi.",
|
||||
"May": "Mei",
|
||||
"Memories accessible by LLMs will be shown here.": "Memori yang boleh diakses oleh LLM akan ditunjukkan di sini.",
|
||||
"Memory": "Memori",
|
||||
"Memory added successfully": "Memori berjaya ditambah",
|
||||
"Memory cleared successfully": "Memori berjaya dikosongkan",
|
||||
"Memory deleted successfully": "Memori berjaya dihapuskan",
|
||||
"Memory updated successfully": "Memori berjaya dikemaskini",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Mesej yang anda hantar selepas membuat pautan anda tidak akan dikongsi. Pengguna dengan URL akan dapat melihat perbualan yang dikongsi.",
|
||||
"Min P": "P Minimum",
|
||||
"Minimum Score": "Skor Minimum",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "DD MMMM YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm:ss A",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{ modelName }}' telah berjaya dimuat turun.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{ modelTag }}' sudah dalam baris gilir untuk dimuat turun.",
|
||||
"Model {{modelId}} not found": "Model {{ modelId }} tidak dijumpai",
|
||||
"Model {{modelName}} is not vision capable": "Model {{ modelName }} tidak mempunyai keupayaan penglihatan",
|
||||
"Model {{name}} is now {{status}}": "Model {{name}} kini {{status}}",
|
||||
"Model created successfully!": "Model berjaya dibuat!",
|
||||
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Laluan sistem fail model dikesan. Nama pendek model diperlukan untuk kemas kini, tidak boleh diteruskan.",
|
||||
"Model ID": "ID Model",
|
||||
"Model not selected": "Model tidak dipilih",
|
||||
"Model Params": "Model Params",
|
||||
"Model updated successfully": "Model berjaya dikemas kini",
|
||||
"Model Whitelisting": "Senarai Putih Model",
|
||||
"Model(s) Whitelisted": "Model Disenarai Putih",
|
||||
"Modelfile Content": "Kandungan Modelfail",
|
||||
"Models": "Model",
|
||||
"More": "Lagi",
|
||||
"Name": "Nama",
|
||||
"Name Tag": "Nama Tag",
|
||||
"Name your model": "Namakan Model Anda",
|
||||
"New Chat": "Perbualan Baru",
|
||||
"New Password": "Kata Laluan Baru",
|
||||
"No content to speak": "Tiada kandungan untuk bercakap",
|
||||
"No documents found": "Tiada dokumen ditemui",
|
||||
"No file selected": "Tiada fail dipilih",
|
||||
"No results found": "Tiada keputusan dijumpai",
|
||||
"No search query generated": "Tiada pertanyaan carian dijana",
|
||||
"No source available": "Tiada sumber tersedia",
|
||||
"No valves to update": "Tiada 'valve' untuk dikemas kini",
|
||||
"None": "Tiada",
|
||||
"Not factually correct": "Tidak tepat secara fakta",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Jika anda menetapkan skor minimum, carian hanya akan mengembalikan dokumen dengan skor lebih besar daripada atau sama dengan skor minimum.",
|
||||
"Notifications": "Pemberitahuan",
|
||||
"November": "November",
|
||||
"num_thread (Ollama)": "num_thread (Ollama)",
|
||||
"OAuth ID": "ID OAuth",
|
||||
"October": "Oktober",
|
||||
"Off": "Mati",
|
||||
"Okay, Let's Go!": "Baiklah, Jom!",
|
||||
"OLED Dark": "OLED Gelap",
|
||||
"Ollama": "Ollama",
|
||||
"Ollama API": "API Ollama",
|
||||
"Ollama API disabled": "API Ollama dilumpuhkan",
|
||||
"Ollama API is disabled": "API Ollama telah dilumpuhkan",
|
||||
"Ollama Version": "Versi Ollama",
|
||||
"On": "Hidup",
|
||||
"Only": "Hanya",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Hanya aksara alfanumerik dan sempang dibenarkan dalam rentetan arahan.",
|
||||
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Maaf! Harap Bersabar. Fail anda masih dalam ketuhar pemprosesan. Kami sedang memasaknya dengan sempurna. Harap bersabar dan kami akan memberitahu anda sebaik sahaja ia siap.",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Maaf, didapati URL tidak sah. Sila semak semula dan cuba lagi.",
|
||||
"Oops! There was an error in the previous response. Please try again or contact admin.": "Maaf, terdapat ralat dalam respons sebelumnya. Sila cuba lagi atau hubungi pentadbir",
|
||||
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Maaf, Anda menggunakan kaedah yang tidak disokong (bahagian 'frontend' sahaja). Sila sediakan WebUI dari 'backend'.",
|
||||
"Open AI (Dall-E)": "Open AI (Dall-E)",
|
||||
"Open new chat": "Buka perbualan baru",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) adalah lebih rendah daripada versi yang diperlukan iaitu (v{{REQUIRED_VERSION}})",
|
||||
"OpenAI": "OpenAI",
|
||||
"OpenAI API": "API OpenAI",
|
||||
"OpenAI API Config": "Tetapan API OpenAI",
|
||||
"OpenAI API Key is required.": "Kekunci API OpenAI diperlukan",
|
||||
"OpenAI URL/Key required.": "URL/Kekunci OpenAI diperlukan",
|
||||
"or": "atau",
|
||||
"Other": "Lain-lain",
|
||||
"Password": "Kata Laluan",
|
||||
"PDF document (.pdf)": "Dokumen PDF (.pdf)",
|
||||
"PDF Extract Images (OCR)": "Imej Ekstrak PDF (OCR)",
|
||||
"pending": "tertunda",
|
||||
"Permission denied when accessing media devices": "Tidak mendapat kebenaran apabila cuba mengakses peranti media",
|
||||
"Permission denied when accessing microphone": "Tidak mendapat kebenaran apabila cuba mengakses mikrofon",
|
||||
"Permission denied when accessing microphone: {{error}}": "Tidak mendapat kebenaran apabila cuba mengakses mikrofon: {{error}}",
|
||||
"Personalization": "Personalisasi",
|
||||
"Pin": "Pin",
|
||||
"Pinned": "Disemat",
|
||||
"Pipeline deleted successfully": "'Pipeline' berjaya dipadam",
|
||||
"Pipeline downloaded successfully": "'Pipeline' berjaya dimuat turun",
|
||||
"Pipelines": "'Pipeline'",
|
||||
"Pipelines Not Detected": "'Pipeline' tidak ditemui",
|
||||
"Pipelines Valves": "'Pipeline Valves'",
|
||||
"Plain text (.txt)": "Teks biasa (.txt)",
|
||||
"Playground": "Taman Permainan",
|
||||
"Please carefully review the following warnings:": "Sila semak dengan teliti amaran berikut:",
|
||||
"Positive attitude": "Sikap positif",
|
||||
"Previous 30 days": "30 hari sebelumnya",
|
||||
"Previous 7 days": "7 hari sebelumnya",
|
||||
"Profile Image": "Imej Profail",
|
||||
"Prompt": "Gesaan",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Gesaan (cth Beritahu saya fakta yang menyeronokkan tentang Kesultanan Melaka)",
|
||||
"Prompt Content": "Kandungan Gesaan",
|
||||
"Prompt suggestions": "Cadangan Gesaan",
|
||||
"Prompts": "Gesaan",
|
||||
"Pull \"{{searchValue}}\" from Ollama.com": "Tarik \"{{ searchValue }}\" daripada Ollama.com",
|
||||
"Pull a model from Ollama.com": "Tarik model dari Ollama.com",
|
||||
"Query Params": "'Query Params'",
|
||||
"RAG Template": "Templat RAG",
|
||||
"Read Aloud": "Baca dengan lantang",
|
||||
"Record voice": "Rakam suara",
|
||||
"Redirecting you to OpenWebUI Community": "Membawa anda ke Komuniti OpenWebUI",
|
||||
"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Rujuk diri anda sebagai \"User\" (cth, \"Pengguna sedang belajar bahasa Sepanyol\")",
|
||||
"Refused when it shouldn't have": "Menolak dimana ia tidak sepatutnya",
|
||||
"Regenerate": "Jana semula",
|
||||
"Release Notes": "Nota Keluaran",
|
||||
"Remove": "Hapuskan",
|
||||
"Remove Model": "Hapuskan Model",
|
||||
"Rename": "Namakan Semula",
|
||||
"Repeat Last N": "Ulang N Terakhir",
|
||||
"Request Mode": "Mod Permintaan",
|
||||
"Reranking Model": "Model 'Reranking'",
|
||||
"Reranking model disabled": "Model 'Reranking' dilumpuhkan",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "Model 'Reranking' ditetapkan kepada \"{{reranking_model}}\"",
|
||||
"Reset": "Tetapkan Semula",
|
||||
"Reset Upload Directory": "Tetapkan Semula Direktori Muat Naik",
|
||||
"Reset Vector Storage": "Tetapkan Semula Storan Vektor",
|
||||
"Response AutoCopy to Clipboard": "Salin Response secara Automatik ke Papan Klip",
|
||||
"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Pemberitahuan respons tidak boleh diaktifkan kerana kebenaran tapak web tidak diberi. Sila lawati tetapan pelayar web anda untuk memberikan akses yang diperlukan.",
|
||||
"Role": "Peranan",
|
||||
"Rosé Pine": "Rosé Pine",
|
||||
"Rosé Pine Dawn": "Rosé Pine Dawn",
|
||||
"RTL": "RTL",
|
||||
"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Jalankan Llama 2, Code Llama dan model lain. Sesuaikan dan buat sendiri.",
|
||||
"Running": "Sedang dijalankan",
|
||||
"Save": "Simpan",
|
||||
"Save & Create": "Simpan & Cipta",
|
||||
"Save & Update": "Simpan & Kemas Kini",
|
||||
"Save Tag": "Simpan Tag",
|
||||
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Penyimpanan log perbualan terus ke storan pelayan web anda tidak lagi disokong. Sila luangkan sedikit masa untuk memuat turun dan memadam log perbualan anda dengan mengklik butang di bawah. Jangan risau, anda boleh mengimport semula log perbualan anda dengan mudah melalui 'backend'",
|
||||
"Scan": "Imbas",
|
||||
"Scan complete!": "Imbasan selesai!",
|
||||
"Scan for documents from {{path}}": "Imbas untuk dokumen dari {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Carian",
|
||||
"Search a model": "Cari Model",
|
||||
"Search Chats": "Cari Perbualan",
|
||||
"Search Documents": "Carian Dokumen",
|
||||
"Search Functions": "Carian Fungsi",
|
||||
"Search Models": "Carian Model",
|
||||
"Search Prompts": "Carian Gesaan",
|
||||
"Search Query Generation Prompt": "Carian Penjanaan Pertanyaan Gesaan",
|
||||
"Search Query Generation Prompt Length Threshold": "Had Panjang Gesaan Penjanaan Pertanyaan Carian",
|
||||
"Search Result Count": "Kiraan Hasil Carian",
|
||||
"Search Tools": "Alat Carian",
|
||||
"Searched {{count}} sites_one": "Mencari {{count}} sites_one",
|
||||
"Searched {{count}} sites_other": "Mencari {{count}} tapak_lain",
|
||||
"Searching \"{{searchQuery}}\"": "encari \"{{ searchQuery }}\"",
|
||||
"Searxng Query URL": "URL Pertanyaan Searxng",
|
||||
"See readme.md for instructions": "Lihat readme.md untuk arahan",
|
||||
"See what's new": "Lihat apa yang terbaru",
|
||||
"Seed": "Benih",
|
||||
"Select a base model": "Pilih model asas",
|
||||
"Select a engine": "Pilih enjin",
|
||||
"Select a function": "Pilih fungsi",
|
||||
"Select a mode": "Pilih Mod",
|
||||
"Select a model": "Pilih model",
|
||||
"Select a pipeline": "Pilih 'pipeline'",
|
||||
"Select a pipeline url": "Pilih url 'pipeline'",
|
||||
"Select a tool": "Pilih alat",
|
||||
"Select an Ollama instance": "Pilih Ollama contoh",
|
||||
"Select Documents": "Pilih Dokumen",
|
||||
"Select model": "Pilih model",
|
||||
"Select only one model to call": "Pilih hanya satu model untuk dipanggil",
|
||||
"Selected model(s) do not support image inputs": "Model dipilih tidak menyokong input imej",
|
||||
"Send": "Hantar",
|
||||
"Send a Message": "Hantar Pesanan",
|
||||
"Send message": "Hantar pesanan",
|
||||
"September": "September",
|
||||
"Serper API Key": "Kunci API Serper",
|
||||
"Serply API Key": "Kunci API Serply",
|
||||
"Serpstack API Key": "Kunci API Serpstack",
|
||||
"Server connection verified": "Sambungan pelayan disahkan",
|
||||
"Set as default": "Tetapkan sebagai lalai",
|
||||
"Set Default Model": "Tetapkan Model Lalai",
|
||||
"Set embedding model (e.g. {{model}})": "Tetapkan model benamkan (cth {{model}})",
|
||||
"Set Image Size": "Tetapkan saiz imej",
|
||||
"Set reranking model (e.g. {{model}})": "Tetapkan model 'reranking' (cth {{model}})",
|
||||
"Set Steps": "tapkan Langkah",
|
||||
"Set Task Model": "Tetapkan Model Tugasan",
|
||||
"Set Voice": "Tetapan Suara",
|
||||
"Settings": "Tetapan",
|
||||
"Settings saved successfully!": "Tetapan berjaya disimpan!",
|
||||
"Settings updated successfully": "Tetapan berjaya dikemas kini",
|
||||
"Share": "Kongsi",
|
||||
"Share Chat": "Kongsi Perbualan",
|
||||
"Share to OpenWebUI Community": "Kongsi kepada Komuniti OpenWebUI",
|
||||
"short-summary": "ringkasan",
|
||||
"Show": "Tunjukkan",
|
||||
"Show Admin Details in Account Pending Overlay": "Tunjukkan Butiran Pentadbir dalam Akaun Menunggu Tindanan",
|
||||
"Show Model": "Tunjukkan Model",
|
||||
"Show shortcuts": "Tunjukkan pintasan",
|
||||
"Show your support!": "Tunjukkan sokongan anda!",
|
||||
"Showcased creativity": "eativiti yang dipamerkan",
|
||||
"Sign in": "Daftar masuk",
|
||||
"Sign Out": "Daftar keluar",
|
||||
"Sign up": "Daftar",
|
||||
"Signing in": "Sedang Mendaftar",
|
||||
"Source": "Sumber",
|
||||
"Speech recognition error: {{error}}": "Ralat pengecaman pertuturan: {{error}}",
|
||||
"Speech-to-Text Engine": "Enjin Ucapan-ke-Teks",
|
||||
"Stop Sequence": "Jujukan Henti",
|
||||
"STT Model": "Model STT",
|
||||
"STT Settings": "Tetapan STT",
|
||||
"Submit": "Hantar",
|
||||
"Subtitle (e.g. about the Roman Empire)": "Sari kata (cth tentang Kesultanan Melaka)",
|
||||
"Success": "Berjaya",
|
||||
"Successfully updated.": "Berjaya Dikemaskini",
|
||||
"Suggested": "Cadangan",
|
||||
"Support": "Sokongan",
|
||||
"Support this plugin:": "Sokong plugin ini",
|
||||
"System": "Sistem",
|
||||
"System Prompt": "Gesaan Sistem",
|
||||
"Tags": "Tag",
|
||||
"Tap to interrupt": "Sentuh untuk mengganggu",
|
||||
"Tavily API Key": "Kunci API Tavily",
|
||||
"Tell us more:": "Beritahu kami lebih lanjut",
|
||||
"Temperature": "Suhu",
|
||||
"Template": "Templat",
|
||||
"Text Completion": "Penyiapan Teks",
|
||||
"Text-to-Speech Engine": "Enjin Teks-ke-Ucapan",
|
||||
"Tfs Z": "Tfs Z",
|
||||
"Thanks for your feedback!": "Terima kasih atas maklum balas anda!",
|
||||
"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Pembangun di sebalik 'plugin' ini adalah sukarelawan yang bersemangat daripada komuniti. Jika anda mendapati 'plugin' ini membantu, sila pertimbangkan untuk menyumbang kepada pembangunannya.",
|
||||
"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Skor hendaklah berada diantara 0.0 (0%) dan 1.0 (100%).",
|
||||
"Theme": "Tema",
|
||||
"Thinking...": "Berfikir...",
|
||||
"This action cannot be undone. Do you wish to continue?": "Tindakan ini tidak boleh diubah semula kepada asal. Adakah anda ingin teruskan",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ini akan memastikan bahawa perbualan berharga anda disimpan dengan selamat ke pangkalan data 'backend' anda. Terima kasih!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "ni adalah ciri percubaan, ia mungkin tidak berfungsi seperti yang diharapkan dan tertakluk kepada perubahan pada bila-bila masa.",
|
||||
"This setting does not sync across browsers or devices.": "Tetapan ini tidak menyegerak merentas pelayar web atau peranti.",
|
||||
"This will delete": "Ini akan memadam",
|
||||
"Thorough explanation": "Penjelasan menyeluruh",
|
||||
"Tika": "Tika",
|
||||
"Tika Server URL required.": "URL Pelayan Tika diperlukan.",
|
||||
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Petua: Kemas kini berbilang slot pembolehubah secara berturut-turut dengan menekan kekunci tab dalam input perbualan selepas setiap penggantian.",
|
||||
"Title": "Tajuk",
|
||||
"Title (e.g. Tell me a fun fact)": "Tajuk (cth Beritahu saya fakta yang menyeronokkan)",
|
||||
"Title Auto-Generation": "Penjanaan Auto Tajuk",
|
||||
"Title cannot be an empty string.": "Tajuk tidak boleh menjadi rentetan kosong",
|
||||
"Title Generation Prompt": "Gesaan Penjanaan Tajuk",
|
||||
"to": "ke",
|
||||
"To access the available model names for downloading,": "Untuk mengakses nama model yang tersedia untuk dimuat turun,",
|
||||
"To access the GGUF models available for downloading,": "Untuk mengakses model GGUF yang tersedia untuk dimuat turun,",
|
||||
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Untuk mengakses WebUI , sila hubungi pentadbir. Pentadbir boleh menguruskan status pengguna daripada Panel Pentadbiran",
|
||||
"To add documents here, upload them to the \"Documents\" workspace first.": "Untuk menambah dokumen di sini, muat naiknya ke ruang kerja \"Documents\" dahulu.",
|
||||
"to chat input.": "untuk input perbualan.",
|
||||
"To select actions here, add them to the \"Functions\" workspace first.": "Untuk memilih tindakan di sini, tambahkannya pada ruang kerja \"Functions\" dahulu.",
|
||||
"To select filters here, add them to the \"Functions\" workspace first.": "Untuk memilih tapisan di sini, tambahkannya pada ruang kerja \"Functions\" dahulu.",
|
||||
"To select toolkits here, add them to the \"Tools\" workspace first.": "Untuk memilih kit alatan di sini, tambahkannya pada ruang kerja \"Tools\" dahulu.",
|
||||
"Today": "Hari Ini",
|
||||
"Toggle settings": "Suis Tetapan ",
|
||||
"Toggle sidebar": "Suis Bar Sisi",
|
||||
"Tokens To Keep On Context Refresh (num_keep)": "Token Untuk Teruskan Dalam Muat Semula Konteks ( num_keep )",
|
||||
"Tool created successfully": "Alat berjaya dibuat",
|
||||
"Tool deleted successfully": "Alat berjaya dipadamkan",
|
||||
"Tool imported successfully": "Alat berjaya diimport",
|
||||
"Tool updated successfully": "Alat berjaya dikemas kini",
|
||||
"Toolkit Description (e.g. A toolkit for performing various operations)": "Perihalan Kit Alatan (cth. Kit alatan untuk melaksanakan pelbagai operasi)",
|
||||
"Toolkit ID (e.g. my_toolkit)": "ID Kit Alatan (cth my_toolkit)",
|
||||
"Toolkit Name (e.g. My ToolKit)": "Nama Kit Alatan (cth My ToolKit)",
|
||||
"Tools": "Alatan",
|
||||
"Tools are a function calling system with arbitrary code execution": "Alatan ialah sistem panggilan fungsi dengan pelaksanaan kod sewenang-wenangnya",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Alatan mempunyai sistem panggilan fungsi yang membolehkan pelaksanaan kod sewenang-wenangnya",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Alatan mempunyai sistem panggilan fungsi yang membolehkan pelaksanaan kod sewenang-wenangnya.",
|
||||
"Top K": "'Top K'",
|
||||
"Top P": "'Top P'",
|
||||
"Trouble accessing Ollama?": "Masalah mengakses Ollama?",
|
||||
"TTS Model": "Model TTS",
|
||||
"TTS Settings": "Tetapan TTS",
|
||||
"TTS Voice": "Suara TTS",
|
||||
"Type": "jenis",
|
||||
"Type Hugging Face Resolve (Download) URL": "Taip URL 'Hugging Face Resolve (Download)'",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "Maaf! Terdapat masalah menyambung ke {{provider}}.",
|
||||
"UI": "UI",
|
||||
"Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "Jenis fail tidak diketahui '{{ file_type }}'. Teruskan dengan muat naik fail juga.",
|
||||
"Unpin": "Nyahsematkan",
|
||||
"Update": "Kemaskini",
|
||||
"Update and Copy Link": "Kemaskini dan salin pautan",
|
||||
"Update password": "Kemaskini Kata Laluan",
|
||||
"Updated at": "Dikemaskini pada",
|
||||
"Upload": "Muatnaik",
|
||||
"Upload a GGUF model": "Muatnaik model GGUF",
|
||||
"Upload Files": "Muatnaik fail",
|
||||
"Upload Pipeline": "Muatnaik 'Pipeline'",
|
||||
"Upload Progress": "Kemajuan Muatnaik",
|
||||
"URL Mode": "Mod URL",
|
||||
"Use '#' in the prompt input to load and select your documents.": "Gunakan '#' dalam input gesaan untuk memuatkan dan memilih dokumen anda",
|
||||
"Use Gravatar": "Gunakan Gravatar",
|
||||
"Use Initials": "Gunakan nama pendek",
|
||||
"use_mlock (Ollama)": "use_mlock (Ollama)",
|
||||
"use_mmap (Ollama)": "se_mmap (Ollama)",
|
||||
"user": "pengguna",
|
||||
"User location successfully retrieved.": "Lokasi pengguna berjaya diambil.",
|
||||
"User Permissions": "Kebenaran Pengguna",
|
||||
"Users": "Pengguna",
|
||||
"Utilize": "Gunakan",
|
||||
"Valid time units:": "Unit masa yang sah:",
|
||||
"Valves": "'Valves'",
|
||||
"Valves updated": "'Valves' dikemaskini",
|
||||
"Valves updated successfully": "'Valves' berjaya dikemaskini",
|
||||
"variable": "pembolehubah",
|
||||
"variable to have them replaced with clipboard content.": "pembolehubah untuk ia digantikan dengan kandungan papan klip.",
|
||||
"Version": "Versi",
|
||||
"Voice": "Suara",
|
||||
"Warning": "Amaran",
|
||||
"Warning:": "Amaran:",
|
||||
"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Amaran: Jika anda mengemas kini atau menukar model benam anda, anda perlu mengimport semula semua dokumen.",
|
||||
"Web": "Web",
|
||||
"Web API": "API Web",
|
||||
"Web Loader Settings": "Tetapan Pemuat Web",
|
||||
"Web Params": "Parameter Web",
|
||||
"Web Search": "Carian Web",
|
||||
"Web Search Engine": "Enjin Carian Web",
|
||||
"Webhook URL": "URL 'Webhook'",
|
||||
"WebUI Settings": "Tetapan WebUI",
|
||||
"WebUI will make requests to": "WebUI akan membuat permintaan kepada",
|
||||
"What’s New in": "Apakah yang terbaru dalam",
|
||||
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Apabila sejarah dimatikan, perbualan baharu pada pelayan web ini tidak akan muncul dalam sejarah pada mana-mana peranti anda.",
|
||||
"Whisper (Local)": "Whisper (Local)",
|
||||
"Widescreen Mode": "Mod Skrin Lebar",
|
||||
"Workspace": "Ruang Kerja",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "Tulis cadangan gesaan (cth Siapakah anda?)",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Tulis ringkasan dalam 50 patah perkataan yang meringkaskan [topik atau kata kunci].",
|
||||
"Yesterday": "Semalam",
|
||||
"You": "Anda",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Anda boleh memperibadikan interaksi anda dengan LLM dengan menambahkan memori melalui butang 'Urus' di bawah, menjadikannya lebih membantu dan disesuaikan dengan anda.",
|
||||
"You cannot clone a base model": "Anda tidak boleh mengklon model asas",
|
||||
"You have no archived conversations.": "Anda tidak mempunyai perbualan yang diarkibkan",
|
||||
"You have shared this chat": "Anda telah berkongsi perbualan ini",
|
||||
"You're a helpful assistant.": "Anda seorang pembantu yang bagus",
|
||||
"You're now logged in.": "Anda kini telah log masuk.",
|
||||
"Your account status is currently pending activation.": "Status akaun anda ialah sedang menunggu pengaktifan.",
|
||||
"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Seluruh sumbangan anda akan dihantar terus kepada pembangun 'plugin'; Open WebUI tidak mengambil sebarang peratusan keuntungan daripadanya. Walau bagaimanapun, platform pembiayaan yang dipilih mungkin mempunyai caj tersendiri.",
|
||||
"Youtube": "Youtube",
|
||||
"Youtube Loader Settings": "Tetapan Pemuat Youtube"
|
||||
}
|
||||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Skann",
|
||||
"Scan complete!": "Skanning fullført!",
|
||||
"Scan for documents from {{path}}": "Skann etter dokumenter fra {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Søk",
|
||||
"Search a model": "Søk en modell",
|
||||
"Search Chats": "Søk chatter",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Scan",
|
||||
"Scan complete!": "Scan voltooid!",
|
||||
"Scan for documents from {{path}}": "Scan voor documenten van {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Zoeken",
|
||||
"Search a model": "Zoek een model",
|
||||
"Search Chats": "Chats zoeken",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "ਸਕੈਨ ਕਰੋ",
|
||||
"Scan complete!": "ਸਕੈਨ ਪੂਰਾ!",
|
||||
"Scan for documents from {{path}}": "{{path}} ਤੋਂ ਡਾਕੂਮੈਂਟਾਂ ਲਈ ਸਕੈਨ ਕਰੋ",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "ਖੋਜ",
|
||||
"Search a model": "ਇੱਕ ਮਾਡਲ ਖੋਜੋ",
|
||||
"Search Chats": "ਖੋਜ ਚੈਟਾਂ",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Skanuj",
|
||||
"Scan complete!": "Skanowanie zakończone!",
|
||||
"Scan for documents from {{path}}": "Skanuj dokumenty z {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Szukaj",
|
||||
"Search a model": "Szukaj modelu",
|
||||
"Search Chats": "Szukaj w czatach",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Escanear",
|
||||
"Scan complete!": "Escaneamento concluído!",
|
||||
"Scan for documents from {{path}}": "Escanear documentos de {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Pesquisar",
|
||||
"Search a model": "Pesquisar um modelo",
|
||||
"Search Chats": "Pesquisar Chats",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Digitalizar",
|
||||
"Scan complete!": "Digitalização concluída!",
|
||||
"Scan for documents from {{path}}": "Digitalizar documentos de {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Pesquisar",
|
||||
"Search a model": "Pesquisar um modelo",
|
||||
"Search Chats": "Pesquisar Conversas",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Scanează",
|
||||
"Scan complete!": "Scanare completă!",
|
||||
"Scan for documents from {{path}}": "Scanează pentru documente din {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Caută",
|
||||
"Search a model": "Caută un model",
|
||||
"Search Chats": "Caută în Conversații",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Сканировать",
|
||||
"Scan complete!": "Сканирование завершено!",
|
||||
"Scan for documents from {{path}}": "Сканирование документов из {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Поиск",
|
||||
"Search a model": "Поиск модели",
|
||||
"Search Chats": "Поиск в чатах",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Скенирај",
|
||||
"Scan complete!": "Скенирање завршено!",
|
||||
"Scan for documents from {{path}}": "Скенирај документе из {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Претражи",
|
||||
"Search a model": "Претражи модел",
|
||||
"Search Chats": "Претражи ћаскања",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Skanna",
|
||||
"Scan complete!": "Skanning klar!",
|
||||
"Scan for documents from {{path}}": "Skanna efter dokument från {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Sök",
|
||||
"Search a model": "Sök efter en modell",
|
||||
"Search Chats": "Sök i chattar",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "สแกน",
|
||||
"Scan complete!": "การสแกนเสร็จสมบูรณ์!",
|
||||
"Scan for documents from {{path}}": "สแกนหาเอกสารจาก {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "ค้นหา",
|
||||
"Search a model": "ค้นหาโมเดล",
|
||||
"Search Chats": "ค้นหาแชท",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "",
|
||||
"Scan complete!": "",
|
||||
"Scan for documents from {{path}}": "",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "",
|
||||
"Search a model": "",
|
||||
"Search Chats": "",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Tarama",
|
||||
"Scan complete!": "Tarama tamamlandı!",
|
||||
"Scan for documents from {{path}}": "{{path}} dizininden belgeleri tarayın",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Ara",
|
||||
"Search a model": "Bir model ara",
|
||||
"Search Chats": "Sohbetleri Ara",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Сканування",
|
||||
"Scan complete!": "Сканування завершено!",
|
||||
"Scan for documents from {{path}}": "Сканування документів з {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Пошук",
|
||||
"Search a model": "Шукати модель",
|
||||
"Search Chats": "Пошук в чатах",
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "Quét tài liệu",
|
||||
"Scan complete!": "Quét hoàn tất!",
|
||||
"Scan for documents from {{path}}": "Quét tài liệu từ đường dẫn: {{path}}",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "Tìm kiếm",
|
||||
"Search a model": "Tìm model",
|
||||
"Search Chats": "Tìm kiếm các cuộc Chat",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"Account": "账号",
|
||||
"Account Activation Pending": "账号待激活",
|
||||
"Accurate information": "提供的信息很准确",
|
||||
"Actions": "",
|
||||
"Actions": "自动化",
|
||||
"Active Users": "当前在线用户",
|
||||
"Add": "添加",
|
||||
"Add a model id": "添加一个模型 ID",
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
"Add Memory": "添加记忆",
|
||||
"Add message": "添加消息",
|
||||
"Add Model": "添加模型",
|
||||
"Add Tag": "",
|
||||
"Add Tag": "添加标签",
|
||||
"Add Tags": "添加标签",
|
||||
"Add User": "添加用户",
|
||||
"Adjusting these settings will apply changes universally to all users.": "调整这些设置将会对所有用户应用更改。",
|
||||
|
|
@ -375,7 +375,7 @@
|
|||
"Memory deleted successfully": "记忆删除成功",
|
||||
"Memory updated successfully": "记忆更新成功",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "创建链接后发送的消息不会被共享。具有 URL 的用户将能够查看共享对话。",
|
||||
"Min P": "",
|
||||
"Min P": "Min P",
|
||||
"Minimum Score": "最低分",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
|
|
@ -509,6 +509,7 @@
|
|||
"Scan": "立即扫描",
|
||||
"Scan complete!": "扫描完成!",
|
||||
"Scan for documents from {{path}}": "从 {{path}} 扫描文档",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Search": "搜索",
|
||||
"Search a model": "搜索模型",
|
||||
"Search Chats": "搜索对话",
|
||||
|
|
@ -621,7 +622,7 @@
|
|||
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "请联系管理员以访问。管理员可以在后台管理面板中管理用户状态。",
|
||||
"To add documents here, upload them to the \"Documents\" workspace first.": "要在此处添加文档,请先将它们上传到工作空间中的“文档”内。",
|
||||
"to chat input.": "到对话输入。",
|
||||
"To select actions here, add them to the \"Functions\" workspace first.": "",
|
||||
"To select actions here, add them to the \"Functions\" workspace first.": "要在这里选择自动化,请先将它们添加到工作空间中的“函数”。",
|
||||
"To select filters here, add them to the \"Functions\" workspace first.": "要在这里选择过滤器,请先将它们添加到工作空间中的“函数”。",
|
||||
"To select toolkits here, add them to the \"Tools\" workspace first.": "要在这里选择工具包,请先将它们添加到工作空间中的“工具”。",
|
||||
"Today": "今天",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -42,6 +42,9 @@ export const showArchivedChats = writable(false);
|
|||
export const showChangelog = writable(false);
|
||||
export const showCallOverlay = writable(false);
|
||||
|
||||
export const scrollPaginationEnabled = writable(false);
|
||||
export const currentChatPage = writable(1);
|
||||
|
||||
export type Model = OpenAIModel | OllamaModel;
|
||||
|
||||
type BaseModel = {
|
||||
|
|
@ -149,6 +152,7 @@ type Config = {
|
|||
enable_web_search?: boolean;
|
||||
enable_image_generation: boolean;
|
||||
enable_admin_export: boolean;
|
||||
enable_admin_chat_access: boolean;
|
||||
enable_community_sharing: boolean;
|
||||
};
|
||||
oauth: {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,11 @@ export const revertSanitizedResponseContent = (content: string) => {
|
|||
return content.replaceAll('<', '<').replaceAll('>', '>');
|
||||
};
|
||||
|
||||
export function unescapeHtml(html: string) {
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
|
||||
export const capitalizeFirstLetter = (string) => {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@
|
|||
|
||||
<td class="px-3 py-2 text-right">
|
||||
<div class="flex justify-end w-full">
|
||||
{#if user.role !== 'admin'}
|
||||
{#if $config.features.enable_admin_chat_access && user.role !== 'admin'}
|
||||
<Tooltip content={$i18n.t('Chats')}>
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
|
|
|
|||
Loading…
Reference in a new issue