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:
Jiang Bohan 2026-02-04 03:12:22 +08:00
parent a157c6546d
commit bdc2006e0e
2 changed files with 188 additions and 7 deletions

View 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&#10;- I prefer TypeScript&#10;- 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

View file

@ -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">