From 07a44d3767d91e640fae1aac783d1c416d9063e8 Mon Sep 17 00:00:00 2001 From: Naman Kumar Date: Tue, 22 Oct 2024 12:49:27 +0530 Subject: [PATCH] Implement edit/insert prompts (#5958) closes: https://linear.app/sourcegraph/issue/SRCH-1172/prompts-can-output-into-3-places This PR integrates with the prompt mode (chat | edit | insert) from the SG API. Prompts with edit or insert mode are executed using the `executeEdit`, the same way the commands are executed. The prompt is used as the instruction text for `executeEdit`. First the chat transcript is created, context is fetched and then the `executeEdit` is called. The `FixupTask` from the execution is used to construct the response in chat. The response for now shows the diff only. In BG, the state for interactions mode is saved as intent. The dropdown and icon indicator for to allow users to manually change the mode and to see which mode is set, is only available behind the OneBox feature flag and will not be released yet. ![CleanShot 2024-10-21 at 18 28 08@2x](https://github.com/user-attachments/assets/5cb3e6b1-b53a-41b8-824b-a74f35d1a4b9) ## Test plan - create a edit/insert prompt - execute it from Cody - it should work as expected. ## Changelog - Add ability to execute prompts to perform edits or insert code. --- .../SerializedChatMessage.kt | 4 +- .../src/nodes/ContextItemMentionNode.tsx | 4 +- lib/shared/src/chat/transcript/messages.ts | 2 +- .../src/sourcegraph-api/graphql/client.ts | 3 + .../src/sourcegraph-api/graphql/queries.ts | 1 + .../src/telemetry-v2/events/chat-question.ts | 3 + vscode/src/chat/chat-view/ChatController.ts | 185 +++++++++++++---- vscode/src/chat/protocol.ts | 1 + vscode/src/commands/CommandsController.ts | 2 +- vscode/src/commands/execute/index.ts | 13 +- .../messageCell/human/HumanMessageCell.tsx | 18 +- .../human/editor/HumanMessageEditor.tsx | 30 ++- .../human/editor/toolbar/SubmitButton.tsx | 196 +++++++++--------- .../human/editor/toolbar/Toolbar.tsx | 6 + vscode/webviews/prompts/PromptsTab.tsx | 17 +- 15 files changed, 316 insertions(+), 169 deletions(-) diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/SerializedChatMessage.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/SerializedChatMessage.kt index 9e193acca994..98dec3384c42 100644 --- a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/SerializedChatMessage.kt +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/SerializedChatMessage.kt @@ -10,7 +10,7 @@ data class SerializedChatMessage( val speaker: SpeakerEnum, // Oneof: human, assistant, system val text: String? = null, val model: String? = null, - val intent: IntentEnum? = null, // Oneof: search, chat + val intent: IntentEnum? = null, // Oneof: search, chat, edit, insert ) { enum class SpeakerEnum { @@ -22,6 +22,8 @@ data class SerializedChatMessage( enum class IntentEnum { @SerializedName("search") Search, @SerializedName("chat") Chat, + @SerializedName("edit") Edit, + @SerializedName("insert") Insert, } } diff --git a/lib/prompt-editor/src/nodes/ContextItemMentionNode.tsx b/lib/prompt-editor/src/nodes/ContextItemMentionNode.tsx index dbfd3945ae54..fe1d2e1ce7f9 100644 --- a/lib/prompt-editor/src/nodes/ContextItemMentionNode.tsx +++ b/lib/prompt-editor/src/nodes/ContextItemMentionNode.tsx @@ -177,7 +177,9 @@ function iconForContextItem(contextItem: SerializedContextItem): React.Component ? SYMBOL_CONTEXT_MENTION_PROVIDER.id : contextItem.type === 'repository' || contextItem.type === 'tree' ? REMOTE_REPOSITORY_PROVIDER_URI - : contextItem.providerUri + : contextItem.type === 'openctx' + ? contextItem.providerUri + : 'unknown' return iconForProvider[providerUri] ?? AtSignIcon } diff --git a/lib/shared/src/chat/transcript/messages.ts b/lib/shared/src/chat/transcript/messages.ts index 699d27cc9c65..1333b6aa407d 100644 --- a/lib/shared/src/chat/transcript/messages.ts +++ b/lib/shared/src/chat/transcript/messages.ts @@ -35,7 +35,7 @@ export interface ChatMessage extends Message { model?: string /* The detected intent of the message */ - intent?: 'search' | 'chat' | undefined | null + intent?: 'search' | 'chat' | 'edit' | 'insert' | undefined | null } // An unsafe version of the {@link ChatMessage} that has the PromptString diff --git a/lib/shared/src/sourcegraph-api/graphql/client.ts b/lib/shared/src/sourcegraph-api/graphql/client.ts index 8185e327e2e7..d7d0e2049d4d 100644 --- a/lib/shared/src/sourcegraph-api/graphql/client.ts +++ b/lib/shared/src/sourcegraph-api/graphql/client.ts @@ -415,6 +415,7 @@ export interface Prompt { description?: string draft: boolean autoSubmit?: boolean + mode?: PromptMode definition: { text: string } @@ -427,6 +428,8 @@ export interface Prompt { } } +export type PromptMode = 'CHAT' | 'EDIT' | 'INSERT' + interface ContextFiltersResponse { site: { codyContextFilters: { diff --git a/lib/shared/src/sourcegraph-api/graphql/queries.ts b/lib/shared/src/sourcegraph-api/graphql/queries.ts index 77498c3c43e2..9725be6358d6 100644 --- a/lib/shared/src/sourcegraph-api/graphql/queries.ts +++ b/lib/shared/src/sourcegraph-api/graphql/queries.ts @@ -354,6 +354,7 @@ query ViewerPrompts($query: String!) { description draft autoSubmit + mode definition { text } diff --git a/lib/shared/src/telemetry-v2/events/chat-question.ts b/lib/shared/src/telemetry-v2/events/chat-question.ts index f3b88ee5b3b3..6c350647e7d5 100644 --- a/lib/shared/src/telemetry-v2/events/chat-question.ts +++ b/lib/shared/src/telemetry-v2/events/chat-question.ts @@ -180,6 +180,8 @@ export const events = [ auto: 1, chat: 2, search: 3, + edit: 4, + insert: 5, } satisfies Record< typeof fallbackValue | 'auto' | Exclude, number @@ -215,6 +217,7 @@ function publicContextSummary(globalPrefix: string, context: ContextItem[]) { openctx: cloneDeep(defaultByTypeCount), repository: cloneDeep(defaultByTypeCount), symbol: cloneDeep(defaultByTypeCount), + mode: cloneDeep(defaultByTypeCount), tree: { ...cloneDeep(defaultByTypeCount), isWorkspaceRoot: undefined as number | undefined, diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 374acae07b9b..cfd3b75d0add 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -1,6 +1,7 @@ import { type ChatModel, type CodyClientConfig, + DefaultEditCommands, cenv, clientCapabilities, currentSiteVersion, @@ -89,6 +90,7 @@ import { startAuthProgressIndicator, } from '../../auth/auth-progress-indicator' import type { startTokenReceiver } from '../../auth/token-receiver' +import { executeCodyCommand } from '../../commands/CommandsController' import { getContextFileFromUri } from '../../commands/context/file-path' import { getContextFileFromCursor } from '../../commands/context/selection' import { resolveContextItems } from '../../editor/utils/editor-context' @@ -719,6 +721,12 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv signal.throwIfAborted() const corpusContext = contextAlternatives[0].items + const inputTextWithoutContextChips = editorState + ? PromptString.unsafe_fromUserQuery( + inputTextWithoutContextChipsFromPromptEditorState(editorState) + ) + : inputText + const repositoryMentioned = mentions.find(contextItem => ['repository', 'tree'].includes(contextItem.type) ) @@ -733,55 +741,64 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv ? detectedIntentScores : undefined - const userSpecifiedIntent = this.featureCodyExperimentalOneBox - ? manuallySelectedIntent && detectedIntent + const userSpecifiedIntent = + manuallySelectedIntent && detectedIntent ? detectedIntent - : 'auto' - : undefined + : this.featureCodyExperimentalOneBox + ? 'auto' + : 'chat' + + const finalIntentDetectionResponse = detectedIntent + ? { intent: detectedIntent, allScores: detectedIntentScores } + : this.featureCodyExperimentalOneBox && repositoryMentioned + ? await this.detectChatIntent({ + requestID, + text: inputTextWithoutContextChips.toString(), + }) + .then(async response => { + signal.throwIfAborted() + this.chatBuilder.setLastMessageIntent(response?.intent) + this.postEmptyMessageInProgress(model) + return response + }) + .catch(() => undefined) + : undefined - if (this.featureCodyExperimentalOneBox && repositoryMentioned) { - const inputTextWithoutContextChips = editorState - ? PromptString.unsafe_fromUserQuery( - inputTextWithoutContextChipsFromPromptEditorState(editorState) - ) - : inputText - - const finalIntentDetectionResponse = detectedIntent - ? { intent: detectedIntent, allScores: detectedIntentScores } - : await this.detectChatIntent({ - requestID, - text: inputTextWithoutContextChips.toString(), - }) - .then(async response => { - signal.throwIfAborted() - this.chatBuilder.setLastMessageIntent(response?.intent) - this.postEmptyMessageInProgress(model) - return response - }) - .catch(() => undefined) - - intent = finalIntentDetectionResponse?.intent - intentScores = finalIntentDetectionResponse?.allScores - signal.throwIfAborted() - if (intent === 'search') { - telemetryEvents['cody.chat-question/executed'].record( - { - ...telemetryProperties, - context: corpusContext, - userSpecifiedIntent, - detectedIntent: intent, - detectedIntentScores: intentScores, - }, - { current: span, firstToken: firstTokenSpan, addMetadata: true }, - tokenCounterUtils - ) + intent = finalIntentDetectionResponse?.intent + intentScores = finalIntentDetectionResponse?.allScores + signal.throwIfAborted() - return await this.handleSearchIntent({ + if (['search', 'edit', 'insert'].includes(intent || '')) { + telemetryEvents['cody.chat-question/executed'].record( + { + ...telemetryProperties, context: corpusContext, - signal, - contextAlternatives, - }) - } + userSpecifiedIntent, + detectedIntent: intent, + detectedIntentScores: intentScores, + }, + { current: span, firstToken: firstTokenSpan, addMetadata: true }, + tokenCounterUtils + ) + } + + if (intent === 'edit' || intent === 'insert') { + return await this.handleEditMode({ + requestID, + mode: intent, + instruction: inputTextWithoutContextChips, + context: corpusContext, + signal, + contextAlternatives, + }) + } + + if (intent === 'search') { + return await this.handleSearchIntent({ + context: corpusContext, + signal, + contextAlternatives, + }) } // Experimental Feature: Deep Cody @@ -897,6 +914,84 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv this.postViewTranscript() } + private async handleEditMode({ + requestID, + mode, + instruction, + context, + signal, + contextAlternatives, + }: { + requestID: string + instruction: PromptString + mode: 'edit' | 'insert' + context: ContextItem[] + signal: AbortSignal + contextAlternatives: RankedContext[] + }): Promise { + signal.throwIfAborted() + + this.chatBuilder.setLastMessageContext(context, contextAlternatives) + this.chatBuilder.setLastMessageIntent(mode) + + const result = await executeCodyCommand(DefaultEditCommands.Edit, { + requestID, + runInChatMode: true, + userContextFiles: context, + configuration: { + instruction, + mode, + intent: mode === 'edit' ? 'edit' : 'add', + }, + }) + + if (result?.type !== 'edit' || !result.task) { + this.postError(new Error('Failed to execute edit command'), 'transcript') + return + } + + const task = result.task + + let responseMessage = `Here is the response for the ${task.intent} instruction:\n` + task.diff?.map(diff => { + responseMessage += '\n```diff\n' + if (diff.type === 'deletion') { + responseMessage += task.document + .getText(diff.range) + .split('\n') + .map(line => `- ${line}`) + .join('\n') + } + if (diff.type === 'decoratedReplacement') { + responseMessage += diff.oldText + .split('\n') + .map(line => `- ${line}`) + .join('\n') + responseMessage += diff.text + .split('\n') + .map(line => `+ ${line}`) + .join('\n') + } + if (diff.type === 'insertion') { + responseMessage += diff.text + .split('\n') + .map(line => `+ ${line}`) + .join('\n') + } + responseMessage += '\n```' + }) + + this.chatBuilder.addBotMessage( + { + text: ps`${PromptString.unsafe_fromLLMResponse(responseMessage)}`, + }, + this.chatBuilder.selectedModel || ChatBuilder.NO_MODEL + ) + + void this.saveSession() + this.postViewTranscript() + } + private async computeContext( { text, mentions }: HumanInput, requestID: string, diff --git a/vscode/src/chat/protocol.ts b/vscode/src/chat/protocol.ts index 643a7b21a4d3..d7c9f347bac7 100644 --- a/vscode/src/chat/protocol.ts +++ b/vscode/src/chat/protocol.ts @@ -169,6 +169,7 @@ export type ExtensionMessage = type: 'clientAction' addContextItemsToLastHumanInput?: ContextItem[] | null | undefined appendTextToLastPromptEditor?: string | null | undefined + setLastHumanInputIntent?: ChatMessage['intent'] | null | undefined smartApplyResult?: SmartApplyResult | undefined | null submitHumanInput?: boolean | undefined | null } diff --git a/vscode/src/commands/CommandsController.ts b/vscode/src/commands/CommandsController.ts index 8c4cf07d1122..b57b8dc80aeb 100644 --- a/vscode/src/commands/CommandsController.ts +++ b/vscode/src/commands/CommandsController.ts @@ -76,7 +76,7 @@ class CommandsController implements vscode.Disposable { // Process default commands if (isDefaultChatCommand(commandKey) || isDefaultEditCommand(commandKey)) { - return executeDefaultCommand(commandKey, additionalInstruction) + return executeDefaultCommand(commandKey, { ...args, additionalInstruction }) } const command = this.provider?.get(commandKey) diff --git a/vscode/src/commands/execute/index.ts b/vscode/src/commands/execute/index.ts index 1bf810f57f3d..a67f6558178f 100644 --- a/vscode/src/commands/execute/index.ts +++ b/vscode/src/commands/execute/index.ts @@ -8,6 +8,7 @@ import { } from '@sourcegraph/cody-shared' import type { CommandResult } from '../../CommandResult' import { executeEdit } from '../../edit/execute' +import type { CodyCommandArgs } from '../types' import { executeDocCommand } from './doc' import { executeExplainCommand } from './explain' import { executeSmellCommand } from './smell' @@ -47,20 +48,20 @@ export function isDefaultEditCommand(id: string): DefaultEditCommands | undefine */ export async function executeDefaultCommand( id: DefaultCodyCommands | string, - additionalInstruction?: PromptString + args?: CodyCommandArgs ): Promise { const key = id.replace(/^\//, '').trim() as DefaultCodyCommands switch (key) { case DefaultChatCommands.Explain: - return executeExplainCommand({ additionalInstruction }) + return executeExplainCommand(args) case DefaultChatCommands.Smell: - return executeSmellCommand({ additionalInstruction }) + return executeSmellCommand(args) case DefaultEditCommands.Test: - return executeTestEditCommand({ additionalInstruction }) + return executeTestEditCommand(args) case DefaultEditCommands.Doc: - return executeDocCommand({ additionalInstruction }) + return executeDocCommand(args) case DefaultEditCommands.Edit: - return { task: await executeEdit({}), type: 'edit' } + return { task: await executeEdit(args || {}), type: 'edit' } default: console.log('not a default command') return undefined diff --git a/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.tsx b/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.tsx index 8e0235ef691c..6146ba43f7af 100644 --- a/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.tsx @@ -60,13 +60,19 @@ export const HumanMessageCell: FC = ({ message, ...otherP [messageJSON] ) - return + return ( + + ) } -type HumanMessageCellContent = { initialEditorState: SerializedPromptEditorState } & Omit< - HumanMessageCellProps, - 'message' -> +type HumanMessageCellContent = { + initialEditorState: SerializedPromptEditorState + intent: ChatMessage['intent'] +} & Omit const HumanMessageCellContent = memo(props => { const { models, @@ -86,6 +92,7 @@ const HumanMessageCellContent = memo(props => { editorRef, __storybook__focus, onEditorFocusChange, + intent, } = props return ( @@ -118,6 +125,7 @@ const HumanMessageCellContent = memo(props => { editorRef={editorRef} __storybook__focus={__storybook__focus} onEditorFocusChange={onEditorFocusChange} + initialIntent={intent} /> } className={className} diff --git a/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx b/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx index 3554a652f09a..5c6bddc51f6e 100644 --- a/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx @@ -26,6 +26,7 @@ import { import type { UserAccountInfo } from '../../../../../Chat' import { type ClientActionListener, useClientActionListener } from '../../../../../client/clientState' import { useTelemetryRecorder } from '../../../../../utils/telemetry' +import { useExperimentalOneBox } from '../../../../../utils/useExperimentalOneBox' import styles from './HumanMessageEditor.module.css' import type { SubmitButtonState } from './toolbar/SubmitButton' import { Toolbar } from './toolbar/Toolbar' @@ -65,6 +66,8 @@ export const HumanMessageEditor: FunctionComponent<{ /** For use in storybooks only. */ __storybook__focus?: boolean + + initialIntent?: ChatMessage['intent'] }> = ({ models, userInfo, @@ -84,6 +87,7 @@ export const HumanMessageEditor: FunctionComponent<{ editorRef: parentEditorRef, __storybook__focus, onEditorFocusChange: parentOnEditorFocusChange, + initialIntent, }) => { const telemetryRecorder = useTelemetryRecorder() @@ -110,6 +114,11 @@ export const HumanMessageEditor: FunctionComponent<{ ? 'emptyEditorValue' : 'submittable' + const experimentalOneBoxEnabled = useExperimentalOneBox() + const [submitIntent, setSubmitIntent] = useState( + initialIntent || (experimentalOneBoxEnabled ? undefined : 'chat') + ) + const onSubmitClick = useCallback( (intent?: ChatMessage['intent']) => { if (submitState === 'emptyEditorValue') { @@ -156,19 +165,21 @@ export const HumanMessageEditor: FunctionComponent<{ // Submit search intent query when CMD + Options + Enter is pressed. if ((event.metaKey || event.ctrlKey) && event.altKey) { + setSubmitIntent('search') onSubmitClick('search') return } // Submit chat intent query when CMD + Enter is pressed. if (event.metaKey || event.ctrlKey) { + setSubmitIntent('chat') onSubmitClick('chat') return } - onSubmitClick() + onSubmitClick(submitIntent) }, - [isEmptyEditorValue, onSubmitClick] + [isEmptyEditorValue, onSubmitClick, submitIntent] ) const [isEditorFocused, setIsEditorFocused] = useState(false) @@ -261,6 +272,7 @@ export const HumanMessageEditor: FunctionComponent<{ addContextItemsToLastHumanInput, appendTextToLastPromptEditor, submitHumanInput, + setLastHumanInputIntent, }) => { // Add new context to chat from the "Cody Add Selection to Cody Chat" // command, etc. Only add to the last human input field. @@ -302,19 +314,25 @@ export const HumanMessageEditor: FunctionComponent<{ } if (editorState) { + const onUpdate = awaitUpdate() requestAnimationFrame(() => { if (editorRef.current) { - editorRef.current.setEditorState(editorState, awaitUpdate()) + editorRef.current.setEditorState(editorState, onUpdate) editorRef.current.setFocus(true) } }) } + if (setLastHumanInputIntent) { + setSubmitIntent(setLastHumanInputIntent) + } if (submitHumanInput) { - Promise.all(updates).then(() => onSubmitClick()) + Promise.all(updates).then(() => + onSubmitClick(setLastHumanInputIntent || submitIntent) + ) } }, - [isSent, onSubmitClick] + [isSent, onSubmitClick, submitIntent] ) ) @@ -392,6 +410,8 @@ export const HumanMessageEditor: FunctionComponent<{ focusEditor={focusEditor} hidden={!focused && isSent} className={styles.toolbar} + intent={submitIntent} + onSelectIntent={setSubmitIntent} /> )} diff --git a/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/SubmitButton.tsx b/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/SubmitButton.tsx index 2cf3ab9b780f..f58c45ac54f3 100644 --- a/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/SubmitButton.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/SubmitButton.tsx @@ -1,7 +1,7 @@ import type { ChatMessage } from '@sourcegraph/cody-shared' import clsx from 'clsx' -import { BadgeCheck, Search } from 'lucide-react' -import type { FunctionComponent } from 'react' +import { BadgeCheck, BetweenHorizonalEnd, Pencil, Search } from 'lucide-react' +import type { FC } from 'react' import { Kbd } from '../../../../../../components/Kbd' import { Button } from '../../../../../../components/shadcn/ui/button' import { Command, CommandItem, CommandList } from '../../../../../../components/shadcn/ui/command' @@ -12,12 +12,46 @@ import { CodyIcon } from '../../../../../components/CodyIcon' export type SubmitButtonState = 'submittable' | 'emptyEditorValue' | 'waitingResponseComplete' -export const SubmitButton: FunctionComponent<{ +const IntentOptions: { + title: string + icon: React.FC<{ className?: string }> + intent: ChatMessage['intent'] +}[] = [ + { + title: 'Best for question', + icon: BadgeCheck, + intent: undefined, + }, + { + title: 'Ask the LLM', + icon: CodyIcon, + intent: 'chat', + }, + { + title: 'Search Code', + icon: Search, + intent: 'search', + }, + { + title: 'Edit Code', + icon: Pencil, + intent: 'edit', + }, + { + title: 'Insert Code', + icon: BetweenHorizonalEnd, + intent: 'insert', + }, +] + +export const SubmitButton: FC<{ onClick: (intent?: ChatMessage['intent']) => void isEditorFocused?: boolean state?: SubmitButtonState className?: string -}> = ({ onClick, state = 'submittable', className }) => { + intent?: ChatMessage['intent'] + onSelectIntent?: (intent: ChatMessage['intent']) => void +}> = ({ onClick, state = 'submittable', className, intent, onSelectIntent }) => { const experimentalOneBoxEnabled = useExperimentalOneBox() if (state === 'waitingResponseComplete') { @@ -45,79 +79,67 @@ export const SubmitButton: FunctionComponent<{ ) } - if (experimentalOneBoxEnabled) { - return ( -
- + {/* biome-ignore lint/a11y/noSvgWithoutTitle: */} + + + + + + + Send + + + {experimentalOneBoxEnabled && ( ( + popoverContent={close => ( - onClick()} - className="tw-flex tw-text-left tw-justify-between" - > -
- - Best for question -
-
- -
-
- onClick('chat')} - className="tw-flex tw-text-left tw-justify-between" - > -
- - Ask the LLM -
-
- - -
-
- onClick('search')} - className="tw-flex tw-text-left tw-justify-between" - > -
- - Search Code -
-
- - - -
-
+ {IntentOptions.map(option => ( + { + onSelectIntent?.(option.intent) + close() + }} + className="tw-flex tw-text-left tw-justify-between" + > +
+ + {option.title} +
+
+ ))}
)} @@ -130,39 +152,7 @@ export const SubmitButton: FunctionComponent<{ }, }} /> -
- ) - } - return ( - - - - - - Send - - + )} + ) } diff --git a/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/Toolbar.tsx b/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/Toolbar.tsx index 77ecde3e8ec8..8a2fa37fe7da 100644 --- a/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/Toolbar.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/editor/toolbar/Toolbar.tsx @@ -31,6 +31,8 @@ export const Toolbar: FunctionComponent<{ hidden?: boolean className?: string + intent?: ChatMessage['intent'] + onSelectIntent?: (intent: ChatMessage['intent']) => void }> = ({ userInfo, isEditorFocused, @@ -42,6 +44,8 @@ export const Toolbar: FunctionComponent<{ hidden, className, models, + intent, + onSelectIntent, }) => { /** * If the user clicks in a gap or on the toolbar outside of any of its buttons, report back to @@ -94,6 +98,8 @@ export const Toolbar: FunctionComponent<{ onClick={onSubmitClick} isEditorFocused={isEditorFocused} state={submitState} + intent={intent} + onSelectIntent={onSelectIntent} /> diff --git a/vscode/webviews/prompts/PromptsTab.tsx b/vscode/webviews/prompts/PromptsTab.tsx index 1b92ee30b35d..e4a37ef4b44f 100644 --- a/vscode/webviews/prompts/PromptsTab.tsx +++ b/vscode/webviews/prompts/PromptsTab.tsx @@ -1,4 +1,4 @@ -import type { Action } from '@sourcegraph/cody-shared' +import type { Action, ChatMessage } from '@sourcegraph/cody-shared' import { useClientActionDispatcher } from '../client/clientState' import { useLocalStorage } from '../components/hooks' import { PromptList } from '../components/promptList/PromptList' @@ -6,6 +6,7 @@ import { View } from '../tabs/types' import { getVSCodeAPI } from '../utils/VSCodeApi' import { firstValueFrom } from '@sourcegraph/cody-shared' +import type { PromptMode } from '@sourcegraph/cody-shared/src/sourcegraph-api/graphql/client' import { useExtensionAPI } from '@sourcegraph/prompt-editor' import styles from './PromptsTab.module.css' @@ -30,6 +31,19 @@ export const PromptsTab: React.FC<{ ) } +const promptModeToIntent = (mode?: PromptMode): ChatMessage['intent'] => { + switch (mode) { + case 'CHAT': + return 'chat' + case 'EDIT': + return 'edit' + case 'INSERT': + return 'insert' + default: + return 'chat' + } +} + export function useActionSelect() { const dispatchClientAction = useClientActionDispatcher() const extensionAPI = useExtensionAPI() @@ -56,6 +70,7 @@ export function useActionSelect() { dispatchClientAction( { editorState: promptEditorState, + setLastHumanInputIntent: promptModeToIntent(action.mode), submitHumanInput: action.autoSubmit, }, // Buffer because PromptEditor is not guaranteed to be mounted after the `setView`