221 lines
9.3 KiB
TypeScript
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>
|
|
);
|
|
}
|