Skip to content

Commit 1ae8c46

Browse files
committed
feat: enhance error handling and display in the application
1 parent 34e029c commit 1ae8c46

File tree

3 files changed

+95
-17
lines changed

3 files changed

+95
-17
lines changed

src/main/shortcuts.ts

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,43 @@ import { getSolutionStream, getFollowUpStream, getGeneralStream } from './ai'
66
import { state } from './state'
77
import { settings } from './settings'
88

9+
/**
10+
* Extract meaningful error message from API errors
11+
*/
12+
function extractErrorMessage(error: unknown): string {
13+
if (!(error instanceof Error)) {
14+
return String(error) || '未知错误'
15+
}
16+
17+
// Try to extract responseBody from AI SDK errors
18+
const apiError = error as Error & {
19+
responseBody?: string
20+
statusCode?: number
21+
data?: unknown
22+
}
23+
24+
// Try to parse responseBody for detailed message
25+
if (apiError.responseBody) {
26+
try {
27+
const body = JSON.parse(apiError.responseBody)
28+
if (body.message) {
29+
return body.message
30+
}
31+
if (body.error?.message) {
32+
return body.error.message
33+
}
34+
} catch {
35+
// If parsing fails, use responseBody as is
36+
if (typeof apiError.responseBody === 'string' && apiError.responseBody.length < 200) {
37+
return apiError.responseBody
38+
}
39+
}
40+
}
41+
42+
// Fallback to error message
43+
return error.message || '未知错误'
44+
}
45+
946
type Shortcut = {
1047
action: string
1148
key: string
@@ -153,8 +190,7 @@ const callbacks: Record<string, () => void> = {
153190
if (!streamContext.controller.signal.aborted) {
154191
endedNaturally = false
155192
console.error('Error streaming solution:', error)
156-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
157-
mainWindow.webContents.send('solution-error', errorMessage)
193+
mainWindow.webContents.send('solution-error', extractErrorMessage(error))
158194
} else {
159195
endedNaturally = false
160196
}
@@ -181,9 +217,8 @@ const callbacks: Record<string, () => void> = {
181217
}
182218
} else {
183219
endedNaturally = false
184-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
185220
console.error('Error streaming solution:', error)
186-
mainWindow.webContents.send('solution-error', errorMessage)
221+
mainWindow.webContents.send('solution-error', extractErrorMessage(error))
187222
}
188223
} finally {
189224
if (currentStreamContext === streamContext) {
@@ -272,8 +307,7 @@ const callbacks: Record<string, () => void> = {
272307
if (!streamContext.controller.signal.aborted) {
273308
endedNaturally = false
274309
console.error('Error streaming continuous solution:', error)
275-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
276-
mainWindow.webContents.send('solution-error', errorMessage)
310+
mainWindow.webContents.send('solution-error', extractErrorMessage(error))
277311
} else {
278312
endedNaturally = false
279313
}
@@ -300,9 +334,8 @@ const callbacks: Record<string, () => void> = {
300334
}
301335
} else {
302336
endedNaturally = false
303-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
304337
console.error('Error streaming continuous solution:', error)
305-
mainWindow.webContents.send('solution-error', errorMessage)
338+
mainWindow.webContents.send('solution-error', extractErrorMessage(error))
306339
}
307340
} finally {
308341
if (currentStreamContext === streamContext) {
@@ -499,8 +532,7 @@ ipcMain.handle('sendFollowUpQuestion', async (_event, question: string) => {
499532
if (!streamContext.controller.signal.aborted) {
500533
endedNaturally = false
501534
console.error('Error streaming follow-up solution:', error)
502-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
503-
mainWindow.webContents.send('solution-error', errorMessage)
535+
mainWindow.webContents.send('solution-error', extractErrorMessage(error))
504536
} else {
505537
endedNaturally = false
506538
}
@@ -536,9 +568,8 @@ ipcMain.handle('sendFollowUpQuestion', async (_event, question: string) => {
536568
}
537569
} else {
538570
endedNaturally = false
539-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
540571
console.error('Error streaming follow-up solution:', error)
541-
mainWindow.webContents.send('solution-error', errorMessage)
572+
mainWindow.webContents.send('solution-error', extractErrorMessage(error))
542573
}
543574
} finally {
544575
if (currentStreamContext === streamContext) {

src/renderer/src/coder/AppContent.tsx

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ export function AppContent() {
1010
const {
1111
screenshotData,
1212
solutionChunks,
13+
errorMessage,
1314
setScreenshotData,
1415
setIsLoading,
1516
addSolutionChunk,
17+
setErrorMessage,
1618
clearSolution
1719
} = useSolutionStore()
1820

@@ -34,6 +36,7 @@ export function AppContent() {
3436
clearSolution()
3537
setRecentScreenshots([])
3638
setScreenshotData(null)
39+
setErrorMessage(null)
3740
})
3841

3942
// Listen for solution chunks
@@ -44,6 +47,7 @@ export function AppContent() {
4447
// AI loading
4548
window.api.onAiLoadingStart(() => {
4649
setIsLoading(true)
50+
setErrorMessage(null) // Clear error when new request starts
4751
})
4852
window.api.onAiLoadingEnd(() => {
4953
setIsLoading(false)
@@ -58,7 +62,7 @@ export function AppContent() {
5862
window.api.removeAiLoadingEndListener()
5963
window.api.removeSolutionClearListener()
6064
}
61-
}, [setScreenshotData, clearSolution, setIsLoading, addSolutionChunk])
65+
}, [setScreenshotData, clearSolution, setIsLoading, addSolutionChunk, setErrorMessage])
6266

6367
useEffect(() => {
6468
window.api.onSolutionComplete(() => {
@@ -69,14 +73,14 @@ export function AppContent() {
6973
})
7074
window.api.onSolutionError((message: string) => {
7175
setIsLoading(false)
72-
addSolutionChunk(`\n\n> 生成失败:${message}`)
76+
setErrorMessage(message)
7377
})
7478
return () => {
7579
window.api.removeSolutionCompleteListener()
7680
window.api.removeSolutionStoppedListener()
7781
window.api.removeSolutionErrorListener()
7882
}
79-
}, [setIsLoading, addSolutionChunk])
83+
}, [setIsLoading, setErrorMessage])
8084

8185
useEffect(() => {
8286
window.api.onScrollPageUp(() => {
@@ -108,6 +112,43 @@ export function AppContent() {
108112

109113
return (
110114
<div id="app-content" className="px-6 py-4">
115+
{/* Error Banner */}
116+
{errorMessage && (
117+
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-lg flex items-start gap-3">
118+
<svg
119+
className="w-5 h-5 text-red-400 flex-shrink-0 mt-0.5"
120+
fill="none"
121+
stroke="currentColor"
122+
viewBox="0 0 24 24"
123+
>
124+
<path
125+
strokeLinecap="round"
126+
strokeLinejoin="round"
127+
strokeWidth={2}
128+
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
129+
/>
130+
</svg>
131+
<div className="flex-1 min-w-0">
132+
<p className="text-red-400 font-medium text-sm">API 调用失败</p>
133+
<p className="text-red-300/80 text-sm mt-0.5 break-words">{errorMessage}</p>
134+
</div>
135+
<button
136+
onClick={() => setErrorMessage(null)}
137+
className="text-red-400/80 hover:text-red-300 flex-shrink-0"
138+
title="关闭"
139+
>
140+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
141+
<path
142+
strokeLinecap="round"
143+
strokeLinejoin="round"
144+
strokeWidth={2}
145+
d="M6 18L18 6M6 6l12 12"
146+
/>
147+
</svg>
148+
</button>
149+
</div>
150+
)}
151+
111152
{/* Screenshot Gallery */}
112153
{recentScreenshots.length > 0 ? (
113154
<div className="mb-4 flex gap-2 overflow-x-auto pb-2">

src/renderer/src/lib/store/solution.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,24 @@ interface SolutionState {
44
isLoading: boolean
55
solutionChunks: string[]
66
screenshotData: string | null
7+
errorMessage: string | null
78
}
89

910
interface SolutionStore extends SolutionState {
1011
setIsLoading: (isReceiving: boolean) => void
1112
addSolutionChunk: (chunk: string) => void
1213
setSolutionChunks: (chunks: string[]) => void
1314
setScreenshotData: (data: string | null) => void
15+
setErrorMessage: (message: string | null) => void
1416
clearSolution: () => void
1517
resetState: () => void
1618
}
1719

1820
const defaultState: SolutionState = {
1921
isLoading: false,
2022
solutionChunks: [],
21-
screenshotData: null
23+
screenshotData: null,
24+
errorMessage: null
2225
}
2326

2427
export const useSolutionStore = create<SolutionStore>()((set) => ({
@@ -37,8 +40,11 @@ export const useSolutionStore = create<SolutionStore>()((set) => ({
3740
setScreenshotData: (data) => {
3841
set({ screenshotData: data })
3942
},
43+
setErrorMessage: (message) => {
44+
set({ errorMessage: message })
45+
},
4046
clearSolution: () => {
41-
set({ solutionChunks: [], isLoading: false })
47+
set({ solutionChunks: [], isLoading: false, errorMessage: null })
4248
},
4349
resetState: () => {
4450
set(defaultState)

0 commit comments

Comments
 (0)