import React, { useState, useEffect, useCallback } from 'react'; import { Plus, Trash2, Copy, Check, RefreshCcw, ToggleLeft, ToggleRight, ExternalLink, Wifi, WifiOff, Loader2 } from 'lucide-react'; import { useAuth } from '../../contexts/AuthContext'; import { useLanguage } from '../../../contexts/LanguageContext'; interface FeishuBotInfo { id: string; appId: string; botName?: string; enabled: boolean; webhookUrl: string; createdAt: string; } type WsState = 'disconnected' | 'connecting' | 'connected' | 'error'; interface WsStatus { botId: string; state: WsState; connectedAt?: string; lastHeartbeat?: string; error?: string; } export const FeishuPluginConfig: React.FC = () => { const { apiKey } = useAuth(); const { t } = useLanguage(); const [bots, setBots] = useState([]); const [loading, setLoading] = useState(true); const [showForm, setShowForm] = useState(false); const [submitting, setSubmitting] = useState(false); const [copiedId, setCopiedId] = useState(null); const [wsStatuses, setWsStatuses] = useState>({}); const [wsLoading, setWsLoading] = useState>({}); const [form, setForm] = useState({ appId: '', appSecret: '', botName: '', verificationToken: '', encryptKey: '', }); const fetchBots = useCallback(async () => { setLoading(true); try { const res = await fetch('/api/feishu/bots', { headers: { 'x-api-key': apiKey }, }); if (res.ok) { const data = await res.json(); setBots(data); } } catch (e) { console.error('Failed to fetch Feishu bots', e); } finally { setLoading(false); } }, [apiKey]); const fetchWsStatuses = useCallback(async () => { try { const res = await fetch('/api/feishu/ws/status', { headers: { 'x-api-key': apiKey }, }); if (res.ok) { const data = await res.json(); const map: Record = {}; for (const s of (data.connections ?? [])) { map[s.botId] = s; } setWsStatuses(map); } } catch (e) { console.error('Failed to fetch WS statuses', e); } }, [apiKey]); useEffect(() => { fetchBots(); }, [fetchBots]); useEffect(() => { fetchWsStatuses(); }, [fetchWsStatuses]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!form.appId || !form.appSecret) return; setSubmitting(true); try { const res = await fetch('/api/feishu/bots', { method: 'POST', headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(form), }); if (res.ok) { await fetchBots(); setShowForm(false); setForm({ appId: '', appSecret: '', botName: '', verificationToken: '', encryptKey: '' }); } } catch (e) { console.error('Failed to create bot', e); } finally { setSubmitting(false); } }; const handleDelete = async (botId: string) => { if (!window.confirm(t('feishuConfirmDelete'))) return; try { await fetch(`/api/feishu/bots/${botId}`, { method: 'DELETE', headers: { 'x-api-key': apiKey }, }); await fetchBots(); } catch (e) { console.error('Failed to delete bot', e); } }; const handleToggle = async (bot: FeishuBotInfo) => { try { await fetch(`/api/feishu/bots/${bot.id}/toggle`, { method: 'PATCH', headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: !bot.enabled }), }); await fetchBots(); } catch (e) { console.error('Failed to toggle bot', e); } }; const handleWsConnect = async (botId: string) => { setWsLoading((prev) => ({ ...prev, [botId]: true })); try { const res = await fetch(`/api/feishu/bots/${botId}/ws/connect`, { method: 'POST', headers: { 'x-api-key': apiKey }, }); if (res.ok) { setWsStatuses((prev) => ({ ...prev, [botId]: { botId, state: 'connecting' }, })); // Poll for status after a short delay setTimeout(async () => { await fetchWsStatuses(); setWsLoading((prev) => ({ ...prev, [botId]: false })); }, 2000); } else { setWsLoading((prev) => ({ ...prev, [botId]: false })); } } catch (e) { console.error('Failed to connect WS', e); setWsLoading((prev) => ({ ...prev, [botId]: false })); } }; const handleWsDisconnect = async (botId: string) => { setWsLoading((prev) => ({ ...prev, [botId]: true })); try { const res = await fetch(`/api/feishu/bots/${botId}/ws/disconnect`, { method: 'POST', headers: { 'x-api-key': apiKey }, }); if (res.ok) { setWsStatuses((prev) => ({ ...prev, [botId]: { botId, state: 'disconnected' }, })); } } catch (e) { console.error('Failed to disconnect WS', e); } finally { setWsLoading((prev) => ({ ...prev, [botId]: false })); } }; const copyWebhookUrl = (url: string, id: string) => { const fullUrl = `${window.location.origin}${url}`; navigator.clipboard.writeText(fullUrl); setCopiedId(id); setTimeout(() => setCopiedId(null), 2000); }; const getWsStateColor = (state: WsState) => { switch (state) { case 'connected': return 'text-emerald-500'; case 'connecting': return 'text-amber-500'; case 'error': return 'text-red-500'; default: return 'text-slate-400'; } }; const getWsStateBg = (state: WsState) => { switch (state) { case 'connected': return 'bg-emerald-50 text-emerald-700 border-emerald-200'; case 'connecting': return 'bg-amber-50 text-amber-700 border-amber-200'; case 'error': return 'bg-red-50 text-red-700 border-red-200'; default: return 'bg-slate-50 text-slate-500 border-slate-200'; } }; const getWsStateLabel = (state: WsState) => { switch (state) { case 'connected': return t('feishuWsConnected'); case 'connecting': return t('feishuWsConnecting'); case 'error': return t('feishuWsError'); default: return t('feishuWsDisconnected'); } }; return (
{/* Header */}
🪶

{t('pluginFeishuName')}

{t('pluginFeishuDesc')}

{/* Setup Guide */}

📌 {t('feishuSetupGuide')}

  1. {t('feishuStep1')}
  2. {t('feishuStep2')}
  3. {t('feishuStep3')}
  4. {t('feishuStep4')}
{t('feishuDocs')}
{/* Add Bot Form */} {showForm && (

{t('feishuAddBot')}

setForm((f) => ({ ...f, appId: e.target.value }))} placeholder="cli_xxxxxxxxxxxxxxxx" className="w-full h-9 px-3 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 outline-none transition" />
setForm((f) => ({ ...f, appSecret: e.target.value }))} placeholder="••••••••••••••••" className="w-full h-9 px-3 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 outline-none transition" />
setForm((f) => ({ ...f, botName: e.target.value }))} placeholder={t('feishuBotNamePlaceholder')} className="w-full h-9 px-3 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 outline-none transition" />
setForm((f) => ({ ...f, verificationToken: e.target.value }))} placeholder={t('feishuTokenPlaceholder')} className="w-full h-9 px-3 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 outline-none transition" />
setForm((f) => ({ ...f, encryptKey: e.target.value }))} placeholder={t('feishuEncryptKeyPlaceholder')} className="w-full h-9 px-3 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 outline-none transition" />
)} {/* Bot List */} {loading ? (
{t('loading')}
) : bots.length === 0 ? (
🪶

{t('feishuNoBots')}

) : (
{bots.map((bot) => { const wsStatus = wsStatuses[bot.id]; const wsState: WsState = wsStatus?.state ?? 'disconnected'; const isWsLoading = wsLoading[bot.id] ?? false; const isWsConnected = wsState === 'connected'; const isWsConnecting = wsState === 'connecting'; return (
{/* Bot Header */}

{bot.botName || bot.appId}

App ID: {bot.appId}

{/* Webhook URL */}

{t('feishuWebhookUrl')}

{window.location.origin}{bot.webhookUrl}
{/* WebSocket Mode Panel */}
{isWsConnected ? ( ) : ( )}

{t('feishuWsMode')}

{t('feishuWsModeDesc')}

{/* WS State Badge + Button */}
{getWsStateLabel(wsState)} {isWsConnected || isWsConnecting ? ( ) : ( )}
{/* Hint text */} {!isWsConnected && (

💡 {t('feishuWsConnectHint')}

)} {/* Connected At info */} {isWsConnected && wsStatus?.connectedAt && (

✓ {t('feishuWsConnected')} · {new Date(wsStatus.connectedAt).toLocaleTimeString()}

)}
{/* Footer badges */}
{bot.enabled ? t('statusRunning') : t('statusStopped')} {t('createdAt')}: {new Date(bot.createdAt).toLocaleDateString()}
); })}
)}
); };