From 65830d70fe4be8cfc6a4b065e8160928d0f4bff1 Mon Sep 17 00:00:00 2001
From: Wxw-Gu
Date: Thu, 20 Aug 2026 10:18:59 +0800
Subject: [PATCH] =?UTF-8?q?refactor(ui):=20=E7=BB=9F=E4=B8=80=E8=BF=81?=
=?UTF-8?q?=E7=A7=BB=E5=8E=9F=E7=94=9F=E6=8C=89=E9=92=AE=E3=80=81=E8=BE=93?=
=?UTF-8?q?=E5=85=A5=E6=A1=86=E5=8F=8A=E4=BA=A4=E4=BA=92=E6=8E=A7=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/components/DatabaseConnectionPage.tsx | 671 +++++++++---------
.../src/components/chat/ChatHeader.tsx | 82 ++-
.../src/components/chat/ChatStatusBar.tsx | 19 +-
.../chat/ConversationContentSearch.tsx | 19 +-
.../src/components/chat/ExportMenu.tsx | 16 +-
.../chat/PersonalWechatSendDialog.tsx | 163 +++--
.../conversation/ConversationSearch.tsx | 7 +-
.../ConversationSidebarHeader.tsx | 9 +-
.../components/reports/AiReportWorkspace.tsx | 28 +-
.../reports/MessageTypeSelector.tsx | 20 +-
.../src/components/reports/ModelSummary.tsx | 55 +-
.../reports/ReportDensitySelector.tsx | 37 +-
.../components/reports/ReportEmptyState.tsx | 5 +-
.../components/reports/ReportExportStatus.tsx | 5 +-
.../reports/ReportGroupMemberSelector.tsx | 30 +-
.../reports/ReportHistorySidebar.tsx | 10 +-
.../components/reports/ReportInfoPanel.tsx | 9 +-
.../reports/ReportMemberNameSelector.tsx | 21 +-
.../reports/ReportRangeSelector.tsx | 37 +-
.../reports/ReportSettingsPanel.tsx | 5 +-
.../reports/ReportSourceSidebar.tsx | 10 +-
.../reports/ReportTaskStatusPanel.tsx | 51 +-
.../reports/ReportTemplateSelector.tsx | 119 ++--
.../src/components/reports/ReportZoomBar.tsx | 17 +-
.../components/search/AISearchWorkspace.tsx | 69 +-
src/renderer/src/components/ui/index.ts | 1 +
.../src/components/ui/segmented-control.tsx | 32 +
src/renderer/src/components/ui/select.tsx | 4 +-
.../features/agent-hub/AgentHubWorkspace.tsx | 66 +-
.../components/ApiRequestTester.tsx | 27 +-
.../api-center/components/ApiRuntimePanel.tsx | 53 +-
.../api-center/components/EndpointCatalog.tsx | 19 +-
.../components/ReaderSkillOverview.tsx | 40 +-
.../api-center/components/SkillDetails.tsx | 13 +-
.../components/SkillInstallFlow.tsx | 18 +-
.../components/SkillTargetSelector.tsx | 19 +-
.../account-database/AccountOverview.tsx | 33 +-
.../ai-model/AIImageUnderstandingTest.tsx | 14 +-
.../settings/ai-model/AIProviderCard.tsx | 17 +-
.../settings/ai-model/AIProviderEditor.tsx | 190 +++--
.../database-key/DatabaseKeyAutoDetect.tsx | 19 +-
.../database-key/DatabaseKeyDangerZone.tsx | 26 +-
.../database-key/DatabaseKeyDiagnostics.tsx | 5 +-
.../database-key/DatabaseKeyEditor.tsx | 16 +-
.../database-key/DatabaseKeyStatus.tsx | 10 +-
.../AutoDetectImageKeySection.tsx | 15 +-
.../image-decryption/ImageDecryptStatus.tsx | 5 +-
.../image-decryption/ImageTestSection.tsx | 122 ++--
.../features/settings/pages/AIModelPage.tsx | 5 +-
.../src/features/settings/pages/AboutPage.tsx | 55 +-
.../settings/pages/AccountDatabasePage.tsx | 15 +-
.../features/settings/pages/AdvancedPage.tsx | 11 +-
.../settings/pages/CacheCleanupPage.tsx | 40 +-
.../settings/pages/RecallProtectionPage.tsx | 13 +-
.../settings/pages/TextToSpeechPage.tsx | 122 ++--
.../settings/pages/VoiceRecognitionPage.tsx | 150 ++--
src/renderer/src/styles/api.scss | 131 +---
src/renderer/src/styles/chat.scss | 496 +------------
src/renderer/src/styles/conversation.scss | 54 +-
src/renderer/src/styles/reports-core.scss | 300 +-------
.../src/styles/reports-templates.scss | 105 +--
src/renderer/src/styles/search.scss | 89 ---
.../src/styles/settings-advanced.scss | 11 -
.../src/styles/settings-preferences.scss | 107 +--
.../src/styles/settings-text-to-speech.scss | 119 +---
src/renderer/src/styles/settings.scss | 189 +----
.../account-database-controls.test.tsx | 64 ++
tests/component/agent-hub-controls.test.tsx | 78 ++
tests/component/ai-model-settings.test.tsx | 114 ++-
.../ai-search-workspace-regression.test.tsx | 44 ++
tests/component/api-center-controls.test.tsx | 178 +++++
tests/component/cache-cleanup.test.tsx | 11 +
tests/component/chat-menus.test.tsx | 94 ++-
.../conversation-sidebar-header.test.tsx | 43 +-
tests/component/conversation-sidebar.test.tsx | 55 +-
tests/component/database-connection.test.tsx | 49 +-
.../component/database-key-controls.test.tsx | 130 ++++
.../component/image-decryption-test.test.tsx | 104 ++-
.../personal-wechat-send-dialog.test.tsx | 17 +
tests/component/report-features.test.tsx | 89 ++-
tests/component/settings-basic-pages.test.tsx | 74 ++
tests/component/text-to-speech-page.test.tsx | 48 +-
.../voice-recognition-settings.test.tsx | 10 +-
tests/e2e/app.spec.ts | 354 ++++++++-
tests/e2e/support/electron-main.cjs | 73 +-
tests/e2e/visual.spec.ts | 519 +++++++++++++-
tests/setup/component.ts | 15 +
87 files changed, 3685 insertions(+), 2768 deletions(-)
create mode 100644 src/renderer/src/components/ui/segmented-control.tsx
create mode 100644 tests/component/account-database-controls.test.tsx
create mode 100644 tests/component/agent-hub-controls.test.tsx
create mode 100644 tests/component/api-center-controls.test.tsx
create mode 100644 tests/component/database-key-controls.test.tsx
create mode 100644 tests/component/settings-basic-pages.test.tsx
diff --git a/src/renderer/src/components/DatabaseConnectionPage.tsx b/src/renderer/src/components/DatabaseConnectionPage.tsx
index 8e4ad2b..f21a179 100644
--- a/src/renderer/src/components/DatabaseConnectionPage.tsx
+++ b/src/renderer/src/components/DatabaseConnectionPage.tsx
@@ -1,6 +1,7 @@
import React from 'react'
import type { DatabaseKeyEnvironment, WechatAccountCandidate } from '../../../shared/database-key'
import { WINDOWS_VC_RUNTIME_DOWNLOAD_URL } from '../../../shared/windows-runtime'
+import { Button, IconButton, Input, Tabs, TabsList, TabsTrigger } from './ui'
const GUIDE_URL =
'https://github.com/Wxw-Gu/WechatExplorer/blob/main/docs/user-guide/getting-started.md'
@@ -207,366 +208,360 @@ export function DatabaseConnectionPage({
查看 5 分钟上手教程 →
-
-
-
-
+ 开始连接
+ 高级用户:已有数据库密钥?手动连接
+
- {mode === 'automatic' ? (
-
-
- {Array.from({ length: 6 }, (_, index) => (
-
- ))}
-
-
-
-
-
-
-
-
- {statusKind === 'error'
- ? '当前步骤未完成'
- : [
- '检查本机环境',
- '让微信停在登录页面',
- '确认开始准备',
- `正在完成 ${isMac ? 'macOS' : 'Windows'} 授权`,
- '获取成功, 现在可以重新登录微信了',
- '验证数据库连接'
- ][guideStep - 1]}
-
-
- {statusKind === 'error'
- ? status
- : status ||
- [
- '确认下方检测结果;没有找到目录时可以手动选择。',
- '请退出当前微信账号,让微信停留在登录页面,然后点击“我已准备好”。',
- '开始后请按页面提示完成系统授权。',
- '正在准备连接组件,请不要关闭微信或 TraceMemo。',
- '请回到微信完成登录,登录成功后再回来验证。',
- '正在验证密钥和本地数据库,请稍候。'
- ][guideStep - 1]}
-
-
+ {mode === 'automatic' ? (
+
+
+ {Array.from({ length: 6 }, (_, index) => (
+
+ ))}
- {guideStep === 1 && (
- <>
-
-
-
- 操作系统
- - {environment?.osVersion || (isMac ? 'macOS' : 'Windows')}
-
-
-
- 微信版本
- - {environment?.wechatVersion || '未检测到'}
-
-
-
- 数据结构
- - {environment?.dataStructureVersion || '未检测到'}
-
-
-
-
- 存储路径
-
-
-
-
-
- onDbRootChange(event.target.value)}
- placeholder={defaultPath}
- title={dbRoot || defaultPath}
- aria-label="微信数据存储路径"
- spellCheck={false}
- onFocus={(event) => event.currentTarget.select()}
- />
-
- {dbRoot || defaultPath}
+
+
+
+
+
+
+
+ {statusKind === 'error'
+ ? '当前步骤未完成'
+ : [
+ '检查本机环境',
+ '让微信停在登录页面',
+ '确认开始准备',
+ `正在完成 ${isMac ? 'macOS' : 'Windows'} 授权`,
+ '获取成功, 现在可以重新登录微信了',
+ '验证数据库连接'
+ ][guideStep - 1]}
+
+
+ {statusKind === 'error'
+ ? status
+ : status ||
+ [
+ '确认下方检测结果;没有找到目录时可以手动选择。',
+ '请退出当前微信账号,让微信停留在登录页面,然后点击“我已准备好”。',
+ '开始后请按页面提示完成系统授权。',
+ '正在准备连接组件,请不要关闭微信或 TraceMemo。',
+ '请回到微信完成登录,登录成功后再回来验证。',
+ '正在验证密钥和本地数据库,请稍候。'
+ ][guideStep - 1]}
+
+
+
+ {guideStep === 1 && (
+ <>
+
+
+
- 操作系统
+ - {environment?.osVersion || (isMac ? 'macOS' : 'Windows')}
+
+
+
- 微信版本
+ - {environment?.wechatVersion || '未检测到'}
+
+
+
- 数据结构
+ - {environment?.dataStructureVersion || '未检测到'}
+
+
+
-
+ 存储路径
+
+
+ -
+
+ onDbRootChange(event.target.value)}
+ placeholder={defaultPath}
+ title={dbRoot || defaultPath}
+ aria-label="微信数据存储路径"
+ spellCheck={false}
+ onFocus={(event) => event.currentTarget.select()}
+ />
+
+ {dbRoot || defaultPath}
+
-
-
+
+
+
+
- 微信状态
+ - {environment?.wechatRunning ? '运行中' : '未检测到'}
+
+
+ {accounts.length > 0 && (
+
+ 选择微信账号
+ {accounts.map((account) => (
+
+ ))}
+
-
-
-
-
- 微信状态
- - {environment?.wechatRunning ? '运行中' : '未检测到'}
-
-
- {accounts.length > 0 && (
-
- 选择微信账号
- {accounts.map((account) => (
-
- ))}
-
-
- )}
+ 选择其他账号
+
+
+ )}
+ >
+ )}
+
+ {guideStep === 1 && (
+ <>
+
+
+
>
)}
-
- {guideStep === 1 && (
- <>
-
+ )}
+ {guideStep === 3 && (
+
+ )}
+ {guideStep === 4 && (
+
+ )}
+ {guideStep === 5 && (
+
-
-
+ {isConnecting && (
+
+ 取消连接
+
+ )}
+
+ 从剪贴板粘贴并安全保存
+
+
+ )}
+
-
+
清除已保存密钥
-
+
TraceMemo
diff --git a/src/renderer/src/components/chat/ChatHeader.tsx b/src/renderer/src/components/chat/ChatHeader.tsx
index 8a0d5f8..3ece724 100644
--- a/src/renderer/src/components/chat/ChatHeader.tsx
+++ b/src/renderer/src/components/chat/ChatHeader.tsx
@@ -1,6 +1,16 @@
import React, { useState } from 'react'
import { Contact } from '../../../../shared/types'
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui'
+import {
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+ IconButton,
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger
+} from '../ui'
import { ConversationContentSearch } from './ConversationContentSearch'
import { AiIcon, MoreIcon, RefreshIcon, SearchIcon, SendIcon } from './icons'
import { supportsPersonalWechatSend } from '../../utils/runtime-environment'
@@ -43,7 +53,7 @@ export function ChatHeader({
}
return (
-
+
{contact.avatar ? (
@@ -69,54 +79,72 @@ export function ChatHeader({
onClose={handleCloseSearch}
/>
) : (
-
setSearchOpen(true)}
- title="搜索当前聊天"
>
-
+
)}
-
+
-
+
-
+
-
+
onRefreshData?.()}>刷新数据
-
-
发送消息
-
-
-
+ ) : (
+
+
+
+
+
+ 发送消息
+
+
+
+ 仅支持 macOS
+
+ )}
+
{isAiLoading ? '生成中' : '生成 AI 日报'}
-
+
)
diff --git a/src/renderer/src/components/chat/ChatStatusBar.tsx b/src/renderer/src/components/chat/ChatStatusBar.tsx
index 235a7ec..a9f80a7 100644
--- a/src/renderer/src/components/chat/ChatStatusBar.tsx
+++ b/src/renderer/src/components/chat/ChatStatusBar.tsx
@@ -1,4 +1,5 @@
import React from 'react'
+import { Button, Switch } from '../ui'
import { RefreshIcon } from './icons'
interface ChatStatusBarProps {
@@ -27,11 +28,7 @@ export function ChatStatusBar({
已显示当前范围 {count} 条消息
头像或名称缺失时,请先在微信中进入该聊天
-
{isReloadingAvatars ? '加载中' : '重新加载头像'}
-
+
-
+
{jumpDisabled ? '已是最新消息' : '跳转到最新消息'}
-
+
)
diff --git a/src/renderer/src/components/chat/ConversationContentSearch.tsx b/src/renderer/src/components/chat/ConversationContentSearch.tsx
index ac4a29b..ec7f926 100644
--- a/src/renderer/src/components/chat/ConversationContentSearch.tsx
+++ b/src/renderer/src/components/chat/ConversationContentSearch.tsx
@@ -1,4 +1,5 @@
import React from 'react'
+import { IconButton, Input } from '../ui'
import { CloseIcon, SearchIcon } from './icons'
interface ConversationContentSearchProps {
@@ -16,22 +17,30 @@ export function ConversationContentSearch({
}: ConversationContentSearchProps): React.ReactElement {
return (
-
+
- onChange(event.target.value)}
+ className="h-9 pr-10 pl-9 text-xs min-[901px]:pr-[7.5rem]"
autoFocus
/>
-
+
{value ? `${resultCount} 条结果` : '输入关键词'}
-
+
-
+
)
}
diff --git a/src/renderer/src/components/chat/ExportMenu.tsx b/src/renderer/src/components/chat/ExportMenu.tsx
index 4b7f7fb..dbca64d 100644
--- a/src/renderer/src/components/chat/ExportMenu.tsx
+++ b/src/renderer/src/components/chat/ExportMenu.tsx
@@ -1,5 +1,11 @@
import React from 'react'
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui'
+import {
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from '../ui'
import { ArrowDownIcon, ExportIcon } from './icons'
export type ExportRange = number | 'all'
@@ -22,16 +28,16 @@ export function ExportMenu({ disabled, onExport }: ExportMenuProps): React.React
return (
-
导出
-
+
{EXPORT_OPTIONS.map((option, index) => (
diff --git a/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx b/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx
index 5ca12c4..4d4ef3e 100644
--- a/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx
+++ b/src/renderer/src/components/chat/PersonalWechatSendDialog.tsx
@@ -2,7 +2,17 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { Contact } from '../../../../shared/types'
import type { PersonalWechatSenderStatus } from '../../../../shared/personal-wechat'
import type { TextToSpeechSettings, TextToSpeechVoice } from '../../../../shared/text-to-speech'
-import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui'
+import {
+ Button,
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ SegmentedControl,
+ SegmentedControlItem,
+ Textarea
+} from '../ui'
type SendMode = 'image' | 'voice'
type VoiceSource = 'generated' | 'file'
@@ -440,22 +450,36 @@ export function PersonalWechatSendDialog({
{(status?.state === 'unsupported_version' || status?.state === 'runtime_missing') &&
onOpenTextToSpeechSettings ? (
-
handleOpenTextToSpeechSettings(status.state === 'unsupported_version')
}
disabled={isBusy}
>
{status.state === 'runtime_missing' ? '前往下载组件' : '查看支持版本'}
-
+
) : null}
- void refreshStatus()} disabled={isBusy}>
+ void refreshStatus()}
+ disabled={isBusy}
+ >
重新检测
-
- void handleRebind()} disabled={isBusy}>
+
+ void handleRebind()}
+ disabled={isBusy}
+ >
{isRebinding ? '绑定中…' : '尝试重新绑定'}
-
+
@@ -468,35 +492,32 @@ export function PersonalWechatSendDialog({
))}
-
- changeMode('image')}
- disabled={isBusy}
- >
+ changeMode(value as SendMode)}
+ disabled={isBusy}
+ >
+
图片
-
- changeMode('voice')}
- disabled={isBusy}
- >
+
+
语音
-
-
+
+
{mode === 'image' ? (
图片
-
void handleSelectImage()} disabled={isBusy}>
+ void handleSelectImage()}
+ disabled={isBusy}
+ >
{image ? '重新选择图片' : '选择图片'}
-
+
{image ? (
{image.name}
@@ -508,28 +529,20 @@ export function PersonalWechatSendDialog({
) : (
-
- changeVoiceSource('generated')}
- disabled={isBusy}
- >
+ changeVoiceSource(value as VoiceSource)}
+ disabled={isBusy}
+ >
+
输入文字生成
-
- changeVoiceSource('file')}
- disabled={isBusy}
- >
+
+
选择本地文件
-
-
+
+
{voiceSource === 'generated' ? (
@@ -539,14 +552,18 @@ export function PersonalWechatSendDialog({
{selectedTtsVoice?.name || '尚未选择音色'}
{onOpenTextToSpeechSettings ? (
-
handleOpenTextToSpeechSettings()}>
+ handleOpenTextToSpeechSettings()}
+ >
前往文字转语音设置
-
+
) : null}
- void handleToggleGeneratedPreview()}
disabled={isSending}
>
{isPreviewPlaying ? '暂停' : '播放'}
-
-
+ void handleSendGeneratedVoice()}
disabled={!canSendGenerated}
>
{isSending ? '正在发送…' : `发送到${isGroupChat ? '群聊' : '联系人'}`}
-
+
) : null}
@@ -627,9 +644,14 @@ export function PersonalWechatSendDialog({
) : (
本地语音文件
-
void handleSelectVoice()} disabled={isBusy}>
+ void handleSelectVoice()}
+ disabled={isBusy}
+ >
{voice ? '重新选择语音' : '选择语音'}
-
+
{voice ? (
{voice.name}
@@ -659,24 +681,19 @@ export function PersonalWechatSendDialog({
)}
diff --git a/src/renderer/src/components/conversation/ConversationSearch.tsx b/src/renderer/src/components/conversation/ConversationSearch.tsx
index 7c5ce65..84f10de 100644
--- a/src/renderer/src/components/conversation/ConversationSearch.tsx
+++ b/src/renderer/src/components/conversation/ConversationSearch.tsx
@@ -1,4 +1,5 @@
import React from 'react'
+import { Input } from '../ui'
interface ConversationSearchProps {
value: string
@@ -23,8 +24,10 @@ export function ConversationSearch({
-
onChange(event.target.value)}
diff --git a/src/renderer/src/components/conversation/ConversationSidebarHeader.tsx b/src/renderer/src/components/conversation/ConversationSidebarHeader.tsx
index 31680e9..a68f432 100644
--- a/src/renderer/src/components/conversation/ConversationSidebarHeader.tsx
+++ b/src/renderer/src/components/conversation/ConversationSidebarHeader.tsx
@@ -1,4 +1,5 @@
import React from 'react'
+import { IconButton } from '../ui'
import { ConversationSearch } from './ConversationSearch'
interface ConversationSidebarHeaderProps {
@@ -22,10 +23,10 @@ export function ConversationSidebarHeader({
聊天档案
diff --git a/src/renderer/src/components/reports/AiReportWorkspace.tsx b/src/renderer/src/components/reports/AiReportWorkspace.tsx
index 6146e5f..bfadcf2 100644
--- a/src/renderer/src/components/reports/AiReportWorkspace.tsx
+++ b/src/renderer/src/components/reports/AiReportWorkspace.tsx
@@ -17,6 +17,7 @@ import { ReportMemberNameSelector } from './ReportMemberNameSelector'
import { ReportGroupMemberSelector } from './ReportGroupMemberSelector'
import { ReportSectionSelector } from './ReportSectionSelector'
import { ReportTemplateSelector, SelectableReportTemplateId } from './ReportTemplateSelector'
+import { Button, Input } from '../ui'
interface AiReportWorkspaceProps {
sourceContact: Contact | null
@@ -198,7 +199,8 @@ export function AiReportWorkspace({
大群聊和图片识别耗时较长,可单独设置本次日报最多等待时间。
-
-
+
+ ))}
+
+
{loading && 正在读取群成员...
}
{error && {error}
}
diff --git a/src/renderer/src/components/reports/ReportHistorySidebar.tsx b/src/renderer/src/components/reports/ReportHistorySidebar.tsx
index 3dd6241..ba90d68 100644
--- a/src/renderer/src/components/reports/ReportHistorySidebar.tsx
+++ b/src/renderer/src/components/reports/ReportHistorySidebar.tsx
@@ -8,7 +8,8 @@ import {
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
- Button
+ Button,
+ Input
} from '../ui'
import type { GeneratedReportRecord } from './types'
@@ -155,15 +156,16 @@ export function ReportHistorySidebar({
- setKeyword(event.target.value)}
placeholder="搜索群聊或日报"
/>
-
+
+ 新建日报
-
+
历史报告
{reportGroups.length ? (
diff --git a/src/renderer/src/components/reports/ReportInfoPanel.tsx b/src/renderer/src/components/reports/ReportInfoPanel.tsx
index 8e88e26..54cb3b1 100644
--- a/src/renderer/src/components/reports/ReportInfoPanel.tsx
+++ b/src/renderer/src/components/reports/ReportInfoPanel.tsx
@@ -1,5 +1,6 @@
import React, { useState } from 'react'
import type { GeneratedReportRecord } from './types'
+import { Button } from '../ui'
interface ReportInfoPanelProps {
report: GeneratedReportRecord | null
@@ -165,9 +166,9 @@ export function ReportInfoPanel({ report, onReveal }: ReportInfoPanelProps): Rea
{(report.pngPath || report.htmlPath) && (
- void revealReport()}>
+ void revealReport()}>
打开文件夹
-
+
)}
{revealStatus && {revealStatus}
}
@@ -199,9 +200,9 @@ export function ReportInfoPanel({ report, onReveal }: ReportInfoPanelProps): Rea
{path ? (
<>
{path}
- void copyPath()}>
+ void copyPath()}>
复制文件路径
-
+
{copyStatus && {copyStatus}
}
>
) : (
diff --git a/src/renderer/src/components/reports/ReportMemberNameSelector.tsx b/src/renderer/src/components/reports/ReportMemberNameSelector.tsx
index 40ac94b..6c75fc4 100644
--- a/src/renderer/src/components/reports/ReportMemberNameSelector.tsx
+++ b/src/renderer/src/components/reports/ReportMemberNameSelector.tsx
@@ -1,5 +1,6 @@
import React from 'react'
import { ReportMemberNamePreference } from '../../hooks/useGroupReportGeneration'
+import { RadioGroup, RadioGroupItem } from '../ui'
const OPTIONS: {
value: ReportMemberNamePreference
@@ -36,18 +37,22 @@ export function ReportMemberNameSelector({
成员名称
选择日报中群成员的显示名称,默认使用群昵称。
-
+
onChange(nextValue as ReportMemberNamePreference)}
+ >
{OPTIONS.map((option) => (
- onChange(option.value)}
+
{option.label}
@@ -55,7 +60,7 @@ export function ReportMemberNameSelector({
))}
-
+
)
}
diff --git a/src/renderer/src/components/reports/ReportRangeSelector.tsx b/src/renderer/src/components/reports/ReportRangeSelector.tsx
index f40a290..39600a0 100644
--- a/src/renderer/src/components/reports/ReportRangeSelector.tsx
+++ b/src/renderer/src/components/reports/ReportRangeSelector.tsx
@@ -1,9 +1,7 @@
import React from 'react'
-import {
- SUMMARY_DATE_OPTIONS,
- SummaryDateRange
-} from '../../utils/group-report'
+import { SUMMARY_DATE_OPTIONS, SummaryDateRange } from '../../utils/group-report'
import { RangeMessageState } from '../../hooks/useGroupReportGeneration'
+import { SegmentedControl, SegmentedControlItem } from '../ui'
interface ReportRangeSelectorProps {
value: SummaryDateRange
@@ -33,22 +31,31 @@ export function ReportRangeSelector({
总结范围
{countText}
-
+ onChange(nextValue as SummaryDateRange)}
+ >
{SUMMARY_DATE_OPTIONS.map((option) => (
- onChange(option.value)}
+ className="h-auto min-h-10 whitespace-normal px-2 py-1.5 text-center"
+ value={option.value}
>
{option.label}
-
+
))}
-
- 自定义 即将支持
-
-
+
+ 自定义 即将支持
+
+
)
}
diff --git a/src/renderer/src/components/reports/ReportSettingsPanel.tsx b/src/renderer/src/components/reports/ReportSettingsPanel.tsx
index 4f1ea3e..86b0f26 100644
--- a/src/renderer/src/components/reports/ReportSettingsPanel.tsx
+++ b/src/renderer/src/components/reports/ReportSettingsPanel.tsx
@@ -1,6 +1,7 @@
import React, { useState } from 'react'
import type { GeneratedReportRecord } from './types'
import { ReportExportStatus } from './ReportExportStatus'
+import { Button } from '../ui'
interface ReportSettingsPanelProps {
report: GeneratedReportRecord | null
@@ -63,9 +64,9 @@ export function ReportSettingsPanel({
{path ? (
<>
{path}
- void copyPath()}>
+ void copyPath()}>
复制文件路径
-
+
{copyStatus && {copyStatus}
}
>
) : (
diff --git a/src/renderer/src/components/reports/ReportSourceSidebar.tsx b/src/renderer/src/components/reports/ReportSourceSidebar.tsx
index b041d10..00a820c 100644
--- a/src/renderer/src/components/reports/ReportSourceSidebar.tsx
+++ b/src/renderer/src/components/reports/ReportSourceSidebar.tsx
@@ -1,6 +1,7 @@
import React, { useMemo, useState } from 'react'
import { Contact } from '../../../../shared/types'
import { AccountSummary } from '../account/AccountSummary'
+import { Button, Input } from '../ui'
interface SelfInfo {
wxid: string
@@ -56,7 +57,8 @@ export function ReportSourceSidebar({
- setKeyword(event.target.value)}
placeholder="搜索群聊"
@@ -69,9 +71,9 @@ export function ReportSourceSidebar({
const nickname = contact.m_nsNickName.trim()
const displayName = nickname || contact.m_nsUsrName || '未命名群聊'
return (
- onSelectContact(contact)}
title={contact.m_nsUsrName}
@@ -86,7 +88,7 @@ export function ReportSourceSidebar({
{displayName}
-
+
)
})
) : (
diff --git a/src/renderer/src/components/reports/ReportTaskStatusPanel.tsx b/src/renderer/src/components/reports/ReportTaskStatusPanel.tsx
index ccbcd8c..4b0ac10 100644
--- a/src/renderer/src/components/reports/ReportTaskStatusPanel.tsx
+++ b/src/renderer/src/components/reports/ReportTaskStatusPanel.tsx
@@ -10,6 +10,7 @@ import type {
ReportImageInsightSummary,
ReportPreparationProgress
} from '../../utils/group-report-facts'
+import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui'
interface ReportTaskStatusPanelProps {
phase: ReportGenerationPhase
@@ -223,12 +224,10 @@ export function ReportTaskStatusPanel({
张图片仍会参与总结。失败图片只按消息类型和聊天上下文处理,不会猜测具体内容。
-
- 继续文字总结
-
-
+ 继续文字总结
+
停止生成
-
+
)}
@@ -258,35 +257,35 @@ export function ReportTaskStatusPanel({
{canRetryModelStep ? (
切换模型
-
重新生成将直接复用已整理的聊天记录和图片识别结果,从第三步继续。
{modelLoadError ? {modelLoadError} : null}
- onRetry(selectedModel)}
- >
+ onRetry(selectedModel)}>
使用所选模型重新生成
-
+
) : (
- onRetry()}>
- 从头重试
-
+ onRetry()}>从头重试
)}
- void window.api.revealAppLog()}>
+ void window.api.revealAppLog()}>
打开诊断日志
-
+
{logPath && {logPath}}
)}
diff --git a/src/renderer/src/components/reports/ReportTemplateSelector.tsx b/src/renderer/src/components/reports/ReportTemplateSelector.tsx
index 1bd4dc7..c465cfe 100644
--- a/src/renderer/src/components/reports/ReportTemplateSelector.tsx
+++ b/src/renderer/src/components/reports/ReportTemplateSelector.tsx
@@ -1,10 +1,22 @@
-import React, { useState } from 'react'
+import React, { useRef, useState } from 'react'
import {
DEFAULT_REPORT_TEMPLATE,
REPORT_TEMPLATES,
type ReportTemplateDefinition,
type SelectableReportTemplateId
} from '../../../../shared/report-templates'
+import {
+ Button,
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ RadioGroup,
+ RadioGroupItem
+} from '../ui'
export type { SelectableReportTemplateId } from '../../../../shared/report-templates'
@@ -58,6 +70,7 @@ export const ReportTemplateSelector: React.FC = ({
disabled
}) => {
const [previewing, setPreviewing] = useState(null)
+ const previewTriggerRef = useRef(null)
const mobileTemplates = REPORT_TEMPLATES.filter((template) => template.platform === 'mobile')
const desktopTemplates = REPORT_TEMPLATES.filter((template) => template.platform === 'desktop')
@@ -75,14 +88,11 @@ export const ReportTemplateSelector: React.FC = ({
key={template.id}
className={`report-template-item ${active ? 'active' : ''} ${disabled ? 'disabled' : ''}`}
>
-
-
+ onChange(template.id)}
+ aria-label={template.name}
/>
@@ -91,13 +101,16 @@ export const ReportTemplateSelector: React.FC
= ({
{template.tagline}
- setPreviewing(template)}
+ {
+ previewTriggerRef.current = event.currentTarget
+ setPreviewing(template)
+ }}
>
查看版式
-
+
)
})}
@@ -111,52 +124,66 @@ export const ReportTemplateSelector: React.FC = ({
默认模板与五套新版模板读取同一份真实日报数据。手机模板适合长图和群内分享,桌面模板适合宽屏阅读与归档。
-
+ onChange(nextValue as SelectableReportTemplateId)}
+ >
{renderGroup('默认模板', [DEFAULT_REPORT_TEMPLATE])}
{renderGroup('手机端 · 375–414 px', mobileTemplates)}
{renderGroup('电脑端 · 1280–1920 px', desktopTemplates)}
-
- {previewing && (
- setPreviewing(null)}>
-
event.stopPropagation()}
+
+
- )}
+
+
+
+ )}
+
)
}
diff --git a/src/renderer/src/components/reports/ReportZoomBar.tsx b/src/renderer/src/components/reports/ReportZoomBar.tsx
index 989fa92..f74fc25 100644
--- a/src/renderer/src/components/reports/ReportZoomBar.tsx
+++ b/src/renderer/src/components/reports/ReportZoomBar.tsx
@@ -1,4 +1,5 @@
import React from 'react'
+import { Button } from '../ui'
interface ReportZoomBarProps {
zoom: number
@@ -17,19 +18,19 @@ export function ReportZoomBar({
}: ReportZoomBarProps): React.ReactElement {
return (
- onZoomChange(clampZoom(zoom / 1.25))}>
+ onZoomChange(clampZoom(zoom / 1.25))}>
缩小
-
+
{Math.round(zoom * 100)}%
- onZoomChange(clampZoom(zoom * 1.25))}>
+ onZoomChange(clampZoom(zoom * 1.25))}>
放大
-
-
+
+
完整显示
-
-
+
+
原始大小
-
+
)
}
diff --git a/src/renderer/src/components/search/AISearchWorkspace.tsx b/src/renderer/src/components/search/AISearchWorkspace.tsx
index d7bee46..70d572d 100644
--- a/src/renderer/src/components/search/AISearchWorkspace.tsx
+++ b/src/renderer/src/components/search/AISearchWorkspace.tsx
@@ -30,6 +30,7 @@ import { ensureAiSearchDataConsent } from './services/aiSearchProviderConsent'
import { ExternalProviderConsentDialog } from './ExternalProviderConsentDialog'
import { AISearchComposer } from './AISearchComposer'
import { AISearchEvidencePanel } from './AISearchEvidencePanel'
+import { Button } from '../ui'
export function AISearchWorkspace({
contacts,
@@ -670,14 +671,28 @@ export function AISearchWorkspace({
{renderSearchDetails()}
-
+
新问题
-
- void copyAnswer()} title="复制 AI 摘要">
+
+ void copyAnswer()}
+ title="复制 AI 摘要"
+ >
复制摘要
-
-
+ {
skipNextCache()
void runAnalysis()
@@ -685,7 +700,7 @@ export function AISearchWorkspace({
title="跳过缓存并重新读取聊天记录"
>
刷新数据
-
+
@@ -719,9 +734,9 @@ export function AISearchWorkspace({
检索反馈
{analysisError || '当前范围没有足够证据'}
可以扩大时间范围、切换群聊,或换一个更具体的问题。
- {
const expandToAll = range === '30d' || range === 'all'
setRange(expandToAll ? 'all' : '30d')
@@ -748,7 +763,7 @@ export function AISearchWorkspace({
}}
>
{range === '30d' || range === 'all' ? '搜索全部历史' : '扩大到近 30 天'}
-
+
)
@@ -779,25 +794,27 @@ export function AISearchWorkspace({
{modelLabel}
{!aiModelConfig.configured && (
-
+
配置模型
-
+
)}
{debugEnabled && (
- setDebugPanelOpen((open) => !open)}
title="查看本次检索诊断信息"
+ aria-expanded={debugPanelOpen}
+ aria-controls="ai-search-debug-panel"
>
诊断日志
-
+
)}
{debugEnabled && debugPanelOpen && (
-
+
检索诊断
@@ -806,12 +823,12 @@ export function AISearchWorkspace({
- setDebugEntries([])}>
+ setDebugEntries([])}>
清空
-
- void window.api.revealAppLog()}>
+
+ void window.api.revealAppLog()}>
打开日志文件夹
-
+
{appLogPath && {appLogPath}}
@@ -965,9 +982,9 @@ export function AISearchWorkspace({
{knowledgeStatus.lastError || '同步异常,旧搜索仍可使用。'}
)}
-
+
同步详情
diff --git a/src/renderer/src/components/ui/index.ts b/src/renderer/src/components/ui/index.ts
index 7b02060..08645a7 100644
--- a/src/renderer/src/components/ui/index.ts
+++ b/src/renderer/src/components/ui/index.ts
@@ -10,6 +10,7 @@ export * from './popover'
export * from './progress'
export * from './radio-group'
export * from './scroll-area'
+export * from './segmented-control'
export * from './select'
export * from './separator'
export * from './skeleton'
diff --git a/src/renderer/src/components/ui/segmented-control.tsx b/src/renderer/src/components/ui/segmented-control.tsx
new file mode 100644
index 0000000..865a0d1
--- /dev/null
+++ b/src/renderer/src/components/ui/segmented-control.tsx
@@ -0,0 +1,32 @@
+import * as React from 'react'
+import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
+import { cn } from '../../lib/cn'
+
+const SegmentedControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SegmentedControl.displayName = 'SegmentedControl'
+
+const SegmentedControlItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SegmentedControlItem.displayName = 'SegmentedControlItem'
+
+export { SegmentedControl, SegmentedControlItem }
diff --git a/src/renderer/src/components/ui/select.tsx b/src/renderer/src/components/ui/select.tsx
index 4040b5f..979b784 100644
--- a/src/renderer/src/components/ui/select.tsx
+++ b/src/renderer/src/components/ui/select.tsx
@@ -71,7 +71,9 @@ const SelectItem = React.forwardRef<
)}
{...props}
>
- ✓
+
+ ✓
+
{children}
))
diff --git a/src/renderer/src/features/agent-hub/AgentHubWorkspace.tsx b/src/renderer/src/features/agent-hub/AgentHubWorkspace.tsx
index 6089b42..8d42e93 100644
--- a/src/renderer/src/features/agent-hub/AgentHubWorkspace.tsx
+++ b/src/renderer/src/features/agent-hub/AgentHubWorkspace.tsx
@@ -5,6 +5,14 @@ import type {
AgentHubStatus,
WechatConnectorStatus
} from '../../../../shared/agent-hub'
+import {
+ Button,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from '../../components/ui'
const STATUS_LABELS: Record = {
checking: '正在检查',
@@ -127,14 +135,13 @@ export function AgentHubWorkspace(): React.ReactElement {
{status.connector === 'scanned' ? '请在手机上确认登录' : '使用微信扫描二维码'}
二维码仅用于机器人账号登录,不会读取你的微信密码。
- void runAction(() => window.api.cancelAgentHubLogin())}
>
取消
-
+
) : status.connector === 'online' ? (
@@ -160,32 +167,28 @@ export function AgentHubWorkspace(): React.ReactElement {
{status.connector === 'online' ? (
<>
- void runAction(() => window.api.startAgentHubLogin())}
>
重新扫码登录
-
-
+ void runAction(() => window.api.disconnectAgentHub())}
>
断开连接
-
+
>
) : !isLoginFlow ? (
- void runAction(() => window.api.startAgentHubLogin())}
>
{busy ? '正在获取二维码…' : '扫码登录微信机器人'}
-
+
) : null}
@@ -236,26 +239,31 @@ export function AgentHubWorkspace(): React.ReactElement {
运行日志
- setLogSource(event.target.value as 'all' | AgentHubLogSource)}
+ onValueChange={(value) => setLogSource(value as 'all' | AgentHubLogSource)}
>
-
-
-
-
-
-
+
+
+
+ 全部来源
+ 系统
+ Agent Hub
+ 微信连接器
+
+
+ void copyLogs()}
disabled={visibleLogs.length === 0}
>
复制日志
-
- void clearLogs()}>
+
+ void clearLogs()}>
清空
-
+
diff --git a/src/renderer/src/features/api-center/components/ApiRequestTester.tsx b/src/renderer/src/features/api-center/components/ApiRequestTester.tsx
index be88c3a..d084a2a 100644
--- a/src/renderer/src/features/api-center/components/ApiRequestTester.tsx
+++ b/src/renderer/src/features/api-center/components/ApiRequestTester.tsx
@@ -1,5 +1,6 @@
import type { ApiEndpoint, ApiServiceState, ApiSettings } from '../model/types'
import { type ReactElement } from 'react'
+import { Button, Input, Textarea } from '../../../components/ui'
import { buildApiUrl } from '../utils/buildApiUrl'
interface Props {
@@ -62,7 +63,8 @@ export function ApiRequestTester({
{parameter.label}
{parameter.required &&
必填}
-
update(parameter.key, event.target.value)}
placeholder={parameter.placeholder}
@@ -74,7 +76,8 @@ export function ApiRequestTester({
{endpoint.body && (
JSON 请求体
-
))}
diff --git a/src/renderer/src/features/api-center/components/ReaderSkillOverview.tsx b/src/renderer/src/features/api-center/components/ReaderSkillOverview.tsx
index 41f4dea..0dee7f4 100644
--- a/src/renderer/src/features/api-center/components/ReaderSkillOverview.tsx
+++ b/src/renderer/src/features/api-center/components/ReaderSkillOverview.tsx
@@ -1,4 +1,11 @@
import { type ReactElement } from 'react'
+import {
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from '../../../components/ui'
import type { ApiServiceState, SkillStatus } from '../model/types'
import type { AgentInstallTarget } from '../model/skillDistribution'
import { SkillDetails } from './SkillDetails'
@@ -46,26 +53,29 @@ export function ReaderSkillOverview({
通过本地 HTTP API 读取 TraceMemo 已解锁的微信聊天数据
-
+
预览 Skill
-
-
+
复制安装指令
-
-
- 更多
-
- 打开本地文件夹
-
-
- 查看 GitHub 最新版本
-
-
+
+
+
+
+ 更多
+
+
+
+
+ 打开本地文件夹
+
+ 查看 GitHub 最新版本
+
+
{!skill?.available && (
diff --git a/src/renderer/src/features/api-center/components/SkillDetails.tsx b/src/renderer/src/features/api-center/components/SkillDetails.tsx
index 559b154..71a174c 100644
--- a/src/renderer/src/features/api-center/components/SkillDetails.tsx
+++ b/src/renderer/src/features/api-center/components/SkillDetails.tsx
@@ -1,4 +1,5 @@
import { type ReactElement } from 'react'
+import { Button } from '../../../components/ui'
import type { SkillStatus } from '../model/types'
export function SkillDetails({
@@ -40,15 +41,15 @@ export function SkillDetails({
-
+
打开本地文件夹
-
-
+
+
预览 SKILL.md
-
-
+
+
查看 GitHub 最新版本
-
+
)
diff --git a/src/renderer/src/features/api-center/components/SkillInstallFlow.tsx b/src/renderer/src/features/api-center/components/SkillInstallFlow.tsx
index ec2b63e..48a98ac 100644
--- a/src/renderer/src/features/api-center/components/SkillInstallFlow.tsx
+++ b/src/renderer/src/features/api-center/components/SkillInstallFlow.tsx
@@ -1,4 +1,5 @@
import { type ReactElement } from 'react'
+import { Button } from '../../../components/ui'
import type { ApiServiceState, SkillStatus } from '../model/types'
import { type AgentInstallTarget } from '../model/skillDistribution'
import { SkillTargetSelector } from './SkillTargetSelector'
@@ -52,9 +53,9 @@ export function SkillInstallFlow({
{skill?.available ? '可用' : '不可用'}
{!service?.running && (
-
+
启动服务
-
+
)}
{!skill?.available && (
{skill?.error || '本地 Skill 文件不可用'}
@@ -74,17 +75,12 @@ export function SkillInstallFlow({
粘贴并验证
将安装指令粘贴到 Agent;安装后应调用 health 接口验证连接。
-
+
{buttonLabel[target]}
-
-
+
+
复制测试问题
-
+
diff --git a/src/renderer/src/features/api-center/components/SkillTargetSelector.tsx b/src/renderer/src/features/api-center/components/SkillTargetSelector.tsx
index b478a32..7f5daf2 100644
--- a/src/renderer/src/features/api-center/components/SkillTargetSelector.tsx
+++ b/src/renderer/src/features/api-center/components/SkillTargetSelector.tsx
@@ -1,4 +1,5 @@
import { type ReactElement } from 'react'
+import { SegmentedControl, SegmentedControlItem } from '../../../components/ui'
import { AGENT_INSTALL_TARGETS, type AgentInstallTarget } from '../model/skillDistribution'
export function SkillTargetSelector({
@@ -9,17 +10,17 @@ export function SkillTargetSelector({
onChange: (value: AgentInstallTarget) => void
}): ReactElement {
return (
-
+ onChange(nextValue as AgentInstallTarget)}
+ aria-label="选择目标 Agent"
+ >
{AGENT_INSTALL_TARGETS.map((target) => (
- onChange(target.value)}
- >
+
{target.label}
-
+
))}
-
+
)
}
diff --git a/src/renderer/src/features/settings/account-database/AccountOverview.tsx b/src/renderer/src/features/settings/account-database/AccountOverview.tsx
index 9bca65c..c8d6961 100644
--- a/src/renderer/src/features/settings/account-database/AccountOverview.tsx
+++ b/src/renderer/src/features/settings/account-database/AccountOverview.tsx
@@ -1,5 +1,6 @@
import type { SettingsSelfInfo } from '../model/types'
import type { ConnectionOverviewStatus } from './types'
+import { Button, IconButton } from '../../../components/ui'
function CopyIcon(): React.ReactElement {
return (
@@ -69,41 +70,29 @@ export function AccountOverview({
-
+
{isChecking ? '正在检测...' : '重新检测'}
-
-
+
+
打开账号目录
-
-
+
+
切换账号
-
+
当前账号目录
{accountRoot || '暂无数据'}
-
-
+
diff --git a/src/renderer/src/features/settings/ai-model/AIImageUnderstandingTest.tsx b/src/renderer/src/features/settings/ai-model/AIImageUnderstandingTest.tsx
index 9c9bba6..fe14a50 100644
--- a/src/renderer/src/features/settings/ai-model/AIImageUnderstandingTest.tsx
+++ b/src/renderer/src/features/settings/ai-model/AIImageUnderstandingTest.tsx
@@ -1,4 +1,5 @@
import type { AIProviderSummary, AIRuntimeModelConfig } from '../../../../../shared/ai-provider'
+import { Button, Textarea } from '../../../components/ui'
import type { AIVisionTestState } from './types'
export function AIImageUnderstandingTest({
@@ -67,7 +68,7 @@ export function AIImageUnderstandingTest({
识别提示词
-