diff --git a/frontend/src/locales/en/common.js b/frontend/src/locales/en/common.js index fd5e8bcac..e1a85e5a1 100644 --- a/frontend/src/locales/en/common.js +++ b/frontend/src/locales/en/common.js @@ -688,7 +688,7 @@ const TRANSLATIONS = { step2: { title: "Step 2: Connect your bot", 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", "default-workspace": "Default Workspace", "no-workspace": "No available workspaces. A new one will be created.", diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/ConnectedBotCard/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/ConnectedBotCard/index.jsx new file mode 100644 index 000000000..b7da1c27c --- /dev/null +++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/ConnectedBotCard/index.jsx @@ -0,0 +1,26 @@ +import { TelegramLogo } from "@phosphor-icons/react"; +import { useTranslation } from "react-i18next"; + +export default function ConnectedBotCard({ config }) { + const { t } = useTranslation(); + return ( +
+

+ Connected Bot +

+
+
+ +
+
+

+ @{config.bot_username} +

+

+ {t("telegram.connected.status")} +

+
+
+
+ ); +} diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/DetailsSection/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/DetailsSection/index.jsx new file mode 100644 index 000000000..a03acbd3d --- /dev/null +++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/DetailsSection/index.jsx @@ -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 ( +
+

+ Details +

+
+
+ + + + + t.me/{config.bot_username} + + } + /> +
+
+ +
+ ); +} + +function DetailRow({ label, value }) { + return ( +
+ + {label} + + {value} +
+ ); +} + +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 ( + + ); +} diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/DisconnectedView/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/DisconnectedView/index.jsx new file mode 100644 index 000000000..e6e419bb3 --- /dev/null +++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/DisconnectedView/index.jsx @@ -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 ( +
+
+

+ Connected Bot +

+
+
+ +
+
+

+ @{config.bot_username} +

+

+ {t("telegram.connected.status-disconnected")} +

+
+
+
+
+ + 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" + /> +
+ +
+
+
+ ); +} + +/** +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 ( +
+ + {t("telegram.connected.voice-response")} + + +
+ ); +} + +*/ diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersSection/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersSection/index.jsx new file mode 100644 index 000000000..17438762a --- /dev/null +++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersSection/index.jsx @@ -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 ( +
+
+

+ Users +

+

+ {t("telegram.users.pending-description")} +

+
+
+
+ {pendingUsers.map((user) => ( + + ))} + {approvedUsers.map((user) => ( + + ))} +
+
+ ); +} + +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 ( + <> +
+
+
+ + {initial} + +
+ + {displayName} + +
+
+ {isPending && code && ( +
+ + {code} + +
+ )} +
+
+ {isPending ? ( + <> + + + + ) : ( + + )} +
+
+
+ + ); +} diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersTable/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersTable/index.jsx deleted file mode 100644 index 1dfe6ec78..000000000 --- a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersTable/index.jsx +++ /dev/null @@ -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 ( -
-

{title}

-

{description}

-
- - - - - {isPending && ( - - )} - - - - - {users.length === 0 ? ( - - - - ) : ( - 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 ( - - - {isPending && ( - - )} - - - ); - }) - )} - -
- {t("telegram.users.user")} - - {t("telegram.users.pairing-code")} - - {" "} -
- {isPending - ? t("telegram.users.no-pending") - : t("telegram.users.no-approved")} -
- - {displayName} - - - - {code} - - - {isPending ? ( - <> - handleApprove(chatId)} - className="hover:light:bg-green-50 hover:light:text-green-500 hover:text-green-300" - > - {t("telegram.users.approve")} - - handleDeny(chatId)} - className="hover:light:bg-red-50 hover:light:text-red-500 hover:text-red-300" - > - {t("telegram.users.deny")} - - - ) : ( - handleRevoke(chatId)} - className="hover:light:bg-red-50" - > - {t("telegram.users.revoke")} - - )} -
-
-
- ); -} - -function ActionButton({ onClick, className = "", children }) { - return ( - - ); -} diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/index.jsx index 7bc0fc202..3525f4db2 100644 --- a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/index.jsx +++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/index.jsx @@ -1,31 +1,19 @@ import { useEffect, useState, useCallback } from "react"; -import { - ArrowSquareOut, - CircleNotch, - Eye, - EyeSlash, - TelegramLogo, -} from "@phosphor-icons/react"; import Telegram from "@/models/telegram"; -import showToast from "@/utils/toast"; -import UsersTable from "./UsersTable"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; +import ConnectedBotCard from "./ConnectedBotCard"; +import DetailsSection from "./DetailsSection"; +import UsersSection from "./UsersSection"; +import DisconnectedView from "./DisconnectedView"; export default function ConnectedView({ config, - workspaces, onDisconnected, onReconnected, }) { - const { t } = useTranslation(); const connected = config.connected; const [newToken, setNewToken] = useState(""); const [pendingUsers, setPendingUsers] = useState([]); const [approvedUsers, setApprovedUsers] = useState([]); - const workspaceName = - workspaces.find((ws) => ws.slug === config.default_workspace)?.name || - config.default_workspace; const fetchUsers = useCallback(async () => { const [pending, approved] = await Promise.all([ @@ -54,260 +42,14 @@ export default function ConnectedView({ } return ( -
-
-
-
- -
-
-

- @{config.bot_username} -

-

- {t("telegram.connected.status")} -

-
-
-
- - - {/* - 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. - */} - {/* */} - -
-
- - - + + +
); } - -function BotLink({ username }) { - const { t } = useTranslation(); - return ( -
- - {t("telegram.connected.bot-link")} - - - t.me/{username} - - -
- ); -} - -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 ( - - ); -} - -function WorkspaceName({ name }) { - const { t } = useTranslation(); - return ( -
- - {t("telegram.connected.workspace")} - - {name} -
- ); -} - -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 ( -
-
-
-
-
- -
-
-

- @{config.bot_username} -

-

- {t("telegram.connected.status-disconnected")} -

-
-
-
-
- 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 && ( - - )} -
- -
-
-
-
- ); -} - -/** -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 ( -
- - {t("telegram.connected.voice-response")} - - -
- ); -} - -*/ diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/CreateBotSection/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/CreateBotSection/index.jsx index 37bbbf986..8455dde12 100644 --- a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/CreateBotSection/index.jsx +++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/CreateBotSection/index.jsx @@ -1,5 +1,5 @@ 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 { Trans, useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -8,72 +8,70 @@ const BOTFATHER_URL = "https://t.me/BotFather"; export default function CreateBotSection() { const { t } = useTranslation(); - const qrSize = 180; - const logoSize = { width: 35 * 1.2, height: 22 * 1.2 }; + const qrSize = 137; + const logoSize = { width: 35, height: 22 }; return ( -
+
-

+

{t("telegram.setup.step1.title")}

-

+

+ ), }} />

-
-
-
- -
-
-
- - - {t("telegram.setup.step1.open-botfather")} - -
-

{t("telegram.setup.step1.instruction-1")}

-

- - ), - }} - /> -

