Skip to content

Commit

Permalink
Add promoted prompts support
Browse files Browse the repository at this point in the history
  • Loading branch information
vovakulikov committed Oct 24, 2024
1 parent 55a5e8b commit dd73412
Show file tree
Hide file tree
Showing 13 changed files with 94 additions and 37 deletions.
8 changes: 7 additions & 1 deletion lib/shared/src/misc/rpc/webviewAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface WebviewToExtensionAPI {
* includes matching builtin commands and custom commands (which are both deprecated in favor of
* the Prompt Library).
*/
prompts(query: string): Observable<PromptsResult>
prompts(input: PromptsInput): Observable<PromptsResult>

/** The commands to prompts library migration information. */
promptsMigrationStatus(): Observable<PromptsMigrationStatus>
Expand Down Expand Up @@ -144,6 +144,12 @@ export interface CommandAction extends CodyCommand {
actionType: 'command'
}

export interface PromptsInput {
query: string
first?: number
recommendedOnly: boolean
}

export type Action = PromptAction | CommandAction

export interface PromptsResult {
Expand Down
21 changes: 19 additions & 2 deletions lib/shared/src/sourcegraph-api/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
LOG_EVENT_MUTATION_DEPRECATED,
PACKAGE_LIST_QUERY,
PROMPTS_QUERY,
PromptsOrderBy,
RECORD_TELEMETRY_EVENTS_MUTATION,
REPOSITORY_IDS_QUERY,
REPOSITORY_ID_QUERY,
Expand Down Expand Up @@ -417,6 +418,7 @@ export interface Prompt {
id: string
name: string
nameWithOwner: string
recommended: boolean
owner: {
namespaceName: string
}
Expand Down Expand Up @@ -1150,12 +1152,27 @@ export class SourcegraphGraphQLAPIClient {
return result
}

public async queryPrompts(query: string, signal?: AbortSignal): Promise<Prompt[]> {
public async queryPrompts({
query,
first,
recommendedOnly,
signal,
}: {
query: string
first: number | undefined
recommendedOnly: boolean
signal?: AbortSignal
}): Promise<Prompt[]> {
const hasIncludeViewerDraftsArg = await this.isValidSiteVersion({ minimumVersion: '5.9.0' })

const response = await this.fetchSourcegraphAPI<APIResponse<{ prompts: { nodes: Prompt[] } }>>(
hasIncludeViewerDraftsArg ? PROMPTS_QUERY : LEGACY_PROMPTS_QUERY_5_8,
{ query },
{
query,
first: first ?? 100,
recommendedOnly: recommendedOnly,
orderBy: [PromptsOrderBy.PROMPT_RECOMMENDED, PromptsOrderBy.PROMPT_UPDATED_AT],
},
signal
)
const result = extractDataOrError(response, data => data.prompts.nodes)
Expand Down
15 changes: 9 additions & 6 deletions lib/shared/src/sourcegraph-api/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,13 +349,20 @@ query ViewerPrompts($query: String!) {
}
}`

export enum PromptsOrderBy {
PROMPT_NAME_WITH_OWNER = 'PROMPT_NAME_WITH_OWNER',
PROMPT_UPDATED_AT = 'PROMPT_UPDATED_AT',
PROMPT_RECOMMENDED = 'PROMPT_RECOMMENDED',
}

export const PROMPTS_QUERY = `
query ViewerPrompts($query: String!) {
prompts(query: $query, first: 100, includeDrafts: false, includeViewerDrafts: true, viewerIsAffiliated: true, orderBy: PROMPT_UPDATED_AT) {
query ViewerPrompts($query: String!, $first: Int!, $recommendedOnly: Boolean!, $orderBy: [PromptsOrderBy!]) {
prompts(query: $query, first: $first, includeDrafts: false, recommendedOnly: $recommendedOnly, includeViewerDrafts: true, viewerIsAffiliated: true, orderByMultiple: $orderBy) {
nodes {
id
name
nameWithOwner
recommended
owner {
namespaceName
}
Expand All @@ -375,10 +382,6 @@ query ViewerPrompts($query: String!) {
}
}
totalCount
pageInfo {
hasNextPage
endCursor
}
}
}`

Expand Down
4 changes: 2 additions & 2 deletions vscode/src/chat/chat-view/ChatController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1711,9 +1711,9 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv
),
promptsMigrationStatus: () => getPromptsMigrationInfo(),
startPromptsMigration: () => promiseFactoryToObservable(startPromptsMigration),
prompts: query =>
prompts: input =>
promiseFactoryToObservable(signal =>
mergedPromptsAndLegacyCommands(query, signal)
mergedPromptsAndLegacyCommands(input, signal)
),
models: () =>
modelsService.modelsChanges.pipe(
Expand Down
6 changes: 5 additions & 1 deletion vscode/src/chat/chat-view/prompts-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ export async function startPromptsMigration(): Promise<void> {
const commandKey = command.key ?? command.slashCommand

try {
const prompts = await graphqlClient.queryPrompts(commandKey.replace(/\s+/g, '-'))
const prompts = await graphqlClient.queryPrompts({
query: commandKey.replace(/\s+/g, '-'),
first: undefined,
recommendedOnly: false,
})

// If there is no prompts associated with the command include this
// command to migration
Expand Down
10 changes: 7 additions & 3 deletions vscode/src/prompts/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
type CommandAction,
FeatureFlag,
type PromptAction,
type PromptsInput,
type PromptsResult,
clientCapabilities,
featureFlagProvider,
Expand Down Expand Up @@ -69,12 +70,13 @@ const STANDARD_PROMPTS_LIKE_COMMAND: CommandAction[] = [
* Library.
*/
export async function mergedPromptsAndLegacyCommands(
query: string,
input: PromptsInput,
signal: AbortSignal
): Promise<PromptsResult> {
const { query, recommendedOnly, first } = input
const queryLower = query.toLowerCase()
const [customPrompts, isUnifiedPromptsEnabled] = await Promise.all([
fetchCustomPrompts(queryLower, signal),
fetchCustomPrompts(queryLower, first, recommendedOnly, signal),
featureFlagProvider.evaluateFeatureFlagEphemerally(FeatureFlag.CodyUnifiedPrompts),
])

Expand Down Expand Up @@ -118,10 +120,12 @@ function matchesQuery(query: string, text: string): boolean {

async function fetchCustomPrompts(
query: string,
first: number | undefined,
recommendedOnly: boolean,
signal: AbortSignal
): Promise<PromptAction[] | 'unsupported'> {
try {
const prompts = await graphqlClient.queryPrompts(query, signal)
const prompts = await graphqlClient.queryPrompts({ query, first, recommendedOnly, signal })
return prompts.map(prompt => ({ ...prompt, actionType: 'prompt' }))
} catch (error) {
if (isAbortError(error)) {
Expand Down
1 change: 1 addition & 0 deletions vscode/webviews/chat/components/WelcomeMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const WelcomeMessage: FunctionComponent<WelcomeMessageProps> = ({
showCommandOrigins={true}
showOnlyPromptInsertableCommands={false}
showPromptLibraryUnsupportedMessage={false}
recommendedOnly={true}
onSelect={item => runAction(item, setView)}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
min-width: 0;
display: flex;
gap: 0.25rem;
align-items: center;
}

&--name {
Expand Down
21 changes: 20 additions & 1 deletion vscode/webviews/components/promptList/ActionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,23 @@ import {
type PromptAction,
} from '@sourcegraph/cody-shared'

import {
BookOpen,
BookUp2,
FileQuestion,
Hammer,
PencilLine,
PencilRuler,
TextSearch,
} from 'lucide-react'

import { UserAvatar } from '../../components/UserAvatar'
import { Badge } from '../../components/shadcn/ui/badge'
import { CommandItem } from '../../components/shadcn/ui/command'
import { Tooltip, TooltipContent, TooltipTrigger } from '../../components/shadcn/ui/tooltip'

import { commandRowValue } from './utils'

import { BookOpen, FileQuestion, Hammer, PencilLine, PencilRuler, TextSearch } from 'lucide-react'
import styles from './ActionItem.module.css'

interface ActionItemProps {
Expand Down Expand Up @@ -63,6 +74,14 @@ const ActionPrompt: FC<ActionPromptProps> = props => {
Draft
</Badge>
)}
{prompt.recommended && (
<Tooltip>
<TooltipTrigger asChild>
<BookUp2 size={12} className={styles.promptIcon} />
</TooltipTrigger>
<TooltipContent>This prompt was put here by your admin</TooltipContent>
</Tooltip>
)}
</div>

<span className={styles.promptDescription}>
Expand Down
34 changes: 17 additions & 17 deletions vscode/webviews/components/promptList/PromptList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import clsx from 'clsx'
import { type FC, useCallback, useState } from 'react'
import { type FC, useCallback, useMemo, useState } from 'react'

import type { Action } from '@sourcegraph/cody-shared'

Expand All @@ -17,6 +17,7 @@ import { ActionItem } from './ActionItem'
import { usePromptsQuery } from './usePromptsQuery'
import { commandRowValue } from './utils'

import type { PromptsInput } from '@sourcegraph/cody-shared'
import { useLocalStorage } from '../../components/hooks'
import styles from './PromptList.module.css'

Expand All @@ -33,7 +34,7 @@ interface PromptListProps {
paddingLevels?: 'none' | 'middle' | 'big'
appearanceMode?: 'flat-list' | 'chips-list'
lastUsedSorting?: boolean
includeEditCommandOnTop?: boolean
recommendedOnly?: boolean
onSelect: (item: Action) => void
}

Expand All @@ -57,7 +58,7 @@ export const PromptList: FC<PromptListProps> = props => {
paddingLevels = 'none',
appearanceMode = 'flat-list',
lastUsedSorting,
includeEditCommandOnTop,
recommendedOnly,
onSelect: parentOnSelect,
} = props

Expand All @@ -71,7 +72,17 @@ export const PromptList: FC<PromptListProps> = props => {

const [query, setQuery] = useState('')
const debouncedQuery = useDebounce(query, 250)
const { value: result, error } = usePromptsQuery(debouncedQuery)

const promptInput = useMemo<PromptsInput>(
() => ({
query: debouncedQuery,
first: showFirstNItems,
recommendedOnly: recommendedOnly ?? false,
}),
[debouncedQuery, showFirstNItems, recommendedOnly]
)

const { value: result, error } = usePromptsQuery(promptInput)

const onSelect = useCallback(
(rowValue: string): void => {
Expand Down Expand Up @@ -137,18 +148,7 @@ export const PromptList: FC<PromptListProps> = props => {
? result?.actions.filter(action => action.actionType === 'prompt' || action.mode === 'ask') ?? []
: result?.actions ?? []

const sortedActions = lastUsedSorting ? getSortedActions(allActions, lastUsedActions) : allActions

const editCommandIndex = sortedActions.findIndex(
action => action.actionType === 'command' && action.key === 'edit'
)

// Bring Edit command on top of the command list
if (includeEditCommandOnTop && editCommandIndex !== -1) {
sortedActions.unshift(sortedActions.splice(editCommandIndex, 1)[0])
}

const actions = showFirstNItems ? sortedActions.slice(0, showFirstNItems) : sortedActions
const actions = lastUsedSorting ? getSortedActions(allActions, lastUsedActions) : allActions

const inputPaddingClass =
paddingLevels !== 'none' ? (paddingLevels === 'middle' ? '!tw-p-2' : '!tw-p-4') : ''
Expand Down Expand Up @@ -184,7 +184,7 @@ export const PromptList: FC<PromptListProps> = props => {
)}
{result && allActions.filter(action => action.actionType === 'prompt').length === 0 && (
<CommandLoading className={itemPaddingClass}>
{result?.query === '' ? (
{result?.query === '' && !recommendedOnly ? (
<>
Your Prompt Library is empty.{' '}
<a
Expand Down
6 changes: 3 additions & 3 deletions vscode/webviews/components/promptList/usePromptsQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { PromptsResult } from '@sourcegraph/cody-shared'
import type { PromptsInput, PromptsResult } from '@sourcegraph/cody-shared'
import { type UseObservableResult, useExtensionAPI, useObservable } from '@sourcegraph/prompt-editor'
import { useMemo } from 'react'

/**
* React hook to query for prompts in the prompt library.
*/
export function usePromptsQuery(query: string): UseObservableResult<PromptsResult> {
export function usePromptsQuery(input: PromptsInput): UseObservableResult<PromptsResult> {
const prompts = useExtensionAPI().prompts
return useObservable(useMemo(() => prompts(query), [prompts, query]))
return useObservable(useMemo(() => prompts(input), [prompts, input]))
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const PromptSelectField: React.FunctionComponent<{
showOnlyPromptInsertableCommands={true}
showPromptLibraryUnsupportedMessage={true}
lastUsedSorting={true}
recommendedOnly={false}
inputClassName="tw-bg-popover"
/>

Expand Down
3 changes: 2 additions & 1 deletion vscode/webviews/prompts/PromptsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ export const PromptsTab: React.FC<{
showCommandOrigins={true}
paddingLevels="big"
telemetryLocation="PromptsTab"
showPromptLibraryUnsupportedMessage={true}
recommendedOnly={false}
showOnlyPromptInsertableCommands={false}
showPromptLibraryUnsupportedMessage={true}
onSelect={item => runAction(item, setView)}
className={styles.promptsContainer}
inputClassName={styles.promptsInput}
Expand Down

0 comments on commit dd73412

Please sign in to comment.