Skip to content
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
136 changes: 78 additions & 58 deletions apps/shinkai-desktop/src/lib/shinkai-node-overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ReloadIcon } from '@radix-ui/react-icons';
import { useGetHealth } from '@shinkai_network/shinkai-node-state/v2/queries/getHealth/useGetHealth';
import { Button } from '@shinkai_network/shinkai-ui';
import { listen } from '@tauri-apps/api/event';
import { openPath } from '@tauri-apps/plugin-opener';
import { DownloadIcon, Loader2 } from 'lucide-react';
import React, { useState } from 'react';
import { AlertCircle, DownloadIcon, Loader2 } from 'lucide-react';
import React, { useEffect, useState } from 'react';
import { toast } from 'sonner';

import { ResetConnectionDialog } from '../components/reset-connection-dialog';
Expand All @@ -29,6 +30,7 @@ export const ShinkaiNodeRunningOverlay = ({
isPending: isHealthPending,
error: healthError,
isError: isHealthError,
refetch: refetchHealth,
} = useGetHealth(
{ nodeAddress: auth?.node_address ?? '' },
{ refetchInterval: 35000 },
Expand Down Expand Up @@ -58,6 +60,18 @@ export const ShinkaiNodeRunningOverlay = ({

const isShinkaiNodeHealthy = isHealthSuccess && health.status === 'ok';

useEffect(() => {
const handleFocus = () => {
void refetchHealth();
};

const unlistenPromise = listen('tauri://focus', handleFocus);

return () => {
void unlistenPromise.then((unlisten) => unlisten());
};
}, [auth?.node_address, refetchHealth]);

if (isHealthPending || isShinkaiNodeRunningPending) {
return (
<div className="flex size-full flex-col items-center justify-center gap-3 py-8">
Expand All @@ -69,48 +83,33 @@ export const ShinkaiNodeRunningOverlay = ({
);
}

if (isHealthError) {
if (isHealthError && !isShinkaiNodeRunning && isInUse) {
return (
<div className="flex size-full items-center justify-center">
<div
className="flex flex-col items-center gap-6 px-3 py-4 text-sm"
role="alert"
>
<div className="space-y-2 text-center text-red-400">
<p>Unable to connect to Shinkai Node.</p>
<pre className="px-4 whitespace-break-spaces">
{healthError.message}
</pre>
<div className="flex size-full flex-col items-center justify-center gap-6 px-8">
<div className="flex max-w-lg flex-col items-center gap-4 text-center">
<div className="flex size-12 items-center justify-center rounded-full bg-red-500/10">
<AlertCircle className="size-6 text-red-400" />
</div>
<div className="flex items-center gap-4">
<Button
className="min-w-[140px]"
onClick={() => {
downloadTauriLogs();
}}
size="sm"
type="button"
variant="outline"
>
<DownloadIcon className="size-3.5" />
Download Logs
</Button>
<Button
className="min-w-[140px]"
onClick={() => setIsResetConnectionDialogOpen(true)}
size="sm"
type="button"
>
<ReloadIcon className="size-3.5" />
Reset App
</Button>
<ResetConnectionDialog
allowClose
isOpen={isResetConnectionDialogOpen}
onOpenChange={setIsResetConnectionDialogOpen}
/>
<div className="space-y-2">
<h1 className="text-2xl font-semibold">
Connection Issue Detected
</h1>
<p className="text-text-secondary text-base">
We're unable to connect to your Shinkai node. Simply start or
reconnect to your node to continue.
</p>
</div>
</div>
<Button
onClick={async () => {
await openShinkaiNodeManagerWindow();
}}
size="md"
className="min-w-[160px]"
type="button"
>
Open Shinkai Node Manager
</Button>
</div>
);
}
Expand All @@ -120,25 +119,46 @@ export const ShinkaiNodeRunningOverlay = ({
}

return (
<div className="flex h-screen flex-col items-center justify-center gap-10">
<div className="mx-auto flex max-w-lg flex-col items-center gap-4">
<span className="text-4xl">⚠️</span>
<h1 className="text-3xl font-bold">
Unable to Connect to Shinkai Node
</h1>
<p className="text-text-secondary text-base">
Please make sure the Shinkai Node is running and try again.
</p>
<div className="flex size-full items-center justify-center">
<div
className="flex flex-col items-center gap-6 px-3 py-4 text-sm"
role="alert"
>
<div className="space-y-2 text-center text-red-400">
<p>Unable to connect to Shinkai Node.</p>
<pre className="px-4 whitespace-break-spaces">
{healthError?.message ?? 'Unknown error'}
</pre>
</div>
<div className="flex items-center gap-4">
<Button
className="min-w-[140px]"
onClick={() => {
downloadTauriLogs();
}}
size="sm"
type="button"
variant="outline"
>
<DownloadIcon className="size-3.5" />
Download Logs
</Button>
<Button
className="min-w-[140px]"
onClick={() => setIsResetConnectionDialogOpen(true)}
size="sm"
type="button"
>
<ReloadIcon className="size-3.5" />
Reset App
</Button>
<ResetConnectionDialog
allowClose
isOpen={isResetConnectionDialogOpen}
onOpenChange={setIsResetConnectionDialogOpen}
/>
</div>
</div>
{isInUse && !isShinkaiNodeRunning && (
<Button
onClick={async () => {
await openShinkaiNodeManagerWindow();
}}
>
Launch Shinkai Node
</Button>
)}
</div>
);
};
188 changes: 84 additions & 104 deletions apps/shinkai-desktop/src/windows/shinkai-node-manager/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
Badge,
Button,
Form,
FormField,
Expand All @@ -23,11 +24,7 @@ import {
TabsTrigger,
TextField,
Toaster,
Tooltip,
TooltipContent,
TooltipPortal,
TooltipProvider,
TooltipTrigger,
} from '@shinkai_network/shinkai-ui';
import { cn } from '@shinkai_network/shinkai-ui/utils';
import { QueryClientProvider } from '@tanstack/react-query';
Expand Down Expand Up @@ -208,111 +205,94 @@ const App = () => {
className="absolute top-0 z-50 h-6 w-full"
data-tauri-drag-region={true}
/>
<div className="flex flex-row items-center p-4">
<div className="flex flex-row items-center p-4 pt-8">
<img alt="shinkai logo" className="h-10 w-10" src={logo} />
<div className="ml-4 flex flex-col">
<span className="text-lg">Local Shinkai Node</span>
<div className="flex items-center gap-2">
<span className="text-lg">Local Shinkai Node</span>
<Badge
className={cn(
'border-transparent',
shinkaiNodeIsRunning
? 'bg-green-500/10 text-green-400'
: 'bg-red-500/10 text-red-400',
)}
variant="outline"
>
{shinkaiNodeIsRunning ? 'Running' : 'Stopped'}
</Badge>
</div>
<span className="text-text-secondary text-sm">{`API URL: http://${shinkaiNodeOptions?.node_api_ip}:${shinkaiNodeOptions?.node_api_port}`}</span>
</div>
<div className="flex grow flex-row items-center justify-end space-x-4">
<Tooltip>
<TooltipTrigger>
<Button
disabled={
shinkaiNodeSpawnIsPending ||
shinkaiNodeKillIsPending ||
shinkaiNodeIsRunning
}
onClick={() => {
console.log('spawning');
void shinkaiNodeSpawn();
}}
variant={'outline'}
size={'icon'}
>
{shinkaiNodeSpawnIsPending || shinkaiNodeKillIsPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<PlayIcon className="size-4" />
)}
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="bottom">
<p>Start Shinkai Node</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>

<Tooltip>
<TooltipTrigger>
<Button
disabled={
shinkaiNodeSpawnIsPending ||
shinkaiNodeKillIsPending ||
!shinkaiNodeIsRunning
}
onClick={() => shinkaiNodeKill()}
variant={'outline'}
size={'icon'}
>
{shinkaiNodeKillIsPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<StopIcon className="size-4" />
)}
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="bottom">
<p>Stop Shinkai Node</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>

<Tooltip>
<TooltipTrigger>
<Button
disabled={shinkaiNodeIsRunning}
onClick={() => setIsConfirmResetDialogOpened(true)}
variant={'outline'}
size={'icon'}
>
{shinkaiNodeRemoveStorageIsPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<Trash2 className="size-4" />
)}
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="bottom">
<p>Reset Shinkai Node</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
<div className="flex grow flex-row items-center justify-end gap-2">
{!shinkaiNodeIsRunning && (
<Button
disabled={
shinkaiNodeSpawnIsPending ||
shinkaiNodeKillIsPending ||
shinkaiNodeIsRunning
}
onClick={() => {
console.log('spawning');
void shinkaiNodeSpawn();
}}
variant={'outline'}
size="sm"
>
{shinkaiNodeSpawnIsPending || shinkaiNodeKillIsPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<PlayIcon className="size-4" />
)}
<span className="text-sm">Start</span>
</Button>
)}

<Tooltip>
<TooltipTrigger>
<Button
disabled={!shinkaiNodeIsRunning}
onClick={() => startSyncOllamaModels()}
variant={'outline'}
size={'icon'}
>
{syncOllamaModelsIsPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<RefreshCcwIcon className="size-4" />
)}
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="bottom">
<p>Sync Ollama Models</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
{shinkaiNodeIsRunning && (
<Button
disabled={
shinkaiNodeSpawnIsPending ||
shinkaiNodeKillIsPending ||
!shinkaiNodeIsRunning
}
onClick={() => shinkaiNodeKill()}
variant={'outline'}
size="sm"
>
{shinkaiNodeKillIsPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<StopIcon className="size-4" />
)}
<span className="text-sm">Stop</span>
</Button>
)}
<Button
disabled={shinkaiNodeIsRunning}
onClick={() => setIsConfirmResetDialogOpened(true)}
variant={'outline'}
size="sm"
>
{shinkaiNodeRemoveStorageIsPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<Trash2 className="size-4" />
)}
<span className="text-sm">Reset</span>
</Button>
<Button
disabled={!shinkaiNodeIsRunning}
onClick={() => startSyncOllamaModels()}
variant={'outline'}
size="sm"
>
{syncOllamaModelsIsPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<RefreshCcwIcon className="size-4" />
)}
<span className="text-sm">Sync Models</span>
</Button>
</div>
</div>

Expand Down