mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-16 23:43:03 +00:00
Merge branch 'dev' into feat/rds-iam-support
This commit is contained in:
commit
8fbea2ff6d
124 changed files with 5737 additions and 395 deletions
|
|
@ -1524,6 +1524,8 @@ 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_SETTINGS_INTERFACE = os.environ.get('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true'
|
||||
|
||||
|
|
@ -1594,6 +1596,7 @@ DEFAULT_USER_PERMISSIONS = {
|
|||
'code_interpreter': USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
|
||||
'memories': USER_PERMISSIONS_FEATURES_MEMORIES,
|
||||
'automations': USER_PERMISSIONS_FEATURES_AUTOMATIONS,
|
||||
'calendar': USER_PERMISSIONS_FEATURES_CALENDAR,
|
||||
},
|
||||
'settings': {
|
||||
'interface': USER_PERMISSIONS_SETTINGS_INTERFACE,
|
||||
|
|
@ -1624,6 +1627,18 @@ ENABLE_CHANNELS = PersistentConfig(
|
|||
os.environ.get('ENABLE_CHANNELS', 'False').lower() == 'true',
|
||||
)
|
||||
|
||||
ENABLE_CALENDAR = PersistentConfig(
|
||||
'ENABLE_CALENDAR',
|
||||
'calendar.enable',
|
||||
os.environ.get('ENABLE_CALENDAR', 'True').lower() == 'true',
|
||||
)
|
||||
|
||||
ENABLE_AUTOMATIONS = PersistentConfig(
|
||||
'ENABLE_AUTOMATIONS',
|
||||
'automations.enable',
|
||||
os.environ.get('ENABLE_AUTOMATIONS', 'True').lower() == 'true',
|
||||
)
|
||||
|
||||
AUTOMATION_MAX_COUNT = PersistentConfig(
|
||||
'AUTOMATION_MAX_COUNT',
|
||||
'automations.max_count',
|
||||
|
|
|
|||
|
|
@ -696,6 +696,15 @@ ENABLE_CHAT_RESPONSE_BASE64_IMAGE_URL_CONVERSION = (
|
|||
os.environ.get('ENABLE_CHAT_RESPONSE_BASE64_IMAGE_URL_CONVERSION', 'False').lower() == 'true'
|
||||
)
|
||||
|
||||
# When enabled, uses a hardcoded extension-to-MIME dictionary as a last-resort
|
||||
# fallback when both mimetypes.guess_type() and file.meta.content_type fail to
|
||||
# determine the content type. This can help on minimal container images (e.g.
|
||||
# wolfi-base) that lack /etc/mime.types AND have legacy files without stored
|
||||
# content_type metadata.
|
||||
ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK = (
|
||||
os.environ.get('ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK', 'False').lower() == 'true'
|
||||
)
|
||||
|
||||
CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE = os.environ.get('CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE', '1')
|
||||
|
||||
if CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE == '':
|
||||
|
|
|
|||
|
|
@ -436,9 +436,19 @@ get_db = contextmanager(get_session)
|
|||
ASYNC_SQLALCHEMY_DATABASE_URL = _make_async_url(SQLALCHEMY_DATABASE_URL)
|
||||
|
||||
if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL:
|
||||
# Generous default — async coroutines + no session sharing = high connection demand.
|
||||
_sqlite_pool_size = (
|
||||
DATABASE_POOL_SIZE
|
||||
if isinstance(DATABASE_POOL_SIZE, int) and DATABASE_POOL_SIZE > 0
|
||||
else 512
|
||||
)
|
||||
async_engine = create_async_engine(
|
||||
ASYNC_SQLALCHEMY_DATABASE_URL,
|
||||
connect_args={'check_same_thread': False},
|
||||
pool_size=_sqlite_pool_size,
|
||||
pool_timeout=DATABASE_POOL_TIMEOUT,
|
||||
pool_recycle=DATABASE_POOL_RECYCLE,
|
||||
pool_pre_ping=True,
|
||||
)
|
||||
|
||||
@event.listens_for(async_engine.sync_engine, 'connect')
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ from open_webui.routers import (
|
|||
scim,
|
||||
terminals,
|
||||
automations,
|
||||
calendar,
|
||||
)
|
||||
|
||||
from open_webui.routers.retrieval import (
|
||||
|
|
@ -392,9 +393,11 @@ from open_webui.config import (
|
|||
API_KEYS_ALLOWED_ENDPOINTS,
|
||||
ENABLE_FOLDERS,
|
||||
FOLDER_MAX_FILE_COUNT,
|
||||
ENABLE_AUTOMATIONS,
|
||||
AUTOMATION_MAX_COUNT,
|
||||
AUTOMATION_MIN_INTERVAL,
|
||||
ENABLE_CHANNELS,
|
||||
ENABLE_CALENDAR,
|
||||
ENABLE_NOTES,
|
||||
ENABLE_USER_STATUS,
|
||||
ENABLE_COMMUNITY_SHARING,
|
||||
|
|
@ -671,9 +674,9 @@ async def lifespan(app: FastAPI):
|
|||
asyncio.create_task(periodic_usage_pool_cleanup())
|
||||
asyncio.create_task(periodic_session_pool_cleanup())
|
||||
|
||||
from open_webui.utils.automations import automation_worker_loop
|
||||
from open_webui.utils.automations import scheduler_worker_loop
|
||||
|
||||
asyncio.create_task(automation_worker_loop(app))
|
||||
asyncio.create_task(scheduler_worker_loop(app))
|
||||
|
||||
if app.state.config.ENABLE_BASE_MODELS_CACHE:
|
||||
try:
|
||||
|
|
@ -898,9 +901,11 @@ app.state.config.BANNERS = WEBUI_BANNERS
|
|||
|
||||
app.state.config.ENABLE_FOLDERS = ENABLE_FOLDERS
|
||||
app.state.config.FOLDER_MAX_FILE_COUNT = FOLDER_MAX_FILE_COUNT
|
||||
app.state.config.ENABLE_AUTOMATIONS = ENABLE_AUTOMATIONS
|
||||
app.state.config.AUTOMATION_MAX_COUNT = AUTOMATION_MAX_COUNT
|
||||
app.state.config.AUTOMATION_MIN_INTERVAL = AUTOMATION_MIN_INTERVAL
|
||||
app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS
|
||||
app.state.config.ENABLE_CALENDAR = ENABLE_CALENDAR
|
||||
app.state.config.ENABLE_NOTES = ENABLE_NOTES
|
||||
app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
|
||||
app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING
|
||||
|
|
@ -1433,6 +1438,7 @@ if ENABLE_ADMIN_ANALYTICS:
|
|||
app.include_router(utils.router, prefix='/api/v1/utils', tags=['utils'])
|
||||
app.include_router(terminals.router, prefix='/api/v1/terminals', tags=['terminals'])
|
||||
app.include_router(automations.router, prefix='/api/v1/automations', tags=['automations'])
|
||||
app.include_router(calendar.router, prefix='/api/v1/calendars', tags=['calendars'])
|
||||
|
||||
# SCIM 2.0 API for identity management
|
||||
if ENABLE_SCIM:
|
||||
|
|
@ -1871,7 +1877,8 @@ async def chat_completion(
|
|||
finally:
|
||||
raise # re-raise to ensure proper task cancellation handling
|
||||
except Exception as e:
|
||||
log.error('Error processing chat payload: %s', e)
|
||||
error_detail = e.detail if isinstance(e, HTTPException) else str(e)
|
||||
log.error('Error processing chat payload: %s', error_detail)
|
||||
if metadata.get('chat_id') and metadata.get('message_id'):
|
||||
# Update the chat message with the error
|
||||
try:
|
||||
|
|
@ -1881,7 +1888,7 @@ async def chat_completion(
|
|||
metadata['message_id'],
|
||||
{
|
||||
'parentId': metadata.get('user_message_id', None),
|
||||
'error': {'content': str(e)},
|
||||
'error': {'content': error_detail},
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -1890,7 +1897,7 @@ async def chat_completion(
|
|||
await event_emitter(
|
||||
{
|
||||
'type': 'chat:message:error',
|
||||
'data': {'error': {'content': str(e)}},
|
||||
'data': {'error': {'content': error_detail}},
|
||||
}
|
||||
)
|
||||
await event_emitter(
|
||||
|
|
@ -2216,6 +2223,8 @@ async def get_app_config(request: Request):
|
|||
'enable_folders': app.state.config.ENABLE_FOLDERS,
|
||||
'folder_max_file_count': app.state.config.FOLDER_MAX_FILE_COUNT,
|
||||
'enable_channels': app.state.config.ENABLE_CHANNELS,
|
||||
'enable_calendar': app.state.config.ENABLE_CALENDAR,
|
||||
'enable_automations': app.state.config.ENABLE_AUTOMATIONS,
|
||||
'enable_notes': app.state.config.ENABLE_NOTES,
|
||||
'enable_web_search': app.state.config.ENABLE_WEB_SEARCH,
|
||||
'enable_code_execution': app.state.config.ENABLE_CODE_EXECUTION,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from logging.config import fileConfig
|
|||
from alembic import context
|
||||
from open_webui.models.auths import Auth
|
||||
from open_webui.env import DATABASE_URL, DATABASE_ENABLE_IAM_TOKEN_AUTH, DATABASE_PASSWORD, LOG_FORMAT
|
||||
from open_webui.models.calendar import Calendar, CalendarEvent, CalendarEventAttendee # noqa: F401
|
||||
from sqlalchemy import engine_from_config, pool, create_engine
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
"""add calendar tables
|
||||
|
||||
Revision ID: 56359461a091
|
||||
Revises: c1d2e3f4a5b6
|
||||
Create Date: 2026-04-19 16:20:58.162045
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '56359461a091'
|
||||
down_revision: Union[str, None] = 'c1d2e3f4a5b6'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
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_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_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_index('ix_calendar_event_attendee_user', 'calendar_event_attendee', ['user_id', 'status'], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('ix_calendar_event_attendee_user', table_name='calendar_event_attendee')
|
||||
op.drop_table('calendar_event_attendee')
|
||||
op.drop_index('ix_calendar_event_user_date', table_name='calendar_event')
|
||||
op.drop_index('ix_calendar_event_calendar', table_name='calendar_event')
|
||||
op.drop_table('calendar_event')
|
||||
op.drop_index('ix_calendar_user', table_name='calendar')
|
||||
op.drop_table('calendar')
|
||||
|
|
@ -153,6 +153,14 @@ 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]:
|
||||
"""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())
|
||||
)
|
||||
return [AutomationModel.model_validate(r) for r in result.scalars().all()]
|
||||
|
||||
async def search_automations(
|
||||
self,
|
||||
user_id: str,
|
||||
|
|
@ -273,9 +281,19 @@ class AutomationTable:
|
|||
|
||||
from open_webui.utils.automations import next_run_ns
|
||||
|
||||
# Batch-fetch user timezones so rescheduling respects each
|
||||
# user's local timezone instead of falling back to server time.
|
||||
user_ids = list({row.user_id for row in rows})
|
||||
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)))
|
||||
timezone_by_user_id = {uid: tz for uid, tz in tz_result.all()}
|
||||
|
||||
for row in rows:
|
||||
row.last_run_at = now_ns
|
||||
row.next_run_at = next_run_ns(row.data.get('rrule', ''))
|
||||
row.next_run_at = next_run_ns(row.data.get('rrule', ''), tz=timezone_by_user_id.get(row.user_id))
|
||||
|
||||
await db.commit()
|
||||
|
||||
|
|
@ -372,6 +390,32 @@ class AutomationRunTable:
|
|||
await db.commit()
|
||||
return result.rowcount
|
||||
|
||||
async def get_runs_by_user_range(
|
||||
self,
|
||||
user_id: str,
|
||||
start_ns: int,
|
||||
end_ns: int,
|
||||
limit: int = 500,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> list[tuple['AutomationRunModel', 'AutomationModel']]:
|
||||
"""Get runs within a date range for a user, joined with parent automation."""
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(AutomationRun, Automation)
|
||||
.join(Automation, Automation.id == AutomationRun.automation_id)
|
||||
.filter(
|
||||
Automation.user_id == user_id,
|
||||
AutomationRun.created_at >= start_ns,
|
||||
AutomationRun.created_at < end_ns,
|
||||
)
|
||||
.order_by(AutomationRun.created_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
return [
|
||||
(AutomationRunModel.model_validate(run), AutomationModel.model_validate(auto))
|
||||
for run, auto in result.all()
|
||||
]
|
||||
|
||||
|
||||
Automations = AutomationTable()
|
||||
AutomationRuns = AutomationRunTable()
|
||||
|
|
|
|||
824
backend/open_webui/models/calendar.py
Normal file
824
backend/open_webui/models/calendar.py
Normal file
|
|
@ -0,0 +1,824 @@
|
|||
import time
|
||||
import logging
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
Text,
|
||||
JSON,
|
||||
Boolean,
|
||||
BigInteger,
|
||||
Index,
|
||||
UniqueConstraint,
|
||||
select,
|
||||
or_,
|
||||
exists,
|
||||
func,
|
||||
delete,
|
||||
update,
|
||||
)
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from open_webui.internal.db import Base, get_async_db_context
|
||||
from open_webui.models.access_grants import AccessGrantModel, AccessGrants
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.users import User, UserModel, UserResponse
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
####################
|
||||
# Calendar DB Schema
|
||||
####################
|
||||
|
||||
|
||||
class Calendar(Base):
|
||||
__tablename__ = 'calendar'
|
||||
|
||||
id = Column(Text, primary_key=True)
|
||||
user_id = Column(Text, nullable=False)
|
||||
name = Column(Text, nullable=False)
|
||||
color = Column(Text, nullable=True)
|
||||
is_default = Column(Boolean, nullable=False, default=False)
|
||||
data = Column(JSON, nullable=True)
|
||||
meta = Column(JSON, nullable=True)
|
||||
|
||||
created_at = Column(BigInteger, nullable=False)
|
||||
updated_at = Column(BigInteger, nullable=False)
|
||||
|
||||
__table_args__ = (Index('ix_calendar_user', 'user_id'),)
|
||||
|
||||
|
||||
class CalendarEvent(Base):
|
||||
__tablename__ = 'calendar_event'
|
||||
|
||||
id = Column(Text, primary_key=True)
|
||||
calendar_id = Column(Text, nullable=False)
|
||||
user_id = Column(Text, nullable=False)
|
||||
title = Column(Text, nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
start_at = Column(BigInteger, nullable=False)
|
||||
end_at = Column(BigInteger, nullable=True)
|
||||
all_day = Column(Boolean, nullable=False, default=False)
|
||||
rrule = Column(Text, nullable=True)
|
||||
color = Column(Text, nullable=True)
|
||||
location = Column(Text, nullable=True)
|
||||
data = Column(JSON, nullable=True)
|
||||
meta = Column(JSON, nullable=True)
|
||||
is_cancelled = Column(Boolean, nullable=False, default=False)
|
||||
|
||||
created_at = Column(BigInteger, nullable=False)
|
||||
updated_at = Column(BigInteger, nullable=False)
|
||||
|
||||
__table_args__ = (
|
||||
Index('ix_calendar_event_calendar', 'calendar_id', 'start_at'),
|
||||
Index('ix_calendar_event_user_date', 'user_id', 'start_at'),
|
||||
)
|
||||
|
||||
|
||||
class CalendarEventAttendee(Base):
|
||||
__tablename__ = 'calendar_event_attendee'
|
||||
|
||||
id = Column(Text, primary_key=True)
|
||||
event_id = Column(Text, nullable=False)
|
||||
user_id = Column(Text, nullable=False)
|
||||
status = Column(Text, nullable=False, default='pending')
|
||||
meta = Column(JSON, nullable=True)
|
||||
|
||||
created_at = Column(BigInteger, nullable=False)
|
||||
updated_at = Column(BigInteger, nullable=False)
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint('event_id', 'user_id', name='uq_event_attendee'),
|
||||
Index('ix_calendar_event_attendee_user', 'user_id', 'status'),
|
||||
)
|
||||
|
||||
|
||||
####################
|
||||
# Pydantic Models
|
||||
####################
|
||||
|
||||
|
||||
class CalendarModel(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: str
|
||||
user_id: str
|
||||
name: str
|
||||
color: Optional[str] = None
|
||||
is_default: bool = False
|
||||
is_system: bool = False
|
||||
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
|
||||
access_grants: list[AccessGrantModel] = Field(default_factory=list)
|
||||
|
||||
created_at: int
|
||||
updated_at: int
|
||||
|
||||
|
||||
class CalendarEventModel(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True, extra='allow')
|
||||
|
||||
id: str
|
||||
calendar_id: str
|
||||
user_id: str
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
start_at: int
|
||||
end_at: Optional[int] = None
|
||||
all_day: bool = False
|
||||
rrule: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
is_cancelled: bool = False
|
||||
|
||||
attendees: list['CalendarEventAttendeeModel'] = Field(default_factory=list)
|
||||
|
||||
created_at: int
|
||||
updated_at: int
|
||||
|
||||
|
||||
class CalendarEventAttendeeModel(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: str
|
||||
event_id: str
|
||||
user_id: str
|
||||
status: str = 'pending'
|
||||
meta: Optional[dict] = None
|
||||
|
||||
created_at: int
|
||||
updated_at: int
|
||||
|
||||
|
||||
####################
|
||||
# Forms
|
||||
####################
|
||||
|
||||
|
||||
class CalendarForm(BaseModel):
|
||||
name: str
|
||||
color: Optional[str] = None
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
access_grants: Optional[list[dict]] = None
|
||||
|
||||
|
||||
class CalendarUpdateForm(BaseModel):
|
||||
name: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
access_grants: Optional[list[dict]] = None
|
||||
|
||||
|
||||
class CalendarEventForm(BaseModel):
|
||||
calendar_id: str
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
start_at: int
|
||||
end_at: Optional[int] = None
|
||||
all_day: bool = False
|
||||
rrule: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
attendees: Optional[list[dict]] = None
|
||||
|
||||
|
||||
class CalendarEventUpdateForm(BaseModel):
|
||||
calendar_id: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
start_at: Optional[int] = None
|
||||
end_at: Optional[int] = None
|
||||
all_day: Optional[bool] = None
|
||||
rrule: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
is_cancelled: Optional[bool] = None
|
||||
attendees: Optional[list[dict]] = None
|
||||
|
||||
|
||||
class RSVPForm(BaseModel):
|
||||
status: str # 'accepted' | 'declined' | 'tentative' | 'pending'
|
||||
|
||||
|
||||
####################
|
||||
# Response Models
|
||||
####################
|
||||
|
||||
|
||||
class CalendarEventUserResponse(CalendarEventModel):
|
||||
user: Optional[UserResponse] = None
|
||||
|
||||
|
||||
class CalendarEventListResponse(BaseModel):
|
||||
items: list[CalendarEventUserResponse]
|
||||
total: int
|
||||
|
||||
|
||||
####################
|
||||
# Table Operations
|
||||
####################
|
||||
|
||||
|
||||
class CalendarTable:
|
||||
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(
|
||||
self,
|
||||
cal: Calendar,
|
||||
access_grants: Optional[list[AccessGrantModel]] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> 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)
|
||||
)
|
||||
return CalendarModel.model_validate(cal_data)
|
||||
|
||||
async def get_or_create_defaults(self, user_id: str, db: Optional[AsyncSession] = None) -> list[CalendarModel]:
|
||||
"""Return user's calendars, creating 'Personal' default if none exist."""
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(Calendar).filter(Calendar.user_id == user_id).order_by(Calendar.created_at.asc())
|
||||
)
|
||||
calendars = result.scalars().all()
|
||||
|
||||
if calendars:
|
||||
return [CalendarModel.model_validate(c) for c in calendars]
|
||||
|
||||
now = int(time.time_ns())
|
||||
cal = Calendar(
|
||||
id=str(uuid4()),
|
||||
user_id=user_id,
|
||||
name='Personal',
|
||||
color='#3b82f6',
|
||||
is_default=True,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
db.add(cal)
|
||||
await db.commit()
|
||||
return [CalendarModel.model_validate(cal)]
|
||||
|
||||
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)
|
||||
user_group_ids = [g.id for g in user_groups]
|
||||
|
||||
stmt = select(Calendar)
|
||||
stmt = AccessGrants.has_permission_filter(
|
||||
db=db,
|
||||
query=stmt,
|
||||
DocumentModel=Calendar,
|
||||
filter={'user_id': user_id, 'group_ids': user_group_ids},
|
||||
resource_type='calendar',
|
||||
permission='read',
|
||||
)
|
||||
stmt = stmt.order_by(Calendar.created_at.asc())
|
||||
|
||||
result = await db.execute(stmt)
|
||||
calendars = result.scalars().all()
|
||||
|
||||
if not calendars:
|
||||
return await self.get_or_create_defaults(user_id, db=db)
|
||||
|
||||
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]
|
||||
|
||||
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()
|
||||
return await self._to_calendar_model(cal, db=db) if cal else None
|
||||
|
||||
|
||||
|
||||
async def insert_new_calendar(
|
||||
self, user_id: str, form_data: CalendarForm, db: Optional[AsyncSession] = None
|
||||
) -> Optional[CalendarModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
now = int(time.time_ns())
|
||||
cal = Calendar(
|
||||
id=str(uuid4()),
|
||||
user_id=user_id,
|
||||
name=form_data.name,
|
||||
color=form_data.color,
|
||||
is_default=False,
|
||||
data=form_data.data,
|
||||
meta=form_data.meta,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
db.add(cal)
|
||||
await db.commit()
|
||||
if form_data.access_grants is not None:
|
||||
await AccessGrants.set_access_grants('calendar', cal.id, form_data.access_grants, db=db)
|
||||
return await self._to_calendar_model(cal, db=db)
|
||||
|
||||
async def update_calendar_by_id(
|
||||
self, id: str, form_data: CalendarUpdateForm, 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()
|
||||
if not cal:
|
||||
return None
|
||||
|
||||
update_data = form_data.model_dump(exclude_unset=True)
|
||||
if 'name' in update_data:
|
||||
cal.name = update_data['name']
|
||||
if 'color' in update_data:
|
||||
cal.color = update_data['color']
|
||||
if 'data' in update_data:
|
||||
cal.data = {**(cal.data or {}), **update_data['data']}
|
||||
if 'meta' in update_data:
|
||||
cal.meta = {**(cal.meta or {}), **update_data['meta']}
|
||||
if 'access_grants' in update_data:
|
||||
await AccessGrants.set_access_grants('calendar', id, update_data['access_grants'], db=db)
|
||||
|
||||
cal.updated_at = int(time.time_ns())
|
||||
await db.commit()
|
||||
return await self._to_calendar_model(cal, db=db)
|
||||
|
||||
async def set_default_calendar(
|
||||
self, user_id: str, calendar_id: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[CalendarModel]:
|
||||
"""Set a calendar as the user's default, clearing all others."""
|
||||
async with get_async_db_context(db) as db:
|
||||
# Clear all defaults for this user
|
||||
await db.execute(
|
||||
update(Calendar)
|
||||
.where(Calendar.user_id == user_id, Calendar.is_default == True)
|
||||
.values(is_default=False)
|
||||
)
|
||||
# Set the new default
|
||||
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
|
||||
cal.is_default = True
|
||||
cal.updated_at = int(time.time_ns())
|
||||
await db.commit()
|
||||
return await self._to_calendar_model(cal, db=db)
|
||||
|
||||
async def delete_calendar_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool:
|
||||
"""Delete a non-default calendar. Cascades to events, attendees, and grants."""
|
||||
try:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(Calendar).filter(Calendar.id == id))
|
||||
cal = result.scalars().first()
|
||||
if not cal or cal.is_default:
|
||||
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 = [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 events
|
||||
await db.execute(delete(CalendarEvent).filter(CalendarEvent.calendar_id == id))
|
||||
|
||||
# Delete access grants
|
||||
await AccessGrants.revoke_all_access('calendar', id, db=db)
|
||||
|
||||
# Delete calendar
|
||||
await db.execute(delete(Calendar).filter(Calendar.id == id))
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class CalendarEventTable:
|
||||
async def _get_attendees(
|
||||
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))
|
||||
rows = result.scalars().all()
|
||||
return [CalendarEventAttendeeModel.model_validate(r) for r in rows]
|
||||
|
||||
async def _to_event_model(
|
||||
self,
|
||||
event: CalendarEvent,
|
||||
attendees: Optional[list[CalendarEventAttendeeModel]] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> CalendarEventModel:
|
||||
event_data = CalendarEventModel.model_validate(event).model_dump(exclude={'attendees'})
|
||||
event_data['attendees'] = (
|
||||
attendees if attendees is not None else await self._get_attendees(event_data['id'], db=db)
|
||||
)
|
||||
return CalendarEventModel.model_validate(event_data)
|
||||
|
||||
async def insert_new_event(
|
||||
self, user_id: str, form_data: CalendarEventForm, db: Optional[AsyncSession] = None
|
||||
) -> Optional[CalendarEventModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
now = int(time.time_ns())
|
||||
event = CalendarEvent(
|
||||
id=str(uuid4()),
|
||||
calendar_id=form_data.calendar_id,
|
||||
user_id=user_id,
|
||||
title=form_data.title,
|
||||
description=form_data.description,
|
||||
start_at=form_data.start_at,
|
||||
end_at=form_data.end_at,
|
||||
all_day=form_data.all_day,
|
||||
rrule=form_data.rrule,
|
||||
color=form_data.color,
|
||||
location=form_data.location,
|
||||
data=form_data.data,
|
||||
meta=form_data.meta,
|
||||
is_cancelled=False,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
db.add(event)
|
||||
await db.commit()
|
||||
|
||||
# Add attendees
|
||||
if form_data.attendees:
|
||||
await CalendarEventAttendees.set_attendees(event.id, form_data.attendees, db=db)
|
||||
|
||||
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 with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(CalendarEvent).filter(CalendarEvent.id == id))
|
||||
event = result.scalars().first()
|
||||
return await self._to_event_model(event, db=db) if event else None
|
||||
|
||||
async def get_events_by_range(
|
||||
self,
|
||||
user_id: str,
|
||||
start: int,
|
||||
end: int,
|
||||
calendar_ids: Optional[list[str]] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> list[CalendarEventUserResponse]:
|
||||
"""Fetch events visible to user within a date range.
|
||||
|
||||
Visible events = events in owned/shared calendars + events user attends.
|
||||
Recurring events are fetched if they have any rrule (expansion in Python).
|
||||
"""
|
||||
async with get_async_db_context(db) as db:
|
||||
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||
user_group_ids = [g.id for g in user_groups]
|
||||
|
||||
# Get calendar IDs accessible to user
|
||||
cal_stmt = select(Calendar.id)
|
||||
cal_stmt = AccessGrants.has_permission_filter(
|
||||
db=db,
|
||||
query=cal_stmt,
|
||||
DocumentModel=Calendar,
|
||||
filter={'user_id': user_id, 'group_ids': user_group_ids},
|
||||
resource_type='calendar',
|
||||
permission='read',
|
||||
)
|
||||
cal_result = await db.execute(cal_stmt)
|
||||
accessible_cal_ids = [r[0] for r in cal_result.all()]
|
||||
|
||||
if calendar_ids:
|
||||
# Filter to requested calendars only
|
||||
accessible_cal_ids = [c for c in accessible_cal_ids if c in calendar_ids]
|
||||
|
||||
# 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)
|
||||
)
|
||||
attendee_event_ids = [r[0] for r in attendee_event_ids_result.all()]
|
||||
|
||||
# Build conditions for accessible events
|
||||
conditions = []
|
||||
if accessible_cal_ids:
|
||||
conditions.append(CalendarEvent.calendar_id.in_(accessible_cal_ids))
|
||||
if attendee_event_ids:
|
||||
conditions.append(CalendarEvent.id.in_(attendee_event_ids))
|
||||
|
||||
if not conditions:
|
||||
return []
|
||||
|
||||
# Build event query
|
||||
stmt = (
|
||||
select(CalendarEvent, User)
|
||||
.outerjoin(User, User.id == CalendarEvent.user_id)
|
||||
.filter(
|
||||
CalendarEvent.is_cancelled == False,
|
||||
or_(*conditions),
|
||||
or_(
|
||||
# Non-recurring: overlaps the range
|
||||
(
|
||||
CalendarEvent.rrule.is_(None)
|
||||
& (CalendarEvent.start_at < end)
|
||||
& or_(
|
||||
CalendarEvent.end_at.is_(None) & (CalendarEvent.start_at >= start),
|
||||
CalendarEvent.end_at.isnot(None) & (CalendarEvent.end_at > start),
|
||||
)
|
||||
),
|
||||
# Recurring: fetch all (expansion in Python)
|
||||
CalendarEvent.rrule.isnot(None),
|
||||
),
|
||||
)
|
||||
.order_by(CalendarEvent.start_at.asc())
|
||||
)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
items = result.all()
|
||||
|
||||
if not items:
|
||||
return []
|
||||
|
||||
# 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))
|
||||
)
|
||||
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))
|
||||
|
||||
events = []
|
||||
for event, user in items:
|
||||
event_data = CalendarEventModel.model_validate(event).model_dump(exclude={'attendees'})
|
||||
event_data['attendees'] = att_map.get(event.id, [])
|
||||
events.append(
|
||||
CalendarEventUserResponse(
|
||||
**event_data,
|
||||
user=(UserResponse(**UserModel.model_validate(user).model_dump()) if user else None),
|
||||
)
|
||||
)
|
||||
|
||||
return events
|
||||
|
||||
async def search_events(
|
||||
self,
|
||||
user_id: str,
|
||||
query: Optional[str] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 30,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> CalendarEventListResponse:
|
||||
async with get_async_db_context(db) as db:
|
||||
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||
user_group_ids = [g.id for g in user_groups]
|
||||
|
||||
# Get accessible calendar IDs
|
||||
cal_stmt = select(Calendar.id)
|
||||
cal_stmt = AccessGrants.has_permission_filter(
|
||||
db=db,
|
||||
query=cal_stmt,
|
||||
DocumentModel=Calendar,
|
||||
filter={'user_id': user_id, 'group_ids': user_group_ids},
|
||||
resource_type='calendar',
|
||||
permission='read',
|
||||
)
|
||||
cal_result = await db.execute(cal_stmt)
|
||||
accessible_cal_ids = [r[0] for r in cal_result.all()]
|
||||
if not accessible_cal_ids:
|
||||
return CalendarEventListResponse(items=[], total=0)
|
||||
|
||||
stmt = (
|
||||
select(CalendarEvent, User)
|
||||
.outerjoin(User, User.id == CalendarEvent.user_id)
|
||||
.filter(
|
||||
CalendarEvent.is_cancelled == False,
|
||||
CalendarEvent.calendar_id.in_(accessible_cal_ids),
|
||||
)
|
||||
)
|
||||
|
||||
if query:
|
||||
search = f'%{query}%'
|
||||
stmt = stmt.filter(
|
||||
or_(
|
||||
CalendarEvent.title.ilike(search),
|
||||
CalendarEvent.description.ilike(search),
|
||||
CalendarEvent.location.ilike(search),
|
||||
)
|
||||
)
|
||||
|
||||
stmt = stmt.order_by(CalendarEvent.start_at.desc())
|
||||
|
||||
count_result = await db.execute(select(func.count()).select_from(stmt.subquery()))
|
||||
total = count_result.scalar()
|
||||
|
||||
if skip:
|
||||
stmt = stmt.offset(skip)
|
||||
if limit:
|
||||
stmt = stmt.limit(limit)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
items = result.all()
|
||||
|
||||
if not items:
|
||||
return CalendarEventListResponse(items=[], total=total)
|
||||
|
||||
# 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))
|
||||
)
|
||||
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))
|
||||
|
||||
events = []
|
||||
for event, user in items:
|
||||
event_data = CalendarEventModel.model_validate(event).model_dump(exclude={'attendees'})
|
||||
event_data['attendees'] = att_map.get(event.id, [])
|
||||
events.append(
|
||||
CalendarEventUserResponse(
|
||||
**event_data,
|
||||
user=(UserResponse(**UserModel.model_validate(user).model_dump()) if user else None),
|
||||
)
|
||||
)
|
||||
|
||||
return CalendarEventListResponse(items=events, total=total)
|
||||
|
||||
async def update_event_by_id(
|
||||
self, id: str, form_data: CalendarEventUpdateForm, 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()
|
||||
if not event:
|
||||
return None
|
||||
|
||||
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',
|
||||
]:
|
||||
if field in update_data:
|
||||
setattr(event, field, update_data[field])
|
||||
|
||||
if 'data' in update_data and update_data['data'] is not None:
|
||||
event.data = {**(event.data or {}), **update_data['data']}
|
||||
if 'meta' in update_data and update_data['meta'] is not None:
|
||||
event.meta = {**(event.meta or {}), **update_data['meta']}
|
||||
|
||||
if 'attendees' in update_data and update_data['attendees'] is not None:
|
||||
await CalendarEventAttendees.set_attendees(id, update_data['attendees'], db=db)
|
||||
|
||||
event.updated_at = int(time.time_ns())
|
||||
await db.commit()
|
||||
return await self._to_event_model(event, db=db)
|
||||
|
||||
async def get_upcoming_events(
|
||||
self,
|
||||
now_ns: int,
|
||||
default_lookahead_ns: int,
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> list[tuple[CalendarEventModel, Optional[str]]]:
|
||||
"""Events starting between now and now + lookahead, for alert processing.
|
||||
|
||||
Per-event lookahead is read from meta.alert_minutes (falls back to
|
||||
default_lookahead_ns). Returns (event, user_timezone) pairs.
|
||||
"""
|
||||
from open_webui.models.users import User as UserRow
|
||||
|
||||
# Use the maximum possible lookahead (60 min) to cast a wide net;
|
||||
# per-event filtering happens in Python after fetching.
|
||||
max_lookahead_ns = max(default_lookahead_ns, 60 * 60 * 1_000_000_000)
|
||||
upper = now_ns + max_lookahead_ns
|
||||
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(CalendarEvent, UserRow.timezone)
|
||||
.outerjoin(UserRow, UserRow.id == CalendarEvent.user_id)
|
||||
.filter(
|
||||
CalendarEvent.is_cancelled == False,
|
||||
CalendarEvent.start_at >= now_ns,
|
||||
CalendarEvent.start_at <= upper,
|
||||
)
|
||||
)
|
||||
rows = result.all()
|
||||
|
||||
events = []
|
||||
for event, tz in rows:
|
||||
model = CalendarEventModel.model_validate(event)
|
||||
# Determine per-event alert window
|
||||
alert_minutes = None
|
||||
if model.meta and 'alert_minutes' in model.meta:
|
||||
alert_minutes = model.meta['alert_minutes']
|
||||
|
||||
if alert_minutes is not None:
|
||||
if alert_minutes < 0:
|
||||
# alert_minutes < 0 means "no alert"
|
||||
continue
|
||||
event_lookahead_ns = alert_minutes * 60 * 1_000_000_000
|
||||
else:
|
||||
event_lookahead_ns = default_lookahead_ns
|
||||
|
||||
if model.start_at <= now_ns + event_lookahead_ns:
|
||||
events.append((model, tz))
|
||||
|
||||
return events
|
||||
|
||||
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(CalendarEvent).filter(CalendarEvent.id == id))
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class CalendarEventAttendeeTable:
|
||||
async def set_attendees(
|
||||
self, event_id: str, attendees: list[dict], db: Optional[AsyncSession] = None
|
||||
) -> list[CalendarEventAttendeeModel]:
|
||||
"""Replace all attendees for an event.
|
||||
|
||||
Each dict in attendees: {user_id: str, status?: str, meta?: dict}
|
||||
"""
|
||||
async with get_async_db_context(db) as db:
|
||||
# Remove existing
|
||||
await db.execute(delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id))
|
||||
|
||||
now = int(time.time_ns())
|
||||
models = []
|
||||
for att in attendees:
|
||||
row = CalendarEventAttendee(
|
||||
id=str(uuid4()),
|
||||
event_id=event_id,
|
||||
user_id=att['user_id'],
|
||||
status=att.get('status', 'pending'),
|
||||
meta=att.get('meta'),
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
db.add(row)
|
||||
models.append(CalendarEventAttendeeModel.model_validate(row))
|
||||
|
||||
await db.commit()
|
||||
return models
|
||||
|
||||
async def update_rsvp(
|
||||
self, event_id: str, user_id: str, status: str, db: Optional[AsyncSession] = None
|
||||
) -> Optional[CalendarEventAttendeeModel]:
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(CalendarEventAttendee).filter(
|
||||
CalendarEventAttendee.event_id == event_id,
|
||||
CalendarEventAttendee.user_id == user_id,
|
||||
)
|
||||
)
|
||||
att = result.scalars().first()
|
||||
if not att:
|
||||
return None
|
||||
|
||||
att.status = status
|
||||
att.updated_at = int(time.time_ns())
|
||||
await db.commit()
|
||||
return CalendarEventAttendeeModel.model_validate(att)
|
||||
|
||||
async def get_attendees_by_event(
|
||||
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))
|
||||
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]:
|
||||
"""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)
|
||||
)
|
||||
return [r[0] for r in result.all()]
|
||||
|
||||
|
||||
Calendars = CalendarTable()
|
||||
CalendarEvents = CalendarEventTable()
|
||||
CalendarEventAttendees = CalendarEventAttendeeTable()
|
||||
|
|
@ -313,7 +313,6 @@ class ChatMessageTable:
|
|||
stmt = select(ChatMessage.model_id, func.count(ChatMessage.id).label('count')).filter(
|
||||
ChatMessage.role == 'assistant',
|
||||
ChatMessage.model_id.isnot(None),
|
||||
~ChatMessage.user_id.like('shared-%'),
|
||||
)
|
||||
|
||||
if start_date:
|
||||
|
|
@ -368,7 +367,6 @@ class ChatMessageTable:
|
|||
ChatMessage.role == 'assistant',
|
||||
ChatMessage.model_id.isnot(None),
|
||||
ChatMessage.usage.isnot(None),
|
||||
~ChatMessage.user_id.like('shared-%'),
|
||||
)
|
||||
|
||||
if start_date:
|
||||
|
|
@ -430,7 +428,6 @@ class ChatMessageTable:
|
|||
ChatMessage.role == 'assistant',
|
||||
ChatMessage.user_id.isnot(None),
|
||||
ChatMessage.usage.isnot(None),
|
||||
~ChatMessage.user_id.like('shared-%'),
|
||||
)
|
||||
|
||||
if start_date:
|
||||
|
|
@ -465,7 +462,7 @@ class ChatMessageTable:
|
|||
from open_webui.models.groups import GroupMember
|
||||
|
||||
stmt = select(ChatMessage.user_id, func.count(ChatMessage.id).label('count')).filter(
|
||||
~ChatMessage.user_id.like('shared-%')
|
||||
ChatMessage.role == 'assistant',
|
||||
)
|
||||
|
||||
if start_date:
|
||||
|
|
@ -491,7 +488,7 @@ class ChatMessageTable:
|
|||
from open_webui.models.groups import GroupMember
|
||||
|
||||
stmt = select(ChatMessage.chat_id, func.count(ChatMessage.id).label('count')).filter(
|
||||
~ChatMessage.user_id.like('shared-%')
|
||||
ChatMessage.role == 'assistant',
|
||||
)
|
||||
|
||||
if start_date:
|
||||
|
|
@ -521,7 +518,6 @@ class ChatMessageTable:
|
|||
stmt = select(ChatMessage.created_at, ChatMessage.model_id).filter(
|
||||
ChatMessage.role == 'assistant',
|
||||
ChatMessage.model_id.isnot(None),
|
||||
~ChatMessage.user_id.like('shared-%'),
|
||||
)
|
||||
|
||||
if start_date:
|
||||
|
|
@ -568,7 +564,6 @@ class ChatMessageTable:
|
|||
stmt = select(ChatMessage.created_at, ChatMessage.model_id).filter(
|
||||
ChatMessage.role == 'assistant',
|
||||
ChatMessage.model_id.isnot(None),
|
||||
~ChatMessage.user_id.like('shared-%'),
|
||||
)
|
||||
|
||||
if start_date:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from typing import List, Dict, Any
|
|||
from contextlib import asynccontextmanager
|
||||
|
||||
from langchain_core.documents import Document
|
||||
from open_webui.env import GLOBAL_LOG_LEVEL
|
||||
from open_webui.env import GLOBAL_LOG_LEVEL, AIOHTTP_CLIENT_SESSION_SSL
|
||||
|
||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -285,6 +285,7 @@ class MistralLoader:
|
|||
data=writer,
|
||||
headers=self.headers,
|
||||
timeout=aiohttp.ClientTimeout(total=self.upload_timeout),
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
return await self._handle_response_async(response)
|
||||
|
||||
|
|
@ -333,6 +334,7 @@ class MistralLoader:
|
|||
headers=headers,
|
||||
params=params,
|
||||
timeout=aiohttp.ClientTimeout(total=self.url_timeout),
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
return await self._handle_response_async(response)
|
||||
|
||||
|
|
@ -404,6 +406,7 @@ class MistralLoader:
|
|||
json=payload,
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=self.ocr_timeout),
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
ocr_response = await self._handle_response_async(response)
|
||||
|
||||
|
|
@ -436,7 +439,8 @@ class MistralLoader:
|
|||
async with session.delete(
|
||||
url=f'{self.base_url}/files/{file_id}',
|
||||
headers=self.headers,
|
||||
timeout=aiohttp.ClientTimeout(total=self.cleanup_timeout), # Shorter timeout for cleanup
|
||||
timeout=aiohttp.ClientTimeout(total=self.cleanup_timeout),
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
return await self._handle_response_async(response)
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ from open_webui.config import (
|
|||
WEB_FETCH_FILTER_LIST,
|
||||
)
|
||||
from open_webui.utils.misc import is_string_allowed
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -511,6 +512,8 @@ class SafeWebBaseLoader(WebBaseLoader):
|
|||
)
|
||||
if not self.session.verify:
|
||||
kwargs['ssl'] = False
|
||||
else:
|
||||
kwargs['ssl'] = AIOHTTP_CLIENT_SESSION_SSL
|
||||
|
||||
async with session.get(
|
||||
url,
|
||||
|
|
|
|||
|
|
@ -1304,6 +1304,7 @@ async def get_available_models(request: Request) -> list[dict]:
|
|||
try:
|
||||
async with session.get(
|
||||
f'{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/models',
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
|
|
@ -1315,6 +1316,7 @@ async def get_available_models(request: Request) -> list[dict]:
|
|||
try:
|
||||
async with session.get(
|
||||
f'{request.app.state.config.TTS_OPENAI_API_BASE_URL}/models',
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
|
|
@ -1335,6 +1337,7 @@ async def get_available_models(request: Request) -> list[dict]:
|
|||
'xi-api-key': request.app.state.config.TTS_API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
models = await response.json()
|
||||
|
|
@ -1362,6 +1365,7 @@ async def get_available_voices(request) -> dict:
|
|||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||
async with session.get(
|
||||
f'{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/voices',
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
|
|
@ -1401,7 +1405,7 @@ async def get_available_voices(request) -> dict:
|
|||
|
||||
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST)
|
||||
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
|
||||
async with session.get(url, headers=headers) as response:
|
||||
async with session.get(url, headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL) as response:
|
||||
response.raise_for_status()
|
||||
voices = await response.json()
|
||||
|
||||
|
|
@ -1422,6 +1426,7 @@ async def get_available_voices(request) -> dict:
|
|||
headers={
|
||||
'Authorization': f'Bearer {api_key}',
|
||||
},
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
voices_data = await response.json()
|
||||
|
|
@ -1456,6 +1461,7 @@ async def get_elevenlabs_voices(api_key: str) -> dict:
|
|||
'xi-api-key': api_key,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
voices_data = await response.json()
|
||||
|
|
|
|||
|
|
@ -810,7 +810,7 @@ async def signout(request: Request, response: Response, db: AsyncSession = Depen
|
|||
oauth_id_token = session.token.get('id_token')
|
||||
try:
|
||||
async with ClientSession(trust_env=True) as session:
|
||||
async with session.get(oauth_server_metadata_url) as r:
|
||||
async with session.get(oauth_server_metadata_url, ssl=AIOHTTP_CLIENT_SESSION_SSL) as r:
|
||||
if r.status == 200:
|
||||
openid_data = await r.json()
|
||||
logout_url = openid_data.get('end_session_endpoint')
|
||||
|
|
@ -971,7 +971,9 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
|
|||
'FOLDER_MAX_FILE_COUNT': request.app.state.config.FOLDER_MAX_FILE_COUNT,
|
||||
'AUTOMATION_MAX_COUNT': request.app.state.config.AUTOMATION_MAX_COUNT,
|
||||
'AUTOMATION_MIN_INTERVAL': request.app.state.config.AUTOMATION_MIN_INTERVAL,
|
||||
'ENABLE_AUTOMATIONS': request.app.state.config.ENABLE_AUTOMATIONS,
|
||||
'ENABLE_CHANNELS': request.app.state.config.ENABLE_CHANNELS,
|
||||
'ENABLE_CALENDAR': request.app.state.config.ENABLE_CALENDAR,
|
||||
'ENABLE_MEMORIES': request.app.state.config.ENABLE_MEMORIES,
|
||||
'ENABLE_NOTES': request.app.state.config.ENABLE_NOTES,
|
||||
'ENABLE_USER_WEBHOOKS': request.app.state.config.ENABLE_USER_WEBHOOKS,
|
||||
|
|
@ -999,7 +1001,9 @@ class AdminConfig(BaseModel):
|
|||
FOLDER_MAX_FILE_COUNT: Optional[int | str] = None
|
||||
AUTOMATION_MAX_COUNT: Optional[int | str] = None
|
||||
AUTOMATION_MIN_INTERVAL: Optional[int | str] = None
|
||||
ENABLE_AUTOMATIONS: bool
|
||||
ENABLE_CHANNELS: bool
|
||||
ENABLE_CALENDAR: bool
|
||||
ENABLE_MEMORIES: bool
|
||||
ENABLE_NOTES: bool
|
||||
ENABLE_USER_WEBHOOKS: bool
|
||||
|
|
@ -1030,7 +1034,9 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep
|
|||
request.app.state.config.AUTOMATION_MIN_INTERVAL = (
|
||||
int(form_data.AUTOMATION_MIN_INTERVAL) if form_data.AUTOMATION_MIN_INTERVAL else ''
|
||||
)
|
||||
request.app.state.config.ENABLE_AUTOMATIONS = form_data.ENABLE_AUTOMATIONS
|
||||
request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS
|
||||
request.app.state.config.ENABLE_CALENDAR = form_data.ENABLE_CALENDAR
|
||||
request.app.state.config.ENABLE_MEMORIES = form_data.ENABLE_MEMORIES
|
||||
request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES
|
||||
|
||||
|
|
@ -1073,7 +1079,9 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep
|
|||
'FOLDER_MAX_FILE_COUNT': request.app.state.config.FOLDER_MAX_FILE_COUNT,
|
||||
'AUTOMATION_MAX_COUNT': request.app.state.config.AUTOMATION_MAX_COUNT,
|
||||
'AUTOMATION_MIN_INTERVAL': request.app.state.config.AUTOMATION_MIN_INTERVAL,
|
||||
'ENABLE_AUTOMATIONS': request.app.state.config.ENABLE_AUTOMATIONS,
|
||||
'ENABLE_CHANNELS': request.app.state.config.ENABLE_CHANNELS,
|
||||
'ENABLE_CALENDAR': request.app.state.config.ENABLE_CALENDAR,
|
||||
'ENABLE_MEMORIES': request.app.state.config.ENABLE_MEMORIES,
|
||||
'ENABLE_NOTES': request.app.state.config.ENABLE_NOTES,
|
||||
'ENABLE_USER_WEBHOOKS': request.app.state.config.ENABLE_USER_WEBHOOKS,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ PAGE_ITEM_COUNT = 30
|
|||
|
||||
|
||||
async def check_automations_permission(request, user):
|
||||
if not request.app.state.config.ENABLE_AUTOMATIONS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
|
|
|
|||
391
backend/open_webui/routers/calendar.py
Normal file
391
backend/open_webui/routers/calendar.py
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
|
||||
from open_webui.models.calendar import (
|
||||
Calendars,
|
||||
CalendarEvents,
|
||||
CalendarEventAttendees,
|
||||
CalendarForm,
|
||||
CalendarUpdateForm,
|
||||
CalendarEventForm,
|
||||
CalendarEventUpdateForm,
|
||||
CalendarModel,
|
||||
CalendarEventModel,
|
||||
CalendarEventUserResponse,
|
||||
CalendarEventListResponse,
|
||||
RSVPForm,
|
||||
)
|
||||
from open_webui.models.access_grants import AccessGrants
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.users import UserModel
|
||||
from open_webui.utils.auth import get_verified_user
|
||||
from open_webui.utils.access_control import has_permission
|
||||
from open_webui.utils.calendar import expand_recurring_event
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
SCHEDULED_TASKS_CALENDAR_ID = '__scheduled_tasks__'
|
||||
|
||||
|
||||
async def check_calendar_permission(request: Request, user):
|
||||
"""Check global feature flag AND per-user permission for calendar access."""
|
||||
if not request.app.state.config.ENABLE_CALENDAR:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.calendar', request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
|
||||
async def _user_has_automations(request: Request, user) -> bool:
|
||||
"""Check if automations feature is available to this user."""
|
||||
if not getattr(request.app.state.config, 'ENABLE_AUTOMATIONS', False):
|
||||
return False
|
||||
if user.role == 'admin':
|
||||
return True
|
||||
return await has_permission(
|
||||
user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
raise HTTPException(status_code=404, detail='Calendar not found')
|
||||
if cal.user_id == user.id or user.role == 'admin':
|
||||
return cal
|
||||
user_groups = await Groups.get_groups_by_member_id(user.id)
|
||||
user_group_ids = [g.id for g in user_groups]
|
||||
if await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='calendar',
|
||||
resource_id=cal.id,
|
||||
permission=permission,
|
||||
user_group_ids=user_group_ids,
|
||||
):
|
||||
return cal
|
||||
raise HTTPException(status_code=403, detail='Access denied')
|
||||
|
||||
|
||||
####################
|
||||
# Calendar CRUD (static paths first)
|
||||
####################
|
||||
|
||||
|
||||
@router.get('/', response_model=list[CalendarModel])
|
||||
async def get_calendars(request: Request, user: UserModel = Depends(get_verified_user)):
|
||||
"""List user's calendars (owned + shared), plus a virtual Scheduled Tasks calendar
|
||||
when automations are available."""
|
||||
await check_calendar_permission(request, user)
|
||||
calendars = await Calendars.get_calendars_by_user(user.id)
|
||||
|
||||
if await _user_has_automations(request, user):
|
||||
now = int(time.time_ns())
|
||||
calendars.append(
|
||||
CalendarModel(
|
||||
id=SCHEDULED_TASKS_CALENDAR_ID,
|
||||
user_id=user.id,
|
||||
name='Scheduled Tasks',
|
||||
color='#8b5cf6',
|
||||
is_default=False,
|
||||
is_system=True,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
)
|
||||
|
||||
return calendars
|
||||
|
||||
|
||||
@router.post('/create', response_model=CalendarModel)
|
||||
async def create_calendar(request: Request, form_data: CalendarForm, user: UserModel = Depends(get_verified_user)):
|
||||
"""Create a new user calendar."""
|
||||
await check_calendar_permission(request, user)
|
||||
return await Calendars.insert_new_calendar(user.id, form_data)
|
||||
|
||||
|
||||
####################
|
||||
# Event CRUD (before /{calendar_id} to avoid route conflicts)
|
||||
####################
|
||||
|
||||
|
||||
@router.get('/events')
|
||||
async def get_events(
|
||||
request: Request,
|
||||
start: str,
|
||||
end: str,
|
||||
calendar_ids: Optional[str] = None,
|
||||
user: UserModel = Depends(get_verified_user),
|
||||
):
|
||||
"""Get events in date range.
|
||||
|
||||
Args:
|
||||
start: ISO 8601 datetime string (e.g. 2026-04-01T00:00:00)
|
||||
end: ISO 8601 datetime string (e.g. 2026-05-01T00:00:00)
|
||||
calendar_ids: optional comma-separated list to filter
|
||||
|
||||
Includes:
|
||||
- Stored events from the database
|
||||
- Virtual events computed from active automation RRULEs (Scheduled Tasks calendar)
|
||||
"""
|
||||
await check_calendar_permission(request, user)
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(start.replace('Z', '+00:00'))
|
||||
end_dt = datetime.fromisoformat(end.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail='Invalid date format. Use ISO 8601 (e.g. 2026-04-01T00:00:00)')
|
||||
|
||||
NS = 1_000_000
|
||||
start_ns = int(start_dt.timestamp() * 1000) * NS
|
||||
end_ns = int(end_dt.timestamp() * 1000) * NS
|
||||
cal_id_list = calendar_ids.split(',') if calendar_ids else None
|
||||
|
||||
# 1. Stored events
|
||||
events = await CalendarEvents.get_events_by_range(
|
||||
user_id=user.id,
|
||||
start=start_ns,
|
||||
end=end_ns,
|
||||
calendar_ids=cal_id_list,
|
||||
)
|
||||
|
||||
# Expand recurring stored events
|
||||
expanded = []
|
||||
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)
|
||||
for inst in instances:
|
||||
expanded.append(CalendarEventUserResponse(**{**inst, 'user': event.user}))
|
||||
else:
|
||||
expanded.append(event)
|
||||
|
||||
# 2. Virtual automation events (Scheduled Tasks calendar)
|
||||
if await _user_has_automations(request, user) and (
|
||||
cal_id_list is None or SCHEDULED_TASKS_CALENDAR_ID in cal_id_list
|
||||
):
|
||||
try:
|
||||
from open_webui.models.automations import Automations, AutomationRuns
|
||||
|
||||
# Future runs: expand RRULEs for active automations only
|
||||
active_automations = await Automations.get_active_by_user(user.id)
|
||||
for auto in active_automations:
|
||||
rrule_str = auto.data.get('rrule', '') if auto.data else ''
|
||||
if not rrule_str:
|
||||
continue
|
||||
|
||||
virtual = {
|
||||
'id': f'auto_{auto.id}',
|
||||
'calendar_id': SCHEDULED_TASKS_CALENDAR_ID,
|
||||
'user_id': user.id,
|
||||
'title': auto.name,
|
||||
'description': auto.data.get('prompt', '') if auto.data else '',
|
||||
'start_at': auto.next_run_at or 0,
|
||||
'end_at': None,
|
||||
'all_day': False,
|
||||
'rrule': rrule_str,
|
||||
'color': None,
|
||||
'location': None,
|
||||
'data': None,
|
||||
'meta': {'automation_id': auto.id},
|
||||
'is_cancelled': False,
|
||||
'attendees': [],
|
||||
'created_at': auto.created_at,
|
||||
'updated_at': auto.updated_at,
|
||||
'user': None,
|
||||
}
|
||||
|
||||
# Only expand into the future — past runs are handled below
|
||||
now_ns = int(time.time_ns())
|
||||
rrule_start = max(start_ns, now_ns)
|
||||
instances = expand_recurring_event(virtual, rrule_start, end_ns, tz=user.timezone)
|
||||
for inst in instances:
|
||||
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)
|
||||
for run, auto in runs_with_auto:
|
||||
expanded.append(
|
||||
CalendarEventUserResponse(
|
||||
id=f'run_{run.id}',
|
||||
calendar_id=SCHEDULED_TASKS_CALENDAR_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)
|
||||
|
||||
return [e.model_dump() if hasattr(e, 'model_dump') else e for e in expanded]
|
||||
|
||||
|
||||
@router.post('/events/create', response_model=CalendarEventModel)
|
||||
async def create_event(request: Request, form_data: CalendarEventForm, user: UserModel = Depends(get_verified_user)):
|
||||
await check_calendar_permission(request, user)
|
||||
await _check_calendar_access(form_data.calendar_id, user, 'write')
|
||||
return await CalendarEvents.insert_new_event(user.id, form_data)
|
||||
|
||||
|
||||
@router.get('/events/search', response_model=CalendarEventListResponse)
|
||||
async def search_events(
|
||||
request: Request,
|
||||
query: Optional[str] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 30,
|
||||
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)
|
||||
|
||||
|
||||
@router.get('/events/{event_id}', response_model=CalendarEventModel)
|
||||
async def get_event(request: Request, event_id: str, user: UserModel = Depends(get_verified_user)):
|
||||
await check_calendar_permission(request, user)
|
||||
event = await CalendarEvents.get_event_by_id(event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail='Event not found')
|
||||
|
||||
await _check_calendar_access(event.calendar_id, user, 'read')
|
||||
|
||||
return event
|
||||
|
||||
|
||||
@router.post('/events/{event_id}/update', response_model=CalendarEventModel)
|
||||
async def update_event(
|
||||
request: Request, event_id: str, form_data: CalendarEventUpdateForm, user: UserModel = Depends(get_verified_user)
|
||||
):
|
||||
await check_calendar_permission(request, user)
|
||||
event = await CalendarEvents.get_event_by_id(event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail='Event not found')
|
||||
|
||||
await _check_calendar_access(event.calendar_id, user, 'write')
|
||||
|
||||
updated = await CalendarEvents.update_event_by_id(event_id, form_data)
|
||||
if not updated:
|
||||
raise HTTPException(status_code=500, detail='Failed to update')
|
||||
return updated
|
||||
|
||||
|
||||
@router.delete('/events/{event_id}/delete')
|
||||
async def delete_event(request: Request, event_id: str, user: UserModel = Depends(get_verified_user)):
|
||||
await check_calendar_permission(request, user)
|
||||
event = await CalendarEvents.get_event_by_id(event_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail='Event not found')
|
||||
|
||||
await _check_calendar_access(event.calendar_id, user, 'write')
|
||||
|
||||
result = await CalendarEvents.delete_event_by_id(event_id)
|
||||
if not result:
|
||||
raise HTTPException(status_code=500, detail='Failed to delete')
|
||||
return {'status': True}
|
||||
|
||||
|
||||
@router.post('/events/{event_id}/rsvp', response_model=dict)
|
||||
async def rsvp_event(
|
||||
request: Request, event_id: str, form_data: RSVPForm, user: UserModel = Depends(get_verified_user)
|
||||
):
|
||||
"""Update own RSVP status for an event."""
|
||||
await check_calendar_permission(request, user)
|
||||
if form_data.status not in ('accepted', 'declined', 'tentative', 'pending'):
|
||||
raise HTTPException(status_code=400, detail='Invalid status')
|
||||
|
||||
result = await CalendarEventAttendees.update_rsvp(event_id, user.id, form_data.status)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail='Not an attendee of this event')
|
||||
return {'status': True, 'rsvp': result.status}
|
||||
|
||||
|
||||
####################
|
||||
# Calendar by ID (dynamic path — MUST come after /events* routes)
|
||||
####################
|
||||
|
||||
|
||||
@router.get('/{calendar_id}', response_model=CalendarModel)
|
||||
async def get_calendar_by_id(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)):
|
||||
await check_calendar_permission(request, user)
|
||||
cal = await _check_calendar_access(calendar_id, user, 'read')
|
||||
return cal
|
||||
|
||||
|
||||
@router.post('/{calendar_id}/update', response_model=CalendarModel)
|
||||
async def update_calendar(
|
||||
request: Request, calendar_id: str, form_data: CalendarUpdateForm, user: UserModel = Depends(get_verified_user)
|
||||
):
|
||||
await check_calendar_permission(request, user)
|
||||
cal = await _check_calendar_access(calendar_id, user, 'write')
|
||||
|
||||
# Only owner/admin can change access grants
|
||||
if form_data.access_grants is not None and cal.user_id != user.id and user.role != 'admin':
|
||||
raise HTTPException(status_code=403, detail='Only owner can manage sharing')
|
||||
|
||||
updated = await Calendars.update_calendar_by_id(calendar_id, form_data)
|
||||
if not updated:
|
||||
raise HTTPException(status_code=500, detail='Failed to update')
|
||||
return updated
|
||||
|
||||
|
||||
@router.delete('/{calendar_id}/delete')
|
||||
async def delete_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)):
|
||||
await check_calendar_permission(request, user)
|
||||
|
||||
# Block deletion of the virtual Scheduled Tasks calendar
|
||||
if calendar_id == SCHEDULED_TASKS_CALENDAR_ID:
|
||||
raise HTTPException(status_code=400, detail='System calendars cannot be deleted')
|
||||
|
||||
cal = await _check_calendar_access(calendar_id, user, 'write')
|
||||
|
||||
# Only owner/admin can delete
|
||||
if cal.user_id != user.id and user.role != 'admin':
|
||||
raise HTTPException(status_code=403, detail='Only owner can delete calendar')
|
||||
|
||||
# Block deletion of default calendar
|
||||
if cal.is_default:
|
||||
raise HTTPException(status_code=400, detail='Default calendar cannot be deleted')
|
||||
|
||||
result = await Calendars.delete_calendar_by_id(calendar_id)
|
||||
if not result:
|
||||
raise HTTPException(status_code=500, detail='Failed to delete')
|
||||
return {'status': True}
|
||||
|
||||
|
||||
@router.post('/{calendar_id}/default')
|
||||
async def set_default_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)):
|
||||
await check_calendar_permission(request, user)
|
||||
cal = await Calendars.set_default_calendar(user.id, calendar_id)
|
||||
if not cal:
|
||||
raise HTTPException(status_code=404, detail='Calendar not found')
|
||||
return cal
|
||||
|
|
@ -923,15 +923,13 @@ async def model_response_handler(request, channel, message, user, db=None):
|
|||
|
||||
thread_history = []
|
||||
images = []
|
||||
message_users = {}
|
||||
|
||||
# Batch fetch all users in a single query (fixes N+1 problem)
|
||||
user_ids = list({message.user_id for message in thread_messages})
|
||||
message_users = {user.id: user for user in await Users.get_users_by_user_ids(user_ids, db=db)}
|
||||
|
||||
for thread_message in thread_messages:
|
||||
message_user = None
|
||||
if thread_message.user_id not in message_users:
|
||||
message_user = await Users.get_user_by_id(thread_message.user_id, db=db)
|
||||
message_users[thread_message.user_id] = message_user
|
||||
else:
|
||||
message_user = message_users[thread_message.user_id]
|
||||
message_user = message_users.get(thread_message.user_id)
|
||||
|
||||
if thread_message.meta and thread_message.meta.get('model_id', None):
|
||||
# If the message was sent by a model, use the model name
|
||||
|
|
|
|||
|
|
@ -978,7 +978,7 @@ async def update_chat_message_by_id(
|
|||
|
||||
event_emitter = await get_event_emitter(
|
||||
{
|
||||
'user_id': user.id,
|
||||
'user_id': chat.user_id,
|
||||
'chat_id': id,
|
||||
'message_id': message_id,
|
||||
},
|
||||
|
|
@ -1032,7 +1032,7 @@ async def send_chat_message_event_by_id(
|
|||
|
||||
event_emitter = await get_event_emitter(
|
||||
{
|
||||
'user_id': user.id,
|
||||
'user_id': chat.user_id,
|
||||
'chat_id': id,
|
||||
'message_id': message_id,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import aiohttp
|
|||
|
||||
from typing import Optional
|
||||
|
||||
from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.config import get_config, save_config, async_save_config
|
||||
from open_webui.config import BannerModel
|
||||
|
|
@ -293,7 +293,7 @@ async def verify_terminal_server_connection(
|
|||
) as session:
|
||||
# Orchestrators expose a policies API; plain terminals don't.
|
||||
try:
|
||||
async with session.get(f'{base_url}/api/v1/policies', headers=headers) as resp:
|
||||
async with session.get(f'{base_url}/api/v1/policies', headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
|
||||
if resp.ok:
|
||||
return {'status': True, 'type': 'orchestrator'}
|
||||
except Exception:
|
||||
|
|
@ -301,7 +301,7 @@ async def verify_terminal_server_connection(
|
|||
|
||||
# Fall back to open-terminal config endpoint.
|
||||
try:
|
||||
async with session.get(f'{base_url}/api/config', headers=headers) as resp:
|
||||
async with session.get(f'{base_url}/api/config', headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
|
||||
if resp.ok:
|
||||
return {'status': True, 'type': 'terminal'}
|
||||
except Exception:
|
||||
|
|
@ -342,7 +342,7 @@ async def put_terminal_server_policy(
|
|||
timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT),
|
||||
) as session:
|
||||
policy_url = f'{base_url}/api/v1/policies/{form_data.policy_id}'
|
||||
async with session.put(policy_url, headers=headers, json=form_data.policy_data) as resp:
|
||||
async with session.put(policy_url, headers=headers, json=form_data.policy_data, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
|
||||
if resp.ok:
|
||||
return await resp.json()
|
||||
detail = await resp.text()
|
||||
|
|
@ -369,7 +369,7 @@ async def verify_tool_servers_config(request: Request, form_data: ToolServerConn
|
|||
trust_env=True,
|
||||
timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT),
|
||||
) as session:
|
||||
async with session.get(discovery_url) as oauth_server_metadata_response:
|
||||
async with session.get(discovery_url, ssl=AIOHTTP_CLIENT_SESSION_SSL) as oauth_server_metadata_response:
|
||||
if oauth_server_metadata_response.status == 200:
|
||||
try:
|
||||
oauth_server_metadata = OAuthMetadata.model_validate(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import aiohttp
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT
|
||||
from open_webui.models.functions import (
|
||||
FunctionForm,
|
||||
FunctionModel,
|
||||
|
|
@ -117,7 +117,7 @@ async def load_function_from_url(request: Request, form_data: LoadUrlForm, user=
|
|||
async with aiohttp.ClientSession(
|
||||
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
||||
) as session:
|
||||
async with session.get(url, headers={'Content-Type': 'application/json'}) as resp:
|
||||
async with session.get(url, headers={'Content-Type': 'application/json'}, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
|
||||
if resp.status != 200:
|
||||
raise HTTPException(status_code=resp.status, detail='Failed to fetch the function')
|
||||
data = await resp.text()
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -1296,6 +1296,7 @@ async def embeddings(request: Request, form_data: dict, user):
|
|||
headers=headers,
|
||||
cookies=cookies,
|
||||
timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT),
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
)
|
||||
|
||||
if 'text/event-stream' in r.headers.get('Content-Type', ''):
|
||||
|
|
|
|||
|
|
@ -94,9 +94,24 @@ async def process_pipeline_inlet_filter(request, payload, user, models):
|
|||
response.raise_for_status()
|
||||
payload = await response.json()
|
||||
except aiohttp.ClientResponseError as e:
|
||||
res = await response.json() if response.content_type == 'application/json' else {}
|
||||
if 'detail' in res:
|
||||
raise Exception(response.status, res['detail'])
|
||||
try:
|
||||
res = await response.json() if 'application/json' in response.content_type else {}
|
||||
if 'detail' in res:
|
||||
raise HTTPException(
|
||||
status_code=response.status,
|
||||
detail=res['detail'],
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
status_code=response.status,
|
||||
detail=e.message,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.exception(f'Connection error: {e}')
|
||||
|
||||
|
|
@ -146,9 +161,21 @@ async def process_pipeline_outlet_filter(request, payload, user, models):
|
|||
try:
|
||||
res = await response.json() if 'application/json' in response.content_type else {}
|
||||
if 'detail' in res:
|
||||
raise Exception(response.status, res)
|
||||
raise HTTPException(
|
||||
status_code=response.status,
|
||||
detail=res['detail'],
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
status_code=response.status,
|
||||
detail=e.message,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.exception(f'Connection error: {e}')
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from starlette.background import BackgroundTask
|
|||
|
||||
from open_webui.utils.auth import get_verified_user
|
||||
from open_webui.utils.access_control import has_connection_access
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.users import Users
|
||||
|
||||
|
|
@ -141,6 +142,7 @@ async def proxy_terminal(
|
|||
headers=headers,
|
||||
cookies=cookies,
|
||||
data=body or None,
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
)
|
||||
|
||||
upstream_content_type = upstream_response.headers.get('content-type', '')
|
||||
|
|
@ -279,7 +281,7 @@ async def ws_terminal(
|
|||
|
||||
session = aiohttp.ClientSession()
|
||||
try:
|
||||
async with session.ws_connect(upstream_url) as upstream:
|
||||
async with session.ws_connect(upstream_url, ssl=AIOHTTP_CLIENT_SESSION_SSL) as upstream:
|
||||
import asyncio
|
||||
import json as _json
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from typing import Optional
|
|||
import time
|
||||
import re
|
||||
import aiohttp
|
||||
from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT
|
||||
from open_webui.models.groups import Groups
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
|
|
@ -274,7 +274,7 @@ async def load_tool_from_url(request: Request, form_data: LoadUrlForm, user=Depe
|
|||
async with aiohttp.ClientSession(
|
||||
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
||||
) as session:
|
||||
async with session.get(url, headers={'Content-Type': 'application/json'}) as resp:
|
||||
async with session.get(url, headers={'Content-Type': 'application/json'}, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
|
||||
if resp.status != 200:
|
||||
raise HTTPException(status_code=resp.status, detail='Failed to fetch the tool')
|
||||
data = await resp.text()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from open_webui.models.auths import Auths
|
|||
from open_webui.models.oauth_sessions import OAuthSessions
|
||||
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.chats import Chats
|
||||
|
||||
from open_webui.models.users import (
|
||||
UserModel,
|
||||
UserGroupIdsModel,
|
||||
|
|
@ -414,18 +414,6 @@ class UserActiveResponse(UserStatus):
|
|||
|
||||
@router.get('/{user_id}', response_model=UserActiveResponse)
|
||||
async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
# Check if user_id is a shared chat
|
||||
# If it is, get the user_id from the chat
|
||||
if user_id.startswith('shared-'):
|
||||
chat_id = user_id.replace('shared-', '')
|
||||
chat = await Chats.get_chat_by_id(chat_id)
|
||||
if chat:
|
||||
user_id = chat.user_id
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.USER_NOT_FOUND,
|
||||
)
|
||||
|
||||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
if user:
|
||||
|
|
|
|||
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 |
|
|
@ -56,19 +56,30 @@ async def get_current_timestamp(
|
|||
"""
|
||||
Get the current Unix timestamp in seconds.
|
||||
|
||||
:return: JSON with current_timestamp (seconds) and current_iso (ISO format)
|
||||
:return: JSON with current_timestamp (seconds), current_iso (UTC ISO format), and user_local_iso (user's local time)
|
||||
"""
|
||||
try:
|
||||
import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
return json.dumps(
|
||||
{
|
||||
'current_timestamp': int(now.timestamp()),
|
||||
'current_iso': now.isoformat(),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
result = {
|
||||
'current_timestamp': int(now.timestamp()),
|
||||
'current_iso': now.isoformat(),
|
||||
}
|
||||
|
||||
# Include the user's local time if timezone is available
|
||||
tz_name = __user__.get('timezone') if __user__ else None
|
||||
if tz_name:
|
||||
try:
|
||||
user_tz = ZoneInfo(tz_name)
|
||||
user_now = now.astimezone(user_tz)
|
||||
result['user_local_iso'] = user_now.isoformat()
|
||||
result['user_timezone'] = tz_name
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
log.exception(f'get_current_timestamp error: {e}')
|
||||
return json.dumps({'error': str(e)})
|
||||
|
|
@ -110,15 +121,27 @@ async def calculate_timestamp(
|
|||
|
||||
adjusted_ts = int(adjusted.timestamp())
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
'current_timestamp': current_ts,
|
||||
'current_iso': now.isoformat(),
|
||||
'calculated_timestamp': adjusted_ts,
|
||||
'calculated_iso': adjusted.isoformat(),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
result = {
|
||||
'current_timestamp': current_ts,
|
||||
'current_iso': now.isoformat(),
|
||||
'calculated_timestamp': adjusted_ts,
|
||||
'calculated_iso': adjusted.isoformat(),
|
||||
}
|
||||
|
||||
# Include the user's local time if timezone is available
|
||||
tz_name = __user__.get('timezone') if __user__ else None
|
||||
if tz_name:
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
user_tz = ZoneInfo(tz_name)
|
||||
result['user_local_iso'] = now.astimezone(user_tz).isoformat()
|
||||
result['calculated_local_iso'] = adjusted.astimezone(user_tz).isoformat()
|
||||
result['user_timezone'] = tz_name
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
except ImportError:
|
||||
# Fallback without dateutil
|
||||
import datetime
|
||||
|
|
@ -128,15 +151,26 @@ async def calculate_timestamp(
|
|||
total_days = days_ago + (weeks_ago * 7) + (months_ago * 30) + (years_ago * 365)
|
||||
adjusted = now - datetime.timedelta(days=total_days)
|
||||
adjusted_ts = int(adjusted.timestamp())
|
||||
return json.dumps(
|
||||
{
|
||||
'current_timestamp': current_ts,
|
||||
'current_iso': now.isoformat(),
|
||||
'calculated_timestamp': adjusted_ts,
|
||||
'calculated_iso': adjusted.isoformat(),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
result = {
|
||||
'current_timestamp': current_ts,
|
||||
'current_iso': now.isoformat(),
|
||||
'calculated_timestamp': adjusted_ts,
|
||||
'calculated_iso': adjusted.isoformat(),
|
||||
}
|
||||
|
||||
tz_name = __user__.get('timezone') if __user__ else None
|
||||
if tz_name:
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
user_tz = ZoneInfo(tz_name)
|
||||
result['user_local_iso'] = now.astimezone(user_tz).isoformat()
|
||||
result['calculated_local_iso'] = adjusted.astimezone(user_tz).isoformat()
|
||||
result['user_timezone'] = tz_name
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
log.exception(f'calculate_timestamp error: {e}')
|
||||
return json.dumps({'error': str(e)})
|
||||
|
|
@ -2810,3 +2844,424 @@ async def delete_automation(
|
|||
except Exception as e:
|
||||
log.exception(f'delete_automation error: {e}')
|
||||
return json.dumps({'error': str(e)})
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CALENDAR TOOLS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def _get_user_tz(user_dict: dict):
|
||||
"""Get the user's timezone as a ZoneInfo, falling back to UTC."""
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
tz_name = None
|
||||
if user_dict:
|
||||
tz_name = user_dict.get('timezone')
|
||||
if tz_name:
|
||||
try:
|
||||
return ZoneInfo(tz_name)
|
||||
except Exception:
|
||||
pass
|
||||
return ZoneInfo('UTC')
|
||||
|
||||
|
||||
def _dt_to_ns(dt_str: str, tz) -> int:
|
||||
"""Convert a datetime string to nanoseconds since epoch, interpreting in the given timezone."""
|
||||
from datetime import datetime
|
||||
|
||||
dt = datetime.fromisoformat(dt_str)
|
||||
# If naive (no timezone info), localize to user's timezone
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=tz)
|
||||
return int(dt.timestamp() * 1_000) * 1_000_000
|
||||
|
||||
|
||||
def _ns_to_dt(ns: int, tz) -> str:
|
||||
"""Convert nanoseconds since epoch to a datetime string in the given timezone."""
|
||||
from datetime import datetime
|
||||
|
||||
seconds = ns / 1_000_000_000
|
||||
dt = datetime.fromtimestamp(seconds, tz=tz)
|
||||
return dt.strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
|
||||
def _event_to_dict(event, tz) -> dict:
|
||||
"""Convert a calendar event model to a human-friendly dict with local timestamps."""
|
||||
return {
|
||||
'id': event.id,
|
||||
'calendar_id': event.calendar_id,
|
||||
'title': event.title,
|
||||
'description': event.description or '',
|
||||
'start': _ns_to_dt(event.start_at, tz),
|
||||
'end': _ns_to_dt(event.end_at, tz) if event.end_at else None,
|
||||
'all_day': event.all_day,
|
||||
'location': event.location or '',
|
||||
'color': event.color,
|
||||
'is_cancelled': event.is_cancelled,
|
||||
}
|
||||
|
||||
|
||||
async def search_calendar_events(
|
||||
query: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
count: int = 10,
|
||||
__request__: Request = None,
|
||||
__user__: dict = None,
|
||||
) -> str:
|
||||
"""
|
||||
Search calendar events by text and/or date range.
|
||||
Returns matching events across all accessible calendars.
|
||||
|
||||
:param query: Search text to match against event title, description, or location (optional)
|
||||
:param start: Only return events starting at or after this datetime, e.g. "2026-04-20 00:00" (optional)
|
||||
:param end: Only return events starting before this datetime, e.g. "2026-04-27 00:00" (optional)
|
||||
:param count: Maximum number of events to return (default: 10)
|
||||
:return: JSON list of matching events with id, title, description, start, end, calendar_id, location
|
||||
"""
|
||||
if __request__ is None:
|
||||
return json.dumps({'error': 'Request context not available'})
|
||||
|
||||
if not __user__:
|
||||
return json.dumps({'error': 'User context not available'})
|
||||
|
||||
try:
|
||||
from open_webui.models.calendar import CalendarEvents
|
||||
|
||||
user_id = __user__.get('id')
|
||||
tz = _get_user_tz(__user__)
|
||||
|
||||
if isinstance(count, str):
|
||||
try:
|
||||
count = int(count)
|
||||
except ValueError:
|
||||
count = 10
|
||||
|
||||
if start or end:
|
||||
# Date range query — use get_events_by_range
|
||||
try:
|
||||
start_ns = _dt_to_ns(start, tz) if start else 0
|
||||
except (ValueError, TypeError) as e:
|
||||
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
|
||||
)
|
||||
except (ValueError, TypeError) as e:
|
||||
return json.dumps({'error': f'Invalid end datetime: {e}'})
|
||||
|
||||
items = await CalendarEvents.get_events_by_range(
|
||||
user_id=user_id,
|
||||
start=start_ns,
|
||||
end=end_ns,
|
||||
)
|
||||
|
||||
# Apply text filter if query is also provided
|
||||
if query:
|
||||
q = query.lower()
|
||||
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()
|
||||
]
|
||||
|
||||
events = [_event_to_dict(item, tz) for item in items[:count]]
|
||||
return json.dumps(
|
||||
{'events': events, 'total': len(items)},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
else:
|
||||
# Text-only search
|
||||
result = await CalendarEvents.search_events(
|
||||
user_id=user_id,
|
||||
query=query,
|
||||
skip=0,
|
||||
limit=count,
|
||||
)
|
||||
|
||||
events = [_event_to_dict(item, tz) for item in result.items]
|
||||
return json.dumps(
|
||||
{'events': events, 'total': result.total},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception(f'search_calendar_events error: {e}')
|
||||
return json.dumps({'error': str(e)})
|
||||
|
||||
|
||||
async def create_calendar_event(
|
||||
title: str,
|
||||
start: str,
|
||||
end: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
calendar_id: Optional[str] = None,
|
||||
all_day: bool = False,
|
||||
location: Optional[str] = None,
|
||||
__request__: Request = None,
|
||||
__user__: dict = None,
|
||||
) -> str:
|
||||
"""
|
||||
Create a new calendar event. If no calendar_id is provided, the event is
|
||||
added to the user's default calendar.
|
||||
|
||||
:param title: Event title
|
||||
:param start: Start datetime string in your local time (e.g. "2026-04-20 09:00" or "2026-04-20T09:00:00")
|
||||
:param end: End datetime string in your local time (optional, omit for point-in-time events)
|
||||
:param description: Event description (optional)
|
||||
:param calendar_id: Target calendar ID (optional, uses default calendar if omitted)
|
||||
:param all_day: Whether this is an all-day event (default: false)
|
||||
:param location: Event location (optional)
|
||||
:return: JSON with the created event details including id
|
||||
"""
|
||||
if __request__ is None:
|
||||
return json.dumps({'error': 'Request context not available'})
|
||||
|
||||
if not __user__:
|
||||
return json.dumps({'error': 'User context not available'})
|
||||
|
||||
try:
|
||||
from open_webui.models.calendar import Calendars, CalendarEvents, CalendarEventForm
|
||||
|
||||
user_id = __user__.get('id')
|
||||
|
||||
# Resolve calendar_id: use provided, or fall back to default
|
||||
if not calendar_id:
|
||||
calendars = await Calendars.get_calendars_by_user(user_id)
|
||||
default_cal = next((c for c in calendars if c.is_default), None)
|
||||
if not default_cal and calendars:
|
||||
default_cal = calendars[0]
|
||||
if not default_cal:
|
||||
return json.dumps({'error': 'No calendars found. Cannot create event.'})
|
||||
calendar_id = default_cal.id
|
||||
|
||||
# Verify access
|
||||
cal = await Calendars.get_calendar_by_id(calendar_id)
|
||||
if not cal:
|
||||
return json.dumps({'error': 'Calendar not found'})
|
||||
if cal.user_id != user_id and __user__.get('role') != 'admin':
|
||||
from open_webui.models.access_grants import AccessGrants
|
||||
from open_webui.models.groups import Groups
|
||||
|
||||
user_group_ids = [g.id for g in await Groups.get_groups_by_member_id(user_id)]
|
||||
if not await AccessGrants.has_access(
|
||||
user_id=user_id,
|
||||
resource_type='calendar',
|
||||
resource_id=cal.id,
|
||||
permission='write',
|
||||
user_group_ids=set(user_group_ids),
|
||||
):
|
||||
return json.dumps({'error': 'Access denied to this calendar'})
|
||||
|
||||
# Coerce boolean from LLM
|
||||
if isinstance(all_day, str):
|
||||
all_day = all_day.lower() in ('true', '1', 'yes')
|
||||
|
||||
# Convert datetime strings to nanoseconds using user's timezone
|
||||
tz = _get_user_tz(__user__)
|
||||
try:
|
||||
start_ns = _dt_to_ns(start, tz)
|
||||
except (ValueError, TypeError) as e:
|
||||
return json.dumps({'error': f'Invalid start datetime: {e}. Use format like "2026-04-20 09:00"'})
|
||||
|
||||
end_ns = None
|
||||
if end:
|
||||
try:
|
||||
end_ns = _dt_to_ns(end, tz)
|
||||
except (ValueError, TypeError) as e:
|
||||
return json.dumps({'error': f'Invalid end datetime: {e}. Use format like "2026-04-20 10:00"'})
|
||||
elif not all_day:
|
||||
# Default to 1 hour duration
|
||||
end_ns = start_ns + 3_600_000_000_000
|
||||
|
||||
form = CalendarEventForm(
|
||||
calendar_id=calendar_id,
|
||||
title=title,
|
||||
description=description,
|
||||
start_at=start_ns,
|
||||
end_at=end_ns,
|
||||
all_day=all_day,
|
||||
location=location,
|
||||
)
|
||||
|
||||
event = await CalendarEvents.insert_new_event(user_id, form)
|
||||
if not event:
|
||||
return json.dumps({'error': 'Failed to create event'})
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
'status': 'success',
|
||||
**_event_to_dict(event, tz),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception(f'create_calendar_event error: {e}')
|
||||
return json.dumps({'error': str(e)})
|
||||
|
||||
|
||||
async def update_calendar_event(
|
||||
event_id: str,
|
||||
title: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
all_day: Optional[bool] = None,
|
||||
location: Optional[str] = None,
|
||||
is_cancelled: Optional[bool] = None,
|
||||
__request__: Request = None,
|
||||
__user__: dict = None,
|
||||
) -> str:
|
||||
"""
|
||||
Update an existing calendar event. Only provided fields are changed;
|
||||
omitted fields stay the same.
|
||||
|
||||
:param event_id: The ID of the event to update
|
||||
:param title: New event title (optional)
|
||||
:param description: New event description (optional)
|
||||
:param start: New start datetime string in your local time, e.g. "2026-04-20 09:00" (optional)
|
||||
:param end: New end datetime string in your local time (optional)
|
||||
:param all_day: Whether this is an all-day event (optional)
|
||||
:param location: New event location (optional)
|
||||
:param is_cancelled: Set to true to cancel the event (optional)
|
||||
:return: JSON with the updated event details
|
||||
"""
|
||||
if __request__ is None:
|
||||
return json.dumps({'error': 'Request context not available'})
|
||||
|
||||
if not __user__:
|
||||
return json.dumps({'error': 'User context not available'})
|
||||
|
||||
try:
|
||||
from open_webui.models.calendar import Calendars, CalendarEvents, CalendarEventUpdateForm
|
||||
from open_webui.models.access_grants import AccessGrants
|
||||
from open_webui.models.groups import Groups
|
||||
|
||||
user_id = __user__.get('id')
|
||||
|
||||
event = await CalendarEvents.get_event_by_id(event_id)
|
||||
if not event:
|
||||
return json.dumps({'error': 'Event not found'})
|
||||
|
||||
# Check write access to the event's calendar
|
||||
cal = await Calendars.get_calendar_by_id(event.calendar_id)
|
||||
if cal and cal.user_id != user_id and __user__.get('role') != 'admin':
|
||||
user_group_ids = [g.id for g in await Groups.get_groups_by_member_id(user_id)]
|
||||
if not await AccessGrants.has_access(
|
||||
user_id=user_id,
|
||||
resource_type='calendar',
|
||||
resource_id=cal.id,
|
||||
permission='write',
|
||||
user_group_ids=set(user_group_ids),
|
||||
):
|
||||
return json.dumps({'error': 'Access denied'})
|
||||
|
||||
# Coerce boolean strings from LLM
|
||||
if isinstance(all_day, str):
|
||||
all_day = all_day.lower() in ('true', '1', 'yes')
|
||||
if isinstance(is_cancelled, str):
|
||||
is_cancelled = is_cancelled.lower() in ('true', '1', 'yes')
|
||||
|
||||
# Convert datetime strings to nanoseconds using user's timezone
|
||||
tz = _get_user_tz(__user__)
|
||||
start_ns = None
|
||||
if start is not None:
|
||||
try:
|
||||
start_ns = _dt_to_ns(start, tz)
|
||||
except (ValueError, TypeError) as e:
|
||||
return json.dumps({'error': f'Invalid start datetime: {e}'})
|
||||
|
||||
end_ns = None
|
||||
if end is not None:
|
||||
try:
|
||||
end_ns = _dt_to_ns(end, tz)
|
||||
except (ValueError, TypeError) as e:
|
||||
return json.dumps({'error': f'Invalid end datetime: {e}'})
|
||||
|
||||
form = CalendarEventUpdateForm(
|
||||
title=title,
|
||||
description=description,
|
||||
start_at=start_ns,
|
||||
end_at=end_ns,
|
||||
all_day=all_day,
|
||||
location=location,
|
||||
is_cancelled=is_cancelled,
|
||||
)
|
||||
|
||||
updated = await CalendarEvents.update_event_by_id(event_id, form)
|
||||
if not updated:
|
||||
return json.dumps({'error': 'Failed to update event'})
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
'status': 'success',
|
||||
**_event_to_dict(updated, tz),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception(f'update_calendar_event error: {e}')
|
||||
return json.dumps({'error': str(e)})
|
||||
|
||||
|
||||
async def delete_calendar_event(
|
||||
event_id: str,
|
||||
__request__: Request = None,
|
||||
__user__: dict = None,
|
||||
) -> str:
|
||||
"""
|
||||
Delete a calendar event permanently.
|
||||
|
||||
:param event_id: The ID of the event to delete
|
||||
:return: JSON confirming the event was deleted
|
||||
"""
|
||||
if __request__ is None:
|
||||
return json.dumps({'error': 'Request context not available'})
|
||||
|
||||
if not __user__:
|
||||
return json.dumps({'error': 'User context not available'})
|
||||
|
||||
try:
|
||||
from open_webui.models.calendar import Calendars, CalendarEvents
|
||||
from open_webui.models.access_grants import AccessGrants
|
||||
from open_webui.models.groups import Groups
|
||||
|
||||
user_id = __user__.get('id')
|
||||
|
||||
event = await CalendarEvents.get_event_by_id(event_id)
|
||||
if not event:
|
||||
return json.dumps({'error': 'Event not found'})
|
||||
|
||||
# Check write access
|
||||
cal = await Calendars.get_calendar_by_id(event.calendar_id)
|
||||
if cal and cal.user_id != user_id and __user__.get('role') != 'admin':
|
||||
user_group_ids = [g.id for g in await Groups.get_groups_by_member_id(user_id)]
|
||||
if not await AccessGrants.has_access(
|
||||
user_id=user_id,
|
||||
resource_type='calendar',
|
||||
resource_id=cal.id,
|
||||
permission='write',
|
||||
user_group_ids=set(user_group_ids),
|
||||
):
|
||||
return json.dumps({'error': 'Access denied'})
|
||||
|
||||
title = event.title
|
||||
result = await CalendarEvents.delete_event_by_id(event_id)
|
||||
if not result:
|
||||
return json.dumps({'error': 'Failed to delete event'})
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
'status': 'success',
|
||||
'message': f'Event "{title}" deleted',
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception(f'delete_calendar_event error: {e}')
|
||||
return json.dumps({'error': str(e)})
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
"""
|
||||
Automation utilities.
|
||||
Automation utilities and unified scheduler.
|
||||
|
||||
RRULE helpers, worker loop, and execution logic.
|
||||
RRULE helpers, scheduler worker loop, and execution logic.
|
||||
Follows the utils/<feature>.py pattern (cf. utils/channels.py, utils/task.py).
|
||||
|
||||
The scheduler_worker_loop handles all time-based background work:
|
||||
- Automation execution (claim_due → execute)
|
||||
- Calendar event alerts (upcoming events → socket + webhook notifications)
|
||||
|
||||
Environment:
|
||||
AUTOMATION_POLL_INTERVAL – seconds between polls (default: 10)
|
||||
SCHEDULER_POLL_INTERVAL – seconds between polls (default: 10)
|
||||
CALENDAR_ALERT_LOOKAHEAD_MINUTES – default alert window (default: 5)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
@ -31,7 +36,8 @@ from open_webui.internal.db import get_async_db
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
AUTOMATION_POLL_INTERVAL = int(os.getenv('AUTOMATION_POLL_INTERVAL', '10'))
|
||||
SCHEDULER_POLL_INTERVAL = int(os.getenv('SCHEDULER_POLL_INTERVAL', os.getenv('AUTOMATION_POLL_INTERVAL', '10')))
|
||||
CALENDAR_ALERT_LOOKAHEAD_MINUTES = int(os.getenv('CALENDAR_ALERT_LOOKAHEAD_MINUTES', '10'))
|
||||
|
||||
|
||||
####################
|
||||
|
|
@ -117,26 +123,49 @@ def rrule_interval_seconds(s: str) -> Optional[int]:
|
|||
############################
|
||||
|
||||
|
||||
# Keep the old name as an alias so any stale imports still work.
|
||||
async def automation_worker_loop(app) -> None:
|
||||
"""Poll for due automations, claim, fire-and-forget execute.
|
||||
"""Deprecated alias — use scheduler_worker_loop."""
|
||||
await scheduler_worker_loop(app)
|
||||
|
||||
|
||||
async def scheduler_worker_loop(app) -> None:
|
||||
"""Unified background scheduler for all time-based work.
|
||||
|
||||
Handles:
|
||||
1. Automation execution (ENABLE_AUTOMATIONS)
|
||||
2. Calendar event alerts (ENABLE_CALENDAR)
|
||||
|
||||
Runs on every instance. Poll interval is configurable via
|
||||
AUTOMATION_POLL_INTERVAL env var (default: 10 seconds).
|
||||
SCHEDULER_POLL_INTERVAL env var (default: 10 seconds).
|
||||
"""
|
||||
log.info(f'Automation worker started (poll interval: {AUTOMATION_POLL_INTERVAL}s)')
|
||||
log.info(f'Scheduler worker started (poll interval: {SCHEDULER_POLL_INTERVAL}s)')
|
||||
while True:
|
||||
try:
|
||||
async with get_async_db() as db:
|
||||
batch = await Automations.claim_due(int(time.time_ns()), limit=10, db=db)
|
||||
if batch:
|
||||
log.info(f'Claimed {len(batch)} due automation(s)')
|
||||
for automation in batch:
|
||||
asyncio.create_task(execute_automation(app, automation))
|
||||
# ── Automations ──
|
||||
if getattr(app.state.config, 'ENABLE_AUTOMATIONS', False):
|
||||
try:
|
||||
async with get_async_db() as db:
|
||||
batch = await Automations.claim_due(int(time.time_ns()), limit=10, db=db)
|
||||
if batch:
|
||||
log.info(f'Claimed {len(batch)} due automation(s)')
|
||||
for automation in batch:
|
||||
asyncio.create_task(execute_automation(app, automation))
|
||||
except Exception:
|
||||
log.exception('Scheduler: automation error')
|
||||
|
||||
# ── Calendar Alerts ──
|
||||
if getattr(app.state.config, 'ENABLE_CALENDAR', False):
|
||||
try:
|
||||
await _check_calendar_alerts(app)
|
||||
except Exception:
|
||||
log.exception('Scheduler: calendar alert error')
|
||||
|
||||
except Exception:
|
||||
log.exception('Automation worker error')
|
||||
log.exception('Scheduler worker error')
|
||||
|
||||
# Jitter to spread load across instances
|
||||
await asyncio.sleep(AUTOMATION_POLL_INTERVAL + random.uniform(0, 2))
|
||||
await asyncio.sleep(SCHEDULER_POLL_INTERVAL + random.uniform(0, 2))
|
||||
|
||||
|
||||
##########################
|
||||
|
|
@ -243,6 +272,7 @@ async def _set_terminal_cwd(app, server_id: str, user, cwd: str, chat_id: str) -
|
|||
handled correctly — same path the frontend uses.
|
||||
"""
|
||||
import aiohttp
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
|
||||
|
||||
connections = getattr(getattr(app, 'state', None), 'config', None)
|
||||
if connections is None:
|
||||
|
|
@ -278,6 +308,7 @@ async def _set_terminal_cwd(app, server_id: str, user, cwd: str, chat_id: str) -
|
|||
target_url,
|
||||
json={'path': cwd},
|
||||
headers=headers,
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
) as resp:
|
||||
if resp.status != 200:
|
||||
body = await resp.text()
|
||||
|
|
@ -429,6 +460,98 @@ async def execute_automation(app, automation: AutomationModel) -> None:
|
|||
####################
|
||||
|
||||
|
||||
async def _check_calendar_alerts(app) -> None:
|
||||
"""Check for upcoming calendar events and send alert notifications.
|
||||
|
||||
De-duplication is DB-backed via meta.alerted_at — survives restarts
|
||||
and works across multiple instances.
|
||||
"""
|
||||
from open_webui.models.calendar import CalendarEvents, CalendarEventUpdateForm
|
||||
from open_webui.socket.main import sio
|
||||
|
||||
now_ns = int(time.time_ns())
|
||||
default_lookahead_ns = CALENDAR_ALERT_LOOKAHEAD_MINUTES * 60 * 1_000_000_000
|
||||
|
||||
async with get_async_db() as db:
|
||||
upcoming = await CalendarEvents.get_upcoming_events(now_ns, default_lookahead_ns, db=db)
|
||||
|
||||
if not upcoming:
|
||||
return
|
||||
|
||||
for event, user_tz in upcoming:
|
||||
# Skip if already alerted for this start time
|
||||
if event.meta and event.meta.get('alerted_at'):
|
||||
continue
|
||||
|
||||
# Compute minutes until event starts
|
||||
minutes_until = max(0, int((event.start_at - now_ns) / (60 * 1_000_000_000)))
|
||||
|
||||
alert_data = {
|
||||
'event_id': event.id,
|
||||
'title': event.title,
|
||||
'description': event.description or '',
|
||||
'start_at': event.start_at,
|
||||
'minutes_until': minutes_until,
|
||||
'calendar_id': event.calendar_id,
|
||||
'location': event.location or '',
|
||||
}
|
||||
|
||||
await sio.emit(
|
||||
'events',
|
||||
{
|
||||
'data': {
|
||||
'type': 'calendar:alert',
|
||||
'data': alert_data,
|
||||
},
|
||||
},
|
||||
room=f'user:{event.user_id}',
|
||||
)
|
||||
|
||||
# Mark as alerted in DB so it survives restarts / multi-instance
|
||||
try:
|
||||
await CalendarEvents.update_event_by_id(
|
||||
event.id,
|
||||
CalendarEventUpdateForm(meta={'alerted_at': now_ns}),
|
||||
)
|
||||
except Exception:
|
||||
log.debug(f'Failed to mark event {event.id} as alerted', exc_info=True)
|
||||
|
||||
# Send webhook notification if user has one configured
|
||||
try:
|
||||
webui_name = getattr(app.state, 'WEBUI_NAME', 'Open WebUI')
|
||||
enable_user_webhooks = getattr(app.state.config, 'ENABLE_USER_WEBHOOKS', False)
|
||||
|
||||
if enable_user_webhooks:
|
||||
user = await Users.get_user_by_id(event.user_id)
|
||||
if user and user.settings:
|
||||
webhook_url = (
|
||||
user.settings.get('ui', {}).get('notifications', {}).get('webhook_url', None)
|
||||
if isinstance(user.settings, dict)
|
||||
else getattr(getattr(user.settings, 'ui', None), 'get', lambda *a: None)(
|
||||
'notifications', {}
|
||||
).get('webhook_url', None)
|
||||
if hasattr(user.settings, 'ui')
|
||||
else None
|
||||
)
|
||||
if webhook_url:
|
||||
from open_webui.utils.webhook import post_webhook
|
||||
|
||||
time_str = f'in {minutes_until} min' if minutes_until > 0 else 'now'
|
||||
await post_webhook(
|
||||
webui_name,
|
||||
webhook_url,
|
||||
f'{event.title} — starting {time_str}',
|
||||
{
|
||||
'action': 'calendar_alert',
|
||||
'title': event.title,
|
||||
'minutes_until': minutes_until,
|
||||
'event_id': event.id,
|
||||
},
|
||||
)
|
||||
except Exception:
|
||||
log.debug(f'Failed to send webhook for calendar alert {event.id}', exc_info=True)
|
||||
|
||||
|
||||
async def _record_run(
|
||||
automation_id: str,
|
||||
status: str,
|
||||
|
|
|
|||
83
backend/open_webui/utils/calendar.py
Normal file
83
backend/open_webui/utils/calendar.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
"""
|
||||
Calendar utilities.
|
||||
|
||||
RRULE expansion reusing the automation infra.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from open_webui.utils.automations import _parse_rule
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def expand_recurring_event(
|
||||
event_dict: dict,
|
||||
range_start_ns: int,
|
||||
range_end_ns: int,
|
||||
tz: Optional[str] = None,
|
||||
max_instances: int = 5000,
|
||||
) -> list[dict]:
|
||||
"""Expand a recurring event into individual instances within a date range.
|
||||
|
||||
Takes an event dict (from CalendarEventModel.model_dump()) and produces
|
||||
one dict per occurrence, with adjusted start_at / end_at.
|
||||
"""
|
||||
from dateutil.rrule import rrulestr
|
||||
|
||||
rrule_str = event_dict.get('rrule')
|
||||
if not rrule_str:
|
||||
return [event_dict]
|
||||
|
||||
range_start_dt = datetime.fromtimestamp(range_start_ns / 1_000_000_000)
|
||||
range_end_dt = datetime.fromtimestamp(range_end_ns / 1_000_000_000)
|
||||
scan_start = range_start_dt - timedelta(days=1)
|
||||
|
||||
try:
|
||||
# Parse with dtstart near the range so we never iterate from epoch
|
||||
rule = rrulestr(rrule_str, dtstart=scan_start, ignoretz=True)
|
||||
except Exception:
|
||||
log.warning(f'Failed to parse RRULE for event {event_dict.get("id")}: {rrule_str}')
|
||||
return [event_dict]
|
||||
|
||||
original_start_ns = event_dict['start_at']
|
||||
original_end_ns = event_dict.get('end_at')
|
||||
duration_ns = (original_end_ns - original_start_ns) if original_end_ns else None
|
||||
|
||||
instances = []
|
||||
dt = rule.after(scan_start, inc=True)
|
||||
|
||||
while dt and dt < range_end_dt and len(instances) < max_instances:
|
||||
if tz:
|
||||
try:
|
||||
dt_tz = dt.replace(tzinfo=ZoneInfo(tz))
|
||||
instance_start_ns = int(dt_tz.timestamp() * 1_000_000_000)
|
||||
except Exception:
|
||||
instance_start_ns = int(dt.timestamp() * 1_000_000_000)
|
||||
else:
|
||||
instance_start_ns = int(dt.timestamp() * 1_000_000_000)
|
||||
|
||||
if instance_start_ns >= range_start_ns:
|
||||
instance = {
|
||||
**event_dict,
|
||||
'start_at': instance_start_ns,
|
||||
'end_at': (instance_start_ns + duration_ns) if duration_ns else None,
|
||||
'instance_id': f'{event_dict["id"]}_{instance_start_ns}',
|
||||
}
|
||||
instances.append(instance)
|
||||
|
||||
dt = rule.after(dt)
|
||||
|
||||
return instances
|
||||
|
||||
|
||||
def ns_from_date(year: int, month: int, day: int, tz: Optional[str] = None) -> int:
|
||||
"""Create epoch nanoseconds from a date."""
|
||||
if tz:
|
||||
dt = datetime(year, month, day, tzinfo=ZoneInfo(tz))
|
||||
else:
|
||||
dt = datetime(year, month, day)
|
||||
return int(dt.timestamp() * 1_000_000_000)
|
||||
|
|
@ -10,7 +10,7 @@ import json
|
|||
import uuid
|
||||
import asyncio
|
||||
|
||||
from fastapi import Request, status
|
||||
from fastapi import HTTPException, Request, status
|
||||
from starlette.responses import Response, StreamingResponse, JSONResponse
|
||||
|
||||
|
||||
|
|
@ -328,6 +328,8 @@ async def chat_completed(request: Request, form_data: dict, user: Any):
|
|||
|
||||
try:
|
||||
data = await process_pipeline_outlet_filter(request, data, user, models)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise Exception(f'Error: {e}')
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,29 @@ import base64
|
|||
import io
|
||||
import re
|
||||
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK
|
||||
from open_webui.utils.session_pool import get_session
|
||||
|
||||
BASE64_IMAGE_URL_PREFIX = re.compile(r'data:image/\w+;base64,', re.IGNORECASE)
|
||||
MARKDOWN_IMAGE_URL_PATTERN = re.compile(r'!\[(.*?)\]\((.+?)\)', re.IGNORECASE)
|
||||
|
||||
# Extension-based MIME fallback, only used when ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK is True.
|
||||
_IMAGE_MIME_FALLBACK = {
|
||||
".webp": "image/webp",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".svg": "image/svg+xml",
|
||||
".bmp": "image/bmp",
|
||||
".tiff": "image/tiff",
|
||||
".tif": "image/tiff",
|
||||
".ico": "image/x-icon",
|
||||
".heic": "image/heic",
|
||||
".heif": "image/heif",
|
||||
".avif": "image/avif",
|
||||
}
|
||||
|
||||
|
||||
async def get_image_base64_from_url(url: str) -> Optional[str]:
|
||||
try:
|
||||
|
|
@ -58,7 +75,14 @@ async def get_image_base64_from_url(url: str) -> Optional[str]:
|
|||
if file_path.is_file():
|
||||
with open(file_path, 'rb') as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
||||
content_type, _ = mimetypes.guess_type(file_path.name)
|
||||
content_type = (
|
||||
mimetypes.guess_type(file_path.name)[0]
|
||||
or (file.meta or {}).get('content_type')
|
||||
)
|
||||
if not content_type and ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK:
|
||||
content_type = _IMAGE_MIME_FALLBACK.get(file_path.suffix.lower())
|
||||
if not content_type:
|
||||
return None
|
||||
return f'data:{content_type};base64,{encoded_string}'
|
||||
else:
|
||||
return None
|
||||
|
|
@ -178,11 +202,16 @@ async def get_image_base64_from_file_id(id: str) -> Optional[str]:
|
|||
|
||||
# Check if the file already exists in the cache
|
||||
if file_path.is_file():
|
||||
import base64
|
||||
|
||||
with open(file_path, 'rb') as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
||||
content_type, _ = mimetypes.guess_type(file_path.name)
|
||||
content_type = (
|
||||
mimetypes.guess_type(file_path.name)[0]
|
||||
or (file.meta or {}).get('content_type')
|
||||
)
|
||||
if not content_type and ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK:
|
||||
content_type = _IMAGE_MIME_FALLBACK.get(file_path.suffix.lower())
|
||||
if not content_type:
|
||||
return None
|
||||
return f'data:{content_type};base64,{encoded_string}'
|
||||
else:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ def set_security_headers() -> Dict[str, str]:
|
|||
- x-frame-options
|
||||
- x-permitted-cross-domain-policies
|
||||
- content-security-policy
|
||||
- content-security-policy-report-only
|
||||
- cross-origin-embedder-policy
|
||||
- cross-origin-opener-policy
|
||||
- cross-origin-resource-policy
|
||||
- reporting-endpoints
|
||||
|
||||
Each environment variable is associated with a specific setter function
|
||||
|
|
@ -48,6 +52,10 @@ def set_security_headers() -> Dict[str, str]:
|
|||
'XFRAME_OPTIONS': set_xframe,
|
||||
'XPERMITTED_CROSS_DOMAIN_POLICIES': set_xpermitted_cross_domain_policies,
|
||||
'CONTENT_SECURITY_POLICY': set_content_security_policy,
|
||||
'CONTENT_SECURITY_POLICY_REPORT_ONLY': set_content_security_policy_report_only,
|
||||
'CROSS_ORIGIN_EMBEDDER_POLICY': set_cross_origin_embedder_policy,
|
||||
'CROSS_ORIGIN_OPENER_POLICY': set_cross_origin_opener_policy,
|
||||
'CROSS_ORIGIN_RESOURCE_POLICY': set_cross_origin_resource_policy,
|
||||
'REPORTING_ENDPOINTS': set_reporting_endpoints,
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +143,38 @@ def set_content_security_policy(value: str):
|
|||
return {'Content-Security-Policy': value}
|
||||
|
||||
|
||||
# Set Content-Security-Policy-Report-Only response header
|
||||
def set_content_security_policy_report_only(value: str):
|
||||
return {'Content-Security-Policy-Report-Only': value}
|
||||
|
||||
|
||||
# Set Cross-Origin-Embedder-Policy response header
|
||||
def set_cross_origin_embedder_policy(value: str):
|
||||
pattern = r'^(unsafe-none|require-corp|credentialless)$'
|
||||
match = re.match(pattern, value, re.IGNORECASE)
|
||||
if not match:
|
||||
value = 'require-corp'
|
||||
return {'Cross-Origin-Embedder-Policy': value}
|
||||
|
||||
|
||||
# Set Cross-Origin-Opener-Policy response header
|
||||
def set_cross_origin_opener_policy(value: str):
|
||||
pattern = r'^(unsafe-none|same-origin-allow-popups|same-origin)$'
|
||||
match = re.match(pattern, value, re.IGNORECASE)
|
||||
if not match:
|
||||
value = 'same-origin'
|
||||
return {'Cross-Origin-Opener-Policy': value}
|
||||
|
||||
|
||||
# Set Cross-Origin-Resource-Policy response header
|
||||
def set_cross_origin_resource_policy(value: str):
|
||||
pattern = r'^(same-site|same-origin|cross-origin)$'
|
||||
match = re.match(pattern, value, re.IGNORECASE)
|
||||
if not match:
|
||||
value = 'same-origin'
|
||||
return {'Cross-Origin-Resource-Policy': value}
|
||||
|
||||
|
||||
# Set Reporting-Endpoints response header
|
||||
def set_reporting_endpoints(value: str):
|
||||
return {'Reporting-Endpoints': value}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ from open_webui.utils.plugin import load_tool_module_by_id
|
|||
from open_webui.utils.access_control import has_access, has_connection_access
|
||||
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
|
||||
from open_webui.env import (
|
||||
AIOHTTP_CLIENT_SESSION_SSL,
|
||||
AIOHTTP_CLIENT_TIMEOUT,
|
||||
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER,
|
||||
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA,
|
||||
|
|
@ -93,6 +94,10 @@ from open_webui.tools.builtin import (
|
|||
list_automations,
|
||||
toggle_automation,
|
||||
delete_automation,
|
||||
search_calendar_events,
|
||||
create_calendar_event,
|
||||
update_calendar_event,
|
||||
delete_calendar_event,
|
||||
)
|
||||
|
||||
import copy
|
||||
|
|
@ -547,11 +552,25 @@ async def get_builtin_tools(
|
|||
builtin_functions.extend([create_tasks, update_task])
|
||||
|
||||
# Automation tools - create and manage scheduled automations from chat
|
||||
if is_builtin_tool_enabled('automations') and await has_user_permission('automations'):
|
||||
if (
|
||||
is_builtin_tool_enabled('automations')
|
||||
and getattr(request.app.state.config, 'ENABLE_AUTOMATIONS', False)
|
||||
and await has_user_permission('automations')
|
||||
):
|
||||
builtin_functions.extend(
|
||||
[create_automation, update_automation, list_automations, toggle_automation, delete_automation]
|
||||
)
|
||||
|
||||
# Calendar tools - search/create/update/delete events
|
||||
if (
|
||||
is_builtin_tool_enabled('calendar')
|
||||
and getattr(request.app.state.config, 'ENABLE_CALENDAR', False)
|
||||
and await has_user_permission('calendar')
|
||||
):
|
||||
builtin_functions.extend(
|
||||
[search_calendar_events, create_calendar_event, update_calendar_event, delete_calendar_event]
|
||||
)
|
||||
|
||||
for func in builtin_functions:
|
||||
callable = await get_async_tool_function_and_apply_extra_params(
|
||||
func,
|
||||
|
|
@ -889,7 +908,7 @@ async def get_terminal_cwd(
|
|||
timeout=aiohttp.ClientTimeout(total=5),
|
||||
trust_env=True,
|
||||
) as session:
|
||||
async with session.get(cwd_url, headers=headers, cookies=cookies or {}) as resp:
|
||||
async with session.get(cwd_url, headers=headers, cookies=cookies or {}, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
return data.get('cwd')
|
||||
|
|
@ -916,7 +935,7 @@ async def get_terminal_system_prompt(
|
|||
trust_env=True,
|
||||
) as session:
|
||||
# 1. Check feature flag
|
||||
async with session.get(f'{base}/api/config') as resp:
|
||||
async with session.get(f'{base}/api/config', ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
|
||||
if resp.status != 200:
|
||||
return None
|
||||
config = await resp.json()
|
||||
|
|
@ -924,7 +943,7 @@ async def get_terminal_system_prompt(
|
|||
return None
|
||||
|
||||
# 2. Fetch system prompt
|
||||
async with session.get(f'{base}/system', headers=headers, cookies=cookies or {}) as resp:
|
||||
async with session.get(f'{base}/system', headers=headers, cookies=cookies or {}, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
return data.get('prompt')
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import logging
|
|||
import aiohttp
|
||||
|
||||
from open_webui.config import WEBUI_FAVICON_URL
|
||||
from open_webui.env import AIOHTTP_CLIENT_TIMEOUT, VERSION
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT, VERSION
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ async def post_webhook(name: str, url: str, message: str, event_data: dict) -> b
|
|||
async with aiohttp.ClientSession(
|
||||
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
||||
) as session:
|
||||
async with session.post(url, json=payload) as r:
|
||||
async with session.post(url, json=payload, ssl=AIOHTTP_CLIENT_SESSION_SSL) as r:
|
||||
r_text = await r.text()
|
||||
r.raise_for_status()
|
||||
log.debug(f'r.text: {r_text}')
|
||||
|
|
|
|||
458
src/lib/apis/calendar/index.ts
Normal file
458
src/lib/apis/calendar/index.ts
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
export type CalendarModel = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
name: string;
|
||||
color: string | null;
|
||||
is_default: boolean;
|
||||
is_system: boolean;
|
||||
data: Record<string, any> | null;
|
||||
meta: Record<string, any> | null;
|
||||
access_grants: any[];
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
};
|
||||
|
||||
export type CalendarEventAttendeeModel = {
|
||||
id: string;
|
||||
event_id: string;
|
||||
user_id: string;
|
||||
status: string;
|
||||
meta: Record<string, any> | null;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
};
|
||||
|
||||
export type CalendarEventModel = {
|
||||
id: string;
|
||||
calendar_id: string;
|
||||
user_id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
start_at: number;
|
||||
end_at: number | null;
|
||||
all_day: boolean;
|
||||
rrule: string | null;
|
||||
color: string | null;
|
||||
location: string | null;
|
||||
data: Record<string, any> | null;
|
||||
meta: Record<string, any> | null;
|
||||
is_cancelled: boolean;
|
||||
attendees: CalendarEventAttendeeModel[];
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
// Set by expand_recurring_event for recurring instances
|
||||
instance_id?: string;
|
||||
};
|
||||
|
||||
export type CalendarEventForm = {
|
||||
calendar_id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
start_at: number;
|
||||
end_at?: number;
|
||||
all_day?: boolean;
|
||||
rrule?: string;
|
||||
color?: string;
|
||||
location?: string;
|
||||
data?: Record<string, any>;
|
||||
meta?: Record<string, any>;
|
||||
attendees?: { user_id: string; status?: string }[];
|
||||
};
|
||||
|
||||
export type CalendarForm = {
|
||||
name: string;
|
||||
color?: string;
|
||||
data?: Record<string, any>;
|
||||
meta?: Record<string, any>;
|
||||
access_grants?: { target_type: string; target_id: string; permission: string }[];
|
||||
};
|
||||
|
||||
// ── Calendars ─────────────────────────────────
|
||||
|
||||
export const getCalendars = async (token: string): Promise<CalendarModel[]> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const createCalendar = async (token: string, form: CalendarForm): Promise<CalendarModel> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(form)
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const updateCalendar = async (
|
||||
token: string,
|
||||
calendarId: string,
|
||||
form: Partial<CalendarForm>
|
||||
): Promise<CalendarModel> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/${calendarId}/update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(form)
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const deleteCalendar = async (token: string, calendarId: string): Promise<boolean> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/${calendarId}/delete`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res?.status ?? false;
|
||||
};
|
||||
|
||||
export const setDefaultCalendar = async (
|
||||
token: string,
|
||||
calendarId: string
|
||||
): Promise<CalendarModel> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/${calendarId}/default`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
// ── Events ─────────────────────────────────
|
||||
|
||||
export const getCalendarEvents = async (
|
||||
token: string,
|
||||
start: string,
|
||||
end: string,
|
||||
calendarIds?: string[]
|
||||
): Promise<CalendarEventModel[]> => {
|
||||
let error = null;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('start', start);
|
||||
params.append('end', end);
|
||||
if (calendarIds && calendarIds.length > 0) {
|
||||
params.append('calendar_ids', calendarIds.join(','));
|
||||
}
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/events?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const createCalendarEvent = async (
|
||||
token: string,
|
||||
form: CalendarEventForm
|
||||
): Promise<CalendarEventModel> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/events/create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(form)
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getCalendarEventById = async (
|
||||
token: string,
|
||||
eventId: string
|
||||
): Promise<CalendarEventModel> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/events/${eventId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const updateCalendarEvent = async (
|
||||
token: string,
|
||||
eventId: string,
|
||||
form: Partial<CalendarEventForm>
|
||||
): Promise<CalendarEventModel> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/events/${eventId}/update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(form)
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const deleteCalendarEvent = async (token: string, eventId: string): Promise<boolean> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/events/${eventId}/delete`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res?.status ?? false;
|
||||
};
|
||||
|
||||
export const rsvpCalendarEvent = async (
|
||||
token: string,
|
||||
eventId: string,
|
||||
status: string
|
||||
): Promise<{ status: boolean; rsvp: string }> => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/events/${eventId}/rsvp`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ status })
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const searchCalendarEvents = async (
|
||||
token: string,
|
||||
query: string | null,
|
||||
skip: number = 0,
|
||||
limit: number = 30
|
||||
): Promise<{ items: CalendarEventModel[]; total: number }> => {
|
||||
let error = null;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (query) params.append('query', query);
|
||||
params.append('skip', skip.toString());
|
||||
params.append('limit', limit.toString());
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/events/search?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
|
@ -740,6 +740,14 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Memories')} ({$i18n.t('Beta')})
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_MEMORIES} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Notes')} ({$i18n.t('Beta')})
|
||||
|
|
@ -758,10 +766,18 @@
|
|||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Memories')} ({$i18n.t('Beta')})
|
||||
{$i18n.t('Calendar')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_MEMORIES} />
|
||||
<Switch bind:state={adminConfig.ENABLE_CALENDAR} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Automations')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_AUTOMATIONS} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
|
|
|
|||
|
|
@ -916,6 +916,22 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex w-full justify-between my-1">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Calendar')}
|
||||
</div>
|
||||
<Switch bind:state={permissions.features.calendar} />
|
||||
</div>
|
||||
{#if defaultPermissions?.features?.calendar && !permissions.features.calendar}
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t('This is a default user permission and will remain enabled.')}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/30 dark:border-gray-850/30" />
|
||||
|
|
|
|||
32
src/lib/components/calendar/CalendarEventChip.svelte
Normal file
32
src/lib/components/calendar/CalendarEventChip.svelte
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { CalendarEventModel } from '$lib/apis/calendar';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
export let event: CalendarEventModel;
|
||||
export let calendarColor: string | null = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<Tooltip content="{event.title}{event.location ? ` · ${event.location}` : ''}">
|
||||
<button
|
||||
class="w-full text-left text-xs flex items-start gap-1.5 py-[1px] px-0.5 rounded-md
|
||||
{event.meta?.automation_id ? 'opacity-60' : ''}
|
||||
hover:bg-gray-50 dark:hover:bg-gray-800/50 transition truncate"
|
||||
on:click|stopPropagation={() => dispatch('click', event)}
|
||||
>
|
||||
<span
|
||||
class="shrink-0 size-[7px] rounded-full mt-[5px]"
|
||||
style="background-color: {event.color || calendarColor || '#3b82f6'};"
|
||||
></span>
|
||||
<span class="truncate">
|
||||
{#if !event.all_day}<span class="text-gray-500 dark:text-gray-400"
|
||||
>{new Date(event.start_at / 1_000_000)
|
||||
.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })
|
||||
.replace(' ', '')}</span
|
||||
>{/if}
|
||||
{event.title}
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
295
src/lib/components/calendar/CalendarEventModal.svelte
Normal file
295
src/lib/components/calendar/CalendarEventModal.svelte
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
|
||||
import type { CalendarModel, CalendarEventModel, CalendarEventForm } from '$lib/apis/calendar';
|
||||
import {
|
||||
createCalendarEvent,
|
||||
updateCalendarEvent,
|
||||
deleteCalendarEvent
|
||||
} from '$lib/apis/calendar';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let show = false;
|
||||
export let event: CalendarEventModel | null = null;
|
||||
export let calendars: CalendarModel[] = [];
|
||||
export let defaultCalendarId: string = '';
|
||||
export let defaultStartAt: number | null = null;
|
||||
|
||||
let title = '';
|
||||
let description = '';
|
||||
let calendarId = '';
|
||||
let startDate = '';
|
||||
let startTime = '';
|
||||
let endDate = '';
|
||||
let endTime = '';
|
||||
let allDay = false;
|
||||
let location = '';
|
||||
let alertMinutes: number = 10;
|
||||
let loading = false;
|
||||
let showDeleteConfirmDialog = false;
|
||||
|
||||
const NS = 1_000_000;
|
||||
|
||||
function nsToDateStr(ns: number): string {
|
||||
return new Date(ns / NS).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function nsToTimeStr(ns: number): string {
|
||||
return new Date(ns / NS).toTimeString().slice(0, 5);
|
||||
}
|
||||
|
||||
function dateTimeToNs(dateStr: string, timeStr: string): number {
|
||||
return new Date(`${dateStr}T${timeStr || '00:00'}`).getTime() * NS;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
if (event) {
|
||||
title = event.title;
|
||||
description = event.description || '';
|
||||
calendarId = event.calendar_id;
|
||||
startDate = nsToDateStr(event.start_at);
|
||||
startTime = nsToTimeStr(event.start_at);
|
||||
endDate = event.end_at ? nsToDateStr(event.end_at) : '';
|
||||
endTime = event.end_at ? nsToTimeStr(event.end_at) : '';
|
||||
allDay = event.all_day;
|
||||
location = event.location || '';
|
||||
alertMinutes = event.meta?.alert_minutes ?? 10;
|
||||
} else {
|
||||
title = '';
|
||||
description = '';
|
||||
calendarId = defaultCalendarId || (calendars.length > 0 ? calendars[0].id : '');
|
||||
if (defaultStartAt) {
|
||||
startDate = nsToDateStr(defaultStartAt);
|
||||
startTime = nsToTimeStr(defaultStartAt);
|
||||
const endNs = defaultStartAt + 60 * 60 * 1000 * NS;
|
||||
endDate = nsToDateStr(endNs);
|
||||
endTime = nsToTimeStr(endNs);
|
||||
} else {
|
||||
const now = new Date();
|
||||
startDate = now.toISOString().slice(0, 10);
|
||||
startTime = now.toTimeString().slice(0, 5);
|
||||
const later = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
endDate = later.toISOString().slice(0, 10);
|
||||
endTime = later.toTimeString().slice(0, 5);
|
||||
}
|
||||
allDay = false;
|
||||
location = '';
|
||||
alertMinutes = 10;
|
||||
}
|
||||
}
|
||||
|
||||
$: if (show) reset();
|
||||
|
||||
const submitHandler = async () => {
|
||||
if (!title.trim()) {
|
||||
toast.error($i18n.t('Title is required'));
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
const startNs = dateTimeToNs(startDate, allDay ? '00:00' : startTime);
|
||||
const endNs = endDate ? dateTimeToNs(endDate, allDay ? '23:59' : endTime) : undefined;
|
||||
|
||||
if (event && !event.meta?.automation_id) {
|
||||
const result = await updateCalendarEvent(localStorage.token, event.id, {
|
||||
calendar_id: calendarId,
|
||||
title: title.trim(),
|
||||
description: description.trim() || undefined,
|
||||
start_at: startNs,
|
||||
end_at: endNs,
|
||||
all_day: allDay,
|
||||
location: location.trim() || undefined,
|
||||
meta: { alert_minutes: alertMinutes }
|
||||
});
|
||||
if (result) {
|
||||
toast.success($i18n.t('Event updated'));
|
||||
dispatch('save', result);
|
||||
show = false;
|
||||
}
|
||||
} else {
|
||||
const form: CalendarEventForm = {
|
||||
calendar_id: calendarId,
|
||||
title: title.trim(),
|
||||
description: description.trim() || undefined,
|
||||
start_at: startNs,
|
||||
end_at: endNs,
|
||||
all_day: allDay,
|
||||
location: location.trim() || undefined,
|
||||
meta: { alert_minutes: alertMinutes }
|
||||
};
|
||||
const result = await createCalendarEvent(localStorage.token, form);
|
||||
if (result) {
|
||||
toast.success($i18n.t('Event created'));
|
||||
dispatch('save', result);
|
||||
show = false;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(`${err}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteHandler = async () => {
|
||||
if (!event || event.meta?.automation_id) return;
|
||||
loading = true;
|
||||
try {
|
||||
await deleteCalendarEvent(localStorage.token, event.id);
|
||||
toast.success($i18n.t('Event deleted'));
|
||||
dispatch('delete', event);
|
||||
show = false;
|
||||
} catch (err) {
|
||||
toast.error(`${err}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal size="md" bind:show>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between dark:text-gray-100 px-5 pt-4 pb-2">
|
||||
<input
|
||||
class="w-full text-lg bg-transparent outline-hidden font-primary placeholder:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="text"
|
||||
bind:value={title}
|
||||
placeholder={$i18n.t('Event title')}
|
||||
/>
|
||||
<button
|
||||
class="self-center shrink-0 ml-2"
|
||||
aria-label={$i18n.t('Close')}
|
||||
on:click={() => (show = false)}
|
||||
>
|
||||
<XMark className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Details -->
|
||||
<div class="px-5 pb-2 flex flex-col gap-3">
|
||||
<!-- Calendar -->
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-500">{$i18n.t('Calendar')}</div>
|
||||
<select
|
||||
class="w-full text-sm bg-transparent outline-hidden cursor-pointer"
|
||||
bind:value={calendarId}
|
||||
>
|
||||
{#each calendars.filter((c) => c.id !== '__scheduled_tasks__') as cal (cal.id)}
|
||||
<option value={cal.id}>{cal.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Date / Time -->
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-500">{$i18n.t('When')}</div>
|
||||
<div class="flex items-center gap-2 text-sm flex-wrap">
|
||||
<input type="date" class="bg-transparent outline-hidden" bind:value={startDate} />
|
||||
{#if !allDay}
|
||||
<input type="time" class="bg-transparent outline-hidden" bind:value={startTime} />
|
||||
<span class="text-gray-300 dark:text-gray-600">–</span>
|
||||
<input type="time" class="bg-transparent outline-hidden" bind:value={endTime} />
|
||||
{/if}
|
||||
<label class="flex items-center gap-1.5 cursor-pointer text-xs text-gray-400 ml-auto">
|
||||
<input type="checkbox" class="accent-blue-500" bind:checked={allDay} />
|
||||
{$i18n.t('All day')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-500">{$i18n.t('Location')}</div>
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700"
|
||||
placeholder={$i18n.t('Add location')}
|
||||
bind:value={location}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Reminder -->
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-500">{$i18n.t('Reminder')}</div>
|
||||
<select
|
||||
class="w-full text-sm bg-transparent outline-hidden cursor-pointer"
|
||||
bind:value={alertMinutes}
|
||||
>
|
||||
<option value={-1}>{$i18n.t('None')}</option>
|
||||
<option value={0}>{$i18n.t('At time of event')}</option>
|
||||
<option value={5}>{$i18n.t('5 minutes before')}</option>
|
||||
<option value={10}>{$i18n.t('10 minutes before')}</option>
|
||||
<option value={15}>{$i18n.t('15 minutes before')}</option>
|
||||
<option value={30}>{$i18n.t('30 minutes before')}</option>
|
||||
<option value={60}>{$i18n.t('1 hour before')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-gray-500">{$i18n.t('Description')}</div>
|
||||
<textarea
|
||||
class="w-full text-sm bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 resize-none min-h-[4rem]"
|
||||
placeholder={$i18n.t('Add description')}
|
||||
bind:value={description}
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom toolbar -->
|
||||
<div class="flex items-center justify-between px-4 pb-3.5 pt-1 gap-2">
|
||||
<div class="flex items-center gap-0.5 flex-1 min-w-0">
|
||||
{#if event && !event.meta?.automation_id}
|
||||
<button
|
||||
class="px-3 py-1 text-xs text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition"
|
||||
type="button"
|
||||
on:click={() => (showDeleteConfirmDialog = true)}
|
||||
disabled={loading}
|
||||
>
|
||||
{$i18n.t('Delete')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<button
|
||||
class="px-3 py-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 transition"
|
||||
type="button"
|
||||
on:click={() => (show = false)}
|
||||
>
|
||||
{$i18n.t('Cancel')}
|
||||
</button>
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex items-center gap-2 {loading
|
||||
? 'cursor-not-allowed'
|
||||
: ''}"
|
||||
on:click={submitHandler}
|
||||
type="button"
|
||||
disabled={loading}
|
||||
>
|
||||
{event && !event.meta?.automation_id ? $i18n.t('Save') : $i18n.t('Create')}
|
||||
{#if loading}
|
||||
<span class="shrink-0"><Spinner /></span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<DeleteConfirmDialog
|
||||
bind:show={showDeleteConfirmDialog}
|
||||
title={$i18n.t('Delete Event')}
|
||||
message={$i18n.t('This action cannot be undone. Do you wish to continue?')}
|
||||
on:confirm={deleteHandler}
|
||||
/>
|
||||
233
src/lib/components/calendar/CalendarSidebar.svelte
Normal file
233
src/lib/components/calendar/CalendarSidebar.svelte
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import type { CalendarModel } from '$lib/apis/calendar';
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let calendars: CalendarModel[] = [];
|
||||
export let visibleCalendarIds: Set<string> = new Set();
|
||||
export let currentDate: Date = new Date();
|
||||
export let onToggle: (id: string) => void = () => {};
|
||||
export let onCreateCalendar: () => void = () => {};
|
||||
export let onDeleteCalendar: (id: string) => void = () => {};
|
||||
export let onDateSelect: (date: Date) => void = () => {};
|
||||
|
||||
// Delete confirmation state
|
||||
let showDeleteConfirm = false;
|
||||
let deleteTargetCalendar: CalendarModel | null = null;
|
||||
|
||||
function isDeletable(cal: CalendarModel): boolean {
|
||||
return !cal.is_default && !cal.is_system;
|
||||
}
|
||||
|
||||
function handleDeleteClick(e: MouseEvent, cal: CalendarModel) {
|
||||
e.stopPropagation();
|
||||
deleteTargetCalendar = cal;
|
||||
showDeleteConfirm = true;
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
if (deleteTargetCalendar) {
|
||||
onDeleteCalendar(deleteTargetCalendar.id);
|
||||
}
|
||||
deleteTargetCalendar = null;
|
||||
}
|
||||
|
||||
// Mini calendar state
|
||||
$: miniMonth = currentDate.getMonth();
|
||||
$: miniYear = currentDate.getFullYear();
|
||||
|
||||
$: miniMonthStart = new Date(miniYear, miniMonth, 1);
|
||||
$: miniCalStart = (() => {
|
||||
const d = new Date(miniMonthStart);
|
||||
d.setDate(d.getDate() - d.getDay());
|
||||
return d;
|
||||
})();
|
||||
|
||||
$: miniDays = (() => {
|
||||
const days: Date[] = [];
|
||||
const d = new Date(miniCalStart);
|
||||
for (let i = 0; i < 42; i++) {
|
||||
days.push(new Date(d));
|
||||
d.setDate(d.getDate() + 1);
|
||||
}
|
||||
return days;
|
||||
})();
|
||||
|
||||
$: miniMonthNames = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
];
|
||||
|
||||
function isToday(d: Date): boolean {
|
||||
return d.toDateString() === new Date().toDateString();
|
||||
}
|
||||
|
||||
function isSelected(d: Date): boolean {
|
||||
return d.toDateString() === currentDate.toDateString();
|
||||
}
|
||||
|
||||
function navigateMini(delta: number) {
|
||||
if (miniMonth + delta > 11) {
|
||||
miniMonth = 0;
|
||||
miniYear++;
|
||||
} else if (miniMonth + delta < 0) {
|
||||
miniMonth = 11;
|
||||
miniYear--;
|
||||
} else {
|
||||
miniMonth += delta;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showDeleteConfirm}
|
||||
title={$i18n.t('Delete Calendar')}
|
||||
message={$i18n.t('This will permanently delete the calendar "{{name}}" and all its events. This action cannot be undone.', { name: deleteTargetCalendar?.name ?? '' })}
|
||||
confirmLabel={$i18n.t('Delete')}
|
||||
onConfirm={confirmDelete}
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- Mini Month Calendar -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between px-1 mb-1.5 mt-1.5">
|
||||
<div class="text-[11px] font-medium">{miniMonthNames[miniMonth]} {miniYear}</div>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<button
|
||||
class="p-0.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => navigateMini(-1)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-3"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 19.5 8.25 12l7.5-7.5"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
class="p-0.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => navigateMini(1)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-3"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m8.25 4.5 7.5 7.5-7.5 7.5"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 text-center text-[9px] text-gray-400 dark:text-gray-500 mb-0.5">
|
||||
{#each ['S', 'M', 'T', 'W', 'T', 'F', 'S'] as d}
|
||||
<div class="py-0.5">{d}</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 text-center text-[10px]">
|
||||
{#each miniDays as day}
|
||||
<button
|
||||
class="w-6 h-6 flex items-center justify-center rounded-full transition
|
||||
{day.getMonth() !== miniMonth ? 'text-gray-300 dark:text-gray-600' : ''}
|
||||
{isToday(day) ? 'bg-blue-500 text-white' : ''}
|
||||
{day.toDateString() === currentDate.toDateString() && !isToday(day)
|
||||
? 'bg-gray-200 dark:bg-gray-700'
|
||||
: ''}
|
||||
{!isToday(day) && day.toDateString() !== currentDate.toDateString()
|
||||
? 'hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
: ''}"
|
||||
on:click={() => onDateSelect(day)}
|
||||
>
|
||||
{day.getDate()}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar List -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1 px-1">
|
||||
<div class="text-[11px] text-gray-400 dark:text-gray-500 uppercase tracking-wider">
|
||||
{$i18n.t('Calendars')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#each calendars as cal (cal.id)}
|
||||
<div class="group flex items-center w-full">
|
||||
<button
|
||||
class="flex items-center gap-2 px-2 py-1 rounded-lg text-xs transition
|
||||
hover:bg-gray-50 dark:hover:bg-gray-800/50 flex-1 text-left min-w-0"
|
||||
on:click={() => onToggle(cal.id)}
|
||||
>
|
||||
<span
|
||||
class="shrink-0 size-2.5 rounded-full transition-opacity"
|
||||
style="background-color: {cal.color || '#3b82f6'}; opacity: {visibleCalendarIds.has(
|
||||
cal.id
|
||||
)
|
||||
? '1'
|
||||
: '0.25'};"
|
||||
></span>
|
||||
<span
|
||||
class="truncate flex-1 {visibleCalendarIds.has(cal.id)
|
||||
? ''
|
||||
: 'text-gray-400 dark:text-gray-500'}"
|
||||
>
|
||||
{cal.name}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{#if isDeletable(cal)}
|
||||
<button
|
||||
class="shrink-0 p-1 rounded-lg opacity-0 group-hover:opacity-100
|
||||
hover:bg-red-50 dark:hover:bg-red-900/20
|
||||
text-gray-400 hover:text-red-500 dark:hover:text-red-400
|
||||
transition-all duration-150"
|
||||
title={$i18n.t('Delete calendar')}
|
||||
on:click={(e) => handleDeleteClick(e, cal)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-3"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
323
src/lib/components/calendar/CalendarView.svelte
Normal file
323
src/lib/components/calendar/CalendarView.svelte
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext } from 'svelte';
|
||||
import type { CalendarEventModel, CalendarModel } from '$lib/apis/calendar';
|
||||
import CalendarEventChip from './CalendarEventChip.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let events: CalendarEventModel[] = [];
|
||||
export let calendars: CalendarModel[] = [];
|
||||
export let visibleCalendarIds: Set<string> = new Set();
|
||||
export let view: 'month' | 'week' | 'day' = 'month';
|
||||
export let currentDate: Date = new Date();
|
||||
|
||||
const NS = 1_000_000;
|
||||
const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
|
||||
$: calColorMap = calendars.reduce(
|
||||
(acc, c) => ({ ...acc, [c.id]: c.color }),
|
||||
{} as Record<string, string | null>
|
||||
);
|
||||
$: filteredEvents = events.filter((e) => visibleCalendarIds.has(e.calendar_id));
|
||||
|
||||
// Pre-group events by day key so the template reactively updates when events change
|
||||
$: eventsByDay = (() => {
|
||||
const map: Record<string, CalendarEventModel[]> = {};
|
||||
for (const e of filteredEvents) {
|
||||
const startMs = e.start_at / NS;
|
||||
const endMs = (e.end_at || e.start_at) / NS;
|
||||
// Get local midnight for event start/end
|
||||
const startDate = new Date(startMs);
|
||||
const endDate = new Date(endMs);
|
||||
const d = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
||||
const last = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()).getTime();
|
||||
while (d.getTime() <= last) {
|
||||
const key = d.getTime().toString();
|
||||
(map[key] ??= []).push(e);
|
||||
d.setDate(d.getDate() + 1);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
})();
|
||||
|
||||
$: monthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
|
||||
$: calendarStart = (() => {
|
||||
const d = new Date(monthStart);
|
||||
d.setDate(d.getDate() - d.getDay());
|
||||
return d;
|
||||
})();
|
||||
|
||||
$: monthDays = (() => {
|
||||
const days: Date[] = [];
|
||||
const d = new Date(calendarStart);
|
||||
for (let i = 0; i < 42; i++) {
|
||||
days.push(new Date(d));
|
||||
d.setDate(d.getDate() + 1);
|
||||
}
|
||||
return days;
|
||||
})();
|
||||
|
||||
$: weekStart = (() => {
|
||||
const d = new Date(currentDate);
|
||||
d.setDate(d.getDate() - d.getDay());
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
})();
|
||||
|
||||
$: weekDays = (() => {
|
||||
const days: Date[] = [];
|
||||
const d = new Date(weekStart);
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(new Date(d));
|
||||
d.setDate(d.getDate() + 1);
|
||||
}
|
||||
return days;
|
||||
})();
|
||||
|
||||
$: hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
|
||||
function isToday(d: Date): boolean {
|
||||
return d.toDateString() === new Date().toDateString();
|
||||
}
|
||||
|
||||
function isCurrentMonth(d: Date): boolean {
|
||||
return d.getMonth() === currentDate.getMonth();
|
||||
}
|
||||
|
||||
function getEventsForDay(day: Date): CalendarEventModel[] {
|
||||
const dayStartMs = new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime();
|
||||
const dayEndMs = dayStartMs + 86_400_000;
|
||||
return filteredEvents.filter((e) => {
|
||||
const startMs = e.start_at / NS;
|
||||
const endMs = (e.end_at || e.start_at) / NS;
|
||||
return startMs < dayEndMs && endMs >= dayStartMs;
|
||||
});
|
||||
}
|
||||
|
||||
function getEventsForHour(
|
||||
day: Date,
|
||||
hour: number,
|
||||
eventsList: CalendarEventModel[] = filteredEvents
|
||||
): CalendarEventModel[] {
|
||||
const hourStartMs = new Date(day.getFullYear(), day.getMonth(), day.getDate(), hour).getTime();
|
||||
const hourEndMs = hourStartMs + 3_600_000;
|
||||
return eventsList.filter((e) => {
|
||||
const startMs = e.start_at / NS;
|
||||
return startMs >= hourStartMs && startMs < hourEndMs;
|
||||
});
|
||||
}
|
||||
|
||||
function formatHour(h: number): string {
|
||||
if (h === 0) return '12 AM';
|
||||
if (h < 12) return `${h} AM`;
|
||||
if (h === 12) return '12 PM';
|
||||
return `${h - 12} PM`;
|
||||
}
|
||||
|
||||
function handleDayClick(day: Date) {
|
||||
currentDate = day;
|
||||
const ms = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9).getTime();
|
||||
dispatch('createEvent', { start_at: ms * NS });
|
||||
}
|
||||
|
||||
function goToDayView(day: Date) {
|
||||
currentDate = day;
|
||||
view = 'day';
|
||||
dispatch('viewChange', view);
|
||||
dispatch('navigate', { date: currentDate });
|
||||
}
|
||||
|
||||
function handleHourClick(day: Date, hour: number) {
|
||||
currentDate = day;
|
||||
const ms = new Date(day.getFullYear(), day.getMonth(), day.getDate(), hour).getTime();
|
||||
dispatch('createEvent', { start_at: ms * NS });
|
||||
}
|
||||
|
||||
function handleEventClick(event: CalendarEventModel) {
|
||||
dispatch('eventClick', event);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full w-full min-h-0 min-w-0">
|
||||
<!-- Month View -->
|
||||
{#if view === 'month'}
|
||||
<div class="flex-1 flex flex-col min-h-0 px-3 pb-3">
|
||||
<div class="grid grid-cols-7">
|
||||
{#each DAY_NAMES as day}
|
||||
<div class="px-2 py-1.5 text-xs text-gray-400 dark:text-gray-500 text-left truncate">
|
||||
{$i18n.t(day)}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 grid grid-cols-7 auto-rows-fr min-h-0 rounded-2xl overflow-hidden bg-white dark:bg-gray-900 border border-gray-100/30 dark:border-gray-850/30"
|
||||
>
|
||||
{#each monthDays as day, i}
|
||||
{@const dayKey = new Date(day.getFullYear(), day.getMonth(), day.getDate())
|
||||
.getTime()
|
||||
.toString()}
|
||||
{@const dayEvents = eventsByDay[dayKey] || []}
|
||||
{@const col = i % 7}
|
||||
{@const row = Math.floor(i / 7)}
|
||||
<button
|
||||
class="p-1 min-h-0 text-left overflow-hidden transition cursor-pointer flex flex-col
|
||||
{isCurrentMonth(day) ? '' : 'opacity-40'}
|
||||
hover:bg-gray-50/80 dark:hover:bg-gray-850/30
|
||||
{col > 0 ? 'border-l border-gray-100/20 dark:border-gray-850/20' : ''}
|
||||
{row > 0 ? 'border-t border-gray-100/20 dark:border-gray-850/20' : ''}"
|
||||
on:click={() => handleDayClick(day)}
|
||||
>
|
||||
<div class="flex justify-start px-0.5 mb-0.5">
|
||||
<span
|
||||
class="text-xs w-6 h-6 flex items-center justify-center rounded-full
|
||||
{isToday(day) ? 'bg-blue-500 text-white' : 'text-gray-500 dark:text-gray-400'}"
|
||||
>
|
||||
{day.getDate()}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0 flex-1 overflow-hidden">
|
||||
{#each dayEvents.slice(0, 3) as evt (evt.instance_id || evt.id)}
|
||||
<CalendarEventChip
|
||||
event={evt}
|
||||
calendarColor={calColorMap[evt.calendar_id]}
|
||||
on:click={() => handleEventClick(evt)}
|
||||
/>
|
||||
{/each}
|
||||
{#if dayEvents.length > 3}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events --><!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="text-[10px] text-gray-400 dark:text-gray-500 px-1 mt-auto hover:text-gray-700 dark:hover:text-gray-200 text-left w-full truncate z-10"
|
||||
on:click|stopPropagation={() => goToDayView(day)}
|
||||
>
|
||||
+{dayEvents.length - 3} more
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Week View -->
|
||||
{:else if view === 'week'}
|
||||
<div class="flex-1 flex flex-col min-h-0 px-3 pb-3">
|
||||
<div
|
||||
class="flex-1 rounded-2xl bg-white dark:bg-gray-900 border border-gray-100/30 dark:border-gray-850/30 overflow-hidden relative"
|
||||
>
|
||||
<div class="absolute inset-0 overflow-x-auto flex flex-col">
|
||||
<div class="min-w-[700px] flex flex-col flex-1">
|
||||
<div
|
||||
class="grid grid-cols-[52px_repeat(7,1fr)] shrink-0 border-b border-gray-100/30 dark:border-gray-850/30"
|
||||
>
|
||||
<div></div>
|
||||
{#each weekDays as day}
|
||||
<div
|
||||
class="text-center py-2.5 {day.getDay() > 0
|
||||
? 'border-l border-gray-100/20 dark:border-gray-850/20'
|
||||
: ''}"
|
||||
>
|
||||
<div class="text-[11px] text-gray-400 dark:text-gray-500">
|
||||
{DAY_NAMES[day.getDay()]}
|
||||
</div>
|
||||
<div
|
||||
class="text-sm mt-0.5 w-7 h-7 flex items-center justify-center mx-auto rounded-full {isToday(
|
||||
day
|
||||
)
|
||||
? 'bg-blue-500 text-white'
|
||||
: ''}"
|
||||
>
|
||||
{day.getDate()}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
{#each hours as hour}
|
||||
<div
|
||||
class="grid grid-cols-[52px_repeat(7,1fr)] min-h-[52px] {hour > 0
|
||||
? 'border-t border-gray-100/15 dark:border-gray-850/15'
|
||||
: ''}"
|
||||
>
|
||||
<div
|
||||
class="text-[10px] text-gray-400 dark:text-gray-500 text-right pr-2 select-none -mt-1.5 z-10"
|
||||
>
|
||||
{hour > 0 ? formatHour(hour) : ''}
|
||||
</div>
|
||||
{#each weekDays as day}
|
||||
{@const hourEvents = getEventsForHour(day, hour, filteredEvents)}
|
||||
<button
|
||||
class="px-0.5 py-0.5 {day.getDay() > 0
|
||||
? 'border-l border-gray-100/15 dark:border-gray-850/15'
|
||||
: ''} hover:bg-gray-50/50 dark:hover:bg-gray-850/20 transition cursor-pointer min-w-0 flex flex-col"
|
||||
on:click={() => handleHourClick(day, hour)}
|
||||
>
|
||||
<div class="flex flex-col gap-0.5 w-full min-h-0">
|
||||
{#each hourEvents.slice(0, 3) as evt (evt.instance_id || evt.id)}
|
||||
<CalendarEventChip
|
||||
event={evt}
|
||||
calendarColor={calColorMap[evt.calendar_id]}
|
||||
on:click={() => handleEventClick(evt)}
|
||||
/>
|
||||
{/each}
|
||||
{#if hourEvents.length > 3}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events --><!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="text-[10px] text-gray-400 dark:text-gray-500 px-1 mt-auto hover:text-gray-700 dark:hover:text-gray-200 text-left w-full truncate z-10"
|
||||
on:click|stopPropagation={() => goToDayView(day)}
|
||||
>
|
||||
+{hourEvents.length - 3} more
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Day View -->
|
||||
{:else}
|
||||
<div class="flex-1 flex flex-col min-h-0 px-3 pb-3">
|
||||
<div
|
||||
class="flex-1 rounded-2xl overflow-hidden bg-white dark:bg-gray-900 border border-gray-100/30 dark:border-gray-850/30 overflow-y-auto"
|
||||
>
|
||||
{#each hours as hour}
|
||||
{@const hourEvents = getEventsForHour(currentDate, hour, filteredEvents)}
|
||||
<div
|
||||
class="flex min-h-[52px] {hour > 0
|
||||
? 'border-t border-gray-100/15 dark:border-gray-850/15'
|
||||
: ''}"
|
||||
>
|
||||
<div
|
||||
class="w-14 shrink-0 text-[10px] text-gray-400 dark:text-gray-500 text-right pr-3 mt-1 select-none"
|
||||
>
|
||||
{formatHour(hour)}
|
||||
</div>
|
||||
<button
|
||||
class="flex-1 border-l border-gray-100/15 dark:border-gray-850/15 px-1.5 py-0.5
|
||||
hover:bg-gray-50/50 dark:hover:bg-gray-850/20 transition cursor-pointer flex flex-col text-left justify-start"
|
||||
on:click={() => handleHourClick(currentDate, hour)}
|
||||
>
|
||||
<div class="flex flex-col gap-0.5 w-full">
|
||||
{#each hourEvents as evt (evt.instance_id || evt.id)}
|
||||
<CalendarEventChip
|
||||
event={evt}
|
||||
calendarColor={calColorMap[evt.calendar_id]}
|
||||
on:click={() => handleEventClick(evt)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
import { get, type Unsubscriber, type Writable } from 'svelte/store';
|
||||
import type { i18n as i18nType } from 'i18next';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import equal from 'fast-deep-equal';
|
||||
|
||||
import {
|
||||
chatId,
|
||||
|
|
@ -277,7 +278,7 @@
|
|||
};
|
||||
|
||||
let oldSelectedModelIds = [''];
|
||||
$: if (JSON.stringify(selectedModelIds) !== JSON.stringify(oldSelectedModelIds)) {
|
||||
$: if (!equal(selectedModelIds, oldSelectedModelIds)) {
|
||||
onSelectedModelIdsChange();
|
||||
}
|
||||
|
||||
|
|
@ -512,7 +513,7 @@
|
|||
}
|
||||
}
|
||||
history = history;
|
||||
return; // Patches history.messages directly; skip the trailing write-back.
|
||||
return; // Patches history.messages directly; skip the trailing write-back.
|
||||
} else if (type === 'chat:message:favorite') {
|
||||
// Update message favorite status
|
||||
message.favorite = data.favorite;
|
||||
|
|
@ -674,7 +675,7 @@
|
|||
if (
|
||||
$selectedFolder &&
|
||||
selectedModels.filter((modelId) => modelId !== '').length > 0 &&
|
||||
JSON.stringify($selectedFolder?.data?.model_ids) !== JSON.stringify(selectedModels)
|
||||
!equal($selectedFolder?.data?.model_ids, selectedModels)
|
||||
) {
|
||||
const res = await updateFolderById(localStorage.token, $selectedFolder.id, {
|
||||
data: {
|
||||
|
|
@ -758,7 +759,7 @@
|
|||
await tick();
|
||||
if (
|
||||
folder?.data?.model_ids &&
|
||||
JSON.stringify(selectedModels) !== JSON.stringify(folder.data.model_ids)
|
||||
!equal(selectedModels, folder.data.model_ids)
|
||||
) {
|
||||
selectedModels = folder.data.model_ids;
|
||||
|
||||
|
|
@ -1836,7 +1837,7 @@
|
|||
chatFiles = chatFiles.filter(
|
||||
// Remove duplicates
|
||||
(item, index, array) =>
|
||||
array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
|
||||
array.findIndex((i) => equal(i, item)) === index
|
||||
);
|
||||
|
||||
// Create user message
|
||||
|
|
@ -1880,7 +1881,7 @@
|
|||
$models.map((m) => m.id).includes(modelId) ? modelId : ''
|
||||
);
|
||||
|
||||
if (JSON.stringify(selectedModels) !== JSON.stringify(_selectedModels)) {
|
||||
if (!equal(selectedModels, _selectedModels)) {
|
||||
selectedModels = _selectedModels;
|
||||
}
|
||||
|
||||
|
|
@ -2177,7 +2178,7 @@
|
|||
// Remove duplicates
|
||||
files = files.filter(
|
||||
(item, index, array) =>
|
||||
array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
|
||||
array.findIndex((i) => equal(i, item)) === index
|
||||
);
|
||||
|
||||
scrollToBottom();
|
||||
|
|
@ -2412,8 +2413,11 @@
|
|||
taskIds = newTaskIds;
|
||||
}
|
||||
|
||||
// Backend returns chat_id for new chats — set store + URL
|
||||
if (res.chat_id && $chatId !== res.chat_id) {
|
||||
// Backend returns chat_id for new chats — set store + URL.
|
||||
// Only update if the user hasn't navigated to a different chat
|
||||
// while the request was in flight (prevents overwriting $chatId
|
||||
// and causing spurious toast notifications / state duplication).
|
||||
if (res.chat_id && $chatId !== res.chat_id && $chatId === _chatId) {
|
||||
await chatId.set(res.chat_id);
|
||||
if (!$temporaryChatEnabled) {
|
||||
window.history.replaceState(history.state, '', `/c/${res.chat_id}`);
|
||||
|
|
|
|||
|
|
@ -228,7 +228,9 @@
|
|||
rawContent.length > CONTENT_PREVIEW_LIMIT &&
|
||||
!expandedDocs.has(documentIdx)}
|
||||
{#if $settings?.renderMarkdownInPreviews ?? true}
|
||||
<div class="text-sm prose dark:prose-invert max-w-full">
|
||||
<div
|
||||
class="text-sm prose dark:prose-invert markdown-prose-sm min-w-full max-w-full"
|
||||
>
|
||||
<Markdown
|
||||
content={isTruncated
|
||||
? rawContent.slice(0, CONTENT_PREVIEW_LIMIT)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
} from '$lib/utils';
|
||||
|
||||
import 'highlight.js/styles/github-dark.min.css';
|
||||
import equal from 'fast-deep-equal';
|
||||
|
||||
import CodeEditor from '$lib/components/common/CodeEditor.svelte';
|
||||
import SvgPanZoom from '$lib/components/common/SVGPanZoom.svelte';
|
||||
|
|
@ -391,7 +392,7 @@
|
|||
$: if (token) {
|
||||
if (token.text !== _token?.text || token.raw !== _token?.raw) {
|
||||
_token = token;
|
||||
} else if (JSON.stringify(token) !== JSON.stringify(_token)) {
|
||||
} else if (!equal(token, _token)) {
|
||||
_token = token;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@
|
|||
let speakingIdx: number | undefined;
|
||||
|
||||
let loadingSpeech = false;
|
||||
let speakAbort: AbortController | null = null;
|
||||
|
||||
let showRateComment = false;
|
||||
|
||||
|
|
@ -202,39 +203,39 @@
|
|||
};
|
||||
|
||||
const stopAudio = () => {
|
||||
speakAbort?.abort();
|
||||
speakAbort = null;
|
||||
|
||||
try {
|
||||
speechSynthesis.cancel();
|
||||
$audioQueue?.stop();
|
||||
} catch {}
|
||||
|
||||
if (speaking) {
|
||||
speaking = false;
|
||||
speakingIdx = undefined;
|
||||
}
|
||||
speaking = false;
|
||||
speakingIdx = undefined;
|
||||
loadingSpeech = false;
|
||||
};
|
||||
|
||||
// Resolve voice: model-specific > user settings > config default
|
||||
const getVoiceId = () =>
|
||||
model?.info?.meta?.tts?.voice ??
|
||||
($settings?.audio?.tts?.defaultVoice === $config.audio.tts.voice
|
||||
? ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
|
||||
: $config?.audio?.tts?.voice);
|
||||
|
||||
const speak = async () => {
|
||||
if (!(message?.content ?? '').trim().length) {
|
||||
toast.info($i18n.t('No content to speak'));
|
||||
return;
|
||||
}
|
||||
|
||||
stopAudio();
|
||||
speakAbort = new AbortController();
|
||||
const { signal } = speakAbort;
|
||||
|
||||
speaking = true;
|
||||
const content = removeAllDetails(message.content);
|
||||
|
||||
// Get voice: model-specific > user settings > config default
|
||||
const getVoiceId = () => {
|
||||
// Check for model-specific TTS voice first
|
||||
if (model?.info?.meta?.tts?.voice) {
|
||||
return model.info.meta.tts.voice;
|
||||
}
|
||||
// Fall back to user settings or config default
|
||||
if ($settings?.audio?.tts?.defaultVoice === $config.audio.tts.voice) {
|
||||
return $settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice;
|
||||
}
|
||||
return $config?.audio?.tts?.voice;
|
||||
};
|
||||
|
||||
if ($config.audio.tts.engine === '') {
|
||||
let voices = [];
|
||||
const getVoicesLoop = setInterval(() => {
|
||||
|
|
@ -242,16 +243,10 @@
|
|||
if (voices.length > 0) {
|
||||
clearInterval(getVoicesLoop);
|
||||
|
||||
const voiceId = getVoiceId();
|
||||
const voice = voices?.filter((v) => v.voiceURI === voiceId)?.at(0) ?? undefined;
|
||||
|
||||
console.log(voice);
|
||||
|
||||
const voice = voices.find((v) => v.voiceURI === getVoiceId());
|
||||
const speech = new SpeechSynthesisUtterance(content);
|
||||
speech.rate = $settings.audio?.tts?.playbackRate ?? 1;
|
||||
|
||||
console.log(speech);
|
||||
|
||||
speech.onend = () => {
|
||||
speaking = false;
|
||||
if ($settings.conversationMode) {
|
||||
|
|
@ -281,9 +276,7 @@
|
|||
);
|
||||
|
||||
if (!messageContentParts.length) {
|
||||
console.log('No content to speak');
|
||||
toast.info($i18n.t('No content to speak'));
|
||||
|
||||
speaking = false;
|
||||
loadingSpeech = false;
|
||||
return;
|
||||
|
|
@ -303,41 +296,43 @@
|
|||
await $TTSWorker.init();
|
||||
}
|
||||
|
||||
for (const [idx, sentence] of messageContentParts.entries()) {
|
||||
for (const [, sentence] of messageContentParts.entries()) {
|
||||
if (signal.aborted) return;
|
||||
|
||||
const url = await $TTSWorker
|
||||
.generate({
|
||||
text: sentence,
|
||||
voice: voiceId
|
||||
})
|
||||
.generate({ text: sentence, voice: voiceId })
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
toast.error(`${error}`);
|
||||
|
||||
speaking = false;
|
||||
loadingSpeech = false;
|
||||
});
|
||||
|
||||
if (signal.aborted) return;
|
||||
|
||||
if (url && speaking) {
|
||||
$audioQueue.enqueue(url);
|
||||
loadingSpeech = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [idx, sentence] of messageContentParts.entries()) {
|
||||
for (const [, sentence] of messageContentParts.entries()) {
|
||||
if (signal.aborted) return;
|
||||
|
||||
const res = await synthesizeOpenAISpeech(localStorage.token, voiceId, sentence).catch(
|
||||
(error) => {
|
||||
console.error(error);
|
||||
toast.error(`${error}`);
|
||||
|
||||
speaking = false;
|
||||
loadingSpeech = false;
|
||||
}
|
||||
);
|
||||
|
||||
if (signal.aborted) return;
|
||||
|
||||
if (res && speaking) {
|
||||
const blob = await res.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
$audioQueue.enqueue(url);
|
||||
loadingSpeech = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import Tooltip from '../common/Tooltip.svelte';
|
||||
|
||||
import { updateUserSettings } from '$lib/apis/users';
|
||||
import equal from 'fast-deep-equal';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let selectedModels = [''];
|
||||
|
|
@ -43,7 +44,7 @@
|
|||
$models.map((m) => m.id).includes(model) ? model : ''
|
||||
);
|
||||
|
||||
if (JSON.stringify(_selectedModels) !== JSON.stringify(selectedModels)) {
|
||||
if (!equal(_selectedModels, selectedModels)) {
|
||||
selectedModels = _selectedModels;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex flex-row justify-center gap-3 @sm:gap-3.5 w-fit px-5 max-w-xl">
|
||||
<div class="flex flex-row justify-center gap-2.5 @sm:gap-3 w-fit px-5 max-w-xl">
|
||||
<div class="flex shrink-0 justify-center">
|
||||
<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 100 }}>
|
||||
{#each models as model, modelIdx}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import equal from 'fast-deep-equal';
|
||||
|
||||
marked.use({
|
||||
breaks: true,
|
||||
|
|
@ -1257,7 +1258,7 @@
|
|||
}
|
||||
|
||||
if (json) {
|
||||
if (JSON.stringify(value) !== JSON.stringify(jsonValue)) {
|
||||
if (!equal(value, jsonValue)) {
|
||||
editor.commands.setContent(value);
|
||||
selectTemplate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import {
|
||||
|
|
@ -41,11 +42,14 @@
|
|||
toggleChatPinnedStatusById,
|
||||
getChatById,
|
||||
updateChatFolderIdById,
|
||||
importChats
|
||||
importChats,
|
||||
deleteAllChats,
|
||||
getChatListBySearchText
|
||||
} from '$lib/apis/chats';
|
||||
import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders';
|
||||
import { createNewNote, getPinnedNoteList, toggleNotePinnedStatusById } from '$lib/apis/notes';
|
||||
import { updateUserSettings } from '$lib/apis/users';
|
||||
import { checkActiveChats } from '$lib/apis/tasks';
|
||||
import { getPinnedNoteList, toggleNotePinnedStatusById } from '$lib/apis/notes';
|
||||
import { createNoteHandler } from '$lib/components/notes/utils';
|
||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
|
|
@ -67,10 +71,12 @@
|
|||
import Sidebar from '../icons/Sidebar.svelte';
|
||||
import PinnedModelList from './Sidebar/PinnedModelList.svelte';
|
||||
import Note from '../icons/Note.svelte';
|
||||
import Code from '../icons/Code.svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import HotkeyHint from '../common/HotkeyHint.svelte';
|
||||
|
||||
const BREAKPOINT = 768;
|
||||
const DEFAULT_PINNED_ITEMS = ['notes', 'workspace'];
|
||||
|
||||
let scrollTop = 0;
|
||||
|
||||
|
|
@ -98,6 +104,70 @@
|
|||
|
||||
let newFolderId = null;
|
||||
|
||||
$: pinnedItems = $settings?.pinnedMenuItems ?? DEFAULT_PINNED_ITEMS;
|
||||
|
||||
const isMenuItemVisible = (id) => {
|
||||
switch (id) {
|
||||
case 'notes':
|
||||
return (
|
||||
($config?.features?.enable_notes ?? false) &&
|
||||
($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))
|
||||
);
|
||||
case 'workspace':
|
||||
return (
|
||||
$user?.role === 'admin' ||
|
||||
$user?.permissions?.workspace?.models ||
|
||||
$user?.permissions?.workspace?.knowledge ||
|
||||
$user?.permissions?.workspace?.prompts ||
|
||||
$user?.permissions?.workspace?.tools
|
||||
);
|
||||
case 'automations':
|
||||
return (
|
||||
$config?.features?.enable_automations &&
|
||||
($user?.role === 'admin' || $user?.permissions?.features?.automations)
|
||||
);
|
||||
case 'calendar':
|
||||
return (
|
||||
$config?.features?.enable_calendar &&
|
||||
($user?.role === 'admin' || $user?.permissions?.features?.calendar)
|
||||
);
|
||||
case 'playground':
|
||||
return $user?.role === 'admin';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getMenuItemMeta = (id) => {
|
||||
const items = {
|
||||
notes: { label: 'Notes', href: '/notes', iconType: 'note' },
|
||||
workspace: { label: 'Workspace', href: '/workspace', iconType: 'workspace' },
|
||||
automations: { label: 'Automations', href: '/automations', iconType: 'automations' },
|
||||
calendar: { label: 'Calendar', href: '/calendar', iconType: 'calendar' },
|
||||
playground: { label: 'Playground', href: '/playground', iconType: 'playground' }
|
||||
};
|
||||
return items[id];
|
||||
};
|
||||
|
||||
const initPinnedMenuSortable = () => {
|
||||
const el = document.getElementById('pinned-menu-items-list');
|
||||
if (el && !$mobile) {
|
||||
new Sortable(el, {
|
||||
animation: 150,
|
||||
onUpdate: async (event) => {
|
||||
const itemId = event.item.dataset.id;
|
||||
const newIndex = event.newIndex;
|
||||
const current = [...pinnedItems];
|
||||
const oldIndex = current.indexOf(itemId);
|
||||
current.splice(oldIndex, 1);
|
||||
current.splice(newIndex, 0, itemId);
|
||||
settings.set({ ...$settings, pinnedMenuItems: current });
|
||||
await updateUserSettings(localStorage.token, { ui: $settings });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$: if ($selectedFolder) {
|
||||
initFolders();
|
||||
}
|
||||
|
|
@ -433,7 +503,7 @@
|
|||
document.documentElement.style.setProperty('--sidebar-width', `${newSidebarWidth}px`);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
try {
|
||||
const width = Number(localStorage.getItem('sidebarWidth'));
|
||||
if (!Number.isNaN(width) && width >= MIN_WIDTH && width <= MAX_WIDTH) {
|
||||
|
|
@ -528,6 +598,9 @@
|
|||
const socketInstance = $socket;
|
||||
socketInstance?.on('events', chatActiveEventHandler);
|
||||
|
||||
await tick();
|
||||
initPinnedMenuSortable();
|
||||
|
||||
return () => {
|
||||
unsubscribers.forEach((unsubscriber) => unsubscriber());
|
||||
|
||||
|
|
@ -783,66 +856,80 @@
|
|||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
||||
<div class="">
|
||||
<Tooltip content={$i18n.t('Notes')} placement="right">
|
||||
<a
|
||||
class=" cursor-pointer flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition group"
|
||||
href="/notes"
|
||||
on:click={async (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
|
||||
goto('/notes');
|
||||
itemClickHandler();
|
||||
}}
|
||||
draggable="false"
|
||||
aria-label={$i18n.t('Notes')}
|
||||
>
|
||||
<div class=" self-center flex items-center justify-center size-9">
|
||||
<Note className="size-4.5" />
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
||||
<div class="">
|
||||
<Tooltip content={$i18n.t('Workspace')} placement="right">
|
||||
<a
|
||||
class=" cursor-pointer flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition group"
|
||||
href="/workspace"
|
||||
on:click={async (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
|
||||
goto('/workspace');
|
||||
itemClickHandler();
|
||||
}}
|
||||
aria-label={$i18n.t('Workspace')}
|
||||
draggable="false"
|
||||
>
|
||||
<div class=" self-center flex items-center justify-center size-9">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
{#each pinnedItems as itemId (itemId)}
|
||||
{@const meta = getMenuItemMeta(itemId)}
|
||||
{#if meta && isMenuItemVisible(itemId)}
|
||||
<div class="">
|
||||
<Tooltip content={$i18n.t(meta.label)} placement="right">
|
||||
<a
|
||||
class=" cursor-pointer flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition group"
|
||||
href={meta.href}
|
||||
on:click={async (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
goto(meta.href);
|
||||
itemClickHandler();
|
||||
}}
|
||||
draggable="false"
|
||||
aria-label={$i18n.t(meta.label)}
|
||||
>
|
||||
<div class=" self-center flex items-center justify-center size-9">
|
||||
{#if itemId === 'notes'}
|
||||
<Note className="size-4.5" />
|
||||
{:else if itemId === 'workspace'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if itemId === 'automations'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if itemId === 'calendar'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"
|
||||
/>
|
||||
</svg>
|
||||
{:else if itemId === 'playground'}
|
||||
<Code className="size-4.5" />
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
|
@ -930,7 +1017,7 @@
|
|||
/>
|
||||
</a>
|
||||
|
||||
<a href="/" class="flex flex-1 px-1.5" on:click={newChatHandler}>
|
||||
<a href="/" class="flex flex-1 px-0.5" on:click={newChatHandler}>
|
||||
<div
|
||||
id="sidebar-webui-name"
|
||||
class=" self-center font-medium text-gray-850 dark:text-white font-primary"
|
||||
|
|
@ -1017,60 +1104,83 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
||||
<div class="px-[0.4375rem] flex justify-center text-gray-800 dark:text-gray-200">
|
||||
<a
|
||||
id="sidebar-notes-button"
|
||||
class="grow flex items-center space-x-3 rounded-2xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||
href="/notes"
|
||||
on:click={itemClickHandler}
|
||||
draggable="false"
|
||||
aria-label={$i18n.t('Notes')}
|
||||
>
|
||||
<div class="self-center">
|
||||
<Note className="size-4.5" strokeWidth="2" />
|
||||
</div>
|
||||
|
||||
<div class="flex self-center translate-y-[0.5px]">
|
||||
<div class=" self-center text-sm font-primary">{$i18n.t('Notes')}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
||||
<div class="px-[0.4375rem] flex justify-center text-gray-800 dark:text-gray-200">
|
||||
<a
|
||||
id="sidebar-workspace-button"
|
||||
class="grow flex items-center space-x-3 rounded-2xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||
href="/workspace"
|
||||
on:click={itemClickHandler}
|
||||
draggable="false"
|
||||
aria-label={$i18n.t('Workspace')}
|
||||
>
|
||||
<div class="self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
<div id="pinned-menu-items-list">
|
||||
{#each pinnedItems as itemId (itemId)}
|
||||
{@const meta = getMenuItemMeta(itemId)}
|
||||
{#if meta && isMenuItemVisible(itemId)}
|
||||
<div
|
||||
class="px-[0.4375rem] flex justify-center text-gray-800 dark:text-gray-200"
|
||||
data-id={itemId}
|
||||
>
|
||||
<a
|
||||
id="sidebar-{itemId}-button"
|
||||
class="grow flex items-center space-x-3 rounded-2xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||
href={meta.href}
|
||||
on:click={itemClickHandler}
|
||||
draggable="false"
|
||||
aria-label={$i18n.t(meta.label)}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
{#if itemId === 'notes'}
|
||||
<Note className="size-4.5" strokeWidth="2" />
|
||||
{:else if itemId === 'workspace'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if itemId === 'automations'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if itemId === 'calendar'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"
|
||||
/>
|
||||
</svg>
|
||||
{:else if itemId === 'playground'}
|
||||
<Code className="size-4.5" strokeWidth="2" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex self-center translate-y-[0.5px]">
|
||||
<div class=" self-center text-sm font-primary">{$i18n.t('Workspace')}</div>
|
||||
<div class="flex self-center translate-y-[0.5px]">
|
||||
<div class=" self-center text-sm font-primary">{$i18n.t(meta.label)}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if ($models ?? []).length > 0 && (($settings?.pinnedModels ?? []).length > 0 || $config?.default_pinned_models)}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,15 @@
|
|||
import { getUsage } from '$lib/apis';
|
||||
import { getSessionUser, userSignOut } from '$lib/apis/auths';
|
||||
|
||||
import { showSettings, mobile, showSidebar, showShortcuts, user, config } from '$lib/stores';
|
||||
import {
|
||||
showSettings,
|
||||
mobile,
|
||||
showSidebar,
|
||||
showShortcuts,
|
||||
user,
|
||||
config,
|
||||
settings
|
||||
} from '$lib/stores';
|
||||
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
|
|
@ -26,7 +34,10 @@
|
|||
import UserStatusModal from './UserStatusModal.svelte';
|
||||
import Emoji from '$lib/components/common/Emoji.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import { updateUserStatus } from '$lib/apis/users';
|
||||
import Note from '$lib/components/icons/Note.svelte';
|
||||
import Pin from '$lib/components/icons/Pin.svelte';
|
||||
import PinSlash from '$lib/components/icons/PinSlash.svelte';
|
||||
import { updateUserStatus, updateUserSettings } from '$lib/apis/users';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
|
@ -43,9 +54,29 @@
|
|||
export let showActiveUsers = true;
|
||||
|
||||
let showUserStatusModal = false;
|
||||
let shiftKey = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const DEFAULT_PINNED_ITEMS = ['notes', 'workspace'];
|
||||
|
||||
$: pinnedItems = $settings?.pinnedMenuItems ?? DEFAULT_PINNED_ITEMS;
|
||||
|
||||
const isPinned = (id: string) => {
|
||||
return pinnedItems.includes(id);
|
||||
};
|
||||
|
||||
const togglePin = async (id: string) => {
|
||||
let updated;
|
||||
if (isPinned(id)) {
|
||||
updated = pinnedItems.filter((item) => item !== id);
|
||||
} else {
|
||||
updated = [...pinnedItems, id];
|
||||
}
|
||||
await settings.set({ ...$settings, pinnedMenuItems: updated });
|
||||
await updateUserSettings(localStorage.token, { ui: $settings });
|
||||
};
|
||||
|
||||
let usage = null;
|
||||
const getUsageInfo = async () => {
|
||||
const res = await getUsage(localStorage.token).catch((error) => {
|
||||
|
|
@ -69,6 +100,15 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Shift') shiftKey = true;
|
||||
}}
|
||||
on:keyup={(e) => {
|
||||
if (e.key === 'Shift') shiftKey = false;
|
||||
}}
|
||||
/>
|
||||
|
||||
<ShortcutsModal bind:show={$showShortcuts} />
|
||||
<UserStatusModal
|
||||
bind:show={showUserStatusModal}
|
||||
|
|
@ -214,45 +254,9 @@
|
|||
<div class=" self-center truncate">{$i18n.t('Settings')}</div>
|
||||
</button>
|
||||
|
||||
{#if $user?.role === 'admin' || $user?.permissions?.features?.automations}
|
||||
<a
|
||||
href="/automations"
|
||||
draggable="false"
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
on:click={async (e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||
e.preventDefault();
|
||||
show = false;
|
||||
goto('/automations');
|
||||
if ($mobile) {
|
||||
await tick();
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="self-center mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="self-center truncate">{$i18n.t('Automations')}</div>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if role === 'admin'}
|
||||
<a
|
||||
href="/playground"
|
||||
href="/admin"
|
||||
draggable="false"
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
on:click={async (e) => {
|
||||
|
|
@ -261,7 +265,7 @@
|
|||
}
|
||||
e.preventDefault();
|
||||
show = false;
|
||||
goto('/playground');
|
||||
goto('/admin');
|
||||
if ($mobile) {
|
||||
await tick();
|
||||
showSidebar.set(false);
|
||||
|
|
@ -269,9 +273,9 @@
|
|||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<Code className="size-5" strokeWidth="1.5" />
|
||||
<UserGroup className="w-5 h-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Playground')}</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Admin Panel')}</div>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
|
|
@ -296,29 +300,261 @@
|
|||
<div class=" self-center truncate">{$i18n.t('Archived Chats')}</div>
|
||||
</button>
|
||||
|
||||
<hr class=" border-gray-50/30 dark:border-gray-800/30 my-1 p-0" />
|
||||
|
||||
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
||||
<div class="flex items-center w-full">
|
||||
<a
|
||||
href="/workspace"
|
||||
draggable="false"
|
||||
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
on:click={async (e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||
e.preventDefault();
|
||||
show = false;
|
||||
goto('/workspace');
|
||||
if ($mobile) {
|
||||
await tick();
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="self-center mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="self-center truncate">{$i18n.t('Workspace')}</div>
|
||||
</a>
|
||||
{#if shiftKey}
|
||||
<Tooltip
|
||||
content={isPinned('workspace')
|
||||
? $i18n.t('Unpin from Sidebar')
|
||||
: $i18n.t('Pin to Sidebar')}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||
on:click|preventDefault|stopPropagation={() => togglePin('workspace')}
|
||||
>
|
||||
{#if isPinned('workspace')}
|
||||
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||
{:else}
|
||||
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||
{/if}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
||||
<div class="flex items-center w-full">
|
||||
<a
|
||||
href="/notes"
|
||||
draggable="false"
|
||||
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
on:click={async (e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||
e.preventDefault();
|
||||
show = false;
|
||||
goto('/notes');
|
||||
if ($mobile) {
|
||||
await tick();
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="self-center mr-3">
|
||||
<Note className="size-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class="self-center truncate">{$i18n.t('Notes')}</div>
|
||||
</a>
|
||||
{#if shiftKey}
|
||||
<Tooltip
|
||||
content={isPinned('notes')
|
||||
? $i18n.t('Unpin from Sidebar')
|
||||
: $i18n.t('Pin to Sidebar')}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||
on:click|preventDefault|stopPropagation={() => togglePin('notes')}
|
||||
>
|
||||
{#if isPinned('notes')}
|
||||
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||
{:else}
|
||||
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||
{/if}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar)}
|
||||
<div class="flex items-center w-full">
|
||||
<a
|
||||
href="/calendar"
|
||||
draggable="false"
|
||||
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
on:click={async (e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||
e.preventDefault();
|
||||
show = false;
|
||||
goto('/calendar');
|
||||
}}
|
||||
>
|
||||
<div class="self-center mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="self-center truncate">{$i18n.t('Calendar')}</div>
|
||||
</a>
|
||||
{#if shiftKey}
|
||||
<Tooltip
|
||||
content={isPinned('calendar')
|
||||
? $i18n.t('Unpin from Sidebar')
|
||||
: $i18n.t('Pin to Sidebar')}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||
on:click|preventDefault|stopPropagation={() => togglePin('calendar')}
|
||||
>
|
||||
{#if isPinned('calendar')}
|
||||
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||
{:else}
|
||||
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||
{/if}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)}
|
||||
<div class="flex items-center w-full">
|
||||
<a
|
||||
href="/automations"
|
||||
draggable="false"
|
||||
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
on:click={async (e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||
e.preventDefault();
|
||||
show = false;
|
||||
goto('/automations');
|
||||
if ($mobile) {
|
||||
await tick();
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="self-center mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="self-center truncate">{$i18n.t('Automations')}</div>
|
||||
</a>
|
||||
{#if shiftKey}
|
||||
<Tooltip
|
||||
content={isPinned('automations')
|
||||
? $i18n.t('Unpin from Sidebar')
|
||||
: $i18n.t('Pin to Sidebar')}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||
on:click|preventDefault|stopPropagation={() => togglePin('automations')}
|
||||
>
|
||||
{#if isPinned('automations')}
|
||||
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||
{:else}
|
||||
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||
{/if}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if role === 'admin'}
|
||||
<a
|
||||
href="/admin"
|
||||
draggable="false"
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
on:click={async (e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
show = false;
|
||||
goto('/admin');
|
||||
if ($mobile) {
|
||||
await tick();
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<UserGroup className="w-5 h-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Admin Panel')}</div>
|
||||
</a>
|
||||
<div class="flex items-center w-full">
|
||||
<a
|
||||
href="/playground"
|
||||
draggable="false"
|
||||
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
on:click={async (e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||
e.preventDefault();
|
||||
show = false;
|
||||
goto('/playground');
|
||||
if ($mobile) {
|
||||
await tick();
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="self-center mr-3">
|
||||
<Code className="size-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class="self-center truncate">{$i18n.t('Playground')}</div>
|
||||
</a>
|
||||
{#if shiftKey}
|
||||
<Tooltip
|
||||
content={isPinned('playground')
|
||||
? $i18n.t('Unpin from Sidebar')
|
||||
: $i18n.t('Pin to Sidebar')}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||
on:click|preventDefault|stopPropagation={() => togglePin('playground')}
|
||||
>
|
||||
{#if isPinned('playground')}
|
||||
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||
{:else}
|
||||
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||
{/if}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if help}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { marked } from 'marked';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import equal from 'fast-deep-equal';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
|
|
@ -226,7 +227,7 @@
|
|||
}
|
||||
|
||||
function areContentsEqual(a, b) {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
return equal(a, b);
|
||||
}
|
||||
|
||||
function insertNoteVersion(note) {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@
|
|||
automations: {
|
||||
label: $i18n.t('Automations'),
|
||||
description: $i18n.t('Create and manage scheduled automations')
|
||||
},
|
||||
calendar: {
|
||||
label: $i18n.t('Calendar'),
|
||||
description: $i18n.t('List calendars, search, create, update, and delete calendar events')
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -77,9 +77,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "إضافة ملفات",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "إضافة ذكرايات",
|
||||
|
|
@ -115,6 +117,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -277,6 +280,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "",
|
||||
|
|
@ -527,6 +531,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "حذف المحادثه.",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -833,6 +838,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1202,6 +1211,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1212,6 +1222,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1321,6 +1332,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "المزيد",
|
||||
"More Concise": "",
|
||||
|
|
@ -1339,6 +1351,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "دردشة جديدة",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2021,6 +2034,7 @@
|
|||
"Title cannot be an empty string.": "العنوان مطلوب",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "موجه إنشاء العنوان",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،",
|
||||
"To access the GGUF models available for downloading,": "للوصول إلى الموديلات GGUF المتاحة للتنزيل،",
|
||||
|
|
@ -2191,6 +2205,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2198,6 +2213,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "ما هو الجديد",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -77,9 +77,11 @@
|
|||
"Add content here": "أضف المحتوى هنا",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "إضافة ملفات",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "إضافة ذاكرة",
|
||||
|
|
@ -115,6 +117,7 @@
|
|||
"AI": "",
|
||||
"All": "الكل",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "تم حذف جميع النماذج بنجاح",
|
||||
|
|
@ -277,6 +280,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "التقويم",
|
||||
"Calendars": "",
|
||||
"Call": "مكالمة",
|
||||
"Call feature is not supported when using Web STT engine": "ميزة الاتصال غير مدعومة عند استخدام محرك Web STT",
|
||||
"Camera": "الكاميرا",
|
||||
|
|
@ -527,6 +531,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "حذف المحادثه.",
|
||||
"Delete chat?": "هل تريد حذف المحادثة؟",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "هل تريد حذف المجلد؟",
|
||||
"Delete function?": "هل تريد حذف الوظيفة؟",
|
||||
|
|
@ -833,6 +838,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "التقييمات",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "مفتاح API لـ Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "مثال: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "مثال: ALL",
|
||||
|
|
@ -1202,6 +1211,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "جارٍ الاستماع...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1212,6 +1222,7 @@
|
|||
"local": "",
|
||||
"Local": "محلي",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "لا يُسمح بالوصول إلى الموقع",
|
||||
"Lost": "ضائع",
|
||||
"Low": "",
|
||||
|
|
@ -1321,6 +1332,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "مفتاح API لـ Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "المزيد",
|
||||
"More Concise": "",
|
||||
|
|
@ -1339,6 +1351,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "دردشة جديدة",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "مجلد جديد",
|
||||
"New Function": "",
|
||||
|
|
@ -2021,6 +2034,7 @@
|
|||
"Title cannot be an empty string.": "العنوان مطلوب",
|
||||
"Title Generation": "توليد العنوان",
|
||||
"Title Generation Prompt": "موجه إنشاء العنوان",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،",
|
||||
"To access the GGUF models available for downloading,": "للوصول إلى الموديلات GGUF المتاحة للتنزيل،",
|
||||
|
|
@ -2191,6 +2205,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "ستقوم WebUI بإرسال الطلبات إلى \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "ستقوم WebUI بإرسال الطلبات إلى \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "ما الذي تحاول تحقيقه؟",
|
||||
"What are you working on?": "على ماذا تعمل؟",
|
||||
|
|
@ -2198,6 +2213,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "ما هو الجديد",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "عند التفعيل، سيستجيب النموذج لكل رسالة في المحادثة بشكل فوري، مولدًا الرد بمجرد إرسال المستخدم لرسالته. هذا الوضع مفيد لتطبيقات الدردشة الحية، لكنه قد يؤثر على الأداء في الأجهزة الأبطأ.",
|
||||
"wherever you are": "أينما كنت",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Məzmunu buraya əlavə edin",
|
||||
"Add Custom Parameter": "Fərdi parametr əlavə et",
|
||||
"Add Custom Prompt": "Fərdi prompt əlavə et",
|
||||
"Add description": "",
|
||||
"Add Details": "Detallar əlavə et",
|
||||
"Add Files": "Fayl əlavə et",
|
||||
"Add Image": "Şəkil əlavə et",
|
||||
"Add location": "",
|
||||
"Add Member": "Üzv əlavə et",
|
||||
"Add Members": "Üzvlər əlavə et",
|
||||
"Add Memory": "Yaddaş əlavə et",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "Süni İntellekt (Sİ)",
|
||||
"All": "Hamısı",
|
||||
"All chats have been unarchived.": "Bütün çatlar arxivdən çıxarıldı.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Bütün modellər indi gizlidir",
|
||||
"All models are now visible": "Bütün modellər indi görünür",
|
||||
"All models deleted successfully": "Bütün modellər uğurla silindi",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "Veb Yükləyicidən Yan Keç",
|
||||
"Cache Base Model List": "Əsas Model Siyahısını Keşlə",
|
||||
"Calendar": "Təqvim",
|
||||
"Calendars": "",
|
||||
"Call": "Zəng",
|
||||
"Call feature is not supported when using Web STT engine": "Veb STT mühərriki istifadə edildikdə zəng funksiyası dəstəklənmir",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Çatı sil",
|
||||
"Delete chat?": "Çat silinsin?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Faylı sil",
|
||||
"Delete folder?": "Qovluq silinsin?",
|
||||
"Delete function?": "Funksiya silinsin?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Xəta: '{{modelId}}' ID-li model artıq mövcuddur. Davam etmək üçün fərqli bir ID seçin.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Xəta: Model ID-si boş ola bilməz. Davam etmək üçün etibarlı bir ID daxil edin.",
|
||||
"Evaluations": "Qiymətləndirmələr",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API Açarı",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Nümunə: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Nümunə: ALL (HAMISI)",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Eyni vaxtda aparılan axtarış sorğularını məhdudlaşdırın. 0 = limitsiz (standart). Ardıcıl icra üçün 1 təyin edin.",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Eyni vaxtda olan yerləşdirmə (embedding) sorğularının sayını məhdudlaşdırır. Limitsiz üçün 0 təyin edin.",
|
||||
"List": "Siyahı",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Dinlənilir...",
|
||||
"Live": "Canlı",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "yerli",
|
||||
"Local": "Yerli",
|
||||
"Local Task Model": "Yerli tapşırıq modeli",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Məkan girişinə icazə verilmir",
|
||||
"Lost": "İtirildi",
|
||||
"Low": "Aşağı",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "Modellərin paylaşılması",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Mojeek Axtarış API Açarı",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Daha çox",
|
||||
"More Concise": "Daha yığcam",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Yeni Düymə",
|
||||
"New Chat": "Yeni Çat",
|
||||
"New Event": "",
|
||||
"New File": "Yeni Fayl",
|
||||
"New Folder": "Yeni Qovluq",
|
||||
"New Function": "Yeni Funksiya",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Başlıq boş qala bilməz.",
|
||||
"Title Generation": "Başlıq yaradılması",
|
||||
"Title Generation Prompt": "Başlıq yaradılması üçün göstəriş",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Yükləmək üçün mövcud model adlarına daxil olmaq üçün,",
|
||||
"To access the GGUF models available for downloading,": "Yükləmək üçün mövcud GGUF modellərinə daxil olmaq üçün,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI \"{{url}}\" ünvanına sorğular göndərəcək",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI \"{{url}}/api/chat\" ünvanına sorğular göndərəcək",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI \"{{url}}/chat/completions\" ünvanına sorğular göndərəcək",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Nəyə nail olmaq istəyirsiniz?",
|
||||
"What are you working on?": "Nəyin üzərində işləyirsiniz?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "Nələr paylaşılır:",
|
||||
"What's New in": "Yeniliklər:",
|
||||
"What's on your mind?": "Ağlınızda nə var?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Aktiv edildikdə, model hər bir mesajı real vaxt rejimində cavablandıracaq. Bu rejim canlı söhbət tətbiqləri üçün faydalıdır, lakin zəif avadanlıqlarda performansa təsir edə bilər.",
|
||||
"wherever you are": "harada olursunuzsa olun",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Nəticənin səhifələrə bölünüb-bölünməməsi. Hər səhifə üfüqi xətt və səhifə nömrəsi ilə ayrılacaq. İlkin olaraq False (Xeyr) seçilir.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Добавете съдържание тук",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Добавяне на Файлове",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Добавяне на Памет",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Всички модели са изтрити успешно",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "Календар",
|
||||
"Calendars": "",
|
||||
"Call": "Обаждане",
|
||||
"Call feature is not supported when using Web STT engine": "Функцията за обаждане не се поддържа при използването на Web STT двигател",
|
||||
"Camera": "Камера",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Изтриване на Чат",
|
||||
"Delete chat?": "Изтриване на чата?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Изтриване на папката?",
|
||||
"Delete function?": "Изтриване на функцията?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Оценки",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "API ключ за Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Пример: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Пример: ВСИЧКИ",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Слушане...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "Локално",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "Изгубено",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "API ключ за Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Повече",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Нов чат",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Нова папка",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Заглавието не може да бъде празно.",
|
||||
"Title Generation": "Генериране на заглавие",
|
||||
"Title Generation Prompt": "Промпт за генериране на заглавие",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "За достъп до наличните имена на моделите за изтегляне,",
|
||||
"To access the GGUF models available for downloading,": "За достъп до наличните GGUF модели за изтегляне,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI ще прави заявки към \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI ще прави заявки към \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Какво се опитвате да постигнете?",
|
||||
"What are you working on?": "Върху какво работите?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Какво е ново в",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Когато е активирано, моделът ще отговаря на всяко съобщение в чата в реално време, генерирайки отговор веднага щом потребителят изпрати съобщение. Този режим е полезен за приложения за чат на живо, но може да повлияе на производителността на по-бавен хардуер.",
|
||||
"wherever you are": "където и да сте",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "ফাইল যোগ করুন",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "মেমোরি যোগ করুন",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "চ্যাট মুছে ফেলুন",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "আরো",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "নতুন চ্যাট",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "শিরোনাম অবশ্যই একটি পাশাপাশি শব্দ হতে হবে।",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "শিরোনামগঠন প্রম্পট",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "ডাউনলোডের জন্য এভেইলএবল মডেলের নামগুলো এক্সেস করতে,",
|
||||
"To access the GGUF models available for downloading,": "ডাউলোডের জন্য এভেইলএবল GGUF মডেলগুলো এক্সেস করতে,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "এতে নতুন কী",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -72,9 +72,11 @@
|
|||
"Add content here": "ནང་དོན་འདིར་སྣོན་པ།",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "ཡིག་ཆ་སྣོན་པ།",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "དྲན་ཤེས་སྣོན་པ།",
|
||||
|
|
@ -110,6 +112,7 @@
|
|||
"AI": "",
|
||||
"All": "ཡོངས།",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "དཔེ་དབྱིབས་ཡོངས་རྫོགས་ལེགས་པར་བསུབས་ཟིན།",
|
||||
|
|
@ -272,6 +275,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "ལོ་ཐོ།",
|
||||
"Calendars": "",
|
||||
"Call": "སྐད་འབོད།",
|
||||
"Call feature is not supported when using Web STT engine": "Web STT མ་ལག་སྤྱོད་སྐབས་སྐད་འབོད་ཀྱི་ཁྱད་ཆོས་ལ་རྒྱབ་སྐྱོར་མེད།",
|
||||
"Camera": "པར་ཆས།",
|
||||
|
|
@ -522,6 +526,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "ཁ་བརྡ་བསུབ་པ།",
|
||||
"Delete chat?": "ཁ་བརྡ་བསུབ་པ།?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "ཡིག་སྣོད་བསུབ་པ།?",
|
||||
"Delete function?": "ལས་འགན་བསུབ་པ།?",
|
||||
|
|
@ -828,6 +833,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "གདེང་འཇོག",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API ལྡེ་མིག",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "དཔེར་ན། (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "དཔེར་ན། ALL",
|
||||
|
|
@ -1197,6 +1206,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "ཉན་བཞིན་པ།...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1207,6 +1217,7 @@
|
|||
"local": "",
|
||||
"Local": "ས་གནས།",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "གནས་ཡུལ་འཛུལ་སྤྱོད་ལ་གནང་བ་མ་སྤྲད།",
|
||||
"Lost": "བརླགས།",
|
||||
"Low": "",
|
||||
|
|
@ -1316,6 +1327,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek Search API ལྡེ་མིག",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "མང་བ།",
|
||||
"More Concise": "",
|
||||
|
|
@ -1334,6 +1346,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "ཁ་བརྡ་གསར་པ།",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "ཡིག་སྣོད་གསར་པ།",
|
||||
"New Function": "",
|
||||
|
|
@ -2011,6 +2024,7 @@
|
|||
"Title cannot be an empty string.": "ཁ་བྱང་ཡིག་ཕྲེང་སྟོང་པ་ཡིན་མི་ཆོག",
|
||||
"Title Generation": "ཁ་བྱང་བཟོ་སྐྲུན།",
|
||||
"Title Generation Prompt": "ཁ་བྱང་བཟོ་སྐྲུན་གྱི་འགུལ་སློང་།",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "ཕབ་ལེན་གྱི་ཆེད་དུ་ཡོད་པའི་དཔེ་དབྱིབས་ཀྱི་མིང་ལ་འཛུལ་སྤྱོད་བྱེད་པར།:",
|
||||
"To access the GGUF models available for downloading,": "ཕབ་ལེན་གྱི་ཆེད་དུ་ཡོད་པའི་ GGUF དཔེ་དབྱིབས་ལ་འཛུལ་སྤྱོད་བྱེད་པར།:",
|
||||
|
|
@ -2181,6 +2195,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI ཡིས་ \"{{url}}/api/chat\" ལ་རེ་ཞུ་གཏོང་ངེས།",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI ཡིས་ \"{{url}}/chat/completions\" ལ་རེ་ཞུ་གཏོང་ངེས།",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "ཁྱེད་ཀྱིས་ཅི་ཞིག་འགྲུབ་ཐབས་བྱེད་བཞིན་ཡོད།",
|
||||
"What are you working on?": "ཁྱེད་ཀྱིས་ཅི་ཞིག་ལས་ཀ་བྱེད་བཞིན་ཡོད།",
|
||||
|
|
@ -2188,6 +2203,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "གསར་པ་ཅི་ཡོད།",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "སྒུལ་བསྐྱོད་བྱས་ཚེ། དཔེ་དབྱིབས་ཀྱིས་ཁ་བརྡའི་འཕྲིན་རེ་རེར་དུས་ཐོག་ཏུ་ལན་འདེབས་བྱེད་ངེས། བེད་སྤྱོད་མཁན་གྱིས་འཕྲིན་བཏང་མ་ཐག་ལན་ཞིག་བཟོ་ངེས། མ་དཔེ་འདི་ཐད་གཏོང་ཁ་བརྡའི་བཀོལ་ཆས་ལ་ཕན་ཐོགས་ཡོད། འོན་ཀྱང་དེས་མཁྲེགས་ཆས་དལ་བའི་སྟེང་ལས་ཆོད་ལ་ཤུགས་རྐྱེན་ཐེབས་སྲིད།",
|
||||
"wherever you are": "ཁྱེད་གང་དུ་ཡོད་ཀྱང་།",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Dodaj datoteke",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Dodaj memoriju",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Svi modeli su uspjesno izbrisani",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "Poziv",
|
||||
"Call feature is not supported when using Web STT engine": "Značajka poziva nije podržana kada se koristi Web STT mehanizam",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Izbriši razgovor",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Slušam...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Više",
|
||||
"More Concise": "",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Novi razgovor",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "Naslov ne može biti prazni niz.",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "Prompt za generiranje naslova",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "Za pristup dostupnim nazivima modela za preuzimanje,",
|
||||
"To access the GGUF models available for downloading,": "Za pristup GGUF modelima dostupnim za preuzimanje,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Što je novo u",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Afegir contingut aquí",
|
||||
"Add Custom Parameter": "Afegir paràmetre personalitzat",
|
||||
"Add Custom Prompt": "Afegir indicació personalitzada",
|
||||
"Add description": "",
|
||||
"Add Details": "Afegir detalls",
|
||||
"Add Files": "Afegir arxius",
|
||||
"Add Image": "Afegir imatge",
|
||||
"Add location": "",
|
||||
"Add Member": "Afegir membre",
|
||||
"Add Members": "Afegir membres",
|
||||
"Add Memory": "Afegir memòria",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "IA",
|
||||
"All": "Tots",
|
||||
"All chats have been unarchived.": "Tots els xats han estat desarxivats.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Tots els models estan amagats, ara",
|
||||
"All models are now visible": "Tots els models són visibles, ara",
|
||||
"All models deleted successfully": "Tots els models s'han eliminat correctament",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "Ometre el càrregador web",
|
||||
"Cache Base Model List": "Llista de models base en memòria cau",
|
||||
"Calendar": "Calendari",
|
||||
"Calendars": "",
|
||||
"Call": "Trucada",
|
||||
"Call feature is not supported when using Web STT engine": "La funció de trucada no s'admet quan s'utilitza el motor Web STT",
|
||||
"Camera": "Càmera",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "Eliminar l'automatització",
|
||||
"Delete Chat": "Eliminar xat",
|
||||
"Delete chat?": "Eliminar el xat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Eliminar el fitxer",
|
||||
"Delete folder?": "Eliminar la carpeta?",
|
||||
"Delete function?": "Eliminar funció?",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Error: Ja existeix un model amb l'ID '{{modelId}}'. Selecciona un ID diferent per continuar.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Error: ID de model no pot ser buit. Entra un ID de model vàlid per continuar.",
|
||||
"Evaluations": "Avaluacions",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Clau API d'EXA",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Exemple: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Exemple: TOTS",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Limita les consultes de cerca simultànies. 0 = il·limitada (per defecte). Estableix-ho a 1 per a l'execució seqüencial (recomanat per a API amb límits de velocitat estrictes com el nivell gratuït de Brave).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Limita el nombre de sol·licituds d'incrustació simultànies. Estableix-ho a 0 per a un nombre il·limitat.",
|
||||
"List": "Llista",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Escoltant...",
|
||||
"Live": "Live",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "local",
|
||||
"Local": "Local",
|
||||
"Local Task Model": "Model local de tasques",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Accés a la ubicació no permesa",
|
||||
"Lost": "Perdut",
|
||||
"Low": "Baix",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "Compartir els models",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Clau API de Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "Cada mes",
|
||||
"More": "Més",
|
||||
"More Concise": "Més precís",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "Nova automatització",
|
||||
"New Button": "Botó nou",
|
||||
"New Chat": "Nou xat",
|
||||
"New Event": "",
|
||||
"New File": "Nou arxiu",
|
||||
"New Folder": "Nova carpeta",
|
||||
"New Function": "Nova funció",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "El títol no pot ser una cadena buida.",
|
||||
"Title Generation": "Generació de títols",
|
||||
"Title Generation Prompt": "Indicació de generació de títol",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Per accedir als noms dels models disponibles per descarregar,",
|
||||
"To access the GGUF models available for downloading,": "Per accedir als models GGUF disponibles per descarregar,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI farà peticions a \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI farà peticions a \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI farà peticions a \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "Cada setmana",
|
||||
"What are you trying to achieve?": "Què intentes aconseguir?",
|
||||
"What are you working on?": "En què estàs treballant?",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "Què es comparteix",
|
||||
"What's New in": "Què hi ha de nou a",
|
||||
"What's on your mind?": "Què tens en ment?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Quan està activat, el model respondrà a cada missatge de xat en temps real, generant una resposta tan bon punt l'usuari envia un missatge. Aquest mode és útil per a aplicacions de xat en directe, però pot afectar el rendiment en maquinari més lent.",
|
||||
"wherever you are": "allà on estiguis",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Si es pagina la sortida. Cada pàgina estarà separada per una regla horitzontal i un número de pàgina. Per defecte és Fals.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Idugang ang mga file",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Bag-ong diskusyon",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "Madasig nga henerasyon sa titulo",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "Aron ma-access ang mga ngalan sa modelo nga ma-download,",
|
||||
"To access the GGUF models available for downloading,": "Aron ma-access ang mga modelo sa GGUF nga magamit alang sa pag-download,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Unsay bag-o sa",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -75,9 +75,11 @@
|
|||
"Add content here": "Zde přidejte obsah",
|
||||
"Add Custom Parameter": "Přidat vlastní parametr",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "Přidat podrobnosti",
|
||||
"Add Files": "Přidat soubory",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Přidat vzpomínku",
|
||||
|
|
@ -113,6 +115,7 @@
|
|||
"AI": "UI",
|
||||
"All": "Vše",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Všechny modely byly úspěšně smazány",
|
||||
|
|
@ -275,6 +278,7 @@
|
|||
"Bypass Web Loader": "Obejít webový zavaděč",
|
||||
"Cache Base Model List": "Ukládat seznam základních modelů do mezipaměti",
|
||||
"Calendar": "Kalendář",
|
||||
"Calendars": "",
|
||||
"Call": "Volání",
|
||||
"Call feature is not supported when using Web STT engine": "Funkce volání není podporována při použití webového STT jádra.",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -525,6 +529,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Smazat konverzaci",
|
||||
"Delete chat?": "Smazat konverzaci?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Smazat složku?",
|
||||
"Delete function?": "Smazat funkci?",
|
||||
|
|
@ -831,6 +836,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Chyba: Model s ID '{{modelId}}' již existuje. Pro pokračování prosím zvolte jiné ID.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Chyba: ID modelu nemůže být prázdné. Pro pokračování prosím zadejte platné ID.",
|
||||
"Evaluations": "Hodnocení",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "API klíč pro Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Příklad: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Příklad: VŠE",
|
||||
|
|
@ -1200,6 +1209,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Poslouchám...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1210,6 +1220,7 @@
|
|||
"local": "",
|
||||
"Local": "Lokální",
|
||||
"Local Task Model": "Lokální model pro úkoly",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Přístup k poloze nebyl povolen",
|
||||
"Lost": "Prohrál",
|
||||
"Low": "Nízká",
|
||||
|
|
@ -1319,6 +1330,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "API klíč pro Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Více",
|
||||
"More Concise": "Stručnější",
|
||||
|
|
@ -1337,6 +1349,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Nové tlačítko",
|
||||
"New Chat": "Nová konverzace",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Nová složka",
|
||||
"New Function": "Nová funkce",
|
||||
|
|
@ -2017,6 +2030,7 @@
|
|||
"Title cannot be an empty string.": "Název nemůže být prázdný řetězec.",
|
||||
"Title Generation": "Generování názvu",
|
||||
"Title Generation Prompt": "Instrukce pro generování názvu",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Pro přístup k dostupným názvům modelů ke stažení,",
|
||||
"To access the GGUF models available for downloading,": "Pro přístup k modelům GGUF dostupným ke stažení,",
|
||||
|
|
@ -2187,6 +2201,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI bude odesílat požadavky na \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI bude odesílat požadavky na \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI bude odesílat požadavky na \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Čeho se snažíte dosáhnout?",
|
||||
"What are you working on?": "Na čem pracujete?",
|
||||
|
|
@ -2194,6 +2209,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Co je nového v",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Když je povoleno, model bude odpovídat na každou zprávu v konverzaci v reálném čase a generovat odpověď, jakmile uživatel odešle zprávu. Tento režim je užitečný pro aplikace s živou konverzací, ale může ovlivnit výkon na pomalejším hardwaru.",
|
||||
"wherever you are": "ať jste kdekoli",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Zda stránkovat výstup. Každá stránka bude oddělena vodorovnou čarou a číslem stránky. Výchozí hodnota je False.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Tilføj indhold her",
|
||||
"Add Custom Parameter": "Tilføj brugerdefineret parameter",
|
||||
"Add Custom Prompt": "Tilføj brugerdefineret prompt",
|
||||
"Add description": "",
|
||||
"Add Details": "Tilføj detaljer",
|
||||
"Add Files": "Tilføj filer",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "Tilføj medlem",
|
||||
"Add Members": "Tilføj medlemmer",
|
||||
"Add Memory": "Tilføj hukommelse",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "AI",
|
||||
"All": "Alle",
|
||||
"All chats have been unarchived.": "Alle chatte er blevet aktive",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Alle modeller slettet uden fejl",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "Omgå Web Loader",
|
||||
"Cache Base Model List": "Cache Base Model List",
|
||||
"Calendar": "Kalender",
|
||||
"Calendars": "",
|
||||
"Call": "Opkald",
|
||||
"Call feature is not supported when using Web STT engine": "Opkaldsfunktion er ikke understøttet for Web STT engine",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Slet chat",
|
||||
"Delete chat?": "Slet chat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Slet mappe?",
|
||||
"Delete function?": "Slet funktion?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Fejl: En model med ID '{{modelId}}' eksisterer allerede. Vælg venligst et andet ID for at fortsætte.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Fejl: Model ID kan ikke være tomt. Indtast venligst et gyldigt ID for at fortsætte.",
|
||||
"Evaluations": "Evalueringer",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API-nøgle",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Eksempel: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Eksempel: ALL",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Lytter...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "lokal",
|
||||
"Local": "Lokal",
|
||||
"Local Task Model": "Lokal opgavemodel",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Adgang til placering ikke tilladt",
|
||||
"Lost": "Tabt",
|
||||
"Low": "Lav",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "Modeldeling",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek Search API nøgle",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Mere",
|
||||
"More Concise": "Mere kortfattet",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Ny knap",
|
||||
"New Chat": "Ny chat",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Ny mappe",
|
||||
"New Function": "Ny funktion",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Titel kan ikke være en tom streng.",
|
||||
"Title Generation": "Titel-generation",
|
||||
"Title Generation Prompt": "Prompt til titelgenerering",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "For at få adgang til de tilgængelige modelnavne til download,",
|
||||
"To access the GGUF models available for downloading,": "For at få adgang til de GGUF-modeller, der er tilgængelige til download,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI vil lave forespørgsler til \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI vil lave forespørgsler til \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI vil lave forespørgsler til \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Hvad prøver du at opnå?",
|
||||
"What are you working on?": "Hvad arbejder du på?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Nyheder i",
|
||||
"What's on your mind?": "Hvad har du på hjertet?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Når aktiveret, vil modellen reagere på hver chatbesked i realtid og generere et svar, så snart brugeren sender en besked. Denne tilstand er nyttig til live chat-applikationer, men kan påvirke ydeevnen på langsommere hardware.",
|
||||
"wherever you are": "hvad end du er",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Om outputtet skal pagineres. Hver side vil være adskilt af en vandret streg og sidetal. Standard er False.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Inhalt hier hinzufügen",
|
||||
"Add Custom Parameter": "Benutzerdefinierten Parameter hinzufügen",
|
||||
"Add Custom Prompt": "Benutzerdefinierten Prompt hinzufügen",
|
||||
"Add description": "",
|
||||
"Add Details": "Details hinzufügen",
|
||||
"Add Files": "Dateien hinzufügen",
|
||||
"Add Image": "Bild hinzufügen",
|
||||
"Add location": "",
|
||||
"Add Member": "Mitglied hinzufügen",
|
||||
"Add Members": "Mitglieder hinzufügen",
|
||||
"Add Memory": "Erinnerung hinzufügen",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "KI",
|
||||
"All": "Alle",
|
||||
"All chats have been unarchived.": "Alle Chats wurden aus dem Archiv wiederhergestellt.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Alle Modelle sind nun versteckt",
|
||||
"All models are now visible": "Alle Modelle sind nun sichtbar",
|
||||
"All models deleted successfully": "Alle Modelle erfolgreich gelöscht",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "Web-Loader umgehen",
|
||||
"Cache Base Model List": "Basismodell-Liste cachen",
|
||||
"Calendar": "Kalender",
|
||||
"Calendars": "",
|
||||
"Call": "Anruf",
|
||||
"Call feature is not supported when using Web STT engine": "Die Anruffunktion wird bei Verwendung der Web-STT-Engine nicht unterstützt.",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "Automatisierung löschen?",
|
||||
"Delete Chat": "Chat löschen",
|
||||
"Delete chat?": "Chat löschen?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Datei löschen",
|
||||
"Delete folder?": "Ordner löschen?",
|
||||
"Delete function?": "Funktion löschen?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Fehler: Ein Modell mit der ID '{{modelId}}' existiert bereits. Bitte wählen Sie eine andere ID.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Fehler: Die Modell-ID darf nicht leer sein. Bitte geben Sie eine gültige ID ein.",
|
||||
"Evaluations": "Evaluationen",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API-Schlüssel",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Bsp: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Bsp: ALL",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Gleichzeitige Suchanfragen begrenzen. 0 = unbegrenzt (Standard). Auf 1 setzen für sequentielle Ausführung (empfohlen für APIs mit strengen Ratenbegrenzungen wie dem kostenlosen Brave-Tarif).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Limitiert die Anzahl gleichzeitiger embedding Anfragen. Auf 0 setzen für unlimitiert.",
|
||||
"List": "Liste",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Höre zu...",
|
||||
"Live": "Live",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "lokal",
|
||||
"Local": "Lokal",
|
||||
"Local Task Model": "Lokales Aufgabenmodell",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Standortzugriff nicht erlaubt",
|
||||
"Lost": "Verloren",
|
||||
"Low": "Niedrig",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "Modelle teilen",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Mojeek Search API-Schlüssel",
|
||||
"Month": "",
|
||||
"Monthly": "Monatlich",
|
||||
"More": "Mehr",
|
||||
"More Concise": "Kürzer",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "Neue Automatisierung",
|
||||
"New Button": "Neuer Button",
|
||||
"New Chat": "Neuer Chat",
|
||||
"New Event": "",
|
||||
"New File": "Neue Datei",
|
||||
"New Folder": "Neuer Ordner",
|
||||
"New Function": "Neue Funktion",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Titel darf nicht leer sein.",
|
||||
"Title Generation": "Titelgenerierung",
|
||||
"Title Generation Prompt": "Titelgenerierungs-Prompt",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Um die verfügbaren Modellnamen für den Download anzuzeigen,",
|
||||
"To access the GGUF models available for downloading,": "Um die verfügbaren GGUF-Modelle für den Download anzuzeigen,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI wird Anfragen an \"{{url}}\" senden",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI wird Anfragen an \"{{url}}/api/chat\" senden",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI wird Anfragen an \"{{url}}/chat/completions\" senden",
|
||||
"Week": "",
|
||||
"Weekly": "Wöchentlich",
|
||||
"What are you trying to achieve?": "Was möchten Sie erreichen?",
|
||||
"What are you working on?": "Woran arbeiten Sie?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "Was geteilt wird:",
|
||||
"What's New in": "Neuigkeiten in",
|
||||
"What's on your mind?": "Was beschäftigt Sie?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Wenn aktiviert, antwortet das Modell in Echtzeit auf jede Chat-Nachricht. Dieser Modus ist nützlich für Live-Chats, kann jedoch die Leistung auf langsamerer Hardware beeinträchtigen.",
|
||||
"wherever you are": "wo immer Sie sind",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Ob die Ausgabe paginiert werden soll. Jede Seite wird durch eine horizontale Linie und Seitennummer getrennt. Standard ist False.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Add Files",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "New Bark",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "Title Generation Prompt very prompt",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "To access the available model names for downloading, much access",
|
||||
"To access the GGUF models available for downloading,": "To access the GGUF models available for downloading, much access",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "What's New in much new",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Προσθέστε περιεχόμενο εδώ",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Προσθήκη Αρχείων",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Προσθήκη Μνήμης",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "Όλα",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Όλα τα μοντέλα διαγράφηκαν με επιτυχία",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "Παράκαμψη Φορτωτή Διαδικτύου",
|
||||
"Cache Base Model List": "Αποθήκευση Λίστας Βασικών Μοντέλων Στην Κρυφή Μνήμη",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "Κλήση",
|
||||
"Call feature is not supported when using Web STT engine": "Η λειτουργία κλήσης δεν υποστηρίζεται όταν χρησιμοποιείται η μηχανή Web STT",
|
||||
"Camera": "Κάμερα",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Διαγραφή Συνομιλίας",
|
||||
"Delete chat?": "Διαγραφή συνομιλίας;",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Διαγραφή φακέλου;",
|
||||
"Delete function?": "Διαγραφή λειτουργίας;",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Αξιολογήσεις",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "API κλειδί του Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Παράδειγμα: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Παράδειγμα: ALL",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Ακούγεται...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "Τοπικά",
|
||||
"Local Task Model": "Τοπικό Μοντέλο Εργασιών",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Η πρόσβαση στην τοποθεσία δεν επιτρέπεται",
|
||||
"Lost": "Χαμένος",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Κλειδί API Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Περισσότερα",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Νέα Συνομιλία",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Νέος Φάκελος",
|
||||
"New Function": "Νέα Λειτουργία",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Ο τίτλος δεν μπορεί να είναι κενή συμβολοσειρά.",
|
||||
"Title Generation": "Δημιουργία Τίτλου",
|
||||
"Title Generation Prompt": "Προτροπή Δημιουργίας Τίτλου",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "Για να αποκτήσετε πρόσβαση στα διαθέσιμα ονόματα μοντέλων για λήψη,",
|
||||
"To access the GGUF models available for downloading,": "Για να αποκτήσετε πρόσβαση στα μοντέλα GGUF διαθέσιμα για λήψη,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Τι προσπαθείτε να πετύχετε?",
|
||||
"What are you working on?": "Τι εργάζεστε;",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Τι νέο υπάρχει στο",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Όταν ενεργοποιηθεί, το μοντέλο θα ανταποκρίνεται σε κάθε μήνυμα συνομιλίας σε πραγματικό χρόνο, δημιουργώντας μια απάντηση μόλις ο χρήστης στείλει ένα μήνυμα. Αυτή η λειτουργία είναι χρήσιμη για εφαρμογές ζωντανής συνομιλίας, αλλά μπορεί να επηρεάσει την απόδοση σε πιο αργό υλικό.",
|
||||
"wherever you are": "οπουδήποτε βρίσκεστε",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "",
|
||||
"To access the GGUF models available for downloading,": "",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "",
|
||||
"To access the GGUF models available for downloading,": "",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Añadir contenido aquí",
|
||||
"Add Custom Parameter": "Añadir Parámetro Personalizado",
|
||||
"Add Custom Prompt": "Añadir Indicador Personalizado",
|
||||
"Add description": "",
|
||||
"Add Details": "Añadir Detalles",
|
||||
"Add Files": "Añadir Archivos",
|
||||
"Add Image": "Añadir Imagen",
|
||||
"Add location": "",
|
||||
"Add Member": "Añadir Miembro",
|
||||
"Add Members": "Añadir Miembros",
|
||||
"Add Memory": "Añadir Memoria",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "IA",
|
||||
"All": "Todos",
|
||||
"All chats have been unarchived.": "Todos los chats han sido desarchivados",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Todos los modelos están ahora ocultos",
|
||||
"All models are now visible": "Todos los modelos están ahora visibles",
|
||||
"All models deleted successfully": "Todos los modelos se han borrados correctamente",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "Desactivar Cargar de Web",
|
||||
"Cache Base Model List": "Cachear Lista de Cache Modelos",
|
||||
"Calendar": "Calendario",
|
||||
"Calendars": "",
|
||||
"Call": "Llamada",
|
||||
"Call feature is not supported when using Web STT engine": "La funcionalidad de Llamada no está soportada cuando se usa el motor Web STT",
|
||||
"Camera": "Cámara",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "¿Borrar automatización?",
|
||||
"Delete Chat": "Borrar Chat",
|
||||
"Delete chat?": "¿Borrar el chat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Borrar Fichero",
|
||||
"Delete folder?": "¿Borrar carpeta?",
|
||||
"Delete function?": "Borrar la función?",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Error: Ya existe un modelo con el ID '{{modelId}}'. Seleccione otro ID para continuar.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Error: El ID del modelo no puede estar vacío. Ingrese un ID válido para continuar.",
|
||||
"Evaluations": "Evaluaciones",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Clave API de Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Ejemplo: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Ejemplo: TODOS",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Limitar consultas de búsqueda simultáneas. 0 = ilimitado (predeterminado). Establécerlo en 1 para ejecución secuencial (recomendado para API con límites de velocidad estrictos, como la versión gratuita de Brave).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Limita el número de peticiones concurrentes al incrustrar. Ajusta a 0 para ilimitadas",
|
||||
"List": "Lista",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Escuchando...",
|
||||
"Live": "En Directo",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "local",
|
||||
"Local": "Local",
|
||||
"Local Task Model": "Modelo Local para Tarea",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Acceso a la Ubicación no permitido",
|
||||
"Lost": "Perdido",
|
||||
"Low": "Bajo",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "Compartir Modelos",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Clave API de Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "Mensual",
|
||||
"More": "Más",
|
||||
"More Concise": "Más Conciso",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "Nueva Automatización",
|
||||
"New Button": "Nuevo Botón",
|
||||
"New Chat": "Nuevo Chat",
|
||||
"New Event": "",
|
||||
"New File": "Nuevo Archivo",
|
||||
"New Folder": "Nueva Carpeta",
|
||||
"New Function": "Nueva Función",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "El título no puede ser una cadena vacía.",
|
||||
"Title Generation": "Generación de Títulos",
|
||||
"Title Generation Prompt": "Indicador para la Generación de Título",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Para acceder a los nombres de modelos disponibles para descargar,",
|
||||
"To access the GGUF models available for downloading,": "Para acceder a los modelos GGUF disponibles para descargar,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI hará solicitudes a \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI hará solicitudes a \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI hará solicitudes a \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "Semanal",
|
||||
"What are you trying to achieve?": "¿Qué estás tratando de conseguir?",
|
||||
"What are you working on?": "¿En qué estás trabajando?",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "Que es compartido:",
|
||||
"What's New in": "Que hay de Nuevo en",
|
||||
"What's on your mind?": "¿En que estás pensando?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Cuando está habilitado, el modelo responderá a cada mensaje de chat en tiempo real, generando una respuesta tan pronto como se envíe un mensaje. Este modo es útil para aplicaciones de chat en vivo, pero puede afectar al rendimiento en equipos más lentos.",
|
||||
"wherever you are": "dondequiera que estés",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Al paginar la salida. Cada página será separada por una línea horizontal y número de página. Por defecto: Falso",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Lisa siia sisu",
|
||||
"Add Custom Parameter": "Lisa kohandatud parameeter",
|
||||
"Add Custom Prompt": "Lisa kohandatud sisend",
|
||||
"Add description": "",
|
||||
"Add Details": "Lisa üksikasjad",
|
||||
"Add Files": "Lisa faile",
|
||||
"Add Image": "Lisa pilt",
|
||||
"Add location": "",
|
||||
"Add Member": "Lisa liige",
|
||||
"Add Members": "Lisa liikmeid",
|
||||
"Add Memory": "Lisa mälu",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "AI",
|
||||
"All": "Kõik",
|
||||
"All chats have been unarchived.": "Kõik vestlused on arhiivist eemaldatud.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Kõik mudelid on nüüd peidetud",
|
||||
"All models are now visible": "Kõik mudelid on nüüd nähtavad",
|
||||
"All models deleted successfully": "Kõik mudelid edukalt kustutatud",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "Jäta veebilaadija vahele",
|
||||
"Cache Base Model List": "Puhverda baasmudelite nimekiri",
|
||||
"Calendar": "Kalender",
|
||||
"Calendars": "",
|
||||
"Call": "Kõne",
|
||||
"Call feature is not supported when using Web STT engine": "Kõnefunktsioon ei ole Web STT mootorit kasutades toetatud",
|
||||
"Camera": "Kaamera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Kustuta vestlus",
|
||||
"Delete chat?": "Kustutada vestlus?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Kustuta fail",
|
||||
"Delete folder?": "Kustutada kaust?",
|
||||
"Delete function?": "Kustutada funktsioon?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Viga: mudel ID-ga '{{modelId}}' on juba olemas. Palun valige jätkamiseks erinev ID.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Viga: mudeli ID ei saa olla tühi. Palun sisestage jätkamiseks kehtiv ID.",
|
||||
"Evaluations": "Hindamised",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API võti",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Näide: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Näide: ALL",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Piira samaaegseid otsingupäringuid. 0 = piiramatu (vaikimisi). Määrake 1 järjestikuse täitmise jaoks (soovitatav API-de puhul, millel on ranged piirangud, nagu Brave tasuta tase).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Piirab samaaegsete manustamispäringute arvu. Määrake 0 piirangu puudumiseks.",
|
||||
"List": "Nimekiri",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Kuulamine...",
|
||||
"Live": "Reaalajas",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "kohalik",
|
||||
"Local": "Kohalik",
|
||||
"Local Task Model": "Kohalik ülesande mudel",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Asukoha juurdepääs pole lubatud",
|
||||
"Lost": "Kaotanud",
|
||||
"Low": "Madal",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "Mudelite jagamine",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Mojeek Search API võti",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Rohkem",
|
||||
"More Concise": "Kokkuvõtlikum",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Uus nupp",
|
||||
"New Chat": "Uus vestlus",
|
||||
"New Event": "",
|
||||
"New File": "Uus fail",
|
||||
"New Folder": "Uus kaust",
|
||||
"New Function": "Uus funktsioon",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Pealkiri ei saa olla tühi string.",
|
||||
"Title Generation": "Pealkirja genereerimine",
|
||||
"Title Generation Prompt": "Pealkirja genereerimise sisend",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Juurdepääsuks saadaolevatele mudelinimedele allalaadimiseks,",
|
||||
"To access the GGUF models available for downloading,": "Juurdepääsuks allalaadimiseks saadaolevatele GGUF mudelitele,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI teeb päringuid aadressile \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI teeb päringuid aadressile \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI teeb päringuid aadressile \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Mida te püüate saavutada?",
|
||||
"What are you working on?": "Millega te tegelete?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "Mida jagatakse:",
|
||||
"What's New in": "Mis on uut",
|
||||
"What's on your mind?": "Mis teil mõttes on?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Kui see on lubatud, vastab mudel igale vestlussõnumile reaalajas, genereerides vastuse niipea, kui kasutaja sõnumi saadab. See režiim on kasulik reaalajas vestlusrakendustes, kuid võib mõjutada jõudlust aeglasema riistvara puhul.",
|
||||
"wherever you are": "kus iganes te olete",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Kas lehekülgedele jagada. Iga leht eraldatakse horisontaalse joonega ja leheküljenumbriga. Vaikimisi välja lülitatud.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Gehitu edukia hemen",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Gehitu Fitxategiak",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Gehitu Memoria",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Eredu guztiak ongi ezabatu dira",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "Deia",
|
||||
"Call feature is not supported when using Web STT engine": "Dei funtzioa ez da onartzen Web STT motorra erabiltzean",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Ezabatu Txata",
|
||||
"Delete chat?": "Ezabatu txata?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Ezabatu karpeta?",
|
||||
"Delete function?": "Ezabatu funtzioa?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Ebaluazioak",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Adibidea: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Adibidea: GUZTIAK",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Entzuten...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "Lokala",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "Galduta",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek bilaketa API gakoa",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Gehiago",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Txat berria",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Izenburua ezin da kate hutsa izan.",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "Izenburua sortzeko prompta",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Deskargatzeko eskuragarri dauden modelo izenak atzitzeko,",
|
||||
"To access the GGUF models available for downloading,": "Deskargatzeko eskuragarri dauden GGUF modeloak atzitzeko,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI-k eskaerak egingo ditu \"{{url}}/api/chat\"-era",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI-k eskaerak egingo ditu \"{{url}}/chat/completions\"-era",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Zer lortu nahi duzu?",
|
||||
"What are you working on?": "Zertan ari zara lanean?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Zer berri honetan:",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Gaituta dagoenean, modeloak txat mezu bakoitzari denbora errealean erantzungo dio, erantzun bat sortuz erabiltzaileak mezua bidaltzen duen bezain laster. Modu hau erabilgarria da zuzeneko txat aplikazioetarako, baina errendimenduan eragina izan dezake hardware motelagoan.",
|
||||
"wherever you are": "zauden tokian zaudela",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "محتوا را اینجا اضافه کنید",
|
||||
"Add Custom Parameter": "افزودن پارامتر سفارشی",
|
||||
"Add Custom Prompt": "افزودن پرامپت سفارشی",
|
||||
"Add description": "",
|
||||
"Add Details": "افزودن جزئیات",
|
||||
"Add Files": "افزودن فایل\u200cها",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "افزودن حافظه",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "هوش مصنوعی",
|
||||
"All": "همه",
|
||||
"All chats have been unarchived.": "همه چت\u200cها از حالت بایگانی خارج شدند.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "همه مدل\u200cها با موفقیت حذف شدند",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "دور زدن بارگذاری وب",
|
||||
"Cache Base Model List": "کش لیست مدل پایه",
|
||||
"Calendar": "تقویم",
|
||||
"Calendars": "",
|
||||
"Call": "تماس",
|
||||
"Call feature is not supported when using Web STT engine": "ویژگی تماس هنگام استفاده از موتور Web STT پشتیبانی نمی\u200cشود",
|
||||
"Camera": "دوربین",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "حذف گپ",
|
||||
"Delete chat?": "گفتگو حذف شود؟",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "پوشه حذف شود؟",
|
||||
"Delete function?": "تابع حذف شود؟",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "خطا: مدلی با شناسه '{{modelId}}' قبلاً وجود دارد. لطفاً برای ادامه، یک شناسه متفاوت انتخاب کنید.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "خطا: شناسه مدل نمی\u200cتواند خالی باشد. لطفاً برای ادامه، یک شناسه معتبر وارد کنید.",
|
||||
"Evaluations": "ارزیابی\u200cها",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "کلید API اکسا",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "مثال: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "مثال: ALL",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "در حال گوش دادن...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "محلی",
|
||||
"Local": "محلی",
|
||||
"Local Task Model": "مدل وظیفه محلی",
|
||||
"Location": "",
|
||||
"Location access not allowed": "دسترسی به موقعیت مکانی مجاز نیست",
|
||||
"Lost": "گم شده",
|
||||
"Low": "پایین",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "کلید API جستجوی موجیک",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "بیشتر",
|
||||
"More Concise": "خلاصه\u200cتر",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "دکمه جدید",
|
||||
"New Chat": "گپ جدید",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "پوشه جدید",
|
||||
"New Function": "تابع جدید",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "عنوان نمی تواند یک رشته خالی باشد.",
|
||||
"Title Generation": "تولید عنوان",
|
||||
"Title Generation Prompt": "پرامپت تولید عنوان",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "برای دسترسی به نام مدل های موجود برای دانلود،",
|
||||
"To access the GGUF models available for downloading,": "برای دسترسی به مدل\u200cهای GGUF موجود برای دانلود،",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI به \"{{url}}\" درخواست خواهد داد",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI به \"{{url}}/api/chat\" درخواست خواهد داد",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI به \"{{url}}/chat/completions\" درخواست خواهد داد",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "به دنبال دستیابی به چه هدفی هستید؟",
|
||||
"What are you working on?": "روی چه چیزی کار می\u200cکنید؟",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "چه چیز جدیدی در",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "وقتی فعال باشد، مدل به هر پیام گفتگو در زمان واقعی پاسخ می\u200cدهد و به محض ارسال پیام توسط کاربر، پاسخی تولید می\u200cکند. این حالت برای برنامه\u200cهای گفتگوی زنده مفید است، اما ممکن است در سخت\u200cافزارهای کندتر بر عملکرد تأثیر بگذارد.",
|
||||
"wherever you are": "هر جا که هستید",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "آیا خروجی صفحه\u200cبندی شود یا خیر. هر صفحه با یک خط افقی و شماره صفحه از هم جدا می\u200cشود. پیش\u200cفرض: False.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Lisää sisältöä tähän",
|
||||
"Add Custom Parameter": "Lisää mukautettu parametri",
|
||||
"Add Custom Prompt": "Lisää mukautettu kehote",
|
||||
"Add description": "",
|
||||
"Add Details": "Lisää yksityiskohtia",
|
||||
"Add Files": "Lisää tiedostoja",
|
||||
"Add Image": "Lisää kuva",
|
||||
"Add location": "",
|
||||
"Add Member": "Lisää jäsen",
|
||||
"Add Members": "Lisää jäseniä",
|
||||
"Add Memory": "Lisää muistiin",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "AI",
|
||||
"All": "Kaikki",
|
||||
"All chats have been unarchived.": "Kaikki keskustelut poistettu arkistosta.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Kaikki mallit ovat nyt piilotettu",
|
||||
"All models are now visible": "Kaikki mallit ovat nyt näkyvissä",
|
||||
"All models deleted successfully": "Kaikki mallit poistettu onnistuneesti",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "Ohita verkkolataaja",
|
||||
"Cache Base Model List": "Malli luettelon välimuisti",
|
||||
"Calendar": "Kalenteri",
|
||||
"Calendars": "",
|
||||
"Call": "Puhelu",
|
||||
"Call feature is not supported when using Web STT engine": "Puhelutoimintoa ei tueta käytettäessä web-puheentunnistusmoottoria",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Poista keskustelu",
|
||||
"Delete chat?": "Haluatko varmasti poistaa tämän keskustelun?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Poista tiedosto",
|
||||
"Delete folder?": "Haluatko varmasti poistaa tämän kansion?",
|
||||
"Delete function?": "Haluatko varmasti poistaa tämän toiminnon?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Virhe: Malli '{{modelId}}' on jo käytössä. Valitse toinen ID jatkaaksesi.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Virhe: Mallin ID ei voi olla tyhjä. Kirjoita ID jatkaaksesi.",
|
||||
"Evaluations": "Arvioinnit",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API -avain",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Esimerkki: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Esimerkki: KAIKKI",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Rajoita samanaikaisia hakukyselyitä. 0 = rajoittamaton (oletus). Aseta arvoon 1 peräkkäistä suoritusta varten (suositellaan API-rajapinnoille, joilla on tiukat nopeusrajoitukset, kuten Brave-ilmaistaso).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Rajoittaa samanaikaisten upotuspyyntöjen määrää. Arvolla 0 ei rajoituksia.",
|
||||
"List": "Lista",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Kuuntelee...",
|
||||
"Live": "Live",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "paikallinen",
|
||||
"Local": "Paikallinen",
|
||||
"Local Task Model": "Paikallinen työmalli",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Ei pääsyä sijaintitietoihin",
|
||||
"Lost": "Mennyt",
|
||||
"Low": "Matala",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "Mallien jako",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek Search API -avain",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Lisää",
|
||||
"More Concise": "Lyhyemmin",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Uusi painike",
|
||||
"New Chat": "Uusi keskustelu",
|
||||
"New Event": "",
|
||||
"New File": "Uusi tiedosto",
|
||||
"New Folder": "Uusi kansio",
|
||||
"New Function": "Uusi toiminto",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Otsikko ei voi olla tyhjä merkkijono.",
|
||||
"Title Generation": "Otsikon luonti",
|
||||
"Title Generation Prompt": "Otsikon luontikehote",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Päästäksesi käsiksi ladattavissa oleviin mallinimiin,",
|
||||
"To access the GGUF models available for downloading,": "Päästäksesi käsiksi ladattavissa oleviin GGUF-malleihin,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI lähettää pyyntöjä osoitteeseen \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI lähettää pyyntöjä osoitteeseen \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI lähettää pyyntöjä osoitteeseen \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Mitä yrität saavuttaa?",
|
||||
"What are you working on?": "Mitä olet työskentelemässä?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "Mitä jaetaan:",
|
||||
"What's New in": "Mitä uutta",
|
||||
"What's on your mind?": "Mitä ajattelet?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Kun käytössä, malli vastaa jokaiseen chatviestiin reaaliajassa, tuottaen vastauksen heti kun käyttäjä lähettää viestin. Tämä tila on hyödyllinen reaaliaikaisissa chat-sovelluksissa, mutta voi vaikuttaa suorituskykyyn hitaammilla laitteistoilla.",
|
||||
"wherever you are": "missä tahansa oletkin",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Sivutetaanko tuloste. Jokainen sivu erotetaan toisistaan vaakasuoralla viivalla ja sivunumerolla. Oletusarvo ei käytössä.",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Ajoutez du contenu ici",
|
||||
"Add Custom Parameter": "Ajoutez votre réglage personnalisé",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Ajouter des fichiers",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Ajouter un souvenir",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "",
|
||||
"All": "Tout",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Tous les modèles ont été supprimés avec succès",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "Ignorer le chargeur Web",
|
||||
"Cache Base Model List": "Mettre en cache la liste des modèles de base",
|
||||
"Calendar": "Calendrier",
|
||||
"Calendars": "",
|
||||
"Call": "Appeler",
|
||||
"Call feature is not supported when using Web STT engine": "La fonction d'appel n'est pas prise en charge lors de l'utilisation du moteur Web STT",
|
||||
"Camera": "Appareil photo",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Supprimer la Conversation",
|
||||
"Delete chat?": "Supprimer la conversation ?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Supprimer le dossier ?",
|
||||
"Delete function?": "Supprimer la fonction ?",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Évaluations",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Clé d'Exa API",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Exemple: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Exemple: TOUS",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Écoute en cours...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "",
|
||||
"Local": "Local",
|
||||
"Local Task Model": "Model de tâche local",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Accès à la localisation non autorisé",
|
||||
"Lost": "Perdu",
|
||||
"Low": "",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Clé API Mojeek",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Plus",
|
||||
"More Concise": "",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Nouvelle conversation",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Nouveau dossier",
|
||||
"New Function": "Nouvelle fonction",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "Le titre ne peut pas être une chaîne de caractères vide.",
|
||||
"Title Generation": "Génération du Titre",
|
||||
"Title Generation Prompt": "Prompt de génération de titre",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Pour accéder aux noms des modèles disponibles,",
|
||||
"To access the GGUF models available for downloading,": "Pour accéder aux modèles GGUF disponibles,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI effectuera des requêtes vers \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI fera des requêtes à \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI fera des requêtes à \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Que cherchez-vous à accomplir ?",
|
||||
"What are you working on?": "Sur quoi travaillez-vous ?",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Quoi de neuf dans",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Lorsqu'il est activé, le modèle répondra à chaque message de la conversation en temps réel, générant une réponse dès que l'utilisateur envoie un message. Ce mode est utile pour les applications de conversation en direct, mais peut affecter les performances sur un matériel plus lent.",
|
||||
"wherever you are": "où que vous soyez",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Indique si la sortie doit être paginée. Chaque page sera séparée par une règle horizontale et un numéro de page. La valeur par défaut est False.",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Ajoutez du contenu ici",
|
||||
"Add Custom Parameter": "Ajoutez votre réglage personnalisé",
|
||||
"Add Custom Prompt": "Ajouter un prompt personnalisé",
|
||||
"Add description": "",
|
||||
"Add Details": "Ajouter des détails",
|
||||
"Add Files": "Ajouter des fichiers",
|
||||
"Add Image": "Ajouter une image",
|
||||
"Add location": "",
|
||||
"Add Member": "Ajouter un membre",
|
||||
"Add Members": "Ajouter des membres",
|
||||
"Add Memory": "Ajouter un souvenir",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "IA",
|
||||
"All": "Tout",
|
||||
"All chats have been unarchived.": "Toutes les conversations ont été désarchivées.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Tous les modèles sont maintenant masqués",
|
||||
"All models are now visible": "Tous les modèles sont maintenant visibles",
|
||||
"All models deleted successfully": "Tous les modèles supprimés avec succès",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "Ignorer le chargeur Web",
|
||||
"Cache Base Model List": "Mettre en cache la liste des modèles de base",
|
||||
"Calendar": "Calendrier",
|
||||
"Calendars": "",
|
||||
"Call": "Appeler",
|
||||
"Call feature is not supported when using Web STT engine": "La fonction d'appel n'est pas prise en charge lors de l'utilisation du moteur Web STT",
|
||||
"Camera": "Appareil photo",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Supprimer la Conversation",
|
||||
"Delete chat?": "Supprimer la conversation ?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Supprimer le fichier",
|
||||
"Delete folder?": "Supprimer le dossier ?",
|
||||
"Delete function?": "Supprimer la fonction ?",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Erreur : Un modèle avec l'ID '{{modelId}}' existe déjà. Veuillez sélectionner un ID différent pour continuer.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Erreur : l'ID du modèle ne peut pas être vide. Veuillez saisir un ID valide pour continuer.",
|
||||
"Evaluations": "Évaluations",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Clé API Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Exemple: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Exemple: TOUS",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Limite les requêtes de recherche simultanées. 0 = illimité (par défaut). Définir à 1 pour une exécution séquentielle (recommandé pour les API avec des limites de débit strictes comme Brave gratuit).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Limite le nombre de requêtes d'embedding simultanées. Définir à 0 pour illimité.",
|
||||
"List": "Liste",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Écoute en cours...",
|
||||
"Live": "Actif",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "local",
|
||||
"Local": "Local",
|
||||
"Local Task Model": "Model de tâche local",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Accès à la localisation non autorisé",
|
||||
"Lost": "Perdu",
|
||||
"Low": "Faible",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "Partage des modèles",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Clé API Mojeek",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Plus",
|
||||
"More Concise": "Plus concis",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Nouveau bouton",
|
||||
"New Chat": "Nouvelle conversation",
|
||||
"New Event": "",
|
||||
"New File": "Nouveau fichier",
|
||||
"New Folder": "Nouveau dossier",
|
||||
"New Function": "Nouvelle fonction",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "Le titre ne peut pas être une chaîne de caractères vide.",
|
||||
"Title Generation": "Génération du Titre",
|
||||
"Title Generation Prompt": "Prompt de génération de titre",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Pour accéder aux noms des modèles disponibles,",
|
||||
"To access the GGUF models available for downloading,": "Pour accéder aux modèles GGUF disponibles,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI effectuera des requêtes vers \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI fera des requêtes à \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI fera des requêtes à \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Que cherchez-vous à accomplir ?",
|
||||
"What are you working on?": "Sur quoi travaillez-vous ?",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "Ce qui est partagé :",
|
||||
"What's New in": "Quoi de neuf dans",
|
||||
"What's on your mind?": "Quoi de neuf ?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Lorsqu'il est activé, le modèle répondra à chaque message de la conversation en temps réel, générant une réponse dès que l'utilisateur envoie un message. Ce mode est utile pour les applications de conversation en direct, mais peut affecter les performances sur un matériel plus lent.",
|
||||
"wherever you are": "où que vous soyez",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Indique si la sortie doit être paginée. Chaque page sera séparée par une règle horizontale et un numéro de page. La valeur par défaut est False.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Agrege contido aquí",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Agregar Arquivos",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Agregar Memoria",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Todos os modelos han sido borrados",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "Chamada",
|
||||
"Call feature is not supported when using Web STT engine": "A funcionalidade da chamada non pode usarse xunto co motor da STT Web",
|
||||
"Camera": "Cámara",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Borrar Chat",
|
||||
"Delete chat?": "Borrar o chat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "¿Eliminar carpeta?",
|
||||
"Delete function?": "Borrar afunción?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Evaluacions",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "chave API de Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Exemplo: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Exemplo: TODOS",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Escoitando...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "Local",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "Perdido",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "chave API de Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "mais",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Novo Chat",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Nova carpeta",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "O título non pode ser unha cadena vacía.",
|
||||
"Title Generation": "Xeneración de titulos",
|
||||
"Title Generation Prompt": "Prompt de xeneración de título",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Para acceder os nomes de modelos dispoñibles para descargar,",
|
||||
"To access the GGUF models available for downloading,": "Para acceder os modelos GGUF dispoñibles para descargar,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI hará solicitudes a \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI hará solicitudes a \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "¿Qué estás tratando de lograr?",
|
||||
"What are you working on?": "¿En qué estás trabajando?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Novedades en",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Cando está habilitado, o modelo responderá a cada mensaxe de chat en tempo real, generando unha resposta tan pronto como o usuario envíe un mensaxe. Este modo es útil para aplicacions de chat en vivo, pero puede afectar o rendimiento en hardware mais lento.",
|
||||
"wherever you are": "Donde queira que estés",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "הוסף תוכן כאן",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "הוסף קבצים",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "הוסף זיכרון",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "כל המודלים נמחקו בהצלחה",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "לוח שנה",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "מצלמה",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "מחק צ'אט",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "עוד",
|
||||
"More Concise": "",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "צ'אט חדש",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "תיקייה חדשה",
|
||||
"New Function": "פונקציה חדשה",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "שם לא יכול להיות מחרוזת ריקה.",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "פרומפט ליצירת כותרת",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "כדי לגשת לשמות הדגמים הזמינים להורדה,",
|
||||
"To access the GGUF models available for downloading,": "כדי לגשת לדגמי GGUF הזמינים להורדה,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "מה חדש ב",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "फाइलें जोड़ें",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "मेमोरी जोड़ें",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "चैट हटाएं",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "और..",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "नई चैट",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "शीर्षक नहीं खाली पाठ हो सकता है.",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "शीर्षक जनरेशन प्रॉम्प्ट",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "डाउनलोड करने के लिए उपलब्ध मॉडल नामों तक पहुंचने के लिए,",
|
||||
"To access the GGUF models available for downloading,": "डाउनलोडिंग के लिए उपलब्ध GGUF मॉडल तक पहुँचने के लिए,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "इसमें नया क्या है",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Dodaj datoteke",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Dodaj memoriju",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "Poziv",
|
||||
"Call feature is not supported when using Web STT engine": "Značajka poziva nije podržana kada se koristi Web STT mehanizam",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Izbriši razgovor",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Slušam...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Više",
|
||||
"More Concise": "",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Novi razgovor",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "Naslov ne može biti prazni niz.",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "Prompt za generiranje naslova",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "Za pristup dostupnim nazivima modela za preuzimanje,",
|
||||
"To access the GGUF models available for downloading,": "Za pristup GGUF modelima dostupnim za preuzimanje,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Što je novo u",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Tartalom hozzáadása ide",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Fájlok hozzáadása",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Memória hozzáadása",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "Mind",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Minden modell sikeresen törölve",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "Naptár",
|
||||
"Calendars": "",
|
||||
"Call": "Hívás",
|
||||
"Call feature is not supported when using Web STT engine": "A hívás funkció nem támogatott Web STT motor használatakor",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Beszélgetés törlése",
|
||||
"Delete chat?": "Törli a beszélgetést?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Törli a mappát?",
|
||||
"Delete function?": "Törli a funkciót?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Értékelések",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API kulcs",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Példa: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Példa: MIND",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Hallgatás...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "Helyi",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Helyhozzáférés nem engedélyezett",
|
||||
"Lost": "Elveszett",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek Search API kulcs",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Több",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Új beszélgetés",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Új mappa",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "A cím nem lehet üres karakterlánc.",
|
||||
"Title Generation": "Cím generálás",
|
||||
"Title Generation Prompt": "Cím generálási prompt",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "A letölthető modellek nevének eléréséhez,",
|
||||
"To access the GGUF models available for downloading,": "A letölthető GGUF modellek eléréséhez,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "A WebUI kéréseket küld a \"{{url}}\" címre",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "A WebUI kéréseket küld a \"{{url}}/api/chat\" címre",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "A WebUI kéréseket küld a \"{{url}}/chat/completions\" címre",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Mit próbálsz elérni?",
|
||||
"What are you working on?": "Min dolgozol?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Mi újság a",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Ha engedélyezve van, a modell valós időben válaszol minden csevegőüzenetre, amint a felhasználó elküldi az üzenetet. Ez a mód hasznos élő csevegőalkalmazásokhoz, de lassabb hardveren befolyásolhatja a teljesítményt.",
|
||||
"wherever you are": "bárhol is vagy",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -72,9 +72,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Menambahkan File",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Menambahkan Memori",
|
||||
|
|
@ -110,6 +112,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -272,6 +275,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "Panggilan",
|
||||
"Call feature is not supported when using Web STT engine": "Fitur panggilan tidak didukung saat menggunakan mesin Web STT",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -522,6 +526,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Menghapus Obrolan",
|
||||
"Delete chat?": "Menghapus obrolan?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "Fungsi hapus?",
|
||||
|
|
@ -828,6 +833,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1197,6 +1206,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Mendengarkan",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1207,6 +1217,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1316,6 +1327,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Lainnya",
|
||||
"More Concise": "",
|
||||
|
|
@ -1334,6 +1346,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Obrolan Baru",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2011,6 +2024,7 @@
|
|||
"Title cannot be an empty string.": "Judul tidak boleh berupa string kosong.",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "Perintah Pembuatan Judul",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "Untuk mengakses nama model yang tersedia untuk diunduh,",
|
||||
"To access the GGUF models available for downloading,": "Untuk mengakses model GGUF yang tersedia untuk diunduh,",
|
||||
|
|
@ -2181,6 +2195,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2188,6 +2203,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Apa yang Baru di",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Cuir ábhar anseo",
|
||||
"Add Custom Parameter": "Cuir Paraiméadar Saincheaptha leis",
|
||||
"Add Custom Prompt": "Cuir Treoir Shaincheaptha leis",
|
||||
"Add description": "",
|
||||
"Add Details": "Cuir Sonraí leis",
|
||||
"Add Files": "Cuir Comhaid",
|
||||
"Add Image": "Cuir Íomhá leis",
|
||||
"Add location": "",
|
||||
"Add Member": "Cuir Ball leis",
|
||||
"Add Members": "Cuir Baill leis",
|
||||
"Add Memory": "Cuir Cuimhne",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "IS",
|
||||
"All": "Gach",
|
||||
"All chats have been unarchived.": "Tá na comhráite uile díchartlannaithe.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Tá na samhlacha uile i bhfolach anois",
|
||||
"All models are now visible": "Tá na samhlacha uile le feiceáil anois",
|
||||
"All models deleted successfully": "Scriosadh na samhlacha go léir go rathúil",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "Seachbhóthar Luchtaire Gréasáin",
|
||||
"Cache Base Model List": "Liosta Samhail Bunáite Taisce",
|
||||
"Calendar": "Féilire",
|
||||
"Calendars": "",
|
||||
"Call": "Glaoigh",
|
||||
"Call feature is not supported when using Web STT engine": "Ní thacaítear le gné glaonna agus inneall Web STT á úsáid",
|
||||
"Camera": "Ceamara",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "Scrios an t-uathoibriú?",
|
||||
"Delete Chat": "Scrios Comhrá",
|
||||
"Delete chat?": "Scrios comhrá?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Scrios Comhad",
|
||||
"Delete folder?": "Scrios fillteán?",
|
||||
"Delete function?": "Scrios feidhm?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Earráid: Tá samhail leis an ID '{{modelId}}' ann cheana féin. Roghnaigh ID difriúil le dul ar aghaidh.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Earráid: Ní féidir ID an tSamhail a fhágáil folamh. Cuir isteach ID bailí le dul ar aghaidh.",
|
||||
"Evaluations": "Meastóireachtaí",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Eochair Exa API",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Sampla: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Sampla: GACH",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Teorainn a chur le fiosrúcháin chuardaigh chomhuaineacha. 0 = gan teorainn (réamhshocraithe). Socraigh go 1 le haghaidh forghníomhú seicheamhach (molta do APIanna le teorainneacha ráta dochta cosúil le sraith saor in aisce Brave).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Cuireann sé teorainn le líon na n-iarratas leabaithe comhuaineach. Socraigh go 0 le haghaidh neamhtheoranta.",
|
||||
"List": "Liosta",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Éisteacht...",
|
||||
"Live": "Beo",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "áitiúil",
|
||||
"Local": "Áitiúil",
|
||||
"Local Task Model": "Samhail Tasc Áitiúil",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Ní cheadaítear rochtain suímh",
|
||||
"Lost": "Cailleadh",
|
||||
"Low": "Íseal",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "Roinnt Samhlacha",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Eochair API Cuardach Mojeek",
|
||||
"Month": "",
|
||||
"Monthly": "Míosúil",
|
||||
"More": "Tuilleadh",
|
||||
"More Concise": "Níos Gonta",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "Uathoibriú Nua",
|
||||
"New Button": "Cnaipe Nua",
|
||||
"New Chat": "Comhrá Nua",
|
||||
"New Event": "",
|
||||
"New File": "Comhad Nua",
|
||||
"New Folder": "Fillteán Nua",
|
||||
"New Function": "Feidhm Nua",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Ní féidir leis an teideal a bheith ina teaghrán folamh.",
|
||||
"Title Generation": "Giniúint Teidil",
|
||||
"Title Generation Prompt": "Treoir Giniúna Teidil",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Chun rochtain a fháil ar ainmneacha na samhlacha atá ar fáil lena n-íoslódáil,",
|
||||
"To access the GGUF models available for downloading,": "Chun rochtain a fháil ar na samhlacha GGUF atá ar fáil lena n-íoslódáil,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "Déanfaidh WebUI iarratais ar \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "Déanfaidh WebUI iarratais ar \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "Déanfaidh WebUI iarratais ar \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "Seachtainiúil",
|
||||
"What are you trying to achieve?": "Cad atá tú ag iarraidh a bhaint amach?",
|
||||
"What are you working on?": "Cad air a bhfuil tú ag obair?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "Cad a roinntear:",
|
||||
"What's New in": "Cad atá Nua i",
|
||||
"What's on your mind?": "Cad atá ar d’intinn?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Nuair a bheidh sé cumasaithe, freagróidh an tsamhail gach teachtaireacht comhrá i bhfíor-am, ag giniúint freagra a luaithe a sheolann an t-úsáideoir teachtaireacht. Tá an mód seo úsáideach le haghaidh feidhmchláir chomhrá beo, ach d'fhéadfadh tionchar a bheith aige ar fheidhmíocht ar chrua-earraí níos moille.",
|
||||
"wherever you are": "aon áit a bhfuil tú",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Cibé acu an ndéanfar an t-aschur a roinnt le leathanaigh nó nach ndéanfar. Beidh riail chothrománach agus uimhir leathanaigh ag scartha ó gach leathanach. Is é Bréag an rogha réamhshocraithe.",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Aggiungi un contenuto qui",
|
||||
"Add Custom Parameter": "Aggiungi Parametri Personalizzati",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Aggiungi dei file",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Aggiungi memoria",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "",
|
||||
"All": "Tutti",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Tutti i modelli eliminati con successo",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "Bypassa il Web Loader",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "Calendario",
|
||||
"Calendars": "",
|
||||
"Call": "Chiamata",
|
||||
"Call feature is not supported when using Web STT engine": "La funzione di chiamata non è supportata quando si utilizza il motore Web STT",
|
||||
"Camera": "Fotocamera",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Elimina chat",
|
||||
"Delete chat?": "Elimina chat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Elimina cartella?",
|
||||
"Delete function?": "Elimina funzione?",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Valutazioni",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Chiave API Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Esempio: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Esempio: TUTTI",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "In ascolto...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "",
|
||||
"Local": "Locale",
|
||||
"Local Task Model": "Modello Task locale",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Accesso alla posizione non consentito",
|
||||
"Lost": "Perso",
|
||||
"Low": "",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Chiave API di Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Altro",
|
||||
"More Concise": "",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Nuova chat",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Nuova cartella",
|
||||
"New Function": "",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "Il titolo non può essere una stringa vuota.",
|
||||
"Title Generation": "Generazione del titolo",
|
||||
"Title Generation Prompt": "Prompt di generazione del titolo",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Per accedere ai nomi dei modelli disponibili per il download,",
|
||||
"To access the GGUF models available for downloading,": "Per accedere ai modelli GGUF disponibili per il download,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI farà richieste a \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI farà richieste a \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI farà richieste a \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Cosa stai cercando di ottenere?",
|
||||
"What are you working on?": "Su cosa stai lavorando?",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Novità in",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Quando abilitato, il modello risponderà a ciascun messaggio della chat in tempo reale, generando una risposta non appena l'utente invia un messaggio. Questa modalità è utile per le applicazioni di chat dal vivo, ma potrebbe influire sulle prestazioni su hardware più lento.",
|
||||
"wherever you are": "Ovunque tu sia",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Specifica se paginare l'output. Ogni pagina sarà separata da una riga orizzontale e dal numero di pagina. Predefinito è Falso.",
|
||||
|
|
|
|||
|
|
@ -72,9 +72,11 @@
|
|||
"Add content here": "ここへコンテンツを追加",
|
||||
"Add Custom Parameter": "カスタムパラメータを追加",
|
||||
"Add Custom Prompt": "カスタムプロンプトを追加",
|
||||
"Add description": "",
|
||||
"Add Details": "より詳しく",
|
||||
"Add Files": "ファイルを追加",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "メモリを追加",
|
||||
|
|
@ -110,6 +112,7 @@
|
|||
"AI": "AI",
|
||||
"All": "全て",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "全てのモデルが正常に削除されました",
|
||||
|
|
@ -272,6 +275,7 @@
|
|||
"Bypass Web Loader": "Webローダーをバイパス",
|
||||
"Cache Base Model List": "ベースモデルリストをキャッシュ",
|
||||
"Calendar": "カレンダー",
|
||||
"Calendars": "",
|
||||
"Call": "コール",
|
||||
"Call feature is not supported when using Web STT engine": "Web STTエンジンを使用している場合、コール機能は使用できません",
|
||||
"Camera": "カメラ",
|
||||
|
|
@ -522,6 +526,7 @@
|
|||
"Delete automation?": "オートメーションを削除しますか?",
|
||||
"Delete Chat": "チャットを削除",
|
||||
"Delete chat?": "チャットを削除しますか?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "フォルダーを削除しますか?",
|
||||
"Delete function?": "Functionを削除しますか?",
|
||||
|
|
@ -828,6 +833,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "ID '{{modelId}}' のモデルはすでに存在します。他のIDを使用してください。",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "モデルIDを空にすることはできません。有効なIDを入力してください。",
|
||||
"Evaluations": "評価",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa APIキー",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "例: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "例: ALL",
|
||||
|
|
@ -1197,6 +1206,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "リスト",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "聞いています...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1207,6 +1217,7 @@
|
|||
"local": "",
|
||||
"Local": "ローカル",
|
||||
"Local Task Model": "ローカルタスクモデル",
|
||||
"Location": "",
|
||||
"Location access not allowed": "位置情報のアクセスが許可されていません",
|
||||
"Lost": "負け",
|
||||
"Low": "低",
|
||||
|
|
@ -1316,6 +1327,7 @@
|
|||
"Models Sharing": "モデルの共有",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek Search APIキー",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "もっと見る",
|
||||
"More Concise": "より簡潔に",
|
||||
|
|
@ -1334,6 +1346,7 @@
|
|||
"New Automation": "新しいオートメーション",
|
||||
"New Button": "新しいボタン",
|
||||
"New Chat": "新しいチャット",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "新しいフォルダ",
|
||||
"New Function": "新しいFunction",
|
||||
|
|
@ -2011,6 +2024,7 @@
|
|||
"Title cannot be an empty string.": "タイトルは空文字列にできません。",
|
||||
"Title Generation": "タイトル生成",
|
||||
"Title Generation Prompt": "タイトル生成プロンプト",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "ダウンロード可能なモデル名にアクセスするには、",
|
||||
"To access the GGUF models available for downloading,": "ダウンロード可能な GGUF モデルにアクセスするには、",
|
||||
|
|
@ -2181,6 +2195,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUIは\"{{url}}\"にリクエストを行います",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUIは\"{{url}}/api/chat\"にリクエストを行います",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUIは\"{{url}}/chat/completions\"にリクエストを行います",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "何を達成したいですか?",
|
||||
"What are you working on?": "何に取り組んでいますか?",
|
||||
|
|
@ -2188,6 +2203,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "新機能",
|
||||
"What's on your mind?": "今の状況を入力してください",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "有効にすると、ユーザのメッセージ送信と同時にリアルタイムで応答を生成します。ライブチャット用途に適しますが、性能の低い環境では動作が重くなる可能性があります。",
|
||||
"wherever you are": "どこにいても",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "出力をページ分けするかどうか。各ページは水平線とページ番号で分割されます。デフォルトでは無効",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "შემცველობის აქ დამატება",
|
||||
"Add Custom Parameter": "მორგებული პარამეტრის დამატება",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "დეტალების დამატება",
|
||||
"Add Files": "ფაილების დამატება",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "მეხსიერების დამატება",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "AI",
|
||||
"All": "ყველა",
|
||||
"All chats have been unarchived.": "ყველა ჩატი ამოღებული იქნა არქივიდან.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "ყველა მოდელი წარმატებით წაიშალა",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "ვებჩამტვირთავის გამოტოვება",
|
||||
"Cache Base Model List": "საბაზისო მოდელების სიის დაკეშვა",
|
||||
"Calendar": "კალენდარი",
|
||||
"Calendars": "",
|
||||
"Call": "ზარი",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "კამერა",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "საუბრის წაშლა",
|
||||
"Delete chat?": "წავშალო ჩატი?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "წავშალო საქაღალდეები?",
|
||||
"Delete function?": "წავშალო ფუნქცია?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "შეფასებები",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API-ის გასაღები",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "მაგალითი: ALL",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "ვისმენ...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "ლოკალური",
|
||||
"Local Task Model": "ლოკალური დავალების მოდელი",
|
||||
"Location": "",
|
||||
"Location access not allowed": "მდებარეობასთან წვდომა დაშვებული არაა",
|
||||
"Lost": "წაგება",
|
||||
"Low": "დაბალი",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "მეტი",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "ახალი ღილაკი",
|
||||
"New Chat": "ახალი მიმოწერა",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "ახალი საქაღალდე",
|
||||
"New Function": "ახალი ფუნქცია",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "სათაურის ველი ცარიელი სტრიქონი ვერ იქნება.",
|
||||
"Title Generation": "სათაურის გენერაცია",
|
||||
"Title Generation Prompt": "სათაურის შექმნის მოთხოვნა",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "ხელმისაწვდომი მოდელის სახელებთან წვდომისთვის, რომ გადმოწეროთ,",
|
||||
"To access the GGUF models available for downloading,": "გადმოსაწერად ხელმისაწვდომი GGUF მოდელებზე წვდომისთვის,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "რას ცდილობთ, მიაღწიოთ?",
|
||||
"What are you working on?": "რაზე მუშაობთ?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "რა არის ახალი",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "სადაც არ უნდა ბრძანდებოდეთ",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Rnu agbur da",
|
||||
"Add Custom Parameter": "Rnu aɣewwar udmawan",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "Rnu talqayt",
|
||||
"Add Files": "Rnu ifuyla",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Rnu cfawat",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "TT",
|
||||
"All": "Akk",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Akk timudmiwin ttwakksent akken iwata",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "Zgel asalay Web",
|
||||
"Cache Base Model List": "Ffer tabdart n tmudmiwin n taffa",
|
||||
"Calendar": "Awitay",
|
||||
"Calendars": "",
|
||||
"Call": "Siwel",
|
||||
"Call feature is not supported when using Web STT engine": "Tamahilt n usiwel ur tettwasefrak ara mi ara tesqedceḍ amsedday Web STT",
|
||||
"Camera": "Takamiṛatt",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Kkes asqerdec",
|
||||
"Delete chat?": "Tebɣiḍ ad tekkseḍ adiwenni?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Kkes akaram?",
|
||||
"Delete function?": "Kkes tasɣent?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Iktazalen",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Tasarut API n Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Amedya: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Amedya: AKK",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Yettmaḥsis…",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "Adigan",
|
||||
"Local Task Model": "Tamudemt n temsekrit tadigant",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Anekcum ɣer tuddna",
|
||||
"Lost": "Iruḥ",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Tasarut API n Mojeek",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Ugar",
|
||||
"More Concise": "Awezlan ugar",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Taqeffalt tamaynut",
|
||||
"New Chat": "Asqerdec amaynut",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Akaram amaynut",
|
||||
"New Function": "Tasɣent tamaynut",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Azwel ur yettili ara d azrir ilem.",
|
||||
"Title Generation": "Asirew n uzwel",
|
||||
"Title Generation Prompt": "Aneftaɣ n usirew n uzwel",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Akken ad tkecmeḍ ɣer yismawen n tmudmiwin yellan i uzdam,",
|
||||
"To access the GGUF models available for downloading,": "Akken ad tkecmeḍ ɣer tmudmin GGUF yellan i usader,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI ad ssutreɣ \"{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI ad ssutreɣ i \"{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI ad ssutreɣ i \"{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Sanda ay tettarmed ad tessiwḍed?",
|
||||
"What are you working on?": "Ɣef wacu ay la tettmahaled?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "D acu d amaynut deg",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "anda yebɣu tiliḍ",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Ma tebɣiḍ ad d-tessugneḍ tuffɣa. Yal asebter ad yebḍu s ulugen igli d wuṭṭun n usebter. Imezwura ɣer False.",
|
||||
|
|
|
|||
|
|
@ -72,9 +72,11 @@
|
|||
"Add content here": "여기에 내용을 추가하세요",
|
||||
"Add Custom Parameter": "사용자 정의 매개변수 추가",
|
||||
"Add Custom Prompt": "사용자 정의 프롬프트 추가",
|
||||
"Add description": "",
|
||||
"Add Details": "디테일 추가",
|
||||
"Add Files": "파일 추가",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "멤버 추가",
|
||||
"Add Members": "멤버 추가",
|
||||
"Add Memory": "메모리 추가",
|
||||
|
|
@ -110,6 +112,7 @@
|
|||
"AI": "",
|
||||
"All": "전체",
|
||||
"All chats have been unarchived.": "모든 채팅이 보관 해제되었습니다.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "성공적으로 모든 모델이 삭제되었습니다",
|
||||
|
|
@ -272,6 +275,7 @@
|
|||
"Bypass Web Loader": "웹 콘텐츠 불러오기 생략",
|
||||
"Cache Base Model List": "기본 모델 목록 캐시",
|
||||
"Calendar": "캘린더",
|
||||
"Calendars": "",
|
||||
"Call": "음성 기능",
|
||||
"Call feature is not supported when using Web STT engine": "웹 STT 엔진 사용 시, 음성 기능은 지원되지 않습니다.",
|
||||
"Camera": "카메라",
|
||||
|
|
@ -522,6 +526,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "채팅 삭제",
|
||||
"Delete chat?": "채팅을 삭제하시겠습니까?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "폴더를 삭제하시겠습니까?",
|
||||
"Delete function?": "함수를 삭제하시겠습니까?",
|
||||
|
|
@ -828,6 +833,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "평가",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API 키",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "예: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "예: 전체",
|
||||
|
|
@ -1197,6 +1206,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "듣는 중...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1207,6 +1217,7 @@
|
|||
"local": "",
|
||||
"Local": "로컬",
|
||||
"Local Task Model": "로컬 작업 모델",
|
||||
"Location": "",
|
||||
"Location access not allowed": "위치 접근이 허용되지 않습니다",
|
||||
"Lost": "패배",
|
||||
"Low": "",
|
||||
|
|
@ -1316,6 +1327,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek Search API 키",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "더보기",
|
||||
"More Concise": "더 간결하게",
|
||||
|
|
@ -1334,6 +1346,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "새 버튼",
|
||||
"New Chat": "새 채팅",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "새 폴더",
|
||||
"New Function": "새 함수",
|
||||
|
|
@ -2011,6 +2024,7 @@
|
|||
"Title cannot be an empty string.": "제목은 빈 문자열일 수 없습니다.",
|
||||
"Title Generation": "제목 생성",
|
||||
"Title Generation Prompt": "제목 생성 프롬프트",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "다운로드 가능한 모델명을 확인하려면,",
|
||||
"To access the GGUF models available for downloading,": "다운로드 가능한 GGUF 모델을 확인하려면,",
|
||||
|
|
@ -2181,6 +2195,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI가 \"{{url}}\"로 요청을 보냅니다",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI가 \"{{url}}/api/chat\"로 요청을 보냅니다",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI가 \"{{url}}/chat/completions\"로 요청을 보냅니다",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "무엇을 성취하고 싶으신가요?",
|
||||
"What are you working on?": "어떤 작업을 하고 계신가요?",
|
||||
|
|
@ -2188,6 +2203,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "새로운 기능:",
|
||||
"What's on your mind?": "무슨 생각을 하고 계신가요?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "활성화하면 모델이 각 채팅 메시지에 실시간으로 응답하여 사용자가 메시지를 보내는 즉시 응답을 생성합니다. 이 모드는 실시간 채팅 애플리케이션에 유용하지만, 느린 하드웨어에서는 성능에 영향을 미칠 수 있습니다.",
|
||||
"wherever you are": "당신이 어디에 있든",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "출력을 페이지로 나눌지 여부입니다. 각 페이지는 구분선과 페이지 번호로 구분됩니다. 기본값은 False입니다.",
|
||||
|
|
|
|||
|
|
@ -75,9 +75,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Pridėti failus",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Pridėti atminį",
|
||||
|
|
@ -113,6 +115,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -275,6 +278,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "Skambinti",
|
||||
"Call feature is not supported when using Web STT engine": "Skambučio funkcionalumas neleidžiamas naudojant Web STT variklį",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -525,6 +529,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Ištrinti pokalbį",
|
||||
"Delete chat?": "Ištrinti pokalbį?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "Ištrinti funkciją",
|
||||
|
|
@ -831,6 +836,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1200,6 +1209,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Klausoma...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1210,6 +1220,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1319,6 +1330,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Daugiau",
|
||||
"More Concise": "",
|
||||
|
|
@ -1337,6 +1349,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Naujas pokalbis",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2017,6 +2030,7 @@
|
|||
"Title cannot be an empty string.": "Pavadinimas negali būti tuščias",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "Pavadinimo generavimo užklausa",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "Tam, kad prieiti prie galimų parsisiųsti modelių",
|
||||
"To access the GGUF models available for downloading,": "Tam, kad prieiti prie galimų parsisiųsti GGUF,",
|
||||
|
|
@ -2187,6 +2201,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2194,6 +2209,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Kas naujo",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Pievienojiet saturu šeit",
|
||||
"Add Custom Parameter": "Pievienot pielāgotu parametru",
|
||||
"Add Custom Prompt": "Pievienot pielāgotu uzvedni",
|
||||
"Add description": "",
|
||||
"Add Details": "Pievienot detaļas",
|
||||
"Add Files": "Pievienot failus",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "Pievienot dalībnieku",
|
||||
"Add Members": "Pievienot dalībniekus",
|
||||
"Add Memory": "Pievienot atmiņu",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "MI",
|
||||
"All": "Visi",
|
||||
"All chats have been unarchived.": "Visas tērzēšanas ir atarhivētas.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Visi modeļi veiksmīgi dzēsti",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "Apiet tīmekļa ielādētāju",
|
||||
"Cache Base Model List": "Kešot bāzes modeļu sarakstu",
|
||||
"Calendar": "Kalendārs",
|
||||
"Calendars": "",
|
||||
"Call": "Zvans",
|
||||
"Call feature is not supported when using Web STT engine": "Zvana funkcija nav atbalstīta, izmantojot Web STT dzinēju",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Dzēst tērzēšanu",
|
||||
"Delete chat?": "Dzēst tērzēšanu?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Dzēst mapi?",
|
||||
"Delete function?": "Dzēst funkciju?",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Kļūda: Modelis ar ID '{{modelId}}' jau eksistē. Lūdzu, izvēlieties citu ID, lai turpinātu.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Kļūda: Modeļa ID nevar būt tukšs. Lūdzu, ievadiet derīgu ID, lai turpinātu.",
|
||||
"Evaluations": "Novērtējumi",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API atslēga",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Piemērs: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Piemērs: ALL",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Ierobežot vienlaicīgos meklēšanas vaicājumus. 0 = neierobežots (noklusējums). Iestatiet 1 secīgai izpildei (ieteicams API ar stingriem ātruma ierobežojumiem, piemēram, Brave bezmaksas līmenim).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "Saraksts",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Klausās...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "lokāls",
|
||||
"Local": "Lokāls",
|
||||
"Local Task Model": "Lokālais uzdevumu modelis",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Atrašanās vietas piekļuve nav atļauta",
|
||||
"Lost": "Zaudēts",
|
||||
"Low": "Zems",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "Modeļu kopīgošana",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Mojeek Search API atslēga",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Vairāk",
|
||||
"More Concise": "Kodolīgāk",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Jauna poga",
|
||||
"New Chat": "Jauna tērzēšana",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Jauna mape",
|
||||
"New Function": "Jauna funkcija",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "Virsraksts nevar būt tukša virkne.",
|
||||
"Title Generation": "Virsraksta ģenerēšana",
|
||||
"Title Generation Prompt": "Virsraksta ģenerēšanas uzvedne",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Lai piekļūtu pieejamajiem modeļu nosaukumiem lejupielādei,",
|
||||
"To access the GGUF models available for downloading,": "Lai piekļūtu GGUF modeļiem, kas pieejami lejupielādei,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI veiks pieprasījumus uz \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI veiks pieprasījumus uz \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI veiks pieprasījumus uz \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Ko jūs mēģināt sasniegt?",
|
||||
"What are you working on?": "Pie kā jūs strādājat?",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "Kas tiek kopīgots:",
|
||||
"What's New in": "Kas jauns",
|
||||
"What's on your mind?": "Par ko jūs domājat?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Ja iespējots, modelis atbildēs uz katru tērzēšanas ziņojumu reāllaikā, ģenerējot atbildi, tiklīdz lietotājs nosūta ziņojumu. Šis režīms ir noderīgs tiešsaistes tērzēšanas lietojumprogrammām, bet var ietekmēt veiktspēju lēnākā aparatūrā.",
|
||||
"wherever you are": "lai kur jūs būtu",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Vai lappušot izvadi. Katra lapa tiks atdalīta ar horizontālu līniju un lapas numuru. Noklusējums ir False.",
|
||||
|
|
|
|||
|
|
@ -72,9 +72,11 @@
|
|||
"Add content here": "Tambah kandungan di sini",
|
||||
"Add Custom Parameter": "Tambah Parameter Tersuai",
|
||||
"Add Custom Prompt": "Tambah Prompt Tersuai",
|
||||
"Add description": "",
|
||||
"Add Details": "Tambah Butiran",
|
||||
"Add Files": "Tambah Fail",
|
||||
"Add Image": "Tambah Imej",
|
||||
"Add location": "",
|
||||
"Add Member": "Tambah Ahli",
|
||||
"Add Members": "Tambah Ahli",
|
||||
"Add Memory": "Tambah Memori",
|
||||
|
|
@ -110,6 +112,7 @@
|
|||
"AI": "AI",
|
||||
"All": "Semua",
|
||||
"All chats have been unarchived.": "Semua sembang telah nyaharkib.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Semua model kini tersembunyi",
|
||||
"All models are now visible": "Semua model kini kelihatan",
|
||||
"All models deleted successfully": "Semua model telah dipadamkan dengan berjaya",
|
||||
|
|
@ -272,6 +275,7 @@
|
|||
"Bypass Web Loader": "Langkau Pemuat Web",
|
||||
"Cache Base Model List": "Senarai Model Asas Cache",
|
||||
"Calendar": "Kalendar",
|
||||
"Calendars": "",
|
||||
"Call": "Hubungi",
|
||||
"Call feature is not supported when using Web STT engine": "Ciri panggilan tidak disokong apabila menggunakan enjin Web STT",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -522,6 +526,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Padam Perbualan",
|
||||
"Delete chat?": "Padam perbualan?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Padam Fail",
|
||||
"Delete folder?": "Padam folder?",
|
||||
"Delete function?": "Padam fungsi?",
|
||||
|
|
@ -828,6 +833,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Ralat: Model dengan ID '{{modelId}}' sudah wujud. Sila pilih ID yang berbeza untuk meneruskan.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Ralat: ID Model tidak boleh kosong. Sila masukkan ID yang sah untuk meneruskan.",
|
||||
"Evaluations": "Penilaian",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Kunci API Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Example: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Contoh: SEMUA",
|
||||
|
|
@ -1197,6 +1206,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Hadkan pertanyaan carian serentak. 0 = tanpa had (lalai). Tetapkan kepada 1 untuk pelaksanaan berurutan (disyorkan untuk API dengan had kadar ketat seperti peringkat percuma Brave).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Mengehadkan jumlah permintaan pembenaman serentak. Tetapkan kepada 0 untuk tanpa had.",
|
||||
"List": "Senarai",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Mendengar...",
|
||||
"Live": "Langsung",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1207,6 +1217,7 @@
|
|||
"local": "tempatan",
|
||||
"Local": "Tempatan",
|
||||
"Local Task Model": "Model Tugas Tempatan",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Akses lokasi tidak dibenarkan",
|
||||
"Lost": "Hilang",
|
||||
"Low": "Rendah",
|
||||
|
|
@ -1316,6 +1327,7 @@
|
|||
"Models Sharing": "Perkongsian Model",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Kunci API Pencarian Mojeek",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Lagi",
|
||||
"More Concise": "Lebih Ringkas",
|
||||
|
|
@ -1334,6 +1346,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Butang Baru",
|
||||
"New Chat": "Perbualan Baru",
|
||||
"New Event": "",
|
||||
"New File": "Fail Baru",
|
||||
"New Folder": "Folder Baru",
|
||||
"New Function": "Fungsi Baru",
|
||||
|
|
@ -2011,6 +2024,7 @@
|
|||
"Title cannot be an empty string.": "Tajuk tidak boleh menjadi rentetan kosong",
|
||||
"Title Generation": "Penjanaan Tajuk",
|
||||
"Title Generation Prompt": "Arahan Penjanaan Tajuk",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Untuk mengakses nama model yang tersedia untuk dimuat turun,",
|
||||
"To access the GGUF models available for downloading,": "Untuk mengakses model GGUF yang tersedia untuk dimuat turun,",
|
||||
|
|
@ -2181,6 +2195,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI akan membuat permintaan ke \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI akan membuat permintaan ke \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI akan membuat permintaan ke \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Apa yang ingin anda capai?",
|
||||
"What are you working on?": "Apa yang sedang anda kerjakan?",
|
||||
|
|
@ -2188,6 +2203,7 @@
|
|||
"What is shared:": "Apa yang dikongsi:",
|
||||
"What's New in": "Apakah yang terbaru dalam",
|
||||
"What's on your mind?": "Apa yang terlintas di fikiran anda?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Apabila didayakan, model akan bertindak balas kepada setiap mesej sembang secara masa nyata, menjana respons sebaik sahaja pengguna menghantar mesej. Mod ini berguna untuk aplikasi sembang langsung, tetapi mungkin menjejaskan prestasi pada perkakasan yang lebih perlahan.",
|
||||
"wherever you are": "di mana pun anda berada",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Sama ada untuk memisahkan output mengikut halaman. Setiap halaman akan dipisahkan oleh garis mendatar dan nombor halaman. Lalai kepada False.",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Legg til innhold her",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Legg til filer",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Legg til minne",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Alle modeller er slettet",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "Kalender",
|
||||
"Calendars": "",
|
||||
"Call": "Ring",
|
||||
"Call feature is not supported when using Web STT engine": "Ringefunksjonen støttes ikke når du bruker Web STT-motoren",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Slett chat",
|
||||
"Delete chat?": "Slette chat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Slette mappe?",
|
||||
"Delete function?": "Slette funksjon?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Vurderinger",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "API-nøkkel for Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Eksempel: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Eksempel: ALL",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Lytter ...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "Lokal",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Tilgang til lokasjon er ikke tillatt",
|
||||
"Lost": "Tapt",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "API-nøekkel for Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Mer",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Ny chat",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Ny mappe",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Tittel kan ikke være en tom streng.",
|
||||
"Title Generation": "Genering av tittel",
|
||||
"Title Generation Prompt": "Ledetekst for tittelgenerering",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Hvis du vil ha tilgang til modellnavn tilgjengelige for nedlasting,",
|
||||
"To access the GGUF models available for downloading,": "Hvis du vil ha tilgang til GGUF-modellene tilgjengelige for nedlasting,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI vil rette forespørsler til \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI vil rette forespørsler til \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Hva prøver du å oppnå?",
|
||||
"What are you working on?": "Hva jobber du på nå?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Hva er nytt i",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Hvis denne modusen er aktivert, svarer modellen på alle chattemeldinger i sanntid, og genererer et svar så snart brukeren sender en melding. Denne modusen er nyttig for live chat-applikasjoner, men kan påvirke ytelsen på tregere maskinvare.",
|
||||
"wherever you are": "uansett hvor du er",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "Voeg hier content toe",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Voeg bestanden toe",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Voeg geheugen toe",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "Alle",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Alle modellen zijn succesvol verwijderd",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "Agenda",
|
||||
"Calendars": "",
|
||||
"Call": "Oproep",
|
||||
"Call feature is not supported when using Web STT engine": "Belfunctie wordt niet ondersteund bij gebruik van de Web STT engine",
|
||||
"Camera": "Camera",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Verwijder chat",
|
||||
"Delete chat?": "Verwijder chat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Verwijder map?",
|
||||
"Delete function?": "Verwijder functie?",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Beoordelingen",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Exa API-sleutel",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Voorbeeld: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Voorbeeld: ALL",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Aan het luisteren...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "Lokaal",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Locatietoegang niet toegestaan",
|
||||
"Lost": "Verloren",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek Search API-sleutel",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Meer",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Nieuwe Chat",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Nieuwe map",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "Titel kan niet leeg zijn.",
|
||||
"Title Generation": "Titelgeneratie",
|
||||
"Title Generation Prompt": "Titel Generatie Prompt",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Om de beschikbare modelnamen voor downloaden te openen,",
|
||||
"To access the GGUF models available for downloading,": "Om toegang te krijgen tot de GGUF-modellen die beschikbaar zijn voor downloaden,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI zal verzoeken doen aan \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI zal verzoeken doen aan \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Wat probeer je te bereiken?",
|
||||
"What are you working on?": "Waar werk je aan?",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Wat is nieuw in",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Als dit is ingeschakeld, reageert het model op elk chatbericht in real-time, waarbij een reactie wordt gegenereerd zodra de gebruiker een bericht stuurt. Deze modus is handig voor live chat-toepassingen, maar kan de prestaties op langzamere hardware beïnvloeden.",
|
||||
"wherever you are": "waar je ook bent",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@
|
|||
"Add content here": "",
|
||||
"Add Custom Parameter": "",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "ਫਾਈਲਾਂ ਸ਼ਾਮਲ ਕਰੋ",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "ਮਿਹਾਨ ਸ਼ਾਮਲ ਕਰੋ",
|
||||
|
|
@ -111,6 +113,7 @@
|
|||
"AI": "",
|
||||
"All": "",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "",
|
||||
|
|
@ -273,6 +276,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "",
|
||||
"Call feature is not supported when using Web STT engine": "",
|
||||
"Camera": "",
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "ਗੱਲਬਾਤ ਮਿਟਾਓ",
|
||||
"Delete chat?": "",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "",
|
||||
"Delete function?": "",
|
||||
|
|
@ -829,6 +834,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1198,6 +1207,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1208,6 +1218,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "",
|
||||
"Low": "",
|
||||
|
|
@ -1317,6 +1328,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "ਹੋਰ",
|
||||
"More Concise": "",
|
||||
|
|
@ -1335,6 +1347,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "ਨਵੀਂ ਗੱਲਬਾਤ",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2013,6 +2026,7 @@
|
|||
"Title cannot be an empty string.": "ਸਿਰਲੇਖ ਖਾਲੀ ਸਤਰ ਨਹੀਂ ਹੋ ਸਕਦਾ।",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "ਸਿਰਲੇਖ ਜਨਰੇਸ਼ਨ ਪ੍ਰੰਪਟ",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਉਪਲਬਧ ਮਾਡਲ ਨਾਮਾਂ ਤੱਕ ਪਹੁੰਚਣ ਲਈ,",
|
||||
"To access the GGUF models available for downloading,": "ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਉਪਲਬਧ GGUF ਮਾਡਲਾਂ ਤੱਕ ਪਹੁੰਚਣ ਲਈ,",
|
||||
|
|
@ -2183,6 +2197,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2190,6 +2205,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "ਨਵਾਂ ਕੀ ਹੈ",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -75,9 +75,11 @@
|
|||
"Add content here": "Dodaj tutaj treść",
|
||||
"Add Custom Parameter": "Dodaj parametr niestandardowy",
|
||||
"Add Custom Prompt": "Dodaj niestandardowy prompt",
|
||||
"Add description": "",
|
||||
"Add Details": "Dodaj szczegóły",
|
||||
"Add Files": "Dodaj pliki",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "Dodaj członka",
|
||||
"Add Members": "Dodaj członków",
|
||||
"Add Memory": "Dodaj wpis do pamięci",
|
||||
|
|
@ -113,6 +115,7 @@
|
|||
"AI": "AI",
|
||||
"All": "Wszystkie",
|
||||
"All chats have been unarchived.": "Wszystkie czaty zostały przywrócone.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Wszystkie modele zostały pomyślnie usunięte.",
|
||||
|
|
@ -275,6 +278,7 @@
|
|||
"Bypass Web Loader": "Pomiń Web Loader",
|
||||
"Cache Base Model List": "Cachuj listę modeli bazowych",
|
||||
"Calendar": "Kalendarz",
|
||||
"Calendars": "",
|
||||
"Call": "Rozmowa",
|
||||
"Call feature is not supported when using Web STT engine": "Funkcja rozmowy nie jest obsługiwana przy użyciu przeglądarkowego silnika STT",
|
||||
"Camera": "Kamera",
|
||||
|
|
@ -525,6 +529,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Usuń czat",
|
||||
"Delete chat?": "Usunąć czat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Usunąć folder?",
|
||||
"Delete function?": "Usunąć funkcję?",
|
||||
|
|
@ -831,6 +836,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Błąd: Model o ID '{{modelId}}' już istnieje. Wybierz inne ID.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Błąd: ID modelu nie może być puste. Wprowadź poprawne ID.",
|
||||
"Evaluations": "Ewaluacje",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Klucz API Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Przykład: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Przykład: ALL",
|
||||
|
|
@ -1200,6 +1209,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Limit jednoczesnych zapytań. 0 = brak (domyślnie). Ustaw 1 dla sekwencyjnego wykonywania (zalecane dla darmowych API np. Brave).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "Lista",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Słucham...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1210,6 +1220,7 @@
|
|||
"local": "lokalny",
|
||||
"Local": "Lokalny",
|
||||
"Local Task Model": "Lokalny Model Zadaniowy",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Brak dostępu do lokalizacji",
|
||||
"Lost": "Przegrano",
|
||||
"Low": "Niski",
|
||||
|
|
@ -1319,6 +1330,7 @@
|
|||
"Models Sharing": "Udostępnianie modeli",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Klucz API Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Więcej",
|
||||
"More Concise": "Bardziej zwięzły",
|
||||
|
|
@ -1337,6 +1349,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Nowy przycisk",
|
||||
"New Chat": "Nowy czat",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "Nowy folder",
|
||||
"New Function": "Nowa funkcja",
|
||||
|
|
@ -2017,6 +2030,7 @@
|
|||
"Title cannot be an empty string.": "Tytuł nie może być pusty.",
|
||||
"Title Generation": "Generowanie tytułu",
|
||||
"Title Generation Prompt": "Prompt generowania tytułu",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Aby zobaczyć nazwy modeli do pobrania,",
|
||||
"To access the GGUF models available for downloading,": "Aby zobaczyć modele GGUF do pobrania,",
|
||||
|
|
@ -2187,6 +2201,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI będzie wysyłać żądania do \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI będzie wysyłać żądania do \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI będzie wysyłać żądania do \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Co chcesz osiągnąć?",
|
||||
"What are you working on?": "Nad czym pracujesz?",
|
||||
|
|
@ -2194,6 +2209,7 @@
|
|||
"What is shared:": "Co jest udostępniane:",
|
||||
"What's New in": "Co nowego w",
|
||||
"What's on your mind?": "O czym myślisz?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Gdy włączone, model odpowiada w czasie rzeczywistym. Tryb przydatny w czacie na żywo, ale może obciążać słabszy sprzęt.",
|
||||
"wherever you are": "gdziekolwiek jesteś",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Określa, czy wyniki mają być stronicowane. Strony są oddzielone linią i numerem. Domyślnie wyłączone.",
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
"Access Control": "Controle de Acesso",
|
||||
"Access Grants": "Concessões de Acesso",
|
||||
"Access List": "Lista de acesso",
|
||||
"Access updated": "",
|
||||
"Access updated": "Acesso atualizado",
|
||||
"Accessible to all users": "Acessível para todos os usuários",
|
||||
"Account": "Conta",
|
||||
"Account Activation Pending": "Ativação da Conta Pendente",
|
||||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Adicionar conteúdo aqui",
|
||||
"Add Custom Parameter": "Adicionar parâmetro personalizado",
|
||||
"Add Custom Prompt": "Adicionar prompt personalizado",
|
||||
"Add description": "",
|
||||
"Add Details": "Adicionar detalhes",
|
||||
"Add Files": "Adicionar Arquivos",
|
||||
"Add Image": "Adicionar imagem",
|
||||
"Add location": "",
|
||||
"Add Member": "Adicionar membro",
|
||||
"Add Members": "Adicionar membros",
|
||||
"Add Memory": "Adicionar Memória",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "IA",
|
||||
"All": "Tudo",
|
||||
"All chats have been unarchived.": "Todos os chats foram desarquivados.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Todos os modelos estão agora ocultos",
|
||||
"All models are now visible": "Todos os modelos estão agora visíveis",
|
||||
"All models deleted successfully": "Todos os modelos foram excluídos com sucesso",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "Ignorar carregador da Web",
|
||||
"Cache Base Model List": "Lista de modelos base de cache",
|
||||
"Calendar": "Calendário",
|
||||
"Calendars": "",
|
||||
"Call": "Chamada",
|
||||
"Call feature is not supported when using Web STT engine": "O recurso de chamada não é suportado ao usar o mecanismo Web STT",
|
||||
"Camera": "Câmera",
|
||||
|
|
@ -441,7 +445,7 @@
|
|||
"Copy Last Response": "Copiar última resposta",
|
||||
"Copy link": "Copiar link",
|
||||
"Copy Link": "Copiar Link",
|
||||
"Copy Path": "",
|
||||
"Copy Path": "Copiar caminho",
|
||||
"Copy Prompt": "Copiar prompt",
|
||||
"Copy Share Link": "Copiar link de compartilhamento",
|
||||
"Copy to clipboard": "Copiar para a área de transferência",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "Excluir automação?",
|
||||
"Delete Chat": "Excluir Chat",
|
||||
"Delete chat?": "Excluir chat?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Excluir arquivo",
|
||||
"Delete folder?": "Excluir pasta?",
|
||||
"Delete function?": "Excluir função?",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Erro: Já existe um modelo com o ID '{{modelId}}'. Selecione um ID diferente para prosseguir.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Erro: O ID do modelo não pode estar vazio. Insira um ID válido para prosseguir.",
|
||||
"Evaluations": "Avaliações",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Chave da API Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Exemplo: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Exemplo: ALL",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Limitar consultas de pesquisa simultâneas. 0 = ilimitado (padrão). Defina como 1 para execução sequencial (recomendado para APIs com limites de taxa rígidos, como o nível gratuito do Brave).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Limita o número de solicitações simultâneas de embedding. Defina como 0 para ilimitado.",
|
||||
"List": "Lista",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Escutando...",
|
||||
"Live": "Ao vivo",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "local",
|
||||
"Local": "Local",
|
||||
"Local Task Model": "Modelo de Tarefa Local",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Acesso ao local não permitido",
|
||||
"Lost": "Perdeu",
|
||||
"Low": "Baixo",
|
||||
|
|
@ -1276,7 +1287,7 @@
|
|||
"Model": "Modelo",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Modelo '{{modelName}}' foi baixado com sucesso.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Modelo '{{modelTag}}' já está na fila para download.",
|
||||
"Model {{modelId}} not found": "",
|
||||
"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
|
||||
"Model {{modelName}} deleted successfully": "Modelo {{modelName}} excluído com sucesso",
|
||||
"Model {{modelName}} is not vision capable": "Modelo {{modelName}} não é capaz de visão",
|
||||
"Model {{name}} is now {{status}}": "Modelo {{name}} está agora {{status}}",
|
||||
|
|
@ -1284,7 +1295,7 @@
|
|||
"Model {{name}} is now visible": "O modelo {{name}} agora está visível",
|
||||
"Model accepts file inputs": "O modelo aceita entradas de arquivo",
|
||||
"Model accepts image inputs": "Modelo aceita entradas de imagens",
|
||||
"Model can access Open Terminal for command execution and file management": "",
|
||||
"Model can access Open Terminal for command execution and file management": "O modelo pode acessar o Open Terminal para execução de comandos e gerenciamento de arquivos",
|
||||
"Model can execute code and perform calculations": "O modelo pode executar código e realizar cálculos",
|
||||
"Model can generate images based on text prompts": "O modelo pode gerar imagens com base em prompts de texto",
|
||||
"Model can search the web for information": "O modelo pode pesquisar informações na web",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "Compartilhamento de Modelos",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Chave de API Mojeek Search",
|
||||
"Month": "",
|
||||
"Monthly": "Mensal",
|
||||
"More": "Mais",
|
||||
"More Concise": "Mais conciso",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "Nova Automação",
|
||||
"New Button": "Novo Botão",
|
||||
"New Chat": "Novo Chat",
|
||||
"New Event": "",
|
||||
"New File": "Novo Arquivo",
|
||||
"New Folder": "Nova Pasta",
|
||||
"New Function": "Nova Função",
|
||||
|
|
@ -1505,7 +1518,7 @@
|
|||
"Password": "Senha",
|
||||
"Passwords do not match.": "As senhas não coincidem.",
|
||||
"Paste Large Text as File": "Cole Textos Longos como Arquivo",
|
||||
"Path copied": "",
|
||||
"Path copied": "Caminho copiado",
|
||||
"Paused": "Em pausa",
|
||||
"PDF document (.pdf)": "Documento PDF (.pdf)",
|
||||
"PDF Extract Images (OCR)": "Extrair Imagens do PDF (OCR)",
|
||||
|
|
@ -1654,7 +1667,7 @@
|
|||
"Reply to thread...": "Responder ao tópico...",
|
||||
"Replying to {{NAME}}": "Respondendo para {{NAME}}",
|
||||
"required": "obrigatório",
|
||||
"Reranking Batch Size": "",
|
||||
"Reranking Batch Size": "Tamanho do lote de reclassificação",
|
||||
"Reranking Engine": "Motor de Reclassificação",
|
||||
"Reranking Model": "Modelo de Reclassificação",
|
||||
"Reset": "Redefinir",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "O Título não pode ser uma string vazia.",
|
||||
"Title Generation": "Geração de Títulos",
|
||||
"Title Generation Prompt": "Prompt de Geração de Título",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Para acessar os nomes de modelos disponíveis para download,",
|
||||
"To access the GGUF models available for downloading,": "Para acessar os modelos GGUF disponíveis para download,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "A WebUI fará requisições para \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "A WebUI fará requisições para \"{{url}}/api/chat\".",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "A WebUI fará requisições para \"{{url}}/chat/completions\".",
|
||||
"Week": "",
|
||||
"Weekly": "Semanal",
|
||||
"What are you trying to achieve?": "O que está tentando alcançar?",
|
||||
"What are you working on?": "No que está trabalhando?",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "O que é compartilhado",
|
||||
"What's New in": "O que há de novo em",
|
||||
"What's on your mind?": "O que você tem em mente?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Quando habilitado, o modelo responderá a cada mensagem de chat em tempo real, gerando uma resposta assim que o usuário enviar uma mensagem. Este modo é útil para aplicativos de chat ao vivo, mas pode impactar o desempenho em hardware mais lento.",
|
||||
"wherever you are": "onde quer que você esteja.",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Se a saída deve ser paginada. Cada página será separada por uma régua horizontal e um número de página. O padrão é Falso.",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Adicionar conteúdo aqui",
|
||||
"Add Custom Parameter": "Adicionar Parâmetro Personalizado",
|
||||
"Add Custom Prompt": "Adicionar Prompt Personalizado",
|
||||
"Add description": "",
|
||||
"Add Details": "Adicionar Detalhes",
|
||||
"Add Files": "Adicionar Ficheiros",
|
||||
"Add Image": "Adicionar Imagem",
|
||||
"Add location": "",
|
||||
"Add Member": "Adicionar Membro",
|
||||
"Add Members": "Adicionar Membros",
|
||||
"Add Memory": "Adicionar memória",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "IA",
|
||||
"All": "Todos",
|
||||
"All chats have been unarchived.": "Todos as conversas foram desarquivadas.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Todos os modelos estão agora ocultos",
|
||||
"All models are now visible": "Todos os modelos estão agora visíveis",
|
||||
"All models deleted successfully": "Todos os modelos foram eliminados com sucesso",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "Ignorar Carregador Web",
|
||||
"Cache Base Model List": "Cache da Lista de Modelos Base",
|
||||
"Calendar": "Calendário",
|
||||
"Calendars": "",
|
||||
"Call": "Chamar",
|
||||
"Call feature is not supported when using Web STT engine": "A funcionalide de Chamar não é suportada quando usa um motor Web STT",
|
||||
"Camera": "Câmara",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Apagar Conversa",
|
||||
"Delete chat?": "Apagar conversa?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Apagar ficheiro",
|
||||
"Delete folder?": "Apagar pasta",
|
||||
"Delete function?": "Apagar função",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Erro: Um modelo com o ID '{{modelId}}' já existe. Por favor, selecione um ID diferente para continuar.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Erro: O ID do modelo não pode estar vazio. Por favor insira o ID válido para continuar.",
|
||||
"Evaluations": "Avaliações",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Chave da API Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Exemplo: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Limitar consultas de pesquisa simultâneas. 0 = ilimitado (padrão). Defina como 1 para execução sequencial (recomendado para APIs com limites de taxa rigorosos, como o nível gratuito do Brave).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Limita o número de solicitações de incorporação simultâneas. Defina como 0 para ilimitado.",
|
||||
"List": "Lista",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "A ouvir...",
|
||||
"Live": "Ao vivo",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "local",
|
||||
"Local": "Local",
|
||||
"Local Task Model": "Modelo de Tarefa Local",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Acesso à localização não permitido",
|
||||
"Lost": "Perdido",
|
||||
"Low": "Baixo",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "Partilha de Modelos",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Chave API de Pesquisa Mojeek",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Mais",
|
||||
"More Concise": "Mais Conciso",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Novo Botão",
|
||||
"New Chat": "Nova Conversa",
|
||||
"New Event": "",
|
||||
"New File": "Novo Ficheiro",
|
||||
"New Folder": "Nova Pasta",
|
||||
"New Function": "Nova Função",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "Título não pode ser uma string vazia.",
|
||||
"Title Generation": "Geração de Título",
|
||||
"Title Generation Prompt": "Prompt de Geração de Título",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Para aceder aos nomes de modelo disponíveis para descarregar,",
|
||||
"To access the GGUF models available for downloading,": "Para aceder aos modelos GGUF disponíveis para descarregar,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "O WebUI fará solicitações para \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "O WebUI fará solicitações para \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "O WebUI fará solicitações para \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "O que você está tentando alcançar?",
|
||||
"What are you working on?": "Em que você está trabalhando?",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "O que é compartilhado:",
|
||||
"What's New in": "O que há de novo em",
|
||||
"What's on your mind?": "O que está A pensaR?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Quando ativado, o modelo responderá a cada mensagem do chat em tempo real, gerando uma resposta assim que o utilizador enviar uma mensagem. Este modo é útil para aplicações de chat ao vivo, mas pode impactar o desempenho em hardware mais lento.",
|
||||
"wherever you are": "onde quer que esteja",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Se deve paginar a saída. Cada página será separada por uma linha horizontal e número de página. Padrão é Falso.",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@
|
|||
"Add content here": "Adăugați conținut aici",
|
||||
"Add Custom Parameter": "Adaugă parametru personalizat",
|
||||
"Add Custom Prompt": "",
|
||||
"Add description": "",
|
||||
"Add Details": "",
|
||||
"Add Files": "Adaugă fișiere",
|
||||
"Add Image": "",
|
||||
"Add location": "",
|
||||
"Add Member": "",
|
||||
"Add Members": "",
|
||||
"Add Memory": "Adaugă memorie",
|
||||
|
|
@ -112,6 +114,7 @@
|
|||
"AI": "",
|
||||
"All": "Toate",
|
||||
"All chats have been unarchived.": "",
|
||||
"All day": "",
|
||||
"All models are now hidden": "",
|
||||
"All models are now visible": "",
|
||||
"All models deleted successfully": "Toate modelele au fost șterse cu succes",
|
||||
|
|
@ -274,6 +277,7 @@
|
|||
"Bypass Web Loader": "",
|
||||
"Cache Base Model List": "",
|
||||
"Calendar": "",
|
||||
"Calendars": "",
|
||||
"Call": "Apel",
|
||||
"Call feature is not supported when using Web STT engine": "Funcția de apel nu este suportată când se utilizează motorul Web STT",
|
||||
"Camera": "Cameră",
|
||||
|
|
@ -524,6 +528,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Șterge Conversația",
|
||||
"Delete chat?": "Șterge conversația?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "",
|
||||
"Delete folder?": "Ștergeți folderul?",
|
||||
"Delete function?": "Șterge funcția?",
|
||||
|
|
@ -830,6 +835,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "",
|
||||
"Evaluations": "Evaluări",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
|
|
@ -1199,6 +1208,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "",
|
||||
"List": "",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Ascult...",
|
||||
"Live": "",
|
||||
"Llama.cpp": "",
|
||||
|
|
@ -1209,6 +1219,7 @@
|
|||
"local": "",
|
||||
"Local": "",
|
||||
"Local Task Model": "",
|
||||
"Location": "",
|
||||
"Location access not allowed": "",
|
||||
"Lost": "Pierdut",
|
||||
"Low": "",
|
||||
|
|
@ -1318,6 +1329,7 @@
|
|||
"Models Sharing": "",
|
||||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Mai multe",
|
||||
"More Concise": "",
|
||||
|
|
@ -1336,6 +1348,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "",
|
||||
"New Chat": "Conversație Nouă",
|
||||
"New Event": "",
|
||||
"New File": "",
|
||||
"New Folder": "",
|
||||
"New Function": "",
|
||||
|
|
@ -2015,6 +2028,7 @@
|
|||
"Title cannot be an empty string.": "Titlul nu poate fi un șir gol.",
|
||||
"Title Generation": "",
|
||||
"Title Generation Prompt": "Prompt de Generare a Titlului",
|
||||
"Title is required": "",
|
||||
"TLS": "",
|
||||
"To access the available model names for downloading,": "Pentru a accesa numele modelelor disponibile pentru descărcare,",
|
||||
"To access the GGUF models available for downloading,": "Pentru a accesa modelele GGUF disponibile pentru descărcare,",
|
||||
|
|
@ -2185,6 +2199,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
|
|
@ -2192,6 +2207,7 @@
|
|||
"What is shared:": "",
|
||||
"What's New in": "Ce e Nou în",
|
||||
"What's on your mind?": "",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
|
||||
"wherever you are": "",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",
|
||||
|
|
|
|||
|
|
@ -75,9 +75,11 @@
|
|||
"Add content here": "Добавить контент сюда",
|
||||
"Add Custom Parameter": "Добавить пользовательский параметр",
|
||||
"Add Custom Prompt": "Добавить пользовательский запрос",
|
||||
"Add description": "",
|
||||
"Add Details": "Добавить детали",
|
||||
"Add Files": "Добавить файлы",
|
||||
"Add Image": "Добавить изображение",
|
||||
"Add location": "",
|
||||
"Add Member": "Добавить участника",
|
||||
"Add Members": "Добавить участников",
|
||||
"Add Memory": "Добавить воспоминание",
|
||||
|
|
@ -113,6 +115,7 @@
|
|||
"AI": "AI",
|
||||
"All": "Все",
|
||||
"All chats have been unarchived.": "Все чаты были разархивированы.",
|
||||
"All day": "",
|
||||
"All models are now hidden": "Все модели скрыты",
|
||||
"All models are now visible": "Все модели видимы",
|
||||
"All models deleted successfully": "Все модели успешно удалены",
|
||||
|
|
@ -275,6 +278,7 @@
|
|||
"Bypass Web Loader": "Обход веб-загрузчика",
|
||||
"Cache Base Model List": "Кэшировать список базовых моделей",
|
||||
"Calendar": "Календарь",
|
||||
"Calendars": "",
|
||||
"Call": "Вызов",
|
||||
"Call feature is not supported when using Web STT engine": "Функция вызова не поддерживается при использовании Web STT (распознавание речи) движка",
|
||||
"Camera": "Камера",
|
||||
|
|
@ -525,6 +529,7 @@
|
|||
"Delete automation?": "",
|
||||
"Delete Chat": "Удалить Чат",
|
||||
"Delete chat?": "Удалить чат?",
|
||||
"Delete Event": "",
|
||||
"Delete File": "Удалить файл",
|
||||
"Delete folder?": "Удалить папку?",
|
||||
"Delete function?": "Удалить функцию?",
|
||||
|
|
@ -831,6 +836,10 @@
|
|||
"Error: A model with the ID '{{modelId}}' already exists. Please select a different ID to proceed.": "Ошибка: Модель с ID '{{modelId}}' уже существует. Пожалуйста, выберите другой ID для продолжения.",
|
||||
"Error: Model ID cannot be empty. Please enter a valid ID to proceed.": "Ошибка: ID модели не может быть пустым. Пожалуйста, введите корректный ID для продолжения.",
|
||||
"Evaluations": "Оценки",
|
||||
"Event created": "",
|
||||
"Event deleted": "",
|
||||
"Event title": "",
|
||||
"Event updated": "",
|
||||
"Exa API Key": "Ключ API для Exa",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Например: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Например: ALL",
|
||||
|
|
@ -1200,6 +1209,7 @@
|
|||
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Лимит параллельных поисковых запросов. 0 = без ограничений (по умолчанию). Установите 1 для последовательного выполнения (рекомендуется для API со строгими лимитами, например, бесплатный тариф Brave).",
|
||||
"Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "Ограничивает число параллельных запросов эмбеддингов. 0 — без ограничений.",
|
||||
"List": "Список",
|
||||
"List calendars, search, create, update, and delete calendar events": "",
|
||||
"Listening...": "Слушаю...",
|
||||
"Live": "Активно",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
|
|
@ -1210,6 +1220,7 @@
|
|||
"local": "локально",
|
||||
"Local": "Локально",
|
||||
"Local Task Model": "Модель локальной задачи",
|
||||
"Location": "",
|
||||
"Location access not allowed": "Доступ к местоположению запрещен",
|
||||
"Lost": "Поражение",
|
||||
"Low": "Низкий",
|
||||
|
|
@ -1319,6 +1330,7 @@
|
|||
"Models Sharing": "Общий доступ к моделям",
|
||||
"Mojeek": "Mojeek",
|
||||
"Mojeek Search API Key": "Ключ API для поиска Mojeek",
|
||||
"Month": "",
|
||||
"Monthly": "",
|
||||
"More": "Больше",
|
||||
"More Concise": "Более кратко",
|
||||
|
|
@ -1337,6 +1349,7 @@
|
|||
"New Automation": "",
|
||||
"New Button": "Новая кнопка",
|
||||
"New Chat": "Новый чат",
|
||||
"New Event": "",
|
||||
"New File": "Новый файл",
|
||||
"New Folder": "Новая папка",
|
||||
"New Function": "Новая функция",
|
||||
|
|
@ -2017,6 +2030,7 @@
|
|||
"Title cannot be an empty string.": "Заголовок не может быть пустой строкой.",
|
||||
"Title Generation": "Генерация заголовка",
|
||||
"Title Generation Prompt": "Промпт для генерации заголовка",
|
||||
"Title is required": "",
|
||||
"TLS": "TLS",
|
||||
"To access the available model names for downloading,": "Чтобы получить доступ к доступным для загрузки названиям моделей,",
|
||||
"To access the GGUF models available for downloading,": "Чтобы получить доступ к моделям GGUF, доступным для загрузки,",
|
||||
|
|
@ -2187,6 +2201,7 @@
|
|||
"WebUI will make requests to \"{{url}}\"": "WebUI будет отправлять запросы к \"{{url}}\"",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI будет отправлять запросы к \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI будет отправлять запросы к \"{{url}}/chat/completions\"",
|
||||
"Week": "",
|
||||
"Weekly": "",
|
||||
"What are you trying to achieve?": "Чего вы пытаетесь достичь?",
|
||||
"What are you working on?": "Над чем вы работаете?",
|
||||
|
|
@ -2194,6 +2209,7 @@
|
|||
"What is shared:": "Что передаётся:",
|
||||
"What's New in": "Что нового в",
|
||||
"What's on your mind?": "О чём хотите поговорить?",
|
||||
"When": "",
|
||||
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Если эта функция включена, модель будет отвечать на каждое сообщение чата в режиме реального времени, генерируя ответ, как только пользователь отправит сообщение. Этот режим полезен для приложений живого чата, но может повлиять на производительность на более медленном оборудовании.",
|
||||
"wherever you are": "где бы вы ни были",
|
||||
"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "Следует ли разбивать выходные данные на страницы. Каждая страница будет разделена горизонтальной линией и номером страницы. По умолчанию установлено значение Выкл.",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue