load topic from DevChat
This commit is contained in:
parent
823eed357d
commit
3ec578c7be
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import { TopicManager } from '../topic/topicManager';
|
import { TopicManager } from '../topic/topicManager';
|
||||||
import { LogEntry } from '../toolwrapper/devchat';
|
import DevChat, { LogEntry, LogOptions } from '../toolwrapper/devchat';
|
||||||
import messageHistory from '../util/messageHistory';
|
import messageHistory from '../util/messageHistory';
|
||||||
import { ApiKeyManager } from '../util/apiKey';
|
import { ApiKeyManager } from '../util/apiKey';
|
||||||
import { logger } from '../util/logger';
|
import { logger } from '../util/logger';
|
||||||
@ -63,15 +63,19 @@ export async function isWaitForApiKey() {
|
|||||||
|
|
||||||
export async function loadTopicHistoryLogs() : Promise<Array<LogEntry> | undefined> {
|
export async function loadTopicHistoryLogs() : Promise<Array<LogEntry> | undefined> {
|
||||||
const topicId = TopicManager.getInstance().currentTopicId;
|
const topicId = TopicManager.getInstance().currentTopicId;
|
||||||
let logEntriesFlat: Array<LogEntry> = [];
|
if (!topicId) {
|
||||||
if (topicId) {
|
return [];
|
||||||
logEntriesFlat = await TopicManager.getInstance().getTopicHistory(topicId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topicId !== TopicManager.getInstance().currentTopicId) {
|
const devChat = new DevChat();
|
||||||
logger.channel()?.info(`Current topic changed dure load topic hsitory!`)
|
const logOptions: LogOptions = {
|
||||||
return undefined;
|
skip: 0,
|
||||||
}
|
maxCount: 10000,
|
||||||
|
topic: topicId
|
||||||
|
};
|
||||||
|
const logEntries = await devChat.log(logOptions);
|
||||||
|
const logEntriesFlat = logEntries.flat();
|
||||||
|
|
||||||
return logEntriesFlat;
|
return logEntriesFlat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ export interface ChatOptions {
|
|||||||
export interface LogOptions {
|
export interface LogOptions {
|
||||||
skip?: number;
|
skip?: number;
|
||||||
maxCount?: number;
|
maxCount?: number;
|
||||||
|
topic?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogEntry {
|
export interface LogEntry {
|
||||||
@ -257,6 +258,31 @@ class DevChat {
|
|||||||
return JSON.parse(stdout.trim()).reverse();
|
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[] {
|
private buildLogArgs(options: LogOptions): string[] {
|
||||||
let args = ["log"];
|
let args = ["log"];
|
||||||
|
|
||||||
@ -270,6 +296,10 @@ class DevChat {
|
|||||||
args.push('--max-count', `${maxLogCount}`);
|
args.push('--max-count', `${maxLogCount}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.topic) {
|
||||||
|
args.push('--topic', `${options.topic}`);
|
||||||
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -231,39 +231,20 @@ export class TopicManager {
|
|||||||
return deletedTopics.includes(topicId);
|
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> {
|
async loadTopics(): Promise<void> {
|
||||||
// 删除已经加载的topic
|
|
||||||
// 重新构建topic信息
|
|
||||||
this._topics = {};
|
this._topics = {};
|
||||||
const logEntriesMap = await this.loadLogEntries();
|
|
||||||
for (const logEntriesList of Object.values(logEntriesMap)) {
|
const devChat = new DevChat();
|
||||||
// 使用logEntriesList第一个元素更新topic的firstMessageHash和name
|
const logEntries: LogEntry[] = await devChat.topics();
|
||||||
// 使用logEntriesList最后一个元素更新topic的lastMessageHash和lastUpdated
|
|
||||||
if (logEntriesList.length === 0) {
|
// visite logEntries
|
||||||
continue;
|
// for each logEntry
|
||||||
}
|
let lastData: number = 0;
|
||||||
const logEntry = logEntriesList[0];
|
for (const logEntry of logEntries) {
|
||||||
|
lastData += 1;
|
||||||
const name = this.createTopicName(logEntry.request, logEntry.response);
|
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));
|
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)) {
|
if (topic.firstMessageHash && this.isDeleteTopic(topic.firstMessageHash)) {
|
||||||
continue;
|
continue;
|
||||||
@ -272,27 +253,4 @@ export class TopicManager {
|
|||||||
}
|
}
|
||||||
this._notifyReloadTopicsListeners(Object.values(this._topics));
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
x
Reference in New Issue
Block a user