refactor(desktop): hide manual Telegram bot creation, use official Gateway bot only

Remove the user-facing ability to create custom Telegram bots via BotFather.
Non-technical users should only need to message @multica_bot on Telegram.
- Disable telegramChannel plugin registration in initChannels()
- Remove ConnectStep from onboarding flow (Privacy → Provider → Start)
- Replace TelegramCard with simple text pointing to @multica_bot

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jiayuan Zhang 2026-02-13 22:20:39 +08:00
parent 38181ab4db
commit 4cb8b93f93
3 changed files with 7 additions and 164 deletions

View file

@ -7,8 +7,6 @@ import {
CardTitle,
} from '@multica/ui/components/ui/card'
import { Button } from '@multica/ui/components/ui/button'
import { Input } from '@multica/ui/components/ui/input'
import { Badge } from '@multica/ui/components/ui/badge'
import {
Tabs,
TabsContent,
@ -16,169 +14,21 @@ import {
TabsTrigger,
} from '@multica/ui/components/ui/tabs'
import { QrCode, Radio, Smartphone, WifiOff, Loader2 } from 'lucide-react'
import { useChannelsStore } from '../stores/channels'
import { useHubStore, selectPrimaryAgent } from '../stores/hub'
import { ConnectionQRCode } from '../components/qr-code'
import { DeviceList } from '../components/device-list'
/** Status badge color mapping */
function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' {
switch (status) {
case 'running': return 'default'
case 'starting': return 'secondary'
case 'error': return 'destructive'
default: return 'outline'
}
}
function TelegramCard() {
const { states, config, saveToken, removeToken, startChannel, stopChannel } = useChannelsStore()
const [token, setToken] = useState('')
const [saving, setSaving] = useState(false)
const [localError, setLocalError] = useState<string | null>(null)
// Current state and config for telegram:default
const state = states.find((s) => s.channelId === 'telegram' && s.accountId === 'default')
const savedConfig = config['telegram']?.['default'] as { botToken?: string } | undefined
const hasToken = Boolean(savedConfig?.botToken)
const isRunning = state?.status === 'running'
const isStarting = state?.status === 'starting'
const handleSave = async () => {
if (!token.trim()) return
setSaving(true)
setLocalError(null)
const result = await saveToken('telegram', 'default', token.trim())
if (!result.ok) {
setLocalError(result.error ?? 'Failed to save')
} else {
setToken('') // Clear input on success
}
setSaving(false)
}
const handleRemove = async () => {
setSaving(true)
setLocalError(null)
const result = await removeToken('telegram', 'default')
if (!result.ok) {
setLocalError(result.error ?? 'Failed to remove')
}
setSaving(false)
}
const handleToggle = async () => {
setSaving(true)
setLocalError(null)
if (isRunning || isStarting) {
await stopChannel('telegram', 'default')
} else {
await startChannel('telegram', 'default')
}
setSaving(false)
}
// Mask the token for display: show first 5 and last 5 chars
const maskedToken = savedConfig?.botToken
? `${savedConfig.botToken.slice(0, 5)}${'*'.repeat(10)}${savedConfig.botToken.slice(-5)}`
: null
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>Telegram</CardTitle>
<CardDescription>
Connect a Telegram bot via Bot API long polling.
</CardDescription>
</div>
{state && (
<Badge variant={statusVariant(state.status)}>
{state.status}
</Badge>
)}
</div>
</CardHeader>
<CardContent className="space-y-4">
{hasToken ? (
// Token is configured — show masked token and actions
<div className="space-y-3">
<div className="flex items-center gap-2">
<code className="text-sm text-muted-foreground bg-muted px-2 py-1 rounded flex-1 truncate">
{maskedToken}
</code>
</div>
{state?.error && (
<p className="text-sm text-destructive">{state.error}</p>
)}
<div className="flex gap-2">
<Button
variant={isRunning ? 'outline' : 'default'}
size="sm"
onClick={handleToggle}
disabled={saving}
>
{isRunning ? 'Stop' : isStarting ? 'Starting...' : 'Start'}
</Button>
<Button
variant="destructive"
size="sm"
onClick={handleRemove}
disabled={saving || isRunning}
title={isRunning ? 'Stop the bot before removing' : undefined}
>
Remove
</Button>
</div>
</div>
) : (
// No token — show input form
<div className="space-y-3">
<Input
type="password"
placeholder="Bot Token (from @BotFather)"
value={token}
onChange={(e) => setToken(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSave()}
/>
<Button
size="sm"
onClick={handleSave}
disabled={saving || !token.trim()}
>
{saving ? 'Saving...' : 'Save & Connect'}
</Button>
</div>
)}
{localError && (
<p className="text-sm text-destructive">{localError}</p>
)}
</CardContent>
</Card>
)
}
function ChannelsTab() {
const { loading, error } = useChannelsStore()
if (loading) {
return <p className="text-sm text-muted-foreground">Loading...</p>
}
if (error) {
return <p className="text-sm text-destructive">{error}</p>
}
return (
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
Connect messaging platforms to chat with your agent.
</p>
<TelegramCard />
<p className="text-sm text-muted-foreground">
Message <span className="font-medium text-foreground">@multica_bot</span> on Telegram to get started.
Discord and Slack coming soon.
</p>
</div>
)
}

View file

@ -5,10 +5,9 @@ import { ModeToggle } from "../../components/mode-toggle";
import WelcomeStep from "./components/welcome-step";
import PermissionsStep from "./components/permissions-step";
import SetupStep from "./components/setup-step";
import ConnectStep from "./components/connect-step";
import TryItStep from "./components/try-it-step";
const steps = ["Privacy", "Provider", "Channels", "Start"];
const steps = ["Privacy", "Provider", "Start"];
export default function OnboardingPage() {
const navigate = useNavigate();
@ -78,9 +77,6 @@ export default function OnboardingPage() {
{currentStep === 1 && <PermissionsStep onNext={nextStep} />}
{currentStep === 2 && <SetupStep onNext={nextStep} onBack={prevStep} />}
{currentStep === 3 && (
<ConnectStep onNext={nextStep} onBack={prevStep} />
)}
{currentStep === 4 && (
<TryItStep onComplete={handleComplete} onBack={prevStep} />
)}
</main>

View file

@ -13,13 +13,10 @@ export type {
ChannelsConfig,
} from "./types.js";
// Built-in channel plugins
import { registerChannel } from "./registry.js";
import { telegramChannel } from "./plugins/telegram.js";
/** Register all built-in channel plugins. Call once at startup. */
export function initChannels(): void {
registerChannel(telegramChannel);
// Telegram: use official bot via Gateway webhook instead of user-created bots.
// The long-polling plugin is kept in plugins/telegram.ts but not registered.
// Future: registerChannel(discordChannel);
// Future: registerChannel(feishuChannel);
}