feat(settings): replicate the WorkBuddy personalization panel and wire identity, tone and prompts (TODO 13)

Recover the WorkBuddy 5.8.3 个性化 page from REA evidence (PersonalizationPanel-D16rRGjy.css + tokens): 45 module rules plus scoped light/dark tokens, the five-card 1160px layout (identity card 638x100 with inline editors, reply preference card 638x204, three 638x264 long-text cards with 598x160 previews) and the 640x520 editor modal.

Persist identity, tone, welcome tips, custom instructions, persona and long-term memory in config.json, expose personalization:get/update over IPC, and append the resulting personalization context to each turn's prompt for both the OMP and built-in kernels. Wire 欢迎语 to the streaming loading tips and keep 显示文件修改过程 visibly disabled because YCode has no incremental file-change renderer yet.

Verify with scripts/verify-personalization-settings.cjs: 29 assertions covering geometry, tone options, switches, inline identity editing with blur-discard, editor modal save/cancel, config persistence, reopen, dark surfaces and no renderer errors. Add a pure prompt-builder regression and keep every existing settings regression green.
This commit is contained in:
xiaohan.yuan committed 2026-10-17 10:46:40 +08:00
1 parent 19460f0b13
commit 0dc088f1f7
54 files changed
+2038 -248

No files matched your search

