diff --git a/apps/desktop/src/renderer/src/pages/admin.tsx b/apps/desktop/src/renderer/src/pages/admin.tsx new file mode 100644 index 00000000..b3365b3d --- /dev/null +++ b/apps/desktop/src/renderer/src/pages/admin.tsx @@ -0,0 +1,409 @@ +import { useState, useEffect, useRef } from 'react' +import { useNavigate } from 'react-router-dom' +import { Button } from '@multica/ui/components/ui/button' +import { HugeiconsIcon } from '@hugeicons/react' +import { + Comment01Icon, + LinkSquare01Icon, + Loading03Icon, + AlertCircleIcon, + Edit02Icon, + ArrowDown01Icon, + Tick02Icon, + Alert02Icon, +} from '@hugeicons/core-free-icons' +import { ConnectionQRCode } from '../components/qr-code' +import { DeviceList } from '../components/device-list' +import { AgentSettingsDialog } from '../components/agent-settings-dialog' +import { ApiKeyDialog } from '../components/api-key-dialog' +import { OAuthDialog } from '../components/oauth-dialog' +import { useHub } from '../hooks/use-hub' +import { useProvider } from '../hooks/use-provider' + +export default function HomePage() { + const navigate = useNavigate() + const { hubInfo, agents, loading, error } = useHub() + const { providers, current, setProvider, refresh, loading: providerLoading } = useProvider() + const [settingsOpen, setSettingsOpen] = useState(false) + const [agentName, setAgentName] = useState() + const [providerDropdownOpen, setProviderDropdownOpen] = useState(false) + const [switching, setSwitching] = useState(false) + const [apiKeyDialogOpen, setApiKeyDialogOpen] = useState(false) + const [oauthDialogOpen, setOauthDialogOpen] = useState(false) + const [selectedProvider, setSelectedProvider] = useState<{ + id: string + name: string + authMethod: 'api-key' | 'oauth' + loginCommand?: string + } | null>(null) + const dropdownRef = useRef(null) + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setProviderDropdownOpen(false) + } + } + + if (providerDropdownOpen) { + document.addEventListener('mousedown', handleClickOutside) + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [providerDropdownOpen]) + + // Load agent profile info + useEffect(() => { + loadAgentInfo() + }, []) + + // Reload agent info when settings dialog closes + useEffect(() => { + if (!settingsOpen) { + loadAgentInfo() + } + }, [settingsOpen]) + + const loadAgentInfo = async () => { + try { + const data = await window.electronAPI.profile.get() + setAgentName(data.name) + } catch (err) { + console.error('Failed to load agent info:', err) + } + } + + // Get the first agent (or create one if none exists) + const primaryAgent = agents[0] + + // Connection state indicator + // Note: 'registered' means fully connected and registered with Gateway + const connectionState = hubInfo?.connectionState ?? 'disconnected' + const isConnected = connectionState === 'connected' || connectionState === 'registered' + + // Loading state + if (loading) { + return ( +
+
+ + Connecting to Hub... +
+
+ ) + } + + // Error state + if (error) { + return ( +
+
+ + Connection Error + {error} +
+
+ ) + } + + return ( +
+ {/* Main content - QR + Status */} +
+ {/* Left: QR Code */} +
+ +
+ + {/* Right: Hub Status */} +
+
+ {/* Hub Header */} +
+
+ + {isConnected ? ( + <> + + + + ) : connectionState === 'connecting' || connectionState === 'reconnecting' ? ( + <> + + + + ) : ( + + )} + + + {isConnected + ? 'Hub Connected' + : connectionState === 'connecting' + ? 'Connecting...' + : connectionState === 'reconnecting' + ? 'Reconnecting...' + : 'Disconnected'} + +
+

+ Local Hub +

+

+ {hubInfo?.hubId ?? 'Initializing...'} +

+
+ + {/* Agent Settings */} +
+
+

+ Agent Settings +

+ +
+

{agentName || 'Unnamed Agent'}

+
+ + {/* Provider Selector */} +
+

+ LLM Provider +

+ + + {/* Provider Dropdown - Compact Grid + Model List */} + {providerDropdownOpen && ( +
+
+ {providers.map((p) => ( + + ))} +
+ + {/* Model List for current provider */} + {(() => { + const currentProvider = providers.find(p => p.id === current?.provider) + if (!currentProvider || currentProvider.models.length <= 1) return null + return ( +
+

+ Models โ€” {currentProvider.name} +

+
+ {currentProvider.models.map((model) => ( + + ))} +
+
+ ) + })()} +
+ )} +
+ + {/* Stats Grid */} +
+
+

+ Gateway +

+

+ {hubInfo?.url ?? '-'} +

+
+
+

+ Connection +

+

{connectionState}

+
+
+
+
+
+ + {/* Verified Devices */} +
+ +
+ + {/* Agent Settings Dialog */} + + + {/* API Key Dialog */} + {selectedProvider && selectedProvider.authMethod === 'api-key' && ( + { + // Refresh provider list and switch to the newly configured provider + await refresh() + const result = await setProvider(selectedProvider.id) + if (!result.ok) { + console.error('Failed to switch provider:', result.error) + } + }} + /> + )} + + {/* OAuth Dialog */} + {selectedProvider && selectedProvider.authMethod === 'oauth' && ( + { + // Refresh provider list and switch to the newly configured provider + await refresh() + const result = await setProvider(selectedProvider.id) + if (!result.ok) { + console.error('Failed to switch provider:', result.error) + } + }} + /> + )} + + {/* Bottom: Actions */} +
+
+ {/* Primary Action: Chat */} + + + {/* Secondary: Connect to Remote */} + +
+
+
+ ) +} diff --git a/apps/desktop/src/renderer/src/pages/home.tsx b/apps/desktop/src/renderer/src/pages/home.tsx index 8865a866..c062d89d 100644 --- a/apps/desktop/src/renderer/src/pages/home.tsx +++ b/apps/desktop/src/renderer/src/pages/home.tsx @@ -1,409 +1,7 @@ -import { useState, useEffect, useRef } from 'react' -import { useNavigate } from 'react-router-dom' -import { Button } from '@multica/ui/components/ui/button' -import { HugeiconsIcon } from '@hugeicons/react' -import { - Comment01Icon, - LinkSquare01Icon, - Loading03Icon, - AlertCircleIcon, - Edit02Icon, - ArrowDown01Icon, - Tick02Icon, - Alert02Icon, -} from '@hugeicons/core-free-icons' -import { ConnectionQRCode } from '../components/qr-code' -import { DeviceList } from '../components/device-list' -import { AgentSettingsDialog } from '../components/agent-settings-dialog' -import { ApiKeyDialog } from '../components/api-key-dialog' -import { OAuthDialog } from '../components/oauth-dialog' -import { useHub } from '../hooks/use-hub' -import { useProvider } from '../hooks/use-provider' - export default function HomePage() { - const navigate = useNavigate() - const { hubInfo, agents, loading, error } = useHub() - const { providers, current, setProvider, refresh, loading: providerLoading } = useProvider() - const [settingsOpen, setSettingsOpen] = useState(false) - const [agentName, setAgentName] = useState() - const [providerDropdownOpen, setProviderDropdownOpen] = useState(false) - const [switching, setSwitching] = useState(false) - const [apiKeyDialogOpen, setApiKeyDialogOpen] = useState(false) - const [oauthDialogOpen, setOauthDialogOpen] = useState(false) - const [selectedProvider, setSelectedProvider] = useState<{ - id: string - name: string - authMethod: 'api-key' | 'oauth' - loginCommand?: string - } | null>(null) - const dropdownRef = useRef(null) - - // Close dropdown when clicking outside - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setProviderDropdownOpen(false) - } - } - - if (providerDropdownOpen) { - document.addEventListener('mousedown', handleClickOutside) - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside) - } - }, [providerDropdownOpen]) - - // Load agent profile info - useEffect(() => { - loadAgentInfo() - }, []) - - // Reload agent info when settings dialog closes - useEffect(() => { - if (!settingsOpen) { - loadAgentInfo() - } - }, [settingsOpen]) - - const loadAgentInfo = async () => { - try { - const data = await window.electronAPI.profile.get() - setAgentName(data.name) - } catch (err) { - console.error('Failed to load agent info:', err) - } - } - - // Get the first agent (or create one if none exists) - const primaryAgent = agents[0] - - // Connection state indicator - // Note: 'registered' means fully connected and registered with Gateway - const connectionState = hubInfo?.connectionState ?? 'disconnected' - const isConnected = connectionState === 'connected' || connectionState === 'registered' - - // Loading state - if (loading) { - return ( -
-
- - Connecting to Hub... -
-
- ) - } - - // Error state - if (error) { - return ( -
-
- - Connection Error - {error} -
-
- ) - } - return ( -
- {/* Main content - QR + Status */} -
- {/* Left: QR Code */} -
- -
- - {/* Right: Hub Status */} -
-
- {/* Hub Header */} -
-
- - {isConnected ? ( - <> - - - - ) : connectionState === 'connecting' || connectionState === 'reconnecting' ? ( - <> - - - - ) : ( - - )} - - - {isConnected - ? 'Hub Connected' - : connectionState === 'connecting' - ? 'Connecting...' - : connectionState === 'reconnecting' - ? 'Reconnecting...' - : 'Disconnected'} - -
-

- Local Hub -

-

- {hubInfo?.hubId ?? 'Initializing...'} -

-
- - {/* Agent Settings */} -
-
-

- Agent Settings -

- -
-

{agentName || 'Unnamed Agent'}

-
- - {/* Provider Selector */} -
-

- LLM Provider -

- - - {/* Provider Dropdown - Compact Grid + Model List */} - {providerDropdownOpen && ( -
-
- {providers.map((p) => ( - - ))} -
- - {/* Model List for current provider */} - {(() => { - const currentProvider = providers.find(p => p.id === current?.provider) - if (!currentProvider || currentProvider.models.length <= 1) return null - return ( -
-

- Models โ€” {currentProvider.name} -

-
- {currentProvider.models.map((model) => ( - - ))} -
-
- ) - })()} -
- )} -
- - {/* Stats Grid */} -
-
-

- Gateway -

-

- {hubInfo?.url ?? '-'} -

-
-
-

- Connection -

-

{connectionState}

-
-
-
-
-
- - {/* Verified Devices */} -
- -
- - {/* Agent Settings Dialog */} - - - {/* API Key Dialog */} - {selectedProvider && selectedProvider.authMethod === 'api-key' && ( - { - // Refresh provider list and switch to the newly configured provider - await refresh() - const result = await setProvider(selectedProvider.id) - if (!result.ok) { - console.error('Failed to switch provider:', result.error) - } - }} - /> - )} - - {/* OAuth Dialog */} - {selectedProvider && selectedProvider.authMethod === 'oauth' && ( - { - // Refresh provider list and switch to the newly configured provider - await refresh() - const result = await setProvider(selectedProvider.id) - if (!result.ok) { - console.error('Failed to switch provider:', result.error) - } - }} - /> - )} - - {/* Bottom: Actions */} -
-
- {/* Primary Action: Chat */} - - - {/* Secondary: Connect to Remote */} - -
-
+
+ HomePage
) -} +} \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/pages/onboarding/components/connect-step.tsx b/apps/desktop/src/renderer/src/pages/onboarding/components/connect-step.tsx index 6a6c304e..75952b05 100644 --- a/apps/desktop/src/renderer/src/pages/onboarding/components/connect-step.tsx +++ b/apps/desktop/src/renderer/src/pages/onboarding/components/connect-step.tsx @@ -85,10 +85,10 @@ export default function ConnectStep({ onNext, onBack }: ConnectStepProps) { {/* Header */}

- Your agent, everywhere + Connect a channel

- Create bots on messaging platforms that talk to your local agent. + Create bots that talk to your local agent from anywhere.

diff --git a/apps/desktop/src/renderer/src/pages/onboarding/components/try-it-step.tsx b/apps/desktop/src/renderer/src/pages/onboarding/components/try-it-step.tsx index 4435991a..0ccf8a8a 100644 --- a/apps/desktop/src/renderer/src/pages/onboarding/components/try-it-step.tsx +++ b/apps/desktop/src/renderer/src/pages/onboarding/components/try-it-step.tsx @@ -62,10 +62,10 @@ export default function TryItStep({ onComplete, onBack }: TryItStepProps) { {/* Header */}

- You're all set ๐ŸŽ‰ + Ready to go

- Your agent is ready to help. Try a sample task, or dive right in. + Your agent is ready. Try a sample task or dive right in.

diff --git a/apps/desktop/src/renderer/src/pages/onboarding/index.tsx b/apps/desktop/src/renderer/src/pages/onboarding/index.tsx index 6dbfe723..97d6fd71 100644 --- a/apps/desktop/src/renderer/src/pages/onboarding/index.tsx +++ b/apps/desktop/src/renderer/src/pages/onboarding/index.tsx @@ -8,7 +8,7 @@ import SetupStep from "./components/setup-step"; import ConnectStep from "./components/connect-step"; import TryItStep from "./components/try-it-step"; -const steps = ["Privacy", "Provider", "Connect", "Try it"]; +const steps = ["Privacy", "Provider", "Channels", "Start"]; export default function OnboardingPage() { const navigate = useNavigate();