Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install tools directly from Shinkai Store. #582

Merged
merged 13 commits into from
Jan 22, 2025
Merged
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
63 changes: 61 additions & 2 deletions apps/shinkai-desktop/src-tauri/src/deep_links.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
use tauri::Emitter;
use tauri::{Emitter, Listener, WindowEvent};
use tauri_plugin_deep_link::DeepLinkExt;

use crate::windows::{recreate_window, Window};
use crate::windows::{get_window, recreate_window, Window};

#[derive(Debug, Clone, serde::Serialize)]
pub struct OAuthDeepLinkPayload {
pub state: String,
pub code: String,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct StoreDeepLinkPayload {
pub tool_type: String,
pub tool_url: String,
}

pub fn setup_deep_links(app: &tauri::AppHandle) -> tauri::Result<()> {
#[cfg(any(windows, target_os = "linux"))]
{
Expand All @@ -24,6 +30,59 @@ pub fn setup_deep_links(app: &tauri::AppHandle) -> tauri::Result<()> {
for url in urls {
log::debug!("handling deep link: {:?}", url);
if let Some(host) = url.host() {
if host.to_string() == "store" {
// shinkai://store?type=tool&url=https://download.shinkai.app/tool/email-fetcher.zip
let query_pairs = url.query_pairs().collect::<Vec<_>>();
let tool_type = query_pairs
.iter()
.find(|(key, _)| key == "type")
.map(|(_, value)| value.to_string())
.unwrap_or_default();
let tool_url = query_pairs
.iter()
.find(|(key, _)| key == "url")
.map(|(_, value)| value.to_string())
.unwrap_or_default();

let payload = StoreDeepLinkPayload {
tool_type,
tool_url,
};
let app_handle_clone = app_handle.clone();

match get_window(app_handle_clone.clone(), Window::Main, true) {
Ok(_) => {
log::info!("Successfully got main window");
let _ = app_handle_clone.emit_to(
Window::Main.as_str(),
"store-deep-link",
payload,
);
}
Err(e) => {
log::error!("Failed to get main window: {}", e);
// Spawn window recreation in a separate task
tauri::async_runtime::spawn(async move {
match recreate_window(app_handle_clone.clone(), Window::Main, true) {
Ok(window) => {
window.once("shinkai-app-ready", move |_| {
log::info!("shinkai-app-ready event received, emitting store-deep-link");
let _ = app_handle_clone.emit_to(
Window::Main.as_str(),
"store-deep-link",
payload,
);
});
}
Err(e) => {
log::error!("Failed to recreate main window: {}", e);
}
}
});
}
}
}

if host.to_string() == "oauth" {
log::debug!("oauth deep link: {:?}", url);
let query_pairs = url.query_pairs().collect::<Vec<_>>();
Expand Down
21 changes: 20 additions & 1 deletion apps/shinkai-desktop/src-tauri/src/windows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use tauri::{AppHandle, Manager, WebviewWindow, WebviewWindowBuilder};
use tauri::{AppHandle, Error, Manager, WebviewWindow, WebviewWindowBuilder};

#[derive(Debug, Clone, Copy)]
pub enum Window {
Expand All @@ -19,6 +19,25 @@ impl Window {
}
}

pub fn get_window(app_handle: AppHandle, window_name: Window, focus: bool) -> tauri::Result<WebviewWindow> {
let label = window_name.as_str();
if let Some(window) = app_handle.get_webview_window(label) {
log::info!("window {} found, bringing to front", label);
if focus {
log::info!("focusing window {}", label);
if window.is_minimized().unwrap_or_default() {
let _ = window.unminimize();
}
window.show().unwrap();
// window.center().unwrap();
let _ = window.set_focus();
}
return Ok(window);
}
log::info!("window {} not found", label);
Err(Error::WindowNotFound)
}

pub fn recreate_window(app_handle: AppHandle, window_name: Window, focus: bool) -> tauri::Result<WebviewWindow> {
let label = window_name.as_str();
if let Some(window) = app_handle.get_webview_window(label) {
Expand Down
47 changes: 45 additions & 2 deletions apps/shinkai-desktop/src/pages/layout/main-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ExitIcon, GearIcon } from '@radix-ui/react-icons';
import { useTranslation } from '@shinkai_network/shinkai-i18n';
import { useImportTool } from '@shinkai_network/shinkai-node-state/v2/mutations/importTool/useImportTool';
import { useSubmitRegistrationNoCode } from '@shinkai_network/shinkai-node-state/v2/mutations/submitRegistation/useSubmitRegistrationNoCode';
import { useGetEncryptionKeys } from '@shinkai_network/shinkai-node-state/v2/queries/getEncryptionKeys/useGetEncryptionKeys';
import { useGetHealth } from '@shinkai_network/shinkai-node-state/v2/queries/getHealth/useGetHealth';
Expand Down Expand Up @@ -36,6 +37,8 @@ import {
} from '@shinkai_network/shinkai-ui/assets';
import { submitRegistrationNoCodeError } from '@shinkai_network/shinkai-ui/helpers';
import { cn } from '@shinkai_network/shinkai-ui/utils';
import { listen } from '@tauri-apps/api/event';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { AnimatePresence, motion, TargetAndTransition } from 'framer-motion';
import {
ArrowLeftToLine,
Expand Down Expand Up @@ -271,7 +274,7 @@ export const ResetConnectionDialog = ({
<AlertDialogDescription>
<div className="flex flex-col space-y-3 text-left text-white/70">
<div className="text-sm">
Were currently in beta and we made some significant updates to
We&apos;re currently in beta and we made some significant updates to
improve your experience. To apply these updates, we need to
reset your data.
<br /> <br />
Expand Down Expand Up @@ -643,12 +646,52 @@ const MainLayout = () => {
const { t } = useTranslation();
const location = useLocation();
const auth = useAuth((state) => state.auth);
const navigate = useNavigate();
const [needsResetApp, setNeedsResetApp] = useState(false);
const { nodeInfo, isSuccess, isFetching } = useGetHealth(
{ nodeAddress: auth?.node_address ?? '' },
{ refetchInterval: 35000, enabled: !!auth },
);

const { mutateAsync: importTool } = useImportTool({
onSuccess: (data) => {
toast.success('Tool imported successfully', {
action: {
label: 'View',
onClick: () => {
navigate(`/tools/${data.tool_key}`);
},
},
});
navigate('/tools');
},
onError: (error) => {
toast.error('Failed to import tool', {
description: error.response?.data?.message ?? error.message,
});
},
});

useEffect(() => {
const unlisten = listen('store-deep-link', (event) => {
if (!auth) return;

const payload = event.payload as { tool_type: string; tool_url: string };
if (payload.tool_type === 'tool') {
importTool({
nodeAddress: auth?.node_address ?? '',
token: auth?.api_v2_key ?? '',
url: payload.tool_url,
});
}
});

getCurrentWindow().emit('shinkai-app-ready');
return () => {
unlisten.then((fn) => fn());
};
}, [importTool, auth]);

useEffect(() => {
if (isSuccess && nodeInfo?.status !== 'ok') {
toast.error(t('errors.nodeUnavailable.title'), {
Expand All @@ -666,7 +709,7 @@ const MainLayout = () => {
} else {
setNeedsResetApp(false);
}
}, [isSuccess, nodeInfo?.status, isFetching]);
}, [isSuccess, nodeInfo, isFetching, t]);

const disabledSidebarRoutes = [
'/connect-ai',
Expand Down
2 changes: 1 addition & 1 deletion apps/shinkai-desktop/src/pages/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ type ImportToolFormSchema = z.infer<typeof importToolFormSchema>;

function ImportToolModal() {
const auth = useAuth((state) => state.auth);

const navigate = useNavigate();
const importToolForm = useForm<ImportToolFormSchema>({
resolver: zodResolver(importToolFormSchema),
Expand Down