Skip to content

Commit b73d30b

Browse files
authored
feat: implement global memories toggle and permissions (open-webui#20462)
1 parent 48f1b2d commit b73d30b

File tree

10 files changed

+152
-5
lines changed

10 files changed

+152
-5
lines changed

backend/open_webui/config.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,6 +1470,10 @@ def feishu_oauth_register(oauth: OAuth):
14701470
os.environ.get("USER_PERMISSIONS_FEATURES_API_KEYS", "False").lower() == "true"
14711471
)
14721472

1473+
USER_PERMISSIONS_FEATURES_MEMORIES = (
1474+
os.environ.get("USER_PERMISSIONS_FEATURES_MEMORIES", "True").lower() == "true"
1475+
)
1476+
14731477

14741478
USER_PERMISSIONS_SETTINGS_INTERFACE = (
14751479
os.environ.get("USER_PERMISSIONS_SETTINGS_INTERFACE", "True").lower() == "true"
@@ -1533,6 +1537,7 @@ def feishu_oauth_register(oauth: OAuth):
15331537
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
15341538
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
15351539
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
1540+
"memories": USER_PERMISSIONS_FEATURES_MEMORIES,
15361541
},
15371542
"settings": {
15381543
"interface": USER_PERMISSIONS_SETTINGS_INTERFACE,
@@ -2083,6 +2088,12 @@ class BannerModel(BaseModel):
20832088
os.environ.get("ENABLE_CODE_INTERPRETER", "True").lower() == "true",
20842089
)
20852090

