dashboard-nanobot/frontend/src/modules/onboarding/components/BotWizardChannelModal.tsx

221 lines
9.3 KiB
TypeScript

import { Plus, Trash2 } from 'lucide-react';
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
import { LucentSelect } from '../../../components/lucent/LucentSelect';
import { PasswordInput } from '../../../components/PasswordInput';
import type { OnboardingChannelLabels } from '../localeTypes';
import type { ChannelType, WizardChannelConfig } from '../types';
import { EMPTY_CHANNEL_PICKER } from '../types';
interface BotWizardChannelModalProps {
open: boolean;
lc: OnboardingChannelLabels;
passwordToggleLabels: { show: string; hide: string };
channels: WizardChannelConfig[];
sendProgress: boolean;
sendToolHints: boolean;
addableChannelTypes: ChannelType[];
newChannelType: ChannelType | '';
onClose: () => void;
onUpdateGlobalDeliveryFlag: (key: 'sendProgress' | 'sendToolHints', value: boolean) => void;
onUpdateChannel: (index: number, patch: Partial<WizardChannelConfig>) => void;
onRemoveChannel: (index: number) => void;
onSetNewChannelType: (value: ChannelType | '') => void;
onAddChannel: () => void;
}
function renderChannelFields({
channel,
idx,
lc,
passwordToggleLabels,
onUpdateChannel,
}: {
channel: WizardChannelConfig;
idx: number;
lc: OnboardingChannelLabels;
passwordToggleLabels: { show: string; hide: string };
onUpdateChannel: (index: number, patch: Partial<WizardChannelConfig>) => void;
}) {
if (channel.channel_type === 'telegram') {
return (
<>
<PasswordInput
className="input"
placeholder={lc.telegramToken}
value={channel.app_secret}
onChange={(e) => onUpdateChannel(idx, { app_secret: e.target.value })}
autoComplete="new-password"
toggleLabels={passwordToggleLabels}
/>
<input
className="input"
placeholder={lc.proxy}
value={String((channel.extra_config || {}).proxy || '')}
onChange={(e) => onUpdateChannel(idx, { extra_config: { ...(channel.extra_config || {}), proxy: e.target.value } })}
autoComplete="off"
/>
<label className="field-label">
<input
type="checkbox"
checked={Boolean((channel.extra_config || {}).replyToMessage)}
onChange={(e) => onUpdateChannel(idx, { extra_config: { ...(channel.extra_config || {}), replyToMessage: e.target.checked } })}
style={{ marginRight: 6 }}
/>
{lc.replyToMessage}
</label>
</>
);
}
if (channel.channel_type === 'feishu') {
return (
<>
<input className="input" placeholder={lc.appId} value={channel.external_app_id} onChange={(e) => onUpdateChannel(idx, { external_app_id: e.target.value })} autoComplete="off" />
<PasswordInput className="input" placeholder={lc.appSecret} value={channel.app_secret} onChange={(e) => onUpdateChannel(idx, { app_secret: e.target.value })} autoComplete="new-password" toggleLabels={passwordToggleLabels} />
<input className="input" placeholder={lc.encryptKey} value={String((channel.extra_config || {}).encryptKey || '')} onChange={(e) => onUpdateChannel(idx, { extra_config: { ...(channel.extra_config || {}), encryptKey: e.target.value } })} autoComplete="off" />
<input className="input" placeholder={lc.verificationToken} value={String((channel.extra_config || {}).verificationToken || '')} onChange={(e) => onUpdateChannel(idx, { extra_config: { ...(channel.extra_config || {}), verificationToken: e.target.value } })} autoComplete="off" />
</>
);
}
if (channel.channel_type === 'dingtalk') {
return (
<>
<input className="input" placeholder={lc.clientId} value={channel.external_app_id} onChange={(e) => onUpdateChannel(idx, { external_app_id: e.target.value })} autoComplete="off" />
<PasswordInput className="input" placeholder={lc.clientSecret} value={channel.app_secret} onChange={(e) => onUpdateChannel(idx, { app_secret: e.target.value })} autoComplete="new-password" toggleLabels={passwordToggleLabels} />
</>
);
}
if (channel.channel_type === 'slack') {
return (
<>
<input className="input" placeholder={lc.botToken} value={channel.external_app_id} onChange={(e) => onUpdateChannel(idx, { external_app_id: e.target.value })} autoComplete="off" />
<PasswordInput className="input" placeholder={lc.appToken} value={channel.app_secret} onChange={(e) => onUpdateChannel(idx, { app_secret: e.target.value })} autoComplete="new-password" toggleLabels={passwordToggleLabels} />
</>
);
}
if (channel.channel_type === 'qq') {
return (
<>
<input className="input" placeholder={lc.appId} value={channel.external_app_id} onChange={(e) => onUpdateChannel(idx, { external_app_id: e.target.value })} autoComplete="off" />
<PasswordInput className="input" placeholder={lc.appSecret} value={channel.app_secret} onChange={(e) => onUpdateChannel(idx, { app_secret: e.target.value })} autoComplete="new-password" toggleLabels={passwordToggleLabels} />
</>
);
}
if (channel.channel_type === 'weixin') {
return null;
}
if (channel.channel_type === 'wecom') {
return (
<>
<input
className="input"
placeholder={lc.botId}
value={channel.external_app_id}
onChange={(e) => onUpdateChannel(idx, { external_app_id: e.target.value })}
autoComplete="off"
/>
<PasswordInput
className="input"
placeholder={lc.secret}
value={channel.app_secret}
onChange={(e) => onUpdateChannel(idx, { app_secret: e.target.value })}
autoComplete="new-password"
toggleLabels={passwordToggleLabels}
/>
</>
);
}
return null;
}
export function BotWizardChannelModal({
open,
lc,
passwordToggleLabels,
channels,
sendProgress,
sendToolHints,
addableChannelTypes,
newChannelType,
onClose,
onUpdateGlobalDeliveryFlag,
onUpdateChannel,
onRemoveChannel,
onSetNewChannelType,
onAddChannel,
}: BotWizardChannelModalProps) {
if (!open) return null;
return (
<div className="modal-mask" onClick={onClose}>
<div className="modal-card modal-wide" onClick={(e) => e.stopPropagation()}>
<h3>{lc.wizardSectionTitle}</h3>
<div className="card">
<div className="section-mini-title">{lc.globalDeliveryTitle}</div>
<div className="field-label">{lc.globalDeliveryDesc}</div>
<div className="bot-wizard-switches" style={{ marginTop: 8 }}>
<label className="field-label">
<input type="checkbox" checked={sendProgress} onChange={(e) => onUpdateGlobalDeliveryFlag('sendProgress', e.target.checked)} style={{ marginRight: 6 }} />
{lc.sendProgress}
</label>
<label className="field-label">
<input type="checkbox" checked={sendToolHints} onChange={(e) => onUpdateGlobalDeliveryFlag('sendToolHints', e.target.checked)} style={{ marginRight: 6 }} />
{lc.sendToolHints}
</label>
</div>
</div>
<div className="bot-wizard-channel-list">
{channels.map((channel, idx) => (
<div key={`${channel.channel_type}-${idx}`} className="card bot-wizard-channel-card bot-wizard-channel-compact">
<div className="row-between">
<strong style={{ textTransform: 'uppercase' }}>{channel.channel_type}</strong>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<label className="field-label">
<input type="checkbox" checked={channel.is_active} onChange={(e) => onUpdateChannel(idx, { is_active: e.target.checked })} style={{ marginRight: 6 }} />
{lc.enabled}
</label>
<LucentIconButton className="btn btn-danger btn-sm bot-wizard-icon-btn" onClick={() => onRemoveChannel(idx)} tooltip={lc.remove} aria-label={lc.remove}>
<Trash2 size={14} />
</LucentIconButton>
</div>
</div>
{renderChannelFields({ channel, idx, lc, passwordToggleLabels, onUpdateChannel })}
</div>
))}
</div>
<div className="row-between">
<LucentSelect
value={newChannelType || EMPTY_CHANNEL_PICKER}
onChange={(e) => {
const next = String(e.target.value || '');
onSetNewChannelType(next === EMPTY_CHANNEL_PICKER ? '' : (next as ChannelType));
}}
disabled={addableChannelTypes.length === 0}
>
<option value={EMPTY_CHANNEL_PICKER} disabled>{lc.selectChannelType}</option>
{addableChannelTypes.map((channelType) => (
<option key={channelType} value={channelType}>{channelType}</option>
))}
</LucentSelect>
<LucentIconButton className="btn btn-secondary btn-sm icon-btn" disabled={addableChannelTypes.length === 0 || !newChannelType} onClick={onAddChannel} tooltip={lc.addChannel} aria-label={lc.addChannel}>
<Plus size={14} />
</LucentIconButton>
</div>
<div className="row-between">
<span className="field-label">{lc.wizardSectionDesc}</span>
<button className="btn btn-primary" onClick={onClose}>{lc.close}</button>
</div>
</div>
</div>
);
}