From 2649e3305c49cb37101c112ae10ffd8beacb5885 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 13 Aug 2026 16:42:10 -0600 Subject: [PATCH] refac --- backend/open_webui/models/automations.py | 8 +- backend/open_webui/routers/automations.py | 46 +++ backend/open_webui/routers/channels.py | 12 +- backend/open_webui/tools/builtin.py | 13 +- backend/open_webui/utils/automations.py | 181 ++++++---- src/lib/apis/automations/index.ts | 6 + src/lib/components/AutomationModal.svelte | 42 ++- .../automations/AutomationEditor.svelte | 39 ++- .../automations/DestinationDropdown.svelte | 308 ++++++++++++++++++ .../calendar/CreateCalendarModal.svelte | 2 +- src/lib/components/layout/Sidebar.svelte | 7 +- .../KnowledgeBase/NewDirectoryModal.svelte | 2 +- src/routes/(app)/automations/+page.svelte | 31 +- 13 files changed, 608 insertions(+), 89 deletions(-) create mode 100644 src/lib/components/automations/DestinationDropdown.svelte diff --git a/backend/open_webui/models/automations.py b/backend/open_webui/models/automations.py index d3c0649455..1eaaca7175 100644 --- a/backend/open_webui/models/automations.py +++ b/backend/open_webui/models/automations.py @@ -1,6 +1,6 @@ import logging import time -from typing import Optional +from typing import Literal, Optional from uuid import uuid4 from open_webui.internal.db import Base, get_async_db_context @@ -64,11 +64,17 @@ class AutomationTerminalConfig(BaseModel): cwd: Optional[str] = None +class AutomationTarget(BaseModel): + type: Literal['chat', 'channel'] = 'chat' + channel_id: Optional[str] = None + + class AutomationData(BaseModel): prompt: str model_id: str rrule: str terminal: Optional[AutomationTerminalConfig] = None + target: Optional[AutomationTarget] = None class AutomationModel(BaseModel): diff --git a/backend/open_webui/routers/automations.py b/backend/open_webui/routers/automations.py index e01c5c255b..8039a61e24 100644 --- a/backend/open_webui/routers/automations.py +++ b/backend/open_webui/routers/automations.py @@ -15,6 +15,8 @@ from open_webui.models.automations import ( AutomationRuns, Automations, ) +from open_webui.models.access_grants import AccessGrants +from open_webui.models.channels import Channels from open_webui.models.config import Config from open_webui.models.folders import Folders from open_webui.utils.access_control import has_permission @@ -104,6 +106,48 @@ async def check_automation_folder_access(folder_id: Optional[str], user, db: Asy ) +async def check_automation_channel_access(form_data: AutomationForm, user, db: AsyncSession): + target = form_data.data.target + if not target or target.type != 'channel': + return + + if not target.channel_id or not await Config.get('channels.enable'): + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=ERROR_MESSAGES.NOT_FOUND, + ) + + channel = await Channels.get_channel_by_id(target.channel_id, db=db) + if not channel: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=ERROR_MESSAGES.NOT_FOUND, + ) + + if user.role == 'admin': + return + if not await has_permission(user.id, 'features.channels', await Config.get('user.permissions')): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.DEFAULT(), + ) + if channel.type in ['group', 'dm']: + allowed = await Channels.is_user_channel_member(channel.id, user.id, db=db) + else: + allowed = await AccessGrants.has_access( + user_id=user.id, + resource_type='channel', + resource_id=channel.id, + permission='write', + db=db, + ) + if not allowed: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.DEFAULT(), + ) + + async def enrich_automation(automation: AutomationModel, db: AsyncSession, tz: str = None) -> AutomationResponse: """Full enrichment for single-item views (includes next_runs computation).""" last_run = await AutomationRuns.get_latest(automation.id, db=db) @@ -174,6 +218,7 @@ async def create_new_automation( ): await check_automations_permission(request, user) await check_automation_folder_access(form_data.folder_id, user, db) + await check_automation_channel_access(form_data, user, db) try: validate_rrule(form_data.data.rrule, tz=user.timezone) except ValueError as e: @@ -232,6 +277,7 @@ async def update_automation_by_id( automation = await Automations.get_by_id(id, db=db) check_automation_access(automation, user) await check_automation_folder_access(form_data.folder_id, user, db) + await check_automation_channel_access(form_data, user, db) try: validate_rrule(form_data.data.rrule, tz=user.timezone) diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py index 652cbdf07f..1e474b4468 100644 --- a/backend/open_webui/routers/channels.py +++ b/backend/open_webui/routers/channels.py @@ -1075,16 +1075,10 @@ async def model_response_handler(request, channel, message, user, db=None): ], ] - # Resolve model config (same helpers automations use) - from open_webui.utils.automations import ( - _resolve_model_features, - _resolve_model_filter_ids, - _resolve_model_tool_ids, - ) + # Resolve model config (same path automations use) + from open_webui.utils.automations import _resolve_model_defaults - tool_ids = _resolve_model_tool_ids(request.app, model_id) - features = await _resolve_model_features(request.app, model_id) - filter_ids = _resolve_model_filter_ids(request.app, model_id) + tool_ids, features, filter_ids, _ = await _resolve_model_defaults(request.app, model_id) # Build full form_data — same shape as frontend POST. # The channel: prefix routes pipeline events to the diff --git a/backend/open_webui/tools/builtin.py b/backend/open_webui/tools/builtin.py index c338e0471f..ac9a36066d 100644 --- a/backend/open_webui/tools/builtin.py +++ b/backend/open_webui/tools/builtin.py @@ -3602,7 +3602,7 @@ async def create_automation( return JSONCodec.dumps({'error': 'User context not available'}) try: - from open_webui.models.automations import AutomationData, AutomationForm, Automations + from open_webui.models.automations import AutomationData, AutomationForm, AutomationTarget, Automations from open_webui.models.users import Users from open_webui.routers.automations import check_automation_limits from open_webui.utils.automations import next_n_runs_ns, next_run_ns, validate_rrule @@ -3644,6 +3644,11 @@ async def create_automation( prompt=prompt, model_id=model_id, rrule=rrule, + target=( + AutomationTarget(type='channel', channel_id=metadata.get('chat_id', '').removeprefix('channel:')) + if metadata.get('chat_id', '').startswith('channel:') + else None + ), ), is_active=True, ) @@ -3657,6 +3662,7 @@ async def create_automation( 'name': automation.name, 'folder_id': automation.folder_id, 'model_id': model_id, + 'target': automation.data.get('target'), 'is_active': automation.is_active, 'next_runs': next_n_runs_ns(rrule, tz=tz), }, @@ -3695,7 +3701,7 @@ async def update_automation( return JSONCodec.dumps({'error': 'User context not available'}) try: - from open_webui.models.automations import AutomationData, AutomationForm, Automations + from open_webui.models.automations import AutomationData, AutomationForm, AutomationTarget, Automations from open_webui.models.users import Users from open_webui.routers.automations import check_automation_limits from open_webui.utils.automations import next_n_runs_ns, next_run_ns, validate_rrule @@ -3746,6 +3752,7 @@ async def update_automation( prompt=new_prompt, model_id=new_model_id, rrule=new_rrule, + target=AutomationTarget(**automation.data['target']) if automation.data.get('target') else None, ), is_active=automation.is_active, ) @@ -3759,6 +3766,7 @@ async def update_automation( 'name': updated.name, 'folder_id': updated.folder_id, 'model_id': new_model_id, + 'target': updated.data.get('target'), 'is_active': updated.is_active, 'next_runs': next_n_runs_ns(new_rrule, tz=tz), }, @@ -3824,6 +3832,7 @@ async def list_automations( 'folder_id': item.folder_id, 'prompt_snippet': snippet, 'model_id': item.data.get('model_id', ''), + 'target': item.data.get('target'), 'rrule': rrule, 'is_active': item.is_active, 'last_run_at': item.last_run_at, diff --git a/backend/open_webui/utils/automations.py b/backend/open_webui/utils/automations.py index 18da05f985..1d4be9b20b 100644 --- a/backend/open_webui/utils/automations.py +++ b/backend/open_webui/utils/automations.py @@ -36,6 +36,7 @@ from open_webui.models.automations import AutomationModel, AutomationRuns, Autom from open_webui.models.chats import ChatForm, Chats from open_webui.models.config import Config from open_webui.models.folders import Folders +from open_webui.models.messages import MessageForm from open_webui.models.users import Users from open_webui.utils.auth import create_token from open_webui.utils.misc import parse_duration @@ -300,33 +301,17 @@ def _build_request( return request -def _resolve_model_tool_ids(app, model_id: str) -> list[str]: - """Read model-attached tool_ids from model config. - - The frontend does this in Chat.svelte (model.info.meta.toolIds). - The backend never auto-resolves them, so we must do it explicitly. - """ - models = getattr(app.state, 'MODELS', {}) - model = models.get(model_id, {}) - tool_ids = model.get('info', {}).get('meta', {}).get('toolIds', []) - return list(tool_ids) if tool_ids else [] - - -async def _resolve_model_features(app, model_id: str) -> dict: - """Read model default features from model config. - - The frontend does this in Chat.svelte (model.info.meta.defaultFeatureIds - + model.info.meta.capabilities). Enables features like web_search, - code_interpreter, image_generation when the model has them as defaults - AND the capability is enabled AND the admin has enabled the feature. - """ +async def _resolve_model_defaults(app, model_id: str) -> tuple[list[str], dict, list[str], Optional[str]]: models = getattr(app.state, 'MODELS', {}) model = models.get(model_id, {}) meta = model.get('info', {}).get('meta', {}) + tool_ids = list(meta.get('toolIds') or []) + filter_ids = list(meta.get('defaultFilterIds') or []) + terminal_id = meta.get('terminalId') or None default_feature_ids = meta.get('defaultFeatureIds', []) if not default_feature_ids: - return {} + return tool_ids, {}, filter_ids, terminal_id capabilities = meta.get('capabilities') or {} features = {} @@ -344,25 +329,7 @@ async def _resolve_model_features(app, model_id: str) -> dict: if capabilities.get(feature_id) and feature_checks[feature_id]: features[feature_id] = True - return features - - -def _resolve_model_filter_ids(app, model_id: str) -> list[str]: - """Read model default filter_ids from model config.""" - models = getattr(app.state, 'MODELS', {}) - model = models.get(model_id, {}) - filter_ids = model.get('info', {}).get('meta', {}).get('defaultFilterIds', []) - return list(filter_ids) if filter_ids else [] - - -def _resolve_model_terminal_id(app, model_id: str) -> Optional[str]: - """Read model default terminal_id from model config. - - The frontend does this in Chat.svelte (model.info.meta.terminalId). - """ - models = getattr(app.state, 'MODELS', {}) - model = models.get(model_id, {}) - return model.get('info', {}).get('meta', {}).get('terminalId') or None + return tool_ids, features, filter_ids, terminal_id async def _set_terminal_cwd(app, server_id: str, user, cwd: str, chat_id: str) -> None: @@ -413,10 +380,113 @@ async def _set_terminal_cwd(app, server_id: str, user, cwd: str, chat_id: str) - log.warning(f'Failed to set terminal CWD: {e}') +async def _execute_channel_automation( + app, + automation: AutomationModel, + user, + prompt: str, + model_id: str, + token: str, +) -> None: + target = automation.data.get('target') or {} + channel_id = target.get('channel_id') + if not channel_id or not await Config.get('channels.enable'): + raise ValueError('Channel not found') + + model = getattr(app.state, 'MODELS', {}).get(model_id, {}) + request = _build_request(app, token=token) + + from open_webui.routers.channels import new_message_handler + + async with get_async_db() as db: + user_message, channel = await new_message_handler( + request, + channel_id, + MessageForm( + content=prompt, + data={}, + meta={'automation_id': automation.id}, + ), + user, + db, + ) + response_parent_id = ( + user_message.parent_id + if user_message.parent_id + else (user_message.id if await Config.get('channels.model_response_mode', 'thread') == 'thread' else None) + ) + assistant_message, channel = await new_message_handler( + request, + channel.id, + MessageForm( + parent_id=response_parent_id, + content='', + data={}, + meta={ + 'automation_id': automation.id, + 'model_id': model_id, + 'model_name': model.get('name', model_id), + }, + ), + user, + db, + ) + + tool_ids, features, filter_ids, _ = await _resolve_model_defaults(app, model_id) + + form_data = { + 'model': model_id, + 'messages': [ + { + 'role': 'system', + 'content': f'You are {model.get("name", model_id)}, participating in a channel conversation. Be concise and conversational.', + }, + {'role': 'user', 'content': f'{user.name if user else "User"}: {prompt}'}, + ], + 'stream': True, + 'chat_id': f'channel:{channel.id}', + 'id': assistant_message.id, + 'session_id': f'channel:{channel.id}', + 'automation_id': automation.id, + 'background_tasks': {}, + } + if tool_ids: + form_data['tool_ids'] = tool_ids + if features: + form_data['features'] = features + if filter_ids: + form_data['filter_ids'] = filter_ids + + await app.state.CHAT_COMPLETION_HANDLER(request, form_data, user=user) + + from open_webui.socket.main import sio + + await sio.emit( + 'automation:result', + { + 'automation_id': automation.id, + 'name': automation.name, + 'chat_id': f'channel:{channel.id}', + 'message_id': assistant_message.id, + 'status': 'success', + }, + room=f'user:{automation.user_id}', + ) + + await _record_run(automation.id, 'success', chat_id=f'channel:{channel.id}') + await publish_event( + app, + EVENTS.AUTOMATION_RUN_COMPLETED, + actor=user, + subject_id=automation.id, + data={'name': automation.name, 'channel_id': channel.id, 'message_id': assistant_message.id}, + ) + + async def execute_automation(app, automation: AutomationModel) -> None: """Execute an automation through the full chat completion pipeline. - Creates a real chat, then calls chat_completion exactly like the frontend: + Creates a real chat or channel message, then calls chat_completion exactly like the frontend: session_id + chat_id + message_id → async task → pipeline handles everything (filters, model params, knowledge/RAG, tools, DB saves, webhooks). """ @@ -452,6 +522,20 @@ async def execute_automation(app, automation: AutomationModel) -> None: prompt = await prompt_template(automation.data['prompt'], user) model_id = automation.data['model_id'] + try: + expires_delta = parse_duration(str(await Config.get('automations.auth_token_expires_in', '1h'))) + except ValueError: + expires_delta = None + token = create_token( + data={'id': user.id, 'typ': 'automation'}, + expires_delta=expires_delta or timedelta(hours=1), + ) + + target = automation.data.get('target') or {} + if target.get('type') == 'channel': + await _execute_channel_automation(app, automation, user, prompt, model_id, token) + return + folder_id = automation.folder_id if folder_id and not await Folders.get_folder_by_id_and_user_id(folder_id, automation.user_id): await Automations.clear_folder_ids(automation.user_id, [folder_id]) @@ -528,12 +612,7 @@ async def execute_automation(app, automation: AutomationModel) -> None: ) # Resolve model defaults (frontend does this, backend doesn't) - tool_ids = _resolve_model_tool_ids(app, model_id) - features = await _resolve_model_features(app, model_id) - filter_ids = _resolve_model_filter_ids(app, model_id) - - # Resolve terminal from model config - terminal_id = _resolve_model_terminal_id(app, model_id) + tool_ids, features, filter_ids, terminal_id = await _resolve_model_defaults(app, model_id) # Build the same payload the frontend sends to /api/chat/completions form_data = { @@ -564,14 +643,6 @@ async def execute_automation(app, automation: AutomationModel) -> None: # Call the full chat completion pipeline (same as POST /api/chat/completions). # The handler reference is stored on app.state to avoid circular imports. - try: - expires_delta = parse_duration(str(await Config.get('automations.auth_token_expires_in', '1h'))) - except ValueError: - expires_delta = None - token = create_token( - data={'id': user.id, 'typ': 'automation'}, - expires_delta=expires_delta or timedelta(hours=1), - ) request = _build_request(app, token=token) await app.state.CHAT_COMPLETION_HANDLER(request, form_data, user=user) diff --git a/src/lib/apis/automations/index.ts b/src/lib/apis/automations/index.ts index 593a56cab1..1a427e81d3 100644 --- a/src/lib/apis/automations/index.ts +++ b/src/lib/apis/automations/index.ts @@ -5,11 +5,17 @@ export type AutomationTerminalConfig = { cwd?: string; }; +export type AutomationTarget = { + type: 'chat' | 'channel'; + channel_id?: string | null; +}; + export type AutomationData = { prompt: string; model_id: string; rrule: string; terminal?: AutomationTerminalConfig; + target?: AutomationTarget | null; }; export type AutomationForm = { diff --git a/src/lib/components/AutomationModal.svelte b/src/lib/components/AutomationModal.svelte index 23028b3bc8..6a709386df 100644 --- a/src/lib/components/AutomationModal.svelte +++ b/src/lib/components/AutomationModal.svelte @@ -8,9 +8,10 @@ import ScheduleDropdown from '$lib/components/automations/ScheduleDropdown.svelte'; import ModelDropdown from '$lib/components/automations/ModelDropdown.svelte'; - import FolderDropdown from '$lib/components/automations/FolderDropdown.svelte'; + import DestinationDropdown from '$lib/components/automations/DestinationDropdown.svelte'; import { getFolders } from '$lib/apis/folders'; - import { folders } from '$lib/stores'; + import { getChannels } from '$lib/apis/channels'; + import { channels, folders } from '$lib/stores'; import { createAutomation, @@ -30,10 +31,13 @@ let prompt = ''; let model_id = ''; let folder_id = ''; + let target_type: 'chat' | 'channel' = 'chat'; + let channel_id = ''; let is_active = true; let loading = false; let foldersLoaded = false; + let channelsLoaded = false; // Schedule dropdown ref let scheduleDropdown: ScheduleDropdown; @@ -43,6 +47,10 @@ toast.error($i18n.t('Name, prompt, and model are required')); return; } + if (target_type === 'channel' && !channel_id) { + toast.error($i18n.t('Channel is required')); + return; + } if (scheduleDropdown?.frequency === 'ONCE') { const scheduled = new Date(`${scheduleDropdown.onceDate}T${scheduleDropdown.onceTime}`); if (scheduled <= new Date()) { @@ -54,11 +62,12 @@ try { const form: AutomationForm = { name: name.trim(), - folder_id: folder_id || null, + folder_id: target_type === 'channel' ? null : folder_id || null, data: { prompt: prompt.trim(), model_id: model_id.trim(), - rrule: scheduleDropdown.buildRrule() + rrule: scheduleDropdown.buildRrule(), + target: target_type === 'channel' ? { type: 'channel', channel_id } : { type: 'chat' } }, is_active }; @@ -88,12 +97,19 @@ if (res) folders.set(res); foldersLoaded = true; } + if (!channelsLoaded && ($channels ?? []).length === 0) { + const res = await getChannels(localStorage.token).catch(() => null); + if (res) channels.set(res); + channelsLoaded = true; + } if (automation) { name = automation.name; prompt = automation.data.prompt; model_id = automation.data.model_id; folder_id = automation.folder_id ?? ''; + target_type = automation.data.target?.type === 'channel' ? 'channel' : 'chat'; + channel_id = automation.data.target?.channel_id ?? ''; is_active = automation.is_active; if (scheduleDropdown) { scheduleDropdown.parseRrule(automation.data.rrule); @@ -105,6 +121,12 @@ folder_id = ($folders ?? []).some((folder) => folder.id === cloneFrom.folder_id) ? (cloneFrom.folder_id ?? '') : ''; + target_type = cloneFrom.data.target?.type === 'channel' ? 'channel' : 'chat'; + channel_id = ($channels ?? []).some( + (channel) => channel.id === cloneFrom.data.target?.channel_id + ) + ? (cloneFrom.data.target?.channel_id ?? '') + : ''; is_active = true; if (scheduleDropdown) { scheduleDropdown.parseRrule(cloneFrom.data.rrule); @@ -114,6 +136,8 @@ prompt = ''; model_id = ''; folder_id = ''; + target_type = 'chat'; + channel_id = ''; is_active = true; } }; @@ -162,7 +186,15 @@ - +
diff --git a/src/lib/components/automations/AutomationEditor.svelte b/src/lib/components/automations/AutomationEditor.svelte index 1bda35ec74..4e42626e7b 100644 --- a/src/lib/components/automations/AutomationEditor.svelte +++ b/src/lib/components/automations/AutomationEditor.svelte @@ -7,8 +7,9 @@ import localizedFormat from 'dayjs/plugin/localizedFormat'; import type i18nType from '$lib/i18n'; - import { WEBUI_NAME, folders } from '$lib/stores'; + import { WEBUI_NAME, channels, folders } from '$lib/stores'; import { getFolders } from '$lib/apis/folders'; + import { getChannels } from '$lib/apis/channels'; import { getAutomationById, @@ -44,6 +45,7 @@ let hasMoreRuns = true; let runsPage = 0; let foldersLoaded = false; + let channelsLoaded = false; const ensureFolders = async () => { if (foldersLoaded || ($folders ?? []).length > 0) return; @@ -52,11 +54,29 @@ foldersLoaded = true; }; + const ensureChannels = async () => { + if (channelsLoaded || ($channels ?? []).length > 0) return; + const res = await getChannels(localStorage.token).catch(() => null); + if (res) channels.set(res); + channelsLoaded = true; + }; + const getFolderName = (folderId: string | null): string => folderId ? (($folders ?? []).find((folder) => folder.id === folderId)?.name ?? $i18n.t('None')) : $i18n.t('None'); + const getDestinationName = (): string => { + const target = automation.data.target; + if (target?.type === 'channel') { + const channel = ($channels ?? []).find((channel) => channel.id === target.channel_id); + return channel?.name ? `#${channel.name}` : $i18n.t('Channel'); + } + return automation.folder_id + ? `${$i18n.t('Folder')}: ${getFolderName(automation.folder_id)}` + : $i18n.t('New chat'); + }; + const formatTime = (ts: number | null): string => { if (!ts) return '-'; return new Date(ts / 1_000_000).toLocaleString(undefined, { @@ -216,6 +236,7 @@ is_active = automation.is_active; await ensureFolders(); + await ensureChannels(); await loadRuns(); }); @@ -283,10 +304,10 @@
- {$i18n.t('Folder')} + {$i18n.t('Destination')} - {getFolderName(automation.folder_id)} + {getDestinationName()}
@@ -356,11 +377,19 @@ {/if} diff --git a/src/lib/components/automations/DestinationDropdown.svelte b/src/lib/components/automations/DestinationDropdown.svelte new file mode 100644 index 0000000000..1c2c1ab779 --- /dev/null +++ b/src/lib/components/automations/DestinationDropdown.svelte @@ -0,0 +1,308 @@ + + + { + if (!state) { + tab = ''; + folderSearch = ''; + channelSearch = ''; + } + }} +> + + +
+ + {#if tab === ''} +
+ + + + + +
+ {:else if tab === 'folders'} +
+ + +
+ + +
+ +
+ {#each filteredFolderOptions as folder (folder.id)} + {@const path = folderPath(folder)} + + {:else} +
+ {folderOptions.length > 0 ? $i18n.t('No results found') : $i18n.t('No folders')} +
+ {/each} +
+
+ {:else if tab === 'channels'} +
+ + +
+ + +
+ +
+ {#each filteredChannelOptions as channel (channel.id)} + + {:else} +
+ {channelOptions.length > 0 ? $i18n.t('No results found') : $i18n.t('No channels')} +
+ {/each} +
+
+ {/if} +
+
+
diff --git a/src/lib/components/calendar/CreateCalendarModal.svelte b/src/lib/components/calendar/CreateCalendarModal.svelte index d1dafd558b..077568abe0 100644 --- a/src/lib/components/calendar/CreateCalendarModal.svelte +++ b/src/lib/components/calendar/CreateCalendarModal.svelte @@ -76,7 +76,7 @@
-
+
{$i18n.t('Name')}
diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index ccdb3778ea..f5ec62e4b6 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -1186,7 +1186,7 @@
{ if (e.target.scrollTop === 0) { scrollTop = 0; @@ -1289,7 +1289,6 @@ @@ -1301,7 +1300,6 @@ { @@ -1320,7 +1318,6 @@ { showCreateFolderModal = true; @@ -1406,7 +1402,6 @@ { selectedFolder.set(null); diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase/NewDirectoryModal.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase/NewDirectoryModal.svelte index 20f7c7d145..a85f446370 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase/NewDirectoryModal.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase/NewDirectoryModal.svelte @@ -43,7 +43,7 @@
-
+
{$i18n.t('Name')}
{ automationsLayout?.setHeader({ @@ -151,6 +153,13 @@ foldersLoaded = true; }; + const ensureChannels = async () => { + if (channelsLoaded || ($channels ?? []).length > 0) return; + const res = await getChannels(localStorage.token).catch(() => null); + if (res) channels.set(res); + channelsLoaded = true; + }; + const toggleHandler = async (automation: AutomationResponse) => { const res = await toggleAutomationById(localStorage.token, automation.id).catch((err) => { toast.error(`${err}`); @@ -216,6 +225,16 @@ : $i18n.t('Never'); }; + const formatDestination = (automation: AutomationResponse): string => { + if (automation.data.target?.type === 'channel') { + const channel = ($channels ?? []).find( + (channel) => channel.id === automation.data.target?.channel_id + ); + return channel?.name ? `#${channel.name}` : $i18n.t('Channel'); + } + return automation.folder_id ? $i18n.t('Folder') : $i18n.t('New chat'); + }; + const getAllAutomations = async () => { let currentPage = 1; let allAutomations: AutomationResponse[] = []; @@ -341,6 +360,7 @@ loaded = true; syncHeader(); + ensureChannels(); return () => { clearTimeout(searchDebounceTimer); @@ -580,11 +600,14 @@