feat: refresh shared chat and server workflows
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
ArrowLeftOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
ControlOutlined,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
PaperClipOutlined,
|
||||
PlusOutlined,
|
||||
ProfileOutlined,
|
||||
RedoOutlined,
|
||||
SendOutlined,
|
||||
ShareAltOutlined,
|
||||
SettingOutlined,
|
||||
@@ -482,7 +484,6 @@ const COLLAPSIBLE_MESSAGE_CHAR_COUNT = 280;
|
||||
const DIFF_CODE_BLOCK_PATTERN = /```diff[^\n]*\n([\s\S]*?)```/g;
|
||||
const IMMEDIATE_SEND_TOGGLE_HOLD_MS = 2000;
|
||||
const SYSTEM_EXECUTION_JUMP_MAX_RETRIES = 4;
|
||||
const SYSTEM_EXECUTION_JUMP_TOP_OFFSET = 12;
|
||||
const MOBILE_SYSTEM_EXECUTION_AUTO_HIDE_MAX_WIDTH = 767;
|
||||
const DEFAULT_QUEUE_SUMMARY_MAX_LENGTH = 32;
|
||||
const TABLET_SYSTEM_EXECUTION_SUMMARY_MAX_LENGTH = 88;
|
||||
@@ -690,6 +691,47 @@ function getElementOffsetWithinContainer(target: HTMLElement, container: HTMLEle
|
||||
return targetRect.top - containerRect.top + container.scrollTop;
|
||||
}
|
||||
|
||||
function getContainerScrollPaddingTop(container: HTMLElement) {
|
||||
if (typeof window === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const paddingTop = Number.parseFloat(window.getComputedStyle(container).paddingTop || '0');
|
||||
return Number.isFinite(paddingTop) ? Math.max(0, paddingTop) : 0;
|
||||
}
|
||||
|
||||
function resolveScrollableAnchorContainer(target: HTMLElement, preferredContainer?: HTMLElement | null) {
|
||||
const candidates: Array<HTMLElement | null | undefined> = [preferredContainer, target.parentElement];
|
||||
|
||||
let currentAncestor = target.parentElement;
|
||||
while (currentAncestor) {
|
||||
candidates.push(currentAncestor);
|
||||
currentAncestor = currentAncestor.parentElement;
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (!(candidate instanceof HTMLElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const style = window.getComputedStyle(candidate);
|
||||
const overflowY = style.overflowY;
|
||||
const isScrollableOverflow = overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay';
|
||||
|
||||
if (!isScrollableOverflow) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidate.scrollHeight <= candidate.clientHeight + 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPhoneLikeViewport() {
|
||||
if (typeof window === 'undefined') {
|
||||
return false;
|
||||
@@ -703,6 +745,25 @@ function isPhoneLikeViewport() {
|
||||
return isNarrowViewport && (hasCoarsePointer || hasTouchPoints);
|
||||
}
|
||||
|
||||
function getSystemExecutionJumpTargetPriority(
|
||||
target: ReturnType<typeof resolveSystemExecutionJumpTarget>,
|
||||
) {
|
||||
if (!target) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (target.kind) {
|
||||
case 'prompt':
|
||||
return 3;
|
||||
case 'response':
|
||||
return 2;
|
||||
case 'request':
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePreviewFileExtension(item: Pick<PreviewOption, 'url' | 'label'>) {
|
||||
const fileName = buildPreviewFileName(item).toLowerCase();
|
||||
const match = fileName.match(/\.([a-z0-9]{1,16})$/i);
|
||||
@@ -1502,6 +1563,18 @@ function isRequestRunningStatus(status: ChatConversationRequestStatus | undefine
|
||||
return status === 'started';
|
||||
}
|
||||
|
||||
function isDisconnectedRequestNeedingAttention(request: ChatConversationRequest | undefined) {
|
||||
if (!request || request.hasResponse) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.status !== 'failed') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (request.statusMessage?.trim() ?? '') === '중단된 오래된 요청';
|
||||
}
|
||||
|
||||
function isRequestUserFinalized(
|
||||
request: ChatConversationRequest,
|
||||
attentionState?: SystemExecutionAttentionState,
|
||||
@@ -1589,6 +1662,21 @@ function resolveAggregatedRequestStatusSummary(
|
||||
};
|
||||
}
|
||||
|
||||
function resolveActiveSystemExecutionActionTargetRequest(
|
||||
requests: ChatConversationRequest[],
|
||||
attentionStateByRequestId: Map<string, SystemExecutionAttentionState>,
|
||||
) {
|
||||
const activeRequests = requests.filter(
|
||||
(request) => isRequestRunningStatus(request.status) || isRequestQueueStatus(request.status),
|
||||
);
|
||||
|
||||
if (activeRequests.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolveRepresentativeSystemExecutionRequest(activeRequests, attentionStateByRequestId) ?? activeRequests[0]!;
|
||||
}
|
||||
|
||||
function getRepresentativeAttentionPriority(
|
||||
request: ChatConversationRequest,
|
||||
attentionState: SystemExecutionAttentionState | undefined,
|
||||
@@ -1659,13 +1747,22 @@ function resolveRepresentativeSystemExecutionRequest(
|
||||
function formatRequestStatusLabel(
|
||||
request: ChatConversationRequest | undefined,
|
||||
attentionState?: SystemExecutionAttentionState,
|
||||
options?: {
|
||||
hideFinalizedLabel?: boolean;
|
||||
},
|
||||
) {
|
||||
const hideFinalizedLabel = options?.hideFinalizedLabel === true;
|
||||
|
||||
if (hasAnsweredRequest(request)) {
|
||||
if (request?.status === "completed") {
|
||||
if (hideFinalizedLabel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return attentionState?.hasPendingPromptBadge || attentionState?.hasPendingVerificationBadge ? "확인대기" : "완료";
|
||||
}
|
||||
|
||||
return "답변도착";
|
||||
return hideFinalizedLabel ? null : "답변도착";
|
||||
}
|
||||
|
||||
switch (request?.status) {
|
||||
@@ -1845,10 +1942,11 @@ function hasVisibleActivityOverviewContent(
|
||||
request.status === 'accepted' ||
|
||||
request.status === 'queued' ||
|
||||
request.status === 'started';
|
||||
const hasPendingAttention = attentionState?.hasOwnAttentionState === true;
|
||||
const hasChecklistEntries = buildChatActivityChecklistEntries(activityOverview.lines, request).length > 0;
|
||||
const hasExecutorEntries = activityOverview.executors.length > 0;
|
||||
|
||||
if (isUserFinalized || !isInProgress) {
|
||||
if (isUserFinalized || (!isInProgress && !hasPendingAttention)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3206,15 +3304,40 @@ const ChatComposerInput = memo(function ChatComposerInput({
|
||||
|
||||
function SharedRoomsRequestCard({
|
||||
request,
|
||||
onSelect,
|
||||
}: {
|
||||
request: ChatConversationRequest;
|
||||
onSelect?: (() => void) | null;
|
||||
}) {
|
||||
const questionText = (request.userText ?? "").trim() || "-";
|
||||
const answerText = (request.responseText ?? "").trim() || request.statusMessage?.trim() || "아직 답변이 없습니다.";
|
||||
const requestStatusLabel = formatRequestStatusLabel(request);
|
||||
|
||||
return (
|
||||
<section className="app-chat-message-group">
|
||||
<section
|
||||
className={`app-chat-message-group${onSelect ? ' app-chat-message-group--interactive' : ''}`}
|
||||
role={onSelect ? 'button' : undefined}
|
||||
tabIndex={onSelect ? 0 : undefined}
|
||||
onClick={
|
||||
onSelect
|
||||
? () => {
|
||||
onSelect();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onKeyDown={
|
||||
onSelect
|
||||
? (event) => {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
onSelect();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<header className="app-chat-message-group__header">
|
||||
<div className="app-chat-message-group__header-meta">
|
||||
{requestStatusLabel ? (
|
||||
@@ -3367,6 +3490,7 @@ export function ChatConversationView({
|
||||
const childComposerRefs = useRef(new Map<string, TextAreaRef | null>());
|
||||
const messageAnchorRefs = useRef(new Map<number, HTMLDivElement>());
|
||||
const messageBodyRefs = useRef(new Map<number, HTMLDivElement>());
|
||||
const promptCardAnchorRefs = useRef(new Map<number, HTMLElement>());
|
||||
const systemExecutionBodyRef = useRef<HTMLDivElement | null>(null);
|
||||
const systemExecutionJumpFrameRef = useRef<number | null>(null);
|
||||
const systemExecutionOlderLoadRequestedRef = useRef(false);
|
||||
@@ -3374,6 +3498,14 @@ export function ChatConversationView({
|
||||
const previousSystemExecutionDisplayModeRef = useRef<SystemExecutionDisplayMode>('collapsed');
|
||||
const previousSessionIdRef = useRef(sessionId);
|
||||
const shouldFollowLatestRoomShareGroupRef = useRef(false);
|
||||
const pendingRoomShareJumpRef = useRef<
|
||||
| {
|
||||
groupId: string;
|
||||
requestId?: string;
|
||||
fallbackMessageId?: number;
|
||||
}
|
||||
| null
|
||||
>(null);
|
||||
const immediateSendHoldTimerRef = useRef<number | null>(null);
|
||||
const suppressImmediateSendClickRef = useRef(false);
|
||||
const composerDraftValueRef = useRef(draft);
|
||||
@@ -3773,6 +3905,27 @@ export function ChatConversationView({
|
||||
|
||||
return nextMap;
|
||||
}, [messageRenderPayloadById, orderedMessages]);
|
||||
const firstPromptMessageIdByRequestId = useMemo(() => {
|
||||
const nextMap = new Map<string, number>();
|
||||
|
||||
orderedMessages.forEach((message) => {
|
||||
const requestId = message.clientRequestId?.trim();
|
||||
|
||||
if (!requestId || nextMap.has(requestId) || (message.author !== 'codex' && message.author !== 'system')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { promptTargets } = messageRenderPayloadById.get(message.id) ?? extractMessageRenderPayload(message);
|
||||
|
||||
if (promptTargets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextMap.set(requestId, message.id);
|
||||
});
|
||||
|
||||
return nextMap;
|
||||
}, [messageRenderPayloadById, orderedMessages]);
|
||||
const promptFollowupCountByParentRequestId = useMemo(() => {
|
||||
const nextMap = new Map<string, number>();
|
||||
|
||||
@@ -4041,7 +4194,8 @@ export function ChatConversationView({
|
||||
hasVerificationTarget,
|
||||
hasConfirmedVerificationTarget,
|
||||
});
|
||||
const hasOwnAttentionState = hasPendingPromptBadge || hasPendingVerificationBadge;
|
||||
const hasOwnAttentionState =
|
||||
hasPendingPromptBadge || hasPendingVerificationBadge || isDisconnectedRequestNeedingAttention(request);
|
||||
|
||||
nextMap.set(request.requestId, {
|
||||
activityLines,
|
||||
@@ -4251,6 +4405,21 @@ export function ChatConversationView({
|
||||
|
||||
return roomShareRequestGroups;
|
||||
}, [roomShareExpandMode, roomShareRequestGroups]);
|
||||
const roomShareGroupIdByRequestId = useMemo(() => {
|
||||
const nextMap = new Map<string, string>();
|
||||
|
||||
roomShareRequestGroups.forEach((entry) => {
|
||||
entry.groupedRequests.forEach((groupedRequest) => {
|
||||
const normalizedRequestId = groupedRequest.requestId.trim();
|
||||
|
||||
if (normalizedRequestId) {
|
||||
nextMap.set(normalizedRequestId, entry.groupId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return nextMap;
|
||||
}, [roomShareRequestGroups]);
|
||||
useEffect(() => {
|
||||
if (!showRoomsShareHeader) {
|
||||
return;
|
||||
@@ -4269,6 +4438,9 @@ export function ChatConversationView({
|
||||
shouldFollowLatestRoomShareGroupRef.current = false;
|
||||
|
||||
if (nextLatestGroupId && nextLatestGroupId !== selectedRoomShareGroupId) {
|
||||
pendingRoomShareJumpRef.current = {
|
||||
groupId: nextLatestGroupId,
|
||||
};
|
||||
setSelectedRoomShareGroupId(nextLatestGroupId);
|
||||
}
|
||||
return;
|
||||
@@ -4514,6 +4686,18 @@ export function ChatConversationView({
|
||||
|
||||
messageBodyRefs.current.delete(messageId);
|
||||
};
|
||||
const setPromptCardAnchorRef = (messageId: number, element: HTMLElement | null) => {
|
||||
if (!Number.isFinite(messageId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
promptCardAnchorRefs.current.set(messageId, element);
|
||||
return;
|
||||
}
|
||||
|
||||
promptCardAnchorRefs.current.delete(messageId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
@@ -4729,16 +4913,15 @@ export function ChatConversationView({
|
||||
visibleSystemExecutionRequests.length,
|
||||
]);
|
||||
|
||||
const scrollToMessageAnchor = (messageId: number, behavior: ScrollBehavior) => {
|
||||
const anchorElement = messageAnchorRefs.current.get(messageId);
|
||||
|
||||
const scrollToAnchorElement = (anchorElement: HTMLElement | null | undefined, behavior: ScrollBehavior) => {
|
||||
if (!anchorElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const viewportElement = viewportRef.current;
|
||||
const scrollContainer = resolveScrollableAnchorContainer(anchorElement, viewportElement);
|
||||
|
||||
if (!viewportElement) {
|
||||
if (!scrollContainer) {
|
||||
anchorElement.scrollIntoView({
|
||||
behavior,
|
||||
block: 'start',
|
||||
@@ -4748,16 +4931,22 @@ export function ChatConversationView({
|
||||
|
||||
const nextTop = Math.max(
|
||||
0,
|
||||
getElementOffsetWithinContainer(anchorElement, viewportElement) - SYSTEM_EXECUTION_JUMP_TOP_OFFSET,
|
||||
getElementOffsetWithinContainer(anchorElement, scrollContainer) - getContainerScrollPaddingTop(scrollContainer),
|
||||
);
|
||||
|
||||
viewportElement.scrollTo({
|
||||
scrollContainer.scrollTo({
|
||||
top: nextTop,
|
||||
behavior,
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
const scrollToMessageAnchor = (messageId: number, behavior: ScrollBehavior) =>
|
||||
scrollToAnchorElement(messageAnchorRefs.current.get(messageId), behavior);
|
||||
const scrollToPromptCardAnchor = (messageId: number | null | undefined, behavior: ScrollBehavior) =>
|
||||
typeof messageId === 'number' && Number.isFinite(messageId)
|
||||
? scrollToAnchorElement(promptCardAnchorRefs.current.get(messageId), behavior)
|
||||
: false;
|
||||
|
||||
const triggerOlderSystemExecutionLoad = () => {
|
||||
if (
|
||||
@@ -4799,31 +4988,94 @@ export function ChatConversationView({
|
||||
triggerOlderSystemExecutionLoad();
|
||||
};
|
||||
|
||||
const scheduleSystemExecutionJump = (anchorMessageId: number, attempt = 0) => {
|
||||
const scheduleSystemExecutionJump = (scroll: (behavior: ScrollBehavior) => boolean, attempt = 0) => {
|
||||
if (systemExecutionJumpFrameRef.current !== null) {
|
||||
window.cancelAnimationFrame(systemExecutionJumpFrameRef.current);
|
||||
}
|
||||
|
||||
systemExecutionJumpFrameRef.current = window.requestAnimationFrame(() => {
|
||||
systemExecutionJumpFrameRef.current = null;
|
||||
const didScroll = scrollToMessageAnchor(anchorMessageId, attempt === 0 ? 'smooth' : 'auto');
|
||||
const didScroll = scroll(attempt === 0 ? 'smooth' : 'auto');
|
||||
|
||||
if (!didScroll && attempt < SYSTEM_EXECUTION_JUMP_MAX_RETRIES) {
|
||||
scheduleSystemExecutionJump(anchorMessageId, attempt + 1);
|
||||
if (attempt < SYSTEM_EXECUTION_JUMP_MAX_RETRIES) {
|
||||
scheduleSystemExecutionJump(scroll, attempt + 1);
|
||||
}
|
||||
|
||||
if (!didScroll && attempt >= SYSTEM_EXECUTION_JUMP_MAX_RETRIES) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const scrollToSystemExecutionRequest = (requestId: string, fallbackMessageId?: number) => {
|
||||
const resolveSystemExecutionJumpTarget = (request: ChatConversationRequest | undefined) => {
|
||||
if (!request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedRequestId = request.requestId.trim();
|
||||
|
||||
if (!normalizedRequestId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const firstPromptMessageId = firstPromptMessageIdByRequestId.get(normalizedRequestId) ?? null;
|
||||
const hasPromptTarget = firstPromptMessageId != null || (promptTargetsByRequestId.get(normalizedRequestId)?.length ?? 0) > 0;
|
||||
|
||||
if (hasPromptTarget) {
|
||||
const anchorMessageId =
|
||||
[firstPromptMessageId, request.responseMessageId].find(
|
||||
(messageId): messageId is number => typeof messageId === 'number' && Number.isFinite(messageId),
|
||||
) ?? null;
|
||||
|
||||
return {
|
||||
kind: 'prompt' as const,
|
||||
requestId: normalizedRequestId,
|
||||
promptMessageId: firstPromptMessageId,
|
||||
anchorMessageId,
|
||||
buttonLabel: 'prompt 위치로 이동',
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof request.responseMessageId === 'number' && Number.isFinite(request.responseMessageId)) {
|
||||
return {
|
||||
kind: 'response' as const,
|
||||
requestId: normalizedRequestId,
|
||||
anchorMessageId: request.responseMessageId,
|
||||
buttonLabel: '답변 위치로 이동',
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof request.userMessageId === 'number' && Number.isFinite(request.userMessageId)) {
|
||||
return {
|
||||
kind: 'request' as const,
|
||||
requestId: normalizedRequestId,
|
||||
anchorMessageId: request.userMessageId,
|
||||
buttonLabel: '요청 위치로 이동',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const scrollToSystemExecutionRequest = (
|
||||
requestId: string,
|
||||
options?: {
|
||||
fallbackMessageId?: number;
|
||||
jumpTarget?: ReturnType<typeof resolveSystemExecutionJumpTarget>;
|
||||
},
|
||||
) => {
|
||||
const request = requestStateMap.get(requestId);
|
||||
const normalizedRequestId = requestId.trim();
|
||||
const rootRequestId =
|
||||
(normalizedRequestId ? resolveConversationRootRequestId(normalizedRequestId, requestStateMap) : '') ||
|
||||
normalizedRequestId;
|
||||
const resolvedJumpTarget = options?.jumpTarget ?? resolveSystemExecutionJumpTarget(request);
|
||||
const anchorMessageId =
|
||||
[request?.responseMessageId, request?.userMessageId, fallbackMessageId].find(
|
||||
resolvedJumpTarget?.anchorMessageId ??
|
||||
[request?.responseMessageId, request?.userMessageId, options?.fallbackMessageId].find(
|
||||
(messageId): messageId is number => typeof messageId === 'number' && Number.isFinite(messageId),
|
||||
) ?? null;
|
||||
) ??
|
||||
null;
|
||||
|
||||
if (!anchorMessageId) {
|
||||
return false;
|
||||
@@ -4833,7 +5085,16 @@ export function ChatConversationView({
|
||||
setExpandedGroupIds((current) => (current.includes(rootRequestId) ? current : [...current, rootRequestId]));
|
||||
}
|
||||
setExpandedMessageIds((current) => (current.includes(anchorMessageId) ? current : [...current, anchorMessageId]));
|
||||
scheduleSystemExecutionJump(anchorMessageId);
|
||||
scheduleSystemExecutionJump((behavior) => {
|
||||
if (resolvedJumpTarget?.kind === 'prompt') {
|
||||
return (
|
||||
scrollToPromptCardAnchor(resolvedJumpTarget.promptMessageId, behavior) ||
|
||||
scrollToMessageAnchor(anchorMessageId, behavior)
|
||||
);
|
||||
}
|
||||
|
||||
return scrollToMessageAnchor(anchorMessageId, behavior);
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -4847,7 +5108,10 @@ export function ChatConversationView({
|
||||
|
||||
scrollToSystemExecutionRequest(
|
||||
representativeRequest.requestId,
|
||||
representativeRequest.responseMessageId ?? representativeRequest.userMessageId ?? undefined,
|
||||
{
|
||||
fallbackMessageId: representativeRequest.responseMessageId ?? representativeRequest.userMessageId ?? undefined,
|
||||
jumpTarget: resolveSystemExecutionJumpTarget(representativeRequest),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4867,15 +5131,72 @@ export function ChatConversationView({
|
||||
scrollToRoomShareGroup(nextGroup.groupId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const pendingJump = pendingRoomShareJumpRef.current;
|
||||
|
||||
if (!pendingJump || pendingJump.groupId !== selectedRoomShareGroupId) {
|
||||
return;
|
||||
}
|
||||
|
||||
pendingRoomShareJumpRef.current = null;
|
||||
|
||||
const frameId = window.requestAnimationFrame(() => {
|
||||
if (pendingJump.requestId) {
|
||||
const pendingRequest = requestStateMap.get(pendingJump.requestId);
|
||||
const pendingJumpTarget = resolveSystemExecutionJumpTarget(pendingRequest);
|
||||
const didScroll = scrollToSystemExecutionRequest(pendingJump.requestId, {
|
||||
fallbackMessageId: pendingJump.fallbackMessageId,
|
||||
jumpTarget: pendingJumpTarget,
|
||||
});
|
||||
|
||||
if (
|
||||
didScroll &&
|
||||
(pendingJumpTarget?.kind === 'prompt' || pendingJumpTarget?.kind === 'response') &&
|
||||
isPhoneLikeViewport() &&
|
||||
systemExecutionDisplayMode === 'expanded'
|
||||
) {
|
||||
setSystemExecutionDisplayMode('hidden');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
scrollToRoomShareGroup(pendingJump.groupId);
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.cancelAnimationFrame(frameId);
|
||||
};
|
||||
}, [requestStateMap, scrollToRoomShareGroup, selectedRoomShareGroupId, systemExecutionDisplayMode]);
|
||||
|
||||
const handleSystemExecutionJump = (request: ChatConversationRequest) => {
|
||||
const didScroll = scrollToSystemExecutionRequest(
|
||||
request.requestId,
|
||||
request.responseMessageId ?? request.userMessageId ?? undefined,
|
||||
);
|
||||
const jumpTarget = resolveSystemExecutionJumpTarget(request);
|
||||
const normalizedRequestId = request.requestId.trim();
|
||||
const targetGroupId = normalizedRequestId ? roomShareGroupIdByRequestId.get(normalizedRequestId) ?? null : null;
|
||||
const fallbackMessageId = request.responseMessageId ?? request.userMessageId ?? undefined;
|
||||
|
||||
if (
|
||||
showRoomsShareHeader &&
|
||||
roomShareExpandMode === 'latest' &&
|
||||
targetGroupId &&
|
||||
targetGroupId !== selectedRoomShareGroupId
|
||||
) {
|
||||
pendingRoomShareJumpRef.current = {
|
||||
groupId: targetGroupId,
|
||||
requestId: normalizedRequestId,
|
||||
fallbackMessageId,
|
||||
};
|
||||
setSelectedRoomShareGroupId(targetGroupId);
|
||||
return;
|
||||
}
|
||||
|
||||
const didScroll = scrollToSystemExecutionRequest(request.requestId, {
|
||||
fallbackMessageId,
|
||||
jumpTarget,
|
||||
});
|
||||
|
||||
if (
|
||||
didScroll &&
|
||||
request.responseMessageId &&
|
||||
(jumpTarget?.kind === 'prompt' || jumpTarget?.kind === 'response') &&
|
||||
isPhoneLikeViewport() &&
|
||||
systemExecutionDisplayMode === 'expanded'
|
||||
) {
|
||||
@@ -5082,13 +5403,27 @@ export function ChatConversationView({
|
||||
systemExecutionActivityOverviewByRequestId,
|
||||
systemExecutionAttentionStateByRequestId,
|
||||
);
|
||||
const activityActionTargetRequest = resolveActiveSystemExecutionActionTargetRequest(
|
||||
depth === 0 ? groupedRequests : [request],
|
||||
systemExecutionAttentionStateByRequestId,
|
||||
);
|
||||
const activityOverview = activityOverviewTargetRequest
|
||||
? systemExecutionActivityOverviewByRequestId.get(activityOverviewTargetRequest.requestId) ?? null
|
||||
: null;
|
||||
const canToggleActivityOverview = activityOverviewTargetRequest != null;
|
||||
const canToggleActivityOverview = activityActionTargetRequest != null;
|
||||
const timestampLabel = resolveSystemExecutionRequestTimestamp(representativeRequest);
|
||||
const elapsedLabel = formatSystemExecutionElapsedLabel(representativeRequest);
|
||||
const jumpButtonLabel = representativeRequest.responseMessageId ? '답변 위치로 이동' : '요청 위치로 이동';
|
||||
const summaryJumpTarget = resolveSystemExecutionJumpTarget(summaryRequest);
|
||||
const representativeJumpTarget = resolveSystemExecutionJumpTarget(representativeRequest);
|
||||
const jumpTargetRequest =
|
||||
getSystemExecutionJumpTargetPriority(representativeJumpTarget) >
|
||||
getSystemExecutionJumpTargetPriority(summaryJumpTarget)
|
||||
? representativeRequest
|
||||
: summaryRequest;
|
||||
const jumpButtonLabel =
|
||||
(jumpTargetRequest.requestId === representativeRequest.requestId
|
||||
? representativeJumpTarget
|
||||
: summaryJumpTarget)?.buttonLabel ?? '위치로 이동';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -5109,7 +5444,7 @@ export function ChatConversationView({
|
||||
className="app-chat-panel__system-execution-record-main"
|
||||
aria-label={jumpButtonLabel}
|
||||
onClick={() => {
|
||||
handleSystemExecutionJump(representativeRequest);
|
||||
handleSystemExecutionJump(jumpTargetRequest);
|
||||
}}
|
||||
>
|
||||
{depth > 0 ? (
|
||||
@@ -5184,12 +5519,10 @@ export function ChatConversationView({
|
||||
icon={<ProfileOutlined />}
|
||||
aria-label="Plan 체크리스트와 실행기 보기"
|
||||
onClick={() => {
|
||||
if (!activityOverviewTargetRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSystemExecutionDisplayMode('expanded');
|
||||
setExpandedSystemExecutionActivityRequestId(activityOverviewTargetRequest.requestId);
|
||||
setExpandedSystemExecutionActivityRequestId(
|
||||
activityOverviewTargetRequest?.requestId ?? activityActionTargetRequest?.requestId ?? null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
@@ -5603,7 +5936,9 @@ export function ChatConversationView({
|
||||
const attentionState = message.clientRequestId
|
||||
? systemExecutionAttentionStateByRequestId.get(message.clientRequestId)
|
||||
: undefined;
|
||||
const requestStatusLabel = formatRequestStatusLabel(requestState, attentionState);
|
||||
const requestStatusLabel = formatRequestStatusLabel(requestState, attentionState, {
|
||||
hideFinalizedLabel: message.author === 'user',
|
||||
});
|
||||
const requestDetailText = getRequestDetailText(requestState);
|
||||
const responsePromptTargets = attentionState?.promptTargets ?? [];
|
||||
const responsePromptSubmittedCount = attentionState?.promptSubmittedCount ?? 0;
|
||||
@@ -5904,7 +6239,6 @@ export function ChatConversationView({
|
||||
완료 처리
|
||||
</Button>
|
||||
) : null}
|
||||
{hasChildRequest ? <Tag color="processing">재답변 이어짐</Tag> : null}
|
||||
{!canCompleteVerificationFromResponse && responsePromptTargets.length === 0 && isResponseVerificationManuallyCompleted ? <Tag color="success">응답 확인 완료</Tag> : null}
|
||||
{canReplyToResponse && message.clientRequestId ? (
|
||||
<Button
|
||||
@@ -6019,6 +6353,13 @@ export function ChatConversationView({
|
||||
sourceMessageId: message.id,
|
||||
})
|
||||
}
|
||||
anchorRef={
|
||||
index === 0 && message.clientRequestId
|
||||
? (element) => {
|
||||
setPromptCardAnchorRef(message.id, element);
|
||||
}
|
||||
: null
|
||||
}
|
||||
onSharePrompt={
|
||||
promptParentRequestId && onSharePromptTarget
|
||||
? () => {
|
||||
@@ -6360,7 +6701,7 @@ export function ChatConversationView({
|
||||
const composerPlaceholder = isComposerDisabled
|
||||
? '권한이 있는 컨텍스트가 없어 입력할 수 없습니다.'
|
||||
: replyReferenceRequest
|
||||
? '선택한 답변을 바탕으로 시스템 채팅에 이어서 보낼 내용을 입력하세요. 첨부만 추가해서 보내도 됩니다.'
|
||||
? '선택한 답변을 바탕으로 새 대화에 이어서 보낼 내용을 입력하세요. 첨부만 추가해서 보내도 됩니다.'
|
||||
: showRoomsShareHeader
|
||||
? isMobileViewport
|
||||
? '공유채팅에 보낼 내용을 입력하세요.'
|
||||
@@ -6535,8 +6876,8 @@ export function ChatConversationView({
|
||||
size="small"
|
||||
className="app-chat-panel__rooms-share-action app-chat-panel__rooms-share-action--icon"
|
||||
icon={<MinusOutlined />}
|
||||
aria-label="시스템 채팅 최소화"
|
||||
title="시스템 채팅 최소화"
|
||||
aria-label="채팅 최소화"
|
||||
title="채팅 최소화"
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
}}
|
||||
@@ -6552,8 +6893,8 @@ export function ChatConversationView({
|
||||
danger
|
||||
className="app-chat-panel__rooms-share-action app-chat-panel__rooms-share-action--icon app-chat-panel__rooms-share-action--close"
|
||||
icon={<CloseOutlined />}
|
||||
aria-label="시스템 채팅 닫기"
|
||||
title="시스템 채팅 닫기"
|
||||
aria-label="채팅 닫기"
|
||||
title="채팅 닫기"
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
}}
|
||||
@@ -6666,7 +7007,16 @@ export function ChatConversationView({
|
||||
}
|
||||
|
||||
if (visibleMessages.length === 0) {
|
||||
return <SharedRoomsRequestCard key={entry.groupId} request={representativeRequest} />;
|
||||
return (
|
||||
<SharedRoomsRequestCard
|
||||
key={entry.groupId}
|
||||
request={representativeRequest}
|
||||
onSelect={() => {
|
||||
setSelectedRoomShareGroupId(entry.groupId);
|
||||
scrollToRoomShareGroup(entry.groupId);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user