From 50d9a01e275ea656fa11160a545ee51c19c16574 Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Sun, 28 Jul 2024 08:30:29 -0600 Subject: [PATCH] extract PromptEditor to @sourcegraph/prompt-editor internal lib (#5031) This makes it possible to reuse this component on https://openctx.org and in the [Prompt Library](https://sourcegraph.com/prompts) editing UI. No behavioral change. ~85% of the diff additions are just pnpm-lock.yaml. ## Test plan CI --- .config/viteShared.ts | 2 +- lib/prompt-editor/README.md | 7 + lib/prompt-editor/package.json | 47 ++ .../prompt-editor/src}/BaseEditor.module.css | 0 .../prompt-editor/src}/BaseEditor.tsx | 6 +- .../src}/PromptEditor.module.css | 0 .../prompt-editor/src}/PromptEditor.test.tsx | 0 .../prompt-editor/src}/PromptEditor.tsx | 12 +- lib/prompt-editor/src/clientState.ts | 17 + lib/prompt-editor/src/config.ts | 81 +++ lib/prompt-editor/src/globals.d.ts | 10 + lib/prompt-editor/src/index.ts | 15 + .../prompt-editor/src}/initialContext.ts | 5 +- .../prompt-editor/src}/lexicalUtils.ts | 0 .../mentionMenu/MentionMenu.module.css | 0 .../mentions/mentionMenu/MentionMenu.test.tsx | 65 ++- .../src}/mentions/mentionMenu/MentionMenu.tsx | 43 +- .../mentionMenu/MentionMenuItem.module.css | 0 .../mentions/mentionMenu/MentionMenuItem.tsx | 36 +- .../mentionMenu/useMentionMenuData.ts | 12 +- .../nodes/ContextItemMentionNode.module.css | 0 .../src}/nodes/ContextItemMentionNode.tsx | 6 +- .../src}/nodes/MentionComponent.tsx | 34 +- .../src}/nodes/TemplateInputComponent.tsx | 20 +- .../src}/nodes/TemplateInputNode.module.css | 0 .../src}/nodes/TemplateInputNode.tsx | 0 .../prompt-editor/src}/nodes/index.ts | 0 .../prompt-editor/src}/nodes/mentionUtils.ts | 0 .../plugins/atMentions/atMentions.module.css | 0 .../src}/plugins/atMentions/atMentions.tsx | 25 +- .../atMentions/chatContextClient.test.tsx | 5 +- .../plugins/atMentions/chatContextClient.tsx | 85 ++-- .../src}/plugins/atMentions/fixtures.ts | 12 + .../src}/plugins/atMentions/util.ts | 0 .../src}/plugins/codeHighlight.tsx | 0 .../src}/plugins/disableEscapeKeyBlurs.tsx | 0 .../src}/plugins/keyboardEvent.tsx | 0 .../src}/plugins/noRichTextShortcuts.tsx | 0 .../prompt-editor/src}/plugins/onFocus.tsx | 0 .../src/providerIcons}/confluence.svg | 0 .../src/providerIcons}/github.svg | 0 .../src/providerIcons}/google.svg | 0 .../prompt-editor/src/providerIcons}/jira.svg | 0 .../src/providerIcons}/linear.svg | 0 .../src/providerIcons}/notion.svg | 0 .../src/providerIcons}/sentry.svg | 0 .../src/providerIcons}/slack.svg | 0 .../src/providerIcons}/sourcegraph.svg | 0 lib/prompt-editor/src/testSetup.ts | 17 + lib/prompt-editor/tsconfig.json | 21 + lib/prompt-editor/vitest.config.ts | 8 + lib/shared/src/codebase-context/messages.ts | 7 + lib/shared/src/context/openctx/api.ts | 4 + lib/shared/src/index.ts | 10 +- pnpm-lock.yaml | 472 +++++++++++++++++- tsconfig.json | 1 + vitest.workspace.js | 2 +- vscode/package.json | 6 +- vscode/src/chat/chat-view/ChatController.ts | 55 +- vscode/src/chat/chat-view/ChatModel.ts | 2 +- vscode/src/chat/chat-view/prompt.test.ts | 3 +- vscode/src/chat/chat-view/prompt.ts | 2 +- vscode/src/chat/context/chatContext.ts | 4 +- vscode/src/chat/context/constants.ts | 7 - vscode/src/context/openctx.ts | 12 +- vscode/src/context/openctx/linear-issues.ts | 4 +- .../src/context/openctx/remoteFileSearch.ts | 7 +- .../context/openctx/remoteRepositorySearch.ts | 9 +- vscode/src/context/openctx/types.ts | 2 +- vscode/src/context/openctx/web.ts | 7 +- vscode/src/edit/input/get-input.ts | 3 +- vscode/tsconfig.json | 4 +- vscode/webviews/App.tsx | 7 +- vscode/webviews/AppWrapper.tsx | 19 +- vscode/webviews/Chat.story.tsx | 4 +- vscode/webviews/Chat.tsx | 4 +- vscode/webviews/chat/Transcript.tsx | 2 +- .../chat/cells/contextCell/ContextCell.tsx | 2 +- .../assistant/AssistantMessageCell.tsx | 2 +- .../human/HumanMessageCell.story.tsx | 2 +- .../messageCell/human/HumanMessageCell.tsx | 2 +- .../human/editor/HumanMessageEditor.tsx | 17 +- .../human/editor/initialContext.module.css | 12 - vscode/webviews/client/clientState.tsx | 16 - .../webviews/components/shadcn/ui/command.tsx | 17 - .../webviews/components/shadcn/ui/dialog.tsx | 90 ---- vscode/webviews/mentions/providers.tsx | 69 --- vscode/webviews/minion/MinionApp.tsx | 3 +- vscode/webviews/openctxClient.tsx | 33 ++ .../promptEditor/BaseEditor.story.tsx | 2 +- .../MentionMenu.story.tsx | 5 +- .../promptEditor/PromptEditor.story.tsx | 6 +- vscode/webviews/promptEditor/config.ts | 46 ++ .../storybook/VSCodeStoryDecorator.tsx | 9 - web/lib/components/CodyWebChat.tsx | 5 +- web/package.json | 1 + 96 files changed, 1094 insertions(+), 495 deletions(-) create mode 100644 lib/prompt-editor/README.md create mode 100644 lib/prompt-editor/package.json rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/BaseEditor.module.css (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/BaseEditor.tsx (95%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/PromptEditor.module.css (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/PromptEditor.test.tsx (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/PromptEditor.tsx (96%) create mode 100644 lib/prompt-editor/src/clientState.ts create mode 100644 lib/prompt-editor/src/config.ts create mode 100644 lib/prompt-editor/src/globals.d.ts create mode 100644 lib/prompt-editor/src/index.ts rename {vscode/webviews/chat/cells/messageCell/human/editor => lib/prompt-editor/src}/initialContext.ts (88%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/lexicalUtils.ts (100%) rename {vscode/webviews => lib/prompt-editor/src}/mentions/mentionMenu/MentionMenu.module.css (100%) rename {vscode/webviews => lib/prompt-editor/src}/mentions/mentionMenu/MentionMenu.test.tsx (89%) rename {vscode/webviews => lib/prompt-editor/src}/mentions/mentionMenu/MentionMenu.tsx (94%) rename {vscode/webviews => lib/prompt-editor/src}/mentions/mentionMenu/MentionMenuItem.module.css (100%) rename {vscode/webviews => lib/prompt-editor/src}/mentions/mentionMenu/MentionMenuItem.tsx (83%) rename {vscode/webviews => lib/prompt-editor/src}/mentions/mentionMenu/useMentionMenuData.ts (89%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/nodes/ContextItemMentionNode.module.css (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/nodes/ContextItemMentionNode.tsx (96%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/nodes/MentionComponent.tsx (88%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/nodes/TemplateInputComponent.tsx (86%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/nodes/TemplateInputNode.module.css (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/nodes/TemplateInputNode.tsx (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/nodes/index.ts (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/nodes/mentionUtils.ts (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/atMentions/atMentions.module.css (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/atMentions/atMentions.tsx (94%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/atMentions/chatContextClient.test.tsx (85%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/atMentions/chatContextClient.tsx (72%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/atMentions/fixtures.ts (85%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/atMentions/util.ts (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/codeHighlight.tsx (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/disableEscapeKeyBlurs.tsx (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/keyboardEvent.tsx (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/noRichTextShortcuts.tsx (100%) rename {vscode/webviews/promptEditor => lib/prompt-editor/src}/plugins/onFocus.tsx (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/confluence.svg (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/github.svg (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/google.svg (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/jira.svg (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/linear.svg (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/notion.svg (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/sentry.svg (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/slack.svg (100%) rename {vscode/webviews/icons/providers => lib/prompt-editor/src/providerIcons}/sourcegraph.svg (100%) create mode 100644 lib/prompt-editor/src/testSetup.ts create mode 100644 lib/prompt-editor/tsconfig.json create mode 100644 lib/prompt-editor/vitest.config.ts delete mode 100644 vscode/webviews/chat/cells/messageCell/human/editor/initialContext.module.css delete mode 100644 vscode/webviews/components/shadcn/ui/dialog.tsx delete mode 100644 vscode/webviews/mentions/providers.tsx create mode 100644 vscode/webviews/openctxClient.tsx rename vscode/webviews/{mentions/mentionMenu => promptEditor}/MentionMenu.story.tsx (98%) create mode 100644 vscode/webviews/promptEditor/config.ts diff --git a/.config/viteShared.ts b/.config/viteShared.ts index 02fbb6f9ebc9..39fc5bda1321 100644 --- a/.config/viteShared.ts +++ b/.config/viteShared.ts @@ -12,7 +12,7 @@ const defaultProjectConfig: UserWorkspaceConfig = { // Build from TypeScript sources so we don't need to run `tsc -b` in the background // during dev. { - find: /^(@sourcegraph\/cody-[\w-]+)$/, + find: /^(@sourcegraph\/(?:cody-[\w-]|prompt-editor)+)$/, replacement: '$1/src/index.ts', }, ], diff --git a/lib/prompt-editor/README.md b/lib/prompt-editor/README.md new file mode 100644 index 000000000000..8b0108130947 --- /dev/null +++ b/lib/prompt-editor/README.md @@ -0,0 +1,7 @@ +# Shared prompt editor UI component + +The `@sourcegraph/prompt-editor` package contains the code for the prompt editor UI component. + +## Development notes + +- Put [UI component storybooks](https://storybook.js.org/) in `vscode/webviews/promptEditor`, not here, so that these components' storybooks can use the VS Code theme switching that we have for those storybooks. diff --git a/lib/prompt-editor/package.json b/lib/prompt-editor/package.json new file mode 100644 index 000000000000..ef9794d72061 --- /dev/null +++ b/lib/prompt-editor/package.json @@ -0,0 +1,47 @@ +{ + "name": "@sourcegraph/prompt-editor", + "version": "0.0.0-dev", + "description": "Shared prompt editor UI component", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/sourcegraph/cody", + "directory": "lib/prompt-editor" + }, + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": ["dist", "src", "!**/*.test.*"], + "sideEffects": false, + "scripts": { + "build": "tsc --build", + "test": "vitest", + "prepublishOnly": "tsc --build --clean && pnpm run build" + }, + "dependencies": { + "@floating-ui/react": "^0.26.9", + "@lexical/code": "^0.16.0", + "@lexical/react": "^0.16.0", + "@lexical/text": "^0.16.0", + "@lexical/utils": "^0.16.0", + "@sourcegraph/cody-shared": "workspace:*", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "lexical": "^0.16.0", + "lodash": "^4.17.21", + "lru-cache": "^10.0.0", + "lucide-react": "^0.378.0", + "vscode-uri": "^3.0.7" + }, + "devDependencies": { + "@types/lodash": "^4.17.7", + "@types/react": "18.2.37", + "@types/react-dom": "18.2.15", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 ^17 ^18", + "react-dom": "^16.8.0 ^17 ^18" + } +} diff --git a/vscode/webviews/promptEditor/BaseEditor.module.css b/lib/prompt-editor/src/BaseEditor.module.css similarity index 100% rename from vscode/webviews/promptEditor/BaseEditor.module.css rename to lib/prompt-editor/src/BaseEditor.module.css diff --git a/vscode/webviews/promptEditor/BaseEditor.tsx b/lib/prompt-editor/src/BaseEditor.tsx similarity index 95% rename from vscode/webviews/promptEditor/BaseEditor.tsx rename to lib/prompt-editor/src/BaseEditor.tsx index 2e7bf0fb84ce..f8dd7beba5fa 100644 --- a/vscode/webviews/promptEditor/BaseEditor.tsx +++ b/lib/prompt-editor/src/BaseEditor.tsx @@ -10,7 +10,7 @@ import type { EditorState, LexicalEditor, SerializedEditorState } from 'lexical' import { type FunctionComponent, type RefObject, useMemo } from 'react' import styles from './BaseEditor.module.css' import { RICH_EDITOR_NODES } from './nodes' -import MentionsPlugin from './plugins/atMentions/atMentions' +import { MentionsPlugin } from './plugins/atMentions/atMentions' import CodeHighlightPlugin from './plugins/codeHighlight' import { DisableEscapeKeyBlursPlugin } from './plugins/disableEscapeKeyBlurs' import { KeyboardEventPlugin, type KeyboardEventPluginProps } from './plugins/keyboardEvent' @@ -21,6 +21,7 @@ interface Props extends KeyboardEventPluginProps { initialEditorState: SerializedEditorState | null onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set) => void onFocusChange?: (focused: boolean) => void + contextWindowSizeInTokens?: number editorRef?: RefObject placeholder?: string disabled?: boolean @@ -36,6 +37,7 @@ export const BaseEditor: FunctionComponent = ({ initialEditorState, onChange, onFocusChange, + contextWindowSizeInTokens, editorRef, placeholder, disabled, @@ -86,7 +88,7 @@ export const BaseEditor: FunctionComponent = ({ // our tests using JSDOM. It doesn't hurt to enable otherwise. ignoreHistoryMergeTagChange={false} /> - + {onFocusChange && } {editorRef && } diff --git a/vscode/webviews/promptEditor/PromptEditor.module.css b/lib/prompt-editor/src/PromptEditor.module.css similarity index 100% rename from vscode/webviews/promptEditor/PromptEditor.module.css rename to lib/prompt-editor/src/PromptEditor.module.css diff --git a/vscode/webviews/promptEditor/PromptEditor.test.tsx b/lib/prompt-editor/src/PromptEditor.test.tsx similarity index 100% rename from vscode/webviews/promptEditor/PromptEditor.test.tsx rename to lib/prompt-editor/src/PromptEditor.test.tsx diff --git a/vscode/webviews/promptEditor/PromptEditor.tsx b/lib/prompt-editor/src/PromptEditor.tsx similarity index 96% rename from vscode/webviews/promptEditor/PromptEditor.tsx rename to lib/prompt-editor/src/PromptEditor.tsx index adca401f998f..a909baa2e57b 100644 --- a/vscode/webviews/promptEditor/PromptEditor.tsx +++ b/lib/prompt-editor/src/PromptEditor.tsx @@ -9,12 +9,10 @@ import { $createTextNode, $getRoot, $getSelection, $insertNodes, type LexicalEdi import type { EditorState, SerializedEditorState, SerializedLexicalNode } from 'lexical' import { isEqual } from 'lodash' import { type FunctionComponent, useCallback, useEffect, useImperativeHandle, useRef } from 'react' -import { - isEditorContentOnlyInitialContext, - lexicalNodesForContextItems, -} from '../chat/cells/messageCell/human/editor/initialContext' import { BaseEditor } from './BaseEditor' import styles from './PromptEditor.module.css' +import { useSetGlobalPromptEditorConfig } from './config' +import { isEditorContentOnlyInitialContext, lexicalNodesForContextItems } from './initialContext' import { $selectAfter, $selectEnd } from './lexicalUtils' import type { KeyboardEventPluginProps } from './plugins/keyboardEvent' @@ -29,6 +27,8 @@ interface Props extends KeyboardEventPluginProps { onChange?: (value: SerializedPromptEditorValue) => void onFocusChange?: (focused: boolean) => void + contextWindowSizeInTokens?: number + disabled?: boolean editorRef?: React.RefObject @@ -54,6 +54,7 @@ export const PromptEditor: FunctionComponent = ({ initialEditorState, onChange, onFocusChange, + contextWindowSizeInTokens, disabled, editorRef: ref, onEnterKey, @@ -161,6 +162,8 @@ export const PromptEditor: FunctionComponent = ({ [] ) + useSetGlobalPromptEditorConfig() + const onBaseEditorChange = useCallback( (_editorState: EditorState, editor: LexicalEditor, tags: Set): void => { if (onChange) { @@ -193,6 +196,7 @@ export const PromptEditor: FunctionComponent = ({ initialEditorState={initialEditorState?.lexicalEditorState ?? null} onChange={onBaseEditorChange} onFocusChange={onFocusChange} + contextWindowSizeInTokens={contextWindowSizeInTokens} editorRef={editorRef} placeholder={placeholder} disabled={disabled} diff --git a/lib/prompt-editor/src/clientState.ts b/lib/prompt-editor/src/clientState.ts new file mode 100644 index 000000000000..3daa6b8b2bc0 --- /dev/null +++ b/lib/prompt-editor/src/clientState.ts @@ -0,0 +1,17 @@ +import type { ClientStateForWebview } from '@sourcegraph/cody-shared' +import { createContext, useContext } from 'react' + +const ClientStateContext = createContext(null) + +export const ClientStateContextProvider = ClientStateContext.Provider + +/** + * Get the {@link ClientState} stored in React context. + */ +export function useClientState(): ClientStateForWebview { + const clientState = useContext(ClientStateContext) + if (!clientState) { + throw new Error('no clientState') + } + return clientState +} diff --git a/lib/prompt-editor/src/config.ts b/lib/prompt-editor/src/config.ts new file mode 100644 index 000000000000..de1b48ee6cef --- /dev/null +++ b/lib/prompt-editor/src/config.ts @@ -0,0 +1,81 @@ +import type { SerializedContextItem } from '@sourcegraph/cody-shared' +import type { Command } from 'cmdk' +import { type ComponentProps, type ComponentType, createContext, useContext } from 'react' + +/** + * The configuration for the prompt editor and related components. + */ +export interface PromptEditorConfig { + onContextItemMentionNodeMetaClick?: (contextItem: SerializedContextItem) => void + + /** + * The [shadcn Tooltip components](https://ui.shadcn.com/docs/components/tooltip). + */ + tooltipComponents?: { + Tooltip: React.ComponentType<{ children: React.ReactNode }> + TooltipContent: React.ComponentType<{ children: React.ReactNode }> + TooltipTrigger: React.ComponentType<{ asChild?: boolean; children: React.ReactNode }> + } + + /** + * The [shadcn Command components](https://ui.shadcn.com/docs/components/command). + */ + commandComponents: { + Command: ComponentType> + CommandInput: typeof Command.Input + CommandList: typeof Command.List + CommandEmpty: typeof Command.Empty + CommandLoading: typeof Command.Loading + CommandGroup: typeof Command.Group + CommandSeparator: typeof Command.Separator + CommandItem: typeof Command.Item + } +} + +const PromptEditorConfigContext = createContext(undefined) + +/** + * React hook for setting the configuration for the prompt editor and related components. + */ +export const PromptEditorConfigProvider = PromptEditorConfigContext.Provider + +export function usePromptEditorConfig(): PromptEditorConfig { + const config = useContext(PromptEditorConfigContext) + if (!config) { + throw new Error('usePromptEditorConfig must be called within a PromptEditorConfigProvider') + } + return config +} + +/** + * This hook must be called somewhere in the render tree. It is to apply config that can't be passed + * via React context. Lexical nodes are rendered in disconnected React DOM trees, so the context + * won't pass down. + */ +export function useSetGlobalPromptEditorConfig(): void { + const config = useContext(PromptEditorConfigContext) + if (!config) { + throw new Error('useApplyPromptEditorConfig must be called within a PromptEditorConfigProvider') + } + setGlobalPromptEditorConfig(config) +} + +/** The subset of the config that must be accessed globally (i.e. not passed via React context). */ +type GlobalConfig = Pick + +let globalConfig: GlobalConfig | undefined + +function setGlobalPromptEditorConfig(config: GlobalConfig): void { + globalConfig = config +} + +/** + * Return the global prompt editor config. Use {@link usePromptEditorConfig} from React. Only use + * this if you need to access it outside of a React render tree. + */ +export function getGlobalPromptEditorConfig(): GlobalConfig { + if (!globalConfig) { + throw new Error('getGlobalPromptEditorConfig must be called after setGlobalPromptEditorConfig') + } + return globalConfig +} diff --git a/lib/prompt-editor/src/globals.d.ts b/lib/prompt-editor/src/globals.d.ts new file mode 100644 index 000000000000..ce0ca5850341 --- /dev/null +++ b/lib/prompt-editor/src/globals.d.ts @@ -0,0 +1,10 @@ +declare module '*.module.css' { + const classes: { readonly [key: string]: string } + export default classes +} + +declare module '*.svg?react' { + // The path to the resource + const component: React.ComponentType> + export default component +} diff --git a/lib/prompt-editor/src/index.ts b/lib/prompt-editor/src/index.ts new file mode 100644 index 000000000000..63f9c461d175 --- /dev/null +++ b/lib/prompt-editor/src/index.ts @@ -0,0 +1,15 @@ +export { PromptEditor, type PromptEditorRefAPI } from './PromptEditor' +export { ContextItemMentionNode, MENTION_CLASS_NAME } from './nodes/ContextItemMentionNode' +export { MentionMenu } from './mentions/mentionMenu/MentionMenu' +export { BaseEditor } from './BaseEditor' +export { type MentionMenuData, type MentionMenuParams } from './mentions/mentionMenu/useMentionMenuData' +export { + ChatContextClientProvider, + type ChatContextClient, + useChatContextMentionProviders, + ChatMentionContext, + type ChatMentionsSettings, +} from './plugins/atMentions/chatContextClient' +export { dummyChatContextClient } from './plugins/atMentions/fixtures' +export { type PromptEditorConfig, PromptEditorConfigProvider } from './config' +export { useClientState, ClientStateContextProvider } from './clientState' diff --git a/vscode/webviews/chat/cells/messageCell/human/editor/initialContext.ts b/lib/prompt-editor/src/initialContext.ts similarity index 88% rename from vscode/webviews/chat/cells/messageCell/human/editor/initialContext.ts rename to lib/prompt-editor/src/initialContext.ts index d270cd2ddeb0..c3251c7ae044 100644 --- a/vscode/webviews/chat/cells/messageCell/human/editor/initialContext.ts +++ b/lib/prompt-editor/src/initialContext.ts @@ -1,9 +1,6 @@ import type { ContextItem } from '@sourcegraph/cody-shared' import { $createTextNode, $getRoot, type LexicalEditor, TextNode } from 'lexical' -import { - $createContextItemMentionNode, - ContextItemMentionNode, -} from '../../../../../promptEditor/nodes/ContextItemMentionNode' +import { $createContextItemMentionNode, ContextItemMentionNode } from './nodes/ContextItemMentionNode' export function lexicalNodesForContextItems( items: ContextItem[], diff --git a/vscode/webviews/promptEditor/lexicalUtils.ts b/lib/prompt-editor/src/lexicalUtils.ts similarity index 100% rename from vscode/webviews/promptEditor/lexicalUtils.ts rename to lib/prompt-editor/src/lexicalUtils.ts diff --git a/vscode/webviews/mentions/mentionMenu/MentionMenu.module.css b/lib/prompt-editor/src/mentions/mentionMenu/MentionMenu.module.css similarity index 100% rename from vscode/webviews/mentions/mentionMenu/MentionMenu.module.css rename to lib/prompt-editor/src/mentions/mentionMenu/MentionMenu.module.css diff --git a/vscode/webviews/mentions/mentionMenu/MentionMenu.test.tsx b/lib/prompt-editor/src/mentions/mentionMenu/MentionMenu.test.tsx similarity index 89% rename from vscode/webviews/mentions/mentionMenu/MentionMenu.test.tsx rename to lib/prompt-editor/src/mentions/mentionMenu/MentionMenu.test.tsx index c6a1d66c093c..cc6784461e92 100644 --- a/vscode/webviews/mentions/mentionMenu/MentionMenu.test.tsx +++ b/lib/prompt-editor/src/mentions/mentionMenu/MentionMenu.test.tsx @@ -4,9 +4,20 @@ import { displayPathBasename, } from '@sourcegraph/cody-shared' import { fireEvent, render, screen } from '@testing-library/react' -import type { ComponentProps } from 'react' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandLoading, + CommandSeparator, +} from 'cmdk' +import type { ComponentProps, FunctionComponent } from 'react' import { type Mock, describe, expect, test, vi } from 'vitest' import { URI } from 'vscode-uri' +import { type PromptEditorConfig, PromptEditorConfigProvider } from '../../config' import { MentionMenu } from './MentionMenu' import type { MentionMenuData } from './useMentionMenuData' @@ -51,26 +62,45 @@ const PROPS: Pick< selectOptionAndCleanUp: () => {}, } +const CONFIG: PromptEditorConfig = { + commandComponents: { + Command, + CommandInput, + CommandList, + CommandEmpty, + CommandLoading, + CommandGroup, + CommandSeparator, + CommandItem, + }, +} +const Wrapper: FunctionComponent<{ children: React.ReactNode }> = ({ children }) => ( + {children} +) + describe('MentionMenu', () => { describe('initial states', () => { describe('all providers', () => { test('loading items', () => { const { container } = render( - + , + { wrapper: Wrapper } ) expectMenu(container, ['>provider p1', '#Loading...']) }) test('empty items', () => { const { container } = render( - + , + { wrapper: Wrapper } ) expectMenu(container, ['>provider p1']) }) test('empty providers', () => { const { container } = render( - + , + { wrapper: Wrapper } ) expectMenu(container, ['>item file file1.go']) }) @@ -84,7 +114,8 @@ describe('MentionMenu', () => { items: [], providers: [], }} - /> + />, + { wrapper: Wrapper } ) expectMenu(container, ['#No files found']) }) @@ -98,7 +129,8 @@ describe('MentionMenu', () => { items: [ITEM_FILE1, ITEM_FILE2], providers: [PROVIDER_P1], }} - /> + />, + { wrapper: Wrapper } ) expectMenu(container, ['>provider p1', 'item file file1.go', 'item file file2.ts']) }) @@ -116,7 +148,8 @@ describe('MentionMenu', () => { items: [], providers: [], }} - /> + />, + { wrapper: Wrapper } ) expectMenu(container, ['#p1 title', '#p1 emptyLabel']) }) @@ -133,7 +166,8 @@ describe('MentionMenu', () => { items: [], providers: [], }} - /> + />, + { wrapper: Wrapper } ) expectMenu(container, ['#p1 title', '#p1 queryLabel']) }) @@ -150,7 +184,8 @@ describe('MentionMenu', () => { items: [ITEM_FILE1], providers: [], }} - /> + />, + { wrapper: Wrapper } ) expectMenu(container, ['#p1 title', 'item file file1.go']) }) @@ -167,7 +202,8 @@ describe('MentionMenu', () => { items: [ITEM_FILE1], providers: [], }} - /> + />, + { wrapper: Wrapper } ) expectMenu(container, ['#p1 title', 'item file file1.go']) }) @@ -201,7 +237,8 @@ describe('MentionMenu', () => { updateMentionMenuParams={updateMentionMenuParams} setEditorQuery={setEditorQuery} selectOptionAndCleanUp={selectOptionAndCleanUp} - /> + />, + { wrapper: Wrapper } ) return { updateMentionMenuParams, @@ -256,7 +293,8 @@ describe('MentionMenu', () => { + />, + { wrapper: Wrapper } ) expectMenu(container, ['>provider p1', 'item file file1.go', 'item file file2.ts']) fireEvent.keyDown(container, { key: 'ArrowDown' }) @@ -272,7 +310,8 @@ describe('MentionMenu', () => { + />, + { wrapper: Wrapper } ) fireEvent.keyDown(container, { key: 'ArrowDown' }) fireEvent.keyDown(container, { key: 'ArrowDown' }) diff --git a/vscode/webviews/mentions/mentionMenu/MentionMenu.tsx b/lib/prompt-editor/src/mentions/mentionMenu/MentionMenu.tsx similarity index 94% rename from vscode/webviews/mentions/mentionMenu/MentionMenu.tsx rename to lib/prompt-editor/src/mentions/mentionMenu/MentionMenu.tsx index 3e9b5300afd9..d0e8e66f416f 100644 --- a/vscode/webviews/mentions/mentionMenu/MentionMenu.tsx +++ b/lib/prompt-editor/src/mentions/mentionMenu/MentionMenu.tsx @@ -4,32 +4,19 @@ import { type ContextItemOpenCtx, type ContextMentionProviderMetadata, FILE_CONTEXT_MENTION_PROVIDER, + FILE_RANGE_TOOLTIP_LABEL, type MentionQuery, + NO_SYMBOL_MATCHES_HELP_LABEL, + REMOTE_FILE_PROVIDER_URI, SYMBOL_CONTEXT_MENTION_PROVIDER, parseMentionQuery, } from '@sourcegraph/cody-shared' import { clsx } from 'clsx' import { type FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { - FILE_RANGE_TOOLTIP_LABEL, - NO_SYMBOL_MATCHES_HELP_LABEL, -} from '../../../src/chat/context/constants' -import RemoteFileProvider from '../../../src/context/openctx/remoteFileSearch' -import { - Command, - CommandEmpty, - CommandGroup, - CommandItem, - CommandList, - CommandLoading, - CommandSeparator, -} from '../../components/shadcn/ui/command' -import { - type MentionMenuOption, - createMentionMenuOption, -} from '../../promptEditor/plugins/atMentions/atMentions' -import type { setEditorQuery } from '../../promptEditor/plugins/atMentions/atMentions' -import { contextItemID } from '../../promptEditor/plugins/atMentions/util' +import { usePromptEditorConfig } from '../../config' +import { type MentionMenuOption, createMentionMenuOption } from '../../plugins/atMentions/atMentions' +import type { setEditorQuery } from '../../plugins/atMentions/atMentions' +import { contextItemID } from '../../plugins/atMentions/util' import styles from './MentionMenu.module.css' import { MentionMenuContextItemContent, MentionMenuProviderItemContent } from './MentionMenuItem' import type { MentionMenuData, MentionMenuParams } from './useMentionMenuData' @@ -160,7 +147,7 @@ export const MentionMenu: FunctionComponent< if (item.provider === 'openctx') { const openCtxItem = item as ContextItemOpenCtx if ( - openCtxItem.providerUri === RemoteFileProvider.providerUri && + openCtxItem.providerUri === REMOTE_FILE_PROVIDER_URI && openCtxItem.mention?.data?.repoName && !openCtxItem.mention?.data?.filePath ) { @@ -170,7 +157,7 @@ export const MentionMenu: FunctionComponent< updateMentionMenuParams({ parentItem: { - id: RemoteFileProvider.providerUri, + id: REMOTE_FILE_PROVIDER_URI, title: 'Remote Files', queryLabel: 'Enter file path to search', emptyLabel: `No matching files found in ${openCtxItem?.mention?.data.repoName} repository`, @@ -222,6 +209,18 @@ export const MentionMenu: FunctionComponent< const heading = getItemsHeading(params.parentItem, mentionQuery) + const { + commandComponents: { + Command, + CommandEmpty, + CommandGroup, + CommandItem, + CommandList, + CommandLoading, + CommandSeparator, + }, + } = usePromptEditorConfig() + const providers = data.providers.map(provider => ( // show remote repositories search provider only if the user is connected to a non-dotcom instance. = ({ nodeKey, node, tooltip, icon: Icon, className, focusedClassName, iconClassName }) => { + const { tooltipComponents, onContextItemMentionNodeMetaClick } = getGlobalPromptEditorConfig() + const [editor] = useLexicalComposerContext() const isEditorFocused = useIsFocused() const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey) @@ -144,19 +144,15 @@ export const MentionComponent: FunctionComponent<{ setSelected(true) // metaKey is true when you press cmd on Mac while clicking. - if (event.metaKey && node.contextItem.uri) { - const uri = URI.parse(node.contextItem.uri) - getVSCodeAPI().postMessage({ - command: 'openURI', - uri, - }) + if (event.metaKey) { + onContextItemMentionNodeMetaClick?.(node.contextItem) } return true } return false }, - [clearSelection, setSelected, node.contextItem.uri] + [clearSelection, setSelected, onContextItemMentionNodeMetaClick, node.contextItem] ) const onBlur = useCallback(() => { @@ -198,14 +194,20 @@ export const MentionComponent: FunctionComponent<{ } }, [editor, onArrowLeftPress, onArrowRightPress, onClick, onDelete, onBlur, onSelectionChange]) + const content = ( + + {Icon && } + {text} + + ) + + if (!tooltipComponents) { + return content + } + const { Tooltip, TooltipContent, TooltipTrigger } = tooltipComponents return ( - - - {Icon && } - {text} - - + {content} {tooltip && {tooltip}} ) diff --git a/vscode/webviews/promptEditor/nodes/TemplateInputComponent.tsx b/lib/prompt-editor/src/nodes/TemplateInputComponent.tsx similarity index 86% rename from vscode/webviews/promptEditor/nodes/TemplateInputComponent.tsx rename to lib/prompt-editor/src/nodes/TemplateInputComponent.tsx index 000042f6fead..ff0c87941951 100644 --- a/vscode/webviews/promptEditor/nodes/TemplateInputComponent.tsx +++ b/lib/prompt-editor/src/nodes/TemplateInputComponent.tsx @@ -9,7 +9,7 @@ import { type NodeKey, } from 'lexical' import { useCallback, useEffect, useMemo, useRef } from 'react' -import { Tooltip, TooltipContent, TooltipTrigger } from '../../components/shadcn/ui/tooltip' +import { getGlobalPromptEditorConfig } from '../config' import { $isTemplateInputNode, type TemplateInputNode } from './TemplateInputNode' import { useIsFocused } from './mentionUtils' @@ -20,6 +20,8 @@ export const TemplateInputComponent: React.FC<{ className: string focusedClassName: string }> = ({ editor, nodeKey, node, className, focusedClassName }) => { + const { tooltipComponents } = getGlobalPromptEditorConfig() + const isEditorFocused = useIsFocused() const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey) const ref = useRef(null) @@ -82,13 +84,19 @@ export const TemplateInputComponent: React.FC<{ const tooltip = 'replaces template placeholder on keypress' const text = node.templateInput.placeholder + const content = ( + + {text} + + ) + + if (!tooltipComponents) { + return content + } + const { Tooltip, TooltipContent, TooltipTrigger } = tooltipComponents return ( - - - {text} - - + {content} {tooltip && {tooltip}} ) diff --git a/vscode/webviews/promptEditor/nodes/TemplateInputNode.module.css b/lib/prompt-editor/src/nodes/TemplateInputNode.module.css similarity index 100% rename from vscode/webviews/promptEditor/nodes/TemplateInputNode.module.css rename to lib/prompt-editor/src/nodes/TemplateInputNode.module.css diff --git a/vscode/webviews/promptEditor/nodes/TemplateInputNode.tsx b/lib/prompt-editor/src/nodes/TemplateInputNode.tsx similarity index 100% rename from vscode/webviews/promptEditor/nodes/TemplateInputNode.tsx rename to lib/prompt-editor/src/nodes/TemplateInputNode.tsx diff --git a/vscode/webviews/promptEditor/nodes/index.ts b/lib/prompt-editor/src/nodes/index.ts similarity index 100% rename from vscode/webviews/promptEditor/nodes/index.ts rename to lib/prompt-editor/src/nodes/index.ts diff --git a/vscode/webviews/promptEditor/nodes/mentionUtils.ts b/lib/prompt-editor/src/nodes/mentionUtils.ts similarity index 100% rename from vscode/webviews/promptEditor/nodes/mentionUtils.ts rename to lib/prompt-editor/src/nodes/mentionUtils.ts diff --git a/vscode/webviews/promptEditor/plugins/atMentions/atMentions.module.css b/lib/prompt-editor/src/plugins/atMentions/atMentions.module.css similarity index 100% rename from vscode/webviews/promptEditor/plugins/atMentions/atMentions.module.css rename to lib/prompt-editor/src/plugins/atMentions/atMentions.module.css diff --git a/vscode/webviews/promptEditor/plugins/atMentions/atMentions.tsx b/lib/prompt-editor/src/plugins/atMentions/atMentions.tsx similarity index 94% rename from vscode/webviews/promptEditor/plugins/atMentions/atMentions.tsx rename to lib/prompt-editor/src/plugins/atMentions/atMentions.tsx index 98fc7be0b682..80f1b6d4b9d0 100644 --- a/vscode/webviews/promptEditor/plugins/atMentions/atMentions.tsx +++ b/lib/prompt-editor/src/plugins/atMentions/atMentions.tsx @@ -19,22 +19,17 @@ import { KEY_ESCAPE_COMMAND, type TextNode, } from 'lexical' -import { useCallback, useEffect, useRef, useState } from 'react' +import { type FunctionComponent, useCallback, useEffect, useRef, useState } from 'react' import styles from './atMentions.module.css' import { type ContextItem, - FAST_CHAT_INPUT_TOKEN_BUDGET, scanForMentionTriggerInUserTextInput, toSerializedPromptEditorValue, } from '@sourcegraph/cody-shared' import { clsx } from 'clsx' -import { useCurrentChatModel } from '../../../chat/models/chatModelContext' -import { MentionMenu } from '../../../mentions/mentionMenu/MentionMenu' -import { - useMentionMenuData, - useMentionMenuParams, -} from '../../../mentions/mentionMenu/useMentionMenuData' +import { MentionMenu } from '../../mentions/mentionMenu/MentionMenu' +import { useMentionMenuData, useMentionMenuParams } from '../../mentions/mentionMenu/useMentionMenuData' import { $createContextItemMentionNode, $createContextItemTextNode, @@ -80,7 +75,9 @@ function scanForMentionTriggerInLexicalInput(text: string) { export type setEditorQuery = (getNewQuery: (currentText: string) => [string, number?]) => void -export default function MentionsPlugin(): JSX.Element | null { +export const MentionsPlugin: FunctionComponent<{ contextWindowSizeInTokens?: number }> = ({ + contextWindowSizeInTokens, +}) => { const [editor] = useLexicalComposerContext() /** @@ -90,12 +87,10 @@ export default function MentionsPlugin(): JSX.Element | null { const { x, y, refs, strategy } = useFloating(FLOATING_OPTIONS) - const model = useCurrentChatModel() - const limit = - model?.contextWindow?.context?.user || - model?.contextWindow?.input || - FAST_CHAT_INPUT_TOKEN_BUDGET - const remainingTokenBudget = limit - tokenAdded + const remainingTokenBudget = + contextWindowSizeInTokens === undefined + ? Number.MAX_SAFE_INTEGER + : contextWindowSizeInTokens - tokenAdded const { params, updateQuery, updateMentionMenuParams } = useMentionMenuParams() diff --git a/vscode/webviews/promptEditor/plugins/atMentions/chatContextClient.test.tsx b/lib/prompt-editor/src/plugins/atMentions/chatContextClient.test.tsx similarity index 85% rename from vscode/webviews/promptEditor/plugins/atMentions/chatContextClient.test.tsx rename to lib/prompt-editor/src/plugins/atMentions/chatContextClient.test.tsx index b031f8762206..f92dfc0eb15b 100644 --- a/vscode/webviews/promptEditor/plugins/atMentions/chatContextClient.test.tsx +++ b/lib/prompt-editor/src/plugins/atMentions/chatContextClient.test.tsx @@ -5,7 +5,7 @@ import { describe, expect, test } from 'vitest' import { URI } from 'vscode-uri' import { type ChatContextClient, - ChatContextClientProviderForTestsOnly, + ChatContextClientProvider, useChatContextItems, } from './chatContextClient' @@ -19,11 +19,12 @@ describe('useChatContextItems', () => { }) const client: ChatContextClient = { getChatContextItems: async () => ({ userContextFiles: await itemsPromise }), + getMentionProvidersMetadata: async () => ({ providers: [] }), } const { result } = renderHook(() => useChatContextItems('q', null), { wrapper: ({ children }) => - React.createElement(ChatContextClientProviderForTestsOnly, { value: client }, children), + React.createElement(ChatContextClientProvider, { value: client }, children), }) expect(result.current).toBe(undefined) diff --git a/vscode/webviews/promptEditor/plugins/atMentions/chatContextClient.tsx b/lib/prompt-editor/src/plugins/atMentions/chatContextClient.tsx similarity index 72% rename from vscode/webviews/promptEditor/plugins/atMentions/chatContextClient.tsx rename to lib/prompt-editor/src/plugins/atMentions/chatContextClient.tsx index 4b55a726907b..e21455ab47b6 100644 --- a/vscode/webviews/promptEditor/plugins/atMentions/chatContextClient.tsx +++ b/lib/prompt-editor/src/plugins/atMentions/chatContextClient.tsx @@ -3,22 +3,10 @@ import { type ContextMentionProviderMetadata, FILE_CONTEXT_MENTION_PROVIDER, type MentionQuery, - createExtensionAPIProxyInWebview, parseMentionQuery, } from '@sourcegraph/cody-shared' import { LRUCache } from 'lru-cache' -import { - type FunctionComponent, - type ReactNode, - createContext, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react' -import type { ExtensionMessage } from '../../../../src/chat/protocol' -import type { VSCodeWrapper } from '../../../utils/VSCodeApi' +import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' export interface ChatMentionsSettings { resolutionMode: 'remote' | 'local' @@ -32,38 +20,14 @@ export interface ChatContextClient { getChatContextItems(params: { query: MentionQuery }): Promise<{ userContextFiles?: ContextItem[] | null | undefined }> + getMentionProvidersMetadata( + params: Record + ): Promise<{ providers: ContextMentionProviderMetadata[] }> } const ChatContextClientContext = createContext(undefined) -export const ChatContextClientProviderFromVSCodeAPI: FunctionComponent<{ - vscodeAPI: VSCodeWrapper | null - children: ReactNode -}> = ({ vscodeAPI, children }) => { - const value = useMemo( - () => - vscodeAPI - ? { - getChatContextItems: createExtensionAPIProxyInWebview( - vscodeAPI, - 'queryContextItems', - 'userContextFiles' - ), - } - : null, - [vscodeAPI] - ) - return value ? ( - {children} - ) : ( - <>{children} - ) -} - -/** - * @internal Used in tests only. - */ -export const ChatContextClientProviderForTestsOnly = ChatContextClientContext.Provider +export const ChatContextClientProvider = ChatContextClientContext.Provider /** Hook to get the chat context items for the given query. */ export function useChatContextItems( @@ -73,9 +37,7 @@ export function useChatContextItems( const mentionSettings = useContext(ChatMentionContext) const unmemoizedClient = useContext(ChatContextClientContext) if (!unmemoizedClient) { - throw new Error( - 'useChatContextItems must be used within a ChatContextClientProvider or ChatContextClientProviderFromVSCodeAPI' - ) + throw new Error('useChatContextItems must be used within a ChatContextClientProvider') } const chatContextClient = useMemo( @@ -145,10 +107,14 @@ export function useChatContextItems( return results } -function memoizeChatContextClient(client: ChatContextClient): ChatContextClient { +function memoizeChatContextClient( + client: ChatContextClient +): Pick { const cache = new LRUCache< string, - Omit, 'type'> + { + userContextFiles?: ContextItem[] | null | undefined + } >({ max: 10 }) return { async getChatContextItems(params) { @@ -164,3 +130,30 @@ function memoizeChatContextClient(client: ChatContextClient): ChatContextClient }, } } + +const EMPTY_PROVIDERS: ContextMentionProviderMetadata[] = [] + +export function useChatContextMentionProviders(): { + providers: ContextMentionProviderMetadata[] + reload: () => void +} { + const client = useContext(ChatContextClientContext) + const [providers, setProviders] = useState() + + const load = useCallback(() => { + if (client) { + client + .getMentionProvidersMetadata({}) + .then(result => setProviders(result.providers)) + .catch(error => { + console.error(error) + setProviders(EMPTY_PROVIDERS) + }) + } + }, [client]) + useEffect(() => { + load() + }, [load]) + + return useMemo(() => ({ providers: providers ?? EMPTY_PROVIDERS, reload: load }), [providers, load]) +} diff --git a/vscode/webviews/promptEditor/plugins/atMentions/fixtures.ts b/lib/prompt-editor/src/plugins/atMentions/fixtures.ts similarity index 85% rename from vscode/webviews/promptEditor/plugins/atMentions/fixtures.ts rename to lib/prompt-editor/src/plugins/atMentions/fixtures.ts index e069bb0e3e11..1c6fe5cd2903 100644 --- a/vscode/webviews/promptEditor/plugins/atMentions/fixtures.ts +++ b/lib/prompt-editor/src/plugins/atMentions/fixtures.ts @@ -34,6 +34,18 @@ export const dummyChatContextClient: ChatContextClient = { ].filter(f => f.uri.path.includes(queryTextLower)) return { userContextFiles: results } }, + async getMentionProvidersMetadata() { + return { + providers: [ + { + title: 'My Context Source', + id: 'my-context-source', + queryLabel: 'Type a query for My Context Source', + emptyLabel: 'No results found from My Context Source', + }, + ], + } + }, } const DUMMY_FILES: ContextItem[] = [ diff --git a/vscode/webviews/promptEditor/plugins/atMentions/util.ts b/lib/prompt-editor/src/plugins/atMentions/util.ts similarity index 100% rename from vscode/webviews/promptEditor/plugins/atMentions/util.ts rename to lib/prompt-editor/src/plugins/atMentions/util.ts diff --git a/vscode/webviews/promptEditor/plugins/codeHighlight.tsx b/lib/prompt-editor/src/plugins/codeHighlight.tsx similarity index 100% rename from vscode/webviews/promptEditor/plugins/codeHighlight.tsx rename to lib/prompt-editor/src/plugins/codeHighlight.tsx diff --git a/vscode/webviews/promptEditor/plugins/disableEscapeKeyBlurs.tsx b/lib/prompt-editor/src/plugins/disableEscapeKeyBlurs.tsx similarity index 100% rename from vscode/webviews/promptEditor/plugins/disableEscapeKeyBlurs.tsx rename to lib/prompt-editor/src/plugins/disableEscapeKeyBlurs.tsx diff --git a/vscode/webviews/promptEditor/plugins/keyboardEvent.tsx b/lib/prompt-editor/src/plugins/keyboardEvent.tsx similarity index 100% rename from vscode/webviews/promptEditor/plugins/keyboardEvent.tsx rename to lib/prompt-editor/src/plugins/keyboardEvent.tsx diff --git a/vscode/webviews/promptEditor/plugins/noRichTextShortcuts.tsx b/lib/prompt-editor/src/plugins/noRichTextShortcuts.tsx similarity index 100% rename from vscode/webviews/promptEditor/plugins/noRichTextShortcuts.tsx rename to lib/prompt-editor/src/plugins/noRichTextShortcuts.tsx diff --git a/vscode/webviews/promptEditor/plugins/onFocus.tsx b/lib/prompt-editor/src/plugins/onFocus.tsx similarity index 100% rename from vscode/webviews/promptEditor/plugins/onFocus.tsx rename to lib/prompt-editor/src/plugins/onFocus.tsx diff --git a/vscode/webviews/icons/providers/confluence.svg b/lib/prompt-editor/src/providerIcons/confluence.svg similarity index 100% rename from vscode/webviews/icons/providers/confluence.svg rename to lib/prompt-editor/src/providerIcons/confluence.svg diff --git a/vscode/webviews/icons/providers/github.svg b/lib/prompt-editor/src/providerIcons/github.svg similarity index 100% rename from vscode/webviews/icons/providers/github.svg rename to lib/prompt-editor/src/providerIcons/github.svg diff --git a/vscode/webviews/icons/providers/google.svg b/lib/prompt-editor/src/providerIcons/google.svg similarity index 100% rename from vscode/webviews/icons/providers/google.svg rename to lib/prompt-editor/src/providerIcons/google.svg diff --git a/vscode/webviews/icons/providers/jira.svg b/lib/prompt-editor/src/providerIcons/jira.svg similarity index 100% rename from vscode/webviews/icons/providers/jira.svg rename to lib/prompt-editor/src/providerIcons/jira.svg diff --git a/vscode/webviews/icons/providers/linear.svg b/lib/prompt-editor/src/providerIcons/linear.svg similarity index 100% rename from vscode/webviews/icons/providers/linear.svg rename to lib/prompt-editor/src/providerIcons/linear.svg diff --git a/vscode/webviews/icons/providers/notion.svg b/lib/prompt-editor/src/providerIcons/notion.svg similarity index 100% rename from vscode/webviews/icons/providers/notion.svg rename to lib/prompt-editor/src/providerIcons/notion.svg diff --git a/vscode/webviews/icons/providers/sentry.svg b/lib/prompt-editor/src/providerIcons/sentry.svg similarity index 100% rename from vscode/webviews/icons/providers/sentry.svg rename to lib/prompt-editor/src/providerIcons/sentry.svg diff --git a/vscode/webviews/icons/providers/slack.svg b/lib/prompt-editor/src/providerIcons/slack.svg similarity index 100% rename from vscode/webviews/icons/providers/slack.svg rename to lib/prompt-editor/src/providerIcons/slack.svg diff --git a/vscode/webviews/icons/providers/sourcegraph.svg b/lib/prompt-editor/src/providerIcons/sourcegraph.svg similarity index 100% rename from vscode/webviews/icons/providers/sourcegraph.svg rename to lib/prompt-editor/src/providerIcons/sourcegraph.svg diff --git a/lib/prompt-editor/src/testSetup.ts b/lib/prompt-editor/src/testSetup.ts new file mode 100644 index 000000000000..81d09e508518 --- /dev/null +++ b/lib/prompt-editor/src/testSetup.ts @@ -0,0 +1,17 @@ +import '@testing-library/jest-dom/vitest' +import { isWindows, setDisplayPathEnvInfo } from '@sourcegraph/cody-shared' +import { cleanup } from '@testing-library/react' +import { afterEach, beforeAll } from 'vitest' +import { URI } from 'vscode-uri' + +beforeAll(() => { + const isWin = isWindows() + setDisplayPathEnvInfo({ + isWindows: isWin, + workspaceFolders: [isWin ? URI.file('C:\\') : URI.file('/')], + }) +}) + +afterEach(() => { + cleanup() +}) diff --git a/lib/prompt-editor/tsconfig.json b/lib/prompt-editor/tsconfig.json new file mode 100644 index 000000000000..0cdb08627268 --- /dev/null +++ b/lib/prompt-editor/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "rootDir": "src", + "outDir": "dist", + "jsx": "react-jsx", + "lib": [ + "ESNext", + "DOM", + "DOM.Iterable" + ], + "moduleResolution": "Bundler", + "types": [ + "@testing-library/jest-dom" + ], + }, + "include": [ + "src" + ], +} diff --git a/lib/prompt-editor/vitest.config.ts b/lib/prompt-editor/vitest.config.ts new file mode 100644 index 000000000000..55a5de7cde7e --- /dev/null +++ b/lib/prompt-editor/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineProjectWithDefaults } from '../../.config/viteShared' + +export default defineProjectWithDefaults(__dirname, { + test: { + environmentMatchGlobs: [['src/**/*.test.tsx', 'happy-dom']], + setupFiles: ['src/testSetup.ts'], + }, +}) diff --git a/lib/shared/src/codebase-context/messages.ts b/lib/shared/src/codebase-context/messages.ts index 59864b5892cc..aae8a89e49f9 100644 --- a/lib/shared/src/codebase-context/messages.ts +++ b/lib/shared/src/codebase-context/messages.ts @@ -207,3 +207,10 @@ export interface ContextMessage extends Required { */ file: ContextItem } + +export const GENERAL_HELP_LABEL = 'Search for a file to include, or type # for symbols...' +export const NO_SYMBOL_MATCHES_HELP_LABEL = ' (language extensions may be loading)' +export const FILE_RANGE_TOOLTIP_LABEL = 'Type a line range to include, e.g. 5-10...' +export const LARGE_FILE_WARNING_LABEL = + 'File too large. Add line range with : or use @# to choose a symbol' +export const IGNORED_FILE_WARNING_LABEL = 'File ignored by an admin setting.' diff --git a/lib/shared/src/context/openctx/api.ts b/lib/shared/src/context/openctx/api.ts index cb13f09b6e15..6d55e11710d6 100644 --- a/lib/shared/src/context/openctx/api.ts +++ b/lib/shared/src/context/openctx/api.ts @@ -15,3 +15,7 @@ export const openCtx = new OpenCtx(undefined) export function setOpenCtxClient(client: OpenCtxClient): void { openCtx.client = client } + +export const REMOTE_REPOSITORY_PROVIDER_URI = 'internal-remote-repository-search' +export const REMOTE_FILE_PROVIDER_URI = 'internal-remote-file-search' +export const WEB_PROVIDER_URI = 'internal-web-provider' diff --git a/lib/shared/src/index.ts b/lib/shared/src/index.ts index 983812ea2abe..86eaef303f28 100644 --- a/lib/shared/src/index.ts +++ b/lib/shared/src/index.ts @@ -73,6 +73,11 @@ export { type SymbolKind, type ContextItemTree, type ContextItemRepository, + FILE_RANGE_TOOLTIP_LABEL, + GENERAL_HELP_LABEL, + IGNORED_FILE_WARNING_LABEL, + LARGE_FILE_WARNING_LABEL, + NO_SYMBOL_MATCHES_HELP_LABEL, } from './codebase-context/messages' export type { CodyCommand, @@ -145,7 +150,7 @@ export { } from './experimentation/FeatureFlagProvider' export { GuardrailsPost, summariseAttribution } from './guardrails' export type { Attribution, Guardrails } from './guardrails' -export { SourcegraphGuardrailsClient, GuardrailsClientConfig } from './guardrails/client' +export { SourcegraphGuardrailsClient, type GuardrailsClientConfig } from './guardrails/client' export { CompletionStopReason, type CodeCompletionsClient, @@ -296,6 +301,9 @@ export * from './configuration' export { setOpenCtxClient, openCtx, + REMOTE_REPOSITORY_PROVIDER_URI, + REMOTE_FILE_PROVIDER_URI, + WEB_PROVIDER_URI, } from './context/openctx/api' export { type ClientStateForWebview } from './clientState' export * from './lexicalEditor/editorState' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4fedffee1520..2b0bbf8d9310 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -272,6 +272,64 @@ importers: specifier: ^3.23.1 version: 3.23.1 + lib/prompt-editor: + dependencies: + '@floating-ui/react': + specifier: ^0.26.9 + version: 0.26.9(react-dom@18.2.0)(react@18.2.0) + '@lexical/code': + specifier: ^0.16.0 + version: 0.16.0 + '@lexical/react': + specifier: ^0.16.0 + version: 0.16.0(react-dom@18.2.0)(react@18.2.0)(yjs@13.6.15) + '@lexical/text': + specifier: ^0.16.0 + version: 0.16.0 + '@lexical/utils': + specifier: ^0.16.0 + version: 0.16.0 + '@sourcegraph/cody-shared': + specifier: workspace:* + version: link:../shared + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.0.0 + version: 1.0.0(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + lexical: + specifier: ^0.16.0 + version: 0.16.0 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + lru-cache: + specifier: ^10.0.0 + version: 10.2.2 + lucide-react: + specifier: ^0.378.0 + version: 0.378.0(react@18.2.0) + vscode-uri: + specifier: ^3.0.7 + version: 3.0.7 + devDependencies: + '@types/lodash': + specifier: ^4.17.7 + version: 4.17.7 + '@types/react': + specifier: 18.2.37 + version: 18.2.37 + '@types/react-dom': + specifier: 18.2.15 + version: 18.2.15 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + lib/shared: dependencies: '@anthropic-ai/sdk': @@ -368,21 +426,6 @@ importers: '@anthropic-ai/sdk': specifier: ^0.20.8 version: 0.20.8 - '@floating-ui/react': - specifier: ^0.26.9 - version: 0.26.9(react-dom@18.2.0)(react@18.2.0) - '@lexical/code': - specifier: ^0.16.0 - version: 0.16.0 - '@lexical/react': - specifier: ^0.16.0 - version: 0.16.0(react-dom@18.2.0)(react@18.2.0)(yjs@13.6.15) - '@lexical/text': - specifier: ^0.16.0 - version: 0.16.0 - '@lexical/utils': - specifier: ^0.16.0 - version: 0.16.0 '@mdi/js': specifier: ^7.2.96 version: 7.2.96 @@ -455,6 +498,9 @@ importers: '@sourcegraph/cody-shared': specifier: workspace:* version: link:../lib/shared + '@sourcegraph/prompt-editor': + specifier: workspace:* + version: link:../lib/prompt-editor '@sourcegraph/telemetry': specifier: ^0.16.0 version: 0.16.0 @@ -804,6 +850,9 @@ importers: '@sourcegraph/cody-shared': specifier: workspace:* version: link:../lib/shared + '@sourcegraph/prompt-editor': + specifier: workspace:* + version: link:../lib/prompt-editor '@types/lodash': specifier: 4.14.195 version: 4.14.195 @@ -3999,6 +4048,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -4025,6 +4088,20 @@ packages: '@types/react': 18.2.79 react: 18.2.0 + /@radix-ui/react-context@1.0.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-context@1.0.1(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} peerDependencies: @@ -4052,6 +4129,40 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.37)(react@18.2.0) + dev: false + /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} peerDependencies: @@ -4099,6 +4210,31 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} peerDependencies: @@ -4124,6 +4260,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -4138,6 +4288,29 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} peerDependencies: @@ -4161,6 +4334,21 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-id@1.0.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-id@1.0.1(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -4255,6 +4443,27 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} peerDependencies: @@ -4276,6 +4485,28 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} peerDependencies: @@ -4319,6 +4550,27 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: @@ -4388,6 +4640,21 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-slot@1.0.2(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-slot@1.0.2(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -4475,6 +4742,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -4502,6 +4783,21 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} peerDependencies: @@ -4531,6 +4827,21 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} peerDependencies: @@ -4546,6 +4857,20 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.5 + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} peerDependencies: @@ -6264,6 +6589,10 @@ packages: resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} dev: true + /@types/lodash@4.17.7: + resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} + dev: true + /@types/long@4.0.2: resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} dev: true @@ -6393,11 +6722,23 @@ packages: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} dev: true + /@types/react-dom@18.2.15: + resolution: {integrity: sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==} + dependencies: + '@types/react': 18.2.79 + /@types/react-dom@18.2.25: resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==} dependencies: '@types/react': 18.2.79 + /@types/react@18.2.37: + resolution: {integrity: sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==} + dependencies: + '@types/prop-types': 15.7.12 + '@types/scheduler': 0.23.0 + csstype: 3.1.3 + /@types/react@18.2.79: resolution: {integrity: sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==} dependencies: @@ -6415,6 +6756,9 @@ packages: '@types/node': 20.12.7 dev: true + /@types/scheduler@0.23.0: + resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==} + /@types/semver@7.5.0: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true @@ -7781,6 +8125,21 @@ packages: engines: {node: '>=6'} dev: false + /cmdk@1.0.0(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + dev: false + /cmdk@1.0.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==} peerDependencies: @@ -13594,6 +13953,22 @@ packages: engines: {node: '>=0.10.0'} dev: true + /react-remove-scroll-bar@2.3.6(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + react: 18.2.0 + react-style-singleton: 2.2.1(@types/react@18.2.37)(react@18.2.0) + tslib: 2.1.0 + dev: false + /react-remove-scroll-bar@2.3.6(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} @@ -13610,6 +13985,25 @@ packages: tslib: 2.1.0 dev: false + /react-remove-scroll@2.5.5(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + react: 18.2.0 + react-remove-scroll-bar: 2.3.6(@types/react@18.2.37)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.37)(react@18.2.0) + tslib: 2.1.0 + use-callback-ref: 1.3.2(@types/react@18.2.37)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.37)(react@18.2.0) + dev: false + /react-remove-scroll@2.5.5(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} engines: {node: '>=10'} @@ -13629,6 +14023,23 @@ packages: use-sidecar: 1.1.2(@types/react@18.2.79)(react@18.2.0) dev: false + /react-style-singleton@2.2.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.1.0 + dev: false + /react-style-singleton@2.2.1(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -15693,6 +16104,21 @@ packages: querystringify: 2.2.0 requires-port: 1.0.0 + /use-callback-ref@1.3.2(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + react: 18.2.0 + tslib: 2.1.0 + dev: false + /use-callback-ref@1.3.2(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} @@ -15708,6 +16134,22 @@ packages: tslib: 2.1.0 dev: false + /use-sidecar@1.1.2(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.1.0 + dev: false + /use-sidecar@1.1.2(@types/react@18.2.79)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} diff --git a/tsconfig.json b/tsconfig.json index ca8514b774be..f6fa2f9b001a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,6 +27,7 @@ "references": [ { "path": "agent" }, { "path": "lib/shared" }, + { "path": "lib/prompt-editor" }, { "path": "vscode" }, { "path": "vscode/test/integration" }, { "path": "vscode/scripts" }, diff --git a/vitest.workspace.js b/vitest.workspace.js index 907afebf5201..9b455e962232 100644 --- a/vitest.workspace.js +++ b/vitest.workspace.js @@ -1,3 +1,3 @@ // @ts-check -export default ['agent', 'cli', 'lib/shared', 'vscode', 'web'] +export default ['agent', 'cli', 'lib/shared', 'lib/prompt-editor', 'vscode', 'web'] diff --git a/vscode/package.json b/vscode/package.json index 8933ebfedc49..cee3e96a1946 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -1393,11 +1393,6 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.20.8", - "@floating-ui/react": "^0.26.9", - "@lexical/code": "^0.16.0", - "@lexical/react": "^0.16.0", - "@lexical/text": "^0.16.0", - "@lexical/utils": "^0.16.0", "@mdi/js": "^7.2.96", "@openctx/provider-linear-issues": "^0.0.6", "@openctx/vscode-lib": "^0.0.13", @@ -1422,6 +1417,7 @@ "@sentry/core": "^7.107.0", "@sentry/node": "^7.107.0", "@sourcegraph/cody-shared": "workspace:*", + "@sourcegraph/prompt-editor": "workspace:*", "@sourcegraph/telemetry": "^0.16.0", "@sourcegraph/tree-sitter-wasms": "^0.1.9", "@types/he": "^1.2.3", diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 679a1284f550..c4e51ebb4f0a 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -195,7 +195,7 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv private readonly contextAPIClient: ContextAPIClient | null private contextFilesQueryAbortController?: AbortController - private allMentionProvidersMetadataQueryCancellation?: vscode.CancellationTokenSource + private allMentionProvidersMetadataQueryAbortController?: AbortController private disposables: vscode.Disposable[] = [] @@ -361,9 +361,6 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv query: parseMentionQuery(message.query, null), }) break - case 'getAllMentionProvidersMetadata': - await this.handleGetAllMentionProvidersMetadata() - break case 'insert': await handleCodeFromInsertAtCursor(message.text) break @@ -940,35 +937,21 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv this.postChatModels() } - private async handleGetAllMentionProvidersMetadata(): Promise { + private async handleGetAllMentionProvidersMetadata(): Promise< + Omit, 'type'> + > { // Cancel previously in-flight query. - const cancellation = new vscode.CancellationTokenSource() - this.allMentionProvidersMetadataQueryCancellation?.cancel() - this.allMentionProvidersMetadataQueryCancellation = cancellation - - try { - const config = await getFullConfig() - const isCodyWeb = config.agentIDE === CodyIDE.Web - const providers = isCodyWeb - ? await webMentionProvidersMetadata() - : await allMentionProvidersMetadata() - - if (cancellation.token.isCancellationRequested) { - return - } - void this.postMessage({ - type: 'allMentionProvidersMetadata', - providers, - }) - } catch (error) { - if (cancellation.token.isCancellationRequested) { - return - } - cancellation.cancel() - this.postError(new Error(`Error retrieving context files: ${error}`)) - } finally { - cancellation.dispose() - } + const abortController = new AbortController() + this.allMentionProvidersMetadataQueryAbortController?.abort() + this.allMentionProvidersMetadataQueryAbortController = abortController + + const config = await getFullConfig() + const isCodyWeb = config.agentIDE === CodyIDE.Web + const providers = isCodyWeb + ? await webMentionProvidersMetadata() + : await allMentionProvidersMetadata() + abortController.signal.throwIfAborted() + return { providers } } private async handleGetUserContextFilesCandidates({ @@ -1648,6 +1631,14 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv } ) ) + this.disposables.push( + handleExtensionAPICallFromWebview( + webviewAPIWrapper, + 'getAllMentionProvidersMetadata', + 'allMentionProvidersMetadata', + async () => this.handleGetAllMentionProvidersMetadata() + ) + ) await this.postConfigFeatures() diff --git a/vscode/src/chat/chat-view/ChatModel.ts b/vscode/src/chat/chat-view/ChatModel.ts index 7616483e2d90..163f83afaf48 100644 --- a/vscode/src/chat/chat-view/ChatModel.ts +++ b/vscode/src/chat/chat-view/ChatModel.ts @@ -10,10 +10,10 @@ import { type SerializedChatTranscript, errorToChatError, isCodyIgnoredFile, + serializeChatMessage, toRangeData, } from '@sourcegraph/cody-shared' -import { serializeChatMessage } from '@sourcegraph/cody-shared' import type { Repo } from '../../context/repo-fetcher' import { getChatPanelTitle } from './chat-helpers' diff --git a/vscode/src/chat/chat-view/prompt.test.ts b/vscode/src/chat/chat-view/prompt.test.ts index 1a5c9e8169f5..b7f2aeffbd52 100644 --- a/vscode/src/chat/chat-view/prompt.test.ts +++ b/vscode/src/chat/chat-view/prompt.test.ts @@ -1,11 +1,12 @@ import { ContextItemSource, + type Message, Model, ModelUsage, ModelsService, contextFiltersProvider, + ps, } from '@sourcegraph/cody-shared' -import { type Message, ps } from '@sourcegraph/cody-shared' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import * as vscode from 'vscode' import { ChatModel } from './ChatModel' diff --git a/vscode/src/chat/chat-view/prompt.ts b/vscode/src/chat/chat-view/prompt.ts index 2a8857aeffcb..d224d874067b 100644 --- a/vscode/src/chat/chat-view/prompt.ts +++ b/vscode/src/chat/chat-view/prompt.ts @@ -4,12 +4,12 @@ import { type ContextItem, type ContextItemWithContent, type Message, + PromptMixin, PromptString, getSimplePreamble, isDefined, wrapInActiveSpan, } from '@sourcegraph/cody-shared' -import { PromptMixin } from '@sourcegraph/cody-shared/src/prompt/prompt-mixin' import { logDebug } from '../../log' import { PromptBuilder } from '../../prompt-builder' import type { ChatModel } from './ChatModel' diff --git a/vscode/src/chat/context/chatContext.ts b/vscode/src/chat/context/chatContext.ts index 3f40e113847d..b770a19ed25e 100644 --- a/vscode/src/chat/context/chatContext.ts +++ b/vscode/src/chat/context/chatContext.ts @@ -5,13 +5,13 @@ import { type ContextItemRepository, FILE_CONTEXT_MENTION_PROVIDER, type MentionQuery, + REMOTE_REPOSITORY_PROVIDER_URI, SYMBOL_CONTEXT_MENTION_PROVIDER, openCtx, } from '@sourcegraph/cody-shared' import * as vscode from 'vscode' import { URI } from 'vscode-uri' import { getContextFileFromUri } from '../../commands/context/file-path' -import RemoteRepositorySearch from '../../context/openctx/remoteRepositorySearch' import { getFileContextFiles, getOpenTabsContextFile, @@ -87,7 +87,7 @@ export function contextItemMentionFromOpenCtxItem( // this for our internal Sourcegraph Repositories provider. const isIgnored = item.data?.isIgnored as boolean | undefined - return item.providerUri === RemoteRepositorySearch.providerUri + return item.providerUri === REMOTE_REPOSITORY_PROVIDER_URI ? ({ type: 'repository', uri: URI.parse(item.uri), diff --git a/vscode/src/chat/context/constants.ts b/vscode/src/chat/context/constants.ts index 75e7a7dd7a35..e5e90febf3ba 100644 --- a/vscode/src/chat/context/constants.ts +++ b/vscode/src/chat/context/constants.ts @@ -1,9 +1,2 @@ -export const GENERAL_HELP_LABEL = 'Search for a file to include, or type # for symbols...' -export const NO_SYMBOL_MATCHES_HELP_LABEL = ' (language extensions may be loading)' -export const FILE_RANGE_TOOLTIP_LABEL = 'Type a line range to include, e.g. 5-10...' -export const LARGE_FILE_WARNING_LABEL = - 'File too large. Add line range with : or use @# to choose a symbol' -export const IGNORED_FILE_WARNING_LABEL = 'File ignored by an admin setting.' - export const QUICK_PICK_ITEM_EMPTY_INDENT_PREFIX = '\u00A0\u00A0\u00A0\u00A0\u00A0' export const QUICK_PICK_ITEM_CHECKED_PREFIX = '$(check)' diff --git a/vscode/src/context/openctx.ts b/vscode/src/context/openctx.ts index 6c00fb1823d9..157dd7e3be65 100644 --- a/vscode/src/context/openctx.ts +++ b/vscode/src/context/openctx.ts @@ -12,17 +12,17 @@ export async function exposeOpenCtxClient( config: ConfigurationWithAccessToken, isDotCom: boolean, // TODO [VK] Expose createController openctx type from vscode-lib - createOpenContextController: ((...args: any[]) => any) | undefined + createOpenCtxController: ((...args: any[]) => any) | undefined ) { logDebug('openctx', 'OpenCtx is enabled in Cody') await warnIfOpenCtxExtensionConflict() try { const isCodyWeb = config.agentIDE === CodyIDE.Web const providers = isCodyWeb - ? getCodyWebOpenContextProviders() - : getStandardOpenContextProviders(config, isDotCom) + ? getCodyWebOpenCtxProviders() + : getStandardOpenCtxProviders(config, isDotCom) const createController = - createOpenContextController ?? (await import('@openctx/vscode-lib')).createController + createOpenCtxController ?? (await import('@openctx/vscode-lib')).createController setOpenCtxClient( createController({ @@ -39,7 +39,7 @@ export async function exposeOpenCtxClient( } } -function getStandardOpenContextProviders(config: ConfigurationWithAccessToken, isDotCom: boolean) { +function getStandardOpenCtxProviders(config: ConfigurationWithAccessToken, isDotCom: boolean) { // TODO [vk] expose types for providers from openctx/client lib const providers: any[] = [ { @@ -78,7 +78,7 @@ function getStandardOpenContextProviders(config: ConfigurationWithAccessToken, i return providers } -function getCodyWebOpenContextProviders() { +function getCodyWebOpenCtxProviders() { return [ { settings: true, diff --git a/vscode/src/context/openctx/linear-issues.ts b/vscode/src/context/openctx/linear-issues.ts index 2b4f0cf52662..81e9722d35a7 100644 --- a/vscode/src/context/openctx/linear-issues.ts +++ b/vscode/src/context/openctx/linear-issues.ts @@ -1,7 +1,7 @@ import linearIssues from '@openctx/provider-linear-issues' -import type { OpenContextProvider } from './types' +import type { OpenCtxProvider } from './types' -const LinearIssuesProvider: OpenContextProvider = { +const LinearIssuesProvider: OpenCtxProvider = { providerUri: 'internal-linear-issues', ...linearIssues, } diff --git a/vscode/src/context/openctx/remoteFileSearch.ts b/vscode/src/context/openctx/remoteFileSearch.ts index c0dcc9d0d335..258adddc7856 100644 --- a/vscode/src/context/openctx/remoteFileSearch.ts +++ b/vscode/src/context/openctx/remoteFileSearch.ts @@ -1,5 +1,6 @@ import type { Item, Mention } from '@openctx/client' import { + REMOTE_FILE_PROVIDER_URI, contextFiltersProvider, displayPathBasename, graphqlClient, @@ -8,13 +9,13 @@ import { } from '@sourcegraph/cody-shared' import { URI } from 'vscode-uri' -import type { OpenContextProvider } from './types' +import type { OpenCtxProvider } from './types' const RemoteFileProvider = createRemoteFileProvider() -export function createRemoteFileProvider(customTitle?: string): OpenContextProvider { +export function createRemoteFileProvider(customTitle?: string): OpenCtxProvider { return { - providerUri: 'internal-remote-file-search', + providerUri: REMOTE_FILE_PROVIDER_URI, meta() { return { diff --git a/vscode/src/context/openctx/remoteRepositorySearch.ts b/vscode/src/context/openctx/remoteRepositorySearch.ts index ab3785d420c6..aaddf36bb003 100644 --- a/vscode/src/context/openctx/remoteRepositorySearch.ts +++ b/vscode/src/context/openctx/remoteRepositorySearch.ts @@ -1,18 +1,19 @@ import type { Item, Mention } from '@openctx/client' import { + REMOTE_REPOSITORY_PROVIDER_URI, type RepoSearchResponse, contextFiltersProvider, graphqlClient, isError, } from '@sourcegraph/cody-shared' -import type { OpenContextProvider } from './types' +import type { OpenCtxProvider } from './types' -const RemoteRepositorySearch: OpenContextProvider = createRemoteRepositoryProvider() +const RemoteRepositorySearch: OpenCtxProvider = createRemoteRepositoryProvider() -export function createRemoteRepositoryProvider(customTitle?: string): OpenContextProvider { +export function createRemoteRepositoryProvider(customTitle?: string): OpenCtxProvider { return { - providerUri: 'internal-remote-repository-search', + providerUri: REMOTE_REPOSITORY_PROVIDER_URI, meta() { return { name: customTitle ?? 'Remote Repositories', mentions: {} } diff --git a/vscode/src/context/openctx/types.ts b/vscode/src/context/openctx/types.ts index 99773fe8d657..8bce79d8f2df 100644 --- a/vscode/src/context/openctx/types.ts +++ b/vscode/src/context/openctx/types.ts @@ -1,5 +1,5 @@ import type { Provider } from '@openctx/client' -export interface OpenContextProvider extends Provider { +export interface OpenCtxProvider extends Provider { providerUri: string } diff --git a/vscode/src/context/openctx/web.ts b/vscode/src/context/openctx/web.ts index 4c40a4e38e2c..7152fab04dec 100644 --- a/vscode/src/context/openctx/web.ts +++ b/vscode/src/context/openctx/web.ts @@ -1,11 +1,12 @@ import type { ItemsParams, ItemsResult } from '@openctx/client' -import type { OpenContextProvider } from './types' +import { WEB_PROVIDER_URI } from '@sourcegraph/cody-shared' +import type { OpenCtxProvider } from './types' /** * An OpenCtx provider that fetches the content of a URL and provides it as an item. */ -const WebProvider: OpenContextProvider = { - providerUri: 'internal-web-provider', +const WebProvider: OpenCtxProvider = { + providerUri: WEB_PROVIDER_URI, meta() { return { diff --git a/vscode/src/edit/input/get-input.ts b/vscode/src/edit/input/get-input.ts index 5fa81173cdd0..50371c8bff78 100644 --- a/vscode/src/edit/input/get-input.ts +++ b/vscode/src/edit/input/get-input.ts @@ -3,6 +3,8 @@ import { type EditModel, type EventSource, FILE_CONTEXT_MENTION_PROVIDER, + GENERAL_HELP_LABEL, + LARGE_FILE_WARNING_LABEL, ModelUsage, ModelsService, PromptString, @@ -15,7 +17,6 @@ import * as vscode from 'vscode' import { telemetryRecorder } from '@sourcegraph/cody-shared' import { EventSourceTelemetryMetadataMapping } from '@sourcegraph/cody-shared/src/chat/transcript/messages' -import { GENERAL_HELP_LABEL, LARGE_FILE_WARNING_LABEL } from '../../chat/context/constants' import { ACCOUNT_UPGRADE_URL } from '../../chat/protocol' import { executeDocCommand, executeTestEditCommand } from '../../commands/execute' import { getEditor } from '../../editor/active-editor' diff --git a/vscode/tsconfig.json b/vscode/tsconfig.json index 41263a395340..d03d91bd4dc6 100644 --- a/vscode/tsconfig.json +++ b/vscode/tsconfig.json @@ -24,7 +24,6 @@ "webviews", "webviews/*.d.ts", "package.json", - ], "exclude": [ "typehacks", @@ -37,6 +36,9 @@ "references": [ { "path": "../lib/shared" + }, + { + "path": "../lib/prompt-editor" } ] } diff --git a/vscode/webviews/App.tsx b/vscode/webviews/App.tsx index b24cd0910147..a9764b05c76f 100644 --- a/vscode/webviews/App.tsx +++ b/vscode/webviews/App.tsx @@ -23,12 +23,12 @@ import { Notices } from './Notices' import { LoginSimplified } from './OnboardingExperiment' import { ConnectionIssuesPage } from './Troubleshooting' import { type ChatModelContext, ChatModelContextProvider } from './chat/models/chatModelContext' -import { ClientStateContextProvider, useClientActionDispatcher } from './client/clientState' +import { useClientActionDispatcher } from './client/clientState' +import { ClientStateContextProvider } from '@sourcegraph/prompt-editor' import { PromptsClientProviderFromVSCodeAPI } from './components/promptSelectField/promptsClient' import { TabContainer, TabRoot } from './components/shadcn/ui/tabs' -import { WithContextProviders } from './mentions/providers' -import { ChatContextClientProviderFromVSCodeAPI } from './promptEditor/plugins/atMentions/chatContextClient' +import { ChatContextClientProviderFromVSCodeAPI } from './openctxClient' import { AccountTab, CommandsTab, HistoryTab, SettingsTab, TabsBar, View } from './tabs' import type { VSCodeWrapper } from './utils/VSCodeApi' import { ComposedWrappers, type Wrapper } from './utils/composeWrappers' @@ -373,7 +373,6 @@ export function getAppWrappers( provider: ChatModelContextProvider, value: chatModelContext, } satisfies Wrapper['value']>, - { component: WithContextProviders }, { provider: ClientStateContextProvider, value: clientState, diff --git a/vscode/webviews/AppWrapper.tsx b/vscode/webviews/AppWrapper.tsx index 4b767f057adb..c71e629dc689 100644 --- a/vscode/webviews/AppWrapper.tsx +++ b/vscode/webviews/AppWrapper.tsx @@ -1,11 +1,16 @@ import type { AuthStatus } from '@sourcegraph/cody-shared' +import { + ChatContextClientProvider, + ClientStateContextProvider, + PromptEditorConfigProvider, + dummyChatContextClient, +} from '@sourcegraph/prompt-editor' import { type ComponentProps, type FunctionComponent, type ReactNode, useMemo } from 'react' -import { ClientActionListenersContextProvider, ClientStateContextProvider } from './client/clientState' +import { ClientActionListenersContextProvider } from './client/clientState' import { dummyPromptsClient } from './components/promptSelectField/fixtures' import { PromptsClientProviderForTestsOnly } from './components/promptSelectField/promptsClient' import { TooltipProvider } from './components/shadcn/ui/tooltip' -import { ChatContextClientProviderForTestsOnly } from './promptEditor/plugins/atMentions/chatContextClient' -import { dummyChatContextClient } from './promptEditor/plugins/atMentions/fixtures' +import { promptEditorConfig } from './promptEditor/config' import { ComposedWrappers, type Wrapper } from './utils/composeWrappers' import { TelemetryRecorderContext } from './utils/telemetry' import { ConfigProvider } from './utils/useConfig' @@ -35,9 +40,9 @@ export const TestAppWrapper: FunctionComponent<{ children: ReactNode }> = ({ chi value: { initialContext: [] }, } satisfies Wrapper['value']>, { - provider: ChatContextClientProviderForTestsOnly, + provider: ChatContextClientProvider, value: dummyChatContextClient, - } satisfies Wrapper['value']>, + } satisfies Wrapper['value']>, { component: ConfigProvider, props: { @@ -71,4 +76,8 @@ const COMMON_WRAPPERS: Wrapper[] = [ { component: ClientActionListenersContextProvider, }, + { + provider: PromptEditorConfigProvider, + value: promptEditorConfig, + } satisfies Wrapper['value']>, ] diff --git a/vscode/webviews/Chat.story.tsx b/vscode/webviews/Chat.story.tsx index 01e585cd481f..6f8f7794482b 100644 --- a/vscode/webviews/Chat.story.tsx +++ b/vscode/webviews/Chat.story.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react' import { Chat } from './Chat' import { FIXTURE_TRANSCRIPT, FIXTURE_USER_ACCOUNT_INFO } from './chat/fixtures' -import { ContextProvidersDecorator, VSCodeWebview } from './storybook/VSCodeStoryDecorator' +import { VSCodeWebview } from './storybook/VSCodeStoryDecorator' const meta: Meta = { title: 'cody/Chat', @@ -28,7 +28,7 @@ const meta: Meta = { isTranscriptError: false, } satisfies React.ComponentProps, - decorators: [VSCodeWebview, ContextProvidersDecorator], + decorators: [VSCodeWebview], } export default meta diff --git a/vscode/webviews/Chat.tsx b/vscode/webviews/Chat.tsx index 4d28e1f63d5c..4cc7bc295320 100644 --- a/vscode/webviews/Chat.tsx +++ b/vscode/webviews/Chat.tsx @@ -8,11 +8,11 @@ import type { VSCodeWrapper } from './utils/VSCodeApi' import { truncateTextStart } from '@sourcegraph/cody-shared/src/prompt/truncation' import { CHAT_INPUT_TOKEN_BUDGET } from '@sourcegraph/cody-shared/src/token/constants' +import { useChatContextMentionProviders } from '@sourcegraph/prompt-editor' import styles from './Chat.module.css' import { GenerateUnitTestsButton } from './chat/components/GenerateUnitTestsButton' import { WelcomeMessage } from './chat/components/WelcomeMessage' import { ScrollDown } from './components/ScrollDown' -import { useContextProviders } from './mentions/providers' import { useTelemetryRecorder } from './utils/telemetry' interface ChatboxProps { @@ -46,7 +46,7 @@ export const Chat: React.FunctionComponent className, experimentalUnitTestEnabled, }) => { - const { reload: reloadMentionProviders } = useContextProviders() + const { reload: reloadMentionProviders } = useChatContextMentionProviders() const telemetryRecorder = useTelemetryRecorder() const feedbackButtonsOnSubmit = useCallback( (text: string) => { diff --git a/vscode/webviews/chat/Transcript.tsx b/vscode/webviews/chat/Transcript.tsx index f40e42078658..79b3b046a8c5 100644 --- a/vscode/webviews/chat/Transcript.tsx +++ b/vscode/webviews/chat/Transcript.tsx @@ -6,6 +6,7 @@ import { deserializeContextItem, isAbortErrorOrSocketHangUp, } from '@sourcegraph/cody-shared' +import type { PromptEditorRefAPI } from '@sourcegraph/prompt-editor' import { type ComponentProps, type FunctionComponent, @@ -16,7 +17,6 @@ import { } from 'react' import type { UserAccountInfo } from '../Chat' import type { ApiPostMessage } from '../Chat' -import type { PromptEditorRefAPI } from '../promptEditor/PromptEditor' import { getVSCodeAPI } from '../utils/VSCodeApi' import type { CodeBlockActionsProps } from './ChatMessageContent' import { ContextCell } from './cells/contextCell/ContextCell' diff --git a/vscode/webviews/chat/cells/contextCell/ContextCell.tsx b/vscode/webviews/chat/cells/contextCell/ContextCell.tsx index 6c5747cb104c..9936798d6daa 100644 --- a/vscode/webviews/chat/cells/contextCell/ContextCell.tsx +++ b/vscode/webviews/chat/cells/contextCell/ContextCell.tsx @@ -1,5 +1,6 @@ import type { ContextItem, Model } from '@sourcegraph/cody-shared' import { pluralize } from '@sourcegraph/cody-shared' +import { MENTION_CLASS_NAME } from '@sourcegraph/prompt-editor' import { clsx } from 'clsx' import { BrainIcon, MessagesSquareIcon } from 'lucide-react' import type React from 'react' @@ -12,7 +13,6 @@ import { } from '../../../components/shadcn/ui/accordion' import { Tooltip, TooltipContent, TooltipTrigger } from '../../../components/shadcn/ui/tooltip' import { SourcegraphLogo } from '../../../icons/SourcegraphLogo' -import { MENTION_CLASS_NAME } from '../../../promptEditor/nodes/ContextItemMentionNode' import { getVSCodeAPI } from '../../../utils/VSCodeApi' import { LoadingDots } from '../../components/LoadingDots' import { Cell } from '../Cell' diff --git a/vscode/webviews/chat/cells/messageCell/assistant/AssistantMessageCell.tsx b/vscode/webviews/chat/cells/messageCell/assistant/AssistantMessageCell.tsx index a4d6da7fa74a..457e118e853c 100644 --- a/vscode/webviews/chat/cells/messageCell/assistant/AssistantMessageCell.tsx +++ b/vscode/webviews/chat/cells/messageCell/assistant/AssistantMessageCell.tsx @@ -9,11 +9,11 @@ import { reformatBotMessageForChat, serializedPromptEditorStateFromChatMessage, } from '@sourcegraph/cody-shared' +import type { PromptEditorRefAPI } from '@sourcegraph/prompt-editor' import { type FunctionComponent, type RefObject, useMemo } from 'react' import type { ApiPostMessage, UserAccountInfo } from '../../../../Chat' import { chatModelIconComponent } from '../../../../components/ChatModelIcon' import { Tooltip, TooltipContent, TooltipTrigger } from '../../../../components/shadcn/ui/tooltip' -import type { PromptEditorRefAPI } from '../../../../promptEditor/PromptEditor' import { ChatMessageContent, type CodeBlockActionsProps } from '../../../ChatMessageContent' import { ErrorItem, RequestErrorItem } from '../../../ErrorItem' import { type Interaction, editHumanMessage } from '../../../Transcript' diff --git a/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.story.tsx b/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.story.tsx index 1188caa7b309..973f7a52b7d9 100644 --- a/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.story.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.story.tsx @@ -1,8 +1,8 @@ import type { Meta, StoryObj } from '@storybook/react' import { PromptString, ps } from '@sourcegraph/cody-shared' +import { ClientStateContextProvider } from '@sourcegraph/prompt-editor' import { URI } from 'vscode-uri' -import { ClientStateContextProvider } from '../../../../client/clientState' import { VSCodeCell } from '../../../../storybook/VSCodeStoryDecorator' import { FIXTURE_TRANSCRIPT, FIXTURE_USER_ACCOUNT_INFO } from '../../../fixtures' import { HumanMessageCell } from './HumanMessageCell' diff --git a/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.tsx b/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.tsx index 4fe2e6c50ec6..0769cd631e99 100644 --- a/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/HumanMessageCell.tsx @@ -3,10 +3,10 @@ import { type SerializedPromptEditorValue, serializedPromptEditorStateFromChatMessage, } from '@sourcegraph/cody-shared' +import type { PromptEditorRefAPI } from '@sourcegraph/prompt-editor' import { type FunctionComponent, useMemo } from 'react' import type { UserAccountInfo } from '../../../../Chat' import { UserAvatar } from '../../../../components/UserAvatar' -import type { PromptEditorRefAPI } from '../../../../promptEditor/PromptEditor' import { BaseMessageCell, MESSAGE_CELL_AVATAR_SIZE } from '../BaseMessageCell' import { HumanMessageEditor } from './editor/HumanMessageEditor' diff --git a/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx b/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx index d2518d77e77f..258adedadb7b 100644 --- a/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx @@ -1,8 +1,10 @@ import { + FAST_CHAT_INPUT_TOKEN_BUDGET, type SerializedPromptEditorState, type SerializedPromptEditorValue, textContentFromSerializedLexicalNode, } from '@sourcegraph/cody-shared' +import { PromptEditor, type PromptEditorRefAPI, useClientState } from '@sourcegraph/prompt-editor' import clsx from 'clsx' import { type FocusEventHandler, @@ -14,13 +16,9 @@ import { useState, } from 'react' import type { UserAccountInfo } from '../../../../../Chat' -import { - type ClientActionListener, - useClientActionListener, - useClientState, -} from '../../../../../client/clientState' -import { PromptEditor, type PromptEditorRefAPI } from '../../../../../promptEditor/PromptEditor' +import { type ClientActionListener, useClientActionListener } from '../../../../../client/clientState' import { useTelemetryRecorder } from '../../../../../utils/telemetry' +import { useCurrentChatModel } from '../../../../models/chatModelContext' import styles from './HumanMessageEditor.module.css' import type { SubmitButtonState } from './toolbar/SubmitButton' import { Toolbar } from './toolbar/Toolbar' @@ -262,6 +260,12 @@ export const HumanMessageEditor: FunctionComponent<{ const focused = Boolean(isEditorFocused || isFocusWithin || __storybook__focus) + const model = useCurrentChatModel() + const contextWindowSizeInTokens = + model?.contextWindow?.context?.user || + model?.contextWindow?.input || + FAST_CHAT_INPUT_TOKEN_BUDGET + return ( // biome-ignore lint/a11y/useKeyWithClickEvents: only relevant to click areas
{!disabled && ( (null) - -export const ClientStateContextProvider = ClientStateContext.Provider - -/** - * Get the {@link ClientState} stored in React context. - */ -export function useClientState(): ClientStateForWebview { - const clientState = useContext(ClientStateContext) - if (!clientState) { - throw new Error('no clientState') - } - return clientState -} - type ClientActionArg = Omit, 'type'> export type ClientActionListener = (arg: ClientActionArg) => void diff --git a/vscode/webviews/components/shadcn/ui/command.tsx b/vscode/webviews/components/shadcn/ui/command.tsx index cd6e3eb7dc8a..df67d2543a6f 100644 --- a/vscode/webviews/components/shadcn/ui/command.tsx +++ b/vscode/webviews/components/shadcn/ui/command.tsx @@ -1,9 +1,7 @@ -import type { DialogProps } from '@radix-ui/react-dialog' import { Command as CommandPrimitive } from 'cmdk' import * as React from 'react' import { cn } from '../utils' -import { Dialog, DialogContent } from './dialog' const Command = React.forwardRef< React.ElementRef, @@ -20,20 +18,6 @@ const Command = React.forwardRef< )) Command.displayName = CommandPrimitive.displayName -interface CommandDialogProps extends DialogProps {} - -const CommandDialog = ({ children, ...props }: CommandDialogProps) => { - return ( - - - - {children} - - - - ) -} - const CommandInput = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -221,7 +205,6 @@ export const CommandLink: React.FunctionComponent< export { Command, - CommandDialog, CommandInput, CommandList, CommandEmpty, diff --git a/vscode/webviews/components/shadcn/ui/dialog.tsx b/vscode/webviews/components/shadcn/ui/dialog.tsx deleted file mode 100644 index 62a0b314bacd..000000000000 --- a/vscode/webviews/components/shadcn/ui/dialog.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import * as DialogPrimitive from '@radix-ui/react-dialog' -import { XIcon } from 'lucide-react' -import * as React from 'react' - -import { cn } from '../utils' - -const Dialog = DialogPrimitive.Root - -const DialogPortal = DialogPrimitive.Portal - -const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName - -const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - -)) -DialogContent.displayName = DialogPrimitive.Content.displayName - -const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
-) -DialogHeader.displayName = 'DialogHeader' - -const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( -
-) -DialogFooter.displayName = 'DialogFooter' - -const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogTitle.displayName = DialogPrimitive.Title.displayName - -const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogDescription.displayName = DialogPrimitive.Description.displayName - -export { Dialog, DialogContent } diff --git a/vscode/webviews/mentions/providers.tsx b/vscode/webviews/mentions/providers.tsx deleted file mode 100644 index 74c8f277ef73..000000000000 --- a/vscode/webviews/mentions/providers.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import type { ContextMentionProviderMetadata } from '@sourcegraph/cody-shared' -import { createContext, useCallback, useContext, useEffect, useState } from 'react' -import { getVSCodeAPI } from '../utils/VSCodeApi' - -/** React context data for the available context providers. */ -interface ContextProviderContext { - providers: ContextMentionProviderMetadata[] - reload: () => void -} - -const context = createContext({ - providers: [], - reload: () => {}, -}) - -const getAllMentionProvidersMetadata = async (): Promise => { - return new Promise(resolve => { - const vscodeApi = getVSCodeAPI() - vscodeApi.postMessage({ command: 'getAllMentionProvidersMetadata' }) - - const RESPONSE_MESSAGE_TYPE = 'allMentionProvidersMetadata' as const - - // Clean up after a while to avoid resource exhaustion in case there is a bug - // somewhere. - const MAX_WAIT_SECONDS = 15 - const rejectTimeout = setTimeout(() => { - resolve([]) - dispose() - }, MAX_WAIT_SECONDS * 1000) - - // Wait for the response. We assume the first message of the right type is the response to - // our call. - const dispose = vscodeApi.onMessage(async message => { - if (message.type === RESPONSE_MESSAGE_TYPE) { - resolve(await (message.providers ?? [])) - dispose() - clearTimeout(rejectTimeout) - } - }) - }) -} - -export const WithContextProviders = (props: { children: React.ReactElement }): React.ReactElement => { - const [providers, setProviders] = useState([]) - - const loadProviders = useCallback(async () => { - const providersData = await getAllMentionProvidersMetadata() - setProviders(providersData) - }, []) - - useEffect(() => { - loadProviders() - }, [loadProviders]) - - return ( - - {props.children} - - ) -} - -export function useContextProviders(): ContextProviderContext { - return useContext(context) -} diff --git a/vscode/webviews/minion/MinionApp.tsx b/vscode/webviews/minion/MinionApp.tsx index 1344c3d77cb3..22f42cc2fc26 100644 --- a/vscode/webviews/minion/MinionApp.tsx +++ b/vscode/webviews/minion/MinionApp.tsx @@ -16,11 +16,10 @@ import { type SerializedPromptEditorValue, markdownCodeBlockLanguageIDForFilename, } from '@sourcegraph/cody-shared' +import { ClientStateContextProvider, PromptEditor } from '@sourcegraph/prompt-editor' import type { URI } from 'vscode-uri' -import { ClientStateContextProvider } from '../client/clientState' import { FileLink } from '../components/FileLink' import { MarkdownFromCody } from '../components/MarkdownFromCody' -import { PromptEditor } from '../promptEditor/PromptEditor' import { updateDisplayPathEnvInfoForWebview } from '../utils/displayPathEnvInfo' import type { MinionExtensionMessage, MinionWebviewMessage } from './webview_protocol' diff --git a/vscode/webviews/openctxClient.tsx b/vscode/webviews/openctxClient.tsx new file mode 100644 index 000000000000..3872becb4499 --- /dev/null +++ b/vscode/webviews/openctxClient.tsx @@ -0,0 +1,33 @@ +import { createExtensionAPIProxyInWebview } from '@sourcegraph/cody-shared' +import { type ChatContextClient, ChatContextClientProvider } from '@sourcegraph/prompt-editor' +import { type FunctionComponent, type ReactNode, useMemo } from 'react' +import type { VSCodeWrapper } from './utils/VSCodeApi' + +export const ChatContextClientProviderFromVSCodeAPI: FunctionComponent<{ + vscodeAPI: VSCodeWrapper | null + children: ReactNode +}> = ({ vscodeAPI, children }) => { + const value = useMemo( + () => + vscodeAPI + ? { + getChatContextItems: createExtensionAPIProxyInWebview( + vscodeAPI, + 'queryContextItems', + 'userContextFiles' + ), + getMentionProvidersMetadata: createExtensionAPIProxyInWebview( + vscodeAPI, + 'getAllMentionProvidersMetadata', + 'allMentionProvidersMetadata' + ), + } + : null, + [vscodeAPI] + ) + return value ? ( + {children} + ) : ( + <>{children} + ) +} diff --git a/vscode/webviews/promptEditor/BaseEditor.story.tsx b/vscode/webviews/promptEditor/BaseEditor.story.tsx index e702fcc7a773..9648890c098d 100644 --- a/vscode/webviews/promptEditor/BaseEditor.story.tsx +++ b/vscode/webviews/promptEditor/BaseEditor.story.tsx @@ -1,9 +1,9 @@ import { editorStateToText } from '@sourcegraph/cody-shared' +import { BaseEditor } from '@sourcegraph/prompt-editor' import type { Meta, StoryObj } from '@storybook/react' import type { EditorState } from 'lexical' import { useState } from 'react' import { VSCodeStandaloneComponent } from '../storybook/VSCodeStoryDecorator' -import { BaseEditor } from './BaseEditor' import styles from './BaseEditor.story.module.css' const meta: Meta = { diff --git a/vscode/webviews/mentions/mentionMenu/MentionMenu.story.tsx b/vscode/webviews/promptEditor/MentionMenu.story.tsx similarity index 98% rename from vscode/webviews/mentions/mentionMenu/MentionMenu.story.tsx rename to vscode/webviews/promptEditor/MentionMenu.story.tsx index c79b8acf9273..7a24b4e34695 100644 --- a/vscode/webviews/mentions/mentionMenu/MentionMenu.story.tsx +++ b/vscode/webviews/promptEditor/MentionMenu.story.tsx @@ -9,9 +9,8 @@ import { SYMBOL_CONTEXT_MENTION_PROVIDER, openCtxProviderMetadata, } from '@sourcegraph/cody-shared' -import { VSCodeDecorator } from '../../storybook/VSCodeStoryDecorator' -import { MentionMenu } from './MentionMenu' -import type { MentionMenuData, MentionMenuParams } from './useMentionMenuData' +import { MentionMenu, type MentionMenuData, type MentionMenuParams } from '@sourcegraph/prompt-editor' +import { VSCodeDecorator } from '../storybook/VSCodeStoryDecorator' const meta: Meta = { title: 'cody/MentionMenu', diff --git a/vscode/webviews/promptEditor/PromptEditor.story.tsx b/vscode/webviews/promptEditor/PromptEditor.story.tsx index 1f1c3e59914a..c8c63c27515e 100644 --- a/vscode/webviews/promptEditor/PromptEditor.story.tsx +++ b/vscode/webviews/promptEditor/PromptEditor.story.tsx @@ -4,11 +4,11 @@ import { type SerializedPromptEditorState, serializedPromptEditorStateFromText, } from '@sourcegraph/cody-shared' +import { PromptEditor } from '@sourcegraph/prompt-editor' import type { Meta, StoryObj } from '@storybook/react' import { type FunctionComponent, useState } from 'react' -import { ContextProvidersDecorator, VSCodeStandaloneComponent } from '../storybook/VSCodeStoryDecorator' +import { VSCodeStandaloneComponent } from '../storybook/VSCodeStoryDecorator' import styles from './BaseEditor.story.module.css' -import { PromptEditor } from './PromptEditor' const meta: Meta = { title: 'ui/PromptEditor', @@ -20,7 +20,7 @@ const meta: Meta = { editorClassName: styles.editor, } satisfies React.ComponentProps, - decorators: [VSCodeStandaloneComponent, ContextProvidersDecorator], + decorators: [VSCodeStandaloneComponent], } as Meta export default meta diff --git a/vscode/webviews/promptEditor/config.ts b/vscode/webviews/promptEditor/config.ts new file mode 100644 index 000000000000..f709d6aba49c --- /dev/null +++ b/vscode/webviews/promptEditor/config.ts @@ -0,0 +1,46 @@ +import type { SerializedContextItem } from '@sourcegraph/cody-shared' +import type { PromptEditorConfig } from '@sourcegraph/prompt-editor' +import { URI } from 'vscode-uri' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandLoading, + CommandSeparator, +} from '../components/shadcn/ui/command' +import { Tooltip, TooltipContent, TooltipTrigger } from '../components/shadcn/ui/tooltip' +import { getVSCodeAPI } from '../utils/VSCodeApi' + +/** + * This is for config that can't be passed via React context because Lexical nodes are rendered in + * disconnected React DOM trees, so the context won't pass down. + */ +export const promptEditorConfig: PromptEditorConfig = { + onContextItemMentionNodeMetaClick: (contextItem: SerializedContextItem) => { + if (contextItem.uri) { + const uri = URI.parse(contextItem.uri) + getVSCodeAPI().postMessage({ + command: 'openURI', + uri, + }) + } + }, + tooltipComponents: { + Tooltip, + TooltipContent, + TooltipTrigger, + }, + commandComponents: { + Command: Command, + CommandInput: CommandInput, + CommandList: CommandList, + CommandEmpty: CommandEmpty, + CommandLoading: CommandLoading, + CommandGroup: CommandGroup, + CommandSeparator: CommandSeparator, + CommandItem: CommandItem, + }, +} diff --git a/vscode/webviews/storybook/VSCodeStoryDecorator.tsx b/vscode/webviews/storybook/VSCodeStoryDecorator.tsx index 99cdad4daf23..674a1c968b50 100644 --- a/vscode/webviews/storybook/VSCodeStoryDecorator.tsx +++ b/vscode/webviews/storybook/VSCodeStoryDecorator.tsx @@ -12,7 +12,6 @@ import { URI } from 'vscode-uri' import '../../node_modules/@vscode/codicons/dist/codicon.css' import { TestAppWrapper } from '../AppWrapper' import { type ChatModelContext, ChatModelContextProvider } from '../chat/models/chatModelContext' -import { WithContextProviders } from '../mentions/providers' import { TelemetryRecorderContext, createWebviewTelemetryRecorder } from '../utils/telemetry' import styles from './VSCodeStoryDecorator.module.css' @@ -116,11 +115,3 @@ if (!(window as any).acquireVsCodeApi) { } const telemetryRecorder = createWebviewTelemetryRecorder(acquireVsCodeApi()) - -export const ContextProvidersDecorator: Decorator = (Story, context) => { - return ( - - - - ) -} diff --git a/web/lib/components/CodyWebChat.tsx b/web/lib/components/CodyWebChat.tsx index 74b21eb5a4a1..92113215faf0 100644 --- a/web/lib/components/CodyWebChat.tsx +++ b/web/lib/components/CodyWebChat.tsx @@ -15,15 +15,12 @@ import { setDisplayPathEnvInfo, } from '@sourcegraph/cody-shared' +import { ChatMentionContext, type ChatMentionsSettings } from '@sourcegraph/prompt-editor' import { getAppWrappers } from 'cody-ai/webviews/App' import { Chat, type UserAccountInfo } from 'cody-ai/webviews/Chat' import { ChatEnvironmentContext } from 'cody-ai/webviews/chat/ChatEnvironmentContext' import type { ChatModelContext } from 'cody-ai/webviews/chat/models/chatModelContext' import { useClientActionDispatcher } from 'cody-ai/webviews/client/clientState' -import { - ChatMentionContext, - type ChatMentionsSettings, -} from 'cody-ai/webviews/promptEditor/plugins/atMentions/chatContextClient' import { createWebviewTelemetryRecorder } from 'cody-ai/webviews/utils/telemetry' import { useWebAgentClient } from './CodyWebChatProvider' diff --git a/web/package.json b/web/package.json index 164e692a5c62..e9a99660bc3f 100644 --- a/web/package.json +++ b/web/package.json @@ -21,6 +21,7 @@ "devDependencies": { "@sourcegraph/cody": "workspace:*", "@sourcegraph/cody-shared": "workspace:*", + "@sourcegraph/prompt-editor": "workspace:*", "@vitejs/plugin-react-swc": "^3.6.0", "@vitest/web-worker": "^1.4.0", "@vscode/codicons": "^0.0.35",