diff --git a/README_CN.md b/README_CN.md index e35ac4a..63c3764 100644 --- a/README_CN.md +++ b/README_CN.md @@ -68,7 +68,7 @@ ## 🚀 快速开始 -> 确保你有一个 Open AI Key 或 Anthropic Key,如果没有,请访问 [Open AI](https://platform.openai.com/account/api-keys) 或 [Anthropic](https://www.anthropic.com/product/) 申请。 +> 1. 确保你有一个 Open AI Key 或 Anthropic Key,如果没有,请访问 [Open AI](https://platform.openai.com/account/api-keys) 或 [Anthropic](https://www.anthropic.com/product/) 申请。 > 2. 确保你的命令终端能 ping 通 google.com (如果你在中国大陆,你可能需要科学上网)。 ### 方式一:CLI diff --git a/packages/gpt-runner-core/src/core/get-gpt-file-tree.ts b/packages/gpt-runner-core/src/core/get-gpt-file-tree.ts index 7147672..802ae2e 100644 --- a/packages/gpt-runner-core/src/core/get-gpt-file-tree.ts +++ b/packages/gpt-runner-core/src/core/get-gpt-file-tree.ts @@ -42,6 +42,92 @@ export async function getGptFilesInfo(params: GetGptFilesInfoParams): Promise() + const startWithRoot = (path: string) => { + return path.startsWith('/') ? path : `/${path}` + } + + const getName = (title: string, filePath: string): string => { + const titleParts = title.split('/') + const pathParts = filePath.split('/') || [] + const name = titleParts[titleParts.length - 1] || pathParts[pathParts.length - 1] || 'unknown' + return name + } + + const getId = (title: string, name: string, filePath: string): string => { + const titleParts = title.split('/') + return startWithRoot([...titleParts.slice(0, -1), name].join('/') || filePath) + } + + const getParentTitle = (title: string): string => { + const titleParts = title.split('/') + const parentTitleParts = titleParts.slice(0, -1) + return startWithRoot(parentTitleParts.join('/').replace(/\/$/, '')) + } + + const getParent = (title: string): GptFileInfoTreeItem => { + const parentTitle = getParentTitle(title) + let parentFileInfo = tempTitleFileInfoTMap.get(parentTitle) + + if (!parentFileInfo) { + const parentPath = '' + parentFileInfo = { + id: parentTitle, + parentId: null, + path: parentPath, + name: getName(parentTitle, parentPath), + type: GptFileTreeItemType.Folder, + children: [], + } + + tempTitleFileInfoTMap.set(parentTitle, parentFileInfo) + + if (parentTitle && parentTitle !== '/') { + // init parent + const parentOfParent = getParent(parentTitle) + + if (!parentOfParent.children) + parentOfParent.children = [] + + parentOfParent.children.push(parentFileInfo) + } + } + + return parentFileInfo + } + + const processFilePath = async (filePath: string) => { + try { + const content = await FileUtils.readFile({ filePath }) + const singleFileConfig = await parseGptFile({ + filePath, + userConfig: resolvedUserConfig as UserConfig, + }) + const title = singleFileConfig.title || '' + const name = getName(title, filePath) + const id = getId(title, name, filePath) + const parentFileInfo = getParent(title) + + if (!parentFileInfo.children) + parentFileInfo.children = [] + + const fileNodeInfo: GptFileInfo = { + id, + parentId: parentFileInfo.id, + path: filePath, + name, + content, + singleFileConfig, + type: GptFileTreeItemType.File, + } + + parentFileInfo.children.push(fileNodeInfo) + filesInfo.push(fileNodeInfo) + } + catch (error) { + console.log('processFilePath error', error) + } + } + await FileUtils.travelFilesByFilterPattern({ filePath: fullRootPath, exts: [...exts], @@ -51,72 +137,12 @@ export async function getGptFilesInfo(params: GetGptFilesInfoParams): Promise { - const content = await FileUtils.readFile({ filePath }) - const singleFileConfig = await parseGptFile({ - filePath, - userConfig: resolvedUserConfig as UserConfig, - }) - - const { title = '' } = singleFileConfig - const titleParts = title.split('/') - - const getName = (title: string, filePath: string): string => { - const titleParts = title.split('/') - const pathParts = filePath.split('/') || [] - const name = titleParts[titleParts.length - 1] || pathParts[pathParts.length - 1] || 'unknown' - return name - } - - const parentTitleParts = titleParts.slice(0, -1) - - const name = getName(title, filePath) - - const fileId = [...titleParts.slice(0, -1), name].join('/') || filePath - - const fileInfo: GptFileInfo = { - id: fileId, - parentId: null, - path: filePath, - name, - content, - singleFileConfig, - type: GptFileTreeItemType.File, - } - - if (parentTitleParts.length) { - const parentTitle = parentTitleParts.join('/') - const parentFileInfo = tempTitleFileInfoTMap.get(parentTitle) - - if (parentFileInfo) { - if (!parentFileInfo.children) - parentFileInfo.children = [] - - parentFileInfo.children.push({ ...fileInfo, parentId: parentFileInfo.id }) - } - else { - const parentPath = '' - - const parentFileInfo: GptFileInfoTreeItem = { - id: parentTitle.endsWith('/') ? parentTitle : `${parentTitle}/`, - parentId: null, - path: parentPath, - name: getName(parentTitle, parentPath), - type: GptFileTreeItemType.Folder, - children: [{ ...fileInfo }], - } - - tempTitleFileInfoTMap.set(parentTitle, parentFileInfo) - filesInfoTree.push(parentFileInfo) - } - } - else { - filesInfoTree.push({ ...fileInfo }) - } - - filesInfo.push({ ...fileInfo }) + await processFilePath(filePath) }, }) + filesInfoTree.push(...tempTitleFileInfoTMap.get('/')?.children || []) + return { filesInfo, filesInfoTree, diff --git a/packages/gpt-runner-shared/src/common/types/enum.ts b/packages/gpt-runner-shared/src/common/types/enum.ts index b5045a5..94d27ff 100644 --- a/packages/gpt-runner-shared/src/common/types/enum.ts +++ b/packages/gpt-runner-shared/src/common/types/enum.ts @@ -28,6 +28,7 @@ export enum ClientEventName { UpdateUserSelectedText = 'updateUserSelectedText', OpenFileInIde = 'openFileInIde', OpenFileInFileEditor = 'openFileInFileEditor', + GoToChatPanel = 'goToChatPanel', } export enum GptFileTreeItemType { diff --git a/packages/gpt-runner-shared/src/common/types/eventemitter.ts b/packages/gpt-runner-shared/src/common/types/eventemitter.ts index c21ec76..7edf853 100644 --- a/packages/gpt-runner-shared/src/common/types/eventemitter.ts +++ b/packages/gpt-runner-shared/src/common/types/eventemitter.ts @@ -36,6 +36,8 @@ export interface ClientEventData { [ClientEventName.OpenFileInFileEditor]: { fileFullPath: string } + + [ClientEventName.GoToChatPanel]: void } export type EventEmitterMap = { diff --git a/packages/gpt-runner-web/client/src/pages/chat/index.tsx b/packages/gpt-runner-web/client/src/pages/chat/index.tsx index 150d0d0..59cc466 100644 --- a/packages/gpt-runner-web/client/src/pages/chat/index.tsx +++ b/packages/gpt-runner-web/client/src/pages/chat/index.tsx @@ -80,6 +80,14 @@ const Chat: FC = memo(() => { setPcTabActiveId(PcTabId.Chat) }, [activeChatId, isMobile]) + useOn({ + eventName: ClientEventName.GoToChatPanel, + listener: () => { + setMobileTabActiveId(MobileTabId.Chat) + setPcTabActiveId(PcTabId.Chat) + }, + }) + // any status will scroll down useEffect(() => { scrollDown() diff --git a/packages/gpt-runner-web/client/src/store/zustand/file-editor/index.ts b/packages/gpt-runner-web/client/src/store/zustand/file-editor/index.ts index 71b3bf5..0957aa2 100644 --- a/packages/gpt-runner-web/client/src/store/zustand/file-editor/index.ts +++ b/packages/gpt-runner-web/client/src/store/zustand/file-editor/index.ts @@ -85,10 +85,14 @@ export const createFileEditorSlice: StateCreator< }, removeFileEditorItem(fullPath) { const state = get() + const finalFileEditorItems = state.fileEditorItems.filter(item => item.fullPath !== fullPath) set({ - fileEditorItems: state.fileEditorItems.filter(item => item.fullPath !== fullPath), + fileEditorItems: finalFileEditorItems, }) + + if (!finalFileEditorItems.length) + emitter.emit(ClientEventName.GoToChatPanel) }, updateFileEditorItem(fullPath, item) { const state = get() diff --git a/playground/scripts/gpt/test.gpt.md b/playground/scripts/gpt/test.gpt.md index 546895a..ea84acd 100644 --- a/playground/scripts/gpt/test.gpt.md +++ b/playground/scripts/gpt/test.gpt.md @@ -1,6 +1,6 @@ ```json { - "title": "common/", + "title": "common/test/bbb/ddd/test", "model": { "type": "openai", "modelName": "gpt-3.5-turbo-16k",