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:
parent
38181ab4db
commit
4cb8b93f93
3 changed files with 7 additions and 164 deletions
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue