diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 8e922dff7..48c623bef 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -847,6 +847,12 @@ USER_PERMISSIONS = PersistentConfig( }, ) +ENABLE_CHANNELS = PersistentConfig( + "ENABLE_CHANNELS", + "channels.enable", + os.environ.get("ENABLE_CHANNELS", "False").lower() == "true", +) + ENABLE_EVALUATION_ARENA_MODELS = PersistentConfig( "ENABLE_EVALUATION_ARENA_MODELS", diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 807c87dcc..d1cca2ac4 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -58,6 +58,7 @@ from open_webui.routers import ( pipelines, tasks, auths, + channels, chats, folders, configs, @@ -198,6 +199,7 @@ from open_webui.config import ( ENABLE_SIGNUP, ENABLE_LOGIN_FORM, ENABLE_API_KEY, + ENABLE_CHANNELS, ENABLE_COMMUNITY_SHARING, ENABLE_MESSAGE_RATING, ENABLE_EVALUATION_ARENA_MODELS, @@ -406,6 +408,8 @@ app.state.config.WEBHOOK_URL = WEBHOOK_URL app.state.config.BANNERS = WEBUI_BANNERS app.state.config.MODEL_ORDER_LIST = MODEL_ORDER_LIST + +app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING @@ -737,6 +741,8 @@ app.include_router(configs.router, prefix="/api/v1/configs", tags=["configs"]) app.include_router(auths.router, prefix="/api/v1/auths", tags=["auths"]) app.include_router(users.router, prefix="/api/v1/users", tags=["users"]) + +app.include_router(channels.router, prefix="/api/v1/channels", tags=["channels"]) app.include_router(chats.router, prefix="/api/v1/chats", tags=["chats"]) app.include_router(models.router, prefix="/api/v1/models", tags=["models"]) @@ -969,6 +975,7 @@ async def get_app_config(request: Request): "enable_websocket": ENABLE_WEBSOCKET_SUPPORT, **( { + "enable_channels": app.state.config.ENABLE_CHANNELS, "enable_web_search": app.state.config.ENABLE_RAG_WEB_SEARCH, "enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION, "enable_image_generation": app.state.config.ENABLE_IMAGE_GENERATION, diff --git a/backend/open_webui/migrations/versions/57c599a3cb57_add_channel_table.py b/backend/open_webui/migrations/versions/57c599a3cb57_add_channel_table.py new file mode 100644 index 000000000..54176dc46 --- /dev/null +++ b/backend/open_webui/migrations/versions/57c599a3cb57_add_channel_table.py @@ -0,0 +1,48 @@ +"""Add channel table + +Revision ID: 57c599a3cb57 +Revises: 922e7a387820 +Create Date: 2024-12-22 03:00:00.000000 + +""" + +from alembic import op +import sqlalchemy as sa + +revision = "57c599a3cb57" +down_revision = "922e7a387820" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "channel", + sa.Column("id", sa.Text(), nullable=False, primary_key=True, unique=True), + sa.Column("user_id", sa.Text()), + sa.Column("name", sa.Text()), + sa.Column("description", sa.Text(), nullable=True), + sa.Column("data", sa.JSON(), nullable=True), + sa.Column("meta", sa.JSON(), nullable=True), + sa.Column("access_control", sa.JSON(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + ) + + op.create_table( + "message", + sa.Column("id", sa.Text(), nullable=False, primary_key=True, unique=True), + sa.Column("user_id", sa.Text()), + sa.Column("channel_id", sa.Text(), nullable=True), + sa.Column("content", sa.Text()), + sa.Column("data", sa.JSON(), nullable=True), + sa.Column("meta", sa.JSON(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + ) + + +def downgrade(): + op.drop_table("channel") + + op.drop_table("message") diff --git a/backend/open_webui/models/channels.py b/backend/open_webui/models/channels.py new file mode 100644 index 000000000..bc36146cf --- /dev/null +++ b/backend/open_webui/models/channels.py @@ -0,0 +1,132 @@ +import json +import time +import uuid +from typing import Optional + +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 or_, func, select, and_, text +from sqlalchemy.sql import exists + +#################### +# Channel DB Schema +#################### + + +class Channel(Base): + __tablename__ = "channel" + + id = Column(Text, primary_key=True) + user_id = Column(Text) + + name = Column(Text) + description = Column(Text, nullable=True) + + data = Column(JSON, nullable=True) + meta = Column(JSON, nullable=True) + access_control = Column(JSON, nullable=True) + + created_at = Column(BigInteger) + updated_at = Column(BigInteger) + + +class ChannelModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: str + user_id: str + description: Optional[str] = None + + name: str + data: Optional[dict] = None + meta: Optional[dict] = None + access_control: Optional[dict] = None + + created_at: int # timestamp in epoch + updated_at: int # timestamp in epoch + + +#################### +# Forms +#################### + + +class ChannelForm(BaseModel): + name: str + description: Optional[str] = None + data: Optional[dict] = None + meta: Optional[dict] = None + access_control: Optional[dict] = None + + +class ChannelTable: + def insert_new_channel( + self, form_data: ChannelForm, user_id: str + ) -> Optional[ChannelModel]: + with get_db() as db: + channel = ChannelModel( + **{ + **form_data.model_dump(), + "name": form_data.name.lower(), + "id": str(uuid.uuid4()), + "user_id": user_id, + "created_at": int(time.time_ns()), + "updated_at": int(time.time_ns()), + } + ) + + new_channel = Channel(**channel.model_dump()) + + db.add(new_channel) + db.commit() + return channel + + def get_channels(self) -> list[ChannelModel]: + with get_db() as db: + channels = db.query(Channel).all() + return [ChannelModel.model_validate(channel) for channel in channels] + + def get_channels_by_user_id( + 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) + ] + + def get_channel_by_id(self, id: str) -> Optional[ChannelModel]: + with get_db() as db: + channel = db.query(Channel).filter(Channel.id == id).first() + return ChannelModel.model_validate(channel) if channel else None + + def update_channel_by_id( + self, id: str, form_data: ChannelForm + ) -> Optional[ChannelModel]: + with get_db() as db: + channel = db.query(Channel).filter(Channel.id == id).first() + if not channel: + return None + + channel.name = form_data.name + channel.data = form_data.data + channel.meta = form_data.meta + channel.access_control = form_data.access_control + channel.updated_at = int(time.time_ns()) + + db.commit() + return ChannelModel.model_validate(channel) if channel else None + + def delete_channel_by_id(self, id: str): + with get_db() as db: + db.query(Channel).filter(Channel.id == id).delete() + db.commit() + return True + + +Channels = ChannelTable() diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py new file mode 100644 index 000000000..2a4322d0d --- /dev/null +++ b/backend/open_webui/models/messages.py @@ -0,0 +1,141 @@ +import json +import time +import uuid +from typing import Optional + +from open_webui.internal.db import Base, get_db +from open_webui.models.tags import TagModel, Tag, Tags + + +from pydantic import BaseModel, ConfigDict +from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON +from sqlalchemy import or_, func, select, and_, text +from sqlalchemy.sql import exists + +#################### +# Message DB Schema +#################### + + +class Message(Base): + __tablename__ = "message" + id = Column(Text, primary_key=True) + + user_id = Column(Text) + channel_id = Column(Text, nullable=True) + + content = Column(Text) + data = Column(JSON, nullable=True) + meta = Column(JSON, nullable=True) + + created_at = Column(BigInteger) # time_ns + updated_at = Column(BigInteger) # time_ns + + +class MessageModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: str + user_id: str + channel_id: Optional[str] = None + + content: str + data: Optional[dict] = None + meta: Optional[dict] = None + + created_at: int # timestamp in epoch + updated_at: int # timestamp in epoch + + +#################### +# Forms +#################### + + +class MessageForm(BaseModel): + content: str + data: Optional[dict] = None + meta: Optional[dict] = None + + +class MessageTable: + def insert_new_message( + self, form_data: MessageForm, channel_id: str, user_id: str + ) -> Optional[MessageModel]: + with get_db() as db: + id = str(uuid.uuid4()) + + ts = int(time.time_ns()) + message = MessageModel( + **{ + "id": id, + "user_id": user_id, + "channel_id": channel_id, + "content": form_data.content, + "data": form_data.data, + "meta": form_data.meta, + "created_at": ts, + "updated_at": ts, + } + ) + + result = Message(**message.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + return MessageModel.model_validate(result) if result else None + + def get_message_by_id(self, id: str) -> Optional[MessageModel]: + with get_db() as db: + message = db.get(Message, id) + return MessageModel.model_validate(message) if message else None + + def get_messages_by_channel_id( + self, channel_id: str, skip: int = 0, limit: int = 50 + ) -> list[MessageModel]: + with get_db() as db: + all_messages = ( + db.query(Message) + .filter_by(channel_id=channel_id) + .order_by(Message.created_at.desc()) + .offset(skip) + .limit(limit) + .all() + ) + return [MessageModel.model_validate(message) for message in all_messages] + + def get_messages_by_user_id( + self, user_id: str, skip: int = 0, limit: int = 50 + ) -> list[MessageModel]: + with get_db() as db: + all_messages = ( + db.query(Message) + .filter_by(user_id=user_id) + .order_by(Message.created_at.desc()) + .offset(skip) + .limit(limit) + .all() + ) + return [MessageModel.model_validate(message) for message in all_messages] + + def update_message_by_id( + self, id: str, form_data: MessageForm + ) -> Optional[MessageModel]: + with get_db() as db: + message = db.get(Message, id) + message.content = form_data.content + message.data = form_data.data + message.meta = form_data.meta + message.updated_at = int(time.time_ns()) + db.commit() + db.refresh(message) + return MessageModel.model_validate(message) if message else None + + def delete_message_by_id(self, id: str) -> bool: + with get_db() as db: + db.query(Message).filter_by(id=id).delete() + db.commit() + return True + + +Messages = MessageTable() diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index bceb72572..931711b9e 100644 --- a/backend/open_webui/models/users.py +++ b/backend/open_webui/models/users.py @@ -70,6 +70,13 @@ class UserResponse(BaseModel): profile_image_url: str +class UserNameResponse(BaseModel): + id: str + name: str + role: str + profile_image_url: str + + class UserRoleUpdateForm(BaseModel): id: str role: str diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 0b1f42edf..26b909376 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -616,6 +616,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)): "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS, "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP, "ENABLE_API_KEY": request.app.state.config.ENABLE_API_KEY, + "ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS, "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE, "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, @@ -627,6 +628,7 @@ class AdminConfig(BaseModel): SHOW_ADMIN_DETAILS: bool ENABLE_SIGNUP: bool ENABLE_API_KEY: bool + ENABLE_CHANNELS: bool DEFAULT_USER_ROLE: str JWT_EXPIRES_IN: str ENABLE_COMMUNITY_SHARING: bool @@ -640,6 +642,7 @@ async def update_admin_config( request.app.state.config.SHOW_ADMIN_DETAILS = form_data.SHOW_ADMIN_DETAILS request.app.state.config.ENABLE_SIGNUP = form_data.ENABLE_SIGNUP request.app.state.config.ENABLE_API_KEY = form_data.ENABLE_API_KEY + request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS if form_data.DEFAULT_USER_ROLE in ["pending", "user", "admin"]: request.app.state.config.DEFAULT_USER_ROLE = form_data.DEFAULT_USER_ROLE diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py new file mode 100644 index 000000000..d15a70798 --- /dev/null +++ b/backend/open_webui/routers/channels.py @@ -0,0 +1,336 @@ +import json +import logging +from typing import Optional + + +from fastapi import APIRouter, Depends, HTTPException, Request, status +from pydantic import BaseModel + + +from open_webui.socket.main import sio +from open_webui.models.users import Users, UserNameResponse + +from open_webui.models.channels import Channels, ChannelModel, ChannelForm +from open_webui.models.messages import Messages, MessageModel, MessageForm + + +from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT +from open_webui.constants import ERROR_MESSAGES +from open_webui.env import SRC_LOG_LEVELS + + +from open_webui.utils.auth import get_admin_user, get_verified_user +from open_webui.utils.access_control import has_access + +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["MODELS"]) + +router = APIRouter() + +############################ +# GetChatList +############################ + + +@router.get("/", response_model=list[ChannelModel]) +async def get_channels(user=Depends(get_verified_user)): + if user.role == "admin": + return Channels.get_channels() + else: + return Channels.get_channels_by_user_id(user.id) + + +############################ +# CreateNewChannel +############################ + + +@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(form_data, user.id) + return ChannelModel(**channel.model_dump()) + except Exception as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) + + +############################ +# GetChannelById +############################ + + +@router.get("/{id}", response_model=Optional[ChannelModel]) +async def get_channel_by_id(id: str, 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 has_access(user.id, type="read", access_control=channel.access_control): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() + ) + + return ChannelModel(**channel.model_dump()) + + +############################ +# UpdateChannelById +############################ + + +@router.post("/{id}/update", response_model=Optional[ChannelModel]) +async def update_channel_by_id( + id: str, form_data: ChannelForm, user=Depends(get_admin_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 + ) + + try: + channel = Channels.update_channel_by_id(id, form_data) + return ChannelModel(**channel.model_dump()) + except Exception as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) + + +############################ +# DeleteChannelById +############################ + + +@router.delete("/{id}/delete", response_model=bool) +async def delete_channel_by_id(id: str, user=Depends(get_admin_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 + ) + + try: + Channels.delete_channel_by_id(id) + return True + except Exception as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) + + +############################ +# GetChannelMessages +############################ + + +class MessageUserModel(MessageModel): + user: UserNameResponse + + +@router.get("/{id}/messages", response_model=list[MessageUserModel]) +async def get_channel_messages( + id: str, skip: int = 0, limit: int = 50, 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 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_channel_id(id, skip, limit) + users = {} + + messages = [] + for message in message_list: + if message.user_id not in users: + user = Users.get_user_by_id(message.user_id) + users[message.user_id] = user + + messages.append( + MessageUserModel( + **{ + **message.model_dump(), + "user": UserNameResponse(**users[message.user_id].model_dump()), + } + ) + ) + + return messages + + +############################ +# PostNewMessage +############################ + + +@router.post("/{id}/messages/post", response_model=Optional[MessageModel]) +async def post_new_message( + id: str, form_data: MessageForm, 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 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.insert_new_message(form_data, channel.id, user.id) + + if message: + await sio.emit( + "channel-events", + { + "channel_id": channel.id, + "message_id": message.id, + "data": { + "type": "message", + "data": { + **message.model_dump(), + "user": UserNameResponse(**user.model_dump()).model_dump(), + }, + }, + }, + to=f"channel:{channel.id}", + ) + + return MessageModel(**message.model_dump()) + except Exception as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) + + +############################ +# UpdateMessageById +############################ + + +@router.post( + "/{id}/messages/{message_id}/update", response_model=Optional[MessageModel] +) +async def update_message_by_id( + id: str, message_id: str, form_data: MessageForm, 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 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: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND + ) + + if message.channel_id != id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) + + try: + message = Messages.update_message_by_id(message_id, form_data) + if message: + await sio.emit( + "channel-events", + { + "channel_id": channel.id, + "message_id": message.id, + "data": { + "type": "message:update", + "data": { + **message.model_dump(), + "user": UserNameResponse(**user.model_dump()).model_dump(), + }, + }, + }, + to=f"channel:{channel.id}", + ) + + return MessageModel(**message.model_dump()) + except Exception as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) + + +############################ +# DeleteMessageById +############################ + + +@router.delete("/{id}/messages/{message_id}/delete", response_model=bool) +async def delete_message_by_id( + id: str, message_id: str, 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 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: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND + ) + + if message.channel_id != id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) + + try: + Messages.delete_message_by_id(message_id) + await sio.emit( + "channel-events", + { + "channel_id": channel.id, + "message_id": message.id, + "data": { + "type": "message:delete", + "data": { + **message.model_dump(), + "user": UserNameResponse(**user.model_dump()).model_dump(), + }, + }, + }, + to=f"channel:{channel.id}", + ) + + return True + except Exception as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index 965fb9396..23be163e9 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -5,6 +5,7 @@ import sys import time from open_webui.models.users import Users +from open_webui.models.channels import Channels from open_webui.env import ( ENABLE_WEBSOCKET_SUPPORT, WEBSOCKET_MANAGER, @@ -162,7 +163,6 @@ async def connect(sid, environ, auth): @sio.on("user-join") async def user_join(sid, data): - # print("user-join", sid, data) auth = data["auth"] if "auth" in data else None if not auth or "token" not in auth: @@ -182,6 +182,12 @@ async def user_join(sid, data): else: USER_POOL[user.id] = [sid] + # Join all the channels + channels = Channels.get_channels_by_user_id(user.id) + log.debug(f"{channels=}") + for channel in channels: + await sio.enter_room(sid, f"channel:{channel.id}") + # print(f"user {user.name}({user.id}) connected with session ID {sid}") await sio.emit("user-count", {"count": len(USER_POOL.items())}) diff --git a/src/lib/apis/channels/index.ts b/src/lib/apis/channels/index.ts new file mode 100644 index 000000000..87b2eaf02 --- /dev/null +++ b/src/lib/apis/channels/index.ts @@ -0,0 +1,300 @@ +import { WEBUI_API_BASE_URL } from '$lib/constants'; + +type ChannelForm = { + name: string; + data?: object; + meta?: object; + access_control?: object; +} + +export const createNewChannel = async (token: string = '', channel: ChannelForm) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/create`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ ...channel }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getChannels = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + + +export const getChannelById = async (token: string = '', channel_id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +} + +export const updateChannelById = async (token: string = '', channel_id: string, channel: ChannelForm) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ ...channel }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +} + +export const deleteChannelById = async (token: string = '', channel_id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/delete`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +} + + +export const getChannelMessages = async (token: string = '', channel_id: string, skip: number = 0, limit: number = 50) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/messages?skip=${skip}&limit=${limit}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +} + +type MessageForm = { + content: string; + data?: object; + meta?: object; + +} + +export const sendMessage = async (token: string = '', channel_id: string, message: MessageForm) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/messages/post`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ ...message }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +} + +export const updateMessage = async (token: string = '', channel_id: string, message_id: string, message: MessageForm) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/messages/${message_id}/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ ...message }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +} + +export const deleteMessage = async (token: string = '', channel_id: string, message_id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/messages/${message_id}/delete`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +} \ No newline at end of file diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index 8fabe5bce..ce3cfec26 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -112,7 +112,7 @@ -