feat(ai): add generic AI configuration deep links
This commit is contained in:
13 files changed
+503
-13
No files matched your search
@@ -2,30 +2,50 @@ use std::collections::HashSet;
|
||||
use std::sync::Mutex;
|
||||
|
||||
const CONNECTION_DEEP_LINK_PREFIX: &str = "dbx://connection/new";
|
||||
const AI_CONFIG_DEEP_LINK_PREFIX: &str = "dbx://settings/ai/new";
|
||||
const APP_OPEN_DEEP_LINK_PREFIX: &str = "dbx://open";
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pending_open_connection_links(state: tauri::State<'_, DeepLinkOpenState>) -> Vec<String> {
|
||||
dedupe_links(state.drain())
|
||||
dedupe_links(state.drain_connection_links())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pending_open_ai_config_links(state: tauri::State<'_, DeepLinkOpenState>) -> Vec<String> {
|
||||
dedupe_links(state.drain_ai_config_links())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DeepLinkOpenState {
|
||||
pending: Mutex<Vec<String>>,
|
||||
pending_connection_links: Mutex<Vec<String>>,
|
||||
pending_ai_config_links: Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
impl DeepLinkOpenState {
|
||||
pub fn push(&self, links: Vec<String>) {
|
||||
pub fn push_connection_links(&self, links: Vec<String>) {
|
||||
if links.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Ok(mut pending) = self.pending.lock() {
|
||||
if let Ok(mut pending) = self.pending_connection_links.lock() {
|
||||
pending.extend(links);
|
||||
}
|
||||
}
|
||||
|
||||
fn drain(&self) -> Vec<String> {
|
||||
self.pending.lock().map(|mut pending| pending.drain(..).collect()).unwrap_or_default()
|
||||
pub fn push_ai_config_links(&self, links: Vec<String>) {
|
||||
if links.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Ok(mut pending) = self.pending_ai_config_links.lock() {
|
||||
pending.extend(links);
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_connection_links(&self) -> Vec<String> {
|
||||
self.pending_connection_links.lock().map(|mut pending| pending.drain(..).collect()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn drain_ai_config_links(&self) -> Vec<String> {
|
||||
self.pending_ai_config_links.lock().map(|mut pending| pending.drain(..).collect()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +62,19 @@ pub fn connection_deep_link_from_arg(arg: &str) -> Option<String> {
|
||||
matches_deep_link_target(trimmed, CONNECTION_DEEP_LINK_PREFIX).then(|| trimmed.to_string())
|
||||
}
|
||||
|
||||
pub fn ai_config_deep_links_from_args<I, S>(args: I) -> Vec<String>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
args.into_iter().filter_map(|arg| ai_config_deep_link_from_arg(arg.as_ref())).collect()
|
||||
}
|
||||
|
||||
pub fn ai_config_deep_link_from_arg(arg: &str) -> Option<String> {
|
||||
let trimmed = arg.trim();
|
||||
matches_deep_link_target(trimmed, AI_CONFIG_DEEP_LINK_PREFIX).then(|| trimmed.to_string())
|
||||
}
|
||||
|
||||
pub fn is_app_open_deep_link(arg: &str) -> bool {
|
||||
matches_deep_link_target(arg.trim(), APP_OPEN_DEEP_LINK_PREFIX)
|
||||
}
|
||||
@@ -77,6 +110,7 @@ mod tests {
|
||||
fn filters_connection_deep_links() {
|
||||
let links = connection_deep_links_from_args([
|
||||
"dbx://connection/new?type=mysql&host=127.0.0.1",
|
||||
"dbx://settings/ai/new?provider=openai-compatible",
|
||||
"--flag",
|
||||
"dbx://open?x=1",
|
||||
"dbx://connections/new?type=postgres",
|
||||
@@ -86,6 +120,19 @@ mod tests {
|
||||
assert_eq!(links, vec!["dbx://connection/new?type=mysql&host=127.0.0.1".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_ai_config_deep_links() {
|
||||
let links = ai_config_deep_links_from_args([
|
||||
"dbx://settings/ai/new?v=1&provider=openai-compatible",
|
||||
"--flag",
|
||||
"dbx://settings/ai/edit?provider=openai-compatible",
|
||||
"dbx://settings/ai/newer?provider=openai-compatible",
|
||||
"dbx://connection/new?type=mysql",
|
||||
]);
|
||||
|
||||
assert_eq!(links, vec!["dbx://settings/ai/new?v=1&provider=openai-compatible".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recognizes_app_open_deep_links() {
|
||||
assert!(is_app_open_deep_link("dbx://open"));
|
||||
@@ -99,10 +146,13 @@ mod tests {
|
||||
#[test]
|
||||
fn drains_pending_links_once() {
|
||||
let state = DeepLinkOpenState::default();
|
||||
state.push(vec!["dbx://connection/new?type=mysql".to_string()]);
|
||||
state.push_connection_links(vec!["dbx://connection/new?type=mysql".to_string()]);
|
||||
state.push_ai_config_links(vec!["dbx://settings/ai/new?provider=openai-compatible".to_string()]);
|
||||
|
||||
assert_eq!(state.drain(), vec!["dbx://connection/new?type=mysql"]);
|
||||
assert!(state.drain().is_empty());
|
||||
assert_eq!(state.drain_connection_links(), vec!["dbx://connection/new?type=mysql"]);
|
||||
assert_eq!(state.drain_ai_config_links(), vec!["dbx://settings/ai/new?provider=openai-compatible"]);
|
||||
assert!(state.drain_connection_links().is_empty());
|
||||
assert!(state.drain_ai_config_links().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+26
-2
@@ -725,12 +725,23 @@ fn open_connection_deep_links(app: &tauri::AppHandle, links: Vec<String>) {
|
||||
return;
|
||||
}
|
||||
if let Some(state) = app.try_state::<commands::deep_link::DeepLinkOpenState>() {
|
||||
state.push(links.clone());
|
||||
state.push_connection_links(links.clone());
|
||||
}
|
||||
let _ = app.emit("dbx-open-connection-links", links);
|
||||
show_main_window(app);
|
||||
}
|
||||
|
||||
fn open_ai_config_deep_links(app: &tauri::AppHandle, links: Vec<String>) {
|
||||
if links.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Some(state) = app.try_state::<commands::deep_link::DeepLinkOpenState>() {
|
||||
state.push_ai_config_links(links.clone());
|
||||
}
|
||||
let _ = app.emit("dbx-open-ai-config-links", links);
|
||||
show_main_window(app);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum LocaleFamily {
|
||||
English,
|
||||
@@ -1521,6 +1532,8 @@ pub fn run() {
|
||||
let app_open_requested = args.iter().any(|arg| commands::deep_link::is_app_open_deep_link(arg));
|
||||
let links = commands::deep_link::connection_deep_links_from_args(args.clone());
|
||||
open_connection_deep_links(app, links);
|
||||
let ai_config_links = commands::deep_link::ai_config_deep_links_from_args(args.clone());
|
||||
open_ai_config_deep_links(app, ai_config_links);
|
||||
|
||||
let paths = commands::external_sql::sql_file_paths_from_args(args.clone(), std::path::Path::new(&cwd));
|
||||
if !paths.is_empty() {
|
||||
@@ -1720,8 +1733,11 @@ pub fn run() {
|
||||
macos_escape_guard::install_escape_fullscreen_guard();
|
||||
#[cfg(target_os = "windows")]
|
||||
webview2_recovery::install(app.handle());
|
||||
let startup_links = commands::deep_link::connection_deep_links_from_args(std::env::args().skip(1));
|
||||
let startup_args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let startup_links = commands::deep_link::connection_deep_links_from_args(&startup_args);
|
||||
open_connection_deep_links(app.handle(), startup_links);
|
||||
let startup_ai_config_links = commands::deep_link::ai_config_deep_links_from_args(&startup_args);
|
||||
open_ai_config_deep_links(app.handle(), startup_ai_config_links);
|
||||
|
||||
let app_handle = app.handle().clone();
|
||||
commands::mcp_bridge::start(app_handle, state, data_dir);
|
||||
@@ -2023,6 +2039,7 @@ pub fn run() {
|
||||
commands::keychain::read_keychain_password,
|
||||
commands::keychain::read_keychain_passwords,
|
||||
commands::deep_link::pending_open_connection_links,
|
||||
commands::deep_link::pending_open_ai_config_links,
|
||||
commands::table_import::preview_table_import_file,
|
||||
commands::table_import::import_table_file,
|
||||
commands::table_import::cancel_table_import,
|
||||
@@ -2610,6 +2627,13 @@ pub fn run() {
|
||||
.collect();
|
||||
open_connection_deep_links(app_handle, links);
|
||||
|
||||
let ai_config_links: Vec<String> = urls
|
||||
.iter()
|
||||
.map(|url| url.to_string())
|
||||
.filter_map(|url| commands::deep_link::ai_config_deep_link_from_arg(&url))
|
||||
.collect();
|
||||
open_ai_config_deep_links(app_handle, ai_config_links);
|
||||
|
||||
let paths: Vec<String> = urls
|
||||
.iter()
|
||||
.filter_map(|url| url.to_file_path().ok())
|
||||
|
||||
Reference in new issue
Block a user