页面美化

This commit is contained in:
2025-11-24 14:47:03 +08:00
parent d86c4b21ae
commit c975e78928
20 changed files with 2296 additions and 3674 deletions
+122 -15
View File
@@ -25,19 +25,31 @@ platformServiceFactory.register(new XiaoheiheService())
// Persistent browser context for maintaining login state
let persistentContext: BrowserContext | null = null
const userDataDir = join(app.getPath('userData'), 'browser-data')
let contextInitializing = false
let contextInitRetries = 0
const MAX_CONTEXT_INIT_RETRIES = 3
// Settings file path
const settingsDir = join(app.getPath('userData'), 'settings')
const settingsFilePath = join(settingsDir, 'config.json')
const loginInfoFilePath = join(settingsDir, 'login-info.json')
// Constants
const RATE_LIMIT_CONFIG = {
MIN_INTERVAL_MS: 3000, // 最小间隔 3 秒
MAX_SEARCH_PER_MINUTE: 10, // 每分钟最多 10 次
RESET_INTERVAL_MS: 60000, // 1 分钟重置计数
PAGE_TIMEOUT_MS: 30000, // 页面加载超时 30 秒
BROWSER_INIT_TIMEOUT_MS: 30000 // 浏览器初始化超时 30 秒
} as const
// Rate limiter for search operations
class SearchRateLimiter {
private lastSearchTime: number = 0
private searchCount: number = 0
private readonly minInterval: number = 3000 // 最小间隔 3 秒
private readonly maxSearchPerMinute: number = 10 // 每分钟最多 10 次
private readonly resetInterval: number = 60000 // 1 分钟重置计数
private readonly minInterval: number = RATE_LIMIT_CONFIG.MIN_INTERVAL_MS
private readonly maxSearchPerMinute: number = RATE_LIMIT_CONFIG.MAX_SEARCH_PER_MINUTE
private readonly resetInterval: number = RATE_LIMIT_CONFIG.RESET_INTERVAL_MS
canSearch(): { allowed: boolean; waitTime?: number; reason?: string } {
const now = Date.now()
@@ -241,12 +253,14 @@ function createChatWindow(initialText?: string): void {
height: 600,
title: 'AI 对话',
show: false, // Hide window during load for better perceived performance
backgroundColor: '#ffffff', // Set background color to avoid flash
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
nodeIntegration: false,
contextIsolation: true,
spellcheck: false // Disable spell check to avoid conflicts with IME
spellcheck: false, // Disable spell check to avoid conflicts with IME
backgroundThrottling: false // Prevent throttling for better performance
}
})
@@ -362,7 +376,7 @@ async function fetchArticleContent(url: string): Promise<{
console.log('fetchArticleContent: Navigating to URL...')
// Navigate to the URL
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 })
await page.goto(url, { waitUntil: 'networkidle', timeout: RATE_LIMIT_CONFIG.PAGE_TIMEOUT_MS })
// Get appropriate scraper for this URL
console.log('fetchArticleContent: Getting scraper for URL...')
@@ -394,15 +408,97 @@ async function fetchArticleContent(url: string): Promise<{
// Get or create persistent browser context
async function getPersistentContext(headless = true): Promise<BrowserContext> {
if (!persistentContext) {
persistentContext = await chromium.launchPersistentContext(userDataDir, {
headless, // 根据参数决定是否无头模式
viewport: { width: 1280, height: 800 },
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
})
// Return existing context if available and not closed
if (persistentContext && !persistentContext.pages().length) {
console.log('Browser context exists but has no pages, reinitializing')
try {
await persistentContext.close()
} catch (error) {
console.error('Error closing stale context:', error)
}
persistentContext = null
}
return persistentContext
if (persistentContext) {
try {
// Test if context is still alive
await persistentContext.pages()
return persistentContext
} catch (error) {
console.error('Browser context is dead, reinitializing:', error)
persistentContext = null
}
}
// Prevent multiple simultaneous initialization attempts
if (contextInitializing) {
console.log('Context initialization in progress, waiting...')
// Wait for initialization to complete
while (contextInitializing && contextInitRetries < MAX_CONTEXT_INIT_RETRIES) {
await new Promise(resolve => setTimeout(resolve, 1000))
}
if (persistentContext) {
return persistentContext
}
}
// Initialize new context with retry logic
contextInitializing = true
let lastError: Error | null = null
for (let attempt = 1; attempt <= MAX_CONTEXT_INIT_RETRIES; attempt++) {
try {
console.log(`Initializing browser context (attempt ${attempt}/${MAX_CONTEXT_INIT_RETRIES})`)
persistentContext = await chromium.launchPersistentContext(userDataDir, {
headless,
viewport: { width: 1280, height: 800 },
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
// Add timeout for initialization
timeout: RATE_LIMIT_CONFIG.BROWSER_INIT_TIMEOUT_MS
})
// Setup crash handler
persistentContext.on('close', () => {
console.log('Browser context closed')
persistentContext = null
contextInitRetries = 0
})
console.log('Browser context initialized successfully')
contextInitializing = false
contextInitRetries = 0
return persistentContext
} catch (error) {
lastError = error as Error
console.error(`Browser context initialization failed (attempt ${attempt}):`, error)
// Clean up failed context
if (persistentContext) {
try {
await persistentContext.close()
} catch (closeError) {
console.error('Error closing failed context:', closeError)
}
persistentContext = null
}
// Wait before retry (exponential backoff)
if (attempt < MAX_CONTEXT_INIT_RETRIES) {
const delayMs = 1000 * attempt
console.log(`Waiting ${delayMs}ms before retry...`)
await new Promise(resolve => setTimeout(resolve, delayMs))
}
}
}
contextInitializing = false
contextInitRetries++
throw new Error(
`Failed to initialize browser context after ${MAX_CONTEXT_INIT_RETRIES} attempts: ${lastError?.message}`
)
}
// Get login QR code
@@ -522,7 +618,7 @@ async function checkPlatformLogin(url: string): Promise<{
const context = await getPersistentContext()
page = await context.newPage()
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 })
await page.goto(url, { waitUntil: 'networkidle', timeout: RATE_LIMIT_CONFIG.PAGE_TIMEOUT_MS })
const loginStatus = await service.checkLoginStatus(page)
@@ -857,8 +953,19 @@ app.on('window-all-closed', () => {
})
// Unregister all shortcuts when app is about to quit
app.on('will-quit', () => {
app.on('will-quit', async () => {
globalShortcut.unregisterAll()
// Clean up browser context
if (persistentContext) {
console.log('Cleaning up browser context on app quit')
try {
await persistentContext.close()
persistentContext = null
} catch (error) {
console.error('Error closing browser context on quit:', error)
}
}
})
// In this file you can include the rest of your app's specific main process