mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 19:47:08 +08:00
fix: 优化数据库启动与设置页响应性能
(cherry picked from commit f6614548b0e742bc703b2a5aaa5060720985ed27)
This commit is contained in:
+5
-1
@@ -486,6 +486,7 @@ app.whenReady().then(async () => {
|
||||
if (dbInitInFlight) return dbInitInFlight
|
||||
|
||||
dbInitInFlight = (async () => {
|
||||
const startedAt = Date.now()
|
||||
try {
|
||||
if (wcdbBootstrapPromise) await wcdbBootstrapPromise
|
||||
const trimmedKey = String(key || '').trim()
|
||||
@@ -511,7 +512,7 @@ app.whenReady().then(async () => {
|
||||
}
|
||||
chat.setChatDb(nextWechatDb)
|
||||
const wcdb4Client = nextWechatDb.getWcdb4Client()
|
||||
const sessions = await wcdb4Client.getSessionsAsync()
|
||||
const sessions = await wcdb4Client.getSessionsAsync({ hydrateDisplayNames: false })
|
||||
configureRecallProtection(wcdb4Client, resolvedRoot, settings.recallProtectionEnabled)
|
||||
voiceService = new VoiceService(wcdb4Client)
|
||||
stickerService = new StickerService(wcdb4Client)
|
||||
@@ -530,6 +531,9 @@ app.whenReady().then(async () => {
|
||||
.catch((error) => console.warn('[WCDB4] message cursor warmup failed:', error))
|
||||
}
|
||||
imageDecryptService = null
|
||||
console.log(
|
||||
`[WCDB4] db:init ready sessions=${sessions.length} monitoring=${monitoring} cost=${Date.now() - startedAt}ms`
|
||||
)
|
||||
return { success: true, monitoring }
|
||||
} catch (error) {
|
||||
console.error('Failed to init DB:', error)
|
||||
|
||||
@@ -174,7 +174,7 @@ export function listContacts(filter?: string): FormattedContact[] {
|
||||
|
||||
export async function listContactsAsync(filter?: string): Promise<FormattedContact[]> {
|
||||
if (!dbRef) return []
|
||||
await dbRef.getWcdb4Client().getSessionsAsync()
|
||||
await dbRef.getWcdb4Client().getSessionsAsync({ hydrateDisplayNames: false })
|
||||
return listContacts(filter)
|
||||
}
|
||||
|
||||
|
||||
+53
-16
@@ -32,6 +32,10 @@ export interface Wcdb4MessageQueryOptions {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface Wcdb4SessionQueryOptions {
|
||||
hydrateDisplayNames?: boolean
|
||||
}
|
||||
|
||||
type Wcdb4MessageStore = {
|
||||
tableName: string
|
||||
dbPath: string
|
||||
@@ -242,6 +246,8 @@ export class Wcdb4Client {
|
||||
private cachedSessions: Wcdb4Session[] | null = null
|
||||
private cachedChatTables: { name: string; db_number: string }[] | null = null
|
||||
private sessionsInFlight: Promise<Wcdb4Session[]> | null = null
|
||||
private sessionDisplayNamesInFlight: Promise<void> | null = null
|
||||
private sessionDisplayNamesHydrated = false
|
||||
private sessionCacheGeneration = 0
|
||||
|
||||
private wcdbShutdown: (() => number) | null = null
|
||||
@@ -558,6 +564,7 @@ export class Wcdb4Client {
|
||||
|
||||
this.handle = null
|
||||
this.cachedSessions = null
|
||||
this.sessionDisplayNamesHydrated = false
|
||||
this.displayNameCache.clear()
|
||||
this.avatarCache.clear()
|
||||
this.groupNicknameCache.clear()
|
||||
@@ -722,13 +729,22 @@ export class Wcdb4Client {
|
||||
...session,
|
||||
nickname: this.displayNameCache.get(session.username) || session.nickname || session.username
|
||||
}))
|
||||
this.sessionDisplayNamesHydrated = true
|
||||
|
||||
return this.cachedSessions
|
||||
}
|
||||
|
||||
async getSessionsAsync(): Promise<Wcdb4Session[]> {
|
||||
if (this.cachedSessions) return this.cachedSessions
|
||||
if (this.sessionsInFlight) return this.sessionsInFlight
|
||||
async getSessionsAsync(options: Wcdb4SessionQueryOptions = {}): Promise<Wcdb4Session[]> {
|
||||
const hydrateDisplayNames = options.hydrateDisplayNames !== false
|
||||
if (this.cachedSessions) {
|
||||
if (hydrateDisplayNames) await this.ensureSessionDisplayNamesAsync()
|
||||
return this.cachedSessions
|
||||
}
|
||||
if (this.sessionsInFlight) {
|
||||
await this.sessionsInFlight
|
||||
if (hydrateDisplayNames) await this.ensureSessionDisplayNamesAsync()
|
||||
return this.cachedSessions || []
|
||||
}
|
||||
if (!this.wcdbGetSessions) return []
|
||||
|
||||
const generation = this.sessionCacheGeneration
|
||||
@@ -739,25 +755,17 @@ export class Wcdb4Client {
|
||||
const sessions = (Array.isArray(rows) ? rows : [])
|
||||
.map((row) => this.normalizeSession(row))
|
||||
.filter((session) => session.username)
|
||||
await this.hydrateDisplayNamesAsync(
|
||||
sessions
|
||||
.filter((session) => this.shouldHydrateSessionDisplayName(session))
|
||||
.map((session) => session.username)
|
||||
)
|
||||
const hydrated = sessions.map((session) => ({
|
||||
...session,
|
||||
nickname:
|
||||
this.displayNameCache.get(session.username) || session.nickname || session.username
|
||||
}))
|
||||
if (generation === this.sessionCacheGeneration) this.cachedSessions = hydrated
|
||||
return hydrated
|
||||
if (generation === this.sessionCacheGeneration) this.cachedSessions = sessions
|
||||
return sessions
|
||||
})()
|
||||
this.sessionsInFlight = request
|
||||
try {
|
||||
return await request
|
||||
await request
|
||||
} finally {
|
||||
if (this.sessionsInFlight === request) this.sessionsInFlight = null
|
||||
}
|
||||
if (hydrateDisplayNames) await this.ensureSessionDisplayNamesAsync()
|
||||
return this.cachedSessions || []
|
||||
}
|
||||
|
||||
invalidateSessionCache(): void {
|
||||
@@ -765,6 +773,7 @@ export class Wcdb4Client {
|
||||
this.cachedSessions = null
|
||||
this.cachedChatTables = null
|
||||
this.sessionsInFlight = null
|
||||
this.sessionDisplayNamesHydrated = false
|
||||
}
|
||||
|
||||
getChatTables(): { name: string; db_number: string }[] {
|
||||
@@ -2214,6 +2223,34 @@ export class Wcdb4Client {
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureSessionDisplayNamesAsync(): Promise<void> {
|
||||
if (this.sessionDisplayNamesHydrated || !this.cachedSessions) return
|
||||
if (this.sessionDisplayNamesInFlight) return this.sessionDisplayNamesInFlight
|
||||
|
||||
const generation = this.sessionCacheGeneration
|
||||
const sessions = this.cachedSessions
|
||||
const request = (async (): Promise<void> => {
|
||||
await this.hydrateDisplayNamesAsync(
|
||||
sessions
|
||||
.filter((session) => this.shouldHydrateSessionDisplayName(session))
|
||||
.map((session) => session.username)
|
||||
)
|
||||
if (generation !== this.sessionCacheGeneration || this.cachedSessions !== sessions) return
|
||||
this.cachedSessions = sessions.map((session) => ({
|
||||
...session,
|
||||
nickname:
|
||||
this.displayNameCache.get(session.username) || session.nickname || session.username
|
||||
}))
|
||||
this.sessionDisplayNamesHydrated = true
|
||||
})()
|
||||
this.sessionDisplayNamesInFlight = request
|
||||
try {
|
||||
await request
|
||||
} finally {
|
||||
if (this.sessionDisplayNamesInFlight === request) this.sessionDisplayNamesInFlight = null
|
||||
}
|
||||
}
|
||||
|
||||
private hydrateAvatarUrls(usernames: string[]): void {
|
||||
const missing = this.uniq(usernames).filter((username) => !this.avatarCache.has(username))
|
||||
if (missing.length === 0) return
|
||||
|
||||
@@ -210,6 +210,7 @@ const mergeMessagePages = (older: Message[], current: Message[]): Message[] => {
|
||||
function App(): React.ReactElement {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
const [isDatabaseConnected, setIsDatabaseConnected] = useState(false)
|
||||
const [isDatabaseConnecting, setIsDatabaseConnecting] = useState(false)
|
||||
const [dbKey, setDbKey] = useState(getDevelopmentDatabaseKey)
|
||||
const [contacts, setContacts] = useState<Contact[]>([])
|
||||
const [selectedContact, setSelectedContact] = useState<Contact | null>(null)
|
||||
@@ -582,6 +583,7 @@ function App(): React.ReactElement {
|
||||
if (!autoLoginEnabled) return
|
||||
try {
|
||||
const startupCacheReady = await loadStartupCache()
|
||||
setIsDatabaseConnecting(true)
|
||||
const initPromise = window.api.initDb(key)
|
||||
if (startupCacheReady) {
|
||||
setIsAuthenticated(true)
|
||||
@@ -606,6 +608,9 @@ function App(): React.ReactElement {
|
||||
console.warn('[Startup] background database init failed:', error)
|
||||
setDbKeyStatusKind('error')
|
||||
})
|
||||
.finally(() => {
|
||||
if (active) setIsDatabaseConnecting(false)
|
||||
})
|
||||
return
|
||||
}
|
||||
const result = await initPromise
|
||||
@@ -638,6 +643,8 @@ function App(): React.ReactElement {
|
||||
setDbKeyStatus(`自动连接失败: ${message}`)
|
||||
setDbKeyStatusKind('error')
|
||||
setBootState('login')
|
||||
} finally {
|
||||
if (active) setIsDatabaseConnecting(false)
|
||||
}
|
||||
}
|
||||
void attemptAutoConnect()
|
||||
@@ -663,6 +670,7 @@ function App(): React.ReactElement {
|
||||
const keyToUse = keyInput || dbKey
|
||||
if (!keyToUse) return
|
||||
setBootState('connecting')
|
||||
setIsDatabaseConnecting(true)
|
||||
// 持久化用户手动指定的微信聊天文件路径,供 db:init 读取 settings.dbRoot
|
||||
const trimmedRoot = dbRootInput.trim()
|
||||
if (trimmedRoot) {
|
||||
@@ -746,6 +754,8 @@ function App(): React.ReactElement {
|
||||
setBootState('login')
|
||||
setStartupProgress(null)
|
||||
alert('Error connecting to database')
|
||||
} finally {
|
||||
setIsDatabaseConnecting(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,6 +928,7 @@ function App(): React.ReactElement {
|
||||
const handleReturnToLogin = (): void => {
|
||||
setIsAuthenticated(false)
|
||||
setIsDatabaseConnected(false)
|
||||
setIsDatabaseConnecting(false)
|
||||
setBootState('login')
|
||||
setDatabaseConnectionMode(dbKey ? 'manual' : 'automatic')
|
||||
setActivePage('archive')
|
||||
@@ -1425,6 +1436,7 @@ function App(): React.ReactElement {
|
||||
width={sidebarWidth}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={isDatabaseConnected}
|
||||
dbConnecting={isDatabaseConnecting}
|
||||
onOpenSettings={openSettings}
|
||||
/>
|
||||
<div className="resizer" onMouseDown={startResizing} />
|
||||
@@ -1454,6 +1466,7 @@ function App(): React.ReactElement {
|
||||
selectedReportId={selectedReportId}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={isDatabaseConnected}
|
||||
dbConnecting={isDatabaseConnecting}
|
||||
onSelectReport={openReport}
|
||||
onCreateReport={openReportConfigure}
|
||||
onDeleteReport={handleDeleteReport}
|
||||
@@ -1476,6 +1489,7 @@ function App(): React.ReactElement {
|
||||
selectedContact={reportSourceContact}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={isDatabaseConnected}
|
||||
dbConnecting={isDatabaseConnecting}
|
||||
onSelectContact={handleSelectReportSource}
|
||||
onOpenSettings={openSettings}
|
||||
/>
|
||||
@@ -1546,6 +1560,7 @@ function App(): React.ReactElement {
|
||||
onCategoryChange={setSettingsCategory}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={isDatabaseConnected}
|
||||
dbConnecting={isDatabaseConnecting}
|
||||
dbKey={dbKey}
|
||||
onDbKeyChange={setDbKey}
|
||||
onDatabaseConnectionChange={setIsDatabaseConnected}
|
||||
@@ -1692,6 +1707,7 @@ function App(): React.ReactElement {
|
||||
activePage={activePage}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={isDatabaseConnected}
|
||||
dbConnecting={isDatabaseConnecting}
|
||||
onPageChange={handlePageChange}
|
||||
onOpenSettings={openSettings}
|
||||
onOpenGuide={openFirstUseGuide}
|
||||
|
||||
@@ -10,6 +10,7 @@ interface SelfInfo {
|
||||
interface AccountSummaryProps {
|
||||
selfInfo: SelfInfo | null
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
compact?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
@@ -17,13 +18,20 @@ interface AccountSummaryProps {
|
||||
export function AccountSummary({
|
||||
selfInfo,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
compact = false,
|
||||
onClick
|
||||
}: AccountSummaryProps): React.ReactElement {
|
||||
const showAccount = Boolean(selfInfo && (dbReady || dbConnecting))
|
||||
const displayName =
|
||||
dbReady && selfInfo ? selfInfo.nickname || selfInfo.wxid || '当前账号' : '未连接'
|
||||
const subtitle = dbReady && selfInfo ? selfInfo.wxid : '打开设置'
|
||||
const statusText = dbReady ? '数据库已连接' : '数据库未连接'
|
||||
showAccount && selfInfo ? selfInfo.nickname || selfInfo.wxid || '当前账号' : '未连接'
|
||||
const subtitle = showAccount && selfInfo ? selfInfo.wxid : '打开设置'
|
||||
const statusText = dbReady
|
||||
? '数据库已连接'
|
||||
: dbConnecting
|
||||
? '正在连接数据库'
|
||||
: '数据库未连接'
|
||||
const statusClass = dbReady ? 'ready' : dbConnecting ? 'connecting' : ''
|
||||
const initial = (displayName || '?').charAt(0)
|
||||
const title = `${displayName}\n${subtitle}`
|
||||
const avatar = (
|
||||
@@ -33,7 +41,7 @@ export function AccountSummary({
|
||||
) : (
|
||||
initial
|
||||
)}
|
||||
<span className={`account-summary-status ${dbReady ? 'ready' : ''}`} aria-hidden />
|
||||
<span className={`account-summary-status ${statusClass}`} aria-hidden />
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -52,7 +60,7 @@ export function AccountSummary({
|
||||
<span className="account-summary-name">{displayName}</span>
|
||||
<span className="account-summary-meta">{subtitle}</span>
|
||||
<span className="account-summary-state">
|
||||
<span className={`account-summary-state-dot ${dbReady ? 'ready' : ''}`} aria-hidden />
|
||||
<span className={`account-summary-state-dot ${statusClass}`} aria-hidden />
|
||||
{statusText}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface ConversationSidebarProps {
|
||||
width: number
|
||||
selfInfo: SelfInfo | null
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
onOpenSettings: () => void
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ export function ConversationSidebar({
|
||||
width,
|
||||
selfInfo,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
onOpenSettings
|
||||
}: ConversationSidebarProps): React.ReactElement {
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
@@ -124,7 +126,12 @@ export function ConversationSidebar({
|
||||
</div>
|
||||
</div>
|
||||
<div className="conversation-sidebar-account">
|
||||
<AccountSummary selfInfo={selfInfo} dbReady={dbReady} onClick={onOpenSettings} />
|
||||
<AccountSummary
|
||||
selfInfo={selfInfo}
|
||||
dbReady={dbReady}
|
||||
dbConnecting={dbConnecting}
|
||||
onClick={onOpenSettings}
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ interface AppShellProps {
|
||||
activePage: AppPage
|
||||
selfInfo: SelfInfo | null
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
onPageChange: (page: AppPage) => void
|
||||
onOpenSettings: () => void
|
||||
onOpenGuide: () => void
|
||||
@@ -35,6 +36,7 @@ export function AppShell({
|
||||
activePage,
|
||||
selfInfo,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
onPageChange,
|
||||
onOpenSettings,
|
||||
onOpenGuide,
|
||||
@@ -63,7 +65,13 @@ export function AppShell({
|
||||
<span className="app-guide-launcher-label">新手引导</span>
|
||||
</button>
|
||||
<div className="app-rail-account">
|
||||
<AccountSummary selfInfo={selfInfo} dbReady={dbReady} compact onClick={onOpenSettings} />
|
||||
<AccountSummary
|
||||
selfInfo={selfInfo}
|
||||
dbReady={dbReady}
|
||||
dbConnecting={dbConnecting}
|
||||
compact
|
||||
onClick={onOpenSettings}
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
<main className="app-shell-main" aria-label={activeItem?.label || '工作区'}>
|
||||
|
||||
@@ -14,6 +14,7 @@ interface ReportHistorySidebarProps {
|
||||
selectedReportId: string | null
|
||||
selfInfo: SelfInfo | null
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
onSelectReport: (reportId: string) => void
|
||||
onCreateReport: () => void
|
||||
onDeleteReport: (reportId: string) => Promise<{ success: boolean; error?: string }>
|
||||
@@ -80,6 +81,7 @@ export function ReportHistorySidebar({
|
||||
selectedReportId,
|
||||
selfInfo,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
onSelectReport,
|
||||
onCreateReport,
|
||||
onDeleteReport,
|
||||
@@ -202,7 +204,12 @@ export function ReportHistorySidebar({
|
||||
)}
|
||||
</div>
|
||||
<div className="report-history-account">
|
||||
<AccountSummary selfInfo={selfInfo} dbReady={dbReady} onClick={onOpenSettings} />
|
||||
<AccountSummary
|
||||
selfInfo={selfInfo}
|
||||
dbReady={dbReady}
|
||||
dbConnecting={dbConnecting}
|
||||
onClick={onOpenSettings}
|
||||
/>
|
||||
</div>
|
||||
{pendingDelete && (
|
||||
<div className="report-delete-confirm" role="dialog" aria-modal="true">
|
||||
|
||||
@@ -14,6 +14,7 @@ interface ReportSourceSidebarProps {
|
||||
selectedContact: Contact | null
|
||||
selfInfo: SelfInfo | null
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
onSelectContact: (contact: Contact) => void
|
||||
onOpenSettings: () => void
|
||||
}
|
||||
@@ -23,6 +24,7 @@ export function ReportSourceSidebar({
|
||||
selectedContact,
|
||||
selfInfo,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
onSelectContact,
|
||||
onOpenSettings
|
||||
}: ReportSourceSidebarProps): React.ReactElement {
|
||||
@@ -95,6 +97,7 @@ export function ReportSourceSidebar({
|
||||
<AccountSummary
|
||||
selfInfo={selfInfo}
|
||||
dbReady={dbReady}
|
||||
dbConnecting={dbConnecting}
|
||||
onClick={onOpenSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@ export function SettingsWorkspace({
|
||||
onCategoryChange,
|
||||
selfInfo,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
dbKey,
|
||||
onDbKeyChange,
|
||||
onDatabaseConnectionChange,
|
||||
@@ -35,6 +36,7 @@ export function SettingsWorkspace({
|
||||
onCategoryChange: (id: SettingsCategoryId) => void
|
||||
selfInfo: SettingsSelfInfo | null
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
dbKey: string
|
||||
onDbKeyChange: (key: string) => void
|
||||
onDatabaseConnectionChange: (connected: boolean) => void
|
||||
@@ -47,6 +49,54 @@ export function SettingsWorkspace({
|
||||
onOpenSettings: () => void
|
||||
onAppearanceChange: (settings: { theme: 'system' | 'light' | 'dark'; compactMode: boolean }) => void
|
||||
}): React.ReactElement {
|
||||
const renderSelectedPage = (): React.ReactElement => {
|
||||
switch (selectedCategory) {
|
||||
case 'account-database':
|
||||
return (
|
||||
<AccountDatabasePage
|
||||
dbKey={dbKey}
|
||||
dbReady={dbReady}
|
||||
dbConnecting={dbConnecting}
|
||||
selfInfo={selfInfo}
|
||||
onNotice={onNotice}
|
||||
/>
|
||||
)
|
||||
case 'database-key':
|
||||
return (
|
||||
<DatabaseKeyPage
|
||||
dbKey={dbKey}
|
||||
dbReady={dbReady}
|
||||
selfInfo={selfInfo}
|
||||
onDbKeyChange={onDbKeyChange}
|
||||
onDatabaseConnectionChange={onDatabaseConnectionChange}
|
||||
onSelfInfoChange={onSelfInfoChange}
|
||||
onContactsChange={onContactsChange}
|
||||
onFilteredContactsChange={onFilteredContactsChange}
|
||||
onReturnToLogin={onReturnToLogin}
|
||||
onNotice={onNotice}
|
||||
/>
|
||||
)
|
||||
case 'image-key':
|
||||
return <ImageDecryptionPage selfInfo={selfInfo} onNotice={onNotice} />
|
||||
case 'ai-model':
|
||||
return <AIModelPage onRuntimeChange={onAIRuntimeChange} onNotice={onNotice} />
|
||||
case 'recall-protection':
|
||||
return <RecallProtectionPage onNotice={onNotice} />
|
||||
case 'advanced':
|
||||
return <AdvancedPage onNotice={onNotice} />
|
||||
case 'cache-cleanup':
|
||||
return <CacheCleanupPage onNotice={onNotice} />
|
||||
case 'appearance':
|
||||
return (
|
||||
<AppearancePage onNotice={onNotice} onAppearanceChange={onAppearanceChange} />
|
||||
)
|
||||
case 'about':
|
||||
return <AboutPage onNotice={onNotice} />
|
||||
default:
|
||||
return <SettingsEmptyState label={SETTINGS_CATEGORY_LABELS[selectedCategory]} />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="settings-workspace">
|
||||
<SettingsSidebar
|
||||
@@ -54,70 +104,10 @@ export function SettingsWorkspace({
|
||||
onSelect={onCategoryChange}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={dbReady}
|
||||
dbConnecting={dbConnecting}
|
||||
onOpenSettings={onOpenSettings}
|
||||
/>
|
||||
<div
|
||||
className={`settings-page-panel ${selectedCategory === 'account-database' ? 'active' : ''}`}
|
||||
>
|
||||
<AccountDatabasePage
|
||||
dbKey={dbKey}
|
||||
dbReady={dbReady}
|
||||
selfInfo={selfInfo}
|
||||
onNotice={onNotice}
|
||||
/>
|
||||
</div>
|
||||
<div className={`settings-page-panel ${selectedCategory === 'database-key' ? 'active' : ''}`}>
|
||||
<DatabaseKeyPage
|
||||
dbKey={dbKey}
|
||||
dbReady={dbReady}
|
||||
selfInfo={selfInfo}
|
||||
onDbKeyChange={onDbKeyChange}
|
||||
onDatabaseConnectionChange={onDatabaseConnectionChange}
|
||||
onSelfInfoChange={onSelfInfoChange}
|
||||
onContactsChange={onContactsChange}
|
||||
onFilteredContactsChange={onFilteredContactsChange}
|
||||
onReturnToLogin={onReturnToLogin}
|
||||
onNotice={onNotice}
|
||||
/>
|
||||
</div>
|
||||
<div className={`settings-page-panel ${selectedCategory === 'image-key' ? 'active' : ''}`}>
|
||||
<ImageDecryptionPage selfInfo={selfInfo} onNotice={onNotice} />
|
||||
</div>
|
||||
<div className={`settings-page-panel ${selectedCategory === 'ai-model' ? 'active' : ''}`}>
|
||||
<AIModelPage onRuntimeChange={onAIRuntimeChange} onNotice={onNotice} />
|
||||
</div>
|
||||
<div
|
||||
className={`settings-page-panel ${selectedCategory === 'recall-protection' ? 'active' : ''}`}
|
||||
>
|
||||
<RecallProtectionPage onNotice={onNotice} />
|
||||
</div>
|
||||
<div className={`settings-page-panel ${selectedCategory === 'advanced' ? 'active' : ''}`}>
|
||||
<AdvancedPage onNotice={onNotice} />
|
||||
</div>
|
||||
<div className={`settings-page-panel ${selectedCategory === 'cache-cleanup' ? 'active' : ''}`}>
|
||||
<CacheCleanupPage onNotice={onNotice} />
|
||||
</div>
|
||||
<div className={`settings-page-panel ${selectedCategory === 'appearance' ? 'active' : ''}`}>
|
||||
<AppearancePage onNotice={onNotice} onAppearanceChange={onAppearanceChange} />
|
||||
</div>
|
||||
<div className={`settings-page-panel ${selectedCategory === 'about' ? 'active' : ''}`}>
|
||||
<AboutPage onNotice={onNotice} />
|
||||
</div>
|
||||
{![
|
||||
'account-database',
|
||||
'database-key',
|
||||
'image-key',
|
||||
'ai-model',
|
||||
'recall-protection',
|
||||
'advanced',
|
||||
'cache-cleanup',
|
||||
'appearance',
|
||||
'about'
|
||||
].includes(selectedCategory) && (
|
||||
<div className="settings-page-panel active">
|
||||
<SettingsEmptyState label={SETTINGS_CATEGORY_LABELS[selectedCategory]} />
|
||||
</div>
|
||||
)}
|
||||
<div className="settings-page-panel active">{renderSelectedPage()}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ import type { ConnectionCheckState } from './types'
|
||||
export function useAccountDatabaseController({
|
||||
dbKey,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
selfInfo,
|
||||
onNotice
|
||||
}: {
|
||||
dbKey: string
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
selfInfo: SettingsSelfInfo | null
|
||||
onNotice: (message: string) => void
|
||||
}) {
|
||||
@@ -28,7 +30,7 @@ export function useAccountDatabaseController({
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
void window.api
|
||||
.getImageDecryptionStatus()
|
||||
.getImageKeyConfig()
|
||||
.then((result) => {
|
||||
if (active) setHasImageKey(result.configured)
|
||||
})
|
||||
@@ -58,8 +60,11 @@ export function useAccountDatabaseController({
|
||||
[checkState, dbKey, dbReady, hasImageKey, selfInfo]
|
||||
)
|
||||
const connectionStatus = useMemo(
|
||||
() => getConnectionOverviewStatus({ dbReady, checkState, diagnostics }),
|
||||
[checkState, dbReady, diagnostics]
|
||||
() =>
|
||||
dbConnecting
|
||||
? ('checking' as const)
|
||||
: getConnectionOverviewStatus({ dbReady, checkState, diagnostics }),
|
||||
[checkState, dbConnecting, dbReady, diagnostics]
|
||||
)
|
||||
const lastCheckedLabel = formatConnectionCheckedAt(checkState, clock)
|
||||
|
||||
@@ -121,7 +126,7 @@ export function useAccountDatabaseController({
|
||||
diagnostics,
|
||||
connectionStatus,
|
||||
checkState,
|
||||
isChecking: checkState.status === 'checking',
|
||||
isChecking: dbConnecting || checkState.status === 'checking',
|
||||
lastCheckedLabel,
|
||||
testConnection,
|
||||
openAccountDirectory,
|
||||
|
||||
@@ -8,12 +8,14 @@ export function SettingsSidebar({
|
||||
onSelect,
|
||||
selfInfo,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
onOpenSettings
|
||||
}: {
|
||||
selectedId: SettingsCategoryId
|
||||
onSelect: (id: SettingsCategoryId) => void
|
||||
selfInfo: SettingsSelfInfo | null
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
onOpenSettings: () => void
|
||||
}): React.ReactElement {
|
||||
const [keyword, setKeyword] = useState('')
|
||||
@@ -57,7 +59,12 @@ export function SettingsSidebar({
|
||||
))}
|
||||
</div>
|
||||
<div className="settings-sidebar-account">
|
||||
<AccountSummary selfInfo={selfInfo} dbReady={dbReady} onClick={onOpenSettings} />
|
||||
<AccountSummary
|
||||
selfInfo={selfInfo}
|
||||
dbReady={dbReady}
|
||||
dbConnecting={dbConnecting}
|
||||
onClick={onOpenSettings}
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
|
||||
@@ -17,15 +17,23 @@ const STATUS_LABELS: Record<ConnectionOverviewStatus, string> = {
|
||||
export function AccountDatabasePage({
|
||||
dbKey,
|
||||
dbReady,
|
||||
dbConnecting = false,
|
||||
selfInfo,
|
||||
onNotice
|
||||
}: {
|
||||
dbKey: string
|
||||
dbReady: boolean
|
||||
dbConnecting?: boolean
|
||||
selfInfo: SettingsSelfInfo | null
|
||||
onNotice: (message: string) => void
|
||||
}): React.ReactElement {
|
||||
const controller = useAccountDatabaseController({ dbKey, dbReady, selfInfo, onNotice })
|
||||
const controller = useAccountDatabaseController({
|
||||
dbKey,
|
||||
dbReady,
|
||||
dbConnecting,
|
||||
selfInfo,
|
||||
onNotice
|
||||
})
|
||||
const [autoLogin, setAutoLogin] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
+8
@@ -253,6 +253,10 @@ body {
|
||||
&.ready {
|
||||
background: var(--wxex-success);
|
||||
}
|
||||
|
||||
&.connecting {
|
||||
background: var(--wxex-brand);
|
||||
}
|
||||
}
|
||||
|
||||
.account-summary:not(.compact) .account-summary-avatar .account-summary-status {
|
||||
@@ -306,6 +310,10 @@ body {
|
||||
&.ready {
|
||||
background: var(--wxex-success);
|
||||
}
|
||||
|
||||
&.connecting {
|
||||
background: var(--wxex-brand);
|
||||
}
|
||||
}
|
||||
|
||||
.account-summary-settings {
|
||||
|
||||
Reference in New Issue
Block a user