diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 5a9844c067..54ca0218d7 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -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", diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index af8e670a53..727bfe65dd 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -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, diff --git a/backend/open_webui/migrations/versions/2f1211949ecc_update_message_and_channel_member_table.py b/backend/open_webui/migrations/versions/2f1211949ecc_update_message_and_channel_member_table.py new file mode 100644 index 0000000000..2d72583ebe --- /dev/null +++ b/backend/open_webui/migrations/versions/2f1211949ecc_update_message_and_channel_member_table.py @@ -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") diff --git a/backend/open_webui/models/auths.py b/backend/open_webui/models/auths.py index 39ff1cc7fb..0d0b881a78 100644 --- a/backend/open_webui/models/auths.py +++ b/backend/open_webui/models/auths.py @@ -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 diff --git a/backend/open_webui/models/channels.py b/backend/open_webui/models/channels.py index 5f4d1436d9..5d452b0216 100644 --- a/backend/open_webui/models/channels.py +++ b/backend/open_webui/models/channels.py @@ -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,12 +179,210 @@ class ChannelTable: self, user_id: str, permission: str = "read" ) -> list[ChannelModel]: channels = self.get_channels() - return [ - channel - for channel in channels - if channel.user_id == user_id - or has_access(user_id, permission, channel.access_control) - ] + + 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 [ + 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: diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py index 6aaf09ca46..1094035fd5 100644 --- a/backend/open_webui/models/messages.py +++ b/backend/open_webui/models/messages.py @@ -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]: diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index e902a978d1..329b87a91f 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -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": diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index d93f7ddeb3..5809a7124f 100644 --- a/backend/open_webui/models/users.py +++ b/backend/open_webui/models/users.py @@ -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 diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 764196c5f1..24cbd9a03f 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -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, diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py index e47c98554e..1bf905155e 100644 --- a/backend/open_webui/routers/channels.py +++ b/backend/open_webui/routers/channels.py @@ -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,33 +167,82 @@ 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 ) - if user.role != "admin" 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() + 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 ) - write_access = has_access( - user.id, type="write", access_control=channel.access_control, strict=False - ) + 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, + } + ) - user_count = len(get_users_with_access("read", channel.access_control)) + else: + if user.role != "admin" 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() + ) - return ChannelResponse( - **{ - **channel.model_dump(), - "write_access": write_access or user.role == "admin", - "user_count": user_count, - } - ) + write_access = has_access( + user.id, type="write", access_control=channel.access_control, strict=False + ) + + user_count = len(get_users_with_access("read", channel.access_control)) + + 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,36 +262,90 @@ async def get_channel_users_by_id( page = max(1, page) skip = (page - 1) * limit - filter = { - "roles": ["!pending"], - } - - if query: - filter["query"] = query - if order_by: - filter["order_by"] = order_by - if direction: - filter["direction"] = direction - - permitted_ids = get_permitted_group_and_user_ids("read", channel.access_control) - if permitted_ids: - filter["user_ids"] = permitted_ids.get("user_ids") - filter["group_ids"] = permitted_ids.get("group_ids") - - result = Users.get_users(filter=filter, skip=skip, limit=limit) - - users = result["users"] - total = result["total"] - - return { - "users": [ - UserModelResponse( - **user.model_dump(), is_active=get_active_status_by_user_id(user.id) + 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() ) - for user in users - ], - "total": total, - } + + 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"], + } + + if query: + filter["query"] = query + if order_by: + filter["order_by"] = order_by + if direction: + filter["direction"] = direction + + permitted_ids = get_permitted_group_and_user_ids("read", channel.access_control) + if permitted_ids: + filter["user_ids"] = permitted_ids.get("user_ids") + filter["group_ids"] = permitted_ids.get("group_ids") + + result = Users.get_users(filter=filter, skip=skip, limit=limit) + + users = result["users"] + total = result["total"] + + return { + "users": [ + UserModelResponse( + **user.model_dump(), is_active=get_active_status_by_user_id(user.id) + ) + for user in users + ], + "total": total, + } + + +################################################# +# 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 ############################ @@ -252,12 +415,22 @@ async def get_channel_messages( status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND ) - if user.role != "admin" 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() - ) + 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 + ): + raise HTTPException( + 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,16 +676,30 @@ async def new_message_handler( status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND ) - if user.role != "admin" and not has_access( - user.id, type="write", access_control=channel.access_control, strict=False - ): - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, 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 not has_access( + user.id, type="write", access_control=channel.access_control, strict=False + ): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() + ) 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,12 +798,18 @@ async def get_channel_message( status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND ) - if user.role != "admin" 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() - ) + 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 + ): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() + ) message = Messages.get_message_by_id(message_id) if not message: @@ -658,12 +853,18 @@ async def get_channel_thread_messages( status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND ) - if user.role != "admin" 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() - ) + 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 + ): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() + ) message_list = Messages.get_messages_by_parent_id(id, message_id, skip, limit) users = {} @@ -717,14 +918,22 @@ async def update_message_by_id( status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() ) - if ( - user.role != "admin" - and message.user_id != user.id - 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() - ) + 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 + ) + ): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() + ) try: message = Messages.update_message_by_id(message_id, form_data) @@ -773,12 +982,18 @@ async def add_reaction_to_message( status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND ) - if user.role != "admin" and not has_access( - user.id, type="write", access_control=channel.access_control, strict=False - ): - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, 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 not has_access( + user.id, type="write", access_control=channel.access_control, strict=False + ): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() + ) message = Messages.get_message_by_id(message_id) if not message: @@ -836,12 +1051,18 @@ async def remove_reaction_by_id_and_user_id_and_name( status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND ) - if user.role != "admin" and not has_access( - user.id, type="write", access_control=channel.access_control, strict=False - ): - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, 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 not has_access( + user.id, type="write", access_control=channel.access_control, strict=False + ): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() + ) message = Messages.get_message_by_id(message_id) if not message: @@ -913,16 +1134,25 @@ async def delete_message_by_id( status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() ) - 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 - ) - ): - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, 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, + ) + ): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() + ) try: Messages.delete_message_by_id(message_id) diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index 03212bdb7c..fe2bf367bf 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -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 diff --git a/backend/open_webui/routers/groups.py b/backend/open_webui/routers/groups.py index 2b531b462b..b68db3a15e 100755 --- a/backend/open_webui/routers/groups.py +++ b/backend/open_webui/routers/groups.py @@ -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 ############################ diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index ad47fc1686..654f11588a 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -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, diff --git a/backend/open_webui/routers/models.py b/backend/open_webui/routers/models.py index 93d8cb8bf7..df5a7377dc 100644 --- a/backend/open_webui/routers/models.py +++ b/backend/open_webui/routers/models.py @@ -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) diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index 6080337250..72090e3ba0 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -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 diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index 0b44e4319a..9b30ba8f20 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -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), } ) diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index f79d1dd958..04b67dd786 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -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") diff --git a/backend/open_webui/socket/utils.py b/backend/open_webui/socket/utils.py index 168d2fd88e..5739a8027a 100644 --- a/backend/open_webui/socket/utils.py +++ b/backend/open_webui/socket/utils.py @@ -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] diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index efa187a382..cc2de8e1c7 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -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: diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index 8b53ce5193..525ba22e76 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -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 diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index dc53be7ac5..ac1a4818c1 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -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, @@ -1059,16 +1060,22 @@ class OAuthManager: for nested_claim in nested_claims: claim_data = claim_data.get(nested_claim, {}) - # Try flat claim structure as alternative - if not claim_data: - claim_data = user_data.get(oauth_claim, {}) + # Try flat claim structure as alternative + if not claim_data: + claim_data = user_data.get(oauth_claim, {}) - oauth_roles = [] + oauth_roles = [] - if isinstance(claim_data, list): - oauth_roles = claim_data - if isinstance(claim_data, str) or isinstance(claim_data, int): - oauth_roles = [str(claim_data)] + if isinstance(claim_data, list): + oauth_roles = claim_data + 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}") log.debug(f"User roles from oauth: {oauth_roles}") @@ -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 diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 268624135d..2baff503ee 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -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"] diff --git a/backend/requirements-min.txt b/backend/requirements-min.txt index c09f1af820..8d63bd4b82 100644 --- a/backend/requirements-min.txt +++ b/backend/requirements-min.txt @@ -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 diff --git a/backend/requirements.txt b/backend/requirements.txt index 658e249090..ba8cbbfc07 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -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 diff --git a/pyproject.toml b/pyproject.toml index f0568a4237..709f4ec672 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/src/lib/apis/channels/index.ts b/src/lib/apis/channels/index.ts index 2872bd89f8..5b510491fe 100644 --- a/src/lib/apis/channels/index.ts +++ b/src/lib/apis/channels/index.ts @@ -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, diff --git a/src/lib/components/AddToolServerModal.svelte b/src/lib/components/AddToolServerModal.svelte index 2b639b3e64..79fe4c97fc 100644 --- a/src/lib/components/AddToolServerModal.svelte +++ b/src/lib/components/AddToolServerModal.svelte @@ -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; } }; diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index a7b62857c0..d46f37a89f 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -676,6 +676,14 @@ +
+
+ {$i18n.t('Folders')} +
+ + +
+
{$i18n.t('Notes')} ({$i18n.t('Beta')}) diff --git a/src/lib/components/admin/Users/Groups/EditGroupModal.svelte b/src/lib/components/admin/Users/Groups/EditGroupModal.svelte index ef97294c96..d105c75d50 100644 --- a/src/lib/components/admin/Users/Groups/EditGroupModal.svelte +++ b/src/lib/components/admin/Users/Groups/EditGroupModal.svelte @@ -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 } }; diff --git a/src/lib/components/admin/Users/Groups/Permissions.svelte b/src/lib/components/admin/Users/Groups/Permissions.svelte index 9f0e4ef2e9..892fc6fb03 100644 --- a/src/lib/components/admin/Users/Groups/Permissions.svelte +++ b/src/lib/components/admin/Users/Groups/Permissions.svelte @@ -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 } }; diff --git a/src/lib/components/admin/Users/Groups/Users.svelte b/src/lib/components/admin/Users/Groups/Users.svelte index e017187677..ab544e5c8a 100644 --- a/src/lib/components/admin/Users/Groups/Users.svelte +++ b/src/lib/components/admin/Users/Groups/Users.svelte @@ -113,7 +113,7 @@ class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full" > - + diff --git a/src/lib/components/channel/Channel.svelte b/src/lib/components/channel/Channel.svelte index 40ce02f3fe..3caf18bcab 100644 --- a/src/lib/components/channel/Channel.svelte +++ b/src/lib/components/channel/Channel.svelte @@ -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); }); - #{channel?.name ?? 'Channel'} • Open WebUI + {#if channel?.type === 'dm'} + {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 + {:else} + #{channel?.name ?? 'Channel'} • Open WebUI + {/if}
-
- {#if channel?.access_control === null} - - {:else} - - {/if} -
+ {#if channel?.type === 'dm'} +
+ {$i18n.t('Direct Message')} +
+ {:else} +
+ {#if channel?.access_control === null} + + {:else} + + {/if} +
-
- {channel.name} -
+
+ {channel.name} +
+ {/if}
diff --git a/src/lib/components/channel/ChannelInfoModal/UserList.svelte b/src/lib/components/channel/ChannelInfoModal/UserList.svelte index a38ad352f9..b8fff44da3 100644 --- a/src/lib/components/channel/ChannelInfoModal/UserList.svelte +++ b/src/lib/components/channel/ChannelInfoModal/UserList.svelte @@ -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(); } @@ -94,31 +96,33 @@
{:else} -
-
-
-
- - - + {#if search} +
+
+
+
+ + + +
+
-
-
+ {/if} {#if users.length > 0}
@@ -127,9 +131,10 @@ class="text-xs text-gray-800 uppercase bg-transparent dark:text-gray-200 w-full mb-0.5" >
{/if} + + + {#if $showSidebar}
{ - if ($user?.role === 'admin') { - await tick(); + onAdd={$user?.role === 'admin' + ? async () => { + await tick(); - setTimeout(() => { - showCreateChannel = true; - }, 0); - } - }} + setTimeout(() => { + showCreateChannel = true; + }, 0); + } + : null} onAddLabel={$i18n.t('Create Channel')} > - {#each $channels as channel} + {#each $channels as channel, channelIdx (`${channel?.id}`)} { await initChannels(); }} /> + + {#if channelIdx < $channels.length - 1 && channel.type !== $channels[channelIdx + 1]?.type}
+ {/if} {/each} {/if} - {#if folders} + {#if $config?.features?.enable_folders && ($user?.role === 'admin' || ($user?.permissions?.features?.folders ?? true))} {}; @@ -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" > { 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" > -
-
- {#if channel?.access_control === null} - +
+
+ {#if channel?.type === 'dm'} + {#if channel?.users} +
+ {#each channel.users.filter((u) => u.id !== $user?.id).slice(0, 2) as u, index} + {u.name} + {/each} +
+ {:else} + + {/if} {:else} - +
+ {#if channel?.access_control === null} + + {:else} + + {/if} +
{/if}
-
- {channel.name} +
+ {#if channel?.name} + {channel.name} + {:else} + {channel?.users + ?.filter((u) => u.id !== $user?.id) + .map((u) => u.name) + .join(', ')} + {/if}
+ +
+ {#if channel?.unread_count > 0} +
+ {new Intl.NumberFormat($i18n.locale, { + notation: 'compact', + compactDisplay: 'short' + }).format(channel.unread_count)} +
+ {/if} +
- {#if $user?.role === 'admin'} + {#if channel?.type === 'dm'} + {:else if $user?.role === 'admin'} + diff --git a/src/lib/components/layout/Sidebar/ChannelModal.svelte b/src/lib/components/layout/Sidebar/ChannelModal.svelte index 618f7508bf..1000caf0a3 100644 --- a/src/lib/components/layout/Sidebar/ChannelModal.svelte +++ b/src/lib/components/layout/Sidebar/ChannelModal.svelte @@ -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; }; @@ -108,26 +118,52 @@ submitHandler(); }} > + {#if !edit} +
+
{$i18n.t('Channel Type')}
+ +
+ +
+
+ {/if} +
-
{$i18n.t('Channel Name')}
+
+ {$i18n.t('Channel Name')} + {type === 'dm' ? `${$i18n.t('Optional')}` : ''} +
-
+
-
-
- -
+
+ {#if type === 'dm'} + + {:else} +
+ +
+ {/if}
diff --git a/src/lib/components/workspace/Models.svelte b/src/lib/components/workspace/Models.svelte index fa727bfeb5..33c7b31cdf 100644 --- a/src/lib/components/workspace/Models.svelte +++ b/src/lib/components/workspace/Models.svelte @@ -214,8 +214,6 @@ viewOption = localStorage.workspaceViewOption ?? ''; page = 1; - await getModelList(); - let groups = await getGroups(localStorage.token); groupIds = groups.map((group) => group.id); diff --git a/src/lib/components/workspace/common/UserListSelector.svelte b/src/lib/components/workspace/common/UserListSelector.svelte new file mode 100644 index 0000000000..4ea725df1a --- /dev/null +++ b/src/lib/components/workspace/common/UserListSelector.svelte @@ -0,0 +1,247 @@ + + +
+ {#if users === null || total === null} +
+ +
+ {:else} + {#if userIds.length > 0} +
+
+ {userIds.length} + {$i18n.t('users')} +
+
+ {#each userIds as id} + {#if selectedUsers[id]} + + {/if} + {/each} +
+
+ {/if} + +
+
+
+
+ + + +
+ +
+
+
+ + {#if users.length > 0} +
+
+
+
+ +
+
+
+ {#each users as user, userIdx} + {#if user?.id !== $_user?.id} + + {/if} + {/each} +
+
+
+ + {#if pagination} + {#if total > 30} + + {/if} + {/if} + {:else} +
+ {$i18n.t('No users were found.')} +
+ {/if} + {/if} +
diff --git a/src/lib/i18n/locales/ar-BH/translation.json b/src/lib/i18n/locales/ar-BH/translation.json index bcf473e6ca..b4dbd39bbe 100644 --- a/src/lib/i18n/locales/ar-BH/translation.json +++ b/src/lib/i18n/locales/ar-BH/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ar/translation.json b/src/lib/i18n/locales/ar/translation.json index 19e83be3ff..10bdf2429a 100644 --- a/src/lib/i18n/locales/ar/translation.json +++ b/src/lib/i18n/locales/ar/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json index e16d5c2bbc..eda09eac9b 100644 --- a/src/lib/i18n/locales/bg-BG/translation.json +++ b/src/lib/i18n/locales/bg-BG/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/bn-BD/translation.json b/src/lib/i18n/locales/bn-BD/translation.json index 11c3b58731..c7b565b365 100644 --- a/src/lib/i18n/locales/bn-BD/translation.json +++ b/src/lib/i18n/locales/bn-BD/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/bo-TB/translation.json b/src/lib/i18n/locales/bo-TB/translation.json index 0baa2c27e5..68922abb3d 100644 --- a/src/lib/i18n/locales/bo-TB/translation.json +++ b/src/lib/i18n/locales/bo-TB/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/bs-BA/translation.json b/src/lib/i18n/locales/bs-BA/translation.json index 3b0c4b46a9..fc467ca52b 100644 --- a/src/lib/i18n/locales/bs-BA/translation.json +++ b/src/lib/i18n/locales/bs-BA/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index 5309cb3734..93c63563ba 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -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", diff --git a/src/lib/i18n/locales/ceb-PH/translation.json b/src/lib/i18n/locales/ceb-PH/translation.json index d5514214c1..2f47887468 100644 --- a/src/lib/i18n/locales/ceb-PH/translation.json +++ b/src/lib/i18n/locales/ceb-PH/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/cs-CZ/translation.json b/src/lib/i18n/locales/cs-CZ/translation.json index 5a9a9a0824..1f9a224677 100644 --- a/src/lib/i18n/locales/cs-CZ/translation.json +++ b/src/lib/i18n/locales/cs-CZ/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/da-DK/translation.json b/src/lib/i18n/locales/da-DK/translation.json index c138bd8b61..8365fce22f 100644 --- a/src/lib/i18n/locales/da-DK/translation.json +++ b/src/lib/i18n/locales/da-DK/translation.json @@ -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", diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 7bc111b0b7..5ee899cbda 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -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", diff --git a/src/lib/i18n/locales/dg-DG/translation.json b/src/lib/i18n/locales/dg-DG/translation.json index 8653d48255..43728e9e1f 100644 --- a/src/lib/i18n/locales/dg-DG/translation.json +++ b/src/lib/i18n/locales/dg-DG/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/el-GR/translation.json b/src/lib/i18n/locales/el-GR/translation.json index 14b6b9e2c4..ef846bc02d 100644 --- a/src/lib/i18n/locales/el-GR/translation.json +++ b/src/lib/i18n/locales/el-GR/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/en-GB/translation.json b/src/lib/i18n/locales/en-GB/translation.json index ec4a5c8720..fce3dba22e 100644 --- a/src/lib/i18n/locales/en-GB/translation.json +++ b/src/lib/i18n/locales/en-GB/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index a8f4a7c958..59f9ef3a21 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json index e232ab519a..757dddd94d 100644 --- a/src/lib/i18n/locales/es-ES/translation.json +++ b/src/lib/i18n/locales/es-ES/translation.json @@ -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", diff --git a/src/lib/i18n/locales/et-EE/translation.json b/src/lib/i18n/locales/et-EE/translation.json index 3952303f7c..9c6406bece 100644 --- a/src/lib/i18n/locales/et-EE/translation.json +++ b/src/lib/i18n/locales/et-EE/translation.json @@ -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", diff --git a/src/lib/i18n/locales/eu-ES/translation.json b/src/lib/i18n/locales/eu-ES/translation.json index f31036da73..e0d42a61b0 100644 --- a/src/lib/i18n/locales/eu-ES/translation.json +++ b/src/lib/i18n/locales/eu-ES/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json index 5ebea13259..0852d5a6ef 100644 --- a/src/lib/i18n/locales/fa-IR/translation.json +++ b/src/lib/i18n/locales/fa-IR/translation.json @@ -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کند", diff --git a/src/lib/i18n/locales/fi-FI/translation.json b/src/lib/i18n/locales/fi-FI/translation.json index 0054d627f5..4410d4f085 100644 --- a/src/lib/i18n/locales/fi-FI/translation.json +++ b/src/lib/i18n/locales/fi-FI/translation.json @@ -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ä", diff --git a/src/lib/i18n/locales/fr-CA/translation.json b/src/lib/i18n/locales/fr-CA/translation.json index de9eed7fd6..71b5f13e70 100644 --- a/src/lib/i18n/locales/fr-CA/translation.json +++ b/src/lib/i18n/locales/fr-CA/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/fr-FR/translation.json b/src/lib/i18n/locales/fr-FR/translation.json index d6b53bebff..3f1a807de6 100644 --- a/src/lib/i18n/locales/fr-FR/translation.json +++ b/src/lib/i18n/locales/fr-FR/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/gl-ES/translation.json b/src/lib/i18n/locales/gl-ES/translation.json index 467ed83724..e02dade916 100644 --- a/src/lib/i18n/locales/gl-ES/translation.json +++ b/src/lib/i18n/locales/gl-ES/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/he-IL/translation.json b/src/lib/i18n/locales/he-IL/translation.json index 4806bfef88..4a0a8c8292 100644 --- a/src/lib/i18n/locales/he-IL/translation.json +++ b/src/lib/i18n/locales/he-IL/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json index 29317a6f1f..6728172bae 100644 --- a/src/lib/i18n/locales/hi-IN/translation.json +++ b/src/lib/i18n/locales/hi-IN/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/hr-HR/translation.json b/src/lib/i18n/locales/hr-HR/translation.json index 3b6e80b7cc..59f27f8a0c 100644 --- a/src/lib/i18n/locales/hr-HR/translation.json +++ b/src/lib/i18n/locales/hr-HR/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/hu-HU/translation.json b/src/lib/i18n/locales/hu-HU/translation.json index b8fb8d29e8..affa5cae4c 100644 --- a/src/lib/i18n/locales/hu-HU/translation.json +++ b/src/lib/i18n/locales/hu-HU/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/id-ID/translation.json b/src/lib/i18n/locales/id-ID/translation.json index da79fc9d24..39cc75d5dc 100644 --- a/src/lib/i18n/locales/id-ID/translation.json +++ b/src/lib/i18n/locales/id-ID/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ie-GA/translation.json b/src/lib/i18n/locales/ie-GA/translation.json index 03064d586a..8aff98baf6 100644 --- a/src/lib/i18n/locales/ie-GA/translation.json +++ b/src/lib/i18n/locales/ie-GA/translation.json @@ -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", diff --git a/src/lib/i18n/locales/it-IT/translation.json b/src/lib/i18n/locales/it-IT/translation.json index 877aa0b585..80eb6f380f 100644 --- a/src/lib/i18n/locales/it-IT/translation.json +++ b/src/lib/i18n/locales/it-IT/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ja-JP/translation.json b/src/lib/i18n/locales/ja-JP/translation.json index 77b7f06880..faa5f40c0b 100644 --- a/src/lib/i18n/locales/ja-JP/translation.json +++ b/src/lib/i18n/locales/ja-JP/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ka-GE/translation.json b/src/lib/i18n/locales/ka-GE/translation.json index 3ffa408131..11998cfa16 100644 --- a/src/lib/i18n/locales/ka-GE/translation.json +++ b/src/lib/i18n/locales/ka-GE/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/kab-DZ/translation.json b/src/lib/i18n/locales/kab-DZ/translation.json index 934fea99a5..388a5fd309 100644 --- a/src/lib/i18n/locales/kab-DZ/translation.json +++ b/src/lib/i18n/locales/kab-DZ/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ko-KR/translation.json b/src/lib/i18n/locales/ko-KR/translation.json index 0eeb6c652f..39a3eefd92 100644 --- a/src/lib/i18n/locales/ko-KR/translation.json +++ b/src/lib/i18n/locales/ko-KR/translation.json @@ -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 동적 클라이언트 등록을 사용합니다", diff --git a/src/lib/i18n/locales/lt-LT/translation.json b/src/lib/i18n/locales/lt-LT/translation.json index 3131187c1f..1368789cc3 100644 --- a/src/lib/i18n/locales/lt-LT/translation.json +++ b/src/lib/i18n/locales/lt-LT/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ms-MY/translation.json b/src/lib/i18n/locales/ms-MY/translation.json index 320a08d8f1..a63fc5c0c8 100644 --- a/src/lib/i18n/locales/ms-MY/translation.json +++ b/src/lib/i18n/locales/ms-MY/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/nb-NO/translation.json b/src/lib/i18n/locales/nb-NO/translation.json index 545b7c0498..b47cb5fcf1 100644 --- a/src/lib/i18n/locales/nb-NO/translation.json +++ b/src/lib/i18n/locales/nb-NO/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/nl-NL/translation.json b/src/lib/i18n/locales/nl-NL/translation.json index 7de6424e2c..2999a0f1c8 100644 --- a/src/lib/i18n/locales/nl-NL/translation.json +++ b/src/lib/i18n/locales/nl-NL/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/pa-IN/translation.json b/src/lib/i18n/locales/pa-IN/translation.json index 06a395c5c7..9b0692c670 100644 --- a/src/lib/i18n/locales/pa-IN/translation.json +++ b/src/lib/i18n/locales/pa-IN/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/pl-PL/translation.json b/src/lib/i18n/locales/pl-PL/translation.json index b3dbf8b471..3b9d2d727a 100644 --- a/src/lib/i18n/locales/pl-PL/translation.json +++ b/src/lib/i18n/locales/pl-PL/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index fb784569a0..38364d279f 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -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", diff --git a/src/lib/i18n/locales/pt-PT/translation.json b/src/lib/i18n/locales/pt-PT/translation.json index 7059e2499d..529bee0d7e 100644 --- a/src/lib/i18n/locales/pt-PT/translation.json +++ b/src/lib/i18n/locales/pt-PT/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ro-RO/translation.json b/src/lib/i18n/locales/ro-RO/translation.json index e288e2476e..57d2472196 100644 --- a/src/lib/i18n/locales/ro-RO/translation.json +++ b/src/lib/i18n/locales/ro-RO/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ru-RU/translation.json b/src/lib/i18n/locales/ru-RU/translation.json index 6fe43a0962..6371cb6fbb 100644 --- a/src/lib/i18n/locales/ru-RU/translation.json +++ b/src/lib/i18n/locales/ru-RU/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/sk-SK/translation.json b/src/lib/i18n/locales/sk-SK/translation.json index bbc8845e41..67c915499a 100644 --- a/src/lib/i18n/locales/sk-SK/translation.json +++ b/src/lib/i18n/locales/sk-SK/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/sr-RS/translation.json b/src/lib/i18n/locales/sr-RS/translation.json index 0ee8837ca1..d5094bf5e3 100644 --- a/src/lib/i18n/locales/sr-RS/translation.json +++ b/src/lib/i18n/locales/sr-RS/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/sv-SE/translation.json b/src/lib/i18n/locales/sv-SE/translation.json index d2824408ed..39a764cf59 100644 --- a/src/lib/i18n/locales/sv-SE/translation.json +++ b/src/lib/i18n/locales/sv-SE/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/th-TH/translation.json b/src/lib/i18n/locales/th-TH/translation.json index 6ab5eb88c2..008cca31f3 100644 --- a/src/lib/i18n/locales/th-TH/translation.json +++ b/src/lib/i18n/locales/th-TH/translation.json @@ -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 การลงทะเบียนไคลเอนต์แบบไดนามิก", diff --git a/src/lib/i18n/locales/tk-TM/translation.json b/src/lib/i18n/locales/tk-TM/translation.json index ad14ed0d4e..5529cbc87b 100644 --- a/src/lib/i18n/locales/tk-TM/translation.json +++ b/src/lib/i18n/locales/tk-TM/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/tr-TR/translation.json b/src/lib/i18n/locales/tr-TR/translation.json index 46bdcb3bb8..4794ca483e 100644 --- a/src/lib/i18n/locales/tr-TR/translation.json +++ b/src/lib/i18n/locales/tr-TR/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ug-CN/translation.json b/src/lib/i18n/locales/ug-CN/translation.json index 2c52dc1d85..87d234dc15 100644 --- a/src/lib/i18n/locales/ug-CN/translation.json +++ b/src/lib/i18n/locales/ug-CN/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json index fdb3b45368..bcabb8cc39 100644 --- a/src/lib/i18n/locales/uk-UA/translation.json +++ b/src/lib/i18n/locales/uk-UA/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/ur-PK/translation.json b/src/lib/i18n/locales/ur-PK/translation.json index 06ec8a4a49..5e932f24b1 100644 --- a/src/lib/i18n/locales/ur-PK/translation.json +++ b/src/lib/i18n/locales/ur-PK/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json b/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json index 4a165dd151..63dbb483f5 100644 --- a/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json +++ b/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json @@ -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": "", diff --git a/src/lib/i18n/locales/uz-Latn-Uz/translation.json b/src/lib/i18n/locales/uz-Latn-Uz/translation.json index ed7b6e4eac..22c9eaa000 100644 --- a/src/lib/i18n/locales/uz-Latn-Uz/translation.json +++ b/src/lib/i18n/locales/uz-Latn-Uz/translation.json @@ -223,6 +223,7 @@ "Channel deleted successfully": "", "Channel Name": "Kanal nomi", "Channel name cannot be empty.": "", + "Channel Type": "", "Channel updated successfully": "", "Channels": "Kanallar", "Character": "Xarakter", @@ -429,6 +430,7 @@ "Direct": "To'g'ridan-to'g'ri", "Direct Connections": "To'g'ridan-to'g'ri ulanishlar", "Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "To'g'ridan-to'g'ri ulanishlar foydalanuvchilarga o'zlarining OpenAI-ga mos keluvchi API so'nggi nuqtalariga ulanish imkonini beradi.", + "Direct Message": "", "Direct Tool Servers": "To'g'ridan-to'g'ri asboblar serverlari", "Directory selection was cancelled": "", "Disable Code Interpreter": "", @@ -1246,6 +1248,7 @@ "Please select a model.": "Iltimos, modelni tanlang.", "Please select a reason": "Sababini tanlang", "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": "Ijobiy munosabat", @@ -1698,6 +1701,7 @@ "User menu": "", "User Webhooks": "Foydalanuvchi veb-huklari", "Username": "Foydalanuvchi nomi", + "users": "", "Users": "Foydalanuvchilar", "Uses DefaultAzureCredential to authenticate": "", "Uses OAuth 2.1 Dynamic Client Registration": "", diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index 982c7d1644..b7949029f3 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -223,6 +223,7 @@ "Channel deleted successfully": "", "Channel Name": "Tên Kênh", "Channel name cannot be empty.": "", + "Channel Type": "", "Channel updated successfully": "", "Channels": "Kênh", "Character": "Nhân vật", @@ -429,6 +430,7 @@ "Direct": "Trực tiếp", "Direct Connections": "Kết nối Trực tiếp", "Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Kết nối Trực tiếp cho phép người dùng kết nối với các điểm cuối API tương thích OpenAI của riêng họ.", + "Direct Message": "", "Direct Tool Servers": "Máy chủ Công cụ Trực tiếp", "Directory selection was cancelled": "", "Disable Code Interpreter": "", @@ -1246,6 +1248,7 @@ "Please select a model.": "Vui lòng chọn một mô hình.", "Please select a reason": "Vui lòng chọn một lý do", "Please select a valid JSON file": "", + "Please select at least one user for Direct Message channel.": "", "Please wait until all files are uploaded.": "", "Port": "Cổng", "Positive attitude": "Thái độ tích cực", @@ -1697,6 +1700,7 @@ "User menu": "", "User Webhooks": "Webhook Người dùng", "Username": "Tên đăng nhập", + "users": "", "Users": "Người sử dụng", "Uses DefaultAzureCredential to authenticate": "", "Uses OAuth 2.1 Dynamic Client Registration": "", diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index 05c79bafdc..b02fa89c20 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -152,7 +152,7 @@ "Ask": "提问", "Ask a question": "提问", "Assistant": "助手", - "Async Embedding Processing": "", + "Async Embedding Processing": "异步嵌入处理", "Attach File From Knowledge": "引用知识库中的文件", "Attach Knowledge": "引用知识库", "Attach Notes": "引用笔记", @@ -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 兼容的接口", + "Direct Message": "", "Direct Tool Servers": "直接连接工具服务器", "Directory selection was cancelled": "已取消选择目录", "Disable Code Interpreter": "禁用代码解释器", @@ -558,7 +560,7 @@ "Enter Datalab Marker API Base URL": "输入 Datalab Marker 接口地址", "Enter Datalab Marker API Key": "输入 Datalab Marker 接口密钥", "Enter description": "输入简介描述", - "Enter Docling API Key": "", + "Enter Docling API Key": "输入 Docling 接口密钥", "Enter Docling Server URL": "输入 Docling 服务器接口地址", "Enter Document Intelligence Endpoint": "输入 Document Intelligence 端点", "Enter Document Intelligence Key": "输入 Document Intelligence 密钥", @@ -573,7 +575,7 @@ "Enter Firecrawl API Base URL": "输入 Firecrawl 接口地址", "Enter Firecrawl API Key": "输入 Firecrawl 接口密钥", "Enter folder name": "输入分组名称", - "Enter function name filter list (e.g. func1, !func2)": "", + "Enter function name filter list (e.g. func1, !func2)": "输入函数名称过滤列表(例如:func1, !func2)", "Enter Github Raw URL": "输入 Github Raw 链接", "Enter Google PSE API Key": "输入 Google PSE 接口密钥", "Enter Google PSE Engine Id": "输入 Google PSE 引擎 ID", @@ -785,7 +787,7 @@ "Function is now globally disabled": "函数全局已禁用", "Function is now globally enabled": "函数全局已启用", "Function Name": "函数名称", - "Function Name Filter List": "", + "Function Name Filter List": "函数名称过滤列表", "Function updated successfully": "函数更新成功", "Functions": "函数", "Functions allow arbitrary code execution.": "注意:函数有权执行任意代码", @@ -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": "态度积极", @@ -1350,7 +1353,7 @@ "Run": "运行", "Running": "运行中", "Running...": "运行中...", - "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.": "并行运行嵌入任务以加快处理速度。如果遇到限速问题,请关闭此选项。", "Save": "保存", "Save & Create": "保存并创建", "Save & Update": "保存并更新", @@ -1697,6 +1700,7 @@ "User menu": "用户菜单", "User Webhooks": "用户 Webhook", "Username": "用户名", + "users": "", "Users": "用户", "Uses DefaultAzureCredential to authenticate": "使用 DefaultAzureCredential 进行身份验证", "Uses OAuth 2.1 Dynamic Client Registration": "使用 OAuth 2.1 的动态客户端注册机制", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index 584b30377c..5900acf20a 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -152,7 +152,7 @@ "Ask": "提問", "Ask a question": "提出問題", "Assistant": "助理", - "Async Embedding Processing": "", + "Async Embedding Processing": "異步嵌入處理", "Attach File From Knowledge": "從知識庫附加檔案", "Attach Knowledge": "附加知識庫", "Attach Notes": "附加筆記", @@ -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": "停用程式碼解譯器", @@ -558,7 +560,7 @@ "Enter Datalab Marker API Base URL": "輸入 Datalab Marker API 請求 URL", "Enter Datalab Marker API Key": "輸入 Datalab Marker API 金鑰", "Enter description": "輸入描述", - "Enter Docling API Key": "", + "Enter Docling API Key": "輸入 Docling API 金鑰", "Enter Docling Server URL": "請輸入 Docling 伺服器 URL", "Enter Document Intelligence Endpoint": "輸入 Document Intelligence 端點", "Enter Document Intelligence Key": "輸入 Document Intelligence 金鑰", @@ -573,7 +575,7 @@ "Enter Firecrawl API Base URL": "輸入 Firecrawl API 基底 URL", "Enter Firecrawl API Key": "輸入 Firecrawl API 金鑰", "Enter folder name": "輸入分組名稱", - "Enter function name filter list (e.g. func1, !func2)": "", + "Enter function name filter list (e.g. func1, !func2)": "輸入函式名稱篩選列表(例如:func1, !func2)", "Enter Github Raw URL": "輸入 GitHub Raw URL", "Enter Google PSE API Key": "輸入 Google PSE API 金鑰", "Enter Google PSE Engine Id": "輸入 Google PSE 引擎 ID", @@ -785,7 +787,7 @@ "Function is now globally disabled": "已全域停用函式", "Function is now globally enabled": "已全域啟用函式", "Function Name": "函式名稱", - "Function Name Filter List": "", + "Function Name Filter List": "函式名稱篩選列表", "Function updated successfully": "成功更新函式", "Functions": "函式", "Functions allow arbitrary code execution.": "函式允許執行任意程式碼。", @@ -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": "積極的態度", @@ -1350,7 +1353,7 @@ "Run": "執行", "Running": "正在執行", "Running...": "正在執行...", - "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.": "同時執行嵌入任務以加快處理速度。如果遇到速率限制問題,請關閉此功能。", "Save": "儲存", "Save & Create": "儲存並建立", "Save & Update": "儲存並更新", @@ -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 動態用戶端登錄", diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index c4c2dc10c9..57257d59d3 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -51,6 +51,8 @@ export const chatId = writable(''); export const chatTitle = writable(''); export const channels = writable([]); +export const channelId = writable(null); + export const chats = writable(null); export const pinnedChats = writable([]); export const tags = writable([]); diff --git a/src/routes/(app)/admin/users/+page.svelte b/src/routes/(app)/admin/users/+page.svelte index 8a8e6be79f..3f20096169 100644 --- a/src/routes/(app)/admin/users/+page.svelte +++ b/src/routes/(app)/admin/users/+page.svelte @@ -2,11 +2,7 @@ import { goto } from '$app/navigation'; import { onMount } from 'svelte'; - import Users from '$lib/components/admin/Users.svelte'; - - onMount(() => { - goto('/admin/users/overview'); + onMount(async () => { + await goto('/admin/users/overview'); }); - - diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 9408604da6..a153423909 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -28,7 +28,9 @@ isApp, appInfo, toolServers, - playingNotificationSound + playingNotificationSound, + channels, + channelId } from '$lib/stores'; import { goto } from '$app/navigation'; import { page } from '$app/stores'; @@ -55,6 +57,7 @@ import Spinner from '$lib/components/common/Spinner.svelte'; import { getUserSettings } from '$lib/apis/users'; import dayjs from 'dayjs'; + import { getChannels } from '$lib/apis/channels'; const unregisterServiceWorkers = async () => { if ('serviceWorker' in navigator) { @@ -483,10 +486,37 @@ const type = event?.data?.type ?? null; const data = event?.data?.data ?? null; + if ($channels) { + if ($channels.find((ch) => ch.id === event.channel_id) && $channelId !== event.channel_id) { + channels.set( + $channels.map((ch) => { + if (ch.id === event.channel_id) { + if (type === 'message') { + return { + ...ch, + unread_count: (ch.unread_count ?? 0) + 1, + last_message_at: event.created_at + }; + } + } + return ch; + }) + ); + } else { + await channels.set( + (await getChannels(localStorage.token)).sort((a, b) => + a.type === b.type ? 0 : a.type === 'dm' ? 1 : -1 + ) + ); + } + } + if (type === 'message') { + const title = `${data?.user?.name}${event?.channel?.type !== 'dm' ? ` (#${event?.channel?.name})` : ''}`; + if ($isLastActiveTab) { if ($settings?.notificationEnabled ?? false) { - new Notification(`${data?.user?.name} (#${event?.channel?.name}) • Open WebUI`, { + new Notification(`${title} • Open WebUI`, { body: data?.content, icon: `${WEBUI_API_BASE_URL}/users/${data?.user?.id}/profile/image` }); @@ -499,7 +529,7 @@ goto(`/channels/${event.channel_id}`); }, content: data?.content, - title: `#${event?.channel?.name}` + title: `${title}` }, duration: 15000, unstyled: true