diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 154be97c90..6f56f3cf60 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -709,7 +709,7 @@ class GenerateChatCompletionForm(BaseModel): format: Optional[str] = None options: Optional[dict] = None template: Optional[str] = None - stream: Optional[bool] = True + stream: Optional[bool] = None keep_alive: Optional[Union[int, str]] = None diff --git a/backend/apps/web/main.py b/backend/apps/web/main.py index bd14f4bda6..dd5c0c704f 100644 --- a/backend/apps/web/main.py +++ b/backend/apps/web/main.py @@ -19,6 +19,7 @@ from config import ( DEFAULT_USER_ROLE, ENABLE_SIGNUP, USER_PERMISSIONS, + WEBHOOK_URL, ) app = FastAPI() @@ -32,6 +33,7 @@ app.state.DEFAULT_MODELS = DEFAULT_MODELS app.state.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS app.state.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE app.state.USER_PERMISSIONS = USER_PERMISSIONS +app.state.WEBHOOK_URL = WEBHOOK_URL app.add_middleware( diff --git a/backend/apps/web/routers/auths.py b/backend/apps/web/routers/auths.py index 3db2d0ad29..d881ec7462 100644 --- a/backend/apps/web/routers/auths.py +++ b/backend/apps/web/routers/auths.py @@ -27,7 +27,8 @@ from utils.utils import ( create_token, ) from utils.misc import parse_duration, validate_email_format -from constants import ERROR_MESSAGES +from utils.webhook import post_webhook +from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES router = APIRouter() @@ -155,6 +156,17 @@ async def signup(request: Request, form_data: SignupForm): ) # response.set_cookie(key='token', value=token, httponly=True) + if request.app.state.WEBHOOK_URL: + post_webhook( + request.app.state.WEBHOOK_URL, + WEBHOOK_MESSAGES.USER_SIGNUP(user.name), + { + "action": "signup", + "message": WEBHOOK_MESSAGES.USER_SIGNUP(user.name), + "user": user.model_dump_json(exclude_none=True), + }, + ) + return { "token": token, "token_type": "Bearer", diff --git a/backend/config.py b/backend/config.py index 099f726ca5..e99e248c53 100644 --- a/backend/config.py +++ b/backend/config.py @@ -290,13 +290,19 @@ DEFAULT_PROMPT_SUGGESTIONS = ( DEFAULT_USER_ROLE = os.getenv("DEFAULT_USER_ROLE", "pending") -USER_PERMISSIONS = {"chat": {"deletion": True}} + +USER_PERMISSIONS_CHAT_DELETION = ( + os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true" +) + +USER_PERMISSIONS = {"chat": {"deletion": USER_PERMISSIONS_CHAT_DELETION}} MODEL_FILTER_ENABLED = os.environ.get("MODEL_FILTER_ENABLED", False) MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "") MODEL_FILTER_LIST = [model.strip() for model in MODEL_FILTER_LIST.split(";")] +WEBHOOK_URL = os.environ.get("WEBHOOK_URL", "") #################################### # WEBUI_VERSION diff --git a/backend/constants.py b/backend/constants.py index 05bdebc540..42c5c85eb7 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -5,6 +5,13 @@ class MESSAGES(str, Enum): DEFAULT = lambda msg="": f"{msg if msg else ''}" +class WEBHOOK_MESSAGES(str, Enum): + DEFAULT = lambda msg="": f"{msg if msg else ''}" + USER_SIGNUP = lambda username="": ( + f"New user signed up: {username}" if username else "New user signed up" + ) + + class ERROR_MESSAGES(str, Enum): def __str__(self) -> str: return super().__str__() @@ -46,7 +53,7 @@ class ERROR_MESSAGES(str, Enum): PANDOC_NOT_INSTALLED = "Pandoc is not installed on the server. Please contact your administrator for assistance." INCORRECT_FORMAT = ( - lambda err="": f"Invalid format. Please use the correct format{err if err else ''}" + lambda err="": f"Invalid format. Please use the correct format{err}" ) RATE_LIMIT_EXCEEDED = "API rate limit exceeded" diff --git a/backend/data/config.json b/backend/data/config.json index d3ada59c91..cd6687d798 100644 --- a/backend/data/config.json +++ b/backend/data/config.json @@ -1,5 +1,5 @@ { - "version": "0.0.1", + "version": 0, "ui": { "prompt_suggestions": [ { diff --git a/backend/main.py b/backend/main.py index 2532271824..35f405e691 100644 --- a/backend/main.py +++ b/backend/main.py @@ -38,6 +38,7 @@ from config import ( FRONTEND_BUILD_DIR, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST, + WEBHOOK_URL, ) from constants import ERROR_MESSAGES @@ -58,6 +59,9 @@ app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None) app.state.MODEL_FILTER_ENABLED = MODEL_FILTER_ENABLED app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST +app.state.WEBHOOK_URL = WEBHOOK_URL + + origins = ["*"] @@ -178,7 +182,7 @@ class ModelFilterConfigForm(BaseModel): @app.post("/api/config/model/filter") -async def get_model_filter_config( +async def update_model_filter_config( form_data: ModelFilterConfigForm, user=Depends(get_admin_user) ): @@ -197,6 +201,28 @@ async def get_model_filter_config( } +@app.get("/api/webhook") +async def get_webhook_url(user=Depends(get_admin_user)): + return { + "url": app.state.WEBHOOK_URL, + } + + +class UrlForm(BaseModel): + url: str + + +@app.post("/api/webhook") +async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)): + app.state.WEBHOOK_URL = form_data.url + + webui_app.state.WEBHOOK_URL = app.state.WEBHOOK_URL + + return { + "url": app.state.WEBHOOK_URL, + } + + @app.get("/api/version") async def get_app_config(): diff --git a/backend/utils/webhook.py b/backend/utils/webhook.py new file mode 100644 index 0000000000..1bc5a60487 --- /dev/null +++ b/backend/utils/webhook.py @@ -0,0 +1,20 @@ +import requests + + +def post_webhook(url: str, message: str, event_data: dict) -> bool: + try: + payload = {} + + if "https://hooks.slack.com" in url: + payload["text"] = message + elif "https://discord.com/api/webhooks" in url: + payload["content"] = message + else: + payload = {**event_data} + + r = requests.post(url, json=payload) + r.raise_for_status() + return True + except Exception as e: + print(e) + return False diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index b33fb571b5..a610f7210a 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -139,3 +139,60 @@ export const updateModelFilterConfig = async ( return res; }; + +export const getWebhookUrl = async (token: string) => { + let error = null; + + const res = await fetch(`${WEBUI_BASE_URL}/api/webhook`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err; + return null; + }); + + if (error) { + throw error; + } + + return res.url; +}; + +export const updateWebhookUrl = async (token: string, url: string) => { + let error = null; + + const res = await fetch(`${WEBUI_BASE_URL}/api/webhook`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + url: url + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err; + return null; + }); + + if (error) { + throw error; + } + + return res.url; +}; diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index 351f6744be..be319e26f2 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -1,4 +1,5 @@