chore: remove gateway REST API and UI (#9989)
Co-authored-by: Lifei Zhou <lifei@squareup.com>
This commit is contained in:
@@ -37,8 +37,8 @@ async fn shutdown_signal() {
|
||||
}
|
||||
|
||||
pub async fn run() -> Result<()> {
|
||||
// Install the rustls crypto provider early, before any spawned tasks (tunnel,
|
||||
// gateways, etc.) try to open TLS connections. Both `ring` and `aws-lc-rs`
|
||||
// Install the rustls crypto provider early, before any spawned tasks (tunnel, etc.)
|
||||
// try to open TLS connections. Both `ring` and `aws-lc-rs`
|
||||
// features are enabled on rustls (via different transitive deps), so rustls
|
||||
// cannot auto-detect a provider — we must pick one explicitly.
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
@@ -86,11 +86,6 @@ pub async fn run() -> Result<()> {
|
||||
|
||||
let addr = settings.socket_addr();
|
||||
|
||||
let gateway_manager = app_state.gateway_manager.clone();
|
||||
tokio::spawn(async move {
|
||||
gateway_manager.check_auto_start().await;
|
||||
});
|
||||
|
||||
if settings.tls {
|
||||
#[cfg(any(feature = "rustls-tls", feature = "native-tls"))]
|
||||
{
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
use crate::routes::errors::ErrorResponse;
|
||||
use crate::state::AppState;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use goose::gateway::manager::GatewayStatus;
|
||||
use goose::gateway::GatewayConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct StartGatewayRequest {
|
||||
pub gateway_type: String,
|
||||
pub platform_config: serde_json::Value,
|
||||
#[serde(default)]
|
||||
pub max_sessions: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct StopGatewayRequest {
|
||||
pub gateway_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct RestartGatewayRequest {
|
||||
pub gateway_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct RemoveGatewayRequest {
|
||||
pub gateway_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreatePairingRequest {
|
||||
pub gateway_type: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct PairingCodeResponse {
|
||||
pub code: String,
|
||||
pub expires_at: i64,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/gateway/start",
|
||||
request_body = StartGatewayRequest,
|
||||
responses(
|
||||
(status = 200, description = "Gateway started"),
|
||||
(status = 400, description = "Bad request", body = ErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ErrorResponse)
|
||||
)
|
||||
)]
|
||||
pub async fn start_gateway(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<StartGatewayRequest>,
|
||||
) -> Response {
|
||||
let mut config = GatewayConfig {
|
||||
gateway_type: request.gateway_type,
|
||||
platform_config: request.platform_config,
|
||||
max_sessions: request.max_sessions,
|
||||
};
|
||||
|
||||
let gw = match goose::gateway::create_gateway(&mut config) {
|
||||
Ok(gw) => gw,
|
||||
Err(e) => return ErrorResponse::bad_request(e.to_string()).into_response(),
|
||||
};
|
||||
|
||||
match state.gateway_manager.start_gateway(config, gw).await {
|
||||
Ok(()) => StatusCode::OK.into_response(),
|
||||
Err(e) => ErrorResponse::bad_request(e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/gateway/stop",
|
||||
request_body = StopGatewayRequest,
|
||||
responses(
|
||||
(status = 200, description = "Gateway stopped"),
|
||||
(status = 404, description = "Gateway not found", body = ErrorResponse)
|
||||
)
|
||||
)]
|
||||
pub async fn stop_gateway(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<StopGatewayRequest>,
|
||||
) -> Response {
|
||||
match state
|
||||
.gateway_manager
|
||||
.stop_gateway(&request.gateway_type)
|
||||
.await
|
||||
{
|
||||
Ok(()) => StatusCode::OK.into_response(),
|
||||
Err(e) => ErrorResponse::not_found(e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/gateway/restart",
|
||||
request_body = RestartGatewayRequest,
|
||||
responses(
|
||||
(status = 200, description = "Gateway restarted"),
|
||||
(status = 400, description = "Bad request", body = ErrorResponse),
|
||||
(status = 404, description = "No saved config", body = ErrorResponse)
|
||||
)
|
||||
)]
|
||||
pub async fn restart_gateway(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<RestartGatewayRequest>,
|
||||
) -> Response {
|
||||
match state
|
||||
.gateway_manager
|
||||
.restart_gateway(&request.gateway_type)
|
||||
.await
|
||||
{
|
||||
Ok(()) => StatusCode::OK.into_response(),
|
||||
Err(e) => ErrorResponse::bad_request(e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/gateway/remove",
|
||||
request_body = RemoveGatewayRequest,
|
||||
responses(
|
||||
(status = 200, description = "Gateway removed"),
|
||||
(status = 500, description = "Internal server error", body = ErrorResponse)
|
||||
)
|
||||
)]
|
||||
pub async fn remove_gateway(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<RemoveGatewayRequest>,
|
||||
) -> Response {
|
||||
match state
|
||||
.gateway_manager
|
||||
.remove_gateway(&request.gateway_type)
|
||||
.await
|
||||
{
|
||||
Ok(()) => StatusCode::OK.into_response(),
|
||||
Err(e) => ErrorResponse::internal(e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/gateway/status",
|
||||
responses(
|
||||
(status = 200, description = "Gateway statuses", body = Vec<GatewayStatus>)
|
||||
)
|
||||
)]
|
||||
pub async fn gateway_status(State(state): State<Arc<AppState>>) -> Json<Vec<GatewayStatus>> {
|
||||
Json(state.gateway_manager.status().await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/gateway/pair",
|
||||
request_body = CreatePairingRequest,
|
||||
responses(
|
||||
(status = 200, description = "Pairing code generated", body = PairingCodeResponse),
|
||||
(status = 500, description = "Internal server error", body = ErrorResponse)
|
||||
)
|
||||
)]
|
||||
pub async fn create_pairing_code(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<CreatePairingRequest>,
|
||||
) -> Response {
|
||||
match state
|
||||
.gateway_manager
|
||||
.generate_pairing_code(&request.gateway_type)
|
||||
.await
|
||||
{
|
||||
Ok((code, expires_at)) => (
|
||||
StatusCode::OK,
|
||||
Json(PairingCodeResponse { code, expires_at }),
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => ErrorResponse::internal(e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/gateway/pair/{platform}/{user_id}",
|
||||
params(
|
||||
("platform" = String, Path, description = "Platform name"),
|
||||
("user_id" = String, Path, description = "Platform user ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "User unpaired"),
|
||||
(status = 404, description = "Pairing not found", body = ErrorResponse)
|
||||
)
|
||||
)]
|
||||
pub async fn unpair_user(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((platform, user_id)): Path<(String, String)>,
|
||||
) -> Response {
|
||||
match state.gateway_manager.unpair_user(&platform, &user_id).await {
|
||||
Ok(true) => StatusCode::OK.into_response(),
|
||||
Ok(false) => {
|
||||
ErrorResponse::not_found(format!("No pairing found for {}/{}", platform, user_id))
|
||||
.into_response()
|
||||
}
|
||||
Err(e) => ErrorResponse::internal(e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routes(state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
.route("/gateway/start", post(start_gateway))
|
||||
.route("/gateway/stop", post(stop_gateway))
|
||||
.route("/gateway/restart", post(restart_gateway))
|
||||
.route("/gateway/remove", post(remove_gateway))
|
||||
.route("/gateway/status", get(gateway_status))
|
||||
.route("/gateway/pair", post(create_pairing_code))
|
||||
.route("/gateway/pair/{platform}/{user_id}", delete(unpair_user))
|
||||
.with_state(state)
|
||||
}
|
||||
@@ -4,7 +4,6 @@ pub mod config_management;
|
||||
pub mod dictation;
|
||||
pub mod errors;
|
||||
pub mod features;
|
||||
pub mod gateway;
|
||||
#[cfg(feature = "local-inference")]
|
||||
pub mod local_inference;
|
||||
pub mod mcp_app_proxy;
|
||||
@@ -42,7 +41,6 @@ pub fn configure(state: Arc<crate::state::AppState>, secret_key: String) -> Rout
|
||||
.merge(setup::routes(state.clone()))
|
||||
.merge(telemetry::routes(state.clone()))
|
||||
.merge(tunnel::routes(state.clone()))
|
||||
.merge(gateway::routes(state.clone()))
|
||||
.merge(mcp_ui_proxy::routes(secret_key.clone()))
|
||||
.merge(mcp_app_proxy::routes(secret_key))
|
||||
.merge(session_events::routes(state.clone()))
|
||||
|
||||
@@ -14,7 +14,6 @@ use tokio::task::JoinHandle;
|
||||
use crate::session_event_bus::SessionEventBus;
|
||||
use crate::tunnel::TunnelManager;
|
||||
use goose::agents::ExtensionLoadResult;
|
||||
use goose::gateway::manager::GatewayManager;
|
||||
#[cfg(feature = "local-inference")]
|
||||
use goose::providers::local_inference::InferenceRuntime;
|
||||
|
||||
@@ -27,7 +26,6 @@ pub struct AppState {
|
||||
pub recipe_file_hash_map: Arc<Mutex<HashMap<String, PathBuf>>>,
|
||||
recipe_session_tracker: Arc<Mutex<HashSet<String>>>,
|
||||
pub tunnel_manager: Arc<TunnelManager>,
|
||||
pub gateway_manager: Arc<GatewayManager>,
|
||||
pub extension_loading_tasks: ExtensionLoadingTasks,
|
||||
#[cfg(feature = "local-inference")]
|
||||
inference_runtime: Arc<OnceLock<Arc<InferenceRuntime>>>,
|
||||
@@ -40,14 +38,12 @@ impl AppState {
|
||||
|
||||
let agent_manager = AgentManager::instance().await?;
|
||||
let tunnel_manager = Arc::new(TunnelManager::new(tls));
|
||||
let gateway_manager = Arc::new(GatewayManager::new(agent_manager.clone())?);
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
agent_manager,
|
||||
recipe_file_hash_map: Arc::new(Mutex::new(HashMap::new())),
|
||||
recipe_session_tracker: Arc::new(Mutex::new(HashSet::new())),
|
||||
tunnel_manager,
|
||||
gateway_manager,
|
||||
extension_loading_tasks: Arc::new(Mutex::new(HashMap::new())),
|
||||
#[cfg(feature = "local-inference")]
|
||||
inference_runtime: Arc::new(OnceLock::new()),
|
||||
|
||||
@@ -20,8 +20,6 @@ import {
|
||||
KeyRound,
|
||||
} from 'lucide-react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import GatewaySettingsSection from './gateways/GatewaySettingsSection';
|
||||
import { getTunnelStatus } from '../../api/sdk.gen';
|
||||
import ChatSettingsSection from './chat/ChatSettingsSection';
|
||||
import KeyboardShortcutsSection from './keyboard/KeyboardShortcutsSection';
|
||||
import AuthSettingsSection from './auth/AuthSettingsSection';
|
||||
@@ -86,7 +84,6 @@ export default function SettingsView({
|
||||
viewOptions: SettingsViewOptions;
|
||||
}) {
|
||||
const [activeTab, setActiveTab] = useState('models');
|
||||
const [tunnelDisabled, setTunnelDisabled] = useState(false);
|
||||
const hasTrackedInitialTab = useRef(false);
|
||||
const { localInference } = useFeatures();
|
||||
const intl = useIntl();
|
||||
@@ -112,7 +109,6 @@ export default function SettingsView({
|
||||
prompts: 'prompts',
|
||||
keyboard: 'keyboard',
|
||||
auth: 'auth',
|
||||
gateway: 'sharing',
|
||||
'local-inference': 'local-inference',
|
||||
};
|
||||
|
||||
@@ -137,16 +133,6 @@ export default function SettingsView({
|
||||
}
|
||||
}, [activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
getTunnelStatus()
|
||||
.then(({ data }) => {
|
||||
setTunnelDisabled(data?.state === 'disabled');
|
||||
})
|
||||
.catch(() => {
|
||||
setTunnelDisabled(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && !event.defaultPrevented) {
|
||||
@@ -269,7 +255,6 @@ export default function SettingsView({
|
||||
<div className="space-y-8 pb-8">
|
||||
<SessionSharingSection />
|
||||
<ExternalBackendSection />
|
||||
{!tunnelDisabled && <GatewaySettingsSection />}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
@@ -1,502 +0,0 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from '../../ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card';
|
||||
import { Input } from '../../ui/input';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '../../ui/dialog';
|
||||
import { Loader2, Copy, Check, Square, Trash2, User } from 'lucide-react';
|
||||
import { getApiUrl } from '../../../config';
|
||||
import { defineMessages, useIntl } from '../../../i18n';
|
||||
|
||||
const i18n = defineMessages({
|
||||
loading: {
|
||||
id: 'gatewaySettings.loading',
|
||||
defaultMessage: 'Loading...',
|
||||
},
|
||||
pairedUsers: {
|
||||
id: 'gatewaySettings.pairedUsers',
|
||||
defaultMessage: 'Paired Users',
|
||||
},
|
||||
telegram: {
|
||||
id: 'gatewaySettings.telegram',
|
||||
defaultMessage: 'Telegram',
|
||||
},
|
||||
running: {
|
||||
id: 'gatewaySettings.running',
|
||||
defaultMessage: 'Running',
|
||||
},
|
||||
stopped: {
|
||||
id: 'gatewaySettings.stopped',
|
||||
defaultMessage: 'Stopped',
|
||||
},
|
||||
pairDevice: {
|
||||
id: 'gatewaySettings.pairDevice',
|
||||
defaultMessage: 'Pair Device',
|
||||
},
|
||||
stop: {
|
||||
id: 'gatewaySettings.stop',
|
||||
defaultMessage: 'Stop',
|
||||
},
|
||||
start: {
|
||||
id: 'gatewaySettings.start',
|
||||
defaultMessage: 'Start',
|
||||
},
|
||||
remove: {
|
||||
id: 'gatewaySettings.remove',
|
||||
defaultMessage: 'Remove',
|
||||
},
|
||||
pasteBotToken: {
|
||||
id: 'gatewaySettings.pasteBotToken',
|
||||
defaultMessage: 'Paste bot token here',
|
||||
},
|
||||
botFatherInstructions: {
|
||||
id: 'gatewaySettings.botFatherInstructions',
|
||||
defaultMessage:
|
||||
'Open @BotFather on your phone, send /newbot, and follow the prompts to name your bot. BotFather will reply with an API token — paste it below.',
|
||||
},
|
||||
pairingCode: {
|
||||
id: 'gatewaySettings.pairingCode',
|
||||
defaultMessage: 'Pairing Code',
|
||||
},
|
||||
sendCodeToPair: {
|
||||
id: 'gatewaySettings.sendCodeToPair',
|
||||
defaultMessage: 'Send this code to your {gatewayType} bot to pair.',
|
||||
},
|
||||
expiresIn: {
|
||||
id: 'gatewaySettings.expiresIn',
|
||||
defaultMessage: 'Expires in {time}',
|
||||
},
|
||||
close: {
|
||||
id: 'gatewaySettings.close',
|
||||
defaultMessage: 'Close',
|
||||
},
|
||||
failedToStart: {
|
||||
id: 'gatewaySettings.failedToStart',
|
||||
defaultMessage: 'Failed to start',
|
||||
},
|
||||
failedToStop: {
|
||||
id: 'gatewaySettings.failedToStop',
|
||||
defaultMessage: 'Failed to stop',
|
||||
},
|
||||
failedToRemove: {
|
||||
id: 'gatewaySettings.failedToRemove',
|
||||
defaultMessage: 'Failed to remove',
|
||||
},
|
||||
failedToUnpairUser: {
|
||||
id: 'gatewaySettings.failedToUnpairUser',
|
||||
defaultMessage: 'Failed to unpair user',
|
||||
},
|
||||
failedToGeneratePairingCode: {
|
||||
id: 'gatewaySettings.failedToGeneratePairingCode',
|
||||
defaultMessage: 'Failed to generate pairing code',
|
||||
},
|
||||
});
|
||||
|
||||
interface PairedUserInfo {
|
||||
platform: string;
|
||||
user_id: string;
|
||||
display_name: string | null;
|
||||
session_id: string;
|
||||
paired_at: number;
|
||||
}
|
||||
|
||||
interface GatewayStatus {
|
||||
gateway_type: string;
|
||||
running: boolean;
|
||||
configured: boolean;
|
||||
paired_users: PairedUserInfo[];
|
||||
info?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface PairingCodeResponse {
|
||||
code: string;
|
||||
expires_at: number;
|
||||
}
|
||||
|
||||
async function gatewayFetch(endpoint: string, options: globalThis.RequestInit = {}) {
|
||||
const secretKey = await window.electron.getSecretKey();
|
||||
const url = getApiUrl(endpoint);
|
||||
return fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': secretKey,
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default function GatewaySettingsSection() {
|
||||
const intl = useIntl();
|
||||
const [gateways, setGateways] = useState<GatewayStatus[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pairingCode, setPairingCode] = useState<PairingCodeResponse | null>(null);
|
||||
const [pairingGatewayType, setPairingGatewayType] = useState<string | null>(null);
|
||||
const [copiedCode, setCopiedCode] = useState(false);
|
||||
|
||||
const fetchStatus = useCallback(async () => {
|
||||
try {
|
||||
const response = await gatewayFetch('/gateway/status');
|
||||
if (response.ok) {
|
||||
setGateways(await response.json());
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch gateway status:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStatus();
|
||||
const interval = setInterval(fetchStatus, 5000);
|
||||
return () => clearInterval(interval);
|
||||
}, [fetchStatus]);
|
||||
|
||||
const doPost = async (endpoint: string, body: object, errorMsg: string) => {
|
||||
setError(null);
|
||||
try {
|
||||
const response = await gatewayFetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
throw new Error(data.message || errorMsg);
|
||||
}
|
||||
await fetchStatus();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : errorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnpairUser = async (platform: string, userId: string) => {
|
||||
setError(null);
|
||||
try {
|
||||
const response = await gatewayFetch(`/gateway/pair/${platform}/${userId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
throw new Error(data.message || intl.formatMessage(i18n.failedToUnpairUser));
|
||||
}
|
||||
await fetchStatus();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : intl.formatMessage(i18n.failedToUnpairUser));
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopiedCode(true);
|
||||
setTimeout(() => setCopiedCode(false), 2000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-text-muted py-4">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{intl.formatMessage(i18n.loading)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const telegram = gateways.find((g) => g.gateway_type === 'telegram');
|
||||
|
||||
return (
|
||||
<>
|
||||
{error && (
|
||||
<div className="p-3 bg-red-100 dark:bg-red-900/20 border border-red-300 dark:border-red-800 rounded text-sm text-red-800 dark:text-red-200 mb-4">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TelegramGatewayCard
|
||||
status={telegram}
|
||||
onStart={(config) =>
|
||||
doPost(
|
||||
'/gateway/start',
|
||||
{ gateway_type: 'telegram', platform_config: config, max_sessions: 0 },
|
||||
intl.formatMessage(i18n.failedToStart)
|
||||
)
|
||||
}
|
||||
onRestart={() =>
|
||||
doPost('/gateway/restart', { gateway_type: 'telegram' }, intl.formatMessage(i18n.failedToStart))
|
||||
}
|
||||
onStop={() => doPost('/gateway/stop', { gateway_type: 'telegram' }, intl.formatMessage(i18n.failedToStop))}
|
||||
onRemove={() => doPost('/gateway/remove', { gateway_type: 'telegram' }, intl.formatMessage(i18n.failedToRemove))}
|
||||
onGenerateCode={async () => {
|
||||
setError(null);
|
||||
try {
|
||||
const response = await gatewayFetch('/gateway/pair', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ gateway_type: 'telegram' }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
throw new Error(data.message || intl.formatMessage(i18n.failedToGeneratePairingCode));
|
||||
}
|
||||
const data: PairingCodeResponse = await response.json();
|
||||
setPairingCode(data);
|
||||
setPairingGatewayType('telegram');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : intl.formatMessage(i18n.failedToGeneratePairingCode));
|
||||
}
|
||||
}}
|
||||
onUnpairUser={handleUnpairUser}
|
||||
/>
|
||||
|
||||
<PairingCodeModal
|
||||
open={pairingCode !== null}
|
||||
onClose={() => {
|
||||
setPairingCode(null);
|
||||
setPairingGatewayType(null);
|
||||
}}
|
||||
code={pairingCode}
|
||||
gatewayType={pairingGatewayType}
|
||||
onCopy={copyToClipboard}
|
||||
copied={copiedCode}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function PairedUsersList({
|
||||
users,
|
||||
onUnpairUser,
|
||||
}: {
|
||||
users: PairedUserInfo[];
|
||||
onUnpairUser: (platform: string, userId: string) => void;
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
if (users.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-1 mt-2">
|
||||
<h4 className="text-xs text-text-muted font-medium">{intl.formatMessage(i18n.pairedUsers)}</h4>
|
||||
{users.map((user) => (
|
||||
<div
|
||||
key={`${user.platform}-${user.user_id}`}
|
||||
className="flex items-center justify-between py-1.5 px-2 bg-background-muted rounded text-sm"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<User className="h-3 w-3 text-text-muted flex-shrink-0" />
|
||||
<span className="truncate">{user.display_name || user.user_id}</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onUnpairUser(user.platform, user.user_id)}
|
||||
className="h-6 w-6 p-0 text-text-muted hover:text-red-600 flex-shrink-0"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TelegramGatewayCard({
|
||||
status,
|
||||
onStart,
|
||||
onRestart,
|
||||
onStop,
|
||||
onRemove,
|
||||
onGenerateCode,
|
||||
onUnpairUser,
|
||||
}: {
|
||||
status: GatewayStatus | undefined;
|
||||
onStart: (config: Record<string, unknown>) => Promise<void>;
|
||||
onRestart: () => Promise<void>;
|
||||
onStop: () => Promise<void>;
|
||||
onRemove: () => Promise<void>;
|
||||
onGenerateCode: () => void;
|
||||
onUnpairUser: (platform: string, userId: string) => void;
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
const [botToken, setBotToken] = useState('');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const running = status?.running ?? false;
|
||||
const configured = status?.configured ?? false;
|
||||
|
||||
const wrap = (fn: () => Promise<void>) => async () => {
|
||||
setBusy(true);
|
||||
try {
|
||||
await fn();
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFirstStart = wrap(async () => {
|
||||
if (!botToken.trim()) return;
|
||||
await onStart({ bot_token: botToken.trim() });
|
||||
setBotToken('');
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="rounded-lg">
|
||||
<CardHeader className="pb-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
{intl.formatMessage(i18n.telegram)}
|
||||
{running && (
|
||||
<span className="inline-flex items-center text-xs text-green-700 dark:text-green-400 bg-green-100 dark:bg-green-900/30 px-2 py-0.5 rounded-full">
|
||||
{intl.formatMessage(i18n.running)}
|
||||
</span>
|
||||
)}
|
||||
{!running && configured && (
|
||||
<span className="inline-flex items-center text-xs text-yellow-700 dark:text-yellow-400 bg-yellow-100 dark:bg-yellow-900/30 px-2 py-0.5 rounded-full">
|
||||
{intl.formatMessage(i18n.stopped)}
|
||||
</span>
|
||||
)}
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
{running && (
|
||||
<>
|
||||
<Button variant="outline" size="sm" onClick={onGenerateCode}>
|
||||
{intl.formatMessage(i18n.pairDevice)}
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm" disabled={busy} onClick={wrap(onStop)}>
|
||||
<Square className="h-3 w-3 mr-1" />
|
||||
{intl.formatMessage(i18n.stop)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{!running && configured && (
|
||||
<>
|
||||
<Button size="sm" disabled={busy} onClick={wrap(onRestart)}>
|
||||
{busy ? <Loader2 className="h-4 w-4 animate-spin" /> : intl.formatMessage(i18n.start)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={busy}
|
||||
onClick={wrap(onRemove)}
|
||||
className="text-red-600 hover:text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:text-red-300 dark:hover:bg-red-900/20"
|
||||
>
|
||||
<Trash2 className="h-3 w-3 mr-1" />
|
||||
{intl.formatMessage(i18n.remove)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3 space-y-2">
|
||||
{!running && !configured && (
|
||||
<>
|
||||
<div className="text-xs text-text-muted space-y-1.5 mb-2">
|
||||
<p>
|
||||
{intl.formatMessage(i18n.botFatherInstructions)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={intl.formatMessage(i18n.pasteBotToken)}
|
||||
value={botToken}
|
||||
onChange={(e) => setBotToken(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleFirstStart()}
|
||||
className="text-sm"
|
||||
/>
|
||||
<Button size="sm" onClick={handleFirstStart} disabled={busy || !botToken.trim()}>
|
||||
{busy ? <Loader2 className="h-4 w-4 animate-spin" /> : intl.formatMessage(i18n.start)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{status && <PairedUsersList users={status.paired_users} onUnpairUser={onUnpairUser} />}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function PairingCodeModal({
|
||||
open,
|
||||
onClose,
|
||||
code,
|
||||
gatewayType,
|
||||
onCopy,
|
||||
copied,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
code: PairingCodeResponse | null;
|
||||
gatewayType: string | null;
|
||||
onCopy: (text: string) => void;
|
||||
copied: boolean;
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
const [timeRemaining, setTimeRemaining] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!code) return;
|
||||
|
||||
const updateTimer = () => {
|
||||
const remaining = Math.max(0, code.expires_at - Math.floor(Date.now() / 1000));
|
||||
setTimeRemaining(remaining);
|
||||
if (remaining === 0) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
updateTimer();
|
||||
const interval = setInterval(updateTimer, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [code, onClose]);
|
||||
|
||||
if (!code) return null;
|
||||
|
||||
const minutes = Math.floor(timeRemaining / 60);
|
||||
const seconds = timeRemaining % 60;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
|
||||
<DialogContent className="sm:max-w-[400px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{intl.formatMessage(i18n.pairingCode)}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-6 space-y-4">
|
||||
<div className="flex justify-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="text-4xl font-mono font-bold tracking-[0.3em] select-all">
|
||||
{code.code}
|
||||
</code>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onCopy(code.code)}
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-sm text-text-muted">
|
||||
{intl.formatMessage(i18n.sendCodeToPair, { gatewayType })}
|
||||
</p>
|
||||
|
||||
<div className="text-center text-xs text-text-muted">
|
||||
{intl.formatMessage(i18n.expiresIn, {
|
||||
time: `${minutes}:${seconds.toString().padStart(2, '0')}`,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
{intl.formatMessage(i18n.close)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1229,66 +1229,6 @@
|
||||
"freeOptionCards.unexpectedError": {
|
||||
"defaultMessage": "An unexpected error occurred during setup."
|
||||
},
|
||||
"gatewaySettings.botFatherInstructions": {
|
||||
"defaultMessage": "Open @BotFather on your phone, send /newbot, and follow the prompts to name your bot. BotFather will reply with an API token — paste it below."
|
||||
},
|
||||
"gatewaySettings.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
"gatewaySettings.expiresIn": {
|
||||
"defaultMessage": "Expires in {time}"
|
||||
},
|
||||
"gatewaySettings.failedToGeneratePairingCode": {
|
||||
"defaultMessage": "Failed to generate pairing code"
|
||||
},
|
||||
"gatewaySettings.failedToRemove": {
|
||||
"defaultMessage": "Failed to remove"
|
||||
},
|
||||
"gatewaySettings.failedToStart": {
|
||||
"defaultMessage": "Failed to start"
|
||||
},
|
||||
"gatewaySettings.failedToStop": {
|
||||
"defaultMessage": "Failed to stop"
|
||||
},
|
||||
"gatewaySettings.failedToUnpairUser": {
|
||||
"defaultMessage": "Failed to unpair user"
|
||||
},
|
||||
"gatewaySettings.loading": {
|
||||
"defaultMessage": "Loading..."
|
||||
},
|
||||
"gatewaySettings.pairDevice": {
|
||||
"defaultMessage": "Pair Device"
|
||||
},
|
||||
"gatewaySettings.pairedUsers": {
|
||||
"defaultMessage": "Paired Users"
|
||||
},
|
||||
"gatewaySettings.pairingCode": {
|
||||
"defaultMessage": "Pairing Code"
|
||||
},
|
||||
"gatewaySettings.pasteBotToken": {
|
||||
"defaultMessage": "Paste bot token here"
|
||||
},
|
||||
"gatewaySettings.remove": {
|
||||
"defaultMessage": "Remove"
|
||||
},
|
||||
"gatewaySettings.running": {
|
||||
"defaultMessage": "Running"
|
||||
},
|
||||
"gatewaySettings.sendCodeToPair": {
|
||||
"defaultMessage": "Send this code to your {gatewayType} bot to pair."
|
||||
},
|
||||
"gatewaySettings.start": {
|
||||
"defaultMessage": "Start"
|
||||
},
|
||||
"gatewaySettings.stop": {
|
||||
"defaultMessage": "Stop"
|
||||
},
|
||||
"gatewaySettings.stopped": {
|
||||
"defaultMessage": "Stopped"
|
||||
},
|
||||
"gatewaySettings.telegram": {
|
||||
"defaultMessage": "Telegram"
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
|
||||
@@ -1229,66 +1229,6 @@
|
||||
"freeOptionCards.unexpectedError": {
|
||||
"defaultMessage": "Ocurrió un error inesperado durante la configuración."
|
||||
},
|
||||
"gatewaySettings.botFatherInstructions": {
|
||||
"defaultMessage": "Abre @BotFather en tu teléfono, envía /newbot y sigue las indicaciones para nombrar tu bot. BotFather te responderá con un token de API — pégalo abajo."
|
||||
},
|
||||
"gatewaySettings.close": {
|
||||
"defaultMessage": "Cerrar"
|
||||
},
|
||||
"gatewaySettings.expiresIn": {
|
||||
"defaultMessage": "Expira en {time}"
|
||||
},
|
||||
"gatewaySettings.failedToGeneratePairingCode": {
|
||||
"defaultMessage": "No se pudo generar el código de emparejamiento"
|
||||
},
|
||||
"gatewaySettings.failedToRemove": {
|
||||
"defaultMessage": "No se pudo quitar"
|
||||
},
|
||||
"gatewaySettings.failedToStart": {
|
||||
"defaultMessage": "No se pudo iniciar"
|
||||
},
|
||||
"gatewaySettings.failedToStop": {
|
||||
"defaultMessage": "No se pudo detener"
|
||||
},
|
||||
"gatewaySettings.failedToUnpairUser": {
|
||||
"defaultMessage": "No se pudo desemparejar al usuario"
|
||||
},
|
||||
"gatewaySettings.loading": {
|
||||
"defaultMessage": "Cargando..."
|
||||
},
|
||||
"gatewaySettings.pairDevice": {
|
||||
"defaultMessage": "Emparejar dispositivo"
|
||||
},
|
||||
"gatewaySettings.pairedUsers": {
|
||||
"defaultMessage": "Usuarios emparejados"
|
||||
},
|
||||
"gatewaySettings.pairingCode": {
|
||||
"defaultMessage": "Código de emparejamiento"
|
||||
},
|
||||
"gatewaySettings.pasteBotToken": {
|
||||
"defaultMessage": "Pega aquí el token del bot"
|
||||
},
|
||||
"gatewaySettings.remove": {
|
||||
"defaultMessage": "Quitar"
|
||||
},
|
||||
"gatewaySettings.running": {
|
||||
"defaultMessage": "En ejecución"
|
||||
},
|
||||
"gatewaySettings.sendCodeToPair": {
|
||||
"defaultMessage": "Envía este código a tu bot de {gatewayType} para emparejar."
|
||||
},
|
||||
"gatewaySettings.start": {
|
||||
"defaultMessage": "Iniciar"
|
||||
},
|
||||
"gatewaySettings.stop": {
|
||||
"defaultMessage": "Detener"
|
||||
},
|
||||
"gatewaySettings.stopped": {
|
||||
"defaultMessage": "Detenido"
|
||||
},
|
||||
"gatewaySettings.telegram": {
|
||||
"defaultMessage": "Telegram"
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "Cerrar"
|
||||
},
|
||||
|
||||
@@ -1229,66 +1229,6 @@
|
||||
"freeOptionCards.unexpectedError": {
|
||||
"defaultMessage": "सेटअप के दौरान एक अप्रत्याशित त्रुटि उत्पन्न हुई."
|
||||
},
|
||||
"gatewaySettings.botFatherInstructions": {
|
||||
"defaultMessage": "अपने फ़ोन पर @BotFather खोलें, /newbot भेजें, और अपने बॉट को नाम देने के लिए संकेतों का पालन करें। BotFather एक API टोकन के साथ उत्तर देगा - इसे नीचे चिपकाएँ।"
|
||||
},
|
||||
"gatewaySettings.close": {
|
||||
"defaultMessage": "बंद करें"
|
||||
},
|
||||
"gatewaySettings.expiresIn": {
|
||||
"defaultMessage": "{time} में समाप्त होता है"
|
||||
},
|
||||
"gatewaySettings.failedToGeneratePairingCode": {
|
||||
"defaultMessage": "युग्मन कोड जनरेट करने में विफल"
|
||||
},
|
||||
"gatewaySettings.failedToRemove": {
|
||||
"defaultMessage": "हटाने में विफल"
|
||||
},
|
||||
"gatewaySettings.failedToStart": {
|
||||
"defaultMessage": "प्रारंभ करने में विफल"
|
||||
},
|
||||
"gatewaySettings.failedToStop": {
|
||||
"defaultMessage": "रोकने में विफल"
|
||||
},
|
||||
"gatewaySettings.failedToUnpairUser": {
|
||||
"defaultMessage": "उपयोगकर्ता को अयुग्मित करने में विफल"
|
||||
},
|
||||
"gatewaySettings.loading": {
|
||||
"defaultMessage": "लोड हो रहा है..."
|
||||
},
|
||||
"gatewaySettings.pairDevice": {
|
||||
"defaultMessage": "जोड़ी डिवाइस"
|
||||
},
|
||||
"gatewaySettings.pairedUsers": {
|
||||
"defaultMessage": "युग्मित उपयोगकर्ता"
|
||||
},
|
||||
"gatewaySettings.pairingCode": {
|
||||
"defaultMessage": "युग्मन कोड"
|
||||
},
|
||||
"gatewaySettings.pasteBotToken": {
|
||||
"defaultMessage": "यहां बॉट टोकन चिपकाएं"
|
||||
},
|
||||
"gatewaySettings.remove": {
|
||||
"defaultMessage": "हटाओ"
|
||||
},
|
||||
"gatewaySettings.running": {
|
||||
"defaultMessage": "चल रहा है"
|
||||
},
|
||||
"gatewaySettings.sendCodeToPair": {
|
||||
"defaultMessage": "जोड़ी बनाने के लिए इस कोड को अपने {gatewayType} बॉट पर भेजें।"
|
||||
},
|
||||
"gatewaySettings.start": {
|
||||
"defaultMessage": "प्रारंभ करें"
|
||||
},
|
||||
"gatewaySettings.stop": {
|
||||
"defaultMessage": "रुकें"
|
||||
},
|
||||
"gatewaySettings.stopped": {
|
||||
"defaultMessage": "रुक गया"
|
||||
},
|
||||
"gatewaySettings.telegram": {
|
||||
"defaultMessage": "टेलीग्राम"
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "बंद करें"
|
||||
},
|
||||
|
||||
@@ -1229,66 +1229,6 @@
|
||||
"freeOptionCards.unexpectedError": {
|
||||
"defaultMessage": "セットアップ中に予期しないエラーが発生しました。"
|
||||
},
|
||||
"gatewaySettings.botFatherInstructions": {
|
||||
"defaultMessage": "スマートフォンで @BotFather を開き、/newbot を送信して、指示に従ってボットに名前を付けます。BotFather から API トークンが届くので、下に貼り付けてください。"
|
||||
},
|
||||
"gatewaySettings.close": {
|
||||
"defaultMessage": "閉じる"
|
||||
},
|
||||
"gatewaySettings.expiresIn": {
|
||||
"defaultMessage": "{time} 後に期限切れ"
|
||||
},
|
||||
"gatewaySettings.failedToGeneratePairingCode": {
|
||||
"defaultMessage": "ペアリングコードの生成に失敗しました"
|
||||
},
|
||||
"gatewaySettings.failedToRemove": {
|
||||
"defaultMessage": "削除に失敗しました"
|
||||
},
|
||||
"gatewaySettings.failedToStart": {
|
||||
"defaultMessage": "開始に失敗しました"
|
||||
},
|
||||
"gatewaySettings.failedToStop": {
|
||||
"defaultMessage": "停止に失敗しました"
|
||||
},
|
||||
"gatewaySettings.failedToUnpairUser": {
|
||||
"defaultMessage": "ユーザーのペアリング解除に失敗しました"
|
||||
},
|
||||
"gatewaySettings.loading": {
|
||||
"defaultMessage": "読み込み中..."
|
||||
},
|
||||
"gatewaySettings.pairDevice": {
|
||||
"defaultMessage": "デバイスをペアリング"
|
||||
},
|
||||
"gatewaySettings.pairedUsers": {
|
||||
"defaultMessage": "ペアリング済みユーザー"
|
||||
},
|
||||
"gatewaySettings.pairingCode": {
|
||||
"defaultMessage": "ペアリングコード"
|
||||
},
|
||||
"gatewaySettings.pasteBotToken": {
|
||||
"defaultMessage": "ボットトークンをここに貼り付け"
|
||||
},
|
||||
"gatewaySettings.remove": {
|
||||
"defaultMessage": "削除"
|
||||
},
|
||||
"gatewaySettings.running": {
|
||||
"defaultMessage": "実行中"
|
||||
},
|
||||
"gatewaySettings.sendCodeToPair": {
|
||||
"defaultMessage": "ペアリングするには、このコードを {gatewayType} ボットに送信してください。"
|
||||
},
|
||||
"gatewaySettings.start": {
|
||||
"defaultMessage": "開始"
|
||||
},
|
||||
"gatewaySettings.stop": {
|
||||
"defaultMessage": "停止"
|
||||
},
|
||||
"gatewaySettings.stopped": {
|
||||
"defaultMessage": "停止中"
|
||||
},
|
||||
"gatewaySettings.telegram": {
|
||||
"defaultMessage": "Telegram"
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "閉じる"
|
||||
},
|
||||
|
||||
@@ -1229,66 +1229,6 @@
|
||||
"freeOptionCards.unexpectedError": {
|
||||
"defaultMessage": "설정 중에 예상치 못한 오류가 발생했습니다."
|
||||
},
|
||||
"gatewaySettings.botFatherInstructions": {
|
||||
"defaultMessage": "휴대폰에서 @BotFather를 열고 /newbot을 보낸 뒤, 안내에 따라 봇 이름을 지정하세요. BotFather가 API 토큰을 보내면 아래에 붙여넣으세요."
|
||||
},
|
||||
"gatewaySettings.close": {
|
||||
"defaultMessage": "닫기"
|
||||
},
|
||||
"gatewaySettings.expiresIn": {
|
||||
"defaultMessage": "{time} 후 만료"
|
||||
},
|
||||
"gatewaySettings.failedToGeneratePairingCode": {
|
||||
"defaultMessage": "페어링 코드를 생성하지 못했습니다."
|
||||
},
|
||||
"gatewaySettings.failedToRemove": {
|
||||
"defaultMessage": "제거하지 못했습니다."
|
||||
},
|
||||
"gatewaySettings.failedToStart": {
|
||||
"defaultMessage": "시작하지 못했습니다."
|
||||
},
|
||||
"gatewaySettings.failedToStop": {
|
||||
"defaultMessage": "중지하지 못했습니다."
|
||||
},
|
||||
"gatewaySettings.failedToUnpairUser": {
|
||||
"defaultMessage": "사용자 페어링을 해제하지 못했습니다."
|
||||
},
|
||||
"gatewaySettings.loading": {
|
||||
"defaultMessage": "로드 중..."
|
||||
},
|
||||
"gatewaySettings.pairDevice": {
|
||||
"defaultMessage": "기기 페어링"
|
||||
},
|
||||
"gatewaySettings.pairedUsers": {
|
||||
"defaultMessage": "페어링된 사용자"
|
||||
},
|
||||
"gatewaySettings.pairingCode": {
|
||||
"defaultMessage": "페어링 코드"
|
||||
},
|
||||
"gatewaySettings.pasteBotToken": {
|
||||
"defaultMessage": "봇 토큰을 여기에 붙여넣으세요"
|
||||
},
|
||||
"gatewaySettings.remove": {
|
||||
"defaultMessage": "제거"
|
||||
},
|
||||
"gatewaySettings.running": {
|
||||
"defaultMessage": "실행 중"
|
||||
},
|
||||
"gatewaySettings.sendCodeToPair": {
|
||||
"defaultMessage": "페어링하려면 이 코드를 {gatewayType} 봇에 보내세요."
|
||||
},
|
||||
"gatewaySettings.start": {
|
||||
"defaultMessage": "시작"
|
||||
},
|
||||
"gatewaySettings.stop": {
|
||||
"defaultMessage": "중지"
|
||||
},
|
||||
"gatewaySettings.stopped": {
|
||||
"defaultMessage": "중지됨"
|
||||
},
|
||||
"gatewaySettings.telegram": {
|
||||
"defaultMessage": "Telegram"
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "닫기"
|
||||
},
|
||||
|
||||
@@ -1229,66 +1229,6 @@
|
||||
"freeOptionCards.unexpectedError": {
|
||||
"defaultMessage": "Во время настройки произошла непредвиденная ошибка."
|
||||
},
|
||||
"gatewaySettings.botFatherInstructions": {
|
||||
"defaultMessage": "Откройте @BotFather на телефоне, отправьте /newbot и следуйте подсказкам, чтобы назвать бота. BotFather ответит API-токеном — вставьте его ниже."
|
||||
},
|
||||
"gatewaySettings.close": {
|
||||
"defaultMessage": "Закрыть"
|
||||
},
|
||||
"gatewaySettings.expiresIn": {
|
||||
"defaultMessage": "Истекает через {time}"
|
||||
},
|
||||
"gatewaySettings.failedToGeneratePairingCode": {
|
||||
"defaultMessage": "Не удалось создать код подключения"
|
||||
},
|
||||
"gatewaySettings.failedToRemove": {
|
||||
"defaultMessage": "Не удалось удалить"
|
||||
},
|
||||
"gatewaySettings.failedToStart": {
|
||||
"defaultMessage": "Не удалось запустить"
|
||||
},
|
||||
"gatewaySettings.failedToStop": {
|
||||
"defaultMessage": "Не удалось остановить"
|
||||
},
|
||||
"gatewaySettings.failedToUnpairUser": {
|
||||
"defaultMessage": "Не удалось отменить подключение пользователя"
|
||||
},
|
||||
"gatewaySettings.loading": {
|
||||
"defaultMessage": "Загрузка..."
|
||||
},
|
||||
"gatewaySettings.pairDevice": {
|
||||
"defaultMessage": "Подключить устройство"
|
||||
},
|
||||
"gatewaySettings.pairedUsers": {
|
||||
"defaultMessage": "Подключенные пользователи"
|
||||
},
|
||||
"gatewaySettings.pairingCode": {
|
||||
"defaultMessage": "Код подключения"
|
||||
},
|
||||
"gatewaySettings.pasteBotToken": {
|
||||
"defaultMessage": "Вставьте токен бота здесь"
|
||||
},
|
||||
"gatewaySettings.remove": {
|
||||
"defaultMessage": "Удалить"
|
||||
},
|
||||
"gatewaySettings.running": {
|
||||
"defaultMessage": "Выполняется"
|
||||
},
|
||||
"gatewaySettings.sendCodeToPair": {
|
||||
"defaultMessage": "Отправьте этот код вашему боту {gatewayType} для подключения."
|
||||
},
|
||||
"gatewaySettings.start": {
|
||||
"defaultMessage": "Запустить"
|
||||
},
|
||||
"gatewaySettings.stop": {
|
||||
"defaultMessage": "Остановить"
|
||||
},
|
||||
"gatewaySettings.stopped": {
|
||||
"defaultMessage": "Остановлено"
|
||||
},
|
||||
"gatewaySettings.telegram": {
|
||||
"defaultMessage": "Telegram"
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "Закрыть"
|
||||
},
|
||||
|
||||
@@ -1229,66 +1229,6 @@
|
||||
"freeOptionCards.unexpectedError": {
|
||||
"defaultMessage": "Kurulum sırasında beklenmeyen bir hata oluştu."
|
||||
},
|
||||
"gatewaySettings.botFatherInstructions": {
|
||||
"defaultMessage": "Telefonunuzda @BotFather'ı açın, /newbot gönderin ve botunuza isim vermek için talimatları izleyin. BotFather bir API jetonuyla yanıt verecektir; bunu aşağıya yapıştırın."
|
||||
},
|
||||
"gatewaySettings.close": {
|
||||
"defaultMessage": "Kapat"
|
||||
},
|
||||
"gatewaySettings.expiresIn": {
|
||||
"defaultMessage": "Süresi {time}'de doluyor"
|
||||
},
|
||||
"gatewaySettings.failedToGeneratePairingCode": {
|
||||
"defaultMessage": "Eşleştirme kodu oluşturulamadı"
|
||||
},
|
||||
"gatewaySettings.failedToRemove": {
|
||||
"defaultMessage": "Kaldırılamadı"
|
||||
},
|
||||
"gatewaySettings.failedToStart": {
|
||||
"defaultMessage": "Başlatılamadı"
|
||||
},
|
||||
"gatewaySettings.failedToStop": {
|
||||
"defaultMessage": "Durdurulamadı"
|
||||
},
|
||||
"gatewaySettings.failedToUnpairUser": {
|
||||
"defaultMessage": "Kullanıcının eşlemesi kaldırılamadı"
|
||||
},
|
||||
"gatewaySettings.loading": {
|
||||
"defaultMessage": "Yükleniyor..."
|
||||
},
|
||||
"gatewaySettings.pairDevice": {
|
||||
"defaultMessage": "Cihazı Eşleştir"
|
||||
},
|
||||
"gatewaySettings.pairedUsers": {
|
||||
"defaultMessage": "Eşlenen Kullanıcılar"
|
||||
},
|
||||
"gatewaySettings.pairingCode": {
|
||||
"defaultMessage": "Eşleştirme Kodu"
|
||||
},
|
||||
"gatewaySettings.pasteBotToken": {
|
||||
"defaultMessage": "Bot jetonunu buraya yapıştırın"
|
||||
},
|
||||
"gatewaySettings.remove": {
|
||||
"defaultMessage": "Kaldır"
|
||||
},
|
||||
"gatewaySettings.running": {
|
||||
"defaultMessage": "Koşu"
|
||||
},
|
||||
"gatewaySettings.sendCodeToPair": {
|
||||
"defaultMessage": "Eşleştirmek için bu kodu {gatewayType} botunuza gönderin."
|
||||
},
|
||||
"gatewaySettings.start": {
|
||||
"defaultMessage": "Başlat"
|
||||
},
|
||||
"gatewaySettings.stop": {
|
||||
"defaultMessage": "Durdur"
|
||||
},
|
||||
"gatewaySettings.stopped": {
|
||||
"defaultMessage": "Durduruldu"
|
||||
},
|
||||
"gatewaySettings.telegram": {
|
||||
"defaultMessage": "Telgraf"
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "Kapat"
|
||||
},
|
||||
|
||||
@@ -1229,66 +1229,6 @@
|
||||
"freeOptionCards.unexpectedError": {
|
||||
"defaultMessage": "设置过程中发生意外错误。"
|
||||
},
|
||||
"gatewaySettings.botFatherInstructions": {
|
||||
"defaultMessage": "在手机上打开 @BotFather,发送 /newbot,按提示为你的 bot 命名。BotFather 会回复一个 API token —— 把它粘贴到下方。"
|
||||
},
|
||||
"gatewaySettings.close": {
|
||||
"defaultMessage": "关闭"
|
||||
},
|
||||
"gatewaySettings.expiresIn": {
|
||||
"defaultMessage": "{time} 后过期"
|
||||
},
|
||||
"gatewaySettings.failedToGeneratePairingCode": {
|
||||
"defaultMessage": "生成配对码失败"
|
||||
},
|
||||
"gatewaySettings.failedToRemove": {
|
||||
"defaultMessage": "移除失败"
|
||||
},
|
||||
"gatewaySettings.failedToStart": {
|
||||
"defaultMessage": "启动失败"
|
||||
},
|
||||
"gatewaySettings.failedToStop": {
|
||||
"defaultMessage": "停止失败"
|
||||
},
|
||||
"gatewaySettings.failedToUnpairUser": {
|
||||
"defaultMessage": "解除用户配对失败"
|
||||
},
|
||||
"gatewaySettings.loading": {
|
||||
"defaultMessage": "加载中…"
|
||||
},
|
||||
"gatewaySettings.pairDevice": {
|
||||
"defaultMessage": "配对设备"
|
||||
},
|
||||
"gatewaySettings.pairedUsers": {
|
||||
"defaultMessage": "已配对用户"
|
||||
},
|
||||
"gatewaySettings.pairingCode": {
|
||||
"defaultMessage": "配对码"
|
||||
},
|
||||
"gatewaySettings.pasteBotToken": {
|
||||
"defaultMessage": "在此粘贴 bot token"
|
||||
},
|
||||
"gatewaySettings.remove": {
|
||||
"defaultMessage": "移除"
|
||||
},
|
||||
"gatewaySettings.running": {
|
||||
"defaultMessage": "运行中"
|
||||
},
|
||||
"gatewaySettings.sendCodeToPair": {
|
||||
"defaultMessage": "把此码发送给你的 {gatewayType} bot 以配对。"
|
||||
},
|
||||
"gatewaySettings.start": {
|
||||
"defaultMessage": "启动"
|
||||
},
|
||||
"gatewaySettings.stop": {
|
||||
"defaultMessage": "停止"
|
||||
},
|
||||
"gatewaySettings.stopped": {
|
||||
"defaultMessage": "已停止"
|
||||
},
|
||||
"gatewaySettings.telegram": {
|
||||
"defaultMessage": "Telegram"
|
||||
},
|
||||
"goosehintsModal.close": {
|
||||
"defaultMessage": "关闭"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user