mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 12:25:20 +00:00
Merge remote-tracking branch 'origin/dev' into feat/google-oauth-groups-dev
# Conflicts: # backend/open_webui/utils/oauth.py
This commit is contained in:
commit
159ef78f6f
109 changed files with 1999 additions and 368 deletions
|
|
@ -583,14 +583,16 @@ OAUTH_ROLES_CLAIM = PersistentConfig(
|
|||
os.environ.get("OAUTH_ROLES_CLAIM", "roles"),
|
||||
)
|
||||
|
||||
SEP = os.environ.get("OAUTH_ROLES_SEPARATOR", ",")
|
||||
OAUTH_ROLES_SEPARATOR = os.environ.get("OAUTH_ROLES_SEPARATOR", ",")
|
||||
|
||||
OAUTH_ALLOWED_ROLES = PersistentConfig(
|
||||
"OAUTH_ALLOWED_ROLES",
|
||||
"oauth.allowed_roles",
|
||||
[
|
||||
role.strip()
|
||||
for role in os.environ.get("OAUTH_ALLOWED_ROLES", f"user{SEP}admin").split(SEP)
|
||||
for role in os.environ.get(
|
||||
"OAUTH_ALLOWED_ROLES", f"user{OAUTH_ROLES_SEPARATOR}admin"
|
||||
).split(OAUTH_ROLES_SEPARATOR)
|
||||
if role
|
||||
],
|
||||
)
|
||||
|
|
@ -600,7 +602,9 @@ OAUTH_ADMIN_ROLES = PersistentConfig(
|
|||
"oauth.admin_roles",
|
||||
[
|
||||
role.strip()
|
||||
for role in os.environ.get("OAUTH_ADMIN_ROLES", "admin").split(SEP)
|
||||
for role in os.environ.get("OAUTH_ADMIN_ROLES", "admin").split(
|
||||
OAUTH_ROLES_SEPARATOR
|
||||
)
|
||||
if role
|
||||
],
|
||||
)
|
||||
|
|
@ -1443,6 +1447,10 @@ USER_PERMISSIONS_FEATURES_CODE_INTERPRETER = (
|
|||
== "true"
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_FEATURES_FOLDERS = (
|
||||
os.environ.get("USER_PERMISSIONS_FEATURES_FOLDERS", "True").lower() == "true"
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_FEATURES_NOTES = (
|
||||
os.environ.get("USER_PERMISSIONS_FEATURES_NOTES", "True").lower() == "true"
|
||||
)
|
||||
|
|
@ -1499,12 +1507,15 @@ DEFAULT_USER_PERMISSIONS = {
|
|||
"temporary_enforced": USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED,
|
||||
},
|
||||
"features": {
|
||||
# General features
|
||||
"api_keys": USER_PERMISSIONS_FEATURES_API_KEYS,
|
||||
"folders": USER_PERMISSIONS_FEATURES_FOLDERS,
|
||||
"notes": USER_PERMISSIONS_FEATURES_NOTES,
|
||||
"direct_tool_servers": USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS,
|
||||
# Chat features
|
||||
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
|
||||
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
|
||||
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
|
||||
"notes": USER_PERMISSIONS_FEATURES_NOTES,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -1514,6 +1525,12 @@ USER_PERMISSIONS = PersistentConfig(
|
|||
DEFAULT_USER_PERMISSIONS,
|
||||
)
|
||||
|
||||
ENABLE_FOLDERS = PersistentConfig(
|
||||
"ENABLE_FOLDERS",
|
||||
"folders.enable",
|
||||
os.environ.get("ENABLE_FOLDERS", "True").lower() == "true",
|
||||
)
|
||||
|
||||
ENABLE_CHANNELS = PersistentConfig(
|
||||
"ENABLE_CHANNELS",
|
||||
"channels.enable",
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ from open_webui.utils import logger
|
|||
from open_webui.utils.audit import AuditLevel, AuditLoggingMiddleware
|
||||
from open_webui.utils.logger import start_logger
|
||||
from open_webui.socket.main import (
|
||||
MODELS,
|
||||
app as socket_app,
|
||||
periodic_usage_pool_cleanup,
|
||||
get_event_emitter,
|
||||
|
|
@ -352,6 +353,7 @@ from open_webui.config import (
|
|||
ENABLE_API_KEYS,
|
||||
ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS,
|
||||
API_KEYS_ALLOWED_ENDPOINTS,
|
||||
ENABLE_FOLDERS,
|
||||
ENABLE_CHANNELS,
|
||||
ENABLE_NOTES,
|
||||
ENABLE_COMMUNITY_SHARING,
|
||||
|
|
@ -767,6 +769,7 @@ app.state.config.WEBHOOK_URL = WEBHOOK_URL
|
|||
app.state.config.BANNERS = WEBUI_BANNERS
|
||||
|
||||
|
||||
app.state.config.ENABLE_FOLDERS = ENABLE_FOLDERS
|
||||
app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS
|
||||
app.state.config.ENABLE_NOTES = ENABLE_NOTES
|
||||
app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
|
||||
|
|
@ -1215,7 +1218,7 @@ app.state.config.VOICE_MODE_PROMPT_TEMPLATE = VOICE_MODE_PROMPT_TEMPLATE
|
|||
#
|
||||
########################################
|
||||
|
||||
app.state.MODELS = {}
|
||||
app.state.MODELS = MODELS
|
||||
|
||||
# Add the middleware to the app
|
||||
if ENABLE_COMPRESSION_MIDDLEWARE:
|
||||
|
|
@ -1575,6 +1578,7 @@ async def chat_completion(
|
|||
"user_id": user.id,
|
||||
"chat_id": form_data.pop("chat_id", None),
|
||||
"message_id": form_data.pop("id", None),
|
||||
"parent_message_id": form_data.pop("parent_id", None),
|
||||
"session_id": form_data.pop("session_id", None),
|
||||
"filter_ids": form_data.pop("filter_ids", []),
|
||||
"tool_ids": form_data.get("tool_ids", None),
|
||||
|
|
@ -1631,6 +1635,7 @@ async def chat_completion(
|
|||
metadata["chat_id"],
|
||||
metadata["message_id"],
|
||||
{
|
||||
"parentId": metadata.get("parent_message_id", None),
|
||||
"model": model_id,
|
||||
},
|
||||
)
|
||||
|
|
@ -1663,6 +1668,7 @@ async def chat_completion(
|
|||
metadata["chat_id"],
|
||||
metadata["message_id"],
|
||||
{
|
||||
"parentId": metadata.get("parent_message_id", None),
|
||||
"error": {"content": str(e)},
|
||||
},
|
||||
)
|
||||
|
|
@ -1842,6 +1848,7 @@ async def get_app_config(request: Request):
|
|||
**(
|
||||
{
|
||||
"enable_direct_connections": app.state.config.ENABLE_DIRECT_CONNECTIONS,
|
||||
"enable_folders": app.state.config.ENABLE_FOLDERS,
|
||||
"enable_channels": app.state.config.ENABLE_CHANNELS,
|
||||
"enable_notes": app.state.config.ENABLE_NOTES,
|
||||
"enable_web_search": app.state.config.ENABLE_WEB_SEARCH,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
"""Update messages and channel member table
|
||||
|
||||
Revision ID: 2f1211949ecc
|
||||
Revises: 37f288994c47
|
||||
Create Date: 2025-11-27 03:07:56.200231
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import open_webui.internal.db
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "2f1211949ecc"
|
||||
down_revision: Union[str, None] = "37f288994c47"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# New columns to be added to channel_member table
|
||||
op.add_column("channel_member", sa.Column("status", sa.Text(), nullable=True))
|
||||
op.add_column(
|
||||
"channel_member",
|
||||
sa.Column(
|
||||
"is_active",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
default=True,
|
||||
server_default=sa.sql.expression.true(),
|
||||
),
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"channel_member",
|
||||
sa.Column(
|
||||
"is_channel_muted",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
default=False,
|
||||
server_default=sa.sql.expression.false(),
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"channel_member",
|
||||
sa.Column(
|
||||
"is_channel_pinned",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
default=False,
|
||||
server_default=sa.sql.expression.false(),
|
||||
),
|
||||
)
|
||||
|
||||
op.add_column("channel_member", sa.Column("data", sa.JSON(), nullable=True))
|
||||
op.add_column("channel_member", sa.Column("meta", sa.JSON(), nullable=True))
|
||||
|
||||
op.add_column(
|
||||
"channel_member", sa.Column("joined_at", sa.BigInteger(), nullable=False)
|
||||
)
|
||||
op.add_column(
|
||||
"channel_member", sa.Column("left_at", sa.BigInteger(), nullable=True)
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"channel_member", sa.Column("last_read_at", sa.BigInteger(), nullable=True)
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"channel_member", sa.Column("updated_at", sa.BigInteger(), nullable=True)
|
||||
)
|
||||
|
||||
# New columns to be added to message table
|
||||
op.add_column(
|
||||
"message",
|
||||
sa.Column(
|
||||
"is_pinned",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
default=False,
|
||||
server_default=sa.sql.expression.false(),
|
||||
),
|
||||
)
|
||||
op.add_column("message", sa.Column("pinned_at", sa.BigInteger(), nullable=True))
|
||||
op.add_column("message", sa.Column("pinned_by", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("channel_member", "updated_at")
|
||||
op.drop_column("channel_member", "last_read_at")
|
||||
|
||||
op.drop_column("channel_member", "meta")
|
||||
op.drop_column("channel_member", "data")
|
||||
|
||||
op.drop_column("channel_member", "is_channel_pinned")
|
||||
op.drop_column("channel_member", "is_channel_muted")
|
||||
|
||||
op.drop_column("message", "pinned_by")
|
||||
op.drop_column("message", "pinned_at")
|
||||
op.drop_column("message", "is_pinned")
|
||||
|
|
@ -3,7 +3,7 @@ import uuid
|
|||
from typing import Optional
|
||||
|
||||
from open_webui.internal.db import Base, get_db
|
||||
from open_webui.models.users import UserModel, Users
|
||||
from open_webui.models.users import UserModel, UserProfileImageResponse, Users
|
||||
from open_webui.env import SRC_LOG_LEVELS
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import Boolean, Column, String, Text
|
||||
|
|
@ -46,15 +46,7 @@ class ApiKey(BaseModel):
|
|||
api_key: Optional[str] = None
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
name: str
|
||||
role: str
|
||||
profile_image_url: str
|
||||
|
||||
|
||||
class SigninResponse(Token, UserResponse):
|
||||
class SigninResponse(Token, UserProfileImageResponse):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from open_webui.internal.db import Base, get_db
|
|||
from open_webui.utils.access_control import has_access
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON
|
||||
from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON, case
|
||||
from sqlalchemy import or_, func, select, and_, text
|
||||
from sqlalchemy.sql import exists
|
||||
|
||||
|
|
@ -48,8 +48,58 @@ class ChannelModel(BaseModel):
|
|||
meta: Optional[dict] = None
|
||||
access_control: Optional[dict] = None
|
||||
|
||||
created_at: int # timestamp in epoch
|
||||
updated_at: int # timestamp in epoch
|
||||
created_at: int # timestamp in epoch (time_ns)
|
||||
updated_at: int # timestamp in epoch (time_ns)
|
||||
|
||||
|
||||
class ChannelMember(Base):
|
||||
__tablename__ = "channel_member"
|
||||
|
||||
id = Column(Text, primary_key=True, unique=True)
|
||||
channel_id = Column(Text, nullable=False)
|
||||
user_id = Column(Text, nullable=False)
|
||||
|
||||
status = Column(Text, nullable=True)
|
||||
is_active = Column(Boolean, nullable=False, default=True)
|
||||
|
||||
is_channel_muted = Column(Boolean, nullable=False, default=False)
|
||||
is_channel_pinned = Column(Boolean, nullable=False, default=False)
|
||||
|
||||
data = Column(JSON, nullable=True)
|
||||
meta = Column(JSON, nullable=True)
|
||||
|
||||
joined_at = Column(BigInteger)
|
||||
left_at = Column(BigInteger, nullable=True)
|
||||
|
||||
last_read_at = Column(BigInteger, nullable=True)
|
||||
|
||||
created_at = Column(BigInteger)
|
||||
updated_at = Column(BigInteger)
|
||||
|
||||
|
||||
class ChannelMemberModel(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: str
|
||||
channel_id: str
|
||||
user_id: str
|
||||
|
||||
status: Optional[str] = None
|
||||
is_active: bool = True
|
||||
|
||||
is_channel_muted: bool = False
|
||||
is_channel_pinned: bool = False
|
||||
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
|
||||
joined_at: Optional[int] = None # timestamp in epoch (time_ns)
|
||||
left_at: Optional[int] = None # timestamp in epoch (time_ns)
|
||||
|
||||
last_read_at: Optional[int] = None # timestamp in epoch (time_ns)
|
||||
|
||||
created_at: Optional[int] = None # timestamp in epoch (time_ns)
|
||||
updated_at: Optional[int] = None # timestamp in epoch (time_ns)
|
||||
|
||||
|
||||
####################
|
||||
|
|
@ -63,22 +113,24 @@ class ChannelResponse(ChannelModel):
|
|||
|
||||
|
||||
class ChannelForm(BaseModel):
|
||||
type: Optional[str] = None
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
access_control: Optional[dict] = None
|
||||
user_ids: Optional[list[str]] = None
|
||||
|
||||
|
||||
class ChannelTable:
|
||||
def insert_new_channel(
|
||||
self, type: Optional[str], form_data: ChannelForm, user_id: str
|
||||
self, form_data: ChannelForm, user_id: str
|
||||
) -> Optional[ChannelModel]:
|
||||
with get_db() as db:
|
||||
channel = ChannelModel(
|
||||
**{
|
||||
**form_data.model_dump(),
|
||||
"type": type,
|
||||
"type": form_data.type if form_data.type else None,
|
||||
"name": form_data.name.lower(),
|
||||
"id": str(uuid.uuid4()),
|
||||
"user_id": user_id,
|
||||
|
|
@ -86,9 +138,34 @@ class ChannelTable:
|
|||
"updated_at": int(time.time_ns()),
|
||||
}
|
||||
)
|
||||
|
||||
new_channel = Channel(**channel.model_dump())
|
||||
|
||||
if form_data.type == "dm":
|
||||
# For direct message channels, automatically add the specified users as members
|
||||
user_ids = form_data.user_ids or []
|
||||
if user_id not in user_ids:
|
||||
user_ids.append(user_id) # Ensure the creator is also a member
|
||||
|
||||
for uid in user_ids:
|
||||
channel_member = ChannelMemberModel(
|
||||
**{
|
||||
"id": str(uuid.uuid4()),
|
||||
"channel_id": channel.id,
|
||||
"user_id": uid,
|
||||
"status": "joined",
|
||||
"is_active": True,
|
||||
"is_channel_muted": False,
|
||||
"is_channel_pinned": False,
|
||||
"joined_at": int(time.time_ns()),
|
||||
"left_at": None,
|
||||
"last_read_at": int(time.time_ns()),
|
||||
"created_at": int(time.time_ns()),
|
||||
"updated_at": int(time.time_ns()),
|
||||
}
|
||||
)
|
||||
new_membership = ChannelMember(**channel_member.model_dump())
|
||||
db.add(new_membership)
|
||||
|
||||
db.add(new_channel)
|
||||
db.commit()
|
||||
return channel
|
||||
|
|
@ -102,13 +179,211 @@ class ChannelTable:
|
|||
self, user_id: str, permission: str = "read"
|
||||
) -> list[ChannelModel]:
|
||||
channels = self.get_channels()
|
||||
|
||||
channel_list = []
|
||||
for channel in channels:
|
||||
if channel.type == "dm":
|
||||
membership = self.get_member_by_channel_and_user_id(channel.id, user_id)
|
||||
if membership and membership.is_active:
|
||||
channel_list.append(channel)
|
||||
else:
|
||||
if channel.user_id == user_id or has_access(
|
||||
user_id, permission, channel.access_control
|
||||
):
|
||||
channel_list.append(channel)
|
||||
|
||||
return channel_list
|
||||
|
||||
def get_dm_channel_by_user_ids(self, user_ids: list[str]) -> Optional[ChannelModel]:
|
||||
with get_db() as db:
|
||||
# Ensure uniqueness in case a list with duplicates is passed
|
||||
unique_user_ids = list(set(user_ids))
|
||||
|
||||
match_count = func.sum(
|
||||
case(
|
||||
(ChannelMember.user_id.in_(unique_user_ids), 1),
|
||||
else_=0,
|
||||
)
|
||||
)
|
||||
|
||||
subquery = (
|
||||
db.query(ChannelMember.channel_id)
|
||||
.group_by(ChannelMember.channel_id)
|
||||
# 1. Channel must have exactly len(user_ids) members
|
||||
.having(func.count(ChannelMember.user_id) == len(unique_user_ids))
|
||||
# 2. All those members must be in unique_user_ids
|
||||
.having(match_count == len(unique_user_ids))
|
||||
.subquery()
|
||||
)
|
||||
|
||||
channel = (
|
||||
db.query(Channel)
|
||||
.filter(
|
||||
Channel.id.in_(subquery),
|
||||
Channel.type == "dm",
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
return ChannelModel.model_validate(channel) if channel else None
|
||||
|
||||
def join_channel(
|
||||
self, channel_id: str, user_id: str
|
||||
) -> Optional[ChannelMemberModel]:
|
||||
with get_db() as db:
|
||||
# Check if the membership already exists
|
||||
existing_membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if existing_membership:
|
||||
return ChannelMemberModel.model_validate(existing_membership)
|
||||
|
||||
# Create new membership
|
||||
channel_member = ChannelMemberModel(
|
||||
**{
|
||||
"id": str(uuid.uuid4()),
|
||||
"channel_id": channel_id,
|
||||
"user_id": user_id,
|
||||
"status": "joined",
|
||||
"is_active": True,
|
||||
"is_channel_muted": False,
|
||||
"is_channel_pinned": False,
|
||||
"joined_at": int(time.time_ns()),
|
||||
"left_at": None,
|
||||
"last_read_at": int(time.time_ns()),
|
||||
"created_at": int(time.time_ns()),
|
||||
"updated_at": int(time.time_ns()),
|
||||
}
|
||||
)
|
||||
new_membership = ChannelMember(**channel_member.model_dump())
|
||||
|
||||
db.add(new_membership)
|
||||
db.commit()
|
||||
return channel_member
|
||||
|
||||
def leave_channel(self, channel_id: str, user_id: str) -> bool:
|
||||
with get_db() as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not membership:
|
||||
return False
|
||||
|
||||
membership.status = "left"
|
||||
membership.is_active = False
|
||||
membership.left_at = int(time.time_ns())
|
||||
membership.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def get_member_by_channel_and_user_id(
|
||||
self, channel_id: str, user_id: str
|
||||
) -> Optional[ChannelMemberModel]:
|
||||
with get_db() as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
return ChannelMemberModel.model_validate(membership) if membership else None
|
||||
|
||||
def get_members_by_channel_id(self, channel_id: str) -> list[ChannelMemberModel]:
|
||||
with get_db() as db:
|
||||
memberships = (
|
||||
db.query(ChannelMember)
|
||||
.filter(ChannelMember.channel_id == channel_id)
|
||||
.all()
|
||||
)
|
||||
return [
|
||||
channel
|
||||
for channel in channels
|
||||
if channel.user_id == user_id
|
||||
or has_access(user_id, permission, channel.access_control)
|
||||
ChannelMemberModel.model_validate(membership)
|
||||
for membership in memberships
|
||||
]
|
||||
|
||||
def pin_channel(self, channel_id: str, user_id: str, is_pinned: bool) -> bool:
|
||||
with get_db() as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not membership:
|
||||
return False
|
||||
|
||||
membership.is_channel_pinned = is_pinned
|
||||
membership.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def update_member_last_read_at(self, channel_id: str, user_id: str) -> bool:
|
||||
with get_db() as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not membership:
|
||||
return False
|
||||
|
||||
membership.last_read_at = int(time.time_ns())
|
||||
membership.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def update_member_active_status(
|
||||
self, channel_id: str, user_id: str, is_active: bool
|
||||
) -> bool:
|
||||
with get_db() as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not membership:
|
||||
return False
|
||||
|
||||
membership.is_active = is_active
|
||||
membership.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def is_user_channel_member(self, channel_id: str, user_id: str) -> bool:
|
||||
with get_db() as db:
|
||||
membership = (
|
||||
db.query(ChannelMember)
|
||||
.filter(
|
||||
ChannelMember.channel_id == channel_id,
|
||||
ChannelMember.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
return membership is not None
|
||||
|
||||
def get_channel_by_id(self, id: str) -> Optional[ChannelModel]:
|
||||
with get_db() as db:
|
||||
channel = db.query(Channel).filter(Channel.id == id).first()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from typing import Optional
|
|||
from open_webui.internal.db import Base, get_db
|
||||
from open_webui.models.tags import TagModel, Tag, Tags
|
||||
from open_webui.models.users import Users, UserNameResponse
|
||||
from open_webui.models.channels import Channels, ChannelMember
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
|
@ -47,6 +48,11 @@ class Message(Base):
|
|||
reply_to_id = Column(Text, nullable=True)
|
||||
parent_id = Column(Text, nullable=True)
|
||||
|
||||
# Pins
|
||||
is_pinned = Column(Boolean, nullable=False, default=False)
|
||||
pinned_at = Column(BigInteger, nullable=True)
|
||||
pinned_by = Column(Text, nullable=True)
|
||||
|
||||
content = Column(Text)
|
||||
data = Column(JSON, nullable=True)
|
||||
meta = Column(JSON, nullable=True)
|
||||
|
|
@ -65,12 +71,17 @@ class MessageModel(BaseModel):
|
|||
reply_to_id: Optional[str] = None
|
||||
parent_id: Optional[str] = None
|
||||
|
||||
# Pins
|
||||
is_pinned: bool = False
|
||||
pinned_by: Optional[str] = None
|
||||
pinned_at: Optional[int] = None # timestamp in epoch (time_ns)
|
||||
|
||||
content: str
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
|
||||
created_at: int # timestamp in epoch
|
||||
updated_at: int # timestamp in epoch
|
||||
created_at: int # timestamp in epoch (time_ns)
|
||||
updated_at: int # timestamp in epoch (time_ns)
|
||||
|
||||
|
||||
####################
|
||||
|
|
@ -111,9 +122,11 @@ class MessageTable:
|
|||
self, form_data: MessageForm, channel_id: str, user_id: str
|
||||
) -> Optional[MessageModel]:
|
||||
with get_db() as db:
|
||||
id = str(uuid.uuid4())
|
||||
channel_member = Channels.join_channel(channel_id, user_id)
|
||||
|
||||
id = str(uuid.uuid4())
|
||||
ts = int(time.time_ns())
|
||||
|
||||
message = MessageModel(
|
||||
**{
|
||||
"id": id,
|
||||
|
|
@ -121,6 +134,9 @@ class MessageTable:
|
|||
"channel_id": channel_id,
|
||||
"reply_to_id": form_data.reply_to_id,
|
||||
"parent_id": form_data.parent_id,
|
||||
"is_pinned": False,
|
||||
"pinned_at": None,
|
||||
"pinned_by": None,
|
||||
"content": form_data.content,
|
||||
"data": form_data.data,
|
||||
"meta": form_data.meta,
|
||||
|
|
@ -128,8 +144,8 @@ class MessageTable:
|
|||
"updated_at": ts,
|
||||
}
|
||||
)
|
||||
|
||||
result = Message(**message.model_dump())
|
||||
|
||||
db.add(result)
|
||||
db.commit()
|
||||
db.refresh(result)
|
||||
|
|
@ -280,6 +296,16 @@ class MessageTable:
|
|||
)
|
||||
return messages
|
||||
|
||||
def get_last_message_by_channel_id(self, channel_id: str) -> Optional[MessageModel]:
|
||||
with get_db() as db:
|
||||
message = (
|
||||
db.query(Message)
|
||||
.filter_by(channel_id=channel_id)
|
||||
.order_by(Message.created_at.desc())
|
||||
.first()
|
||||
)
|
||||
return MessageModel.model_validate(message) if message else None
|
||||
|
||||
def update_message_by_id(
|
||||
self, id: str, form_data: MessageForm
|
||||
) -> Optional[MessageModel]:
|
||||
|
|
@ -299,6 +325,32 @@ class MessageTable:
|
|||
db.refresh(message)
|
||||
return MessageModel.model_validate(message) if message else None
|
||||
|
||||
def update_message_pin_by_id(
|
||||
self, id: str, is_pinned: bool, pinned_by: Optional[str] = None
|
||||
) -> Optional[MessageModel]:
|
||||
with get_db() as db:
|
||||
message = db.get(Message, id)
|
||||
message.is_pinned = is_pinned
|
||||
message.pinned_at = int(time.time_ns()) if is_pinned else None
|
||||
message.pinned_by = pinned_by if is_pinned else None
|
||||
message.updated_at = int(time.time_ns())
|
||||
db.commit()
|
||||
db.refresh(message)
|
||||
return MessageModel.model_validate(message) if message else None
|
||||
|
||||
def get_unread_message_count(
|
||||
self, channel_id: str, user_id: str, last_read_at: Optional[int] = None
|
||||
) -> int:
|
||||
with get_db() as db:
|
||||
query = db.query(Message).filter(
|
||||
Message.channel_id == channel_id,
|
||||
Message.parent_id == None, # only count top-level messages
|
||||
Message.created_at > (last_read_at if last_read_at else 0),
|
||||
)
|
||||
if user_id:
|
||||
query = query.filter(Message.user_id != user_id)
|
||||
return query.count()
|
||||
|
||||
def add_reaction_to_message(
|
||||
self, id: str, user_id: str, name: str
|
||||
) -> Optional[MessageReactionModel]:
|
||||
|
|
|
|||
|
|
@ -220,6 +220,34 @@ class ModelsTable:
|
|||
or has_access(user_id, permission, model.access_control, user_group_ids)
|
||||
]
|
||||
|
||||
def _has_write_permission(self, query, filter: dict):
|
||||
if filter.get("group_ids") or filter.get("user_id"):
|
||||
conditions = []
|
||||
|
||||
# --- ANY group_ids match ("write".group_ids) ---
|
||||
if filter.get("group_ids"):
|
||||
group_ids = filter["group_ids"]
|
||||
like_clauses = []
|
||||
|
||||
for gid in group_ids:
|
||||
like_clauses.append(
|
||||
cast(Model.access_control, String).like(
|
||||
f'%"write"%"group_ids"%"{gid}"%'
|
||||
)
|
||||
)
|
||||
|
||||
# ANY → OR
|
||||
conditions.append(or_(*like_clauses))
|
||||
|
||||
# --- user_id match (owner) ---
|
||||
if filter.get("user_id"):
|
||||
conditions.append(Model.user_id == filter["user_id"])
|
||||
|
||||
# Apply OR across the two groups of conditions
|
||||
query = query.filter(or_(*conditions))
|
||||
|
||||
return query
|
||||
|
||||
def search_models(
|
||||
self, user_id: str, filter: dict = {}, skip: int = 0, limit: int = 30
|
||||
) -> ModelListResponse:
|
||||
|
|
@ -238,11 +266,10 @@ class ModelsTable:
|
|||
)
|
||||
)
|
||||
|
||||
if filter.get("user_id"):
|
||||
query = query.filter(Model.user_id == filter.get("user_id"))
|
||||
# Apply access control filtering
|
||||
query = self._has_write_permission(query, filter)
|
||||
|
||||
view_option = filter.get("view_option")
|
||||
|
||||
if view_option == "created":
|
||||
query = query.filter(Model.user_id == user_id)
|
||||
elif view_option == "shared":
|
||||
|
|
|
|||
|
|
@ -135,18 +135,18 @@ class UserIdNameListResponse(BaseModel):
|
|||
total: int
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
email: str
|
||||
role: str
|
||||
profile_image_url: str
|
||||
|
||||
|
||||
class UserNameResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
role: str
|
||||
|
||||
|
||||
class UserResponse(UserNameResponse):
|
||||
email: str
|
||||
|
||||
|
||||
class UserProfileImageResponse(UserNameResponse):
|
||||
email: str
|
||||
profile_image_url: str
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ from open_webui.models.auths import (
|
|||
SigninResponse,
|
||||
SignupForm,
|
||||
UpdatePasswordForm,
|
||||
UserResponse,
|
||||
)
|
||||
from open_webui.models.users import Users, UpdateProfileForm
|
||||
from open_webui.models.users import UserProfileImageResponse, Users, UpdateProfileForm
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.oauth_sessions import OAuthSessions
|
||||
|
||||
|
|
@ -78,7 +77,7 @@ log.setLevel(SRC_LOG_LEVELS["MAIN"])
|
|||
############################
|
||||
|
||||
|
||||
class SessionUserResponse(Token, UserResponse):
|
||||
class SessionUserResponse(Token, UserProfileImageResponse):
|
||||
expires_at: Optional[int] = None
|
||||
permissions: Optional[dict] = None
|
||||
|
||||
|
|
@ -149,7 +148,7 @@ async def get_session_user(
|
|||
############################
|
||||
|
||||
|
||||
@router.post("/update/profile", response_model=UserResponse)
|
||||
@router.post("/update/profile", response_model=UserProfileImageResponse)
|
||||
async def update_profile(
|
||||
form_data: UpdateProfileForm, session_user=Depends(get_verified_user)
|
||||
):
|
||||
|
|
@ -901,6 +900,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
|
|||
"JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
|
||||
"ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
|
||||
"ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
|
||||
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
|
||||
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
|
||||
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
|
||||
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
|
||||
|
|
@ -922,6 +922,7 @@ class AdminConfig(BaseModel):
|
|||
JWT_EXPIRES_IN: str
|
||||
ENABLE_COMMUNITY_SHARING: bool
|
||||
ENABLE_MESSAGE_RATING: bool
|
||||
ENABLE_FOLDERS: bool
|
||||
ENABLE_CHANNELS: bool
|
||||
ENABLE_NOTES: bool
|
||||
ENABLE_USER_WEBHOOKS: bool
|
||||
|
|
@ -946,6 +947,7 @@ async def update_admin_config(
|
|||
form_data.API_KEYS_ALLOWED_ENDPOINTS
|
||||
)
|
||||
|
||||
request.app.state.config.ENABLE_FOLDERS = form_data.ENABLE_FOLDERS
|
||||
request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS
|
||||
request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES
|
||||
|
||||
|
|
@ -988,6 +990,7 @@ async def update_admin_config(
|
|||
"JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
|
||||
"ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
|
||||
"ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
|
||||
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
|
||||
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
|
||||
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
|
||||
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from open_webui.socket.main import (
|
|||
get_active_status_by_user_id,
|
||||
)
|
||||
from open_webui.models.users import (
|
||||
UserIdNameResponse,
|
||||
UserListResponse,
|
||||
UserModelResponse,
|
||||
Users,
|
||||
|
|
@ -65,9 +66,52 @@ router = APIRouter()
|
|||
############################
|
||||
|
||||
|
||||
@router.get("/", response_model=list[ChannelModel])
|
||||
class ChannelListItemResponse(ChannelModel):
|
||||
user_ids: Optional[list[str]] = None # 'dm' channels only
|
||||
users: Optional[list[UserIdNameResponse]] = None # 'dm' channels only
|
||||
|
||||
last_message_at: Optional[int] = None # timestamp in epoch (time_ns)
|
||||
unread_count: int = 0
|
||||
|
||||
|
||||
@router.get("/", response_model=list[ChannelListItemResponse])
|
||||
async def get_channels(user=Depends(get_verified_user)):
|
||||
return Channels.get_channels_by_user_id(user.id)
|
||||
|
||||
channels = Channels.get_channels_by_user_id(user.id)
|
||||
|
||||
channel_list = []
|
||||
for channel in channels:
|
||||
last_message = Messages.get_last_message_by_channel_id(channel.id)
|
||||
last_message_at = last_message.created_at if last_message else None
|
||||
|
||||
channel_member = Channels.get_member_by_channel_and_user_id(channel.id, user.id)
|
||||
unread_count = Messages.get_unread_message_count(
|
||||
channel.id, user.id, channel_member.last_read_at if channel_member else None
|
||||
)
|
||||
|
||||
user_ids = None
|
||||
users = None
|
||||
if channel.type == "dm":
|
||||
user_ids = [
|
||||
member.user_id
|
||||
for member in Channels.get_members_by_channel_id(channel.id)
|
||||
]
|
||||
users = [
|
||||
UserIdNameResponse(**user.model_dump())
|
||||
for user in Users.get_users_by_user_ids(user_ids)
|
||||
]
|
||||
|
||||
channel_list.append(
|
||||
ChannelListItemResponse(
|
||||
**channel.model_dump(),
|
||||
user_ids=user_ids,
|
||||
users=users,
|
||||
last_message_at=last_message_at,
|
||||
unread_count=unread_count,
|
||||
)
|
||||
)
|
||||
|
||||
return channel_list
|
||||
|
||||
|
||||
@router.get("/list", response_model=list[ChannelModel])
|
||||
|
|
@ -85,7 +129,15 @@ async def get_all_channels(user=Depends(get_verified_user)):
|
|||
@router.post("/create", response_model=Optional[ChannelModel])
|
||||
async def create_new_channel(form_data: ChannelForm, user=Depends(get_admin_user)):
|
||||
try:
|
||||
channel = Channels.insert_new_channel(None, form_data, user.id)
|
||||
if form_data.type == "dm":
|
||||
existing_channel = Channels.get_dm_channel_by_user_ids(
|
||||
[user.id, *form_data.user_ids]
|
||||
)
|
||||
if existing_channel:
|
||||
Channels.update_member_active_status(existing_channel.id, user.id, True)
|
||||
return ChannelModel(**existing_channel.model_dump())
|
||||
|
||||
channel = Channels.insert_new_channel(form_data, user.id)
|
||||
return ChannelModel(**channel.model_dump())
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
|
|
@ -99,7 +151,15 @@ async def create_new_channel(form_data: ChannelForm, user=Depends(get_admin_user
|
|||
############################
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=Optional[ChannelResponse])
|
||||
class ChannelFullResponse(ChannelResponse):
|
||||
user_ids: Optional[list[str]] = None # 'dm' channels only
|
||||
users: Optional[list[UserIdNameResponse]] = None # 'dm' channels only
|
||||
|
||||
last_read_at: Optional[int] = None # timestamp in epoch (time_ns)
|
||||
unread_count: int = 0
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=Optional[ChannelFullResponse])
|
||||
async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
|
||||
channel = Channels.get_channel_by_id(id)
|
||||
if not channel:
|
||||
|
|
@ -107,6 +167,41 @@ async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
|
|||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
user_ids = None
|
||||
users = None
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
user_ids = [
|
||||
member.user_id for member in Channels.get_members_by_channel_id(channel.id)
|
||||
]
|
||||
users = [
|
||||
UserIdNameResponse(**user.model_dump())
|
||||
for user in Users.get_users_by_user_ids(user_ids)
|
||||
]
|
||||
|
||||
channel_member = Channels.get_member_by_channel_and_user_id(channel.id, user.id)
|
||||
unread_count = Messages.get_unread_message_count(
|
||||
channel.id, user.id, channel_member.last_read_at if channel_member else None
|
||||
)
|
||||
|
||||
return ChannelFullResponse(
|
||||
**{
|
||||
**channel.model_dump(),
|
||||
"user_ids": user_ids,
|
||||
"users": users,
|
||||
"write_access": True,
|
||||
"user_count": len(user_ids),
|
||||
"last_read_at": channel_member.last_read_at if channel_member else None,
|
||||
"unread_count": unread_count,
|
||||
}
|
||||
)
|
||||
|
||||
else:
|
||||
if user.role != "admin" and not has_access(
|
||||
user.id, type="read", access_control=channel.access_control
|
||||
):
|
||||
|
|
@ -120,20 +215,34 @@ async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
|
|||
|
||||
user_count = len(get_users_with_access("read", channel.access_control))
|
||||
|
||||
return ChannelResponse(
|
||||
channel_member = Channels.get_member_by_channel_and_user_id(channel.id, user.id)
|
||||
unread_count = Messages.get_unread_message_count(
|
||||
channel.id, user.id, channel_member.last_read_at if channel_member else None
|
||||
)
|
||||
|
||||
return ChannelFullResponse(
|
||||
**{
|
||||
**channel.model_dump(),
|
||||
"user_ids": user_ids,
|
||||
"users": users,
|
||||
"write_access": write_access or user.role == "admin",
|
||||
"user_count": user_count,
|
||||
"last_read_at": channel_member.last_read_at if channel_member else None,
|
||||
"unread_count": unread_count,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
# GetChannelMembersById
|
||||
############################
|
||||
|
||||
|
||||
PAGE_ITEM_COUNT = 30
|
||||
|
||||
|
||||
@router.get("/{id}/users", response_model=UserListResponse)
|
||||
async def get_channel_users_by_id(
|
||||
@router.get("/{id}/members", response_model=UserListResponse)
|
||||
async def get_channel_members_by_id(
|
||||
id: str,
|
||||
query: Optional[str] = None,
|
||||
order_by: Optional[str] = None,
|
||||
|
|
@ -153,6 +262,30 @@ async def get_channel_users_by_id(
|
|||
page = max(1, page)
|
||||
skip = (page - 1) * limit
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
user_ids = [
|
||||
member.user_id for member in Channels.get_members_by_channel_id(channel.id)
|
||||
]
|
||||
users = Users.get_users_by_user_ids(user_ids)
|
||||
|
||||
total = len(users)
|
||||
|
||||
return {
|
||||
"users": [
|
||||
UserModelResponse(
|
||||
**user.model_dump(), is_active=get_active_status_by_user_id(user.id)
|
||||
)
|
||||
for user in users
|
||||
],
|
||||
"total": total,
|
||||
}
|
||||
|
||||
else:
|
||||
filter = {
|
||||
"roles": ["!pending"],
|
||||
}
|
||||
|
|
@ -185,6 +318,36 @@ async def get_channel_users_by_id(
|
|||
}
|
||||
|
||||
|
||||
#################################################
|
||||
# UpdateIsActiveMemberByIdAndUserId
|
||||
#################################################
|
||||
|
||||
|
||||
class UpdateActiveMemberForm(BaseModel):
|
||||
is_active: bool
|
||||
|
||||
|
||||
@router.post("/{id}/members/active", response_model=bool)
|
||||
async def update_is_active_member_by_id_and_user_id(
|
||||
id: str,
|
||||
form_data: UpdateActiveMemberForm,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
channel = Channels.get_channel_by_id(id)
|
||||
if not channel:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
Channels.update_member_active_status(channel.id, user.id, form_data.is_active)
|
||||
return True
|
||||
|
||||
|
||||
############################
|
||||
# UpdateChannelById
|
||||
############################
|
||||
|
|
@ -252,6 +415,12 @@ async def get_channel_messages(
|
|||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
else:
|
||||
if user.role != "admin" and not has_access(
|
||||
user.id, type="read", access_control=channel.access_control
|
||||
):
|
||||
|
|
@ -259,6 +428,10 @@ async def get_channel_messages(
|
|||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
channel_member = Channels.join_channel(
|
||||
id, user.id
|
||||
) # Ensure user is a member of the channel
|
||||
|
||||
message_list = Messages.get_messages_by_channel_id(id, skip, limit)
|
||||
users = {}
|
||||
|
||||
|
|
@ -297,7 +470,9 @@ async def send_notification(name, webui_url, channel, message, active_user_ids):
|
|||
users = get_users_with_access("read", channel.access_control)
|
||||
|
||||
for user in users:
|
||||
if user.id not in active_user_ids:
|
||||
if (user.id not in active_user_ids) and Channels.is_user_channel_member(
|
||||
channel.id, user.id
|
||||
):
|
||||
if user.settings:
|
||||
webhook_url = user.settings.ui.get("notifications", {}).get(
|
||||
"webhook_url", None
|
||||
|
|
@ -501,6 +676,12 @@ async def new_message_handler(
|
|||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
else:
|
||||
if user.role != "admin" and not has_access(
|
||||
user.id, type="write", access_control=channel.access_control, strict=False
|
||||
):
|
||||
|
|
@ -511,6 +692,14 @@ async def new_message_handler(
|
|||
try:
|
||||
message = Messages.insert_new_message(form_data, channel.id, user.id)
|
||||
if message:
|
||||
if channel.type == "dm":
|
||||
members = Channels.get_members_by_channel_id(channel.id)
|
||||
for member in members:
|
||||
if not member.is_active:
|
||||
Channels.update_member_active_status(
|
||||
channel.id, member.user_id, True
|
||||
)
|
||||
|
||||
message = Messages.get_message_by_id(message.id)
|
||||
event_data = {
|
||||
"channel_id": channel.id,
|
||||
|
|
@ -609,6 +798,12 @@ async def get_channel_message(
|
|||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
else:
|
||||
if user.role != "admin" and not has_access(
|
||||
user.id, type="read", access_control=channel.access_control
|
||||
):
|
||||
|
|
@ -658,6 +853,12 @@ async def get_channel_thread_messages(
|
|||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
else:
|
||||
if user.role != "admin" and not has_access(
|
||||
user.id, type="read", access_control=channel.access_control
|
||||
):
|
||||
|
|
@ -717,10 +918,18 @@ async def update_message_by_id(
|
|||
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
else:
|
||||
if (
|
||||
user.role != "admin"
|
||||
and message.user_id != user.id
|
||||
and not has_access(user.id, type="read", access_control=channel.access_control)
|
||||
and not has_access(
|
||||
user.id, type="read", access_control=channel.access_control
|
||||
)
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
|
|
@ -773,6 +982,12 @@ async def add_reaction_to_message(
|
|||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
else:
|
||||
if user.role != "admin" and not has_access(
|
||||
user.id, type="write", access_control=channel.access_control, strict=False
|
||||
):
|
||||
|
|
@ -836,6 +1051,12 @@ async def remove_reaction_by_id_and_user_id_and_name(
|
|||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
else:
|
||||
if user.role != "admin" and not has_access(
|
||||
user.id, type="write", access_control=channel.access_control, strict=False
|
||||
):
|
||||
|
|
@ -913,11 +1134,20 @@ async def delete_message_by_id(
|
|||
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
if channel.type == "dm":
|
||||
if not Channels.is_user_channel_member(channel.id, user.id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
else:
|
||||
if (
|
||||
user.role != "admin"
|
||||
and message.user_id != user.id
|
||||
and not has_access(
|
||||
user.id, type="write", access_control=channel.access_control, strict=False
|
||||
user.id,
|
||||
type="write",
|
||||
access_control=channel.access_control,
|
||||
strict=False,
|
||||
)
|
||||
):
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -46,7 +46,23 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.get("/", response_model=list[FolderNameIdResponse])
|
||||
async def get_folders(user=Depends(get_verified_user)):
|
||||
async def get_folders(request: Request, user=Depends(get_verified_user)):
|
||||
if request.app.state.config.ENABLE_FOLDERS is False:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
if user.role != "admin" and not has_permission(
|
||||
user.id,
|
||||
"features.folders",
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
folders = Folders.get_folders_by_user_id(user.id)
|
||||
|
||||
# Verify folder data integrity
|
||||
|
|
|
|||
|
|
@ -106,6 +106,32 @@ async def get_group_by_id(id: str, user=Depends(get_admin_user)):
|
|||
)
|
||||
|
||||
|
||||
############################
|
||||
# ExportGroupById
|
||||
############################
|
||||
|
||||
|
||||
class GroupExportResponse(GroupResponse):
|
||||
user_ids: list[str] = []
|
||||
pass
|
||||
|
||||
|
||||
@router.get("/id/{id}/export", response_model=Optional[GroupExportResponse])
|
||||
async def export_group_by_id(id: str, user=Depends(get_admin_user)):
|
||||
group = Groups.get_group_by_id(id)
|
||||
if group:
|
||||
return GroupExportResponse(
|
||||
**group.model_dump(),
|
||||
member_count=Groups.get_group_member_count_by_id(group.id),
|
||||
user_ids=Groups.get_group_user_ids_by_id(group.id),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
# UpdateGroupById
|
||||
############################
|
||||
|
|
|
|||
|
|
@ -708,7 +708,7 @@ async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user)):
|
|||
|
||||
|
||||
@router.post("/{id}/files/batch/add", response_model=Optional[KnowledgeFilesResponse])
|
||||
def add_files_to_knowledge_batch(
|
||||
async def add_files_to_knowledge_batch(
|
||||
request: Request,
|
||||
id: str,
|
||||
form_data: list[KnowledgeFileIdForm],
|
||||
|
|
@ -748,7 +748,7 @@ def add_files_to_knowledge_batch(
|
|||
|
||||
# Process files
|
||||
try:
|
||||
result = process_files_batch(
|
||||
result = await process_files_batch(
|
||||
request=request,
|
||||
form_data=BatchProcessFilesForm(files=files, collection_name=id),
|
||||
user=user,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import json
|
|||
import asyncio
|
||||
import logging
|
||||
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.models import (
|
||||
ModelForm,
|
||||
ModelModel,
|
||||
|
|
@ -78,6 +79,10 @@ async def get_models(
|
|||
filter["direction"] = direction
|
||||
|
||||
if not user.role == "admin" or not BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
groups = Groups.get_groups_by_member_id(user.id)
|
||||
if groups:
|
||||
filter["group_ids"] = [group.id for group in groups]
|
||||
|
||||
filter["user_id"] = user.id
|
||||
|
||||
return Models.search_models(user.id, filter=filter, skip=skip, limit=limit)
|
||||
|
|
|
|||
|
|
@ -1249,7 +1249,7 @@ def save_docs_to_vector_db(
|
|||
|
||||
return ", ".join(docs_info)
|
||||
|
||||
log.info(
|
||||
log.debug(
|
||||
f"save_docs_to_vector_db: document {_get_docs_info(docs)} {collection_name}"
|
||||
)
|
||||
|
||||
|
|
@ -1689,7 +1689,7 @@ async def process_text(
|
|||
log.debug(f"text_content: {text_content}")
|
||||
|
||||
result = await run_in_threadpool(
|
||||
save_docs_to_vector_db, request, docs, collection_name, user
|
||||
save_docs_to_vector_db, request, docs, collection_name, user=user
|
||||
)
|
||||
if result:
|
||||
return {
|
||||
|
|
@ -1721,7 +1721,12 @@ async def process_web(
|
|||
|
||||
if not request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL:
|
||||
await run_in_threadpool(
|
||||
save_docs_to_vector_db, request, docs, collection_name, True, user
|
||||
save_docs_to_vector_db,
|
||||
request,
|
||||
docs,
|
||||
collection_name,
|
||||
overwrite=True,
|
||||
user=user,
|
||||
)
|
||||
else:
|
||||
collection_name = None
|
||||
|
|
@ -2464,7 +2469,12 @@ async def process_files_batch(
|
|||
if all_docs:
|
||||
try:
|
||||
await run_in_threadpool(
|
||||
save_docs_to_vector_db, request, all_docs, collection_name, True, user
|
||||
save_docs_to_vector_db,
|
||||
request,
|
||||
all_docs,
|
||||
collection_name,
|
||||
add=True,
|
||||
user=user,
|
||||
)
|
||||
|
||||
# Update all files with collection name
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import io
|
|||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi.responses import Response, StreamingResponse, FileResponse
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
from open_webui.models.auths import Auths
|
||||
|
|
@ -219,11 +219,13 @@ class ChatPermissions(BaseModel):
|
|||
|
||||
class FeaturesPermissions(BaseModel):
|
||||
api_keys: bool = False
|
||||
folders: bool = True
|
||||
notes: bool = True
|
||||
direct_tool_servers: bool = False
|
||||
|
||||
web_search: bool = True
|
||||
image_generation: bool = True
|
||||
code_interpreter: bool = True
|
||||
notes: bool = True
|
||||
|
||||
|
||||
class UserPermissions(BaseModel):
|
||||
|
|
@ -359,13 +361,14 @@ async def update_user_info_by_session_user(
|
|||
############################
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
class UserActiveResponse(BaseModel):
|
||||
name: str
|
||||
profile_image_url: str
|
||||
profile_image_url: Optional[str] = None
|
||||
active: Optional[bool] = None
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=UserResponse)
|
||||
@router.get("/{user_id}", response_model=UserActiveResponse)
|
||||
async def get_user_by_id(user_id: str, user=Depends(get_verified_user)):
|
||||
# Check if user_id is a shared chat
|
||||
# If it is, get the user_id from the chat
|
||||
|
|
@ -383,10 +386,10 @@ async def get_user_by_id(user_id: str, user=Depends(get_verified_user)):
|
|||
user = Users.get_user_by_id(user_id)
|
||||
|
||||
if user:
|
||||
return UserResponse(
|
||||
return UserActiveResponse(
|
||||
**{
|
||||
"id": user.id,
|
||||
"name": user.name,
|
||||
"profile_image_url": user.profile_image_url,
|
||||
"active": get_active_status_by_user_id(user_id),
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -118,6 +118,14 @@ if WEBSOCKET_MANAGER == "redis":
|
|||
redis_sentinels = get_sentinels_from_env(
|
||||
WEBSOCKET_SENTINEL_HOSTS, WEBSOCKET_SENTINEL_PORT
|
||||
)
|
||||
|
||||
MODELS = RedisDict(
|
||||
f"{REDIS_KEY_PREFIX}:models",
|
||||
redis_url=WEBSOCKET_REDIS_URL,
|
||||
redis_sentinels=redis_sentinels,
|
||||
redis_cluster=WEBSOCKET_REDIS_CLUSTER,
|
||||
)
|
||||
|
||||
SESSION_POOL = RedisDict(
|
||||
f"{REDIS_KEY_PREFIX}:session_pool",
|
||||
redis_url=WEBSOCKET_REDIS_URL,
|
||||
|
|
@ -148,6 +156,8 @@ if WEBSOCKET_MANAGER == "redis":
|
|||
renew_func = clean_up_lock.renew_lock
|
||||
release_func = clean_up_lock.release_lock
|
||||
else:
|
||||
MODELS = {}
|
||||
|
||||
SESSION_POOL = {}
|
||||
USER_POOL = {}
|
||||
USAGE_POOL = {}
|
||||
|
|
@ -398,6 +408,11 @@ async def channel_events(sid, data):
|
|||
event_data = data["data"]
|
||||
event_type = event_data["type"]
|
||||
|
||||
user = SESSION_POOL.get(sid)
|
||||
|
||||
if not user:
|
||||
return
|
||||
|
||||
if event_type == "typing":
|
||||
await sio.emit(
|
||||
"events:channel",
|
||||
|
|
@ -405,10 +420,12 @@ async def channel_events(sid, data):
|
|||
"channel_id": data["channel_id"],
|
||||
"message_id": data.get("message_id", None),
|
||||
"data": event_data,
|
||||
"user": UserNameResponse(**SESSION_POOL[sid]).model_dump(),
|
||||
"user": UserNameResponse(**user).model_dump(),
|
||||
},
|
||||
room=room,
|
||||
)
|
||||
elif event_type == "last_read_at":
|
||||
Channels.update_member_last_read_at(data["channel_id"], user["id"])
|
||||
|
||||
|
||||
@sio.on("ydoc:document:join")
|
||||
|
|
|
|||
|
|
@ -86,6 +86,15 @@ class RedisDict:
|
|||
def items(self):
|
||||
return [(k, json.loads(v)) for k, v in self.redis.hgetall(self.name).items()]
|
||||
|
||||
def set(self, mapping: dict):
|
||||
pipe = self.redis.pipeline()
|
||||
|
||||
pipe.delete(self.name)
|
||||
if mapping:
|
||||
pipe.hset(self.name, mapping={k: json.dumps(v) for k, v in mapping.items()})
|
||||
|
||||
pipe.execute()
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return self[key]
|
||||
|
|
|
|||
|
|
@ -1409,11 +1409,12 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
|||
headers=headers if headers else None,
|
||||
)
|
||||
|
||||
function_name_filter_list = (
|
||||
mcp_server_connection.get("config", {})
|
||||
.get("function_name_filter_list", "")
|
||||
.split(",")
|
||||
)
|
||||
function_name_filter_list = mcp_server_connection.get(
|
||||
"config", {}
|
||||
).get("function_name_filter_list", "")
|
||||
|
||||
if isinstance(function_name_filter_list, str):
|
||||
function_name_filter_list = function_name_filter_list.split(",")
|
||||
|
||||
tool_specs = await mcp_clients[server_id].list_tool_specs()
|
||||
for tool_spec in tool_specs:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import sys
|
|||
from aiocache import cached
|
||||
from fastapi import Request
|
||||
|
||||
from open_webui.socket.utils import RedisDict
|
||||
from open_webui.routers import openai, ollama
|
||||
from open_webui.functions import get_function_models
|
||||
|
||||
|
|
@ -323,7 +324,12 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
|
|||
|
||||
log.debug(f"get_all_models() returned {len(models)} models")
|
||||
|
||||
request.app.state.MODELS = {model["id"]: model for model in models}
|
||||
models_dict = {model["id"]: model for model in models}
|
||||
if isinstance(request.app.state.MODELS, RedisDict):
|
||||
request.app.state.MODELS.set(models_dict)
|
||||
else:
|
||||
request.app.state.MODELS = models_dict
|
||||
|
||||
return models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ from open_webui.config import (
|
|||
ENABLE_OAUTH_GROUP_CREATION,
|
||||
OAUTH_BLOCKED_GROUPS,
|
||||
OAUTH_GROUPS_SEPARATOR,
|
||||
OAUTH_ROLES_SEPARATOR,
|
||||
OAUTH_ROLES_CLAIM,
|
||||
OAUTH_SUB_CLAIM,
|
||||
OAUTH_GROUPS_CLAIM,
|
||||
|
|
@ -1067,7 +1068,13 @@ class OAuthManager:
|
|||
|
||||
if isinstance(claim_data, list):
|
||||
oauth_roles = claim_data
|
||||
if isinstance(claim_data, str) or isinstance(claim_data, int):
|
||||
elif isinstance(claim_data, str):
|
||||
# Split by the configured separator if present
|
||||
if OAUTH_ROLES_SEPARATOR and OAUTH_ROLES_SEPARATOR in claim_data:
|
||||
oauth_roles = claim_data.split(OAUTH_ROLES_SEPARATOR)
|
||||
else:
|
||||
oauth_roles = [claim_data]
|
||||
elif isinstance(claim_data, int):
|
||||
oauth_roles = [str(claim_data)]
|
||||
|
||||
log.debug(f"Oauth Roles claim: {oauth_claim}")
|
||||
|
|
@ -1529,7 +1536,9 @@ class OAuthManager:
|
|||
)
|
||||
if user.role != determined_role:
|
||||
Users.update_user_role_by_id(user.id, determined_role)
|
||||
|
||||
# Update the user object in memory as well,
|
||||
# to avoid problems with the ENABLE_OAUTH_GROUP_MANAGEMENT check below
|
||||
user.role = determined_role
|
||||
# Update profile picture if enabled and different from current
|
||||
if auth_manager_config.OAUTH_UPDATE_PICTURE_ON_LOGIN:
|
||||
picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM
|
||||
|
|
|
|||
|
|
@ -150,11 +150,12 @@ async def get_tools(
|
|||
)
|
||||
|
||||
specs = tool_server_data.get("specs", [])
|
||||
function_name_filter_list = (
|
||||
tool_server_connection.get("config", {})
|
||||
.get("function_name_filter_list", "")
|
||||
.split(",")
|
||||
)
|
||||
function_name_filter_list = tool_server_connection.get(
|
||||
"config", {}
|
||||
).get("function_name_filter_list", "")
|
||||
|
||||
if isinstance(function_name_filter_list, str):
|
||||
function_name_filter_list = function_name_filter_list.split(",")
|
||||
|
||||
for spec in specs:
|
||||
function_name = spec["name"]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pydantic==2.11.9
|
|||
python-multipart==0.0.20
|
||||
itsdangerous==2.2.0
|
||||
|
||||
python-socketio==5.14.0
|
||||
python-socketio==5.15.0
|
||||
python-jose==3.5.0
|
||||
cryptography
|
||||
bcrypt==5.0.0
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ pydantic==2.11.9
|
|||
python-multipart==0.0.20
|
||||
itsdangerous==2.2.0
|
||||
|
||||
python-socketio==5.14.0
|
||||
python-socketio==5.15.0
|
||||
python-jose==3.5.0
|
||||
cryptography
|
||||
bcrypt==5.0.0
|
||||
|
|
@ -52,15 +52,15 @@ chromadb==1.1.0
|
|||
weaviate-client==4.17.0
|
||||
opensearch-py==2.8.0
|
||||
|
||||
transformers
|
||||
sentence-transformers==5.1.1
|
||||
transformers==4.57.3
|
||||
sentence-transformers==5.1.2
|
||||
accelerate
|
||||
pyarrow==20.0.0 # fix: pin pyarrow version to 20 for rpi compatibility #15897
|
||||
einops==0.8.1
|
||||
|
||||
ftfy==6.2.3
|
||||
chardet==5.2.0
|
||||
pypdf==6.0.0
|
||||
pypdf==6.4.0
|
||||
fpdf2==2.8.2
|
||||
pymdown-extensions==10.14.2
|
||||
docx2txt==0.8
|
||||
|
|
@ -115,7 +115,7 @@ pgvector==0.4.1
|
|||
PyMySQL==1.1.1
|
||||
boto3==1.40.5
|
||||
|
||||
pymilvus==2.6.2
|
||||
pymilvus==2.6.4
|
||||
qdrant-client==1.14.3
|
||||
playwright==1.49.1 # Caution: version must match docker-compose.playwright.yaml
|
||||
elasticsearch==9.1.0
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ dependencies = [
|
|||
"python-multipart==0.0.20",
|
||||
"itsdangerous==2.2.0",
|
||||
|
||||
"python-socketio==5.14.0",
|
||||
"python-socketio==5.15.0",
|
||||
"python-jose==3.5.0",
|
||||
"cryptography",
|
||||
"bcrypt==5.0.0",
|
||||
|
|
@ -60,15 +60,15 @@ dependencies = [
|
|||
"PyMySQL==1.1.1",
|
||||
"boto3==1.40.5",
|
||||
|
||||
"transformers",
|
||||
"sentence-transformers==5.1.1",
|
||||
"transformers==4.57.3",
|
||||
"sentence-transformers==5.1.2",
|
||||
"accelerate",
|
||||
"pyarrow==20.0.0",
|
||||
"einops==0.8.1",
|
||||
|
||||
"ftfy==6.2.3",
|
||||
"chardet==5.2.0",
|
||||
"pypdf==6.0.0",
|
||||
"pypdf==6.4.0",
|
||||
"fpdf2==2.8.2",
|
||||
"pymdown-extensions==10.14.2",
|
||||
"docx2txt==0.8",
|
||||
|
|
@ -148,7 +148,7 @@ all = [
|
|||
|
||||
"qdrant-client==1.14.3",
|
||||
"weaviate-client==4.17.0",
|
||||
"pymilvus==2.6.2",
|
||||
"pymilvus==2.6.4",
|
||||
"pinecone==6.0.2",
|
||||
"oracledb==3.2.0",
|
||||
"colbert-ai==0.2.21",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
type ChannelForm = {
|
||||
type?: string;
|
||||
name: string;
|
||||
data?: object;
|
||||
meta?: object;
|
||||
access_control?: object;
|
||||
user_ids?: string[];
|
||||
};
|
||||
|
||||
export const createNewChannel = async (token: string = '', channel: ChannelForm) => {
|
||||
|
|
@ -101,7 +103,7 @@ export const getChannelById = async (token: string = '', channel_id: string) =>
|
|||
return res;
|
||||
};
|
||||
|
||||
export const getChannelUsersById = async (
|
||||
export const getChannelMembersById = async (
|
||||
token: string,
|
||||
channel_id: string,
|
||||
query?: string,
|
||||
|
|
@ -129,7 +131,7 @@ export const getChannelUsersById = async (
|
|||
}
|
||||
|
||||
res = await fetch(
|
||||
`${WEBUI_API_BASE_URL}/channels/${channel_id}/users?${searchParams.toString()}`,
|
||||
`${WEBUI_API_BASE_URL}/channels/${channel_id}/members?${searchParams.toString()}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
|
|
@ -155,6 +157,42 @@ export const getChannelUsersById = async (
|
|||
return res;
|
||||
};
|
||||
|
||||
export const updateChannelMemberActiveStatusById = async (
|
||||
token: string = '',
|
||||
channel_id: string,
|
||||
is_active: boolean
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/members/active`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ is_active })
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const updateChannelById = async (
|
||||
token: string = '',
|
||||
channel_id: string,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
let key = '';
|
||||
let headers = '';
|
||||
|
||||
let functionNameFilterList = [];
|
||||
let functionNameFilterList = '';
|
||||
let accessControl = {};
|
||||
|
||||
let id = '';
|
||||
|
|
@ -338,7 +338,7 @@
|
|||
oauthClientInfo = null;
|
||||
|
||||
enable = true;
|
||||
functionNameFilterList = [];
|
||||
functionNameFilterList = '';
|
||||
accessControl = null;
|
||||
};
|
||||
|
||||
|
|
@ -362,7 +362,7 @@
|
|||
oauthClientInfo = connection.info?.oauth_client_info ?? null;
|
||||
|
||||
enable = connection.config?.enable ?? true;
|
||||
functionNameFilterList = connection.config?.function_name_filter_list ?? [];
|
||||
functionNameFilterList = connection.config?.function_name_filter_list ?? '';
|
||||
accessControl = connection.config?.access_control ?? null;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -676,6 +676,14 @@
|
|||
<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Folders')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_FOLDERS} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Notes')} ({$i18n.t('Beta')})
|
||||
|
|
|
|||
|
|
@ -84,11 +84,12 @@
|
|||
},
|
||||
features: {
|
||||
api_keys: false,
|
||||
folders: true,
|
||||
notes: true,
|
||||
direct_tool_servers: false,
|
||||
web_search: true,
|
||||
image_generation: true,
|
||||
code_interpreter: true,
|
||||
notes: true
|
||||
code_interpreter: true
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -54,11 +54,12 @@
|
|||
},
|
||||
features: {
|
||||
api_keys: false,
|
||||
folders: true,
|
||||
notes: true,
|
||||
direct_tool_servers: false,
|
||||
web_search: true,
|
||||
image_generation: true,
|
||||
code_interpreter: true,
|
||||
notes: true
|
||||
code_interpreter: true
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@
|
|||
class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full"
|
||||
>
|
||||
<thead class="text-xs text-gray-800 uppercase bg-transparent dark:text-gray-200">
|
||||
<tr class=" border-b-[1.5px] border-gray-50 dark:border-gray-850">
|
||||
<tr class=" border-b-[1.5px] border-gray-50/50 dark:border-gray-800/10">
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer text-left w-8"
|
||||
|
|
|
|||
|
|
@ -96,11 +96,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
$: if (page) {
|
||||
getUserList();
|
||||
}
|
||||
|
||||
$: if (query !== null && orderBy && direction) {
|
||||
$: if (query !== null && page !== null && orderBy !== null && direction !== null) {
|
||||
getUserList();
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import { onDestroy, onMount, tick } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { chatId, showSidebar, socket, user } from '$lib/stores';
|
||||
import { chatId, channelId as _channelId, showSidebar, socket, user } from '$lib/stores';
|
||||
import { getChannelById, getChannelMessages, sendMessage } from '$lib/apis/channels';
|
||||
|
||||
import Messages from './Messages.svelte';
|
||||
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
export let id = '';
|
||||
|
||||
let currentId = null;
|
||||
|
||||
let scrollEnd = true;
|
||||
let messagesContainerElement = null;
|
||||
let chatInputElement = null;
|
||||
|
|
@ -43,7 +45,25 @@
|
|||
}
|
||||
};
|
||||
|
||||
const updateLastReadAt = async (channelId) => {
|
||||
$socket?.emit('events:channel', {
|
||||
channel_id: channelId,
|
||||
message_id: null,
|
||||
data: {
|
||||
type: 'last_read_at'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initHandler = async () => {
|
||||
if (currentId) {
|
||||
updateLastReadAt(currentId);
|
||||
}
|
||||
|
||||
currentId = id;
|
||||
updateLastReadAt(id);
|
||||
_channelId.set(id);
|
||||
|
||||
top = false;
|
||||
messages = null;
|
||||
channel = null;
|
||||
|
|
@ -170,6 +190,8 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateLastReadAt(id);
|
||||
};
|
||||
|
||||
let mediaQuery;
|
||||
|
|
@ -197,12 +219,32 @@
|
|||
});
|
||||
|
||||
onDestroy(() => {
|
||||
// last read at
|
||||
updateLastReadAt(id);
|
||||
_channelId.set(null);
|
||||
$socket?.off('events:channel', channelEventHandler);
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if channel?.type === 'dm'}
|
||||
<title
|
||||
>{channel?.name.trim() ||
|
||||
channel?.users.reduce((a, e, i, arr) => {
|
||||
if (e.id === $user?.id) {
|
||||
return a;
|
||||
}
|
||||
|
||||
if (a) {
|
||||
return `${a}, ${e.name}`;
|
||||
} else {
|
||||
return e.name;
|
||||
}
|
||||
}, '')} • Open WebUI</title
|
||||
>
|
||||
{:else}
|
||||
<title>#{channel?.name ?? 'Channel'} • Open WebUI</title>
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@
|
|||
<div class=" flex justify-between dark:text-gray-100 px-5 pt-4 mb-1.5">
|
||||
<div class="self-center text-base">
|
||||
<div class="flex items-center gap-0.5 shrink-0">
|
||||
{#if channel?.type === 'dm'}
|
||||
<div
|
||||
class=" text-left self-center overflow-hidden w-full line-clamp-1 capitalize flex-1"
|
||||
>
|
||||
{$i18n.t('Direct Message')}
|
||||
</div>
|
||||
{:else}
|
||||
<div class=" size-4 justify-center flex items-center">
|
||||
{#if channel?.access_control === null}
|
||||
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
||||
|
|
@ -49,6 +56,7 @@
|
|||
>
|
||||
{channel.name}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
|
@ -71,7 +79,7 @@
|
|||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full h-full pb-2">
|
||||
<UserList {channel} />
|
||||
<UserList {channel} search={channel?.type !== 'dm'} sort={channel?.type !== 'dm'} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
dayjs.extend(localizedFormat);
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getChannelUsersById } from '$lib/apis/channels';
|
||||
import { getChannelMembersById } from '$lib/apis/channels';
|
||||
|
||||
import Pagination from '$lib/components/common/Pagination.svelte';
|
||||
import ChatBubbles from '$lib/components/icons/ChatBubbles.svelte';
|
||||
|
|
@ -37,6 +37,8 @@
|
|||
const i18n = getContext('i18n');
|
||||
|
||||
export let channel = null;
|
||||
export let search = true;
|
||||
export let sort = true;
|
||||
|
||||
let page = 1;
|
||||
|
||||
|
|
@ -48,6 +50,10 @@
|
|||
let direction = 'asc'; // default sort order
|
||||
|
||||
const setSortKey = (key) => {
|
||||
if (!sort) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (orderBy === key) {
|
||||
direction = direction === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
|
|
@ -58,7 +64,7 @@
|
|||
|
||||
const getUserList = async () => {
|
||||
try {
|
||||
const res = await getChannelUsersById(
|
||||
const res = await getChannelMembersById(
|
||||
localStorage.token,
|
||||
channel.id,
|
||||
query,
|
||||
|
|
@ -79,11 +85,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
$: if (page) {
|
||||
getUserList();
|
||||
}
|
||||
|
||||
$: if (query !== null && orderBy && direction) {
|
||||
$: if (page !== null && query !== null && orderBy !== null && direction !== null) {
|
||||
getUserList();
|
||||
}
|
||||
</script>
|
||||
|
|
@ -94,7 +96,8 @@
|
|||
<Spinner className="size-5" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex gap-1">
|
||||
{#if search}
|
||||
<div class="flex gap-1 px-0.5">
|
||||
<div class=" flex w-full space-x-2">
|
||||
<div class="flex flex-1">
|
||||
<div class=" self-center ml-1 mr-3">
|
||||
|
|
@ -119,6 +122,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if users.length > 0}
|
||||
<div class="scrollbar-hidden relative whitespace-nowrap w-full max-w-full">
|
||||
|
|
@ -127,9 +131,10 @@
|
|||
class="text-xs text-gray-800 uppercase bg-transparent dark:text-gray-200 w-full mb-0.5"
|
||||
>
|
||||
<div
|
||||
class=" border-b-[1.5px] border-gray-50 dark:border-gray-850 flex items-center justify-between"
|
||||
class=" border-b-[1.5px] border-gray-50/50 dark:border-gray-800/10 flex items-center justify-between"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('name')}
|
||||
>
|
||||
|
|
@ -153,6 +158,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('role')}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,9 @@
|
|||
if (channelSuggestions) {
|
||||
// Add a dummy channel item
|
||||
_channels = [
|
||||
...$channels.map((c) => ({ type: 'channel', id: c.id, label: c.name, data: c }))
|
||||
...$channels
|
||||
.filter((c) => c?.type !== 'dm')
|
||||
.map((c) => ({ type: 'channel', id: c.id, label: c.name, data: c }))
|
||||
];
|
||||
} else {
|
||||
if (userSuggestions) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
import Loader from '../common/Loader.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import { addReaction, deleteMessage, removeReaction, updateMessage } from '$lib/apis/channels';
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
|
@ -68,7 +69,31 @@
|
|||
<div class="px-5 max-w-full mx-auto">
|
||||
{#if channel}
|
||||
<div class="flex flex-col gap-1.5 pb-5 pt-10">
|
||||
<div class="text-2xl font-medium capitalize">{channel.name}</div>
|
||||
{#if channel?.type === 'dm'}
|
||||
<div class="flex ml-[1px] mr-0.5">
|
||||
{#each channel.users.filter((u) => u.id !== $user?.id).slice(0, 2) as u, index}
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/users/${u.id}/profile/image`}
|
||||
alt={u.name}
|
||||
class=" size-7.5 rounded-full border-2 border-white dark:border-gray-900 {index ===
|
||||
1
|
||||
? '-ml-2.5'
|
||||
: ''}"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="text-2xl font-medium capitalize">
|
||||
{#if channel?.name}
|
||||
{channel.name}
|
||||
{:else}
|
||||
{channel?.users
|
||||
?.filter((u) => u.id !== $user?.id)
|
||||
.map((u) => u.name)
|
||||
.join(', ')}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" text-gray-500">
|
||||
{$i18n.t(
|
||||
|
|
|
|||
|
|
@ -252,14 +252,18 @@
|
|||
|
||||
{#if message.created_at}
|
||||
<div
|
||||
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
||||
class=" self-center text-xs text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
||||
>
|
||||
<Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}>
|
||||
<span class="line-clamp-1">
|
||||
{#if dayjs(message.created_at / 1000000).isToday()}
|
||||
{dayjs(message.created_at / 1000000).format('LT')}
|
||||
{:else}
|
||||
{$i18n.t(formatDate(message.created_at / 1000000), {
|
||||
LOCALIZED_TIME: dayjs(message.created_at / 1000000).format('LT'),
|
||||
LOCALIZED_DATE: dayjs(message.created_at / 1000000).format('L')
|
||||
})}
|
||||
{/if}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
import Lock from '../icons/Lock.svelte';
|
||||
import UserAlt from '../icons/UserAlt.svelte';
|
||||
import ChannelInfoModal from './ChannelInfoModal.svelte';
|
||||
import Users from '../icons/Users.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
|
@ -60,24 +61,50 @@
|
|||
{/if}
|
||||
|
||||
<div
|
||||
class="flex-1 overflow-hidden max-w-full py-0.5
|
||||
class="flex-1 overflow-hidden max-w-full py-0.5 flex items-center
|
||||
{$showSidebar ? 'ml-1' : ''}
|
||||
"
|
||||
>
|
||||
{#if channel}
|
||||
<div class="flex items-center gap-0.5 shrink-0">
|
||||
<div class=" size-4 justify-center flex items-center">
|
||||
{#if channel?.type === 'dm'}
|
||||
{#if channel?.users}
|
||||
<div class="flex mr-1.5">
|
||||
{#each channel.users.filter((u) => u.id !== $user?.id).slice(0, 2) as u, index}
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/users/${u.id}/profile/image`}
|
||||
alt={u.name}
|
||||
class=" size-6.5 rounded-full border-2 border-white dark:border-gray-900 {index ===
|
||||
1
|
||||
? '-ml-3'
|
||||
: ''}"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<Users className="size-4 ml-1 mr-0.5" strokeWidth="2" />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class=" size-4.5 justify-center flex items-center">
|
||||
{#if channel?.access_control === null}
|
||||
<Hashtag className="size-3" strokeWidth="2.5" />
|
||||
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
||||
{:else}
|
||||
<Lock className="size-5" strokeWidth="2" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class=" text-left self-center overflow-hidden w-full line-clamp-1 capitalize flex-1"
|
||||
>
|
||||
{#if channel?.name}
|
||||
{channel.name}
|
||||
{:else}
|
||||
{channel?.users
|
||||
?.filter((u) => u.id !== $user?.id)
|
||||
.map((u) => u.name)
|
||||
.join(', ')}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1955,7 +1955,9 @@
|
|||
|
||||
session_id: $socket?.id,
|
||||
chat_id: $chatId,
|
||||
|
||||
id: responseMessageId,
|
||||
parent_id: userMessage?.id ?? null,
|
||||
|
||||
background_tasks: {
|
||||
...(!$temporaryChatEnabled &&
|
||||
|
|
|
|||
|
|
@ -1480,6 +1480,7 @@
|
|||
<div class="ml-1 flex gap-1.5">
|
||||
<Tooltip content={$i18n.t('Valves')} placement="top">
|
||||
<button
|
||||
type="button"
|
||||
id="model-valves-button"
|
||||
class="bg-transparent hover:bg-gray-100 text-gray-700 dark:text-white dark:hover:bg-gray-800 rounded-full size-8 flex justify-center items-center outline-hidden focus:outline-hidden"
|
||||
on:click={() => {
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@
|
|||
...item,
|
||||
type: 'collection'
|
||||
}));
|
||||
|
||||
let collection_files =
|
||||
knowledge.length > 0
|
||||
? [
|
||||
|
|
@ -139,7 +140,7 @@
|
|||
.map((file) => ({
|
||||
...file,
|
||||
name: file?.meta?.name,
|
||||
description: `${file?.collection?.name} - ${file?.collection?.description}`,
|
||||
description: `${file?.collection?.description}`,
|
||||
knowledge: true, // DO NOT REMOVE, USED TO INDICATE KNOWLEDGE BASE FILE
|
||||
type: 'file'
|
||||
}))
|
||||
|
|
@ -218,7 +219,7 @@
|
|||
content={item?.legacy
|
||||
? $i18n.t('Legacy')
|
||||
: item?.type === 'file'
|
||||
? $i18n.t('File')
|
||||
? `${item?.collection?.name} > ${$i18n.t('File')}`
|
||||
: item?.type === 'collection'
|
||||
? $i18n.t('Collection')
|
||||
: ''}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
export let indeterminate = false;
|
||||
export let disabled = false;
|
||||
|
||||
export let disabledClassName = 'opacity-50 cursor-not-allowed';
|
||||
|
||||
let _state = 'unchecked';
|
||||
|
||||
$: _state = state;
|
||||
|
|
@ -16,7 +18,7 @@
|
|||
'unchecked'
|
||||
? 'bg-black outline-black '
|
||||
: 'hover:outline-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800'} text-white transition-all rounded-sm inline-block w-3.5 h-3.5 relative {disabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
? disabledClassName
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
if (disabled) return;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
import Note from '../icons/Note.svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import HotkeyHint from '../common/HotkeyHint.svelte';
|
||||
import { key } from 'vega';
|
||||
|
||||
const BREAKPOINT = 768;
|
||||
|
||||
|
|
@ -90,8 +91,11 @@
|
|||
}
|
||||
|
||||
const initFolders = async () => {
|
||||
if ($config?.features?.enable_folders === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const folderList = await getFolders(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return [];
|
||||
});
|
||||
_folders.set(folderList.sort((a, b) => b.updated_at - a.updated_at));
|
||||
|
|
@ -177,7 +181,11 @@
|
|||
};
|
||||
|
||||
const initChannels = async () => {
|
||||
await channels.set(await getChannels(localStorage.token));
|
||||
await channels.set(
|
||||
(await getChannels(localStorage.token)).sort((a, b) =>
|
||||
a.type === b.type ? 0 : a.type === 'dm' ? 1 : -1
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const initChatList = async () => {
|
||||
|
|
@ -478,16 +486,26 @@
|
|||
|
||||
<ChannelModal
|
||||
bind:show={showCreateChannel}
|
||||
onSubmit={async ({ name, access_control }) => {
|
||||
onSubmit={async ({ type, name, access_control, user_ids }) => {
|
||||
name = name?.trim();
|
||||
|
||||
if (type === 'dm') {
|
||||
if (!user_ids || user_ids.length === 0) {
|
||||
toast.error($i18n.t('Please select at least one user for Direct Message channel.'));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!name) {
|
||||
toast.error($i18n.t('Channel name cannot be empty.'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await createNewChannel(localStorage.token, {
|
||||
type: type,
|
||||
name: name,
|
||||
access_control: access_control
|
||||
access_control: access_control,
|
||||
user_ids: user_ids
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
|
|
@ -497,6 +515,8 @@
|
|||
$socket.emit('join-channels', { auth: { token: $user?.token } });
|
||||
await initChannels();
|
||||
showCreateChannel = false;
|
||||
|
||||
goto(`/channels/${res.id}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
@ -713,6 +733,9 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- {$i18n.t('New Folder')} -->
|
||||
<!-- {$i18n.t('Pinned')} -->
|
||||
|
||||
{#if $showSidebar}
|
||||
<div
|
||||
bind:this={navElement}
|
||||
|
|
@ -910,29 +933,34 @@
|
|||
name={$i18n.t('Channels')}
|
||||
chevron={false}
|
||||
dragAndDrop={false}
|
||||
onAdd={async () => {
|
||||
if ($user?.role === 'admin') {
|
||||
onAdd={$user?.role === 'admin'
|
||||
? async () => {
|
||||
await tick();
|
||||
|
||||
setTimeout(() => {
|
||||
showCreateChannel = true;
|
||||
}, 0);
|
||||
}
|
||||
}}
|
||||
: null}
|
||||
onAddLabel={$i18n.t('Create Channel')}
|
||||
>
|
||||
{#each $channels as channel}
|
||||
{#each $channels as channel, channelIdx (`${channel?.id}`)}
|
||||
<ChannelItem
|
||||
{channel}
|
||||
onUpdate={async () => {
|
||||
await initChannels();
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if channelIdx < $channels.length - 1 && channel.type !== $channels[channelIdx + 1]?.type}<hr
|
||||
class=" border-gray-100/40 dark:border-gray-800/10 my-1.5 w-full"
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</Folder>
|
||||
{/if}
|
||||
|
||||
{#if folders}
|
||||
{#if $config?.features?.enable_folders && ($user?.role === 'admin' || ($user?.permissions?.features?.folders ?? true))}
|
||||
<Folder
|
||||
id="sidebar-folders"
|
||||
className="px-2 mt-0.5"
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@
|
|||
const i18n = getContext('i18n');
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { mobile, showSidebar, user } from '$lib/stores';
|
||||
import { updateChannelById } from '$lib/apis/channels';
|
||||
import { channels, mobile, showSidebar, user } from '$lib/stores';
|
||||
import { updateChannelById, updateChannelMemberActiveStatusById } from '$lib/apis/channels';
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
||||
import ChannelModal from './ChannelModal.svelte';
|
||||
import Lock from '$lib/components/icons/Lock.svelte';
|
||||
import Hashtag from '$lib/components/icons/Hashtag.svelte';
|
||||
import Users from '$lib/components/icons/Users.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
|
||||
export let onUpdate: Function = () => {};
|
||||
|
||||
|
|
@ -49,43 +52,129 @@
|
|||
class=" w-full {className} rounded-xl flex relative group hover:bg-gray-100 dark:hover:bg-gray-900 {$page
|
||||
.url.pathname === `/channels/${channel.id}`
|
||||
? 'bg-gray-100 dark:bg-gray-900 selected'
|
||||
: ''} px-2.5 py-1"
|
||||
: ''} {channel?.type === 'dm' ? 'px-1 py-[3px]' : 'p-1'} {channel?.unread_count > 0
|
||||
? 'font-medium dark:text-white text-black'
|
||||
: ' dark:text-gray-400 text-gray-600'} cursor-pointer select-none"
|
||||
>
|
||||
<a
|
||||
class=" w-full flex justify-between"
|
||||
href="/channels/{channel.id}"
|
||||
on:click={() => {
|
||||
console.log(channel);
|
||||
|
||||
if ($channels) {
|
||||
channels.set(
|
||||
$channels.map((ch) => {
|
||||
if (ch.id === channel.id) {
|
||||
ch.unread_count = 0;
|
||||
}
|
||||
return ch;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ($mobile) {
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
draggable="false"
|
||||
>
|
||||
<div class="flex items-center gap-1 shrink-0">
|
||||
<div class=" size-4 justify-center flex items-center">
|
||||
<div class="flex items-center gap-1">
|
||||
<div>
|
||||
{#if channel?.type === 'dm'}
|
||||
{#if channel?.users}
|
||||
<div class="flex ml-[1px] mr-0.5">
|
||||
{#each channel.users.filter((u) => u.id !== $user?.id).slice(0, 2) as u, index}
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/users/${u.id}/profile/image`}
|
||||
alt={u.name}
|
||||
class=" size-5.5 rounded-full border-2 border-white dark:border-gray-900 {index ===
|
||||
1
|
||||
? '-ml-2.5'
|
||||
: ''}"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<Users className="size-4 ml-1 mr-0.5" strokeWidth="2" />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class=" size-4 justify-center flex items-center ml-1">
|
||||
{#if channel?.access_control === null}
|
||||
<Hashtag className="size-3" strokeWidth="2.5" />
|
||||
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
||||
{:else}
|
||||
<Lock className="size-[15px]" strokeWidth="2" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" text-left self-center overflow-hidden w-full line-clamp-1 flex-1">
|
||||
{channel.name}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" text-left self-center overflow-hidden w-full line-clamp-1 flex-1 pr-1">
|
||||
{#if channel?.name}
|
||||
{channel.name}
|
||||
{:else}
|
||||
{channel?.users
|
||||
?.filter((u) => u.id !== $user?.id)
|
||||
.map((u) => u.name)
|
||||
.join(', ')}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
{#if channel?.unread_count > 0}
|
||||
<div
|
||||
class="text-xs py-[1px] px-2 rounded-xl bg-gray-100 text-black dark:bg-gray-800 dark:text-white font-medium"
|
||||
>
|
||||
{new Intl.NumberFormat($i18n.locale, {
|
||||
notation: 'compact',
|
||||
compactDisplay: 'short'
|
||||
}).format(channel.unread_count)}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{#if $user?.role === 'admin'}
|
||||
{#if channel?.type === 'dm'}
|
||||
<div
|
||||
class="absolute z-10 right-2 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
|
||||
class="ml-0.5 mr-1 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto"
|
||||
on:click={async (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
|
||||
channels.update((chs) =>
|
||||
chs.filter((ch) => {
|
||||
return ch.id !== channel.id;
|
||||
})
|
||||
);
|
||||
|
||||
await updateChannelMemberActiveStatusById(localStorage.token, channel.id, false).catch(
|
||||
(error) => {
|
||||
toast.error(`${error}`);
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<XMark className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{:else if $user?.role === 'admin'}
|
||||
<div
|
||||
class="ml-0.5 mr-1 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto"
|
||||
on:click={(e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
showEditChannelModal = true;
|
||||
}}
|
||||
>
|
||||
<button class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto" on:click={(e) => {}}>
|
||||
<Cog6 className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
import { toast } from 'svelte-sonner';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import UserListSelector from '$lib/components/workspace/common/UserListSelector.svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let show = false;
|
||||
|
|
@ -20,8 +21,11 @@
|
|||
export let channel = null;
|
||||
export let edit = false;
|
||||
|
||||
let type = '';
|
||||
let name = '';
|
||||
|
||||
let accessControl = {};
|
||||
let userIds = [];
|
||||
|
||||
let loading = false;
|
||||
|
||||
|
|
@ -32,16 +36,20 @@
|
|||
const submitHandler = async () => {
|
||||
loading = true;
|
||||
await onSubmit({
|
||||
type: type,
|
||||
name: name.replace(/\s/g, '-'),
|
||||
access_control: accessControl
|
||||
access_control: accessControl,
|
||||
user_ids: userIds
|
||||
});
|
||||
show = false;
|
||||
loading = false;
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
name = channel.name;
|
||||
type = channel?.type ?? '';
|
||||
name = channel?.name ?? '';
|
||||
accessControl = channel.access_control;
|
||||
userIds = channel?.user_ids ?? [];
|
||||
};
|
||||
|
||||
$: if (show) {
|
||||
|
|
@ -74,8 +82,10 @@
|
|||
};
|
||||
|
||||
const resetHandler = () => {
|
||||
type = '';
|
||||
name = '';
|
||||
accessControl = {};
|
||||
userIds = [];
|
||||
loading = false;
|
||||
};
|
||||
</script>
|
||||
|
|
@ -108,26 +118,52 @@
|
|||
submitHandler();
|
||||
}}
|
||||
>
|
||||
{#if !edit}
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Channel Name')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Channel Type')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
bind:value={type}
|
||||
>
|
||||
<option value="">{$i18n.t('Channel')}</option>
|
||||
<option value="dm">{$i18n.t('Direct Message')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs text-gray-500">
|
||||
{$i18n.t('Channel Name')}
|
||||
<span class="text-xs text-gray-200 dark:text-gray-800 ml-0.5"
|
||||
>{type === 'dm' ? `${$i18n.t('Optional')}` : ''}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
placeholder={$i18n.t('new-channel')}
|
||||
placeholder={`${$i18n.t('new-channel')}`}
|
||||
autocomplete="off"
|
||||
required={type !== 'dm'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
<hr class=" border-gray-100/40 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="my-2 -mx-2">
|
||||
<div class="-mx-2">
|
||||
{#if type === 'dm'}
|
||||
<UserListSelector bind:userIds />
|
||||
{:else}
|
||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
|
||||
<AccessControl bind:accessControl accessRoles={['read', 'write']} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
|
||||
|
|
|
|||
|
|
@ -214,8 +214,6 @@
|
|||
viewOption = localStorage.workspaceViewOption ?? '';
|
||||
page = 1;
|
||||
|
||||
await getModelList();
|
||||
|
||||
let groups = await getGroups(localStorage.token);
|
||||
groupIds = groups.map((group) => group.id);
|
||||
|
||||
|
|
|
|||
247
src/lib/components/workspace/common/UserListSelector.svelte
Normal file
247
src/lib/components/workspace/common/UserListSelector.svelte
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getContext, onMount } from 'svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { user as _user } from '$lib/stores';
|
||||
import { getUserById, getUsers } from '$lib/apis/users';
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import Pagination from '$lib/components/common/Pagination.svelte';
|
||||
import ProfilePreview from '$lib/components/channel/Messages/Message/ProfilePreview.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Checkbox from '$lib/components/common/Checkbox.svelte';
|
||||
|
||||
export let onChange: Function = () => {};
|
||||
export let userIds = [];
|
||||
|
||||
export let pagination = false;
|
||||
|
||||
let selectedUsers = {};
|
||||
|
||||
let page = 1;
|
||||
let users = null;
|
||||
let total = null;
|
||||
|
||||
let query = '';
|
||||
let orderBy = 'name'; // default sort key
|
||||
let direction = 'asc'; // default sort order
|
||||
|
||||
const setSortKey = (key) => {
|
||||
if (orderBy === key) {
|
||||
direction = direction === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
orderBy = key;
|
||||
direction = 'asc';
|
||||
}
|
||||
};
|
||||
|
||||
const getUserList = async () => {
|
||||
try {
|
||||
const res = await getUsers(localStorage.token, query, orderBy, direction, page).catch(
|
||||
(error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
if (res) {
|
||||
users = res.users;
|
||||
total = res.total;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
$: if (page !== null && query !== null && orderBy !== null && direction !== null) {
|
||||
getUserList();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (userIds.length > 0) {
|
||||
userIds.forEach(async (id) => {
|
||||
const res = await getUserById(localStorage.token, id).catch((error) => {
|
||||
console.error(error);
|
||||
return null;
|
||||
});
|
||||
if (res) {
|
||||
selectedUsers[id] = res;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="">
|
||||
{#if users === null || total === null}
|
||||
<div class="my-10">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
{:else}
|
||||
{#if userIds.length > 0}
|
||||
<div class="mx-1 mb-1.5">
|
||||
<div class="text-xs text-gray-500 mx-0.5 mb-1">
|
||||
{userIds.length}
|
||||
{$i18n.t('users')}
|
||||
</div>
|
||||
<div class="flex gap-1 flex-wrap">
|
||||
{#each userIds as id}
|
||||
{#if selectedUsers[id]}
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center space-x-1 px-2 py-1 bg-gray-100/50 dark:bg-gray-850 rounded-lg text-xs"
|
||||
on:click={() => {
|
||||
userIds = userIds.filter((uid) => uid !== id);
|
||||
delete selectedUsers[id];
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{selectedUsers[id].name}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<XMark className="size-3" />
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex gap-1 px-0.5">
|
||||
<div class=" flex w-full space-x-2">
|
||||
<div class="flex flex-1">
|
||||
<div class=" self-center ml-1 mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
|
||||
bind:value={query}
|
||||
placeholder={$i18n.t('Search')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if users.length > 0}
|
||||
<div class="scrollbar-hidden relative whitespace-nowrap w-full max-w-full">
|
||||
<div class=" text-sm text-left text-gray-500 dark:text-gray-400 w-full max-w-full">
|
||||
<div
|
||||
class="text-xs text-gray-800 uppercase bg-transparent dark:text-gray-200 w-full mb-0.5"
|
||||
>
|
||||
<div
|
||||
class=" border-b-[1.5px] border-gray-50/50 dark:border-gray-800/10 flex items-center justify-between"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('name')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Name')}
|
||||
|
||||
{#if orderBy === 'name'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
{#each users as user, userIdx}
|
||||
{#if user?.id !== $_user?.id}
|
||||
<button
|
||||
class=" dark:border-gray-850 text-xs flex items-center justify-between w-full"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if ((userIds ?? []).includes(user.id)) {
|
||||
userIds = userIds.filter((id) => id !== user.id);
|
||||
delete selectedUsers[user.id];
|
||||
} else {
|
||||
userIds = [...userIds, user.id];
|
||||
selectedUsers[user.id] = user;
|
||||
}
|
||||
onChange(userIds);
|
||||
}}
|
||||
>
|
||||
<div class="px-3 py-1.5 font-medium text-gray-900 dark:text-white flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<ProfilePreview {user} side="right" align="center" sideOffset={6}>
|
||||
<img
|
||||
class="rounded-2xl w-6 h-6 object-cover flex-shrink-0"
|
||||
src={`${WEBUI_API_BASE_URL}/users/${user.id}/profile/image`}
|
||||
alt="user"
|
||||
/>
|
||||
</ProfilePreview>
|
||||
<Tooltip content={user.email} placement="top-start">
|
||||
<div class="font-medium truncate">{user.name}</div>
|
||||
</Tooltip>
|
||||
|
||||
{#if user?.is_active}
|
||||
<div>
|
||||
<span class="relative flex size-1.5">
|
||||
<span
|
||||
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"
|
||||
></span>
|
||||
<span class="relative inline-flex size-1.5 rounded-full bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-3 py-1">
|
||||
<div class=" translate-y-0.5">
|
||||
<Checkbox
|
||||
state={(userIds ?? []).includes(user.id) ? 'checked' : 'unchecked'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if pagination}
|
||||
{#if total > 30}
|
||||
<Pagination bind:page count={total} perPage={30} />
|
||||
{/if}
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-gray-500 text-xs text-center py-5 px-10">
|
||||
{$i18n.t('No users were found.')}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "موقف ايجابي",
|
||||
|
|
@ -1702,6 +1705,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "المستخدمين",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "اسم القناة",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "القنوات",
|
||||
"Character": "الشخصية",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "الاتصالات المباشرة",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "تتيح الاتصالات المباشرة للمستخدمين الاتصال بنقاط نهاية API متوافقة مع OpenAI الخاصة بهم.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "الرجاء اختيار نموذج.",
|
||||
"Please select a reason": "الرجاء اختيار سبب",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "المنفذ",
|
||||
"Positive attitude": "موقف ايجابي",
|
||||
|
|
@ -1702,6 +1705,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "اسم المستخدم",
|
||||
"users": "",
|
||||
"Users": "المستخدمين",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Име на канала",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Канали",
|
||||
"Character": "Герой",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "Директни връзки",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Директните връзки позволяват на потребителите да се свързват със собствени OpenAI съвместими API крайни точки.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Моля, изберете модел.",
|
||||
"Please select a reason": "Моля, изберете причина",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Порт",
|
||||
"Positive attitude": "Позитивно отношение",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "Потребителско име",
|
||||
"users": "",
|
||||
"Users": "Потребители",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "পজিটিভ আক্রমণ",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "ব্যাবহারকারীগণ",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "བགྲོ་གླེང་གི་མིང་།",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "བགྲོ་གླེང་།",
|
||||
"Character": "ཡིག་འབྲུ།",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "ཐད་ཀར།",
|
||||
"Direct Connections": "ཐད་ཀར་སྦྲེལ་མཐུད།",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "ཐད་ཀར་སྦྲེལ་མཐུད་ཀྱིས་བེད་སྤྱོད་མཁན་ཚོར་ཁོ་ཚོའི་རང་གི་ OpenAI དང་མཐུན་པའི་ API མཇུག་མཐུད་ལ་སྦྲེལ་བར་གནང་བ་སྤྲོད།",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "དཔེ་དབྱིབས་ཤིག་གདམ་ག་བྱེད་རོགས།",
|
||||
"Please select a reason": "རྒྱུ་མཚན་ཞིག་གདམ་ག་བྱེད་རོགས།",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "ལྟ་སྟངས་དགེ་མཚན།",
|
||||
|
|
@ -1697,6 +1700,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "བེད་སྤྱོད་མཁན་གྱི་ Webhooks",
|
||||
"Username": "བེད་སྤྱོད་མིང་།",
|
||||
"users": "",
|
||||
"Users": "བེད་སྤྱོད་མཁན།",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Pozitivan stav",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Korisnici",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
"Allow Continue Response": "Permetre continuar la resposta",
|
||||
"Allow Delete Messages": "Permetre eliminar missatges",
|
||||
"Allow File Upload": "Permetre la pujada d'arxius",
|
||||
"Allow Group Sharing": "",
|
||||
"Allow Group Sharing": "Permetre compartir en grup",
|
||||
"Allow Multiple Models in Chat": "Permetre múltiple models al xat",
|
||||
"Allow non-local voices": "Permetre veus no locals",
|
||||
"Allow Rate Response": "Permetre valorar les respostes",
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
"Archived Chats": "Xats arxivats",
|
||||
"archived-chat-export": "archived-chat-export",
|
||||
"Are you sure you want to clear all memories? This action cannot be undone.": "Estàs segur que vols netejar totes les memòries? Aquesta acció no es pot desfer.",
|
||||
"Are you sure you want to delete \"{{NAME}}\"?": "",
|
||||
"Are you sure you want to delete \"{{NAME}}\"?": "Estàs segur que vols eliminar \"{{NAME}}\"?",
|
||||
"Are you sure you want to delete this channel?": "Estàs segur que vols eliminar aquest canal?",
|
||||
"Are you sure you want to delete this message?": "Estàs segur que vols eliminar aquest missatge?",
|
||||
"Are you sure you want to unarchive all archived chats?": "Estàs segur que vols desarxivar tots els xats arxivats?",
|
||||
|
|
@ -152,7 +152,7 @@
|
|||
"Ask": "Preguntar",
|
||||
"Ask a question": "Fer una pregunta",
|
||||
"Assistant": "Assistent",
|
||||
"Async Embedding Processing": "",
|
||||
"Async Embedding Processing": "Procés d'incrustat asíncron",
|
||||
"Attach File From Knowledge": "Adjuntar arxiu del coneixement",
|
||||
"Attach Knowledge": "Adjuntar coneixement",
|
||||
"Attach Notes": "Adjuntar notes",
|
||||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Canal suprimit correctament",
|
||||
"Channel Name": "Nom del canal",
|
||||
"Channel name cannot be empty.": "El nom del canal no pot estar buit.",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Canal actualitzat correctament",
|
||||
"Channels": "Canals",
|
||||
"Character": "Personatge",
|
||||
|
|
@ -387,14 +388,14 @@
|
|||
"Default description enabled": "Descripcions per defecte habilitades",
|
||||
"Default Features": "Característiques per defecte",
|
||||
"Default Filters": "Filres per defecte",
|
||||
"Default Group": "",
|
||||
"Default Group": "Grup per defecte",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "El mode predeterminat funciona amb una gamma més àmplia de models cridant a les eines una vegada abans de l'execució. El mode natiu aprofita les capacitats de crida d'eines integrades del model, però requereix que el model admeti aquesta funció de manera inherent.",
|
||||
"Default Model": "Model per defecte",
|
||||
"Default model updated": "Model per defecte actualitzat",
|
||||
"Default Models": "Models per defecte",
|
||||
"Default permissions": "Permisos per defecte",
|
||||
"Default permissions updated successfully": "Permisos per defecte actualitzats correctament",
|
||||
"Default Pinned Models": "",
|
||||
"Default Pinned Models": "Model marcat per defecte",
|
||||
"Default Prompt Suggestions": "Suggeriments d'indicació per defecte",
|
||||
"Default to 389 or 636 if TLS is enabled": "Per defecte 389 o 636 si TLS està habilitat",
|
||||
"Default to ALL": "Per defecte TOTS",
|
||||
|
|
@ -403,7 +404,7 @@
|
|||
"Delete": "Eliminar",
|
||||
"Delete a model": "Eliminar un model",
|
||||
"Delete All Chats": "Eliminar tots els xats",
|
||||
"Delete all contents inside this folder": "",
|
||||
"Delete all contents inside this folder": "Eliminar tot el contingut d'aquesta carpeta",
|
||||
"Delete All Models": "Eliminar tots els models",
|
||||
"Delete Chat": "Eliminar xat",
|
||||
"Delete chat?": "Eliminar el xat?",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Directe",
|
||||
"Direct Connections": "Connexions directes",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Les connexions directes permeten als usuaris connectar-se als seus propis endpoints d'API compatibles amb OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Servidors d'eines directes",
|
||||
"Directory selection was cancelled": "La selecció de directori s'ha cancel·lat",
|
||||
"Disable Code Interpreter": "Deshabilitar l'interpret de codi",
|
||||
|
|
@ -558,7 +560,7 @@
|
|||
"Enter Datalab Marker API Base URL": "Introdueix la URL de base de l'API Datalab Marker",
|
||||
"Enter Datalab Marker API Key": "Introdueix la clau API de Datalab Marker",
|
||||
"Enter description": "Introdueix la descripció",
|
||||
"Enter Docling API Key": "",
|
||||
"Enter Docling API Key": "Introdueix la clau API de Docling",
|
||||
"Enter Docling Server URL": "Introdueix la URL del servidor Docling",
|
||||
"Enter Document Intelligence Endpoint": "Introdueix el punt de connexió de Document Intelligence",
|
||||
"Enter Document Intelligence Key": "Introdueix la clau de Document Intelligence",
|
||||
|
|
@ -573,7 +575,7 @@
|
|||
"Enter Firecrawl API Base URL": "Introdueix la URL base de Firecrawl API",
|
||||
"Enter Firecrawl API Key": "Introdueix la clau API de Firecrawl",
|
||||
"Enter folder name": "Introdueix el nom de la carpeta",
|
||||
"Enter function name filter list (e.g. func1, !func2)": "",
|
||||
"Enter function name filter list (e.g. func1, !func2)": "Introdueix la llista de filtres de noms de funció (per exemple, func1, !func2)",
|
||||
"Enter Github Raw URL": "Introdueix la URL en brut de Github",
|
||||
"Enter Google PSE API Key": "Introdueix la clau API de Google PSE",
|
||||
"Enter Google PSE Engine Id": "Introdueix l'identificador del motor PSE de Google",
|
||||
|
|
@ -725,7 +727,7 @@
|
|||
"Features": "Característiques",
|
||||
"Features Permissions": "Permisos de les característiques",
|
||||
"February": "Febrer",
|
||||
"Feedback deleted successfully": "",
|
||||
"Feedback deleted successfully": "Retorn eliminat correctament",
|
||||
"Feedback Details": "Detalls del retorn",
|
||||
"Feedback History": "Històric de comentaris",
|
||||
"Feedbacks": "Comentaris",
|
||||
|
|
@ -739,8 +741,8 @@
|
|||
"File removed successfully.": "Arxiu eliminat correctament.",
|
||||
"File size should not exceed {{maxSize}} MB.": "La mida del fitxer no ha de superar els {{maxSize}} MB.",
|
||||
"File Upload": "Pujar arxiu",
|
||||
"File uploaded successfully": "arxiu pujat satisfactòriament",
|
||||
"File uploaded!": "",
|
||||
"File uploaded successfully": "Arxiu pujat satisfactòriament",
|
||||
"File uploaded!": "Arxiu pujat!",
|
||||
"Files": "Arxius",
|
||||
"Filter": "Filtre",
|
||||
"Filter is now globally disabled": "El filtre ha estat desactivat globalment",
|
||||
|
|
@ -785,7 +787,7 @@
|
|||
"Function is now globally disabled": "La funció ha estat desactivada globalment",
|
||||
"Function is now globally enabled": "La funció ha estat activada globalment",
|
||||
"Function Name": "Nom de la funció",
|
||||
"Function Name Filter List": "",
|
||||
"Function Name Filter List": "Llista de filtres de noms de funció",
|
||||
"Function updated successfully": "La funció s'ha actualitzat correctament",
|
||||
"Functions": "Funcions",
|
||||
"Functions allow arbitrary code execution.": "Les funcions permeten l'execució de codi arbitrari.",
|
||||
|
|
@ -855,7 +857,7 @@
|
|||
"Image Compression": "Compressió d'imatges",
|
||||
"Image Compression Height": "Alçada de la compressió d'imatges",
|
||||
"Image Compression Width": "Amplada de la compressió d'imatges",
|
||||
"Image Edit": "",
|
||||
"Image Edit": "Editar imatge",
|
||||
"Image Edit Engine": "Motor d'edició d'imatges",
|
||||
"Image Generation": "Generació d'imatges",
|
||||
"Image Generation Engine": "Motor de generació d'imatges",
|
||||
|
|
@ -939,7 +941,7 @@
|
|||
"Knowledge Name": "Nom del coneixement",
|
||||
"Knowledge Public Sharing": "Compartir públicament el Coneixement",
|
||||
"Knowledge reset successfully.": "Coneixement restablert correctament.",
|
||||
"Knowledge Sharing": "",
|
||||
"Knowledge Sharing": "Compartir el coneixement",
|
||||
"Knowledge updated successfully": "Coneixement actualitzat correctament.",
|
||||
"Kokoro.js (Browser)": "Kokoro.js (Navegador)",
|
||||
"Kokoro.js Dtype": "Kokoro.js Dtype",
|
||||
|
|
@ -1005,7 +1007,7 @@
|
|||
"Max Upload Size": "Mida màxima de càrrega",
|
||||
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es poden descarregar un màxim de 3 models simultàniament. Si us plau, prova-ho més tard.",
|
||||
"May": "Maig",
|
||||
"MBR": "",
|
||||
"MBR": "MBR",
|
||||
"MCP": "MCP",
|
||||
"MCP support is experimental and its specification changes often, which can lead to incompatibilities. OpenAPI specification support is directly maintained by the Open WebUI team, making it the more reliable option for compatibility.": "El suport per a MCP és experimental i la seva especificació canvia sovint, cosa que pot provocar incompatibilitats. El suport per a l'especificació d'OpenAPI el manté directament l'equip d'Open WebUI, cosa que el converteix en l'opció més fiable per a la compatibilitat.",
|
||||
"Medium": "Mig",
|
||||
|
|
@ -1062,7 +1064,7 @@
|
|||
"Models configuration saved successfully": "La configuració dels models s'ha desat correctament",
|
||||
"Models imported successfully": "Els models s'han importat correctament",
|
||||
"Models Public Sharing": "Compartició pública de models",
|
||||
"Models Sharing": "",
|
||||
"Models Sharing": "Compartir els models",
|
||||
"Mojeek Search API Key": "Clau API de Mojeek Search",
|
||||
"More": "Més",
|
||||
"More Concise": "Més precís",
|
||||
|
|
@ -1129,7 +1131,7 @@
|
|||
"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: Si s'estableix una puntuació mínima, la cerca només retornarà documents amb una puntuació major o igual a la puntuació mínima.",
|
||||
"Notes": "Notes",
|
||||
"Notes Public Sharing": "Compartició pública de les notes",
|
||||
"Notes Sharing": "",
|
||||
"Notes Sharing": "Compartir les notes",
|
||||
"Notification Sound": "So de la notificació",
|
||||
"Notification Webhook": "Webhook de la notificació",
|
||||
"Notifications": "Notificacions",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Si us plau, selecciona un model.",
|
||||
"Please select a reason": "Si us plau, selecciona una raó",
|
||||
"Please select a valid JSON file": "Si us plau, selecciona un arxiu JSON vàlid",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Si us plau, espera fins que s'hagin carregat tots els fitxers.",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Actitud positiva",
|
||||
|
|
@ -1268,7 +1271,7 @@
|
|||
"Prompts": "Indicacions",
|
||||
"Prompts Access": "Accés a les indicacions",
|
||||
"Prompts Public Sharing": "Compartició pública de indicacions",
|
||||
"Prompts Sharing": "",
|
||||
"Prompts Sharing": "Compartir les indicacions",
|
||||
"Provider Type": "Tipus de proveïdor",
|
||||
"Public": "Públic",
|
||||
"Pull \"{{searchValue}}\" from Ollama.com": "Obtenir \"{{searchValue}}\" de Ollama.com",
|
||||
|
|
@ -1352,7 +1355,7 @@
|
|||
"Run": "Executar",
|
||||
"Running": "S'està executant",
|
||||
"Running...": "S'està executant...",
|
||||
"Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "",
|
||||
"Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Executa tasques d'incrustació simultàniament per accelerar el processament. Desactiva-ho si els límits de velocitat es converteixen en un problema.",
|
||||
"Save": "Desar",
|
||||
"Save & Create": "Desar i crear",
|
||||
"Save & Update": "Desar i actualitzar",
|
||||
|
|
@ -1453,7 +1456,7 @@
|
|||
"Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt.": "Estableix la llavor del nombre aleatori que s'utilitzarà per a la generació. Establir-ho a un número específic farà que el model generi el mateix text per a la mateixa sol·licitud.",
|
||||
"Sets the size of the context window used to generate the next token.": "Estableix la mida de la finestra de context utilitzada per generar el següent token.",
|
||||
"Sets the stop sequences to use. When this pattern is encountered, the LLM will stop generating text and return. Multiple stop patterns may be set by specifying multiple separate stop parameters in a modelfile.": "Establir les seqüències d'aturada a utilitzar. Quan es trobi aquest patró, el LLM deixarà de generar text. Es poden establir diversos patrons de parada especificant diversos paràmetres de parada separats en un fitxer model.",
|
||||
"Setting": "",
|
||||
"Setting": "Preferència",
|
||||
"Settings": "Preferències",
|
||||
"Settings saved successfully!": "Les preferències s'han desat correctament",
|
||||
"Share": "Compartir",
|
||||
|
|
@ -1632,7 +1635,7 @@
|
|||
"Tools Function Calling Prompt": "Indicació per a la crida de funcions",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari.",
|
||||
"Tools Public Sharing": "Compartició pública d'eines",
|
||||
"Tools Sharing": "",
|
||||
"Tools Sharing": "Compartir les eines",
|
||||
"Top K": "Top K",
|
||||
"Top K Reranker": "Top K Reranker",
|
||||
"Transformers": "Transformadors",
|
||||
|
|
@ -1680,7 +1683,7 @@
|
|||
"Upload Pipeline": "Pujar una Pipeline",
|
||||
"Upload Progress": "Progrés de càrrega",
|
||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "Progrés de la pujada: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)",
|
||||
"Uploading file...": "",
|
||||
"Uploading file...": "Pujant l'arxiu...",
|
||||
"URL": "URL",
|
||||
"URL is required": "La URL és necessaria",
|
||||
"URL Mode": "Mode URL",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "Menú d'usuari",
|
||||
"User Webhooks": "Webhooks d'usuari",
|
||||
"Username": "Nom d'usuari",
|
||||
"users": "",
|
||||
"Users": "Usuaris",
|
||||
"Uses DefaultAzureCredential to authenticate": "Utilitza DefaultAzureCredential per a l'autenticació",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "Utilitza el registre dinàmic de clients d'OAuth 2.1",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Mga tiggamit",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Kanál byl úspěšně smazán",
|
||||
"Channel Name": "Název kanálu",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Kanál byl úspěšně aktualizován",
|
||||
"Channels": "Kanály",
|
||||
"Character": "Postava",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Přímé",
|
||||
"Direct Connections": "Přímá připojení",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Přímá připojení umožňují uživatelům připojit se k vlastním koncovým bodům API kompatibilním s OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Přímé servery nástrojů",
|
||||
"Directory selection was cancelled": "Výběr adresáře byl zrušen",
|
||||
"Disable Code Interpreter": "Zakázat interpret kódu",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Vyberte prosím model.",
|
||||
"Please select a reason": "Vyberte prosím důvod",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Prosím počkejte dokud nebudou všechny soubory nahrány.",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Pozitivní přístup",
|
||||
|
|
@ -1700,6 +1703,7 @@
|
|||
"User menu": "Uživatelská nabídka",
|
||||
"User Webhooks": "Uživatelské webhooky",
|
||||
"Username": "Uživatelské jméno",
|
||||
"users": "",
|
||||
"Users": "Uživatelé",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Kanal slettet",
|
||||
"Channel Name": "Kanalnavn",
|
||||
"Channel name cannot be empty.": "Kanalnavn må ikke være tom.",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Kanal redigeret",
|
||||
"Channels": "Kanaler",
|
||||
"Character": "Karakterer",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Direkte",
|
||||
"Direct Connections": "Direkte forbindelser",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Direkte forbindelser tillader brugere at oprette forbindelse til deres egen OpenAI kompatible API endpoints.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Direkte værktøjsservere",
|
||||
"Directory selection was cancelled": "Valg af mappe annulleret",
|
||||
"Disable Code Interpreter": "Deaktiver kode interpreter",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Vælg en model.",
|
||||
"Please select a reason": "Vælg en årsag",
|
||||
"Please select a valid JSON file": "Vælg en valid JSON-fil",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Vent venligst indtil alle filerne er uploadet.",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Positiv holdning",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Brugermenu",
|
||||
"User Webhooks": "Bruger Webhooks",
|
||||
"Username": "Brugernavn",
|
||||
"users": "",
|
||||
"Users": "Brugere",
|
||||
"Uses DefaultAzureCredential to authenticate": "Bruger DefaultAzureCredential til at autentificere",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "Bruger OAuth 2.1 Dynamic Client Registration",
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@
|
|||
"Ask": "Fragen",
|
||||
"Ask a question": "Stellen Sie eine Frage",
|
||||
"Assistant": "Assistent",
|
||||
"Async Embedding Processing": "",
|
||||
"Async Embedding Processing": "Paralleles Embedding Processing",
|
||||
"Attach File From Knowledge": "Datei von Wissensspeicher hinzufügen",
|
||||
"Attach Knowledge": "Wissensspeicher anhängen",
|
||||
"Attach Notes": "Notizen anhängen",
|
||||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Kanal erfolgreich gelöscht",
|
||||
"Channel Name": "Kanalname",
|
||||
"Channel name cannot be empty.": "Kanalname darf nicht leer sein",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Kanal erfolgreich aktualisiert",
|
||||
"Channels": "Kanäle",
|
||||
"Character": "Zeichen",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Direkt",
|
||||
"Direct Connections": "Direktverbindungen",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Direktverbindungen ermöglichen es Benutzern, sich mit ihren eigenen OpenAI-kompatiblen API-Endpunkten zu verbinden.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Direkt verbundene Werkzeug-Server",
|
||||
"Directory selection was cancelled": "Die Verzeichnisauswahl wurde abgebrochen",
|
||||
"Disable Code Interpreter": "Deaktivierte Code Interpreter",
|
||||
|
|
@ -558,7 +560,7 @@
|
|||
"Enter Datalab Marker API Base URL": "Geben Sie die Basis-URL für die Datalab Marker API ein",
|
||||
"Enter Datalab Marker API Key": "Geben Sie den Datalab Marker API-Schlüssel ein",
|
||||
"Enter description": "Geben Sie eine Beschreibung ein",
|
||||
"Enter Docling API Key": "",
|
||||
"Enter Docling API Key": "Docling API Key eingeben",
|
||||
"Enter Docling Server URL": "Docling Server-URL eingeben",
|
||||
"Enter Document Intelligence Endpoint": "Endpunkt für Document Intelligence eingeben",
|
||||
"Enter Document Intelligence Key": "Schlüssel für Document Intelligence eingeben",
|
||||
|
|
@ -573,7 +575,7 @@
|
|||
"Enter Firecrawl API Base URL": "Geben Sie die Firecrawl Basis-URL ein",
|
||||
"Enter Firecrawl API Key": "Geben Sie den Firecrawl API-Schlüssel ein",
|
||||
"Enter folder name": "Ordnernamen eingeben",
|
||||
"Enter function name filter list (e.g. func1, !func2)": "",
|
||||
"Enter function name filter list (e.g. func1, !func2)": "Funktionsnamen Filter-Liste eingeben (z.B. func1, !func2)",
|
||||
"Enter Github Raw URL": "Geben Sie die Github Raw-URL ein",
|
||||
"Enter Google PSE API Key": "Geben Sie den Google PSE-API-Schlüssel ein",
|
||||
"Enter Google PSE Engine Id": "Geben Sie die Google PSE-Engine-ID ein",
|
||||
|
|
@ -785,7 +787,7 @@
|
|||
"Function is now globally disabled": "Die Funktion ist jetzt global deaktiviert",
|
||||
"Function is now globally enabled": "Die Funktion ist jetzt global aktiviert",
|
||||
"Function Name": "Funktionsname",
|
||||
"Function Name Filter List": "",
|
||||
"Function Name Filter List": "Funktionsnamen Filter-Liste",
|
||||
"Function updated successfully": "Funktion erfolgreich aktualisiert",
|
||||
"Functions": "Funktionen",
|
||||
"Functions allow arbitrary code execution.": "Funktionen ermöglichen die Ausführung beliebigen Codes.",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Bitte wählen Sie ein Modell aus.",
|
||||
"Please select a reason": "Bitte wählen Sie einen Grund aus",
|
||||
"Please select a valid JSON file": "Bitte wählen Sie eine gültige JSON-Datei aus",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Bitte warten Sie, bis alle Dateien hochgeladen sind.",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Positive Einstellung",
|
||||
|
|
@ -1351,7 +1354,7 @@
|
|||
"Run": "Ausführen",
|
||||
"Running": "Läuft",
|
||||
"Running...": "Läuft...",
|
||||
"Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "",
|
||||
"Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Lässt embeddings parallel laufen für schnelleres verarbeiten der Dokumente. Schalte es aus, falls Rate Limits oder Rechenressourcen knapp sind.",
|
||||
"Save": "Speichern",
|
||||
"Save & Create": "Erstellen",
|
||||
"Save & Update": "Aktualisieren",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Benutzermenü",
|
||||
"User Webhooks": "Benutzer Webhooks",
|
||||
"Username": "Benutzername",
|
||||
"users": "",
|
||||
"Users": "Benutzer",
|
||||
"Uses DefaultAzureCredential to authenticate": "Verwendet DefaultAzureCredential zur Authentifizierung",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "Verwendet OAuth 2.1 Dynamic Client Registration",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Users much users",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Το κανάλι διαγράφηκε επιτυχώς",
|
||||
"Channel Name": "Όνομα Καναλιού",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Το κανάλι ενημερώθηκε επιτυχώς",
|
||||
"Channels": "Κανάλια",
|
||||
"Character": "Χαρακτήρας",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "Άμεσες Συνδέσεις",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Οι Άμεσες Συνδέσεις επιτρέπουν στους χρήστες να συνδέσουν τα δικά τους API endpoints συμβατά με OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "Παρακαλώ επιλέξτε έναν λόγο",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Θύρα",
|
||||
"Positive attitude": "Θετική στάση",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Μενού Χρήστη",
|
||||
"User Webhooks": "Webhooks Χρήστη",
|
||||
"Username": "Όνομα Χρήστη",
|
||||
"users": "",
|
||||
"Users": "Χρήστες",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Canal borrado correctamente",
|
||||
"Channel Name": "Nombre del Canal",
|
||||
"Channel name cannot be empty.": "El nombre del Canal no puede estar vacío",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Canal actualizado correctamente",
|
||||
"Channels": "Canal",
|
||||
"Character": "Carácter",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Directo",
|
||||
"Direct Connections": "Conexiones Directas",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Las Conexiones Directas permiten a los usuarios conectar a sus propios endpoints compatibles API OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Servidores de Herramientas Directos",
|
||||
"Directory selection was cancelled": "La selección de directorio ha sido cancelada",
|
||||
"Disable Code Interpreter": "Deshabilitar Interprete de Código",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Por favor selecciona un modelo.",
|
||||
"Please select a reason": "Por favor selecciona un motivo",
|
||||
"Please select a valid JSON file": "Por favor selecciona un fichero JSON válido",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Por favor, espera a que todos los ficheros se acaben de subir",
|
||||
"Port": "Puerto",
|
||||
"Positive attitude": "Actitud Positiva",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "Menu de Usuario",
|
||||
"User Webhooks": "Usuario Webhooks",
|
||||
"Username": "Nombre de Usuario",
|
||||
"users": "",
|
||||
"Users": "Usuarios",
|
||||
"Uses DefaultAzureCredential to authenticate": "Usa DefaultAzureCredential para autentificar",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "Usa Registro dinámico de cliente OAuth 2.1",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Kanal edukalt kustutatud",
|
||||
"Channel Name": "Kanali nimi",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Kanal edukalt uuendatud",
|
||||
"Channels": "Kanalid",
|
||||
"Character": "Tegelane",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Otsene",
|
||||
"Direct Connections": "Otsesed ühendused",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Otsesed ühendused võimaldavad kasutajatel ühenduda oma OpenAI-ga ühilduvate API lõpp-punktidega.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Otsesed tööriistaserverid",
|
||||
"Directory selection was cancelled": "Kataloogi valik tühistati",
|
||||
"Disable Code Interpreter": "Keela koodi interpretaator",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Palun valige mudel.",
|
||||
"Please select a reason": "Palun valige põhjus",
|
||||
"Please select a valid JSON file": "Please select a valid JSON fail",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Please wait until all failid are uploaded.",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Positiivne suhtumine",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Kasutaja menu",
|
||||
"User Webhooks": "Kasutaja Webhooks",
|
||||
"Username": "Kasutajanimi",
|
||||
"users": "",
|
||||
"Users": "Kasutajad",
|
||||
"Uses DefaultAzureCredential to authenticate": "Uses DefaultAzureCredential kuni autendi",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "Uses OAuth 2.1 Dynamic Client Registration",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "Karakterea",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "Mesedez, hautatu arrazoi bat",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Ataka",
|
||||
"Positive attitude": "Jarrera positiboa",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "Erabiltzaile-izena",
|
||||
"users": "",
|
||||
"Users": "Erabiltzaileak",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "کانال با موفقیت حذف شد",
|
||||
"Channel Name": "نام کانال",
|
||||
"Channel name cannot be empty.": "نام کانال نمی\u200cتواند خالی باشد.",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "کانال با موفقیت به\u200cروز شد",
|
||||
"Channels": "کانال\u200cها",
|
||||
"Character": "شخصیت",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "مستقیم",
|
||||
"Direct Connections": "اتصالات مستقیم",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "اتصالات مستقیم به کاربران اجازه می\u200cدهد به نقاط پایانی API سازگار با OpenAI خود متصل شوند.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "سرورهای ابزار مستقیم",
|
||||
"Directory selection was cancelled": "انتخاب دایرکتوری لغو شد",
|
||||
"Disable Code Interpreter": "غیرفعال کردن مفسر کد",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "لطفاً یک مدل انتخاب کنید.",
|
||||
"Please select a reason": "لطفاً یک دلیل انتخاب کنید",
|
||||
"Please select a valid JSON file": "لطفاً یک فایل JSON معتبر انتخاب کنید",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "لطفاً منتظر بمانید تا همه فایل\u200cها آپلود شوند.",
|
||||
"Port": "پورت",
|
||||
"Positive attitude": "نظرات مثبت",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "منوی کاربر",
|
||||
"User Webhooks": "وب\u200cهوک\u200cهای کاربر",
|
||||
"Username": "نام کاربری",
|
||||
"users": "",
|
||||
"Users": "کاربران",
|
||||
"Uses DefaultAzureCredential to authenticate": "از DefaultAzureCredential برای احراز هویت استفاده می\u200cکند",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "از ثبت کلاینت پویا OAuth 2.1 استفاده می\u200cکند",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Kanavan poisto onnistui",
|
||||
"Channel Name": "Kanavan nimi",
|
||||
"Channel name cannot be empty.": "Kanavan nimi ei voi olla tyhjä",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Kanavan päivitys onnistui",
|
||||
"Channels": "Kanavat",
|
||||
"Character": "Kirjain",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Suora",
|
||||
"Direct Connections": "Suorat yhteydet",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Suorat yhteydet mahdollistavat käyttäjien yhdistää omia OpenAI-yhteensopivia API-päätepisteitä.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Suorat työkalu palvelimet",
|
||||
"Directory selection was cancelled": "Hakemiston valinta keskeytettiin",
|
||||
"Disable Code Interpreter": "Poista Koodin suoritus käytöstä",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Valitse malli.",
|
||||
"Please select a reason": "Valitse syy",
|
||||
"Please select a valid JSON file": "Valitse kelvollinen JSON-tiedosto",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Odota kunnes kaikki tiedostot ovat ladattu.",
|
||||
"Port": "Portti",
|
||||
"Positive attitude": "Positiivinen asenne",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Käyttäjävalikko",
|
||||
"User Webhooks": "Käyttäjän Webhook:it",
|
||||
"Username": "Käyttäjätunnus",
|
||||
"users": "",
|
||||
"Users": "Käyttäjät",
|
||||
"Uses DefaultAzureCredential to authenticate": "Käyttää DefaultAzureCredential todentamiseen",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "Käyttää OAuth 2.1 -dynaamista asiakasrekisteröintiä",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Nom du canal",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Canaux",
|
||||
"Character": "Caractère",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Direct",
|
||||
"Direct Connections": "Direct connexions",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Les connexions directes permettent aux utilisateurs de se connecter à leurs propres points d'extension API compatibles OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Serveur d'outils directs",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "Désactiver l'interpréteur de code",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Veuillez sélectionner un modèle.",
|
||||
"Please select a reason": "Veuillez sélectionner une raison",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Attitude positive",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "Menu utilisateur",
|
||||
"User Webhooks": "Webhooks utilisateur",
|
||||
"Username": "Nom d'utilisateur",
|
||||
"users": "",
|
||||
"Users": "Utilisateurs",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -153,10 +153,10 @@
|
|||
"Ask a question": "Posez votre question",
|
||||
"Assistant": "Assistant",
|
||||
"Async Embedding Processing": "",
|
||||
"Attach File From Knowledge": "",
|
||||
"Attach File From Knowledge": "Joindre un fichier depuis les connaissances",
|
||||
"Attach Knowledge": "Joindre une connaissance",
|
||||
"Attach Notes": "Joindre une note",
|
||||
"Attach Webpage": "",
|
||||
"Attach Webpage": "Joindre une page web",
|
||||
"Attention to detail": "Attention aux détails",
|
||||
"Attribute for Mail": "Attribut pour l'e-mail",
|
||||
"Attribute for Username": "Attribut pour le nom d'utilisateur",
|
||||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Le canal a été supprimée avec succès",
|
||||
"Channel Name": "Nom du canal",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Le canal a été mise à jour avec succès",
|
||||
"Channels": "Canaux",
|
||||
"Character": "Caractère",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Direct",
|
||||
"Direct Connections": "Direct connexions",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Les connexions directes permettent aux utilisateurs de se connecter à leurs propres points d'extension API compatibles OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Serveur d'outils directs",
|
||||
"Directory selection was cancelled": "La sélection du répertoire a été annulée",
|
||||
"Disable Code Interpreter": "Désactiver l'interpréteur de code",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Veuillez sélectionner un modèle.",
|
||||
"Please select a reason": "Veuillez sélectionner une raison",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Veuillez patienter jusqu'à ce que tous les fichiers soient téléchargés.",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Attitude positive",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "Menu utilisateur",
|
||||
"User Webhooks": "Webhooks utilisateur",
|
||||
"Username": "Nom d'utilisateur",
|
||||
"users": "",
|
||||
"Users": "Utilisateurs",
|
||||
"Uses DefaultAzureCredential to authenticate": "Utilise DefaultAzureCredential pour s'authentifier",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Nome do Canal",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Canal",
|
||||
"Character": "Caracter",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "Conexións directas",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Conexións directas permiten aos usuarios conectar cos seus propios puntos finais de API compatibles con OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Por favor seleccione un modelo.",
|
||||
"Please select a reason": "Por favor seleccione unha razón",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Puerto",
|
||||
"Positive attitude": "Actitud positiva",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "Nombre de usuario",
|
||||
"users": "",
|
||||
"Users": "Usuarios",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "גישה חיובית",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "משתמשים",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "सकारात्मक रवैया",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "उपयोगकर्ताओं",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Pozitivan stav",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Korisnici",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Csatorna neve",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Csatornák",
|
||||
"Character": "Karakter",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Közvetlen",
|
||||
"Direct Connections": "Közvetlen kapcsolatok",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "A közvetlen kapcsolatok lehetővé teszik a felhasználók számára, hogy saját OpenAI kompatibilis API végpontjaikhoz csatlakozzanak.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Közvetlen eszköszerverek",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Kérjük, válasszon egy modellt.",
|
||||
"Please select a reason": "Kérjük, válasszon egy okot",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Pozitív hozzáállás",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "Felhasználói webhookok",
|
||||
"Username": "Felhasználónév",
|
||||
"users": "",
|
||||
"Users": "Felhasználók",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Sikap positif",
|
||||
|
|
@ -1697,6 +1700,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Pengguna",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Scriosadh an cainéal go rathúil",
|
||||
"Channel Name": "Ainm Cainéal",
|
||||
"Channel name cannot be empty.": "Ní féidir ainm an chainéil a fhágáil folamh.",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Nuashonraíodh an cainéal go rathúil",
|
||||
"Channels": "Cainéil",
|
||||
"Character": "Carachtar",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Díreach",
|
||||
"Direct Connections": "Naisc Dhíreacha",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Ligeann Connections Direct d'úsáideoirí ceangal lena gcríochphointí API féin atá comhoiriúnach le OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Freastalaithe Uirlisí Díreacha",
|
||||
"Directory selection was cancelled": "Cealaíodh roghnú an eolaire",
|
||||
"Disable Code Interpreter": "Díchumasaigh Léirmhínitheoir Cód",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Roghnaigh samhail le do thoil.",
|
||||
"Please select a reason": "Roghnaigh cúis le do thoil",
|
||||
"Please select a valid JSON file": "Roghnaigh comhad JSON bailí le do thoil",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Fan go dtí go mbeidh na comhaid go léir uaslódáilte.",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Dearcadh dearfach",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Roghchlár úsáideora",
|
||||
"User Webhooks": "Crúcaí Gréasáin Úsáideoir",
|
||||
"Username": "Ainm Úsáideora",
|
||||
"users": "",
|
||||
"Users": "Úsáideoirí",
|
||||
"Uses DefaultAzureCredential to authenticate": "Úsáideann sé DefaultAzureCredential chun fíordheimhniú",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "Úsáideann Clárú Cliant Dinimiciúil OAuth 2.1",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Nome canale",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Canali",
|
||||
"Character": "Carattere",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Diretto",
|
||||
"Direct Connections": "Connessioni Dirette",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Le Connessioni Dirette consentono agli utenti di connettersi ai propri endpoint API compatibili con OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Strimentu Server Diretti",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Si prega di selezionare un modello.",
|
||||
"Please select a reason": "Si prega di selezionare un motivo",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Porta",
|
||||
"Positive attitude": "Attitudine positiva",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "Webhook Utente",
|
||||
"Username": "Nome Utente",
|
||||
"users": "",
|
||||
"Users": "Utenti",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "チャンネルが正常に削除されました",
|
||||
"Channel Name": "チャンネル名",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "チャンネルが正常に更新されました",
|
||||
"Channels": "チャンネル",
|
||||
"Character": "文字",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "直接",
|
||||
"Direct Connections": "ダイレクトコネクション",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "ダイレクトコネクションは、ユーザーが独自のOpenAI互換APIエンドポイントに接続できるようにします。",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "ダイレクトツールサーバー",
|
||||
"Directory selection was cancelled": "ディレクトリ選択がキャンセルされました",
|
||||
"Disable Code Interpreter": "コードインタプリタを無効化",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "モデルを選択してください。",
|
||||
"Please select a reason": "理由を選択してください。",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "ファイルがすべてアップロードされるまでお待ちください。",
|
||||
"Port": "ポート",
|
||||
"Positive attitude": "ポジティブな態度",
|
||||
|
|
@ -1697,6 +1700,7 @@
|
|||
"User menu": "ユーザーメニュー",
|
||||
"User Webhooks": "ユーザWebhook",
|
||||
"Username": "ユーザー名",
|
||||
"users": "",
|
||||
"Users": "ユーザー",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "არხი წარმატებით წაიშალა",
|
||||
"Channel Name": "არხის სახელი",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "არხი წარმატებით განახლდა",
|
||||
"Channels": "არხები",
|
||||
"Character": "სიმბოლო",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "პირდაპირი",
|
||||
"Direct Connections": "პირდაპირი მიერთება",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "აირჩიეთ მოდელი.",
|
||||
"Please select a reason": "აირჩიეთ მიზეზი",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "პორტი",
|
||||
"Positive attitude": "პოზიტიური დამოკიდებულება",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "მომხმარებლის მენიუ",
|
||||
"User Webhooks": "ვებჰუკების გამოყენება",
|
||||
"Username": "მომხმარებლის სახელი",
|
||||
"users": "",
|
||||
"Users": "მომხმარებლები",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Yettwakkes ubadu akken iwata",
|
||||
"Channel Name": "Isem n ubadu",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Yettwaleqqem ubadu akken iwata",
|
||||
"Channels": "Ibuda",
|
||||
"Character": "Asekkil",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Srid",
|
||||
"Direct Connections": "Tuqqniwin tusridin",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Tuqqniwin tusridin ttaǧǧant iseqdacen ad qqnen ɣer wagazen-nsen n taggara API imṣadan OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Iqeddacen n ifecka usriden",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "Sens asegzay n tengalt",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Ttxil-k, fren tamudemt.",
|
||||
"Please select a reason": "Ma ulac aɣilif ini-d acuɣeṛ",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Ttxil-k·m, ṛǧu alamma ulin-d akk ifuyla.",
|
||||
"Port": "Tawwurt",
|
||||
"Positive attitude": "",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Umuɣ n useqdac",
|
||||
"User Webhooks": "Webhooks n yiseqdacen",
|
||||
"Username": "Isem n useqdac",
|
||||
"users": "",
|
||||
"Users": "Iseqdacen",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "채널 삭제 성공",
|
||||
"Channel Name": "채널 이름",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "채널 업데이트 성공",
|
||||
"Channels": "채널",
|
||||
"Character": "캐릭터",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "직접 연결",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "직접 연결을 통해 사용자는 자체 OpenAI 호환 API 엔드포인트에 연결할 수 있습니다.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "다이렉트 도구 서버",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "코드 인터프리터 비활성화",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "모델을 선택하세요.",
|
||||
"Please select a reason": "이유를 선택해주세요",
|
||||
"Please select a valid JSON file": "올바른 Json 파일을 선택해 주세요",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "모든 파일이 업로드될 때까지 기다려 주세요.",
|
||||
"Port": "포트",
|
||||
"Positive attitude": "긍정적인 자세",
|
||||
|
|
@ -1697,6 +1700,7 @@
|
|||
"User menu": "사용자 메뉴",
|
||||
"User Webhooks": "사용자 웹훅",
|
||||
"Username": "사용자 이름",
|
||||
"users": "",
|
||||
"Users": "사용자",
|
||||
"Uses DefaultAzureCredential to authenticate": "DefaultAzureCredential을 사용하여 인증합니다",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "OAuth 2.1 동적 클라이언트 등록을 사용합니다",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Pozityvus elgesys",
|
||||
|
|
@ -1700,6 +1703,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Naudotojai",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Sikap positif",
|
||||
|
|
@ -1697,6 +1700,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Pengguna",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Kanalens navn",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Kanaler",
|
||||
"Character": "Karakter",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "Direkte koblinger",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Med direkte koblinger kan brukerne koble til egne OpenAI-kompatible API-endepunkter.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Velg en modell.",
|
||||
"Please select a reason": "Velg en årsak",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Positiv holdning",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "Brukernavn",
|
||||
"users": "",
|
||||
"Users": "Brukere",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Kanaalnaam",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Kanalen",
|
||||
"Character": "Karakter",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Direct",
|
||||
"Direct Connections": "Directe verbindingen",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Directe verbindingen stellen gebruikers in staat om met hun eigen OpenAI compatibele API-endpoints te verbinden.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Selecteer een model",
|
||||
"Please select a reason": "Voer een reden in",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Poort",
|
||||
"Positive attitude": "Positieve houding",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "Gebruiker-webhooks",
|
||||
"Username": "Gebruikersnaam",
|
||||
"users": "",
|
||||
"Users": "Gebruikers",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "ਸਕਾਰਾਤਮਕ ਰਵੱਈਆ",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "ਉਪਭੋਗਤਾ",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Nazwa kanału",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Kanały",
|
||||
"Character": "Znak",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "Połączenia bezpośrednie",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Połączenia bezpośrednie umożliwiają użytkownikom łączenie się z własnymi końcówkami API kompatybilnymi z OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Proszę wybrać model.",
|
||||
"Please select a reason": "Proszę wybrać powód",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Pozytywne nastawienie",
|
||||
|
|
@ -1700,6 +1703,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "Webhooki użytkownika",
|
||||
"Username": "Nazwa użytkownika",
|
||||
"users": "",
|
||||
"Users": "Użytkownicy",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Canal apagado com sucesso",
|
||||
"Channel Name": "Nome do canal",
|
||||
"Channel name cannot be empty.": "O nome do canal não pode estar vazio.",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Canal atualizado com sucesso",
|
||||
"Channels": "Canais",
|
||||
"Character": "Caracter",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Direto",
|
||||
"Direct Connections": "Conexões Diretas",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "As conexões diretas permitem que os usuários se conectem aos seus próprios terminais de API compatíveis com OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Servidores de ferramentas diretas",
|
||||
"Directory selection was cancelled": "A seleção do diretório foi cancelada",
|
||||
"Disable Code Interpreter": "Desativar o interpretador de código",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Selecione um modelo.",
|
||||
"Please select a reason": "Por favor, seleccione uma razão",
|
||||
"Please select a valid JSON file": "Selecione um arquivo JSON válido",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Aguarde até que todos os arquivos sejam enviados.",
|
||||
"Port": "Porta",
|
||||
"Positive attitude": "Atitude positiva",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "Menu do usuário",
|
||||
"User Webhooks": "Webhooks do usuário",
|
||||
"Username": "Nome do Usuário",
|
||||
"users": "",
|
||||
"Users": "Usuários",
|
||||
"Uses DefaultAzureCredential to authenticate": "Usa DefaultAzureCredential para autenticar",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "Utiliza o registro dinâmico de cliente OAuth 2.1",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Atitude Positiva",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Utilizadores",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "Caracter",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "Vă rugăm să selectați un motiv",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Atitudine pozitivă",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "",
|
||||
"users": "",
|
||||
"Users": "Utilizatori",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Канал успешно удалён",
|
||||
"Channel Name": "Название канала",
|
||||
"Channel name cannot be empty.": "Название канала не может быть пустым.",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Канал успешно обновлён",
|
||||
"Channels": "Каналы",
|
||||
"Character": "Символ",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Прямое",
|
||||
"Direct Connections": "Прямые подключения",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Прямые подключения позволяют пользователям подключаться к своим собственным конечным точкам API, совместимым с OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Доступ к серверам инструментов",
|
||||
"Directory selection was cancelled": "Выбор папки был отменён",
|
||||
"Disable Code Interpreter": "Отключить интерпретатор кода",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Пожалуйста, выберите модель.",
|
||||
"Please select a reason": "Пожалуйста, выберите причину",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Пожалуйста, подождите, пока все файлы будут загружены.",
|
||||
"Port": "Порт",
|
||||
"Positive attitude": "Позитивный настрой",
|
||||
|
|
@ -1700,6 +1703,7 @@
|
|||
"User menu": "Меню пользователя",
|
||||
"User Webhooks": "Пользовательские веб-хуки",
|
||||
"Username": "Имя пользователя",
|
||||
"users": "",
|
||||
"Users": "Пользователи",
|
||||
"Uses DefaultAzureCredential to authenticate": "Использует DefaultAzureCredential для аутентификации",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "Znak",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "Prosím vyberte dôvod",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Pozitívny prístup",
|
||||
|
|
@ -1700,6 +1703,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "Používateľské meno",
|
||||
"users": "",
|
||||
"Users": "Používatelia",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Назив канала",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Канали",
|
||||
"Character": "Знак",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Позитиван став",
|
||||
|
|
@ -1699,6 +1702,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "Корисничко име",
|
||||
"users": "",
|
||||
"Users": "Корисници",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Kanalen raderad",
|
||||
"Channel Name": "Kanalnamn",
|
||||
"Channel name cannot be empty.": "Kanalnamnet kan inte vara tomt.",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Kanalen uppdaterad",
|
||||
"Channels": "Kanaler",
|
||||
"Character": "Tecken",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Direkt",
|
||||
"Direct Connections": "Direkta anslutningar",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Direkta anslutningar tillåter användare att ansluta till sina egna OpenAI-kompatibla API-endpoints.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "Direkta verktygsservrar",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "Inaktivera kodtolkare",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Vänligen välj en modell.",
|
||||
"Please select a reason": "Vänligen välj en anledning",
|
||||
"Please select a valid JSON file": "Vänligen välj en giltig JSON-fil",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "Vänta tills alla filer har laddats upp.",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Positivt inställning",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Användarmenyn",
|
||||
"User Webhooks": "Användar-webhooks",
|
||||
"Username": "Användarnamn",
|
||||
"users": "",
|
||||
"Users": "Användare",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "ลบช่องสำเร็จแล้ว",
|
||||
"Channel Name": "ชื่อช่องทาง",
|
||||
"Channel name cannot be empty.": "ชื่อช่องไม่สามารถเว้นว่างได้",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "อัปเดตช่องสำเร็จแล้ว",
|
||||
"Channels": "ช่องทาง",
|
||||
"Character": "ตัวละคร",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "โดยตรง",
|
||||
"Direct Connections": "การเชื่อมต่อโดยตรง",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Direct Connections อนุญาตให้ผู้ใช้เชื่อมต่อกับปลายทาง API ที่เข้ากันได้กับ OpenAI ของตนเอง",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "เซิร์ฟเวอร์เครื่องมือโดยตรง",
|
||||
"Directory selection was cancelled": "การเลือกไดเรกทอรีถูกยกเลิก",
|
||||
"Disable Code Interpreter": "ปิดใช้งาน Code Interpreter",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "โปรดเลือกโมเดล",
|
||||
"Please select a reason": "โปรดเลือกเหตุผล",
|
||||
"Please select a valid JSON file": "โปรดเลือกไฟล์ JSON ที่ถูกต้อง",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "โปรดรอจนกว่าไฟล์ทั้งหมดจะอัปโหลดเสร็จสิ้น",
|
||||
"Port": "พอร์ต",
|
||||
"Positive attitude": "ทัศนคติเชิงบวก",
|
||||
|
|
@ -1697,6 +1700,7 @@
|
|||
"User menu": "เมนูผู้ใช้",
|
||||
"User Webhooks": "Webhooks ของผู้ใช้",
|
||||
"Username": "ชื่อผู้ใช้",
|
||||
"users": "",
|
||||
"Users": "ผู้ใช้",
|
||||
"Uses DefaultAzureCredential to authenticate": "ใช้ DefaultAzureCredential เพื่อยืนยันตัวตน",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "ใช้ OAuth 2.1 การลงทะเบียนไคลเอนต์แบบไดนามิก",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "",
|
||||
"Character": "",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "",
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "",
|
||||
"Username": "Ulanyjy Ady",
|
||||
"users": "",
|
||||
"Users": "Ulanyjylar",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "Kanal başarıyla silindi",
|
||||
"Channel Name": "Kanal Adı",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "Kanal başarıyla güncellendi",
|
||||
"Channels": "Kanallar",
|
||||
"Character": "Karakter",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Doğrudan",
|
||||
"Direct Connections": "Doğrudan Bağlantılar",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "Kod Yorumlayıcıyı Devre Dışı Bırak",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Lütfen bir model seçin",
|
||||
"Please select a reason": "Lütfen bir neden seçin",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Olumlu yaklaşım",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "Kullanıcı menüsü",
|
||||
"User Webhooks": "",
|
||||
"Username": "Kullanıcı Adı",
|
||||
"users": "",
|
||||
"Users": "Kullanıcılar",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "قانال ئاتى",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "قاناللار",
|
||||
"Character": "ھەرپ",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "بىۋاسىتە",
|
||||
"Direct Connections": "بىۋاسىتە ئۇلىنىشلار",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "بىۋاسىتە ئۇلىنىشلار ئىشلەتكۈچىلەرگە ئۆز OpenAI ماس كېلىدىغان API ئۇلانمىسىغا باغلىنىشقا يول قويىدۇ.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "بىۋاسىتە قورال مۇلازىمېتىرلىرى",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "مودېل تاللاڭ.",
|
||||
"Please select a reason": "سەۋەب تاللاڭ",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "ئېغىز",
|
||||
"Positive attitude": "ئىجابىي پوزىتسىيە",
|
||||
|
|
@ -1698,6 +1701,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "ئىشلەتكۈچى Webhookلىرى",
|
||||
"Username": "ئىشلەتكۈچى نامى",
|
||||
"users": "",
|
||||
"Users": "ئىشلەتكۈچىلەر",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"Channel deleted successfully": "",
|
||||
"Channel Name": "Назва каналу",
|
||||
"Channel name cannot be empty.": "",
|
||||
"Channel Type": "",
|
||||
"Channel updated successfully": "",
|
||||
"Channels": "Канали",
|
||||
"Character": "Персонаж",
|
||||
|
|
@ -429,6 +430,7 @@
|
|||
"Direct": "Прямий",
|
||||
"Direct Connections": "Прямі з'єднання",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Прямі з'єднання дозволяють користувачам підключатися до своїх власних API-кінцевих точок, сумісних з OpenAI.",
|
||||
"Direct Message": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Directory selection was cancelled": "",
|
||||
"Disable Code Interpreter": "",
|
||||
|
|
@ -1246,6 +1248,7 @@
|
|||
"Please select a model.": "Будь ласка, виберіть модель.",
|
||||
"Please select a reason": "Будь ласка, виберіть причину",
|
||||
"Please select a valid JSON file": "",
|
||||
"Please select at least one user for Direct Message channel.": "",
|
||||
"Please wait until all files are uploaded.": "",
|
||||
"Port": "Порт",
|
||||
"Positive attitude": "Позитивне ставлення",
|
||||
|
|
@ -1700,6 +1703,7 @@
|
|||
"User menu": "",
|
||||
"User Webhooks": "Вебхуки користувача",
|
||||
"Username": "Ім'я користувача",
|
||||
"users": "",
|
||||
"Users": "Користувачі",
|
||||
"Uses DefaultAzureCredential to authenticate": "",
|
||||
"Uses OAuth 2.1 Dynamic Client Registration": "",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue