Skip to content

Commit

Permalink
feat(ui): add multi context support to chat (TabbyML#2205)
Browse files Browse the repository at this point in the history
* feat(ui): multi context support

* git_url

* eventsource-parser

* navigation

* convert file context

* Update clients/tabby-chat-panel/src/index.ts

Co-authored-by: Meng Zhang <[email protected]>

* update

* revert

* useLatest callback

* Update ee/tabby-ui/lib/types/chat.ts

Co-authored-by: Meng Zhang <[email protected]>

* Update ee/tabby-ui/lib/types/chat.ts

Co-authored-by: Meng Zhang <[email protected]>

* update

---------

Co-authored-by: Meng Zhang <[email protected]>
  • Loading branch information
liangfung and wsxiaoys authored May 22, 2024
1 parent 4cc8eed commit 9c878e7
Show file tree
Hide file tree
Showing 15 changed files with 426 additions and 223 deletions.
1 change: 1 addition & 0 deletions clients/tabby-chat-panel/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface FileContext {
range: LineRange
filepath: string
content: string
git_url: string
}

export type Context = FileContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let delayTimer: number
interface Options {
language?: string
path: string
gitUrl: string
}

function ActionBarWidgetExtension(options: Options): Extension {
Expand Down Expand Up @@ -69,6 +70,7 @@ function createActionBarWidget(state: EditorState, options: Options): Tooltip {
lineFrom={lineFrom.number}
lineTo={lineTo.number}
path={options?.path}
gitUrl={options?.gitUrl}
/>
)
}, 1000)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface ActionBarWidgetProps extends React.HTMLAttributes<HTMLDivElement> {
path: string
lineFrom: number
lineTo: number
gitUrl: string
}

