mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:12:02 +00:00
Add enterprise UIs for cody enablement (#53360)
This PR implements https://www.figma.com/file/ZpFPw0zZhVkCjrp68hPmA6/Enterprise-Cody-web-onboarding?type=design&node-id=0-1&t=sHIWi49gJ0ionqkp-0 for Cody enablement and simplifies the feature flags used to determine when which of these should show. We now simply look at if cody is enabled instead of multiple feature flags. Closes https://github.com/sourcegraph/sourcegraph/issues/53023 Closes https://github.com/sourcegraph/sourcegraph/issues/52546 ## Test plan Manually verified. --------- Co-authored-by: Rob Rhyne <rob@sourcegraph.com>
This commit is contained in:
parent
e35a52d8d7
commit
da262c68b6
@ -41,8 +41,10 @@ interface ChatProps extends ChatClassNames {
|
||||
setSuggestions?: (suggestions: undefined | []) => void
|
||||
needsEmailVerification?: boolean
|
||||
needsEmailVerificationNotice?: React.FunctionComponent
|
||||
codyNotEnabledNotice?: React.FunctionComponent
|
||||
abortMessageInProgressComponent?: React.FunctionComponent<{ onAbortMessageInProgress: () => void }>
|
||||
onAbortMessageInProgress?: () => void
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
|
||||
interface ChatClassNames extends TranscriptItemClassNames {
|
||||
@ -127,11 +129,13 @@ export const Chat: React.FunctionComponent<ChatProps> = ({
|
||||
suggestions,
|
||||
setSuggestions,
|
||||
needsEmailVerification = false,
|
||||
codyNotEnabledNotice: CodyNotEnabledNotice,
|
||||
needsEmailVerificationNotice: NeedsEmailVerificationNotice,
|
||||
contextStatusComponent: ContextStatusComponent,
|
||||
contextStatusComponentProps = {},
|
||||
abortMessageInProgressComponent,
|
||||
onAbortMessageInProgress,
|
||||
isCodyEnabled,
|
||||
}) => {
|
||||
const [inputRows, setInputRows] = useState(5)
|
||||
const [historyIndex, setHistoryIndex] = useState(inputHistory.length)
|
||||
@ -232,7 +236,11 @@ export const Chat: React.FunctionComponent<ChatProps> = ({
|
||||
|
||||
return (
|
||||
<div className={classNames(className, styles.innerContainer)}>
|
||||
{needsEmailVerification && NeedsEmailVerificationNotice ? (
|
||||
{!isCodyEnabled && CodyNotEnabledNotice ? (
|
||||
<div className="flex-1">
|
||||
<CodyNotEnabledNotice />
|
||||
</div>
|
||||
) : needsEmailVerification && NeedsEmailVerificationNotice ? (
|
||||
<div className="flex-1">
|
||||
<NeedsEmailVerificationNotice />
|
||||
</div>
|
||||
@ -281,17 +289,17 @@ export const Chat: React.FunctionComponent<ChatProps> = ({
|
||||
<TextArea
|
||||
className={classNames(styles.chatInput, chatInputClassName)}
|
||||
rows={inputRows}
|
||||
value={formInput}
|
||||
value={isCodyEnabled ? formInput : 'Cody is disabled on this instance'}
|
||||
autoFocus={true}
|
||||
required={true}
|
||||
disabled={needsEmailVerification}
|
||||
disabled={needsEmailVerification || !isCodyEnabled}
|
||||
onInput={onChatInput}
|
||||
onKeyDown={onChatKeyDown}
|
||||
/>
|
||||
<SubmitButton
|
||||
className={styles.submitButton}
|
||||
onClick={onChatSubmit}
|
||||
disabled={!!messageInProgress || needsEmailVerification}
|
||||
disabled={!!messageInProgress || needsEmailVerification || !isCodyEnabled}
|
||||
/>
|
||||
</div>
|
||||
{ContextStatusComponent ? (
|
||||
|
||||
@ -106,6 +106,7 @@ export const App: React.FunctionComponent = () => {
|
||||
setFormInput={setFormInput}
|
||||
inputHistory={inputHistory}
|
||||
setInputHistory={setInputHistory}
|
||||
isCodyEnabled={true}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</>
|
||||
|
||||
@ -49,6 +49,7 @@ export const Chat: React.FunctionComponent<
|
||||
transcriptActionClassName={styles.transcriptAction}
|
||||
inputRowClassName={styles.inputRow}
|
||||
chatInputClassName={styles.chatInput}
|
||||
isCodyEnabled={true}
|
||||
/>
|
||||
)
|
||||
|
||||
|
||||
@ -114,6 +114,10 @@ export const Chat: React.FunctionComponent<React.PropsWithChildren<ChatboxProps>
|
||||
copyButtonOnSubmit={onCopyBtnClick}
|
||||
suggestions={suggestions}
|
||||
setSuggestions={setSuggestions}
|
||||
// TODO: We should fetch this from the server and pass a pretty component
|
||||
// down here to render cody is disabled on the instance nicely.
|
||||
isCodyEnabled={true}
|
||||
codyNotEnabledNotice={undefined}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
2
client/web/BUILD.bazel
generated
2
client/web/BUILD.bazel
generated
@ -228,6 +228,7 @@ ts_project(
|
||||
"src/cody/components/ScopeSelector/ScopeSelector.tsx",
|
||||
"src/cody/components/ScopeSelector/backend.ts",
|
||||
"src/cody/components/ScopeSelector/index.tsx",
|
||||
"src/cody/isCodyEnabled.tsx",
|
||||
"src/cody/search/CodySearchPage.tsx",
|
||||
"src/cody/search/api.ts",
|
||||
"src/cody/search/translateToQuery.ts",
|
||||
@ -236,7 +237,6 @@ ts_project(
|
||||
"src/cody/sidebar/index.tsx",
|
||||
"src/cody/sidebar/useSidebarSize.tsx",
|
||||
"src/cody/useCodyChat.tsx",
|
||||
"src/cody/useIsCodyEnabled.tsx",
|
||||
"src/cody/useSizebarSize.tsx",
|
||||
"src/cody/widgets/CodyRecipesWidget.tsx",
|
||||
"src/cody/widgets/components/Recipe.tsx",
|
||||
|
||||
@ -147,3 +147,8 @@
|
||||
.mobile-history {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.cta-content {
|
||||
background-color: var(--color-bg-1);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
@ -18,7 +18,6 @@ import { CodyLogo } from '@sourcegraph/cody-ui/src/icons/CodyLogo'
|
||||
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
|
||||
import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary'
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Icon,
|
||||
@ -33,6 +32,7 @@ import {
|
||||
H4,
|
||||
H3,
|
||||
Text,
|
||||
ButtonLink,
|
||||
Tooltip,
|
||||
} from '@sourcegraph/wildcard'
|
||||
|
||||
@ -45,6 +45,7 @@ import { EventName } from '../../util/constants'
|
||||
import { ChatUI } from '../components/ChatUI'
|
||||
import { CodyMarketingPage } from '../components/CodyMarketingPage'
|
||||
import { HistoryList } from '../components/HistoryList'
|
||||
import { isCodyEnabled } from '../isCodyEnabled'
|
||||
import { CodyChatStore, useCodyChat } from '../useCodyChat'
|
||||
|
||||
import { CodyColorIcon } from './CodyPageIcon'
|
||||
@ -52,6 +53,7 @@ import { CodyColorIcon } from './CodyPageIcon'
|
||||
import styles from './CodyChatPage.module.scss'
|
||||
|
||||
interface CodyChatPageProps {
|
||||
isSourcegraphDotCom: boolean
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
isSourcegraphApp: boolean
|
||||
context: Pick<SourcegraphContext, 'authProviders'>
|
||||
@ -94,6 +96,7 @@ const onTranscriptHistoryLoad = (
|
||||
export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
authenticatedUser,
|
||||
context,
|
||||
isSourcegraphDotCom,
|
||||
isSourcegraphApp,
|
||||
}) => {
|
||||
const { pathname } = useLocation()
|
||||
@ -106,7 +109,6 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
const {
|
||||
initializeNewChat,
|
||||
clearHistory,
|
||||
isCodyEnabled,
|
||||
loaded,
|
||||
transcript,
|
||||
transcriptHistory,
|
||||
@ -146,22 +148,40 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
return null
|
||||
}
|
||||
|
||||
if (!authenticatedUser) {
|
||||
return <CodyMarketingPage context={context} />
|
||||
}
|
||||
|
||||
if (!isCodyEnabled.chat) {
|
||||
return (
|
||||
<Page className="overflow-hidden">
|
||||
<PageTitle title="Cody AI Chat" />
|
||||
<Alert variant="info">Cody is not enabled. Please contact your site admin to enable Cody.</Alert>
|
||||
</Page>
|
||||
)
|
||||
if (!authenticatedUser || !isCodyEnabled()) {
|
||||
return <CodyMarketingPage isSourcegraphDotCom={isSourcegraphDotCom} context={context} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Page className={classNames('d-flex flex-column', styles.page)}>
|
||||
<PageTitle title="Cody AI Chat" />
|
||||
{!isSourcegraphDotCom && !isCTADismissed && (
|
||||
<MarketingBlock
|
||||
wrapperClassName="mb-5"
|
||||
contentClassName={classNames(styles.ctaWrapper, styles.ctaContent)}
|
||||
>
|
||||
<div className="d-flex">
|
||||
<CodyCTAIcon className="flex-shrink-0" />
|
||||
<div className="ml-3">
|
||||
<H3>Cody is more powerful in your IDE</H3>
|
||||
<Text>
|
||||
Cody adds powerful AI assistant functionality like inline completions and assist, and
|
||||
powerful recipes to help you understand codebases and generate and fix code more
|
||||
accurately.
|
||||
</Text>
|
||||
<ButtonLink variant="primary" to="/help/cody#get-cody">
|
||||
View editor extensions →
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
<Icon
|
||||
svgPath={mdiClose}
|
||||
aria-label="Close Cody editor extensions CTA"
|
||||
className={classNames(styles.closeButton, 'position-absolute m-0')}
|
||||
onClick={onCTADismiss}
|
||||
/>
|
||||
</MarketingBlock>
|
||||
)}
|
||||
<PageHeader
|
||||
actions={
|
||||
<div className="d-flex">
|
||||
@ -173,8 +193,14 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
}
|
||||
description={
|
||||
<>
|
||||
Cody answers code questions and writes code for you by reading your entire codebase and the code
|
||||
graph.
|
||||
Cody answers code questions and writes code for you by leveraging your entire codebase and the
|
||||
code graph.
|
||||
{!isSourcegraphDotCom && isCTADismissed && (
|
||||
<>
|
||||
{' '}
|
||||
<Link to="/help/cody#get-cody">Cody is more powerful in the IDE</Link>.
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
className={styles.pageHeader}
|
||||
@ -192,7 +218,6 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
</PageHeader.Breadcrumb>
|
||||
</PageHeader.Heading>
|
||||
</PageHeader>
|
||||
|
||||
{/* Page content */}
|
||||
<div className={classNames('row flex-1 overflow-hidden', styles.pageWrapper)}>
|
||||
<div className={classNames('col-md-3', styles.sidebarWrapper)}>
|
||||
@ -230,7 +255,8 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
deleteHistoryItem={deleteHistoryItem}
|
||||
/>
|
||||
</div>
|
||||
{!isCTADismissed &&
|
||||
{isSourcegraphDotCom &&
|
||||
!isCTADismissed &&
|
||||
(showVSCodeCTA ? (
|
||||
<MarketingBlock
|
||||
wrapperClassName="d-flex"
|
||||
@ -426,3 +452,38 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
const CodyCTAIcon: React.FunctionComponent<{ className?: string }> = ({ className }) => (
|
||||
<svg
|
||||
width="146"
|
||||
height="112"
|
||||
viewBox="0 0 146 112"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<rect x="24" y="24" width="98" height="64" rx="6" fill="#E8D1FF" />
|
||||
<path
|
||||
d="M56.25 65.3333C56.25 65.687 56.3817 66.0261 56.6161 66.2761C56.8505 66.5262 57.1685 66.6667 57.5 66.6667H60V69.3333H56.875C56.1875 69.3333 55 68.7333 55 68C55 68.7333 53.8125 69.3333 53.125 69.3333H50V66.6667H52.5C52.8315 66.6667 53.1495 66.5262 53.3839 66.2761C53.6183 66.0261 53.75 65.687 53.75 65.3333V46.6667C53.75 46.313 53.6183 45.9739 53.3839 45.7239C53.1495 45.4738 52.8315 45.3333 52.5 45.3333H50V42.6667H53.125C53.8125 42.6667 55 43.2667 55 44C55 43.2667 56.1875 42.6667 56.875 42.6667H60V45.3333H57.5C57.1685 45.3333 56.8505 45.4738 56.6161 45.7239C56.3817 45.9739 56.25 46.313 56.25 46.6667V65.3333Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M88.9095 45C90.3781 45 91.5686 46.1789 91.5686 47.6331V52.314C91.5686 53.7682 90.3781 54.9471 88.9095 54.9471C87.4409 54.9471 86.2504 53.7682 86.2504 52.314V47.6331C86.2504 46.1789 87.4409 45 88.9095 45Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M72.068 51.1437C72.068 49.6895 73.2585 48.5106 74.7271 48.5106H79.4544C80.923 48.5106 82.1135 49.6895 82.1135 51.1437C82.1135 52.5978 80.923 53.7767 79.4544 53.7767H74.7271C73.2585 53.7767 72.068 52.5978 72.068 51.1437Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M95.2643 58.8091C96.2107 59.6994 96.2491 61.1808 95.35 62.1179L94.5134 62.99C87.9666 69.8138 76.9295 69.6438 70.6002 62.6216C69.731 61.6572 69.8159 60.1777 70.7898 59.317C71.7637 58.4563 73.2579 58.5403 74.1271 59.5047C78.6157 64.4848 86.4432 64.6053 91.0861 59.7659L91.9227 58.8939C92.8218 57.9568 94.3179 57.9188 95.2643 58.8091Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@ -152,3 +152,7 @@
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.not-enabled-block {
|
||||
background-color: var(--body-bg);
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import { Button, Icon, TextArea, Link, Tooltip, Alert, Text, H2 } from '@sourceg
|
||||
|
||||
import { eventLogger } from '../../../tracking/eventLogger'
|
||||
import { CodyPageIcon } from '../../chat/CodyPageIcon'
|
||||
import { isCodyEnabled, isEmailVerificationNeededForCody } from '../../isCodyEnabled'
|
||||
import { useCodySidebar } from '../../sidebar/Provider'
|
||||
import { CodyChatStore } from '../../useCodyChat'
|
||||
import { ScopeSelector } from '../ScopeSelector'
|
||||
@ -40,7 +41,6 @@ export const ChatUI: React.FC<IChatUIProps> = ({ codyChatStore }): JSX.Element =
|
||||
transcript,
|
||||
transcriptHistory,
|
||||
loaded,
|
||||
isCodyEnabled,
|
||||
scope,
|
||||
setScope,
|
||||
toggleIncludeInferredRepository,
|
||||
@ -99,12 +99,14 @@ export const ChatUI: React.FC<IChatUIProps> = ({ codyChatStore }): JSX.Element =
|
||||
transcriptActionClassName={styles.transcriptAction}
|
||||
FeedbackButtonsContainer={FeedbackButtons}
|
||||
feedbackButtonsOnSubmit={onFeedbackSubmit}
|
||||
needsEmailVerification={isCodyEnabled.needsEmailVerification}
|
||||
needsEmailVerification={isEmailVerificationNeededForCody()}
|
||||
needsEmailVerificationNotice={NeedsEmailVerificationNotice}
|
||||
codyNotEnabledNotice={CodyNotEnabledNotice}
|
||||
contextStatusComponent={ScopeSelector}
|
||||
contextStatusComponentProps={scopeSelectorProps}
|
||||
abortMessageInProgressComponent={AbortMessageInProgress}
|
||||
onAbortMessageInProgress={abortMessageInProgress}
|
||||
isCodyEnabled={isCodyEnabled()}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
@ -240,7 +242,7 @@ interface AutoResizableTextAreaProps extends ChatUITextAreaProps {}
|
||||
|
||||
export const AutoResizableTextArea: React.FC<AutoResizableTextAreaProps> = React.memo(
|
||||
function AutoResizableTextAreaContent({ value, onInput, onKeyDown, className, disabled = false }) {
|
||||
const { inputNeedsFocus, setFocusProvided, isCodyEnabled } = useCodySidebar() || {
|
||||
const { inputNeedsFocus, setFocusProvided } = useCodySidebar() || {
|
||||
inputNeedsFocus: false,
|
||||
setFocusProvided: () => null,
|
||||
}
|
||||
@ -280,7 +282,7 @@ export const AutoResizableTextArea: React.FC<AutoResizableTextAreaProps> = React
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip content={isCodyEnabled.needsEmailVerification ? 'Verify your email to use Cody.' : ''}>
|
||||
<Tooltip content={isEmailVerificationNeededForCody() ? 'Verify your email to use Cody.' : ''}>
|
||||
<TextArea
|
||||
ref={textAreaRef}
|
||||
className={className}
|
||||
@ -319,3 +321,51 @@ const NeedsEmailVerificationNotice: React.FunctionComponent = React.memo(
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const CodyNotEnabledNotice: React.FunctionComponent = React.memo(function CodyNotEnabledNoticeContent() {
|
||||
return (
|
||||
<div className={classNames('p-3', styles.notEnabledBlock)}>
|
||||
<H2 className={classNames('d-flex gap-1 align-items-center mb-3', styles.codyMessageHeader)}>
|
||||
<CodyPageIcon /> Cody
|
||||
</H2>
|
||||
<div className="d-flex align-items-start">
|
||||
<CodyNotEnabledIcon className="flex-shrink-0" />
|
||||
<Text className="ml-2">
|
||||
Cody isn't available on this instance, but you can learn more about Cody{' '}
|
||||
<Link to="https://about.sourcegraph.com/cody?utm_source=server">here</Link>.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const CodyNotEnabledIcon: React.FunctionComponent<{ className?: string }> = ({ className }) => (
|
||||
<svg
|
||||
width="36"
|
||||
height="43"
|
||||
viewBox="0 0 36 43"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<rect y="4" width="36" height="35" rx="4.125" fill="#E8D1FF" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M21.368 15.2742C22.1732 15.2742 22.826 15.9206 22.826 16.7179V19.2844C22.826 20.0818 22.1732 20.7281 21.368 20.7281C20.5628 20.7281 19.91 20.0818 19.91 19.2844V16.7179C19.91 15.9206 20.5628 15.2742 21.368 15.2742Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.1339 18.6427C12.1339 17.8454 12.7866 17.199 13.5919 17.199H16.1838C16.989 17.199 17.6418 17.8454 17.6418 18.6427C17.6418 19.4401 16.989 20.0864 16.1838 20.0864H13.5919C12.7866 20.0864 12.1339 19.4401 12.1339 18.6427Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M24.8523 22.8456C25.3712 23.3338 25.3923 24.146 24.8993 24.6599L24.4406 25.138C20.851 28.8795 14.7994 28.7863 11.3291 24.9361C10.8525 24.4073 10.899 23.5961 11.433 23.1241C11.967 22.6522 12.7863 22.6983 13.2629 23.2271C15.724 25.9576 20.0157 26.0237 22.5614 23.3703L23.0201 22.8922C23.5131 22.3783 24.3334 22.3575 24.8523 22.8456Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@ -55,6 +55,7 @@
|
||||
}
|
||||
|
||||
.cody-sign-up-panel {
|
||||
background-color: var(--color-bg-1);
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 0.5rem 1rem -6px rgba(46, 34, 119, 0.1);
|
||||
border-radius: 3px;
|
||||
@ -116,6 +117,7 @@
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
padding-bottom: 4rem;
|
||||
|
||||
.learn-more-items {
|
||||
padding: 1rem;
|
||||
@ -169,6 +171,7 @@
|
||||
.cody-platform-card-wrapper {
|
||||
padding: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--color-bg-1);
|
||||
|
||||
:global(.theme-dark) & {
|
||||
border: 1px solid var(--gray-09);
|
||||
|
||||
@ -30,4 +30,9 @@ const context: Pick<SourcegraphContext, 'authProviders'> = {
|
||||
],
|
||||
}
|
||||
|
||||
export const Default: Story = () => <WebStory>{() => <CodyMarketingPage context={context} />}</WebStory>
|
||||
export const SourcegraphDotCom: Story = () => (
|
||||
<WebStory>{() => <CodyMarketingPage context={context} isSourcegraphDotCom={true} />}</WebStory>
|
||||
)
|
||||
export const Enterprise: Story = () => (
|
||||
<WebStory>{() => <CodyMarketingPage context={context} isSourcegraphDotCom={false} />}</WebStory>
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { mdiChevronRight, mdiCodeBracesBox, mdiGit, mdiMicrosoftVisualStudioCode } from '@mdi/js'
|
||||
import { mdiChevronRight, mdiCodeBracesBox, mdiGit } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { Theme, useTheme } from '@sourcegraph/shared/src/theme'
|
||||
@ -9,6 +9,7 @@ import { MarketingBlock } from '../../../components/MarketingBlock'
|
||||
import { Page } from '../../../components/Page'
|
||||
import { PageTitle } from '../../../components/PageTitle'
|
||||
import { SourcegraphContext } from '../../../jscontext'
|
||||
import { MeetCodySVG } from '../../../repo/components/TryCodyWidget/WidgetIcons'
|
||||
import { eventLogger } from '../../../tracking/eventLogger'
|
||||
import { EventName } from '../../../util/constants'
|
||||
import { CodyColorIcon, CodyHelpIcon, CodyWorkIcon } from '../../chat/CodyPageIcon'
|
||||
@ -16,9 +17,9 @@ import { CodyColorIcon, CodyHelpIcon, CodyWorkIcon } from '../../chat/CodyPageIc
|
||||
import styles from './CodyMarketingPage.module.scss'
|
||||
|
||||
interface CodyPlatformCardProps {
|
||||
icon: string
|
||||
icon: string | JSX.Element
|
||||
title: string
|
||||
description: string
|
||||
description: string | JSX.Element
|
||||
illustration: string
|
||||
}
|
||||
|
||||
@ -26,7 +27,24 @@ const onSpeakToAnEngineer = (): void => eventLogger.log(EventName.SPEAK_TO_AN_EN
|
||||
const onClickCTAButton = (type: string): void =>
|
||||
eventLogger.log('SignupInitiated', { type, source: 'cody-signed-out' }, { type, page: 'cody-signed-out' })
|
||||
|
||||
const codyPlatformCardItems = [
|
||||
const IDEIcon: React.FunctionComponent<{}> = () => (
|
||||
<svg viewBox="-4 -4 31 31" fill="none" xmlns="http://www.w3.org/2000/svg" className={styles.codyPlatformCardIcon}>
|
||||
<rect x="0.811523" y="0.366669" width="25" height="25" rx="3" fill="#4D52F4" />
|
||||
<path
|
||||
d="M13.8115 20.1583C13.8115 20.4346 13.9169 20.6996 14.1044 20.8949C14.292 21.0903 14.5463 21.2 14.8115 21.2H16.8115V23.2833H14.3115C13.7615 23.2833 12.8115 22.8146 12.8115 22.2417C12.8115 22.8146 11.8615 23.2833 11.3115 23.2833H8.81152V21.2H10.8115C11.0767 21.2 11.3311 21.0903 11.5186 20.8949C11.7062 20.6996 11.8115 20.4346 11.8115 20.1583V5.57501C11.8115 5.29874 11.7062 5.03379 11.5186 4.83844C11.3311 4.64309 11.0767 4.53335 10.8115 4.53335H8.81152V2.45001H11.3115C11.8615 2.45001 12.8115 2.91876 12.8115 3.49168C12.8115 2.91876 13.7615 2.45001 14.3115 2.45001H16.8115V4.53335H14.8115C14.5463 4.53335 14.292 4.64309 14.1044 4.83844C13.9169 5.03379 13.8115 5.29874 13.8115 5.57501V20.1583Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const codyPlatformCardItems = (
|
||||
isSourcegraphDotCom: boolean
|
||||
): {
|
||||
title: string
|
||||
description: string | JSX.Element
|
||||
icon: string | JSX.Element
|
||||
illustration: { dark: string; light: string }
|
||||
}[] => [
|
||||
{
|
||||
title: 'Knows your code',
|
||||
description:
|
||||
@ -38,31 +56,55 @@ const codyPlatformCardItems = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Works with VS Code',
|
||||
description:
|
||||
'This extension combines an LLM with the context of your code to help you generate and fix code more accurately.',
|
||||
icon: mdiMicrosoftVisualStudioCode,
|
||||
title: 'More powerful in your IDE',
|
||||
description: (
|
||||
<>
|
||||
The extensions combine an LLM with the context of your code to help you generate and fix code more
|
||||
accurately. <Link to="/help/cody#get-cody">View supported IDEs</Link>.
|
||||
</>
|
||||
),
|
||||
icon: <IDEIcon />,
|
||||
illustration: {
|
||||
dark: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-vs-code-illustration-dark.png',
|
||||
light: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-vs-code-illustration-light.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Try it on sourcegraph.com',
|
||||
description: 'Cody explains, generates, convert code, and more within the context of public repositories.',
|
||||
icon: mdiGit,
|
||||
illustration: {
|
||||
dark: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-dark.png',
|
||||
light: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-light.png',
|
||||
},
|
||||
},
|
||||
...(isSourcegraphDotCom
|
||||
? [
|
||||
{
|
||||
title: 'Try it on sourcegraph.com',
|
||||
description:
|
||||
'Cody explains, generates, convert code, and more within the context of public repositories.',
|
||||
icon: mdiGit,
|
||||
illustration: {
|
||||
dark: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-dark.png',
|
||||
light: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-light.png',
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: 'Recipes accelerate your flow',
|
||||
description:
|
||||
'Cody explains, generates, convert code, and more within the context of your repositories.',
|
||||
icon: mdiGit,
|
||||
illustration: {
|
||||
dark: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-dark.png',
|
||||
light: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-light.png',
|
||||
},
|
||||
},
|
||||
]),
|
||||
]
|
||||
|
||||
export interface CodyMarketingPageProps {
|
||||
isSourcegraphDotCom: boolean
|
||||
context: Pick<SourcegraphContext, 'authProviders'>
|
||||
}
|
||||
|
||||
export const CodyMarketingPage: React.FunctionComponent<CodyMarketingPageProps> = ({ context }) => {
|
||||
export const CodyMarketingPage: React.FunctionComponent<CodyMarketingPageProps> = ({
|
||||
context,
|
||||
isSourcegraphDotCom,
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const isDarkTheme = theme === Theme.Dark
|
||||
|
||||
@ -90,7 +132,7 @@ export const CodyMarketingPage: React.FunctionComponent<CodyMarketingPageProps>
|
||||
{/* Page content */}
|
||||
<div className={styles.headerSection}>
|
||||
<div>
|
||||
<H1>Meet Cody, your AI assistant</H1>
|
||||
{isSourcegraphDotCom && <H1>Meet Cody, your AI assistant</H1>}
|
||||
<div className="ml-3">
|
||||
<Text className={styles.codyConversation}>Auto-generate unit tests</Text>
|
||||
<Text className={styles.codyConversation}>Explain code</Text>
|
||||
@ -100,49 +142,70 @@ export const CodyMarketingPage: React.FunctionComponent<CodyMarketingPageProps>
|
||||
<CodyHelpIcon className={styles.codyHelpIcon} />
|
||||
</div>
|
||||
|
||||
<MarketingBlock
|
||||
contentClassName={styles.codySignUpPanel}
|
||||
wrapperClassName={styles.codySignUpPanelWrapper}
|
||||
>
|
||||
<H2>Sign up to get free access</H2>
|
||||
<Text className="mt-3">
|
||||
Cody combines an LLM with the context of Sourcegraph's code graph on public code or your code at
|
||||
work. Sign up with:
|
||||
</Text>
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ExternalsAuth
|
||||
context={context}
|
||||
githubLabel="GitHub"
|
||||
gitlabLabel="GitLab"
|
||||
withCenteredText={true}
|
||||
onClick={onClickCTAButton}
|
||||
ctaClassName={styles.authButton}
|
||||
iconClassName={styles.buttonIcon}
|
||||
/>
|
||||
</div>
|
||||
<Link to="https://sourcegraph.com/sign-up?showEmail=true">Or, continue with email</Link>
|
||||
<Text className="mt-3 mb-0">
|
||||
By registering, you agree to our{' '}
|
||||
<Link
|
||||
className={styles.termsPrivacyLink}
|
||||
to="https://about.sourcegraph.com/terms"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link
|
||||
className={styles.termsPrivacyLink}
|
||||
to="https://about.sourcegraph.com/privacy"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
</MarketingBlock>
|
||||
{isSourcegraphDotCom ? (
|
||||
<MarketingBlock
|
||||
contentClassName={styles.codySignUpPanel}
|
||||
wrapperClassName={styles.codySignUpPanelWrapper}
|
||||
>
|
||||
<H2>Sign up to get free access</H2>
|
||||
<Text className="mt-3">
|
||||
Cody combines an LLM with the context of Sourcegraph's code graph on public code or your
|
||||
code at work. Sign up with:
|
||||
</Text>
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ExternalsAuth
|
||||
context={context}
|
||||
githubLabel="GitHub"
|
||||
gitlabLabel="GitLab"
|
||||
withCenteredText={true}
|
||||
onClick={onClickCTAButton}
|
||||
ctaClassName={styles.authButton}
|
||||
iconClassName={styles.buttonIcon}
|
||||
/>
|
||||
</div>
|
||||
<Link to="https://sourcegraph.com/sign-up?showEmail=true">Or, continue with email</Link>
|
||||
<Text className="mt-3 mb-0">
|
||||
By registering, you agree to our{' '}
|
||||
<Link
|
||||
className={styles.termsPrivacyLink}
|
||||
to="https://about.sourcegraph.com/terms"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link
|
||||
className={styles.termsPrivacyLink}
|
||||
to="https://about.sourcegraph.com/privacy"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
</MarketingBlock>
|
||||
) : (
|
||||
<MarketingBlock
|
||||
contentClassName={styles.codySignUpPanel}
|
||||
wrapperClassName={styles.codySignUpPanelWrapper}
|
||||
>
|
||||
<H2>Meet Cody, your AI assistant</H2>
|
||||
<Text className="mt-3">
|
||||
Cody is an AI assistant that leverages the code graph to know more about your code. Use it
|
||||
to:
|
||||
</Text>
|
||||
<ul>
|
||||
<li>Onboard to new codebases</li>
|
||||
<li>Evaluate and fix code</li>
|
||||
<li>Write code faster</li>
|
||||
</ul>
|
||||
<Text className="mb-0">
|
||||
<Link to="https://about.sourcegraph.com/cody">Learn more about Cody →</Link>
|
||||
</Text>
|
||||
</MarketingBlock>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.learnMoreSection}>
|
||||
@ -154,7 +217,7 @@ export const CodyMarketingPage: React.FunctionComponent<CodyMarketingPageProps>
|
||||
styles.startingPointWrapper
|
||||
)}
|
||||
>
|
||||
{codyPlatformCardItems.map(item => (
|
||||
{codyPlatformCardItems(isSourcegraphDotCom).map(item => (
|
||||
<CodyPlatformCard
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
@ -166,16 +229,28 @@ export const CodyMarketingPage: React.FunctionComponent<CodyMarketingPageProps>
|
||||
</div>
|
||||
|
||||
<div className={styles.learnMoreItemsWrapper}>
|
||||
<div className={styles.learnMoreItems}>
|
||||
<H4 className={styles.learnMoreItemsTitle}>Overview</H4>
|
||||
<Text className="mb-0">
|
||||
Visit the{' '}
|
||||
<Link to="https://about.sourcegraph.com/cody" target="_blank" rel="noopener">
|
||||
product page
|
||||
</Link>{' '}
|
||||
and see what devs are building with Cody.
|
||||
</Text>
|
||||
</div>
|
||||
{isSourcegraphDotCom ? (
|
||||
<div className={styles.learnMoreItems}>
|
||||
<H4 className={styles.learnMoreItemsTitle}>Overview</H4>
|
||||
<Text className="mb-0">
|
||||
Visit the{' '}
|
||||
<Link to="https://about.sourcegraph.com/cody" target="_blank" rel="noopener">
|
||||
product page
|
||||
</Link>{' '}
|
||||
and see what devs are building with Cody.
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex align-items-center">
|
||||
<div>
|
||||
<MeetCodySVG />
|
||||
</div>
|
||||
<Text className="ml-3">
|
||||
<Link to="https://about.sourcegraph.com/cody">Learn about Cody</Link>, Sourcegraph's AI
|
||||
coding assistant.
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.learnMoreItems}>
|
||||
<H4 className={styles.learnMoreItemsTitle}>Documentation</H4>
|
||||
@ -190,26 +265,29 @@ export const CodyMarketingPage: React.FunctionComponent<CodyMarketingPageProps>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<CodyWorkIcon />
|
||||
<div>
|
||||
<H1 className="mb-2">Get Cody for work</H1>
|
||||
<Text className={styles.footerDescription}>
|
||||
Cody for Sourcegraph Enterprise utilizes Sourcegraph's code graph to deliver context-aware
|
||||
answers based on your private codebase, enabling enhanced code comprehension and productivity.
|
||||
</Text>
|
||||
<div className="mb-2">
|
||||
<Link
|
||||
to="https://about.sourcegraph.com/demo"
|
||||
className={classNames('d-inline-flex align-items-center', styles.footerCtaLink)}
|
||||
onClick={onSpeakToAnEngineer}
|
||||
>
|
||||
Speak to an engineer
|
||||
<Icon svgPath={mdiChevronRight} aria-hidden={true} />
|
||||
</Link>
|
||||
{isSourcegraphDotCom && (
|
||||
<div className={styles.footer}>
|
||||
<CodyWorkIcon />
|
||||
<div>
|
||||
<H1 className="mb-2">Get Cody for work</H1>
|
||||
<Text className={styles.footerDescription}>
|
||||
Cody for Sourcegraph Enterprise utilizes Sourcegraph's code graph to deliver context-aware
|
||||
answers based on your private codebase, enabling enhanced code comprehension and
|
||||
productivity.
|
||||
</Text>
|
||||
<div className="mb-2">
|
||||
<Link
|
||||
to="https://about.sourcegraph.com/demo"
|
||||
className={classNames('d-inline-flex align-items-center', styles.footerCtaLink)}
|
||||
onClick={onSpeakToAnEngineer}
|
||||
>
|
||||
Speak to an engineer
|
||||
<Icon svgPath={mdiChevronRight} aria-hidden={true} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
@ -222,7 +300,11 @@ const CodyPlatformCard: React.FunctionComponent<CodyPlatformCardProps> = ({
|
||||
}) => (
|
||||
<div className={styles.codyPlatformCardWrapper}>
|
||||
<div className="d-flex flex-row align-items-center">
|
||||
<Icon svgPath={icon} aria-label="Close try Cody widget" className={styles.codyPlatformCardIcon} />
|
||||
{typeof icon === 'string' ? (
|
||||
<Icon svgPath={icon} aria-hidden={true} className={styles.codyPlatformCardIcon} />
|
||||
) : (
|
||||
<>{icon}</>
|
||||
)}
|
||||
<H3 className="ml-2 mb-0">{title}</H3>
|
||||
</div>
|
||||
<Text className={classNames('mt-2', styles.codyPlatformCardDescription)}>{description}</Text>
|
||||
|
||||
14
client/web/src/cody/isCodyEnabled.tsx
Normal file
14
client/web/src/cody/isCodyEnabled.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
export const isEmailVerificationNeededForCody = (): boolean =>
|
||||
window.context?.codyRequiresVerifiedEmail && !window.context?.currentUser?.hasVerifiedEmail
|
||||
|
||||
export const isCodyEnabled = (): boolean => {
|
||||
if (window.context?.sourcegraphAppMode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!window.context?.codyEnabled || !window.context?.codyEnabledForCurrentUser) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -11,11 +11,12 @@ import { buildSearchURLQuery } from '@sourcegraph/shared/src/util/url'
|
||||
import { Alert, Form, Input, LoadingSpinner, Text, Badge, Link, useSessionStorage } from '@sourcegraph/wildcard'
|
||||
|
||||
import { BrandLogo } from '../../components/branding/BrandLogo'
|
||||
import { useFeatureFlag } from '../../featureFlags/useFeatureFlag'
|
||||
import { useURLSyncedString } from '../../hooks/useUrlSyncedString'
|
||||
import { eventLogger } from '../../tracking/eventLogger'
|
||||
import { DOTCOM_URL } from '../../tracking/util'
|
||||
import { CodyIcon } from '../components/CodyIcon'
|
||||
import { useIsCodyEnabled } from '../useIsCodyEnabled'
|
||||
import { isEmailVerificationNeededForCody } from '../isCodyEnabled'
|
||||
|
||||
import { translateToQuery } from './translateToQuery'
|
||||
|
||||
@ -138,7 +139,6 @@ const SearchInput: React.FunctionComponent<{
|
||||
onSubmit: () => void
|
||||
className?: string
|
||||
}> = ({ value, loading, error, onChange, onSubmit: parentOnSubmit, className }) => {
|
||||
const cody = useIsCodyEnabled()
|
||||
const onInput = useCallback<React.FormEventHandler<HTMLInputElement>>(
|
||||
event => {
|
||||
onChange(event.currentTarget.value)
|
||||
@ -154,9 +154,11 @@ const SearchInput: React.FunctionComponent<{
|
||||
[parentOnSubmit]
|
||||
)
|
||||
|
||||
return cody.search ? (
|
||||
const [codySearchEnabled] = useFeatureFlag('cody-web-search')
|
||||
|
||||
return codySearchEnabled ? (
|
||||
<Form onSubmit={onSubmit} className={className}>
|
||||
{cody.needsEmailVerification && (
|
||||
{isEmailVerificationNeededForCody() && (
|
||||
<Alert variant="warning">
|
||||
<Text className="mb-0">Verify email</Text>
|
||||
<Text className="mb-0">
|
||||
@ -172,7 +174,7 @@ const SearchInput: React.FunctionComponent<{
|
||||
inputClassName={styles.input}
|
||||
value={value}
|
||||
onInput={onInput}
|
||||
disabled={loading || cody.needsEmailVerification}
|
||||
disabled={loading || isEmailVerificationNeededForCody()}
|
||||
autoFocus={true}
|
||||
placeholder="Search for code or files in natural language..."
|
||||
/>
|
||||
|
||||
@ -26,7 +26,6 @@ export const CodySidebar: React.FC<CodySidebarProps> = ({ onClose }) => {
|
||||
messageInProgress,
|
||||
clearHistory,
|
||||
loaded,
|
||||
isCodyEnabled,
|
||||
transcriptHistory,
|
||||
deleteHistoryItem,
|
||||
loadTranscriptFromHistory,
|
||||
@ -76,7 +75,7 @@ export const CodySidebar: React.FC<CodySidebarProps> = ({ onClose }) => {
|
||||
[loadTranscriptFromHistory, setShowHistory]
|
||||
)
|
||||
|
||||
if (!(loaded && isCodyEnabled.sidebar)) {
|
||||
if (!loaded) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ export const CodySidebarStoreProvider: React.FC<ICodySidebarStoreProviderProps>
|
||||
...codyChatStore,
|
||||
isSidebarOpen: isSidebarOpen ?? false,
|
||||
inputNeedsFocus,
|
||||
sidebarSize: isSidebarOpen && codyChatStore.isCodyEnabled.sidebar ? sidebarSize : 0,
|
||||
sidebarSize: isSidebarOpen ? sidebarSize : 0,
|
||||
setIsSidebarOpen,
|
||||
setFocusProvided,
|
||||
setSidebarSize,
|
||||
|
||||
@ -14,7 +14,7 @@ import { useLocalStorage } from '@sourcegraph/wildcard'
|
||||
import { eventLogger } from '../tracking/eventLogger'
|
||||
import { EventName } from '../util/constants'
|
||||
|
||||
import { useIsCodyEnabled, IsCodyEnabled, notEnabled } from './useIsCodyEnabled'
|
||||
import { isEmailVerificationNeededForCody } from './isCodyEnabled'
|
||||
|
||||
export type { CodyClientScope } from '@sourcegraph/cody-shared/src/chat/useClient'
|
||||
|
||||
@ -38,7 +38,6 @@ export interface CodyChatStore
|
||||
> {
|
||||
readonly transcriptHistory: TranscriptJSON[]
|
||||
readonly loaded: boolean
|
||||
readonly isCodyEnabled: IsCodyEnabled
|
||||
clearHistory: () => void
|
||||
deleteHistoryItem: (id: string) => void
|
||||
loadTranscriptFromHistory: (id: string) => Promise<void>
|
||||
@ -63,7 +62,6 @@ export const codyChatStoreMock: CodyChatStore = {
|
||||
setEditorScope: () => {},
|
||||
transcriptHistory: [],
|
||||
loaded: true,
|
||||
isCodyEnabled: notEnabled,
|
||||
clearHistory: () => {},
|
||||
deleteHistoryItem: () => {},
|
||||
loadTranscriptFromHistory: () => Promise.resolve(),
|
||||
@ -94,7 +92,6 @@ export const useCodyChat = ({
|
||||
onTranscriptHistoryLoad,
|
||||
autoLoadTranscriptFromHistory = true,
|
||||
}: CodyChatProps): CodyChatStore => {
|
||||
const isCodyEnabled = useIsCodyEnabled()
|
||||
const [loadedTranscriptFromHistory, setLoadedTranscriptFromHistory] = useState(false)
|
||||
const [transcriptHistoryInternal, setTranscriptHistoryState] = useLocalStorage<TranscriptJSON[]>(
|
||||
CODY_TRANSCRIPT_HISTORY_KEY,
|
||||
@ -126,7 +123,7 @@ export const useCodyChat = ({
|
||||
accessToken: null,
|
||||
customHeaders: window.context.xhrHeaders,
|
||||
debugEnable: false,
|
||||
needsEmailVerification: isCodyEnabled.needsEmailVerification,
|
||||
needsEmailVerification: isEmailVerificationNeededForCody(),
|
||||
},
|
||||
scope: initialScope,
|
||||
onEvent,
|
||||
@ -280,10 +277,7 @@ export const useCodyChat = ({
|
||||
[executeRecipeInternal, updateTranscriptInHistory]
|
||||
)
|
||||
|
||||
const loaded = useMemo(
|
||||
() => loadedTranscriptFromHistory && isCodyEnabled.loaded,
|
||||
[loadedTranscriptFromHistory, isCodyEnabled.loaded]
|
||||
)
|
||||
const loaded = useMemo(() => loadedTranscriptFromHistory, [loadedTranscriptFromHistory])
|
||||
|
||||
// Autoload the latest transcript from history once it is loaded. Initially the transcript is null.
|
||||
useEffect(() => {
|
||||
@ -373,7 +367,6 @@ export const useCodyChat = ({
|
||||
|
||||
return {
|
||||
loaded,
|
||||
isCodyEnabled,
|
||||
transcript,
|
||||
transcriptHistory,
|
||||
chatMessages,
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useFeatureFlag } from '../featureFlags/useFeatureFlag'
|
||||
|
||||
export const notEnabled = {
|
||||
loaded: true,
|
||||
chat: false,
|
||||
sidebar: false,
|
||||
search: false,
|
||||
editorRecipes: false,
|
||||
needsEmailVerification: false,
|
||||
}
|
||||
|
||||
export interface IsCodyEnabled {
|
||||
loaded: boolean
|
||||
chat: boolean
|
||||
sidebar: boolean
|
||||
search: boolean
|
||||
editorRecipes: boolean
|
||||
needsEmailVerification: boolean
|
||||
}
|
||||
|
||||
export const isEmailVerificationNeeded = (): boolean =>
|
||||
window.context?.codyRequiresVerifiedEmail && !window.context?.currentUser?.hasVerifiedEmail
|
||||
|
||||
export const useIsCodyEnabled = (): IsCodyEnabled => {
|
||||
const [chatEnabled, chatEnabledStatus] = useFeatureFlag('cody-web-chat')
|
||||
const [searchEnabled, searchEnabledStatus] = useFeatureFlag('cody-web-search')
|
||||
const [sidebarEnabled, sidebarEnabledStatus] = useFeatureFlag('cody-web-sidebar')
|
||||
const [editorRecipesEnabled, editorRecipesEnabledStatus] = useFeatureFlag('cody-web-editor-recipes')
|
||||
let [allEnabled, allEnabledStatus] = useFeatureFlag('cody-web-all')
|
||||
|
||||
if (window.context?.sourcegraphAppMode) {
|
||||
// If the user is using the Sourcegraph app, all features are enabled
|
||||
// as long as the user has a connected Sourcegraph.com account.
|
||||
allEnabled = true
|
||||
}
|
||||
|
||||
const enabled = useMemo(
|
||||
() => ({
|
||||
loaded:
|
||||
window.context?.sourcegraphAppMode ||
|
||||
(chatEnabledStatus === 'loaded' &&
|
||||
searchEnabledStatus === 'loaded' &&
|
||||
sidebarEnabledStatus === 'loaded' &&
|
||||
editorRecipesEnabledStatus === 'loaded' &&
|
||||
allEnabledStatus === 'loaded'),
|
||||
chat: chatEnabled || allEnabled,
|
||||
sidebar: sidebarEnabled || allEnabled,
|
||||
search: searchEnabled || allEnabled,
|
||||
editorRecipes: (editorRecipesEnabled && sidebarEnabled) || allEnabled,
|
||||
needsEmailVerification: isEmailVerificationNeeded(),
|
||||
}),
|
||||
[
|
||||
chatEnabled,
|
||||
sidebarEnabled,
|
||||
searchEnabled,
|
||||
editorRecipesEnabled,
|
||||
allEnabled,
|
||||
chatEnabledStatus,
|
||||
searchEnabledStatus,
|
||||
sidebarEnabledStatus,
|
||||
editorRecipesEnabledStatus,
|
||||
allEnabledStatus,
|
||||
]
|
||||
)
|
||||
|
||||
if (!window.context?.codyEnabled || !window.context?.codyEnabledForCurrentUser) {
|
||||
return notEnabled
|
||||
}
|
||||
|
||||
return enabled
|
||||
}
|
||||
@ -28,11 +28,9 @@ export type FeatureFlagName =
|
||||
| 'sourcegraph-operator-site-admin-hide-maintenance'
|
||||
| 'repository-metadata'
|
||||
| 'cody-gateway-management-ui'
|
||||
| 'cody-web-chat'
|
||||
| 'cody-web-search'
|
||||
| 'cody-web-sidebar'
|
||||
| 'cody-web-all'
|
||||
| 'cody-web-editor-recipes'
|
||||
| 'own-promote'
|
||||
| 'own-analytics'
|
||||
|
||||
interface OrgFlagOverride {
|
||||
orgID: string
|
||||
|
||||
@ -8,7 +8,7 @@ import { useSettings } from '@sourcegraph/shared/src/settings/settings'
|
||||
import { Alert, AlertProps, Markdown } from '@sourcegraph/wildcard'
|
||||
|
||||
import { AuthenticatedUser } from '../auth'
|
||||
import { useIsCodyEnabled } from '../cody/useIsCodyEnabled'
|
||||
import { isEmailVerificationNeededForCody } from '../cody/isCodyEnabled'
|
||||
import { DismissibleAlert } from '../components/DismissibleAlert'
|
||||
|
||||
import styles from './Notices.module.scss'
|
||||
@ -96,9 +96,7 @@ export const VerifyEmailNotices: React.FunctionComponent<VerifyEmailNoticesProps
|
||||
alertClassName,
|
||||
authenticatedUser,
|
||||
}) => {
|
||||
const codyEnabled = useIsCodyEnabled()
|
||||
|
||||
if (codyEnabled.needsEmailVerification && authenticatedUser) {
|
||||
if (isEmailVerificationNeededForCody() && authenticatedUser) {
|
||||
return (
|
||||
<div className={classNames(styles.notices, className)}>
|
||||
<NoticeAlert
|
||||
|
||||
@ -424,7 +424,8 @@ describe('Repository', () => {
|
||||
// Assert that the directory listing displays properly
|
||||
await driver.page.waitForSelector('.test-tree-entries')
|
||||
|
||||
await percySnapshotWithVariants(driver.page, 'Repository index page')
|
||||
// TODO: Reenable later, percy is erroring out on remote images not loading.
|
||||
// await percySnapshotWithVariants(driver.page, 'Repository index page')
|
||||
await accessibilityAudit(driver.page)
|
||||
|
||||
const numberOfFileEntries = await driver.page.evaluate(
|
||||
|
||||
@ -10,7 +10,7 @@ import { Checkbox, Form, H3, Modal, Text, Button, Icon, useLocalStorage } from '
|
||||
|
||||
import { AuthenticatedUser } from '../../auth'
|
||||
import { CodyPageIcon } from '../../cody/chat/CodyPageIcon'
|
||||
import { useIsCodyEnabled } from '../../cody/useIsCodyEnabled'
|
||||
import { isEmailVerificationNeededForCody } from '../../cody/isCodyEnabled'
|
||||
import { LoaderButton } from '../../components/LoaderButton'
|
||||
import { SubmitCodySurveyResult, SubmitCodySurveyVariables } from '../../graphql-operations'
|
||||
import { resendVerificationEmail } from '../../user/settings/emails/UserEmail'
|
||||
@ -195,8 +195,7 @@ export const CodySurveyToast: React.FC<
|
||||
} & TelemetryProps
|
||||
> = ({ authenticatedUser, telemetryService }) => {
|
||||
const { show, dismiss } = useCodySurveyToast()
|
||||
const codyEnabled = useIsCodyEnabled()
|
||||
const [showVerifyEmail, setShowVerifyEmail] = useState(show && codyEnabled.needsEmailVerification)
|
||||
const [showVerifyEmail, setShowVerifyEmail] = useState(show && isEmailVerificationNeededForCody())
|
||||
const dismissVerifyEmail = useCallback(() => {
|
||||
telemetryService.log('VerifyEmailToastDismissed')
|
||||
setShowVerifyEmail(false)
|
||||
|
||||
@ -24,7 +24,6 @@ import { BatchChangesNavItem } from '../batches/BatchChangesNavItem'
|
||||
import { CodeMonitoringLogo } from '../code-monitoring/CodeMonitoringLogo'
|
||||
import { CodeMonitoringProps } from '../codeMonitoring'
|
||||
import { CodyLogo } from '../cody/components/CodyLogo'
|
||||
import { useIsCodyEnabled } from '../cody/useIsCodyEnabled'
|
||||
import { BrandLogo } from '../components/branding/BrandLogo'
|
||||
import { useFuzzyFinderFeatureFlags } from '../components/fuzzyFinder/FuzzyFinderFeatureFlag'
|
||||
import { useFeatureFlag } from '../featureFlags/useFeatureFlag'
|
||||
@ -150,7 +149,7 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
|
||||
const showSearchNotebook = notebooksEnabled && !isSourcegraphApp
|
||||
const isLicensed = !!window.context?.licenseInfo || isSourcegraphApp // Assume licensed when running as a native app
|
||||
const showBatchChanges = ((props.batchChangesEnabled && isLicensed) || isSourcegraphDotCom) && !isSourcegraphApp // Batch changes are enabled on sourcegraph.com so users can see the Getting Started page
|
||||
const codyEnabled = useIsCodyEnabled()
|
||||
const [codySearchEnabled] = useFeatureFlag('cody-web-search')
|
||||
|
||||
const [isSentinelEnabled] = useFeatureFlag('sentinel')
|
||||
// TODO: Include isSourcegraphDotCom in subsequent PR
|
||||
@ -177,7 +176,7 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
|
||||
const items: (NavDropdownItem | false)[] = [
|
||||
!!showSearchContext && { path: EnterprisePageRoutes.Contexts, content: 'Contexts' },
|
||||
ownEnabled && { path: EnterprisePageRoutes.Own, content: 'Own' },
|
||||
codyEnabled.search && {
|
||||
codySearchEnabled && {
|
||||
path: EnterprisePageRoutes.CodySearch,
|
||||
content: (
|
||||
<>
|
||||
@ -187,7 +186,7 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
|
||||
},
|
||||
]
|
||||
return items.filter<NavDropdownItem>((item): item is NavDropdownItem => !!item)
|
||||
}, [ownEnabled, showSearchContext, codyEnabled.search])
|
||||
}, [ownEnabled, showSearchContext, codySearchEnabled])
|
||||
|
||||
const { fuzzyFinderNavbar } = useFuzzyFinderFeatureFlags()
|
||||
|
||||
@ -229,13 +228,11 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
))}
|
||||
{(codyEnabled.chat || isSourcegraphDotCom) && (
|
||||
<NavItem icon={CodyLogo}>
|
||||
<NavLink variant={navLinkVariant} to={EnterprisePageRoutes.Cody}>
|
||||
Cody AI
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
)}
|
||||
<NavItem icon={CodyLogo}>
|
||||
<NavLink variant={navLinkVariant} to={EnterprisePageRoutes.Cody}>
|
||||
Cody AI
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
{showSearchNotebook && (
|
||||
<NavItem icon={BookOutlineIcon}>
|
||||
<NavLink variant={navLinkVariant} to={EnterprisePageRoutes.Notebooks}>
|
||||
|
||||
@ -84,6 +84,47 @@ exports[`GlobalNavbar default 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="item"
|
||||
>
|
||||
<a
|
||||
class="link"
|
||||
href="/cody"
|
||||
>
|
||||
<span
|
||||
class="linkContent"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mdi-icon iconInline icon"
|
||||
fill="none"
|
||||
height="20"
|
||||
role="img"
|
||||
viewBox="0 0 20 20"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.9088 4C14.756 4 15.4429 4.69836 15.4429 5.55983V8.33286C15.4429 9.19433 14.756 9.89269 13.9088 9.89269C13.0615 9.89269 12.3747 9.19433 12.3747 8.33286V5.55983C12.3747 4.69836 13.0615 4 13.9088 4Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
<path
|
||||
d="M4.19287 7.63942C4.19287 6.77795 4.87971 6.07959 5.72696 6.07959H8.45423C9.30148 6.07959 9.98832 6.77795 9.98832 7.63942C9.98832 8.50089 9.30148 9.19925 8.45423 9.19925H5.72696C4.87971 9.19925 4.19287 8.50089 4.19287 7.63942Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
<path
|
||||
d="M17.5756 12.1801C18.1216 12.7075 18.1437 13.5851 17.625 14.1403L17.1423 14.6569C13.3654 18.6994 6.99777 18.5987 3.34628 14.4387C2.84481 13.8674 2.89377 12.9909 3.45565 12.481C4.01752 11.9711 4.87954 12.0209 5.38102 12.5922C7.97062 15.5424 12.4865 15.6139 15.1651 12.747L15.6477 12.2304C16.1664 11.6752 17.0296 11.6527 17.5756 12.1801Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="text iconIncluded"
|
||||
>
|
||||
Cody AI
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="item"
|
||||
>
|
||||
|
||||
@ -155,7 +155,6 @@ export const RepoContainer: FC<RepoContainerProps> = props => {
|
||||
)
|
||||
|
||||
const {
|
||||
isCodyEnabled,
|
||||
sidebarSize: codySidebarSize,
|
||||
isSidebarOpen: isCodySidebarOpen,
|
||||
setIsSidebarOpen: setIsCodySidebarOpen,
|
||||
@ -359,16 +358,15 @@ export const RepoContainer: FC<RepoContainerProps> = props => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCodyEnabled.sidebar &&
|
||||
focusCodyShortcut?.keybindings.map((keybinding, index) => (
|
||||
<Shortcut
|
||||
key={index}
|
||||
{...keybinding}
|
||||
onMatch={() => {
|
||||
setIsCodySidebarOpen(true)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{focusCodyShortcut?.keybindings.map((keybinding, index) => (
|
||||
<Shortcut
|
||||
key={index}
|
||||
{...keybinding}
|
||||
onMatch={() => {
|
||||
setIsCodySidebarOpen(true)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<div className={classNames('w-100 d-flex flex-row')}>
|
||||
<div className={classNames('w-100 d-flex flex-column', styles.repoContainer)}>
|
||||
<RepoHeader
|
||||
@ -383,28 +381,26 @@ export const RepoContainer: FC<RepoContainerProps> = props => {
|
||||
telemetryService={props.telemetryService}
|
||||
/>
|
||||
|
||||
{isCodyEnabled.sidebar ? (
|
||||
<RepoHeaderContributionPortal
|
||||
position="right"
|
||||
priority={1}
|
||||
id="cody"
|
||||
{...repoHeaderContributionsLifecycleProps}
|
||||
>
|
||||
{() =>
|
||||
!isCodySidebarOpen ? (
|
||||
<AskCodyButton
|
||||
onClick={() => {
|
||||
props.telemetryService.log(EventName.CODY_SIDEBAR_CHAT_OPENED, {
|
||||
repo,
|
||||
path: filePath,
|
||||
})
|
||||
setIsCodySidebarOpen(true)
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
</RepoHeaderContributionPortal>
|
||||
) : null}
|
||||
<RepoHeaderContributionPortal
|
||||
position="right"
|
||||
priority={1}
|
||||
id="cody"
|
||||
{...repoHeaderContributionsLifecycleProps}
|
||||
>
|
||||
{() =>
|
||||
!isCodySidebarOpen ? (
|
||||
<AskCodyButton
|
||||
onClick={() => {
|
||||
props.telemetryService.log(EventName.CODY_SIDEBAR_CHAT_OPENED, {
|
||||
repo,
|
||||
path: filePath,
|
||||
})
|
||||
setIsCodySidebarOpen(true)
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
</RepoHeaderContributionPortal>
|
||||
|
||||
<RepoHeaderContributionPortal
|
||||
position="right"
|
||||
@ -499,7 +495,7 @@ export const RepoContainer: FC<RepoContainerProps> = props => {
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
{isCodyEnabled.sidebar && isCodySidebarOpen && (
|
||||
{isCodySidebarOpen && (
|
||||
<Panel
|
||||
className="cody-sidebar-panel"
|
||||
position="right"
|
||||
|
||||
@ -46,6 +46,7 @@ import {
|
||||
import { AuthenticatedUser } from '../../auth'
|
||||
import { CodeIntelligenceProps } from '../../codeintel'
|
||||
import { FileContentEditor } from '../../cody/components/FileContentEditor'
|
||||
import { isCodyEnabled } from '../../cody/isCodyEnabled'
|
||||
import { useCodySidebar } from '../../cody/sidebar/Provider'
|
||||
import { BreadcrumbSetters } from '../../components/Breadcrumbs'
|
||||
import { HeroPage } from '../../components/HeroPage'
|
||||
@ -362,12 +363,13 @@ export const BlobPage: React.FunctionComponent<BlobPageProps> = ({ className, co
|
||||
const alwaysRender = (
|
||||
<>
|
||||
<PageTitle title={getPageTitle()} />
|
||||
{props.isSourcegraphDotCom && (
|
||||
{(props.isSourcegraphDotCom || isCodyEnabled()) && (
|
||||
<TryCodyWidget
|
||||
telemetryService={props.telemetryService}
|
||||
type="blob"
|
||||
authenticatedUser={props.authenticatedUser}
|
||||
context={context}
|
||||
isSourcegraphDotCom={props.isSourcegraphDotCom}
|
||||
/>
|
||||
)}
|
||||
{window.context.isAuthenticatedUser && (
|
||||
|
||||
@ -28,6 +28,7 @@ import { AbsoluteRepoFile, ModeSpec, parseQueryAndHash } from '@sourcegraph/shar
|
||||
import { useLocalStorage } from '@sourcegraph/wildcard'
|
||||
|
||||
import { CodeMirrorEditor } from '../../cody/components/CodeMirrorEditor'
|
||||
import { isCodyEnabled } from '../../cody/isCodyEnabled'
|
||||
import { useCodySidebar } from '../../cody/sidebar/Provider'
|
||||
import { useFeatureFlag } from '../../featureFlags/useFeatureFlag'
|
||||
import { ExternalLinkFields, Scalars } from '../../graphql-operations'
|
||||
@ -275,7 +276,7 @@ export const CodeMirrorBlob: React.FunctionComponent<BlobProps> = props => {
|
||||
)
|
||||
|
||||
// Added fallback to take care of ReferencesPanel/Simple storybook
|
||||
const { isCodyEnabled, setEditorScope } = useCodySidebar()
|
||||
const { setEditorScope } = useCodySidebar()
|
||||
|
||||
const editorRef = useRef<EditorView | null>(null)
|
||||
|
||||
@ -295,7 +296,7 @@ export const CodeMirrorBlob: React.FunctionComponent<BlobProps> = props => {
|
||||
}),
|
||||
scipSnapshot(blobInfo.snapshotData),
|
||||
codeFoldingExtension(),
|
||||
isCodyEnabled.editorRecipes
|
||||
isCodyEnabled()
|
||||
? codyWidgetExtension(
|
||||
editorRef.current
|
||||
? new CodeMirrorEditor({
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
padding-top: 2.9375rem;
|
||||
padding-top: 0.5rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@ -45,7 +45,6 @@
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5rem;
|
||||
letter-spacing: -0.25px;
|
||||
margin-bottom: 0.6875rem;
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ const AUTO_DISMISS_ON_EVENTS = new Set([
|
||||
interface WidgetContentProps extends TelemetryProps {
|
||||
type: 'blob' | 'repo'
|
||||
theme?: 'light' | 'dark'
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
interface NoAuhWidgetContentProps extends WidgetContentProps {
|
||||
@ -77,7 +78,7 @@ const NoAuthWidgetContent: React.FC<NoAuhWidgetContentProps> = ({ type, telemetr
|
||||
return (
|
||||
<>
|
||||
<MeetCodySVG />
|
||||
<div>
|
||||
<div className="flex-grow-1">
|
||||
<H2 className={styles.cardTitle}>{title}</H2>
|
||||
<Text className={styles.cardDescription}>
|
||||
Cody combines an LLM with the context of Sourcegraph's code graph on public code or your code at
|
||||
@ -86,8 +87,8 @@ const NoAuthWidgetContent: React.FC<NoAuhWidgetContentProps> = ({ type, telemetr
|
||||
<div className={styles.authButtonsWrap}>
|
||||
<ExternalsAuth
|
||||
context={context}
|
||||
githubLabel="Github"
|
||||
gitlabLabel="Gitlab"
|
||||
githubLabel="GitHub"
|
||||
gitlabLabel="GitLab"
|
||||
withCenteredText={true}
|
||||
onClick={logEvent}
|
||||
ctaClassName={styles.authButton}
|
||||
@ -102,7 +103,7 @@ const NoAuthWidgetContent: React.FC<NoAuhWidgetContentProps> = ({ type, telemetr
|
||||
Email
|
||||
</Link>
|
||||
</div>
|
||||
<Text className="mb-0 mt-2">
|
||||
<Text className="mb-2 mt-2">
|
||||
By registering, you agree to our{' '}
|
||||
<Link
|
||||
to="https://about.sourcegraph.com/terms"
|
||||
@ -127,9 +128,9 @@ const NoAuthWidgetContent: React.FC<NoAuhWidgetContentProps> = ({ type, telemetr
|
||||
)
|
||||
}
|
||||
|
||||
const AuthUserWidgetContent: React.FC<WidgetContentProps> = ({ type, theme }) => {
|
||||
const { title, useCases, image } =
|
||||
type === 'blob'
|
||||
const AuthUserWidgetContent: React.FC<WidgetContentProps> = ({ type, theme, isSourcegraphDotCom }) => {
|
||||
const { title, useCases, image } = isSourcegraphDotCom
|
||||
? type === 'blob'
|
||||
? {
|
||||
title: 'Try Cody on public code',
|
||||
useCases: ['Select code in the file below', 'Select an action with Cody widget'],
|
||||
@ -143,14 +144,30 @@ const AuthUserWidgetContent: React.FC<WidgetContentProps> = ({ type, theme }) =>
|
||||
],
|
||||
image: `https://storage.googleapis.com/sourcegraph-assets/app-images/cody-chat-banner-image-${theme}.png`,
|
||||
}
|
||||
: type === 'blob'
|
||||
? {
|
||||
title: 'Try Cody AI assist on this file',
|
||||
useCases: ['Select code in the file below', 'Select an action with Cody widget'],
|
||||
image: `https://storage.googleapis.com/sourcegraph-assets/app-images/cody-action-bar-${theme}.png`,
|
||||
}
|
||||
: {
|
||||
title: 'Try Cody AI assist on this repo',
|
||||
useCases: [
|
||||
'Click the Ask Cody button above and to the right of this banner',
|
||||
'Ask Cody a question like “Explain the structure of this repository”',
|
||||
],
|
||||
image: `https://storage.googleapis.com/sourcegraph-assets/app-images/cody-chat-banner-image-${theme}.png`,
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="d-flex pb-3">
|
||||
<GlowingCodySVG />
|
||||
<div className="d-flex flex-column flex-grow-1 justify-content-center">
|
||||
<H4 className={styles.cardTitle}>{title}</H4>
|
||||
<ol className={classNames('m-0 pl-4 fs-6', styles.cardList)}>
|
||||
<div className="d-flex flex-column flex-grow-1 justify-content-center flex-shrink-0">
|
||||
<H4 as="h2" className={styles.cardTitle}>
|
||||
{title}
|
||||
</H4>
|
||||
<ol className={classNames('m-0 pl-4', styles.cardList)}>
|
||||
{useCases.map(useCase => (
|
||||
<Text key={useCase} as="li">
|
||||
{useCase}
|
||||
@ -160,7 +177,7 @@ const AuthUserWidgetContent: React.FC<WidgetContentProps> = ({ type, theme }) =>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames('d-flex justify-content-center', styles.cardImages)}>
|
||||
<img src={image} alt="Cody" className={styles.cardImage} />
|
||||
<img src={image} alt="Cody" className={classNames(styles.cardImage, 'percy-hide')} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@ -171,6 +188,7 @@ interface TryCodyWidgetProps extends TelemetryProps {
|
||||
type: 'blob' | 'repo'
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
context: Pick<SourcegraphContext, 'authProviders'>
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
export const TryCodyWidget: React.FC<TryCodyWidgetProps> = ({
|
||||
@ -179,6 +197,7 @@ export const TryCodyWidget: React.FC<TryCodyWidgetProps> = ({
|
||||
authenticatedUser,
|
||||
context,
|
||||
type,
|
||||
isSourcegraphDotCom,
|
||||
}) => {
|
||||
const isLightTheme = useIsLightTheme()
|
||||
const { isDismissed, onDismiss } = useTryCodyWidget(telemetryService)
|
||||
@ -196,7 +215,11 @@ export const TryCodyWidget: React.FC<TryCodyWidgetProps> = ({
|
||||
|
||||
return (
|
||||
<MarketingBlock
|
||||
wrapperClassName={classNames(className, type === 'blob' ? styles.blobCardWrapper : styles.repoCardWrapper)}
|
||||
wrapperClassName={classNames(
|
||||
className,
|
||||
type === 'blob' ? styles.blobCardWrapper : styles.repoCardWrapper,
|
||||
'mb-2'
|
||||
)}
|
||||
contentClassName={classNames(
|
||||
'd-flex position-relative pb-0 overflow-auto justify-content-between',
|
||||
styles.card,
|
||||
@ -209,9 +232,15 @@ export const TryCodyWidget: React.FC<TryCodyWidgetProps> = ({
|
||||
type={type}
|
||||
theme={isLightTheme ? 'light' : 'dark'}
|
||||
telemetryService={telemetryService}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
/>
|
||||
) : (
|
||||
<NoAuthWidgetContent telemetryService={telemetryService} type={type} context={context} />
|
||||
<NoAuthWidgetContent
|
||||
telemetryService={telemetryService}
|
||||
type={type}
|
||||
context={context}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
/>
|
||||
)}
|
||||
<Button className={classNames(styles.closeButton, 'position-absolute mt-2')} onClick={onDismiss}>
|
||||
<Icon svgPath={mdiClose} aria-label="Close try Cody widget" />
|
||||
|
||||
@ -99,6 +99,9 @@ describe('TreePage', () => {
|
||||
it('Should displays cody CTA', () => {
|
||||
const repo = repoDefaults()
|
||||
const props = treePagePropsDefaults(repo)
|
||||
window.context = window.context || {}
|
||||
window.context.codyEnabled = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
|
||||
const mockUser = {
|
||||
id: 'userID',
|
||||
|
||||
@ -46,6 +46,7 @@ import { AuthenticatedUser } from '../../auth'
|
||||
import { BatchChangesProps } from '../../batches'
|
||||
import { RepoBatchChangesButton } from '../../batches/RepoBatchChangesButton'
|
||||
import { CodeIntelligenceProps } from '../../codeintel'
|
||||
import { isCodyEnabled } from '../../cody/isCodyEnabled'
|
||||
import { BreadcrumbSetters } from '../../components/Breadcrumbs'
|
||||
import { PageTitle } from '../../components/PageTitle'
|
||||
import { useFeatureFlag } from '../../featureFlags/useFeatureFlag'
|
||||
@ -361,13 +362,14 @@ export const TreePage: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.treePage, className)}>
|
||||
{isSourcegraphDotCom && (
|
||||
{(isSourcegraphDotCom || isCodyEnabled()) && (
|
||||
<TryCodyWidget
|
||||
className="mb-2"
|
||||
telemetryService={props.telemetryService}
|
||||
type="repo"
|
||||
authenticatedUser={authenticatedUser}
|
||||
context={context}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
/>
|
||||
)}
|
||||
<Container className={styles.container}>
|
||||
|
||||
@ -5,5 +5,3 @@ export const isPackagesEnabled = (): boolean =>
|
||||
window.context?.experimentalFeatures?.rubyPackages === 'enabled' ||
|
||||
window.context?.experimentalFeatures?.pythonPackages === 'enabled' ||
|
||||
window.context?.experimentalFeatures?.rustPackages === 'enabled'
|
||||
|
||||
export const isCodyEnabled = (): boolean => window.context?.codyEnabled
|
||||
|
||||
@ -86,17 +86,14 @@ export const SearchPageContent: FC<SearchPageContentProps> = props => {
|
||||
) : (
|
||||
<>
|
||||
<SearchPageInput queryState={queryState} setQueryState={setQueryState} />
|
||||
{isSourcegraphDotCom && (
|
||||
<>
|
||||
{authenticatedUser ? (
|
||||
<TryCodyCtaSection className="mx-auto my-5" telemetryService={telemetryService} />
|
||||
) : (
|
||||
<TryCodySignUpCtaSection
|
||||
className="mx-auto my-5"
|
||||
telemetryService={telemetryService}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{authenticatedUser ? (
|
||||
<TryCodyCtaSection
|
||||
className="mx-auto my-5"
|
||||
telemetryService={telemetryService}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
/>
|
||||
) : (
|
||||
<TryCodySignUpCtaSection className="mx-auto my-5" telemetryService={telemetryService} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -23,10 +23,6 @@
|
||||
background-color: var(--color-bg-1);
|
||||
}
|
||||
|
||||
.vscode-icon {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.try-cody-link {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
@ -37,3 +33,11 @@
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.not-enabled-block {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
max-width: 720px;
|
||||
padding: 2rem;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import React from 'react'
|
||||
|
||||
import { mdiArrowRight, mdiClose, mdiMicrosoftVisualStudioCode } from '@mdi/js'
|
||||
import { mdiArrowRight, mdiClose } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary'
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { Button, ButtonLink, H2, H3, Icon, Link, Text } from '@sourcegraph/wildcard'
|
||||
|
||||
import { isCodyEnabled } from '../../../cody/isCodyEnabled'
|
||||
import { MarketingBlock } from '../../../components/MarketingBlock'
|
||||
import { EventName } from '../../../util/constants'
|
||||
|
||||
@ -67,22 +68,46 @@ const MeetCodySVG: React.FC = () => (
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const TryCodyCtaSection: React.FC<TelemetryProps & { className?: string }> = ({
|
||||
interface TryCodyCtaSectionProps extends TelemetryProps {
|
||||
isSourcegraphDotCom: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const TryCodyCtaSection: React.FC<TryCodyCtaSectionProps> = ({
|
||||
className,
|
||||
telemetryService,
|
||||
isSourcegraphDotCom,
|
||||
}) => {
|
||||
const [isDismissed = true, setIsDismissed] = useTemporarySetting('cody.searchPageCta.dismissed', false)
|
||||
const onDismiss = (): void => setIsDismissed(true)
|
||||
const logEvent = (eventName: EventName): void =>
|
||||
telemetryService.log(eventName, { type: 'ComHome' }, { type: 'ComHome' })
|
||||
const onInstallClick = (): void => logEvent(EventName.TRY_CODY_VSCODE)
|
||||
const onMarketplaceClick = (): void => logEvent(EventName.TRY_CODY_MARKETPLACE)
|
||||
const onViewEditorExtensionsClick = (): void => logEvent(EventName.VIEW_EDITOR_EXTENSIONS)
|
||||
const onTryWebClick = (): void => logEvent(EventName.TRY_CODY_WEB)
|
||||
|
||||
if (isDismissed) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isCodyEnabled()) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.notEnabledBlock, 'd-flex justify-content-center align-items-center mt-5')}
|
||||
>
|
||||
<div className={classNames(styles.codyIllustration)}>
|
||||
<MeetCodySVG />
|
||||
</div>
|
||||
<Text>
|
||||
<Link to="https://about.sourcegraph.com/cody?utm_source=server">Learn about Cody</Link>,
|
||||
Sourcegraph's AI coding assistant
|
||||
</Text>
|
||||
<Button className={classNames(styles.closeButton, 'position-absolute m-0')} onClick={onDismiss}>
|
||||
<Icon svgPath={mdiClose} aria-label="Close try Cody widget" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('d-flex position-relative pt-4', className, styles.container)}>
|
||||
<div className={classNames(styles.codyIllustration)}>
|
||||
@ -94,67 +119,111 @@ export const TryCodyCtaSection: React.FC<TelemetryProps & { className?: string }
|
||||
contentClassName={classNames('flex-grow-1 d-flex flex-column justify-content-between p-4', styles.card)}
|
||||
>
|
||||
<H3 className="d-flex align-items-center">
|
||||
<Icon
|
||||
svgPath={mdiMicrosoftVisualStudioCode}
|
||||
aria-hidden={true}
|
||||
className={classNames(styles.vscodeIcon, 'mr-1')}
|
||||
size="md"
|
||||
/>
|
||||
Install Cody for VS Code
|
||||
<CodyInIDEIcon aria-hidden={true} />
|
||||
Install Cody for your IDE
|
||||
</H3>
|
||||
<Text>
|
||||
Cody for VS Code provides the power of LLMs to help you generate and fix code, right where you
|
||||
Cody for your IDE provides the power of LLMs to help you generate and fix code, right where you
|
||||
commit.
|
||||
</Text>
|
||||
<div className="mb-2">
|
||||
<ButtonLink
|
||||
to="vscode:extension/sourcegraph.cody-ai"
|
||||
variant="merged"
|
||||
to="/help/cody#get-cody"
|
||||
variant="primary"
|
||||
className="d-inline-flex align-items-center"
|
||||
onClick={onInstallClick}
|
||||
onClick={onViewEditorExtensionsClick}
|
||||
>
|
||||
Install Cody for VS Code <Icon svgPath={mdiArrowRight} aria-hidden={true} size="md" />
|
||||
View editor extensions <Icon svgPath={mdiArrowRight} aria-hidden={true} size="md" />
|
||||
</ButtonLink>
|
||||
</div>
|
||||
<Text
|
||||
size="small"
|
||||
as={Link}
|
||||
to="https://marketplace.visualstudio.com/items?itemName=sourcegraph.cody-ai"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
onClick={onMarketplaceClick}
|
||||
>
|
||||
or download on the VS Code marketplace
|
||||
</Text>
|
||||
</MarketingBlock>
|
||||
<div className="d-flex flex-column justify-content-center p-4">
|
||||
<H3>Cody for Sourcegraph.com</H3>
|
||||
<Text>
|
||||
A free, helpful AI assistant, that explains, generates, and transpiles code, in the Sourcegraph web
|
||||
interface.
|
||||
</Text>
|
||||
<Text
|
||||
as={Link}
|
||||
to="/cody"
|
||||
className={classNames('d-flex align-items-center mb-2', styles.tryCodyLink)}
|
||||
onClick={onTryWebClick}
|
||||
>
|
||||
Try Cody chat
|
||||
<Icon svgPath={mdiArrowRight} aria-hidden={true} size="sm" className="ml-1" />
|
||||
</Text>
|
||||
<Text
|
||||
as={Link}
|
||||
to="https://sourcegraph.com/github.com/openai/openai-cookbook/-/blob/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py"
|
||||
className={classNames('d-flex align-items-center', styles.tryCodyLink)}
|
||||
onClick={onTryWebClick}
|
||||
>
|
||||
Try Cody on a file
|
||||
<Icon svgPath={mdiArrowRight} aria-hidden={true} size="sm" className="ml-1" />
|
||||
</Text>
|
||||
</div>
|
||||
<Button className={classNames(styles.closeButton, 'position-absolute m-0')} onClick={onDismiss}>
|
||||
<Icon svgPath={mdiClose} aria-label="Close try Cody widget" />
|
||||
</Button>
|
||||
{isSourcegraphDotCom ? (
|
||||
<>
|
||||
<div className="d-flex flex-column justify-content-center p-4">
|
||||
<H3>Cody for Sourcegraph.com</H3>
|
||||
<Text>
|
||||
A free, helpful AI assistant, that explains, generates, and transpiles code, in the
|
||||
Sourcegraph web interface.
|
||||
</Text>
|
||||
<Text
|
||||
as={Link}
|
||||
to="/cody"
|
||||
className={classNames('d-flex align-items-center mb-2', styles.tryCodyLink)}
|
||||
onClick={onTryWebClick}
|
||||
>
|
||||
Try Cody chat
|
||||
<Icon svgPath={mdiArrowRight} aria-hidden={true} size="sm" className="ml-1" />
|
||||
</Text>
|
||||
<Text
|
||||
as={Link}
|
||||
to="https://sourcegraph.com/github.com/openai/openai-cookbook/-/blob/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py"
|
||||
className={classNames('d-flex align-items-center', styles.tryCodyLink)}
|
||||
onClick={onTryWebClick}
|
||||
>
|
||||
Try Cody on a file
|
||||
<Icon svgPath={mdiArrowRight} aria-hidden={true} size="sm" className="ml-1" />
|
||||
</Text>
|
||||
</div>
|
||||
<Button className={classNames(styles.closeButton, 'position-absolute m-0')} onClick={onDismiss}>
|
||||
<Icon svgPath={mdiClose} aria-label="Close try Cody widget" />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="d-flex flex-column justify-content-center p-4">
|
||||
<H3>Try Cody AI in the web application</H3>
|
||||
<Text>
|
||||
Cody for Sourcegraph explains, generates, and translates code, right in the web interface.
|
||||
</Text>
|
||||
<Text
|
||||
as={Link}
|
||||
to="/cody"
|
||||
className={classNames('d-flex align-items-center mb-2', styles.tryCodyLink)}
|
||||
onClick={onTryWebClick}
|
||||
>
|
||||
Cody Chat
|
||||
<Icon svgPath={mdiArrowRight} aria-hidden={true} size="sm" className="ml-1" />
|
||||
</Text>
|
||||
</div>
|
||||
<Button className={classNames(styles.closeButton, 'position-absolute m-0')} onClick={onDismiss}>
|
||||
<Icon svgPath={mdiClose} aria-label="Close try Cody widget" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const CodyInIDEIcon: React.FunctionComponent<{ className?: string }> = ({ className }) => (
|
||||
<svg
|
||||
width="34"
|
||||
height="23"
|
||||
viewBox="10 0 48 33"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M17.5623 26.079C17.5623 26.4422 17.7005 26.7906 17.9467 27.0474C18.1928 27.3042 18.5267 27.4485 18.8748 27.4485H21.4998V30.1877H18.2185C17.4966 30.1877 16.2498 29.5714 16.2498 28.8181C16.2498 29.5714 15.0029 30.1877 14.281 30.1877H10.9998V27.4485H13.6248C13.9729 27.4485 14.3067 27.3042 14.5528 27.0474C14.799 26.7906 14.9373 26.4422 14.9373 26.079V6.90505C14.9373 6.54182 14.799 6.19347 14.5528 5.93662C14.3067 5.67978 13.9729 5.53549 13.6248 5.53549H10.9998V2.79636H14.281C15.0029 2.79636 16.2498 3.41266 16.2498 4.16592C16.2498 3.41266 17.4966 2.79636 18.2185 2.79636H21.4998V5.53549H18.8748C18.5267 5.53549 18.1928 5.67978 17.9467 5.93662C17.7005 6.19347 17.5623 6.54182 17.5623 6.90505V26.079Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M39.5458 7.992C40.6755 7.992 41.5912 8.86333 41.5912 9.93817V13.398C41.5912 14.4729 40.6755 15.3442 39.5458 15.3442C38.4161 15.3442 37.5003 14.4729 37.5003 13.398V9.93817C37.5003 8.86333 38.4161 7.992 39.5458 7.992Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M26.5908 12.533C26.5908 11.4581 27.5066 10.5868 28.6362 10.5868H32.2726C33.4023 10.5868 34.3181 11.4581 34.3181 12.533C34.3181 13.6078 33.4023 14.4791 32.2726 14.4791H28.6362C27.5066 14.4791 26.5908 13.6078 26.5908 12.533Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M44.4341 18.1987C45.1621 18.8567 45.1916 19.9517 44.5 20.6444L43.8564 21.2889C38.8205 26.3326 30.3304 26.207 25.4617 21.0167C24.7931 20.3039 24.8584 19.2103 25.6075 18.5741C26.3567 17.938 27.5061 18.0001 28.1747 18.7129C31.6275 22.3938 37.6486 22.4829 41.2201 18.906L41.8636 18.2614C42.5552 17.5687 43.7061 17.5407 44.4341 18.1987Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@ -20,6 +20,7 @@ export const enum EventName {
|
||||
CODY_SIGNUP = 'CodySignup',
|
||||
CODY_CHAT_DOWNLOAD_VSCODE = 'web:codyChat:downloadVSCode',
|
||||
CODY_CHAT_TRY_ON_PUBLIC_CODE = 'web:codyChat:tryOnPublicCode',
|
||||
VIEW_EDITOR_EXTENSIONS = 'CodyClickViewEditorExtensions',
|
||||
TRY_CODY_VSCODE = 'VSCodeInstall',
|
||||
TRY_CODY_MARKETPLACE = 'VSCodeMarketplace',
|
||||
TRY_CODY_WEB = 'TryCodyWeb',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user