mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-14 18:17:29 +08:00
fix whatsapp
This commit is contained in:
@@ -6,13 +6,14 @@ interface NavItemProps {
|
||||
label: string;
|
||||
to: string;
|
||||
collapsed?: boolean;
|
||||
nested?: boolean;
|
||||
}
|
||||
|
||||
const NavItem: React.FC<NavItemProps> = ({ icon, label, to, collapsed = false }) => (
|
||||
const NavItem: React.FC<NavItemProps> = ({ icon, label, to, collapsed = false, nested = false }) => (
|
||||
<NavLink
|
||||
to={to}
|
||||
title={collapsed ? label : undefined}
|
||||
className={({ isActive }) => `w-full flex items-center ${collapsed ? 'justify-center' : 'gap-3'} px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 border ${
|
||||
className={({ isActive }) => `w-full flex items-center ${collapsed ? 'justify-center' : 'gap-3'} ${nested ? 'px-3 py-2' : 'px-3 py-2.5'} rounded-lg text-sm font-medium transition-all duration-200 border ${
|
||||
isActive
|
||||
? 'nav-item-active text-indigo-700 border-indigo-500/30'
|
||||
: 'text-zinc-400 border-transparent'
|
||||
|
||||
@@ -57,7 +57,7 @@ const PrimitiveArrayEditor: React.FC<{
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{value.length === 0 && <span className="text-xs text-zinc-500 italic">{t('empty')}</span>}
|
||||
{value.map((item, idx) => (
|
||||
<span key={`${item}-${idx}`} className="inline-flex items-center gap-1 px-2 py-1 rounded-xl bg-zinc-900/70 border border-zinc-700 text-xs font-mono text-zinc-200">
|
||||
<span key={`${item}-${idx}`} className="inline-flex items-center gap-1 px-2 py-1 rounded-xl ui-soft-panel text-xs font-mono text-zinc-700 dark:text-zinc-200">
|
||||
{String(item)}
|
||||
<button onClick={() => removeAt(idx)} className="text-zinc-400 hover:text-red-400">×</button>
|
||||
</span>
|
||||
@@ -70,7 +70,7 @@ const PrimitiveArrayEditor: React.FC<{
|
||||
value={draft}
|
||||
onChange={(e) => setDraft(e.target.value)}
|
||||
placeholder={t('recursiveAddValuePlaceholder')}
|
||||
className="w-full bg-zinc-950/70 border border-zinc-800 rounded-xl px-3 py-2 text-sm focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20"
|
||||
className="ui-input rounded-xl px-3 py-2 text-sm"
|
||||
/>
|
||||
<datalist id={`${path}-suggestions`}>
|
||||
{suggestions.map((s) => (
|
||||
@@ -83,7 +83,7 @@ const PrimitiveArrayEditor: React.FC<{
|
||||
addValue(draft);
|
||||
setDraft('');
|
||||
}}
|
||||
className="px-3 py-2 text-xs rounded-xl bg-zinc-800 hover:bg-zinc-700"
|
||||
className="ui-button ui-button-neutral px-3 py-2 text-xs rounded-xl"
|
||||
>
|
||||
{t('add')}
|
||||
</button>
|
||||
@@ -95,7 +95,7 @@ const PrimitiveArrayEditor: React.FC<{
|
||||
setSelected(v);
|
||||
if (v) addValue(v);
|
||||
}}
|
||||
className="px-3 py-2 text-xs rounded-xl bg-zinc-950/70 border border-zinc-800"
|
||||
className="ui-select px-3 py-2 text-xs rounded-xl"
|
||||
>
|
||||
<option value="">{t('recursiveSelectOption')}</option>
|
||||
{suggestions.filter((s) => !value.includes(s)).map((s) => (
|
||||
@@ -129,7 +129,7 @@ const RecursiveConfig: React.FC<RecursiveConfigProps> = ({ data, labels, path =
|
||||
<span className="text-sm font-medium text-zinc-300 block capitalize">{label}</span>
|
||||
<span className="text-[10px] text-zinc-600 font-mono">{currentPath}</span>
|
||||
</div>
|
||||
<div className="p-3 bg-zinc-950/60 border border-zinc-800 rounded-2xl">
|
||||
<div className="ui-soft-panel p-3">
|
||||
{allPrimitive ? (
|
||||
<PrimitiveArrayEditor
|
||||
value={value}
|
||||
@@ -147,7 +147,7 @@ const RecursiveConfig: React.FC<RecursiveConfigProps> = ({ data, labels, path =
|
||||
// ignore invalid json during typing
|
||||
}
|
||||
}}
|
||||
className="w-full min-h-28 bg-zinc-950/70 border border-zinc-800 rounded-xl px-3 py-2 text-sm font-mono focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20"
|
||||
className="ui-textarea w-full min-h-28 rounded-xl px-3 py-2 text-sm font-mono"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -162,7 +162,7 @@ const RecursiveConfig: React.FC<RecursiveConfigProps> = ({ data, labels, path =
|
||||
<span className="w-1.5 h-4 bg-indigo-500 rounded-full" />
|
||||
{label}
|
||||
</h3>
|
||||
<div className="pl-6 border-l border-zinc-800/50">
|
||||
<div className="pl-6 border-l border-zinc-800/50 dark:border-zinc-700/50">
|
||||
<RecursiveConfig data={value} labels={labels} path={currentPath} onChange={onChange} hotPaths={hotPaths} onlyHot={onlyHot} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,7 +176,7 @@ const RecursiveConfig: React.FC<RecursiveConfigProps> = ({ data, labels, path =
|
||||
<span className="text-[10px] text-zinc-600 font-mono">{currentPath}</span>
|
||||
</div>
|
||||
{typeof value === 'boolean' ? (
|
||||
<label className="flex items-center gap-3 p-3 bg-zinc-950/60 border border-zinc-800 rounded-2xl cursor-pointer hover:border-zinc-700 transition-colors group">
|
||||
<label className="ui-toggle-card flex items-center gap-3 p-3 cursor-pointer transition-colors group">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
@@ -192,7 +192,7 @@ const RecursiveConfig: React.FC<RecursiveConfigProps> = ({ data, labels, path =
|
||||
type={typeof value === 'number' ? 'number' : 'text'}
|
||||
value={value === null || value === undefined ? '' : String(value)}
|
||||
onChange={(e) => onChange(currentPath, typeof value === 'number' ? Number(e.target.value) : e.target.value)}
|
||||
className="w-full bg-zinc-950/70 border border-zinc-800 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 transition-colors font-mono text-zinc-300"
|
||||
className="ui-input w-full rounded-xl px-3 py-2.5 text-sm transition-colors font-mono"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { LayoutDashboard, MessageSquare, Settings, Clock, Terminal, Zap, FolderOpen, ClipboardList, BrainCircuit, Hash, Bot, Boxes, PanelLeftClose, PanelLeftOpen, Plug } from 'lucide-react';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import { LayoutDashboard, MessageSquare, Settings, Clock, Terminal, Zap, FolderOpen, ClipboardList, BrainCircuit, Hash, Bot, Boxes, PanelLeftClose, PanelLeftOpen, Plug, Smartphone, ChevronDown, Radio, MonitorSmartphone } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
import NavItem from './NavItem';
|
||||
@@ -7,9 +8,20 @@ import NavItem from './NavItem';
|
||||
const Sidebar: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { token, setToken, sidebarOpen, sidebarCollapsed, setSidebarCollapsed } = useAppContext();
|
||||
const location = useLocation();
|
||||
const [expandedSections, setExpandedSections] = React.useState<Record<string, boolean>>({
|
||||
main: true,
|
||||
agents: true,
|
||||
ops: true,
|
||||
config: true,
|
||||
knowledge: true,
|
||||
insights: true,
|
||||
channels: true,
|
||||
});
|
||||
|
||||
const sections = [
|
||||
{
|
||||
id: 'main',
|
||||
title: t('sidebarMain'),
|
||||
items: [
|
||||
{ icon: <LayoutDashboard className="w-5 h-5" />, label: t('dashboard'), to: '/' },
|
||||
@@ -17,6 +29,7 @@ const Sidebar: React.FC = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'agents',
|
||||
title: t('sidebarAgents'),
|
||||
items: [
|
||||
{ icon: <Boxes className="w-5 h-5" />, label: t('subagentsRuntime'), to: '/subagents' },
|
||||
@@ -24,6 +37,7 @@ const Sidebar: React.FC = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ops',
|
||||
title: t('sidebarOps'),
|
||||
items: [
|
||||
{ icon: <Terminal className="w-5 h-5" />, label: t('nodes'), to: '/nodes' },
|
||||
@@ -32,14 +46,30 @@ const Sidebar: React.FC = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'config',
|
||||
title: t('sidebarConfig'),
|
||||
items: [
|
||||
{ icon: <Settings className="w-5 h-5" />, label: t('config'), to: '/config' },
|
||||
{
|
||||
icon: <Smartphone className="w-5 h-5" />,
|
||||
label: t('channelsGroup'),
|
||||
childrenId: 'channels',
|
||||
children: [
|
||||
{ icon: <Smartphone className="w-4 h-4" />, label: t('whatsappBridge'), to: '/channels/whatsapp' },
|
||||
{ icon: <Radio className="w-4 h-4" />, label: t('telegram'), to: '/channels/telegram' },
|
||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('discord'), to: '/channels/discord' },
|
||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('feishu'), to: '/channels/feishu' },
|
||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('qq'), to: '/channels/qq' },
|
||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('dingtalk'), to: '/channels/dingtalk' },
|
||||
{ icon: <MonitorSmartphone className="w-4 h-4" />, label: t('maixcam'), to: '/channels/maixcam' },
|
||||
],
|
||||
},
|
||||
{ icon: <Plug className="w-5 h-5" />, label: t('mcpServices'), to: '/mcp' },
|
||||
{ icon: <Clock className="w-5 h-5" />, label: t('cronJobs'), to: '/cron' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'knowledge',
|
||||
title: t('sidebarKnowledge'),
|
||||
items: [
|
||||
{ icon: <FolderOpen className="w-5 h-5" />, label: t('memory'), to: '/memory' },
|
||||
@@ -47,6 +77,7 @@ const Sidebar: React.FC = () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'insights',
|
||||
title: t('sidebarInsights'),
|
||||
items: [
|
||||
{ icon: <Terminal className="w-5 h-5" />, label: t('logs'), to: '/logs' },
|
||||
@@ -56,17 +87,60 @@ const Sidebar: React.FC = () => {
|
||||
},
|
||||
];
|
||||
|
||||
const toggle = (id: string) => setExpandedSections((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||
const isSubmenuActive = (items: Array<{ to: string }>) => items.some((item) => location.pathname === item.to);
|
||||
|
||||
return (
|
||||
<aside className={`sidebar-shell fixed md:static inset-y-14 md:inset-y-16 left-0 z-40 ${sidebarCollapsed ? 'md:w-20' : 'md:w-64'} w-[86vw] max-w-72 border-r border-zinc-800 backdrop-blur-xl flex flex-col shrink-0 transform transition-all duration-200 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}`}>
|
||||
<nav className={`flex-1 ${sidebarCollapsed ? 'px-2' : 'px-3'} py-3 space-y-2 overflow-y-auto`}>
|
||||
{sections.map((sec) => (
|
||||
<div key={sec.title} className={`sidebar-section rounded-2xl border border-zinc-800/60 ${sidebarCollapsed ? 'p-2' : 'p-2'}`}>
|
||||
{!sidebarCollapsed && <div className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest px-2 pb-1.5">{sec.title}</div>}
|
||||
<div className="space-y-0.5">
|
||||
{sec.items.map((it) => (
|
||||
<NavItem key={it.to} icon={it.icon} label={it.label} to={it.to} collapsed={sidebarCollapsed} />
|
||||
))}
|
||||
</div>
|
||||
{!sidebarCollapsed && (
|
||||
<button
|
||||
onClick={() => toggle(sec.id)}
|
||||
className="flex w-full items-center justify-between px-2 pb-1.5 text-[10px] font-bold uppercase tracking-widest text-zinc-500"
|
||||
>
|
||||
<span>{sec.title}</span>
|
||||
<ChevronDown className={`h-3.5 w-3.5 transition-transform ${expandedSections[sec.id] ? 'rotate-0' : '-rotate-90'}`} />
|
||||
</button>
|
||||
)}
|
||||
{(sidebarCollapsed || expandedSections[sec.id]) && (
|
||||
<div className="space-y-0.5">
|
||||
{sec.items.map((it: any) => {
|
||||
if (!it.children) {
|
||||
return <NavItem key={it.to} icon={it.icon} label={it.label} to={it.to} collapsed={sidebarCollapsed} />;
|
||||
}
|
||||
const submenuActive = isSubmenuActive(it.children);
|
||||
const childrenOpen = sidebarCollapsed || expandedSections[it.childrenId];
|
||||
return (
|
||||
<div key={it.childrenId} className="space-y-1">
|
||||
<button
|
||||
onClick={() => !sidebarCollapsed && toggle(it.childrenId)}
|
||||
className={`w-full flex items-center ${sidebarCollapsed ? 'justify-center' : 'gap-3'} px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 border ${
|
||||
submenuActive ? 'nav-item-active text-indigo-700 border-indigo-500/30' : 'text-zinc-400 border-transparent'
|
||||
}`}
|
||||
title={sidebarCollapsed ? it.label : undefined}
|
||||
>
|
||||
<span className="shrink-0">{it.icon}</span>
|
||||
{!sidebarCollapsed && (
|
||||
<>
|
||||
<span className="min-w-0 flex-1 truncate text-left">{it.label}</span>
|
||||
<ChevronDown className={`h-4 w-4 transition-transform ${childrenOpen ? 'rotate-0' : '-rotate-90'}`} />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{childrenOpen && !sidebarCollapsed && (
|
||||
<div className="ml-4 space-y-1 border-l border-zinc-800/70 pl-2">
|
||||
{it.children.map((child: any) => (
|
||||
<NavItem key={child.to} icon={child.icon} label={child.label} to={child.to} nested />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
Reference in New Issue
Block a user