diff --git a/lib/shared/src/misc/rpc/webviewAPI.ts b/lib/shared/src/misc/rpc/webviewAPI.ts index 27f62d2d42d1..61ef9a2866cb 100644 --- a/lib/shared/src/misc/rpc/webviewAPI.ts +++ b/lib/shared/src/misc/rpc/webviewAPI.ts @@ -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 { @@ -28,6 +32,8 @@ export interface WebviewToExtensionAPI { * the Prompt Library). */ prompts(input: PromptsInput): Observable + promptTags(input: PromptTagsInput): Observable + getCurrentUserId(): Observable /** * Stream with actions from cody agent service, serves as transport for any client @@ -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'), @@ -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 @@ -170,6 +182,12 @@ export interface PromptsResult { query: string } +export type PromptTagsInput = { + first?: number +} + +export type PromptTagsResult = PromptTag[] + export type PromptsMigrationStatus = | InitialPromptsMigrationStatus | InProgressPromptsMigrationStatus diff --git a/lib/shared/src/sourcegraph-api/graphql/client.ts b/lib/shared/src/sourcegraph-api/graphql/client.ts index e189e4eef288..d60d5fce9706 100644 --- a/lib/shared/src/sourcegraph-api/graphql/client.ts +++ b/lib/shared/src/sourcegraph-api/graphql/client.ts @@ -55,6 +55,7 @@ import { NLS_SEARCH_QUERY, PACKAGE_LIST_QUERY, PROMPTS_QUERY, + PROMPT_TAGS_QUERY, PromptsOrderBy, RECORD_TELEMETRY_EVENTS_MUTATION, REPOSITORY_IDS_QUERY, @@ -526,6 +527,11 @@ export enum PromptMode { INSERT = 'INSERT', } +export interface PromptTag { + id: string + name: string +} + interface ContextFiltersResponse { site: { codyContextFilters: { @@ -844,10 +850,11 @@ export class SourcegraphGraphQLAPIClient { ).then(response => extractDataOrError(response, data => data.site?.isCodyEnabled ?? false)) } - public async getCurrentUserId(): Promise { + public async getCurrentUserId(signal?: AbortSignal): Promise { return this.fetchSourcegraphAPI>( CURRENT_USER_ID_QUERY, - {} + {}, + signal ).then(response => extractDataOrError(response, data => (data.currentUser ? data.currentUser.id : null)) ) @@ -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 { 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>( hasIncludeViewerDraftsArg ? PROMPTS_QUERY : LEGACY_PROMPTS_QUERY_5_8, @@ -1309,6 +1328,10 @@ export class SourcegraphGraphQLAPIClient { PromptsOrderBy.PROMPT_RECOMMENDED, PromptsOrderBy.PROMPT_UPDATED_AT, ], + tags: hasPromptTagsField ? tags : undefined, + owner, + includeViewerDrafts: includeViewerDrafts ?? true, + builtinOnly, }, signal ) @@ -1397,6 +1420,29 @@ export class SourcegraphGraphQLAPIClient { return } + public async queryPromptTags({ + signal, + }: { + signal?: AbortSignal + }): Promise { + 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 diff --git a/lib/shared/src/sourcegraph-api/graphql/queries.ts b/lib/shared/src/sourcegraph-api/graphql/queries.ts index d1a256c060ad..f31502f3dcc9 100644 --- a/lib/shared/src/sourcegraph-api/graphql/queries.ts +++ b/lib/shared/src/sourcegraph-api/graphql/queries.ts @@ -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 @@ -407,6 +407,12 @@ query ViewerPrompts($query: String, $first: Int!, $recommendedOnly: Boolean!, $o displayName avatarURL } + tags(first: 999) { + nodes { + id + name + } + } } totalCount } @@ -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) { diff --git a/vscode/src/auth/user.ts b/vscode/src/auth/user.ts new file mode 100644 index 000000000000..6fcb867213f4 --- /dev/null +++ b/vscode/src/auth/user.ts @@ -0,0 +1,13 @@ +import { graphqlClient, isAbortError } from '@sourcegraph/cody-shared' + +export async function getCurrentUserId(signal: AbortSignal): Promise { + try { + return await graphqlClient.getCurrentUserId(signal) + } catch (error) { + if (isAbortError(error)) { + throw error + } + + return null + } +} diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 76e76d7b0b89..c262a53413e8 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -1,50 +1,33 @@ 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, @@ -52,15 +35,23 @@ import { Typewriter, addMessageListenersForExtensionAPI, authStatus, + cenv, + clientCapabilities, createMessageAPIForExtension, currentAuthStatus, currentAuthStatusAuthed, currentResolvedConfig, + currentSiteVersion, currentUserProductSubscription, + distinctUntilChanged, + extractContextFromTraceparent, featureFlagProvider, + firstResultFromOperation, + forceHydration, getContextForChatMessage, graphqlClient, hydrateAfterPostMessage, + inputTextWithMappedContextChipsFromPromptEditorState, inputTextWithoutContextChipsFromPromptEditorState, isAbortErrorOrSocketHangUp, isContextWindowLimitError, @@ -70,10 +61,16 @@ import { isRateLimitError, logError, modelsService, + pendingOperation, promiseFactoryToObservable, + ps, recordErrorToSpan, reformatBotMessageForChat, + resolvedConfig, serializeChatMessage, + shareReplay, + skip, + skipPendingOperation, startWith, storeLastValue, subscriptionDisposable, @@ -82,6 +79,7 @@ import { tracer, truncatePromptString, userProductSubscription, + wrapInActiveSpan, } from '@sourcegraph/cody-shared' import * as uuid from 'uuid' import * as vscode from 'vscode' @@ -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' @@ -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' @@ -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)) diff --git a/vscode/src/prompts/prompts.ts b/vscode/src/prompts/prompts.ts index 879f0bb35618..87cc8544159b 100644 --- a/vscode/src/prompts/prompts.ts +++ b/vscode/src/prompts/prompts.ts @@ -3,6 +3,7 @@ import { type CommandAction, FeatureFlag, type PromptAction, + type PromptTagsResult, type PromptsInput, type PromptsResult, clientCapabilities, @@ -66,6 +67,24 @@ const STANDARD_PROMPTS_LIKE_COMMAND: CommandAction[] = [ }, ] +export async function listPromptTags(signal: AbortSignal): Promise { + try { + return await graphqlClient.queryPromptTags({ signal }) + } catch (error) { + if (isAbortError(error)) { + throw error + } + + const errorMessage = isErrorLike(error) ? error.message : String(error) + if (errorMessage.startsWith(`Cannot query field "promptTags"`)) { + // Server does not yet support prompt tags. + return [] + } + + return [] + } +} + /** * Merges results of querying the prompts from the Prompt Library, (deprecated) built-in commands, * and (deprecated) custom commands. Commands are deprecated in favor of prompts in the Prompt @@ -75,10 +94,19 @@ export async function mergedPromptsAndLegacyCommands( input: PromptsInput, signal: AbortSignal ): Promise { - const { query, recommendedOnly, first } = input + const { query, recommendedOnly, first, tags, owner, includeViewerDrafts, builtinOnly } = input const queryLower = query.toLowerCase() const [customPrompts, isUnifiedPromptsEnabled, isNewPromptsSgVersion] = await Promise.all([ - fetchCustomPrompts(queryLower, first, recommendedOnly, signal), + fetchCustomPrompts( + queryLower, + first, + recommendedOnly, + signal, + tags, + owner, + includeViewerDrafts, + builtinOnly + ), // Unified prompts flag provides prompts-like commands API featureFlagProvider.evaluateFeatureFlagEphemerally(FeatureFlag.CodyUnifiedPrompts), @@ -116,10 +144,23 @@ async function fetchCustomPrompts( query: string, first: number | undefined, recommendedOnly: boolean, - signal: AbortSignal + signal: AbortSignal, + tags?: string[], + owner?: string, + includeViewerDrafts?: boolean, + builtinOnly?: boolean ): Promise { try { - const prompts = await graphqlClient.queryPrompts({ query, first, recommendedOnly, signal }) + const prompts = await graphqlClient.queryPrompts({ + query, + first, + recommendedOnly, + signal, + tags, + owner, + includeViewerDrafts, + builtinOnly, + }) return prompts.map(prompt => ({ ...prompt, actionType: 'prompt' })) } catch (error) { if (isAbortError(error)) { diff --git a/vscode/webviews/AppWrapperForTest.tsx b/vscode/webviews/AppWrapperForTest.tsx index 819a39e98a1a..b0f6c686c736 100644 --- a/vscode/webviews/AppWrapperForTest.tsx +++ b/vscode/webviews/AppWrapperForTest.tsx @@ -84,6 +84,8 @@ export const AppWrapperForTest: FunctionComponent<{ children: ReactNode }> = ({ prompts: FIXTURE_PROMPTS, commands: FIXTURE_COMMANDS, }), + promptTags: () => Observable.of([]), + getCurrentUserId: () => Observable.of(null), highlights: () => Observable.of([]), clientActionBroadcast: () => Observable.of(), models: () => diff --git a/vscode/webviews/components/promptFilter/PromptsFilter.tsx b/vscode/webviews/components/promptFilter/PromptsFilter.tsx new file mode 100644 index 000000000000..4a83039c29d9 --- /dev/null +++ b/vscode/webviews/components/promptFilter/PromptsFilter.tsx @@ -0,0 +1,197 @@ +import { Book, BookUp2, Box, ChevronDown, ExternalLink, Plus, Tag, UserRoundPlus } from 'lucide-react' +import { type FC, useState } from 'react' +import { useConfig } from '../../utils/useConfig' +import { Button } from '../shadcn/ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '../shadcn/ui/popover' +import { useCurrentUserId } from './useCurrentUserId' +import { usePromptTagsQuery } from './usePromptTagsQuery' + +export interface PromptFilterProps { + promptFilters: PromptsFilterArgs + setPromptFilters: (promptFilters: PromptsFilterArgs) => void +} + +export interface PromptsFilterArgs { + owner?: string + tags?: string[] + promoted?: boolean + core?: boolean +} + +export const PromptsFilter: FC = props => { + const { value: resultTags, error: errorTags } = usePromptTagsQuery() + const [isPromptTagsOpen, setIsPromptTagsOpen] = useState(false) + const [selectedFilter, setSelectedFilter] = useState({ value: 'all' }) + + const { + config: { serverEndpoint }, + } = useConfig() + + const { value: userId, error } = useCurrentUserId() + + const selectPromptFilter = (param: PromptsFilterArgs, origin: FilterContentArgs) => { + setIsPromptTagsOpen(false) + setSelectedFilter(origin) + props.setPromptFilters(param) + } + + return ( + // we need the surrounding div to prevent the remaining content from jumping +
+ + setIsPromptTagsOpen(!isPromptTagsOpen)} + className="tw-ml-8 tw-mt-8" + > + + + +
+ + + +
+ selectPromptFilter({}, { value: 'all' })} + value={'all'} + /> + {typeof userId === 'string' && !error && ( + + selectPromptFilter({ owner: userId }, { value: 'you' }) + } + value={'you'} + /> + )} +
+
+ + selectPromptFilter({ promoted: true }, { value: 'promoted' }) + } + value={'promoted'} + /> + selectPromptFilter({ core: true }, { value: 'core' })} + value={'core'} + /> +
+ {!!resultTags?.length && !errorTags && ( +
+
By tag
+
    + {resultTags.map(tag => ( +
  • + + selectPromptFilter( + { tags: [tag.id] }, + { + value: 'tag', + nameOverride: tag.name, + } + ) + } + value={tag.id} + nameOverride={tag.name} + /> +
  • + ))} +
+
+ )} + + +
+
+
+
+ ) +} + +type PromptsFilterValue = 'all' | 'you' | 'promoted' | 'core' | string + +interface PromptsFilterItemProps extends FilterContentArgs { + onSelect: () => void +} + +const iconForFilter: Record = { + all: { + icon: Book, + name: 'All Prompts', + }, + you: { + icon: UserRoundPlus, + name: 'Owned by You', + }, + promoted: { + icon: BookUp2, + name: 'Promoted', + }, + core: { + icon: Box, + name: 'Core', + }, +} + +const PromptsFilterItem: FC = props => { + return ( +
+ +
+ ) +} + +type FilterContentArgs = { value: string; nameOverride?: string } + +const FilterContent: FC = props => { + const filter = iconForFilter[props.value] + const Icon = filter?.icon ?? Tag + + return ( + <> + {props.nameOverride ?? filter?.name} + + ) +} diff --git a/vscode/webviews/components/promptFilter/useCurrentUserId.tsx b/vscode/webviews/components/promptFilter/useCurrentUserId.tsx new file mode 100644 index 000000000000..f7d017ca5b46 --- /dev/null +++ b/vscode/webviews/components/promptFilter/useCurrentUserId.tsx @@ -0,0 +1,10 @@ +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 useCurrentUserId(): UseObservableResult { + const currentUserId = useExtensionAPI().getCurrentUserId + return useObservable(useMemo(() => currentUserId(), [currentUserId])) +} diff --git a/vscode/webviews/components/promptFilter/usePromptTagsQuery.tsx b/vscode/webviews/components/promptFilter/usePromptTagsQuery.tsx new file mode 100644 index 000000000000..f5723e0db377 --- /dev/null +++ b/vscode/webviews/components/promptFilter/usePromptTagsQuery.tsx @@ -0,0 +1,11 @@ +import type { PromptTagsResult } 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 usePromptTagsQuery(): UseObservableResult { + const promptTags = useExtensionAPI().promptTags + return useObservable(useMemo(() => promptTags({}), [promptTags])) +} diff --git a/vscode/webviews/components/promptList/PromptList.tsx b/vscode/webviews/components/promptList/PromptList.tsx index 3e9a797556c5..0cc4e737aec8 100644 --- a/vscode/webviews/components/promptList/PromptList.tsx +++ b/vscode/webviews/components/promptList/PromptList.tsx @@ -1,11 +1,13 @@ import clsx from 'clsx' import { type FC, useCallback, useMemo, useState } from 'react' -import type { Action } from '@sourcegraph/cody-shared' +import type { Action, PromptsInput } from '@sourcegraph/cody-shared' +import { useLocalStorage } from '../../components/hooks' import { useTelemetryRecorder } from '../../utils/telemetry' import { useConfig } from '../../utils/useConfig' import { useDebounce } from '../../utils/useDebounce' +import type { PromptsFilterArgs } from '../promptFilter/PromptsFilter' import { Command, CommandInput, @@ -14,13 +16,10 @@ import { CommandSeparator, } from '../shadcn/ui/command' import { ActionItem } from './ActionItem' +import styles from './PromptList.module.css' 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' - const BUILT_IN_PROMPTS_CODE: Record = { 'document-code': 1, 'explain-code': 2, @@ -42,6 +41,7 @@ interface PromptListProps { lastUsedSorting?: boolean recommendedOnly?: boolean onSelect: (item: Action) => void + promptFilters?: PromptsFilterArgs } /** @@ -65,6 +65,7 @@ export const PromptList: FC = props => { lastUsedSorting, recommendedOnly, onSelect: parentOnSelect, + promptFilters, } = props const endpointURL = new URL(useConfig().authStatus.endpoint) @@ -82,9 +83,12 @@ export const PromptList: FC = props => { () => ({ query: debouncedQuery, first: showFirstNItems, - recommendedOnly: recommendedOnly ?? false, + recommendedOnly: promptFilters?.promoted ?? recommendedOnly ?? false, + builtinOnly: promptFilters?.core ?? false, + tags: promptFilters?.tags, + owner: promptFilters?.owner, }), - [debouncedQuery, showFirstNItems, recommendedOnly] + [debouncedQuery, showFirstNItems, recommendedOnly, promptFilters] ) const { value: result, error } = usePromptsQuery(promptInput) @@ -151,12 +155,29 @@ export const PromptList: FC = props => { ] ) + const filteredActions = useCallback( + (actions: Action[]) => { + if (promptFilters?.core) { + return actions.filter(action => action.actionType === 'prompt' && action.builtin) + } + const shouldExcludeBuiltinCommands = + promptFilters?.promoted || promptFilters?.owner || promptFilters?.tags + if (shouldExcludeBuiltinCommands) { + return actions.filter(action => action.actionType === 'prompt' && !action.builtin) + } + return actions + }, + [promptFilters] + ) + // Don't show builtin commands to insert in the prompt editor. const allActions = showOnlyPromptInsertableCommands ? result?.actions.filter(action => action.actionType === 'prompt' || action.mode === 'ask') ?? [] : result?.actions ?? [] - const sortedActions = lastUsedSorting ? getSortedActions(allActions, lastUsedActions) : allActions + const sortedActions = lastUsedSorting + ? getSortedActions(filteredActions(allActions), lastUsedActions) + : filteredActions(allActions) const actions = showFirstNItems ? sortedActions.slice(0, showFirstNItems) : sortedActions const inputPaddingClass = diff --git a/vscode/webviews/prompts/PromptsTab.tsx b/vscode/webviews/prompts/PromptsTab.tsx index 357e03a7f452..b71b44a10026 100644 --- a/vscode/webviews/prompts/PromptsTab.tsx +++ b/vscode/webviews/prompts/PromptsTab.tsx @@ -7,6 +7,8 @@ import { getVSCodeAPI } from '../utils/VSCodeApi' import { CodyIDE } from '@sourcegraph/cody-shared' import type { PromptMode } from '@sourcegraph/cody-shared/src/sourcegraph-api/graphql/client' +import { useState } from 'react' +import { PromptsFilter, type PromptsFilterArgs } from '../components/promptFilter/PromptsFilter' import { PromptMigrationWidget } from '../components/promptsMigration/PromptsMigration' import styles from './PromptsTab.module.css' @@ -17,11 +19,14 @@ export const PromptsTab: React.FC<{ }> = ({ IDE, setView, isPromptsV2Enabled }) => { const runAction = useActionSelect() + const [promptsFilter, setPromptsFilter] = useState({}) + return (
{isPromptsV2Enabled && IDE !== CodyIDE.Web && ( )} + runAction(item, setView)} className={styles.promptsContainer} inputClassName={styles.promptsInput} + promptFilters={promptsFilter} />
) diff --git a/vscode/webviews/tabs/TabsBar.tsx b/vscode/webviews/tabs/TabsBar.tsx index 9f3ef4e5966f..bf573bb44ea3 100644 --- a/vscode/webviews/tabs/TabsBar.tsx +++ b/vscode/webviews/tabs/TabsBar.tsx @@ -5,12 +5,10 @@ import clsx from 'clsx' import { BookTextIcon, DownloadIcon, - ExternalLink, HistoryIcon, type LucideProps, MessageSquarePlusIcon, MessagesSquareIcon, - PlusCircle, Trash2Icon, } from 'lucide-react' import { getVSCodeAPI } from '../utils/VSCodeApi' @@ -348,9 +346,6 @@ TabButton.displayName = 'TabButton' */ function useTabs(input: Pick): TabConfig[] { const IDE = input.user.IDE - const { - config: { serverEndpoint }, - } = useConfig() const extensionAPI = useExtensionAPI<'userHistory'>() @@ -406,23 +401,9 @@ function useTabs(input: Pick): TabConfig[] { title: 'Prompts', Icon: BookTextIcon, changesView: true, - subActions: [ - { - title: 'Create Prompt', - Icon: PlusCircle, - command: '', - uri: `${serverEndpoint}prompts/new`, - }, - { - title: 'Prompts Library', - Icon: ExternalLink, - command: '', - uri: `${serverEndpoint}prompts`, - }, - ], }, ] as (TabConfig | null)[] ).filter(isDefined), - [IDE, extensionAPI, serverEndpoint] + [IDE, extensionAPI] ) }