refactor(profile): remove Communication Style UI and programmatic API

Style is now solely managed by the agent editing soul.md directly,
removing the need for UI controls, IPC handlers, and typed constants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jiang Bohan 2026-02-09 14:24:07 +08:00
parent b7085b2bf5
commit 22e225c6a8
9 changed files with 24 additions and 190 deletions

View file

@ -81,7 +81,6 @@ interface SkillAddResult {
interface ProfileData {
profileId: string | undefined
name: string | undefined
style: string | undefined
userContent: string | undefined
}
@ -177,7 +176,6 @@ interface ElectronAPI {
profile: {
get: () => Promise<ProfileData>
updateName: (name: string) => Promise<unknown>
updateStyle: (style: string) => Promise<unknown>
updateUser: (content: string) => Promise<unknown>
}
provider: {
@ -190,17 +188,17 @@ interface ElectronAPI {
saveApiKey: (providerId: string, apiKey: string) => Promise<{ ok: boolean; error?: string }>
importOAuth: (providerId: string) => Promise<{ ok: boolean; expiresAt?: number; error?: string }>
}
cron: {
list: () => Promise<unknown[]>
toggle: (jobId: string) => Promise<{ ok: boolean }>
remove: (jobId: string) => Promise<{ ok: boolean }>
}
heartbeat: {
last: () => Promise<unknown>
setEnabled: (enabled: boolean) => Promise<{ ok: boolean; enabled?: boolean; error?: string }>
wake: (reason?: string) => Promise<{ ok: boolean; result?: unknown; error?: string }>
}
localChat: {
cron: {
list: () => Promise<unknown[]>
toggle: (jobId: string) => Promise<{ ok: boolean }>
remove: (jobId: string) => Promise<{ ok: boolean }>
}
heartbeat: {
last: () => Promise<unknown>
setEnabled: (enabled: boolean) => Promise<{ ok: boolean; enabled?: boolean; error?: string }>
wake: (reason?: string) => Promise<{ ok: boolean; result?: unknown; error?: string }>
}
localChat: {
subscribe: (agentId: string) => Promise<{ ok?: boolean; error?: string; alreadySubscribed?: boolean }>
unsubscribe: (agentId: string) => Promise<{ ok: boolean }>
getHistory: (agentId: string, options?: { offset?: number; limit?: number }) => Promise<{ messages: unknown[]; total: number; offset: number; limit: number }>

View file

@ -25,7 +25,6 @@ function getDefaultAgent() {
export interface ProfileData {
profileId: string | undefined
name: string | undefined
style: string | undefined
userContent: string | undefined
}
@ -42,7 +41,6 @@ export function registerProfileIpcHandlers(): void {
return {
profileId: undefined,
name: undefined,
style: undefined,
userContent: undefined,
}
}
@ -50,7 +48,6 @@ export function registerProfileIpcHandlers(): void {
return {
profileId: agent.getProfileId(),
name: agent.getAgentName(),
style: agent.getAgentStyle(),
userContent: agent.getUserContent(),
}
})
@ -92,19 +89,4 @@ 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,7 +40,6 @@ export interface SkillInfo {
export interface ProfileData {
profileId: string | undefined
name: string | undefined
style: string | undefined
userContent: string | undefined
}
@ -93,10 +92,6 @@ export interface LocalChatApproval {
expiresAtMs: number
}
// 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
// ============================================================================
@ -174,7 +169,6 @@ 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),
},
@ -202,17 +196,17 @@ const electronAPI = {
},
// Cron jobs management
cron: {
list: () => ipcRenderer.invoke('cron:list'),
toggle: (jobId: string) => ipcRenderer.invoke('cron:toggle', jobId),
remove: (jobId: string) => ipcRenderer.invoke('cron:remove', jobId),
},
heartbeat: {
last: () => ipcRenderer.invoke('heartbeat:last'),
setEnabled: (enabled: boolean) => ipcRenderer.invoke('heartbeat:setEnabled', enabled),
wake: (reason?: string) => ipcRenderer.invoke('heartbeat:wake', reason),
},
cron: {
list: () => ipcRenderer.invoke('cron:list'),
toggle: (jobId: string) => ipcRenderer.invoke('cron:toggle', jobId),
remove: (jobId: string) => ipcRenderer.invoke('cron:remove', jobId),
},
heartbeat: {
last: () => ipcRenderer.invoke('heartbeat:last'),
setEnabled: (enabled: boolean) => ipcRenderer.invoke('heartbeat:setEnabled', enabled),
wake: (reason?: string) => ipcRenderer.invoke('heartbeat:wake', reason),
},
// Local chat (direct IPC, no Gateway required)
localChat: {

View file

@ -12,15 +12,7 @@ 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, 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
import { Loading03Icon } from '@hugeicons/core-free-icons'
interface AgentSettingsDialogProps {
open: boolean
@ -31,7 +23,6 @@ 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('')
// Load profile data when dialog opens
@ -46,7 +37,6 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
try {
const data = await window.electronAPI.profile.get()
setName(data.name ?? '')
setStyle(data.style ?? 'concise')
setUserContent(data.userContent ?? '')
} catch (err) {
console.error('Failed to load profile:', err)
@ -60,8 +50,6 @@ 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)
@ -78,7 +66,7 @@ export function AgentSettingsDialog({ open, onOpenChange }: AgentSettingsDialogP
<DialogHeader>
<DialogTitle>Edit Agent</DialogTitle>
<DialogDescription>
Customize your agent's name, style and personal settings.
Customize your agent's name and personal settings.
</DialogDescription>
</DialogHeader>
@ -99,35 +87,6 @@ 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>

View file

@ -58,10 +58,6 @@ vi.mock("./runner.js", () => ({
return undefined;
}
setUserContent() {}
getAgentStyle() {
return undefined;
}
setAgentStyle() {}
reloadSystemPrompt() {}
getProviderInfo() {
return { provider: "test", model: "test-model" };

View file

@ -296,20 +296,6 @@ export class AsyncAgent {
this.agent.setUserContent(content);
}
/**
* Get agent communication style from profile config.
*/
getAgentStyle(): string | undefined {
return this.agent.getAgentStyle();
}
/**
* Update agent communication style in profile config.
*/
setAgentStyle(style: string): void {
this.agent.setAgentStyle(style);
}
/**
* Reload profile from disk and rebuild system prompt.
* Call this after updating profile files to apply changes immediately.

View file

@ -297,59 +297,4 @@ export class ProfileManager {
}
}
/** 获取 Agent 风格 */
getStyle(): string | undefined {
const profile = this.getProfile();
return profile?.config?.style;
}
/** 更新 Agent 风格 */
updateStyle(style: string): void {
const profile = this.getOrCreateProfile(false);
const currentConfig = profile.config ?? {};
// Use Object.assign to avoid exactOptionalPropertyTypes issues with spread
const newConfig: ProfileConfig = Object.assign({}, currentConfig, {
style: style as ProfileConfig["style"],
});
profile.config = newConfig;
this.profile = profile;
writeProfileConfig(this.profileId, newConfig, { baseDir: this.baseDir });
// Also update soul.md to include the style
this.updateSoulWithStyle(style);
}
/** 更新 soul.md确保包含 Agent 风格 */
private updateSoulWithStyle(style: string): void {
const profile = this.getOrCreateProfile(true);
let soulContent = profile.soul ?? DEFAULT_TEMPLATES.soul;
// 替换 soul.md 中的 Style 字段
// 匹配 "- **Style:** xxx" 格式
const stylePattern = /- \*\*Style:\*\* .*/;
const newStyleLine = `- **Style:** ${style}`;
if (stylePattern.test(soulContent)) {
soulContent = soulContent.replace(stylePattern, newStyleLine);
} else {
// 如果没有找到 Style 字段,在 Identity 部分的 Role 后添加
const rolePattern = /(- \*\*Role:\*\* .*)/;
if (rolePattern.test(soulContent)) {
soulContent = soulContent.replace(rolePattern, `$1\n${newStyleLine}`);
} else {
// 如果没有 Role尝试在 Name 后添加
const namePattern = /(- \*\*Name:\*\* .*)/;
if (namePattern.test(soulContent)) {
soulContent = soulContent.replace(namePattern, `$1\n${newStyleLine}`);
}
}
}
// 保存更新后的 soul.md
writeProfileFile(this.profileId, PROFILE_FILES.soul, soulContent, { baseDir: this.baseDir });
// 更新缓存
if (this.profile) {
this.profile.soul = soulContent;
}
}
}

View file

@ -15,22 +15,10 @@ export const PROFILE_FILES = {
config: "config.json",
} as const;
/** Available style options for agent personality */
export const AGENT_STYLES = [
"concise", // 简洁直接
"warm", // 温暖友好
"playful", // 轻松活泼
"professional", // 专业正式
] as const;
export type AgentStyle = (typeof AGENT_STYLES)[number];
/** Profile config.json structure */
export interface ProfileConfig {
/** Agent display name */
name?: string;
/** Agent communication style */
style?: AgentStyle;
/** Tools policy configuration */
tools?: ToolsConfig;
/** Default LLM provider */

View file

@ -751,20 +751,6 @@ export class Agent {
this.profile?.updateUserContent(content);
}
/**
* Get agent communication style from profile config.
*/
getAgentStyle(): string | undefined {
return this.profile?.getStyle();
}
/**
* Update agent communication style in profile config.
*/
setAgentStyle(style: string): void {
this.profile?.updateStyle(style);
}
/**
* Get current provider and model information.
*/