update apply diff logic
This commit is contained in:
parent
1ea353cfd9
commit
25915cec29
@ -71,7 +71,12 @@ async function getNewCode(message: any) : Promise<string | undefined> {
|
|||||||
|
|
||||||
let newCode = message.content;
|
let newCode = message.content;
|
||||||
if (isValidActionString(message.content)) {
|
if (isValidActionString(message.content)) {
|
||||||
|
if (codeTextObj.select) {
|
||||||
|
const diffResult = applyCodeChanges(codeTextObj.select, message.content);
|
||||||
|
newCode = codeTextObj.beforSelect + diffResult + codeTextObj.afterSelect;
|
||||||
|
} else {
|
||||||
newCode = applyCodeChanges(codeTextObj.text, message.content);
|
newCode = applyCodeChanges(codeTextObj.text, message.content);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// if select some text, then reconstruct the code
|
// if select some text, then reconstruct the code
|
||||||
if (codeTextObj.select) {
|
if (codeTextObj.select) {
|
||||||
|
@ -57,7 +57,7 @@ class DevChat {
|
|||||||
this.commandRun.stop();
|
this.commandRun.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async chat(content: string, options: ChatOptions = {}, onData: (data: ChatResponse) => void): Promise<ChatResponse> {
|
async buildArgs(options: ChatOptions): Promise<string[]> {
|
||||||
let args = ["prompt"];
|
let args = ["prompt"];
|
||||||
|
|
||||||
if (options.reference) {
|
if (options.reference) {
|
||||||
@ -76,10 +76,14 @@ class DevChat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args.push(content)
|
if (options.parent) {
|
||||||
|
args.push("-p", options.parent);
|
||||||
|
}
|
||||||
|
|
||||||
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOpenAiApiKey(): Promise<string | undefined> {
|
||||||
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets;
|
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets;
|
||||||
let openaiApiKey = await secretStorage.get("devchat_OPENAI_API_KEY");
|
let openaiApiKey = await secretStorage.get("devchat_OPENAI_API_KEY");
|
||||||
if (!openaiApiKey) {
|
if (!openaiApiKey) {
|
||||||
@ -88,46 +92,10 @@ class DevChat {
|
|||||||
if (!openaiApiKey) {
|
if (!openaiApiKey) {
|
||||||
openaiApiKey = process.env.OPENAI_API_KEY;
|
openaiApiKey = process.env.OPENAI_API_KEY;
|
||||||
}
|
}
|
||||||
if (!openaiApiKey) {
|
return openaiApiKey;
|
||||||
logger.channel()?.error('openAI key is invalid!');
|
|
||||||
logger.channel()?.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const openAiApiBase = vscode.workspace.getConfiguration('DevChat').get('OpenAI.EndPoint');
|
private parseOutData(stdout: string, isPartial: boolean): ChatResponse {
|
||||||
const openAiApiBaseObject = openAiApiBase ? { OPENAI_API_BASE: openAiApiBase } : {};
|
|
||||||
|
|
||||||
const openaiModel = vscode.workspace.getConfiguration('DevChat').get('OpenAI.model');
|
|
||||||
const openaiTemperature = vscode.workspace.getConfiguration('DevChat').get('OpenAI.temperature');
|
|
||||||
const openaiStream = vscode.workspace.getConfiguration('DevChat').get('OpenAI.stream');
|
|
||||||
const llmModel = vscode.workspace.getConfiguration('DevChat').get('llmModel');
|
|
||||||
const tokensPerPrompt = vscode.workspace.getConfiguration('DevChat').get('OpenAI.tokensPerPrompt');
|
|
||||||
|
|
||||||
let devChat: string | undefined = vscode.workspace.getConfiguration('DevChat').get('DevChatPath');
|
|
||||||
if (!devChat) {
|
|
||||||
devChat = 'devchat';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.parent) {
|
|
||||||
args.push("-p", options.parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
const devchatConfig = {
|
|
||||||
model: openaiModel,
|
|
||||||
provider: llmModel,
|
|
||||||
"tokens-per-prompt": tokensPerPrompt,
|
|
||||||
OpenAI: {
|
|
||||||
temperature: openaiTemperature,
|
|
||||||
stream: openaiStream,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// write to config file
|
|
||||||
const configPath = path.join(workspaceDir!, '.chat', 'config.json');
|
|
||||||
// write devchatConfig to configPath
|
|
||||||
const configJson = JSON.stringify(devchatConfig, null, 2);
|
|
||||||
fs.writeFileSync(configPath, configJson);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parseOutData = (stdout: string, isPartial: boolean) => {
|
|
||||||
const responseLines = stdout.trim().split("\n");
|
const responseLines = stdout.trim().split("\n");
|
||||||
|
|
||||||
if (responseLines.length < 2) {
|
if (responseLines.length < 2) {
|
||||||
@ -176,12 +144,54 @@ class DevChat {
|
|||||||
response,
|
response,
|
||||||
isError: false,
|
isError: false,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
async chat(content: string, options: ChatOptions = {}, onData: (data: ChatResponse) => void): Promise<ChatResponse> {
|
||||||
|
const args = await this.buildArgs(options);
|
||||||
|
args.push(content);
|
||||||
|
|
||||||
|
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
|
||||||
|
let openaiApiKey = await this.getOpenAiApiKey();
|
||||||
|
if (!openaiApiKey) {
|
||||||
|
logger.channel()?.error('openAI key is invalid!');
|
||||||
|
logger.channel()?.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const openAiApiBase = vscode.workspace.getConfiguration('DevChat').get('OpenAI.EndPoint');
|
||||||
|
const openAiApiBaseObject = openAiApiBase ? { OPENAI_API_BASE: openAiApiBase } : {};
|
||||||
|
|
||||||
|
const openaiModel = vscode.workspace.getConfiguration('DevChat').get('OpenAI.model');
|
||||||
|
const openaiTemperature = vscode.workspace.getConfiguration('DevChat').get('OpenAI.temperature');
|
||||||
|
const openaiStream = vscode.workspace.getConfiguration('DevChat').get('OpenAI.stream');
|
||||||
|
const llmModel = vscode.workspace.getConfiguration('DevChat').get('llmModel');
|
||||||
|
const tokensPerPrompt = vscode.workspace.getConfiguration('DevChat').get('OpenAI.tokensPerPrompt');
|
||||||
|
|
||||||
|
let devChat: string | undefined = vscode.workspace.getConfiguration('DevChat').get('DevChatPath');
|
||||||
|
if (!devChat) {
|
||||||
|
devChat = 'devchat';
|
||||||
|
}
|
||||||
|
|
||||||
|
const devchatConfig = {
|
||||||
|
model: openaiModel,
|
||||||
|
provider: llmModel,
|
||||||
|
"tokens-per-prompt": tokensPerPrompt,
|
||||||
|
OpenAI: {
|
||||||
|
temperature: openaiTemperature,
|
||||||
|
stream: openaiStream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write to config file
|
||||||
|
const configPath = path.join(workspaceDir!, '.chat', 'config.json');
|
||||||
|
// write devchatConfig to configPath
|
||||||
|
const configJson = JSON.stringify(devchatConfig, null, 2);
|
||||||
|
fs.writeFileSync(configPath, configJson);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
let receviedStdout = "";
|
let receviedStdout = "";
|
||||||
const onStdoutPartial = (stdout: string) => {
|
const onStdoutPartial = (stdout: string) => {
|
||||||
receviedStdout += stdout;
|
receviedStdout += stdout;
|
||||||
const data = parseOutData(receviedStdout, true);
|
const data = this.parseOutData(receviedStdout, true);
|
||||||
onData(data);
|
onData(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -207,7 +217,7 @@ class DevChat {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = parseOutData(stdout, false);
|
const response = this.parseOutData(stdout, false);
|
||||||
return response;
|
return response;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
@ -226,16 +236,16 @@ class DevChat {
|
|||||||
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
|
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
|
||||||
const openaiApiKey = process.env.OPENAI_API_KEY;
|
const openaiApiKey = process.env.OPENAI_API_KEY;
|
||||||
|
|
||||||
try {
|
|
||||||
logger.channel()?.info(`Running devchat with args: ${args.join(" ")}`);
|
logger.channel()?.info(`Running devchat with args: ${args.join(" ")}`);
|
||||||
const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(devChat, args, {
|
const spawnOptions = {
|
||||||
maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB
|
maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB
|
||||||
cwd: workspaceDir,
|
cwd: workspaceDir,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
OPENAI_API_KEY: openaiApiKey,
|
OPENAI_API_KEY: openaiApiKey,
|
||||||
},
|
},
|
||||||
}, undefined, undefined, undefined, undefined);
|
};
|
||||||
|
const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(devChat, args, spawnOptions, undefined, undefined, undefined, undefined);
|
||||||
|
|
||||||
logger.channel()?.info(`Finish devchat with args: ${args.join(" ")}`);
|
logger.channel()?.info(`Finish devchat with args: ${args.join(" ")}`);
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
@ -245,11 +255,6 @@ class DevChat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return JSON.parse(stdout.trim()).reverse();
|
return JSON.parse(stdout.trim()).reverse();
|
||||||
} catch (error) {
|
|
||||||
logger.channel()?.error(`Error getting log: ${error}`);
|
|
||||||
logger.channel()?.show();
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildLogArgs(options: LogOptions): string[] {
|
private buildLogArgs(options: LogOptions): string[] {
|
||||||
|
@ -5,58 +5,117 @@ type Action = {
|
|||||||
action: "delete" | "insert" | "modify";
|
action: "delete" | "insert" | "modify";
|
||||||
content?: string;
|
content?: string;
|
||||||
insert_after?: string;
|
insert_after?: string;
|
||||||
|
insert_before?: string;
|
||||||
original_content?: string;
|
original_content?: string;
|
||||||
new_content?: string;
|
new_content?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function findMatchingIndex(list1: string[], list2: string[]): number {
|
|
||||||
|
function findMatchingIndex(list1: string[], list2: string[]): number[] {
|
||||||
|
logger.channel()?.info(`findMatchingIndex start: ${list2.join('\n')}`);
|
||||||
|
|
||||||
|
const matchingIndexes: number[] = [];
|
||||||
for (let i = 0; i <= list1.length - list2.length; i++) {
|
for (let i = 0; i <= list1.length - list2.length; i++) {
|
||||||
let isMatch = true;
|
let isMatch = true;
|
||||||
for (let j = 0; j < list2.length; j++) {
|
for (let j = 0; j < list2.length; j++) {
|
||||||
if (list1[i + j].trim() !== list2[j].trim()) {
|
if (list1[i + j].trim() !== list2[j].trim()) {
|
||||||
|
if (j > 0) {
|
||||||
|
logger.channel()?.info(`findMatchingIndex end at ${j} ${list1[i + j].trim()} != ${list2[j].trim()}`);
|
||||||
|
}
|
||||||
|
|
||||||
isMatch = false;
|
isMatch = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isMatch) {
|
if (isMatch) {
|
||||||
return i;
|
matchingIndexes.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
|
logger.channel()?.info(`findMatchingIndex result: ${matchingIndexes.join(' ')}`);
|
||||||
|
return matchingIndexes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyCodeChanges(originalCode: string, actionsString: string): string {
|
export function applyCodeChanges(originalCode: string, actionsString: string): string {
|
||||||
const actions = JSON.parse(actionsString) as Array<Action>;
|
const actions = JSON.parse(actionsString) as Array<Action>;
|
||||||
|
|
||||||
const lines = originalCode.split('\n');
|
const lines = originalCode.split('\n');
|
||||||
|
// 构建与lines等长的数组,用于记录哪些行被修改过
|
||||||
|
const modifiedIndexes: number[] = new Array(lines.length).fill(0);
|
||||||
|
|
||||||
|
// 构建子函数,用于在多个匹配索引中找出最优的索引
|
||||||
|
const findOptimalMatchingIndex = (matchingIndexList: number[]) => {
|
||||||
|
// 优先找出未被修改过的索引
|
||||||
|
const optimalMatchingIndex = matchingIndexList.find(index => modifiedIndexes[index] === 0);
|
||||||
|
// 如果所有索引都被修改过,则找出第一个索引
|
||||||
|
if (optimalMatchingIndex === undefined) {
|
||||||
|
if (matchingIndexList.length > 0) {
|
||||||
|
return matchingIndexList[0];
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return optimalMatchingIndex;
|
||||||
|
};
|
||||||
|
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
const contentLines = action.content?.split('\n') || [];
|
const contentLines = action.content?.split('\n') || [];
|
||||||
const insertAfterLines = action.insert_after?.split('\n') || [];
|
const insertAfterLines = action.insert_after?.split('\n') || [];
|
||||||
|
const insertBeforeLines = action.insert_before?.split('\n') || [];
|
||||||
const originalContentLines = action.original_content?.split('\n') || [];
|
const originalContentLines = action.original_content?.split('\n') || [];
|
||||||
|
|
||||||
switch (action.action) {
|
switch (action.action) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
// find the matching index
|
// find the matching index
|
||||||
const matchingIndex = findMatchingIndex(lines, contentLines);
|
const matchingIndexList = findMatchingIndex(lines, contentLines);
|
||||||
if (matchingIndex !== -1) {
|
const optimalMatchingIndex = findOptimalMatchingIndex(matchingIndexList);
|
||||||
lines.splice(matchingIndex, contentLines.length);
|
if (matchingIndexList.length > 0) {
|
||||||
|
if (optimalMatchingIndex !== undefined) {
|
||||||
|
lines.splice(optimalMatchingIndex, contentLines.length);
|
||||||
|
// 同步删除modifiedIndexes中记录
|
||||||
|
modifiedIndexes.splice(optimalMatchingIndex, contentLines.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'insert':
|
case 'insert':
|
||||||
// find the matching index
|
// find the matching index
|
||||||
const matchingIndex2 = findMatchingIndex(lines, insertAfterLines);
|
if (insertBeforeLines.length > 0) {
|
||||||
if (matchingIndex2 !== -1) {
|
const matchingIndexList1 = findMatchingIndex(lines, insertBeforeLines);
|
||||||
lines.splice(matchingIndex2 + 1, 0, ...contentLines);
|
const optimalMatchingIndex1 = findOptimalMatchingIndex(matchingIndexList1);
|
||||||
|
|
||||||
|
if (matchingIndexList1.length > 0) {
|
||||||
|
if (optimalMatchingIndex1 !== undefined) {
|
||||||
|
lines.splice(optimalMatchingIndex1, 0, ...contentLines);
|
||||||
|
// 同步modifiedIndexes添加记录
|
||||||
|
modifiedIndexes.splice(optimalMatchingIndex1, 0, ...new Array(contentLines.length).fill(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertAfterLines.length > 0) {
|
||||||
|
const matchingIndexList2 = findMatchingIndex(lines, insertAfterLines);
|
||||||
|
const optimalMatchingIndex2 = findOptimalMatchingIndex(matchingIndexList2);
|
||||||
|
|
||||||
|
if (matchingIndexList2.length > 0) {
|
||||||
|
if (optimalMatchingIndex2 !== undefined) {
|
||||||
|
lines.splice(optimalMatchingIndex2 + insertAfterLines.length, 0, ...contentLines);
|
||||||
|
// 同步modifiedIndexes添加记录
|
||||||
|
modifiedIndexes.splice(optimalMatchingIndex2 + insertAfterLines.length, 0, ...new Array(contentLines.length).fill(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'modify':
|
case 'modify':
|
||||||
// find the matching index
|
// find the matching index
|
||||||
const matchingIndex3 = findMatchingIndex(lines, originalContentLines);
|
const matchingIndexList3 = findMatchingIndex(lines, originalContentLines);
|
||||||
if (matchingIndex3 !== -1) {
|
const optimalMatchingIndex3 = findOptimalMatchingIndex(matchingIndexList3);
|
||||||
lines.splice(matchingIndex3, originalContentLines.length, ...action.new_content!.split('\n'));
|
|
||||||
|
if (matchingIndexList3.length > 0) {
|
||||||
|
if (optimalMatchingIndex3 !== undefined) {
|
||||||
|
lines.splice(optimalMatchingIndex3, originalContentLines.length, ...action.new_content!.split('\n'));
|
||||||
|
// 同步modifiedIndexes添加记录
|
||||||
|
modifiedIndexes.splice(optimalMatchingIndex3, originalContentLines.length, ...new Array(action.new_content!.split('\n').length).fill(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -80,7 +139,7 @@ export function isValidActionString(actionString: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.action === "insert" && (!action.content || !action.insert_after)) {
|
if (action.action === "insert" && (!action.content || (!action.insert_after && !action.insert_before))) {
|
||||||
logger.channel()?.error(`Invalid action string: ${action}`);
|
logger.channel()?.error(`Invalid action string: ${action}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user