@@ -2284,8 +2116,6 @@ export function SettingsPanel({
onClick={() => {
setClearingLocalData(true)
setError(undefined)
- setSaved(false)
- setConnectionResult(undefined)
void onClearLocalData()
.then(() => {
setConfirmingClear(false)
diff --git a/src/renderer/src/SettingsPrimitives.tsx b/src/renderer/src/SettingsPrimitives.tsx
new file mode 100644
index 0000000..7391523
--- /dev/null
+++ b/src/renderer/src/SettingsPrimitives.tsx
@@ -0,0 +1,37 @@
+import type { ReactNode } from 'react'
+import {
+ settingsCategories,
+ type SettingsCategoryId
+} from './settings-categories'
+
+export function SettingsCategoryHeader({
+ actions,
+ category,
+ error,
+ headingId = `settings-category-${category}`
+}: {
+ actions?: ReactNode
+ category: SettingsCategoryId
+ error?: string
+ headingId?: string
+}): React.JSX.Element {
+ const definition = settingsCategories[category]
+ return (
+
+
+
{definition.label}
+
{definition.description}
+
+ {actions && (
+
+ {actions}
+
+ )}
+ {error && (
+
+ {error}
+
+ )}
+
+ )
+}
diff --git a/src/renderer/src/SkillsSettingsSection.tsx b/src/renderer/src/SkillsSettingsSection.tsx
index fbb6c05..ce68c10 100644
--- a/src/renderer/src/SkillsSettingsSection.tsx
+++ b/src/renderer/src/SkillsSettingsSection.tsx
@@ -1,10 +1,11 @@
-import { BookOpen, Download, Trash2 } from 'lucide-react'
+import { Download, Trash2 } from 'lucide-react'
import { useEffect, useState } from 'react'
import type {
CapabilityAssignments,
CapabilitySnapshot,
RuntimeTarget
} from '../../shared/capability-contracts'
+import { SettingsCategoryHeader } from './SettingsPrimitives'
const runtimeLabels: Record
= {
model: '模型',
@@ -56,46 +57,48 @@ export function SkillsSettingsSection(): React.JSX.Element {
}
return (
-
-
-
-
- Skills
- 支持直连模型、OpenCode 和 Continue
-
-
-
-
+ <>
+
+
+
+ >
+ }
+ category="skills"
+ error={error}
+ headingId="skills-settings-heading"
+ />
+
Skill 以本地能力说明注入所选目标,不会写入 Runtime
自有配置。新导入的 Skill 默认启用,并分配给直连模型、OpenCode 和 Continue。
- {error && {error}
}
{!snapshot && !error && 正在读取 Skills…
}
{snapshot?.skills.map((skill) => (
@@ -174,6 +177,7 @@ export function SkillsSettingsSection(): React.JSX.Element {
))}
-
+
+ >
)
}
diff --git a/src/renderer/src/UpdateSettingsSection.tsx b/src/renderer/src/UpdateSettingsSection.tsx
index 9bc0dcd..1423e99 100644
--- a/src/renderer/src/UpdateSettingsSection.tsx
+++ b/src/renderer/src/UpdateSettingsSection.tsx
@@ -1,10 +1,11 @@
-import { ExternalLink, Info, RefreshCw } from 'lucide-react'
+import { ExternalLink, RefreshCw } from 'lucide-react'
import { useEffect, useState } from 'react'
import type {
ApplicationSettings,
VersionCheckResult
} from '../../shared/application-settings-contracts'
import type { AppInfo } from '../../shared/contracts'
+import { SettingsCategoryHeader } from './SettingsPrimitives'
function formatBytes(bytes: number): string {
if (bytes >= 1024 * 1024 * 1024) {
@@ -106,17 +107,16 @@ export function UpdateSettingsSection(): React.JSX.Element {
}
return (
-
-
-
-
- 关于与更新
- 只检查 GoodBuddy 官方 GitHub Release,不自动下载安装
-
-
+ <>
+
+
@@ -165,11 +165,6 @@ export function UpdateSettingsSection(): React.JSX.Element {
- {error && (
-
- {error}
-
- )}
{result && (
)}
-
+
+ >
)
}
diff --git a/src/renderer/src/settings-categories.ts b/src/renderer/src/settings-categories.ts
new file mode 100644
index 0000000..506c1fb
--- /dev/null
+++ b/src/renderer/src/settings-categories.ts
@@ -0,0 +1,76 @@
+export const settingsCategoryList = [
+ {
+ id: 'appearance',
+ label: '外观',
+ navigationDescription: '亮色、暗色与系统主题',
+ description: '亮色、暗色与系统主题'
+ },
+ {
+ id: 'platform-features',
+ label: '平台功能',
+ navigationDescription: '功能入口与工作区能力',
+ description: '控制 GoodBuddy 工作区中显示的功能入口'
+ },
+ {
+ id: 'model',
+ label: '模型连接',
+ navigationDescription: 'LLM、向量模型与凭据',
+ description: 'LLM、向量模型与凭据'
+ },
+ {
+ id: 'runtime',
+ label: 'Agent Runtime',
+ navigationDescription: 'OpenCode、Continue 与工作区',
+ description: 'OpenCode、Continue 与工作区'
+ },
+ {
+ id: 'security',
+ label: '安全与数据',
+ navigationDescription: '工具策略与本地隐私',
+ description: '工具策略与本地隐私'
+ },
+ {
+ id: 'automation',
+ label: '自动化',
+ navigationDescription: '智能心跳与周期回顾',
+ description: '智能心跳与周期回顾'
+ },
+ {
+ id: 'channels',
+ label: '消息通道',
+ navigationDescription: '微信、企业微信与钉钉',
+ description: '配置连接、工作目录、消息处理后端与默认模式'
+ },
+ {
+ id: 'roles',
+ label: '角色与提示词',
+ navigationDescription: '角色、说明与系统提示词',
+ description: '角色、说明与系统提示词'
+ },
+ {
+ id: 'skills',
+ label: 'Skills',
+ navigationDescription: '内置与自定义能力',
+ description: '支持直连模型、OpenCode 和 Continue'
+ },
+ {
+ id: 'mcp',
+ label: 'MCP',
+ navigationDescription: '工具服务与凭据',
+ description: '查看内置工具、内置 MCP,并管理外部 MCP Server'
+ },
+ {
+ id: 'about',
+ label: '关于与更新',
+ navigationDescription: '版本检查与下载页',
+ description: '只检查 GoodBuddy 官方 GitHub Release,不自动下载安装'
+ }
+] as const
+
+export type SettingsCategoryDefinition =
+ (typeof settingsCategoryList)[number]
+export type SettingsCategoryId = SettingsCategoryDefinition['id']
+
+export const settingsCategories = Object.fromEntries(
+ settingsCategoryList.map((category) => [category.id, category])
+) as Record
diff --git a/src/renderer/src/styles.css b/src/renderer/src/styles.css
index 67e2118..504fa5a 100644
--- a/src/renderer/src/styles.css
+++ b/src/renderer/src/styles.css
@@ -4065,15 +4065,34 @@ button > svg * {
scrollbar-gutter: stable;
}
-.settings-panel__content-toolbar {
- display: flex;
- min-height: var(--control-height);
- align-items: center;
- justify-content: flex-end;
- gap: var(--space-3);
+.settings-category-header {
+ display: grid;
+ min-width: 0;
+ align-items: start;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: var(--space-3) var(--space-4);
}
-.settings-panel__content-actions {
+.settings-category-header__content {
+ min-width: 0;
+}
+
+.settings-category-header__content h2 {
+ margin: 0;
+ color: var(--text-primary);
+ font-size: var(--font-section-title);
+ font-weight: 650;
+ line-height: 1.4;
+}
+
+.settings-category-header__content p {
+ margin: var(--space-1) 0 0;
+ color: var(--text-muted);
+ font-size: var(--font-caption);
+ line-height: 1.5;
+}
+
+.settings-category-header__actions {
display: flex;
align-items: center;
justify-content: flex-end;
@@ -4081,13 +4100,19 @@ button > svg * {
gap: var(--space-2);
}
-.settings-panel__content-actions .primary-button {
+.settings-category-header__actions .primary-button,
+.settings-category-header__actions .secondary-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
}
+.settings-category-header > .settings-warning {
+ margin: 0;
+ grid-column: 1 / -1;
+}
+
.settings-page .settings-tabs button {
min-height: 58px;
padding: 10px 12px;
@@ -5567,12 +5592,6 @@ details.settings-section > :not(summary) + :not(summary) {
color: var(--success) !important;
}
-.settings-feedback {
- flex: 1;
- min-width: 0;
- font-size: 10px;
-}
-
.settings-error {
color: var(--danger);
}
@@ -8773,17 +8792,13 @@ details.settings-section > :not(summary) + :not(summary) {
flex-basis: 100%;
}
- .settings-panel__content-toolbar {
- align-items: stretch;
- flex-wrap: wrap;
+ .settings-category-header {
+ grid-template-columns: 1fr;
}
- .settings-feedback {
- flex-basis: 100%;
- }
-
- .settings-panel__content-actions {
+ .settings-category-header__actions {
width: 100%;
+ justify-content: flex-start;
}
.mcp-editor {