Timothy Jaeryang Baek пре 4 месеци
родитељ
комит
4820ecc371

+ 12 - 0
backend/open_webui/models/users.py

@@ -168,6 +168,18 @@ class UsersTable:
         except Exception:
         except Exception:
             return None
             return None
 
 
+    def get_user_webhook_url_by_id(self, id: str) -> Optional[str]:
+        try:
+            with get_db() as db:
+                user = db.query(User).filter_by(id=id).first()
+                return (
+                    user.settings.get("ui", {})
+                    .get("notifications", {})
+                    .get("webhook_url", None)
+                )
+        except Exception:
+            return None
+
     def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]:
     def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]:
         try:
         try:
             with get_db() as db:
             with get_db() as db:

+ 6 - 0
backend/open_webui/socket/main.py

@@ -249,3 +249,9 @@ def get_event_call(request_info):
         return response
         return response
 
 
     return __event_call__
     return __event_call__
+
+
+def get_user_id_from_session_pool(sid):
+    print("get_user_id_from_session_pool", sid)
+    print(SESSION_POOL.get(sid))
+    return SESSION_POOL.get(sid)

+ 28 - 1
backend/open_webui/utils/middleware.py

@@ -18,15 +18,18 @@ from starlette.responses import Response, StreamingResponse
 
 
 
 
 from open_webui.models.chats import Chats
 from open_webui.models.chats import Chats
+from open_webui.models.users import Users
 from open_webui.socket.main import (
 from open_webui.socket.main import (
     get_event_call,
     get_event_call,
     get_event_emitter,
     get_event_emitter,
+    get_user_id_from_session_pool,
 )
 )
 from open_webui.routers.tasks import (
 from open_webui.routers.tasks import (
     generate_queries,
     generate_queries,
     generate_title,
     generate_title,
     generate_chat_tags,
     generate_chat_tags,
 )
 )
+from open_webui.utils.webhook import post_webhook
 
 
 
 
 from open_webui.models.users import UserModel
 from open_webui.models.users import UserModel
@@ -55,7 +58,12 @@ from open_webui.utils.plugin import load_function_module_by_id
 from open_webui.tasks import create_task
 from open_webui.tasks import create_task
 
 
 from open_webui.config import DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
 from open_webui.config import DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
-from open_webui.env import SRC_LOG_LEVELS, GLOBAL_LOG_LEVEL, BYPASS_MODEL_ACCESS_CONTROL
+from open_webui.env import (
+    SRC_LOG_LEVELS,
+    GLOBAL_LOG_LEVEL,
+    BYPASS_MODEL_ACCESS_CONTROL,
+    WEBUI_URL,
+)
 from open_webui.constants import TASKS
 from open_webui.constants import TASKS
 
 
 
 
@@ -593,6 +601,25 @@ async def process_chat_response(request, response, user, events, metadata, tasks
 
 
                         if done:
                         if done:
                             data = {"done": True, "content": content, "title": title}
                             data = {"done": True, "content": content, "title": title}
+
+                            if (
+                                get_user_id_from_session_pool(metadata["session_id"])
+                                is None
+                            ):
+                                webhook_url = Users.get_user_webhook_url_by_id(user.id)
+                                print(f"webhook_url: {webhook_url}")
+                                if webhook_url:
+                                    post_webhook(
+                                        webhook_url,
+                                        f"{title} - {WEBUI_URL}/{metadata['chat_id']}\n\n{content}",
+                                        {
+                                            "action": "chat",
+                                            "message": content,
+                                            "title": title,
+                                            "url": f"{WEBUI_URL}/{metadata['chat_id']}",
+                                        },
+                                    )
+
                         else:
                         else:
                             continue
                             continue
 
 

+ 1 - 0
backend/open_webui/utils/webhook.py

@@ -11,6 +11,7 @@ log.setLevel(SRC_LOG_LEVELS["WEBHOOK"])
 
 
 def post_webhook(url: str, message: str, event_data: dict) -> bool:
 def post_webhook(url: str, message: str, event_data: dict) -> bool:
     try:
     try:
+        log.debug(f"post_webhook: {url}, {message}, {event_data}")
         payload = {}
         payload = {}
 
 
         # Slack and Google Chat Webhooks
         # Slack and Google Chat Webhooks

+ 29 - 1
src/lib/components/chat/Settings/Account.svelte

@@ -2,7 +2,7 @@
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 
 
-	import { user, config } from '$lib/stores';
+	import { user, config, settings } from '$lib/stores';
 	import { updateUserProfile, createAPIKey, getAPIKey } from '$lib/apis/auths';
 	import { updateUserProfile, createAPIKey, getAPIKey } from '$lib/apis/auths';
 
 
 	import UpdatePassword from './Account/UpdatePassword.svelte';
 	import UpdatePassword from './Account/UpdatePassword.svelte';
@@ -16,10 +16,12 @@
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
 	export let saveHandler: Function;
 	export let saveHandler: Function;
+	export let saveSettings: Function;
 
 
 	let profileImageUrl = '';
 	let profileImageUrl = '';
 	let name = '';
 	let name = '';
 
 
+	let webhookUrl = '';
 	let showAPIKeys = false;
 	let showAPIKeys = false;
 
 
 	let JWTTokenCopied = false;
 	let JWTTokenCopied = false;
@@ -35,6 +37,15 @@
 			}
 			}
 		}
 		}
 
 
