Add commands for DevChat

- Added commands for adding context and summary to DevChat.
- Updated the package.json file to include the new commands.
- Added command handlers for indexing and describing codebase summaries.
- Created a new file, askcode_summary_index.py, for indexing and describing codebase summaries.
- Added a new setting file, _setting_.json, for the summary action in the auto_command workflow.
- Added a new handler file, handler.py, for the summary action in the auto_command workflow.
This commit is contained in:
bobo.yang 2023-08-21 11:52:00 +08:00
parent 3bf09924d7
commit 6713a98df9
6 changed files with 406 additions and 52 deletions

View File

@ -248,6 +248,10 @@
"command": "devchat.addConext", "command": "devchat.addConext",
"title": "Add to DevChat" "title": "Add to DevChat"
}, },
{
"command": "devchat.addSummaryContext",
"title": "Add summary to DevChat"
},
{ {
"command": "devchat.askForCode", "command": "devchat.askForCode",
"title": "Add to DevChat" "title": "Add to DevChat"
@ -277,6 +281,16 @@
"command": "DevChat.AskCodeIndexStop", "command": "DevChat.AskCodeIndexStop",
"title": "Stop AskCode Index", "title": "Stop AskCode Index",
"category": "DevChat" "category": "DevChat"
},
{
"command": "DevChat.AskCodeSummaryIndexStart",
"title": "Start AskCode Summary Index",
"category": "DevChat"
},
{
"command": "DevChat.AskCodeIndexSummaryStop",
"title": "Stop AskCode Summary Index",
"category": "DevChat"
} }
], ],
"menus": { "menus": {
@ -340,6 +354,10 @@
"command": "devchat.addConext", "command": "devchat.addConext",
"when": "false" "when": "false"
}, },
{
"command": "devchat.addSummaryContext",
"when": "false"
},
{ {
"command": "devchat.askForCode", "command": "devchat.askForCode",
"when": "false" "when": "false"
@ -371,6 +389,11 @@
"when": "!isChineseLocale && resourceLangId != 'git'", "when": "!isChineseLocale && resourceLangId != 'git'",
"command": "devchat.addConext", "command": "devchat.addConext",
"group": "navigation" "group": "navigation"
},
{
"when": "!isChineseLocale && resourceLangId != 'git'",
"command": "devchat.addSummaryContext",
"group": "navigation"
} }
], ],
"editor/context": [ "editor/context": [

View File

@ -1,4 +1,5 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as fs from 'fs';
import { sendFileSelectMessage, sendCodeSelectMessage } from './util'; import { sendFileSelectMessage, sendCodeSelectMessage } from './util';
import ExtensionContextHolder from '../util/extensionContext'; import ExtensionContextHolder from '../util/extensionContext';
import { TopicManager } from '../topic/topicManager'; import { TopicManager } from '../topic/topicManager';
@ -9,14 +10,16 @@ import { UiUtilWrapper } from '../util/uiUtil';
import { isValidApiKey } from '../handler/historyMessagesBase'; import { isValidApiKey } from '../handler/historyMessagesBase';
import { logger } from '../util/logger'; import { logger } from '../util/logger';
import { CommandRun } from '../util/commonUtil'; import { CommandRun, createTempSubdirectory, runCommandAndWriteOutput, runCommandStringAndWriteOutput, runCommandStringArrayAndWriteOutput } from '../util/commonUtil';
import { updateIndexingStatus, updateLastModifyTime } from '../util/askCodeUtil'; import { updateIndexingStatus, updateLastModifyTime } from '../util/askCodeUtil';
import { installAskCode as installAskCodeFun } from '../util/python_installer/install_askcode'; import { installAskCode as installAskCodeFun } from '../util/python_installer/install_askcode';
import { ProgressBar } from '../util/progressBar'; import { ProgressBar } from '../util/progressBar';
import path from 'path';
import { MessageHandler } from '../handler/messageHandler';
let indexProcess: CommandRun | null = null; let indexProcess: CommandRun | null = null;
let summaryIndexProcess: CommandRun | null = null;
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) { function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('devchat.openChatPanel', async () => { let disposable = vscode.commands.registerCommand('devchat.openChatPanel', async () => {
@ -248,7 +251,7 @@ export function registerAskCodeIndexStartCommand(context: vscode.ExtensionContex
if (!pythonVirtualEnv) { if (!pythonVirtualEnv) {
progressBar.update("Install devchat-ask package ...", 0); progressBar.update("Install devchat-ask package ...", 0);
await installAskCode(supportedFileTypes, progressBar); await installAskCode(supportedFileTypes, progressBar, indexCode);
} else { } else {
progressBar.update("Index source files ...", 0); progressBar.update("Index source files ...", 0);
await indexCode(pythonVirtualEnv, supportedFileTypes, progressBar); await indexCode(pythonVirtualEnv, supportedFileTypes, progressBar);
@ -266,7 +269,8 @@ function getConfig() {
}; };
} }
async function installAskCode(supportedFileTypes, progressBar: any) {
async function installAskCode(supportedFileTypes, progressBar: any, callback: Function) {
const pythonEnvPath : string = await installAskCodeFun(); const pythonEnvPath : string = await installAskCodeFun();
if (!pythonEnvPath) { if (!pythonEnvPath) {
logger.channel()?.error(`Installation failed!`); logger.channel()?.error(`Installation failed!`);
@ -277,8 +281,8 @@ async function installAskCode(supportedFileTypes, progressBar: any) {
UiUtilWrapper.updateConfiguration("DevChat", "PythonVirtualEnv", pythonEnvPath.trim()); UiUtilWrapper.updateConfiguration("DevChat", "PythonVirtualEnv", pythonEnvPath.trim());
logger.channel()?.info(`Installation finished.`); logger.channel()?.info(`Installation finished.`);
// Execute the indexing command after the installation is finished // Execute the callback function after the installation is finished
await indexCode(pythonEnvPath, supportedFileTypes, progressBar); await callback(pythonEnvPath, supportedFileTypes, progressBar);
} }
async function indexCode(pythonVirtualEnv, supportedFileTypes, progressBar: any) { async function indexCode(pythonVirtualEnv, supportedFileTypes, progressBar: any) {
@ -349,6 +353,136 @@ export function registerAskCodeIndexStopCommand(context: vscode.ExtensionContext
context.subscriptions.push(disposable); context.subscriptions.push(disposable);
} }
export function registerAskCodeSummaryIndexStartCommand(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('DevChat.AskCodeSummaryIndexStart', async () => {
const progressBar = new ProgressBar();
progressBar.init();
progressBar.update("Index source code files for ask codebase summary...", 0);
const config = getConfig();
const pythonVirtualEnv = config.pythonVirtualEnv;
const supportedFileTypes = config.supportedFileTypes;
updateIndexingStatus("started");
if (!pythonVirtualEnv) {
progressBar.update("Install devchat-ask package ...", 0);
await installAskCode(supportedFileTypes, progressBar, indexCodeSummary);
} else {
progressBar.update("Index source files for summary...", 0);
await indexCodeSummary(pythonVirtualEnv, supportedFileTypes, progressBar);
}
updateIndexingStatus("stopped");
});
context.subscriptions.push(disposable);
}
async function indexCodeSummary(pythonVirtualEnv, supportedFileTypes, progressBar: any) {
let envs = {};
let openaiApiKey = await ApiKeyManager.getApiKey();
if (!openaiApiKey) {
logger.channel()?.error('The OpenAI key is invalid!');
logger.channel()?.show();
progressBar.endWithError("The OpenAI key is invalid!");
return;
}
envs['OPENAI_API_KEY'] = openaiApiKey;
const openAiApiBase = ApiKeyManager.getEndPoint(openaiApiKey);
if (openAiApiBase) {
envs['OPENAI_API_BASE'] = openAiApiBase;
}
const workspaceDir = UiUtilWrapper.workspaceFoldersFirstPath();
const command = pythonVirtualEnv.trim();
const args = [UiUtilWrapper.extensionPath() + "/tools/askcode_summary_index.py", "index", supportedFileTypes];
const options = { env: envs, cwd: workspaceDir };
summaryIndexProcess = new CommandRun();
const result = await summaryIndexProcess.spawnAsync(command, args, options, (data) => {
if (data.includes('Skip file:')) {
return;
}
logger.channel()?.info(`${data}`);
}, (data) => {
if (data.includes('Skip file:')) {
return;
}
logger.channel()?.info(`${data}`);
}, undefined, undefined);
if (result.exitCode !== 0) {
if (result.exitCode === null) {
logger.channel()?.info(`Indexing stopped!`);
progressBar.endWithError(`Indexing stopped!`);
} else {
logger.channel()?.error(`Indexing failed: ${result.stderr}`);
logger.channel()?.show();
progressBar.endWithError(`Indexing failed: ${result.stderr}`);
}
return;
}
updateLastModifyTime();
logger.channel()?.info(`index finished.`);
progressBar.update("Indexing finished.");
progressBar.end();
}
export function registerAskCodeSummaryIndexStopCommand(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('DevChat.AskCodeIndexSummaryStop', async () => {
// 在这里实现停止索引的功能
// 你可能需要检查summaryIndexProcess变量是否为null如果不为null那么停止索引进程
if (summaryIndexProcess) {
summaryIndexProcess.stop();
summaryIndexProcess = null;
}
});
context.subscriptions.push(disposable);
}
export function registerAddSummaryContextCommand(context: vscode.ExtensionContext) {
const callback = async (uri: { fsPath: any; }) => {
if (!await ensureChatPanel(context)) {
return;
}
const workspaceDir = UiUtilWrapper.workspaceFoldersFirstPath();
if (!workspaceDir) {
return ;
}
// check whether workspaceDir/.chat/.summary.json文件存在
if (!fs.existsSync(path.join(workspaceDir, '.chat', '.summary.json'))) {
logger.channel()?.info(`You should index this workspace first.`);
logger.channel()?.show();
return;
}
const config = getConfig();
const pythonVirtualEnv: any = config.pythonVirtualEnv;
const tempDir = await createTempSubdirectory('devchat/context');
const summaryFile = path.join(tempDir, 'summary.txt');
const summaryArgs = [pythonVirtualEnv, UiUtilWrapper.extensionPath() + "/tools/askcode_summary_index.py", "desc", uri.fsPath];
const result = await runCommandStringArrayAndWriteOutput(summaryArgs, summaryFile);
logger.channel()?.info(` exit code:`, result.exitCode);
logger.channel()?.debug(` stdout:`, result.stdout);
logger.channel()?.debug(` stderr:`, result.stderr);
MessageHandler.sendMessage(ExtensionContextHolder.provider?.view()!, { command: 'appendContext', context: `[context|${summaryFile}]` });
};
context.subscriptions.push(vscode.commands.registerCommand('devchat.addSummaryContext', callback));
}
export { export {
registerOpenChatPanelCommand, registerOpenChatPanelCommand,
registerAddContextCommand, registerAddContextCommand,

View File

@ -17,6 +17,9 @@ import {
regPythonPathCommand, regPythonPathCommand,
registerAskCodeIndexStartCommand, registerAskCodeIndexStartCommand,
registerAskCodeIndexStopCommand, registerAskCodeIndexStopCommand,
registerAskCodeSummaryIndexStartCommand,
registerAskCodeSummaryIndexStopCommand,
registerAddSummaryContextCommand,
} from './contributes/commands'; } from './contributes/commands';
import { regLanguageContext } from './contributes/context'; import { regLanguageContext } from './contributes/context';
import { regDevChatView, regTopicView } from './contributes/views'; import { regDevChatView, regTopicView } from './contributes/views';
@ -61,5 +64,8 @@ function activate(context: vscode.ExtensionContext) {
regPythonPathCommand(context); regPythonPathCommand(context);
registerAskCodeIndexStartCommand(context); registerAskCodeIndexStartCommand(context);
registerAskCodeIndexStopCommand(context); registerAskCodeIndexStopCommand(context);
registerAskCodeSummaryIndexStartCommand(context);
registerAskCodeSummaryIndexStopCommand(context);
registerAddSummaryContextCommand(context);
} }
exports.activate = activate; exports.activate = activate;

View File

@ -0,0 +1,139 @@
import os
import re
import json
import sys
from chat.ask_codebase.indexing.loader.file import FileMetadata, FileSource, simple_file_filter
from chat.ask_codebase.indexing.module_summary import SummaryWrapper
# 为已经分析的文件记录最后修改时间
g_file_last_modified_saved = {}
def load_file_last_modified(filePath: str):
if not os.path.exists(filePath):
return {}
with open(filePath, 'r', encoding="utf-8") as f:
fileLastModified = json.load(f)
return fileLastModified
def save_file_last_modified(filePath: str, fileLastModified: dict):
with open(filePath, 'w+', encoding="utf-8") as f:
json.dump(fileLastModified, f)
return fileLastModified
def is_source_code_new(filePath: str, supportedFileTypes):
for pattern in supportedFileTypes:
if re.match(pattern.strip(), filePath):
return True
return False
def is_file_modified(filePath: str, supportedFileTypes) -> bool:
if not is_source_code_new(filePath, supportedFileTypes):
return False
relativePath = os.path.relpath(filePath, os.getcwd())
for part in relativePath.split(os.sep):
if part.startswith('.'):
return False
fileLastModified = g_file_last_modified_saved.get(relativePath, 0)
fileCurrentModified = os.path.getmtime(filePath)
if fileLastModified != fileCurrentModified:
g_file_last_modified_saved[relativePath] = fileCurrentModified
return True
return False
def custom_file_filter(file_path: str, supportedFileTypes) -> bool:
print("==> ", file_path)
if os.path.isdir(file_path):
return True
return is_file_modified(file_path, supportedFileTypes)
def index_directory(repo_dir: str, repo_cache_path: str, supportedFileTypes):
"""
index files in repo_dir
"""
global g_file_last_modified_saved
g_file_last_modified_saved = load_file_last_modified('.chat/.index_modified.json')
sw = SummaryWrapper(repo_cache_path, FileSource(
path=repo_dir,
rel_root=repo_dir,
file_filter=lambda file_path: custom_file_filter(file_path, supportedFileTypes),
))
for progress_info in sw.reindex(True, []):
print(progress_info)
save_file_last_modified('.chat/.index_modified.json', g_file_last_modified_saved)
def desc(repo_dir: str, repo_cache_path: str, target_path: str):
"""
"""
target_path = target_path.replace(repo_dir, '')
sw = SummaryWrapper(repo_cache_path, FileSource(
path=repo_dir,
rel_root=repo_dir,
file_filter=simple_file_filter,
))
return sw.get_desc(target_path)
def context(repo_dir: str, repo_cache_path: str, target_path: str):
"""
"""
target_path = os.path.relpath(target_path, repo_dir)
sw = SummaryWrapper(repo_cache_path, FileSource(
path=repo_dir,
rel_root=repo_dir,
file_filter=simple_file_filter,
))
return sw.prepare_context_by_top_module(target_path)
def main():
if len(sys.argv) < 2:
print("Usage: python askcode_summary_index.py [command] [args]")
print("Available commands: index, desc, context")
sys.exit(1)
command = sys.argv[1]
# Set default values for repo_dir and repo_cache_path
repo_dir = os.getcwd()
repo_cache_path = os.path.join(repo_dir, '.chat', '.summary.json')
if command == "index":
if len(sys.argv) < 3:
print("Usage: python askcode_summary_index.py index [supportedFileTypes]")
sys.exit(1)
supportedFileTypes = sys.argv[2].split(',')
index_directory(repo_dir, repo_cache_path, supportedFileTypes)
elif command == "desc":
if len(sys.argv) < 3:
print("Usage: python askcode_summary_index.py desc [target_path]")
sys.exit(1)
target_path = sys.argv[2]
print(desc(repo_dir, repo_cache_path, target_path))
elif command == "context":
if len(sys.argv) < 3:
print("Usage: python askcode_summary_index.py context [target_path]")
sys.exit(1)
target_path = sys.argv[2]
print(context(repo_dir, repo_cache_path, target_path))
else:
print("Invalid command. Available commands: index, desc, context")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,15 @@
{
"name": "summary",
"description": "Get summary of specified file or direcotry in workspace",
"type": ["project"],
"action": "summary",
"args": [
{
"name": "path",
"description": "The relative path of the specified file or folder, for example, /hello.py, represents the hello.py file in the root directory. This arg is required.",
"type": "string",
"from": "content.content.path"
}
],
"handler": ["${PythonVirtualEnv}", "${CurDir}/handler.py", "${path}"]
}

View File

@ -0,0 +1,37 @@
"""
"""
import os
import sys
from chat.ask_codebase.indexing.loader.file import FileMetadata, FileSource, simple_file_filter
from chat.ask_codebase.indexing.module_summary import SummaryWrapper
def desc(repo_dir: str, repo_cache_path: str, target_path: str):
"""
"""
target_path = target_path.replace(repo_dir, '')
sw = SummaryWrapper(repo_cache_path, FileSource(
path=repo_dir,
rel_root=repo_dir,
file_filter=simple_file_filter,
))
return sw.get_desc(target_path)
def summary():
"""
Get file or directory 's summary
"""
try:
repo_dir = os.getcwd()
repo_cache_path = os.path.join(repo_dir, '.chat', '.summary.json')
target_path = sys.argv[1]
return desc(repo_dir, repo_cache_path, target_path)
except Exception as e:
sys.stderr.write(f"Error: {str(e)}\n")
sys.exit(1)
if __name__ == "__main__":
print(summary())
sys.exit(0)