feat: restore GLM 4.7 fixes - auto-scroll and retry logic

Changes from GLM 4.7 Progress Log:

1. Multi-task chat auto-scroll (multi-task-chat.tsx):
   - Added createEffect that monitors message count changes
   - Auto-scrolls using requestAnimationFrame + setTimeout(50ms)
   - Scrolls when new messages arrive or during streaming

2. Electron black screen fix (main.ts):
   - Added exponential backoff retry (1s, 2s, 4s, 8s, 16s max)
   - Added 30-second timeout for load operations
   - Added user-friendly error screen with retry button
   - Handles errno -3 network errors gracefully
   - Max 5 retry attempts before showing error
This commit is contained in:
Gemini AI
2025-12-23 13:33:39 +04:00
Unverified
parent c4ac079660
commit 9c6d92efcd
2 changed files with 123 additions and 2 deletions

View File

@@ -18,6 +18,11 @@ let pendingCliUrl: string | null = null
let showingLoadingScreen = false
let preloadingView: BrowserView | null = null
// Retry logic constants
const MAX_RETRY_ATTEMPTS = 5
const LOAD_TIMEOUT_MS = 30000
let retryAttempts = 0
if (isMac) {
app.commandLine.appendSwitch("disable-spell-checking")
}
@@ -89,6 +94,61 @@ function loadLoadingScreen(window: BrowserWindow) {
})
}
// Calculate exponential backoff delay
function getRetryDelay(attempt: number): number {
return Math.min(1000 * Math.pow(2, attempt), 16000) // 1s, 2s, 4s, 8s, 16s max
}
// Show user-friendly error screen
function showErrorScreen(window: BrowserWindow, errorMessage: string) {
const errorHtml = `
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
padding: 40px;
font-family: system-ui, -apple-system, sans-serif;
background: #1a1a1a;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
box-sizing: border-box;
}
.error-icon { font-size: 48px; margin-bottom: 20px; }
h1 { margin: 0 0 16px; font-size: 24px; font-weight: 600; }
p { margin: 0 0 24px; color: #888; font-size: 14px; text-align: center; max-width: 400px; }
.error-code { font-family: monospace; background: #2a2a2a; padding: 8px 16px; border-radius: 6px; font-size: 12px; color: #f87171; margin-bottom: 24px; }
button {
background: #6366f1;
color: white;
border: none;
padding: 12px 32px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
button:hover { background: #818cf8; transform: scale(1.02); }
</style>
</head>
<body>
<div class="error-icon">⚠️</div>
<h1>Connection Failed</h1>
<p>NomadArch couldn't connect to the development server after multiple attempts. Please ensure the server is running.</p>
<div class="error-code">${errorMessage}</div>
<button onclick="location.reload()">Retry</button>
</body>
</html>
`
window.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(errorHtml)}`)
}
function getAllowedRendererOrigins(): string[] {
const origins = new Set<string>()
const rendererCandidates = [currentCliUrl, process.env.VITE_DEV_SERVER_URL, process.env.ELECTRON_RENDERER_URL]
@@ -305,7 +365,55 @@ function finalizeCliSwap(url: string) {
showingLoadingScreen = false
currentCliUrl = url
pendingCliUrl = null
mainWindow.loadURL(url).catch((error) => console.error("[cli] failed to load CLI view:", error))
// Reset retry counter on new URL
retryAttempts = 0
const loadWithRetry = () => {
if (!mainWindow || mainWindow.isDestroyed()) return
// Set timeout for load
const timeoutId = setTimeout(() => {
console.warn(`[cli] Load timeout after ${LOAD_TIMEOUT_MS}ms`)
handleLoadError(new Error(`Load timeout after ${LOAD_TIMEOUT_MS}ms`))
}, LOAD_TIMEOUT_MS)
mainWindow.loadURL(url)
.then(() => {
clearTimeout(timeoutId)
retryAttempts = 0 // Reset on success
console.info("[cli] Successfully loaded CLI view")
})
.catch((error) => {
clearTimeout(timeoutId)
handleLoadError(error)
})
}
const handleLoadError = (error: Error) => {
const errorCode = (error as any).errno
console.error(`[cli] failed to load CLI view (attempt ${retryAttempts + 1}/${MAX_RETRY_ATTEMPTS}):`, error.message)
// Retry on network errors (errno -3)
if (errorCode === -3 && retryAttempts < MAX_RETRY_ATTEMPTS) {
retryAttempts++
const delay = getRetryDelay(retryAttempts)
console.info(`[cli] Retrying in ${delay}ms (attempt ${retryAttempts}/${MAX_RETRY_ATTEMPTS})`)
if (mainWindow && !mainWindow.isDestroyed()) {
loadLoadingScreen(mainWindow)
}
setTimeout(loadWithRetry, delay)
} else if (retryAttempts >= MAX_RETRY_ATTEMPTS) {
console.error("[cli] Max retry attempts reached, showing error screen")
if (mainWindow && !mainWindow.isDestroyed()) {
showErrorScreen(mainWindow, `Failed after ${MAX_RETRY_ATTEMPTS} attempts: ${error.message}`)
}
}
}
loadWithRetry()
}
@@ -370,7 +478,7 @@ app.whenReady().then(() => {
app.on("before-quit", async (event) => {
event.preventDefault()
await cliManager.stop().catch(() => {})
await cliManager.stop().catch(() => { })
app.exit(0)
})

View File

@@ -149,6 +149,19 @@ export default function MultiTaskChat(props: MultiTaskChatProps) {
return () => clearInterval(interval);
});
// Auto-scroll when new messages arrive
createEffect(() => {
const ids = filteredMessageIds();
const thinking = isAgentThinking();
// Scroll when message count changes or when thinking starts
if (ids.length > 0 || thinking) {
requestAnimationFrame(() => {
setTimeout(scrollToBottom, 50);
});
}
});
const handleSendMessage = async () => {
const message = chatInput().trim();
if (!message || isSending()) return;