feat:精简
Some checks failed
Create and publish Docker images with specific build args / build-main-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-main-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Create and publish Docker images with specific build args / build-cuda126-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-cuda126-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Create and publish Docker images with specific build args / build-slim-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-slim-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Python CI / Format Backend (3.11.x) (push) Has been cancelled
Python CI / Format Backend (3.12.x) (push) Has been cancelled
Frontend Build / Format & Build Frontend (push) Has been cancelled
Frontend Build / Frontend Unit Tests (push) Has been cancelled
Create and publish Docker images with specific build args / merge-main-images (push) Has been cancelled
Create and publish Docker images with specific build args / merge-cuda-images (push) Has been cancelled
Create and publish Docker images with specific build args / merge-cuda126-images (push) Has been cancelled
Create and publish Docker images with specific build args / merge-ollama-images (push) Has been cancelled
Create and publish Docker images with specific build args / merge-slim-images (push) Has been cancelled
Close inactive issues / close-issues (push) Has been cancelled
Some checks failed
Create and publish Docker images with specific build args / build-main-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-main-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Create and publish Docker images with specific build args / build-cuda126-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-cuda126-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Create and publish Docker images with specific build args / build-slim-image (linux/amd64, ubuntu-latest) (push) Has been cancelled
Create and publish Docker images with specific build args / build-slim-image (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Python CI / Format Backend (3.11.x) (push) Has been cancelled
Python CI / Format Backend (3.12.x) (push) Has been cancelled
Frontend Build / Format & Build Frontend (push) Has been cancelled
Frontend Build / Frontend Unit Tests (push) Has been cancelled
Create and publish Docker images with specific build args / merge-main-images (push) Has been cancelled
Create and publish Docker images with specific build args / merge-cuda-images (push) Has been cancelled
Create and publish Docker images with specific build args / merge-cuda126-images (push) Has been cancelled
Create and publish Docker images with specific build args / merge-ollama-images (push) Has been cancelled
Create and publish Docker images with specific build args / merge-slim-images (push) Has been cancelled
Close inactive issues / close-issues (push) Has been cancelled
This commit is contained in:
@@ -71,6 +71,9 @@ from open_webui.utils.auth import (
|
||||
get_http_authorization_cred,
|
||||
send_verify_email,
|
||||
verify_email_by_code,
|
||||
apply_branding,
|
||||
send_signup_email_code,
|
||||
verify_signup_email_code,
|
||||
)
|
||||
from open_webui.utils.webhook import post_webhook
|
||||
from open_webui.utils.access_control import get_permissions, has_permission
|
||||
@@ -107,7 +110,6 @@ signin_rate_limiter = RateLimiter(
|
||||
class SessionUserResponse(Token, UserProfileImageResponse):
|
||||
expires_at: Optional[int] = None
|
||||
permissions: Optional[dict] = None
|
||||
credit: Decimal
|
||||
|
||||
|
||||
class SessionUserInfoResponse(SessionUserResponse, UserStatus):
|
||||
@@ -173,7 +175,6 @@ async def get_session_user(
|
||||
"status_message": user.status_message,
|
||||
"status_expires_at": user.status_expires_at,
|
||||
"permissions": user_permissions,
|
||||
"credit": credit.credit,
|
||||
}
|
||||
|
||||
|
||||
@@ -513,7 +514,6 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
"role": user.role,
|
||||
"profile_image_url": user.profile_image_url,
|
||||
"permissions": user_permissions,
|
||||
"credit": credit.credit,
|
||||
}
|
||||
else:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||
@@ -654,12 +654,39 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
|
||||
"role": user.role,
|
||||
"profile_image_url": user.profile_image_url,
|
||||
"permissions": user_permissions,
|
||||
"credit": credit.credit,
|
||||
}
|
||||
else:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||
|
||||
|
||||
############################
|
||||
# Send Email Code
|
||||
############################
|
||||
|
||||
|
||||
class SendEmailCodeForm(BaseModel):
|
||||
email: str
|
||||
|
||||
|
||||
@router.post("/send_email_code")
|
||||
async def send_email_code(request: Request, form_data: SendEmailCodeForm):
|
||||
if not validate_email_format(form_data.email.lower()):
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
|
||||
)
|
||||
|
||||
# check if email already registered
|
||||
if Users.get_user_by_email(form_data.email.lower()):
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
|
||||
|
||||
try:
|
||||
send_signup_email_code(form_data.email.lower())
|
||||
return {"success": True, "message": "Verification code sent"}
|
||||
except Exception as e:
|
||||
log.error(f"Failed to send email code: {e}")
|
||||
raise HTTPException(500, detail="Failed to send verification code")
|
||||
|
||||
|
||||
############################
|
||||
# SignUp
|
||||
############################
|
||||
@@ -706,6 +733,13 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
|
||||
if Users.get_user_by_email(form_data.email.lower()):
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
|
||||
|
||||
# verify email code for non-admin signup
|
||||
if has_users and form_data.email_code:
|
||||
if not verify_signup_email_code(form_data.email.lower(), form_data.email_code):
|
||||
raise HTTPException(400, detail="Invalid or expired verification code")
|
||||
elif has_users and not form_data.email_code:
|
||||
raise HTTPException(400, detail="Verification code is required")
|
||||
|
||||
try:
|
||||
try:
|
||||
validate_password(form_data.password)
|
||||
@@ -795,7 +829,6 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
|
||||
"role": user.role,
|
||||
"profile_image_url": user.profile_image_url,
|
||||
"permissions": user_permissions,
|
||||
"credit": credit.credit,
|
||||
}
|
||||
else:
|
||||
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
|
||||
@@ -1250,6 +1283,154 @@ async def update_ldap_config(
|
||||
return {"ENABLE_LDAP": request.app.state.config.ENABLE_LDAP}
|
||||
|
||||
|
||||
############################
|
||||
# Branding Config
|
||||
############################
|
||||
|
||||
|
||||
@router.get("/admin/config/branding")
|
||||
async def get_branding_config(request: Request, user=Depends(get_admin_user)):
|
||||
def get_value(config_obj):
|
||||
if hasattr(config_obj, "value"):
|
||||
return config_obj.value
|
||||
return config_obj
|
||||
|
||||
return {
|
||||
"ORGANIZATION_NAME": get_value(request.app.state.config.BRANDING_ORGANIZATION_NAME),
|
||||
"CUSTOM_NAME": get_value(request.app.state.config.BRANDING_CUSTOM_NAME),
|
||||
"FAVICON_ICO": get_value(request.app.state.config.BRANDING_FAVICON_ICO),
|
||||
"FAVICON_PNG": get_value(request.app.state.config.BRANDING_FAVICON_PNG),
|
||||
"FAVICON_DARK_PNG": get_value(request.app.state.config.BRANDING_FAVICON_DARK_PNG),
|
||||
"FAVICON_SVG": get_value(request.app.state.config.BRANDING_FAVICON_SVG),
|
||||
"AUTH_LOGO_URL": get_value(request.app.state.config.BRANDING_AUTH_LOGO_URL),
|
||||
"SIDEBAR_LOGO_URL": get_value(request.app.state.config.BRANDING_SIDEBAR_LOGO_URL),
|
||||
"AUTH_LOGO_LINK_URL": get_value(request.app.state.config.BRANDING_AUTH_LOGO_LINK_URL),
|
||||
}
|
||||
|
||||
|
||||
class BrandingConfig(BaseModel):
|
||||
ORGANIZATION_NAME: Optional[str] = ""
|
||||
CUSTOM_NAME: Optional[str] = ""
|
||||
FAVICON_ICO: Optional[str] = ""
|
||||
FAVICON_PNG: Optional[str] = ""
|
||||
FAVICON_DARK_PNG: Optional[str] = ""
|
||||
FAVICON_SVG: Optional[str] = ""
|
||||
AUTH_LOGO_URL: Optional[str] = ""
|
||||
SIDEBAR_LOGO_URL: Optional[str] = ""
|
||||
AUTH_LOGO_LINK_URL: Optional[str] = ""
|
||||
|
||||
|
||||
@router.post("/admin/config/branding")
|
||||
async def update_branding_config(
|
||||
request: Request, form_data: BrandingConfig, user=Depends(get_admin_user)
|
||||
):
|
||||
# Helper function to update config value
|
||||
# Must access _state directly to get PersistentConfig object (not the value)
|
||||
def update_config(key: str, new_value):
|
||||
config_obj = request.app.state.config._state.get(key)
|
||||
if config_obj and hasattr(config_obj, "value") and hasattr(config_obj, "save"):
|
||||
config_obj.value = new_value
|
||||
config_obj.save()
|
||||
log.info(f"Branding: Saved config {key} = {new_value}")
|
||||
else:
|
||||
log.warning(f"Branding: config key '{key}' not found or not PersistentConfig")
|
||||
|
||||
# Update config values
|
||||
update_config("BRANDING_ORGANIZATION_NAME", form_data.ORGANIZATION_NAME)
|
||||
update_config("BRANDING_CUSTOM_NAME", form_data.CUSTOM_NAME)
|
||||
update_config("BRANDING_FAVICON_ICO", form_data.FAVICON_ICO)
|
||||
update_config("BRANDING_FAVICON_PNG", form_data.FAVICON_PNG)
|
||||
update_config("BRANDING_FAVICON_DARK_PNG", form_data.FAVICON_DARK_PNG)
|
||||
update_config("BRANDING_FAVICON_SVG", form_data.FAVICON_SVG)
|
||||
update_config("BRANDING_AUTH_LOGO_URL", form_data.AUTH_LOGO_URL)
|
||||
update_config("BRANDING_SIDEBAR_LOGO_URL", form_data.SIDEBAR_LOGO_URL)
|
||||
update_config("BRANDING_AUTH_LOGO_LINK_URL", form_data.AUTH_LOGO_LINK_URL)
|
||||
|
||||
# Apply branding changes immediately
|
||||
apply_branding(request.app)
|
||||
|
||||
# Helper to get value
|
||||
def get_value(config_obj):
|
||||
if hasattr(config_obj, "value"):
|
||||
return config_obj.value
|
||||
return config_obj
|
||||
|
||||
return {
|
||||
"ORGANIZATION_NAME": get_value(request.app.state.config.BRANDING_ORGANIZATION_NAME),
|
||||
"CUSTOM_NAME": get_value(request.app.state.config.BRANDING_CUSTOM_NAME),
|
||||
"FAVICON_ICO": get_value(request.app.state.config.BRANDING_FAVICON_ICO),
|
||||
"FAVICON_PNG": get_value(request.app.state.config.BRANDING_FAVICON_PNG),
|
||||
"FAVICON_DARK_PNG": get_value(request.app.state.config.BRANDING_FAVICON_DARK_PNG),
|
||||
"FAVICON_SVG": get_value(request.app.state.config.BRANDING_FAVICON_SVG),
|
||||
"AUTH_LOGO_URL": get_value(request.app.state.config.BRANDING_AUTH_LOGO_URL),
|
||||
"SIDEBAR_LOGO_URL": get_value(request.app.state.config.BRANDING_SIDEBAR_LOGO_URL),
|
||||
"AUTH_LOGO_LINK_URL": get_value(request.app.state.config.BRANDING_AUTH_LOGO_LINK_URL),
|
||||
}
|
||||
|
||||
|
||||
############################
|
||||
# Email Templates
|
||||
############################
|
||||
|
||||
|
||||
@router.get("/admin/config/email-templates")
|
||||
async def get_email_templates(request: Request, user=Depends(get_admin_user)):
|
||||
from open_webui.config import (
|
||||
DEFAULT_EMAIL_VERIFY_TEMPLATE,
|
||||
DEFAULT_EMAIL_CODE_TEMPLATE,
|
||||
)
|
||||
|
||||
def get_value(config_obj):
|
||||
if hasattr(config_obj, "value"):
|
||||
return config_obj.value
|
||||
return config_obj
|
||||
|
||||
return {
|
||||
"EMAIL_VERIFY_TEMPLATE": get_value(
|
||||
request.app.state.config.EMAIL_VERIFY_TEMPLATE
|
||||
),
|
||||
"EMAIL_CODE_TEMPLATE": get_value(
|
||||
request.app.state.config.EMAIL_CODE_TEMPLATE
|
||||
),
|
||||
"DEFAULT_EMAIL_VERIFY_TEMPLATE": DEFAULT_EMAIL_VERIFY_TEMPLATE,
|
||||
"DEFAULT_EMAIL_CODE_TEMPLATE": DEFAULT_EMAIL_CODE_TEMPLATE,
|
||||
}
|
||||
|
||||
|
||||
class EmailTemplatesConfig(BaseModel):
|
||||
EMAIL_VERIFY_TEMPLATE: Optional[str] = ""
|
||||
EMAIL_CODE_TEMPLATE: Optional[str] = ""
|
||||
|
||||
|
||||
@router.post("/admin/config/email-templates")
|
||||
async def update_email_templates(
|
||||
request: Request, form_data: EmailTemplatesConfig, user=Depends(get_admin_user)
|
||||
):
|
||||
def update_config(key: str, new_value):
|
||||
config_obj = request.app.state.config._state.get(key)
|
||||
if config_obj and hasattr(config_obj, "value") and hasattr(config_obj, "save"):
|
||||
config_obj.value = new_value
|
||||
config_obj.save()
|
||||
log.info(f"Email Templates: Saved config {key}")
|
||||
|
||||
update_config("EMAIL_VERIFY_TEMPLATE", form_data.EMAIL_VERIFY_TEMPLATE)
|
||||
update_config("EMAIL_CODE_TEMPLATE", form_data.EMAIL_CODE_TEMPLATE)
|
||||
|
||||
def get_value(config_obj):
|
||||
if hasattr(config_obj, "value"):
|
||||
return config_obj.value
|
||||
return config_obj
|
||||
|
||||
return {
|
||||
"EMAIL_VERIFY_TEMPLATE": get_value(
|
||||
request.app.state.config.EMAIL_VERIFY_TEMPLATE
|
||||
),
|
||||
"EMAIL_CODE_TEMPLATE": get_value(
|
||||
request.app.state.config.EMAIL_CODE_TEMPLATE
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
############################
|
||||
# API Key
|
||||
############################
|
||||
|
||||
@@ -1,293 +0,0 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status, BackgroundTasks
|
||||
from pydantic import BaseModel
|
||||
|
||||
from open_webui.socket.main import sio
|
||||
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.users import Users, UserResponse
|
||||
from open_webui.models.notes import (
|
||||
NoteListResponse,
|
||||
Notes,
|
||||
NoteModel,
|
||||
NoteForm,
|
||||
NoteUserResponse,
|
||||
)
|
||||
|
||||
from open_webui.config import (
|
||||
BYPASS_ADMIN_ACCESS_CONTROL,
|
||||
ENABLE_ADMIN_CHAT_ACCESS,
|
||||
ENABLE_ADMIN_EXPORT,
|
||||
)
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
|
||||
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.access_control import has_access, has_permission
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
############################
|
||||
# GetNotes
|
||||
############################
|
||||
|
||||
|
||||
class NoteItemResponse(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
data: Optional[dict]
|
||||
updated_at: int
|
||||
created_at: int
|
||||
user: Optional[UserResponse] = None
|
||||
|
||||
|
||||
@router.get("/", response_model=list[NoteItemResponse])
|
||||
async def get_notes(
|
||||
request: Request, page: Optional[int] = None, user=Depends(get_verified_user)
|
||||
):
|
||||
if user.role != "admin" and not has_permission(
|
||||
user.id, "features.notes", request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
limit = None
|
||||
skip = None
|
||||
if page is not None:
|
||||
limit = 60
|
||||
skip = (page - 1) * limit
|
||||
|
||||
notes = [
|
||||
NoteUserResponse(
|
||||
**{
|
||||
**note.model_dump(),
|
||||
"user": UserResponse(**Users.get_user_by_id(note.user_id).model_dump()),
|
||||
}
|
||||
)
|
||||
for note in Notes.get_notes_by_user_id(user.id, "read", skip=skip, limit=limit)
|
||||
]
|
||||
return notes
|
||||
|
||||
|
||||
@router.get("/search", response_model=NoteListResponse)
|
||||
async def search_notes(
|
||||
request: Request,
|
||||
query: Optional[str] = None,
|
||||
view_option: Optional[str] = None,
|
||||
permission: Optional[str] = None,
|
||||
order_by: Optional[str] = None,
|
||||
direction: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
if user.role != "admin" and not has_permission(
|
||||
user.id, "features.notes", request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
limit = None
|
||||
skip = None
|
||||
if page is not None:
|
||||
limit = 60
|
||||
skip = (page - 1) * limit
|
||||
|
||||
filter = {}
|
||||
if query:
|
||||
filter["query"] = query
|
||||
if view_option:
|
||||
filter["view_option"] = view_option
|
||||
if permission:
|
||||
filter["permission"] = permission
|
||||
if order_by:
|
||||
filter["order_by"] = order_by
|
||||
if direction:
|
||||
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 Notes.search_notes(user.id, filter, skip=skip, limit=limit)
|
||||
|
||||
|
||||
############################
|
||||
# CreateNewNote
|
||||
############################
|
||||
|
||||
|
||||
@router.post("/create", response_model=Optional[NoteModel])
|
||||
async def create_new_note(
|
||||
request: Request, form_data: NoteForm, user=Depends(get_verified_user)
|
||||
):
|
||||
if user.role != "admin" and not has_permission(
|
||||
user.id, "features.notes", request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
try:
|
||||
note = Notes.insert_new_note(form_data, user.id)
|
||||
return note
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
# GetNoteById
|
||||
############################
|
||||
|
||||
|
||||
class NoteResponse(NoteModel):
|
||||
write_access: bool = False
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=Optional[NoteResponse])
|
||||
async def get_note_by_id(request: Request, id: str, user=Depends(get_verified_user)):
|
||||
if user.role != "admin" and not has_permission(
|
||||
user.id, "features.notes", request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
note = Notes.get_note_by_id(id)
|
||||
if not note:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if user.role != "admin" and (
|
||||
user.id != note.user_id
|
||||
and (not has_access(user.id, type="read", access_control=note.access_control))
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
write_access = (
|
||||
user.role == "admin"
|
||||
or (user.id == note.user_id)
|
||||
or has_access(
|
||||
user.id, type="write", access_control=note.access_control, strict=False
|
||||
)
|
||||
)
|
||||
|
||||
return NoteResponse(**note.model_dump(), write_access=write_access)
|
||||
|
||||
|
||||
############################
|
||||
# UpdateNoteById
|
||||
############################
|
||||
|
||||
|
||||
@router.post("/{id}/update", response_model=Optional[NoteModel])
|
||||
async def update_note_by_id(
|
||||
request: Request, id: str, form_data: NoteForm, user=Depends(get_verified_user)
|
||||
):
|
||||
if user.role != "admin" and not has_permission(
|
||||
user.id, "features.notes", request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
note = Notes.get_note_by_id(id)
|
||||
if not note:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if user.role != "admin" and (
|
||||
user.id != note.user_id
|
||||
and not has_access(user.id, type="write", access_control=note.access_control)
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
# Check if user can share publicly
|
||||
if (
|
||||
user.role != "admin"
|
||||
and form_data.access_control == None
|
||||
and not has_permission(
|
||||
user.id,
|
||||
"sharing.public_notes",
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
)
|
||||
):
|
||||
form_data.access_control = {}
|
||||
|
||||
try:
|
||||
note = Notes.update_note_by_id(id, form_data)
|
||||
await sio.emit(
|
||||
"note-events",
|
||||
note.model_dump(),
|
||||
to=f"note:{note.id}",
|
||||
)
|
||||
|
||||
return note
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
# DeleteNoteById
|
||||
############################
|
||||
|
||||
|
||||
@router.delete("/{id}/delete", response_model=bool)
|
||||
async def delete_note_by_id(request: Request, id: str, user=Depends(get_verified_user)):
|
||||
if user.role != "admin" and not has_permission(
|
||||
user.id, "features.notes", request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
note = Notes.get_note_by_id(id)
|
||||
if not note:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if user.role != "admin" and (
|
||||
user.id != note.user_id
|
||||
and not has_access(user.id, type="write", access_control=note.access_control)
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
try:
|
||||
note = Notes.delete_note_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()
|
||||
)
|
||||
@@ -41,8 +41,6 @@ from starlette.background import BackgroundTask
|
||||
|
||||
|
||||
from open_webui.models.models import Models
|
||||
from open_webui.utils.credit.usage import CreditDeduct
|
||||
from open_webui.utils.credit.utils import check_credit_by_user_id
|
||||
from open_webui.utils.misc import (
|
||||
calculate_sha256,
|
||||
)
|
||||
@@ -1077,10 +1075,6 @@ async def embeddings(
|
||||
):
|
||||
log.info(f"generate_ollama_embeddings {form_data}")
|
||||
|
||||
# check credit
|
||||
if user:
|
||||
check_credit_by_user_id(user_id=user.id, form_data={}, is_embedding=True)
|
||||
|
||||
if url_idx is None:
|
||||
await get_all_models(request, user=user)
|
||||
models = request.app.state.OLLAMA_MODELS
|
||||
@@ -1128,18 +1122,6 @@ async def embeddings(
|
||||
|
||||
data = r.json()
|
||||
|
||||
# calculate usage
|
||||
if user:
|
||||
input_text = form_data.prompt
|
||||
with CreditDeduct(
|
||||
user=user,
|
||||
model_id=form_data.model,
|
||||
body={"messages": [{"role": "user", "content": input_text}]},
|
||||
is_stream=False,
|
||||
is_embedding=True,
|
||||
) as credit_deduct:
|
||||
credit_deduct.run(input_text)
|
||||
|
||||
return data
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -1469,8 +1451,6 @@ async def generate_openai_chat_completion(
|
||||
url_idx: Optional[int] = None,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
metadata = form_data.pop("metadata", None)
|
||||
|
||||
try:
|
||||
|
||||
@@ -36,7 +36,6 @@ from open_webui.env import (
|
||||
from open_webui.models.users import UserModel
|
||||
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.utils.credit.utils import check_credit_by_user_id
|
||||
|
||||
from open_webui.utils.payload import (
|
||||
apply_model_params_to_body_openai,
|
||||
@@ -49,7 +48,6 @@ from open_webui.utils.misc import (
|
||||
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.access_control import has_access
|
||||
from open_webui.utils.credit.usage import CreditDeduct
|
||||
from open_webui.utils.headers import include_user_info_headers
|
||||
|
||||
|
||||
@@ -805,8 +803,6 @@ async def generate_chat_completion(
|
||||
user=Depends(get_verified_user),
|
||||
bypass_filter: Optional[bool] = False,
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
if BYPASS_MODEL_ACCESS_CONTROL:
|
||||
bypass_filter = True
|
||||
|
||||
@@ -975,14 +971,7 @@ async def generate_chat_completion(
|
||||
else:
|
||||
return PlainTextResponse(status_code=r.status, content=response)
|
||||
|
||||
with CreditDeduct(
|
||||
user=user,
|
||||
model_id=model_id,
|
||||
body=form_data,
|
||||
is_stream=False,
|
||||
) as credit_deduct:
|
||||
credit_deduct.run(response=response)
|
||||
return credit_deduct.add_usage_to_resp(response)
|
||||
return response
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
|
||||
@@ -1008,10 +997,6 @@ async def embeddings(request: Request, form_data: dict, user):
|
||||
dict: OpenAI-compatible embeddings response.
|
||||
"""
|
||||
|
||||
# check credit
|
||||
if user:
|
||||
check_credit_by_user_id(user_id=user.id, form_data={}, is_embedding=True)
|
||||
|
||||
idx = 0
|
||||
# Prepare payload/body
|
||||
body = json.dumps(form_data)
|
||||
@@ -1047,17 +1032,6 @@ async def embeddings(request: Request, form_data: dict, user):
|
||||
)
|
||||
|
||||
if "text/event-stream" in r.headers.get("Content-Type", ""):
|
||||
if user:
|
||||
with CreditDeduct(
|
||||
user=user,
|
||||
model_id=model_id,
|
||||
body={
|
||||
"messages": [{"role": "user", "content": form_data["input"]}]
|
||||
},
|
||||
is_stream=False,
|
||||
is_embedding=True,
|
||||
) as credit_deduct:
|
||||
credit_deduct.run(form_data["input"])
|
||||
streaming = True
|
||||
return StreamingResponse(
|
||||
r.content,
|
||||
@@ -1081,24 +1055,6 @@ async def embeddings(request: Request, form_data: dict, user):
|
||||
status_code=r.status, content=response_data
|
||||
)
|
||||
|
||||
if user:
|
||||
with CreditDeduct(
|
||||
user=user,
|
||||
model_id=model_id,
|
||||
body={
|
||||
"messages": [{"role": "user", "content": form_data["input"]}]
|
||||
},
|
||||
is_stream=False,
|
||||
is_embedding=True,
|
||||
) as credit_deduct:
|
||||
if "usage" in response_data:
|
||||
credit_deduct.is_official_usage = True
|
||||
prompt_tokens = response_data["usage"]["prompt_tokens"]
|
||||
credit_deduct.usage.prompt_tokens = prompt_tokens
|
||||
credit_deduct.usage.total_tokens = prompt_tokens
|
||||
else:
|
||||
credit_deduct.run(form_data["input"])
|
||||
|
||||
return response_data
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
|
||||
@@ -533,6 +533,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
|
||||
"SERPER_API_KEY": request.app.state.config.SERPER_API_KEY,
|
||||
"SERPLY_API_KEY": request.app.state.config.SERPLY_API_KEY,
|
||||
"TAVILY_API_KEY": request.app.state.config.TAVILY_API_KEY,
|
||||
"TAVILY_API_BASE_URL": request.app.state.config.TAVILY_API_BASE_URL,
|
||||
"SEARCHAPI_API_KEY": request.app.state.config.SEARCHAPI_API_KEY,
|
||||
"SEARCHAPI_ENGINE": request.app.state.config.SEARCHAPI_ENGINE,
|
||||
"SERPAPI_API_KEY": request.app.state.config.SERPAPI_API_KEY,
|
||||
@@ -593,6 +594,7 @@ class WebConfig(BaseModel):
|
||||
SERPER_API_KEY: Optional[str] = None
|
||||
SERPLY_API_KEY: Optional[str] = None
|
||||
TAVILY_API_KEY: Optional[str] = None
|
||||
TAVILY_API_BASE_URL: Optional[str] = None
|
||||
SEARCHAPI_API_KEY: Optional[str] = None
|
||||
SEARCHAPI_ENGINE: Optional[str] = None
|
||||
SERPAPI_API_KEY: Optional[str] = None
|
||||
@@ -1076,6 +1078,7 @@ async def update_rag_config(
|
||||
request.app.state.config.SERPER_API_KEY = form_data.web.SERPER_API_KEY
|
||||
request.app.state.config.SERPLY_API_KEY = form_data.web.SERPLY_API_KEY
|
||||
request.app.state.config.TAVILY_API_KEY = form_data.web.TAVILY_API_KEY
|
||||
request.app.state.config.TAVILY_API_BASE_URL = form_data.web.TAVILY_API_BASE_URL
|
||||
request.app.state.config.SEARCHAPI_API_KEY = form_data.web.SEARCHAPI_API_KEY
|
||||
request.app.state.config.SEARCHAPI_ENGINE = form_data.web.SEARCHAPI_ENGINE
|
||||
request.app.state.config.SERPAPI_API_KEY = form_data.web.SERPAPI_API_KEY
|
||||
@@ -1226,6 +1229,7 @@ async def update_rag_config(
|
||||
"SERPER_API_KEY": request.app.state.config.SERPER_API_KEY,
|
||||
"SERPLY_API_KEY": request.app.state.config.SERPLY_API_KEY,
|
||||
"TAVILY_API_KEY": request.app.state.config.TAVILY_API_KEY,
|
||||
"TAVILY_API_BASE_URL": request.app.state.config.TAVILY_API_BASE_URL,
|
||||
"SEARCHAPI_API_KEY": request.app.state.config.SEARCHAPI_API_KEY,
|
||||
"SEARCHAPI_ENGINE": request.app.state.config.SEARCHAPI_ENGINE,
|
||||
"SERPAPI_API_KEY": request.app.state.config.SERPAPI_API_KEY,
|
||||
@@ -1980,6 +1984,7 @@ def search_web(
|
||||
query,
|
||||
request.app.state.config.WEB_SEARCH_RESULT_COUNT,
|
||||
request.app.state.config.WEB_SEARCH_DOMAIN_FILTER_LIST,
|
||||
request.app.state.config.TAVILY_API_BASE_URL,
|
||||
)
|
||||
else:
|
||||
raise Exception("No TAVILY_API_KEY found in environment variables")
|
||||
|
||||
@@ -307,8 +307,8 @@ async def create_redemption_codes(
|
||||
if not form_data.duration_days:
|
||||
raise HTTPException(status_code=400, detail="duration_days is required for duration type")
|
||||
elif form_data.redemption_type == "upgrade":
|
||||
if not form_data.upgrade_expires_at:
|
||||
raise HTTPException(status_code=400, detail="upgrade_expires_at is required for upgrade type")
|
||||
if not form_data.upgrade_days:
|
||||
raise HTTPException(status_code=400, detail="upgrade_days is required for upgrade type")
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Invalid redemption_type")
|
||||
|
||||
@@ -321,12 +321,12 @@ async def create_redemption_codes(
|
||||
codes = []
|
||||
for _ in range(form_data.count):
|
||||
code = RedemptionCodeModel(
|
||||
code=f"{uuid.uuid4().hex}{uuid.uuid1().hex}",
|
||||
code=uuid.uuid4().hex[:16].upper(),
|
||||
purpose=form_data.purpose,
|
||||
redemption_type=form_data.redemption_type,
|
||||
plan_id=form_data.plan_id,
|
||||
duration_days=form_data.duration_days,
|
||||
upgrade_expires_at=form_data.upgrade_expires_at,
|
||||
upgrade_days=form_data.upgrade_days,
|
||||
expired_at=form_data.expired_at,
|
||||
created_at=now,
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ import logging
|
||||
import re
|
||||
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
from open_webui.utils.credit.utils import check_credit_by_user_id
|
||||
from open_webui.utils.task import (
|
||||
title_generation_template,
|
||||
follow_up_generation_template,
|
||||
@@ -167,8 +166,6 @@ async def update_task_config(
|
||||
async def generate_title(
|
||||
request: Request, form_data: dict, user=Depends(get_verified_user)
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
if not request.app.state.config.ENABLE_TITLE_GENERATION:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
@@ -252,8 +249,6 @@ async def generate_title(
|
||||
async def generate_follow_ups(
|
||||
request: Request, form_data: dict, user=Depends(get_verified_user)
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
if not request.app.state.config.ENABLE_FOLLOW_UP_GENERATION:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
@@ -326,8 +321,6 @@ async def generate_follow_ups(
|
||||
async def generate_chat_tags(
|
||||
request: Request, form_data: dict, user=Depends(get_verified_user)
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
if not request.app.state.config.ENABLE_TAGS_GENERATION:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
@@ -400,8 +393,6 @@ async def generate_chat_tags(
|
||||
async def generate_image_prompt(
|
||||
request: Request, form_data: dict, user=Depends(get_verified_user)
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
if getattr(request.state, "direct", False) and hasattr(request.state, "model"):
|
||||
models = {
|
||||
request.state.model["id"]: request.state.model,
|
||||
@@ -468,8 +459,6 @@ async def generate_image_prompt(
|
||||
async def generate_queries(
|
||||
request: Request, form_data: dict, user=Depends(get_verified_user)
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
type = form_data.get("type")
|
||||
if type == "web_search":
|
||||
if not request.app.state.config.ENABLE_SEARCH_QUERY_GENERATION:
|
||||
@@ -553,8 +542,6 @@ async def generate_queries(
|
||||
async def generate_autocompletion(
|
||||
request: Request, form_data: dict, user=Depends(get_verified_user)
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
if not request.app.state.config.ENABLE_AUTOCOMPLETE_GENERATION:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -641,8 +628,6 @@ async def generate_autocompletion(
|
||||
async def generate_emoji(
|
||||
request: Request, form_data: dict, user=Depends(get_verified_user)
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
if getattr(request.state, "direct", False) and hasattr(request.state, "model"):
|
||||
models = {
|
||||
request.state.model["id"]: request.state.model,
|
||||
@@ -710,8 +695,6 @@ async def generate_emoji(
|
||||
async def generate_moa_response(
|
||||
request: Request, form_data: dict, user=Depends(get_verified_user)
|
||||
):
|
||||
check_credit_by_user_id(user_id=user.id, form_data=form_data)
|
||||
|
||||
if getattr(request.state, "direct", False) and hasattr(request.state, "model"):
|
||||
models = {
|
||||
request.state.model["id"]: request.state.model,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from typing import Optional, List
|
||||
import base64
|
||||
import io
|
||||
@@ -15,12 +14,6 @@ from open_webui.models.oauth_sessions import OAuthSessions
|
||||
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.chats import Chats
|
||||
from open_webui.models.credits import (
|
||||
Credits,
|
||||
SetCreditForm,
|
||||
SetCreditFormDetail,
|
||||
AddCreditForm,
|
||||
)
|
||||
from open_webui.models.users import (
|
||||
UserModel,
|
||||
UserGroupIdsModel,
|
||||
@@ -32,7 +25,6 @@ from open_webui.models.users import (
|
||||
Users,
|
||||
UserSettings,
|
||||
UserUpdateForm,
|
||||
UserCreditUpdateForm,
|
||||
)
|
||||
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
@@ -87,15 +79,6 @@ async def get_users(
|
||||
users = result["users"]
|
||||
total = result["total"]
|
||||
|
||||
credit_map = {
|
||||
credit.user_id: {"credit": "%.4f" % credit.credit}
|
||||
for credit in Credits.list_credits_by_user_id(
|
||||
user_ids=(user.id for user in users)
|
||||
)
|
||||
}
|
||||
for user in users:
|
||||
setattr(user, "credit", credit_map.get(user.id, {}).get("credit", 0))
|
||||
|
||||
return {
|
||||
"users": [
|
||||
UserGroupIdsModel(
|
||||
@@ -117,15 +100,6 @@ async def get_all_users(
|
||||
user=Depends(get_admin_user),
|
||||
):
|
||||
user_data = Users.get_users()
|
||||
users = user_data["users"]
|
||||
credit_map = {
|
||||
credit.user_id: {"credit": "%.4f" % credit.credit}
|
||||
for credit in Credits.list_credits_by_user_id(
|
||||
user_ids=(user.id for user in users)
|
||||
)
|
||||
}
|
||||
for user in users:
|
||||
setattr(user, "credit", credit_map.get(user.id, {}).get("credit", 0))
|
||||
return user_data
|
||||
|
||||
|
||||
@@ -585,20 +559,6 @@ async def update_user_by_id(
|
||||
},
|
||||
)
|
||||
|
||||
if form_data.credit is not None:
|
||||
credit = Credits.set_credit_by_user_id(
|
||||
SetCreditForm(
|
||||
user_id=user_id,
|
||||
credit=Decimal(form_data.credit),
|
||||
detail=SetCreditFormDetail(
|
||||
api_path=str(request.url),
|
||||
api_params={"credit": form_data.credit},
|
||||
desc=f"updated by {session_user.name}",
|
||||
),
|
||||
)
|
||||
)
|
||||
setattr(updated_user, "credit", "%.4f" % credit.credit)
|
||||
|
||||
if updated_user:
|
||||
return updated_user
|
||||
|
||||
@@ -613,50 +573,6 @@ async def update_user_by_id(
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
# UpdateCreditByUserId
|
||||
############################
|
||||
|
||||
|
||||
@router.put("/{user_id}/credit", response_model=Optional[UserModel])
|
||||
async def update_credit_by_user_id(
|
||||
request: Request,
|
||||
user_id: str,
|
||||
form_data: UserCreditUpdateForm,
|
||||
session_user: UserModel = Depends(get_admin_user),
|
||||
) -> Response:
|
||||
user = Users.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=ERROR_MESSAGES.USER_NOT_FOUND,
|
||||
)
|
||||
|
||||
if form_data.amount is None and form_data.credit is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="amount or credit must be specified",
|
||||
)
|
||||
|
||||
params = {
|
||||
"user_id": user_id,
|
||||
"detail": SetCreditFormDetail(
|
||||
api_path=str(request.url),
|
||||
api_params=form_data.model_dump(),
|
||||
desc=f"updated by {session_user.name}",
|
||||
),
|
||||
}
|
||||
|
||||
if form_data.credit is not None:
|
||||
params["credit"] = Decimal(form_data.credit)
|
||||
Credits.set_credit_by_user_id(form_data=SetCreditForm(**params))
|
||||
elif form_data.amount is not None:
|
||||
params["amount"] = Decimal(form_data.amount)
|
||||
Credits.add_credit_by_user_id(form_data=AddCreditForm(**params))
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
############################
|
||||
# DeleteUserById
|
||||
############################
|
||||
|
||||
Reference in New Issue
Block a user