mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 13:11:49 +00:00
feat(svelte): Add Cody chat sidebar (#63638)
This commit adds the React version of the cody chat sidebar to the Svelte web app. I used @vovakulikov's work in the React app as guidance. I'm sure we'll have to do follow up work, but it's a start. This PR also fixes `sg start web-sveltekit-standalone` (because I originally thought that running it from `sourcegraph.test` is necessary to make auth work). ## Test plan Manual testing. https://github.com/sourcegraph/sourcegraph/assets/179026/3fa3f2ea-b23e-44ca-a75a-0089bf07fb2b
This commit is contained in:
parent
3e1c8fce97
commit
f0c9551f56
@ -64,6 +64,8 @@ BUILD_DEPS = [
|
||||
"//:node_modules/@reach/menu-button",
|
||||
"//:node_modules/@types/lodash",
|
||||
"//:node_modules/@types/node",
|
||||
"//:node_modules/@types/react",
|
||||
"//:node_modules/@types/react-dom",
|
||||
"//:node_modules/classnames",
|
||||
"//:node_modules/copy-to-clipboard",
|
||||
"//:node_modules/date-fns",
|
||||
@ -71,11 +73,15 @@ BUILD_DEPS = [
|
||||
"//:node_modules/lodash-es",
|
||||
"//:node_modules/open-color",
|
||||
"//:node_modules/path-browserify",
|
||||
"//:node_modules/react",
|
||||
"//:node_modules/react-dom",
|
||||
"//:node_modules/react-resizable",
|
||||
"//:node_modules/rxjs",
|
||||
"//:node_modules/uuid",
|
||||
":node_modules/@faker-js/faker",
|
||||
":node_modules/@floating-ui/dom",
|
||||
":node_modules/@fontsource-variable/inter",
|
||||
":node_modules/@fontsource-variable/roboto-mono",
|
||||
":node_modules/@graphql-codegen/cli",
|
||||
":node_modules/@graphql-codegen/near-operation-file-preset",
|
||||
":node_modules/@graphql-codegen/typed-document-node",
|
||||
@ -92,19 +98,18 @@ BUILD_DEPS = [
|
||||
":node_modules/@sourcegraph/branded",
|
||||
":node_modules/@sourcegraph/client-api",
|
||||
":node_modules/@sourcegraph/common",
|
||||
":node_modules/@fontsource-variable/inter",
|
||||
":node_modules/@fontsource-variable/roboto-mono",
|
||||
":node_modules/@sourcegraph/http-client",
|
||||
":node_modules/@sourcegraph/shared",
|
||||
":node_modules/@sourcegraph/telemetry",
|
||||
":node_modules/@sourcegraph/web",
|
||||
":node_modules/@sourcegraph/wildcard",
|
||||
":node_modules/@sourcegraph/telemetry",
|
||||
":node_modules/@storybook/svelte",
|
||||
":node_modules/@sveltejs/adapter-static",
|
||||
":node_modules/@sveltejs/kit",
|
||||
":node_modules/@sveltejs/vite-plugin-svelte",
|
||||
":node_modules/@types/prismjs",
|
||||
":node_modules/@urql/core",
|
||||
":node_modules/cody-web-experimental",
|
||||
":node_modules/fzf",
|
||||
":node_modules/graphql",
|
||||
":node_modules/hotkeys-js",
|
||||
|
||||
@ -92,6 +92,7 @@
|
||||
"@sourcegraph/wildcard": "workspace:*",
|
||||
"@storybook/test": "^8.0.5",
|
||||
"@urql/core": "^4.2.3",
|
||||
"cody-web-experimental": "^0.1.4",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"fzf": "^0.5.2",
|
||||
"highlight.js": "^10.0.0",
|
||||
|
||||
4
client/web-sveltekit/src/lib/cody/CodySidebar.gql
Normal file
4
client/web-sveltekit/src/lib/cody/CodySidebar.gql
Normal file
@ -0,0 +1,4 @@
|
||||
fragment CodySidebar_ResolvedRevision on Repository {
|
||||
id
|
||||
name
|
||||
}
|
||||
73
client/web-sveltekit/src/lib/cody/CodySidebar.svelte
Normal file
73
client/web-sveltekit/src/lib/cody/CodySidebar.svelte
Normal file
@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import Icon from '$lib/Icon.svelte'
|
||||
import LoadingSpinner from '$lib/LoadingSpinner.svelte'
|
||||
import { user } from '$lib/stores'
|
||||
import Tooltip from '$lib/Tooltip.svelte'
|
||||
import { Alert, Badge, Button } from '$lib/wildcard'
|
||||
|
||||
import type { CodySidebar_ResolvedRevision } from './CodySidebar.gql'
|
||||
|
||||
export let repository: CodySidebar_ResolvedRevision
|
||||
export let filePath: string
|
||||
|
||||
const dispatch = createEventDispatcher<{ close: void }>()
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<div class="header">
|
||||
<h3>
|
||||
<Icon icon={ISgCody} /> Cody
|
||||
<Badge variant="warning">Experimental</Badge>
|
||||
</h3>
|
||||
<Tooltip tooltip="Close Cody chat">
|
||||
<Button variant="icon" aria-label="Close Cody" on:click={() => dispatch('close')}>
|
||||
<Icon icon={ILucideX} inline aria-hidden />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{#if $user}
|
||||
{#await import('./CodySidebarChat.svelte')}
|
||||
<LoadingSpinner />
|
||||
{:then module}
|
||||
<svelte:component this={module.default} {repository} {filePath} />
|
||||
{/await}
|
||||
{:else}
|
||||
<Alert variant="info">
|
||||
<strong>Cody is only available to signed-in users.</strong>
|
||||
<a href="/sign-in">Sign in</a> to use Cody.
|
||||
</Alert>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: var(--font-size-small);
|
||||
background-color: var(--input-bg);
|
||||
border-bottom: 1px solid var(--border-color-2);
|
||||
padding: 0.25rem 1rem;
|
||||
|
||||
// Shows the cody icon in color
|
||||
--icon-color: initial;
|
||||
|
||||
h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
93
client/web-sveltekit/src/lib/cody/CodySidebarChat.svelte
Normal file
93
client/web-sveltekit/src/lib/cody/CodySidebarChat.svelte
Normal file
@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
import { CodyWebChat, CodyWebChatProvider } from 'cody-web-experimental'
|
||||
import { createElement } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { onDestroy } from 'svelte'
|
||||
import type { CodySidebar_ResolvedRevision } from './CodySidebar.gql'
|
||||
|
||||
import 'cody-web-experimental/dist/style.css'
|
||||
import { createLocalWritable } from '$lib/stores'
|
||||
|
||||
export let repository: CodySidebar_ResolvedRevision
|
||||
export let filePath: string
|
||||
|
||||
const chatIDs = createLocalWritable<Record<string, string>>('cody.context-to-chat-ids', {})
|
||||
let container: HTMLDivElement
|
||||
let root: Root | null
|
||||
|
||||
$: if (container) {
|
||||
render(repository, filePath)
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
root?.unmount()
|
||||
root = null
|
||||
})
|
||||
|
||||
function render(repository: CodySidebar_ResolvedRevision, filePath: string) {
|
||||
if (!root) {
|
||||
root = createRoot(container)
|
||||
}
|
||||
const chat = createElement(CodyWebChat)
|
||||
const provider = createElement(
|
||||
CodyWebChatProvider,
|
||||
{
|
||||
accessToken: '',
|
||||
chatID: $chatIDs[`${repository.id}-${filePath}`] ?? null,
|
||||
initialContext: {
|
||||
repositories: [repository],
|
||||
fileURL: filePath ? (!filePath.startsWith('/') ? `/${filePath}` : filePath) : undefined,
|
||||
},
|
||||
serverEndpoint: window.location.origin,
|
||||
onNewChatCreated: (chatID: string) => {
|
||||
chatIDs.update(ids => {
|
||||
ids[`${repository.id}-${filePath}`] = chatID
|
||||
return ids
|
||||
})
|
||||
},
|
||||
},
|
||||
[chat]
|
||||
)
|
||||
root.render(provider)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="chat" bind:this={container} />
|
||||
|
||||
<style lang="scss">
|
||||
.chat {
|
||||
--vscode-editor-background: var(--body-bg);
|
||||
--vscode-editor-foreground: var(--body-color);
|
||||
--vscode-input-background: var(--input-bg);
|
||||
--vscode-input-foreground: var(--body-color);
|
||||
--vscode-textLink-foreground: var(--primary);
|
||||
--vscode-input-border: var(--border-color-2);
|
||||
--vscode-inputOption-activeBackground: var(--search-input-token-filter);
|
||||
--vscode-inputOption-activeForeground: var(--body-color);
|
||||
--vscode-loading-dot-color: var(--body-color);
|
||||
--mention-color-opacity: 100%;
|
||||
|
||||
height: 100%;
|
||||
|
||||
:global(h3) {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(ul) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(a) {
|
||||
color: var(--link-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
:global([data-floating-ui-portal]) {
|
||||
--vscode-quickInput-background: var(--secondary-2);
|
||||
--vscode-widget-border: var(--border-color);
|
||||
--vscode-list-activeSelectionBackground: var(--primary);
|
||||
--vscode-foreground: var(--body-color);
|
||||
--vscode-widget-shadow: rgba(36, 41, 54, 0.2);
|
||||
}
|
||||
</style>
|
||||
23
client/web-sveltekit/src/lib/repo/OpenCodyAction.svelte
Normal file
23
client/web-sveltekit/src/lib/repo/OpenCodyAction.svelte
Normal file
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import Icon from '$lib/Icon.svelte'
|
||||
import { rightPanelOpen } from '$lib/repo/stores'
|
||||
import Tooltip from '$lib/Tooltip.svelte'
|
||||
|
||||
function handleClick(): void {
|
||||
$rightPanelOpen = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<Tooltip tooltip="Open Cody chat">
|
||||
<button on:click={handleClick}>
|
||||
<Icon icon={ISgCody} />
|
||||
<span data-action-label>Cody</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<style lang="scss">
|
||||
button {
|
||||
all: unset;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,7 @@
|
||||
import { memoize } from 'lodash'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
import { createLocalWritable } from '$lib/stores'
|
||||
import { createEmptySingleSelectTreeState, type TreeState } from '$lib/TreeView'
|
||||
|
||||
/**
|
||||
@ -11,3 +12,5 @@ export const getSidebarFileTreeStateForRepo = memoize(
|
||||
(_repoName: string): Writable<TreeState> => writable<TreeState>(createEmptySingleSelectTreeState()),
|
||||
repoName => repoName
|
||||
)
|
||||
|
||||
export const rightPanelOpen = createLocalWritable<boolean>('repo.right-panel.open', false)
|
||||
|
||||
@ -41,6 +41,7 @@
|
||||
|
||||
import { afterNavigate, goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import CodySidebar from '$lib/cody/CodySidebar.svelte'
|
||||
import { isErrorLike, SourcegraphURL } from '$lib/common'
|
||||
import { openFuzzyFinder } from '$lib/fuzzyfinder/FuzzyFinderContainer.svelte'
|
||||
import { filesHotkey } from '$lib/fuzzyfinder/keys'
|
||||
@ -50,6 +51,7 @@
|
||||
import { fetchSidebarFileTree } from '$lib/repo/api/tree'
|
||||
import HistoryPanel from '$lib/repo/HistoryPanel.svelte'
|
||||
import LastCommit from '$lib/repo/LastCommit.svelte'
|
||||
import { rightPanelOpen } from '$lib/repo/stores'
|
||||
import TabPanel from '$lib/TabPanel.svelte'
|
||||
import Tabs from '$lib/Tabs.svelte'
|
||||
import Tooltip from '$lib/Tooltip.svelte'
|
||||
@ -249,8 +251,22 @@
|
||||
|
||||
<Panel id="blob-content-panels" order={2}>
|
||||
<PanelGroup id="content-panels" direction="vertical">
|
||||
<Panel id="main-content-panel" order={1}>
|
||||
<slot />
|
||||
<Panel id="content-panel" order={1}>
|
||||
<PanelGroup id="content-sidebar-panels">
|
||||
<Panel order={1} id="main-content-panel">
|
||||
<slot />
|
||||
</Panel>
|
||||
{#if $rightPanelOpen}
|
||||
<PanelResizeHandle id="right-sidebar-resize-handle" />
|
||||
<Panel id="right-sidebar-panel" order={2} minSize={20} maxSize={70}>
|
||||
<CodySidebar
|
||||
repository={data.resolvedRevision.repo}
|
||||
filePath={data.filePath}
|
||||
on:close={() => ($rightPanelOpen = false)}
|
||||
/>
|
||||
</Panel>
|
||||
{/if}
|
||||
</PanelGroup>
|
||||
</Panel>
|
||||
<PanelResizeHandle />
|
||||
<Panel
|
||||
@ -345,6 +361,7 @@
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
:global([data-panel-resize-handle-id='right-sidebar-resize-handle']),
|
||||
:global([data-panel-resize-handle-id='blob-page-panels-separator']) {
|
||||
&::before {
|
||||
// Even though side-panel shadow should be rendered over
|
||||
@ -438,6 +455,11 @@
|
||||
box-shadow: var(--bottom-panel-shadow);
|
||||
}
|
||||
|
||||
:global([data-panel-id='right-sidebar-panel']) {
|
||||
z-index: 1;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
--align-tabs: flex-start;
|
||||
|
||||
|
||||
@ -53,15 +53,15 @@ fragment FileViewCodeGraphData on CodeGraphData {
|
||||
|
||||
fragment FileViewOccurrence on SCIPOccurrence {
|
||||
symbol
|
||||
range {
|
||||
start {
|
||||
line
|
||||
character
|
||||
}
|
||||
end {
|
||||
line
|
||||
character
|
||||
}
|
||||
range {
|
||||
start {
|
||||
line
|
||||
character
|
||||
}
|
||||
end {
|
||||
line
|
||||
character
|
||||
}
|
||||
}
|
||||
roles
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
import { FileViewGitBlob, FileViewHighlightedFile } from './FileView.gql'
|
||||
import FileViewModeSwitcher from './FileViewModeSwitcher.svelte'
|
||||
import OpenInCodeHostAction from './OpenInCodeHostAction.svelte'
|
||||
import OpenCodyAction from '$lib/repo/OpenCodyAction.svelte'
|
||||
import { CodeViewMode, toCodeViewMode } from './util'
|
||||
|
||||
export let data: Extract<PageData, { type: 'FileView' }>
|
||||
@ -184,6 +185,7 @@
|
||||
<OpenInCodeHostAction data={blob} lineOrPosition={data.lineOrPosition} />
|
||||
{/if}
|
||||
<Permalink {commitID} />
|
||||
<OpenCodyAction />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actionmenu">
|
||||
<MenuLink href={rawURL} target="_blank">
|
||||
|
||||
@ -64,8 +64,8 @@ query BlobFileViewCodeGraphDataQuery($repoName: String!, $revspec: String!, $pat
|
||||
|
||||
query BlobViewCodeGraphDataNextPage($codeGraphDataID: ID!, $after: String!) {
|
||||
node(id: $codeGraphDataID) {
|
||||
...on CodeGraphData {
|
||||
occurrences(first: 10000, after: $after){
|
||||
... on CodeGraphData {
|
||||
occurrences(first: 10000, after: $after) {
|
||||
nodes {
|
||||
...FileViewOccurrence
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
import Readme from '$lib/repo/Readme.svelte'
|
||||
import { createPromiseStore } from '$lib/utils'
|
||||
import { Alert } from '$lib/wildcard'
|
||||
import OpenCodyAction from '$lib/repo/OpenCodyAction.svelte'
|
||||
|
||||
import { getRepositoryPageContext } from '../../../../../context'
|
||||
|
||||
@ -37,6 +38,7 @@
|
||||
<FileHeader type="tree" repoName={data.repoName} revision={data.revision} path={data.filePath}>
|
||||
<svelte:fragment slot="actions">
|
||||
<Permalink commitID={data.resolvedRevision.commitID} />
|
||||
<OpenCodyAction />
|
||||
</svelte:fragment>
|
||||
</FileHeader>
|
||||
|
||||
|
||||
@ -34,4 +34,5 @@ fragment ResolvedRepository on Repository {
|
||||
}
|
||||
...RepoPage_ResolvedRevision
|
||||
...BlobPage_ResolvedRevision
|
||||
...CodySidebar_ResolvedRevision
|
||||
}
|
||||
|
||||
@ -72,18 +72,25 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
},
|
||||
server: {
|
||||
// When running behind caddy we have to listen to a different host.
|
||||
host: process.env.SK_HOST || 'localhost',
|
||||
// Allow setting the port via env variables to make it easier to integrate with
|
||||
// our existing caddy setup (which proxies requests to a specific port).
|
||||
port: process.env.SK_PORT ? +process.env.SK_PORT : undefined,
|
||||
strictPort: !!process.env.SV_PORT,
|
||||
strictPort: !!process.env.SK_PORT,
|
||||
proxy: {
|
||||
// Proxy requests to specific endpoints to a real Sourcegraph
|
||||
// instance.
|
||||
'^(/sign-in|/.assets|/-|/.api|/search/stream|/users|/notebooks|/insights|/batch-changes)|/-/(raw|compare|own|code-graph|batch-changes|settings)(/|$)':
|
||||
'^(/sign-(in|out)|/.assets|/-|/.api|/.auth|/search/stream|/users|/notebooks|/insights|/batch-changes)|/-/(raw|compare|own|code-graph|batch-changes|settings)(/|$)':
|
||||
{
|
||||
target: process.env.SOURCEGRAPH_API_URL || 'https://sourcegraph.sourcegraph.com',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
headers: {
|
||||
// This needs to be set to make the cody sidebar work, which doesn't use the web graphql client work.
|
||||
// todo(fkling): Figure out how the React app makes this work without this header.
|
||||
'X-Requested-With': 'Sourcegraph',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1593,6 +1593,9 @@ importers:
|
||||
'@urql/core':
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3(graphql@15.4.0)
|
||||
cody-web-experimental:
|
||||
specifier: ^0.1.4
|
||||
version: 0.1.4
|
||||
copy-to-clipboard:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
|
||||
@ -510,6 +510,12 @@ commands:
|
||||
install: |
|
||||
pnpm install
|
||||
pnpm generate
|
||||
env:
|
||||
SOURCEGRAPH_API_URL: https://sourcegraph.sourcegraph.com
|
||||
# The SvelteKit app uses this environment variable to determine where
|
||||
# to store the generated assets. We don't need to store them in a different
|
||||
# place in standalone mode.
|
||||
DEPLOY_TYPE: ""
|
||||
|
||||
web-sveltekit-prod-watch:
|
||||
description: Builds the prod version of the SvelteKit web app and rebuilds on changes
|
||||
@ -1789,6 +1795,7 @@ commandsets:
|
||||
- caddy
|
||||
env:
|
||||
SK_PORT: 3080
|
||||
SK_HOST: 127.0.0.1
|
||||
|
||||
# For testing our OpenTelemetry stack
|
||||
otel:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user