mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-15 03:57:30 +08:00
hide unavailable channels in webui
This commit is contained in:
@@ -7,7 +7,7 @@ import NavItem from './NavItem';
|
||||
|
||||
const Sidebar: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { token, setToken, sidebarOpen, sidebarCollapsed, setSidebarCollapsed } = useAppContext();
|
||||
const { token, setToken, sidebarOpen, sidebarCollapsed, setSidebarCollapsed, compiledChannels } = useAppContext();
|
||||
const location = useLocation();
|
||||
const [expandedSections, setExpandedSections] = React.useState<Record<string, boolean>>({
|
||||
main: true,
|
||||
@@ -19,6 +19,16 @@ const Sidebar: React.FC = () => {
|
||||
channels: false,
|
||||
});
|
||||
|
||||
const channelChildren = [
|
||||
{ id: 'whatsapp', icon: <Smartphone className="w-4 h-4" />, label: t('whatsappBridge'), to: '/channels/whatsapp' },
|
||||
{ id: 'telegram', icon: <Radio className="w-4 h-4" />, label: t('telegram'), to: '/channels/telegram' },
|
||||
{ id: 'discord', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('discord'), to: '/channels/discord' },
|
||||
{ id: 'feishu', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('feishu'), to: '/channels/feishu' },
|
||||
{ id: 'qq', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('qq'), to: '/channels/qq' },
|
||||
{ id: 'dingtalk', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('dingtalk'), to: '/channels/dingtalk' },
|
||||
{ id: 'maixcam', icon: <MonitorSmartphone className="w-4 h-4" />, label: t('maixcam'), to: '/channels/maixcam' },
|
||||
].filter((item) => compiledChannels.includes(item.id));
|
||||
|
||||
const sections = [
|
||||
{
|
||||
id: 'main',
|
||||
@@ -54,15 +64,7 @@ const Sidebar: React.FC = () => {
|
||||
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' },
|
||||
],
|
||||
children: channelChildren,
|
||||
},
|
||||
{ icon: <Plug className="w-5 h-5" />, label: t('mcpServices'), to: '/mcp' },
|
||||
{ icon: <Clock className="w-5 h-5" />, label: t('cronJobs'), to: '/cron' },
|
||||
@@ -86,6 +88,10 @@ const Sidebar: React.FC = () => {
|
||||
],
|
||||
},
|
||||
];
|
||||
const normalizedSections = sections.map((sec) => ({
|
||||
...sec,
|
||||
items: sec.items.filter((item: any) => !item.children || item.children.length > 0),
|
||||
}));
|
||||
|
||||
const toggle = (id: string) => setExpandedSections((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||
const isSubmenuActive = (items: Array<{ to: string }>) => items.some((item) => location.pathname === item.to);
|
||||
@@ -93,7 +99,7 @@ const Sidebar: React.FC = () => {
|
||||
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) => (
|
||||
{normalizedSections.map((sec) => (
|
||||
<div key={sec.title} className={`sidebar-section rounded-2xl border border-zinc-800/60 ${sidebarCollapsed ? 'p-2' : 'p-2'}`}>
|
||||
{!sidebarCollapsed && (
|
||||
<button
|
||||
|
||||
@@ -5,6 +5,7 @@ type RuntimeSnapshot = {
|
||||
version?: {
|
||||
gateway_version?: string;
|
||||
webui_version?: string;
|
||||
compiled_channels?: string[];
|
||||
};
|
||||
nodes?: {
|
||||
nodes?: any[];
|
||||
@@ -84,6 +85,7 @@ interface AppContextType {
|
||||
loadConfig: (force?: boolean) => Promise<void>;
|
||||
gatewayVersion: string;
|
||||
webuiVersion: string;
|
||||
compiledChannels: string[];
|
||||
hotReloadFields: string[];
|
||||
hotReloadFieldDetails: Array<{ path: string; name?: string; description?: string }>;
|
||||
q: string;
|
||||
@@ -131,6 +133,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const [subagentStreamItems, setSubagentStreamItems] = useState<any[]>([]);
|
||||
const [gatewayVersion, setGatewayVersion] = useState('unknown');
|
||||
const [webuiVersion, setWebuiVersion] = useState('unknown');
|
||||
const [compiledChannels, setCompiledChannels] = useState<string[]>(['telegram', 'whatsapp', 'discord', 'feishu', 'qq', 'dingtalk', 'maixcam']);
|
||||
const [hotReloadFields, setHotReloadFields] = useState<string[]>([]);
|
||||
const [hotReloadFieldDetails, setHotReloadFieldDetails] = useState<Array<{ path: string; name?: string; description?: string }>>([]);
|
||||
|
||||
@@ -237,6 +240,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const j = await r.json();
|
||||
setGatewayVersion(j.gateway_version || 'unknown');
|
||||
setWebuiVersion(j.webui_version || 'unknown');
|
||||
setCompiledChannels(Array.isArray(j.compiled_channels) ? j.compiled_channels : []);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -281,6 +285,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
if (snapshot.version) {
|
||||
setGatewayVersion(snapshot.version.gateway_version || 'unknown');
|
||||
setWebuiVersion(snapshot.version.webui_version || 'unknown');
|
||||
setCompiledChannels(Array.isArray(snapshot.version.compiled_channels) ? snapshot.version.compiled_channels : []);
|
||||
}
|
||||
if (snapshot.nodes) {
|
||||
setNodes(JSON.stringify(Array.isArray(snapshot.nodes.nodes) ? snapshot.nodes.nodes : [], null, 2));
|
||||
@@ -373,7 +378,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
taskQueueItems, setTaskQueueItems, ekgSummary, setEkgSummary,
|
||||
subagentRuntimeItems, setSubagentRuntimeItems, subagentRegistryItems, setSubagentRegistryItems, subagentStreamItems, setSubagentStreamItems,
|
||||
refreshAll, refreshCron, refreshNodes, refreshSkills, refreshSessions, refreshTaskQueue, refreshEKGSummary, refreshVersion, loadConfig,
|
||||
gatewayVersion, webuiVersion, hotReloadFields, hotReloadFieldDetails, q
|
||||
gatewayVersion, webuiVersion, compiledChannels, hotReloadFields, hotReloadFieldDetails, q
|
||||
}}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
|
||||
@@ -400,8 +400,13 @@ const ChannelSettings: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const ui = useUI();
|
||||
const { cfg, setCfg, q, loadConfig } = useAppContext();
|
||||
const key = (channelId || 'whatsapp') as ChannelKey;
|
||||
const { cfg, setCfg, q, loadConfig, compiledChannels } = useAppContext();
|
||||
const availableChannelKeys = useMemo(
|
||||
() => (Object.keys(channelDefinitions) as ChannelKey[]).filter((item) => compiledChannels.includes(item)),
|
||||
[compiledChannels],
|
||||
);
|
||||
const fallbackChannel = availableChannelKeys[0];
|
||||
const key = (channelId || fallbackChannel || 'whatsapp') as ChannelKey;
|
||||
const definition = channelDefinitions[key];
|
||||
|
||||
const [draft, setDraft] = useState<Record<string, any>>({});
|
||||
@@ -409,13 +414,17 @@ const ChannelSettings: React.FC = () => {
|
||||
const [waStatus, setWaStatus] = useState<WhatsAppStatusPayload | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!definition) {
|
||||
navigate('/channels/whatsapp', { replace: true });
|
||||
if (!fallbackChannel) {
|
||||
navigate('/config', { replace: true });
|
||||
return;
|
||||
}
|
||||
if (!definition || !availableChannelKeys.includes(key)) {
|
||||
navigate(`/channels/${fallbackChannel}`, { replace: true });
|
||||
return;
|
||||
}
|
||||
const next = clone(((cfg as any)?.channels?.[definition.id] || {}) as Record<string, any>);
|
||||
setDraft(next);
|
||||
}, [cfg, definition, navigate]);
|
||||
}, [availableChannelKeys, cfg, definition, fallbackChannel, key, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (key !== 'whatsapp') return;
|
||||
@@ -443,7 +452,7 @@ const ChannelSettings: React.FC = () => {
|
||||
return `/webui/api/whatsapp/qr.svg${q}${sep}ts=${encodeURIComponent(String(updatedAt))}`;
|
||||
}, [q, waStatus?.status?.updated_at]);
|
||||
|
||||
if (!definition) return null;
|
||||
if (!definition || !availableChannelKeys.includes(key)) return null;
|
||||
|
||||
const saveChannel = async () => {
|
||||
setSaving(true);
|
||||
|
||||
Reference in New Issue
Block a user