Redesign Telegram bot settings UI (#5306)
* redesign telegram bot settings ui/refactor ui components * fix positioning of user row * move ConnectedBotCard to subcomponent * fix redirect * remove redundant guard --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
parent
3934b1a585
commit
b9f0d98345
@ -688,7 +688,7 @@ const TRANSLATIONS = {
|
|||||||
step2: {
|
step2: {
|
||||||
title: "Step 2: Connect your bot",
|
title: "Step 2: Connect your bot",
|
||||||
description:
|
description:
|
||||||
"Paste the API token you received from @BotFather and select a default workspace for your bot to chat with.",
|
"Paste the API token you received from @BotFather to connect your bot.",
|
||||||
"bot-token": "Bot Token",
|
"bot-token": "Bot Token",
|
||||||
"default-workspace": "Default Workspace",
|
"default-workspace": "Default Workspace",
|
||||||
"no-workspace": "No available workspaces. A new one will be created.",
|
"no-workspace": "No available workspaces. A new one will be created.",
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { TelegramLogo } from "@phosphor-icons/react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function ConnectedBotCard({ config }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-[18px]">
|
||||||
|
<p className="text-base font-semibold text-white light:text-slate-900">
|
||||||
|
Connected Bot
|
||||||
|
</p>
|
||||||
|
<div className="flex items-start gap-x-1 border border-zinc-700 light:border-slate-200 rounded-xl p-3 w-[700px]">
|
||||||
|
<div className="flex items-center justify-center w-9 h-9 rounded-full bg-[#00ADEC] shrink-0">
|
||||||
|
<TelegramLogo className="h-5 w-5 !text-white" weight="fill" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-y-1 ml-1">
|
||||||
|
<p className="text-sm font-semibold text-white light:text-slate-900">
|
||||||
|
@{config.bot_username}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-zinc-400 light:text-slate-600">
|
||||||
|
{t("telegram.connected.status")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { CircleNotch } from "@phosphor-icons/react";
|
||||||
|
import Telegram from "@/models/telegram";
|
||||||
|
import showToast from "@/utils/toast";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function DetailsSection({ config, onDisconnected }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-[18px]">
|
||||||
|
<p className="text-base font-semibold text-white light:text-slate-900">
|
||||||
|
Details
|
||||||
|
</p>
|
||||||
|
<div className="border border-zinc-700 light:border-slate-200 rounded-xl p-4 w-[700px]">
|
||||||
|
<div className="flex flex-col gap-y-4 text-sm">
|
||||||
|
<DetailRow
|
||||||
|
label={t("telegram.connected.workspace")}
|
||||||
|
value={config.default_workspace}
|
||||||
|
/>
|
||||||
|
<DetailRow label="Thread" value={config.active_thread_name} />
|
||||||
|
<DetailRow label="Model" value={config.chat_model} />
|
||||||
|
<DetailRow
|
||||||
|
label={t("telegram.connected.bot-link")}
|
||||||
|
value={
|
||||||
|
<Link
|
||||||
|
to={`https://t.me/${config.bot_username}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-400 light:text-blue-500 underline"
|
||||||
|
>
|
||||||
|
t.me/{config.bot_username}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DisconnectButton onDisconnected={onDisconnected} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DetailRow({ label, value }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<span className="font-medium text-white light:text-slate-900">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<span className="text-zinc-300 light:text-slate-700">{value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DisconnectButton({ onDisconnected }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [disconnecting, setDisconnecting] = useState(false);
|
||||||
|
|
||||||
|
async function handleDisconnect() {
|
||||||
|
setDisconnecting(true);
|
||||||
|
const res = await Telegram.disconnect();
|
||||||
|
setDisconnecting(false);
|
||||||
|
|
||||||
|
if (!res.success) {
|
||||||
|
showToast(
|
||||||
|
res.error || t("telegram.connected.toast-disconnect-failed"),
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleDisconnect}
|
||||||
|
disabled={disconnecting}
|
||||||
|
className="flex items-center justify-center gap-x-2 text-sm font-medium bg-zinc-50 light:bg-slate-900 text-zinc-950 light:text-white rounded-lg h-9 px-5 w-fit hover:opacity-90 transition-opacity duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{disconnecting ? (
|
||||||
|
<>
|
||||||
|
<CircleNotch className="h-4 w-4 animate-spin" />
|
||||||
|
{t("telegram.connected.disconnecting")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
t("telegram.connected.disconnect")
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,157 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
CircleNotch,
|
||||||
|
Eye,
|
||||||
|
EyeSlash,
|
||||||
|
TelegramLogo,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
import Telegram from "@/models/telegram";
|
||||||
|
import showToast from "@/utils/toast";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function DisconnectedView({
|
||||||
|
config,
|
||||||
|
onReconnected,
|
||||||
|
newToken,
|
||||||
|
setNewToken,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [reconnecting, setReconnecting] = useState(false);
|
||||||
|
const [showToken, setShowToken] = useState(false);
|
||||||
|
const Icon = showToken ? Eye : EyeSlash;
|
||||||
|
|
||||||
|
async function handleReconnect(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!newToken.trim()) return;
|
||||||
|
setReconnecting(true);
|
||||||
|
const res = await Telegram.connect(
|
||||||
|
newToken.trim(),
|
||||||
|
config.default_workspace
|
||||||
|
);
|
||||||
|
setReconnecting(false);
|
||||||
|
if (!res.success)
|
||||||
|
return showToast(
|
||||||
|
res.error || t("telegram.connected.toast-reconnect-failed"),
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
|
||||||
|
setNewToken("");
|
||||||
|
const configRes = await Telegram.getConfig();
|
||||||
|
onReconnected(configRes?.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-8 mt-8">
|
||||||
|
<div className="flex flex-col gap-y-[18px]">
|
||||||
|
<p className="text-base font-semibold text-white light:text-slate-900">
|
||||||
|
Connected Bot
|
||||||
|
</p>
|
||||||
|
<div className="flex items-start gap-x-1 border border-red-500/30 light:border-red-300 rounded-xl p-3 w-[700px]">
|
||||||
|
<div className="flex items-center justify-center w-9 h-9 rounded-full bg-red-500/20 shrink-0">
|
||||||
|
<TelegramLogo
|
||||||
|
className="h-5 w-5 text-red-400 light:text-red-500"
|
||||||
|
weight="fill"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-y-1 ml-1">
|
||||||
|
<p className="text-sm font-semibold text-white light:text-slate-900">
|
||||||
|
@{config.bot_username}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-red-400 light:text-red-500">
|
||||||
|
{t("telegram.connected.status-disconnected")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
onSubmit={handleReconnect}
|
||||||
|
className="flex items-end gap-x-2 w-[700px]"
|
||||||
|
>
|
||||||
|
<div className="bg-zinc-800 light:bg-white light:border light:border-slate-300 h-8 rounded-lg px-3.5 flex items-center gap-x-2 flex-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowToken(!showToken)}
|
||||||
|
className="text-zinc-400 light:text-slate-500 hover:text-zinc-300 light:hover:text-slate-700 transition-colors shrink-0"
|
||||||
|
>
|
||||||
|
<Icon className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type={showToken ? "text" : "password"}
|
||||||
|
value={newToken}
|
||||||
|
onChange={(e) => setNewToken(e.target.value)}
|
||||||
|
placeholder={t("telegram.connected.placeholder-token")}
|
||||||
|
className="bg-transparent flex-1 text-sm text-white light:text-slate-900 placeholder:text-zinc-400 light:placeholder:text-slate-500 outline-none min-w-0"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={reconnecting}
|
||||||
|
className="flex items-center justify-center gap-x-1.5 text-sm font-medium bg-zinc-50 light:bg-slate-900 text-zinc-900 light:text-white rounded-lg h-8 px-5 hover:opacity-90 transition-opacity duration-200 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{reconnecting ? (
|
||||||
|
<CircleNotch className="h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
t("telegram.connected.reconnect")
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This code is disabled for now - works fine, but I am not sure we want to enabled this feature.
|
||||||
|
How many people really need a REPLY with voice mode? Even then, we should support on device TTS
|
||||||
|
and more it out the frontend so people can do voice gen without having to pay for it.
|
||||||
|
|
||||||
|
When we do enabled this, we should uncomment this code and remove the disabled comment.
|
||||||
|
|
||||||
|
const getVoiceModeOptions = (t) => {
|
||||||
|
return [
|
||||||
|
{ value: "text_only", label: t("telegram.connected.voice-text-only") },
|
||||||
|
{ value: "mirror", label: t("telegram.connected.voice-mirror") },
|
||||||
|
{ value: "always_voice", label: t("telegram.connected.voice-always") },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
function VoiceModeSelector({ config }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [voiceMode, setVoiceMode] = useState(
|
||||||
|
config.voice_response_mode || "text_only"
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleVoiceModeChange(e) {
|
||||||
|
const mode = e.target.value;
|
||||||
|
setVoiceMode(mode);
|
||||||
|
const res = await Telegram.updateConfig({ voice_response_mode: mode });
|
||||||
|
if (!res.success) {
|
||||||
|
showToast(
|
||||||
|
res.error || t("telegram.connected.toast-voice-failed"),
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
setVoiceMode(config.voice_response_mode || "text_only");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-xs text-white">
|
||||||
|
{t("telegram.connected.voice-response")}
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
value={voiceMode}
|
||||||
|
onChange={handleVoiceModeChange}
|
||||||
|
className="text-xs text-right bg-transparent text-white rounded-md px-2 py-1 outline-none max-w-[260px]"
|
||||||
|
>
|
||||||
|
{getVoiceModeOptions(t).map((opt) => (
|
||||||
|
<option key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
@ -0,0 +1,148 @@
|
|||||||
|
import { X, Check } from "@phosphor-icons/react";
|
||||||
|
import Telegram from "@/models/telegram";
|
||||||
|
import showToast from "@/utils/toast";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function UsersSection({
|
||||||
|
pendingUsers,
|
||||||
|
approvedUsers,
|
||||||
|
fetchUsers,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
async function handleApprove(chatId) {
|
||||||
|
const res = await Telegram.approveUser(chatId);
|
||||||
|
if (!res.success) {
|
||||||
|
showToast(
|
||||||
|
res.error || t("telegram.connected.toast-approve-failed"),
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeny(chatId) {
|
||||||
|
const res = await Telegram.denyUser(chatId);
|
||||||
|
if (!res.success) {
|
||||||
|
showToast(
|
||||||
|
res.error || t("telegram.connected.toast-deny-failed"),
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRevoke(chatId) {
|
||||||
|
const res = await Telegram.revokeUser(chatId);
|
||||||
|
if (!res.success) {
|
||||||
|
showToast(
|
||||||
|
res.error || t("telegram.connected.toast-revoke-failed"),
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPending = pendingUsers.length > 0;
|
||||||
|
const hasApproved = approvedUsers.length > 0;
|
||||||
|
if (!hasPending && !hasApproved) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-[18px] w-[700px]">
|
||||||
|
<div className="flex flex-col gap-y-2">
|
||||||
|
<p className="text-base font-semibold text-white light:text-slate-900">
|
||||||
|
Users
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-zinc-400 light:text-slate-600">
|
||||||
|
{t("telegram.users.pending-description")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-zinc-700 light:border-slate-200" />
|
||||||
|
<div className="flex flex-col gap-y-2">
|
||||||
|
{pendingUsers.map((user) => (
|
||||||
|
<UserRow
|
||||||
|
key={user.chatId || user}
|
||||||
|
user={user}
|
||||||
|
isPending
|
||||||
|
onApprove={handleApprove}
|
||||||
|
onDeny={handleDeny}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{approvedUsers.map((user) => (
|
||||||
|
<UserRow
|
||||||
|
key={user.chatId || user}
|
||||||
|
user={user}
|
||||||
|
onRevoke={handleRevoke}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserRow({ user, isPending = false, onApprove, onDeny, onRevoke }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const chatId = typeof user === "string" ? user : user.chatId;
|
||||||
|
const username = user.telegramUsername || user.username || null;
|
||||||
|
const firstName = user.firstName || null;
|
||||||
|
const displayName = username
|
||||||
|
? `@${username}`
|
||||||
|
: firstName || t("telegram.users.unknown");
|
||||||
|
const initial = (username || firstName || "?")[0].toUpperCase();
|
||||||
|
const code = user.code;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="flex items-center gap-x-3 flex-1 min-w-0">
|
||||||
|
<div className="bg-zinc-800 light:bg-slate-300 size-8 rounded-full flex items-center justify-center shrink-0">
|
||||||
|
<span className="text-sm font-semibold text-white light:text-slate-900">
|
||||||
|
{initial}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-white light:text-slate-900 truncate">
|
||||||
|
{displayName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-[60px] flex items-center justify-center shrink-0 mr-36">
|
||||||
|
{isPending && code && (
|
||||||
|
<div className="bg-zinc-950 light:bg-slate-200 h-[26px] w-[60px] flex items-center justify-center rounded">
|
||||||
|
<span className="text-sm text-white/80 light:text-slate-900 text-center">
|
||||||
|
{code}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-end gap-x-3 w-[80px] shrink-0">
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => onDeny(chatId)}
|
||||||
|
className="text-zinc-400 light:text-slate-400 hover:text-red-400 light:hover:text-red-500 transition-colors"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" weight="bold" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => onApprove(chatId)}
|
||||||
|
className="text-zinc-400 light:text-slate-400 hover:text-green-400 light:hover:text-green-500 transition-colors"
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4" weight="bold" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => onRevoke(chatId)}
|
||||||
|
className="text-sm text-white/80 light:text-slate-500 hover:text-white light:hover:text-slate-700 transition-colors"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-zinc-800 light:border-slate-200" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,153 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import Telegram from "@/models/telegram";
|
|
||||||
import showToast from "@/utils/toast";
|
|
||||||
|
|
||||||
export default function UsersTable({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
users = [],
|
|
||||||
isPending = false,
|
|
||||||
fetchUsers = () => {},
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
if (users.length === 0) return null;
|
|
||||||
const colCount = isPending ? 4 : 3;
|
|
||||||
|
|
||||||
async function handleApprove(chatId) {
|
|
||||||
const res = await Telegram.approveUser(chatId);
|
|
||||||
if (!res.success) {
|
|
||||||
showToast(
|
|
||||||
res.error || t("telegram.connected.toast-approve-failed"),
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDeny(chatId) {
|
|
||||||
const res = await Telegram.denyUser(chatId);
|
|
||||||
if (!res.success) {
|
|
||||||
showToast(
|
|
||||||
res.error || t("telegram.connected.toast-deny-failed"),
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleRevoke(chatId) {
|
|
||||||
const res = await Telegram.revokeUser(chatId);
|
|
||||||
if (!res.success) {
|
|
||||||
showToast(
|
|
||||||
res.error || t("telegram.connected.toast-revoke-failed"),
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-y-2">
|
|
||||||
<p className="text-sm font-semibold text-theme-text-primary">{title}</p>
|
|
||||||
<p className="text-xs text-theme-text-secondary">{description}</p>
|
|
||||||
<div className="overflow-x-auto mt-2">
|
|
||||||
<table className="w-1/2 text-xs text-left rounded-lg min-w-[480px] border-spacing-0">
|
|
||||||
<thead className="text-theme-text-secondary text-xs leading-[18px] font-bold uppercase border-white/10 border-b">
|
|
||||||
<tr>
|
|
||||||
<th scope="col" className="px-6 py-3">
|
|
||||||
{t("telegram.users.user")}
|
|
||||||
</th>
|
|
||||||
{isPending && (
|
|
||||||
<th scope="col" className="px-6 py-3">
|
|
||||||
{t("telegram.users.pairing-code")}
|
|
||||||
</th>
|
|
||||||
)}
|
|
||||||
<th scope="col" className="px-6 py-3">
|
|
||||||
{" "}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{users.length === 0 ? (
|
|
||||||
<tr className="bg-transparent text-theme-text-secondary text-sm font-medium">
|
|
||||||
<td colSpan={colCount} className="px-6 py-4 text-center">
|
|
||||||
{isPending
|
|
||||||
? t("telegram.users.no-pending")
|
|
||||||
: t("telegram.users.no-approved")}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
users.map((user) => {
|
|
||||||
const chatId = typeof user === "string" ? user : user.chatId;
|
|
||||||
const username = user.telegramUsername || user.username || null;
|
|
||||||
const firstName = user.firstName || null;
|
|
||||||
const displayName = username
|
|
||||||
? `@${username}`
|
|
||||||
: firstName || t("telegram.users.unknown");
|
|
||||||
const code = user.code;
|
|
||||||
return (
|
|
||||||
<tr
|
|
||||||
key={chatId}
|
|
||||||
className="bg-transparent text-white text-opacity-80 text-xs font-medium border-b border-white/10 h-10"
|
|
||||||
>
|
|
||||||
<td className="px-6 whitespace-nowrap">
|
|
||||||
<span className="text-sm text-theme-text-primary">
|
|
||||||
{displayName}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
{isPending && (
|
|
||||||
<td className="px-6 whitespace-nowrap">
|
|
||||||
<code className="bg-theme-bg-primary px-2 py-1 rounded text-theme-text-primary font-mono text-sm">
|
|
||||||
{code}
|
|
||||||
</code>
|
|
||||||
</td>
|
|
||||||
)}
|
|
||||||
<td className="px-6 flex items-center gap-x-6 h-full mt-1">
|
|
||||||
{isPending ? (
|
|
||||||
<>
|
|
||||||
<ActionButton
|
|
||||||
onClick={() => handleApprove(chatId)}
|
|
||||||
className="hover:light:bg-green-50 hover:light:text-green-500 hover:text-green-300"
|
|
||||||
>
|
|
||||||
{t("telegram.users.approve")}
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
onClick={() => handleDeny(chatId)}
|
|
||||||
className="hover:light:bg-red-50 hover:light:text-red-500 hover:text-red-300"
|
|
||||||
>
|
|
||||||
{t("telegram.users.deny")}
|
|
||||||
</ActionButton>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<ActionButton
|
|
||||||
onClick={() => handleRevoke(chatId)}
|
|
||||||
className="hover:light:bg-red-50"
|
|
||||||
>
|
|
||||||
{t("telegram.users.revoke")}
|
|
||||||
</ActionButton>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ActionButton({ onClick, className = "", children }) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={onClick}
|
|
||||||
className={`border-none flex items-center justify-center text-xs font-medium text-white/80 light:text-black/80 rounded-lg p-1 hover:bg-white hover:bg-opacity-10 ${className}`}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,31 +1,19 @@
|
|||||||
import { useEffect, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import {
|
|
||||||
ArrowSquareOut,
|
|
||||||
CircleNotch,
|
|
||||||
Eye,
|
|
||||||
EyeSlash,
|
|
||||||
TelegramLogo,
|
|
||||||
} from "@phosphor-icons/react";
|
|
||||||
import Telegram from "@/models/telegram";
|
import Telegram from "@/models/telegram";
|
||||||
import showToast from "@/utils/toast";
|
import ConnectedBotCard from "./ConnectedBotCard";
|
||||||
import UsersTable from "./UsersTable";
|
import DetailsSection from "./DetailsSection";
|
||||||
import { useTranslation } from "react-i18next";
|
import UsersSection from "./UsersSection";
|
||||||
import { Link } from "react-router-dom";
|
import DisconnectedView from "./DisconnectedView";
|
||||||
|
|
||||||
export default function ConnectedView({
|
export default function ConnectedView({
|
||||||
config,
|
config,
|
||||||
workspaces,
|
|
||||||
onDisconnected,
|
onDisconnected,
|
||||||
onReconnected,
|
onReconnected,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const connected = config.connected;
|
const connected = config.connected;
|
||||||
const [newToken, setNewToken] = useState("");
|
const [newToken, setNewToken] = useState("");
|
||||||
const [pendingUsers, setPendingUsers] = useState([]);
|
const [pendingUsers, setPendingUsers] = useState([]);
|
||||||
const [approvedUsers, setApprovedUsers] = useState([]);
|
const [approvedUsers, setApprovedUsers] = useState([]);
|
||||||
const workspaceName =
|
|
||||||
workspaces.find((ws) => ws.slug === config.default_workspace)?.name ||
|
|
||||||
config.default_workspace;
|
|
||||||
|
|
||||||
const fetchUsers = useCallback(async () => {
|
const fetchUsers = useCallback(async () => {
|
||||||
const [pending, approved] = await Promise.all([
|
const [pending, approved] = await Promise.all([
|
||||||
@ -54,260 +42,14 @@ export default function ConnectedView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-6 flex flex-col gap-y-6">
|
<div className="flex flex-col gap-y-8 mt-8">
|
||||||
<div className="flex flex-col gap-y-4 max-w-[480px]">
|
<ConnectedBotCard config={config} />
|
||||||
<div className="flex items-center gap-x-3 rounded-lg border border-green-500/20 bg-green-500/5 light:border-green-700/20 light:bg-green-500/10 p-4">
|
<DetailsSection config={config} onDisconnected={onDisconnected} />
|
||||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-green-500/10">
|
<UsersSection
|
||||||
<TelegramLogo
|
pendingUsers={pendingUsers}
|
||||||
className="h-5 w-5 text-green-400 light:text-green-700"
|
approvedUsers={approvedUsers}
|
||||||
weight="fill"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<p className="text-sm font-semibold text-theme-text-primary">
|
|
||||||
@{config.bot_username}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-green-400 light:text-green-700">
|
|
||||||
{t("telegram.connected.status")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-y-2 rounded-lg bg-black light:bg-black/5 light:border light:border-black/10 p-4">
|
|
||||||
<WorkspaceName name={workspaceName} />
|
|
||||||
<BotLink username={config.bot_username} />
|
|
||||||
{/*
|
|
||||||
Disabled for now - works fine, but I am not sure we want to enabled this feature.
|
|
||||||
How many people really need a REPLY with voice mode? Even then, we should support on device TTS
|
|
||||||
and more it out the frontend so people can do voice gen without having to pay for it.
|
|
||||||
*/}
|
|
||||||
{/* <VoiceModeSelector config={config} /> */}
|
|
||||||
<DisconnectButton onDisconnected={onDisconnected} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<UsersTable
|
|
||||||
title={t("telegram.users.pending-title")}
|
|
||||||
description={t("telegram.users.pending-description")}
|
|
||||||
users={pendingUsers}
|
|
||||||
isPending
|
|
||||||
fetchUsers={fetchUsers}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UsersTable
|
|
||||||
title={t("telegram.users.approved-title")}
|
|
||||||
description={t("telegram.users.approved-description")}
|
|
||||||
users={approvedUsers}
|
|
||||||
fetchUsers={fetchUsers}
|
fetchUsers={fetchUsers}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BotLink({ username }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-xs text-white">
|
|
||||||
{t("telegram.connected.bot-link")}
|
|
||||||
</span>
|
|
||||||
<Link
|
|
||||||
to={`https://t.me/${username}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-xs text-sky-500 light:text-sky-600 hover:underline flex items-center gap-x-1"
|
|
||||||
>
|
|
||||||
t.me/{username}
|
|
||||||
<ArrowSquareOut className="h-3 w-3" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DisconnectButton({ onDisconnected }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [disconnecting, setDisconnecting] = useState(false);
|
|
||||||
|
|
||||||
async function handleDisconnect() {
|
|
||||||
setDisconnecting(true);
|
|
||||||
const res = await Telegram.disconnect();
|
|
||||||
setDisconnecting(false);
|
|
||||||
|
|
||||||
if (!res.success) {
|
|
||||||
showToast(
|
|
||||||
res.error || t("telegram.connected.toast-disconnect-failed"),
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onDisconnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={handleDisconnect}
|
|
||||||
disabled={disconnecting}
|
|
||||||
className="flex items-center justify-center gap-x-2 text-sm font-medium text-white bg-red-800 hover:bg-red-700 light:bg-red-600 light:hover:bg-red-500 light:text-white rounded-lg px-4 py-0.5 w-fit transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
{disconnecting ? (
|
|
||||||
<>
|
|
||||||
<CircleNotch className="h-4 w-4 animate-spin" />
|
|
||||||
{t("telegram.connected.disconnecting")}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
t("telegram.connected.disconnect")
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function WorkspaceName({ name }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-xs text-white">
|
|
||||||
{t("telegram.connected.workspace")}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-white font-medium">{name}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DisconnectedView({ config, onReconnected, newToken, setNewToken }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [reconnecting, setReconnecting] = useState(false);
|
|
||||||
const [showToken, setShowToken] = useState(false);
|
|
||||||
const Icon = showToken ? Eye : EyeSlash;
|
|
||||||
|
|
||||||
async function handleReconnect(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!newToken.trim()) return;
|
|
||||||
setReconnecting(true);
|
|
||||||
const res = await Telegram.connect(
|
|
||||||
newToken.trim(),
|
|
||||||
config.default_workspace
|
|
||||||
);
|
|
||||||
setReconnecting(false);
|
|
||||||
if (!res.success)
|
|
||||||
return showToast(
|
|
||||||
res.error || t("telegram.connected.toast-reconnect-failed"),
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
|
|
||||||
setNewToken("");
|
|
||||||
onReconnected({
|
|
||||||
active: true,
|
|
||||||
connected: true,
|
|
||||||
bot_username: res.bot_username,
|
|
||||||
default_workspace: config.default_workspace,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mt-6 flex flex-col gap-y-6">
|
|
||||||
<div className="flex flex-col gap-y-4 max-w-[480px]">
|
|
||||||
<div className="flex flex-col gap-y-3">
|
|
||||||
<div className="flex items-center gap-x-3 rounded-lg border border-red-500/20 bg-red-500/5 p-4">
|
|
||||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-red-500/10">
|
|
||||||
<TelegramLogo className="h-5 w-5 text-red-400" weight="fill" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<p className="text-sm font-semibold text-white">
|
|
||||||
@{config.bot_username}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-red-400">
|
|
||||||
{t("telegram.connected.status-disconnected")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form onSubmit={handleReconnect} className="flex items-end gap-x-2">
|
|
||||||
<div className="relative w-full">
|
|
||||||
<input
|
|
||||||
type={showToken ? "text" : "password"}
|
|
||||||
value={newToken}
|
|
||||||
onChange={(e) => setNewToken(e.target.value)}
|
|
||||||
placeholder={t("telegram.connected.placeholder-token")}
|
|
||||||
className="w-[99%] bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5 pr-10"
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
{newToken.length > 0 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowToken(!showToken)}
|
|
||||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
||||||
>
|
|
||||||
<Icon className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={reconnecting}
|
|
||||||
className="flex items-center gap-x-2 text-sm font-medium !text-white bg-sky-500 hover:bg-sky-600 rounded-lg px-4 py-2.5 whitespace-nowrap transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
{reconnecting ? (
|
|
||||||
<CircleNotch className="h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
t("telegram.connected.reconnect")
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
This code is disabled for now - works fine, but I am not sure we want to enabled this feature.
|
|
||||||
How many people really need a REPLY with voice mode? Even then, we should support on device TTS
|
|
||||||
and more it out the frontend so people can do voice gen without having to pay for it.
|
|
||||||
|
|
||||||
When we do enabled this, we should uncomment this code and remove the disabled comment.
|
|
||||||
|
|
||||||
const getVoiceModeOptions = (t) => {
|
|
||||||
return [
|
|
||||||
{ value: "text_only", label: t("telegram.connected.voice-text-only") },
|
|
||||||
{ value: "mirror", label: t("telegram.connected.voice-mirror") },
|
|
||||||
{ value: "always_voice", label: t("telegram.connected.voice-always") },
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
function VoiceModeSelector({ config }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [voiceMode, setVoiceMode] = useState(
|
|
||||||
config.voice_response_mode || "text_only"
|
|
||||||
);
|
|
||||||
|
|
||||||
async function handleVoiceModeChange(e) {
|
|
||||||
const mode = e.target.value;
|
|
||||||
setVoiceMode(mode);
|
|
||||||
const res = await Telegram.updateConfig({ voice_response_mode: mode });
|
|
||||||
if (!res.success) {
|
|
||||||
showToast(
|
|
||||||
res.error || t("telegram.connected.toast-voice-failed"),
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
setVoiceMode(config.voice_response_mode || "text_only");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-xs text-white">
|
|
||||||
{t("telegram.connected.voice-response")}
|
|
||||||
</span>
|
|
||||||
<select
|
|
||||||
value={voiceMode}
|
|
||||||
onChange={handleVoiceModeChange}
|
|
||||||
className="text-xs text-right bg-transparent text-white rounded-md px-2 py-1 outline-none max-w-[260px]"
|
|
||||||
>
|
|
||||||
{getVoiceModeOptions(t).map((opt) => (
|
|
||||||
<option key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { QRCodeSVG } from "qrcode.react";
|
import { QRCodeSVG } from "qrcode.react";
|
||||||
import { ShieldCheck, TelegramLogo } from "@phosphor-icons/react";
|
import { TelegramLogo } from "@phosphor-icons/react";
|
||||||
import Logo from "@/media/logo/anything-llm-infinity.png";
|
import Logo from "@/media/logo/anything-llm-infinity.png";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@ -8,72 +8,70 @@ const BOTFATHER_URL = "https://t.me/BotFather";
|
|||||||
|
|
||||||
export default function CreateBotSection() {
|
export default function CreateBotSection() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const qrSize = 180;
|
const qrSize = 137;
|
||||||
const logoSize = { width: 35 * 1.2, height: 22 * 1.2 };
|
const logoSize = { width: 35, height: 22 };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-4">
|
<div className="flex flex-col gap-y-8">
|
||||||
<div className="flex flex-col gap-y-1">
|
<div className="flex flex-col gap-y-1">
|
||||||
<p className="text-sm font-semibold text-theme-text-primary">
|
<p className="text-sm light:text-base font-semibold text-white light:text-slate-900">
|
||||||
{t("telegram.setup.step1.title")}
|
{t("telegram.setup.step1.title")}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-theme-text-secondary mb-3">
|
<p className="text-xs text-zinc-400 light:text-slate-600 max-w-[600px]">
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="telegram.setup.step1.description"
|
i18nKey="telegram.setup.step1.description"
|
||||||
components={{
|
components={{
|
||||||
code: (
|
code: (
|
||||||
<code className="bg-theme-bg-primary px-1 py-0.5 rounded text-theme-text-primary" />
|
<code className="bg-zinc-800 light:bg-slate-200 px-1 py-0.5 rounded text-white light:text-slate-900" />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-start gap-x-6 flex-wrap gap-y-4">
|
</div>
|
||||||
<div className="flex flex-col items-center gap-y-2">
|
<div className="flex items-center gap-x-8">
|
||||||
<div className="bg-white/10 light:bg-black/5 rounded-lg p-4 flex items-center justify-center">
|
<div className="flex flex-col items-start gap-y-2">
|
||||||
<QRCodeSVG
|
<div className="bg-zinc-700 light:bg-slate-200 rounded-2xl p-[18px]">
|
||||||
value={BOTFATHER_URL}
|
<QRCodeSVG
|
||||||
size={qrSize}
|
value={BOTFATHER_URL}
|
||||||
bgColor="transparent"
|
size={qrSize}
|
||||||
fgColor="currentColor"
|
bgColor="transparent"
|
||||||
className="text-white light:text-black light:[&_image]:invert"
|
fgColor="currentColor"
|
||||||
level="L"
|
className="text-white light:text-slate-900 light:[&_image]:invert"
|
||||||
imageSettings={{
|
level="L"
|
||||||
src: Logo,
|
imageSettings={{
|
||||||
x: qrSize / 2 - logoSize.width / 2,
|
src: Logo,
|
||||||
y: qrSize / 2 - logoSize.height / 2,
|
x: qrSize / 2 - logoSize.width / 2,
|
||||||
height: logoSize.height,
|
y: qrSize / 2 - logoSize.height / 2,
|
||||||
width: logoSize.width,
|
height: logoSize.height,
|
||||||
excavate: true,
|
width: logoSize.width,
|
||||||
}}
|
excavate: true,
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-y-3 pt-2">
|
|
||||||
<Link
|
|
||||||
to={BOTFATHER_URL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="flex items-center gap-x-2 text-sm font-medium text-white bg-sky-500 hover:bg-sky-600 rounded-lg px-4 py-2.5 w-fit transition-colors duration-200"
|
|
||||||
>
|
|
||||||
<TelegramLogo className="h-4 w-4" weight="fill" />
|
|
||||||
{t("telegram.setup.step1.open-botfather")}
|
|
||||||
</Link>
|
|
||||||
<div className="flex flex-col gap-y-1.5 text-xs text-theme-text-secondary">
|
|
||||||
<p>{t("telegram.setup.step1.instruction-1")}</p>
|
|
||||||
<p>
|
|
||||||
<Trans
|
|
||||||
i18nKey="telegram.setup.step1.instruction-2"
|
|
||||||
components={{
|
|
||||||
code: (
|
|
||||||
<code className="bg-theme-bg-primary px-1 py-0.5 rounded text-theme-text-primary" />
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<p>{t("telegram.setup.step1.instruction-3")}</p>
|
|
||||||
<p>{t("telegram.setup.step1.instruction-4")}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Link
|
||||||
|
to={BOTFATHER_URL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="flex items-center justify-center gap-x-1.5 text-sm font-medium bg-zinc-50 light:bg-slate-900 text-zinc-900 light:text-white rounded-lg h-9 w-[172px] hover:opacity-90 transition-opacity duration-200"
|
||||||
|
>
|
||||||
|
<TelegramLogo className="h-5 w-5" weight="fill" />
|
||||||
|
{t("telegram.setup.step1.open-botfather")}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col text-sm text-white light:text-slate-900">
|
||||||
|
<p className="leading-5">{t("telegram.setup.step1.instruction-1")}</p>
|
||||||
|
<p className="leading-5">
|
||||||
|
<Trans
|
||||||
|
i18nKey="telegram.setup.step1.instruction-2"
|
||||||
|
components={{
|
||||||
|
code: (
|
||||||
|
<code className="bg-zinc-800 light:bg-slate-200 px-1 py-0.5 rounded" />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className="leading-5">{t("telegram.setup.step1.instruction-3")}</p>
|
||||||
|
<p className="leading-5">{t("telegram.setup.step1.instruction-4")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SecurityTips />
|
<SecurityTips />
|
||||||
@ -85,32 +83,16 @@ function SecurityTips() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-2 p-3 rounded-lg bg-theme-bg-primary border border-theme-sidebar-border">
|
<div className="border border-zinc-600 light:border-slate-400 rounded-xl p-[18px] max-w-[640px]">
|
||||||
<div className="flex items-center gap-x-2">
|
<p className="text-sm font-medium text-white light:text-slate-900 mb-1">
|
||||||
<ShieldCheck
|
{t("telegram.setup.security.title")}
|
||||||
className="h-4 w-4 text-theme-text-secondary"
|
</p>
|
||||||
weight="bold"
|
<p className="text-sm text-zinc-400 light:text-slate-600 mb-2">
|
||||||
/>
|
|
||||||
<p className="text-xs font-semibold text-theme-text-primary">
|
|
||||||
{t("telegram.setup.security.title")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-theme-text-secondary">
|
|
||||||
{t("telegram.setup.security.description")}
|
{t("telegram.setup.security.description")}
|
||||||
</p>
|
</p>
|
||||||
<ul className="text-xs text-theme-text-secondary list-disc list-inside space-y-1 ml-1">
|
<ul className="text-sm text-zinc-400 light:text-slate-600 list-disc list-inside space-y-0.5">
|
||||||
<li>
|
<li>Disable Groups {t("telegram.setup.security.disable-groups")}</li>
|
||||||
<code className="bg-theme-bg-secondary px-1 py-0.5 rounded text-theme-text-primary">
|
<li>Disable Inline {t("telegram.setup.security.disable-inline")}</li>
|
||||||
Disable Groups
|
|
||||||
</code>{" "}
|
|
||||||
{t("telegram.setup.security.disable-groups")}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<code className="bg-theme-bg-secondary px-1 py-0.5 rounded text-theme-text-primary">
|
|
||||||
Disable Inline
|
|
||||||
</code>{" "}
|
|
||||||
{t("telegram.setup.security.disable-inline")}
|
|
||||||
</li>
|
|
||||||
<li>{t("telegram.setup.security.obscure-username")}</li>
|
<li>{t("telegram.setup.security.obscure-username")}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,12 +10,9 @@ import showToast from "@/utils/toast";
|
|||||||
import CreateBotSection from "./CreateBotSection";
|
import CreateBotSection from "./CreateBotSection";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function SetupView({ workspaces, onConnected }) {
|
export default function SetupView({ onConnected }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [botToken, setBotToken] = useState("");
|
const [botToken, setBotToken] = useState("");
|
||||||
const [selectedWorkspace, setSelectedWorkspace] = useState(
|
|
||||||
workspaces[0]?.slug || ""
|
|
||||||
);
|
|
||||||
const [connecting, setConnecting] = useState(false);
|
const [connecting, setConnecting] = useState(false);
|
||||||
|
|
||||||
async function handleConnect(e) {
|
async function handleConnect(e) {
|
||||||
@ -24,58 +21,48 @@ export default function SetupView({ workspaces, onConnected }) {
|
|||||||
return showToast(t("telegram.setup.toast-enter-token"), "error");
|
return showToast(t("telegram.setup.toast-enter-token"), "error");
|
||||||
|
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
const res = await Telegram.connect(botToken.trim(), selectedWorkspace);
|
const res = await Telegram.connect(botToken.trim());
|
||||||
setConnecting(false);
|
setConnecting(false);
|
||||||
|
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
showToast(res.error || t("telegram.setup.toast-connect-failed"), "error");
|
showToast(res.error || t("telegram.setup.toast-connect-failed"), "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onConnected({
|
|
||||||
active: true,
|
const configRes = await Telegram.getConfig();
|
||||||
connected: true,
|
onConnected(configRes?.config);
|
||||||
bot_username: res.bot_username,
|
|
||||||
default_workspace: selectedWorkspace || null,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-6 mt-6">
|
<div className="flex flex-col gap-y-8 mt-8">
|
||||||
<CreateBotSection />
|
<CreateBotSection />
|
||||||
<form onSubmit={handleConnect} className="flex flex-col gap-y-4">
|
<form onSubmit={handleConnect} className="flex flex-col gap-y-[18px]">
|
||||||
<div className="flex flex-col gap-y-1">
|
<div className="flex flex-col gap-y-2">
|
||||||
<p className="text-sm font-semibold text-theme-text-primary">
|
<p className="text-sm light:text-base font-semibold text-white light:text-slate-900">
|
||||||
{t("telegram.setup.step2.title")}
|
{t("telegram.setup.step2.title")}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-theme-text-secondary">
|
<p className="text-xs text-zinc-400 light:text-slate-600 max-w-[700px]">
|
||||||
{t("telegram.setup.step2.description")}
|
{t("telegram.setup.step2.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-y-4 max-w-[480px]">
|
<BotTokenInput botToken={botToken} setBotToken={setBotToken} />
|
||||||
<BotTokenInput botToken={botToken} setBotToken={setBotToken} />
|
<button
|
||||||
<WorkspaceSelect
|
type="submit"
|
||||||
workspaces={workspaces}
|
disabled={connecting}
|
||||||
selectedWorkspace={selectedWorkspace}
|
className="flex items-center justify-center gap-x-1.5 text-sm font-medium bg-zinc-50 light:bg-slate-900 text-zinc-900 light:text-white rounded-lg h-9 px-5 w-fit hover:opacity-90 transition-opacity duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
setSelectedWorkspace={setSelectedWorkspace}
|
>
|
||||||
/>
|
{connecting ? (
|
||||||
<button
|
<>
|
||||||
type="submit"
|
<CircleNotch className="h-4 w-4 animate-spin" />
|
||||||
disabled={connecting}
|
{t("telegram.setup.step2.connecting")}
|
||||||
className="flex items-center justify-center gap-x-2 text-sm font-medium text-white bg-sky-500 hover:bg-sky-600 rounded-lg px-4 py-2.5 w-fit transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
</>
|
||||||
>
|
) : (
|
||||||
{connecting ? (
|
<>
|
||||||
<>
|
<TelegramLogo className="h-5 w-5" weight="fill" />
|
||||||
<CircleNotch className="h-4 w-4 animate-spin" />
|
{t("telegram.setup.step2.connect-bot")}
|
||||||
{t("telegram.setup.step2.connecting")}
|
</>
|
||||||
</>
|
)}
|
||||||
) : (
|
</button>
|
||||||
<>
|
|
||||||
<TelegramLogo className="h-4 w-4" weight="bold" />
|
|
||||||
{t("telegram.setup.step2.connect-bot")}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -87,73 +74,27 @@ function BotTokenInput({ botToken, setBotToken }) {
|
|||||||
const Icon = showToken ? Eye : EyeSlash;
|
const Icon = showToken ? Eye : EyeSlash;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-1">
|
<div className="flex flex-col gap-y-1.5 w-[320px]">
|
||||||
<label className="text-xs font-medium text-theme-text-secondary">
|
<label className="text-sm font-medium text-zinc-200 light:text-slate-900">
|
||||||
{t("telegram.setup.step2.bot-token")}
|
{t("telegram.setup.step2.bot-token")}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="bg-zinc-800 light:bg-white light:border light:border-slate-300 h-8 rounded-lg px-3.5 flex items-center gap-x-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowToken(!showToken)}
|
||||||
|
className="text-zinc-400 light:text-slate-500 hover:text-zinc-300 light:hover:text-slate-700 transition-colors shrink-0"
|
||||||
|
>
|
||||||
|
<Icon className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
<input
|
<input
|
||||||
type={showToken ? "text" : "password"}
|
type={showToken ? "text" : "password"}
|
||||||
value={botToken}
|
value={botToken}
|
||||||
onChange={(e) => setBotToken(e.target.value)}
|
onChange={(e) => setBotToken(e.target.value)}
|
||||||
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v..."
|
placeholder="123456:ABC-DEF123ghlkl-zyx57W2v"
|
||||||
className="bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5 pr-10"
|
className="bg-transparent flex-1 text-sm text-white light:text-slate-900 placeholder:text-zinc-400 light:placeholder:text-slate-500 outline-none min-w-0"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
{botToken.length > 0 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowToken(!showToken)}
|
|
||||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
||||||
>
|
|
||||||
<Icon className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WorkspaceSelect({
|
|
||||||
workspaces,
|
|
||||||
selectedWorkspace,
|
|
||||||
setSelectedWorkspace,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
if (!workspaces.length) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-y-1">
|
|
||||||
<label className="text-xs font-medium text-theme-text-secondary">
|
|
||||||
{t("telegram.setup.step2.default-workspace")}{" "}
|
|
||||||
<span className="italic font-normal">({t("common.optional")})</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
disabled
|
|
||||||
placeholder={t("telegram.setup.step2.no-workspace")}
|
|
||||||
className="bg-theme-settings-input-bg text-theme-text-primary text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-y-1">
|
|
||||||
<label className="text-xs font-medium text-theme-text-secondary">
|
|
||||||
{t("telegram.setup.step2.default-workspace")}{" "}
|
|
||||||
<span className="italic font-normal">({t("common.optional")})</span>
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={selectedWorkspace}
|
|
||||||
onChange={(e) => setSelectedWorkspace(e.target.value)}
|
|
||||||
className="bg-theme-settings-input-bg text-theme-text-primary text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
|
||||||
>
|
|
||||||
{workspaces.map((ws) => (
|
|
||||||
<option key={ws.slug} value={ws.slug}>
|
|
||||||
{ws.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import Sidebar from "@/components/SettingsSidebar";
|
import Sidebar from "@/components/SettingsSidebar";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import { CircleNotch } from "@phosphor-icons/react";
|
import { CircleNotch } from "@phosphor-icons/react";
|
||||||
import Workspace from "@/models/workspace";
|
|
||||||
import Telegram from "@/models/telegram";
|
import Telegram from "@/models/telegram";
|
||||||
import ConnectedView from "./ConnectedView";
|
import ConnectedView from "./ConnectedView";
|
||||||
import SetupView from "./SetupView";
|
import SetupView from "./SetupView";
|
||||||
@ -11,21 +11,19 @@ import System from "@/models/system";
|
|||||||
import paths from "@/utils/paths";
|
import paths from "@/utils/paths";
|
||||||
|
|
||||||
export default function TelegramBotSettings() {
|
export default function TelegramBotSettings() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [config, setConfig] = useState(null);
|
const [config, setConfig] = useState(null);
|
||||||
const [workspaces, setWorkspaces] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const [isMultiUserMode, configRes, allWorkspaces] = await Promise.all([
|
const [isMultiUserMode, configRes] = await Promise.all([
|
||||||
System.isMultiUserMode(),
|
System.isMultiUserMode(),
|
||||||
Telegram.getConfig(),
|
Telegram.getConfig(),
|
||||||
Workspace.all(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (isMultiUserMode) window.location = paths.home();
|
if (isMultiUserMode) navigate(paths.home());
|
||||||
setConfig(configRes?.config || null);
|
setConfig(configRes?.config || null);
|
||||||
setWorkspaces(allWorkspaces || []);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
@ -38,7 +36,7 @@ export default function TelegramBotSettings() {
|
|||||||
return (
|
return (
|
||||||
<ConnectionsLayout>
|
<ConnectionsLayout>
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<CircleNotch className="h-8 w-8 text-theme-text-secondary animate-spin" />
|
<CircleNotch className="h-8 w-8 text-zinc-400 light:text-slate-400 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
</ConnectionsLayout>
|
</ConnectionsLayout>
|
||||||
);
|
);
|
||||||
@ -48,7 +46,7 @@ export default function TelegramBotSettings() {
|
|||||||
if (!hasConfig) {
|
if (!hasConfig) {
|
||||||
return (
|
return (
|
||||||
<ConnectionsLayout fullPage={true}>
|
<ConnectionsLayout fullPage={true}>
|
||||||
<SetupView workspaces={workspaces} onConnected={handleConnected} />
|
<SetupView onConnected={handleConnected} />
|
||||||
</ConnectionsLayout>
|
</ConnectionsLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -57,7 +55,6 @@ export default function TelegramBotSettings() {
|
|||||||
<ConnectionsLayout fullPage={true}>
|
<ConnectionsLayout fullPage={true}>
|
||||||
<ConnectedView
|
<ConnectedView
|
||||||
config={config}
|
config={config}
|
||||||
workspaces={workspaces}
|
|
||||||
onDisconnected={handleDisconnected}
|
onDisconnected={handleDisconnected}
|
||||||
onReconnected={handleConnected}
|
onReconnected={handleConnected}
|
||||||
/>
|
/>
|
||||||
@ -68,23 +65,29 @@ export default function TelegramBotSettings() {
|
|||||||
function ConnectionsLayout({ children, fullPage = false }) {
|
function ConnectionsLayout({ children, fullPage = false }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex md:mt-0 mt-6">
|
<div className="w-screen h-screen overflow-hidden bg-zinc-950 light:bg-slate-50 flex md:mt-0 mt-6">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div
|
<div
|
||||||
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
|
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
|
||||||
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll p-4 md:p-0"
|
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-2xl bg-zinc-900 light:bg-white light:border light:border-slate-300 w-full h-full overflow-y-scroll p-4 md:p-0"
|
||||||
>
|
>
|
||||||
{fullPage ? (
|
{fullPage ? (
|
||||||
<div className="flex flex-col w-full px-1 md:pl-6 md:pr-[50px] md:py-6 py-16">
|
<div className="flex flex-col w-full px-1 md:pl-6 md:pr-[50px] md:py-6 py-16">
|
||||||
<div className="w-full flex flex-col gap-y-1 pb-4 border-white/10 border-b-2">
|
<div className="w-full flex flex-col gap-y-2 pb-6 border-b border-white/20 light:border-slate-300">
|
||||||
<div className="items-center flex gap-x-4">
|
<p className="text-lg font-semibold leading-7 text-white light:text-slate-900">
|
||||||
<p className="text-lg leading-6 font-bold text-theme-text-primary">
|
{t("telegram.title")}
|
||||||
{t("telegram.title")}
|
</p>
|
||||||
</p>
|
<p className="text-xs leading-4 text-zinc-400 light:text-slate-600 max-w-[700px]">
|
||||||
</div>
|
|
||||||
<p className="text-xs leading-[18px] font-base text-theme-text-secondary mt-2">
|
|
||||||
{t("telegram.description")}
|
{t("telegram.description")}
|
||||||
</p>
|
</p>
|
||||||
|
<a
|
||||||
|
href={paths.docs("/channels/telegram")}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-xs leading-4 text-white light:text-slate-900 underline w-fit"
|
||||||
|
>
|
||||||
|
View Documentation
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -52,8 +52,8 @@ export default {
|
|||||||
discord: () => {
|
discord: () => {
|
||||||
return "https://discord.com/invite/6UyHPeGZAC";
|
return "https://discord.com/invite/6UyHPeGZAC";
|
||||||
},
|
},
|
||||||
docs: () => {
|
docs: (path = "") => {
|
||||||
return "https://docs.anythingllm.com";
|
return `https://docs.anythingllm.com${path}`;
|
||||||
},
|
},
|
||||||
chatModes: () => {
|
chatModes: () => {
|
||||||
return "https://docs.anythingllm.com/features/chat-modes";
|
return "https://docs.anythingllm.com/features/chat-modes";
|
||||||
|
|||||||
@ -8,6 +8,7 @@ const { isSingleUserMode } = require("../utils/middleware/multiUserProtected");
|
|||||||
const { reqBody } = require("../utils/http");
|
const { reqBody } = require("../utils/http");
|
||||||
const { EventLogs } = require("../models/eventLogs");
|
const { EventLogs } = require("../models/eventLogs");
|
||||||
const { Workspace } = require("../models/workspace");
|
const { Workspace } = require("../models/workspace");
|
||||||
|
const { WorkspaceThread } = require("../models/workspaceThread");
|
||||||
const { encryptToken } = require("../utils/telegramBot/utils");
|
const { encryptToken } = require("../utils/telegramBot/utils");
|
||||||
|
|
||||||
function telegramEndpoints(app) {
|
function telegramEndpoints(app) {
|
||||||
@ -24,12 +25,32 @@ function telegramEndpoints(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const service = new TelegramBotService();
|
const service = new TelegramBotService();
|
||||||
|
|
||||||
|
// Resolve workspace, thread, and model from the first approved user's
|
||||||
|
// active state, falling back to the default workspace config.
|
||||||
|
const approvedUsers = connector.config.approved_users || [];
|
||||||
|
const activeUser = approvedUsers[0];
|
||||||
|
const workspaceSlug =
|
||||||
|
activeUser?.active_workspace ||
|
||||||
|
connector.config.default_workspace ||
|
||||||
|
null;
|
||||||
|
const threadSlug = activeUser?.active_thread || null;
|
||||||
|
|
||||||
|
let workspace = await Workspace.get({ slug: workspaceSlug });
|
||||||
|
if (!workspace) {
|
||||||
|
const available = await Workspace.where({}, 1);
|
||||||
|
if (available.length) workspace = available[0];
|
||||||
|
}
|
||||||
|
const thread = await WorkspaceThread.get({ slug: threadSlug });
|
||||||
|
|
||||||
return response.status(200).json({
|
return response.status(200).json({
|
||||||
config: {
|
config: {
|
||||||
active: connector.active,
|
active: connector.active,
|
||||||
connected: service.isRunning,
|
connected: service.isRunning,
|
||||||
bot_username: connector.config.bot_username || null,
|
bot_username: connector.config.bot_username || null,
|
||||||
default_workspace: connector.config.default_workspace || null,
|
default_workspace: workspace?.name || workspaceSlug || "—",
|
||||||
|
active_thread_name: thread?.name || "Default",
|
||||||
|
chat_model: workspace?.chatModel || "System default",
|
||||||
voice_response_mode:
|
voice_response_mode:
|
||||||
connector.config.voice_response_mode || "text_only",
|
connector.config.voice_response_mode || "text_only",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -324,6 +324,7 @@ class TelegramBotService {
|
|||||||
#setupHandlers() {
|
#setupHandlers() {
|
||||||
const ctx = this.#createContext();
|
const ctx = this.#createContext();
|
||||||
const guard = async (msg, handler) => {
|
const guard = async (msg, handler) => {
|
||||||
|
if (!this.#config) return;
|
||||||
if (!isVerified(this.#config.approved_users, msg.chat.id)) {
|
if (!isVerified(this.#config.approved_users, msg.chat.id)) {
|
||||||
sendPairingRequest(this.#bot, msg, this.#pendingPairings);
|
sendPairingRequest(this.#bot, msg, this.#pendingPairings);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user