Skip to content

Commit

Permalink
feat: prompts library (#6346)
Browse files Browse the repository at this point in the history
For CODY-3525

This PR turns the action row with "Create prompt" and "Explore prompt
library" into a dropdown with more filters (also tagging).

Tag filters are only available if the backing sourcegraph instance is at
least v5.11 (to be released on Wednesday) or an insider build.

The height of the tags filters is currently limited to 200px, and
becomes scrollable afterwards. We may need a bit more time to understand
how pagination should be used for tags in the editor (or if that should
be used at all), and for now we're just requesting the first 999 tags.
Should be plenty for most customers.


https://github.com/user-attachments/assets/0175aef7-2bed-442e-bd6a-11953bb4d6fb

## Test plan

Manual testing

## Changelog

feat: prompts library
  • Loading branch information
bahrmichael authored Dec 17, 2024
1 parent ab5164b commit 4da049a
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 65 deletions.
20 changes: 19 additions & 1 deletion lib/shared/src/misc/rpc/webviewAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import type { FeatureFlag } from '../../experimentation/FeatureFlagProvider'
import type { ContextMentionProviderMetadata } from '../../mentions/api'
import type { MentionQuery } from '../../mentions/query'
import type { Model } from '../../models/model'
import type { FetchHighlightFileParameters, Prompt } from '../../sourcegraph-api/graphql/client'
import type {
FetchHighlightFileParameters,
Prompt,
PromptTag,
} from '../../sourcegraph-api/graphql/client'
import { type createMessageAPIForWebview, proxyExtensionAPI } from './rpc'

export interface WebviewToExtensionAPI {
Expand All @@ -28,6 +32,8 @@ export interface WebviewToExtensionAPI {
* the Prompt Library).
*/
prompts(input: PromptsInput): Observable<PromptsResult>
promptTags(input: PromptTagsInput): Observable<PromptTagsResult>
getCurrentUserId(): Observable<string | null | Error>

/**
* Stream with actions from cody agent service, serves as transport for any client
Expand Down Expand Up @@ -112,6 +118,8 @@ export function createExtensionAPI(
mentionMenuData: proxyExtensionAPI(messageAPI, 'mentionMenuData'),
evaluatedFeatureFlag: proxyExtensionAPI(messageAPI, 'evaluatedFeatureFlag'),
prompts: proxyExtensionAPI(messageAPI, 'prompts'),
promptTags: proxyExtensionAPI(messageAPI, 'promptTags'),
getCurrentUserId: proxyExtensionAPI(messageAPI, 'getCurrentUserId'),
clientActionBroadcast: proxyExtensionAPI(messageAPI, 'clientActionBroadcast'),
models: proxyExtensionAPI(messageAPI, 'models'),
chatModels: proxyExtensionAPI(messageAPI, 'chatModels'),
Expand Down Expand Up @@ -156,6 +164,10 @@ export interface PromptsInput {
query: string
first?: number
recommendedOnly: boolean
tags?: string[]
owner?: string
includeViewerDrafts?: boolean
builtinOnly?: boolean
}

export type Action = PromptAction | CommandAction
Expand All @@ -170,6 +182,12 @@ export interface PromptsResult {
query: string
}

export type PromptTagsInput = {
first?: number
}

export type PromptTagsResult = PromptTag[]

export type PromptsMigrationStatus =
| InitialPromptsMigrationStatus
| InProgressPromptsMigrationStatus
Expand Down
50 changes: 48 additions & 2 deletions lib/shared/src/sourcegraph-api/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
NLS_SEARCH_QUERY,
PACKAGE_LIST_QUERY,
PROMPTS_QUERY,
PROMPT_TAGS_QUERY,
PromptsOrderBy,
RECORD_TELEMETRY_EVENTS_MUTATION,
REPOSITORY_IDS_QUERY,
Expand Down Expand Up @@ -526,6 +527,11 @@ export enum PromptMode {
INSERT = 'INSERT',
}

export interface PromptTag {
id: string
name: string
}

interface ContextFiltersResponse {
site: {
codyContextFilters: {
Expand Down Expand Up @@ -844,10 +850,11 @@ export class SourcegraphGraphQLAPIClient {
).then(response => extractDataOrError(response, data => data.site?.isCodyEnabled ?? false))
}

public async getCurrentUserId(): Promise<string | null | Error> {
public async getCurrentUserId(signal?: AbortSignal): Promise<string | null | Error> {
return this.fetchSourcegraphAPI<APIResponse<CurrentUserIdResponse>>(
CURRENT_USER_ID_QUERY,
{}
{},
signal
).then(response =>
extractDataOrError(response, data => (data.currentUser ? data.currentUser.id : null))
)
Expand Down Expand Up @@ -1286,18 +1293,30 @@ export class SourcegraphGraphQLAPIClient {
query,
first,
recommendedOnly,
tags,
signal,
orderByMultiple,
owner,
includeViewerDrafts,
builtinOnly,
}: {
query?: string
first: number | undefined
recommendedOnly?: boolean
tags?: string[]
signal?: AbortSignal
orderByMultiple?: PromptsOrderBy[]
owner?: string
includeViewerDrafts?: boolean
builtinOnly?: boolean
}): Promise<Prompt[]> {
const hasIncludeViewerDraftsArg = await this.isValidSiteVersion({
minimumVersion: '5.9.0',
})
const hasPromptTagsField = await this.isValidSiteVersion({
minimumVersion: '5.11.0',
insider: true,
})

const response = await this.fetchSourcegraphAPI<APIResponse<{ prompts: { nodes: Prompt[] } }>>(
hasIncludeViewerDraftsArg ? PROMPTS_QUERY : LEGACY_PROMPTS_QUERY_5_8,
Expand All @@ -1309,6 +1328,10 @@ export class SourcegraphGraphQLAPIClient {
PromptsOrderBy.PROMPT_RECOMMENDED,
PromptsOrderBy.PROMPT_UPDATED_AT,
],
tags: hasPromptTagsField ? tags : undefined,
owner,
includeViewerDrafts: includeViewerDrafts ?? true,
builtinOnly,
},
signal
)
Expand Down Expand Up @@ -1397,6 +1420,29 @@ export class SourcegraphGraphQLAPIClient {
return
}

public async queryPromptTags({
signal,
}: {
signal?: AbortSignal
}): Promise<PromptTag[]> {
const hasPromptTags = await this.isValidSiteVersion({
minimumVersion: '5.11.0',
insider: true,
})
if (!hasPromptTags) {
return []
}

const response = await this.fetchSourcegraphAPI<
APIResponse<{ promptTags: { nodes: PromptTag[] } }>
>(PROMPT_TAGS_QUERY, signal)
const result = extractDataOrError(response, data => data.promptTags.nodes)
if (result instanceof Error) {
throw result
}
return result
}

/**
* recordTelemetryEvents uses the new Telemetry API to record events that
* gets exported: https://sourcegraph.com/docs/dev/background-information/telemetry
Expand Down
20 changes: 18 additions & 2 deletions lib/shared/src/sourcegraph-api/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,8 @@ export enum PromptsOrderBy {
}

export const PROMPTS_QUERY = `
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) {
query ViewerPrompts($query: String, $first: Int!, $recommendedOnly: Boolean!, $orderByMultiple: [PromptsOrderBy!], $tags: [ID!], $owner: ID, $includeViewerDrafts: Boolean!) {
prompts(query: $query, first: $first, includeDrafts: false, recommendedOnly: $recommendedOnly, includeViewerDrafts: $includeViewerDrafts, viewerIsAffiliated: true, orderByMultiple: $orderByMultiple, tags: $tags, owner: $owner) {
nodes {
id
name
Expand All @@ -407,6 +407,12 @@ query ViewerPrompts($query: String, $first: Int!, $recommendedOnly: Boolean!, $o
displayName
avatarURL
}
tags(first: 999) {
nodes {
id
name
}
}
}
totalCount
}
Expand Down Expand Up @@ -442,6 +448,16 @@ query ViewerBuiltinPrompts($query: String!, $first: Int!, $orderByMultiple: [Pro
}
}`

export const PROMPT_TAGS_QUERY = `
query PromptTags() {
promptTags(first: 999) {
nodes {
id
name
}
}
}`

export const REPO_NAME_QUERY = `
query ResolveRepoName($cloneURL: String!) {
repository(cloneURL: $cloneURL) {
Expand Down
13 changes: 13 additions & 0 deletions vscode/src/auth/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { graphqlClient, isAbortError } from '@sourcegraph/cody-shared'

export async function getCurrentUserId(signal: AbortSignal): Promise<string | null | Error> {
try {
return await graphqlClient.getCurrentUserId(signal)
} catch (error) {
if (isAbortError(error)) {
throw error
}

return null
}
}
58 changes: 30 additions & 28 deletions vscode/src/chat/chat-view/ChatController.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,57 @@
import {
type AuthStatus,
type ChatModel,
type ClientActionBroadcast,
type CodyClientConfig,
type ContextItemFile,
type ContextItemRepository,
DefaultEditCommands,
type NLSSearchDynamicFilter,
REMOTE_DIRECTORY_PROVIDER_URI,
REMOTE_FILE_PROVIDER_URI,
REMOTE_REPOSITORY_PROVIDER_URI,
cenv,
clientCapabilities,
currentSiteVersion,
distinctUntilChanged,
extractContextFromTraceparent,
firstResultFromOperation,
forceHydration,
inputTextWithMappedContextChipsFromPromptEditorState,
pendingOperation,
ps,
resolvedConfig,
shareReplay,
skip,
skipPendingOperation,
wrapInActiveSpan,
} from '@sourcegraph/cody-shared'
import {
type BillingCategory,
type BillingProduct,
CHAT_OUTPUT_TOKEN_BUDGET,
type ChatClient,
type ChatMessage,
type ChatModel,
type ClientActionBroadcast,
ClientConfigSingleton,
type CodyClientConfig,
type CompletionParameters,
type ContextItem,
type ContextItemFile,
type ContextItemOpenCtx,
type ContextItemRepository,
ContextItemSource,
DOTCOM_URL,
type DefaultChatCommands,
DefaultEditCommands,
type EventSource,
FeatureFlag,
type Guardrails,
type Message,
ModelUsage,
type NLSSearchDynamicFilter,
PromptString,
REMOTE_DIRECTORY_PROVIDER_URI,
REMOTE_FILE_PROVIDER_URI,
REMOTE_REPOSITORY_PROVIDER_URI,
type RankedContext,
type SerializedChatInteraction,
type SerializedChatTranscript,
type SerializedPromptEditorState,
Typewriter,
addMessageListenersForExtensionAPI,
authStatus,
cenv,
clientCapabilities,
createMessageAPIForExtension,
currentAuthStatus,
currentAuthStatusAuthed,
currentResolvedConfig,
currentSiteVersion,
currentUserProductSubscription,
distinctUntilChanged,
extractContextFromTraceparent,
featureFlagProvider,
firstResultFromOperation,
forceHydration,
getContextForChatMessage,
graphqlClient,
hydrateAfterPostMessage,
inputTextWithMappedContextChipsFromPromptEditorState,
inputTextWithoutContextChipsFromPromptEditorState,
isAbortErrorOrSocketHangUp,
isContextWindowLimitError,
Expand All @@ -70,10 +61,16 @@ import {
isRateLimitError,
logError,
modelsService,
pendingOperation,
promiseFactoryToObservable,
ps,
recordErrorToSpan,
reformatBotMessageForChat,
resolvedConfig,
serializeChatMessage,
shareReplay,
skip,
skipPendingOperation,
startWith,
storeLastValue,
subscriptionDisposable,
Expand All @@ -82,6 +79,7 @@ import {
tracer,
truncatePromptString,
userProductSubscription,
wrapInActiveSpan,
} from '@sourcegraph/cody-shared'
import * as uuid from 'uuid'
import * as vscode from 'vscode'
Expand All @@ -99,6 +97,7 @@ import {
startAuthProgressIndicator,
} from '../../auth/auth-progress-indicator'
import type { startTokenReceiver } from '../../auth/token-receiver'
import { getCurrentUserId } from '../../auth/user'
import { executeCodyCommand } from '../../commands/CommandsController'
import { getContextFileFromUri } from '../../commands/context/file-path'
import { getContextFileFromCursor } from '../../commands/context/selection'
Expand All @@ -110,7 +109,7 @@ import { migrateAndNotifyForOutdatedModels } from '../../models/modelMigrator'
import { logDebug, outputChannelLogger } from '../../output-channel-logger'
import { getCategorizedMentions } from '../../prompt-builder/utils'
import { hydratePromptText } from '../../prompts/prompt-hydration'
import { mergedPromptsAndLegacyCommands } from '../../prompts/prompts'
import { listPromptTags, mergedPromptsAndLegacyCommands } from '../../prompts/prompts'
import { publicRepoMetadataIfAllWorkspaceReposArePublic } from '../../repository/githubRepoMetadata'
import { getFirstRepoNameContainingUri } from '../../repository/repo-name-resolver'
import { authProvider } from '../../services/AuthProvider'
Expand Down Expand Up @@ -2030,10 +2029,13 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv
),
promptsMigrationStatus: () => getPromptsMigrationInfo(),
startPromptsMigration: () => promiseFactoryToObservable(startPromptsMigration),
getCurrentUserId: () =>
promiseFactoryToObservable(signal => getCurrentUserId(signal)),
prompts: input =>
promiseFactoryToObservable(signal =>
mergedPromptsAndLegacyCommands(input, signal)
),
promptTags: () => promiseFactoryToObservable(signal => listPromptTags(signal)),
models: () =>
modelsService.modelsChanges.pipe(
map(models => (models === pendingOperation ? null : models))
Expand Down
Loading

0 comments on commit 4da049a

Please sign in to comment.