diff --git a/src/pages/Channels/index.tsx b/src/pages/Channels/index.tsx index 176a09205..22a814b88 100644 --- a/src/pages/Channels/index.tsx +++ b/src/pages/Channels/index.tsx @@ -111,18 +111,20 @@ export function Channels() { ); } + const safeChannels = Array.isArray(channels) ? channels : []; + return (
{/* Header */} -
+

- {t('title', 'Messaging Channels')} + {t('title') || 'Channels'}

- {t('subtitle', 'Manage your messaging channels and connections')} + {t('subtitle') || 'Connect to messaging platforms.'}

@@ -133,15 +135,16 @@ export function Channels() { void fetchChannels(); void fetchConfiguredTypes(); }} + disabled={gatewayStatus.state !== 'running'} className="h-9 text-[13px] font-medium rounded-full px-4 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5 shadow-none text-foreground/80 hover:text-foreground transition-colors" > - + {t('refresh')}
{/* Content Area */} -
+
{/* Gateway Warning */} {gatewayStatus.state !== 'running' && ( @@ -164,13 +167,13 @@ export function Channels() { )} {/* Available Channels (Configured) */} - {channels.length > 0 && ( + {safeChannels.length > 0 && (

Available Channels

-
- {channels.map((channel) => ( +
+ {safeChannels.map((channel) => ( {displayedChannelTypes.map((type) => { const meta = CHANNEL_META[type]; - const isConfigured = channels.some(c => c.type === type) || configuredTypes.includes(type); + const isConfigured = safeChannels.some(c => c.type === type) || configuredTypes.includes(type); // Hide already configured channels from "Supported Channels" section if (isConfigured) return null; diff --git a/src/pages/Cron/index.tsx b/src/pages/Cron/index.tsx index d74dcd1ef..f3e28a22a 100644 --- a/src/pages/Cron/index.tsx +++ b/src/pages/Cron/index.tsx @@ -284,8 +284,8 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { onClick={() => setSchedule(preset.value)} className={cn( "justify-start h-10 rounded-xl font-medium text-[13px] transition-all", - schedule === preset.value - ? "bg-[#0a84ff] hover:bg-[#007aff] text-white shadow-sm border-transparent" + schedule === preset.value + ? "bg-[#0a84ff] hover:bg-[#007aff] text-white shadow-sm border-transparent" : "bg-[#eeece3] dark:bg-[#151514] border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/5 text-foreground/80 hover:text-foreground" )} > @@ -387,7 +387,7 @@ function CronJobCard({ job, onToggle, onEdit, onDelete, onTrigger }: CronJobCard }; return ( -
@@ -399,11 +399,11 @@ function CronJobCard({ job, onToggle, onEdit, onDelete, onTrigger }: CronJobCard

{job.name}

-
@@ -413,7 +413,7 @@ function CronJobCard({ job, onToggle, onEdit, onDelete, onTrigger }: CronJobCard

- +
e.stopPropagation()}>
- +
@@ -483,9 +483,9 @@ function CronJobCard({ job, onToggle, onEdit, onDelete, onTrigger }: CronJobCard )} {t('card.runNow')} -
-

{jobs.length}

+

{safeJobs.length}

{t('stats.total')}

- +
@@ -658,7 +659,7 @@ export function Cron() {
{/* Jobs List */} - {jobs.length === 0 ? ( + {safeJobs.length === 0 ? (

{t('empty.title')}

@@ -679,7 +680,7 @@ export function Cron() {
) : (
- {jobs.map((job) => ( + {safeJobs.map((job) => ( {t('connectedChannels')} - {channels.length === 0 ? ( + {(!Array.isArray(channels) || channels.length === 0) ? ( {t('activeSkills')} - {skills.filter((s) => s.enabled).length === 0 ? ( + {(!Array.isArray(skills) || skills.filter((s) => s.enabled).length === 0) ? ( { if (!skill) return; - + // API Key if (skill.config?.apiKey) { setApiKey(String(skill.config.apiKey)); @@ -88,14 +88,14 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall }: Sk method: 'POST', body: JSON.stringify({ slug: skill.slug }), }); - if (result.success) { - toast.success(t('toast.openedEditor')); - } else { - toast.error(result.error || t('toast.failedEditor')); - } - } catch (err) { - toast.error(t('toast.failedEditor') + ': ' + String(err)); + if (result.success) { + toast.success(t('toast.openedEditor')); + } else { + toast.error(result.error || t('toast.failedEditor')); } + } catch (err) { + toast.error(t('toast.failedEditor') + ': ' + String(err)); + } }; const handleAddEnv = () => { @@ -157,20 +157,20 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall }: Sk return ( !open && onClose()}> - {/* Scrollable Content */}
- {skill.icon || '🔧'} - {skill.isCore && ( -
- -
- )} + {skill.icon || '🔧'} + {skill.isCore && ( +
+ +
+ )}

{skill.name} @@ -239,7 +239,7 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall }: Sk
{envVars.length === 0 && ( -
+
{t('detail.noEnvVars', 'No environment variables configured.')}
)} @@ -286,14 +286,14 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall }: Sk
)}
- + {/* Centered Footer Buttons */}
{!skill.isCore && ( - )} @@ -380,7 +380,8 @@ export function Skills() { }, [fetchSkills, isGatewayRunning]); // Filter skills - const filteredSkills = skills.filter((skill) => { + const safeSkills = Array.isArray(skills) ? skills : []; + const filteredSkills = safeSkills.filter((skill) => { const matchesSearch = skill.name.toLowerCase().includes(searchQuery.toLowerCase()) || skill.description.toLowerCase().includes(searchQuery.toLowerCase()); @@ -404,9 +405,9 @@ export function Skills() { }); const sourceStats = { - all: skills.length, - builtIn: skills.filter(s => s.isBundled).length, - marketplace: skills.filter(s => !s.isBundled).length, + all: safeSkills.length, + builtIn: safeSkills.filter(s => s.isBundled).length, + marketplace: safeSkills.filter(s => !s.isBundled).length, }; const bulkToggleVisible = useCallback(async (enable: boolean) => { @@ -453,7 +454,7 @@ export function Skills() { } }, [enableSkill, disableSkill, t]); - const hasInstalledSkills = skills.some(s => !s.isBundled); + const hasInstalledSkills = safeSkills.some(s => !s.isBundled); const handleOpenSkillsFolder = useCallback(async () => { try { @@ -548,7 +549,7 @@ export function Skills() { return (
- + {/* Header */}
@@ -559,11 +560,11 @@ export function Skills() { {t('subtitle') || 'Browse and manage AI capabilities.'}

- +
{hasInstalledSkills && ( - - ) : ( - - )} + {skill.version && ( + + v{skill.version} + + )} + {isInstalled ? ( + + ) : ( + + )}
);