diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index dc5ae4d..c3ef4fc 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -32,7 +32,7 @@ sacp-tokio = "11.0.0-alpha.1" tokio = { version = "1", features = ["process", "io-util", "sync", "macros", "rt"] } uuid = { version = "1", features = ["v4"] } futures = "0.3" -reqwest = { version = "0.12", features = ["stream"] } +reqwest = { version = "0.12", features = ["stream", "json"] } flate2 = "1" bzip2 = "0.5" tar = "0.4" diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index db6eee2..369ea11 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -5,4 +5,5 @@ pub mod folders; pub mod mcp; pub mod system_settings; pub mod terminal; +pub mod version_control; pub mod windows; diff --git a/src-tauri/src/commands/version_control.rs b/src-tauri/src/commands/version_control.rs new file mode 100644 index 0000000..cacb420 --- /dev/null +++ b/src-tauri/src/commands/version_control.rs @@ -0,0 +1,288 @@ +use serde::Deserialize; +use tauri::State; + +use crate::app_error::AppCommandError; +use crate::db::service::app_metadata_service; +use crate::db::AppDatabase; +use crate::models::{ + GitDetectResult, GitHubAccountsSettings, GitHubTokenValidation, GitSettings, +}; + +const GIT_SETTINGS_KEY: &str = "git_settings"; +const GITHUB_ACCOUNTS_KEY: &str = "github_accounts"; + +// --------------------------------------------------------------------------- +// Git detection +// --------------------------------------------------------------------------- + +async fn run_git_version(git_path: &str) -> Result { + let output = crate::process::tokio_command(git_path) + .arg("--version") + .output() + .await + .map_err(|_| { + AppCommandError::not_found(format!("Cannot execute git at: {git_path}")) + })?; + + if !output.status.success() { + return Ok(GitDetectResult { + installed: false, + version: None, + path: Some(git_path.to_string()), + }); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let version = stdout + .trim() + .strip_prefix("git version ") + .unwrap_or(stdout.trim()) + .to_string(); + + Ok(GitDetectResult { + installed: true, + version: Some(version), + path: Some(git_path.to_string()), + }) +} + +async fn detect_git_path() -> Option { + let which_cmd = if cfg!(target_os = "windows") { + "where" + } else { + "which" + }; + + let output = crate::process::tokio_command(which_cmd) + .arg("git") + .output() + .await + .ok()?; + + if !output.status.success() { + return None; + } + + let path = String::from_utf8_lossy(&output.stdout) + .lines() + .next()? + .trim() + .to_string(); + + if path.is_empty() { + None + } else { + Some(path) + } +} + +#[tauri::command] +pub async fn detect_git( + db: State<'_, AppDatabase>, +) -> Result { + // Check if there's a custom path configured + let settings = load_git_settings(&db.conn).await?; + + if let Some(custom) = &settings.custom_path { + let trimmed = custom.trim(); + if !trimmed.is_empty() { + return run_git_version(trimmed).await; + } + } + + // Auto-detect + if let Some(path) = detect_git_path().await { + return run_git_version(&path).await; + } + + // Fallback: try "git" directly (might be in PATH but `which` failed) + match run_git_version("git").await { + Ok(result) if result.installed => Ok(result), + _ => Ok(GitDetectResult { + installed: false, + version: None, + path: None, + }), + } +} + +#[tauri::command] +pub async fn test_git_path(path: String) -> Result { + let trimmed = path.trim(); + if trimmed.is_empty() { + return Err(AppCommandError::invalid_input("Git path cannot be empty")); + } + run_git_version(trimmed).await +} + +// --------------------------------------------------------------------------- +// Git settings +// --------------------------------------------------------------------------- + +async fn load_git_settings( + conn: &sea_orm::DatabaseConnection, +) -> Result { + let raw = app_metadata_service::get_value(conn, GIT_SETTINGS_KEY) + .await + .map_err(AppCommandError::from)?; + + match raw { + Some(raw) => serde_json::from_str::(&raw).map_err(|e| { + AppCommandError::configuration_invalid("Failed to parse stored git settings") + .with_detail(e.to_string()) + }), + None => Ok(GitSettings::default()), + } +} + +#[tauri::command] +pub async fn get_git_settings( + db: State<'_, AppDatabase>, +) -> Result { + load_git_settings(&db.conn).await +} + +#[tauri::command] +pub async fn update_git_settings( + settings: GitSettings, + db: State<'_, AppDatabase>, +) -> Result { + let serialized = serde_json::to_string(&settings).map_err(|e| { + AppCommandError::invalid_input("Failed to serialize git settings") + .with_detail(e.to_string()) + })?; + + app_metadata_service::upsert_value(&db.conn, GIT_SETTINGS_KEY, &serialized) + .await + .map_err(AppCommandError::from)?; + + Ok(settings) +} + +// --------------------------------------------------------------------------- +// GitHub accounts +// --------------------------------------------------------------------------- + +async fn load_github_accounts( + conn: &sea_orm::DatabaseConnection, +) -> Result { + let raw = app_metadata_service::get_value(conn, GITHUB_ACCOUNTS_KEY) + .await + .map_err(AppCommandError::from)?; + + match raw { + Some(raw) => serde_json::from_str::(&raw).map_err(|e| { + AppCommandError::configuration_invalid("Failed to parse stored GitHub accounts") + .with_detail(e.to_string()) + }), + None => Ok(GitHubAccountsSettings::default()), + } +} + +#[tauri::command] +pub async fn get_github_accounts( + db: State<'_, AppDatabase>, +) -> Result { + load_github_accounts(&db.conn).await +} + +#[tauri::command] +pub async fn update_github_accounts( + settings: GitHubAccountsSettings, + db: State<'_, AppDatabase>, +) -> Result { + let serialized = serde_json::to_string(&settings).map_err(|e| { + AppCommandError::invalid_input("Failed to serialize GitHub accounts") + .with_detail(e.to_string()) + })?; + + app_metadata_service::upsert_value(&db.conn, GITHUB_ACCOUNTS_KEY, &serialized) + .await + .map_err(AppCommandError::from)?; + + Ok(settings) +} + +#[derive(Debug, Deserialize)] +struct GitHubUserResponse { + login: String, + avatar_url: Option, +} + +#[tauri::command] +pub async fn validate_github_token( + server_url: String, + token: String, +) -> Result { + let trimmed_url = server_url.trim().trim_end_matches('/'); + let trimmed_token = token.trim(); + + if trimmed_token.is_empty() { + return Err(AppCommandError::invalid_input("Token cannot be empty")); + } + + // Build API URL: github.com uses api.github.com, enterprise uses {url}/api/v3 + let api_url = if trimmed_url.is_empty() + || trimmed_url == "https://github.com" + || trimmed_url == "http://github.com" + { + "https://api.github.com/user".to_string() + } else { + format!("{trimmed_url}/api/v3/user") + }; + + let response = reqwest::Client::new() + .get(&api_url) + .header("Authorization", format!("Bearer {trimmed_token}")) + .header("User-Agent", "codeg") + .header("Accept", "application/vnd.github+json") + .send() + .await + .map_err(|e| AppCommandError::network("Failed to connect to GitHub API").with_detail(e.to_string()))?; + + if !response.status().is_success() { + let status = response.status().as_u16(); + let body = response.text().await.unwrap_or_default(); + let message = if status == 401 { + "Invalid or expired token".to_string() + } else { + format!("GitHub API returned status {status}: {body}") + }; + return Ok(GitHubTokenValidation { + success: false, + username: None, + scopes: vec![], + avatar_url: None, + message: Some(message), + }); + } + + // Parse scopes from x-oauth-scopes header + let scopes: Vec = response + .headers() + .get("x-oauth-scopes") + .and_then(|v| v.to_str().ok()) + .map(|s| { + s.split(',') + .map(|scope| scope.trim().to_string()) + .filter(|scope| !scope.is_empty()) + .collect() + }) + .unwrap_or_default(); + + let user = response + .json::() + .await + .map_err(|e| { + AppCommandError::network("Failed to parse GitHub API response") + .with_detail(e.to_string()) + })?; + + Ok(GitHubTokenValidation { + success: true, + username: Some(user.login), + scopes, + avatar_url: user.avatar_url, + message: None, + }) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c0d974d..3ee979f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -13,7 +13,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use acp::manager::ConnectionManager; use commands::{ acp as acp_commands, conversations, folder_commands, folders, mcp as mcp_commands, - system_settings, terminal as terminal_commands, windows, + system_settings, terminal as terminal_commands, version_control, windows, }; use tauri::Manager; use terminal::manager::TerminalManager; @@ -264,6 +264,13 @@ pub fn run() { system_settings::update_system_proxy_settings, system_settings::get_system_language_settings, system_settings::update_system_language_settings, + version_control::detect_git, + version_control::test_git_path, + version_control::get_git_settings, + version_control::update_git_settings, + version_control::get_github_accounts, + version_control::validate_github_token, + version_control::update_github_accounts, acp_commands::acp_preflight, acp_commands::acp_connect, acp_commands::acp_prompt, diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs index 11b349e..c59d2dc 100644 --- a/src-tauri/src/models/mod.rs +++ b/src-tauri/src/models/mod.rs @@ -12,4 +12,7 @@ pub use conversation::{ }; pub use folder::{FolderCommandInfo, FolderDetail, FolderHistoryEntry, OpenedConversation}; pub use message::{ContentBlock, MessageRole, MessageTurn, TurnRole, TurnUsage, UnifiedMessage}; -pub use system::{SystemLanguageSettings, SystemProxySettings}; +pub use system::{ + GitDetectResult, GitHubAccountsSettings, GitHubTokenValidation, GitSettings, + SystemLanguageSettings, SystemProxySettings, +}; diff --git a/src-tauri/src/models/system.rs b/src-tauri/src/models/system.rs index fb2412d..e993e7b 100644 --- a/src-tauri/src/models/system.rs +++ b/src-tauri/src/models/system.rs @@ -36,3 +36,45 @@ pub struct SystemLanguageSettings { pub mode: LanguageMode, pub language: AppLocale, } + +// --- Version Control --- + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GitDetectResult { + pub installed: bool, + pub version: Option, + pub path: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct GitSettings { + pub custom_path: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GitHubAccount { + pub id: String, + pub server_url: String, + pub username: String, + pub token: String, + pub scopes: Vec, + pub avatar_url: Option, + pub is_default: bool, + pub created_at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct GitHubAccountsSettings { + pub accounts: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GitHubTokenValidation { + pub success: bool, + pub username: Option, + pub scopes: Vec, + pub avatar_url: Option, + pub message: Option, +} diff --git a/src/app/settings/version-control/page.tsx b/src/app/settings/version-control/page.tsx new file mode 100644 index 0000000..568c3f2 --- /dev/null +++ b/src/app/settings/version-control/page.tsx @@ -0,0 +1,5 @@ +import { VersionControlSettings } from "@/components/settings/version-control-settings" + +export default function SettingsVersionControlPage() { + return +} diff --git a/src/components/settings/add-github-account-dialog.tsx b/src/components/settings/add-github-account-dialog.tsx new file mode 100644 index 0000000..05adc4c --- /dev/null +++ b/src/components/settings/add-github-account-dialog.tsx @@ -0,0 +1,179 @@ +"use client" + +import { useCallback, useState } from "react" +import { Eye, EyeOff, Loader2 } from "lucide-react" +import { useTranslations } from "next-intl" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { validateGitHubToken } from "@/lib/tauri" +import type { GitHubAccount } from "@/lib/types" + +interface AddGitHubAccountDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + onAccountAdded: (account: GitHubAccount) => void + isFirstAccount: boolean +} + +export function AddGitHubAccountDialog({ + open, + onOpenChange, + onAccountAdded, + isFirstAccount, +}: AddGitHubAccountDialogProps) { + const t = useTranslations("VersionControlSettings") + + const [serverUrl, setServerUrl] = useState("https://github.com") + const [token, setToken] = useState("") + const [showToken, setShowToken] = useState(false) + const [validating, setValidating] = useState(false) + const [error, setError] = useState(null) + + const resetForm = useCallback(() => { + setServerUrl("https://github.com") + setToken("") + setShowToken(false) + setValidating(false) + setError(null) + }, []) + + const handleOpenChange = useCallback( + (nextOpen: boolean) => { + if (!nextOpen) { + resetForm() + } + onOpenChange(nextOpen) + }, + [onOpenChange, resetForm] + ) + + const handleSubmit = useCallback(async () => { + const trimmedToken = token.trim() + if (!trimmedToken) { + setError(t("addFailed", { message: "Token is required" })) + return + } + + setValidating(true) + setError(null) + + try { + const result = await validateGitHubToken(serverUrl.trim(), trimmedToken) + + if (!result.success) { + setError( + t("addFailed", { message: result.message ?? "Validation failed" }) + ) + return + } + + const account: GitHubAccount = { + id: crypto.randomUUID(), + server_url: serverUrl.trim() || "https://github.com", + username: result.username ?? "unknown", + token: trimmedToken, + scopes: result.scopes, + avatar_url: result.avatar_url, + is_default: isFirstAccount, + created_at: new Date().toISOString(), + } + + onAccountAdded(account) + handleOpenChange(false) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + setError(t("addFailed", { message })) + } finally { + setValidating(false) + } + }, [serverUrl, token, isFirstAccount, onAccountAdded, handleOpenChange, t]) + + return ( + + + + {t("addAccount")} + {t("githubDescription")} + + +
+
+ + setServerUrl(e.target.value)} + placeholder={t("serverUrlPlaceholder")} + /> +
+ +
+ +
+ { + setToken(e.target.value) + setError(null) + }} + placeholder={t("tokenPlaceholder")} + className="pr-9" + /> + +
+

