chore: format

This commit is contained in:
Timothy Jaeryang Baek committed 2026-05-09 15:25:27 +09:00
1 parent 46ff3abbb8
commit 7bcc0e2e5c
27 files changed
+136 -140

No files matched your search

+6 -3
View File
@@ -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',
+1 -3
View File
@@ -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
+1
View File
@@ -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:
+7 -7
View File
@@ -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')
+1 -3
View File
@@ -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:
+10 -6
View File
@@ -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)
+1 -5
View File
@@ -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:
+2 -3
View File
@@ -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
+5 -1
View File
@@ -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}')
+1 -3
View File
@@ -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:
-2
View File
@@ -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,
+1
View File
@@ -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 {}
+4 -4
View File
@@ -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:
+11 -11
View File
@@ -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
View File
@@ -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",
+9 -4
View File
@@ -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);
+14 -3
View File
@@ -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>
+2 -2
View File
@@ -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
View File
@@ -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) => {
+1 -4
View File
@@ -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