Skip to content

Commit

Permalink
Chat: Display Enhanced Context settings on first chat (#3547)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim Lucas <t@toolmantim.com>
  • Loading branch information
2 people authored and valerybugakov committed Apr 22, 2024
1 parent db41538 commit f02baf2
Show file tree
Hide file tree
Showing 24 changed files with 201 additions and 160 deletions.
2 changes: 2 additions & 0 deletions vscode/CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@ This is a log of all notable changes to Cody for VS Code. [Unreleased] changes a
- Edit: New `cody.edit.preInstruction` configuration option for adding custom instruction at the end of all your requests. [pull/3542](https://github.com/sourcegraph/cody/pull/3542)
- Edit: Add support for the new `cody.edit.preInstruction` setting. [pull/3542](https://github.com/sourcegraph/cody/pull/3542)
- Edit: Added telemetry to measure the persistence of edits in the document. [pull/3550](https://github.com/sourcegraph/cody/pull/3550)
- Chat: Added buttons in the chat input box for enabling/disabling Enhanced Context. [pull/3547](https://github.com/sourcegraph/cody/pull/3547)

### Fixed

Expand All @@ -19,6 +20,7 @@ This is a log of all notable changes to Cody for VS Code. [Unreleased] changes a
### Changed

- Autocomplete: Subsequent new lines are added to the singleline stop sequences. [pull/3549](https://github.com/sourcegraph/cody/pull/3549)
- Chat: The Enhanced Context Settings modal is opened by default for the first chat session. [pull/3547](https://github.com/sourcegraph/cody/pull/3547)
- Add information on which Cody tier is being used to analytics events. [pull/3508](https://github.com/sourcegraph/cody/pull/3508)

## [1.10.0]
Expand Down
19 changes: 6 additions & 13 deletions vscode/test/e2e/attribution.test.ts
@@ -1,8 +1,8 @@
import { type Frame, type FrameLocator, type Locator, type Page, expect } from '@playwright/test'
import { expect } from '@playwright/test'

import * as mockServer from '../fixtures/mock-server'

import { sidebarSignin } from './common'
import { createEmptyChatPanel, sidebarSignin } from './common'
import {
type DotcomUrlOverride,
type ExpectedEvents,
Expand Down Expand Up @@ -35,25 +35,18 @@ const test = baseTest

test('attribution search enabled in chat', async ({ page, sidebar, expectedEvents }) => {
await fetch(`${mockServer.SERVER_URL}/.test/attribution/enable`, { method: 'POST' })
const [chatFrame, chatInput] = await prepareChat2(page, sidebar)
await sidebarSignin(page, sidebar)
const [chatFrame, chatInput] = await createEmptyChatPanel(page)
await chatInput.fill('show me a code snippet')
await chatInput.press('Enter')
await expect(chatFrame.getByTestId('attribution-indicator')).toBeVisible()
})

test('attribution search disabled in chat', async ({ page, sidebar, expectedEvents }) => {
await fetch(`${mockServer.SERVER_URL}/.test/attribution/disable`, { method: 'POST' })
const [chatFrame, chatInput] = await prepareChat2(page, sidebar)
await sidebarSignin(page, sidebar)
const [chatFrame, chatInput] = await createEmptyChatPanel(page)
await chatInput.fill('show me a code snippet')
await chatInput.press('Enter')
await expect(chatFrame.getByTestId('attribution-indicator')).toBeHidden()
})

async function prepareChat2(page: Page, sidebar: Frame): Promise<[FrameLocator, Locator]> {
await sidebarSignin(page, sidebar)
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
// Chat webview iframe is the second and last frame (search is the first)
const chatFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatFrame.getByRole('textbox', { name: 'Chat message' })
return [chatFrame, chatInput]
}
31 changes: 10 additions & 21 deletions vscode/test/e2e/chat-atFile.test.ts
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test'

import { isWindows } from '@sourcegraph/cody-shared'

import { sidebarExplorer, sidebarSignin } from './common'
import { createEmptyChatPanel, sidebarExplorer, sidebarSignin } from './common'
import { type ExpectedEvents, test, withPlatformSlashes } from './helpers'

// See chat-atFile.test.md for the expected behavior for this feature.
Expand All @@ -24,12 +24,10 @@ test.extend<ExpectedEvents>({

await sidebarSignin(page, sidebar)

await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const [chatPanelFrame, chatInput] = await createEmptyChatPanel(page)

const chatPanelFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')

const chatInput = chatPanelFrame.getByRole('textbox', { name: 'Chat message' })
await chatInput.click()
await chatInput.dblclick()
await chatInput.focus()
await page.keyboard.type('@')
await expect(
chatPanelFrame.getByRole('heading', {
Expand Down Expand Up @@ -197,9 +195,8 @@ test.extend<ExpectedEvents>({

test('editing a chat message with @-mention', async ({ page, sidebar }) => {
await sidebarSignin(page, sidebar)
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const chatPanelFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatPanelFrame.getByRole('textbox', { name: 'Chat message' })

const [chatPanelFrame, chatInput] = await createEmptyChatPanel(page)

// Send a message with an @-mention.
await chatInput.fill('Explain @mj')
Expand Down Expand Up @@ -235,10 +232,8 @@ test('pressing Enter with @-mention menu open selects item, does not submit mess
sidebar,
}) => {
await sidebarSignin(page, sidebar)
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const chatPanelFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatPanelFrame.getByRole('textbox', { name: 'Chat message' })

const [chatPanelFrame, chatInput] = await createEmptyChatPanel(page)
await chatInput.fill('Explain @index.htm')
await expect(chatPanelFrame.getByRole('option', { name: 'index.html' })).toBeVisible()
await chatInput.press('Enter')
Expand All @@ -250,9 +245,7 @@ test('@-mention links in transcript message', async ({ page, sidebar }) => {
await sidebarSignin(page, sidebar)

// Open chat.
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const chatPanelFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatPanelFrame.getByRole('textbox', { name: 'Chat message' })
const [chatPanelFrame, chatInput] = await createEmptyChatPanel(page)

// Submit a message with an @-mention.
await chatInput.fill('Hello @buzz.ts')
Expand All @@ -273,9 +266,7 @@ test('@-mention file range', async ({ page, sidebar }) => {
await sidebarSignin(page, sidebar)

// Open chat.
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const chatPanelFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatPanelFrame.getByRole('textbox', { name: 'Chat message' })
const [chatPanelFrame, chatInput] = await createEmptyChatPanel(page)

// Type a file with range.
await chatInput.fill('@buzz.ts:2-4')
Expand Down Expand Up @@ -303,9 +294,7 @@ test.extend<ExpectedEvents>({
await sidebarSignin(page, sidebar)

// Open chat.
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const chatPanelFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatPanelFrame.getByRole('textbox', { name: 'Chat message' })
const [chatPanelFrame, chatInput] = await createEmptyChatPanel(page)

// Open the buzz.ts file so that VS Code starts to populate symbols.
await sidebarExplorer(page).click()
Expand Down
7 changes: 2 additions & 5 deletions vscode/test/e2e/chat-edits.test.ts
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test'

import { isMacOS } from '@sourcegraph/cody-shared'
import { sidebarSignin } from './common'
import { createEmptyChatPanel, sidebarSignin } from './common'
import { type ExpectedEvents, test, withPlatformSlashes } from './helpers'

const osKey = isMacOS() ? 'Meta' : 'Control'
Expand All @@ -21,10 +21,7 @@ test.extend<ExpectedEvents>({
})('editing follow-up messages in chat view', async ({ page, sidebar }) => {
await sidebarSignin(page, sidebar)

await page.getByRole('button', { name: 'New Chat', exact: true }).click()

const chatFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatFrame.getByRole('textbox', { name: 'Chat message' })
const [chatFrame, chatInput] = await createEmptyChatPanel(page)

// Chat Action Buttons - above the input box
const editLastMessageButton = chatFrame.getByRole('button', { name: /^Edit Last Message / })
Expand Down
7 changes: 2 additions & 5 deletions vscode/test/e2e/chat-history.test.ts
@@ -1,6 +1,6 @@
import { expect } from '@playwright/test'

import { sidebarSignin } from './common'
import { createEmptyChatPanel, sidebarSignin } from './common'
import { type ExpectedEvents, test } from './helpers'

test.extend<ExpectedEvents>({
Expand All @@ -25,11 +25,8 @@ test.extend<ExpectedEvents>({
const heyTreeItem = page.getByRole('treeitem', { name: 'Hey' })
const holaTreeItem = page.getByRole('treeitem', { name: 'Hola' })

await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const [chatPanelFrame, chatInput] = await createEmptyChatPanel(page)

const chatPanelFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')

const chatInput = chatPanelFrame.getByRole('textbox', { name: 'Chat message' })
await chatInput.fill('Hey')
await chatInput.press('Enter')

Expand Down
18 changes: 5 additions & 13 deletions vscode/test/e2e/chat-input.test.ts
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test'

import * as mockServer from '../fixtures/mock-server'
import { sidebarExplorer, sidebarSignin } from './common'
import { createEmptyChatPanel, sidebarExplorer, sidebarSignin } from './common'
import { type DotcomUrlOverride, type ExpectedEvents, test } from './helpers'

test.extend<ExpectedEvents>({
Expand All @@ -19,10 +19,7 @@ test.extend<ExpectedEvents>({
})('editing messages in the chat input', async ({ page, sidebar }) => {
await sidebarSignin(page, sidebar)

await page.getByRole('button', { name: 'New Chat', exact: true }).click()

const chatFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatFrame.getByRole('textbox', { name: 'Chat message' })
const [_chatFrame, chatInput] = await createEmptyChatPanel(page)

// Test that empty chat messages cannot be submitted.
await chatInput.fill(' ')
Expand Down Expand Up @@ -68,14 +65,10 @@ test('chat input focus', async ({ page, sidebar }) => {
// when we submit a question later as the question will be streamed to this panel
// directly instead of opening a new one.
await page.click('.badge[aria-label="Cody"]')
await page.getByRole('button', { name: 'New Chat', exact: true }).hover()
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const [chatPanel, chatInput] = await createEmptyChatPanel(page)
await page.click('.badge[aria-label="Cody"]')
await page.getByRole('tab', { name: 'buzz.ts' }).dblclick()

const chatPanel = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatPanel.getByRole('textbox', { name: 'Chat message' })

// Submit a new chat question from the command menu.
await page.getByLabel(/Commands \(/).hover()
await page.getByLabel(/Commands \(/).click()
Expand Down Expand Up @@ -113,9 +106,8 @@ test.extend<DotcomUrlOverride>({ dotcomUrl: mockServer.SERVER_URL })(
async ({ page, sidebar }) => {
await sidebarSignin(page, sidebar)

await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const chatFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatFrame.getByRole('textbox', { name: 'Chat message' })
const [chatFrame, chatInput] = await createEmptyChatPanel(page)

const modelSelect = chatFrame.getByRole('combobox', { name: 'Choose a model' })

// Model selector is initially enabled.
Expand Down
22 changes: 8 additions & 14 deletions vscode/test/e2e/chat-rateLimit.test.ts
@@ -1,8 +1,8 @@
import { type Frame, type FrameLocator, type Locator, type Page, expect } from '@playwright/test'
import { expect } from '@playwright/test'

import * as mockServer from '../fixtures/mock-server'

import { sidebarSignin } from './common'
import { createEmptyChatPanel, sidebarSignin } from './common'
import { type DotcomUrlOverride, type ExpectedEvents, test as baseTest } from './helpers'

const test = baseTest.extend<DotcomUrlOverride>({ dotcomUrl: mockServer.SERVER_URL })
Expand All @@ -25,7 +25,8 @@ test.extend<ExpectedEvents>({
method: 'POST',
})

const [chatFrame, chatInput] = await prepareChat(page, sidebar)
await sidebarSignin(page, sidebar)
const [chatFrame, chatInput] = await createEmptyChatPanel(page)
await chatInput.fill('test message')
await chatInput.press('Enter')

Expand All @@ -50,7 +51,8 @@ test.extend<ExpectedEvents>({
method: 'POST',
})

const [chatFrame, chatInput] = await prepareChat(page, sidebar)
await sidebarSignin(page, sidebar)
const [chatFrame, chatInput] = await createEmptyChatPanel(page)
await chatInput.fill('test message')
await chatInput.press('Enter')

Expand All @@ -75,19 +77,11 @@ test.extend<ExpectedEvents>({
method: 'POST',
})

const [chatFrame, chatInput] = await prepareChat(page, sidebar)
await sidebarSignin(page, sidebar)
const [chatFrame, chatInput] = await createEmptyChatPanel(page)
await chatInput.fill('test message')
await chatInput.press('Enter')

await expect(chatFrame.getByRole('heading', { name: 'Unable to Send Message' })).toBeVisible()
await expect(chatFrame.getByRole('button', { name: 'Learn More' })).toBeVisible()
})

export async function prepareChat(page: Page, sidebar: Frame): Promise<[FrameLocator, Locator]> {
await sidebarSignin(page, sidebar)
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
// Chat webview iframe is the second and last frame (search is the first)
const chatFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatFrame.getByRole('textbox', { name: 'Chat message' })
return [chatFrame, chatInput]
}
6 changes: 2 additions & 4 deletions vscode/test/e2e/cody-ignore.test.ts
@@ -1,6 +1,6 @@
import path from 'path'
import { expect } from '@playwright/test'
import { sidebarExplorer, sidebarSignin } from './common'
import { createEmptyChatPanel, sidebarExplorer, sidebarSignin } from './common'
import { type ExpectedEvents, test } from './helpers'

/**
Expand Down Expand Up @@ -46,11 +46,9 @@ test.extend<ExpectedEvents>({
await page.click('.badge[aria-label="Cody"]')

// Start new chat
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const [chatPanel, chatInput] = await createEmptyChatPanel(page)

/* TEST: Chat Context - Ignored file do not show up with context */
const chatPanel = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const chatInput = chatPanel.getByRole('textbox', { name: 'Chat message' })
await chatInput.focus()
await chatInput.fill('Ignore me')
await chatInput.press('Enter')
Expand Down
15 changes: 15 additions & 0 deletions vscode/test/e2e/common.ts
Expand Up @@ -41,3 +41,18 @@ async function disableNotifications(page: Page): Promise<void> {
export function getChatPanel(page: Page): FrameLocator {
return page.frameLocator('iframe.webview').frameLocator('iframe[title="New Chat"]')
}

/**
* Create and open a new chat panel, and close the enhanced context settings window.
* Returns the chat panel frame locator.
*/
export async function createEmptyChatPanel(page: Page): Promise<[FrameLocator, Locator]> {
await page.getByRole('button', { name: 'New Chat', exact: true }).click()
const chatFrame = page.frameLocator('iframe.webview').last().frameLocator('iframe')
const enhancedContextCheckbox = chatFrame.locator('#enhanced-context-checkbox')
await expect(enhancedContextCheckbox).toBeFocused()
await page.keyboard.press('Escape')
await expect(enhancedContextCheckbox).not.toBeVisible()
const chatInput = chatFrame.getByRole('textbox', { name: 'Chat message' })
return [chatFrame, chatInput]
}
23 changes: 16 additions & 7 deletions vscode/test/e2e/context-settings.test.ts
Expand Up @@ -22,17 +22,14 @@ test.extend<ExpectedEvents>({

await sidebarSignin(page, sidebar)
const chatFrame = await newChat(page)
const contextSettingsButton = chatFrame.getByTitle('Configure Enhanced Context')
await contextSettingsButton.focus()

await page.keyboard.press('Space')
// Opening the enhanced context settings should focus the checkbox for toggling it.
const enhancedContextCheckbox = chatFrame.locator('#enhanced-context-checkbox')
await expect(enhancedContextCheckbox).toBeFocused()

// Enhanced context should be enabled by default.
await expect(enhancedContextCheckbox).toBeChecked()
await page.keyboard.press('Space')
await page.keyboard.press('Space') // Disable enhanced context
// The keyboard should toggle the checkbox, but not dismiss the popup.
await expect(enhancedContextCheckbox).not.toBeChecked()
await expect(enhancedContextCheckbox).toBeVisible()
Expand All @@ -41,8 +38,21 @@ test.extend<ExpectedEvents>({
await page.keyboard.press('Escape')
// Closing the enhanced context settings should close the dialog...
await expect(enhancedContextCheckbox).not.toBeVisible()
// ... and focus the button which re-opens it.
await expect(contextSettingsButton.and(page.locator(':focus'))).toBeVisible()
// ... and the focus is moved to the chat input on close.
const contextSettingsButton = chatFrame.getByTitle('Configure Enhanced Context')
await expect(contextSettingsButton.and(page.locator(':focus'))).not.toBeVisible()
const chatInput = chatFrame.getByRole('textbox', { name: 'Chat message' })
await expect(chatInput).toBeFocused()

// Tab should move the focus to the Enhanced Context Toggle button
await chatInput.press('Tab')
await expect(chatFrame.getByTitle('Enable Enhanced Context')).toBeFocused()

// Enter/Space key should toggle the setting
await page.keyboard.press('Space') // From disabled to enabled
await expect(chatFrame.getByTitle('Disable Enhanced Context')).toBeFocused()
await page.keyboard.press('Enter') // From enabled to disabled
await expect(chatFrame.getByTitle('Enable Enhanced Context')).toBeFocused()
})

test('enterprise context selector can pick repos', async ({ page, sidebar, server, expectedEvents }) => {
Expand Down Expand Up @@ -78,7 +88,6 @@ test('enterprise context selector can pick repos', async ({ page, sidebar, serve
const chatFrame = await newChat(page)

// Because there are no repositories in the workspace, none should be selected by default.
await chatFrame.getByTitle('Configure Enhanced Context').click()
await expect(chatFrame.getByText('No repositories selected')).toBeVisible()

// Choosing a repository should open the repository picker.
Expand Down

0 comments on commit f02baf2

Please sign in to comment.