Commit df47c496 authored by Jonathan Rohde's avatar Jonathan Rohde
Browse files

Merge branch 'refs/heads/dev' into feat/sqlalchemy-instead-of-peewee

# Conflicts:
#	backend/apps/webui/models/functions.py
#	backend/apps/webui/routers/chats.py
parents 827b1e58 cd9170ed
...@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. ...@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.6] - 2024-06-27
### Added
- **✨ "Functions" Feature**: You can now utilize "Functions" like filters (middleware) and pipe (model) functions directly within the WebUI. While largely compatible with Pipelines, these native functions can be executed easily within Open WebUI. Example use cases for filter functions include usage monitoring, real-time translation, moderation, and automemory. For pipe functions, the scope ranges from Cohere and Anthropic integration directly within Open WebUI, enabling "Valves" for per-user OpenAI API key usage, and much more. If you encounter issues, SAFE_MODE has been introduced.
- **📁 Files API**: Compatible with OpenAI, this feature allows for custom Retrieval-Augmented Generation (RAG) in conjunction with the Filter Function. More examples will be shared on our community platform and official documentation website.
- **🛠️ Tool Enhancements**: Tools now support citations and "Valves". Documentation will be available shortly.
- **🔗 Iframe Support via Files API**: Enables rendering HTML directly into your chat interface using functions and tools. Use cases include playing games like DOOM and Snake, displaying a weather applet, and implementing Anthropic "artifacts"-like features. Stay tuned for updates on our community platform and documentation.
- **🔒 Experimental OAuth Support**: New experimental OAuth support. Check our documentation for more details.
- **🖼️ Custom Background Support**: Set a custom background from Settings > Interface to personalize your experience.
- **🔑 AUTOMATIC1111_API_AUTH Support**: Enhanced security for the AUTOMATIC1111 API.
- **🎨 Code Highlight Optimization**: Improved code highlighting features.
- **🎙️ Voice Interruption Feature**: Reintroduced and now toggleable from Settings > Interface.
- **💤 Wakelock API**: Now in use to prevent screen dimming during important tasks.
- **🔐 API Key Privacy**: All API keys are now hidden by default for better security.
- **🔍 New Web Search Provider**: Added jina_search as a new option.
- **🌐 Enhanced Internationalization (i18n)**: Improved Korean translation and updated Chinese and Ukrainian translations.
### Fixed
- **🔧 Conversation Mode Issue**: Fixed the issue where Conversation Mode remained active after being removed from settings.
- **📏 Scroll Button Obstruction**: Resolved the issue where the scrollToBottom button container obstructed clicks on buttons beneath it.
### Changed
- **⏲️ AIOHTTP_CLIENT_TIMEOUT**: Now set to 'None' by default for improved configuration flexibility.
- **📞 Voice Call Enhancements**: Improved by skipping code blocks and expressions during calls.
- **🚫 Error Message Handling**: Disabled the continuation of operations with error messages.
- **🗂️ Playground Relocation**: Moved the Playground from the workspace to the user menu for better user experience.
## [0.3.5] - 2024-06-16 ## [0.3.5] - 2024-06-16
### Added ### Added
......
...@@ -16,7 +16,7 @@ from faster_whisper import WhisperModel ...@@ -16,7 +16,7 @@ from faster_whisper import WhisperModel
from constants import ERROR_MESSAGES from constants import ERROR_MESSAGES
from utils.utils import ( from utils.utils import (
get_current_user, get_verified_user,
get_admin_user, get_admin_user,
) )
...@@ -258,7 +258,7 @@ async def update_image_size( ...@@ -258,7 +258,7 @@ async def update_image_size(
@app.get("/models") @app.get("/models")
def get_models(user=Depends(get_current_user)): def get_models(user=Depends(get_verified_user)):
try: try:
if app.state.config.ENGINE == "openai": if app.state.config.ENGINE == "openai":
return [ return [
...@@ -347,7 +347,7 @@ def set_model_handler(model: str): ...@@ -347,7 +347,7 @@ def set_model_handler(model: str):
@app.post("/models/default/update") @app.post("/models/default/update")
def update_default_model( def update_default_model(
form_data: UpdateModelForm, form_data: UpdateModelForm,
user=Depends(get_current_user), user=Depends(get_verified_user),
): ):
return set_model_handler(form_data.model) return set_model_handler(form_data.model)
...@@ -424,7 +424,7 @@ def save_url_image(url): ...@@ -424,7 +424,7 @@ def save_url_image(url):
@app.post("/generations") @app.post("/generations")
def generate_image( def generate_image(
form_data: GenerateImageForm, form_data: GenerateImageForm,
user=Depends(get_current_user), user=Depends(get_verified_user),
): ):
width, height = tuple(map(int, app.state.config.IMAGE_SIZE.split("x"))) width, height = tuple(map(int, app.state.config.IMAGE_SIZE.split("x")))
......
...@@ -16,7 +16,7 @@ from apps.webui.models.users import Users ...@@ -16,7 +16,7 @@ from apps.webui.models.users import Users
from constants import ERROR_MESSAGES from constants import ERROR_MESSAGES
from utils.utils import ( from utils.utils import (
decode_token, decode_token,
get_current_user, get_verified_user,
get_verified_user, get_verified_user,
get_admin_user, get_admin_user,
) )
...@@ -296,7 +296,7 @@ async def get_all_models(raw: bool = False): ...@@ -296,7 +296,7 @@ async def get_all_models(raw: bool = False):
@app.get("/models") @app.get("/models")
@app.get("/models/{url_idx}") @app.get("/models/{url_idx}")
async def get_models(url_idx: Optional[int] = None, user=Depends(get_current_user)): async def get_models(url_idx: Optional[int] = None, user=Depends(get_verified_user)):
if url_idx == None: if url_idx == None:
models = await get_all_models() models = await get_all_models()
if app.state.config.ENABLE_MODEL_FILTER: if app.state.config.ENABLE_MODEL_FILTER:
......
...@@ -85,7 +85,7 @@ from utils.misc import ( ...@@ -85,7 +85,7 @@ from utils.misc import (
sanitize_filename, sanitize_filename,
extract_folders_after_data_docs, extract_folders_after_data_docs,
) )
from utils.utils import get_current_user, get_admin_user from utils.utils import get_verified_user, get_admin_user
from config import ( from config import (
AppConfig, AppConfig,
...@@ -529,7 +529,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_ ...@@ -529,7 +529,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
@app.get("/template") @app.get("/template")
async def get_rag_template(user=Depends(get_current_user)): async def get_rag_template(user=Depends(get_verified_user)):
return { return {
"status": True, "status": True,
"template": app.state.config.RAG_TEMPLATE, "template": app.state.config.RAG_TEMPLATE,
...@@ -586,7 +586,7 @@ class QueryDocForm(BaseModel): ...@@ -586,7 +586,7 @@ class QueryDocForm(BaseModel):
@app.post("/query/doc") @app.post("/query/doc")
def query_doc_handler( def query_doc_handler(
form_data: QueryDocForm, form_data: QueryDocForm,
user=Depends(get_current_user), user=Depends(get_verified_user),
): ):
try: try:
if app.state.config.ENABLE_RAG_HYBRID_SEARCH: if app.state.config.ENABLE_RAG_HYBRID_SEARCH:
...@@ -626,7 +626,7 @@ class QueryCollectionsForm(BaseModel): ...@@ -626,7 +626,7 @@ class QueryCollectionsForm(BaseModel):
@app.post("/query/collection") @app.post("/query/collection")
def query_collection_handler( def query_collection_handler(
form_data: QueryCollectionsForm, form_data: QueryCollectionsForm,
user=Depends(get_current_user), user=Depends(get_verified_user),
): ):
try: try:
if app.state.config.ENABLE_RAG_HYBRID_SEARCH: if app.state.config.ENABLE_RAG_HYBRID_SEARCH:
...@@ -657,7 +657,7 @@ def query_collection_handler( ...@@ -657,7 +657,7 @@ def query_collection_handler(
@app.post("/youtube") @app.post("/youtube")
def store_youtube_video(form_data: UrlForm, user=Depends(get_current_user)): def store_youtube_video(form_data: UrlForm, user=Depends(get_verified_user)):
try: try:
loader = YoutubeLoader.from_youtube_url( loader = YoutubeLoader.from_youtube_url(
form_data.url, form_data.url,
...@@ -686,7 +686,7 @@ def store_youtube_video(form_data: UrlForm, user=Depends(get_current_user)): ...@@ -686,7 +686,7 @@ def store_youtube_video(form_data: UrlForm, user=Depends(get_current_user)):
@app.post("/web") @app.post("/web")
def store_web(form_data: UrlForm, user=Depends(get_current_user)): def store_web(form_data: UrlForm, user=Depends(get_verified_user)):
# "https://www.gutenberg.org/files/1727/1727-h/1727-h.htm" # "https://www.gutenberg.org/files/1727/1727-h/1727-h.htm"
try: try:
loader = get_web_loader( loader = get_web_loader(
...@@ -864,7 +864,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]: ...@@ -864,7 +864,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
@app.post("/web/search") @app.post("/web/search")
def store_web_search(form_data: SearchForm, user=Depends(get_current_user)): def store_web_search(form_data: SearchForm, user=Depends(get_verified_user)):
try: try:
logging.info( logging.info(
f"trying to web search with {app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query}" f"trying to web search with {app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query}"
...@@ -1084,7 +1084,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str): ...@@ -1084,7 +1084,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
def store_doc( def store_doc(
collection_name: Optional[str] = Form(None), collection_name: Optional[str] = Form(None),
file: UploadFile = File(...), file: UploadFile = File(...),
user=Depends(get_current_user), user=Depends(get_verified_user),
): ):
# "https://www.gutenberg.org/files/1727/1727-h/1727-h.htm" # "https://www.gutenberg.org/files/1727/1727-h/1727-h.htm"
...@@ -1145,7 +1145,7 @@ class ProcessDocForm(BaseModel): ...@@ -1145,7 +1145,7 @@ class ProcessDocForm(BaseModel):
@app.post("/process/doc") @app.post("/process/doc")
def process_doc( def process_doc(
form_data: ProcessDocForm, form_data: ProcessDocForm,
user=Depends(get_current_user), user=Depends(get_verified_user),
): ):
try: try:
file = Files.get_file_by_id(form_data.file_id) file = Files.get_file_by_id(form_data.file_id)
...@@ -1200,7 +1200,7 @@ class TextRAGForm(BaseModel): ...@@ -1200,7 +1200,7 @@ class TextRAGForm(BaseModel):
@app.post("/text") @app.post("/text")
def store_text( def store_text(
form_data: TextRAGForm, form_data: TextRAGForm,
user=Depends(get_current_user), user=Depends(get_verified_user),
): ):
collection_name = form_data.collection_name collection_name = form_data.collection_name
......
"""Peewee migrations -- 017_add_user_oauth_sub.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
migrator.add_fields(
"function",
is_global=pw.BooleanField(default=False),
)
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
migrator.remove_fields("function", "is_global")
...@@ -33,6 +33,7 @@ class Function(Base): ...@@ -33,6 +33,7 @@ class Function(Base):
meta = Column(JSONField) meta = Column(JSONField)
valves = Column(JSONField) valves = Column(JSONField)
is_active = Column(Boolean) is_active = Column(Boolean)
is_global = Column(Boolean)
updated_at = Column(BigInteger) updated_at = Column(BigInteger)
created_at = Column(BigInteger) created_at = Column(BigInteger)
...@@ -50,6 +51,7 @@ class FunctionModel(BaseModel): ...@@ -50,6 +51,7 @@ class FunctionModel(BaseModel):
content: str content: str
meta: FunctionMeta meta: FunctionMeta
is_active: bool = False is_active: bool = False
is_global: bool = False
updated_at: int # timestamp in epoch updated_at: int # timestamp in epoch
created_at: int # timestamp in epoch created_at: int # timestamp in epoch
...@@ -68,6 +70,7 @@ class FunctionResponse(BaseModel): ...@@ -68,6 +70,7 @@ class FunctionResponse(BaseModel):
name: str name: str
meta: FunctionMeta meta: FunctionMeta
is_active: bool is_active: bool
is_global: bool
updated_at: int # timestamp in epoch updated_at: int # timestamp in epoch
created_at: int # timestamp in epoch created_at: int # timestamp in epoch
...@@ -146,6 +149,16 @@ class FunctionsTable: ...@@ -146,6 +149,16 @@ class FunctionsTable:
for function in Session.query(Function).filter_by(type=type).all() for function in Session.query(Function).filter_by(type=type).all()
] ]
def get_global_filter_functions(self) -> List[FunctionModel]:
return [
FunctionModel(**model_to_dict(function))
for function in Function.select().where(
Function.type == "filter",
Function.is_active == True,
Function.is_global == True,
)
]
def get_function_valves_by_id(self, id: str) -> Optional[dict]: def get_function_valves_by_id(self, id: str) -> Optional[dict]:
try: try:
function = Session.get(Function, id) function = Session.get(Function, id)
......
...@@ -136,6 +136,7 @@ async def signin(request: Request, response: Response, form_data: SigninForm): ...@@ -136,6 +136,7 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
if not Users.get_user_by_email(trusted_email.lower()): if not Users.get_user_by_email(trusted_email.lower()):
await signup( await signup(
request, request,
response,
SignupForm( SignupForm(
email=trusted_email, password=str(uuid.uuid4()), name=trusted_name email=trusted_email, password=str(uuid.uuid4()), name=trusted_name
), ),
...@@ -153,6 +154,7 @@ async def signin(request: Request, response: Response, form_data: SigninForm): ...@@ -153,6 +154,7 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
await signup( await signup(
request, request,
response,
SignupForm(email=admin_email, password=admin_password, name="User"), SignupForm(email=admin_email, password=admin_password, name="User"),
) )
......
from fastapi import Depends, Request, HTTPException, status from fastapi import Depends, Request, HTTPException, status
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List, Union, Optional from typing import List, Union, Optional
from utils.utils import get_verified_user, get_admin_user
from utils.utils import get_current_user, get_admin_user
from fastapi import APIRouter from fastapi import APIRouter
from pydantic import BaseModel from pydantic import BaseModel
import json import json
...@@ -44,7 +43,7 @@ router = APIRouter() ...@@ -44,7 +43,7 @@ router = APIRouter()
@router.get("/", response_model=List[ChatTitleIdResponse]) @router.get("/", response_model=List[ChatTitleIdResponse])
@router.get("/list", response_model=List[ChatTitleIdResponse]) @router.get("/list", response_model=List[ChatTitleIdResponse])
async def get_session_user_chat_list( async def get_session_user_chat_list(
user=Depends(get_current_user), skip: int = 0, limit: int = 50 user=Depends(get_verified_user), skip: int = 0, limit: int = 50
): ):
return Chats.get_chat_list_by_user_id(user.id, skip, limit) return Chats.get_chat_list_by_user_id(user.id, skip, limit)
...@@ -55,7 +54,7 @@ async def get_session_user_chat_list( ...@@ -55,7 +54,7 @@ async def get_session_user_chat_list(
@router.delete("/", response_model=bool) @router.delete("/", response_model=bool)
async def delete_all_user_chats(request: Request, user=Depends(get_current_user)): async def delete_all_user_chats(request: Request, user=Depends(get_verified_user)):
if ( if (
user.role == "user" user.role == "user"
...@@ -93,7 +92,7 @@ async def get_user_chat_list_by_user_id( ...@@ -93,7 +92,7 @@ async def get_user_chat_list_by_user_id(
@router.post("/new", response_model=Optional[ChatResponse]) @router.post("/new", response_model=Optional[ChatResponse])
async def create_new_chat(form_data: ChatForm, user=Depends(get_current_user)): async def create_new_chat(form_data: ChatForm, user=Depends(get_verified_user)):
try: try:
chat = Chats.insert_new_chat(user.id, form_data) chat = Chats.insert_new_chat(user.id, form_data)
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
...@@ -110,7 +109,7 @@ async def create_new_chat(form_data: ChatForm, user=Depends(get_current_user)): ...@@ -110,7 +109,7 @@ async def create_new_chat(form_data: ChatForm, user=Depends(get_current_user)):
@router.get("/all", response_model=List[ChatResponse]) @router.get("/all", response_model=List[ChatResponse])
async def get_user_chats(user=Depends(get_current_user)): async def get_user_chats(user=Depends(get_verified_user)):
return [ return [
ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
for chat in Chats.get_chats_by_user_id(user.id) for chat in Chats.get_chats_by_user_id(user.id)
...@@ -123,7 +122,7 @@ async def get_user_chats(user=Depends(get_current_user)): ...@@ -123,7 +122,7 @@ async def get_user_chats(user=Depends(get_current_user)):
@router.get("/all/archived", response_model=List[ChatResponse]) @router.get("/all/archived", response_model=List[ChatResponse])
async def get_user_archived_chats(user=Depends(get_current_user)): async def get_user_archived_chats(user=Depends(get_verified_user)):
return [ return [
ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
for chat in Chats.get_archived_chats_by_user_id(user.id) for chat in Chats.get_archived_chats_by_user_id(user.id)
...@@ -155,7 +154,7 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user)): ...@@ -155,7 +154,7 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user)):
@router.get("/archived", response_model=List[ChatTitleIdResponse]) @router.get("/archived", response_model=List[ChatTitleIdResponse])
async def get_archived_session_user_chat_list( async def get_archived_session_user_chat_list(
user=Depends(get_current_user), skip: int = 0, limit: int = 50 user=Depends(get_verified_user), skip: int = 0, limit: int = 50
): ):
return Chats.get_archived_chat_list_by_user_id(user.id, skip, limit) return Chats.get_archived_chat_list_by_user_id(user.id, skip, limit)
...@@ -166,7 +165,7 @@ async def get_archived_session_user_chat_list( ...@@ -166,7 +165,7 @@ async def get_archived_session_user_chat_list(
@router.post("/archive/all", response_model=bool) @router.post("/archive/all", response_model=bool)
async def archive_all_chats(user=Depends(get_current_user)): async def archive_all_chats(user=Depends(get_verified_user)):
return Chats.archive_all_chats_by_user_id(user.id) return Chats.archive_all_chats_by_user_id(user.id)
...@@ -176,7 +175,7 @@ async def archive_all_chats(user=Depends(get_current_user)): ...@@ -176,7 +175,7 @@ async def archive_all_chats(user=Depends(get_current_user)):
@router.get("/share/{share_id}", response_model=Optional[ChatResponse]) @router.get("/share/{share_id}", response_model=Optional[ChatResponse])
async def get_shared_chat_by_id(share_id: str, user=Depends(get_current_user)): async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user)):
if user.role == "pending": if user.role == "pending":
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
...@@ -208,7 +207,7 @@ class TagNameForm(BaseModel): ...@@ -208,7 +207,7 @@ class TagNameForm(BaseModel):
@router.post("/tags", response_model=List[ChatTitleIdResponse]) @router.post("/tags", response_model=List[ChatTitleIdResponse])
async def get_user_chat_list_by_tag_name( async def get_user_chat_list_by_tag_name(
form_data: TagNameForm, user=Depends(get_current_user) form_data: TagNameForm, user=Depends(get_verified_user)
): ):
print(form_data) print(form_data)
...@@ -233,7 +232,7 @@ async def get_user_chat_list_by_tag_name( ...@@ -233,7 +232,7 @@ async def get_user_chat_list_by_tag_name(
@router.get("/tags/all", response_model=List[TagModel]) @router.get("/tags/all", response_model=List[TagModel])
async def get_all_tags(user=Depends(get_current_user)): async def get_all_tags(user=Depends(get_verified_user)):
try: try:
tags = Tags.get_tags_by_user_id(user.id) tags = Tags.get_tags_by_user_id(user.id)
return tags return tags
...@@ -250,7 +249,7 @@ async def get_all_tags(user=Depends(get_current_user)): ...@@ -250,7 +249,7 @@ async def get_all_tags(user=Depends(get_current_user)):
@router.get("/{id}", response_model=Optional[ChatResponse]) @router.get("/{id}", response_model=Optional[ChatResponse])
async def get_chat_by_id(id: str, user=Depends(get_current_user)): async def get_chat_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id) chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat: if chat:
...@@ -268,7 +267,7 @@ async def get_chat_by_id(id: str, user=Depends(get_current_user)): ...@@ -268,7 +267,7 @@ async def get_chat_by_id(id: str, user=Depends(get_current_user)):
@router.post("/{id}", response_model=Optional[ChatResponse]) @router.post("/{id}", response_model=Optional[ChatResponse])
async def update_chat_by_id( async def update_chat_by_id(
id: str, form_data: ChatForm, user=Depends(get_current_user) id: str, form_data: ChatForm, user=Depends(get_verified_user)
): ):
chat = Chats.get_chat_by_id_and_user_id(id, user.id) chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat: if chat:
...@@ -289,7 +288,7 @@ async def update_chat_by_id( ...@@ -289,7 +288,7 @@ async def update_chat_by_id(
@router.delete("/{id}", response_model=bool) @router.delete("/{id}", response_model=bool)
async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_user)): async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified_user)):
if user.role == "admin": if user.role == "admin":
result = Chats.delete_chat_by_id(id) result = Chats.delete_chat_by_id(id)
...@@ -311,7 +310,7 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_ ...@@ -311,7 +310,7 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_
@router.get("/{id}/clone", response_model=Optional[ChatResponse]) @router.get("/{id}/clone", response_model=Optional[ChatResponse])
async def clone_chat_by_id(id: str, user=Depends(get_current_user)): async def clone_chat_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id) chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat: if chat:
...@@ -337,7 +336,7 @@ async def clone_chat_by_id(id: str, user=Depends(get_current_user)): ...@@ -337,7 +336,7 @@ async def clone_chat_by_id(id: str, user=Depends(get_current_user)):
@router.get("/{id}/archive", response_model=Optional[ChatResponse]) @router.get("/{id}/archive", response_model=Optional[ChatResponse])
async def archive_chat_by_id(id: str, user=Depends(get_current_user)): async def archive_chat_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id) chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat: if chat:
chat = Chats.toggle_chat_archive_by_id(id) chat = Chats.toggle_chat_archive_by_id(id)
...@@ -354,7 +353,7 @@ async def archive_chat_by_id(id: str, user=Depends(get_current_user)): ...@@ -354,7 +353,7 @@ async def archive_chat_by_id(id: str, user=Depends(get_current_user)):
@router.post("/{id}/share", response_model=Optional[ChatResponse]) @router.post("/{id}/share", response_model=Optional[ChatResponse])
async def share_chat_by_id(id: str, user=Depends(get_current_user)): async def share_chat_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id) chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat: if chat:
if chat.share_id: if chat.share_id:
...@@ -386,7 +385,7 @@ async def share_chat_by_id(id: str, user=Depends(get_current_user)): ...@@ -386,7 +385,7 @@ async def share_chat_by_id(id: str, user=Depends(get_current_user)):
@router.delete("/{id}/share", response_model=Optional[bool]) @router.delete("/{id}/share", response_model=Optional[bool])
async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)): async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id) chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat: if chat:
if not chat.share_id: if not chat.share_id:
...@@ -409,7 +408,7 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)): ...@@ -409,7 +408,7 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)):
@router.get("/{id}/tags", response_model=List[TagModel]) @router.get("/{id}/tags", response_model=List[TagModel])
async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)): async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user)):
tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id)
if tags != None: if tags != None:
...@@ -427,7 +426,7 @@ async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)): ...@@ -427,7 +426,7 @@ async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)):
@router.post("/{id}/tags", response_model=Optional[ChatIdTagModel]) @router.post("/{id}/tags", response_model=Optional[ChatIdTagModel])
async def add_chat_tag_by_id( async def add_chat_tag_by_id(
id: str, form_data: ChatIdTagForm, user=Depends(get_current_user) id: str, form_data: ChatIdTagForm, user=Depends(get_verified_user)
): ):
tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id)
...@@ -454,9 +453,7 @@ async def add_chat_tag_by_id( ...@@ -454,9 +453,7 @@ async def add_chat_tag_by_id(
@router.delete("/{id}/tags", response_model=Optional[bool]) @router.delete("/{id}/tags", response_model=Optional[bool])
async def delete_chat_tag_by_id( async def delete_chat_tag_by_id(
id: str, id: str, form_data: ChatIdTagForm, user=Depends(get_verified_user)
form_data: ChatIdTagForm,
user=Depends(get_current_user),
): ):
result = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id( result = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id(
form_data.tag_name, id, user.id form_data.tag_name, id, user.id
...@@ -476,7 +473,7 @@ async def delete_chat_tag_by_id( ...@@ -476,7 +473,7 @@ async def delete_chat_tag_by_id(
@router.delete("/{id}/tags/all", response_model=Optional[bool]) @router.delete("/{id}/tags/all", response_model=Optional[bool])
async def delete_all_chat_tags_by_id(id: str, user=Depends(get_current_user)): async def delete_all_chat_tags_by_id(id: str, user=Depends(get_verified_user)):
result = Tags.delete_tags_by_chat_id_and_user_id(id, user.id) result = Tags.delete_tags_by_chat_id_and_user_id(id, user.id)
if result: if result:
......
...@@ -14,7 +14,7 @@ from apps.webui.models.users import Users ...@@ -14,7 +14,7 @@ from apps.webui.models.users import Users
from utils.utils import ( from utils.utils import (
get_password_hash, get_password_hash,
get_current_user, get_verified_user,
get_admin_user, get_admin_user,
create_token, create_token,
) )
...@@ -84,6 +84,6 @@ async def set_banners( ...@@ -84,6 +84,6 @@ async def set_banners(
@router.get("/banners", response_model=List[BannerModel]) @router.get("/banners", response_model=List[BannerModel])
async def get_banners( async def get_banners(
request: Request, request: Request,
user=Depends(get_current_user), user=Depends(get_verified_user),
): ):
return request.app.state.config.BANNERS return request.app.state.config.BANNERS
...@@ -14,7 +14,7 @@ from apps.webui.models.documents import ( ...@@ -14,7 +14,7 @@ from apps.webui.models.documents import (
DocumentResponse, DocumentResponse,
) )
from utils.utils import get_current_user, get_admin_user from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES from constants import ERROR_MESSAGES
router = APIRouter() router = APIRouter()
...@@ -25,7 +25,7 @@ router = APIRouter() ...@@ -25,7 +25,7 @@ router = APIRouter()
@router.get("/", response_model=List[DocumentResponse]) @router.get("/", response_model=List[DocumentResponse])
async def get_documents(user=Depends(get_current_user)): async def get_documents(user=Depends(get_verified_user)):
docs = [ docs = [
DocumentResponse( DocumentResponse(
**{ **{
...@@ -74,7 +74,7 @@ async def create_new_doc(form_data: DocumentForm, user=Depends(get_admin_user)): ...@@ -74,7 +74,7 @@ async def create_new_doc(form_data: DocumentForm, user=Depends(get_admin_user)):
@router.get("/doc", response_model=Optional[DocumentResponse]) @router.get("/doc", response_model=Optional[DocumentResponse])
async def get_doc_by_name(name: str, user=Depends(get_current_user)): async def get_doc_by_name(name: str, user=Depends(get_verified_user)):
doc = Documents.get_doc_by_name(name) doc = Documents.get_doc_by_name(name)
if doc: if doc:
...@@ -106,7 +106,7 @@ class TagDocumentForm(BaseModel): ...@@ -106,7 +106,7 @@ class TagDocumentForm(BaseModel):
@router.post("/doc/tags", response_model=Optional[DocumentResponse]) @router.post("/doc/tags", response_model=Optional[DocumentResponse])
async def tag_doc_by_name(form_data: TagDocumentForm, user=Depends(get_current_user)): async def tag_doc_by_name(form_data: TagDocumentForm, user=Depends(get_verified_user)):
doc = Documents.update_doc_content_by_name(form_data.name, {"tags": form_data.tags}) doc = Documents.update_doc_content_by_name(form_data.name, {"tags": form_data.tags})
if doc: if doc:
......
...@@ -147,6 +147,33 @@ async def toggle_function_by_id(id: str, user=Depends(get_admin_user)): ...@@ -147,6 +147,33 @@ async def toggle_function_by_id(id: str, user=Depends(get_admin_user)):
) )
############################
# ToggleGlobalById
############################
@router.post("/id/{id}/toggle/global", response_model=Optional[FunctionModel])
async def toggle_global_by_id(id: str, user=Depends(get_admin_user)):
function = Functions.get_function_by_id(id)
if function:
function = Functions.update_function_by_id(
id, {"is_global": not function.is_global}
)
if function:
return function
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT("Error updating function"),
)
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.NOT_FOUND,
)
############################ ############################
# UpdateFunctionById # UpdateFunctionById
############################ ############################
......
...@@ -8,7 +8,7 @@ import json ...@@ -8,7 +8,7 @@ import json
from apps.webui.models.prompts import Prompts, PromptForm, PromptModel from apps.webui.models.prompts import Prompts, PromptForm, PromptModel
from utils.utils import get_current_user, get_admin_user from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES from constants import ERROR_MESSAGES
router = APIRouter() router = APIRouter()
...@@ -19,7 +19,7 @@ router = APIRouter() ...@@ -19,7 +19,7 @@ router = APIRouter()
@router.get("/", response_model=List[PromptModel]) @router.get("/", response_model=List[PromptModel])
async def get_prompts(user=Depends(get_current_user)): async def get_prompts(user=Depends(get_verified_user)):
return Prompts.get_prompts() return Prompts.get_prompts()
...@@ -52,7 +52,7 @@ async def create_new_prompt(form_data: PromptForm, user=Depends(get_admin_user)) ...@@ -52,7 +52,7 @@ async def create_new_prompt(form_data: PromptForm, user=Depends(get_admin_user))
@router.get("/command/{command}", response_model=Optional[PromptModel]) @router.get("/command/{command}", response_model=Optional[PromptModel])
async def get_prompt_by_command(command: str, user=Depends(get_current_user)): async def get_prompt_by_command(command: str, user=Depends(get_verified_user)):
prompt = Prompts.get_prompt_by_command(f"/{command}") prompt = Prompts.get_prompt_by_command(f"/{command}")
if prompt: if prompt:
......
...@@ -433,15 +433,21 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware): ...@@ -433,15 +433,21 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware):
return 0 return 0
filter_ids = [ filter_ids = [
function.id for function in Functions.get_global_filter_functions()
]
if "info" in model and "meta" in model["info"]:
filter_ids.extend(model["info"]["meta"].get("filterIds", []))
filter_ids = list(set(filter_ids))
enabled_filter_ids = [
function.id function.id
for function in Functions.get_functions_by_type( for function in Functions.get_functions_by_type(
"filter", active_only=True "filter", active_only=True
) )
] ]
# Check if the model has any filters filter_ids = [
if "info" in model and "meta" in model["info"]: filter_id for filter_id in filter_ids if filter_id in enabled_filter_ids
filter_ids.extend(model["info"]["meta"].get("filterIds", [])) ]
filter_ids = list(set(filter_ids))
filter_ids.sort(key=get_priority) filter_ids.sort(key=get_priority)
for filter_id in filter_ids: for filter_id in filter_ids:
...@@ -939,7 +945,6 @@ async def generate_chat_completions(form_data: dict, user=Depends(get_verified_u ...@@ -939,7 +945,6 @@ async def generate_chat_completions(form_data: dict, user=Depends(get_verified_u
) )
model = app.state.MODELS[model_id] model = app.state.MODELS[model_id]
print(model)
pipe = model.get("pipe") pipe = model.get("pipe")
if pipe: if pipe:
...@@ -1030,15 +1035,19 @@ async def chat_completed(form_data: dict, user=Depends(get_verified_user)): ...@@ -1030,15 +1035,19 @@ async def chat_completed(form_data: dict, user=Depends(get_verified_user)):
return (function.valves if function.valves else {}).get("priority", 0) return (function.valves if function.valves else {}).get("priority", 0)
return 0 return 0
filter_ids = [ filter_ids = [function.id for function in Functions.get_global_filter_functions()]
function.id
for function in Functions.get_functions_by_type("filter", active_only=True)
]
# Check if the model has any filters
if "info" in model and "meta" in model["info"]: if "info" in model and "meta" in model["info"]:
filter_ids.extend(model["info"]["meta"].get("filterIds", [])) filter_ids.extend(model["info"]["meta"].get("filterIds", []))
filter_ids = list(set(filter_ids)) filter_ids = list(set(filter_ids))
enabled_filter_ids = [
function.id
for function in Functions.get_functions_by_type("filter", active_only=True)
]
filter_ids = [
filter_id for filter_id in filter_ids if filter_id in enabled_filter_ids
]
# Sort filter_ids by priority, using the get_priority function # Sort filter_ids by priority, using the get_priority function
filter_ids.sort(key=get_priority) filter_ids.sort(key=get_priority)
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.3.6.dev1", "version": "0.3.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "open-webui", "name": "open-webui",
"version": "0.3.6.dev1", "version": "0.3.6",
"dependencies": { "dependencies": {
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6", "@codemirror/lang-python": "^6.1.6",
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.3.6.dev1", "version": "0.3.6",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run pyodide:fetch && vite dev --host", "dev": "npm run pyodide:fetch && vite dev --host",
......
...@@ -224,6 +224,38 @@ export const toggleFunctionById = async (token: string, id: string) => { ...@@ -224,6 +224,38 @@ export const toggleFunctionById = async (token: string, id: string) => {
return res; return res;
}; };
export const toggleGlobalById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/toggle/global`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getFunctionValvesById = async (token: string, id: string) => { export const getFunctionValvesById = async (token: string, id: string) => {
let error = null; let error = null;
......
...@@ -230,7 +230,10 @@ ...@@ -230,7 +230,10 @@
{/if} {/if}
</div> </div>
<SensitiveInput placeholder={$i18n.t('API Key')} value={OPENAI_API_KEYS[idx]} /> <SensitiveInput
placeholder={$i18n.t('API Key')}
bind:value={OPENAI_API_KEYS[idx]}
/>
<div class="self-center flex items-center"> <div class="self-center flex items-center">
{#if idx === 0} {#if idx === 0}
<button <button
......
...@@ -234,35 +234,40 @@ ...@@ -234,35 +234,40 @@
console.log(sentences); console.log(sentences);
sentencesAudio = sentences.reduce((a, e, i, arr) => { if (sentences.length > 0) {
a[i] = null; sentencesAudio = sentences.reduce((a, e, i, arr) => {
return a; a[i] = null;
}, {}); return a;
}, {});
let lastPlayedAudioPromise = Promise.resolve(); // Initialize a promise that resolves immediately
let lastPlayedAudioPromise = Promise.resolve(); // Initialize a promise that resolves immediately
for (const [idx, sentence] of sentences.entries()) {
const res = await synthesizeOpenAISpeech( for (const [idx, sentence] of sentences.entries()) {
localStorage.token, const res = await synthesizeOpenAISpeech(
$settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice, localStorage.token,
sentence $settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice,
).catch((error) => { sentence
toast.error(error); ).catch((error) => {
toast.error(error);
speaking = null;
loadingSpeech = false; speaking = null;
loadingSpeech = false;
return null;
}); return null;
});
if (res) {
const blob = await res.blob(); if (res) {
const blobUrl = URL.createObjectURL(blob); const blob = await res.blob();
const audio = new Audio(blobUrl); const blobUrl = URL.createObjectURL(blob);
sentencesAudio[idx] = audio; const audio = new Audio(blobUrl);
loadingSpeech = false; sentencesAudio[idx] = audio;
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx)); loadingSpeech = false;
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
}
} }
} else {
speaking = null;
loadingSpeech = false;
} }
} else { } else {
let voices = []; let voices = [];
......
<script lang="ts"> <script lang="ts">
export let value: string; export let value: string = '';
export let placeholder = ''; export let placeholder = '';
export let readOnly = false; export let readOnly = false;
export let outerClassName = 'flex flex-1'; export let outerClassName = 'flex flex-1';
......
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"
/>
</svg>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment