feat(desktop): add agent settings dialog
Add UI for editing agent name and user preferences: - AgentSettingsDialog component with name input and user.md textarea - Agent Settings section on Home page with Edit button - Auto-reload agent info when settings are saved Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a157c6546d
commit
bdc2006e0e
2 changed files with 188 additions and 7 deletions
130
apps/desktop/src/components/agent-settings-dialog.tsx
Normal file
130
apps/desktop/src/components/agent-settings-dialog.tsx
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from '@multica/ui/components/ui/dialog'
|
||||
import { Button } from '@multica/ui/components/ui/button'
|
||||
import { Input } from '@multica/ui/components/ui/input'
|
||||
import { Textarea } from '@multica/ui/components/ui/textarea'
|
||||
import { Label } from '@multica/ui/components/ui/label'
|
||||
import { HugeiconsIcon } from '@hugeicons/react'
|
||||
import { Loading03Icon } from '@hugeicons/core-free-icons'
|
||||
|
||||
interface AgentSettingsDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogProps) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [name, setName] = useState('')
|
||||
const [userContent, setUserContent] = useState('')
|
||||
const [profileId, setProfileId] = useState<string | undefined>()
|
||||
|
||||
// Load profile data when dialog opens
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
loadProfile()
|
||||
}
|
||||
}, [open])
|
||||
|
||||
const loadProfile = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await window.electronAPI.profile.get()
|
||||
setProfileId(data.profileId)
|
||||
setName(data.name ?? '')
|
||||
setUserContent(data.userContent ?? '')
|
||||
} catch (err) {
|
||||
console.error('Failed to load profile:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true)
|
||||
try {
|
||||
// Update name if changed
|
||||
await window.electronAPI.profile.updateName(name)
|
||||
// Update user content
|
||||
await window.electronAPI.profile.updateUser(userContent)
|
||||
onOpenChange(false)
|
||||
} catch (err) {
|
||||
console.error('Failed to save profile:', err)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Agent</DialogTitle>
|
||||
<DialogDescription>
|
||||
Customize your agent's name and personal settings.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<HugeiconsIcon icon={Loading03Icon} className="size-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{/* Profile ID (read-only) */}
|
||||
{profileId && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Profile: {profileId}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Name */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="agent-name">Name</Label>
|
||||
<Input
|
||||
id="agent-name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="My Assistant"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* User Content */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="user-content">About You</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Help the agent understand you better. Share your preferences, role, or any context.
|
||||
</p>
|
||||
<Textarea
|
||||
id="user-content"
|
||||
value={userContent}
|
||||
onChange={(e) => setUserContent(e.target.value)}
|
||||
placeholder="- I'm a frontend developer - I prefer TypeScript - Please respond in Chinese"
|
||||
className="min-h-[160px] font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={loading || saving}>
|
||||
{saving && <HugeiconsIcon icon={Loading03Icon} className="size-4 animate-spin mr-2" />}
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentSettingsDialog
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Button } from '@multica/ui/components/ui/button'
|
||||
import { HugeiconsIcon } from '@hugeicons/react'
|
||||
|
|
@ -6,13 +7,40 @@ import {
|
|||
LinkSquare01Icon,
|
||||
Loading03Icon,
|
||||
AlertCircleIcon,
|
||||
Edit02Icon,
|
||||
} from '@hugeicons/core-free-icons'
|
||||
import { ConnectionQRCode } from '../components/qr-code'
|
||||
import { AgentSettingsDialog } from '../components/agent-settings-dialog'
|
||||
import { useHub } from '../hooks/use-hub'
|
||||
|
||||
export default function HomePage() {
|
||||
const navigate = useNavigate()
|
||||
const { hubInfo, agents, loading, error } = useHub()
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
const [agentName, setAgentName] = useState<string | undefined>()
|
||||
const [profileId, setProfileId] = useState<string | undefined>()
|
||||
|
||||
// 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)
|
||||
setProfileId(data.profileId)
|
||||
} catch (err) {
|
||||
console.error('Failed to load agent info:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the first agent (or create one if none exists)
|
||||
const primaryAgent = agents[0]
|
||||
|
|
@ -53,13 +81,13 @@ export default function HomePage() {
|
|||
<div className="flex-1 flex gap-8 p-2">
|
||||
{/* Left: QR Code */}
|
||||
<div className="flex-1 flex flex-col items-center justify-center">
|
||||
<ConnectionQRCode
|
||||
gateway={hubInfo?.url ?? 'http://localhost:3000'}
|
||||
hubId={hubInfo?.hubId ?? 'unknown'}
|
||||
agentId={primaryAgent?.id}
|
||||
expirySeconds={30}
|
||||
size={180}
|
||||
/>
|
||||
<ConnectionQRCode
|
||||
gateway={hubInfo?.url ?? 'http://localhost:3000'}
|
||||
hubId={hubInfo?.hubId ?? 'unknown'}
|
||||
agentId={primaryAgent?.id}
|
||||
expirySeconds={30}
|
||||
size={180}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right: Hub Status */}
|
||||
|
|
@ -107,6 +135,26 @@ export default function HomePage() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{/* Agent Settings */}
|
||||
<div className="p-4 rounded-lg bg-muted/50 border border-border/50">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<p className="text-xs text-muted-foreground uppercase tracking-wider">
|
||||
Agent Settings
|
||||
</p>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => setSettingsOpen(true)}
|
||||
>
|
||||
<HugeiconsIcon icon={Edit02Icon} className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="font-medium">{agentName || 'Unnamed Agent'}</p>
|
||||
<p className="text-xs text-muted-foreground font-mono">
|
||||
Profile: {profileId ?? 'default'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="p-4 rounded-lg bg-muted/50 border border-border/50">
|
||||
|
|
@ -142,6 +190,9 @@ export default function HomePage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Agent Settings Dialog */}
|
||||
<AgentSettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
||||
|
||||
{/* Bottom: Actions */}
|
||||
<div className="border-t p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue