支持渠道、指令(自定义前缀)和事件(启用/禁用)管理
This commit is contained in:
@@ -5,7 +5,10 @@ use tokio::task::JoinHandle;
|
|||||||
use super::command_handlers;
|
use super::command_handlers;
|
||||||
use super::manager::ChatChannelManager;
|
use super::manager::ChatChannelManager;
|
||||||
use super::types::IncomingCommand;
|
use super::types::IncomingCommand;
|
||||||
use crate::db::service::chat_channel_message_log_service;
|
use crate::db::service::{app_metadata_service, chat_channel_message_log_service};
|
||||||
|
|
||||||
|
const COMMAND_PREFIX_KEY: &str = "chat_command_prefix";
|
||||||
|
const DEFAULT_COMMAND_PREFIX: &str = "/";
|
||||||
|
|
||||||
pub fn spawn_command_dispatcher(
|
pub fn spawn_command_dispatcher(
|
||||||
mut command_rx: mpsc::Receiver<IncomingCommand>,
|
mut command_rx: mpsc::Receiver<IncomingCommand>,
|
||||||
@@ -28,7 +31,13 @@ pub fn spawn_command_dispatcher(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let response = dispatch_command(text, &db_conn, &manager).await;
|
let prefix = app_metadata_service::get_value(&db_conn, COMMAND_PREFIX_KEY)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_else(|| DEFAULT_COMMAND_PREFIX.to_string());
|
||||||
|
|
||||||
|
let response = dispatch_command(text, &prefix, &db_conn, &manager).await;
|
||||||
|
|
||||||
// Send response back via the same channel
|
// Send response back via the same channel
|
||||||
let send_result = manager.send_to_channel(cmd.channel_id, &response).await;
|
let send_result = manager.send_to_channel(cmd.channel_id, &response).await;
|
||||||
@@ -59,45 +68,47 @@ pub fn spawn_command_dispatcher(
|
|||||||
|
|
||||||
async fn dispatch_command(
|
async fn dispatch_command(
|
||||||
text: &str,
|
text: &str,
|
||||||
|
prefix: &str,
|
||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
manager: &ChatChannelManager,
|
manager: &ChatChannelManager,
|
||||||
) -> super::types::RichMessage {
|
) -> super::types::RichMessage {
|
||||||
let parts: Vec<&str> = text.splitn(2, ' ').collect();
|
// Check if text starts with the configured prefix
|
||||||
|
if !text.starts_with(prefix) {
|
||||||
|
return command_handlers::handle_help(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip prefix and parse command + args
|
||||||
|
let without_prefix = &text[prefix.len()..];
|
||||||
|
let parts: Vec<&str> = without_prefix.splitn(2, ' ').collect();
|
||||||
let command = parts[0].to_lowercase();
|
let command = parts[0].to_lowercase();
|
||||||
let args = parts.get(1).map(|s| s.trim()).unwrap_or("");
|
let args = parts.get(1).map(|s| s.trim()).unwrap_or("");
|
||||||
|
|
||||||
match command.as_str() {
|
match command.as_str() {
|
||||||
"/recent" => command_handlers::handle_recent(db).await,
|
"recent" => command_handlers::handle_recent(db).await,
|
||||||
"/search" => {
|
"search" => {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
super::types::RichMessage::info("用法: /search <关键词>")
|
super::types::RichMessage::info(format!("用法: {prefix}search <关键词>"))
|
||||||
.with_title("参数错误")
|
.with_title("参数错误")
|
||||||
} else {
|
} else {
|
||||||
command_handlers::handle_search(db, args).await
|
command_handlers::handle_search(db, args).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"/detail" => {
|
"detail" => {
|
||||||
if let Ok(id) = args.parse::<i32>() {
|
if let Ok(id) = args.parse::<i32>() {
|
||||||
command_handlers::handle_detail(db, id).await
|
command_handlers::handle_detail(db, id).await
|
||||||
} else {
|
} else {
|
||||||
super::types::RichMessage::info("用法: /detail <会话ID>")
|
super::types::RichMessage::info(format!("用法: {prefix}detail <会话ID>"))
|
||||||
.with_title("参数错误")
|
.with_title("参数错误")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"/today" => command_handlers::handle_today(db).await,
|
"today" => command_handlers::handle_today(db).await,
|
||||||
"/status" => command_handlers::handle_status(manager).await,
|
"status" => command_handlers::handle_status(manager).await,
|
||||||
"/help" | "/start" => command_handlers::handle_help(),
|
"help" | "start" => command_handlers::handle_help(prefix),
|
||||||
_ => {
|
_ => {
|
||||||
if text.starts_with('/') {
|
super::types::RichMessage::info(format!(
|
||||||
super::types::RichMessage::info(format!(
|
"未知命令: {prefix}{command}\n输入 {prefix}help 查看可用命令",
|
||||||
"未知命令: {}\n输入 /help 查看可用命令",
|
))
|
||||||
command
|
.with_title("未知命令")
|
||||||
))
|
|
||||||
.with_title("未知命令")
|
|
||||||
} else {
|
|
||||||
// Non-command messages are ignored
|
|
||||||
return command_handlers::handle_help();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,14 +205,14 @@ pub async fn handle_status(manager: &ChatChannelManager) -> RichMessage {
|
|||||||
RichMessage::info(body.trim_end()).with_title("渠道状态")
|
RichMessage::info(body.trim_end()).with_title("渠道状态")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_help() -> RichMessage {
|
pub fn handle_help(prefix: &str) -> RichMessage {
|
||||||
RichMessage::info(
|
RichMessage::info(format!(
|
||||||
"/recent - 最近 5 条会话\n\
|
"{prefix}recent - 最近 5 条会话\n\
|
||||||
/search <关键词> - 搜索会话\n\
|
{prefix}search <关键词> - 搜索会话\n\
|
||||||
/detail <ID> - 会话详情\n\
|
{prefix}detail <ID> - 会话详情\n\
|
||||||
/today - 今日活动汇总\n\
|
{prefix}today - 今日活动汇总\n\
|
||||||
/status - 渠道连接状态\n\
|
{prefix}status - 渠道连接状态\n\
|
||||||
/help - 显示帮助",
|
{prefix}help - 显示帮助",
|
||||||
)
|
))
|
||||||
.with_title("Codeg Bot 帮助")
|
.with_title("Codeg Bot 帮助")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use tokio::task::JoinHandle;
|
|||||||
use super::manager::ChatChannelManager;
|
use super::manager::ChatChannelManager;
|
||||||
use super::message_formatter;
|
use super::message_formatter;
|
||||||
use super::types::RichMessage;
|
use super::types::RichMessage;
|
||||||
use crate::db::service::{chat_channel_message_log_service, chat_channel_service};
|
use crate::db::service::{app_metadata_service, chat_channel_message_log_service, chat_channel_service};
|
||||||
use crate::web::event_bridge::WebEventBroadcaster;
|
use crate::web::event_bridge::WebEventBroadcaster;
|
||||||
|
|
||||||
/// Minimum interval between pushes for the same event type per channel (debounce).
|
/// Minimum interval between pushes for the same event type per channel (debounce).
|
||||||
@@ -38,6 +38,19 @@ pub fn spawn_event_subscriber(
|
|||||||
|
|
||||||
let message = match parse_event(&event.channel, &event.payload) {
|
let message = match parse_event(&event.channel, &event.payload) {
|
||||||
Some((event_type, msg)) => {
|
Some((event_type, msg)) => {
|
||||||
|
// Global event filter check
|
||||||
|
let global_filter = app_metadata_service::get_value(&db_conn, "chat_event_filter")
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|json| serde_json::from_str::<Vec<String>>(&json).ok());
|
||||||
|
|
||||||
|
if let Some(filter) = &global_filter {
|
||||||
|
if !filter.contains(&event_type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check enabled channels and forward
|
// Check enabled channels and forward
|
||||||
let channels = match chat_channel_service::list_enabled(&db_conn).await {
|
let channels = match chat_channel_service::list_enabled(&db_conn).await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
@@ -48,17 +61,6 @@ pub fn spawn_event_subscriber(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for ch in &channels {
|
for ch in &channels {
|
||||||
// Check event filter
|
|
||||||
if let Some(filter_json) = &ch.event_filter_json {
|
|
||||||
if let Ok(filter) =
|
|
||||||
serde_json::from_str::<Vec<String>>(filter_json)
|
|
||||||
{
|
|
||||||
if !filter.contains(&event_type) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debounce
|
// Debounce
|
||||||
let key = (ch.id, event_type.clone());
|
let key = (ch.id, event_type.clone());
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|||||||
@@ -264,6 +264,85 @@ pub async fn list_chat_channel_messages_core(
|
|||||||
Ok(rows.into_iter().map(ChatChannelMessageLogInfo::from).collect())
|
Ok(rows.into_iter().map(ChatChannelMessageLogInfo::from).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const COMMAND_PREFIX_KEY: &str = "chat_command_prefix";
|
||||||
|
const DEFAULT_COMMAND_PREFIX: &str = "/";
|
||||||
|
|
||||||
|
pub async fn get_chat_command_prefix_core(
|
||||||
|
db: &AppDatabase,
|
||||||
|
) -> Result<String, AppCommandError> {
|
||||||
|
let val = crate::db::service::app_metadata_service::get_value(&db.conn, COMMAND_PREFIX_KEY)
|
||||||
|
.await
|
||||||
|
.map_err(AppCommandError::from)?;
|
||||||
|
Ok(val.unwrap_or_else(|| DEFAULT_COMMAND_PREFIX.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_chat_command_prefix_core(
|
||||||
|
db: &AppDatabase,
|
||||||
|
prefix: String,
|
||||||
|
) -> Result<(), AppCommandError> {
|
||||||
|
let trimmed = prefix.trim();
|
||||||
|
if trimmed.is_empty()
|
||||||
|
|| trimmed.len() > 3
|
||||||
|
|| trimmed.chars().any(|c| c.is_alphanumeric())
|
||||||
|
{
|
||||||
|
return Err(AppCommandError::invalid_input(
|
||||||
|
"Prefix must be 1-3 non-alphanumeric characters",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
crate::db::service::app_metadata_service::upsert_value(&db.conn, COMMAND_PREFIX_KEY, trimmed)
|
||||||
|
.await
|
||||||
|
.map_err(AppCommandError::from)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVENT_FILTER_KEY: &str = "chat_event_filter";
|
||||||
|
|
||||||
|
pub async fn get_chat_event_filter_core(
|
||||||
|
db: &AppDatabase,
|
||||||
|
) -> Result<Option<Vec<String>>, AppCommandError> {
|
||||||
|
let val = crate::db::service::app_metadata_service::get_value(&db.conn, EVENT_FILTER_KEY)
|
||||||
|
.await
|
||||||
|
.map_err(AppCommandError::from)?;
|
||||||
|
match val {
|
||||||
|
Some(json) => {
|
||||||
|
let arr: Vec<String> =
|
||||||
|
serde_json::from_str(&json).map_err(|e| AppCommandError::invalid_input(e.to_string()))?;
|
||||||
|
Ok(Some(arr))
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_chat_event_filter_core(
|
||||||
|
db: &AppDatabase,
|
||||||
|
filter: Option<Vec<String>>,
|
||||||
|
) -> Result<(), AppCommandError> {
|
||||||
|
match filter {
|
||||||
|
Some(arr) => {
|
||||||
|
let json = serde_json::to_string(&arr)
|
||||||
|
.map_err(|e| AppCommandError::invalid_input(e.to_string()))?;
|
||||||
|
crate::db::service::app_metadata_service::upsert_value(
|
||||||
|
&db.conn,
|
||||||
|
EVENT_FILTER_KEY,
|
||||||
|
&json,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(AppCommandError::from)?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// null means all events enabled — remove the key
|
||||||
|
crate::db::service::app_metadata_service::upsert_value(
|
||||||
|
&db.conn,
|
||||||
|
EVENT_FILTER_KEY,
|
||||||
|
"null",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(AppCommandError::from)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Tauri commands (use tauri::State for injection)
|
// Tauri commands (use tauri::State for injection)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -378,3 +457,37 @@ pub async fn list_chat_channel_messages(
|
|||||||
) -> Result<Vec<ChatChannelMessageLogInfo>, AppCommandError> {
|
) -> Result<Vec<ChatChannelMessageLogInfo>, AppCommandError> {
|
||||||
list_chat_channel_messages_core(&db, channel_id, limit, offset).await
|
list_chat_channel_messages_core(&db, channel_id, limit, offset).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tauri-runtime")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_chat_command_prefix(
|
||||||
|
db: tauri::State<'_, AppDatabase>,
|
||||||
|
) -> Result<String, AppCommandError> {
|
||||||
|
get_chat_command_prefix_core(&db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tauri-runtime")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_chat_command_prefix(
|
||||||
|
db: tauri::State<'_, AppDatabase>,
|
||||||
|
prefix: String,
|
||||||
|
) -> Result<(), AppCommandError> {
|
||||||
|
set_chat_command_prefix_core(&db, prefix).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tauri-runtime")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_chat_event_filter(
|
||||||
|
db: tauri::State<'_, AppDatabase>,
|
||||||
|
) -> Result<Option<Vec<String>>, AppCommandError> {
|
||||||
|
get_chat_event_filter_core(&db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tauri-runtime")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_chat_event_filter(
|
||||||
|
db: tauri::State<'_, AppDatabase>,
|
||||||
|
filter: Option<Vec<String>>,
|
||||||
|
) -> Result<(), AppCommandError> {
|
||||||
|
set_chat_event_filter_core(&db, filter).await
|
||||||
|
}
|
||||||
|
|||||||
@@ -379,6 +379,10 @@ mod tauri_app {
|
|||||||
chat_channel_commands::test_chat_channel,
|
chat_channel_commands::test_chat_channel,
|
||||||
chat_channel_commands::get_chat_channel_status,
|
chat_channel_commands::get_chat_channel_status,
|
||||||
chat_channel_commands::list_chat_channel_messages,
|
chat_channel_commands::list_chat_channel_messages,
|
||||||
|
chat_channel_commands::get_chat_command_prefix,
|
||||||
|
chat_channel_commands::set_chat_command_prefix,
|
||||||
|
chat_channel_commands::get_chat_event_filter,
|
||||||
|
chat_channel_commands::set_chat_event_filter,
|
||||||
web::start_web_server,
|
web::start_web_server,
|
||||||
web::stop_web_server,
|
web::stop_web_server,
|
||||||
web::get_web_server_status,
|
web::get_web_server_status,
|
||||||
|
|||||||
@@ -183,3 +183,45 @@ pub async fn list_chat_channel_messages(
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SetCommandPrefixParams {
|
||||||
|
pub prefix: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_chat_command_prefix(
|
||||||
|
Extension(state): Extension<Arc<AppState>>,
|
||||||
|
) -> Result<Json<String>, AppCommandError> {
|
||||||
|
let result = cc_commands::get_chat_command_prefix_core(&state.db).await?;
|
||||||
|
Ok(Json(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_chat_command_prefix(
|
||||||
|
Extension(state): Extension<Arc<AppState>>,
|
||||||
|
Json(params): Json<SetCommandPrefixParams>,
|
||||||
|
) -> Result<Json<()>, AppCommandError> {
|
||||||
|
cc_commands::set_chat_command_prefix_core(&state.db, params.prefix).await?;
|
||||||
|
Ok(Json(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SetEventFilterParams {
|
||||||
|
pub filter: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_chat_event_filter(
|
||||||
|
Extension(state): Extension<Arc<AppState>>,
|
||||||
|
) -> Result<Json<Option<Vec<String>>>, AppCommandError> {
|
||||||
|
let result = cc_commands::get_chat_event_filter_core(&state.db).await?;
|
||||||
|
Ok(Json(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_chat_event_filter(
|
||||||
|
Extension(state): Extension<Arc<AppState>>,
|
||||||
|
Json(params): Json<SetEventFilterParams>,
|
||||||
|
) -> Result<Json<()>, AppCommandError> {
|
||||||
|
cc_commands::set_chat_event_filter_core(&state.db, params.filter).await?;
|
||||||
|
Ok(Json(()))
|
||||||
|
}
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ pub fn build_router(state: Arc<AppState>, token: String, static_dir: std::path::
|
|||||||
.route("/test_chat_channel", post(handlers::chat_channel::test_chat_channel))
|
.route("/test_chat_channel", post(handlers::chat_channel::test_chat_channel))
|
||||||
.route("/get_chat_channel_status", post(handlers::chat_channel::get_chat_channel_status))
|
.route("/get_chat_channel_status", post(handlers::chat_channel::get_chat_channel_status))
|
||||||
.route("/list_chat_channel_messages", post(handlers::chat_channel::list_chat_channel_messages))
|
.route("/list_chat_channel_messages", post(handlers::chat_channel::list_chat_channel_messages))
|
||||||
|
.route("/get_chat_command_prefix", post(handlers::chat_channel::get_chat_command_prefix))
|
||||||
|
.route("/set_chat_command_prefix", post(handlers::chat_channel::set_chat_command_prefix))
|
||||||
|
.route("/get_chat_event_filter", post(handlers::chat_channel::get_chat_event_filter))
|
||||||
|
.route("/set_chat_event_filter", post(handlers::chat_channel::set_chat_event_filter))
|
||||||
// ─── Terminal ───
|
// ─── Terminal ───
|
||||||
.route("/terminal_spawn", post(handlers::terminal::terminal_spawn))
|
.route("/terminal_spawn", post(handlers::terminal::terminal_spawn))
|
||||||
.route("/terminal_write", post(handlers::terminal::terminal_write))
|
.route("/terminal_write", post(handlers::terminal::terminal_write))
|
||||||
|
|||||||
121
src/components/settings/channel-commands-tab.tsx
Normal file
121
src/components/settings/channel-commands-tab.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
import { Loader2, Save } from "lucide-react"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { getChatCommandPrefix, setChatCommandPrefix } from "@/lib/api"
|
||||||
|
|
||||||
|
const BUILT_IN_COMMANDS = [
|
||||||
|
{ name: "recent", descKey: "recentDesc" },
|
||||||
|
{ name: "search <keyword>", descKey: "searchDesc" },
|
||||||
|
{ name: "detail <id>", descKey: "detailDesc" },
|
||||||
|
{ name: "today", descKey: "todayDesc" },
|
||||||
|
{ name: "status", descKey: "statusDesc" },
|
||||||
|
{ name: "help", descKey: "helpDesc" },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export function ChannelCommandsTab() {
|
||||||
|
const t = useTranslations("ChatChannelSettings.commands")
|
||||||
|
const [prefix, setPrefix] = useState("/")
|
||||||
|
const [inputPrefix, setInputPrefix] = useState("/")
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getChatCommandPrefix()
|
||||||
|
.then((p) => {
|
||||||
|
setPrefix(p)
|
||||||
|
setInputPrefix(p)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => setLoading(false))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSavePrefix = useCallback(async () => {
|
||||||
|
const trimmed = inputPrefix.trim()
|
||||||
|
if (
|
||||||
|
trimmed.length === 0 ||
|
||||||
|
trimmed.length > 3 ||
|
||||||
|
/[a-zA-Z0-9]/.test(trimmed)
|
||||||
|
) {
|
||||||
|
toast.error(t("prefixInvalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSaving(true)
|
||||||
|
try {
|
||||||
|
await setChatCommandPrefix(trimmed)
|
||||||
|
setPrefix(trimmed)
|
||||||
|
toast.success(t("prefixSaved"))
|
||||||
|
} catch {
|
||||||
|
toast.error(t("prefixSaveFailed"))
|
||||||
|
} finally {
|
||||||
|
setSaving(false)
|
||||||
|
}
|
||||||
|
}, [inputPrefix, t])
|
||||||
|
|
||||||
|
const dirty = inputPrefix !== prefix
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex items-center justify-center text-sm text-muted-foreground gap-2">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<section className="space-y-2">
|
||||||
|
<h3 className="text-sm font-medium">{t("prefixLabel")}</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t("prefixDescription")}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
value={inputPrefix}
|
||||||
|
onChange={(e) => setInputPrefix(e.target.value)}
|
||||||
|
className="w-20 text-center font-mono"
|
||||||
|
maxLength={3}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
disabled={!dirty || saving}
|
||||||
|
onClick={handleSavePrefix}
|
||||||
|
>
|
||||||
|
{saving ? (
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Save className="h-3.5 w-3.5 mr-1" />
|
||||||
|
)}
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="space-y-2">
|
||||||
|
<h3 className="text-sm font-medium">{t("title")}</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">{t("description")}</p>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{BUILT_IN_COMMANDS.map((cmd) => (
|
||||||
|
<div
|
||||||
|
key={cmd.name}
|
||||||
|
className="flex items-center justify-between rounded-lg border bg-card px-4 py-3"
|
||||||
|
>
|
||||||
|
<code className="text-sm font-mono">
|
||||||
|
{prefix}
|
||||||
|
{cmd.name}
|
||||||
|
</code>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{t(cmd.descKey)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
115
src/components/settings/channel-events-tab.tsx
Normal file
115
src/components/settings/channel-events-tab.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
import { Loader2 } from "lucide-react"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
import { getChatEventFilter, setChatEventFilter } from "@/lib/api"
|
||||||
|
|
||||||
|
const ALL_EVENT_TYPES = [
|
||||||
|
{
|
||||||
|
id: "session_started",
|
||||||
|
labelKey: "sessionStarted",
|
||||||
|
descKey: "sessionStartedDesc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "turn_complete",
|
||||||
|
labelKey: "turnComplete",
|
||||||
|
descKey: "turnCompleteDesc",
|
||||||
|
},
|
||||||
|
{ id: "error", labelKey: "error", descKey: "errorDesc" },
|
||||||
|
{
|
||||||
|
id: "status_disconnected",
|
||||||
|
labelKey: "statusDisconnected",
|
||||||
|
descKey: "statusDisconnectedDesc",
|
||||||
|
},
|
||||||
|
{ id: "git_push", labelKey: "gitPush", descKey: "gitPushDesc" },
|
||||||
|
{ id: "git_commit", labelKey: "gitCommit", descKey: "gitCommitDesc" },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const ALL_IDS = ALL_EVENT_TYPES.map((e) => e.id)
|
||||||
|
|
||||||
|
function parseFilter(arr: string[] | null): Set<string> {
|
||||||
|
if (!arr) return new Set(ALL_IDS)
|
||||||
|
return new Set(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ChannelEventsTab() {
|
||||||
|
const t = useTranslations("ChatChannelSettings.events")
|
||||||
|
const [enabledEvents, setEnabledEvents] = useState<Set<string>>(
|
||||||
|
new Set(ALL_IDS)
|
||||||
|
)
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getChatEventFilter()
|
||||||
|
.then((arr) => setEnabledEvents(parseFilter(arr)))
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => setLoading(false))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const allEnabled = enabledEvents.size === ALL_EVENT_TYPES.length
|
||||||
|
|
||||||
|
const handleToggle = useCallback(
|
||||||
|
async (eventId: string, checked: boolean) => {
|
||||||
|
setSaving(true)
|
||||||
|
try {
|
||||||
|
const next = new Set(enabledEvents)
|
||||||
|
if (checked) {
|
||||||
|
next.add(eventId)
|
||||||
|
} else {
|
||||||
|
next.delete(eventId)
|
||||||
|
}
|
||||||
|
const isAll = next.size === ALL_EVENT_TYPES.length
|
||||||
|
await setChatEventFilter(isAll ? null : [...next])
|
||||||
|
setEnabledEvents(next)
|
||||||
|
toast.success(t("saved"))
|
||||||
|
} catch {
|
||||||
|
toast.error(t("saveFailed"))
|
||||||
|
} finally {
|
||||||
|
setSaving(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[enabledEvents, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex items-center justify-center text-sm text-muted-foreground gap-2">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{allEnabled && (
|
||||||
|
<p className="text-xs text-muted-foreground">{t("allEnabled")}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<section className="space-y-1">
|
||||||
|
{ALL_EVENT_TYPES.map((evt) => (
|
||||||
|
<div
|
||||||
|
key={evt.id}
|
||||||
|
className="flex items-center justify-between rounded-lg border bg-card px-4 py-3"
|
||||||
|
>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="text-sm font-medium">{t(evt.labelKey)}</div>
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
{t(evt.descKey)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={enabledEvents.has(evt.id)}
|
||||||
|
disabled={saving}
|
||||||
|
onCheckedChange={(checked) => handleToggle(evt.id, checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
323
src/components/settings/channel-list-tab.tsx
Normal file
323
src/components/settings/channel-list-tab.tsx
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
import {
|
||||||
|
Loader2,
|
||||||
|
MessageCircle,
|
||||||
|
Pencil,
|
||||||
|
Plus,
|
||||||
|
Power,
|
||||||
|
PowerOff,
|
||||||
|
Trash2,
|
||||||
|
Zap,
|
||||||
|
} from "lucide-react"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "@/components/ui/alert-dialog"
|
||||||
|
import {
|
||||||
|
listChatChannels,
|
||||||
|
deleteChatChannel,
|
||||||
|
connectChatChannel,
|
||||||
|
disconnectChatChannel,
|
||||||
|
testChatChannel,
|
||||||
|
updateChatChannel,
|
||||||
|
getChatChannelStatus,
|
||||||
|
} from "@/lib/api"
|
||||||
|
import type { ChatChannelInfo, ChannelStatusInfo } from "@/lib/types"
|
||||||
|
import { AddChatChannelDialog } from "./add-chat-channel-dialog"
|
||||||
|
import { EditChatChannelDialog } from "./edit-chat-channel-dialog"
|
||||||
|
|
||||||
|
export function ChannelListTab() {
|
||||||
|
const t = useTranslations("ChatChannelSettings")
|
||||||
|
const [channels, setChannels] = useState<ChatChannelInfo[]>([])
|
||||||
|
const [statuses, setStatuses] = useState<ChannelStatusInfo[]>([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [addDialogOpen, setAddDialogOpen] = useState(false)
|
||||||
|
const [editTarget, setEditTarget] = useState<ChatChannelInfo | null>(null)
|
||||||
|
const [deleteTarget, setDeleteTarget] = useState<ChatChannelInfo | null>(null)
|
||||||
|
const [actionLoading, setActionLoading] = useState<number | null>(null)
|
||||||
|
|
||||||
|
const loadChannels = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const [chs, sts] = await Promise.all([
|
||||||
|
listChatChannels(),
|
||||||
|
getChatChannelStatus().catch(() => []),
|
||||||
|
])
|
||||||
|
setChannels(chs)
|
||||||
|
setStatuses(sts)
|
||||||
|
} catch {
|
||||||
|
toast.error(t("loadFailed"))
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [t])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadChannels().catch(console.error)
|
||||||
|
}, [loadChannels])
|
||||||
|
|
||||||
|
const handleToggleEnabled = useCallback(
|
||||||
|
async (ch: ChatChannelInfo, connected: boolean) => {
|
||||||
|
try {
|
||||||
|
const disabling = ch.enabled
|
||||||
|
if (disabling && connected) {
|
||||||
|
await disconnectChatChannel(ch.id)
|
||||||
|
}
|
||||||
|
await updateChatChannel({ id: ch.id, enabled: !ch.enabled })
|
||||||
|
await loadChannels()
|
||||||
|
} catch {
|
||||||
|
toast.error(t("saveFailed"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loadChannels, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleConnect = useCallback(
|
||||||
|
async (id: number) => {
|
||||||
|
setActionLoading(id)
|
||||||
|
try {
|
||||||
|
await connectChatChannel(id)
|
||||||
|
toast.success(t("connectSuccess"))
|
||||||
|
await loadChannels()
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err)
|
||||||
|
toast.error(t("connectFailed") + ": " + msg)
|
||||||
|
} finally {
|
||||||
|
setActionLoading(null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loadChannels, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleDisconnect = useCallback(
|
||||||
|
async (id: number) => {
|
||||||
|
setActionLoading(id)
|
||||||
|
try {
|
||||||
|
await disconnectChatChannel(id)
|
||||||
|
toast.success(t("disconnectSuccess"))
|
||||||
|
await loadChannels()
|
||||||
|
} catch {
|
||||||
|
toast.error(t("disconnectFailed"))
|
||||||
|
} finally {
|
||||||
|
setActionLoading(null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loadChannels, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleTest = useCallback(
|
||||||
|
async (id: number) => {
|
||||||
|
setActionLoading(id)
|
||||||
|
try {
|
||||||
|
await testChatChannel(id)
|
||||||
|
toast.success(t("testSuccess"))
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err)
|
||||||
|
toast.error(t("testFailed") + ": " + msg)
|
||||||
|
} finally {
|
||||||
|
setActionLoading(null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[t]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleDelete = useCallback(async () => {
|
||||||
|
if (!deleteTarget) return
|
||||||
|
try {
|
||||||
|
await deleteChatChannel(deleteTarget.id)
|
||||||
|
toast.success(t("deleteSuccess"))
|
||||||
|
setDeleteTarget(null)
|
||||||
|
await loadChannels()
|
||||||
|
} catch {
|
||||||
|
toast.error(t("deleteFailed"))
|
||||||
|
}
|
||||||
|
}, [deleteTarget, loadChannels, t])
|
||||||
|
|
||||||
|
const getChannelStatus = (id: number) =>
|
||||||
|
statuses.find((s) => s.channel_id === id)?.status ?? "disconnected"
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex items-center justify-center text-sm text-muted-foreground gap-2">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
{t("loading")}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-medium">{t("channelListTitle")}</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t("channelListDescription")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button size="sm" onClick={() => setAddDialogOpen(true)}>
|
||||||
|
<Plus className="h-3.5 w-3.5 mr-1" />
|
||||||
|
{t("addChannel")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{channels.length === 0 ? (
|
||||||
|
<section className="rounded-xl border bg-card p-8 text-center">
|
||||||
|
<MessageCircle className="h-8 w-8 mx-auto text-muted-foreground mb-2" />
|
||||||
|
<p className="text-sm text-muted-foreground">{t("noChannels")}</p>
|
||||||
|
</section>
|
||||||
|
) : (
|
||||||
|
<section className="space-y-2">
|
||||||
|
{channels.map((ch) => {
|
||||||
|
const status = getChannelStatus(ch.id)
|
||||||
|
const isConnected = status === "connected"
|
||||||
|
const isLoading = actionLoading === ch.id
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={ch.id}
|
||||||
|
className="rounded-xl border bg-card p-4 flex items-center gap-4"
|
||||||
|
>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium">{ch.name}</span>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
{ch.channel_type}
|
||||||
|
</Badge>
|
||||||
|
<span
|
||||||
|
className={`inline-block h-2 w-2 rounded-full ${
|
||||||
|
isConnected
|
||||||
|
? "bg-green-500"
|
||||||
|
: status === "connecting"
|
||||||
|
? "bg-yellow-500 animate-pulse"
|
||||||
|
: status === "error"
|
||||||
|
? "bg-red-500"
|
||||||
|
: "bg-gray-400"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 mt-1">
|
||||||
|
{ch.daily_report_enabled && (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{t("dailyReport")}: {ch.daily_report_time || "18:00"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Switch
|
||||||
|
checked={ch.enabled}
|
||||||
|
onCheckedChange={() => handleToggleEnabled(ch, isConnected)}
|
||||||
|
/>
|
||||||
|
{isConnected ? (
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
title={t("disconnect")}
|
||||||
|
disabled={isLoading}
|
||||||
|
onClick={() => handleDisconnect(ch.id)}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<PowerOff className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
title={t("connect")}
|
||||||
|
disabled={isLoading || !ch.enabled}
|
||||||
|
onClick={() => handleConnect(ch.id)}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Power className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
title={t("test")}
|
||||||
|
disabled={isLoading}
|
||||||
|
onClick={() => handleTest(ch.id)}
|
||||||
|
>
|
||||||
|
<Zap className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
title={t("editChannel")}
|
||||||
|
disabled={isConnected || isLoading}
|
||||||
|
onClick={() => setEditTarget(ch)}
|
||||||
|
>
|
||||||
|
<Pencil className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
title={t("delete")}
|
||||||
|
onClick={() => setDeleteTarget(ch)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<AddChatChannelDialog
|
||||||
|
open={addDialogOpen}
|
||||||
|
onOpenChange={setAddDialogOpen}
|
||||||
|
onChannelAdded={loadChannels}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{editTarget && (
|
||||||
|
<EditChatChannelDialog
|
||||||
|
open={!!editTarget}
|
||||||
|
channel={editTarget}
|
||||||
|
onOpenChange={(open) => !open && setEditTarget(null)}
|
||||||
|
onChannelUpdated={loadChannels}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
open={!!deleteTarget}
|
||||||
|
onOpenChange={(open) => !open && setDeleteTarget(null)}
|
||||||
|
>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{t("deleteConfirmTitle")}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{t("deleteConfirmMessage")}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={handleDelete}>
|
||||||
|
{t("delete")}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,300 +1,42 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react"
|
|
||||||
import {
|
|
||||||
Loader2,
|
|
||||||
MessageCircle,
|
|
||||||
Plus,
|
|
||||||
Power,
|
|
||||||
PowerOff,
|
|
||||||
TestTube,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react"
|
|
||||||
import { useTranslations } from "next-intl"
|
import { useTranslations } from "next-intl"
|
||||||
import { toast } from "sonner"
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { ChannelListTab } from "./channel-list-tab"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { ChannelCommandsTab } from "./channel-commands-tab"
|
||||||
import {
|
import { ChannelEventsTab } from "./channel-events-tab"
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
} from "@/components/ui/alert-dialog"
|
|
||||||
import {
|
|
||||||
listChatChannels,
|
|
||||||
deleteChatChannel,
|
|
||||||
connectChatChannel,
|
|
||||||
disconnectChatChannel,
|
|
||||||
testChatChannel,
|
|
||||||
updateChatChannel,
|
|
||||||
getChatChannelStatus,
|
|
||||||
} from "@/lib/api"
|
|
||||||
import type { ChatChannelInfo, ChannelStatusInfo } from "@/lib/types"
|
|
||||||
import { AddChatChannelDialog } from "./add-chat-channel-dialog"
|
|
||||||
|
|
||||||
export function ChatChannelSettings() {
|
export function ChatChannelSettings() {
|
||||||
const t = useTranslations("ChatChannelSettings")
|
const t = useTranslations("ChatChannelSettings")
|
||||||
const [channels, setChannels] = useState<ChatChannelInfo[]>([])
|
|
||||||
const [statuses, setStatuses] = useState<ChannelStatusInfo[]>([])
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [addDialogOpen, setAddDialogOpen] = useState(false)
|
|
||||||
const [deleteTarget, setDeleteTarget] = useState<ChatChannelInfo | null>(null)
|
|
||||||
const [actionLoading, setActionLoading] = useState<number | null>(null)
|
|
||||||
|
|
||||||
const loadChannels = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const [chs, sts] = await Promise.all([
|
|
||||||
listChatChannels(),
|
|
||||||
getChatChannelStatus().catch(() => []),
|
|
||||||
])
|
|
||||||
setChannels(chs)
|
|
||||||
setStatuses(sts)
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(t("loadFailed"))
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}, [t])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadChannels().catch(console.error)
|
|
||||||
}, [loadChannels])
|
|
||||||
|
|
||||||
const handleToggleEnabled = useCallback(
|
|
||||||
async (ch: ChatChannelInfo) => {
|
|
||||||
try {
|
|
||||||
await updateChatChannel({ id: ch.id, enabled: !ch.enabled })
|
|
||||||
await loadChannels()
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(t("saveFailed"))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[loadChannels, t],
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleConnect = useCallback(
|
|
||||||
async (id: number) => {
|
|
||||||
setActionLoading(id)
|
|
||||||
try {
|
|
||||||
await connectChatChannel(id)
|
|
||||||
toast.success(t("connectSuccess"))
|
|
||||||
await loadChannels()
|
|
||||||
} catch (err) {
|
|
||||||
const msg = err instanceof Error ? err.message : String(err)
|
|
||||||
toast.error(t("connectFailed") + ": " + msg)
|
|
||||||
} finally {
|
|
||||||
setActionLoading(null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[loadChannels, t],
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleDisconnect = useCallback(
|
|
||||||
async (id: number) => {
|
|
||||||
setActionLoading(id)
|
|
||||||
try {
|
|
||||||
await disconnectChatChannel(id)
|
|
||||||
toast.success(t("disconnectSuccess"))
|
|
||||||
await loadChannels()
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(t("disconnectFailed"))
|
|
||||||
} finally {
|
|
||||||
setActionLoading(null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[loadChannels, t],
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleTest = useCallback(
|
|
||||||
async (id: number) => {
|
|
||||||
setActionLoading(id)
|
|
||||||
try {
|
|
||||||
await testChatChannel(id)
|
|
||||||
toast.success(t("testSuccess"))
|
|
||||||
} catch (err) {
|
|
||||||
const msg = err instanceof Error ? err.message : String(err)
|
|
||||||
toast.error(t("testFailed") + ": " + msg)
|
|
||||||
} finally {
|
|
||||||
setActionLoading(null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[t],
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleDelete = useCallback(async () => {
|
|
||||||
if (!deleteTarget) return
|
|
||||||
try {
|
|
||||||
await deleteChatChannel(deleteTarget.id)
|
|
||||||
toast.success(t("deleteSuccess"))
|
|
||||||
setDeleteTarget(null)
|
|
||||||
await loadChannels()
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(t("deleteFailed"))
|
|
||||||
}
|
|
||||||
}, [deleteTarget, loadChannels, t])
|
|
||||||
|
|
||||||
const getChannelStatus = (id: number) =>
|
|
||||||
statuses.find((s) => s.channel_id === id)?.status ?? "disconnected"
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="h-full flex items-center justify-center text-sm text-muted-foreground gap-2">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
|
||||||
{t("loading")}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full overflow-auto">
|
<div className="h-full overflow-auto">
|
||||||
<div className="w-full space-y-4">
|
<Tabs defaultValue="channels" className="w-full space-y-4">
|
||||||
<section className="space-y-1">
|
<section className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div>
|
||||||
<div>
|
<h1 className="text-sm font-semibold">{t("sectionTitle")}</h1>
|
||||||
<h1 className="text-sm font-semibold">{t("sectionTitle")}</h1>
|
<p className="text-sm text-muted-foreground">
|
||||||
<p className="text-sm text-muted-foreground">
|
{t("sectionDescription")}
|
||||||
{t("sectionDescription")}
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button size="sm" onClick={() => setAddDialogOpen(true)}>
|
|
||||||
<Plus className="h-3.5 w-3.5 mr-1" />
|
|
||||||
{t("addChannel")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="channels">{t("tabs.channels")}</TabsTrigger>
|
||||||
|
<TabsTrigger value="commands">{t("tabs.commands")}</TabsTrigger>
|
||||||
|
<TabsTrigger value="events">{t("tabs.events")}</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{channels.length === 0 ? (
|
<TabsContent value="channels" className="mt-0">
|
||||||
<section className="rounded-xl border bg-card p-8 text-center">
|
<ChannelListTab />
|
||||||
<MessageCircle className="h-8 w-8 mx-auto text-muted-foreground mb-2" />
|
</TabsContent>
|
||||||
<p className="text-sm text-muted-foreground">
|
<TabsContent value="commands" className="mt-0">
|
||||||
{t("noChannels")}
|
<ChannelCommandsTab />
|
||||||
</p>
|
</TabsContent>
|
||||||
</section>
|
<TabsContent value="events" className="mt-0">
|
||||||
) : (
|
<ChannelEventsTab />
|
||||||
<section className="space-y-2">
|
</TabsContent>
|
||||||
{channels.map((ch) => {
|
</Tabs>
|
||||||
const status = getChannelStatus(ch.id)
|
|
||||||
const isConnected = status === "connected"
|
|
||||||
const isLoading = actionLoading === ch.id
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={ch.id}
|
|
||||||
className="rounded-xl border bg-card p-4 flex items-center gap-4"
|
|
||||||
>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm font-medium">{ch.name}</span>
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
{ch.channel_type}
|
|
||||||
</Badge>
|
|
||||||
<span
|
|
||||||
className={`inline-block h-2 w-2 rounded-full ${
|
|
||||||
isConnected
|
|
||||||
? "bg-green-500"
|
|
||||||
: status === "connecting"
|
|
||||||
? "bg-yellow-500 animate-pulse"
|
|
||||||
: status === "error"
|
|
||||||
? "bg-red-500"
|
|
||||||
: "bg-gray-400"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-3 mt-1">
|
|
||||||
{ch.daily_report_enabled && (
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{t("dailyReport")}: {ch.daily_report_time || "18:00"}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Switch
|
|
||||||
checked={ch.enabled}
|
|
||||||
onCheckedChange={() => handleToggleEnabled(ch)}
|
|
||||||
/>
|
|
||||||
{isConnected ? (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
disabled={isLoading}
|
|
||||||
onClick={() => handleDisconnect(ch.id)}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<PowerOff className="h-3.5 w-3.5" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
disabled={isLoading || !ch.enabled}
|
|
||||||
onClick={() => handleConnect(ch.id)}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Power className="h-3.5 w-3.5" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
disabled={isLoading}
|
|
||||||
onClick={() => handleTest(ch.id)}
|
|
||||||
>
|
|
||||||
<TestTube className="h-3.5 w-3.5" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setDeleteTarget(ch)}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AddChatChannelDialog
|
|
||||||
open={addDialogOpen}
|
|
||||||
onOpenChange={setAddDialogOpen}
|
|
||||||
onChannelAdded={loadChannels}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AlertDialog
|
|
||||||
open={!!deleteTarget}
|
|
||||||
onOpenChange={(open) => !open && setDeleteTarget(null)}
|
|
||||||
>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>{t("deleteConfirmTitle")}</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
{t("deleteConfirmMessage")}
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
|
||||||
<AlertDialogAction onClick={handleDelete}>
|
|
||||||
{t("delete")}
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
214
src/components/settings/edit-chat-channel-dialog.tsx
Normal file
214
src/components/settings/edit-chat-channel-dialog.tsx
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
import { Loader2 } from "lucide-react"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
import {
|
||||||
|
updateChatChannel,
|
||||||
|
saveChatChannelToken,
|
||||||
|
getChatChannelHasToken,
|
||||||
|
} from "@/lib/api"
|
||||||
|
import type { ChatChannelInfo } from "@/lib/types"
|
||||||
|
|
||||||
|
interface EditChatChannelDialogProps {
|
||||||
|
open: boolean
|
||||||
|
channel: ChatChannelInfo
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
|
onChannelUpdated: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditChatChannelDialog({
|
||||||
|
open,
|
||||||
|
channel,
|
||||||
|
onOpenChange,
|
||||||
|
onChannelUpdated,
|
||||||
|
}: EditChatChannelDialogProps) {
|
||||||
|
const t = useTranslations("ChatChannelSettings")
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const config = JSON.parse(channel.config_json || "{}")
|
||||||
|
const [name, setName] = useState(channel.name)
|
||||||
|
const [token, setToken] = useState("")
|
||||||
|
const [chatId, setChatId] = useState(config.chat_id ?? "")
|
||||||
|
const [appId, setAppId] = useState(config.app_id ?? "")
|
||||||
|
const [dailyReportEnabled, setDailyReportEnabled] = useState(
|
||||||
|
channel.daily_report_enabled
|
||||||
|
)
|
||||||
|
const [dailyReportTime, setDailyReportTime] = useState(
|
||||||
|
channel.daily_report_time || "18:00"
|
||||||
|
)
|
||||||
|
const [hasToken, setHasToken] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
getChatChannelHasToken(channel.id)
|
||||||
|
.then(setHasToken)
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
}, [open, channel.id])
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(async () => {
|
||||||
|
if (!name.trim()) {
|
||||||
|
setError(t("nameRequired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!chatId.trim()) {
|
||||||
|
setError(t("chatIdRequired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
const configJson =
|
||||||
|
channel.channel_type === "lark"
|
||||||
|
? JSON.stringify({ app_id: appId, chat_id: chatId })
|
||||||
|
: JSON.stringify({ chat_id: chatId })
|
||||||
|
|
||||||
|
await updateChatChannel({
|
||||||
|
id: channel.id,
|
||||||
|
name: name.trim(),
|
||||||
|
configJson,
|
||||||
|
dailyReportEnabled,
|
||||||
|
dailyReportTime: dailyReportEnabled ? dailyReportTime : null,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (token.trim()) {
|
||||||
|
await saveChatChannelToken(channel.id, token.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenChange(false)
|
||||||
|
onChannelUpdated()
|
||||||
|
toast.success(t("editSuccess"))
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err)
|
||||||
|
setError(msg)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
name,
|
||||||
|
token,
|
||||||
|
chatId,
|
||||||
|
channel,
|
||||||
|
appId,
|
||||||
|
dailyReportEnabled,
|
||||||
|
dailyReportTime,
|
||||||
|
onOpenChange,
|
||||||
|
onChannelUpdated,
|
||||||
|
t,
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t("editChannel")}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium">{t("channelName")}</label>
|
||||||
|
<Input
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder={t("channelNamePlaceholder")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{channel.channel_type === "lark" && (
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium">App ID</label>
|
||||||
|
<Input
|
||||||
|
value={appId}
|
||||||
|
onChange={(e) => setAppId(e.target.value)}
|
||||||
|
placeholder="cli_xxxxx"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium">
|
||||||
|
{channel.channel_type === "telegram" ? "Bot Token" : "App Secret"}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={token}
|
||||||
|
onChange={(e) => setToken(e.target.value)}
|
||||||
|
placeholder={
|
||||||
|
hasToken ? t("tokenPlaceholderKeep") : t("tokenRequired")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium">Chat ID</label>
|
||||||
|
<Input
|
||||||
|
value={chatId}
|
||||||
|
onChange={(e) => setChatId(e.target.value)}
|
||||||
|
placeholder={
|
||||||
|
channel.channel_type === "telegram"
|
||||||
|
? "-100123456789"
|
||||||
|
: "oc_xxxxx"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="text-xs font-medium">{t("dailyReport")}</label>
|
||||||
|
<Switch
|
||||||
|
checked={dailyReportEnabled}
|
||||||
|
onCheckedChange={setDailyReportEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{dailyReportEnabled && (
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium">
|
||||||
|
{t("dailyReportTime")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="time"
|
||||||
|
value={dailyReportTime}
|
||||||
|
onChange={(e) => setDailyReportTime(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="rounded-md border border-red-500/30 bg-red-500/5 px-3 py-2 text-xs text-red-400">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onOpenChange(false)}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSubmit} disabled={loading}>
|
||||||
|
{loading && <Loader2 className="h-3.5 w-3.5 animate-spin mr-1" />}
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
GitBranch,
|
GitBranch,
|
||||||
Globe,
|
Globe,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
MessageCircle,
|
BotMessageSquare,
|
||||||
Palette,
|
Palette,
|
||||||
PlugZap,
|
PlugZap,
|
||||||
Settings,
|
Settings,
|
||||||
@@ -74,7 +74,7 @@ const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [
|
|||||||
{
|
{
|
||||||
href: "/settings/chat-channels",
|
href: "/settings/chat-channels",
|
||||||
labelKey: "chat_channels",
|
labelKey: "chat_channels",
|
||||||
icon: MessageCircle,
|
icon: BotMessageSquare,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/settings/web-service",
|
href: "/settings/web-service",
|
||||||
|
|||||||
@@ -1670,5 +1670,85 @@
|
|||||||
"emptyDirectory": "هذا المجلد فارغ",
|
"emptyDirectory": "هذا المجلد فارغ",
|
||||||
"errorLoadingDir": "فشل في تحميل المجلد",
|
"errorLoadingDir": "فشل في تحميل المجلد",
|
||||||
"permissionDenied": "تم رفض الإذن"
|
"permissionDenied": "تم رفض الإذن"
|
||||||
|
},
|
||||||
|
"ChatChannelSettings": {
|
||||||
|
"loading": "جاري التحميل...",
|
||||||
|
"sectionTitle": "قنوات المحادثة",
|
||||||
|
"sectionDescription": "تكوين بوتات المراسلة لتلقي إشعارات الأحداث واستعلام نشاط البرمجة.",
|
||||||
|
"addChannel": "إضافة قناة",
|
||||||
|
"noChannels": "لم يتم تكوين أي قنوات محادثة بعد.",
|
||||||
|
"channelName": "الاسم",
|
||||||
|
"channelNamePlaceholder": "بوت Telegram الخاص بي",
|
||||||
|
"channelType": "نوع القناة",
|
||||||
|
"lark": "Lark (Feishu)",
|
||||||
|
"dailyReport": "التقرير اليومي",
|
||||||
|
"dailyReportTime": "وقت التقرير",
|
||||||
|
"nameRequired": "اسم القناة مطلوب.",
|
||||||
|
"tokenRequired": "الرمز مطلوب.",
|
||||||
|
"chatIdRequired": "معرف المحادثة مطلوب.",
|
||||||
|
"loadFailed": "فشل في تحميل القنوات.",
|
||||||
|
"saveFailed": "فشل في حفظ التغييرات.",
|
||||||
|
"connectSuccess": "تم توصيل القناة.",
|
||||||
|
"connectFailed": "فشل الاتصال",
|
||||||
|
"disconnectSuccess": "تم قطع اتصال القناة.",
|
||||||
|
"disconnectFailed": "فشل قطع الاتصال.",
|
||||||
|
"testSuccess": "نجح اختبار الاتصال.",
|
||||||
|
"testFailed": "فشل اختبار الاتصال",
|
||||||
|
"deleteSuccess": "تم حذف القناة.",
|
||||||
|
"deleteFailed": "فشل في حذف القناة.",
|
||||||
|
"deleteConfirmTitle": "حذف القناة",
|
||||||
|
"deleteConfirmMessage": "سيتم حذف القناة وسجلات رسائلها نهائياً. هل أنت متأكد؟",
|
||||||
|
"cancel": "إلغاء",
|
||||||
|
"delete": "حذف",
|
||||||
|
"create": "إنشاء",
|
||||||
|
"save": "حفظ",
|
||||||
|
"channelListTitle": "القنوات المُعدة",
|
||||||
|
"channelListDescription": "القنوات المفعّلة ستتصل تلقائيًا عند بدء تشغيل الخدمة.",
|
||||||
|
"editChannel": "تعديل القناة",
|
||||||
|
"editSuccess": "تم تحديث القناة.",
|
||||||
|
"tokenPlaceholderKeep": "اتركه فارغاً للاحتفاظ بالقيمة الحالية",
|
||||||
|
"connect": "اتصال",
|
||||||
|
"disconnect": "قطع الاتصال",
|
||||||
|
"test": "اختبار الاتصال",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "القنوات",
|
||||||
|
"commands": "الأوامر",
|
||||||
|
"events": "الأحداث"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "الأوامر المدمجة",
|
||||||
|
"description": "أوامر البوت المتاحة في قنوات المحادثة.",
|
||||||
|
"prefixLabel": "بادئة الأمر",
|
||||||
|
"prefixDescription": "1-3 أحرف غير أبجدية رقمية لتشغيل أوامر البوت (الافتراضي /).",
|
||||||
|
"prefixSaved": "تم حفظ بادئة الأمر.",
|
||||||
|
"prefixSaveFailed": "فشل حفظ بادئة الأمر.",
|
||||||
|
"prefixInvalid": "يجب أن تكون البادئة 1-3 أحرف غير أبجدية رقمية.",
|
||||||
|
"save": "حفظ",
|
||||||
|
"recentDesc": "عرض آخر 5 محادثات",
|
||||||
|
"searchDesc": "البحث في المحادثات حسب الكلمة المفتاحية",
|
||||||
|
"detailDesc": "عرض تفاصيل المحادثة",
|
||||||
|
"todayDesc": "ملخص نشاط اليوم",
|
||||||
|
"statusDesc": "حالة اتصال القناة",
|
||||||
|
"helpDesc": "عرض المساعدة"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "إشعارات الأحداث",
|
||||||
|
"description": "تكوين الأحداث التي يتم إرسالها لكل قناة.",
|
||||||
|
"allEnabled": "جميع الأحداث مفعلة (الافتراضي)",
|
||||||
|
"sessionStarted": "بدء الجلسة",
|
||||||
|
"sessionStartedDesc": "عندما يبدأ وكيل الذكاء الاصطناعي جلسة جديدة",
|
||||||
|
"turnComplete": "اكتمال الدور",
|
||||||
|
"turnCompleteDesc": "عند انتهاء دور الوكيل",
|
||||||
|
"error": "خطأ الوكيل",
|
||||||
|
"errorDesc": "عندما يواجه الوكيل خطأ",
|
||||||
|
"statusDisconnected": "انقطاع الوكيل",
|
||||||
|
"statusDisconnectedDesc": "عند فقدان اتصال الوكيل",
|
||||||
|
"gitPush": "Git Push",
|
||||||
|
"gitPushDesc": "عند نجاح عملية Git Push",
|
||||||
|
"gitCommit": "Git Commit",
|
||||||
|
"gitCommitDesc": "عند نجاح عملية Git Commit",
|
||||||
|
"saved": "تم تحديث فلتر الأحداث.",
|
||||||
|
"saveFailed": "فشل حفظ فلتر الأحداث."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1670,5 +1670,85 @@
|
|||||||
"emptyDirectory": "Dieses Verzeichnis ist leer",
|
"emptyDirectory": "Dieses Verzeichnis ist leer",
|
||||||
"errorLoadingDir": "Verzeichnis konnte nicht geladen werden",
|
"errorLoadingDir": "Verzeichnis konnte nicht geladen werden",
|
||||||
"permissionDenied": "Zugriff verweigert"
|
"permissionDenied": "Zugriff verweigert"
|
||||||
|
},
|
||||||
|
"ChatChannelSettings": {
|
||||||
|
"loading": "Wird geladen...",
|
||||||
|
"sectionTitle": "Chat-Kanäle",
|
||||||
|
"sectionDescription": "Konfigurieren Sie IM-Bots, um Ereignisbenachrichtigungen zu empfangen und Codieraktivitäten abzufragen.",
|
||||||
|
"addChannel": "Kanal hinzufügen",
|
||||||
|
"noChannels": "Noch keine Chat-Kanäle konfiguriert.",
|
||||||
|
"channelName": "Name",
|
||||||
|
"channelNamePlaceholder": "Mein Telegram Bot",
|
||||||
|
"channelType": "Kanaltyp",
|
||||||
|
"lark": "Lark (Feishu)",
|
||||||
|
"dailyReport": "Tagesbericht",
|
||||||
|
"dailyReportTime": "Berichtszeit",
|
||||||
|
"nameRequired": "Kanalname ist erforderlich.",
|
||||||
|
"tokenRequired": "Token ist erforderlich.",
|
||||||
|
"chatIdRequired": "Chat-ID ist erforderlich.",
|
||||||
|
"loadFailed": "Kanäle konnten nicht geladen werden.",
|
||||||
|
"saveFailed": "Änderungen konnten nicht gespeichert werden.",
|
||||||
|
"connectSuccess": "Kanal verbunden.",
|
||||||
|
"connectFailed": "Verbindung fehlgeschlagen",
|
||||||
|
"disconnectSuccess": "Kanal getrennt.",
|
||||||
|
"disconnectFailed": "Trennung fehlgeschlagen.",
|
||||||
|
"testSuccess": "Verbindungstest bestanden.",
|
||||||
|
"testFailed": "Verbindungstest fehlgeschlagen",
|
||||||
|
"deleteSuccess": "Kanal gelöscht.",
|
||||||
|
"deleteFailed": "Kanal konnte nicht gelöscht werden.",
|
||||||
|
"deleteConfirmTitle": "Kanal löschen",
|
||||||
|
"deleteConfirmMessage": "Der Kanal und seine Nachrichtenprotokolle werden dauerhaft gelöscht. Sind Sie sicher?",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"create": "Erstellen",
|
||||||
|
"save": "Speichern",
|
||||||
|
"channelListTitle": "Konfigurierte Kanäle",
|
||||||
|
"channelListDescription": "Aktivierte Kanäle werden beim Dienststart automatisch verbunden.",
|
||||||
|
"editChannel": "Kanal bearbeiten",
|
||||||
|
"editSuccess": "Kanal aktualisiert.",
|
||||||
|
"tokenPlaceholderKeep": "Leer lassen, um aktuellen Wert beizubehalten",
|
||||||
|
"connect": "Verbinden",
|
||||||
|
"disconnect": "Trennen",
|
||||||
|
"test": "Verbindung testen",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "Kanäle",
|
||||||
|
"commands": "Befehle",
|
||||||
|
"events": "Ereignisse"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "Integrierte Befehle",
|
||||||
|
"description": "Im Chat-Kanal verfügbare Bot-Befehle.",
|
||||||
|
"prefixLabel": "Befehlspräfix",
|
||||||
|
"prefixDescription": "1-3 nicht-alphanumerische Zeichen zum Auslösen von Bot-Befehlen (Standard /).",
|
||||||
|
"prefixSaved": "Befehlspräfix gespeichert.",
|
||||||
|
"prefixSaveFailed": "Fehler beim Speichern des Präfixes.",
|
||||||
|
"prefixInvalid": "Das Präfix muss 1-3 nicht-alphanumerische Zeichen sein.",
|
||||||
|
"save": "Speichern",
|
||||||
|
"recentDesc": "Die 5 neuesten Konversationen anzeigen",
|
||||||
|
"searchDesc": "Konversationen nach Stichwort suchen",
|
||||||
|
"detailDesc": "Konversationsdetails anzeigen",
|
||||||
|
"todayDesc": "Heutige Aktivitätsübersicht",
|
||||||
|
"statusDesc": "Kanal-Verbindungsstatus",
|
||||||
|
"helpDesc": "Hilfe anzeigen"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "Ereignisbenachrichtigungen",
|
||||||
|
"description": "Konfigurieren Sie, welche Ereignisse an jeden Kanal gesendet werden.",
|
||||||
|
"allEnabled": "Alle Ereignisse aktiviert (Standard)",
|
||||||
|
"sessionStarted": "Sitzung gestartet",
|
||||||
|
"sessionStartedDesc": "Wenn ein KI-Agent eine neue Sitzung startet",
|
||||||
|
"turnComplete": "Runde abgeschlossen",
|
||||||
|
"turnCompleteDesc": "Wenn eine Agentenrunde endet",
|
||||||
|
"error": "Agentenfehler",
|
||||||
|
"errorDesc": "Wenn ein Agent einen Fehler feststellt",
|
||||||
|
"statusDisconnected": "Agent getrennt",
|
||||||
|
"statusDisconnectedDesc": "Wenn die Agentenverbindung verloren geht",
|
||||||
|
"gitPush": "Git Push",
|
||||||
|
"gitPushDesc": "Wenn ein Git Push erfolgreich ist",
|
||||||
|
"gitCommit": "Git Commit",
|
||||||
|
"gitCommitDesc": "Wenn ein Git Commit erfolgreich ist",
|
||||||
|
"saved": "Ereignisfilter aktualisiert.",
|
||||||
|
"saveFailed": "Fehler beim Speichern des Ereignisfilters."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1701,6 +1701,55 @@
|
|||||||
"deleteConfirmMessage": "This will permanently delete the channel and its message logs. Are you sure?",
|
"deleteConfirmMessage": "This will permanently delete the channel and its message logs. Are you sure?",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"create": "Create"
|
"create": "Create",
|
||||||
|
"save": "Save",
|
||||||
|
"channelListTitle": "Configured Channels",
|
||||||
|
"channelListDescription": "Enabled channels will auto-connect when the service starts.",
|
||||||
|
"editChannel": "Edit Channel",
|
||||||
|
"editSuccess": "Channel updated.",
|
||||||
|
"tokenPlaceholderKeep": "Leave blank to keep current",
|
||||||
|
"connect": "Connect",
|
||||||
|
"disconnect": "Disconnect",
|
||||||
|
"test": "Test Connection",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "Channels",
|
||||||
|
"commands": "Commands",
|
||||||
|
"events": "Events"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "Built-in Commands",
|
||||||
|
"description": "Bot commands available in chat channels.",
|
||||||
|
"prefixLabel": "Command Prefix",
|
||||||
|
"prefixDescription": "1-3 non-alphanumeric characters used to trigger bot commands (default /).",
|
||||||
|
"prefixSaved": "Command prefix saved.",
|
||||||
|
"prefixSaveFailed": "Failed to save command prefix.",
|
||||||
|
"prefixInvalid": "Prefix must be 1-3 non-alphanumeric characters.",
|
||||||
|
"save": "Save",
|
||||||
|
"recentDesc": "Show 5 most recent conversations",
|
||||||
|
"searchDesc": "Search conversations by keyword",
|
||||||
|
"detailDesc": "Show conversation details",
|
||||||
|
"todayDesc": "Today's activity summary",
|
||||||
|
"statusDesc": "Channel connection status",
|
||||||
|
"helpDesc": "Show help message"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "Event Notifications",
|
||||||
|
"description": "Configure which events are pushed to each channel.",
|
||||||
|
"allEnabled": "All events enabled (default)",
|
||||||
|
"sessionStarted": "Session Started",
|
||||||
|
"sessionStartedDesc": "When an AI agent session begins",
|
||||||
|
"turnComplete": "Turn Complete",
|
||||||
|
"turnCompleteDesc": "When an agent turn ends",
|
||||||
|
"error": "Agent Error",
|
||||||
|
"errorDesc": "When an agent encounters an error",
|
||||||
|
"statusDisconnected": "Agent Disconnected",
|
||||||
|
"statusDisconnectedDesc": "When agent connection is lost",
|
||||||
|
"gitPush": "Git Push",
|
||||||
|
"gitPushDesc": "When a git push succeeds",
|
||||||
|
"gitCommit": "Git Commit",
|
||||||
|
"gitCommitDesc": "When a git commit succeeds",
|
||||||
|
"saved": "Event filter updated.",
|
||||||
|
"saveFailed": "Failed to save event filter."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1670,5 +1670,85 @@
|
|||||||
"emptyDirectory": "Este directorio está vacío",
|
"emptyDirectory": "Este directorio está vacío",
|
||||||
"errorLoadingDir": "Error al cargar el directorio",
|
"errorLoadingDir": "Error al cargar el directorio",
|
||||||
"permissionDenied": "Permiso denegado"
|
"permissionDenied": "Permiso denegado"
|
||||||
|
},
|
||||||
|
"ChatChannelSettings": {
|
||||||
|
"loading": "Cargando...",
|
||||||
|
"sectionTitle": "Canales de chat",
|
||||||
|
"sectionDescription": "Configure bots de IM para recibir notificaciones de eventos y consultar actividad de codificación.",
|
||||||
|
"addChannel": "Agregar canal",
|
||||||
|
"noChannels": "Aún no se han configurado canales de chat.",
|
||||||
|
"channelName": "Nombre",
|
||||||
|
"channelNamePlaceholder": "Mi bot de Telegram",
|
||||||
|
"channelType": "Tipo de canal",
|
||||||
|
"lark": "Lark (Feishu)",
|
||||||
|
"dailyReport": "Informe diario",
|
||||||
|
"dailyReportTime": "Hora del informe",
|
||||||
|
"nameRequired": "El nombre del canal es obligatorio.",
|
||||||
|
"tokenRequired": "El token es obligatorio.",
|
||||||
|
"chatIdRequired": "El Chat ID es obligatorio.",
|
||||||
|
"loadFailed": "Error al cargar los canales.",
|
||||||
|
"saveFailed": "Error al guardar los cambios.",
|
||||||
|
"connectSuccess": "Canal conectado.",
|
||||||
|
"connectFailed": "Error al conectar",
|
||||||
|
"disconnectSuccess": "Canal desconectado.",
|
||||||
|
"disconnectFailed": "Error al desconectar.",
|
||||||
|
"testSuccess": "Prueba de conexión exitosa.",
|
||||||
|
"testFailed": "Prueba de conexión fallida",
|
||||||
|
"deleteSuccess": "Canal eliminado.",
|
||||||
|
"deleteFailed": "Error al eliminar el canal.",
|
||||||
|
"deleteConfirmTitle": "Eliminar canal",
|
||||||
|
"deleteConfirmMessage": "Se eliminará permanentemente el canal y sus registros de mensajes. ¿Está seguro?",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"create": "Crear",
|
||||||
|
"save": "Guardar",
|
||||||
|
"channelListTitle": "Canales configurados",
|
||||||
|
"channelListDescription": "Los canales habilitados se conectarán automáticamente al iniciar el servicio.",
|
||||||
|
"editChannel": "Editar canal",
|
||||||
|
"editSuccess": "Canal actualizado.",
|
||||||
|
"tokenPlaceholderKeep": "Dejar vacío para mantener actual",
|
||||||
|
"connect": "Conectar",
|
||||||
|
"disconnect": "Desconectar",
|
||||||
|
"test": "Probar conexión",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "Canales",
|
||||||
|
"commands": "Comandos",
|
||||||
|
"events": "Eventos"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "Comandos integrados",
|
||||||
|
"description": "Comandos de bot disponibles en los canales de chat.",
|
||||||
|
"prefixLabel": "Prefijo de comando",
|
||||||
|
"prefixDescription": "1-3 caracteres no alfanuméricos para activar comandos del bot (por defecto /).",
|
||||||
|
"prefixSaved": "Prefijo de comando guardado.",
|
||||||
|
"prefixSaveFailed": "Error al guardar el prefijo.",
|
||||||
|
"prefixInvalid": "El prefijo debe ser de 1-3 caracteres no alfanuméricos.",
|
||||||
|
"save": "Guardar",
|
||||||
|
"recentDesc": "Mostrar las 5 conversaciones más recientes",
|
||||||
|
"searchDesc": "Buscar conversaciones por palabra clave",
|
||||||
|
"detailDesc": "Mostrar detalles de la conversación",
|
||||||
|
"todayDesc": "Resumen de actividad de hoy",
|
||||||
|
"statusDesc": "Estado de conexión del canal",
|
||||||
|
"helpDesc": "Mostrar ayuda"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "Notificaciones de eventos",
|
||||||
|
"description": "Configura qué eventos se envían a cada canal.",
|
||||||
|
"allEnabled": "Todos los eventos habilitados (predeterminado)",
|
||||||
|
"sessionStarted": "Sesión iniciada",
|
||||||
|
"sessionStartedDesc": "Cuando un agente IA inicia una nueva sesión",
|
||||||
|
"turnComplete": "Turno completado",
|
||||||
|
"turnCompleteDesc": "Cuando finaliza un turno del agente",
|
||||||
|
"error": "Error del agente",
|
||||||
|
"errorDesc": "Cuando un agente encuentra un error",
|
||||||
|
"statusDisconnected": "Agente desconectado",
|
||||||
|
"statusDisconnectedDesc": "Cuando se pierde la conexión del agente",
|
||||||
|
"gitPush": "Git Push",
|
||||||
|
"gitPushDesc": "Cuando un git push tiene éxito",
|
||||||
|
"gitCommit": "Git Commit",
|
||||||
|
"gitCommitDesc": "Cuando un git commit tiene éxito",
|
||||||
|
"saved": "Filtro de eventos actualizado.",
|
||||||
|
"saveFailed": "Error al guardar el filtro de eventos."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1670,5 +1670,85 @@
|
|||||||
"emptyDirectory": "Ce répertoire est vide",
|
"emptyDirectory": "Ce répertoire est vide",
|
||||||
"errorLoadingDir": "Échec du chargement du répertoire",
|
"errorLoadingDir": "Échec du chargement du répertoire",
|
||||||
"permissionDenied": "Permission refusée"
|
"permissionDenied": "Permission refusée"
|
||||||
|
},
|
||||||
|
"ChatChannelSettings": {
|
||||||
|
"loading": "Chargement...",
|
||||||
|
"sectionTitle": "Canaux de chat",
|
||||||
|
"sectionDescription": "Configurez des bots IM pour recevoir des notifications d'événements et interroger l'activité de codage.",
|
||||||
|
"addChannel": "Ajouter un canal",
|
||||||
|
"noChannels": "Aucun canal de chat configuré pour le moment.",
|
||||||
|
"channelName": "Nom",
|
||||||
|
"channelNamePlaceholder": "Mon bot Telegram",
|
||||||
|
"channelType": "Type de canal",
|
||||||
|
"lark": "Lark (Feishu)",
|
||||||
|
"dailyReport": "Rapport quotidien",
|
||||||
|
"dailyReportTime": "Heure du rapport",
|
||||||
|
"nameRequired": "Le nom du canal est requis.",
|
||||||
|
"tokenRequired": "Le token est requis.",
|
||||||
|
"chatIdRequired": "Le Chat ID est requis.",
|
||||||
|
"loadFailed": "Échec du chargement des canaux.",
|
||||||
|
"saveFailed": "Échec de l'enregistrement.",
|
||||||
|
"connectSuccess": "Canal connecté.",
|
||||||
|
"connectFailed": "Échec de la connexion",
|
||||||
|
"disconnectSuccess": "Canal déconnecté.",
|
||||||
|
"disconnectFailed": "Échec de la déconnexion.",
|
||||||
|
"testSuccess": "Test de connexion réussi.",
|
||||||
|
"testFailed": "Test de connexion échoué",
|
||||||
|
"deleteSuccess": "Canal supprimé.",
|
||||||
|
"deleteFailed": "Échec de la suppression du canal.",
|
||||||
|
"deleteConfirmTitle": "Supprimer le canal",
|
||||||
|
"deleteConfirmMessage": "Le canal et ses journaux de messages seront définitivement supprimés. Êtes-vous sûr ?",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"delete": "Supprimer",
|
||||||
|
"create": "Créer",
|
||||||
|
"save": "Enregistrer",
|
||||||
|
"channelListTitle": "Canaux configurés",
|
||||||
|
"channelListDescription": "Les canaux activés se connectent automatiquement au démarrage du service.",
|
||||||
|
"editChannel": "Modifier le canal",
|
||||||
|
"editSuccess": "Canal mis à jour.",
|
||||||
|
"tokenPlaceholderKeep": "Laisser vide pour conserver l'actuel",
|
||||||
|
"connect": "Connecter",
|
||||||
|
"disconnect": "Déconnecter",
|
||||||
|
"test": "Tester la connexion",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "Canaux",
|
||||||
|
"commands": "Commandes",
|
||||||
|
"events": "Événements"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "Commandes intégrées",
|
||||||
|
"description": "Commandes bot disponibles dans les canaux de chat.",
|
||||||
|
"prefixLabel": "Préfixe de commande",
|
||||||
|
"prefixDescription": "1-3 caractères non alphanumériques pour déclencher les commandes du bot (par défaut /).",
|
||||||
|
"prefixSaved": "Préfixe de commande enregistré.",
|
||||||
|
"prefixSaveFailed": "Échec de l'enregistrement du préfixe.",
|
||||||
|
"prefixInvalid": "Le préfixe doit être de 1-3 caractères non alphanumériques.",
|
||||||
|
"save": "Enregistrer",
|
||||||
|
"recentDesc": "Afficher les 5 conversations les plus récentes",
|
||||||
|
"searchDesc": "Rechercher des conversations par mot-clé",
|
||||||
|
"detailDesc": "Afficher les détails de la conversation",
|
||||||
|
"todayDesc": "Résumé de l'activité du jour",
|
||||||
|
"statusDesc": "État de connexion du canal",
|
||||||
|
"helpDesc": "Afficher l'aide"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "Notifications d'événements",
|
||||||
|
"description": "Configurez les événements envoyés à chaque canal.",
|
||||||
|
"allEnabled": "Tous les événements activés (par défaut)",
|
||||||
|
"sessionStarted": "Session démarrée",
|
||||||
|
"sessionStartedDesc": "Lorsqu'un agent IA démarre une nouvelle session",
|
||||||
|
"turnComplete": "Tour terminé",
|
||||||
|
"turnCompleteDesc": "Lorsqu'un tour d'agent se termine",
|
||||||
|
"error": "Erreur de l'agent",
|
||||||
|
"errorDesc": "Lorsqu'un agent rencontre une erreur",
|
||||||
|
"statusDisconnected": "Agent déconnecté",
|
||||||
|
"statusDisconnectedDesc": "Lorsque la connexion de l'agent est perdue",
|
||||||
|
"gitPush": "Git Push",
|
||||||
|
"gitPushDesc": "Lorsqu'un git push réussit",
|
||||||
|
"gitCommit": "Git Commit",
|
||||||
|
"gitCommitDesc": "Lorsqu'un git commit réussit",
|
||||||
|
"saved": "Filtre d'événements mis à jour.",
|
||||||
|
"saveFailed": "Échec de l'enregistrement du filtre d'événements."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1670,5 +1670,85 @@
|
|||||||
"emptyDirectory": "このディレクトリは空です",
|
"emptyDirectory": "このディレクトリは空です",
|
||||||
"errorLoadingDir": "ディレクトリの読み込みに失敗しました",
|
"errorLoadingDir": "ディレクトリの読み込みに失敗しました",
|
||||||
"permissionDenied": "アクセス権がありません"
|
"permissionDenied": "アクセス権がありません"
|
||||||
|
},
|
||||||
|
"ChatChannelSettings": {
|
||||||
|
"loading": "読み込み中...",
|
||||||
|
"sectionTitle": "チャットチャンネル",
|
||||||
|
"sectionDescription": "IM ボットを設定して、イベント通知やコーディング活動の照会を行います。",
|
||||||
|
"addChannel": "チャンネルを追加",
|
||||||
|
"noChannels": "チャットチャンネルはまだ設定されていません。",
|
||||||
|
"channelName": "名前",
|
||||||
|
"channelNamePlaceholder": "My Telegram Bot",
|
||||||
|
"channelType": "チャンネルタイプ",
|
||||||
|
"lark": "Lark(飛書)",
|
||||||
|
"dailyReport": "デイリーレポート",
|
||||||
|
"dailyReportTime": "送信時刻",
|
||||||
|
"nameRequired": "チャンネル名を入力してください。",
|
||||||
|
"tokenRequired": "トークンを入力してください。",
|
||||||
|
"chatIdRequired": "Chat ID を入力してください。",
|
||||||
|
"loadFailed": "チャンネルの読み込みに失敗しました。",
|
||||||
|
"saveFailed": "保存に失敗しました。",
|
||||||
|
"connectSuccess": "チャンネルに接続しました。",
|
||||||
|
"connectFailed": "接続に失敗しました",
|
||||||
|
"disconnectSuccess": "チャンネルを切断しました。",
|
||||||
|
"disconnectFailed": "切断に失敗しました。",
|
||||||
|
"testSuccess": "接続テストに合格しました。",
|
||||||
|
"testFailed": "接続テストに失敗しました",
|
||||||
|
"deleteSuccess": "チャンネルを削除しました。",
|
||||||
|
"deleteFailed": "チャンネルの削除に失敗しました。",
|
||||||
|
"deleteConfirmTitle": "チャンネルを削除",
|
||||||
|
"deleteConfirmMessage": "このチャンネルとメッセージログを完全に削除します。よろしいですか?",
|
||||||
|
"cancel": "キャンセル",
|
||||||
|
"delete": "削除",
|
||||||
|
"create": "作成",
|
||||||
|
"save": "保存",
|
||||||
|
"channelListTitle": "設定済みチャンネル",
|
||||||
|
"channelListDescription": "有効なチャンネルはサービス起動時に自動接続されます。",
|
||||||
|
"editChannel": "チャンネルを編集",
|
||||||
|
"editSuccess": "チャンネルを更新しました。",
|
||||||
|
"tokenPlaceholderKeep": "空欄で現在の値を維持",
|
||||||
|
"connect": "接続",
|
||||||
|
"disconnect": "切断",
|
||||||
|
"test": "接続テスト",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "チャンネル",
|
||||||
|
"commands": "コマンド",
|
||||||
|
"events": "イベント"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "組み込みコマンド",
|
||||||
|
"description": "チャットチャンネルで使用可能な Bot コマンド。",
|
||||||
|
"prefixLabel": "コマンドプレフィックス",
|
||||||
|
"prefixDescription": "Bot コマンドを起動するプレフィックス、1-3 文字の英数字以外の文字(デフォルト /)。",
|
||||||
|
"prefixSaved": "コマンドプレフィックスを保存しました。",
|
||||||
|
"prefixSaveFailed": "コマンドプレフィックスの保存に失敗しました。",
|
||||||
|
"prefixInvalid": "プレフィックスは1-3文字の英数字以外の文字である必要があります。",
|
||||||
|
"save": "保存",
|
||||||
|
"recentDesc": "最近の会話5件を表示",
|
||||||
|
"searchDesc": "キーワードで会話を検索",
|
||||||
|
"detailDesc": "会話の詳細を表示",
|
||||||
|
"todayDesc": "本日のアクティビティ概要",
|
||||||
|
"statusDesc": "チャンネル接続状態",
|
||||||
|
"helpDesc": "ヘルプを表示"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "イベント通知",
|
||||||
|
"description": "各チャンネルにプッシュするイベントを設定します。",
|
||||||
|
"allEnabled": "すべてのイベントが有効(デフォルト)",
|
||||||
|
"sessionStarted": "セッション開始",
|
||||||
|
"sessionStartedDesc": "AI エージェントが新しいセッションを開始した時",
|
||||||
|
"turnComplete": "ターン完了",
|
||||||
|
"turnCompleteDesc": "エージェントのターンが終了した時",
|
||||||
|
"error": "エージェントエラー",
|
||||||
|
"errorDesc": "エージェントがエラーに遭遇した時",
|
||||||
|
"statusDisconnected": "エージェント切断",
|
||||||
|
"statusDisconnectedDesc": "エージェントの接続が切断された時",
|
||||||
|
"gitPush": "Git プッシュ",
|
||||||
|
"gitPushDesc": "Git Push が成功した時",
|
||||||
|
"gitCommit": "Git コミット",
|
||||||
|
"gitCommitDesc": "Git Commit が成功した時",
|
||||||
|
"saved": "イベントフィルターを更新しました。",
|
||||||
|
"saveFailed": "イベントフィルターの保存に失敗しました。"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1670,5 +1670,85 @@
|
|||||||
"emptyDirectory": "이 디렉토리는 비어 있습니다",
|
"emptyDirectory": "이 디렉토리는 비어 있습니다",
|
||||||
"errorLoadingDir": "디렉토리 로딩 실패",
|
"errorLoadingDir": "디렉토리 로딩 실패",
|
||||||
"permissionDenied": "권한이 없습니다"
|
"permissionDenied": "권한이 없습니다"
|
||||||
|
},
|
||||||
|
"ChatChannelSettings": {
|
||||||
|
"loading": "로딩 중...",
|
||||||
|
"sectionTitle": "채팅 채널",
|
||||||
|
"sectionDescription": "IM 봇을 설정하여 이벤트 알림을 수신하고 코딩 활동을 조회합니다.",
|
||||||
|
"addChannel": "채널 추가",
|
||||||
|
"noChannels": "설정된 채팅 채널이 없습니다.",
|
||||||
|
"channelName": "이름",
|
||||||
|
"channelNamePlaceholder": "내 Telegram 봇",
|
||||||
|
"channelType": "채널 유형",
|
||||||
|
"lark": "Lark (飛書)",
|
||||||
|
"dailyReport": "일일 리포트",
|
||||||
|
"dailyReportTime": "발송 시간",
|
||||||
|
"nameRequired": "채널 이름을 입력하세요.",
|
||||||
|
"tokenRequired": "토큰을 입력하세요.",
|
||||||
|
"chatIdRequired": "Chat ID를 입력하세요.",
|
||||||
|
"loadFailed": "채널 로딩에 실패했습니다.",
|
||||||
|
"saveFailed": "저장에 실패했습니다.",
|
||||||
|
"connectSuccess": "채널이 연결되었습니다.",
|
||||||
|
"connectFailed": "연결에 실패했습니다",
|
||||||
|
"disconnectSuccess": "채널이 연결 해제되었습니다.",
|
||||||
|
"disconnectFailed": "연결 해제에 실패했습니다.",
|
||||||
|
"testSuccess": "연결 테스트를 통과했습니다.",
|
||||||
|
"testFailed": "연결 테스트에 실패했습니다",
|
||||||
|
"deleteSuccess": "채널이 삭제되었습니다.",
|
||||||
|
"deleteFailed": "채널 삭제에 실패했습니다.",
|
||||||
|
"deleteConfirmTitle": "채널 삭제",
|
||||||
|
"deleteConfirmMessage": "이 채널과 메시지 기록이 영구 삭제됩니다. 계속하시겠습니까?",
|
||||||
|
"cancel": "취소",
|
||||||
|
"delete": "삭제",
|
||||||
|
"create": "생성",
|
||||||
|
"save": "저장",
|
||||||
|
"channelListTitle": "설정된 채널",
|
||||||
|
"channelListDescription": "활성화된 채널은 서비스 시작 시 자동으로 연결됩니다.",
|
||||||
|
"editChannel": "채널 편집",
|
||||||
|
"editSuccess": "채널이 업데이트되었습니다.",
|
||||||
|
"tokenPlaceholderKeep": "비워두면 현재 값 유지",
|
||||||
|
"connect": "연결",
|
||||||
|
"disconnect": "연결 해제",
|
||||||
|
"test": "연결 테스트",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "채널",
|
||||||
|
"commands": "명령어",
|
||||||
|
"events": "이벤트"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "내장 명령어",
|
||||||
|
"description": "채팅 채널에서 사용 가능한 Bot 명령어입니다.",
|
||||||
|
"prefixLabel": "명령어 접두사",
|
||||||
|
"prefixDescription": "Bot 명령어를 실행하는 접두사, 1-3개의 영숫자가 아닌 문자 (기본값 /).",
|
||||||
|
"prefixSaved": "명령어 접두사가 저장되었습니다.",
|
||||||
|
"prefixSaveFailed": "명령어 접두사 저장에 실패했습니다.",
|
||||||
|
"prefixInvalid": "접두사는 1-3개의 영숫자가 아닌 문자여야 합니다.",
|
||||||
|
"save": "저장",
|
||||||
|
"recentDesc": "최근 대화 5개 표시",
|
||||||
|
"searchDesc": "키워드로 대화 검색",
|
||||||
|
"detailDesc": "대화 상세 정보 표시",
|
||||||
|
"todayDesc": "오늘의 활동 요약",
|
||||||
|
"statusDesc": "채널 연결 상태",
|
||||||
|
"helpDesc": "도움말 표시"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "이벤트 알림",
|
||||||
|
"description": "각 채널에 전송할 이벤트를 설정합니다.",
|
||||||
|
"allEnabled": "모든 이벤트 활성화 (기본값)",
|
||||||
|
"sessionStarted": "세션 시작",
|
||||||
|
"sessionStartedDesc": "AI 에이전트가 새 세션을 시작할 때",
|
||||||
|
"turnComplete": "턴 완료",
|
||||||
|
"turnCompleteDesc": "에이전트 턴이 종료될 때",
|
||||||
|
"error": "에이전트 오류",
|
||||||
|
"errorDesc": "에이전트에 오류가 발생했을 때",
|
||||||
|
"statusDisconnected": "에이전트 연결 해제",
|
||||||
|
"statusDisconnectedDesc": "에이전트 연결이 끊어졌을 때",
|
||||||
|
"gitPush": "Git 푸시",
|
||||||
|
"gitPushDesc": "Git Push 성공 시",
|
||||||
|
"gitCommit": "Git 커밋",
|
||||||
|
"gitCommitDesc": "Git Commit 성공 시",
|
||||||
|
"saved": "이벤트 필터가 업데이트되었습니다.",
|
||||||
|
"saveFailed": "이벤트 필터 저장에 실패했습니다."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1670,5 +1670,85 @@
|
|||||||
"emptyDirectory": "Este diretório está vazio",
|
"emptyDirectory": "Este diretório está vazio",
|
||||||
"errorLoadingDir": "Falha ao carregar o diretório",
|
"errorLoadingDir": "Falha ao carregar o diretório",
|
||||||
"permissionDenied": "Permissão negada"
|
"permissionDenied": "Permissão negada"
|
||||||
|
},
|
||||||
|
"ChatChannelSettings": {
|
||||||
|
"loading": "Carregando...",
|
||||||
|
"sectionTitle": "Canais de chat",
|
||||||
|
"sectionDescription": "Configure bots de IM para receber notificações de eventos e consultar atividade de codificação.",
|
||||||
|
"addChannel": "Adicionar canal",
|
||||||
|
"noChannels": "Nenhum canal de chat configurado ainda.",
|
||||||
|
"channelName": "Nome",
|
||||||
|
"channelNamePlaceholder": "Meu bot do Telegram",
|
||||||
|
"channelType": "Tipo de canal",
|
||||||
|
"lark": "Lark (Feishu)",
|
||||||
|
"dailyReport": "Relatório diário",
|
||||||
|
"dailyReportTime": "Horário do relatório",
|
||||||
|
"nameRequired": "O nome do canal é obrigatório.",
|
||||||
|
"tokenRequired": "O token é obrigatório.",
|
||||||
|
"chatIdRequired": "O Chat ID é obrigatório.",
|
||||||
|
"loadFailed": "Falha ao carregar os canais.",
|
||||||
|
"saveFailed": "Falha ao salvar as alterações.",
|
||||||
|
"connectSuccess": "Canal conectado.",
|
||||||
|
"connectFailed": "Falha na conexão",
|
||||||
|
"disconnectSuccess": "Canal desconectado.",
|
||||||
|
"disconnectFailed": "Falha ao desconectar.",
|
||||||
|
"testSuccess": "Teste de conexão aprovado.",
|
||||||
|
"testFailed": "Teste de conexão falhou",
|
||||||
|
"deleteSuccess": "Canal excluído.",
|
||||||
|
"deleteFailed": "Falha ao excluir o canal.",
|
||||||
|
"deleteConfirmTitle": "Excluir canal",
|
||||||
|
"deleteConfirmMessage": "O canal e seus registros de mensagens serão excluídos permanentemente. Tem certeza?",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"delete": "Excluir",
|
||||||
|
"create": "Criar",
|
||||||
|
"save": "Salvar",
|
||||||
|
"channelListTitle": "Canais configurados",
|
||||||
|
"channelListDescription": "Os canais habilitados serão conectados automaticamente ao iniciar o serviço.",
|
||||||
|
"editChannel": "Editar canal",
|
||||||
|
"editSuccess": "Canal atualizado.",
|
||||||
|
"tokenPlaceholderKeep": "Deixar em branco para manter atual",
|
||||||
|
"connect": "Conectar",
|
||||||
|
"disconnect": "Desconectar",
|
||||||
|
"test": "Testar conexão",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "Canais",
|
||||||
|
"commands": "Comandos",
|
||||||
|
"events": "Eventos"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "Comandos integrados",
|
||||||
|
"description": "Comandos de bot disponíveis nos canais de chat.",
|
||||||
|
"prefixLabel": "Prefixo de comando",
|
||||||
|
"prefixDescription": "1-3 caracteres não alfanuméricos para acionar comandos do bot (padrão /).",
|
||||||
|
"prefixSaved": "Prefixo de comando salvo.",
|
||||||
|
"prefixSaveFailed": "Falha ao salvar o prefixo.",
|
||||||
|
"prefixInvalid": "O prefixo deve ser de 1-3 caracteres não alfanuméricos.",
|
||||||
|
"save": "Salvar",
|
||||||
|
"recentDesc": "Mostrar as 5 conversas mais recentes",
|
||||||
|
"searchDesc": "Pesquisar conversas por palavra-chave",
|
||||||
|
"detailDesc": "Mostrar detalhes da conversa",
|
||||||
|
"todayDesc": "Resumo da atividade de hoje",
|
||||||
|
"statusDesc": "Status da conexão do canal",
|
||||||
|
"helpDesc": "Mostrar ajuda"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "Notificações de eventos",
|
||||||
|
"description": "Configure quais eventos são enviados para cada canal.",
|
||||||
|
"allEnabled": "Todos os eventos habilitados (padrão)",
|
||||||
|
"sessionStarted": "Sessão iniciada",
|
||||||
|
"sessionStartedDesc": "Quando um agente IA inicia uma nova sessão",
|
||||||
|
"turnComplete": "Turno concluído",
|
||||||
|
"turnCompleteDesc": "Quando um turno do agente termina",
|
||||||
|
"error": "Erro do agente",
|
||||||
|
"errorDesc": "Quando um agente encontra um erro",
|
||||||
|
"statusDisconnected": "Agente desconectado",
|
||||||
|
"statusDisconnectedDesc": "Quando a conexão do agente é perdida",
|
||||||
|
"gitPush": "Git Push",
|
||||||
|
"gitPushDesc": "Quando um git push é bem-sucedido",
|
||||||
|
"gitCommit": "Git Commit",
|
||||||
|
"gitCommitDesc": "Quando um git commit é bem-sucedido",
|
||||||
|
"saved": "Filtro de eventos atualizado.",
|
||||||
|
"saveFailed": "Falha ao salvar o filtro de eventos."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1701,6 +1701,55 @@
|
|||||||
"deleteConfirmMessage": "将永久删除该渠道及其消息日志,确定吗?",
|
"deleteConfirmMessage": "将永久删除该渠道及其消息日志,确定吗?",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"create": "创建"
|
"create": "创建",
|
||||||
|
"save": "保存",
|
||||||
|
"channelListTitle": "已配置渠道",
|
||||||
|
"channelListDescription": "已启用的渠道在服务启动时会自动连接。",
|
||||||
|
"editChannel": "编辑渠道",
|
||||||
|
"editSuccess": "渠道已更新。",
|
||||||
|
"tokenPlaceholderKeep": "留空保持不变",
|
||||||
|
"connect": "连接",
|
||||||
|
"disconnect": "断开",
|
||||||
|
"test": "测试连接",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "渠道",
|
||||||
|
"commands": "指令",
|
||||||
|
"events": "事件"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "内置指令",
|
||||||
|
"description": "消息渠道中可用的 Bot 指令。",
|
||||||
|
"prefixLabel": "指令前缀",
|
||||||
|
"prefixDescription": "触发 Bot 指令的前缀,1-3 个非字母数字字符(默认 /)。",
|
||||||
|
"prefixSaved": "指令前缀已保存。",
|
||||||
|
"prefixSaveFailed": "保存指令前缀失败。",
|
||||||
|
"prefixInvalid": "前缀必须是 1-3 个非字母数字字符。",
|
||||||
|
"save": "保存",
|
||||||
|
"recentDesc": "最近 5 条会话",
|
||||||
|
"searchDesc": "按关键词搜索会话",
|
||||||
|
"detailDesc": "会话详情",
|
||||||
|
"todayDesc": "今日活动汇总",
|
||||||
|
"statusDesc": "渠道连接状态",
|
||||||
|
"helpDesc": "显示帮助"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "事件通知",
|
||||||
|
"description": "配置每个渠道推送哪些事件。",
|
||||||
|
"allEnabled": "所有事件已启用(默认)",
|
||||||
|
"sessionStarted": "会话开始",
|
||||||
|
"sessionStartedDesc": "AI 代理开始新会话时",
|
||||||
|
"turnComplete": "对话完成",
|
||||||
|
"turnCompleteDesc": "代理回合结束时",
|
||||||
|
"error": "代理错误",
|
||||||
|
"errorDesc": "代理遇到错误时",
|
||||||
|
"statusDisconnected": "代理断开",
|
||||||
|
"statusDisconnectedDesc": "代理连接断开时",
|
||||||
|
"gitPush": "Git 推送",
|
||||||
|
"gitPushDesc": "Git Push 成功时",
|
||||||
|
"gitCommit": "Git 提交",
|
||||||
|
"gitCommitDesc": "Git Commit 成功时",
|
||||||
|
"saved": "事件过滤已更新。",
|
||||||
|
"saveFailed": "保存事件过滤失败。"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1670,5 +1670,85 @@
|
|||||||
"emptyDirectory": "此目錄為空",
|
"emptyDirectory": "此目錄為空",
|
||||||
"errorLoadingDir": "載入目錄失敗",
|
"errorLoadingDir": "載入目錄失敗",
|
||||||
"permissionDenied": "權限不足"
|
"permissionDenied": "權限不足"
|
||||||
|
},
|
||||||
|
"ChatChannelSettings": {
|
||||||
|
"loading": "載入中...",
|
||||||
|
"sectionTitle": "訊息頻道",
|
||||||
|
"sectionDescription": "設定 IM 機器人,接收事件通知和查詢編碼活動。",
|
||||||
|
"addChannel": "新增頻道",
|
||||||
|
"noChannels": "尚未設定任何訊息頻道。",
|
||||||
|
"channelName": "名稱",
|
||||||
|
"channelNamePlaceholder": "我的 Telegram 機器人",
|
||||||
|
"channelType": "頻道類型",
|
||||||
|
"lark": "飛書",
|
||||||
|
"dailyReport": "每日報告",
|
||||||
|
"dailyReportTime": "推送時間",
|
||||||
|
"nameRequired": "請輸入頻道名稱。",
|
||||||
|
"tokenRequired": "請輸入 Token。",
|
||||||
|
"chatIdRequired": "請輸入 Chat ID。",
|
||||||
|
"loadFailed": "載入頻道失敗。",
|
||||||
|
"saveFailed": "儲存失敗。",
|
||||||
|
"connectSuccess": "頻道已連線。",
|
||||||
|
"connectFailed": "連線失敗",
|
||||||
|
"disconnectSuccess": "頻道已斷線。",
|
||||||
|
"disconnectFailed": "斷線失敗。",
|
||||||
|
"testSuccess": "連線測試通過。",
|
||||||
|
"testFailed": "連線測試失敗",
|
||||||
|
"deleteSuccess": "頻道已刪除。",
|
||||||
|
"deleteFailed": "刪除頻道失敗。",
|
||||||
|
"deleteConfirmTitle": "刪除頻道",
|
||||||
|
"deleteConfirmMessage": "將永久刪除該頻道及其訊息紀錄,確定嗎?",
|
||||||
|
"cancel": "取消",
|
||||||
|
"delete": "刪除",
|
||||||
|
"create": "建立",
|
||||||
|
"save": "儲存",
|
||||||
|
"channelListTitle": "已設定頻道",
|
||||||
|
"channelListDescription": "已啟用的頻道在服務啟動時會自動連線。",
|
||||||
|
"editChannel": "編輯頻道",
|
||||||
|
"editSuccess": "頻道已更新。",
|
||||||
|
"tokenPlaceholderKeep": "留空保持不變",
|
||||||
|
"connect": "連線",
|
||||||
|
"disconnect": "斷開",
|
||||||
|
"test": "測試連線",
|
||||||
|
"tabs": {
|
||||||
|
"channels": "頻道",
|
||||||
|
"commands": "指令",
|
||||||
|
"events": "事件"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"title": "內建指令",
|
||||||
|
"description": "訊息頻道中可用的 Bot 指令。",
|
||||||
|
"prefixLabel": "指令前綴",
|
||||||
|
"prefixDescription": "觸發 Bot 指令的前綴,1-3 個非字母數字字元(預設 /)。",
|
||||||
|
"prefixSaved": "指令前綴已儲存。",
|
||||||
|
"prefixSaveFailed": "儲存指令前綴失敗。",
|
||||||
|
"prefixInvalid": "前綴必須是 1-3 個非字母數字字元。",
|
||||||
|
"save": "儲存",
|
||||||
|
"recentDesc": "最近 5 筆對話",
|
||||||
|
"searchDesc": "依關鍵字搜尋對話",
|
||||||
|
"detailDesc": "對話詳情",
|
||||||
|
"todayDesc": "今日活動摘要",
|
||||||
|
"statusDesc": "頻道連線狀態",
|
||||||
|
"helpDesc": "顯示說明"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"title": "事件通知",
|
||||||
|
"description": "設定每個頻道推送哪些事件。",
|
||||||
|
"allEnabled": "所有事件已啟用(預設)",
|
||||||
|
"sessionStarted": "工作階段開始",
|
||||||
|
"sessionStartedDesc": "AI 代理開始新工作階段時",
|
||||||
|
"turnComplete": "對話完成",
|
||||||
|
"turnCompleteDesc": "代理回合結束時",
|
||||||
|
"error": "代理錯誤",
|
||||||
|
"errorDesc": "代理遇到錯誤時",
|
||||||
|
"statusDisconnected": "代理斷線",
|
||||||
|
"statusDisconnectedDesc": "代理連線中斷時",
|
||||||
|
"gitPush": "Git 推送",
|
||||||
|
"gitPushDesc": "Git Push 成功時",
|
||||||
|
"gitCommit": "Git 提交",
|
||||||
|
"gitCommitDesc": "Git Commit 成功時",
|
||||||
|
"saved": "事件篩選已更新。",
|
||||||
|
"saveFailed": "儲存事件篩選失敗。"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1402,3 +1402,21 @@ export async function listChatChannelMessages(params: {
|
|||||||
offset: params.offset ?? null,
|
offset: params.offset ?? null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getChatCommandPrefix(): Promise<string> {
|
||||||
|
return getTransport().call("get_chat_command_prefix")
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setChatCommandPrefix(prefix: string): Promise<void> {
|
||||||
|
return getTransport().call("set_chat_command_prefix", { prefix })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getChatEventFilter(): Promise<string[] | null> {
|
||||||
|
return getTransport().call("get_chat_event_filter")
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setChatEventFilter(
|
||||||
|
filter: string[] | null,
|
||||||
|
): Promise<void> {
|
||||||
|
return getTransport().call("set_chat_event_filter", { filter })
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user