+260 -230
View File
@@ -1,230 +1,260 @@
/**
* 主进程 / 渲染进程共享的类型与 IPC 契约。
*
* 已存 API Key 不返回渲染进程;用户可在模型表单编辑地址和新密钥。
* 模型网络请求经由主进程网关或 OMP 子进程完成。
*/
/** 会话内的一条消息(对外视图) */
import type { ArtifactContent, ChatAttachment, ChatInteraction, MessageQuote, PlanContent, TaskContent } from './conversation'
export interface ChatMessage {
id: string
role: 'user' | 'assistant'
content: string
/** 助手消息携带的工具调用记录 */
tools?: ToolCallRecord[]
/** 按事件顺序记录正文与工具位置;旧会话没有此字段时按兼容方式渲染。 */
parts?: MessagePart[]
attachments?: ChatAttachment[]
resources?: string[]
/** 发送时引用进对话的表格等内容块快照(WorkBuddy messageQuote)。 */
quotes?: MessageQuote[]
skills?: Array<{ id: string; name: string; uri: string }>
/** Native-history reconstruction; never includes connector credentials. */
capabilityContext?: string
expertId?: string
assistantDisplay?: import('./capabilities').ExpertBinding
/** The model selected when this assistant turn started; kept with history. */
modelId?: string
modelName?: string
interactions?: ChatInteraction[]
requestId?: string
planId?: string
nativeEntryId?: string
nativeSessionPath?: string
feedback?: 'like' | 'dislike'
interrupted?: boolean
createdAt: number
/** User-submit timestamp shared by the live timer and terminal turn duration. */
turnStartedAt?: number
/** Measured user-send to terminal-event duration, used by the source completed fold. */
turnDurationMs?: number
/** 本回合的终态 */
status?: 'streaming' | 'done' | 'error' | 'cancelled'
error?: string
/** 渲染进程专用的启动占位阶段(新任务发送后、主进程回合就绪前);不参与持久化。 */
prepareStage?: string
}
export interface ToolCallRecord {
id: string
name: string
/** Human-readable action description supplied by the tool runtime, when available. */
intent?: string
/** 已解析的参数(原始 JSON 字符串的解析结果,解析失败则保留原文) */
args: unknown
argsRaw: string
status: 'running' | 'ok' | 'error' | 'cancelled'
/** 完整的文本输出;summary 仅供折叠态摘要,不能替代原文。 */
output?: string
/** 展示用摘要(截断后的结果) */
summary?: string
durationMs?: number
/** Native structured result and renderer metadata are retained separately from text. */
result?: unknown
metadata?: Record<string, unknown>
}
export type MessagePart =
| { type: 'text'; text: string }
| { type: 'tool'; toolId: string }
| { type: 'reasoning'; id: string; text: string; finished?: boolean }
| { type: 'plan'; plan: PlanContent }
| { type: 'task'; task: TaskContent }
| { type: 'artifact'; artifact: ArtifactContent }
| { type: 'image'; id: string; url: string; alt?: string }
/** 面向模型暴露的工具描述(OpenAI function tool 格式) */
export interface ToolSpec {
type: 'function'
function: {
name: string
description: string
parameters: Record<string, unknown>
}
}
/** 网关配置:一个 OpenAI 兼容的上游 */
export interface GatewayConfig {
speechBaseUrl?: string
speechModel?: string
/** 上游 baseUrl,例如 https://api.deepseek.com/v1 */
baseUrl: string
/** 模型名,例如 deepseek-chat */
model: string
/** API Key(仅存在于主进程) */
apiKey: string
/** 采样温度 */
temperature: number
/** 单回合最大工具轮次 */
maxToolRounds: number
authMode?: 'api-key' | 'none'
maxOutputTokens?: number
}
/** 渲染进程可见的配置视图(不含 apiKey) */
export interface GatewayConfigView {
speechBaseUrl?: string
speechModel?: string
baseUrl: string
model: string
temperature: number
maxToolRounds: number
/** 是否已配置 Key(不回传明文) */
hasApiKey: boolean
/** Key 来源:safeStorage 加密 / 明文降级 */
keyStorage: 'encrypted' | 'plaintext' | 'none'
}
/** 设置 → 通用:真实状态视图(未接入项不下发数据)。 */
export interface GeneralSettingsView {
/** 系统登录项的真实状态 */
autoLaunch: boolean
/** 运行任务时阻止系统休眠(偏好) */
preventSleep: boolean
/** 当前是否有 powerSaveBlocker 生效 */
preventSleepActive: boolean
/** 数据目录(存放对话记录、运行缓存与临时文件) */
dataDir: string
/** 数据目录占用字节 */
dataBytes: number
/** 扫描是否因预算被截断 */
dataUsagePartial: boolean
/** 数据目录所在磁盘容量 */
disk: { totalBytes: number; freeBytes: number }
/** 默认工作空间存储路径 */
workspaceRoot: string
}
export interface GeneralSettingsPatch {
autoLaunch?: boolean
preventSleep?: boolean
}
/** 主进程 → 渲染进程的流式事件 */
export type ChatEvent = { sessionId?: string; turnDurationMs?: number } & (
| { runId: string; type: 'started'; messageId: string }
| { runId: string; type: 'text'; delta: string }
| { runId: string; type: 'part'; part: Exclude<MessagePart, { type: 'text' } | { type: 'tool' }> }
| { runId: string; type: 'interaction'; interaction: ChatInteraction }
| { runId: string; type: 'tool_call'; call: ToolCallRecord }
| { runId: string; type: 'tool_result'; call: ToolCallRecord }
| { runId: string; type: 'usage'; usage: TokenUsage }
| { runId: string; type: 'done'; content: string; tools: ToolCallRecord[] }
| { runId: string; type: 'cancelled' }
| { runId: string; type: 'error'; message: string }
)
/**
* 上游返回的真实 token 用量(Responses `response.completed.usage`)。
* 只做透传,不做估算:拿不到就不产生该事件。
*/
export interface TokenUsage {
inputTokens: number
outputTokens: number
totalTokens: number
cachedTokens?: number
}
/**
* 会话工作目录的 Git 信息(用于对话框的分支标签)。
* `repo: false` 表示该目录不在 Git 仓库内;`detached: true` 时 `branch` 为空、`head` 为短 SHA。
*/
export interface GitBranchInfo {
/** 目录是否位于 Git 工作树内 */
repo: boolean
/** 分支名;detached HEAD 或尚无提交时为 null */
branch: string | null
detached: boolean
/** detached HEAD 的短 SHA;无提交时为 null */
head: string | null
/** git 可执行文件缺失或命令超时时为 true,UI 不显示分支标签 */
unavailable?: boolean
}
/** 一次会话的元信息 */
export interface SessionMeta {
id: string
title: string
createdAt: number
updatedAt: number
workspaceId?: string
cwd?: string
modelId?: string
expertId?: string | null
agentMode?: import('./conversation').AgentMode
thinkingLevel?: import('./workspaces').ThinkingLevel
/** 置顶时间(毫秒)。有值即视为已置顶,用于侧栏任务排序,不改变 createdAt/updatedAt 语义。 */
pinnedAt?: number
/** 归档时间(毫秒)。归档任务从默认任务列表移出,但不删除消息与工作目录。 */
archivedAt?: number
}
/** 工作目录信息 */
export interface WorkspaceInfo {
root: string
}
/** 无边框窗口状态 */
export interface WindowState {
maximized: boolean
fullscreen: boolean
}
/** 应用信息(关于对话框用) */
export interface AppInfo {
name: string
version: string
electron: string
chrome: string
node: string
configDir: string
/** OMP 内核状态(未探测到时 available 为 false) */
omp: {
available: boolean
exePath: string | null
profile: string
model: string | null
}
}
/**
* 主进程 / 渲染进程共享的类型与 IPC 契约。
*
* 已存 API Key 不返回渲染进程;用户可在模型表单编辑地址和新密钥。
* 模型网络请求经由主进程网关或 OMP 子进程完成。
*/
/** 会话内的一条消息(对外视图) */
import type { ArtifactContent, ChatAttachment, ChatInteraction, MessageQuote, PlanContent, TaskContent } from './conversation'
export interface ChatMessage {
id: string
role: 'user' | 'assistant'
content: string
/** 助手消息携带的工具调用记录 */
tools?: ToolCallRecord[]
/** 按事件顺序记录正文与工具位置;旧会话没有此字段时按兼容方式渲染。 */
parts?: MessagePart[]
attachments?: ChatAttachment[]
resources?: string[]
/** 发送时引用进对话的表格等内容块快照(WorkBuddy messageQuote)。 */
quotes?: MessageQuote[]
skills?: Array<{ id: string; name: string; uri: string }>
/** Native-history reconstruction; never includes connector credentials. */
capabilityContext?: string
expertId?: string
assistantDisplay?: import('./capabilities').ExpertBinding
/** The model selected when this assistant turn started; kept with history. */
modelId?: string
modelName?: string
interactions?: ChatInteraction[]
requestId?: string
planId?: string
nativeEntryId?: string
nativeSessionPath?: string
feedback?: 'like' | 'dislike'
interrupted?: boolean
createdAt: number
/** User-submit timestamp shared by the live timer and terminal turn duration. */
turnStartedAt?: number
/** Measured user-send to terminal-event duration, used by the source completed fold. */
turnDurationMs?: number
/** 本回合的终态 */
status?: 'streaming' | 'done' | 'error' | 'cancelled'
error?: string
/** 渲染进程专用的启动占位阶段(新任务发送后、主进程回合就绪前);不参与持久化。 */
prepareStage?: string
}
export interface ToolCallRecord {
id: string
name: string
/** Human-readable action description supplied by the tool runtime, when available. */
intent?: string
/** 已解析的参数(原始 JSON 字符串的解析结果,解析失败则保留原文) */
args: unknown
argsRaw: string
status: 'running' | 'ok' | 'error' | 'cancelled'
/** 完整的文本输出;summary 仅供折叠态摘要,不能替代原文。 */
output?: string
/** 展示用摘要(截断后的结果) */
summary?: string
durationMs?: number
/** Native structured result and renderer metadata are retained separately from text. */
result?: unknown
metadata?: Record<string, unknown>
}
export type MessagePart =
| { type: 'text'; text: string }
| { type: 'tool'; toolId: string }
| { type: 'reasoning'; id: string; text: string; finished?: boolean }
| { type: 'plan'; plan: PlanContent }
| { type: 'task'; task: TaskContent }
| { type: 'artifact'; artifact: ArtifactContent }
| { type: 'image'; id: string; url: string; alt?: string }
/** 面向模型暴露的工具描述(OpenAI function tool 格式) */
export interface ToolSpec {
type: 'function'
function: {
name: string
description: string
parameters: Record<string, unknown>
}
}
/** 网关配置:一个 OpenAI 兼容的上游 */
export interface GatewayConfig {
speechBaseUrl?: string
speechModel?: string
/** 上游 baseUrl,例如 https://api.deepseek.com/v1 */
baseUrl: string
/** 模型名,例如 deepseek-chat */
model: string
/** API Key(仅存在于主进程) */
apiKey: string
/** 采样温度 */
temperature: number
/** 单回合最大工具轮次 */
maxToolRounds: number
authMode?: 'api-key' | 'none'
maxOutputTokens?: number
}
/** 渲染进程可见的配置视图(不含 apiKey) */
export interface GatewayConfigView {
speechBaseUrl?: string
speechModel?: string
baseUrl: string
model: string
temperature: number
maxToolRounds: number
/** 是否已配置 Key(不回传明文) */
hasApiKey: boolean
/** Key 来源:safeStorage 加密 / 明文降级 */
keyStorage: 'encrypted' | 'plaintext' | 'none'
}
/** 设置 → 通用:真实状态视图(未接入项不下发数据)。 */
export interface GeneralSettingsView {
/** 系统登录项的真实状态 */
autoLaunch: boolean
/** 运行任务时阻止系统休眠(偏好) */
preventSleep: boolean
/** 当前是否有 powerSaveBlocker 生效 */
preventSleepActive: boolean
/** 数据目录(存放对话记录、运行缓存与临时文件) */
dataDir: string
/** 数据目录占用字节 */
dataBytes: number
/** 扫描是否因预算被截断 */
dataUsagePartial: boolean
/** 数据目录所在磁盘容量 */
disk: { totalBytes: number; freeBytes: number }
/** 默认工作空间存储路径 */
workspaceRoot: string
}
export interface GeneralSettingsPatch {
autoLaunch?: boolean
preventSleep?: boolean
}
/** 设置 → 个性化的回复风格(REA ev_af920…:默认/专业严谨/亲和友善/直言不讳/天马行空/高效务实/毒舌吐槽/启发引导)。 */
export type PersonalizationTone = 'default' | 'professional' | 'friendly' | 'direct' | 'imaginative' | 'efficient' | 'sarcastic' | 'socratic'
/**
* 设置 → 个性化的持久化数据。
*
* 已接入运行链路:userCallName / aiName / tone / customPrompt / persona / longTermMemory 会拼进
* 每次对话的请求上下文;loadingTips 控制流式加载提示的欢迎语是否显示。
* showFileChanges 已在原版页面呈现,但 YCode 的「逐步展示文件修改过程」渲染尚未接入,UI 标记为不可用。
*/
export interface PersonalizationSettings {
/** WorkBuddy 对你的称呼(YCode 品牌化为「YCode 对你的称呼」)。 */
userCallName: string
/** WorkBuddy 的名字(YCode 品牌化为「YCode 的名字」)。 */
aiName: string
tone: PersonalizationTone
/** 欢迎语:对话加载较慢时显示的问候/提示。 */
loadingTips: boolean
/** 显示文件修改过程(尚未接入渲染链路)。 */
showFileChanges: boolean
/** 给 WorkBuddy 的指令(长期生效的附加要求)。 */
customPrompt: string
/** 人设设定。 */
persona: string
/** 长期记忆。 */
longTermMemory: string
}
export type PersonalizationPatch = Partial<PersonalizationSettings>
/** 主进程 → 渲染进程的流式事件 */
export type ChatEvent = { sessionId?: string; turnDurationMs?: number } & (
| { runId: string; type: 'started'; messageId: string }
| { runId: string; type: 'text'; delta: string }
| { runId: string; type: 'part'; part: Exclude<MessagePart, { type: 'text' } | { type: 'tool' }> }
| { runId: string; type: 'interaction'; interaction: ChatInteraction }
| { runId: string; type: 'tool_call'; call: ToolCallRecord }
| { runId: string; type: 'tool_result'; call: ToolCallRecord }
| { runId: string; type: 'usage'; usage: TokenUsage }
| { runId: string; type: 'done'; content: string; tools: ToolCallRecord[] }
| { runId: string; type: 'cancelled' }
| { runId: string; type: 'error'; message: string }
)
/**
* 上游返回的真实 token 用量(Responses `response.completed.usage`)。
* 只做透传,不做估算:拿不到就不产生该事件。
*/
export interface TokenUsage {
inputTokens: number
outputTokens: number
totalTokens: number
cachedTokens?: number
}
/**
* 会话工作目录的 Git 信息(用于对话框的分支标签)。
* `repo: false` 表示该目录不在 Git 仓库内;`detached: true` 时 `branch` 为空、`head` 为短 SHA。
*/
export interface GitBranchInfo {
/** 目录是否位于 Git 工作树内 */
repo: boolean
/** 分支名;detached HEAD 或尚无提交时为 null */
branch: string | null
detached: boolean
/** detached HEAD 的短 SHA;无提交时为 null */
head: string | null
/** git 可执行文件缺失或命令超时时为 true,UI 不显示分支标签 */
unavailable?: boolean
}
/** 一次会话的元信息 */
export interface SessionMeta {
id: string
title: string
createdAt: number
updatedAt: number
workspaceId?: string
cwd?: string
modelId?: string
expertId?: string | null
agentMode?: import('./conversation').AgentMode
thinkingLevel?: import('./workspaces').ThinkingLevel
/** 置顶时间(毫秒)。有值即视为已置顶,用于侧栏任务排序,不改变 createdAt/updatedAt 语义。 */
pinnedAt?: number
/** 归档时间(毫秒)。归档任务从默认任务列表移出,但不删除消息与工作目录。 */
archivedAt?: number
}
/** 工作目录信息 */
export interface WorkspaceInfo {
root: string
}
/** 无边框窗口状态 */
export interface WindowState {
maximized: boolean
fullscreen: boolean
}
/** 应用信息(关于对话框用) */
export interface AppInfo {
name: string
version: string
electron: string
chrome: string
node: string
configDir: string
/** OMP 内核状态(未探测到时 available 为 false) */
omp: {
available: boolean
exePath: string | null
profile: string
model: string | null
}
}