Unverified Commit 75e51ecf authored by Que Nguyen's avatar Que Nguyen Committed by GitHub
Browse files

Merge branch 'open-webui:main' into searxng

parents a02ba52d 9e4dd4b8
...@@ -11,8 +11,6 @@ on: ...@@ -11,8 +11,6 @@ on:
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
FULL_IMAGE_NAME: ghcr.io/${{ github.repository }}
jobs: jobs:
build-main-image: build-main-image:
...@@ -28,6 +26,15 @@ jobs: ...@@ -28,6 +26,15 @@ jobs:
- linux/arm64 - linux/arm64
steps: steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking
- name: Set repository and image name to lowercase
run: |
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
env:
IMAGE_NAME: '${{ github.repository }}'
- name: Prepare - name: Prepare
run: | run: |
platform=${{ matrix.platform }} platform=${{ matrix.platform }}
...@@ -116,6 +123,15 @@ jobs: ...@@ -116,6 +123,15 @@ jobs:
- linux/arm64 - linux/arm64
steps: steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking
- name: Set repository and image name to lowercase
run: |
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
env:
IMAGE_NAME: '${{ github.repository }}'
- name: Prepare - name: Prepare
run: | run: |
platform=${{ matrix.platform }} platform=${{ matrix.platform }}
...@@ -207,6 +223,15 @@ jobs: ...@@ -207,6 +223,15 @@ jobs:
- linux/arm64 - linux/arm64
steps: steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking
- name: Set repository and image name to lowercase
run: |
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
env:
IMAGE_NAME: '${{ github.repository }}'
- name: Prepare - name: Prepare
run: | run: |
platform=${{ matrix.platform }} platform=${{ matrix.platform }}
...@@ -289,6 +314,15 @@ jobs: ...@@ -289,6 +314,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ build-main-image ] needs: [ build-main-image ]
steps: steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking
- name: Set repository and image name to lowercase
run: |
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
env:
IMAGE_NAME: '${{ github.repository }}'
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
...@@ -335,6 +369,15 @@ jobs: ...@@ -335,6 +369,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ build-cuda-image ] needs: [ build-cuda-image ]
steps: steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking
- name: Set repository and image name to lowercase
run: |
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
env:
IMAGE_NAME: '${{ github.repository }}'
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
...@@ -382,6 +425,15 @@ jobs: ...@@ -382,6 +425,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ build-ollama-image ] needs: [ build-ollama-image ]
steps: steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking
- name: Set repository and image name to lowercase
run: |
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
env:
IMAGE_NAME: '${{ github.repository }}'
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
......
...@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. ...@@ -5,6 +5,30 @@ 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.5] - 2024-06-16
### Added
- **📞 Enhanced Voice Call**: Text-to-speech (TTS) callback now operates in real-time for each sentence, reducing latency by not waiting for full completion.
- **👆 Tap to Interrupt**: During a call, you can now stop the assistant from speaking by simply tapping, instead of using voice. This resolves the issue of the speaker's voice being mistakenly registered as input.
- **😊 Emoji Call**: Toggle this feature on from the Settings > Interface, allowing LLMs to express emotions using emojis during voice calls for a more dynamic interaction.
- **🖱️ Quick Archive/Delete**: Use the Shift key + mouseover on the chat list to swiftly archive or delete items.
- **📝 Markdown Support in Model Descriptions**: You can now format model descriptions with markdown, enabling bold text, links, etc.
- **🧠 Editable Memories**: Adds the capability to modify memories.
- **📋 Admin Panel Sorting**: Introduces the ability to sort users/chats within the admin panel.
- **🌑 Dark Mode for Quick Selectors**: Dark mode now available for chat quick selectors (prompts, models, documents).
- **🔧 Advanced Parameters**: Adds 'num_keep' and 'num_batch' to advanced parameters for customization.
- **📅 Dynamic System Prompts**: New variables '{{CURRENT_DATETIME}}', '{{CURRENT_TIME}}', '{{USER_LOCATION}}' added for system prompts. Ensure '{{USER_LOCATION}}' is toggled on from Settings > Interface.
- **🌐 Tavily Web Search**: Includes Tavily as a web search provider option.
- **🖊️ Federated Auth Usernames**: Ability to set user names for federated authentication.
- **🔗 Auto Clean URLs**: When adding connection URLs, trailing slashes are now automatically removed.
- **🌐 Enhanced Translations**: Improved Chinese and Swedish translations.
### Fixed
- **⏳ AIOHTTP_CLIENT_TIMEOUT**: Introduced a new environment variable 'AIOHTTP_CLIENT_TIMEOUT' for requests to Ollama lasting longer than 5 minutes. Default is 300 seconds; set to blank ('') for no timeout.
- **❌ Message Delete Freeze**: Resolved an issue where message deletion would sometimes cause the web UI to freeze.
## [0.3.4] - 2024-06-12 ## [0.3.4] - 2024-06-12
### Fixed ### Fixed
......
...@@ -160,7 +160,7 @@ Check our Migration Guide available in our [Open WebUI Documentation](https://do ...@@ -160,7 +160,7 @@ Check our Migration Guide available in our [Open WebUI Documentation](https://do
If you want to try out the latest bleeding-edge features and are okay with occasional instability, you can use the `:dev` tag like this: If you want to try out the latest bleeding-edge features and are okay with occasional instability, you can use the `:dev` tag like this:
```bash ```bash
docker run -d -p 3000:8080 -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:dev docker run -d -p 3000:8080 -v open-webui:/app/backend/data --name open-webui --add-host=host.docker.internal:host-gateway --restart always ghcr.io/open-webui/open-webui:dev
``` ```
## What's Next? 🌟 ## What's Next? 🌟
......
...@@ -37,6 +37,10 @@ from config import ( ...@@ -37,6 +37,10 @@ from config import (
ENABLE_IMAGE_GENERATION, ENABLE_IMAGE_GENERATION,
AUTOMATIC1111_BASE_URL, AUTOMATIC1111_BASE_URL,
COMFYUI_BASE_URL, COMFYUI_BASE_URL,
COMFYUI_CFG_SCALE,
COMFYUI_SAMPLER,
COMFYUI_SCHEDULER,
COMFYUI_SD3,
IMAGES_OPENAI_API_BASE_URL, IMAGES_OPENAI_API_BASE_URL,
IMAGES_OPENAI_API_KEY, IMAGES_OPENAI_API_KEY,
IMAGE_GENERATION_MODEL, IMAGE_GENERATION_MODEL,
...@@ -78,6 +82,10 @@ app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL ...@@ -78,6 +82,10 @@ app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
app.state.config.IMAGE_SIZE = IMAGE_SIZE app.state.config.IMAGE_SIZE = IMAGE_SIZE
app.state.config.IMAGE_STEPS = IMAGE_STEPS app.state.config.IMAGE_STEPS = IMAGE_STEPS
app.state.config.COMFYUI_CFG_SCALE = COMFYUI_CFG_SCALE
app.state.config.COMFYUI_SAMPLER = COMFYUI_SAMPLER
app.state.config.COMFYUI_SCHEDULER = COMFYUI_SCHEDULER
app.state.config.COMFYUI_SD3 = COMFYUI_SD3
@app.get("/config") @app.get("/config")
...@@ -457,6 +465,18 @@ def generate_image( ...@@ -457,6 +465,18 @@ def generate_image(
if form_data.negative_prompt is not None: if form_data.negative_prompt is not None:
data["negative_prompt"] = form_data.negative_prompt data["negative_prompt"] = form_data.negative_prompt
if app.state.config.COMFYUI_CFG_SCALE:
data["cfg_scale"] = app.state.config.COMFYUI_CFG_SCALE
if app.state.config.COMFYUI_SAMPLER is not None:
data["sampler"] = app.state.config.COMFYUI_SAMPLER
if app.state.config.COMFYUI_SCHEDULER is not None:
data["scheduler"] = app.state.config.COMFYUI_SCHEDULER
if app.state.config.COMFYUI_SD3 is not None:
data["sd3"] = app.state.config.COMFYUI_SD3
data = ImageGenerationPayload(**data) data = ImageGenerationPayload(**data)
res = comfyui_generate_image( res = comfyui_generate_image(
......
...@@ -190,6 +190,10 @@ class ImageGenerationPayload(BaseModel): ...@@ -190,6 +190,10 @@ class ImageGenerationPayload(BaseModel):
width: int width: int
height: int height: int
n: int = 1 n: int = 1
cfg_scale: Optional[float] = None
sampler: Optional[str] = None
scheduler: Optional[str] = None
sd3: Optional[bool] = None
def comfyui_generate_image( def comfyui_generate_image(
...@@ -199,6 +203,18 @@ def comfyui_generate_image( ...@@ -199,6 +203,18 @@ def comfyui_generate_image(
comfyui_prompt = json.loads(COMFYUI_DEFAULT_PROMPT) comfyui_prompt = json.loads(COMFYUI_DEFAULT_PROMPT)
if payload.cfg_scale:
comfyui_prompt["3"]["inputs"]["cfg"] = payload.cfg_scale
if payload.sampler:
comfyui_prompt["3"]["inputs"]["sampler"] = payload.sampler
if payload.scheduler:
comfyui_prompt["3"]["inputs"]["scheduler"] = payload.scheduler
if payload.sd3:
comfyui_prompt["5"]["class_type"] = "EmptySD3LatentImage"
comfyui_prompt["4"]["inputs"]["ckpt_name"] = model comfyui_prompt["4"]["inputs"]["ckpt_name"] = model
comfyui_prompt["5"]["inputs"]["batch_size"] = payload.n comfyui_prompt["5"]["inputs"]["batch_size"] = payload.n
comfyui_prompt["5"]["inputs"]["width"] = payload.width comfyui_prompt["5"]["inputs"]["width"] = payload.width
......
...@@ -850,8 +850,7 @@ async def generate_chat_completion( ...@@ -850,8 +850,7 @@ async def generate_chat_completion(
url = app.state.config.OLLAMA_BASE_URLS[url_idx] url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}") log.info(f"url: {url}")
log.debug(payload)
print(payload)
return await post_streaming_url(f"{url}/api/chat", json.dumps(payload)) return await post_streaming_url(f"{url}/api/chat", json.dumps(payload))
......
...@@ -430,13 +430,11 @@ async def generate_chat_completion( ...@@ -430,13 +430,11 @@ async def generate_chat_completion(
# Convert the modified body back to JSON # Convert the modified body back to JSON
payload = json.dumps(payload) payload = json.dumps(payload)
print(payload) log.debug(payload)
url = app.state.config.OPENAI_API_BASE_URLS[idx] url = app.state.config.OPENAI_API_BASE_URLS[idx]
key = app.state.config.OPENAI_API_KEYS[idx] key = app.state.config.OPENAI_API_KEYS[idx]
print(payload)
headers = {} headers = {}
headers["Authorization"] = f"Bearer {key}" headers["Authorization"] = f"Bearer {key}"
headers["Content-Type"] = "application/json" headers["Content-Type"] = "application/json"
......
"""Peewee migrations -- 002_add_local_sharing.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."""
# Adding fields info to the 'user' table
migrator.add_fields("user", info=pw.TextField(null=True))
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
# Remove the settings field
migrator.remove_fields("user", "info")
...@@ -25,6 +25,7 @@ from config import ( ...@@ -25,6 +25,7 @@ from config import (
USER_PERMISSIONS, USER_PERMISSIONS,
WEBHOOK_URL, WEBHOOK_URL,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER, WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
JWT_EXPIRES_IN, JWT_EXPIRES_IN,
WEBUI_BANNERS, WEBUI_BANNERS,
ENABLE_COMMUNITY_SHARING, ENABLE_COMMUNITY_SHARING,
...@@ -40,6 +41,7 @@ app.state.config = AppConfig() ...@@ -40,6 +41,7 @@ app.state.config = AppConfig()
app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP
app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN
app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
app.state.config.SHOW_ADMIN_DETAILS = SHOW_ADMIN_DETAILS app.state.config.SHOW_ADMIN_DETAILS = SHOW_ADMIN_DETAILS
......
...@@ -26,6 +26,7 @@ class User(Model): ...@@ -26,6 +26,7 @@ class User(Model):
api_key = CharField(null=True, unique=True) api_key = CharField(null=True, unique=True)
settings = JSONField(null=True) settings = JSONField(null=True)
info = JSONField(null=True)
class Meta: class Meta:
database = DB database = DB
...@@ -50,6 +51,7 @@ class UserModel(BaseModel): ...@@ -50,6 +51,7 @@ class UserModel(BaseModel):
api_key: Optional[str] = None api_key: Optional[str] = None
settings: Optional[UserSettings] = None settings: Optional[UserSettings] = None
info: Optional[dict] = None
#################### ####################
......
...@@ -33,7 +33,11 @@ from utils.utils import ( ...@@ -33,7 +33,11 @@ from utils.utils import (
from utils.misc import parse_duration, validate_email_format from utils.misc import parse_duration, validate_email_format
from utils.webhook import post_webhook from utils.webhook import post_webhook
from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
from config import WEBUI_AUTH, WEBUI_AUTH_TRUSTED_EMAIL_HEADER from config import (
WEBUI_AUTH,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
)
router = APIRouter() router = APIRouter()
...@@ -110,11 +114,16 @@ async def signin(request: Request, form_data: SigninForm): ...@@ -110,11 +114,16 @@ async def signin(request: Request, form_data: SigninForm):
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER) raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
trusted_email = request.headers[WEBUI_AUTH_TRUSTED_EMAIL_HEADER].lower() trusted_email = request.headers[WEBUI_AUTH_TRUSTED_EMAIL_HEADER].lower()
trusted_name = trusted_email
if WEBUI_AUTH_TRUSTED_NAME_HEADER:
trusted_name = request.headers.get(
WEBUI_AUTH_TRUSTED_NAME_HEADER, trusted_email
)
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,
SignupForm( SignupForm(
email=trusted_email, password=str(uuid.uuid4()), name=trusted_email email=trusted_email, password=str(uuid.uuid4()), name=trusted_name
), ),
) )
user = Auths.authenticate_user_by_trusted_header(trusted_email) user = Auths.authenticate_user_by_trusted_header(trusted_email)
......
...@@ -115,6 +115,52 @@ async def update_user_settings_by_session_user( ...@@ -115,6 +115,52 @@ async def update_user_settings_by_session_user(
) )
############################
# GetUserInfoBySessionUser
############################
@router.get("/user/info", response_model=Optional[dict])
async def get_user_info_by_session_user(user=Depends(get_verified_user)):
user = Users.get_user_by_id(user.id)
if user:
return user.info
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.USER_NOT_FOUND,
)
############################
# UpdateUserInfoBySessionUser
############################
@router.post("/user/info/update", response_model=Optional[dict])
async def update_user_settings_by_session_user(
form_data: dict, user=Depends(get_verified_user)
):
user = Users.get_user_by_id(user.id)
if user:
if user.info is None:
user.info = {}
user = Users.update_user_by_id(user.id, {"info": {**user.info, **form_data}})
if user:
return user.info
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.USER_NOT_FOUND,
)
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.USER_NOT_FOUND,
)
############################ ############################
# GetUserById # GetUserById
############################ ############################
......
...@@ -294,6 +294,7 @@ WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true" ...@@ -294,6 +294,7 @@ WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get( WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
"WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
) )
WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
JWT_EXPIRES_IN = PersistentConfig( JWT_EXPIRES_IN = PersistentConfig(
"JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1") "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1")
) )
...@@ -425,7 +426,14 @@ OLLAMA_API_BASE_URL = os.environ.get( ...@@ -425,7 +426,14 @@ OLLAMA_API_BASE_URL = os.environ.get(
) )
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "") OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
AIOHTTP_CLIENT_TIMEOUT = int(os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "300")) AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "300")
if AIOHTTP_CLIENT_TIMEOUT == "":
AIOHTTP_CLIENT_TIMEOUT = None
else:
AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT)
K8S_FLAG = os.environ.get("K8S_FLAG", "") K8S_FLAG = os.environ.get("K8S_FLAG", "")
USE_OLLAMA_DOCKER = os.environ.get("USE_OLLAMA_DOCKER", "false") USE_OLLAMA_DOCKER = os.environ.get("USE_OLLAMA_DOCKER", "false")
...@@ -1009,6 +1017,30 @@ COMFYUI_BASE_URL = PersistentConfig( ...@@ -1009,6 +1017,30 @@ COMFYUI_BASE_URL = PersistentConfig(
os.getenv("COMFYUI_BASE_URL", ""), os.getenv("COMFYUI_BASE_URL", ""),
) )
COMFYUI_CFG_SCALE = PersistentConfig(
"COMFYUI_CFG_SCALE",
"image_generation.comfyui.cfg_scale",
os.getenv("COMFYUI_CFG_SCALE", ""),
)
COMFYUI_SAMPLER = PersistentConfig(
"COMFYUI_SAMPLER",
"image_generation.comfyui.sampler",
os.getenv("COMFYUI_SAMPLER", ""),
)
COMFYUI_SCHEDULER = PersistentConfig(
"COMFYUI_SCHEDULER",
"image_generation.comfyui.scheduler",
os.getenv("COMFYUI_SCHEDULER", ""),
)
COMFYUI_SD3 = PersistentConfig(
"COMFYUI_SD3",
"image_generation.comfyui.sd3",
os.environ.get("COMFYUI_SD3", "").lower() == "true",
)
IMAGES_OPENAI_API_BASE_URL = PersistentConfig( IMAGES_OPENAI_API_BASE_URL = PersistentConfig(
"IMAGES_OPENAI_API_BASE_URL", "IMAGES_OPENAI_API_BASE_URL",
"image_generation.openai.api_base_url", "image_generation.openai.api_base_url",
......
...@@ -764,7 +764,12 @@ async def generate_title(form_data: dict, user=Depends(get_verified_user)): ...@@ -764,7 +764,12 @@ async def generate_title(form_data: dict, user=Depends(get_verified_user)):
template = app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE template = app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE
content = title_generation_template( content = title_generation_template(
template, form_data["prompt"], user.model_dump() template,
form_data["prompt"],
{
"name": user.name,
"location": user.info.get("location") if user.info else None,
},
) )
payload = { payload = {
...@@ -776,7 +781,7 @@ async def generate_title(form_data: dict, user=Depends(get_verified_user)): ...@@ -776,7 +781,7 @@ async def generate_title(form_data: dict, user=Depends(get_verified_user)):
"title": True, "title": True,
} }
print(payload) log.debug(payload)
try: try:
payload = filter_pipeline(payload, user) payload = filter_pipeline(payload, user)
...@@ -830,7 +835,7 @@ async def generate_search_query(form_data: dict, user=Depends(get_verified_user) ...@@ -830,7 +835,7 @@ async def generate_search_query(form_data: dict, user=Depends(get_verified_user)
template = app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE template = app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE
content = search_query_generation_template( content = search_query_generation_template(
template, form_data["prompt"], user.model_dump() template, form_data["prompt"], {"name": user.name}
) )
payload = { payload = {
...@@ -893,7 +898,12 @@ Message: """{{prompt}}""" ...@@ -893,7 +898,12 @@ Message: """{{prompt}}"""
''' '''
content = title_generation_template( content = title_generation_template(
template, form_data["prompt"], user.model_dump() template,
form_data["prompt"],
{
"name": user.name,
"location": user.info.get("location") if user.info else None,
},
) )
payload = { payload = {
...@@ -905,7 +915,7 @@ Message: """{{prompt}}""" ...@@ -905,7 +915,7 @@ Message: """{{prompt}}"""
"task": True, "task": True,
} }
print(payload) log.debug(payload)
try: try:
payload = filter_pipeline(payload, user) payload = filter_pipeline(payload, user)
......
...@@ -6,24 +6,28 @@ from typing import Optional ...@@ -6,24 +6,28 @@ from typing import Optional
def prompt_template( def prompt_template(
template: str, user_name: str = None, current_location: str = None template: str, user_name: str = None, user_location: str = None
) -> str: ) -> str:
# Get the current date # Get the current date
current_date = datetime.now() current_date = datetime.now()
# Format the date to YYYY-MM-DD # Format the date to YYYY-MM-DD
formatted_date = current_date.strftime("%Y-%m-%d") formatted_date = current_date.strftime("%Y-%m-%d")
formatted_time = current_date.strftime("%I:%M:%S %p")
# Replace {{CURRENT_DATE}} in the template with the formatted date
template = template.replace("{{CURRENT_DATE}}", formatted_date) template = template.replace("{{CURRENT_DATE}}", formatted_date)
template = template.replace("{{CURRENT_TIME}}", formatted_time)
template = template.replace(
"{{CURRENT_DATETIME}}", f"{formatted_date} {formatted_time}"
)
if user_name: if user_name:
# Replace {{USER_NAME}} in the template with the user's name # Replace {{USER_NAME}} in the template with the user's name
template = template.replace("{{USER_NAME}}", user_name) template = template.replace("{{USER_NAME}}", user_name)
if current_location: if user_location:
# Replace {{CURRENT_LOCATION}} in the template with the current location # Replace {{USER_LOCATION}} in the template with the current location
template = template.replace("{{CURRENT_LOCATION}}", current_location) template = template.replace("{{USER_LOCATION}}", user_location)
return template return template
...@@ -61,7 +65,7 @@ def title_generation_template( ...@@ -61,7 +65,7 @@ def title_generation_template(
template = prompt_template( template = prompt_template(
template, template,
**( **(
{"user_name": user.get("name"), "current_location": user.get("location")} {"user_name": user.get("name"), "user_location": user.get("location")}
if user if user
else {} else {}
), ),
...@@ -104,7 +108,7 @@ def search_query_generation_template( ...@@ -104,7 +108,7 @@ def search_query_generation_template(
template = prompt_template( template = prompt_template(
template, template,
**( **(
{"user_name": user.get("name"), "current_location": user.get("location")} {"user_name": user.get("name"), "user_location": user.get("location")}
if user if user
else {} else {}
), ),
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.3.4", "version": "0.3.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "open-webui", "name": "open-webui",
"version": "0.3.4", "version": "0.3.5",
"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.4", "version": "0.3.5",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run pyodide:fetch && vite dev --host", "dev": "npm run pyodide:fetch && vite dev --host",
......
import { WEBUI_API_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL } from '$lib/constants';
import { getUserPosition } from '$lib/utils';
export const getUserPermissions = async (token: string) => { export const getUserPermissions = async (token: string) => {
let error = null; let error = null;
...@@ -198,6 +199,75 @@ export const getUserById = async (token: string, userId: string) => { ...@@ -198,6 +199,75 @@ export const getUserById = async (token: string, userId: string) => {
return res; return res;
}; };
export const getUserInfo = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/info`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
export const updateUserInfo = async (token: string, info: object) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/info/update`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
...info
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
export const getAndUpdateUserLocation = async (token: string) => {
const location = await getUserPosition().catch((err) => {
throw err;
});
if (location) {
await updateUserInfo(token, { location: location });
return location;
} else {
throw new Error('Failed to get user location');
}
};
export const deleteUserById = async (token: string, userId: string) => { export const deleteUserById = async (token: string, userId: string) => {
let error = null; let error = null;
......
...@@ -44,6 +44,8 @@ ...@@ -44,6 +44,8 @@
let ENABLE_OLLAMA_API = null; let ENABLE_OLLAMA_API = null;
const verifyOpenAIHandler = async (idx) => { const verifyOpenAIHandler = async (idx) => {
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.map((url) => url.replace(/\/$/, ''));
OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS); OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS); OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS);
...@@ -63,6 +65,10 @@ ...@@ -63,6 +65,10 @@
}; };
const verifyOllamaHandler = async (idx) => { const verifyOllamaHandler = async (idx) => {
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url) => url !== '').map((url) =>
url.replace(/\/$/, '')
);
OLLAMA_BASE_URLS = await updateOllamaUrls(localStorage.token, OLLAMA_BASE_URLS); OLLAMA_BASE_URLS = await updateOllamaUrls(localStorage.token, OLLAMA_BASE_URLS);
const res = await getOllamaVersion(localStorage.token, idx).catch((error) => { const res = await getOllamaVersion(localStorage.token, idx).catch((error) => {
...@@ -78,6 +84,8 @@ ...@@ -78,6 +84,8 @@
}; };
const updateOpenAIHandler = async () => { const updateOpenAIHandler = async () => {
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.map((url) => url.replace(/\/$/, ''));
// Check if API KEYS length is same than API URLS length // Check if API KEYS length is same than API URLS length
if (OPENAI_API_KEYS.length !== OPENAI_API_BASE_URLS.length) { if (OPENAI_API_KEYS.length !== OPENAI_API_BASE_URLS.length) {
// if there are more keys than urls, remove the extra keys // if there are more keys than urls, remove the extra keys
...@@ -100,7 +108,10 @@ ...@@ -100,7 +108,10 @@
}; };
const updateOllamaUrlsHandler = async () => { const updateOllamaUrlsHandler = async () => {
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url) => url !== ''); OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url) => url !== '').map((url) =>
url.replace(/\/$/, '')
);
console.log(OLLAMA_BASE_URLS); console.log(OLLAMA_BASE_URLS);
if (OLLAMA_BASE_URLS.length === 0) { if (OLLAMA_BASE_URLS.length === 0) {
......
...@@ -31,6 +31,17 @@ ...@@ -31,6 +31,17 @@
} }
})(); })();
} }
let sortKey = 'updated_at'; // default sort key
let sortOrder = 'desc'; // default sort order
function setSortKey(key) {
if (sortKey === key) {
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
} else {
sortKey = key;
sortOrder = 'asc';
}
}
</script> </script>
<Modal size="lg" bind:show> <Modal size="lg" bind:show>
...@@ -69,18 +80,56 @@ ...@@ -69,18 +80,56 @@
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800" class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800"
> >
<tr> <tr>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th> <th
<th scope="col" class="px-3 py-2 hidden md:flex"> {$i18n.t('Created at')} </th> scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('title')}
>
{$i18n.t('Title')}
{#if sortKey === 'title'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible">▲</span>
{/if}
</th>
<th
scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('created_at')}
>
{$i18n.t('Created at')}
{#if sortKey === 'created_at'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible">▲</span>
{/if}
</th>
<th
scope="col"
class="px-3 py-2 hidden md:flex cursor-pointer select-none"
on:click={() => setSortKey('updated_at')}
>
{$i18n.t('Updated at')}
{#if sortKey === 'updated_at'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible">▲</span>
{/if}
</th>
<th scope="col" class="px-3 py-2 text-right" /> <th scope="col" class="px-3 py-2 text-right" />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each chats as chat, idx} {#each chats.sort((a, b) => {
if (a[sortKey] < b[sortKey]) return sortOrder === 'asc' ? -1 : 1;
if (a[sortKey] > b[sortKey]) return sortOrder === 'asc' ? 1 : -1;
return 0;
}) as chat, idx}
<tr <tr
class="bg-transparent {idx !== chats.length - 1 && class="bg-transparent {idx !== chats.length - 1 &&
'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs" 'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
> >
<td class="px-3 py-1 w-2/3"> <td class="px-3 py-1">
<a href="/s/{chat.id}" target="_blank"> <a href="/s/{chat.id}" target="_blank">
<div class=" underline line-clamp-1"> <div class=" underline line-clamp-1">
{chat.title} {chat.title}
...@@ -88,11 +137,16 @@ ...@@ -88,11 +137,16 @@
</a> </a>
</td> </td>
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]"> <td class=" px-3 py-1 h-[2.5rem]">
<div class="my-auto"> <div class="my-auto">
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))} {dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
</div> </div>
</td> </td>
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
<div class="my-auto">
{dayjs(chat.updated_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
</div>
</td>
<td class="px-3 py-1 text-right"> <td class="px-3 py-1 text-right">
<div class="flex justify-end w-full"> <div class="flex justify-end w-full">
......
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