feat: refresh shared chat and server workflows

This commit is contained in:
2026-05-26 12:26:33 +09:00
parent 51e0099bea
commit c1d0f4c1db
82 changed files with 18604 additions and 12461 deletions

View File

@@ -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 (