@miphamai/cli 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/providers/anthropic.ts +19 -1
- package/src/providers/openai-compat.ts +20 -1
- package/src/ui/app.tsx +25 -1
package/package.json
CHANGED
|
@@ -101,8 +101,26 @@ export class AnthropicProvider implements ProviderInstance {
|
|
|
101
101
|
const decoder = new TextDecoder()
|
|
102
102
|
let buffer = ''
|
|
103
103
|
|
|
104
|
+
// Streaming read timeout: if no data arrives for 90s, abort to prevent UI freeze.
|
|
105
|
+
const STREAM_READ_TIMEOUT_MS = 90_000
|
|
106
|
+
|
|
104
107
|
while (true) {
|
|
105
|
-
|
|
108
|
+
let readResult: Awaited<ReturnType<typeof reader.read>>
|
|
109
|
+
try {
|
|
110
|
+
readResult = await Promise.race([
|
|
111
|
+
reader.read(),
|
|
112
|
+
new Promise<never>((_, reject) =>
|
|
113
|
+
setTimeout(
|
|
114
|
+
() => reject(new Error('Stream read timeout — no data for 90s')),
|
|
115
|
+
STREAM_READ_TIMEOUT_MS,
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
])
|
|
119
|
+
} catch (err) {
|
|
120
|
+
yield { type: 'error', error: `Stream stalled: ${String(err)}` }
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const { done, value } = readResult
|
|
106
124
|
if (done) break
|
|
107
125
|
|
|
108
126
|
buffer += decoder.decode(value, { stream: true })
|
|
@@ -48,8 +48,27 @@ export class OpenAICompatProvider implements ProviderInstance {
|
|
|
48
48
|
const pendingToolCalls = new Map<number, { id: string; name: string; arguments: string }>()
|
|
49
49
|
let reasoningContent = ''
|
|
50
50
|
|
|
51
|
+
// Streaming read timeout: if no data arrives for 90s, abort to prevent UI freeze.
|
|
52
|
+
// DeepSeek V4 thinking mode can take 30-60s between chunks — 90s is a generous ceiling.
|
|
53
|
+
const STREAM_READ_TIMEOUT_MS = 90_000
|
|
54
|
+
|
|
51
55
|
while (true) {
|
|
52
|
-
|
|
56
|
+
let readResult: Awaited<ReturnType<typeof reader.read>>
|
|
57
|
+
try {
|
|
58
|
+
readResult = await Promise.race([
|
|
59
|
+
reader.read(),
|
|
60
|
+
new Promise<never>((_, reject) =>
|
|
61
|
+
setTimeout(
|
|
62
|
+
() => reject(new Error('Stream read timeout — no data for 90s')),
|
|
63
|
+
STREAM_READ_TIMEOUT_MS,
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
])
|
|
67
|
+
} catch (err) {
|
|
68
|
+
yield { type: 'error', error: `Stream stalled: ${String(err)}` }
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
const { done, value } = readResult
|
|
53
72
|
if (done) break
|
|
54
73
|
|
|
55
74
|
buffer += decoder.decode(value, { stream: true })
|
package/src/ui/app.tsx
CHANGED
|
@@ -220,12 +220,36 @@ export function App({
|
|
|
220
220
|
|
|
221
221
|
try {
|
|
222
222
|
for await (const chunk of engine.process(input, controller.signal)) {
|
|
223
|
+
// Show thinking indicator when model is reasoning (DeepSeek V4 thinking mode)
|
|
224
|
+
if (chunk.reasoning_content && turnContent === '') {
|
|
225
|
+
setMessages((prev) => {
|
|
226
|
+
const updated = [...prev]
|
|
227
|
+
const last = updated[updated.length - 1]
|
|
228
|
+
if (last?.role === 'assistant' && last.content === '') {
|
|
229
|
+
last.content = '💭 思考中…'
|
|
230
|
+
} else if (!last || last.role !== 'assistant' || last.content !== '💭 思考中…') {
|
|
231
|
+
updated.push({ role: 'assistant', content: '💭 思考中…' })
|
|
232
|
+
}
|
|
233
|
+
return updated
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
223
237
|
if (chunk.type === 'text' && chunk.content) {
|
|
224
238
|
// New turn: reset per-turn accumulator and push a fresh assistant message
|
|
225
239
|
if (isNewTurn) {
|
|
226
240
|
turnContent = chunk.content
|
|
227
241
|
isNewTurn = false
|
|
228
|
-
setMessages((prev) =>
|
|
242
|
+
setMessages((prev) => {
|
|
243
|
+
const updated = [...prev]
|
|
244
|
+
const last = updated[updated.length - 1]
|
|
245
|
+
// Replace "thinking" placeholder with real content
|
|
246
|
+
if (last?.role === 'assistant' && last.content === '💭 思考中…') {
|
|
247
|
+
last.content = turnContent
|
|
248
|
+
} else {
|
|
249
|
+
updated.push({ role: 'assistant', content: turnContent })
|
|
250
|
+
}
|
|
251
|
+
return updated
|
|
252
|
+
})
|
|
229
253
|
} else {
|
|
230
254
|
turnContent += chunk.content
|
|
231
255
|
setMessages((prev) => {
|