fix(ui): smart display for long convos, action buttons for isBlocking, no middle gap
This commit is contained in:
112
public/js/app.js
112
public/js/app.js
@@ -149,57 +149,104 @@
|
||||
|
||||
async function refreshTrajectory(sessionId, scrollToBottom = true) {
|
||||
try {
|
||||
// 1) trajectory (앞부분 ~336개)
|
||||
// 2) cascades (최신 상태: latestNotifyUserStep + latestTaskBoundaryStep)
|
||||
const [trajRes, cascRes] = await Promise.all([
|
||||
fetch(`/api/bridge/trajectory/${sessionId}`),
|
||||
fetch('/api/bridge/cascades'),
|
||||
]);
|
||||
|
||||
let messages = [];
|
||||
let isComplete = true; // trajectory가 전체를 포함하는지
|
||||
|
||||
// trajectory 파싱
|
||||
if (trajRes.ok) {
|
||||
const trajData = await trajRes.json();
|
||||
if (trajData.trajectory?.steps) {
|
||||
messages = parseTrajectoryToMessages(trajData.trajectory.steps);
|
||||
|
||||
// trajectory가 전체가 아닌 경우 (numTotalSteps > steps.length)
|
||||
const total = trajData.numTotalSteps || 0;
|
||||
const got = trajData.trajectory.steps.length;
|
||||
if (total > got) {
|
||||
messages.push({
|
||||
type: 'status',
|
||||
text: `⋯ ${total - got}개 스텝 생략 ⋯`,
|
||||
});
|
||||
isComplete = (total <= got);
|
||||
|
||||
if (isComplete) {
|
||||
// ≤336 스텝: 전체 표시 가능
|
||||
messages = parseTrajectoryToMessages(trajData.trajectory.steps);
|
||||
}
|
||||
// > 336 스텝: trajectory 건너뛰고 cascades 중심으로 표시
|
||||
}
|
||||
}
|
||||
|
||||
// cascades에서 최신 상태 보강
|
||||
// cascades에서 최신 상태
|
||||
if (cascRes.ok) {
|
||||
const cascData = await cascRes.json();
|
||||
const cascade = (cascData.cascades || cascData)[sessionId];
|
||||
if (cascade) {
|
||||
// latestTaskBoundaryStep → 현재 작업 상태
|
||||
if (cascade.latestTaskBoundaryStep?.step?.taskBoundary) {
|
||||
const tb = cascade.latestTaskBoundaryStep.step.taskBoundary;
|
||||
// 긴 대화일 때: cascades 기반 표시
|
||||
if (!isComplete) {
|
||||
// 대화 요약
|
||||
if (cascade.summary) {
|
||||
messages.push({
|
||||
type: 'status',
|
||||
text: `💬 대화 요약: ${cascade.summary}`,
|
||||
});
|
||||
}
|
||||
messages.push({
|
||||
type: 'task',
|
||||
title: tb.taskName || '',
|
||||
summary: tb.taskSummary || '',
|
||||
status: tb.taskStatus || '',
|
||||
mode: tb.mode || '',
|
||||
tools: [],
|
||||
type: 'status',
|
||||
text: `📊 총 ${cascade.stepCount || '?'}개 스텝 · 최종 입력: ${formatRelativeTime(cascade.lastModifiedTime)}`,
|
||||
});
|
||||
}
|
||||
// latestNotifyUserStep → 마지막 AI 응답
|
||||
if (cascade.latestNotifyUserStep?.step?.notifyUser?.notificationContent) {
|
||||
const content = cascade.latestNotifyUserStep.step.notifyUser.notificationContent;
|
||||
messages.push({
|
||||
type: 'text',
|
||||
html: simpleMarkdown(content),
|
||||
});
|
||||
|
||||
// 현재 Task 상태 (항상 최신)
|
||||
if (cascade.latestTaskBoundaryStep?.step?.taskBoundary) {
|
||||
const tb = cascade.latestTaskBoundaryStep.step.taskBoundary;
|
||||
// 이미 trajectory에서 같은 task가 없는 경우만 추가
|
||||
const lastTask = messages.filter(m => m.type === 'task').pop();
|
||||
if (!lastTask || lastTask.title !== tb.taskName) {
|
||||
messages.push({
|
||||
type: 'task',
|
||||
title: tb.taskName || '',
|
||||
summary: tb.taskSummary || '',
|
||||
status: tb.taskStatus || '',
|
||||
mode: tb.mode || '',
|
||||
tools: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 최신 AI 응답 (항상 표시)
|
||||
if (cascade.latestNotifyUserStep?.step?.notifyUser) {
|
||||
const nu = cascade.latestNotifyUserStep.step.notifyUser;
|
||||
const content = nu.notificationContent || '';
|
||||
if (content) {
|
||||
// 이미 trajectory에서 같은 내용이 없는 경우만 추가
|
||||
const lastText = messages.filter(m => m.type === 'text').pop();
|
||||
const contentSnip = content.substring(0, 50);
|
||||
if (!lastText || !lastText.html?.includes(contentSnip.replace(/[<>&]/g, ''))) {
|
||||
messages.push({
|
||||
type: 'text',
|
||||
html: simpleMarkdown(content),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 🔴 사용자 행동 필요 (isBlocking)
|
||||
if (nu.isBlocking) {
|
||||
messages.push({
|
||||
type: 'actions',
|
||||
label: '⚠️ Antigravity에서 리뷰가 필요합니다',
|
||||
buttons: [
|
||||
{ label: '미러 탭에서 확인', action: 'switch_mirror' },
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 실행 중 상태 표시
|
||||
if (cascade.status === 'CASCADE_RUN_STATUS_RUNNING') {
|
||||
const lastMsg = messages[messages.length - 1];
|
||||
if (!lastMsg || lastMsg.type !== 'actions') {
|
||||
messages.push({
|
||||
type: 'status',
|
||||
text: '🔄 AI가 작업 중...',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,6 +261,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
function formatRelativeTime(isoStr) {
|
||||
if (!isoStr) return '';
|
||||
const diff = Date.now() - new Date(isoStr).getTime();
|
||||
const mins = Math.floor(diff / 60000);
|
||||
if (mins < 1) return '방금 전';
|
||||
if (mins < 60) return `${mins}분 전`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
if (hrs < 24) return `${hrs}시간 전`;
|
||||
return `${Math.floor(hrs / 24)}일 전`;
|
||||
}
|
||||
|
||||
// 실시간 갱신 디바운스
|
||||
let refreshTimer = null;
|
||||
function scheduleRefresh() {
|
||||
|
||||
Reference in New Issue
Block a user