33import { useState } from 'react' ;
44import Link from 'next/link' ;
55import { useApp } from '@/providers/app' ;
6- import { NonStreamingChoice } from '@/lib/openrouter' ;
6+ import { NonStreamingChoice , OpenrouterNonStreamingResponse } from '@/lib/openrouter' ;
77import { connectOpenrouter } from '@/app/actions/openrouter' ;
88import { routes } from '@/utils/routes' ;
99import { Button } from '@/components/ui/button' ;
@@ -17,8 +17,7 @@ import {
1717} from '@/components/ui/dialog' ;
1818import { Alert , AlertDescription } from '@/components/ui/alert' ;
1919import { Card , CardContent } from '@/components/ui/card' ;
20- import merge from 'lodash.merge' ;
21- import { streamToIterator } from '@/utils/stream-to-iterator' ;
20+ import { OpenrouterStreamEvent , StreamEvent , streamToIterator } from '@/utils/stream-to-iterator' ;
2221import { Tabs , TabsContent , TabsList , TabsTrigger } from '@/components/ui/tabs' ;
2322import { MarkdownEditor } from '../editors/markdown-editor' ;
2423import { MarkdownRenderer } from '../markdown-renderer' ;
@@ -29,6 +28,7 @@ import { VariableInput } from '../variable-input';
2928import { JsonEditor } from '../editors/json-editor' ;
3029import { Loader2 } from 'lucide-react' ;
3130import { toast } from 'sonner' ;
31+ import { accumulateStreamToCompletion } from '@/utils/accumulate-stream' ;
3232
3333const TABS = {
3434 content : 'content' ,
@@ -58,8 +58,12 @@ export const PromptTestModal = () => {
5858 } = state ;
5959
6060 const [ isRunning , setIsRunning ] = useState ( false ) ;
61- const [ testResult , setTestResult ] = useState < string | null > ( null ) ;
62- const [ fullResult , setFullResult ] = useState < any | null > ( null ) ;
61+ const [ completionContent , setCompletionContent ] = useState < string | null > ( null ) ;
62+ const [ reasoningContent , setReasoningContent ] = useState < string | null > ( null ) ;
63+ const [ fullResult , setFullResult ] = useState < {
64+ completion ?: OpenrouterNonStreamingResponse ;
65+ logUuid ?: string ;
66+ } | null > ( null ) ;
6367 const [ testError , setTestError ] = useState < string | null > ( null ) ;
6468 const [ selectedTab , setSelectedTab ] = useState < Tab > ( TABS . content ) ;
6569
@@ -70,7 +74,7 @@ export const PromptTestModal = () => {
7074
7175 const handleTestPrompt = async ( ) => {
7276 setIsRunning ( true ) ;
73- setTestResult ( null ) ;
77+ setCompletionContent ( null ) ;
7478 setFullResult ( null ) ;
7579 setTestError ( null ) ;
7680
@@ -106,38 +110,34 @@ export const PromptTestModal = () => {
106110 }
107111
108112 if ( editorConfig . stream && response . body ) {
109- let fullResult : any = { } ;
110- let content = '' ;
111-
112113 try {
113- const stream = streamToIterator ( response . body ) ;
114- for await ( const event of stream ) {
114+ const [ streamA , streamB ] = response . body . tee ( ) ;
115+
116+ const iterator = streamToIterator < StreamEvent > ( streamA ) ;
117+
118+ for await ( const event of iterator ) {
115119 if ( event . type === 'logUuid' ) {
116120 if ( event . data . logUuid ) {
117- fullResult . logUuid = event . data . logUuid ;
121+ setFullResult ( ( prev ) => ( { ... prev , logUuid : event . data . logUuid } ) ) ;
118122 }
119- } else {
120- const chunk = event . data ;
121- // usage chunk contains null stop values we don't want to merge
122- if ( chunk . usage ) {
123- fullResult . completion . usage = merge ( fullResult . completion . usage , chunk . usage ) ;
124- } else if ( chunk . choices ) {
125- content += chunk . choices [ 0 ] . delta . content ?? '' ;
126- setTestResult ( content ) ;
127- }
128- fullResult . completion = merge ( fullResult . completion , chunk ) ;
123+ }
124+ if ( event . type === 'message' && event . data . choices ?. [ 0 ] ?. delta ?. content ) {
125+ setCompletionContent (
126+ ( prev ) => ( prev ?? '' ) + ( event . data . choices [ 0 ] . delta . content ?? '' ) ,
127+ ) ;
128+ }
129+ if ( event . type === 'message' && event . data . choices ?. [ 0 ] ? .delta ?. reasoning ) {
130+ setReasoningContent (
131+ ( prev ) => ( prev ?? '' ) + ( event . data . choices [ 0 ] . delta . reasoning ?? '' ) ,
132+ ) ;
129133 }
130134 }
131135
132- if ( fullResult . completion . choices ?. [ 0 ] ) {
133- delete fullResult . completion . choices [ 0 ] . delta ;
134- fullResult . completion . choices [ 0 ] . message = {
135- role : 'assistant' ,
136- content,
137- } ;
138- }
136+ const completion = await accumulateStreamToCompletion (
137+ streamToIterator < OpenrouterStreamEvent > ( streamB ) ,
138+ ) ;
139139
140- setFullResult ( fullResult ) ;
140+ setFullResult ( ( prev ) => ( { ... prev , completion } ) ) ;
141141 } catch ( error ) {
142142 console . error ( 'Error testing prompt:' , error ) ;
143143 setTestError ( error instanceof Error ? error . message : 'Unknown error occurred' ) ;
@@ -150,10 +150,17 @@ export const PromptTestModal = () => {
150150
151151 const data = await response . json ( ) ;
152152
153- const result =
154- ( data . completion . choices [ 0 ] as NonStreamingChoice ) . message . content || 'No response content' ;
153+ const choice = data . completion . choices [ 0 ] as NonStreamingChoice ;
154+
155+ const result = choice . message . content || 'No response content' ;
156+ const reasoning = choice . message . reasoning ;
157+
158+ setCompletionContent ( result ) ;
159+
160+ if ( reasoning ) {
161+ setReasoningContent ( reasoning ) ;
162+ }
155163
156- setTestResult ( result ) ;
157164 setFullResult ( data ) ;
158165 } catch ( error ) {
159166 console . error ( 'Error testing prompt:' , error ) ;
@@ -166,7 +173,8 @@ export const PromptTestModal = () => {
166173
167174 const resetTest = ( ) => {
168175 setInputVariables ( { } ) ;
169- setTestResult ( null ) ;
176+ setCompletionContent ( null ) ;
177+ setReasoningContent ( null ) ;
170178 setFullResult ( null ) ;
171179 setTestError ( null ) ;
172180 } ;
@@ -219,8 +227,8 @@ export const PromptTestModal = () => {
219227
220228 let isContentJson = false ;
221229 try {
222- JSON . parse ( testResult as string ) ;
223- isContentJson = true ;
230+ const parsed = JSON . parse ( completionContent as string ) ;
231+ if ( parsed !== null && parsed !== undefined ) isContentJson = true ;
224232 } catch ( error ) {
225233 //
226234 }
@@ -271,7 +279,7 @@ export const PromptTestModal = () => {
271279 >
272280 { isRunning ? 'Running...' : 'Run Test' }
273281 </ Button >
274- { testResult && (
282+ { completionContent && (
275283 < Button onClick = { resetTest } variant = "outline" >
276284 Reset
277285 </ Button >
@@ -294,7 +302,7 @@ export const PromptTestModal = () => {
294302 ) }
295303 </ div >
296304
297- { testResult ? (
305+ { completionContent || reasoningContent ? (
298306 < Tabs
299307 value = { selectedTab }
300308 onValueChange = { ( value ) => setSelectedTab ( value as Tab ) }
@@ -316,29 +324,53 @@ export const PromptTestModal = () => {
316324 />
317325 </ TabsContent >
318326 < TabsContent value = { TABS . content } className = "flex-1 overflow-auto" >
327+ { reasoningContent && (
328+ < >
329+ < h3 className = "font-medium text-lg" > Reasoning</ h3 >
330+ < MarkdownEditor
331+ className = "mb-4"
332+ value = { reasoningContent }
333+ readOnly
334+ minHeight = "100%"
335+ />
336+ </ >
337+ ) }
338+ { completionContent && reasoningContent && (
339+ < h3 className = "font-medium text-lg" > Completion</ h3 >
340+ ) }
319341 { isContentJson ? (
320342 < JsonEditor
321- value = { JSON . parse ( testResult as string ) }
343+ value = { JSON . parse ( completionContent as string ) }
322344 readOnly
323345 minHeight = "100%"
324346 />
347+ ) : completionContent ? (
348+ < MarkdownEditor value = { completionContent } readOnly minHeight = "100%" />
325349 ) : (
326- < MarkdownEditor value = { testResult } readOnly minHeight = "100%" />
350+ < div className = "flex items-center justify-center h-full" >
351+ < Loader2 className = "w-4 h-4 animate-spin" />
352+ </ div >
327353 ) }
328354 </ TabsContent >
329355 < TabsContent value = { TABS . markdown } className = "flex-1 overflow-auto" >
330356 < div className = "p-4 border rounded-md" >
331- < MarkdownRenderer > { testResult } </ MarkdownRenderer >
357+ { completionContent ? (
358+ < MarkdownRenderer > { completionContent } </ MarkdownRenderer >
359+ ) : (
360+ < div className = "flex items-center justify-center h-full" >
361+ < Loader2 className = "w-4 h-4 animate-spin" />
362+ </ div >
363+ ) }
332364 </ div >
333365 </ TabsContent >
334366 < TabsContent value = { TABS . response } className = "flex-1 overflow-auto" >
335367 { isRunning ? (
336368 < div className = "flex items-center justify-center h-full" >
337369 < Loader2 className = "w-4 h-4 animate-spin" />
338370 </ div >
339- ) : (
371+ ) : fullResult ? (
340372 < JsonEditor value = { fullResult } readOnly minHeight = "100%" />
341- ) }
373+ ) : null }
342374 </ TabsContent >
343375 </ Tabs >
344376 ) : (
0 commit comments