feat(chat): message accumulation + initial full scroll scrape — history persistence
This commit is contained in:
@@ -21,12 +21,79 @@ class CDPClient {
|
||||
this.port = port;
|
||||
this.client = null;
|
||||
this.connected = false;
|
||||
this.lastChatHTML = '';
|
||||
this.pollInterval = null;
|
||||
this.onChatUpdate = null; // callback(html)
|
||||
this.onChatUpdate = null; // callback(messages[])
|
||||
this.onDisconnect = null; // callback()
|
||||
this.onScreencastFrame = null; // callback({data, metadata})
|
||||
this.screencastRunning = false;
|
||||
|
||||
// 메시지 누적 시스템
|
||||
this.messageHistory = []; // 전체 누적 메시지
|
||||
this.seenHashes = new Set(); // 중복 제거용 해시
|
||||
this.initialScrapeComplete = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메시지 해시 생성 (중복 제거용)
|
||||
*/
|
||||
_msgHash(msg) {
|
||||
if (msg.type === 'task') return `task:${msg.title}:${msg.summary?.substring(0, 50) || ''}`;
|
||||
if (msg.type === 'user') return `user:${msg.content?.substring(0, 100) || ''}`;
|
||||
if (msg.type === 'text') return `text:${msg.content?.substring(0, 100) || ''}`;
|
||||
if (msg.type === 'thought') return `thought:${msg.label || ''}`;
|
||||
if (msg.type === 'status') return `status:${msg.content || ''}`;
|
||||
return `${msg.type}:${JSON.stringify(msg).substring(0, 100)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 새 메시지들을 히스토리에 누적 (중복 제거)
|
||||
* @returns {boolean} 새 메시지가 추가되었는지
|
||||
*/
|
||||
_accumulateMessages(newMessages) {
|
||||
if (!newMessages || newMessages.length === 0) return false;
|
||||
|
||||
let added = false;
|
||||
|
||||
// status 타입은 항상 마지막 것만 유지 (변경될 수 있음)
|
||||
const statusMsgs = newMessages.filter(m => m.type === 'status');
|
||||
const nonStatusMsgs = newMessages.filter(m => m.type !== 'status');
|
||||
|
||||
for (const msg of nonStatusMsgs) {
|
||||
const hash = this._msgHash(msg);
|
||||
if (!this.seenHashes.has(hash)) {
|
||||
this.seenHashes.add(hash);
|
||||
// task 카드의 actions/steps는 업데이트될 수 있음 → 기존 것 교체
|
||||
if (msg.type === 'task') {
|
||||
const existIdx = this.messageHistory.findIndex(m =>
|
||||
m.type === 'task' && m.title === msg.title
|
||||
);
|
||||
if (existIdx >= 0) {
|
||||
this.messageHistory[existIdx] = msg;
|
||||
added = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.messageHistory.push(msg);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
// status는 마지막 것으로 교체
|
||||
if (statusMsgs.length > 0) {
|
||||
const lastStatus = statusMsgs[statusMsgs.length - 1];
|
||||
const existStatusIdx = this.messageHistory.findIndex(m => m.type === 'status');
|
||||
if (existStatusIdx >= 0) {
|
||||
if (this.messageHistory[existStatusIdx].content !== lastStatus.content) {
|
||||
this.messageHistory[existStatusIdx] = lastStatus;
|
||||
added = true;
|
||||
}
|
||||
} else {
|
||||
this.messageHistory.push(lastStatus);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,6 +435,14 @@ class CDPClient {
|
||||
startPolling(intervalMs = 1000) {
|
||||
this.stopPolling();
|
||||
|
||||
// 초기 전체 스캔 (한 번만)
|
||||
if (!this.initialScrapeComplete) {
|
||||
this._initialFullScrape().then(() => {
|
||||
this.initialScrapeComplete = true;
|
||||
console.log(`[CDP] 초기 전체 스캔 완료: ${this.messageHistory.length}개 메시지`);
|
||||
});
|
||||
}
|
||||
|
||||
this.pollInterval = setInterval(async () => {
|
||||
if (!this.connected) {
|
||||
this.stopPolling();
|
||||
@@ -375,11 +450,9 @@ class CDPClient {
|
||||
}
|
||||
|
||||
const messages = await this.scrapeChatDOM();
|
||||
const hash = JSON.stringify(messages);
|
||||
if (messages && messages.length > 0 && hash !== this.lastChatHTML) {
|
||||
this.lastChatHTML = hash;
|
||||
if (this._accumulateMessages(messages)) {
|
||||
if (this.onChatUpdate) {
|
||||
this.onChatUpdate(messages);
|
||||
this.onChatUpdate([...this.messageHistory]);
|
||||
}
|
||||
}
|
||||
}, intervalMs);
|
||||
@@ -387,6 +460,78 @@ class CDPClient {
|
||||
console.log(`[CDP] 폴링 시작 (${intervalMs}ms 간격)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 초기 전체 스크롤 스캔 — Antigravity 대화를 top→bottom으로 순회하며 전체 수집
|
||||
*/
|
||||
async _initialFullScrape() {
|
||||
if (!this.connected || !this.client) return;
|
||||
|
||||
try {
|
||||
// 스크롤 정보 가져오기
|
||||
const { result: scrollInfo } = await this.client.Runtime.evaluate({
|
||||
expression: `
|
||||
(function() {
|
||||
const conv = document.querySelector('#conversation');
|
||||
if (!conv) return JSON.stringify(null);
|
||||
const sc = conv.querySelector('.overflow-y-auto');
|
||||
if (!sc) return JSON.stringify(null);
|
||||
return JSON.stringify({
|
||||
scrollTop: sc.scrollTop,
|
||||
scrollHeight: sc.scrollHeight,
|
||||
clientHeight: sc.clientHeight,
|
||||
});
|
||||
})()
|
||||
`,
|
||||
returnByValue: true,
|
||||
});
|
||||
|
||||
const info = JSON.parse(scrollInfo.value);
|
||||
if (!info) return;
|
||||
|
||||
const savedScrollTop = info.scrollTop;
|
||||
const totalHeight = info.scrollHeight;
|
||||
const viewHeight = info.clientHeight;
|
||||
const steps = Math.ceil(totalHeight / (viewHeight * 0.8)); // 80% 겹침두며 스크롤
|
||||
|
||||
console.log(`[CDP] 전체 스캔 시작: ${totalHeight}px, ${steps}단계`);
|
||||
|
||||
// top으로 이동
|
||||
await this.client.Runtime.evaluate({
|
||||
expression: `document.querySelector('#conversation .overflow-y-auto').scrollTop = 0;`,
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
|
||||
// 상단부터 하단까지 스크래핑
|
||||
for (let step = 0; step < steps; step++) {
|
||||
const messages = await this.scrapeChatDOM();
|
||||
this._accumulateMessages(messages);
|
||||
|
||||
// 80% 단위로 스크롤
|
||||
await this.client.Runtime.evaluate({
|
||||
expression: `
|
||||
const sc = document.querySelector('#conversation .overflow-y-auto');
|
||||
sc.scrollTop += Math.round(sc.clientHeight * 0.8);
|
||||
`,
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
}
|
||||
|
||||
// 원래 스크롤 위치 복원
|
||||
await this.client.Runtime.evaluate({
|
||||
expression: `document.querySelector('#conversation .overflow-y-auto').scrollTop = ${savedScrollTop};`,
|
||||
});
|
||||
|
||||
// 초기 수집 결과 전송
|
||||
if (this.messageHistory.length > 0 && this.onChatUpdate) {
|
||||
this.onChatUpdate([...this.messageHistory]);
|
||||
}
|
||||
|
||||
console.log(`[CDP] 전체 스캔 결과: ${this.messageHistory.length}개 메시지 수집`);
|
||||
} catch (err) {
|
||||
console.error('[CDP] 전체 스캔 오류:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 채팅 폴링 중지
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user