设置界面支持版本控制和github账号管理
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -5,4 +5,5 @@ pub mod folders;
|
||||
pub mod mcp;
|
||||
pub mod system_settings;
|
||||
pub mod terminal;
|
||||
pub mod version_control;
|
||||
pub mod windows;
|
||||
|
||||
288
src-tauri/src/commands/version_control.rs
Normal file
288
src-tauri/src/commands/version_control.rs
Normal file
@@ -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<GitDetectResult, AppCommandError> {
|
||||
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<String> {
|
||||
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<GitDetectResult, AppCommandError> {
|
||||
// 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<GitDetectResult, AppCommandError> {
|
||||
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<GitSettings, AppCommandError> {
|
||||
let raw = app_metadata_service::get_value(conn, GIT_SETTINGS_KEY)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
match raw {
|
||||
Some(raw) => serde_json::from_str::<GitSettings>(&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<GitSettings, AppCommandError> {
|
||||
load_git_settings(&db.conn).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_git_settings(
|
||||
settings: GitSettings,
|
||||
db: State<'_, AppDatabase>,
|
||||
) -> Result<GitSettings, AppCommandError> {
|
||||
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<GitHubAccountsSettings, AppCommandError> {
|
||||
let raw = app_metadata_service::get_value(conn, GITHUB_ACCOUNTS_KEY)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
match raw {
|
||||
Some(raw) => serde_json::from_str::<GitHubAccountsSettings>(&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<GitHubAccountsSettings, AppCommandError> {
|
||||
load_github_accounts(&db.conn).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_github_accounts(
|
||||
settings: GitHubAccountsSettings,
|
||||
db: State<'_, AppDatabase>,
|
||||
) -> Result<GitHubAccountsSettings, AppCommandError> {
|
||||
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<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn validate_github_token(
|
||||
server_url: String,
|
||||
token: String,
|
||||
) -> Result<GitHubTokenValidation, AppCommandError> {
|
||||
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<String> = 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::<GitHubUserResponse>()
|
||||
.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,
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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<String>,
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct GitSettings {
|
||||
pub custom_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GitHubAccount {
|
||||
pub id: String,
|
||||
pub server_url: String,
|
||||
pub username: String,
|
||||
pub token: String,
|
||||
pub scopes: Vec<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub is_default: bool,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct GitHubAccountsSettings {
|
||||
pub accounts: Vec<GitHubAccount>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GitHubTokenValidation {
|
||||
pub success: bool,
|
||||
pub username: Option<String>,
|
||||
pub scopes: Vec<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
5
src/app/settings/version-control/page.tsx
Normal file
5
src/app/settings/version-control/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { VersionControlSettings } from "@/components/settings/version-control-settings"
|
||||
|
||||
export default function SettingsVersionControlPage() {
|
||||
return <VersionControlSettings />
|
||||
}
|
||||
179
src/components/settings/add-github-account-dialog.tsx
Normal file
179
src/components/settings/add-github-account-dialog.tsx
Normal file
@@ -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<string | null>(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 (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("addAccount")}</DialogTitle>
|
||||
<DialogDescription>{t("githubDescription")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-2">
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{t("serverUrl")}
|
||||
</label>
|
||||
<Input
|
||||
value={serverUrl}
|
||||
onChange={(e) => setServerUrl(e.target.value)}
|
||||
placeholder={t("serverUrlPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{t("token")}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showToken ? "text" : "password"}
|
||||
value={token}
|
||||
onChange={(e) => {
|
||||
setToken(e.target.value)
|
||||
setError(null)
|
||||
}}
|
||||
placeholder={t("tokenPlaceholder")}
|
||||
className="pr-9"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 h-6 w-6 p-0"
|
||||
onClick={() => setShowToken(!showToken)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{showToken ? (
|
||||
<EyeOff className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Eye className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{t("tokenHint")}
|
||||
</p>
|
||||
</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
|
||||
onClick={handleSubmit}
|
||||
disabled={validating || !token.trim()}
|
||||
>
|
||||
{validating ? (
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
{t("validating")}
|
||||
</>
|
||||
) : (
|
||||
t("validateAndAdd")
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
477
src/components/settings/version-control-settings.tsx
Normal file
477
src/components/settings/version-control-settings.tsx
Normal file
@@ -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<GitDetectResult | null>(null)
|
||||
const [customPath, setCustomPath] = useState("")
|
||||
const [savingGit, setSavingGit] = useState(false)
|
||||
const [testingGit, setTestingGit] = useState(false)
|
||||
const [testResult, setTestResult] = useState<GitDetectResult | null>(null)
|
||||
|
||||
const [accounts, setAccounts] = useState<GitHubAccountsSettings>({
|
||||
accounts: [],
|
||||
})
|
||||
const [addDialogOpen, setAddDialogOpen] = useState(false)
|
||||
const [testingAccountId, setTestingAccountId] = useState<string | null>(null)
|
||||
const [removeTarget, setRemoveTarget] = useState<GitHubAccount | null>(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 (
|
||||
<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="h-full overflow-auto">
|
||||
<div className="w-full space-y-4">
|
||||
<section className="space-y-1">
|
||||
<h1 className="text-sm font-semibold">{t("sectionTitle")}</h1>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("sectionDescription")}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Git Configuration */}
|
||||
<section className="rounded-xl border bg-card p-4 space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||
<h2 className="text-sm font-semibold">{t("gitTitle")}</h2>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground leading-5">
|
||||
{t("gitDescription")}
|
||||
</p>
|
||||
|
||||
{/* Git status */}
|
||||
<div className="rounded-md border bg-muted/20 px-3 py-3 text-xs space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{gitInfo?.installed ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
|
||||
<span className="text-green-600 dark:text-green-400 font-medium">
|
||||
{t("gitDetected")}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<XCircle className="h-3.5 w-3.5 text-red-500" />
|
||||
<span className="text-red-600 dark:text-red-400 font-medium">
|
||||
{t("gitNotFound")}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{gitInfo?.version && (
|
||||
<p className="text-muted-foreground">
|
||||
{t("gitVersion")}: {gitInfo.version}
|
||||
</p>
|
||||
)}
|
||||
{gitInfo?.path && (
|
||||
<p className="text-muted-foreground">
|
||||
{t("gitPath")}: {gitInfo.path}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Custom path */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{t("customGitPath")}
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={customPath}
|
||||
onChange={(e) => {
|
||||
setCustomPath(e.target.value)
|
||||
setTestResult(null)
|
||||
}}
|
||||
placeholder={t("customGitPathPlaceholder")}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleTestGit}
|
||||
disabled={testingGit}
|
||||
>
|
||||
{testingGit ? (
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
{t("testing")}
|
||||
</>
|
||||
) : (
|
||||
t("test")
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{t("customGitPathHint")}
|
||||
</p>
|
||||
{testResult && (
|
||||
<div
|
||||
className={`flex items-center gap-1.5 text-xs ${
|
||||
testResult.installed
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-red-600 dark:text-red-400"
|
||||
}`}
|
||||
>
|
||||
{testResult.installed ? (
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
) : (
|
||||
<XCircle className="h-3 w-3" />
|
||||
)}
|
||||
{testResult.installed
|
||||
? `${t("testSuccess")} (${testResult.version})`
|
||||
: t("testFailed", { message: "invalid" })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" onClick={handleSaveGit} disabled={savingGit}>
|
||||
{savingGit ? (
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
{t("saving")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-3.5 w-3.5" />
|
||||
{t("save")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GitHub Accounts */}
|
||||
<section className="rounded-xl border bg-card p-4 space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Github className="h-4 w-4 text-muted-foreground" />
|
||||
<h2 className="text-sm font-semibold">{t("githubTitle")}</h2>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground leading-5">
|
||||
{t("githubDescription")}
|
||||
</p>
|
||||
|
||||
{/* Account list */}
|
||||
{accounts.accounts.length === 0 ? (
|
||||
<div className="rounded-md border border-dashed bg-muted/10 px-4 py-6 text-center text-xs text-muted-foreground">
|
||||
{t("noAccounts")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{accounts.accounts.map((account) => (
|
||||
<div
|
||||
key={account.id}
|
||||
className="flex items-center gap-3 rounded-lg border bg-muted/10 px-3 py-2.5"
|
||||
>
|
||||
{/* Avatar */}
|
||||
{account.avatar_url ? (
|
||||
<img
|
||||
src={account.avatar_url}
|
||||
alt={account.username}
|
||||
className="h-8 w-8 rounded-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-8 w-8 rounded-full bg-muted flex items-center justify-center text-xs font-medium">
|
||||
{account.username[0]?.toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info */}
|
||||
<div className="flex-1 min-w-0 space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium truncate">
|
||||
{account.username}
|
||||
</span>
|
||||
{account.is_default && (
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
|
||||
{t("defaultLabel")}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
|
||||
<span className="truncate">{account.server_url}</span>
|
||||
{account.scopes.length > 0 && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span className="truncate">
|
||||
{account.scopes.join(", ")}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
onClick={() => handleTestConnection(account)}
|
||||
disabled={testingAccountId === account.id}
|
||||
>
|
||||
{testingAccountId === account.id ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
t("testConnection")
|
||||
)}
|
||||
</Button>
|
||||
{!account.is_default && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
onClick={() => handleSetDefault(account.id)}
|
||||
>
|
||||
{t("setDefault")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => setRemoveTarget(account)}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" onClick={() => setAddDialogOpen(true)}>
|
||||
{t("addAccount")}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Add Account Dialog */}
|
||||
<AddGitHubAccountDialog
|
||||
open={addDialogOpen}
|
||||
onOpenChange={setAddDialogOpen}
|
||||
onAccountAdded={handleAccountAdded}
|
||||
isFirstAccount={accounts.accounts.length === 0}
|
||||
/>
|
||||
|
||||
{/* Remove Confirmation */}
|
||||
<AlertDialog
|
||||
open={!!removeTarget}
|
||||
onOpenChange={(open) => !open && setRemoveTarget(null)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("removeConfirmTitle")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("removeConfirmMessage", {
|
||||
username: removeTarget?.username ?? "",
|
||||
})}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("removeCancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleRemoveAccount}>
|
||||
{t("removeConfirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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": "استعادة الإعدادات الافتراضية",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "デフォルトに戻す",
|
||||
|
||||
@@ -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": "기본값으로 재설정",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "恢复默认",
|
||||
|
||||
@@ -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": "恢復預設",
|
||||
|
||||
@@ -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<GitDetectResult> {
|
||||
return invoke("detect_git")
|
||||
}
|
||||
|
||||
export async function testGitPath(path: string): Promise<GitDetectResult> {
|
||||
return invoke("test_git_path", { path })
|
||||
}
|
||||
|
||||
export async function getGitSettings(): Promise<GitSettings> {
|
||||
return invoke("get_git_settings")
|
||||
}
|
||||
|
||||
export async function updateGitSettings(
|
||||
settings: GitSettings
|
||||
): Promise<GitSettings> {
|
||||
return invoke("update_git_settings", { settings })
|
||||
}
|
||||
|
||||
export async function getGitHubAccounts(): Promise<GitHubAccountsSettings> {
|
||||
return invoke("get_github_accounts")
|
||||
}
|
||||
|
||||
export async function validateGitHubToken(
|
||||
serverUrl: string,
|
||||
token: string
|
||||
): Promise<GitHubTokenValidation> {
|
||||
return invoke("validate_github_token", { serverUrl, token })
|
||||
}
|
||||
|
||||
export async function updateGitHubAccounts(
|
||||
settings: GitHubAccountsSettings
|
||||
): Promise<GitHubAccountsSettings> {
|
||||
return invoke("update_github_accounts", { settings })
|
||||
}
|
||||
|
||||
export async function mcpScanLocal(): Promise<LocalMcpServer[]> {
|
||||
return invoke("mcp_scan_local")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user