Header.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. /**
  2. * 页面头部组件 - 移动端优化
  3. *
  4. * 核心概念:
  5. * - Web UI 是 MCP 测试工具,不需要全局登录
  6. * - 显示已连接的 MCP 服务器数量
  7. * - 移动端:两行布局,避免挤在一起
  8. */
  9. 'use client';
  10. import { useState, useEffect } from 'react';
  11. import Link from 'next/link';
  12. import { MCP_SERVERS, mcpTokenManager } from '@/lib/mcp-token-manager';
  13. export default function Header() {
  14. // 客户端挂载标志 - 用于避免 hydration 错误
  15. const [mounted, setMounted] = useState(false);
  16. // 初始值设为 0,避免 SSR/CSR 不匹配
  17. const [enabledCount, setEnabledCount] = useState(0);
  18. const [loggedInCount, setLoggedInCount] = useState(0);
  19. // 从 MCP_SERVERS 配置获取总数
  20. const total = Object.keys(MCP_SERVERS).length;
  21. useEffect(() => {
  22. // 设置客户端挂载标志
  23. setMounted(true);
  24. // 从 localStorage 读取真实状态(只在客户端执行)
  25. const updateStatus = () => {
  26. const enabled = mcpTokenManager.getEnabledMcpList().length;
  27. const loggedIn = mcpTokenManager.getLoggedInMcpList().length;
  28. console.log('[Header] updateStatus:', { enabled, loggedIn });
  29. setEnabledCount(enabled);
  30. setLoggedInCount(loggedIn);
  31. };
  32. // 初始化时更新状态
  33. updateStatus();
  34. // 监听 storage 变化(其他标签页修改 localStorage)
  35. const handleStorageChange = () => updateStatus();
  36. window.addEventListener('storage', handleStorageChange);
  37. // 自定义事件:MCP 启用状态变化(当前页面内)
  38. const handleMcpEnabledChange = () => updateStatus();
  39. window.addEventListener('mcp-enabled-change', handleMcpEnabledChange);
  40. // 监听 focus 事件,确保页面获得焦点时更新状态
  41. const handleFocus = () => updateStatus();
  42. window.addEventListener('focus', handleFocus);
  43. return () => {
  44. window.removeEventListener('storage', handleStorageChange);
  45. window.removeEventListener('mcp-enabled-change', handleMcpEnabledChange);
  46. window.removeEventListener('focus', handleFocus);
  47. };
  48. }, []); // 空依赖数组,只在挂载时执行一次
  49. return (
  50. <header className="bg-white dark:bg-gray-800 border-b dark:border-gray-700 px-3 py-2 md:px-6 md:py-4 safe-area-inset-top" suppressHydrationWarning>
  51. {/* 移动端:两行布局 */}
  52. <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-2 md:gap-0">
  53. {/* 第一行:标题和导航 */}
  54. <div className="flex items-center justify-between">
  55. <h1 className="text-lg md:text-xl font-bold text-gray-800 dark:text-white">
  56. AI MCP Web UI
  57. </h1>
  58. <nav className="flex space-x-2 md:space-x-4">
  59. <Link href="/" className="text-sm md:text-base text-gray-600 dark:text-gray-300 hover:text-blue-500 px-2 py-1">
  60. 聊天
  61. </Link>
  62. <Link href="/mcp" className="text-sm md:text-base text-gray-600 dark:text-gray-300 hover:text-blue-500 px-2 py-1">
  63. MCP 管理
  64. </Link>
  65. </nav>
  66. </div>
  67. {/* 第二行(移动端)/ 右侧(桌面端):MCP 状态 */}
  68. <div className="flex items-center justify-between md:justify-end space-x-2 md:space-x-4">
  69. {/* MCP 连接状态 */}
  70. <div className="flex items-center space-x-1 md:space-x-2 text-xs md:text-sm">
  71. <span className="text-gray-600 dark:text-gray-400">MCP:</span>
  72. <span className={`px-1.5 md:px-2 py-0.5 md:py-1 rounded ${
  73. mounted && enabledCount > 0
  74. ? 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200'
  75. : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400'
  76. }`}>
  77. {mounted ? `已启用 ${enabledCount}/${total}` : '加载中...'}
  78. </span>
  79. {mounted && loggedInCount > 0 && (
  80. <span className="hidden sm:inline-block px-1.5 md:px-2 py-0.5 md:py-1 rounded bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200">
  81. 已登录 {loggedInCount}/{total}
  82. </span>
  83. )}
  84. </div>
  85. <Link
  86. href="/mcp"
  87. className="text-xs md:text-sm px-2 md:px-4 py-1.5 md:py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
  88. >
  89. {mounted ? (loggedInCount > 0 ? '管理' : '连接 MCP') : 'MCP'}
  90. </Link>
  91. </div>
  92. </div>
  93. </header>
  94. );
  95. }