Skip to content

Commit e2146cd

Browse files
authored
fix: auto increase context size (#7609)
1 parent 91b000a commit e2146cd

File tree

5 files changed

+177
-18
lines changed

5 files changed

+177
-18
lines changed

web-app/src/containers/MessageItem.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,17 @@ export type MessageItemProps = {
4848
onDelete?: (messageId: string) => void
4949
assistant?: { avatar?: React.ReactNode; name?: string }
5050
showAssistant?: boolean
51+
isAnimating?: boolean
52+
hideActions?: boolean
5153
}
5254

5355
export const MessageItem = memo(
5456
({
5557
message,
5658
isLastMessage,
5759
status,
60+
isAnimating,
61+
hideActions,
5862
reasoningContainerRef,
5963
onRegenerate,
6064
onEdit,
@@ -185,6 +189,7 @@ export const MessageItem = memo(
185189
content={part.text}
186190
isStreaming={isStreaming && isLastPart}
187191
messageId={message.id}
192+
isAnimating={isAnimating}
188193
/>
189194
</>
190195
)}
@@ -345,7 +350,7 @@ export const MessageItem = memo(
345350
})}
346351

347352
{/* Message actions for user messages */}
348-
{message.role === 'user' && (
353+
{message.role === 'user' && !hideActions && (
349354
<div className="flex items-center justify-end gap-1 text-muted-foreground text-xs mt-4">
350355
<CopyButton text={getFullTextContent()} />
351356

@@ -369,7 +374,7 @@ export const MessageItem = memo(
369374
<div
370375
className={cn(
371376
'flex items-center gap-1',
372-
isStreaming && 'hidden'
377+
(isStreaming || hideActions) && 'hidden'
373378
)}
374379
>
375380
<CopyButton text={getFullTextContent()} />
@@ -434,7 +439,8 @@ export const MessageItem = memo(
434439
prevProps.isFirstMessage === nextProps.isFirstMessage &&
435440
prevProps.isLastMessage === nextProps.isLastMessage &&
436441
prevProps.status === nextProps.status &&
437-
prevProps.showAssistant === nextProps.showAssistant
442+
prevProps.showAssistant === nextProps.showAssistant &&
443+
prevProps.hideActions === nextProps.hideActions
438444
)
439445
}
440446
)

web-app/src/containers/RenderMarkdown.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface MarkdownProps {
2121
isUser?: boolean
2222
isStreaming?: boolean
2323
messageId?: string
24+
isAnimating?: boolean
2425
}
2526

2627
// Cache for normalized LaTeX content
@@ -85,6 +86,7 @@ function RenderMarkdownComponent({
8586
isUser,
8687
components,
8788
messageId,
89+
isAnimating
8890
}: MarkdownProps) {
8991

9092
// Memoize the normalized content to avoid reprocessing on every render
@@ -101,7 +103,7 @@ function RenderMarkdownComponent({
101103
)}
102104
>
103105
<Streamdown
104-
animate={true}
106+
animate={isAnimating ?? true}
105107
animationDuration={500}
106108
linkSafety={{
107109
enabled: false,

web-app/src/hooks/use-chat.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ export function useChat(
126126
}
127127
}, [mcpToolNames, ragToolNames])
128128

129+
const setContinueFromContent = useCallback((content: string) => {
130+
transportRef.current?.setContinueFromContent(content)
131+
}, [])
132+
129133
// Expose method to update RAG tools availability
130134
const updateRagToolsAvailability = useCallback(
131135
async (
@@ -147,5 +151,6 @@ export function useChat(
147151
return {
148152
...chatResult,
149153
updateRagToolsAvailability,
154+
setContinueFromContent,
150155
}
151156
}

web-app/src/lib/custom-chat-transport.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,38 @@ export type ServiceHub = {
4848
}
4949
}
5050

51+
/**
52+
* Wraps a UIMessageChunk stream so that when the first `text-start` chunk
53+
* arrives, a `text-delta` carrying `prefixText` is immediately injected into
54+
* the same text block. This makes the new message show the partial content
55+
* right away while continuation tokens stream in after it.
56+
*/
57+
function prependTextDeltaToUIStream(
58+
stream: ReadableStream<UIMessageChunk>,
59+
prefixText: string
60+
): ReadableStream<UIMessageChunk> {
61+
const reader = stream.getReader()
62+
let prefixEmitted = false
63+
return new ReadableStream<UIMessageChunk>({
64+
async pull(controller) {
65+
const { done, value } = await reader.read()
66+
if (done) {
67+
controller.close()
68+
return
69+
}
70+
controller.enqueue(value)
71+
if (!prefixEmitted && (value as { type: string }).type === 'text-start') {
72+
prefixEmitted = true
73+
const id = (value as { type: 'text-start'; id: string }).id
74+
controller.enqueue({ type: 'text-delta', id, delta: prefixText } as UIMessageChunk)
75+
}
76+
},
77+
cancel() {
78+
reader.cancel()
79+
},
80+
})
81+
}
82+
5183
export class CustomChatTransport implements ChatTransport<UIMessage> {
5284
public model: LanguageModel | null = null
5385
private tools: Record<string, Tool> = {}
@@ -58,6 +90,7 @@ export class CustomChatTransport implements ChatTransport<UIMessage> {
5890
private systemMessage?: string
5991
private serviceHub: ServiceHub | null
6092
private threadId?: string
93+
private continueFromContent: string | null = null
6194

6295
constructor(systemMessage?: string, threadId?: string) {
6396
this.systemMessage = systemMessage
@@ -213,6 +246,14 @@ export class CustomChatTransport implements ChatTransport<UIMessage> {
213246
return this.tools
214247
}
215248

249+
/**
250+
* Set partial assistant content to send as a prefill on the next request,
251+
* so the model continues generation from where it left off.
252+
*/
253+
setContinueFromContent(content: string) {
254+
this.continueFromContent = content
255+
}
256+
216257
async sendMessages(
217258
options: {
218259
chatId: string
@@ -258,10 +299,18 @@ export class CustomChatTransport implements ChatTransport<UIMessage> {
258299
}
259300

260301
// Convert UI messages to model messages
261-
const modelMessages = convertToModelMessages(
302+
const baseMessages = convertToModelMessages(
262303
this.mapUserInlineAttachments(options.messages)
263304
)
264305

306+
// If continuing a truncated response, append the partial assistant content as a
307+
// prefill so the model resumes from where it left off rather than regenerating.
308+
const continueContent = this.continueFromContent
309+
this.continueFromContent = null
310+
const modelMessages = continueContent
311+
? [...baseMessages, { role: 'assistant' as const, content: continueContent }]
312+
: baseMessages
313+
265314
// Include tools only if we have tools loaded AND model supports them
266315
const hasTools = Object.keys(this.tools).length > 0
267316
const selectedModel = useModelProvider.getState().selectedModel
@@ -282,7 +331,7 @@ export class CustomChatTransport implements ChatTransport<UIMessage> {
282331

283332
let tokensPerSecond = 0
284333

285-
return result.toUIMessageStream({
334+
const uiStream = result.toUIMessageStream({
286335
messageMetadata: ({ part }) => {
287336
// Track stream start time on start
288337
if (part.type === 'start' && !streamStartTime) {
@@ -320,6 +369,7 @@ export class CustomChatTransport implements ChatTransport<UIMessage> {
320369
}
321370

322371
return {
372+
finishReason: finishPart.finishReason,
323373
usage: {
324374
inputTokens: inputTokens,
325375
outputTokens: outputTokens,
@@ -364,6 +414,13 @@ export class CustomChatTransport implements ChatTransport<UIMessage> {
364414
}
365415
},
366416
})
417+
418+
// When continuing a truncated response, inject the partial content as the
419+
// very first text-delta so the new message immediately shows it and the
420+
// user sees a seamless continuation rather than an empty box.
421+
return continueContent
422+
? prependTextDeltaToUIStream(uiStream, continueContent)
423+
: uiStream
367424
}
368425

369426
async reconnectToStream(

0 commit comments

Comments
 (0)