All files / src/components ChannelList.tsx

0% Statements 0/95
0% Branches 0/1
0% Functions 0/1
0% Lines 0/95

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133                                                                                                                                                                                                                                                                         
import { useEffect } from 'react';
import { useConnectionStore } from '../store/connectionStore';
import { useAgentStore } from '../store/agentStore';
import { useConfigStore } from '../store/configStore';
import { Radio, RefreshCw, MessageCircle, Settings } from 'lucide-react';
 
const CHANNEL_ICONS: Record<string, string> = {
  feishu: '飞',
  qqbot: 'QQ',
  wechat: '微',
};
 
// 可用频道类型(用于显示未配置的频道)
const AVAILABLE_CHANNEL_TYPES = [
  { type: 'feishu', name: '飞书 (Feishu)' },
  { type: 'wechat', name: '微信' },
  { type: 'qqbot', name: 'QQ 机器人' },
];
 
interface ChannelListProps {
  onOpenSettings?: () => void;
}
 
export function ChannelList({ onOpenSettings }: ChannelListProps) {
  const connectionState = useConnectionStore((s) => s.connectionState);
  const loadPluginStatus = useAgentStore((s) => s.loadPluginStatus);
  const channels = useConfigStore((s) => s.channels);
  const loadChannels = useConfigStore((s) => s.loadChannels);
 
  const connected = connectionState === 'connected';
 
  useEffect(() => {
    if (connected) {
      loadPluginStatus().then(() => loadChannels());
    }
  }, [connected]);
 
  const handleRefresh = () => {
    loadPluginStatus().then(() => loadChannels());
  };
 
  // 去重:基于 channel id
  const uniqueChannels = channels.filter((ch, index, self) =>
    index === self.findIndex(c => c.id === ch.id)
  );
 
  // 获取已配置的频道类型
  const configuredTypes = new Set(uniqueChannels.map(c => c.type));
 
  // 未配置的频道类型
  const unconfiguredTypes = AVAILABLE_CHANNEL_TYPES.filter(ct => !configuredTypes.has(ct.type));
 
  if (!connected) {
    return (
      <div className="flex flex-col items-center justify-center h-full text-gray-400 text-xs px-4 text-center">
        <Radio className="w-8 h-8 mb-2 opacity-30" />
        <p>IM 频道</p>
        <p className="mt-1">连接 Gateway 后可用</p>
      </div>
    );
  }
 
  return (
    <div className="h-full flex flex-col">
      {/* Header */}
      <div className="flex items-center justify-between px-3 py-2 border-b border-gray-200">
        <span className="text-xs font-medium text-gray-500">频道列表</span>
        <button
          onClick={handleRefresh}
          className="p-1 text-gray-400 hover:text-orange-500 rounded"
          title="刷新"
        >
          <RefreshCw className="w-3.5 h-3.5" />
        </button>
      </div>
 
      <div className="flex-1 overflow-y-auto custom-scrollbar">
        {/* Configured channels */}
        {uniqueChannels.map((ch) => (
          <div
            key={ch.id}
            className="flex items-center gap-3 px-3 py-3 hover:bg-gray-100 border-b border-gray-50"
          >
            <div className={`w-8 h-8 rounded-lg flex items-center justify-center text-white text-xs font-bold flex-shrink-0 ${
              ch.status === 'active'
                ? 'bg-gradient-to-br from-blue-500 to-indigo-500'
                : 'bg-gray-300'
            }`}>
              {CHANNEL_ICONS[ch.type] || <MessageCircle className="w-4 h-4" />}
            </div>
            <div className="flex-1 min-w-0">
              <div className="text-xs font-medium text-gray-900 truncate">{ch.label}</div>
              <div className={`text-[11px] ${
                ch.status === 'active' ? 'text-green-500' : ch.status === 'error' ? 'text-red-500' : 'text-gray-400'
              }`}>
                {ch.status === 'active' ? '已连接' : ch.status === 'error' ? ch.error || '错误' : '未配置'}
                {ch.accounts !== undefined && ch.accounts > 0 && ` · ${ch.accounts} 个账号`}
              </div>
            </div>
          </div>
        ))}
 
        {/* Unconfigured channels - 只显示一次 */}
        {unconfiguredTypes.map((ct) => (
          <div key={ct.type} className="flex items-center gap-3 px-3 py-3 hover:bg-gray-100 border-b border-gray-50 opacity-60">
            <div className="w-8 h-8 rounded-lg flex items-center justify-center text-white text-xs font-bold flex-shrink-0 bg-gray-300">
              {CHANNEL_ICONS[ct.type] || <MessageCircle className="w-4 h-4" />}
            </div>
            <div className="flex-1 min-w-0">
              <div className="text-xs font-medium text-gray-600">{ct.name}</div>
              <div className="text-[11px] text-gray-400">未配置</div>
            </div>
          </div>
        ))}
 
        {/* Help text */}
        <div className="px-3 py-4 text-center">
          <p className="text-[11px] text-gray-400 mb-2">在设置中配置 IM 频道</p>
          {onOpenSettings && (
            <button
              onClick={onOpenSettings}
              className="inline-flex items-center gap-1 text-xs text-orange-500 hover:text-orange-600"
            >
              <Settings className="w-3 h-3" />
              打开设置
            </button>
          )}
        </div>
      </div>
    </div>
  );
}