chore: format
This commit is contained in:
@@ -1524,9 +1524,7 @@ USER_PERMISSIONS_FEATURES_AUTOMATIONS = (
|
||||
os.environ.get('USER_PERMISSIONS_FEATURES_AUTOMATIONS', 'False').lower() == 'true'
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_FEATURES_CALENDAR = (
|
||||
os.environ.get('USER_PERMISSIONS_FEATURES_CALENDAR', 'True').lower() == 'true'
|
||||
)
|
||||
USER_PERMISSIONS_FEATURES_CALENDAR = os.environ.get('USER_PERMISSIONS_FEATURES_CALENDAR', 'True').lower() == 'true'
|
||||
|
||||
|
||||
USER_PERMISSIONS_SETTINGS_INTERFACE = os.environ.get('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true'
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: c1d2e3f4a5b6
|
||||
Create Date: 2026-04-19 16:20:58.162045
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
@@ -19,52 +20,55 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table('calendar',
|
||||
sa.Column('id', sa.Text(), nullable=False),
|
||||
sa.Column('user_id', sa.Text(), nullable=False),
|
||||
sa.Column('name', sa.Text(), nullable=False),
|
||||
sa.Column('color', sa.Text(), nullable=True),
|
||||
sa.Column('is_default', sa.Boolean(), nullable=False),
|
||||
sa.Column('data', sa.JSON(), nullable=True),
|
||||
sa.Column('meta', sa.JSON(), nullable=True),
|
||||
sa.Column('created_at', sa.BigInteger(), nullable=False),
|
||||
sa.Column('updated_at', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_table(
|
||||
'calendar',
|
||||
sa.Column('id', sa.Text(), nullable=False),
|
||||
sa.Column('user_id', sa.Text(), nullable=False),
|
||||
sa.Column('name', sa.Text(), nullable=False),
|
||||
sa.Column('color', sa.Text(), nullable=True),
|
||||
sa.Column('is_default', sa.Boolean(), nullable=False),
|
||||
sa.Column('data', sa.JSON(), nullable=True),
|
||||
sa.Column('meta', sa.JSON(), nullable=True),
|
||||
sa.Column('created_at', sa.BigInteger(), nullable=False),
|
||||
sa.Column('updated_at', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index('ix_calendar_user', 'calendar', ['user_id'], unique=False)
|
||||
|
||||
op.create_table('calendar_event',
|
||||
sa.Column('id', sa.Text(), nullable=False),
|
||||
sa.Column('calendar_id', sa.Text(), nullable=False),
|
||||
sa.Column('user_id', sa.Text(), nullable=False),
|
||||
sa.Column('title', sa.Text(), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('start_at', sa.BigInteger(), nullable=False),
|
||||
sa.Column('end_at', sa.BigInteger(), nullable=True),
|
||||
sa.Column('all_day', sa.Boolean(), nullable=False),
|
||||
sa.Column('rrule', sa.Text(), nullable=True),
|
||||
sa.Column('color', sa.Text(), nullable=True),
|
||||
sa.Column('location', sa.Text(), nullable=True),
|
||||
sa.Column('data', sa.JSON(), nullable=True),
|
||||
sa.Column('meta', sa.JSON(), nullable=True),
|
||||
sa.Column('is_cancelled', sa.Boolean(), nullable=False),
|
||||
sa.Column('created_at', sa.BigInteger(), nullable=False),
|
||||
sa.Column('updated_at', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
op.create_table(
|
||||
'calendar_event',
|
||||
sa.Column('id', sa.Text(), nullable=False),
|
||||
sa.Column('calendar_id', sa.Text(), nullable=False),
|
||||
sa.Column('user_id', sa.Text(), nullable=False),
|
||||
sa.Column('title', sa.Text(), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('start_at', sa.BigInteger(), nullable=False),
|
||||
sa.Column('end_at', sa.BigInteger(), nullable=True),
|
||||
sa.Column('all_day', sa.Boolean(), nullable=False),
|
||||
sa.Column('rrule', sa.Text(), nullable=True),
|
||||
sa.Column('color', sa.Text(), nullable=True),
|
||||
sa.Column('location', sa.Text(), nullable=True),
|
||||
sa.Column('data', sa.JSON(), nullable=True),
|
||||
sa.Column('meta', sa.JSON(), nullable=True),
|
||||
sa.Column('is_cancelled', sa.Boolean(), nullable=False),
|
||||
sa.Column('created_at', sa.BigInteger(), nullable=False),
|
||||
sa.Column('updated_at', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index('ix_calendar_event_calendar', 'calendar_event', ['calendar_id', 'start_at'], unique=False)
|
||||
op.create_index('ix_calendar_event_user_date', 'calendar_event', ['user_id', 'start_at'], unique=False)
|
||||
|
||||
op.create_table('calendar_event_attendee',
|
||||
sa.Column('id', sa.Text(), nullable=False),
|
||||
sa.Column('event_id', sa.Text(), nullable=False),
|
||||
sa.Column('user_id', sa.Text(), nullable=False),
|
||||
sa.Column('status', sa.Text(), nullable=False),
|
||||
sa.Column('meta', sa.JSON(), nullable=True),
|
||||
sa.Column('created_at', sa.BigInteger(), nullable=False),
|
||||
sa.Column('updated_at', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('event_id', 'user_id', name='uq_event_attendee')
|
||||
op.create_table(
|
||||
'calendar_event_attendee',
|
||||
sa.Column('id', sa.Text(), nullable=False),
|
||||
sa.Column('event_id', sa.Text(), nullable=False),
|
||||
sa.Column('user_id', sa.Text(), nullable=False),
|
||||
sa.Column('status', sa.Text(), nullable=False),
|
||||
sa.Column('meta', sa.JSON(), nullable=True),
|
||||
sa.Column('created_at', sa.BigInteger(), nullable=False),
|
||||
sa.Column('updated_at', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('event_id', 'user_id', name='uq_event_attendee'),
|
||||
)
|
||||
op.create_index('ix_calendar_event_attendee_user', 'calendar_event_attendee', ['user_id', 'status'], unique=False)
|
||||
|
||||
|
||||
@@ -153,15 +153,11 @@ class AutomationTable:
|
||||
row = await db.get(Automation, id)
|
||||
return AutomationModel.model_validate(row) if row else None
|
||||
|
||||
async def get_active_by_user(
|
||||
self, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> list[AutomationModel]:
|
||||
async def get_active_by_user(self, user_id: str, db: Optional[AsyncSession] = None) -> list[AutomationModel]:
|
||||
"""Get active automations for a user (for calendar RRULE expansion)."""
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(Automation)
|
||||
.filter_by(user_id=user_id, is_active=True)
|
||||
.order_by(Automation.created_at.desc())
|
||||
select(Automation).filter_by(user_id=user_id, is_active=True).order_by(Automation.created_at.desc())
|
||||
)
|
||||
return [AutomationModel.model_validate(r) for r in result.scalars().all()]
|
||||
|
||||
@@ -291,9 +287,8 @@ class AutomationTable:
|
||||
timezone_by_user_id: dict[str, Optional[str]] = {}
|
||||
if user_ids:
|
||||
from open_webui.models.users import User
|
||||
tz_result = await db.execute(
|
||||
select(User.id, User.timezone).where(User.id.in_(user_ids))
|
||||
)
|
||||
|
||||
tz_result = await db.execute(select(User.id, User.timezone).where(User.id.in_(user_ids)))
|
||||
timezone_by_user_id = {uid: tz for uid, tz in tz_result.all()}
|
||||
|
||||
for row in rows:
|
||||
|
||||
@@ -232,9 +232,7 @@ class CalendarEventListResponse(BaseModel):
|
||||
|
||||
|
||||
class CalendarTable:
|
||||
async def _get_access_grants(
|
||||
self, calendar_id: str, db: Optional[AsyncSession] = None
|
||||
) -> list[AccessGrantModel]:
|
||||
async def _get_access_grants(self, calendar_id: str, db: Optional[AsyncSession] = None) -> list[AccessGrantModel]:
|
||||
return await AccessGrants.get_grants_by_resource('calendar', calendar_id, db=db)
|
||||
|
||||
async def _to_calendar_model(
|
||||
@@ -245,15 +243,11 @@ class CalendarTable:
|
||||
) -> CalendarModel:
|
||||
cal_data = CalendarModel.model_validate(cal).model_dump(exclude={'access_grants'})
|
||||
cal_data['access_grants'] = (
|
||||
access_grants
|
||||
if access_grants is not None
|
||||
else await self._get_access_grants(cal_data['id'], db=db)
|
||||
access_grants if access_grants is not None else await self._get_access_grants(cal_data['id'], db=db)
|
||||
)
|
||||
return CalendarModel.model_validate(cal_data)
|
||||
|
||||
async def get_or_create_defaults(
|
||||
self, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> list[CalendarModel]:
|
||||
async def get_or_create_defaults(self, user_id: str, db: Optional[AsyncSession] = None) -> list[CalendarModel]:
|
||||
"""Return user's calendars, creating 'Personal' and 'Scheduled Tasks' if none exist."""
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
@@ -289,9 +283,7 @@ class CalendarTable:
|
||||
await db.commit()
|
||||
return [CalendarModel.model_validate(c) for c in defaults]
|
||||
|
||||
async def get_calendars_by_user(
|
||||
self, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> list[CalendarModel]:
|
||||
async def get_calendars_by_user(self, user_id: str, db: Optional[AsyncSession] = None) -> list[CalendarModel]:
|
||||
"""Owned + shared calendars."""
|
||||
async with get_async_db_context(db) as db:
|
||||
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||
@@ -317,14 +309,9 @@ class CalendarTable:
|
||||
cal_ids = [c.id for c in calendars]
|
||||
grants_map = await AccessGrants.get_grants_by_resources('calendar', cal_ids, db=db)
|
||||
|
||||
return [
|
||||
await self._to_calendar_model(c, access_grants=grants_map.get(c.id, []), db=db)
|
||||
for c in calendars
|
||||
]
|
||||
return [await self._to_calendar_model(c, access_grants=grants_map.get(c.id, []), db=db) for c in calendars]
|
||||
|
||||
async def get_calendar_by_id(
|
||||
self, id: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[CalendarModel]:
|
||||
async def get_calendar_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[CalendarModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(Calendar).filter(Calendar.id == id))
|
||||
cal = result.scalars().first()
|
||||
@@ -414,9 +401,7 @@ class CalendarTable:
|
||||
.values(is_default=False)
|
||||
)
|
||||
# Set the new default
|
||||
result = await db.execute(
|
||||
select(Calendar).filter(Calendar.id == calendar_id, Calendar.user_id == user_id)
|
||||
)
|
||||
result = await db.execute(select(Calendar).filter(Calendar.id == calendar_id, Calendar.user_id == user_id))
|
||||
cal = result.scalars().first()
|
||||
if not cal:
|
||||
return None
|
||||
@@ -435,15 +420,11 @@ class CalendarTable:
|
||||
return False
|
||||
|
||||
# Delete attendees for all events in this calendar
|
||||
event_ids_result = await db.execute(
|
||||
select(CalendarEvent.id).filter(CalendarEvent.calendar_id == id)
|
||||
)
|
||||
event_ids_result = await db.execute(select(CalendarEvent.id).filter(CalendarEvent.calendar_id == id))
|
||||
event_ids = [r[0] for r in event_ids_result.all()]
|
||||
if event_ids:
|
||||
await db.execute(
|
||||
delete(CalendarEventAttendee).filter(
|
||||
CalendarEventAttendee.event_id.in_(event_ids)
|
||||
)
|
||||
delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id.in_(event_ids))
|
||||
)
|
||||
|
||||
# Delete events
|
||||
@@ -465,9 +446,7 @@ class CalendarEventTable:
|
||||
self, event_id: str, db: Optional[AsyncSession] = None
|
||||
) -> list[CalendarEventAttendeeModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id)
|
||||
)
|
||||
result = await db.execute(select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id))
|
||||
rows = result.scalars().all()
|
||||
return [CalendarEventAttendeeModel.model_validate(r) for r in rows]
|
||||
|
||||
@@ -515,9 +494,7 @@ class CalendarEventTable:
|
||||
|
||||
return await self._to_event_model(event, db=db)
|
||||
|
||||
async def get_event_by_id(
|
||||
self, id: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[CalendarEventModel]:
|
||||
async def get_event_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[CalendarEventModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(CalendarEvent).filter(CalendarEvent.id == id))
|
||||
event = result.scalars().first()
|
||||
@@ -559,9 +536,7 @@ class CalendarEventTable:
|
||||
|
||||
# Also get event IDs where user is an attendee
|
||||
attendee_event_ids_result = await db.execute(
|
||||
select(CalendarEventAttendee.event_id).filter(
|
||||
CalendarEventAttendee.user_id == user_id
|
||||
)
|
||||
select(CalendarEventAttendee.event_id).filter(CalendarEventAttendee.user_id == user_id)
|
||||
)
|
||||
attendee_event_ids = [r[0] for r in attendee_event_ids_result.all()]
|
||||
|
||||
@@ -608,16 +583,12 @@ class CalendarEventTable:
|
||||
# Batch-load attendees for all events in one query (avoid N+1)
|
||||
event_ids = [event.id for event, _user in items]
|
||||
att_result = await db.execute(
|
||||
select(CalendarEventAttendee).filter(
|
||||
CalendarEventAttendee.event_id.in_(event_ids)
|
||||
)
|
||||
select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id.in_(event_ids))
|
||||
)
|
||||
att_rows = att_result.scalars().all()
|
||||
att_map: dict[str, list[CalendarEventAttendeeModel]] = {}
|
||||
for a in att_rows:
|
||||
att_map.setdefault(a.event_id, []).append(
|
||||
CalendarEventAttendeeModel.model_validate(a)
|
||||
)
|
||||
att_map.setdefault(a.event_id, []).append(CalendarEventAttendeeModel.model_validate(a))
|
||||
|
||||
events = []
|
||||
for event, user in items:
|
||||
@@ -697,16 +668,12 @@ class CalendarEventTable:
|
||||
# Batch-load attendees
|
||||
event_ids = [event.id for event, _user in items]
|
||||
att_result = await db.execute(
|
||||
select(CalendarEventAttendee).filter(
|
||||
CalendarEventAttendee.event_id.in_(event_ids)
|
||||
)
|
||||
select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id.in_(event_ids))
|
||||
)
|
||||
att_rows = att_result.scalars().all()
|
||||
att_map: dict[str, list[CalendarEventAttendeeModel]] = {}
|
||||
for a in att_rows:
|
||||
att_map.setdefault(a.event_id, []).append(
|
||||
CalendarEventAttendeeModel.model_validate(a)
|
||||
)
|
||||
att_map.setdefault(a.event_id, []).append(CalendarEventAttendeeModel.model_validate(a))
|
||||
|
||||
events = []
|
||||
for event, user in items:
|
||||
@@ -732,8 +699,16 @@ class CalendarEventTable:
|
||||
|
||||
update_data = form_data.model_dump(exclude_unset=True)
|
||||
for field in [
|
||||
'calendar_id', 'title', 'description', 'start_at', 'end_at',
|
||||
'all_day', 'rrule', 'color', 'location', 'is_cancelled',
|
||||
'calendar_id',
|
||||
'title',
|
||||
'description',
|
||||
'start_at',
|
||||
'end_at',
|
||||
'all_day',
|
||||
'rrule',
|
||||
'color',
|
||||
'location',
|
||||
'is_cancelled',
|
||||
]:
|
||||
if field in update_data:
|
||||
setattr(event, field, update_data[field])
|
||||
@@ -750,13 +725,10 @@ class CalendarEventTable:
|
||||
await db.commit()
|
||||
return await self._to_event_model(event, db=db)
|
||||
|
||||
|
||||
async def delete_event_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
try:
|
||||
async with get_async_db_context(db) as db:
|
||||
await db.execute(
|
||||
delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == id)
|
||||
)
|
||||
await db.execute(delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == id))
|
||||
await db.execute(delete(CalendarEvent).filter(CalendarEvent.id == id))
|
||||
await db.commit()
|
||||
return True
|
||||
@@ -774,9 +746,7 @@ class CalendarEventAttendeeTable:
|
||||
"""
|
||||
async with get_async_db_context(db) as db:
|
||||
# Remove existing
|
||||
await db.execute(
|
||||
delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id)
|
||||
)
|
||||
await db.execute(delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id))
|
||||
|
||||
now = int(time.time_ns())
|
||||
models = []
|
||||
@@ -819,20 +789,14 @@ class CalendarEventAttendeeTable:
|
||||
self, event_id: str, db: Optional[AsyncSession] = None
|
||||
) -> list[CalendarEventAttendeeModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id)
|
||||
)
|
||||
result = await db.execute(select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id))
|
||||
return [CalendarEventAttendeeModel.model_validate(r) for r in result.scalars().all()]
|
||||
|
||||
async def get_events_by_attendee(
|
||||
self, user_id: str, db: Optional[AsyncSession] = None
|
||||
) -> list[str]:
|
||||
async def get_events_by_attendee(self, user_id: str, db: Optional[AsyncSession] = None) -> list[str]:
|
||||
"""Return event IDs where user is an attendee."""
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(CalendarEventAttendee.event_id).filter(
|
||||
CalendarEventAttendee.user_id == user_id
|
||||
)
|
||||
select(CalendarEventAttendee.event_id).filter(CalendarEventAttendee.user_id == user_id)
|
||||
)
|
||||
return [r[0] for r in result.all()]
|
||||
|
||||
|
||||
@@ -47,9 +47,7 @@ async def check_calendar_permission(request: Request, user):
|
||||
)
|
||||
|
||||
|
||||
async def _check_calendar_access(
|
||||
calendar_id: str, user: UserModel, permission: str = 'write'
|
||||
) -> CalendarModel:
|
||||
async def _check_calendar_access(calendar_id: str, user: UserModel, permission: str = 'write') -> CalendarModel:
|
||||
"""Verify user has access to a calendar. Returns the calendar or raises 403/404."""
|
||||
cal = await Calendars.get_calendar_by_id(calendar_id)
|
||||
if not cal:
|
||||
@@ -139,9 +137,7 @@ async def get_events(
|
||||
for event in events:
|
||||
event_dict = event.model_dump()
|
||||
if event_dict.get('rrule'):
|
||||
instances = expand_recurring_event(
|
||||
event_dict, start_ns, end_ns, tz=user.timezone
|
||||
)
|
||||
instances = expand_recurring_event(event_dict, start_ns, end_ns, tz=user.timezone)
|
||||
for inst in instances:
|
||||
expanded.append(CalendarEventUserResponse(**{**inst, 'user': event.user}))
|
||||
else:
|
||||
@@ -189,34 +185,34 @@ async def get_events(
|
||||
expanded.append(CalendarEventUserResponse(**inst))
|
||||
|
||||
# Past runs: single range query joined with automation
|
||||
runs_with_auto = await AutomationRuns.get_runs_by_user_range(
|
||||
user.id, start_ns, end_ns
|
||||
)
|
||||
runs_with_auto = await AutomationRuns.get_runs_by_user_range(user.id, start_ns, end_ns)
|
||||
for run, auto in runs_with_auto:
|
||||
expanded.append(CalendarEventUserResponse(
|
||||
id=f'run_{run.id}',
|
||||
calendar_id=scheduled_cal.id,
|
||||
user_id=user.id,
|
||||
title=auto.name,
|
||||
description=run.error if run.status == 'error' else '',
|
||||
start_at=run.created_at,
|
||||
end_at=None,
|
||||
all_day=False,
|
||||
color=None,
|
||||
location=None,
|
||||
data=None,
|
||||
meta={
|
||||
'automation_id': auto.id,
|
||||
'run_id': run.id,
|
||||
'chat_id': run.chat_id,
|
||||
'status': run.status,
|
||||
},
|
||||
is_cancelled=False,
|
||||
attendees=[],
|
||||
created_at=run.created_at,
|
||||
updated_at=run.created_at,
|
||||
user=None,
|
||||
))
|
||||
expanded.append(
|
||||
CalendarEventUserResponse(
|
||||
id=f'run_{run.id}',
|
||||
calendar_id=scheduled_cal.id,
|
||||
user_id=user.id,
|
||||
title=auto.name,
|
||||
description=run.error if run.status == 'error' else '',
|
||||
start_at=run.created_at,
|
||||
end_at=None,
|
||||
all_day=False,
|
||||
color=None,
|
||||
location=None,
|
||||
data=None,
|
||||
meta={
|
||||
'automation_id': auto.id,
|
||||
'run_id': run.id,
|
||||
'chat_id': run.chat_id,
|
||||
'status': run.status,
|
||||
},
|
||||
is_cancelled=False,
|
||||
attendees=[],
|
||||
created_at=run.created_at,
|
||||
updated_at=run.created_at,
|
||||
user=None,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(f'Failed to compute automation events: {e}', exc_info=True)
|
||||
|
||||
@@ -239,9 +235,7 @@ async def search_events(
|
||||
user: UserModel = Depends(get_verified_user),
|
||||
):
|
||||
await check_calendar_permission(request, user)
|
||||
return await CalendarEvents.search_events(
|
||||
user_id=user.id, query=query, skip=skip, limit=limit
|
||||
)
|
||||
return await CalendarEvents.search_events(user_id=user.id, query=query, skip=skip, limit=limit)
|
||||
|
||||
|
||||
@router.get('/events/{event_id}', response_model=CalendarEventModel)
|
||||
@@ -341,7 +335,6 @@ async def delete_calendar(request: Request, calendar_id: str, user: UserModel =
|
||||
if cal.user_id != user.id and user.role != 'admin':
|
||||
raise HTTPException(status_code=403, detail='Only owner can delete calendar')
|
||||
|
||||
|
||||
result = await Calendars.delete_calendar_by_id(calendar_id)
|
||||
if not result:
|
||||
raise HTTPException(status_code=500, detail='Failed to delete')
|
||||
|
||||
@@ -539,9 +539,7 @@ async def update_knowledge_access_by_id(
|
||||
'sharing.public_knowledge',
|
||||
)
|
||||
|
||||
knowledge.access_grants = await AccessGrants.set_access_grants(
|
||||
'knowledge', id, form_data.access_grants, db=db
|
||||
)
|
||||
knowledge.access_grants = await AccessGrants.set_access_grants('knowledge', id, form_data.access_grants, db=db)
|
||||
|
||||
return KnowledgeFilesResponse(
|
||||
**knowledge.model_dump(),
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 21 KiB |
@@ -2868,9 +2868,6 @@ def _event_to_dict(event, tz) -> dict:
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async def search_calendar_events(
|
||||
query: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
@@ -2915,7 +2912,11 @@ async def search_calendar_events(
|
||||
return json.dumps({'error': f'Invalid start datetime: {e}'})
|
||||
|
||||
try:
|
||||
end_ns = _dt_to_ns(end, tz) if end else int(time.time() * 1_000) * 1_000_000 + 365 * 86400 * 1_000_000_000_000
|
||||
end_ns = (
|
||||
_dt_to_ns(end, tz)
|
||||
if end
|
||||
else int(time.time() * 1_000) * 1_000_000 + 365 * 86400 * 1_000_000_000_000
|
||||
)
|
||||
except (ValueError, TypeError) as e:
|
||||
return json.dumps({'error': f'Invalid end datetime: {e}'})
|
||||
|
||||
@@ -2929,7 +2930,8 @@ async def search_calendar_events(
|
||||
if query:
|
||||
q = query.lower()
|
||||
items = [
|
||||
e for e in items
|
||||
e
|
||||
for e in items
|
||||
if q in (e.title or '').lower()
|
||||
or q in (e.description or '').lower()
|
||||
or q in (e.location or '').lower()
|
||||
@@ -3229,4 +3231,3 @@ async def delete_calendar_event(
|
||||
except Exception as e:
|
||||
log.exception(f'delete_calendar_event error: {e}')
|
||||
return json.dumps({'error': str(e)})
|
||||
|
||||
|
||||
@@ -427,7 +427,11 @@ def _render_openai_tool_call_handler(item: dict, done: bool) -> str:
|
||||
if atype == 'search':
|
||||
queries = action.get('queries') or []
|
||||
query = action.get('query', '')
|
||||
summary = f'Search: {", ".join(str(q) for q in queries)}' if queries else (f'Search: {query}' if query else '')
|
||||
summary = (
|
||||
f'Search: {", ".join(str(q) for q in queries)}'
|
||||
if queries
|
||||
else (f'Search: {query}' if query else '')
|
||||
)
|
||||
elif atype == 'open_page':
|
||||
summary = f'Open page: {action.get("url", "")}' if action.get('url') else ''
|
||||
elif atype == 'find_in_page':
|
||||
@@ -490,9 +494,13 @@ def serialize_output(output: list) -> str:
|
||||
files = result_item.get('files')
|
||||
embeds = result_item.get('embeds', '')
|
||||
|
||||
parts.append(f'<details type="tool_calls" done="true" id="{call_id}" name="{name}" arguments="{html.escape(json.dumps(arguments))}" files="{html.escape(json.dumps(files)) if files else ""}" embeds="{html.escape(json.dumps(embeds))}">\n<summary>Tool Executed</summary>\n{html.escape(json.dumps(result_text, ensure_ascii=False))}\n</details>')
|
||||
parts.append(
|
||||
f'<details type="tool_calls" done="true" id="{call_id}" name="{name}" arguments="{html.escape(json.dumps(arguments))}" files="{html.escape(json.dumps(files)) if files else ""}" embeds="{html.escape(json.dumps(embeds))}">\n<summary>Tool Executed</summary>\n{html.escape(json.dumps(result_text, ensure_ascii=False))}\n</details>'
|
||||
)
|
||||
else:
|
||||
parts.append(f'<details type="tool_calls" done="false" id="{call_id}" name="{name}" arguments="{html.escape(json.dumps(arguments))}">\n<summary>Executing...</summary>\n</details>')
|
||||
parts.append(
|
||||
f'<details type="tool_calls" done="false" id="{call_id}" name="{name}" arguments="{html.escape(json.dumps(arguments))}">\n<summary>Executing...</summary>\n</details>'
|
||||
)
|
||||
|
||||
elif item_type == 'function_call_output':
|
||||
# Already handled inline with function_call above
|
||||
@@ -529,9 +537,13 @@ def serialize_output(output: list) -> str:
|
||||
)
|
||||
|
||||
if status == 'completed' or duration is not None or not is_last_item:
|
||||
parts.append(f'<details type="reasoning" done="true" duration="{duration or 0}">\n<summary>Thought for {duration or 0} seconds</summary>\n{display}\n</details>')
|
||||
parts.append(
|
||||
f'<details type="reasoning" done="true" duration="{duration or 0}">\n<summary>Thought for {duration or 0} seconds</summary>\n{display}\n</details>'
|
||||
)
|
||||
else:
|
||||
parts.append(f'<details type="reasoning" done="false">\n<summary>Thinking…</summary>\n{display}\n</details>')
|
||||
parts.append(
|
||||
f'<details type="reasoning" done="false">\n<summary>Thinking…</summary>\n{display}\n</details>'
|
||||
)
|
||||
|
||||
elif item_type == 'open_webui:code_interpreter':
|
||||
# Code interpreter needs to inspect/mutate prior accumulated content
|
||||
@@ -570,9 +582,13 @@ def serialize_output(output: list) -> str:
|
||||
output_attr = f' output="{html.escape(output_json)}"'
|
||||
|
||||
if status == 'completed' or duration is not None or not is_last_item:
|
||||
parts.append(f'<details type="code_interpreter" done="true" duration="{duration or 0}"{output_attr}>\n<summary>Analyzed</summary>\n{display}\n</details>')
|
||||
parts.append(
|
||||
f'<details type="code_interpreter" done="true" duration="{duration or 0}"{output_attr}>\n<summary>Analyzed</summary>\n{display}\n</details>'
|
||||
)
|
||||
else:
|
||||
parts.append(f'<details type="code_interpreter" done="false"{output_attr}>\n<summary>Analyzing…</summary>\n{display}\n</details>')
|
||||
parts.append(
|
||||
f'<details type="code_interpreter" done="false"{output_attr}>\n<summary>Analyzing…</summary>\n{display}\n</details>'
|
||||
)
|
||||
|
||||
return '\n'.join(parts).strip()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user