Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Prompts & Commands list UI #5438

Merged
merged 11 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/shared/src/misc/rpc/webviewAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export interface PromptsResult {
* `undefined` means that commands should not be shown at all (not even as an empty
* list). Builtin and custom commands are deprecated in favor of the Prompt Library.
*/
commands: CodyCommand[]
commands: CodyCommand[] | undefined

/** The original query used to fetch this result. */
query: string
Expand Down
1 change: 1 addition & 0 deletions lib/shared/src/sourcegraph-api/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ export interface Prompt {
nameWithOwner: string
owner: {
namespaceName: string
displayName?: string | null
}
description?: string
draft: boolean
Expand Down
8 changes: 7 additions & 1 deletion vscode/test/e2e/command-core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,13 @@ test.extend<ExpectedV2Events>({
'cody.auth:connected',
'cody.command.explain:executed',
],
})('Explain Command from Prompts Tab', async ({ page, sidebar }) => {
})('Explain Command from Prompts Tab', async ({ page, sidebar, server }) => {
server.onGraphQl('ViewerPrompts').replyJson({
data: {
prompts: { nodes: [] },
},
})

// Sign into Cody
await sidebarSignin(page, sidebar)

Expand Down
7 changes: 5 additions & 2 deletions vscode/webviews/AppWrapperForTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import { Observable } from 'observable-fns'
import { type ComponentProps, type FunctionComponent, type ReactNode, useMemo } from 'react'
import { URI } from 'vscode-uri'
import { COMMON_WRAPPERS } from './AppWrapper'
import { FIXTURE_COMMANDS, makePromptsAPIWithData } from './components/promptList/fixtures'
import { FIXTURE_PROMPTS } from './components/promptSelectField/fixtures'
import {
FIXTURE_COMMANDS,
FIXTURE_PROMPTS,
makePromptsAPIWithData,
} from './components/promptList/fixtures'
import { ComposedWrappers, type Wrapper } from './utils/composeWrappers'
import { TelemetryRecorderContext } from './utils/telemetry'
import { ConfigProvider } from './utils/useConfig'
Expand Down
2 changes: 2 additions & 0 deletions vscode/webviews/Chat.story.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CodyIDE } from '@sourcegraph/cody-shared'
import { ExtensionAPIProviderForTestsOnly, MOCK_API } from '@sourcegraph/prompt-editor'
import type { Meta, StoryObj } from '@storybook/react'
import { Observable } from 'observable-fns'
Expand All @@ -19,6 +20,7 @@ const meta: Meta<typeof Chat> = {
},
},
args: {
IDE: CodyIDE.VSCode,
transcript: FIXTURE_TRANSCRIPT.simple2,
messageInProgress: null,
chatEnabled: true,
Expand Down
36 changes: 34 additions & 2 deletions vscode/webviews/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ import { truncateTextStart } from '@sourcegraph/cody-shared/src/prompt/truncatio
import { CHAT_INPUT_TOKEN_BUDGET } from '@sourcegraph/cody-shared/src/token/constants'
import styles from './Chat.module.css'
import { WelcomeMessage } from './chat/components/WelcomeMessage'
import { useClientActionDispatcher } from './client/clientState'
import { ScrollDown } from './components/ScrollDown'
import type { View } from './tabs'
import { PromptList } from './components/promptList/PromptList'
import { onPromptSelectInPanel, onPromptSelectInPanelActionLabels } from './prompts/PromptsTab'
import { View } from './tabs'
import { useTelemetryRecorder } from './utils/telemetry'
import { useUserAccountInfo } from './utils/useConfig'

interface ChatboxProps {
IDE: CodyIDE
chatEnabled: boolean
messageInProgress: ChatMessage | null
transcript: ChatMessage[]
Expand All @@ -35,6 +39,7 @@ interface ChatboxProps {
}

export const Chat: React.FunctionComponent<React.PropsWithChildren<ChatboxProps>> = ({
IDE,
messageInProgress,
transcript,
vscodeAPI,
Expand Down Expand Up @@ -196,6 +201,19 @@ export const Chat: React.FunctionComponent<React.PropsWithChildren<ChatboxProps>
}
}, [])

const dispatchClientAction = useClientActionDispatcher()

const handleScrollDownClick = useCallback(() => {
// Scroll to the bottom instead of focus input for unsent message
// it's possible that we just want to scroll to the bottom in case of
// welcome message screen
if (transcript.length === 0) {
return
}

focusLastHumanMessageEditor()
}, [transcript])

return (
<>
{!chatEnabled && (
Expand All @@ -217,11 +235,25 @@ export const Chat: React.FunctionComponent<React.PropsWithChildren<ChatboxProps>
guardrails={guardrails}
smartApplyEnabled={smartApplyEnabled}
/>
{transcript.length === 0 && (
<PromptList
telemetryLocation="ChatTab"
showSearch={false}
showSwitchToPromptAction={true}
showInitialSelectedItem={false}
showCommandOrigins={true}
showPromptLibraryUnsupportedMessage={false}
className="tw-rounded-none tw-px-4 tw-flex-shrink-0"
onSelectActionLabels={onPromptSelectInPanelActionLabels}
onSwitchToPromptsTab={() => setView(View.Prompts)}
onSelect={item => onPromptSelectInPanel(item, setView, dispatchClientAction)}
/>
)}
{transcript.length === 0 && showWelcomeMessage && (
<WelcomeMessage IDE={userInfo.ide} setView={setView} />
)}
{scrollableParent && (
<ScrollDown scrollableParent={scrollableParent} onClick={focusLastHumanMessageEditor} />
<ScrollDown scrollableParent={scrollableParent} onClick={handleScrollDownClick} />
)}
</>
)
Expand Down
5 changes: 4 additions & 1 deletion vscode/webviews/CodyPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const CodyPanel: FunctionComponent<
<TabContainer value={view} ref={tabContainerRef}>
{view === View.Chat && (
<Chat
IDE={config.agentIDE || CodyIDE.VSCode}
chatEnabled={chatEnabled}
messageInProgress={messageInProgress}
transcript={transcript}
Expand All @@ -104,7 +105,9 @@ export const CodyPanel: FunctionComponent<
userHistory={userHistory}
/>
)}
{view === View.Prompts && <PromptsTab setView={setView} />}
{view === View.Prompts && (
<PromptsTab setView={setView} IDE={config.agentIDE || CodyIDE.VSCode} />
)}
{view === View.Account && <AccountTab />}
{view === View.Settings && <SettingsTab />}
</TabContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,6 @@ export const HumanMessageEditor: FunctionComponent<{
[onGapClick]
)

const appendTextToEditor = useCallback((text: string): void => {
if (!editorRef.current) {
throw new Error('No editorRef')
}
editorRef.current.appendText(text)
}, [])

const onMentionClick = useCallback((): void => {
if (!editorRef.current) {
throw new Error('No editorRef')
Expand Down Expand Up @@ -254,9 +247,14 @@ export const HumanMessageEditor: FunctionComponent<{
if (isSent) {
return
}
if (editorRef.current) {
editorRef.current.appendText(appendTextToLastPromptEditor)
}

// Schedule append text task to the next tick to avoid collisions with
// initial text set (add initial mentions first then append text from prompt)
requestAnimationFrame(() => {
if (editorRef.current) {
editorRef.current.appendText(appendTextToLastPromptEditor)
}
})
}
},
[isSent]
Expand Down Expand Up @@ -329,7 +327,6 @@ export const HumanMessageEditor: FunctionComponent<{
submitState={submitState}
onGapClick={onGapClick}
focusEditor={focusEditor}
appendTextToEditor={appendTextToEditor}
hidden={!focused && isSent}
className={styles.toolbar}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import clsx from 'clsx'
import { type FunctionComponent, useCallback, useMemo } from 'react'
import type { UserAccountInfo } from '../../../../../../Chat'
import { ModelSelectField } from '../../../../../../components/modelSelectField/ModelSelectField'
import type { PromptOrDeprecatedCommand } from '../../../../../../components/promptList/PromptList'
import { PromptSelectField } from '../../../../../../components/promptSelectField/PromptSelectField'
import { useConfig } from '../../../../../../utils/useConfig'
import { AddContextButton } from './AddContextButton'
import { SubmitButton, type SubmitButtonState } from './SubmitButton'
Expand All @@ -27,7 +25,6 @@ export const Toolbar: FunctionComponent<{
onGapClick?: () => void

focusEditor?: () => void
appendTextToEditor: (text: string) => void

hidden?: boolean
className?: string
Expand All @@ -39,7 +36,6 @@ export const Toolbar: FunctionComponent<{
submitState,
onGapClick,
focusEditor,
appendTextToEditor,
hidden,
className,
}) => {
Expand Down Expand Up @@ -80,11 +76,6 @@ export const Toolbar: FunctionComponent<{
className="tw-opacity-60 focus-visible:tw-opacity-100 hover:tw-opacity-100 tw-mr-2"
/>
)}
<PromptSelectFieldToolbarItem
focusEditor={focusEditor}
appendTextToEditor={appendTextToEditor}
className="tw-ml-1 tw-mr-1"
/>
<ModelSelectFieldToolbarItem
userInfo={userInfo}
focusEditor={focusEditor}
Expand All @@ -102,22 +93,6 @@ export const Toolbar: FunctionComponent<{
)
}

const PromptSelectFieldToolbarItem: FunctionComponent<{
focusEditor?: () => void
appendTextToEditor: (text: string) => void
className?: string
}> = ({ focusEditor, appendTextToEditor, className }) => {
const onSelect = useCallback(
(item: PromptOrDeprecatedCommand) => {
appendTextToEditor(item.type === 'prompt' ? item.value.definition.text : item.value.prompt)
focusEditor?.()
},
[appendTextToEditor, focusEditor]
)

return <PromptSelectField onSelect={onSelect} onCloseByEscape={focusEditor} className={className} />
}

const ModelSelectFieldToolbarItem: FunctionComponent<{
userInfo: UserAccountInfo
focusEditor?: () => void
Expand Down
23 changes: 5 additions & 18 deletions vscode/webviews/chat/components/WelcomeMessage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
import { CodyIDE } from '@sourcegraph/cody-shared'
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, test, vi } from 'vitest'
import { describe, expect, test } from 'vitest'
import { AppWrapperForTest } from '../../AppWrapperForTest'
import { usePromptsQuery } from '../../components/promptList/usePromptsQuery'
import { FIXTURE_PROMPTS } from '../../components/promptSelectField/fixtures'
import { WelcomeMessage } from './WelcomeMessage'

vi.mock('../../components/promptList/usePromptsQuery')
vi.mocked(usePromptsQuery).mockReturnValue({
value: {
query: '',
commands: [],
prompts: { type: 'results', results: [FIXTURE_PROMPTS[0]] },
},
done: false,
error: null,
})

describe('WelcomeMessage', () => {
function openCollapsiblePanels(): void {
const closedPanelButtons = document.querySelectorAll('button[data-state="closed"]')
Expand All @@ -32,12 +19,12 @@ describe('WelcomeMessage', () => {

// Check common elements
expect(screen.getByText(/Chat Help/)).toBeInTheDocument()
expect(screen.getByText(FIXTURE_PROMPTS[0].name)).toBeInTheDocument()

// Check elements specific to CodyIDE.VSCode
expect(screen.getByText(/To add code context from an editor/)).toBeInTheDocument()
expect(screen.getByText(/Start a new chat using/)).toBeInTheDocument()
expect(screen.getByText(/Customize chat settings/)).toBeInTheDocument()
expect(screen.getByText(/Documentation/)).toBeInTheDocument()
expect(screen.getByText(/Help & Support/)).toBeInTheDocument()
})

test('renders for CodyIDE.JetBrains', () => {
Expand All @@ -48,11 +35,11 @@ describe('WelcomeMessage', () => {

// Check common elements
expect(screen.getByText(/Chat Help/)).toBeInTheDocument()
expect(screen.getByText(FIXTURE_PROMPTS[0].name)).toBeInTheDocument()

// Check elements specific to CodyIDE.JetBrains
expect(screen.queryByText(/To add code context from an editor/)).not.toBeInTheDocument()
expect(screen.queryByText(/Start a new chat using/)).not.toBeInTheDocument()
expect(screen.queryByText(/Customize chat settings/)).not.toBeInTheDocument()
expect(screen.getByText(/Documentation/)).toBeInTheDocument()
expect(screen.getByText(/Help & Support/)).toBeInTheDocument()
})
})
Loading
Loading