From 8128615b777723fc01eecb23d4214b115d259f30 Mon Sep 17 00:00:00 2001 From: liangfung Date: Fri, 27 Dec 2024 15:45:34 +0800 Subject: [PATCH] update --- clients/tabby-chat-panel/src/index.ts | 17 ------ clients/vscode/src/chat/WebviewHelper.ts | 3 +- clients/vscode/src/chat/chat.d.ts | 7 +++ clients/vscode/src/chat/fileContext.ts | 1 - clients/vscode/src/chat/utils.ts | 78 +++++++++++++++++++++--- ee/tabby-ui/lib/types/chat.ts | 14 ----- ee/tabby-ui/lib/utils/chat.ts | 47 +++++++------- 7 files changed, 102 insertions(+), 65 deletions(-) create mode 100644 clients/vscode/src/chat/chat.d.ts diff --git a/clients/tabby-chat-panel/src/index.ts b/clients/tabby-chat-panel/src/index.ts index e093b68b54a3..6ce003335471 100644 --- a/clients/tabby-chat-panel/src/index.ts +++ b/clients/tabby-chat-panel/src/index.ts @@ -67,23 +67,6 @@ export interface EditorFileContext { * If the range is not provided, the whole file is considered. */ range?: LineRange | PositionRange - // /** - // * The information of a jupyter notebook file - // */ - // notebookCell?: { - // /** - // * numeric identifier of the cell - // */ - // handle: number - // /** - // * The scheme of the notebook cell document - // * eg: 'file', 'untitled' - // */ - // scheme: string - // } - /** - * The content of the file context. - */ content: string } diff --git a/clients/vscode/src/chat/WebviewHelper.ts b/clients/vscode/src/chat/WebviewHelper.ts index 255d9f5a078e..5a7af8461307 100644 --- a/clients/vscode/src/chat/WebviewHelper.ts +++ b/clients/vscode/src/chat/WebviewHelper.ts @@ -42,6 +42,7 @@ import { vscodeRangeToChatPanelPositionRange, chatPanelLocationToVSCodeRange, } from "./utils"; +import { Schemes } from "./chat"; export class WebviewHelper { webview?: Webview; @@ -264,7 +265,7 @@ export class WebviewHelper { } public isSupportedSchemeForActiveSelection(scheme: string) { - const supportedSchemes = ["file", "untitled", "vscode-notebook-cell"]; + const supportedSchemes: string[] = [Schemes.file, Schemes.untitled, Schemes.vscodeNotebookCell]; return supportedSchemes.includes(scheme); } diff --git a/clients/vscode/src/chat/chat.d.ts b/clients/vscode/src/chat/chat.d.ts new file mode 100644 index 000000000000..08317279495a --- /dev/null +++ b/clients/vscode/src/chat/chat.d.ts @@ -0,0 +1,7 @@ +export enum Schemes { + file = 'file', + untitled = 'untitled', + vscodeNotebookCell = 'vscode-notebook-cell', + vscodeVfs = 'vscode-vfs', + vscodeUserdata = 'vscode-userdata', +} \ No newline at end of file diff --git a/clients/vscode/src/chat/fileContext.ts b/clients/vscode/src/chat/fileContext.ts index 27810f249538..c008b0543375 100644 --- a/clients/vscode/src/chat/fileContext.ts +++ b/clients/vscode/src/chat/fileContext.ts @@ -26,7 +26,6 @@ export async function getFileContext( end: editor.selection.end.line + 1, } : undefined; - const filepath = localUriToChatPanelFilepath(editor.document.uri, gitProvider); return { diff --git a/clients/vscode/src/chat/utils.ts b/clients/vscode/src/chat/utils.ts index b9a01c791b9c..bba9bcd1db1b 100644 --- a/clients/vscode/src/chat/utils.ts +++ b/clients/vscode/src/chat/utils.ts @@ -10,6 +10,7 @@ import type { } from "tabby-chat-panel"; import type { GitProvider } from "../git/GitProvider"; import { getLogger } from "../logger"; +import { Schemes } from "./chat"; const logger = getLogger("chat/utils"); @@ -22,8 +23,12 @@ export function localUriToChatPanelFilepath(uri: Uri, gitProvider: GitProvider): } const gitRemoteUrl = repo ? gitProvider.getDefaultRemoteUrl(repo) : undefined; if (repo && gitRemoteUrl) { - const uriFilePath = - uri.scheme === "vscode-notebook-cell" ? uri.with({ scheme: "file" }).toString(true) : uri.toString(true); + let uriFilePath: string = uri.toString(true); + + if (uri.scheme === Schemes.vscodeNotebookCell) { + uriFilePath = localUriToUriFilePath(uri); + } + const relativeFilePath = path.relative(repo.rootUri.toString(true), uriFilePath); if (!relativeFilePath.startsWith("..")) { return { @@ -33,12 +38,26 @@ export function localUriToChatPanelFilepath(uri: Uri, gitProvider: GitProvider): }; } } + return { kind: "uri", - uri: uri.toString(true), + uri: localUriToUriFilePath(uri), }; } +function localUriToUriFilePath(uri: Uri): string { + let uriFilePath = uri.toString(true); + + if (uri.scheme === Schemes.vscodeNotebookCell) { + const notebook = parseNotebookCellUri(uri); + if (notebook) { + // add fragment `#cell={number}` to filepath + uriFilePath = uri.with({ scheme: notebook.notebook.scheme, fragment: `cell=${notebook.handle}` }).toString(true); + } + } + return uriFilePath; +} + export function chatPanelFilepathToLocalUri(filepath: Filepath, gitProvider: GitProvider): Uri | null { if (filepath.kind === "uri") { try { @@ -73,10 +92,24 @@ function chatPanelFilepathToVscodeNotebookCellUri(root: Uri, filepath: FilepathI return null; } - const parsedUrl = new URL(filepath.filepath, "file://"); - const hash = parsedUrl.hash; - const cleanPath = parsedUrl.pathname; - return Uri.joinPath(root, cleanPath).with({ scheme: "vscode-notebook-cell", fragment: hash.slice(1) }); + const uri = Uri.joinPath(root, filepath.filepath); + let handle: number | undefined; + const fragment = uri.fragment; + if (fragment.startsWith("cell=")) { + const handleStr = fragment.slice("cell=".length); + const _handle = parseInt(handleStr, 10); + if (isNaN(_handle)) { + return uri; + } + } + + if (typeof handle === "undefined") { + logger.warn(`Invalid handle in filepath.`, filepath.filepath); + return uri; + } + + const cellUri = generateNotebookCellUri(uri, handle); + return cellUri; } export function vscodePositionToChatPanelPosition(position: VSCodePosition): ChatPanelPosition { @@ -129,3 +162,34 @@ export function chatPanelLocationToVSCodeRange(location: Location | undefined): logger.warn(`Invalid location params.`, location); return null; } + +const nb_lengths = ["W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f"]; +const nb_padRegexp = new RegExp(`^[${nb_lengths.join("")}]+`); +const nb_radix = 7; +export function parseNotebookCellUri(cell: Uri): { notebook: Uri; handle: number } | undefined { + if (cell.scheme !== Schemes.vscodeNotebookCell) { + return undefined; + } + + const idx = cell.fragment.indexOf("s"); + if (idx < 0) { + return undefined; + } + + const handle = parseInt(cell.fragment.substring(0, idx).replace(nb_padRegexp, ""), nb_radix); + const _scheme = Buffer.from(cell.fragment.substring(idx + 1), "base64").toString("utf-8"); + if (isNaN(handle)) { + return undefined; + } + return { + handle, + notebook: cell.with({ scheme: _scheme, fragment: "" }), + }; +} + +export function generateNotebookCellUri(notebook: Uri, handle: number): Uri { + const s = handle.toString(nb_radix); + const p = s.length < nb_lengths.length ? nb_lengths[s.length - 1] : "z"; + const fragment = `${p}${s}s${Buffer.from(notebook.scheme).toString("base64")}`; + return notebook.with({ scheme: Schemes.vscodeNotebookCell, fragment }); +} diff --git a/ee/tabby-ui/lib/types/chat.ts b/ee/tabby-ui/lib/types/chat.ts index 0aa018b3703e..515c923da74c 100644 --- a/ee/tabby-ui/lib/types/chat.ts +++ b/ee/tabby-ui/lib/types/chat.ts @@ -17,20 +17,6 @@ export interface FileContext { * If the range is not provided, the whole file is considered. */ range?: { start: number; end: number } - // /** - // * The info of jupyter notebook cell - // */ - // notebookCell?: { - // /** - // * numeric identifier of the cell - // */ - // handle: number - // /** - // * The scheme of the document in notebook cell - // * eg 'file', 'untitled' - // */ - // scheme: string - // } content: string git_url: string } diff --git a/ee/tabby-ui/lib/utils/chat.ts b/ee/tabby-ui/lib/utils/chat.ts index 531a482e8d5f..cda43acffa02 100644 --- a/ee/tabby-ui/lib/utils/chat.ts +++ b/ee/tabby-ui/lib/utils/chat.ts @@ -106,31 +106,29 @@ export function checkSourcesAvailability( return { hasCodebaseSource, hasDocumentSource } } -function parseNotebookCellFragment(fragment: string) { +/** + * url e.g: path/to/file.ipynb#handle=1 + * @param uri + * @returns + */ +function parseNotebookCellUri(fragment: string) { if (!fragment) return undefined + try { + if (!fragment.startsWith('cell=')) { + return undefined + } - const _lengths = ['W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f'] - const _padRegexp = new RegExp(`^[${_lengths.join('')}]+`) - const _radix = 7 - const idx = fragment.indexOf('s') - if (idx < 0) { - return undefined - } - const handle = parseInt( - fragment.substring(0, idx).replace(_padRegexp, ''), - _radix - ) - const scheme = Buffer.from(fragment.substring(idx + 1), 'base64').toString( - 'utf-8' - ) - - if (isNaN(handle)) { + const handle = parseInt(fragment.slice('cell='.length), 10) + + if (isNaN(handle)) { + return undefined + } + return { + handle + } + } catch (error) { return undefined } - return { - handle, - scheme - } } export function resolveFileNameForDisplay(uri: string) { @@ -144,10 +142,9 @@ export function resolveFileNameForDisplay(uri: string) { const extname = filename.includes('.') ? `.${filename.split('.').pop()}` : '' const isNotebook = extname.startsWith('.ipynb') const hash = url.hash ? url.hash.substring(1) : '' - const notebook = parseNotebookCellFragment(hash) - - if (isNotebook && notebook) { - return `${filename} · Cell ${(notebook.handle || 0) + 1}` + const cell = parseNotebookCellUri(hash) + if (isNotebook && cell) { + return `${filename} · Cell ${(cell.handle || 0) + 1}` } return filename }