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

This commit is contained in:
2026-01-16 18:34:38 +08:00
parent 16263710d9
commit 11fcec9387
137 changed files with 68993 additions and 6435 deletions

View File

@@ -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
############################

View File

@@ -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()
)

View File

@@ -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:

View File

@@ -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)

View File

@@ -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")

View File

@@ -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,
)

View File

@@ -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,

View File

@@ -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
############################