2091+
ENABLE_MEMORIES = PersistentConfig(
2092+
"ENABLE_MEMORIES",
2093+
"memories.enable",
2094+
os.environ.get("ENABLE_MEMORIES", "True").lower() == "true",
2095+
)
2096+
20862097
CODE_INTERPRETER_ENGINE = PersistentConfig(
20872098
"CODE_INTERPRETER_ENGINE",
20882099
"code_interpreter.engine",

backend/open_webui/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN,
146146
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD,
147147
CODE_INTERPRETER_JUPYTER_TIMEOUT,
148+
ENABLE_MEMORIES,
148149
# Image
149150
AUTOMATIC1111_API_AUTH,
150151
AUTOMATIC1111_BASE_URL,
@@ -1106,6 +1107,7 @@ async def lifespan(app: FastAPI):
11061107
app.state.config.IMAGE_GENERATION_ENGINE = IMAGE_GENERATION_ENGINE
11071108
app.state.config.ENABLE_IMAGE_GENERATION = ENABLE_IMAGE_GENERATION
11081109
app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION
1110+
app.state.config.ENABLE_MEMORIES = ENABLE_MEMORIES
11091111

11101112
app.state.config.IMAGE_GENERATION_MODEL = IMAGE_GENERATION_MODEL
11111113
app.state.config.IMAGE_SIZE = IMAGE_SIZE
@@ -1935,6 +1937,7 @@ async def get_app_config(request: Request):
19351937
"enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
19361938
"enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
19371939
"enable_onedrive_integration": app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
1940+
"enable_memories": app.state.config.ENABLE_MEMORIES,
19381941
**(
19391942
{
19401943
"enable_onedrive_personal": ENABLE_ONEDRIVE_PERSONAL,

backend/open_webui/routers/auths.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
991991
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
992992
"FOLDER_MAX_FILE_COUNT": request.app.state.config.FOLDER_MAX_FILE_COUNT,
993993
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
994+
"ENABLE_MEMORIES": request.app.state.config.ENABLE_MEMORIES,
994995
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
995996
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
996997
"PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE,
@@ -1015,6 +1016,7 @@ class AdminConfig(BaseModel):
10151016
ENABLE_FOLDERS: bool
10161017
FOLDER_MAX_FILE_COUNT: Optional[int | str] = None
10171018
ENABLE_CHANNELS: bool
1019+
ENABLE_MEMORIES: bool
10181020
ENABLE_NOTES: bool
10191021
ENABLE_USER_WEBHOOKS: bool
10201022
PENDING_USER_OVERLAY_TITLE: Optional[str] = None
@@ -1046,6 +1048,7 @@ async def update_admin_config(
10461048
else None
10471049
)
10481050
request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS
1051+
request.app.state.config.ENABLE_MEMORIES = form_data.ENABLE_MEMORIES
10491052
request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES
10501053

10511054
if form_data.DEFAULT_USER_ROLE in ["pending", "user", "admin"]:
@@ -1091,6 +1094,7 @@ async def update_admin_config(
10911094
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
10921095
"FOLDER_MAX_FILE_COUNT": request.app.state.config.FOLDER_MAX_FILE_COUNT,
10931096
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
1097+
"ENABLE_MEMORIES": request.app.state.config.ENABLE_MEMORIES,
10941098
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
10951099
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
10961100
"PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE,

backend/open_webui/routers/memories.py

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi import APIRouter, Depends, HTTPException, Request
1+
from fastapi import APIRouter, Depends, HTTPException, Request, status
22
from pydantic import BaseModel
33
import logging
44
import asyncio
@@ -10,11 +10,16 @@
1010
from open_webui.internal.db import get_session
1111
from sqlalchemy.orm import Session
1212

13+
from open_webui.utils.access_control import has_permission
14+
from open_webui.constants import ERROR_MESSAGES
15+
1316
log = logging.getLogger(__name__)
1417

1518
router = APIRouter()
1619

1720

21+
22+
1823
@router.get("/ef")
1924
async def get_embeddings(request: Request):
2025
return {"result": await request.app.state.EMBEDDING_FUNCTION("hello world")}
@@ -26,7 +31,21 @@ async def get_embeddings(request: Request):
2631

2732

2833
@router.get("/", response_model=list[MemoryModel])
29-
async def get_memories(user=Depends(get_verified_user), db: Session = Depends(get_session)):
34+
async def get_memories(
35+
request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
36+
):
37+
if not request.app.state.config.ENABLE_MEMORIES:
38+
raise HTTPException(
39+
status_code=status.HTTP_404_NOT_FOUND,
40+
detail=ERROR_MESSAGES.NOT_FOUND,
41+
)
42+
43+
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
44+
raise HTTPException(
45+
status_code=status.HTTP_403_FORBIDDEN,
46+
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
47+
)
48+
3049
return Memories.get_memories_by_user_id(user.id, db=db)
3150

3251

@@ -50,6 +69,18 @@ async def add_memory(
5069
user=Depends(get_verified_user),
5170
db: Session = Depends(get_session),
5271
):
72+
if not request.app.state.config.ENABLE_MEMORIES:
73+
raise HTTPException(
74+
status_code=status.HTTP_404_NOT_FOUND,
75+
detail=ERROR_MESSAGES.NOT_FOUND,
76+
)
77+
78+
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
79+
raise HTTPException(
80+
status_code=status.HTTP_403_FORBIDDEN,
81+
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
82+
)
83+
5384
memory = Memories.insert_new_memory(user.id, form_data.content, db=db)
5485

5586
vector = await request.app.state.EMBEDDING_FUNCTION(memory.content, user=user)
@@ -83,6 +114,18 @@ class QueryMemoryForm(BaseModel):
83114
async def query_memory(
84115
request: Request, form_data: QueryMemoryForm, user=Depends(get_verified_user), db: Session = Depends(get_session)
85116
):
117+
if not request.app.state.config.ENABLE_MEMORIES:
118+
raise HTTPException(
119+
status_code=status.HTTP_404_NOT_FOUND,
120+
detail=ERROR_MESSAGES.NOT_FOUND,
121+
)
122+
123+
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
124+
raise HTTPException(
125+
status_code=status.HTTP_403_FORBIDDEN,
126+
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
127+
)
128+
86129
memories = Memories.get_memories_by_user_id(user.id, db=db)
87130
if not memories:
88131
raise HTTPException(status_code=404, detail="No memories found for user")
@@ -105,6 +148,18 @@ async def query_memory(
105148
async def reset_memory_from_vector_db(
106149
request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
107150
):
151+
if not request.app.state.config.ENABLE_MEMORIES:
152+
raise HTTPException(
153+
status_code=status.HTTP_404_NOT_FOUND,
154+
detail=ERROR_MESSAGES.NOT_FOUND,
155+
)
156+
157+
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
158+
raise HTTPException(
159+
status_code=status.HTTP_403_FORBIDDEN,
160+
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
161+
)
162+
108163
VECTOR_DB_CLIENT.delete_collection(f"user-memory-{user.id}")
109164

110165
memories = Memories.get_memories_by_user_id(user.id, db=db)
@@ -142,7 +197,21 @@ async def reset_memory_from_vector_db(
142197

143198

144199
@router.delete("/delete/user", response_model=bool)
145-
async def delete_memory_by_user_id(user=Depends(get_verified_user), db: Session = Depends(get_session)):
200+
async def delete_memory_by_user_id(
201+
request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
202+
):
203+
if not request.app.state.config.ENABLE_MEMORIES:
204+
raise HTTPException(
205+
status_code=status.HTTP_404_NOT_FOUND,
206+
detail=ERROR_MESSAGES.NOT_FOUND,
207+
)
208+
209+
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
210+
raise HTTPException(
211+
status_code=status.HTTP_403_FORBIDDEN,
212+
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
213+
)
214+
146215
result = Memories.delete_memories_by_user_id(user.id, db=db)
147216

148217
if result:
@@ -168,6 +237,18 @@ async def update_memory_by_id(
168237
user=Depends(get_verified_user),
169238
db: Session = Depends(get_session),
170239
):
240+
if not request.app.state.config.ENABLE_MEMORIES:
241+
raise HTTPException(
242+
status_code=status.HTTP_404_NOT_FOUND,
243+
detail=ERROR_MESSAGES.NOT_FOUND,
244+
)
245+
246+
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
247+
raise HTTPException(
248+
status_code=status.HTTP_403_FORBIDDEN,
249+
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
250+
)
251+
171252
memory = Memories.update_memory_by_id_and_user_id(
172253
memory_id, user.id, form_data.content, db=db
173254
)
@@ -201,7 +282,21 @@ async def update_memory_by_id(
201282

202283

203284
@router.delete("/{memory_id}", response_model=bool)
204-
async def delete_memory_by_id(memory_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
285+
async def delete_memory_by_id(
286+
memory_id: str, request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
287+
):
288+
if not request.app.state.config.ENABLE_MEMORIES:
289+
raise HTTPException(
290+
status_code=status.HTTP_404_NOT_FOUND,
291+
detail=ERROR_MESSAGES.NOT_FOUND,
292+
)
293+
294+
if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
295+
raise HTTPException(
296+
status_code=status.HTTP_403_FORBIDDEN,
297+
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
298+
)
299+
205300
result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id, db=db)
206301

207302
if result:

backend/open_webui/routers/users.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class FeaturesPermissions(BaseModel):
226226
web_search: bool = True
227227
image_generation: bool = True
228228
code_interpreter: bool = True
229+
memories: bool = True
229230

230231

231232
class SettingsPermissions(BaseModel):

src/lib/components/admin/Settings/General.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,14 @@
741741
<Switch bind:state={adminConfig.ENABLE_CHANNELS} />
742742
</div>
743743

744+
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
745+
<div class=" self-center text-xs font-medium">
746+
{$i18n.t('Memories')} ({$i18n.t('Beta')})
747+
</div>
748+
749+
<Switch bind:state={adminConfig.ENABLE_MEMORIES} />
750+
</div>
751+
744752
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
745753
<div class=" self-center text-xs font-medium">
746754
{$i18n.t('User Webhooks')}

src/lib/components/admin/Users/Groups/Permissions.svelte

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,22 @@
782782
</div>
783783
{/if}
784784
</div>
785+
786+
<div class="flex flex-col w-full">
787+
<div class="flex w-full justify-between my-1">
788+
<div class=" self-center text-xs font-medium">
789+
{$i18n.t('Memories')}
790+
</div>
791+
<Switch bind:state={permissions.features.memories} />
792+
</div>
793+
{#if defaultPermissions?.features?.memories && !permissions.features.memories}
794+
<div>
795+
<div class="text-xs text-gray-500">
796+
{$i18n.t('This is a default user permission and will remain enabled.')}
797+
</div>
798+
</div>
799+
{/if}
800+
</div>
785801
</div>
786802

787803
<hr class=" border-gray-100/30 dark:border-gray-850/30" />

src/lib/components/chat/SettingsModal.svelte

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,13 @@
491491
return $user?.role === 'admin' || ($user?.permissions?.settings?.interface ?? true);
492492
}
493493
494+
if (tab.id === 'personalization') {
495+
return (
496+
$config?.features?.enable_memories &&
497+
($user?.role === 'admin' || ($user?.permissions?.features?.memories ?? true))
498+
);
499+
}
500+
494501
return true;
495502
});
496503
};

src/lib/constants/permissions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ export const DEFAULT_PERMISSIONS = {
5252
direct_tool_servers: false,
5353
web_search: true,
5454
image_generation: true,
55-
code_interpreter: true
55+
code_interpreter: true,
56+
memories: true
5657
},
5758
settings: {
5859
interface: true

src/lib/stores/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ type Config = {
273273
enable_admin_export: boolean;
274274
enable_admin_chat_access: boolean;
275275
enable_community_sharing: boolean;
276+
enable_memories: boolean;
276277
enable_autocomplete_generation: boolean;
277278
enable_direct_connections: boolean;
278279
enable_version_update_check: boolean;

0 commit comments

Comments
 (0)