feat: add directory selection prompt and update usage descriptions for macOS
This commit is contained in:
@@ -6,5 +6,13 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>NSSupportsSuddenTermination</key>
|
<key>NSSupportsSuddenTermination</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>NSAppleEventsUsageDescription</key>
|
||||||
|
<string>OpenChamber needs to run the OpenCode CLI to provide AI coding assistance.</string>
|
||||||
|
<key>NSDesktopFolderUsageDescription</key>
|
||||||
|
<string>OpenChamber needs access to work with your projects.</string>
|
||||||
|
<key>NSDocumentsFolderUsageDescription</key>
|
||||||
|
<string>OpenChamber needs access to work with your projects.</string>
|
||||||
|
<key>NSDownloadsFolderUsageDescription</key>
|
||||||
|
<string>OpenChamber needs access to work with your projects.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -191,17 +191,20 @@ struct ServerInfoPayload {
|
|||||||
opencode_port: Option<u16>,
|
opencode_port: Option<u16>,
|
||||||
api_prefix: String,
|
api_prefix: String,
|
||||||
cli_available: bool,
|
cli_available: bool,
|
||||||
|
has_last_directory: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn desktop_server_info(
|
async fn desktop_server_info(
|
||||||
state: tauri::State<'_, DesktopRuntime>,
|
state: tauri::State<'_, DesktopRuntime>,
|
||||||
) -> Result<ServerInfoPayload, String> {
|
) -> Result<ServerInfoPayload, String> {
|
||||||
|
let has_last_directory = state.settings().last_directory().await.ok().flatten().is_some();
|
||||||
Ok(ServerInfoPayload {
|
Ok(ServerInfoPayload {
|
||||||
server_port: state.server_port,
|
server_port: state.server_port,
|
||||||
opencode_port: state.opencode.current_port(),
|
opencode_port: state.opencode.current_port(),
|
||||||
api_prefix: state.opencode.api_prefix(),
|
api_prefix: state.opencode.api_prefix(),
|
||||||
cli_available: state.opencode.is_cli_available(),
|
cli_available: state.opencode.is_cli_available(),
|
||||||
|
has_last_directory,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,8 +335,14 @@ fn main() {
|
|||||||
|
|
||||||
let app_handle = app.app_handle().clone();
|
let app_handle = app.app_handle().clone();
|
||||||
let runtime_clone = runtime.clone();
|
let runtime_clone = runtime.clone();
|
||||||
|
let has_initial_dir = tauri::async_runtime::block_on(runtime.settings().last_directory()).ok().flatten().is_some();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
runtime_clone.start_opencode().await;
|
// Only start opencode if we have a saved directory, otherwise frontend will prompt
|
||||||
|
if has_initial_dir {
|
||||||
|
runtime_clone.start_opencode().await;
|
||||||
|
} else {
|
||||||
|
info!("[desktop] No saved directory - waiting for user to select one");
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = restore_bookmarks_on_startup(app_handle.state::<DesktopRuntime>().clone()).await {
|
if let Err(e) = restore_bookmarks_on_startup(app_handle.state::<DesktopRuntime>().clone()).await {
|
||||||
warn!("Failed to restore bookmarks on startup: {}", e);
|
warn!("Failed to restore bookmarks on startup: {}", e);
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ interface ServerInfo {
|
|||||||
opencode_port: number | null;
|
opencode_port: number | null;
|
||||||
api_prefix: string;
|
api_prefix: string;
|
||||||
cli_available: boolean;
|
cli_available: boolean;
|
||||||
|
has_last_directory: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkRuntimeReady = async (): Promise<boolean> => {
|
const checkRuntimeReady = async (): Promise<boolean> => {
|
||||||
@@ -268,15 +269,46 @@ const checkRuntimeReady = async (): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const alreadyReady = await checkRuntimeReady();
|
// Check if we need to prompt for directory selection first
|
||||||
if (!alreadyReady) {
|
const promptForDirectoryIfNeeded = async (): Promise<void> => {
|
||||||
await new Promise<void>((resolve) => {
|
try {
|
||||||
listen('openchamber:runtime-ready', () => {
|
const info = await invoke<ServerInfo>('desktop_server_info');
|
||||||
console.info('[main] Runtime ready event received');
|
// If CLI available but no saved directory, prompt user
|
||||||
resolve();
|
if (info.cli_available && !info.has_last_directory) {
|
||||||
});
|
console.info('[main] No saved directory - prompting user');
|
||||||
});
|
const { open } = await import('@tauri-apps/plugin-dialog');
|
||||||
}
|
const selected = await open({
|
||||||
|
directory: true,
|
||||||
|
multiple: false,
|
||||||
|
title: 'Select a project folder to get started'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selected && typeof selected === 'string') {
|
||||||
|
await invoke('process_directory_selection', { path: selected });
|
||||||
|
await invoke('restart_opencode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[main] Directory selection failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if directory selection is needed, then wait for opencode
|
||||||
|
await promptForDirectoryIfNeeded();
|
||||||
|
|
||||||
|
// Wait for opencode to be ready (or timeout if no CLI)
|
||||||
|
const waitForOpencode = async (): Promise<void> => {
|
||||||
|
const maxAttempts = 50;
|
||||||
|
for (let i = 0; i < maxAttempts; i++) {
|
||||||
|
const info = await invoke<ServerInfo>('desktop_server_info');
|
||||||
|
// Ready if opencode running, or no CLI (will show onboarding)
|
||||||
|
if (!info.cli_available || info.opencode_port !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await new Promise(r => setTimeout(r, 200));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await waitForOpencode();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await import('@openchamber/ui/main');
|
await import('@openchamber/ui/main');
|
||||||
|
|||||||
@@ -133,23 +133,22 @@ export const useUpdateCheck = (checkOnMount = true): UseUpdateCheckReturn => {
|
|||||||
|
|
||||||
const downloadUpdate = useCallback(async () => {
|
const downloadUpdate = useCallback(async () => {
|
||||||
if (mockMode) {
|
if (mockMode) {
|
||||||
setMockState((prev) => ({ ...prev, downloading: true }));
|
setMockState((prev) => prev ? { ...prev, downloading: true } : prev);
|
||||||
// Simulate download progress
|
|
||||||
let progress = 0;
|
let progress = 0;
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
progress += 20;
|
progress += 20;
|
||||||
setMockState((prev) => ({
|
setMockState((prev) => prev ? {
|
||||||
...prev,
|
...prev,
|
||||||
progress: { downloaded: progress * 1000, total: 100000 }
|
progress: { downloaded: progress * 1000, total: 100000 }
|
||||||
}));
|
} : prev);
|
||||||
if (progress >= 100) {
|
if (progress >= 100) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
setMockState((prev) => ({
|
setMockState((prev) => prev ? {
|
||||||
...prev,
|
...prev,
|
||||||
downloading: false,
|
downloading: false,
|
||||||
downloaded: true,
|
downloaded: true,
|
||||||
progress: null,
|
progress: null,
|
||||||
}));
|
} : prev);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user