-

{t("telegram.setup.step1.instruction-3")}

-

{t("telegram.setup.step1.instruction-4")}

-
+
+
+
+
+
+ + + {t("telegram.setup.step1.open-botfather")} + +
+
+

{t("telegram.setup.step1.instruction-1")}

+

+ + ), + }} + /> +

+

{t("telegram.setup.step1.instruction-3")}

+

{t("telegram.setup.step1.instruction-4")}

@@ -85,32 +83,16 @@ function SecurityTips() { const { t } = useTranslation(); return ( -
-
- -

- {t("telegram.setup.security.title")} -

-
-

+

+

+ {t("telegram.setup.security.title")} +

+

{t("telegram.setup.security.description")}

-
    -
  • - - Disable Groups - {" "} - {t("telegram.setup.security.disable-groups")} -
  • -
  • - - Disable Inline - {" "} - {t("telegram.setup.security.disable-inline")} -
  • +
      +
    • Disable Groups {t("telegram.setup.security.disable-groups")}
    • +
    • Disable Inline {t("telegram.setup.security.disable-inline")}
    • {t("telegram.setup.security.obscure-username")}
diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/index.jsx index 62385f9a9..8d2f27f98 100644 --- a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/index.jsx +++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/index.jsx @@ -10,12 +10,9 @@ import showToast from "@/utils/toast"; import CreateBotSection from "./CreateBotSection"; import { useTranslation } from "react-i18next"; -export default function SetupView({ workspaces, onConnected }) { +export default function SetupView({ onConnected }) { const { t } = useTranslation(); const [botToken, setBotToken] = useState(""); - const [selectedWorkspace, setSelectedWorkspace] = useState( - workspaces[0]?.slug || "" - ); const [connecting, setConnecting] = useState(false); async function handleConnect(e) { @@ -24,58 +21,48 @@ export default function SetupView({ workspaces, onConnected }) { return showToast(t("telegram.setup.toast-enter-token"), "error"); setConnecting(true); - const res = await Telegram.connect(botToken.trim(), selectedWorkspace); + const res = await Telegram.connect(botToken.trim()); setConnecting(false); if (!res.success) { showToast(res.error || t("telegram.setup.toast-connect-failed"), "error"); return; } - onConnected({ - active: true, - connected: true, - bot_username: res.bot_username, - default_workspace: selectedWorkspace || null, - }); + + const configRes = await Telegram.getConfig(); + onConnected(configRes?.config); } return ( -
+
-
-
-

+ +

+

{t("telegram.setup.step2.title")}

-

+

{t("telegram.setup.step2.description")}

-
- - - -
+ +
); @@ -87,73 +74,27 @@ function BotTokenInput({ botToken, setBotToken }) { const Icon = showToken ? Eye : EyeSlash; return ( -
-