+ {t("tokenHint")} +

+
+ + {error && ( +
+ {error} +
+ )} +
+ + + + +
+
+ ) +} diff --git a/src/components/settings/settings-shell.tsx b/src/components/settings/settings-shell.tsx index a676534..26eac5d 100644 --- a/src/components/settings/settings-shell.tsx +++ b/src/components/settings/settings-shell.tsx @@ -4,6 +4,7 @@ import { useCallback, type ComponentType, type ReactNode } from "react" import { Bot, BookOpenText, + GitBranch, Keyboard, Palette, PlugZap, @@ -19,7 +20,14 @@ import { AppTitleBar } from "@/components/layout/app-title-bar" interface SettingsNavItem { href: string - labelKey: "appearance" | "agents" | "mcp" | "skills" | "shortcuts" | "system" + labelKey: + | "appearance" + | "agents" + | "mcp" + | "skills" + | "shortcuts" + | "version_control" + | "system" icon: ComponentType<{ className?: string }> } @@ -49,6 +57,11 @@ const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [ labelKey: "shortcuts", icon: Keyboard, }, + { + href: "/settings/version-control", + labelKey: "version_control", + icon: GitBranch, + }, { href: "/settings/system", labelKey: "system", diff --git a/src/components/settings/version-control-settings.tsx b/src/components/settings/version-control-settings.tsx new file mode 100644 index 0000000..056b2d8 --- /dev/null +++ b/src/components/settings/version-control-settings.tsx @@ -0,0 +1,477 @@ +"use client" + +import { useCallback, useEffect, useState } from "react" +import { + CheckCircle2, + GitBranch, + Github, + Loader2, + Save, + Trash2, + XCircle, +} 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 { Badge } from "@/components/ui/badge" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { + detectGit, + getGitSettings, + updateGitSettings, + testGitPath, + getGitHubAccounts, + updateGitHubAccounts, + validateGitHubToken, +} from "@/lib/tauri" +import type { + GitDetectResult, + GitHubAccount, + GitHubAccountsSettings, +} from "@/lib/types" +import { AddGitHubAccountDialog } from "./add-github-account-dialog" + +export function VersionControlSettings() { + const t = useTranslations("VersionControlSettings") + + const [loading, setLoading] = useState(true) + const [gitInfo, setGitInfo] = useState(null) + const [customPath, setCustomPath] = useState("") + const [savingGit, setSavingGit] = useState(false) + const [testingGit, setTestingGit] = useState(false) + const [testResult, setTestResult] = useState(null) + + const [accounts, setAccounts] = useState({ + accounts: [], + }) + const [addDialogOpen, setAddDialogOpen] = useState(false) + const [testingAccountId, setTestingAccountId] = useState(null) + const [removeTarget, setRemoveTarget] = useState(null) + + const loadData = useCallback(async () => { + setLoading(true) + try { + const [git, settings, ghAccounts] = await Promise.all([ + detectGit(), + getGitSettings(), + getGitHubAccounts(), + ]) + setGitInfo(git) + setCustomPath(settings.custom_path ?? "") + setAccounts(ghAccounts) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + toast.error(t("loadFailed", { message })) + } finally { + setLoading(false) + } + }, [t]) + + useEffect(() => { + loadData().catch(console.error) + }, [loadData]) + + const handleTestGit = useCallback(async () => { + const pathToTest = customPath.trim() || "git" + setTestingGit(true) + setTestResult(null) + try { + const result = await testGitPath(pathToTest) + setTestResult(result) + if (result.installed) { + toast.success(t("testSuccess")) + } else { + toast.error(t("testFailed", { message: "not a valid git executable" })) + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + toast.error(t("testFailed", { message })) + } finally { + setTestingGit(false) + } + }, [customPath, t]) + + const handleSaveGit = useCallback(async () => { + setSavingGit(true) + try { + await updateGitSettings({ + custom_path: customPath.trim() || null, + }) + const git = await detectGit() + setGitInfo(git) + toast.success(t("saveSuccess")) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + toast.error(t("saveFailed", { message })) + } finally { + setSavingGit(false) + } + }, [customPath, t]) + + const handleAccountAdded = useCallback( + async (account: GitHubAccount) => { + const updated: GitHubAccountsSettings = { + accounts: [ + ...accounts.accounts.map((a) => + account.is_default ? { ...a, is_default: false } : a + ), + account, + ], + } + try { + const saved = await updateGitHubAccounts(updated) + setAccounts(saved) + toast.success(t("addSuccess", { username: account.username })) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + toast.error(t("addFailed", { message })) + } + }, + [accounts, t] + ) + + const handleTestConnection = useCallback( + async (account: GitHubAccount) => { + setTestingAccountId(account.id) + try { + const result = await validateGitHubToken( + account.server_url, + account.token + ) + if (result.success) { + toast.success(t("connectionSuccess")) + } else { + toast.error( + t("connectionFailed", { + message: result.message ?? "Unknown error", + }) + ) + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + toast.error(t("connectionFailed", { message })) + } finally { + setTestingAccountId(null) + } + }, + [t] + ) + + const handleSetDefault = useCallback( + async (accountId: string) => { + const updated: GitHubAccountsSettings = { + accounts: accounts.accounts.map((a) => ({ + ...a, + is_default: a.id === accountId, + })), + } + try { + const saved = await updateGitHubAccounts(updated) + setAccounts(saved) + toast.success(t("defaultSet")) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + toast.error(message) + } + }, + [accounts, t] + ) + + const handleRemoveAccount = useCallback(async () => { + if (!removeTarget) return + const updated: GitHubAccountsSettings = { + accounts: accounts.accounts.filter((a) => a.id !== removeTarget.id), + } + try { + const saved = await updateGitHubAccounts(updated) + setAccounts(saved) + toast.success(t("removeSuccess")) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + toast.error(message) + } finally { + setRemoveTarget(null) + } + }, [accounts, removeTarget, t]) + + if (loading) { + return ( +
+ + {t("loading")} +
+ ) + } + + return ( +
+
+
+

{t("sectionTitle")}

+

+ {t("sectionDescription")} +

+
+ + {/* Git Configuration */} +
+
+ +

{t("gitTitle")}

+
+ +

+ {t("gitDescription")} +

+ + {/* Git status */} +
+
+ {gitInfo?.installed ? ( + <> + + + {t("gitDetected")} + + + ) : ( + <> + + + {t("gitNotFound")} + + + )} +
+ {gitInfo?.version && ( +

+ {t("gitVersion")}: {gitInfo.version} +

+ )} + {gitInfo?.path && ( +

+ {t("gitPath")}: {gitInfo.path} +

+ )} +
+ + {/* Custom path */} +
+ +
+ { + setCustomPath(e.target.value) + setTestResult(null) + }} + placeholder={t("customGitPathPlaceholder")} + className="flex-1" + /> + +
+

+ {t("customGitPathHint")} +

+ {testResult && ( +
+ {testResult.installed ? ( + + ) : ( + + )} + {testResult.installed + ? `${t("testSuccess")} (${testResult.version})` + : t("testFailed", { message: "invalid" })} +
+ )} +
+ +
+ +
+
+ + {/* GitHub Accounts */} +
+
+ +

{t("githubTitle")}

+
+ +

+ {t("githubDescription")} +

+ + {/* Account list */} + {accounts.accounts.length === 0 ? ( +
+ {t("noAccounts")} +
+ ) : ( +
+ {accounts.accounts.map((account) => ( +
+ {/* Avatar */} + {account.avatar_url ? ( + {account.username} + ) : ( +
+ {account.username[0]?.toUpperCase()} +
+ )} + + {/* Info */} +
+
+ + {account.username} + + {account.is_default && ( + + {t("defaultLabel")} + + )} +
+
+ {account.server_url} + {account.scopes.length > 0 && ( + <> + · + + {account.scopes.join(", ")} + + + )} +
+
+ + {/* Actions */} +
+ + {!account.is_default && ( + + )} + +
+
+ ))} +
+ )} + +
+ +
+
+
+ + {/* Add Account Dialog */} + + + {/* Remove Confirmation */} + !open && setRemoveTarget(null)} + > + + + {t("removeConfirmTitle")} + + {t("removeConfirmMessage", { + username: removeTarget?.username ?? "", + })} + + + + {t("removeCancel")} + + {t("removeConfirm")} + + + + +
+ ) +} diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 0fe3e58..14479e5 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "الاختصارات", + "version_control": "التحكم بالإصدارات", "system": "النظام" } }, @@ -128,6 +129,55 @@ "unknown": "فشل التحديث. يرجى المحاولة مرة أخرى لاحقًا." } }, + "VersionControlSettings": { + "loading": "جارٍ التحميل...", + "sectionTitle": "التحكم بالإصدارات", + "sectionDescription": "تكوين ملف Git التنفيذي وإدارة حسابات GitHub.", + "gitTitle": "إعدادات Git", + "gitDescription": "تكوين ملف Git التنفيذي المستخدم بواسطة التطبيق.", + "gitDetected": "تم اكتشاف Git", + "gitNotFound": "لم يتم العثور على Git في النظام", + "gitVersion": "الإصدار", + "gitPath": "المسار", + "customGitPath": "مسار Git مخصص", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "اتركه فارغًا لاستخدام المسار المكتشف تلقائيًا.", + "test": "اختبار", + "testing": "جارٍ الاختبار...", + "testSuccess": "ملف Git التنفيذي صالح.", + "testFailed": "فشل اختبار Git: {message}", + "save": "حفظ", + "saving": "جارٍ الحفظ...", + "saveSuccess": "تم حفظ إعدادات Git.", + "saveFailed": "فشل الحفظ: {message}", + "githubTitle": "حسابات GitHub", + "githubDescription": "إدارة حسابات GitHub للمصادقة. يتم تخزين الرموز محليًا.", + "noAccounts": "لا توجد حسابات GitHub مكوّنة.", + "addAccount": "إضافة حساب", + "serverUrl": "عنوان الخادم", + "serverUrlPlaceholder": "https://github.com", + "token": "رمز الوصول الشخصي", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "أنشئ رمزًا في GitHub → Settings → Developer settings → Personal access tokens.", + "validateAndAdd": "التحقق والإضافة", + "validating": "جارٍ التحقق...", + "addSuccess": "تمت إضافة الحساب {username} بنجاح.", + "addFailed": "فشل إضافة الحساب: {message}", + "testConnection": "اختبار", + "connectionSuccess": "نجح الاتصال.", + "connectionFailed": "فشل الاتصال: {message}", + "setDefault": "تعيين كافتراضي", + "defaultLabel": "افتراضي", + "defaultSet": "تم تحديث الحساب الافتراضي.", + "removeAccount": "إزالة", + "removeConfirmTitle": "إزالة الحساب", + "removeConfirmMessage": "هل أنت متأكد من إزالة الحساب \"{username}\"؟", + "removeConfirm": "إزالة", + "removeCancel": "إلغاء", + "removeSuccess": "تمت إزالة الحساب.", + "scopes": "النطاقات", + "loadFailed": "فشل تحميل الإعدادات: {message}" + }, "ShortcutSettings": { "sectionTitle": "الاختصارات", "resetDefault": "استعادة الإعدادات الافتراضية", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 2227e89..ed600d4 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "Kurzbefehle", + "version_control": "Versionskontrolle", "system": "Systemeinstellungen" } }, @@ -128,6 +129,55 @@ "unknown": "Update fehlgeschlagen. Bitte später erneut versuchen." } }, + "VersionControlSettings": { + "loading": "Laden...", + "sectionTitle": "Versionskontrolle", + "sectionDescription": "Git-Programm konfigurieren und GitHub-Konten verwalten.", + "gitTitle": "Git-Konfiguration", + "gitDescription": "Konfigurieren Sie das von der Anwendung verwendete Git-Programm.", + "gitDetected": "Git erkannt", + "gitNotFound": "Git wurde nicht gefunden", + "gitVersion": "Version", + "gitPath": "Pfad", + "customGitPath": "Benutzerdefinierter Git-Pfad", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "Leer lassen, um den automatisch erkannten Pfad zu verwenden.", + "test": "Testen", + "testing": "Teste...", + "testSuccess": "Git-Programm ist gültig.", + "testFailed": "Git-Test fehlgeschlagen: {message}", + "save": "Speichern", + "saving": "Speichern...", + "saveSuccess": "Git-Einstellungen gespeichert.", + "saveFailed": "Speichern fehlgeschlagen: {message}", + "githubTitle": "GitHub-Konten", + "githubDescription": "GitHub-Konten für die Authentifizierung verwalten. Token werden lokal gespeichert.", + "noAccounts": "Keine GitHub-Konten konfiguriert.", + "addAccount": "Konto hinzufügen", + "serverUrl": "Server-URL", + "serverUrlPlaceholder": "https://github.com", + "token": "Persönlicher Zugriffstoken", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "Erstellen Sie einen Token unter GitHub → Settings → Developer settings → Personal access tokens.", + "validateAndAdd": "Validieren & hinzufügen", + "validating": "Validiere...", + "addSuccess": "Konto {username} erfolgreich hinzugefügt.", + "addFailed": "Konto konnte nicht hinzugefügt werden: {message}", + "testConnection": "Testen", + "connectionSuccess": "Verbindung erfolgreich.", + "connectionFailed": "Verbindung fehlgeschlagen: {message}", + "setDefault": "Als Standard festlegen", + "defaultLabel": "Standard", + "defaultSet": "Standardkonto aktualisiert.", + "removeAccount": "Entfernen", + "removeConfirmTitle": "Konto entfernen", + "removeConfirmMessage": "Möchten Sie das Konto \"{username}\" wirklich entfernen?", + "removeConfirm": "Entfernen", + "removeCancel": "Abbrechen", + "removeSuccess": "Konto entfernt.", + "scopes": "Berechtigungen", + "loadFailed": "Einstellungen konnten nicht geladen werden: {message}" + }, "ShortcutSettings": { "sectionTitle": "Kurzbefehle", "resetDefault": "Standardwerte zurücksetzen", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 35120d1..b1087a0 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "Shortcuts", + "version_control": "Version Control", "system": "System" } }, @@ -128,6 +129,55 @@ "unknown": "Update failed. Please try again later." } }, + "VersionControlSettings": { + "loading": "Loading...", + "sectionTitle": "Version Control", + "sectionDescription": "Configure Git executable and manage GitHub accounts.", + "gitTitle": "Git Configuration", + "gitDescription": "Configure the Git executable used by the application.", + "gitDetected": "Git detected", + "gitNotFound": "Git not found on this system", + "gitVersion": "Version", + "gitPath": "Path", + "customGitPath": "Custom Git Path", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "Leave empty to use the auto-detected path.", + "test": "Test", + "testing": "Testing...", + "testSuccess": "Git executable is valid.", + "testFailed": "Git test failed: {message}", + "save": "Save", + "saving": "Saving...", + "saveSuccess": "Git settings saved.", + "saveFailed": "Failed to save: {message}", + "githubTitle": "GitHub Accounts", + "githubDescription": "Manage GitHub accounts for authentication. Tokens are stored locally.", + "noAccounts": "No GitHub accounts configured.", + "addAccount": "Add Account", + "serverUrl": "Server URL", + "serverUrlPlaceholder": "https://github.com", + "token": "Personal Access Token", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "Generate a token at GitHub → Settings → Developer settings → Personal access tokens.", + "validateAndAdd": "Validate & Add", + "validating": "Validating...", + "addSuccess": "Account {username} added successfully.", + "addFailed": "Failed to add account: {message}", + "testConnection": "Test", + "connectionSuccess": "Connection successful.", + "connectionFailed": "Connection failed: {message}", + "setDefault": "Set Default", + "defaultLabel": "Default", + "defaultSet": "Default account updated.", + "removeAccount": "Remove", + "removeConfirmTitle": "Remove Account", + "removeConfirmMessage": "Are you sure you want to remove the account \"{username}\"?", + "removeConfirm": "Remove", + "removeCancel": "Cancel", + "removeSuccess": "Account removed.", + "scopes": "Scopes", + "loadFailed": "Failed to load settings: {message}" + }, "ShortcutSettings": { "sectionTitle": "Shortcuts", "resetDefault": "Reset defaults", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index f4a2e38..cdc644c 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "Atajos", + "version_control": "Control de versiones", "system": "Sistema" } }, @@ -128,6 +129,55 @@ "unknown": "La actualización falló. Inténtalo más tarde." } }, + "VersionControlSettings": { + "loading": "Cargando...", + "sectionTitle": "Control de versiones", + "sectionDescription": "Configura el ejecutable de Git y gestiona las cuentas de GitHub.", + "gitTitle": "Configuración de Git", + "gitDescription": "Configura el ejecutable de Git utilizado por la aplicación.", + "gitDetected": "Git detectado", + "gitNotFound": "Git no encontrado en el sistema", + "gitVersion": "Versión", + "gitPath": "Ruta", + "customGitPath": "Ruta personalizada de Git", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "Dejar vacío para usar la ruta detectada automáticamente.", + "test": "Probar", + "testing": "Probando...", + "testSuccess": "El ejecutable de Git es válido.", + "testFailed": "Prueba de Git fallida: {message}", + "save": "Guardar", + "saving": "Guardando...", + "saveSuccess": "Configuración de Git guardada.", + "saveFailed": "Error al guardar: {message}", + "githubTitle": "Cuentas de GitHub", + "githubDescription": "Gestiona las cuentas de GitHub para autenticación. Los tokens se almacenan localmente.", + "noAccounts": "No hay cuentas de GitHub configuradas.", + "addAccount": "Añadir cuenta", + "serverUrl": "URL del servidor", + "serverUrlPlaceholder": "https://github.com", + "token": "Token de acceso personal", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "Genera un token en GitHub → Settings → Developer settings → Personal access tokens.", + "validateAndAdd": "Validar y añadir", + "validating": "Validando...", + "addSuccess": "Cuenta {username} añadida correctamente.", + "addFailed": "Error al añadir cuenta: {message}", + "testConnection": "Probar", + "connectionSuccess": "Conexión exitosa.", + "connectionFailed": "Conexión fallida: {message}", + "setDefault": "Establecer como predeterminada", + "defaultLabel": "Predeterminada", + "defaultSet": "Cuenta predeterminada actualizada.", + "removeAccount": "Eliminar", + "removeConfirmTitle": "Eliminar cuenta", + "removeConfirmMessage": "¿Estás seguro de que deseas eliminar la cuenta \"{username}\"?", + "removeConfirm": "Eliminar", + "removeCancel": "Cancelar", + "removeSuccess": "Cuenta eliminada.", + "scopes": "Alcances", + "loadFailed": "Error al cargar configuración: {message}" + }, "ShortcutSettings": { "sectionTitle": "Atajos", "resetDefault": "Restablecer valores predeterminados", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index dd4737c..6ee8a63 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "Raccourcis", + "version_control": "Contrôle de version", "system": "Système" } }, @@ -128,6 +129,55 @@ "unknown": "La mise à jour a échoué. Veuillez réessayer plus tard." } }, + "VersionControlSettings": { + "loading": "Chargement...", + "sectionTitle": "Contrôle de version", + "sectionDescription": "Configurez l'exécutable Git et gérez les comptes GitHub.", + "gitTitle": "Configuration Git", + "gitDescription": "Configurez l'exécutable Git utilisé par l'application.", + "gitDetected": "Git détecté", + "gitNotFound": "Git introuvable sur le système", + "gitVersion": "Version", + "gitPath": "Chemin", + "customGitPath": "Chemin Git personnalisé", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "Laissez vide pour utiliser le chemin détecté automatiquement.", + "test": "Tester", + "testing": "Test en cours...", + "testSuccess": "L'exécutable Git est valide.", + "testFailed": "Test Git échoué : {message}", + "save": "Enregistrer", + "saving": "Enregistrement...", + "saveSuccess": "Paramètres Git enregistrés.", + "saveFailed": "Échec de l'enregistrement : {message}", + "githubTitle": "Comptes GitHub", + "githubDescription": "Gérez les comptes GitHub pour l'authentification. Les jetons sont stockés localement.", + "noAccounts": "Aucun compte GitHub configuré.", + "addAccount": "Ajouter un compte", + "serverUrl": "URL du serveur", + "serverUrlPlaceholder": "https://github.com", + "token": "Jeton d'accès personnel", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "Générez un jeton dans GitHub → Settings → Developer settings → Personal access tokens.", + "validateAndAdd": "Valider et ajouter", + "validating": "Validation...", + "addSuccess": "Compte {username} ajouté avec succès.", + "addFailed": "Échec de l'ajout du compte : {message}", + "testConnection": "Tester", + "connectionSuccess": "Connexion réussie.", + "connectionFailed": "Échec de la connexion : {message}", + "setDefault": "Définir par défaut", + "defaultLabel": "Par défaut", + "defaultSet": "Compte par défaut mis à jour.", + "removeAccount": "Supprimer", + "removeConfirmTitle": "Supprimer le compte", + "removeConfirmMessage": "Êtes-vous sûr de vouloir supprimer le compte « {username} » ?", + "removeConfirm": "Supprimer", + "removeCancel": "Annuler", + "removeSuccess": "Compte supprimé.", + "scopes": "Portées", + "loadFailed": "Échec du chargement des paramètres : {message}" + }, "ShortcutSettings": { "sectionTitle": "Raccourcis", "resetDefault": "Rétablir les valeurs par défaut", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index d4b9dc7..952a40e 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "ショートカット", + "version_control": "バージョン管理", "system": "システム" } }, @@ -128,6 +129,55 @@ "unknown": "更新に失敗しました。しばらくしてから再試行してください。" } }, + "VersionControlSettings": { + "loading": "読み込み中...", + "sectionTitle": "バージョン管理", + "sectionDescription": "Git 実行ファイルの設定と GitHub アカウントの管理。", + "gitTitle": "Git 設定", + "gitDescription": "アプリケーションで使用する Git 実行ファイルを設定します。", + "gitDetected": "Git が検出されました", + "gitNotFound": "システムに Git が見つかりません", + "gitVersion": "バージョン", + "gitPath": "パス", + "customGitPath": "カスタム Git パス", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "空欄の場合、自動検出されたパスを使用します。", + "test": "テスト", + "testing": "テスト中...", + "testSuccess": "Git 実行ファイルは有効です。", + "testFailed": "Git テスト失敗:{message}", + "save": "保存", + "saving": "保存中...", + "saveSuccess": "Git 設定を保存しました。", + "saveFailed": "保存に失敗しました:{message}", + "githubTitle": "GitHub アカウント", + "githubDescription": "認証用の GitHub アカウントを管理します。トークンはローカルに保存されます。", + "noAccounts": "GitHub アカウントが設定されていません。", + "addAccount": "アカウントを追加", + "serverUrl": "サーバー URL", + "serverUrlPlaceholder": "https://github.com", + "token": "個人アクセストークン", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "GitHub → Settings → Developer settings → Personal access tokens でトークンを生成してください。", + "validateAndAdd": "検証して追加", + "validating": "検証中...", + "addSuccess": "アカウント {username} を追加しました。", + "addFailed": "アカウントの追加に失敗しました:{message}", + "testConnection": "テスト", + "connectionSuccess": "接続成功。", + "connectionFailed": "接続失敗:{message}", + "setDefault": "デフォルトに設定", + "defaultLabel": "デフォルト", + "defaultSet": "デフォルトアカウントを更新しました。", + "removeAccount": "削除", + "removeConfirmTitle": "アカウントを削除", + "removeConfirmMessage": "アカウント「{username}」を削除してもよろしいですか?", + "removeConfirm": "削除", + "removeCancel": "キャンセル", + "removeSuccess": "アカウントを削除しました。", + "scopes": "スコープ", + "loadFailed": "設定の読み込みに失敗しました:{message}" + }, "ShortcutSettings": { "sectionTitle": "ショートカット", "resetDefault": "デフォルトに戻す", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 4f515ef..e4c73d9 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "단축키", + "version_control": "버전 관리", "system": "시스템" } }, @@ -128,6 +129,55 @@ "unknown": "업데이트에 실패했습니다. 잠시 후 다시 시도하세요." } }, + "VersionControlSettings": { + "loading": "로딩 중...", + "sectionTitle": "버전 관리", + "sectionDescription": "Git 실행 파일을 설정하고 GitHub 계정을 관리합니다.", + "gitTitle": "Git 설정", + "gitDescription": "애플리케이션에서 사용할 Git 실행 파일을 설정합니다.", + "gitDetected": "Git이 감지되었습니다", + "gitNotFound": "시스템에서 Git을 찾을 수 없습니다", + "gitVersion": "버전", + "gitPath": "경로", + "customGitPath": "사용자 지정 Git 경로", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "비워두면 자동 감지된 경로를 사용합니다.", + "test": "테스트", + "testing": "테스트 중...", + "testSuccess": "Git 실행 파일이 유효합니다.", + "testFailed": "Git 테스트 실패: {message}", + "save": "저장", + "saving": "저장 중...", + "saveSuccess": "Git 설정이 저장되었습니다.", + "saveFailed": "저장 실패: {message}", + "githubTitle": "GitHub 계정", + "githubDescription": "인증용 GitHub 계정을 관리합니다. 토큰은 로컬에 저장됩니다.", + "noAccounts": "설정된 GitHub 계정이 없습니다.", + "addAccount": "계정 추가", + "serverUrl": "서버 URL", + "serverUrlPlaceholder": "https://github.com", + "token": "개인 액세스 토큰", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "GitHub → Settings → Developer settings → Personal access tokens에서 토큰을 생성하세요.", + "validateAndAdd": "검증 및 추가", + "validating": "검증 중...", + "addSuccess": "계정 {username}이(가) 추가되었습니다.", + "addFailed": "계정 추가 실패: {message}", + "testConnection": "테스트", + "connectionSuccess": "연결 성공.", + "connectionFailed": "연결 실패: {message}", + "setDefault": "기본값으로 설정", + "defaultLabel": "기본", + "defaultSet": "기본 계정이 업데이트되었습니다.", + "removeAccount": "삭제", + "removeConfirmTitle": "계정 삭제", + "removeConfirmMessage": "계정 \"{username}\"을(를) 삭제하시겠습니까?", + "removeConfirm": "삭제", + "removeCancel": "취소", + "removeSuccess": "계정이 삭제되었습니다.", + "scopes": "범위", + "loadFailed": "설정 로드 실패: {message}" + }, "ShortcutSettings": { "sectionTitle": "단축키", "resetDefault": "기본값으로 재설정", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 880673c..b3c7e7e 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "Atalhos", + "version_control": "Controle de versão", "system": "Sistema" } }, @@ -128,6 +129,55 @@ "unknown": "Falha na atualização. Tente novamente mais tarde." } }, + "VersionControlSettings": { + "loading": "Carregando...", + "sectionTitle": "Controle de versão", + "sectionDescription": "Configure o executável Git e gerencie contas do GitHub.", + "gitTitle": "Configuração do Git", + "gitDescription": "Configure o executável Git usado pelo aplicativo.", + "gitDetected": "Git detectado", + "gitNotFound": "Git não encontrado no sistema", + "gitVersion": "Versão", + "gitPath": "Caminho", + "customGitPath": "Caminho personalizado do Git", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "Deixe vazio para usar o caminho detectado automaticamente.", + "test": "Testar", + "testing": "Testando...", + "testSuccess": "Executável Git é válido.", + "testFailed": "Teste do Git falhou: {message}", + "save": "Salvar", + "saving": "Salvando...", + "saveSuccess": "Configurações do Git salvas.", + "saveFailed": "Falha ao salvar: {message}", + "githubTitle": "Contas do GitHub", + "githubDescription": "Gerencie contas do GitHub para autenticação. Os tokens são armazenados localmente.", + "noAccounts": "Nenhuma conta do GitHub configurada.", + "addAccount": "Adicionar conta", + "serverUrl": "URL do servidor", + "serverUrlPlaceholder": "https://github.com", + "token": "Token de acesso pessoal", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "Gere um token em GitHub → Settings → Developer settings → Personal access tokens.", + "validateAndAdd": "Validar e adicionar", + "validating": "Validando...", + "addSuccess": "Conta {username} adicionada com sucesso.", + "addFailed": "Falha ao adicionar conta: {message}", + "testConnection": "Testar", + "connectionSuccess": "Conexão bem-sucedida.", + "connectionFailed": "Falha na conexão: {message}", + "setDefault": "Definir como padrão", + "defaultLabel": "Padrão", + "defaultSet": "Conta padrão atualizada.", + "removeAccount": "Remover", + "removeConfirmTitle": "Remover conta", + "removeConfirmMessage": "Tem certeza de que deseja remover a conta \"{username}\"?", + "removeConfirm": "Remover", + "removeCancel": "Cancelar", + "removeSuccess": "Conta removida.", + "scopes": "Escopos", + "loadFailed": "Falha ao carregar configurações: {message}" + }, "ShortcutSettings": { "sectionTitle": "Atalhos", "resetDefault": "Restaurar padrões", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index e7d1fd2..a7da336 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "快捷键", + "version_control": "版本控制", "system": "系统" } }, @@ -128,6 +129,55 @@ "unknown": "更新失败,请稍后重试。" } }, + "VersionControlSettings": { + "loading": "加载中...", + "sectionTitle": "版本控制", + "sectionDescription": "配置 Git 可执行文件并管理 GitHub 账号。", + "gitTitle": "Git 配置", + "gitDescription": "配置应用使用的 Git 可执行文件。", + "gitDetected": "已检测到 Git", + "gitNotFound": "未在系统中找到 Git", + "gitVersion": "版本", + "gitPath": "路径", + "customGitPath": "自定义 Git 路径", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "留空则使用自动检测的路径。", + "test": "测试", + "testing": "测试中...", + "testSuccess": "Git 可执行文件有效。", + "testFailed": "Git 测试失败:{message}", + "save": "保存", + "saving": "保存中...", + "saveSuccess": "Git 设置已保存。", + "saveFailed": "保存失败:{message}", + "githubTitle": "GitHub 账号", + "githubDescription": "管理用于身份验证的 GitHub 账号。令牌存储在本地。", + "noAccounts": "暂无 GitHub 账号。", + "addAccount": "添加账号", + "serverUrl": "服务器地址", + "serverUrlPlaceholder": "https://github.com", + "token": "个人访问令牌", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "在 GitHub → Settings → Developer settings → Personal access tokens 中生成令牌。", + "validateAndAdd": "验证并添加", + "validating": "验证中...", + "addSuccess": "账号 {username} 添加成功。", + "addFailed": "添加账号失败:{message}", + "testConnection": "测试", + "connectionSuccess": "连接成功。", + "connectionFailed": "连接失败:{message}", + "setDefault": "设为默认", + "defaultLabel": "默认", + "defaultSet": "默认账号已更新。", + "removeAccount": "删除", + "removeConfirmTitle": "删除账号", + "removeConfirmMessage": "确定要删除账号「{username}」吗?", + "removeConfirm": "删除", + "removeCancel": "取消", + "removeSuccess": "账号已删除。", + "scopes": "权限范围", + "loadFailed": "加载设置失败:{message}" + }, "ShortcutSettings": { "sectionTitle": "快捷键", "resetDefault": "恢复默认", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index e8bbe37..8a8d8c5 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -66,6 +66,7 @@ "mcp": "MCP", "skills": "Skills", "shortcuts": "快捷鍵", + "version_control": "版本控制", "system": "系統" } }, @@ -128,6 +129,55 @@ "unknown": "更新失敗,請稍後再試。" } }, + "VersionControlSettings": { + "loading": "載入中...", + "sectionTitle": "版本控制", + "sectionDescription": "設定 Git 執行檔並管理 GitHub 帳號。", + "gitTitle": "Git 設定", + "gitDescription": "設定應用程式使用的 Git 執行檔。", + "gitDetected": "已偵測到 Git", + "gitNotFound": "未在系統中找到 Git", + "gitVersion": "版本", + "gitPath": "路徑", + "customGitPath": "自訂 Git 路徑", + "customGitPathPlaceholder": "/usr/bin/git", + "customGitPathHint": "留空則使用自動偵測的路徑。", + "test": "測試", + "testing": "測試中...", + "testSuccess": "Git 執行檔有效。", + "testFailed": "Git 測試失敗:{message}", + "save": "儲存", + "saving": "儲存中...", + "saveSuccess": "Git 設定已儲存。", + "saveFailed": "儲存失敗:{message}", + "githubTitle": "GitHub 帳號", + "githubDescription": "管理用於身份驗證的 GitHub 帳號。權杖儲存在本機。", + "noAccounts": "尚未設定 GitHub 帳號。", + "addAccount": "新增帳號", + "serverUrl": "伺服器網址", + "serverUrlPlaceholder": "https://github.com", + "token": "個人存取權杖", + "tokenPlaceholder": "ghp_xxxxxxxxxxxx", + "tokenHint": "在 GitHub → Settings → Developer settings → Personal access tokens 中產生權杖。", + "validateAndAdd": "驗證並新增", + "validating": "驗證中...", + "addSuccess": "帳號 {username} 新增成功。", + "addFailed": "新增帳號失敗:{message}", + "testConnection": "測試", + "connectionSuccess": "連線成功。", + "connectionFailed": "連線失敗:{message}", + "setDefault": "設為預設", + "defaultLabel": "預設", + "defaultSet": "預設帳號已更新。", + "removeAccount": "刪除", + "removeConfirmTitle": "刪除帳號", + "removeConfirmMessage": "確定要刪除帳號「{username}」嗎?", + "removeConfirm": "刪除", + "removeCancel": "取消", + "removeSuccess": "帳號已刪除。", + "scopes": "權限範圍", + "loadFailed": "載入設定失敗:{message}" + }, "ShortcutSettings": { "sectionTitle": "快捷鍵", "resetDefault": "恢復預設", diff --git a/src/lib/tauri.ts b/src/lib/tauri.ts index 54fc14f..cea5479 100644 --- a/src/lib/tauri.ts +++ b/src/lib/tauri.ts @@ -41,6 +41,10 @@ import type { GitLogEntry, SystemLanguageSettings, SystemProxySettings, + GitDetectResult, + GitSettings, + GitHubAccountsSettings, + GitHubTokenValidation, McpAppType, LocalMcpServer, McpMarketplaceProvider, @@ -302,6 +306,43 @@ export async function updateSystemLanguageSettings( return invoke("update_system_language_settings", { settings }) } +// --- Version Control --- + +export async function detectGit(): Promise { + return invoke("detect_git") +} + +export async function testGitPath(path: string): Promise { + return invoke("test_git_path", { path }) +} + +export async function getGitSettings(): Promise { + return invoke("get_git_settings") +} + +export async function updateGitSettings( + settings: GitSettings +): Promise { + return invoke("update_git_settings", { settings }) +} + +export async function getGitHubAccounts(): Promise { + return invoke("get_github_accounts") +} + +export async function validateGitHubToken( + serverUrl: string, + token: string +): Promise { + return invoke("validate_github_token", { serverUrl, token }) +} + +export async function updateGitHubAccounts( + settings: GitHubAccountsSettings +): Promise { + return invoke("update_github_accounts", { settings }) +} + export async function mcpScanLocal(): Promise { return invoke("mcp_scan_local") } diff --git a/src/lib/types.ts b/src/lib/types.ts index 08acedd..ccfa7d8 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -523,6 +523,41 @@ export interface SystemLanguageSettings { language: AppLocale } +// --- Version Control --- + +export interface GitDetectResult { + installed: boolean + version: string | null + path: string | null +} + +export interface GitSettings { + custom_path: string | null +} + +export interface GitHubAccount { + id: string + server_url: string + username: string + token: string + scopes: string[] + avatar_url: string | null + is_default: boolean + created_at: string +} + +export interface GitHubAccountsSettings { + accounts: GitHubAccount[] +} + +export interface GitHubTokenValidation { + success: boolean + username: string | null + scopes: string[] + avatar_url: string | null + message: string | null +} + export type McpAppType = "claude_code" | "codex" | "open_code" export interface LocalMcpServer {