export const ActionBarWidget: React.FC<ActionBarWidgetProps> = ({
Expand All @@ -28,6 +29,7 @@ export const ActionBarWidget: React.FC<ActionBarWidgetProps> = ({
path,
lineFrom,
lineTo,
gitUrl,
...props
}) => {
const handleAction = (action: CodeBrowserQuickAction) => {
Expand All @@ -37,7 +39,8 @@ export const ActionBarWidget: React.FC<ActionBarWidgetProps> = ({
language,
path,
lineFrom,
lineTo
lineTo,
gitUrl
})
}

Expand Down
126 changes: 86 additions & 40 deletions ee/tabby-ui/app/files/components/chat-side-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import React from 'react'
import { find } from 'lodash-es'
import type { Context } from 'tabby-chat-panel'
import { useClient } from 'tabby-chat-panel/react'

import { useLatest } from '@/lib/hooks/use-latest'
import { useMe } from '@/lib/hooks/use-me'
import useRouterStuff from '@/lib/hooks/use-router-stuff'
import { useStore } from '@/lib/hooks/use-store'
import { useChatStore } from '@/lib/stores/chat-store'
import { UserMessage } from '@/lib/types'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { IconClose } from '@/components/ui/icons'
import { useTopbarProgress } from '@/components/topbar-progress-indicator'

import { QuickActionEventPayload } from '../lib/event-emitter'
import { SourceCodeBrowserContext } from './source-code-browser'
import { SourceCodeBrowserContext, TFileMap } from './source-code-browser'
import {
fetchEntriesFromPath,
getDirectoriesFromBasename,
resolveFileNameFromPath,
resolveRepoSpecifierFromRepoInfo
} from './utils'

interface ChatSideBarProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {}
Expand All @@ -21,25 +29,85 @@ export const ChatSideBar: React.FC<ChatSideBarProps> = ({
className,
...props
}) => {
const { setProgress } = useTopbarProgress()
const { updateSearchParams } = useRouterStuff()
const [{ data }] = useMe()
const { pendingEvent, setPendingEvent } = React.useContext(
SourceCodeBrowserContext
)
const {
pendingEvent,
setPendingEvent,
repoMap,
setExpandedKeys,
updateFileMap
} = React.useContext(SourceCodeBrowserContext)
const activeChatId = useStore(useChatStore, state => state.activeChatId)
const iframeRef = React.useRef<HTMLIFrameElement>(null)
const client = useClient(iframeRef, {
navigate: (context: Context) => {
if (context?.filepath) {
updateSearchParams({
set: {
path: context.filepath,
line: String(context.range.start ?? '')
},
del: 'plain'
})
const repoMapRef = useLatest(repoMap)

const onNavigate = async (context: Context) => {
if (context?.filepath && context?.git_url) {
const repoMap = repoMapRef.current
const matchedRepositoryKey = find(
Object.keys(repoMap),
key => repoMap?.[key]?.gitUrl === context.git_url
)
if (matchedRepositoryKey) {
const repository = repoMap[matchedRepositoryKey]
const repositorySpecifier = resolveRepoSpecifierFromRepoInfo(repository)
const fullPath = `${repositorySpecifier}/${context.filepath}`
if (!fullPath) return
try {
setProgress(true)
const entries = await fetchEntriesFromPath(
fullPath,
repositorySpecifier ? repoMap?.[repositorySpecifier] : undefined
)
const initialExpandedDirs = getDirectoriesFromBasename(
context.filepath
)

const patchMap: TFileMap = {}
// fetch dirs
for (const entry of entries) {
const path = `${repositorySpecifier}/${entry.basename}`
patchMap[path] = {
file: entry,
name: resolveFileNameFromPath(path),
fullPath: path,
treeExpanded: initialExpandedDirs.includes(entry.basename)
}
}
const expandedKeys = initialExpandedDirs.map(dir =>
[repositorySpecifier, dir].filter(Boolean).join('/')
)
if (patchMap) {
updateFileMap(patchMap)
}
if (expandedKeys?.length) {
setExpandedKeys(prevKeys => {
const newSet = new Set(prevKeys)
for (const k of expandedKeys) {
newSet.add(k)
}
return newSet
})
}
} catch (e) {
} finally {
updateSearchParams({
set: {
path: `${repositorySpecifier ?? ''}/${context.filepath}`,
line: String(context.range.start ?? '')
},
del: 'plain'
})
setProgress(false)
}
}
}
}

const client = useClient(iframeRef, {
navigate: onNavigate
})

const getPrompt = ({ action }: QuickActionEventPayload) => {
Expand All @@ -61,29 +129,6 @@ export const ChatSideBar: React.FC<ChatSideBarProps> = ({
return builtInPrompt
}

React.useEffect(() => {
const contentWindow = iframeRef.current?.contentWindow

if (pendingEvent) {
const { lineFrom, lineTo, code, path } = pendingEvent
contentWindow?.postMessage({
action: 'sendUserChat',
payload: {
message: getPrompt(pendingEvent),
selectContext: {
content: code,
range: {
start: lineFrom,
end: lineTo
},
filepath: path
}
} as UserMessage
})
setPendingEvent(undefined)
}
}, [pendingEvent, iframeRef.current?.contentWindow])

React.useEffect(() => {
if (iframeRef?.current && data) {
client?.init({
Expand All @@ -96,7 +141,7 @@ export const ChatSideBar: React.FC<ChatSideBarProps> = ({

React.useEffect(() => {
if (pendingEvent && client) {
const { lineFrom, lineTo, code, path } = pendingEvent
const { lineFrom, lineTo, code, path, gitUrl } = pendingEvent
client.sendMessage({
message: getPrompt(pendingEvent),
selectContext: {
Expand All @@ -106,7 +151,8 @@ export const ChatSideBar: React.FC<ChatSideBarProps> = ({
start: lineFrom,
end: lineTo ?? lineFrom
},
filepath: path
filepath: path,
git_url: gitUrl
}
})
}
Expand Down
15 changes: 12 additions & 3 deletions ee/tabby-ui/app/files/components/code-editor-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
import useRouterStuff from '@/lib/hooks/use-router-stuff'

import { emitter, LineMenuActionEventPayload } from '../lib/event-emitter'
import { resolveRepositoryInfoFromPath } from './utils'

interface CodeEditorViewProps {
value: string
Expand All @@ -43,9 +44,14 @@ const CodeEditorView: React.FC<CodeEditorViewProps> = ({ value, language }) => {
const line = searchParams.get('line')?.toString()
const [editorView, setEditorView] = React.useState<EditorView | null>(null)

const { isChatEnabled, activePath } = React.useContext(
const { isChatEnabled, activePath, fileMap } = React.useContext(
SourceCodeBrowserContext
)
const { repositorySpecifier, basename } =
resolveRepositoryInfoFromPath(activePath)
const gitUrl = repositorySpecifier
? fileMap[repositorySpecifier]?.repository?.gitUrl ?? ''
: ''

const extensions = React.useMemo(() => {
let result: Extension[] = [
Expand Down Expand Up @@ -83,9 +89,12 @@ const CodeEditorView: React.FC<CodeEditorViewProps> = ({ value, language }) => {
if (
EXP_enable_code_browser_quick_action_bar.value &&
isChatEnabled &&
activePath
activePath &&
basename
) {
result.push(ActionBarWidgetExtension({ language, path: activePath }))
result.push(
ActionBarWidgetExtension({ language, path: basename, gitUrl })
)
}
if (value && tags) {
result.push(
Expand Down
1 change: 1 addition & 0 deletions ee/tabby-ui/app/files/components/source-code-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const repositoryListQuery = graphql(/* GraphQL */ `
id
name
kind
gitUrl
}
}
`)
Expand Down
1 change: 1 addition & 0 deletions ee/tabby-ui/app/files/lib/event-emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type QuickActionEventPayload = {
path: string
lineFrom: number
lineTo?: number
gitUrl: string
}

type LineMenuActionEventPayload = {
Expand Down
1 change: 1 addition & 0 deletions ee/tabby-ui/app/playground/components/chats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export default function Chats() {
const style = isShowDemoBanner
? { height: `calc(100vh - ${BANNER_HEIGHT})` }
: { height: '100vh' }

return (
<div className="grid flex-1 overflow-hidden lg:grid-cols-[280px_1fr]">
<ChatSessions
Expand Down
Loading

0 comments on commit 9c878e7

Please sign in to comment.