refactor(ui): Antigravity-style conversation — task cards group tool actions, remove internal steps, mode badges

This commit is contained in:
2026-03-08 00:34:24 +09:00
parent 410d77537e
commit 1d0eae769a
3 changed files with 116 additions and 49 deletions

View File

@@ -159,20 +159,46 @@
}
/**
* Trajectory 스텝 배열을 ChatPanel message[] 형식으로 변환
* Trajectory 스텝을 Antigravity 스타일 대화 흐름으로 변환
*
* 규칙:
* - USER_INPUT → 사용자 턴 구분자 (새 대화 블록 시작)
* - NOTIFY_USER → AI의 최종 응답 (마크다운)
* - TASK_BOUNDARY → 작업 카드 (이후 도구 스텝들을 내부 요약으로 포함)
* - RUN_COMMAND, CODE_ACTION → task 카드 내부 도구 호출 요약
* - 기타 (PLANNER_RESPONSE, EPHEMERAL, VIEW_FILE 등) → 건너뛰기
*/
function parseTrajectoryToMessages(steps) {
const msgs = [];
let currentTask = null; // 현재 활성 task 카드
let toolActions = []; // 현재 task 내 도구 호출들
function flushTask() {
if (currentTask) {
currentTask.tools = [...toolActions];
msgs.push(currentTask);
currentTask = null;
toolActions = [];
}
}
for (const step of steps) {
switch (step.type) {
case 'CORTEX_STEP_TYPE_USER_INPUT': {
// 이전 task 플러시
flushTask();
// 사용자 턴 구분
const text = step.userInput?.items?.[0]?.chunk?.value || '';
if (text) {
msgs.push({ type: 'user', text });
}
msgs.push({
type: 'user',
text: text || '(사용자 입력)',
time: step.metadata?.createdAt || '',
});
break;
}
case 'CORTEX_STEP_TYPE_NOTIFY_USER': {
// 이전 task 플러시
flushTask();
const content = step.notifyUser?.notificationContent || '';
if (content) {
msgs.push({
@@ -184,13 +210,32 @@
}
case 'CORTEX_STEP_TYPE_TASK_BOUNDARY': {
const tb = step.taskBoundary || {};
if (tb.taskName) {
msgs.push({
if (!tb.taskName) break;
// 같은 이름의 task는 업데이트 (중복 카드 방지)
if (currentTask && currentTask.title === tb.taskName) {
currentTask.summary = tb.taskSummary || currentTask.summary;
currentTask.status = tb.taskStatus || currentTask.status;
currentTask.mode = tb.mode || currentTask.mode;
} else {
flushTask();
currentTask = {
type: 'task',
title: tb.taskName || '',
title: tb.taskName,
summary: tb.taskSummary || '',
status: tb.taskStatus || '',
mode: tb.mode || '',
tools: [],
};
}
break;
}
case 'CORTEX_STEP_TYPE_RUN_COMMAND': {
const cmd = step.runCommand?.commandLine || step.runCommand?.command || '';
if (cmd) {
toolActions.push({
icon: '⚡',
label: cmd.length > 60 ? cmd.substring(0, 57) + '...' : cmd,
done: step.status === 'CORTEX_STEP_STATUS_DONE',
});
}
break;
@@ -198,55 +243,37 @@
case 'CORTEX_STEP_TYPE_CODE_ACTION': {
const ca = step.codeAction || {};
const file = ca.filePath || ca.targetFile || '';
const desc = ca.description || '';
if (file || desc) {
msgs.push({
type: 'code',
language: '',
code: '',
description: `📝 ${file ? file.split(/[\\/]/).pop() : '파일'}: ${desc || '코드 수정'}`,
});
}
const desc = ca.description || '코드 수정';
toolActions.push({
icon: '📝',
label: file ? file.split(/[\\/]/).pop() + ': ' + desc : desc,
done: step.status === 'CORTEX_STEP_STATUS_DONE',
});
break;
}
case 'CORTEX_STEP_TYPE_RUN_COMMAND': {
const cmd = step.runCommand?.commandLine || step.runCommand?.command || '';
if (cmd) {
msgs.push({
type: 'code',
language: 'bash',
code: cmd,
description: `⚡ 명령 실행 (${step.status === 'CORTEX_STEP_STATUS_DONE' ? '완료' : '실행 중'})`,
});
}
case 'CORTEX_STEP_TYPE_VIEW_FILE':
case 'CORTEX_STEP_TYPE_LIST_DIRECTORY': {
// 카운트만 — 개별 표시 안 함
break;
}
case 'CORTEX_STEP_TYPE_PLANNER_RESPONSE': {
// 에이전트 사고 과정 — 접기 가능
const text = step.plannerResponse?.text || step.plannerResponse?.rawText || '';
if (text && text.length > 10) {
msgs.push({
type: 'thought',
text: text.substring(0, 300) + (text.length > 300 ? '...' : ''),
});
}
break;
}
// 덜 중요한 스텝은 생략 (VIEW_FILE, LIST_DIRECTORY, EPHEMERAL 등)
// PLANNER_RESPONSE, EPHEMERAL, CHECKPOINT, CONVERSATION_HISTORY, KNOWLEDGE_ARTIFACTS → 건너뛰기
}
}
flushTask();
return msgs;
}
function simpleMarkdown(text) {
return text
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre class="md-code"><code>$2</code></pre>')
.replace(/\n/g, '<br>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/^## (.*)/gm, '<h3>$1</h3>')
.replace(/^### (.*)/gm, '<h4>$1</h4>')
.replace(/\| (.+?) \|/g, (m) => `<span style="font-family:monospace">${m}</span>`);
.replace(/^\- (.*)/gm, '<li>$1</li>')
.replace(/^\d+\. (.*)/gm, '<li>$1</li>');
}
// ─── 서버 메시지 핸들러 ───────────────────────────────