Skip to content
This repository was archived by the owner on Aug 1, 2025. It is now read-only.

Commit 1716682

Browse files
committed
Allow testing with mock LLM resposnse
1 parent 05a601e commit 1716682

2 files changed

Lines changed: 360 additions & 99 deletions

File tree

vscode/webviews/chat/Transcript.tsx

Lines changed: 72 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
type Guardrails,
44
type Model,
55
type NLSSearchDynamicFilter,
6+
PromptString,
67
type SerializedPromptEditorValue,
78
deserializeContextItem,
89
isAbortErrorOrSocketHangUp,
@@ -14,7 +15,6 @@ import {
1415
type FC,
1516
memo,
1617
useCallback,
17-
useContext,
1818
useEffect,
1919
useImperativeHandle,
2020
useMemo,
@@ -25,7 +25,6 @@ import type { UserAccountInfo } from '../Chat'
2525
import type { ApiPostMessage } from '../Chat'
2626
import { getVSCodeAPI } from '../utils/VSCodeApi'
2727
import { SpanManager } from '../utils/spanManager'
28-
import { getTraceparentFromSpanContext } from '../utils/telemetry'
2928
import { useOmniBox } from '../utils/useOmniBox'
3029
import type { CodeBlockActionsProps } from './ChatMessageContent/ChatMessageContent'
3130
import {
@@ -37,7 +36,7 @@ import { HumanMessageCell } from './cells/messageCell/human/HumanMessageCell'
3736
import { type Context, type Span, context, trace } from '@opentelemetry/api'
3837
import { DeepCodyAgentID } from '@sourcegraph/cody-shared/src/models/client'
3938
import * as uuid from 'uuid'
40-
import { isCodeSearchContextItem } from '../../src/context/openctx/codeSearch'
39+
4140
import { useClientActionListener } from '../client/clientState'
4241
import { useLocalStorage } from '../components/hooks'
4342

@@ -50,6 +49,7 @@ import { ToolStatusCell } from './cells/toolCell/ToolStatusCell'
5049
import { LoadingDots } from './components/LoadingDots'
5150
import { ScrollbarMarkers } from './components/ScrollbarMarkers'
5251
import { LastEditorContext } from './context'
52+
import { MOCK_LONG_RESPONSE } from './mockData'
5353

5454
interface TranscriptProps {
5555
activeChatContext?: Context
@@ -97,11 +97,11 @@ export const Transcript: FC<TranscriptProps> = props => {
9797
activeChatContext,
9898
setActiveChatContext,
9999
chatEnabled,
100-
transcript,
100+
transcript: originalTranscript,
101101
tokenUsage,
102102
models,
103103
userInfo,
104-
messageInProgress,
104+
messageInProgress: originalMessageInProgress,
105105
guardrails,
106106
postMessage,
107107
copyButtonOnSubmit,
@@ -110,6 +110,66 @@ export const Transcript: FC<TranscriptProps> = props => {
110110
welcomeContent,
111111
} = props
112112

113+
// Simulation state
114+
const [isSimulating, setIsSimulating] = useState(false)
115+
const [simulationTranscript, setSimulationTranscript] = useState<ChatMessage[]>([])
116+
const [simulationMessageInProgress, setSimulationMessageInProgress] = useState<ChatMessage | null>(
117+
null
118+
)
119+
120+
// Use simulation state when active, otherwise use original props
121+
const transcript = isSimulating ? simulationTranscript : originalTranscript
122+
const messageInProgress = isSimulating ? simulationMessageInProgress : originalMessageInProgress
123+
124+
// Simulation function
125+
const startSimulation = useCallback(() => {
126+
setIsSimulating(true)
127+
128+
// Create a human message
129+
const humanMessage: ChatMessage = {
130+
speaker: 'human',
131+
text: PromptString.unsafe_fromUserQuery(
132+
'Please help me implement a complex feature with multiple code examples.'
133+
),
134+
intent: 'chat',
135+
}
136+
137+
// Set initial transcript with human message
138+
setSimulationTranscript([humanMessage])
139+
140+
// Start streaming assistant response
141+
const assistantMessage: ChatMessage = {
142+
speaker: 'assistant',
143+
text: PromptString.unsafe_fromLLMResponse(''),
144+
intent: 'chat',
145+
}
146+
147+
setSimulationMessageInProgress(assistantMessage)
148+
149+
// Long response with multiple code snippets
150+
const fullResponse = MOCK_LONG_RESPONSE
151+
152+
// Simulate streaming by updating text character by character
153+
let currentIndex = 0
154+
const streamingInterval = setInterval(() => {
155+
if (currentIndex < fullResponse.length) {
156+
const currentText = fullResponse.substring(0, currentIndex + 1)
157+
setSimulationMessageInProgress(prev =>
158+
prev ? { ...prev, text: PromptString.unsafe_fromLLMResponse(currentText) } : null
159+
)
160+
currentIndex++
161+
} else {
162+
// Streaming complete
163+
clearInterval(streamingInterval)
164+
setSimulationTranscript(prev => [
165+
...prev,
166+
{ ...assistantMessage, text: PromptString.unsafe_fromLLMResponse(fullResponse) },
167+
])
168+
setSimulationMessageInProgress(null)
169+
}
170+
}, 0)
171+
}, [])
172+
113173
const interactions = useMemo(
114174
() => transcriptToInteractionPairs(transcript, messageInProgress),
115175
[transcript, messageInProgress]
@@ -243,6 +303,7 @@ export const Transcript: FC<TranscriptProps> = props => {
243303
? lastHumanEditorRef
244304
: undefined
245305
}
306+
startSimulation={startSimulation}
246307
/>
247308
)
248309
},
@@ -260,6 +321,7 @@ export const Transcript: FC<TranscriptProps> = props => {
260321
smartApply,
261322
interactions,
262323
messageInProgress,
324+
startSimulation,
263325
]
264326
)
265327

@@ -381,6 +443,7 @@ interface TranscriptInteractionProps
381443
isLastSentInteraction: boolean
382444
priorAssistantMessageIsLoading: boolean
383445
editorRef?: React.RefObject<PromptEditorRefAPI | null>
446+
startSimulation: () => void
384447
}
385448

386449
export type RegeneratingCodeBlockState = {
@@ -406,11 +469,11 @@ const TranscriptInteraction: FC<TranscriptInteractionProps> = memo(props => {
406469
copyButtonOnSubmit,
407470
smartApply,
408471
editorRef: parentEditorRef,
472+
startSimulation,
409473
} = props
410474

411475
const { activeChatContext, setActiveChatContext } = props
412476
const humanEditorRef = useRef<PromptEditorRefAPI | null>(null)
413-
const lastEditorRef = useContext(LastEditorContext)
414477
useImperativeHandle(parentEditorRef, () => humanEditorRef.current)
415478

416479
const [selectedIntent, setSelectedIntent] = useState<ChatMessage['intent']>(humanMessage?.intent)
@@ -423,65 +486,6 @@ const TranscriptInteraction: FC<TranscriptInteractionProps> = memo(props => {
423486
}
424487
}, [humanMessage, isFirstInteraction, isLastInteraction])
425488

426-
const onUserAction = useCallback(
427-
(action: 'edit' | 'submit', manuallySelectedIntent: ChatMessage['intent']) => {
428-
// Start the span as soon as the user initiates the action
429-
const startMark = performance.mark('startSubmit')
430-
const spanManager = new SpanManager('cody-webview')
431-
const span = spanManager.startSpan('chat-interaction', {
432-
attributes: {
433-
sampled: true,
434-
'render.state': 'started',
435-
'startSubmit.mark': startMark.startTime,
436-
},
437-
})
438-
439-
if (!span) {
440-
throw new Error('Failed to start span for chat interaction')
441-
}
442-
443-
const spanContext = trace.setSpan(context.active(), span)
444-
setActiveChatContext(spanContext)
445-
const currentSpanContext = span.spanContext()
446-
447-
const traceparent = getTraceparentFromSpanContext(currentSpanContext)
448-
449-
// Serialize the editor value after starting the span
450-
const editorValue = humanEditorRef.current?.getSerializedValue()
451-
if (!editorValue) {
452-
console.error('Failed to serialize editor value')
453-
return
454-
}
455-
456-
const commonProps = {
457-
editorValue,
458-
traceparent,
459-
manuallySelectedIntent,
460-
}
461-
462-
if (action === 'edit') {
463-
// Remove search context chips from the next input so that the user cannot
464-
// reference search results that don't exist anymore.
465-
// This is a no-op if the input does not contain any search context chips.
466-
// NOTE: Doing this for the penultimate input only seems to suffice because
467-
// editing a message earlier in the transcript will clear the conversation
468-
// and reset the last input anyway.
469-
if (isLastSentInteraction) {
470-
lastEditorRef.current?.filterMentions(item => !isCodeSearchContextItem(item))
471-
}
472-
editHumanMessage({
473-
messageIndexInTranscript: humanMessage.index,
474-
...commonProps,
475-
})
476-
} else {
477-
submitHumanMessage({
478-
...commonProps,
479-
})
480-
}
481-
},
482-
[humanMessage, setActiveChatContext, isLastSentInteraction, lastEditorRef]
483-
)
484-
485489
// Omnibox is enabled if the user is not a dotcom user and the omnibox is enabled
486490
const omniboxEnabled = useOmniBox() && !userInfo?.isDotComUser
487491

@@ -639,22 +643,13 @@ const TranscriptInteraction: FC<TranscriptInteractionProps> = memo(props => {
639643

640644
const onHumanMessageSubmit = useCallback(
641645
(intentOnSubmit: ChatMessage['intent']) => {
642-
// Current intent is the last selected intent if any or the current intent of the human message
643-
const currentIntent = selectedIntent || humanMessage?.intent
644-
// If no intent on submit provided, use the current intent instead
645-
const newIntent = intentOnSubmit === undefined ? currentIntent : intentOnSubmit
646-
setSelectedIntent(newIntent)
647-
if (humanMessage.isUnsentFollowup) {
648-
onUserAction('submit', newIntent)
649-
} else {
650-
// Use onUserAction directly with the new intent
651-
onUserAction('edit', newIntent)
652-
}
646+
// Start simulation instead of actual submission
647+
startSimulation()
653648
// Set the unsent followup flag to false after submitting
654649
// to makes sure the last editor for Agent mode gets reset.
655650
humanMessage.isUnsentFollowup = false
656651
},
657-
[humanMessage, onUserAction, selectedIntent]
652+
[humanMessage, startSimulation]
658653
)
659654

660655
const onSelectedFiltersUpdate = useCallback(
@@ -870,28 +865,6 @@ export function editHumanMessage({
870865
}, 50)
871866
}
872867

873-
function submitHumanMessage({
874-
editorValue,
875-
manuallySelectedIntent,
876-
traceparent,
877-
}: {
878-
editorValue: SerializedPromptEditorValue
879-
manuallySelectedIntent?: ChatMessage['intent']
880-
traceparent: string
881-
}): void {
882-
getVSCodeAPI().postMessage({
883-
command: 'submit',
884-
text: editorValue.text,
885-
editorState: editorValue.editorState,
886-
contextItems: editorValue.contextItems.map(deserializeContextItem),
887-
manuallySelectedIntent,
888-
traceparent,
889-
})
890-
setTimeout(() => {
891-
focusLastHumanMessageEditor()
892-
}, 50)
893-
}
894-
895868
function reevaluateSearchWithSelectedFilters({
896869
messageIndexInTranscript,
897870
selectedFilters,

0 commit comments

Comments
 (0)