| 296 | |
| 297 | // ─── Main Component ───────────────────────────────────── |
| 298 | export default function ChannelConfig({ mode, agentId, canManage = true, values, onChange }: ChannelConfigProps) { |
| 299 | const { t } = useTranslation(); |
| 300 | const queryClient = useQueryClient(); |
| 301 | |
| 302 | // Feishu Permission Mode |
| 303 | const [feishuPermMode, setFeishuPermMode] = useState<'basic' | 'full'>('basic'); |
| 304 | |
| 305 | // Collapsible state per channel |
| 306 | const [openChannels, setOpenChannels] = useState<Record<string, boolean>>({}); |
| 307 | const toggleChannel = (id: string) => setOpenChannels(prev => ({ ...prev, [id]: !prev[id] })); |
| 308 | |
| 309 | // Editing state per channel (edit mode only) |
| 310 | const [editingChannels, setEditingChannels] = useState<Record<string, boolean>>({}); |
| 311 | const setEditing = (id: string, val: boolean) => setEditingChannels(prev => ({ ...prev, [id]: val })); |
| 312 | |
| 313 | // Form state per channel (edit mode only) |
| 314 | const [forms, setForms] = useState<Record<string, Record<string, string>>>({}); |
| 315 | const setFormField = (channelId: string, key: string, val: string) => |
| 316 | setForms(prev => ({ ...prev, [channelId]: { ...prev[channelId], [key]: val } })); |
| 317 | const getForm = (channelId: string) => forms[channelId] || {}; |
| 318 | |
| 319 | // Connection mode state for feishu/wecom (edit mode) |
| 320 | const [connectionModes, setConnectionModes] = useState<Record<string, string>>({ |
| 321 | feishu: 'websocket', |
| 322 | wecom: 'websocket', |
| 323 | dingtalk: 'websocket', |
| 324 | discord: 'gateway', |
| 325 | }); |
| 326 | |
| 327 | // Password visibility |
| 328 | const [showPwds, setShowPwds] = useState<Record<string, boolean>>({}); |
| 329 | const togglePwd = (fieldId: string) => setShowPwds(p => ({ ...p, [fieldId]: !p[fieldId] })); |
| 330 | |
| 331 | // Atlassian test connection state |
| 332 | const [atlassianTesting, setAtlassianTesting] = useState(false); |
| 333 | const [atlassianTestResult, setAtlassianTestResult] = useState<{ ok: boolean; message?: string; tool_count?: number; error?: string } | null>(null); |
| 334 | const [actionFeedback, setActionFeedback] = useState<{ type: 'success' | 'error'; text: string } | null>(null); |
| 335 | const [wechatQr, setWechatQr] = useState<{ qrcode: string; qrcode_img_content: string } | null>(null); |
| 336 | const [wechatQrImageSrc, setWechatQrImageSrc] = useState(''); |
| 337 | const [wechatQrStatus, setWechatQrStatus] = useState(''); |
| 338 | const [wechatLoadingQr, setWechatLoadingQr] = useState(false); |
| 339 | |
| 340 | // ─── Edit mode: queries for each channel ──────────── |
| 341 | const enabled = mode === 'edit' && !!agentId; |
| 342 | |
| 343 | const { data: feishuConfig } = useQuery({ |
| 344 | queryKey: ['channel', agentId], |
| 345 | queryFn: () => channelApi.get(agentId!), |
| 346 | enabled: enabled, |
| 347 | }); |
| 348 | const { data: feishuWebhook } = useQuery({ |
| 349 | queryKey: ['webhook-url', agentId], |
| 350 | queryFn: () => channelApi.webhookUrl(agentId!), |
| 351 | enabled: enabled, |
| 352 | }); |
| 353 | const { data: slackConfig } = useQuery({ |
| 354 | queryKey: ['slack-channel', agentId], |
| 355 | queryFn: () => fetchAuth<any>(`/agents/${agentId}/slack-channel`).catch(() => null), |