Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions apps/codex-plus-launcher/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,27 +859,46 @@ impl BridgeRuntimeService for LauncherRuntimeService {
}))
}

async fn open_manager(&self) -> anyhow::Result<Value> {
let target = codex_plus_core::install::spawn_companion(
codex_plus_core::install::MANAGER_BINARY,
std::iter::empty::<&str>(),
)
.map_err(|error| anyhow::anyhow!("启动管理工具失败:{error}"))?;
async fn open_manager(&self, payload: Value) -> anyhow::Result<Value> {
let navigation =
codex_plus_core::manager_navigation::save_pending_manager_navigation_from_payload(
&payload,
)?;
let target = codex_plus_core::install::open_or_activate_manager()
.map_err(|error| anyhow::anyhow!("启动管理工具失败:{error}"))
.map_err(|error| {
codex_plus_core::manager_navigation::rollback_pending_manager_navigation_after_launch_failure(
navigation.as_ref(),
error,
)
})?;
Ok(json!({
"status": "ok",
"path": target
"path": target,
"navigation": navigation
}))
}

async fn open_transient_manager(&self) -> anyhow::Result<Value> {
async fn open_transient_manager(&self, payload: Value) -> anyhow::Result<Value> {
let navigation =
codex_plus_core::manager_navigation::save_pending_manager_navigation_from_payload(
&payload,
)?;
let target = codex_plus_core::install::spawn_companion(
codex_plus_core::install::MANAGER_BINARY,
["--transient"],
)
.map_err(|error| anyhow::anyhow!("启动管理工具失败:{error}"))?;
.map_err(|error| anyhow::anyhow!("启动管理工具失败:{error}"))
.map_err(|error| {
codex_plus_core::manager_navigation::rollback_pending_manager_navigation_after_launch_failure(
navigation.as_ref(),
error,
)
})?;
Ok(json!({
"status": "ok",
"path": target
"path": target,
"navigation": navigation
}))
}

Expand Down
7 changes: 7 additions & 0 deletions apps/codex-plus-manager/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,13 @@ pub fn startup_options() -> CommandResult<StartupPayload> {
)
}

#[tauri::command]
pub fn consume_pending_manager_navigation(
) -> Result<Option<codex_plus_core::manager_navigation::ManagerNavigationIntent>, String> {
codex_plus_core::manager_navigation::consume_pending_manager_navigation()
.map_err(|error| error.to_string())
}

pub fn startup_should_show_update() -> bool {
should_show_update(
std::env::args(),
Expand Down
8 changes: 7 additions & 1 deletion apps/codex-plus-manager/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering};

use tauri::menu::{Menu, MenuItem};
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
use tauri::{Manager, WindowEvent};
use tauri::{Emitter, Manager, WindowEvent};

const TRAY_ID: &str = "codex_plus_tray";

Expand All @@ -14,6 +14,7 @@ const TRAY_MENU_SHOW: &str = "tray_show_main";
const TRAY_MENU_DREAM_SKIN_APPLY: &str = "tray_apply_dream_skin";
const TRAY_MENU_QUIT: &str = "tray_quit_app";
const DREAM_SKIN_DEBUG_PORT: u16 = 9229;
const MANAGER_NAVIGATION_EVENT: &str = "manager-navigation-requested";

pub fn run() {
install_panic_logger();
Expand Down Expand Up @@ -65,6 +66,7 @@ pub fn run() {
.invoke_handler(tauri::generate_handler![
commands::backend_version,
commands::startup_options,
commands::consume_pending_manager_navigation,
commands::load_overview,
commands::launch_codex_plus,
commands::restart_codex_plus,
Expand Down Expand Up @@ -271,13 +273,17 @@ fn register_main_window_events<R: tauri::Runtime>(
let minimized_window = event_window.clone();
let close_event_window = event_window.clone();
let close_event_app = event_window.app_handle().clone();
let focus_event_window = event_window.clone();

event_window.on_window_event(move |event| match event {
WindowEvent::Resized(_) => {
if matches!(minimized_window.is_minimized(), Ok(true)) {
let _ = minimized_window.hide();
}
}
WindowEvent::Focused(true) => {
let _ = focus_event_window.emit(MANAGER_NAVIGATION_EVENT, ());
}
WindowEvent::CloseRequested { api, .. } => {
if APP_EXITING.load(Ordering::SeqCst) {
return;
Expand Down
65 changes: 62 additions & 3 deletions apps/codex-plus-manager/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -827,9 +827,17 @@ type StartupResult = CommandResult<{
showUpdate: boolean;
}>;

type ManagerNavigationIntent = {
page: "settings";
section?: "stepwise";
};

type Route = "overview" | "relay" | "relayEnvironment" | "sessions" | "context" | "weixin" | "enhance" | "dreamSkin" | "zedRemote" | "userScripts" | "recommendations" | "maintenance" | "about" | "settings";
type Theme = "dark" | "light";

const MANAGER_NAVIGATION_EVENT = "manager-navigation-requested";
const SETTINGS_STEPWISE_SECTION_ID = "settings-stepwise";

const routes: Array<{ id: Route; label: string; icon: LucideIcon; badge?: string }> = [
{ id: "overview", label: t("概览"), icon: LayoutDashboard },
{ id: "relay", label: t("供应商配置"), icon: KeyRound },
Expand Down Expand Up @@ -966,6 +974,7 @@ const defaultSettings: BackendSettings = {
export function App() {
const [theme, setTheme] = useState<Theme>(() => loadInitialTheme());
const [route, setRoute] = useState<Route>(() => loadInitialRoute());
const [pendingSettingsSection, setPendingSettingsSection] = useState<ManagerNavigationIntent["section"] | null>(null);
const [notice, setNotice] = useState<{ title: string; message: string; status?: Status } | null>(null);
const [confirmDialog, setConfirmDialog] = useState<{
title: string;
Expand Down Expand Up @@ -2700,6 +2709,22 @@ export function App() {
await call<void>("manager_hide_to_tray");
};

const consumePendingManagerNavigation = async (): Promise<boolean> => {
try {
const navigation = await invoke<ManagerNavigationIntent | null>("consume_pending_manager_navigation");
if (!navigation) return false;
if (navigation.page === "settings") {
setPendingSettingsSection(navigation.section ?? null);
setRoute("settings");
await refreshSettings(true);
return true;
}
} catch (error) {
logDiagnostic("manager.navigation_failed", { error: stringifyError(error) });
}
return false;
};

const showResultNotice = (
title: string,
result: Pick<CommandResult<unknown>, "message" | "status">,
Expand All @@ -2712,14 +2737,15 @@ export function App() {
useEffect(() => {
void (async () => {
const startup = await run(() => call<StartupResult>("startup_options"));
if (startup?.showUpdate) {
const handledNavigation = await consumePendingManagerNavigation();
if (!handledNavigation && startup?.showUpdate) {
setRoute("about");
void checkUpdate(false);
} else {
void checkUpdate(true);
}
await refreshOverview(true);
await refreshSettings(true);
if (!handledNavigation) await refreshSettings(true);
await refreshRelay(true);
await refreshEnvConflicts(true);
await refreshProviderSyncTargets(true);
Expand All @@ -2729,6 +2755,39 @@ export function App() {
})();
}, []);

useEffect(() => {
let disposed = false;
let stopListening: (() => void) | undefined;
void listen(MANAGER_NAVIGATION_EVENT, () => {
if (!disposed) void consumePendingManagerNavigation();
}).then((unlisten) => {
if (disposed) unlisten();
else stopListening = unlisten;
});
return () => {
disposed = true;
stopListening?.();
};
}, []);

useEffect(() => {
if (route !== "settings" || pendingSettingsSection !== "stepwise") return;
let secondFrame = 0;
const firstFrame = window.requestAnimationFrame(() => {
secondFrame = window.requestAnimationFrame(() => {
document.getElementById(SETTINGS_STEPWISE_SECTION_ID)?.scrollIntoView({
behavior: "smooth",
block: "start",
});
setPendingSettingsSection(null);
});
});
return () => {
window.cancelAnimationFrame(firstFrame);
if (secondFrame) window.cancelAnimationFrame(secondFrame);
};
}, [pendingSettingsSection, route]);

useEffect(() => {
if (getLanguage() === "en") {
void invoke("update_tray_labels", {
Expand Down Expand Up @@ -6204,7 +6263,7 @@ function SettingsScreen({
placeholder={t("例如 gpt-5.4-mini")}
/>
</Field>
<div className="settings-block stepwise-settings-block">
<div className="settings-block stepwise-settings-block" id={SETTINGS_STEPWISE_SECTION_ID}>
<div className="section-title">Stepwise</div>
<div className="stepwise-settings-section">{t("连接")}</div>
<div className="form-row">
Expand Down
18 changes: 18 additions & 0 deletions crates/codex-plus-core/src/install/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,24 @@ where
Ok(path.to_string_lossy().to_string())
}

pub fn open_or_activate_manager() -> anyhow::Result<String> {
#[cfg(target_os = "macos")]
{
let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("."));
if let Some(bundle_id) = macos_companion_bundle_identifier_from_exe(&exe, MANAGER_BINARY) {
let activated = Command::new("/usr/bin/open")
.args(["-b", bundle_id])
.status()
.is_ok_and(|status| status.success());
if activated {
return Ok(format!("bundle:{bundle_id}"));
}
}
}

spawn_companion(MANAGER_BINARY, std::iter::empty::<&str>())
}

pub fn macos_companion_bundle_identifier_from_exe(
exe: &Path,
binary: &str,
Expand Down
1 change: 1 addition & 0 deletions crates/codex-plus-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod env_conflicts;
pub mod http_client;
pub mod install;
pub mod launcher;
pub mod manager_navigation;
pub mod model_catalog;
pub mod model_suffix;
pub mod models;
Expand Down
Loading
Loading