load topic from DevChat

This commit is contained in:
bobo.yang 2023-06-06 17:52:31 +08:00
parent 823eed357d
commit 3ec578c7be
5 changed files with 52 additions and 159 deletions

View File

@ -1,7 +1,7 @@
import { TopicManager } from '../topic/topicManager';
import { LogEntry } from '../toolwrapper/devchat';
import DevChat, { LogEntry, LogOptions } from '../toolwrapper/devchat';
import messageHistory from '../util/messageHistory';
import { ApiKeyManager } from '../util/apiKey';
import { logger } from '../util/logger';
@ -63,15 +63,19 @@ export async function isWaitForApiKey() {
export async function loadTopicHistoryLogs() : Promise<Array<LogEntry> | undefined> {
const topicId = TopicManager.getInstance().currentTopicId;
let logEntriesFlat: Array<LogEntry> = [];
if (topicId) {
logEntriesFlat = await TopicManager.getInstance().getTopicHistory(topicId);
if (!topicId) {
return [];
}
if (topicId !== TopicManager.getInstance().currentTopicId) {
logger.channel()?.info(`Current topic changed dure load topic hsitory!`)
return undefined;
}
const devChat = new DevChat();
const logOptions: LogOptions = {
skip: 0,
maxCount: 10000,
topic: topicId
};
const logEntries = await devChat.log(logOptions);
const logEntriesFlat = logEntries.flat();
return logEntriesFlat;
}

View File

@ -24,6 +24,7 @@ export interface ChatOptions {
export interface LogOptions {
skip?: number;
maxCount?: number;
topic?: string;
}
export interface LogEntry {
@ -257,6 +258,31 @@ class DevChat {
return JSON.parse(stdout.trim()).reverse();
}
async topics(): Promise<LogEntry[]> {
const args = ["topic", "-l"];
const devChat = this.getDevChatPath();
const workspaceDir = UiUtilWrapper.workspaceFoldersFirstPath();
logger.channel()?.info(`Running devchat with args: ${args.join(" ")}`);
const spawnOptions = {
maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB
cwd: workspaceDir,
env: {
...process.env
},
};
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(" ")}`);
if (stderr) {
logger.channel()?.error(`Error getting log: ${stderr}`);
logger.channel()?.show();
return [];
}
return JSON.parse(stdout.trim()).reverse();
}
private buildLogArgs(options: LogOptions): string[] {
let args = ["log"];
@ -270,6 +296,10 @@ class DevChat {
args.push('--max-count', `${maxLogCount}`);
}
if (options.topic) {
args.push('--topic', `${options.topic}`);
}
return args;
}

View File

@ -1,50 +0,0 @@
import DevChat, { LogEntry } from "../toolwrapper/devchat";
export class LinkedList {
logEntries: LogEntry[];
constructor() {
this.logEntries = [];
}
append(chatLog: LogEntry): void {
this.logEntries.push(chatLog);
}
}
export function loadTopicList(chatLogs: LogEntry[]): { [key: string]: LogEntry[] } {
const topicLists: { [key: string]: LogEntry[] } = {};
// create map from parent to hash
// collect logEntry with parent is null
const parentToHash: { [key: string]: LogEntry } = {};
const rootHashes: LogEntry[] = [];
for (const chatLog of chatLogs) {
if (chatLog.parent) {
parentToHash[chatLog.parent] = chatLog;
} else {
rootHashes.push(chatLog);
}
}
// visite logEntry with parent is null
// find all children Entries from map, then create LinkedList
for (const rootHash of rootHashes) {
const topicList = new LinkedList();
topicList.append(rootHash);
let current: LogEntry|undefined = rootHash;
while (current) {
const parent: LogEntry = parentToHash[current.hash];
if (parent) {
topicList.append(parent);
current = parent;
} else {
current = undefined;
}
}
topicLists[rootHash.hash] = topicList.logEntries;
}
return topicLists;
}

View File

@ -231,39 +231,20 @@ export class TopicManager {
return deletedTopics.includes(topicId);
}
// loadTopics
// 功能将DevChat日志根据parentHash进行分组当前条目与该条目parentHash对应条目归属一组以此类推直到parentHash为空
// 返回值多个链表每个链表中当前元素的hash是下一个元素的parentHash
async loadLogEntries(): Promise<{ [key: string]: LogEntry[] }> {
// 通过DevChat获取日志
const devChat = new DevChat();
const logOptions: LogOptions = {
skip: 0,
maxCount: 10000
};
const logEntries = await devChat.log(logOptions);
const logEntriesFlat = logEntries.flat();
const logTopicLinkedList = loadTopicList(logEntriesFlat);
return logTopicLinkedList;
}
async loadTopics(): Promise<void> {
// 删除已经加载的topic
// 重新构建topic信息
this._topics = {};
const logEntriesMap = await this.loadLogEntries();
for (const logEntriesList of Object.values(logEntriesMap)) {
// 使用logEntriesList第一个元素更新topic的firstMessageHash和name
// 使用logEntriesList最后一个元素更新topic的lastMessageHash和lastUpdated
if (logEntriesList.length === 0) {
continue;
}
const logEntry = logEntriesList[0];
const devChat = new DevChat();
const logEntries: LogEntry[] = await devChat.topics();
// visite logEntries
// for each logEntry
let lastData: number = 0;
for (const logEntry of logEntries) {
lastData += 1;
const name = this.createTopicName(logEntry.request, logEntry.response);
const lastLogEntry = logEntriesList[logEntriesList.length - 1];
const topic = new Topic(name, logEntry.hash, logEntry.hash, Number(logEntry.date));
topic.updateLastMessageHashAndLastUpdated(lastLogEntry.hash, Number(lastLogEntry.date));
topic.updateLastMessageHashAndLastUpdated(logEntry.hash, lastData);
if (topic.firstMessageHash && this.isDeleteTopic(topic.firstMessageHash)) {
continue;
@ -272,27 +253,4 @@ export class TopicManager {
}
this._notifyReloadTopicsListeners(Object.values(this._topics));
}
async getTopicHistory(topicId: string): Promise<LogEntry[]> {
/**
* topic历史聊天记录
*/
// TOPIC对象中firstMessageHash可以作为日志查询的起始点
// 在DevChat日志中找出第一个hash为firstMessageHash的日志然后向下遍历直到找不到parentHash为当前日志hash的日志
const topic = this._topics[topicId];
if (!topic || !topic.firstMessageHash) {
logger.channel()?.info(`Topic ${topicId} not found`);
return [];
}
const logEntriesMap = await this.loadLogEntries();
if (!logEntriesMap[topic.firstMessageHash!]) {
logger.channel()?.info(`Topic ${topicId} not found in logEntriesMap`);
return [];
}
const logEntriesFlat = logEntriesMap[topic.firstMessageHash!];
return logEntriesFlat;
}
}

View File

@ -1,49 +0,0 @@
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { LogEntry } from '../../src/toolwrapper/devchat';
import { loadTopicList } from '../../src/topic/loadTopics';
describe('loadTopicList', () => {
it('should create topic lists from chat logs', () => {
const chatLogs: LogEntry[] = [
{ hash: '1', parent: '', user: 'user1', date: '2022-01-01', request: 'request1', response: 'response1', context: []},
{ hash: '2', parent: '1', user: 'user2', date: '2022-01-02', request: 'request2', response: 'response2', context: []},
{ hash: '3', parent: '2', user: 'user3', date: '2022-01-03', request: 'request3', response: 'response3', context: []},
{ hash: '4', parent: '', user: 'user4', date: '2022-01-04', request: 'request4', response: 'response4', context: []},
{ hash: '5', parent: '4', user: 'user5', date: '2022-01-05', request: 'request5', response: 'response5', context: []},
];
const expectedTopicLists = {
'1': [
{ hash: '1', parent: '', user: 'user1', date: '2022-01-01', request: 'request1', response: 'response1', context: []},
{ hash: '2', parent: '1', user: 'user2', date: '2022-01-02', request: 'request2', response: 'response2', context: []},
{ hash: '3', parent: '2', user: 'user3', date: '2022-01-03', request: 'request3', response: 'response3', context: []},
],
'4': [
{ hash: '4', parent: '', user: 'user4', date: '2022-01-04', request: 'request4', response: 'response4', context: []},
{ hash: '5', parent: '4', user: 'user5', date: '2022-01-05', request: 'request5', response: 'response5', context: []},
],
};
const topicLists = loadTopicList(chatLogs);
expect(topicLists).to.deep.equal(expectedTopicLists);
});
it('should handle empty chat logs', () => {
const chatLogs: LogEntry[] = [];
const expectedTopicLists = {};
const topicLists = loadTopicList(chatLogs);
expect(topicLists).to.deep.equal(expectedTopicLists);
});
it('should handle chat logs with no root entries', () => {
const chatLogs: LogEntry[] = [
{ hash: '1', parent: '0', user: 'user1', date: '2022-01-01', request: 'request1', response: 'response1', context: []},
{ hash: '2', parent: '1', user: 'user2', date: '2022-01-02', request: 'request2', response: 'response2', context: []},
];
const expectedTopicLists = {};
const topicLists = loadTopicList(chatLogs);
expect(topicLists).to.deep.equal(expectedTopicLists);
});
});