chore: format
This commit is contained in:
1 parent
46ff3abbb8
commit
7bcc0e2e5c
27 files changed
+136
-140
No files matched your search
@@ -172,7 +172,6 @@ def get_config_value(config_path: str):
|
|||||||
PERSISTENT_CONFIG_REGISTRY = []
|
PERSISTENT_CONFIG_REGISTRY = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def save_config(config):
|
def save_config(config):
|
||||||
"""Sync save — used ONLY at startup/import time."""
|
"""Sync save — used ONLY at startup/import time."""
|
||||||
global CONFIG_DATA
|
global CONFIG_DATA
|
||||||
@@ -2666,8 +2665,12 @@ ONEDRIVE_CLIENT_ID = os.environ.get('ONEDRIVE_CLIENT_ID', '')
|
|||||||
ONEDRIVE_CLIENT_ID_PERSONAL = os.environ.get('ONEDRIVE_CLIENT_ID_PERSONAL', ONEDRIVE_CLIENT_ID)
|
ONEDRIVE_CLIENT_ID_PERSONAL = os.environ.get('ONEDRIVE_CLIENT_ID_PERSONAL', ONEDRIVE_CLIENT_ID)
|
||||||
ONEDRIVE_CLIENT_ID_BUSINESS = os.environ.get('ONEDRIVE_CLIENT_ID_BUSINESS', ONEDRIVE_CLIENT_ID)
|
ONEDRIVE_CLIENT_ID_BUSINESS = os.environ.get('ONEDRIVE_CLIENT_ID_BUSINESS', ONEDRIVE_CLIENT_ID)
|
||||||
|
|
||||||
ENABLE_ONEDRIVE_PERSONAL = os.environ.get('ENABLE_ONEDRIVE_PERSONAL', 'True').lower() == 'true' and bool(ONEDRIVE_CLIENT_ID_PERSONAL)
|
ENABLE_ONEDRIVE_PERSONAL = os.environ.get('ENABLE_ONEDRIVE_PERSONAL', 'True').lower() == 'true' and bool(
|
||||||
ENABLE_ONEDRIVE_BUSINESS = os.environ.get('ENABLE_ONEDRIVE_BUSINESS', 'True').lower() == 'true' and bool(ONEDRIVE_CLIENT_ID_BUSINESS)
|
ONEDRIVE_CLIENT_ID_PERSONAL
|
||||||
|
)
|
||||||
|
ENABLE_ONEDRIVE_BUSINESS = os.environ.get('ENABLE_ONEDRIVE_BUSINESS', 'True').lower() == 'true' and bool(
|
||||||
|
ONEDRIVE_CLIENT_ID_BUSINESS
|
||||||
|
)
|
||||||
|
|
||||||
ONEDRIVE_SHAREPOINT_URL = PersistentConfig(
|
ONEDRIVE_SHAREPOINT_URL = PersistentConfig(
|
||||||
'ONEDRIVE_SHAREPOINT_URL',
|
'ONEDRIVE_SHAREPOINT_URL',
|
||||||
|
|||||||
@@ -258,9 +258,7 @@ ENABLE_EASTER_EGGS = os.environ.get('ENABLE_EASTER_EGGS', 'True').lower() == 'tr
|
|||||||
# 302 redirect to the original origin. Set to False to suppress the
|
# 302 redirect to the original origin. Set to False to suppress the
|
||||||
# redirect (prevents client-side IP/UA/Referer leaks to attacker-
|
# redirect (prevents client-side IP/UA/Referer leaks to attacker-
|
||||||
# controlled origins) and fall through to the default image instead.
|
# controlled origins) and fall through to the default image instead.
|
||||||
ENABLE_PROFILE_IMAGE_URL_FORWARDING = os.environ.get(
|
ENABLE_PROFILE_IMAGE_URL_FORWARDING = os.environ.get('ENABLE_PROFILE_IMAGE_URL_FORWARDING', 'True').lower() == 'true'
|
||||||
'ENABLE_PROFILE_IMAGE_URL_FORWARDING', 'True'
|
|
||||||
).lower() == 'true'
|
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# WEBUI_BUILD_HASH
|
# WEBUI_BUILD_HASH
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ ASYNC_SQLALCHEMY_DATABASE_URL = _make_async_url(SQLALCHEMY_DATABASE_URL)
|
|||||||
# to cover every entry point (workers, reload, direct invocations).
|
# to cover every entry point (workers, reload, direct invocations).
|
||||||
if sys.platform == 'win32' and _is_postgres_url(DATABASE_URL):
|
if sys.platform == 'win32' and _is_postgres_url(DATABASE_URL):
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||||
|
|
||||||
if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL:
|
if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL:
|
||||||
|
|||||||
@@ -1565,7 +1565,7 @@ async def unload_model(request: Request, form_data: ModelUnloadForm, user=Depend
|
|||||||
prefix_id = api_config.get('prefix_id', None)
|
prefix_id = api_config.get('prefix_id', None)
|
||||||
actual_model = model_id
|
actual_model = model_id
|
||||||
if prefix_id and actual_model.startswith(f'{prefix_id}.'):
|
if prefix_id and actual_model.startswith(f'{prefix_id}.'):
|
||||||
actual_model = actual_model[len(f'{prefix_id}.'):]
|
actual_model = actual_model[len(f'{prefix_id}.') :]
|
||||||
|
|
||||||
payload = json.dumps({'model': actual_model, 'keep_alive': 0, 'prompt': ''})
|
payload = json.dumps({'model': actual_model, 'keep_alive': 0, 'prompt': ''})
|
||||||
|
|
||||||
@@ -1574,7 +1574,7 @@ async def unload_model(request: Request, form_data: ModelUnloadForm, user=Depend
|
|||||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
**({"Authorization": f"Bearer {key}"} if key else {}),
|
**({'Authorization': f'Bearer {key}'} if key else {}),
|
||||||
}
|
}
|
||||||
async with session.post(
|
async with session.post(
|
||||||
f'{url}/api/generate',
|
f'{url}/api/generate',
|
||||||
@@ -1602,7 +1602,9 @@ async def unload_model(request: Request, form_data: ModelUnloadForm, user=Depend
|
|||||||
api_config = request.app.state.config.OPENAI_API_CONFIGS.get(str(idx), {})
|
api_config = request.app.state.config.OPENAI_API_CONFIGS.get(str(idx), {})
|
||||||
provider = api_config.get('provider', '')
|
provider = api_config.get('provider', '')
|
||||||
base_url = request.app.state.config.OPENAI_API_BASE_URLS[idx]
|
base_url = request.app.state.config.OPENAI_API_BASE_URLS[idx]
|
||||||
key = request.app.state.config.OPENAI_API_KEYS[idx] if idx < len(request.app.state.config.OPENAI_API_KEYS) else ''
|
key = (
|
||||||
|
request.app.state.config.OPENAI_API_KEYS[idx] if idx < len(request.app.state.config.OPENAI_API_KEYS) else ''
|
||||||
|
)
|
||||||
|
|
||||||
if provider == 'llama.cpp':
|
if provider == 'llama.cpp':
|
||||||
root_url = base_url.rstrip('/').removesuffix('/v1')
|
root_url = base_url.rstrip('/').removesuffix('/v1')
|
||||||
@@ -1611,7 +1613,7 @@ async def unload_model(request: Request, form_data: ModelUnloadForm, user=Depend
|
|||||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
**({"Authorization": f"Bearer {key}"} if key else {}),
|
**({'Authorization': f'Bearer {key}'} if key else {}),
|
||||||
}
|
}
|
||||||
async with session.post(
|
async with session.post(
|
||||||
f'{root_url}/models/unload',
|
f'{root_url}/models/unload',
|
||||||
@@ -2076,9 +2078,7 @@ async def chat_completion(
|
|||||||
event_emitter = await get_event_emitter(metadata, update_db=False)
|
event_emitter = await get_event_emitter(metadata, update_db=False)
|
||||||
if event_emitter:
|
if event_emitter:
|
||||||
try:
|
try:
|
||||||
await asyncio.shield(
|
await asyncio.shield(event_emitter({'type': 'chat:active', 'data': {'active': False}}))
|
||||||
event_emitter({'type': 'chat:active', 'data': {'active': False}})
|
|
||||||
)
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Revises: 56359461a091
|
|||||||
Create Date: 2026-05-09 04:29:27.651341
|
Create Date: 2026-05-09 04:29:27.651341
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@@ -24,6 +25,7 @@ import time
|
|||||||
from sqlalchemy import select, update, insert
|
from sqlalchemy import select, update, insert
|
||||||
from sqlalchemy.sql import table, column
|
from sqlalchemy.sql import table, column
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'pinned_note',
|
'pinned_note',
|
||||||
@@ -32,66 +34,47 @@ def upgrade() -> None:
|
|||||||
sa.Column('note_id', sa.Text(), sa.ForeignKey('note.id', ondelete='CASCADE'), nullable=False),
|
sa.Column('note_id', sa.Text(), sa.ForeignKey('note.id', ondelete='CASCADE'), nullable=False),
|
||||||
sa.Column('created_at', sa.BigInteger(), nullable=False),
|
sa.Column('created_at', sa.BigInteger(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id'),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.UniqueConstraint('user_id', 'note_id', name='uq_pinned_note')
|
sa.UniqueConstraint('user_id', 'note_id', name='uq_pinned_note'),
|
||||||
)
|
)
|
||||||
|
|
||||||
conn = op.get_bind()
|
conn = op.get_bind()
|
||||||
|
|
||||||
note_table = table('note',
|
note_table = table('note', column('id', sa.Text), column('user_id', sa.Text), column('is_pinned', sa.Boolean))
|
||||||
column('id', sa.Text),
|
|
||||||
column('user_id', sa.Text),
|
|
||||||
column('is_pinned', sa.Boolean)
|
|
||||||
)
|
|
||||||
|
|
||||||
pinned_note_table = table('pinned_note',
|
pinned_note_table = table(
|
||||||
|
'pinned_note',
|
||||||
column('id', sa.Text),
|
column('id', sa.Text),
|
||||||
column('user_id', sa.Text),
|
column('user_id', sa.Text),
|
||||||
column('note_id', sa.Text),
|
column('note_id', sa.Text),
|
||||||
column('created_at', sa.BigInteger)
|
column('created_at', sa.BigInteger),
|
||||||
)
|
)
|
||||||
|
|
||||||
notes = conn.execute(
|
notes = conn.execute(select(note_table.c.id, note_table.c.user_id).where(note_table.c.is_pinned == True)).fetchall()
|
||||||
select(note_table.c.id, note_table.c.user_id).where(note_table.c.is_pinned == True)
|
|
||||||
).fetchall()
|
|
||||||
|
|
||||||
if notes:
|
if notes:
|
||||||
now = int(time.time_ns())
|
now = int(time.time_ns())
|
||||||
conn.execute(
|
conn.execute(
|
||||||
insert(pinned_note_table),
|
insert(pinned_note_table),
|
||||||
[
|
[{'id': str(uuid.uuid4()), 'user_id': note[1], 'note_id': note[0], 'created_at': now} for note in notes],
|
||||||
{
|
|
||||||
"id": str(uuid.uuid4()),
|
|
||||||
"user_id": note[1],
|
|
||||||
"note_id": note[0],
|
|
||||||
"created_at": now
|
|
||||||
}
|
|
||||||
for note in notes
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with op.batch_alter_table('note', schema=None) as batch_op:
|
with op.batch_alter_table('note', schema=None) as batch_op:
|
||||||
batch_op.drop_column('is_pinned')
|
batch_op.drop_column('is_pinned')
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
with op.batch_alter_table('note', schema=None) as batch_op:
|
with op.batch_alter_table('note', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('is_pinned', sa.Boolean(), nullable=True))
|
batch_op.add_column(sa.Column('is_pinned', sa.Boolean(), nullable=True))
|
||||||
|
|
||||||
conn = op.get_bind()
|
conn = op.get_bind()
|
||||||
|
|
||||||
note_table = table('note',
|
note_table = table('note', column('id', sa.Text), column('is_pinned', sa.Boolean))
|
||||||
column('id', sa.Text),
|
|
||||||
column('is_pinned', sa.Boolean)
|
|
||||||
)
|
|
||||||
|
|
||||||
pinned_note_table = table('pinned_note',
|
pinned_note_table = table('pinned_note', column('note_id', sa.Text))
|
||||||
column('note_id', sa.Text)
|
|
||||||
)
|
|
||||||
|
|
||||||
notes = conn.execute(select(pinned_note_table.c.note_id)).fetchall()
|
notes = conn.execute(select(pinned_note_table.c.note_id)).fetchall()
|
||||||
|
|
||||||
for note in notes:
|
for note in notes:
|
||||||
conn.execute(
|
conn.execute(update(note_table).where(note_table.c.id == note[0]).values(is_pinned=True))
|
||||||
update(note_table).where(note_table.c.id == note[0]).values(is_pinned=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
op.drop_table('pinned_note')
|
op.drop_table('pinned_note')
|
||||||
@@ -260,9 +260,7 @@ class ChatMessageTable:
|
|||||||
embedded JSON blob for legacy chats).
|
embedded JSON blob for legacy chats).
|
||||||
"""
|
"""
|
||||||
async with get_async_db_context(db) as db:
|
async with get_async_db_context(db) as db:
|
||||||
result = await db.execute(
|
result = await db.execute(select(ChatMessage).filter_by(chat_id=chat_id))
|
||||||
select(ChatMessage).filter_by(chat_id=chat_id)
|
|
||||||
)
|
|
||||||
rows = result.scalars().all()
|
rows = result.scalars().all()
|
||||||
|
|
||||||
if not rows:
|
if not rows:
|
||||||
|
|||||||
@@ -323,7 +323,9 @@ class NoteTable:
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
return await self._to_note_model(note, db=db) if note else None
|
return await self._to_note_model(note, db=db) if note else None
|
||||||
|
|
||||||
async def toggle_note_pinned_by_id(self, id: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[NoteModel]:
|
async def toggle_note_pinned_by_id(
|
||||||
|
self, id: str, user_id: str, db: Optional[AsyncSession] = None
|
||||||
|
) -> Optional[NoteModel]:
|
||||||
try:
|
try:
|
||||||
async with get_async_db_context(db) as db:
|
async with get_async_db_context(db) as db:
|
||||||
result = await db.execute(select(Note).filter(Note.id == id))
|
result = await db.execute(select(Note).filter(Note.id == id))
|
||||||
@@ -339,10 +341,7 @@ class NoteTable:
|
|||||||
await db.execute(delete(PinnedNote).filter_by(user_id=user_id, note_id=id))
|
await db.execute(delete(PinnedNote).filter_by(user_id=user_id, note_id=id))
|
||||||
else:
|
else:
|
||||||
new_pin = PinnedNote(
|
new_pin = PinnedNote(
|
||||||
id=str(uuid.uuid4()),
|
id=str(uuid.uuid4()), user_id=user_id, note_id=id, created_at=int(time.time_ns())
|
||||||
user_id=user_id,
|
|
||||||
note_id=id,
|
|
||||||
created_at=int(time.time_ns())
|
|
||||||
)
|
)
|
||||||
db.add(new_pin)
|
db.add(new_pin)
|
||||||
|
|
||||||
@@ -361,7 +360,12 @@ class NoteTable:
|
|||||||
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||||
user_group_ids = [group.id for group in user_groups]
|
user_group_ids = [group.id for group in user_groups]
|
||||||
|
|
||||||
stmt = select(Note).join(PinnedNote, PinnedNote.note_id == Note.id).filter(PinnedNote.user_id == user_id).order_by(PinnedNote.created_at.desc())
|
stmt = (
|
||||||
|
select(Note)
|
||||||
|
.join(PinnedNote, PinnedNote.note_id == Note.id)
|
||||||
|
.filter(PinnedNote.user_id == user_id)
|
||||||
|
.order_by(PinnedNote.created_at.desc())
|
||||||
|
)
|
||||||
stmt = self._has_permission(db, stmt, {'user_id': user_id, 'group_ids': user_group_ids}, permission)
|
stmt = self._has_permission(db, stmt, {'user_id': user_id, 'group_ids': user_group_ids}, permission)
|
||||||
|
|
||||||
result = await db.execute(stmt)
|
result = await db.execute(stmt)
|
||||||
|
|||||||
@@ -235,11 +235,7 @@ class PromptsTable:
|
|||||||
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||||
user_group_ids = [group.id for group in user_groups]
|
user_group_ids = [group.id for group in user_groups]
|
||||||
|
|
||||||
query = (
|
query = select(Prompt).filter(Prompt.is_active == True).order_by(Prompt.updated_at.desc())
|
||||||
select(Prompt)
|
|
||||||
.filter(Prompt.is_active == True)
|
|
||||||
.order_by(Prompt.updated_at.desc())
|
|
||||||
)
|
|
||||||
query = AccessGrants.has_permission_filter(
|
query = AccessGrants.has_permission_filter(
|
||||||
db=db,
|
db=db,
|
||||||
query=query,
|
query=query,
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ class PptxLoader:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TikaLoader:
|
class TikaLoader:
|
||||||
def __init__(self, url, file_path, mime_type=None, extract_images=None):
|
def __init__(self, url, file_path, mime_type=None, extract_images=None):
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ def search_duckduckgo(
|
|||||||
|
|
||||||
# Use the ddgs.text() method to perform the search
|
# Use the ddgs.text() method to perform the search
|
||||||
try:
|
try:
|
||||||
kwargs = {"safesearch": "moderate", "max_results": count}
|
kwargs = {'safesearch': 'moderate', 'max_results': count}
|
||||||
if backend and backend != "auto":
|
if backend and backend != 'auto':
|
||||||
kwargs["backend"] = backend
|
kwargs['backend'] = backend
|
||||||
results = ddgs.text(query, **kwargs)
|
results = ddgs.text(query, **kwargs)
|
||||||
search_results = results if results is not None else []
|
search_results = results if results is not None else []
|
||||||
except RatelimitException as e:
|
except RatelimitException as e:
|
||||||
|
|||||||
@@ -147,8 +147,7 @@ def transcode_audio_to_mp3(audio_data: bytes, content_type_header: str, output_p
|
|||||||
|
|
||||||
if BYPASS_PYDUB_PREPROCESSING:
|
if BYPASS_PYDUB_PREPROCESSING:
|
||||||
log.warning(
|
log.warning(
|
||||||
f'TTS returned {mime_type} but BYPASS_PYDUB_PREPROCESSING is set; '
|
f'TTS returned {mime_type} but BYPASS_PYDUB_PREPROCESSING is set; writing raw audio without transcoding'
|
||||||
f'writing raw audio without transcoding'
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1190,7 +1189,7 @@ def transcribe(request: Request, file_path: str, metadata: Optional[dict] = None
|
|||||||
|
|
||||||
results = []
|
results = []
|
||||||
try:
|
try:
|
||||||
if getattr(request.app.state.config, "STT_ENGINE", "") == "":
|
if getattr(request.app.state.config, 'STT_ENGINE', '') == '':
|
||||||
max_workers = 1
|
max_workers = 1
|
||||||
else:
|
else:
|
||||||
max_workers = None
|
max_workers = None
|
||||||
|
|||||||
@@ -374,7 +374,11 @@ async def verify_tool_servers_config(request: Request, form_data: ToolServerConn
|
|||||||
try:
|
try:
|
||||||
if form_data.type == 'mcp':
|
if form_data.type == 'mcp':
|
||||||
if form_data.auth_type in ('oauth_2.1', 'oauth_2.1_static'):
|
if form_data.auth_type in ('oauth_2.1', 'oauth_2.1_static'):
|
||||||
oauth_server_url = form_data.info.get('oauth_server_url') if form_data.info and form_data.info.get('oauth_server_url') else form_data.url
|
oauth_server_url = (
|
||||||
|
form_data.info.get('oauth_server_url')
|
||||||
|
if form_data.info and form_data.info.get('oauth_server_url')
|
||||||
|
else form_data.url
|
||||||
|
)
|
||||||
discovery_urls = await get_discovery_urls(oauth_server_url)
|
discovery_urls = await get_discovery_urls(oauth_server_url)
|
||||||
for discovery_url in discovery_urls:
|
for discovery_url in discovery_urls:
|
||||||
log.debug(f'Trying to fetch OAuth 2.1 discovery document from {discovery_url}')
|
log.debug(f'Trying to fetch OAuth 2.1 discovery document from {discovery_url}')
|
||||||
|
|||||||
@@ -517,9 +517,7 @@ async def get_openai_loaded_models(request: Request, models: dict, api_base_urls
|
|||||||
key = api_keys[idx] if idx < len(api_keys) else None
|
key = api_keys[idx] if idx < len(api_keys) else None
|
||||||
slots = await send_get_request(url=f'{root_url}/slots', key=key)
|
slots = await send_get_request(url=f'{root_url}/slots', key=key)
|
||||||
loaded_model_ids = (
|
loaded_model_ids = (
|
||||||
{s.get('model') for s in slots if s.get('model')}
|
{s.get('model') for s in slots if s.get('model')} if isinstance(slots, list) else set()
|
||||||
if isinstance(slots, list)
|
|
||||||
else set()
|
|
||||||
)
|
)
|
||||||
for model_id, model in models.items():
|
for model_id, model in models.items():
|
||||||
if model.get('urlIdx') == idx:
|
if model.get('urlIdx') == idx:
|
||||||
|
|||||||
@@ -3140,8 +3140,6 @@ async def create_calendar_event(
|
|||||||
return json.dumps({'error': str(e)})
|
return json.dumps({'error': str(e)})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def update_calendar_event(
|
async def update_calendar_event(
|
||||||
event_id: str,
|
event_id: str,
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ def include_user_info_headers(headers, user):
|
|||||||
FORWARD_USER_INFO_HEADER_USER_ROLE: user.role,
|
FORWARD_USER_INFO_HEADER_USER_ROLE: user.role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_custom_headers(custom_headers: dict, user=None, metadata: dict = None) -> dict:
|
def get_custom_headers(custom_headers: dict, user=None, metadata: dict = None) -> dict:
|
||||||
if not custom_headers or not isinstance(custom_headers, dict):
|
if not custom_headers or not isinstance(custom_headers, dict):
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -2459,7 +2459,7 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
|||||||
extra_params['__features__'] = features
|
extra_params['__features__'] = features
|
||||||
if features:
|
if features:
|
||||||
if 'voice' in features and features['voice']:
|
if 'voice' in features and features['voice']:
|
||||||
if getattr(request.app.state.config, "ENABLE_VOICE_MODE_PROMPT", True):
|
if getattr(request.app.state.config, 'ENABLE_VOICE_MODE_PROMPT', True):
|
||||||
if request.app.state.config.VOICE_MODE_PROMPT_TEMPLATE:
|
if request.app.state.config.VOICE_MODE_PROMPT_TEMPLATE:
|
||||||
template = request.app.state.config.VOICE_MODE_PROMPT_TEMPLATE
|
template = request.app.state.config.VOICE_MODE_PROMPT_TEMPLATE
|
||||||
else:
|
else:
|
||||||
@@ -4085,9 +4085,9 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
current_response_tool_call['function']['name'] = delta_name
|
current_response_tool_call['function']['name'] = delta_name
|
||||||
|
|
||||||
if delta_arguments:
|
if delta_arguments:
|
||||||
current_response_tool_call['function'][
|
current_response_tool_call['function']['arguments'] += (
|
||||||
'arguments'
|
delta_arguments
|
||||||
] += delta_arguments
|
)
|
||||||
|
|
||||||
# Emit pending tool calls in real-time
|
# Emit pending tool calls in real-time
|
||||||
if response_tool_calls:
|
if response_tool_calls:
|
||||||
|
|||||||
Generated
+11
-11
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.9.2",
|
"version": "0.9.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.9.2",
|
"version": "0.9.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-browser": "^4.5.0",
|
"@azure/msal-browser": "^4.5.0",
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
@@ -3582,9 +3582,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
"version": "2.58.0",
|
"version": "2.59.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.58.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.59.1.tgz",
|
||||||
"integrity": "sha512-kT9GCN8yJTkCK1W+Gi/bvGooWAM7y7WXP+yd+rf6QOIjyoK1ERPrMwSufXJUNu2pMWIqruhFvmz+LbOqsEmKmA==",
|
"integrity": "sha512-d8OON70AphLdDesuTIl//M2O6fRTIicX8aYv8vhCiYEhTTI2OboKqey0Hu1A4VFhqwgqtq0vKDmPFGkw8kKmgw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
@@ -10989,9 +10989,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mermaid/node_modules/uuid": {
|
"node_modules/mermaid/node_modules/uuid": {
|
||||||
"version": "11.1.0",
|
"version": "11.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
|
||||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
"integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
"https://github.com/sponsors/broofa",
|
"https://github.com/sponsors/broofa",
|
||||||
"https://github.com/sponsors/ctavan"
|
"https://github.com/sponsors/ctavan"
|
||||||
@@ -11847,9 +11847,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.8",
|
"version": "8.5.14",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||||
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
|
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.9.2",
|
"version": "0.9.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run pyodide:fetch && vite dev --host",
|
"dev": "npm run pyodide:fetch && vite dev --host",
|
||||||
|
|||||||
@@ -7,7 +7,14 @@ const TOOL_SERVER_FETCH_TIMEOUT = 10000;
|
|||||||
// Valid HTTP methods per OpenAPI 3.x – used to skip extension keys (x-*)
|
// Valid HTTP methods per OpenAPI 3.x – used to skip extension keys (x-*)
|
||||||
// and non-operation path-item fields (summary, description, servers, parameters).
|
// and non-operation path-item fields (summary, description, servers, parameters).
|
||||||
const OPENAPI_HTTP_METHODS = new Set([
|
const OPENAPI_HTTP_METHODS = new Set([
|
||||||
'get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'
|
'get',
|
||||||
|
'put',
|
||||||
|
'post',
|
||||||
|
'delete',
|
||||||
|
'options',
|
||||||
|
'head',
|
||||||
|
'patch',
|
||||||
|
'trace'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Every request sent from here is a petition. May it reach
|
// Every request sent from here is a petition. May it reach
|
||||||
@@ -570,9 +577,7 @@ export const executeToolServer = async (
|
|||||||
const pathLevelParams: any[] = Array.isArray((methods as any).parameters)
|
const pathLevelParams: any[] = Array.isArray((methods as any).parameters)
|
||||||
? (methods as any).parameters
|
? (methods as any).parameters
|
||||||
: [];
|
: [];
|
||||||
const opParams: any[] = Array.isArray(operation.parameters)
|
const opParams: any[] = Array.isArray(operation.parameters) ? operation.parameters : [];
|
||||||
? operation.parameters
|
|
||||||
: [];
|
|
||||||
const mergedParams = new Map();
|
const mergedParams = new Map();
|
||||||
for (const param of pathLevelParams) {
|
for (const param of pathLevelParams) {
|
||||||
if (param?.name) mergedParams.set(`${param.name}:${param.in ?? ''}`, param);
|
if (param?.name) mergedParams.set(`${param.name}:${param.in ?? ''}`, param);
|
||||||
|
|||||||
@@ -87,10 +87,17 @@
|
|||||||
// client_id is the tool server ID (used as the internal lookup key for both flows).
|
// client_id is the tool server ID (used as the internal lookup key for both flows).
|
||||||
// For static, client_secret signals the backend to use the static credential path.
|
// For static, client_secret signals the backend to use the static credential path.
|
||||||
// The actual OAuth client_id/secret come from the connection info at save time.
|
// The actual OAuth client_id/secret come from the connection info at save time.
|
||||||
const formData: { url: string; client_id: string; client_secret?: string; oauth_server_url?: string } = {
|
const formData: {
|
||||||
|
url: string;
|
||||||
|
client_id: string;
|
||||||
|
client_secret?: string;
|
||||||
|
oauth_server_url?: string;
|
||||||
|
} = {
|
||||||
url: url,
|
url: url,
|
||||||
client_id: id,
|
client_id: id,
|
||||||
...(auth_type === 'oauth_2.1_static' ? { client_secret: oauthClientSecret, oauth_server_url: oauthServerUrl } : {})
|
...(auth_type === 'oauth_2.1_static'
|
||||||
|
? { client_secret: oauthClientSecret, oauth_server_url: oauthServerUrl }
|
||||||
|
: {})
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await registerOAuthClient(localStorage.token, formData, 'mcp').catch((err) => {
|
const res = await registerOAuthClient(localStorage.token, formData, 'mcp').catch((err) => {
|
||||||
@@ -337,7 +344,11 @@
|
|||||||
description: description,
|
description: description,
|
||||||
...(oauthClientInfo ? { oauth_client_info: oauthClientInfo } : {}),
|
...(oauthClientInfo ? { oauth_client_info: oauthClientInfo } : {}),
|
||||||
...(auth_type === 'oauth_2.1_static'
|
...(auth_type === 'oauth_2.1_static'
|
||||||
? { oauth_client_id: oauthClientId, oauth_client_secret: oauthClientSecret, oauth_server_url: oauthServerUrl }
|
? {
|
||||||
|
oauth_client_id: oauthClientId,
|
||||||
|
oauth_client_secret: oauthClientSecret,
|
||||||
|
oauth_server_url: oauthServerUrl
|
||||||
|
}
|
||||||
: {})
|
: {})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
import AddToolServerModal from '$lib/components/AddToolServerModal.svelte';
|
import AddToolServerModal from '$lib/components/AddToolServerModal.svelte';
|
||||||
import AddTerminalServerModal from '$lib/components/AddTerminalServerModal.svelte';
|
import AddTerminalServerModal from '$lib/components/AddTerminalServerModal.svelte';
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getToolServerConnections,
|
getToolServerConnections,
|
||||||
setToolServerConnections,
|
setToolServerConnections,
|
||||||
@@ -41,7 +40,6 @@
|
|||||||
let showAddTerminalModal = false;
|
let showAddTerminalModal = false;
|
||||||
let editTerminalIdx: number | null = null;
|
let editTerminalIdx: number | null = null;
|
||||||
|
|
||||||
|
|
||||||
const addConnectionHandler = async (server) => {
|
const addConnectionHandler = async (server) => {
|
||||||
servers = [...servers, server];
|
servers = [...servers, server];
|
||||||
await updateHandler();
|
await updateHandler();
|
||||||
|
|||||||
@@ -359,39 +359,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'brave_llm_context'}
|
{:else if webConfig.WEB_SEARCH_ENGINE === 'brave_llm_context'}
|
||||||
<div class="mb-2.5 flex w-full flex-col">
|
<div class="mb-2.5 flex w-full flex-col">
|
||||||
<div>
|
<div>
|
||||||
<div class=" self-center text-xs font-medium mb-1">
|
<div class=" self-center text-xs font-medium mb-1">
|
||||||
{$i18n.t('Brave Search API Key')}
|
{$i18n.t('Brave Search API Key')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SensitiveInput
|
<SensitiveInput
|
||||||
placeholder={$i18n.t('Enter Brave Search API Key')}
|
placeholder={$i18n.t('Enter Brave Search API Key')}
|
||||||
bind:value={webConfig.BRAVE_SEARCH_API_KEY}
|
bind:value={webConfig.BRAVE_SEARCH_API_KEY}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div class="mt-1.5">
|
|
||||||
<div class=" self-center text-xs font-medium mb-1">
|
|
||||||
{$i18n.t('Context Tokens')}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-1.5">
|
||||||
|
<div class=" self-center text-xs font-medium mb-1">
|
||||||
|
{$i18n.t('Context Tokens')}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||||
type="number"
|
type="number"
|
||||||
min="1024"
|
min="1024"
|
||||||
max="32768"
|
max="32768"
|
||||||
step="1024"
|
step="1024"
|
||||||
placeholder={$i18n.t('Max tokens to retrieve (1024-32768, default 8192)')}
|
placeholder={$i18n.t('Max tokens to retrieve (1024-32768, default 8192)')}
|
||||||
bind:value={webConfig.BRAVE_SEARCH_CONTEXT_TOKENS}
|
bind:value={webConfig.BRAVE_SEARCH_CONTEXT_TOKENS}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{:else if webConfig.WEB_SEARCH_ENGINE === 'kagi'}
|
||||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'kagi'}
|
|
||||||
<div class="mb-2.5 flex w-full flex-col">
|
<div class="mb-2.5 flex w-full flex-col">
|
||||||
<div>
|
<div>
|
||||||
<div class=" self-center text-xs font-medium mb-1">
|
<div class=" self-center text-xs font-medium mb-1">
|
||||||
|
|||||||
@@ -191,11 +191,7 @@
|
|||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
class="size-3 text-gray-400 dark:text-gray-500"
|
class="size-3 text-gray-400 dark:text-gray-500"
|
||||||
><path
|
><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /></svg
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M12 4.5v15m7.5-7.5h-15"
|
|
||||||
/></svg
|
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -107,17 +107,15 @@
|
|||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<label
|
<label
|
||||||
class="size-6 rounded-full overflow-hidden cursor-pointer border-2 transition-all {!PRESET_COLORS.includes(color)
|
class="size-6 rounded-full overflow-hidden cursor-pointer border-2 transition-all {!PRESET_COLORS.includes(
|
||||||
|
color
|
||||||
|
)
|
||||||
? 'border-gray-800 dark:border-white scale-110'
|
? 'border-gray-800 dark:border-white scale-110'
|
||||||
: 'border-transparent hover:scale-110'}"
|
: 'border-transparent hover:scale-110'}"
|
||||||
style="background-color: {color};"
|
style="background-color: {color};"
|
||||||
title={$i18n.t('Custom color')}
|
title={$i18n.t('Custom color')}
|
||||||
>
|
>
|
||||||
<input
|
<input type="color" bind:value={color} class="opacity-0 w-0 h-0 absolute" />
|
||||||
type="color"
|
|
||||||
bind:value={color}
|
|
||||||
class="opacity-0 w-0 h-0 absolute"
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2798,8 +2798,8 @@
|
|||||||
|
|
||||||
const saveControls = async () => {
|
const saveControls = async () => {
|
||||||
if (!$chatId || $temporaryChatEnabled) return;
|
if (!$chatId || $temporaryChatEnabled) return;
|
||||||
await updateChatById(localStorage.token, $chatId, { params, files: chatFiles }).catch(
|
await updateChatById(localStorage.token, $chatId, { params, files: chatFiles }).catch((err) =>
|
||||||
(err) => console.error('[controls autosave]', err)
|
console.error('[controls autosave]', err)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -1412,7 +1412,16 @@ function resolveSchema(schemaRef, components, resolvedSchemas = new Set()) {
|
|||||||
|
|
||||||
// Valid HTTP methods per OpenAPI 3.x – used to skip extension keys (x-*)
|
// Valid HTTP methods per OpenAPI 3.x – used to skip extension keys (x-*)
|
||||||
// and non-operation path-item fields (summary, description, servers, parameters).
|
// and non-operation path-item fields (summary, description, servers, parameters).
|
||||||
const OPENAPI_HTTP_METHODS = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']);
|
const OPENAPI_HTTP_METHODS = new Set([
|
||||||
|
'get',
|
||||||
|
'put',
|
||||||
|
'post',
|
||||||
|
'delete',
|
||||||
|
'options',
|
||||||
|
'head',
|
||||||
|
'patch',
|
||||||
|
'trace'
|
||||||
|
]);
|
||||||
|
|
||||||
// Main conversion function
|
// Main conversion function
|
||||||
export const convertOpenApiToToolPayload = (openApiSpec) => {
|
export const convertOpenApiToToolPayload = (openApiSpec) => {
|
||||||
|
|||||||
@@ -220,10 +220,7 @@
|
|||||||
on:delete={() => refresh()}
|
on:delete={() => refresh()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CreateCalendarModal
|
<CreateCalendarModal bind:show={showCreateCalendarModal} on:created={handleCalendarCreated} />
|
||||||
bind:show={showCreateCalendarModal}
|
|
||||||
on:created={handleCalendarCreated}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-col w-full h-screen max-h-[100dvh] transition-width duration-200 ease-in-out {$showSidebar
|
class="flex flex-col w-full h-screen max-h-[100dvh] transition-width duration-200 ease-in-out {$showSidebar
|
||||||
|
|||||||
Reference in new issue
Block a user