diff --git a/client/cody-shared/src/chat/context.ts b/client/cody-shared/src/chat/context.ts index eab6f2ddaf7..e065459e5e3 100644 --- a/client/cody-shared/src/chat/context.ts +++ b/client/cody-shared/src/chat/context.ts @@ -1,4 +1,8 @@ +import { ConfigurationUseContext } from '../configuration' + export interface ChatContextStatus { + mode?: ConfigurationUseContext + connection?: boolean codebase?: string filePath?: string } diff --git a/client/cody-shared/src/codebase-context/index.ts b/client/cody-shared/src/codebase-context/index.ts index d1295f2c9d6..f6b816cc9c8 100644 --- a/client/cody-shared/src/codebase-context/index.ts +++ b/client/cody-shared/src/codebase-context/index.ts @@ -26,19 +26,21 @@ export class CodebaseContext { public async getContextMessages(query: string, options: ContextSearchOptions): Promise { switch (this.config.useContext) { - case 'blended': + case 'embeddings' || 'blended': return this.embeddings ? this.getEmbeddingsContextMessages(query, options) : this.getKeywordContextMessages(query, options) - case 'embeddings': - return this.getEmbeddingsContextMessages(query, options) case 'keyword': return this.getKeywordContextMessages(query, options) default: - return [] + return this.getEmbeddingsContextMessages(query, options) } } + public checkEmbeddingsConnection(): boolean { + return !!this.embeddings + } + // We split the context into multiple messages instead of joining them into a single giant message. // We can gradually eliminate them from the prompt, instead of losing them all at once with a single large messeage // when we run out of tokens. diff --git a/client/cody-ui/src/Chat.tsx b/client/cody-ui/src/Chat.tsx index 9369bd9861a..59d4e5e8297 100644 --- a/client/cody-ui/src/Chat.tsx +++ b/client/cody-ui/src/Chat.tsx @@ -106,15 +106,15 @@ export const Chat: React.FunctionComponent = ({ ) const onChatSubmit = useCallback((): void => { - // Submit chat only when input is not empty - if (formInput.trim()) { + // Submit chat only when input is not empty and not in progress + if (formInput.trim() && !messageInProgress) { onSubmit(formInput) setHistoryIndex(inputHistory.length + 1) setInputHistory([...inputHistory, formInput]) setInputRows(5) setFormInput('') } - }, [formInput, inputHistory, onSubmit, setFormInput, setInputHistory]) + }, [formInput, inputHistory, messageInProgress, onSubmit, setFormInput, setInputHistory]) const onChatKeyDown = useCallback( (event: React.KeyboardEvent): void => { diff --git a/client/cody-ui/src/chat/TranscriptItem.module.css b/client/cody-ui/src/chat/TranscriptItem.module.css index dcaf287bacf..98bd25d6498 100644 --- a/client/cody-ui/src/chat/TranscriptItem.module.css +++ b/client/cody-ui/src/chat/TranscriptItem.module.css @@ -51,8 +51,7 @@ .actions { display: flex; flex-direction: column; - align-items: flex-start; - gap: calc(var(--spacing) * 0.5); + align-items: center; padding: 0 var(--spacing); } @@ -61,6 +60,7 @@ word-break: break-word; line-height: 150%; } + .content pre { padding: calc(var(--spacing) * 0.5); overflow-x: auto; diff --git a/client/cody-ui/src/chat/TranscriptItem.tsx b/client/cody-ui/src/chat/TranscriptItem.tsx index 76416d0fbb6..2f1b2b5362e 100644 --- a/client/cody-ui/src/chat/TranscriptItem.tsx +++ b/client/cody-ui/src/chat/TranscriptItem.tsx @@ -92,7 +92,9 @@ export const TranscriptItem: React.FunctionComponent< {message.displayText ? ( ) : inProgress ? ( - + + Fetching context... + ) : null} diff --git a/client/cody-ui/src/chat/actions/TranscriptAction.module.css b/client/cody-ui/src/chat/actions/TranscriptAction.module.css index bd85337fe1d..90c37084d6c 100644 --- a/client/cody-ui/src/chat/actions/TranscriptAction.module.css +++ b/client/cody-ui/src/chat/actions/TranscriptAction.module.css @@ -2,6 +2,7 @@ display: inline-block; padding: 0.4rem 0.4rem 0.3rem 0.7rem; font-size: 0.9rem; + min-width: calc(100% - 1.1rem); } .container-open { padding-bottom: 0.6rem; @@ -16,7 +17,7 @@ align-items: center; gap: 0.2rem; cursor: pointer; - + width: 100%; padding: 0; appearance: none; background: none; @@ -24,6 +25,7 @@ outline: none; color: currentColor; font-family: inherit; + justify-content: space-between; } .open-close-icon { opacity: 0.8; diff --git a/client/cody-ui/src/chat/inputContext/ChatInputContext.module.css b/client/cody-ui/src/chat/inputContext/ChatInputContext.module.css index 9dbe3543c40..1e87412d10f 100644 --- a/client/cody-ui/src/chat/inputContext/ChatInputContext.module.css +++ b/client/cody-ui/src/chat/inputContext/ChatInputContext.module.css @@ -46,3 +46,12 @@ text-overflow: ellipsis; overflow: hidden; } + +.fail { + display: inline-flex; + align-items: center; + gap: 0.25rem; + overflow: hidden; + + color: var(--vscode-errorForeground, red); +} diff --git a/client/cody-ui/src/chat/inputContext/ChatInputContext.tsx b/client/cody-ui/src/chat/inputContext/ChatInputContext.tsx index ec4c1d98bb9..adc287f1c09 100644 --- a/client/cody-ui/src/chat/inputContext/ChatInputContext.tsx +++ b/client/cody-ui/src/chat/inputContext/ChatInputContext.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react' -import { mdiFileDocumentOutline, mdiSourceRepository } from '@mdi/js' +import { mdiFileDocumentOutline, mdiSourceRepository, mdiFileExcel } from '@mdi/js' import classNames from 'classnames' import { ChatContextStatus } from '@sourcegraph/cody-shared/src/chat/context' @@ -19,9 +19,9 @@ export const ChatInputContext: React.FunctionComponent<{ [ contextStatus.codebase ? { - icon: mdiSourceRepository, + icon: contextStatus.connection ? mdiSourceRepository : mdiFileExcel, text: basename(contextStatus.codebase.replace(/^(github|gitlab)\.com\//, '')), - tooltip: contextStatus.codebase, + tooltip: contextStatus.connection ? contextStatus.codebase : 'connection failed', } : null, contextStatus.filePath @@ -32,12 +32,14 @@ export const ChatInputContext: React.FunctionComponent<{ } : null, ].filter(isDefined), - [contextStatus.codebase, contextStatus.filePath] + [contextStatus.codebase, contextStatus.connection, contextStatus.filePath] ) return (
-

{items.length > 0 ? 'Context' : 'No context'}

+

+ {contextStatus.mode && contextStatus.connection ? 'Embeddings' : 'Keyword'} +

{items.length > 0 && (
    {items.map(({ icon, text, tooltip }, index) => ( @@ -56,7 +58,7 @@ const ContextItem: React.FunctionComponent<{ icon: string; text: string; tooltip tooltip, as: Tag, }) => ( - + {text} diff --git a/client/cody/src/chat/ChatViewProvider.ts b/client/cody/src/chat/ChatViewProvider.ts index 38ddbfdc665..31581ad32c5 100644 --- a/client/cody/src/chat/ChatViewProvider.ts +++ b/client/cody/src/chat/ChatViewProvider.ts @@ -37,7 +37,7 @@ export async function isValidLogin( type Config = Pick< ConfigurationWithAccessToken, - 'codebase' | 'serverEndpoint' | 'debug' | 'customHeaders' | 'accessToken' + 'codebase' | 'serverEndpoint' | 'debug' | 'customHeaders' | 'accessToken' | 'useContext' > export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disposable { @@ -318,6 +318,8 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp void this.webview?.postMessage({ type: 'contextStatus', contextStatus: { + mode: this.config.useContext, + connection: this.codebaseContext.checkEmbeddingsConnection(), codebase: this.config.codebase, filePath: editorContext ? vscode.workspace.asRelativePath(editorContext.filePath) : undefined, }, diff --git a/client/cody/src/external-services.ts b/client/cody/src/external-services.ts index b17738720e7..19e7c17940f 100644 --- a/client/cody/src/external-services.ts +++ b/client/cody/src/external-services.ts @@ -1,3 +1,5 @@ +import { window } from 'vscode' + import { ChatClient } from '@sourcegraph/cody-shared/src/chat/chat' import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context' import { ConfigurationWithAccessToken } from '@sourcegraph/cody-shared/src/configuration' @@ -41,6 +43,7 @@ export async function configureExternalServices( `Cody could not find the '${initialConfig.codebase}' repository on your Sourcegraph instance.\n` + 'Please check that the repository exists and is entered correctly in the cody.codebase setting.' console.error(errorMessage) + void window.showErrorMessage(errorMessage) } const embeddingsSearch = repoId && !isError(repoId) ? new SourcegraphEmbeddingsSearchClient(client, repoId) : null diff --git a/client/cody/src/main.ts b/client/cody/src/main.ts index 0c33ee5f94b..b2fb9ae3501 100644 --- a/client/cody/src/main.ts +++ b/client/cody/src/main.ts @@ -54,9 +54,19 @@ export async function start(context: vscode.ExtensionContext): Promise { - if (event.affectsConfiguration('cody') || event.affectsConfiguration('sourcegraph')) { + if (event.affectsConfiguration('cody')) { onConfigurationChange(await getFullConfig()) } + if (event.affectsConfiguration('cody.codebase')) { + const action = await vscode.window.showInformationMessage( + 'You must reload VS Code for Cody to pick up your new codebase.', + 'Reload VS Code', + 'Close' + ) + if (action === 'Reload VS Code') { + void vscode.commands.executeCommand('workbench.action.reloadWindow') + } + } }) ) @@ -161,13 +171,6 @@ const register = async ( await secretStorage.delete(CODY_ACCESS_TOKEN_SECRET) logEvent('CodyVSCodeExtension:codyDeleteAccessToken:clicked') }), - // TOS - vscode.commands.registerCommand('cody.accept-tos', version => - localStorage.set('cody.tos-version-accepted', version) - ), - vscode.commands.registerCommand('cody.get-accepted-tos-version', () => - localStorage.get('cody.tos-version-accepted') - ), // Commands vscode.commands.registerCommand('cody.focus', () => vscode.commands.executeCommand('cody.chat.focus')), vscode.commands.registerCommand('cody.settings', () => chatProvider.setWebviewView('settings')), diff --git a/client/cody/webviews/App.css b/client/cody/webviews/App.css index 416bd1c0e28..c9ec35cfa51 100644 --- a/client/cody/webviews/App.css +++ b/client/cody/webviews/App.css @@ -24,4 +24,28 @@ button { flex-direction: column; overflow: auto; flex: 1; -} \ No newline at end of file +} + +.error { + flex-direction: row; + display: flex; + margin: 0.5em; + padding: 1rem; + color: var(--vscode-input-foreground); + background-color: var(--vscode-inputValidation-errorBackground); + border: 2px solid var(--vscode-inputValidation-errorBorder); + justify-content: space-between; + align-items: center; + min-height: 2rem; + position: relative; +} + +.close-btn { + position: absolute; + top: 0; + right: 0; + background: none; + border: none; + color: var(--vscode-input-foreground); + cursor: pointer; +} diff --git a/client/cody/webviews/App.tsx b/client/cody/webviews/App.tsx index 6dd2d97b16b..c7f4446cac9 100644 --- a/client/cody/webviews/App.tsx +++ b/client/cody/webviews/App.tsx @@ -28,6 +28,7 @@ export const App: React.FunctionComponent<{ vscodeAPI: VSCodeWrapper }> = ({ vsc const [inputHistory, setInputHistory] = useState([]) const [userHistory, setUserHistory] = useState(null) const [contextStatus, setContextStatus] = useState(null) + const [errorMessage, setErrorMessage] = useState('') useEffect(() => { vscodeAPI.onMessage(message => { @@ -65,6 +66,16 @@ export const App: React.FunctionComponent<{ vscodeAPI: VSCodeWrapper }> = ({ vsc break case 'contextStatus': setContextStatus(message.contextStatus) + if (message.contextStatus.mode !== 'keyword' && !message.contextStatus?.codebase) { + setErrorMessage( + 'Codebase is missing. A codebase must be provided via the cody.codebase setting to enable embeddings. Failling back to local keyword search for context.' + ) + } + if (message.contextStatus?.codebase && !message.contextStatus?.connection) { + setErrorMessage( + 'Codebase connection failed. Please make sure the codebase in your cody.codebase setting is correct and exists in your Sourcegraph instance. Falling back to local keyword search for context.' + ) + } break case 'view': setView(message.messages) @@ -102,7 +113,7 @@ export const App: React.FunctionComponent<{ vscodeAPI: VSCodeWrapper }> = ({ vsc {view === 'login' && ( )} - {view && view !== 'login' && } + {view !== 'login' && } {view === 'debug' && config?.debug && } {view === 'history' && ( = ({ vsc {view === 'settings' && ( )} + {view === 'chat' && errorMessage && ( +
    + Error: {errorMessage} + +
    + )} {view === 'chat' && (