+		if (webhookUrl !== $settings?.notifications?.webhook_url) {
+			saveSettings({
+				notifications: {
+					...$settings.notifications,
+					webhook_url: webhookUrl
+				}
+			});
+		}
+
 		const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch(
 		const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch(
 			(error) => {
 			(error) => {
 				toast.error(error);
 				toast.error(error);
@@ -60,6 +71,7 @@
 	onMount(async () => {
 	onMount(async () => {
 		name = $user.name;
 		name = $user.name;
 		profileImageUrl = $user.profile_image_url;
 		profileImageUrl = $user.profile_image_url;
+		webhookUrl = $settings?.notifications?.webhook_url ?? '';
 
 
 		APIKey = await getAPIKey(localStorage.token).catch((error) => {
 		APIKey = await getAPIKey(localStorage.token).catch((error) => {
 			console.log(error);
 			console.log(error);
@@ -226,6 +238,22 @@
 					</div>
 					</div>
 				</div>
 				</div>
 			</div>
 			</div>
+
+			<div class="pt-2">
+				<div class="flex flex-col w-full">
+					<div class=" mb-1 text-xs font-medium">{$i18n.t('Notification Webhook')}</div>
+
+					<div class="flex-1">
+						<input
+							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+							type="url"
+							placeholder={$i18n.t('Enter your webhook URL')}
+							bind:value={webhookUrl}
+							required
+						/>
+					</div>
+				</div>
+			</div>
 		</div>
 		</div>
 
 
 		<div class="py-0.5">
 		<div class="py-0.5">

+ 7 - 4
src/lib/components/chat/Settings/Account/UpdatePassword.svelte

@@ -60,9 +60,10 @@
 
 
 				<div class="flex-1">
 				<div class="flex-1">
 					<input
 					<input
-						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+						class="w-full bg-transparent dark:text-gray-300 outline-none placeholder:opacity-30"
 						type="password"
 						type="password"
 						bind:value={currentPassword}
 						bind:value={currentPassword}
+						placeholder={$i18n.t('Enter your current password')}
 						autocomplete="current-password"
 						autocomplete="current-password"
 						required
 						required
 					/>
 					/>
@@ -74,9 +75,10 @@
 
 
 				<div class="flex-1">
 				<div class="flex-1">
 					<input
 					<input
-						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+						class="w-full bg-transparent text-sm dark:text-gray-300 outline-none placeholder:opacity-30"
 						type="password"
 						type="password"
 						bind:value={newPassword}
 						bind:value={newPassword}
+						placeholder={$i18n.t('Enter your new password')}
 						autocomplete="new-password"
 						autocomplete="new-password"
 						required
 						required
 					/>
 					/>
@@ -88,9 +90,10 @@
 
 
 				<div class="flex-1">
 				<div class="flex-1">
 					<input
 					<input
-						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+						class="w-full bg-transparent text-sm dark:text-gray-300 outline-none placeholder:opacity-30"
 						type="password"
 						type="password"
 						bind:value={newPasswordConfirm}
 						bind:value={newPasswordConfirm}
+						placeholder={$i18n.t('Confirm your new password')}
 						autocomplete="off"
 						autocomplete="off"
 						required
 						required
 					/>
 					/>
@@ -100,7 +103,7 @@
 
 
 		<div class="mt-3 flex justify-end">
 		<div class="mt-3 flex justify-end">
 			<button
 			<button
-				class=" px-4 py-2 text-xs bg-gray-800 hover:bg-gray-900 dark:bg-gray-700 dark:hover:bg-gray-800 text-gray-100 transition rounded-md font-medium"
+				class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
 			>
 			>
 				{$i18n.t('Update password')}
 				{$i18n.t('Update password')}
 			</button>
 			</button>

+ 1 - 0
src/lib/components/chat/SettingsModal.svelte

@@ -637,6 +637,7 @@
 					<Chats {saveSettings} />
 					<Chats {saveSettings} />
 				{:else if selectedTab === 'account'}
 				{:else if selectedTab === 'account'}
 					<Account
 					<Account
+						{saveSettings}
 						saveHandler={() => {
 						saveHandler={() => {
 							toast.success($i18n.t('Settings saved successfully!'));
 							toast.success($i18n.t('Settings saved successfully!'));
 						}}
 						}}