feat(desktop): add style selector to agent settings dialog

- Add IPC handlers for profile:getStyle and profile:setStyle
- Add style dropdown with 6 preset options in agent settings UI
- Display style descriptions to help users choose

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jiang Bohan 2026-02-04 14:42:29 +08:00
parent 4df39448be
commit 5d102c0241
4 changed files with 71 additions and 12 deletions

View file

@ -68,6 +68,7 @@ interface SkillAddResult {
interface ProfileData {
profileId: string | undefined
name: string | undefined
style: string | undefined
userContent: string | undefined
}
@ -106,6 +107,7 @@ interface ElectronAPI {
profile: {
get: () => Promise<ProfileData>
updateName: (name: string) => Promise<unknown>
updateStyle: (style: string) => Promise<unknown>
updateUser: (content: string) => Promise<unknown>
}
}

View file

@ -25,6 +25,7 @@ function getDefaultAgent() {
export interface ProfileData {
profileId: string | undefined
name: string | undefined
style: string | undefined
userContent: string | undefined
}
@ -41,6 +42,7 @@ export function registerProfileIpcHandlers(): void {
return {
profileId: undefined,
name: undefined,
style: undefined,
userContent: undefined,
}
}
@ -48,6 +50,7 @@ export function registerProfileIpcHandlers(): void {
return {
profileId: agent.getProfileId(),
name: agent.getAgentName(),
style: agent.getAgentStyle(),
userContent: agent.getUserContent(),
}
})
@ -88,4 +91,20 @@ export function registerProfileIpcHandlers(): void {
return { ok: true }
})
/**
* Update agent communication style.
*/
ipcMain.handle('profile:updateStyle', async (_event, style: string) => {
const agent = getDefaultAgent()
if (!agent) {
return { error: 'No agent available' }
}
agent.setAgentStyle(style)
// Reload system prompt to apply changes immediately
agent.reloadSystemPrompt()
return { ok: true, style }
})
}

View file

@ -40,9 +40,14 @@ export interface SkillInfo {
export interface ProfileData {
profileId: string | undefined
name: string | undefined
style: string | undefined
userContent: string | undefined
}
// Available style options
export const AGENT_STYLES = ['concise', 'warm', 'playful', 'professional'] as const
export type AgentStyle = (typeof AGENT_STYLES)[number]
// ============================================================================
// Expose typed API to Renderer process
// ============================================================================
@ -95,6 +100,7 @@ const electronAPI = {
profile: {
get: (): Promise<ProfileData> => ipcRenderer.invoke('profile:get'),
updateName: (name: string) => ipcRenderer.invoke('profile:updateName', name),
updateStyle: (style: string) => ipcRenderer.invoke('profile:updateStyle', style),
updateUser: (content: string) => ipcRenderer.invoke('profile:updateUser', content),
},
}

View file

@ -12,7 +12,15 @@ 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'
import { Loading03Icon, Tick02Icon } from '@hugeicons/core-free-icons'
// Style options with labels
const STYLE_OPTIONS = [
{ value: 'concise', label: 'Concise', description: 'Brief and to the point' },
{ value: 'warm', label: 'Warm', description: 'Friendly and approachable' },
{ value: 'playful', label: 'Playful', description: 'Fun and lighthearted' },
{ value: 'professional', label: 'Professional', description: 'Formal and business-like' },
] as const
interface AgentSettingsDialogProps {
open: boolean
@ -23,8 +31,8 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [name, setName] = useState('')
const [style, setStyle] = useState<string>('concise')
const [userContent, setUserContent] = useState('')
const [profileId, setProfileId] = useState<string | undefined>()
// Load profile data when dialog opens
useEffect(() => {
@ -37,8 +45,8 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
setLoading(true)
try {
const data = await window.electronAPI.profile.get()
setProfileId(data.profileId)
setName(data.name ?? '')
setStyle(data.style ?? 'concise')
setUserContent(data.userContent ?? '')
} catch (err) {
console.error('Failed to load profile:', err)
@ -52,6 +60,8 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
try {
// Update name if changed
await window.electronAPI.profile.updateName(name)
// Update style
await window.electronAPI.profile.updateStyle(style)
// Update user content
await window.electronAPI.profile.updateUser(userContent)
onOpenChange(false)
@ -68,7 +78,7 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
<DialogHeader>
<DialogTitle>Edit Agent</DialogTitle>
<DialogDescription>
Customize your agent's name and personal settings.
Customize your agent's name, style and personal settings.
</DialogDescription>
</DialogHeader>
@ -78,13 +88,6 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
</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>
@ -96,6 +99,35 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
/>
</div>
{/* Style */}
<div className="space-y-2">
<Label>Communication Style</Label>
<div className="grid grid-cols-2 gap-2">
{STYLE_OPTIONS.map((option) => (
<button
key={option.value}
type="button"
onClick={() => setStyle(option.value)}
className={`relative flex flex-col items-start rounded-lg border p-3 text-left transition-colors hover:bg-accent ${
style === option.value
? 'border-primary bg-primary/5'
: 'border-border'
}`}
>
<div className="flex w-full items-center justify-between">
<span className="font-medium text-sm">{option.label}</span>
{style === option.value && (
<HugeiconsIcon icon={Tick02Icon} className="size-4 text-primary" />
)}
</div>
<span className="text-xs text-muted-foreground mt-0.5">
{option.description}
</span>
</button>
))}
</div>
</div>
{/* User Content */}
<div className="space-y-2">
<Label htmlFor="user-content">About You</Label>
@ -107,7 +139,7 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
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"
className="min-h-[120px] font-mono text-sm"
/>
</div>
</div>