Skip to content

Commit

Permalink
Add Prompts Quick Pick
Browse files Browse the repository at this point in the history
  • Loading branch information
thenamankumar committed Nov 20, 2024
1 parent 053e0c0 commit 6df5557
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 25 deletions.
11 changes: 8 additions & 3 deletions lib/shared/src/sourcegraph-api/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1239,11 +1239,13 @@ export class SourcegraphGraphQLAPIClient {
first,
recommendedOnly,
signal,
orderByMultiple,
}: {
query: string
query?: string
first: number | undefined
recommendedOnly: boolean
recommendedOnly?: boolean
signal?: AbortSignal
orderByMultiple?: PromptsOrderBy[]
}): Promise<Prompt[]> {
const hasIncludeViewerDraftsArg = await this.isValidSiteVersion({ minimumVersion: '5.9.0' })

Expand All @@ -1253,7 +1255,10 @@ export class SourcegraphGraphQLAPIClient {
query,
first: first ?? 100,
recommendedOnly: recommendedOnly,
orderByMultiple: [PromptsOrderBy.PROMPT_RECOMMENDED, PromptsOrderBy.PROMPT_UPDATED_AT],
orderByMultiple: orderByMultiple || [
PromptsOrderBy.PROMPT_RECOMMENDED,
PromptsOrderBy.PROMPT_UPDATED_AT,
],
},
signal
)
Expand Down
2 changes: 1 addition & 1 deletion lib/shared/src/sourcegraph-api/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export enum PromptsOrderBy {
}

