diff --git a/lib/prompt-editor/src/PromptEditor.tsx b/lib/prompt-editor/src/PromptEditor.tsx index 19e9a80677f7..5d7da092b745 100644 --- a/lib/prompt-editor/src/PromptEditor.tsx +++ b/lib/prompt-editor/src/PromptEditor.tsx @@ -117,7 +117,7 @@ export const PromptEditor: FunctionComponent = ({ // Ensure element is focused in case the editor is empty. Copied // from LexicalAutoFocusPlugin. const doFocus = () => - editor.getRootElement()?.focus({ preventScroll: true }) + editor.getRootElement()?.focus({ preventScroll: false }) doFocus() // HACK(sqs): Needed in VS Code webviews to actually get it to focus diff --git a/vscode/webviews/CodyPanel.tsx b/vscode/webviews/CodyPanel.tsx index c0a8c80f6e12..1b3e9211de95 100644 --- a/vscode/webviews/CodyPanel.tsx +++ b/vscode/webviews/CodyPanel.tsx @@ -1,50 +1,54 @@ import { type AuthStatus, + type ChatMessage, type ClientCapabilitiesWithLegacyFields, CodyIDE, FeatureFlag, + type Guardrails, + firstValueFrom, } from '@sourcegraph/cody-shared' import { useExtensionAPI, useObservable } from '@sourcegraph/prompt-editor' import type React from 'react' -import { type ComponentProps, type FunctionComponent, useEffect, useMemo, useRef } from 'react' +import { type FunctionComponent, useEffect, useMemo, useRef } from 'react' import type { ConfigurationSubsetForWebview, LocalEnv } from '../src/chat/protocol' import styles from './App.module.css' import { Chat } from './Chat' +import { useClientActionDispatcher } from './client/clientState' import { ConnectivityStatusBanner } from './components/ConnectivityStatusBanner' import { Notices } from './components/Notices' import { StateDebugOverlay } from './components/StateDebugOverlay' import { TabContainer, TabRoot } from './components/shadcn/ui/tabs' import { AccountTab, HistoryTab, PromptsTab, SettingsTab, TabsBar, View } from './tabs' +import type { VSCodeWrapper } from './utils/VSCodeApi' import { useFeatureFlag } from './utils/useFeatureFlags' import { TabViewContext } from './utils/useTabView' +interface CodyPanelProps { + view: View + setView: (view: View) => void + configuration: { + config: LocalEnv & ConfigurationSubsetForWebview + clientCapabilities: ClientCapabilitiesWithLegacyFields + authStatus: AuthStatus + } + errorMessages: string[] + attributionEnabled: boolean + chatEnabled: boolean + messageInProgress: ChatMessage | null + transcript: ChatMessage[] + vscodeAPI: Pick + setErrorMessages: (errors: string[]) => void + guardrails?: Guardrails + showWelcomeMessage?: boolean + showIDESnippetActions?: boolean + smartApplyEnabled?: boolean + onExternalApiReady?: (api: CodyExternalApi) => void +} + /** * The Cody tab panel, with tabs for chat, history, prompts, etc. */ -export const CodyPanel: FunctionComponent< - { - view: View - setView: (view: View) => void - configuration: { - config: LocalEnv & ConfigurationSubsetForWebview - clientCapabilities: ClientCapabilitiesWithLegacyFields - authStatus: AuthStatus - } - errorMessages: string[] - setErrorMessages: (errors: string[]) => void - attributionEnabled: boolean - } & Pick< - ComponentProps, - | 'chatEnabled' - | 'messageInProgress' - | 'transcript' - | 'vscodeAPI' - | 'guardrails' - | 'showWelcomeMessage' - | 'showIDESnippetActions' - | 'smartApplyEnabled' - > -> = ({ +export const CodyPanel: FunctionComponent = ({ view, setView, configuration: { config, clientCapabilities, authStatus }, @@ -59,13 +63,19 @@ export const CodyPanel: FunctionComponent< showIDESnippetActions, showWelcomeMessage, smartApplyEnabled, + onExternalApiReady, }) => { const tabContainerRef = useRef(null) + const externalAPI = useExternalAPI() const api = useExtensionAPI() const { value: chatModels } = useObservable(useMemo(() => api.chatModels(), [api.chatModels])) const isPromptsV2Enabled = useFeatureFlag(FeatureFlag.CodyPromptsV2) + useEffect(() => { + onExternalApiReady?.(externalAPI) + }, [onExternalApiReady, externalAPI]) + useEffect(() => { const subscription = api.clientActionBroadcast().subscribe(action => { switch (action.type) { @@ -126,7 +136,11 @@ export const CodyPanel: FunctionComponent< /> )} {view === View.Prompts && ( - + )} {view === View.Account && } {view === View.Settings && } @@ -155,3 +169,40 @@ const ErrorBanner: React.FunctionComponent<{ errors: string[]; setErrors: (error ))} ) + +export interface ExternalPrompt { + text: string + autoSubmit: boolean + mode?: ChatMessage['intent'] +} + +export interface CodyExternalApi { + runPrompt: (action: ExternalPrompt) => Promise +} + +function useExternalAPI(): CodyExternalApi { + const dispatchClientAction = useClientActionDispatcher() + const extensionAPI = useExtensionAPI() + + return useMemo( + () => ({ + runPrompt: async (prompt: ExternalPrompt) => { + const promptEditorState = await firstValueFrom( + extensionAPI.hydratePromptMessage(prompt.text) + ) + + dispatchClientAction( + { + editorState: promptEditorState, + submitHumanInput: prompt.autoSubmit, + setLastHumanInputIntent: prompt.mode ?? 'chat', + }, + // Buffer because PromptEditor is not guaranteed to be mounted after the `setView` + // call above, and it needs to be mounted to receive the action. + { buffer: true } + ) + }, + }), + [extensionAPI, dispatchClientAction] + ) +} diff --git a/vscode/webviews/chat/components/WelcomeMessage.tsx b/vscode/webviews/chat/components/WelcomeMessage.tsx index 4fa722bed31d..f8c9a113a097 100644 --- a/vscode/webviews/chat/components/WelcomeMessage.tsx +++ b/vscode/webviews/chat/components/WelcomeMessage.tsx @@ -33,7 +33,9 @@ export const WelcomeMessage: FunctionComponent = ({ return (
- {isPromptsV2Enabled && } + {isPromptsV2Enabled && IDE !== CodyIDE.Web && ( + + )}
void isPromptsV2Enabled?: boolean -}> = ({ setView, isPromptsV2Enabled }) => { +}> = ({ IDE, setView, isPromptsV2Enabled }) => { const runAction = useActionSelect() return (
- {isPromptsV2Enabled && ( + {isPromptsV2Enabled && IDE !== CodyIDE.Web && ( )} className?: string + + /** + * Whenever an external (imperative) Cody Chat API instance is ready, + * for example it gives you ability to run prompt, Note that this handler + * should be memoized and not change between components re-render, otherwise + * it will be stuck in infinite update loop + */ + onExternalApiReady?: (api: CodyExternalApi) => void } /** * The root component node for Cody Web Chat, implements Cody Agent client @@ -66,6 +74,7 @@ export const CodyWebChat: FunctionComponent = ({ telemetryClientName, customHeaders, className, + onExternalApiReady, }) => { const { client, vscodeAPI } = useCodyWebAgent({ serverEndpoint, @@ -91,6 +100,7 @@ export const CodyWebChat: FunctionComponent = ({ vscodeAPI={vscodeAPI} initialContext={initialContext} className={styles.container} + onExternalApiReady={onExternalApiReady} />
@@ -101,10 +111,11 @@ interface CodyWebPanelProps { vscodeAPI: VSCodeWrapper initialContext: InitialContext | undefined className?: string + onExternalApiReady?: (api: CodyExternalApi) => void } const CodyWebPanel: FC = props => { - const { vscodeAPI, initialContext: initialContextData, className } = props + const { vscodeAPI, initialContext: initialContextData, className, onExternalApiReady } = props const dispatchClientAction = useClientActionDispatcher() const [errorMessages, setErrorMessages] = useState([]) @@ -257,6 +268,7 @@ const CodyWebPanel: FC = props => { messageInProgress={messageInProgress} transcript={transcript} vscodeAPI={vscodeAPI} + onExternalApiReady={onExternalApiReady} /> diff --git a/web/lib/index.ts b/web/lib/index.ts index 12a842db9c63..b64591fe4243 100644 --- a/web/lib/index.ts +++ b/web/lib/index.ts @@ -1,4 +1,4 @@ export { CodyWebChat, type CodyWebChatProps } from './components/CodyWebChat' export { ChatSkeleton } from './components/skeleton/ChatSkeleton' -export type { Repository, InitialContext } from './types' +export type { Repository, InitialContext, CodyExternalApi } from './types' diff --git a/web/lib/types.ts b/web/lib/types.ts index a3fb782ecd6c..f82981ef61c2 100644 --- a/web/lib/types.ts +++ b/web/lib/types.ts @@ -1,3 +1,15 @@ +// Copy of external prompts interface from CodyPanel component since +// type re-exports don't work with Cody Web bundle +export interface ExternalPrompt { + text: string + autoSubmit: boolean + mode?: 'search' | 'chat' | 'edit' | 'insert' +} + +export interface CodyExternalApi { + runPrompt: (action: ExternalPrompt) => Promise +} + export interface Repository { id: string name: string diff --git a/web/package.json b/web/package.json index 6632eede6d5d..3c05fce76526 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "@sourcegraph/cody-web", - "version": "0.10.0", + "version": "0.12.0", "description": "Cody standalone web app", "license": "Apache-2.0", "repository": {