8 files changed
+257
-54
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cpa-gui",
|
||||
"private": true,
|
||||
"version": "0.2.10",
|
||||
"version": "0.2.11",
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.3.14",
|
||||
"scripts": {
|
||||
|
||||
Generated
+1
-1
@@ -582,7 +582,7 @@ checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
|
||||
[[package]]
|
||||
name = "cpa-gui"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cpa-gui"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
description = "CPA desktop management shell"
|
||||
authors = ["lzt404"]
|
||||
edition = "2021"
|
||||
|
||||
+127
-5
@@ -1765,6 +1765,47 @@ async fn install_pi_provider(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn sync_pi_provider(
|
||||
app: tauri::AppHandle,
|
||||
gui_config_state: tauri::State<'_, GuiConfigState>,
|
||||
cache: tauri::State<'_, AgentConfigStatusCache>,
|
||||
) -> Result<AgentConfigActionResult, String> {
|
||||
let home = app
|
||||
.path()
|
||||
.home_dir()
|
||||
.map_err(|error| format!("鏃犳硶鑾峰彇鐢ㄦ埛鐩綍: {error}"))?;
|
||||
let config = gui_config_state.snapshot()?;
|
||||
let port = config.port;
|
||||
let api_key = effective_agent_api_key(&config).to_string();
|
||||
let result =
|
||||
tauri::async_runtime::spawn_blocking(move || sync_pi_provider_inner(&home, port, &api_key))
|
||||
.await
|
||||
.map_err(|error| format!("鍚屾 Pi 閰嶇疆浠诲姟澶辫触: {error}"))??;
|
||||
cache.clear()?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn uninstall_pi_provider(
|
||||
app: tauri::AppHandle,
|
||||
cache: tauri::State<'_, AgentConfigStatusCache>,
|
||||
) -> Result<AgentConfigActionResult, String> {
|
||||
let home = app
|
||||
.path()
|
||||
.home_dir()
|
||||
.map_err(|error| format!("鏃犳硶鑾峰彇鐢ㄦ埛鐩綍: {error}"))?;
|
||||
let executable =
|
||||
find_pi_executable(&home).ok_or_else(|| "鏈娴嬪埌 Pi CLI锛岃鍏堝畨瑁?Pi".to_string())?;
|
||||
let result = tauri::async_runtime::spawn_blocking(move || {
|
||||
uninstall_pi_provider_inner(&home, &executable)
|
||||
})
|
||||
.await
|
||||
.map_err(|error| format!("鍗歌浇 Pi 鎻掍欢浠诲姟澶辫触: {error}"))??;
|
||||
cache.clear()?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn validate_claude_code_working_directory(value: &str) -> Result<PathBuf, String> {
|
||||
let value = value.trim();
|
||||
if value.is_empty() {
|
||||
@@ -3257,7 +3298,28 @@ fn install_pi_provider_inner(
|
||||
changed_files.push(path_to_string(&settings_path));
|
||||
}
|
||||
|
||||
let mut result = sync_pi_provider_inner(home, port, api_key)?;
|
||||
changed_files.extend(result.changed_files);
|
||||
result.changed_files = changed_files;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn sync_pi_provider_inner(
|
||||
home: &Path,
|
||||
port: u16,
|
||||
api_key: &str,
|
||||
) -> Result<AgentConfigActionResult, String> {
|
||||
if port == 0 {
|
||||
return Err("Core port is invalid".to_string());
|
||||
}
|
||||
if api_key.trim().is_empty() {
|
||||
return Err("EasyCLIProxyAPI has no usable API key".to_string());
|
||||
}
|
||||
let config_path = pi_provider_config_path(home);
|
||||
let mut changed_files = Vec::new();
|
||||
if !pi_provider_package_installed(home)? {
|
||||
return Err("Pi CLIProxyAPI provider is not installed".to_string());
|
||||
}
|
||||
let existing = if config_path.is_file() {
|
||||
Some(fs::read_to_string(&config_path).map_err(|error| {
|
||||
format!(
|
||||
@@ -3284,6 +3346,29 @@ fn install_pi_provider_inner(
|
||||
))
|
||||
}
|
||||
|
||||
fn uninstall_pi_provider_inner(
|
||||
home: &Path,
|
||||
executable: &Path,
|
||||
) -> Result<AgentConfigActionResult, String> {
|
||||
if !pi_provider_package_installed(home)? {
|
||||
return Ok(action_result(
|
||||
"not-installed",
|
||||
false,
|
||||
None,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
remove_pi_package(executable, home)?;
|
||||
Ok(action_result(
|
||||
"removed",
|
||||
false,
|
||||
None,
|
||||
vec![path_to_string(&pi_provider_settings_path(home))],
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
|
||||
fn install_pi_package(executable: &Path, home: &Path) -> Result<(), String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut command = windows_command_for_executable(executable, false);
|
||||
@@ -3315,6 +3400,37 @@ fn install_pi_package(executable: &Path, home: &Path) -> Result<(), String> {
|
||||
))
|
||||
}
|
||||
|
||||
fn remove_pi_package(executable: &Path, home: &Path) -> Result<(), String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut command = windows_command_for_executable(executable, false);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let mut command = Command::new(executable);
|
||||
|
||||
command
|
||||
.arg("remove")
|
||||
.arg(PI_CLIPROXYAPI_PACKAGE)
|
||||
.current_dir(home)
|
||||
.stdin(Stdio::null());
|
||||
configure_background_command(&mut command);
|
||||
let output = command
|
||||
.output()
|
||||
.map_err(|error| format!("鎵ц Pi 鎻掍欢鍗歌浇澶辫触: {error}"))?;
|
||||
if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
let detail = if !stderr.is_empty() { stderr } else { stdout };
|
||||
Err(format!(
|
||||
"Pi CLIProxyAPI provider 鎻掍欢鍗歌浇澶辫触{}",
|
||||
if detail.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(": {detail}")
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
fn codex_configuration_directory(home: &Path) -> PathBuf {
|
||||
env::var_os("CODEX_HOME")
|
||||
.map(PathBuf::from)
|
||||
@@ -17670,11 +17786,15 @@ fn ensure_configuration_watch_directories(
|
||||
fn start_configuration_file_watcher(app: tauri::AppHandle) -> Result<(), String> {
|
||||
let tracked_paths = tracked_configuration_paths(&app)?;
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
let mut watcher: RecommendedWatcher =
|
||||
notify::recommended_watcher(move |event: Result<notify::Event, notify::Error>| {
|
||||
let _ = sender.send(event);
|
||||
})
|
||||
.map_err(|error| format!("创建配置文件监控器失败: {error}"))?;
|
||||
let mut watcher: RecommendedWatcher = notify::recommended_watcher(
|
||||
move |event: Result<notify::Event, notify::Error>| match event {
|
||||
Ok(event) if event.kind.is_access() => {}
|
||||
event => {
|
||||
let _ = sender.send(event);
|
||||
}
|
||||
},
|
||||
)
|
||||
.map_err(|error| format!("创建配置文件监控器失败: {error}"))?;
|
||||
let mut watched_directories = Vec::new();
|
||||
ensure_configuration_watch_directories(&mut watcher, &tracked_paths, &mut watched_directories)?;
|
||||
|
||||
@@ -17929,6 +18049,8 @@ fn main() {
|
||||
refresh_agent_config_statuses,
|
||||
get_agent_models,
|
||||
install_pi_provider,
|
||||
sync_pi_provider,
|
||||
uninstall_pi_provider,
|
||||
check_codex_oauth_login,
|
||||
update_codex_model_catalog,
|
||||
get_thinking_aliases,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "EasyCLIProxyAPI",
|
||||
"version": "0.2.10",
|
||||
"version": "0.2.11",
|
||||
"identifier": "com.cpa.gui",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
|
||||
+18
-8
@@ -441,6 +441,7 @@ export const zhCN = {
|
||||
'agents.list.installedVersion': '已安装 · {version}',
|
||||
'agents.list.installed': '已安装 · 保持原配置',
|
||||
'agents.list.pluginNotInstalled': 'Pi provider 插件未安装',
|
||||
'agents.list.piInstalled': 'Pi 插件已安装',
|
||||
'agents.list.configured': '已配置 · EasyCLIProxyAPI',
|
||||
'agents.model.loading': '正在读取模型',
|
||||
'agents.model.loadFailed': '获取模型失败',
|
||||
@@ -598,10 +599,14 @@ export const zhCN = {
|
||||
'agents.launch.closeChatgpt': '关闭 ChatGPT App',
|
||||
'agents.launch.closingChatgpt': '关闭中',
|
||||
'agents.pi.installTitle': 'Pi CLIProxyAPI provider',
|
||||
'agents.pi.installDescription': '安装插件,并自动配置 Pi 的 baseUrl 和 API key',
|
||||
'agents.pi.install': '安装并配置 Pi 插件',
|
||||
'agents.pi.update': '重新同步 Pi 配置',
|
||||
'agents.pi.installing': '正在安装并配置',
|
||||
'agents.pi.installDescription': '安装插件后自动配置',
|
||||
'agents.pi.install': '安装 Pi 插件',
|
||||
'agents.pi.update': '同步 Pi 配置',
|
||||
'agents.pi.installing': '正在安装插件',
|
||||
'agents.pi.sync': '同步 Pi 配置',
|
||||
'agents.pi.syncing': '正在同步配置',
|
||||
'agents.pi.uninstall': '卸载 Pi 插件',
|
||||
'agents.pi.uninstalling': '正在卸载插件',
|
||||
'agents.default.title': '重置为默认配置',
|
||||
'agents.default.description': '这会使用当前选中模型重写 {name} 的全部相关配置文件,并删除其中其他用户内容。该操作没有备份且无法恢复。',
|
||||
'agents.default.confirm': '确认写入默认配置',
|
||||
@@ -1377,6 +1382,7 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.list.installedVersion': 'Installed · {version}',
|
||||
'agents.list.installed': 'Installed · original configuration',
|
||||
'agents.list.pluginNotInstalled': 'Pi provider not installed',
|
||||
'agents.list.piInstalled': 'Pi plugin installed',
|
||||
'agents.list.configured': 'Configured · EasyCLIProxyAPI',
|
||||
'agents.model.loading': 'Loading models',
|
||||
'agents.model.loadFailed': 'Failed to load models',
|
||||
@@ -1534,10 +1540,14 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.launch.closeChatgpt': 'Close ChatGPT App',
|
||||
'agents.launch.closingChatgpt': 'Closing',
|
||||
'agents.pi.installTitle': 'Pi CLIProxyAPI provider',
|
||||
'agents.pi.installDescription': "Install the plugin and automatically configure Pi's baseUrl and API key.",
|
||||
'agents.pi.install': 'Install and Configure Pi',
|
||||
'agents.pi.update': 'Resync Pi Configuration',
|
||||
'agents.pi.installing': 'Installing and configuring',
|
||||
'agents.pi.installDescription': 'Automatically configure after installing the plugin.',
|
||||
'agents.pi.install': 'Install Pi Plugin',
|
||||
'agents.pi.update': 'Sync Pi Configuration',
|
||||
'agents.pi.installing': 'Installing plugin',
|
||||
'agents.pi.sync': 'Sync Pi Configuration',
|
||||
'agents.pi.syncing': 'Syncing configuration',
|
||||
'agents.pi.uninstall': 'Uninstall Pi Plugin',
|
||||
'agents.pi.uninstalling': 'Uninstalling plugin',
|
||||
'agents.default.title': 'Reset to Default Configuration',
|
||||
'agents.default.description': 'This rewrites every related {name} configuration file using the selected model and deletes all other user content in those files. No backup is created and the operation cannot be undone.',
|
||||
'agents.default.confirm': 'Write Default Configuration',
|
||||
|
||||
+53
-17
@@ -277,11 +277,10 @@ const listStatusText = (status: AgentConfigStatus | undefined) => {
|
||||
if (!status) return translate(locale, 'agents.list.detecting');
|
||||
if (!status.supportedPlatform) return translate(locale, 'agents.list.unsupported');
|
||||
if (!status.installed) return translate(locale, 'agents.list.notInstalled');
|
||||
if (status.id === 'pi' && !status.pluginInstalled) {
|
||||
return translate(locale, 'agents.list.pluginNotInstalled');
|
||||
}
|
||||
if (status.id === 'pi' && status.configured) {
|
||||
return translate(locale, 'agents.list.configured');
|
||||
if (status.id === 'pi') {
|
||||
return status.pluginInstalled
|
||||
? translate(locale, 'agents.list.piInstalled')
|
||||
: translate(locale, 'agents.list.pluginNotInstalled');
|
||||
}
|
||||
if (status.modificationState === 'invalid') return translate(locale, 'agents.status.invalid');
|
||||
if (status.modificationState === 'applied') return translate(locale, 'agents.list.modified', { model: status.appliedModel ?? '—' });
|
||||
@@ -553,7 +552,7 @@ export function AgentsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [modelLoading, setModelLoading] = useState(false);
|
||||
const [busyAction, setBusyAction] = useState<
|
||||
'apply' | 'close-config' | 'default' | 'clear' | 'install-pi' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | 'oauth-check' | 'directory' | null
|
||||
'apply' | 'close-config' | 'default' | 'clear' | 'install-pi' | 'sync-pi' | 'uninstall-pi' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | 'oauth-check' | 'directory' | null
|
||||
>(null);
|
||||
const busy = busyAction !== null;
|
||||
const [detectionError, setDetectionError] = useState('');
|
||||
@@ -937,6 +936,32 @@ export function AgentsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const syncPiProvider = async () => {
|
||||
setBusyAction('sync-pi');
|
||||
setConfigurationError('');
|
||||
try {
|
||||
await invoke<AgentConfigActionResult>('sync_pi_provider');
|
||||
await reloadStatusesAfterAction();
|
||||
} catch (requestError) {
|
||||
setConfigurationError(String(requestError));
|
||||
} finally {
|
||||
setBusyAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
const uninstallPiProvider = async () => {
|
||||
setBusyAction('uninstall-pi');
|
||||
setConfigurationError('');
|
||||
try {
|
||||
await invoke<AgentConfigActionResult>('uninstall_pi_provider');
|
||||
await reloadStatusesAfterAction();
|
||||
} catch (requestError) {
|
||||
setConfigurationError(String(requestError));
|
||||
} finally {
|
||||
setBusyAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
const closeConfigurationChanges = async () => {
|
||||
setConfigurationError('');
|
||||
setBusyAction('close-config');
|
||||
@@ -1197,7 +1222,9 @@ export function AgentsPage() {
|
||||
<span className="agent-client-icon"><AgentMark definition={agent} /></span>
|
||||
<span><strong>{agent.name}</strong><small>{listStatusText(status)}</small></span>
|
||||
<i
|
||||
className={status?.modificationEnabled ? 'configured' : status?.installed ? 'installed' : ''}
|
||||
className={status?.id === 'pi'
|
||||
? status?.installed ? 'installed' : ''
|
||||
: status?.modificationEnabled ? 'configured' : status?.installed ? 'installed' : ''}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
@@ -1243,7 +1270,7 @@ export function AgentsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeStatus?.error || activeStatus?.warnings.length ? (
|
||||
{activeStatus?.error || (!isPiClient && activeStatus?.warnings.length) ? (
|
||||
<div className="agent-status-messages" aria-live="polite">
|
||||
{activeStatus.error ? (
|
||||
<span className="agent-inline-message error" role="alert">{activeStatus.error}</span>
|
||||
@@ -1330,7 +1357,7 @@ export function AgentsPage() {
|
||||
) : null}
|
||||
|
||||
{isPiClient ? (
|
||||
<section className={`agent-core-setting-section agent-modification-actions ${activeStatus?.configured ? 'enabled' : ''}`}>
|
||||
<section className="agent-core-setting-section agent-modification-actions">
|
||||
<div className="agent-section-heading">
|
||||
<div>
|
||||
<strong>{t('agents.pi.installTitle')}</strong>
|
||||
@@ -1341,16 +1368,25 @@ export function AgentsPage() {
|
||||
<div className="agent-modification-buttons">
|
||||
<button
|
||||
type="button"
|
||||
className="primary-button"
|
||||
onClick={() => void installPiProvider()}
|
||||
className={activeStatus?.pluginInstalled ? 'danger-button' : 'primary-button'}
|
||||
onClick={() => void (activeStatus?.pluginInstalled ? uninstallPiProvider() : installPiProvider())}
|
||||
disabled={busy || !activeStatus?.installed || !activeStatus.supportedPlatform}
|
||||
>
|
||||
{busyAction === 'install-pi' ? <LoaderCircle size={16} className="spin" /> : null}
|
||||
{busyAction === 'install-pi'
|
||||
? t('agents.pi.installing')
|
||||
: activeStatus?.configured
|
||||
? t('agents.pi.update')
|
||||
: t('agents.pi.install')}
|
||||
{busyAction === 'install-pi' || busyAction === 'uninstall-pi'
|
||||
? <LoaderCircle size={16} className="spin" />
|
||||
: null}
|
||||
{activeStatus?.pluginInstalled
|
||||
? busyAction === 'uninstall-pi' ? t('agents.pi.uninstalling') : t('agents.pi.uninstall')
|
||||
: busyAction === 'install-pi' ? t('agents.pi.installing') : t('agents.pi.install')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-button"
|
||||
onClick={() => void syncPiProvider()}
|
||||
disabled={busy || !activeStatus?.installed || !activeStatus.supportedPlatform || !activeStatus.pluginInstalled}
|
||||
>
|
||||
{busyAction === 'sync-pi' ? <LoaderCircle size={16} className="spin" /> : <RefreshCw size={16} />}
|
||||
{busyAction === 'sync-pi' ? t('agents.pi.syncing') : t('agents.pi.sync')}
|
||||
</button>
|
||||
</div>
|
||||
{configurationError ? (
|
||||
|
||||
+55
-20
@@ -51,6 +51,18 @@ type ConfigAction =
|
||||
| null;
|
||||
type NoticeTone = 'success' | 'error';
|
||||
type ConfigSubpage = 'general' | 'network' | 'aliases';
|
||||
type NetworkDraftField = 'port' | 'allowLan' | 'proxyUrl' | 'sessionAffinity' | 'sessionTtl';
|
||||
type DraftRefreshMode = 'replace' | 'preserve';
|
||||
|
||||
type NetworkDraftDirty = Record<NetworkDraftField, boolean>;
|
||||
|
||||
const cleanNetworkDraft = (): NetworkDraftDirty => ({
|
||||
port: false,
|
||||
allowLan: false,
|
||||
proxyUrl: false,
|
||||
sessionAffinity: false,
|
||||
sessionTtl: false,
|
||||
});
|
||||
|
||||
const ROUTING_OPTIONS = [
|
||||
{ value: 'round-robin', labelKey: 'config.routing.roundRobin' },
|
||||
@@ -85,6 +97,7 @@ export function ConfigPanelPage() {
|
||||
const [sessionTtlDraft, setSessionTtlDraft] = useState('');
|
||||
const [portError, setPortError] = useState('');
|
||||
const [savedStatusVisible, setSavedStatusVisible] = useState(false);
|
||||
const networkDraftDirtyRef = useRef<NetworkDraftDirty>(cleanNetworkDraft());
|
||||
const noticeTimerRef = useRef<number | null>(null);
|
||||
const copyTimerRef = useRef<number | null>(null);
|
||||
|
||||
@@ -93,7 +106,7 @@ export function ConfigPanelPage() {
|
||||
let stop: (() => void) | null = null;
|
||||
void loadSettings();
|
||||
void listen('config-files-changed', () => {
|
||||
if (!disposed) void loadSettings();
|
||||
if (!disposed) void loadSettings('preserve');
|
||||
}).then((unlisten) => {
|
||||
if (disposed) unlisten();
|
||||
else stop = unlisten;
|
||||
@@ -121,24 +134,41 @@ export function ConfigPanelPage() {
|
||||
}, 3200);
|
||||
};
|
||||
|
||||
const applySettings = (result: CoreConfigSettings, syncNetworkDrafts = true) => {
|
||||
const applySettings = (result: CoreConfigSettings, mode: DraftRefreshMode = 'replace') => {
|
||||
setSettings(result);
|
||||
if (syncNetworkDrafts) {
|
||||
setPortDraft(String(result.port));
|
||||
setAllowLanDraft(result.allowLan);
|
||||
setProxyUrlDraft(result.proxyUrl);
|
||||
setSessionAffinityDraft(result.routingSessionAffinity);
|
||||
setSessionTtlDraft(result.routingSessionAffinityTtl);
|
||||
setPortError('');
|
||||
if (mode === 'preserve') {
|
||||
const dirty = networkDraftDirtyRef.current;
|
||||
if (!dirty.port) setPortDraft(String(result.port));
|
||||
if (!dirty.allowLan) setAllowLanDraft(result.allowLan);
|
||||
if (!dirty.proxyUrl) setProxyUrlDraft(result.proxyUrl);
|
||||
if (!dirty.sessionAffinity) setSessionAffinityDraft(result.routingSessionAffinity);
|
||||
if (!dirty.sessionTtl) setSessionTtlDraft(result.routingSessionAffinityTtl);
|
||||
return;
|
||||
}
|
||||
networkDraftDirtyRef.current = cleanNetworkDraft();
|
||||
setPortDraft(String(result.port));
|
||||
setAllowLanDraft(result.allowLan);
|
||||
setProxyUrlDraft(result.proxyUrl);
|
||||
setSessionAffinityDraft(result.routingSessionAffinity);
|
||||
setSessionTtlDraft(result.routingSessionAffinityTtl);
|
||||
setPortError('');
|
||||
};
|
||||
|
||||
async function loadSettings(syncNetworkDrafts = true) {
|
||||
const markDraftDirty = (field: NetworkDraftField) => {
|
||||
networkDraftDirtyRef.current[field] = true;
|
||||
setSavedStatusVisible(false);
|
||||
};
|
||||
|
||||
const clearDraftDirty = (field: NetworkDraftField) => {
|
||||
networkDraftDirtyRef.current[field] = false;
|
||||
};
|
||||
|
||||
async function loadSettings(mode: DraftRefreshMode = 'replace') {
|
||||
setLoading(true);
|
||||
setLoadError('');
|
||||
try {
|
||||
const result = await invoke<CoreConfigSettings>('get_core_config_settings');
|
||||
applySettings(result, syncNetworkDrafts);
|
||||
applySettings(result, mode);
|
||||
} catch (error) {
|
||||
setSettings(null);
|
||||
setLoadError(String(error));
|
||||
@@ -156,14 +186,14 @@ export function ConfigPanelPage() {
|
||||
setBusyAction(action);
|
||||
try {
|
||||
const result = await invoke<CoreConfigSettings>(command, args);
|
||||
applySettings(result, false);
|
||||
setSettings(result);
|
||||
setLoadError('');
|
||||
showNotice(successMessage, 'success');
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (settings) applySettings(settings, false);
|
||||
if (settings) setSettings(settings);
|
||||
showNotice(t('config.error.saveFailed', { error: String(error) }), 'error');
|
||||
void loadSettings(false);
|
||||
void loadSettings('preserve');
|
||||
return false;
|
||||
} finally {
|
||||
setBusyAction(null);
|
||||
@@ -325,8 +355,10 @@ export function ConfigPanelPage() {
|
||||
|
||||
const openWebUi = async () => {
|
||||
try {
|
||||
const latestSettings = await invoke<CoreConfigSettings>('get_core_config_settings');
|
||||
applySettings(latestSettings, 'preserve');
|
||||
await invoke('open_external_url', {
|
||||
url: webUiManagementUrl(settings?.port ?? 8317),
|
||||
url: webUiManagementUrl(latestSettings.port),
|
||||
});
|
||||
} catch (error) {
|
||||
showNotice(t('config.webuiKey.error.openFailed', { error: String(error) }), 'error');
|
||||
@@ -751,12 +783,13 @@ export function ConfigPanelPage() {
|
||||
aria-invalid={Boolean(portError)}
|
||||
title={portError || t('config.network.portHint')}
|
||||
onChange={(event) => {
|
||||
setSavedStatusVisible(false);
|
||||
markDraftDirty('port');
|
||||
setPortDraft(event.currentTarget.value.replace(/\D/g, '').slice(0, 5));
|
||||
setPortError('');
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape' && settings) {
|
||||
clearDraftDirty('port');
|
||||
setPortDraft(String(settings.port));
|
||||
setPortError('');
|
||||
event.currentTarget.blur();
|
||||
@@ -778,7 +811,7 @@ export function ConfigPanelPage() {
|
||||
checked={allowLanDraft}
|
||||
disabled={controlsDisabled}
|
||||
onChange={(event) => {
|
||||
setSavedStatusVisible(false);
|
||||
markDraftDirty('allowLan');
|
||||
setAllowLanDraft(event.currentTarget.checked);
|
||||
}}
|
||||
/>
|
||||
@@ -798,11 +831,12 @@ export function ConfigPanelPage() {
|
||||
disabled={controlsDisabled}
|
||||
placeholder={t('config.network.proxyPlaceholder')}
|
||||
onChange={(event) => {
|
||||
setSavedStatusVisible(false);
|
||||
markDraftDirty('proxyUrl');
|
||||
setProxyUrlDraft(event.currentTarget.value);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape' && settings) {
|
||||
clearDraftDirty('proxyUrl');
|
||||
setProxyUrlDraft(settings.proxyUrl);
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
@@ -831,7 +865,7 @@ export function ConfigPanelPage() {
|
||||
checked={sessionAffinityDraft}
|
||||
disabled={controlsDisabled}
|
||||
onChange={(event) => {
|
||||
setSavedStatusVisible(false);
|
||||
markDraftDirty('sessionAffinity');
|
||||
setSessionAffinityDraft(event.currentTarget.checked);
|
||||
}}
|
||||
/>
|
||||
@@ -851,11 +885,12 @@ export function ConfigPanelPage() {
|
||||
disabled={controlsDisabled}
|
||||
placeholder="1h"
|
||||
onChange={(event) => {
|
||||
setSavedStatusVisible(false);
|
||||
markDraftDirty('sessionTtl');
|
||||
setSessionTtlDraft(event.currentTarget.value);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape' && settings) {
|
||||
clearDraftDirty('sessionTtl');
|
||||
setSessionTtlDraft(settings.routingSessionAffinityTtl);
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user