export const PROMPTS_QUERY = `
query ViewerPrompts($query: String!, $first: Int!, $recommendedOnly: Boolean!, $orderByMultiple: [PromptsOrderBy!]) {
query ViewerPrompts($query: String, $first: Int!, $recommendedOnly: Boolean!, $orderByMultiple: [PromptsOrderBy!]) {
prompts(query: $query, first: $first, includeDrafts: false, recommendedOnly: $recommendedOnly, includeViewerDrafts: true, viewerIsAffiliated: true, orderByMultiple: $orderByMultiple) {
nodes {
id
Expand Down
9 changes: 5 additions & 4 deletions vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,11 @@
"key": "alt+tab",
"when": "cody.activated && !editorReadonly && config.cody.internal.unstable"
},
{
"command": "cody.command.execute-prompt",
"key": "alt+p",
"when": "cody.activated"
},
{
"command": "cody.tutorial.edit",
"key": "alt+k",
Expand Down Expand Up @@ -804,10 +809,6 @@
"command": "cody.command.abort-commit",
"when": "cody.activated && cody.isGeneratingCommit"
},
{
"command": "cody.menu.custom-commands",
"when": "cody.activated"
},
{
"command": "cody.chat.signIn",
"when": "!cody.activated"
Expand Down
21 changes: 21 additions & 0 deletions vscode/src/chat/chat-view/ChatsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type ChatClient,
DEFAULT_EVENT_SOURCE,
type Guardrails,
type PromptMode,
authStatus,
currentAuthStatus,
currentAuthStatusAuthed,
Expand Down Expand Up @@ -102,6 +103,26 @@ export class ChatsController implements vscode.Disposable {
}
}

public async executePrompt({
text,
mode,
autoSubmit,
}: { text: string; mode: PromptMode; autoSubmit: boolean }): Promise<void> {
await vscode.commands.executeCommand('cody.chat.new')

const webviewPanelOrView =
this.panel.webviewPanelOrView || (await this.panel.createWebviewViewOrPanel())

setTimeout(
() =>
webviewPanelOrView.webview.postMessage({
type: 'clientAction',
setPromptAsInput: { text, mode, autoSubmit },
}),
1000
)
}

public registerViewsAndCommands() {
this.disposables.push(
vscode.window.registerWebviewViewProvider('cody.chat', this.panel, {
Expand Down
5 changes: 5 additions & 0 deletions vscode/src/chat/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
CodyIDE,
ContextItem,
ContextItemSource,
PromptMode,
RangeData,
RequestMessage,
ResponseMessage,
Expand Down Expand Up @@ -172,6 +173,10 @@ export type ExtensionMessage =
setLastHumanInputIntent?: ChatMessage['intent'] | null | undefined
smartApplyResult?: SmartApplyResult | undefined | null
submitHumanInput?: boolean | undefined | null
setPromptAsInput?:
| { text: string; mode?: PromptMode | undefined | null; autoSubmit: boolean }
| undefined
| null
}
| ({ type: 'attribution' } & ExtensionAttributionMessage)
| { type: 'rpc/response'; message: ResponseMessage }
Expand Down
7 changes: 4 additions & 3 deletions vscode/src/edit/input/get-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,13 +314,14 @@ export const getInput = async (
const editInput = createQuickPick({
title: activeTitle,
placeHolder: 'Enter edit instructions (type @ to include code, ⏎ to submit)',
getItems: () =>
getEditInputItems(
getItems: () => {
return getEditInputItems(
editInput.input.value,
activeRangeItem,
activeModelItem,
showModelSelector
),
)
},
onDidHide: () => editor.setDecorations(PREVIEW_RANGE_DECORATION, []),
...(source === 'menu'
? {
Expand Down
6 changes: 6 additions & 0 deletions vscode/src/edit/input/quick-pick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ interface QuickPickConfiguration {
interface QuickPick {
input: vscode.QuickPick<vscode.QuickPickItem>
render: (value: string) => void
setItems: (items: vscode.QuickPickItem[]) => void
hide: () => void
}

export const createQuickPick = ({
Expand Down Expand Up @@ -90,5 +92,9 @@ export const createQuickPick = ({

quickPick.show()
},
setItems: (items: vscode.QuickPickItem[]) => {
quickPick.items = items
},
hide: () => quickPick.hide(),
}
}
4 changes: 3 additions & 1 deletion vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { PoorMansBash } from './minion/environment'
import { CodyProExpirationNotifications } from './notifications/cody-pro-expiration'
import { showSetupNotification } from './notifications/setup-notification'
import { logDebug, logError } from './output-channel-logger'
import { PromptsManager } from './prompts/manager'
import { initVSCodeGitApi } from './repository/git-extension-api'
import { authProvider } from './services/AuthProvider'
import { charactersLogger } from './services/CharactersLogger'
Expand Down Expand Up @@ -896,7 +897,8 @@ function registerChat(
ghostHintDecorator,
extensionClient: platform.extensionClient,
})
disposables.push(ghostHintDecorator, editorManager, new CodeActionProvider())
const promptsManager = new PromptsManager({ chatsController })
disposables.push(ghostHintDecorator, editorManager, new CodeActionProvider(), promptsManager)

// Register a serializer for reviving the chat panel on reload
if (vscode.window.registerWebviewPanelSerializer) {
Expand Down
86 changes: 86 additions & 0 deletions vscode/src/prompts/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { type PromptMode, graphqlClient } from '@sourcegraph/cody-shared'
import * as vscode from 'vscode'
import type { ChatsController } from '../chat/chat-view/ChatsController'
import { createQuickPick } from '../edit/input/quick-pick'

export class PromptsManager implements vscode.Disposable {
private disposables: vscode.Disposable[] = []
private chatsController: ChatsController

constructor(args: { chatsController: ChatsController }) {
this.chatsController = args.chatsController

const executePrompt = vscode.commands.registerCommand(
'cody.command.execute-prompt',
this.showPromptsQuickPick
)
this.disposables.push(executePrompt)
}

public showPromptsQuickPick = async (args: any): Promise<undefined> => {
const getItems = async (query?: string) => {
const prompts = await graphqlClient.queryPrompts({
query: query || '',
first: 10,
recommendedOnly: false,
})

return {
items: prompts.map(
prompt =>
({
label: prompt.name,
description: prompt.description,
value: JSON.stringify({
id: prompt.id,
text: prompt.definition.text,
mode: prompt.mode,
autoSubmit: prompt.autoSubmit,
}),
}) as vscode.QuickPickItem
),
}
}
const quickPick = createQuickPick({
title: 'Prompts',
placeHolder: 'Search a prompt',
getItems,
onDidAccept: async item => {
// execute prompt
console.log(item)
if (!item) {
return
}

const {
text,
mode,
autoSubmit,
}: { text: string; mode: PromptMode; autoSubmit: boolean } = JSON.parse(
(item as unknown as { value: string }).value
)

this.chatsController.executePrompt({
text,
mode,
autoSubmit,
})

quickPick.hide()
},
onDidChangeValue: async query => {
const { items } = await getItems(query)
quickPick.setItems(items)
},
})

quickPick.render('')
}

public dispose(): void {
for (const disposable of this.disposables) {
disposable.dispose()
}
this.disposables = []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import {
ModelTag,
type SerializedPromptEditorState,
type SerializedPromptEditorValue,
firstValueFrom,
skipPendingOperation,
textContentFromSerializedLexicalNode,
} from '@sourcegraph/cody-shared'
import {
PromptEditor,
type PromptEditorRefAPI,
useDefaultContextForChat,
useExtensionAPI,
} from '@sourcegraph/prompt-editor'
import clsx from 'clsx'
import {
Expand All @@ -25,6 +28,7 @@ import {
} from 'react'
import type { UserAccountInfo } from '../../../../../Chat'
import { type ClientActionListener, useClientActionListener } from '../../../../../client/clientState'
import { promptModeToIntent } from '../../../../../prompts/PromptsTab'
import { useTelemetryRecorder } from '../../../../../utils/telemetry'
import { useExperimentalOneBox } from '../../../../../utils/useExperimentalOneBox'
import styles from './HumanMessageEditor.module.css'
Expand Down Expand Up @@ -276,6 +280,8 @@ export const HumanMessageEditor: FunctionComponent<{
})
}, [telemetryRecorder.recordEvent, isFirstMessage, isSent])

const extensionAPI = useExtensionAPI()

// Set up the message listener so the extension can control the input field.
useClientActionListener(
useCallback<ClientActionListener>(
Expand All @@ -285,6 +291,7 @@ export const HumanMessageEditor: FunctionComponent<{
appendTextToLastPromptEditor,
submitHumanInput,
setLastHumanInputIntent,
setPromptAsInput,
}) => {
// Add new context to chat from the "Cody Add Selection to Cody Chat"
// command, etc. Only add to the last human input field.
Expand Down Expand Up @@ -339,13 +346,51 @@ export const HumanMessageEditor: FunctionComponent<{
setSubmitIntent(setLastHumanInputIntent)
}

if (submitHumanInput) {
let promptIntent = undefined

if (setPromptAsInput) {
// set the intent
promptIntent = promptModeToIntent(setPromptAsInput.mode)

updates.push(
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: <explanation>
new Promise<void>(async resolve => {
// get initial context
const { initialContext } = await firstValueFrom(
extensionAPI.defaultContext().pipe(skipPendingOperation())
)
// hydrate raw prompt text
const promptEditorState = await firstValueFrom(
extensionAPI.hydratePromptMessage(setPromptAsInput.text, initialContext)
)

// update editor state
requestAnimationFrame(async () => {
if (editorRef.current) {
await Promise.all([
editorRef.current.setEditorState(promptEditorState),
editorRef.current.setFocus(true),
])
}
resolve()
})
})
)
}

if (submitHumanInput || setPromptAsInput?.autoSubmit) {
Promise.all(updates).then(() =>
onSubmitClick(setLastHumanInputIntent || submitIntent, true)
onSubmitClick(promptIntent || setLastHumanInputIntent || submitIntent, true)
)
}
},
[isSent, onSubmitClick, submitIntent]
[
isSent,
onSubmitClick,
submitIntent,
extensionAPI.hydratePromptMessage,
extensionAPI.defaultContext,
]
)
)

Expand Down
Loading

0 comments on commit 6df5557

Please sign in to comment.