chore: test deploy snapshot
This commit is contained in:
@@ -5171,9 +5171,11 @@ export function MainChatPanel({
|
|||||||
activePreview,
|
activePreview,
|
||||||
isPreviewLoading,
|
isPreviewLoading,
|
||||||
isPreviewModalOpen,
|
isPreviewModalOpen,
|
||||||
|
previewModalTarget,
|
||||||
previewContentType,
|
previewContentType,
|
||||||
previewError,
|
previewError,
|
||||||
previewText,
|
previewText,
|
||||||
|
handlePreviewModalAfterOpenChange,
|
||||||
setActivePreviewId,
|
setActivePreviewId,
|
||||||
setActivePreviewOverride,
|
setActivePreviewOverride,
|
||||||
setIsPreviewModalOpen,
|
setIsPreviewModalOpen,
|
||||||
@@ -9456,16 +9458,17 @@ export function MainChatPanel({
|
|||||||
</Space>
|
</Space>
|
||||||
</Modal>
|
</Modal>
|
||||||
<FullscreenPreviewModal
|
<FullscreenPreviewModal
|
||||||
open={isPreviewModalOpen && Boolean(activePreview)}
|
open={isPreviewModalOpen}
|
||||||
|
afterOpenChange={handlePreviewModalAfterOpenChange}
|
||||||
title={
|
title={
|
||||||
activePreview
|
previewModalTarget
|
||||||
? `${activePreview.label} · ${activePreview.kind} · ${activePreview.source === 'context' ? '현재 화면' : '채팅 결과'}`
|
? `${previewModalTarget.label} · ${previewModalTarget.kind} · ${previewModalTarget.source === 'context' ? '현재 화면' : '채팅 결과'}`
|
||||||
: 'preview'
|
: 'preview'
|
||||||
}
|
}
|
||||||
meta={null}
|
meta={null}
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
{isActivePreviewHtml ? (
|
{previewModalTarget && isHtmlPreviewItem(previewModalTarget) ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
@@ -9487,7 +9490,7 @@ export function MainChatPanel({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{canSearchActivePreview ? (
|
{previewModalTarget && canSearchActivePreview ? (
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
className="fullscreen-preview-modal__icon-button"
|
className="fullscreen-preview-modal__icon-button"
|
||||||
@@ -9526,7 +9529,7 @@ export function MainChatPanel({
|
|||||||
}}
|
}}
|
||||||
className={`app-chat-panel__preview-modal${isHtmlPreviewFullscreen ? ' app-chat-panel__preview-modal--html-mobile' : ''}`}
|
className={`app-chat-panel__preview-modal${isHtmlPreviewFullscreen ? ' app-chat-panel__preview-modal--html-mobile' : ''}`}
|
||||||
>
|
>
|
||||||
{activePreview ? (
|
{previewModalTarget ? (
|
||||||
<div className="app-chat-panel__preview-modal-body">
|
<div className="app-chat-panel__preview-modal-body">
|
||||||
{canSearchActivePreview && isPreviewFindOpen ? (
|
{canSearchActivePreview && isPreviewFindOpen ? (
|
||||||
<div className="app-chat-panel__preview-modal-findbar">
|
<div className="app-chat-panel__preview-modal-findbar">
|
||||||
@@ -9570,7 +9573,7 @@ export function MainChatPanel({
|
|||||||
onPointerDownCapture={handlePreviewSearchRootPointerDown}
|
onPointerDownCapture={handlePreviewSearchRootPointerDown}
|
||||||
>
|
>
|
||||||
<ChatPreviewBody
|
<ChatPreviewBody
|
||||||
target={activePreview}
|
target={previewModalTarget}
|
||||||
previewText={previewText}
|
previewText={previewText}
|
||||||
isPreviewLoading={isPreviewLoading}
|
isPreviewLoading={isPreviewLoading}
|
||||||
previewError={previewError}
|
previewError={previewError}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export function useConversationViewController({
|
|||||||
const [activePreviewId, setActivePreviewId] = useState<string | null>(null);
|
const [activePreviewId, setActivePreviewId] = useState<string | null>(null);
|
||||||
const [activePreviewOverride, setActivePreviewOverride] = useState<PreviewItem | null>(null);
|
const [activePreviewOverride, setActivePreviewOverride] = useState<PreviewItem | null>(null);
|
||||||
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
|
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
|
||||||
|
const [previewModalTarget, setPreviewModalTarget] = useState<PreviewItem | null>(null);
|
||||||
const [previewText, setPreviewText] = useState('');
|
const [previewText, setPreviewText] = useState('');
|
||||||
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
|
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
|
||||||
const [previewError, setPreviewError] = useState('');
|
const [previewError, setPreviewError] = useState('');
|
||||||
@@ -49,6 +50,14 @@ export function useConversationViewController({
|
|||||||
|
|
||||||
const activePreview = activePreviewOverride ?? previewItems.find((item) => item.id === activePreviewId) ?? previewItems[0] ?? null;
|
const activePreview = activePreviewOverride ?? previewItems.find((item) => item.id === activePreviewId) ?? previewItems[0] ?? null;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPreviewModalOpen || !activePreview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviewModalTarget(activePreview);
|
||||||
|
}, [activePreview, isPreviewModalOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const previousSessionId = previousSessionIdRef.current;
|
const previousSessionId = previousSessionIdRef.current;
|
||||||
const hasSessionChanged = previousSessionId !== activeSessionId;
|
const hasSessionChanged = previousSessionId !== activeSessionId;
|
||||||
@@ -98,7 +107,7 @@ export function useConversationViewController({
|
|||||||
}, [activePreviewId, activePreviewOverride, previewItems]);
|
}, [activePreviewId, activePreviewOverride, previewItems]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPreviewModalOpen || !activePreview) {
|
if (!previewModalTarget) {
|
||||||
setPreviewText('');
|
setPreviewText('');
|
||||||
setPreviewError('');
|
setPreviewError('');
|
||||||
setPreviewContentType('');
|
setPreviewContentType('');
|
||||||
@@ -106,11 +115,15 @@ export function useConversationViewController({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isPreviewModalOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
activePreview.kind === 'image' ||
|
previewModalTarget.kind === 'image' ||
|
||||||
activePreview.kind === 'video' ||
|
previewModalTarget.kind === 'video' ||
|
||||||
activePreview.kind === 'pdf' ||
|
previewModalTarget.kind === 'pdf' ||
|
||||||
activePreview.kind === 'file'
|
previewModalTarget.kind === 'file'
|
||||||
) {
|
) {
|
||||||
setPreviewText('');
|
setPreviewText('');
|
||||||
setPreviewError('');
|
setPreviewError('');
|
||||||
@@ -124,7 +137,7 @@ export function useConversationViewController({
|
|||||||
setPreviewError('');
|
setPreviewError('');
|
||||||
setPreviewContentType('');
|
setPreviewContentType('');
|
||||||
|
|
||||||
fetch(activePreview.url, {
|
fetch(previewModalTarget.url, {
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
})
|
})
|
||||||
@@ -155,7 +168,19 @@ export function useConversationViewController({
|
|||||||
return () => {
|
return () => {
|
||||||
controller.abort();
|
controller.abort();
|
||||||
};
|
};
|
||||||
}, [activePreview, isPreviewModalOpen]);
|
}, [isPreviewModalOpen, previewModalTarget]);
|
||||||
|
|
||||||
|
const handlePreviewModalAfterOpenChange = (open: boolean) => {
|
||||||
|
if (open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviewModalTarget(null);
|
||||||
|
setPreviewText('');
|
||||||
|
setPreviewError('');
|
||||||
|
setPreviewContentType('');
|
||||||
|
setIsPreviewLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const previousActiveView = previousActiveViewRef.current;
|
const previousActiveView = previousActiveViewRef.current;
|
||||||
@@ -216,9 +241,11 @@ export function useConversationViewController({
|
|||||||
activePreviewId,
|
activePreviewId,
|
||||||
isPreviewLoading,
|
isPreviewLoading,
|
||||||
isPreviewModalOpen,
|
isPreviewModalOpen,
|
||||||
|
previewModalTarget,
|
||||||
previewContentType,
|
previewContentType,
|
||||||
previewError,
|
previewError,
|
||||||
previewText,
|
previewText,
|
||||||
|
handlePreviewModalAfterOpenChange,
|
||||||
setActivePreviewId,
|
setActivePreviewId,
|
||||||
setActivePreviewOverride,
|
setActivePreviewOverride,
|
||||||
setIsPreviewModalOpen,
|
setIsPreviewModalOpen,
|
||||||
|
|||||||
@@ -1626,6 +1626,7 @@ async function requestChatApi<T>(
|
|||||||
init?: RequestInit,
|
init?: RequestInit,
|
||||||
options?: {
|
options?: {
|
||||||
allowUnauthenticated?: boolean;
|
allowUnauthenticated?: boolean;
|
||||||
|
signal?: AbortSignal;
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
sharePin?: string | null;
|
sharePin?: string | null;
|
||||||
},
|
},
|
||||||
@@ -1635,9 +1636,18 @@ async function requestChatApi<T>(
|
|||||||
const accessToken = getRegisteredAccessToken();
|
const accessToken = getRegisteredAccessToken();
|
||||||
const method = init?.method?.toUpperCase() ?? 'GET';
|
const method = init?.method?.toUpperCase() ?? 'GET';
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
const externalAbortHandler = () => controller.abort();
|
||||||
const timeoutMs = Number.isFinite(options?.timeoutMs) ? Math.max(1000, Number(options?.timeoutMs)) : 8000;
|
const timeoutMs = Number.isFinite(options?.timeoutMs) ? Math.max(1000, Number(options?.timeoutMs)) : 8000;
|
||||||
const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);
|
const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
|
||||||
|
if (options?.signal) {
|
||||||
|
if (options.signal.aborted) {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
throw new DOMException('Aborted', 'AbortError');
|
||||||
|
}
|
||||||
|
options.signal.addEventListener('abort', externalAbortHandler, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (!allowUnauthenticated && !hasRegisteredAccessTokenAccess()) {
|
if (!allowUnauthenticated && !hasRegisteredAccessTokenAccess()) {
|
||||||
window.clearTimeout(timeoutId);
|
window.clearTimeout(timeoutId);
|
||||||
throw new Error('등록된 접근 토큰이 없어 채팅 요청을 보낼 수 없습니다.');
|
throw new Error('등록된 접근 토큰이 없어 채팅 요청을 보낼 수 없습니다.');
|
||||||
@@ -1681,8 +1691,14 @@ async function requestChatApi<T>(
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.clearTimeout(timeoutId);
|
window.clearTimeout(timeoutId);
|
||||||
|
if (options?.signal) {
|
||||||
|
options.signal.removeEventListener('abort', externalAbortHandler);
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||||
|
if (options?.signal?.aborted) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new Error('채팅 서버 응답이 지연됩니다.');
|
throw new Error('채팅 서버 응답이 지연됩니다.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1690,6 +1706,9 @@ async function requestChatApi<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.clearTimeout(timeoutId);
|
window.clearTimeout(timeoutId);
|
||||||
|
if (options?.signal) {
|
||||||
|
options.signal.removeEventListener('abort', externalAbortHandler);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
|
const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
|
||||||
@@ -2847,6 +2866,7 @@ export async function fetchChatShareSnapshot(
|
|||||||
options?: {
|
options?: {
|
||||||
sharePin?: string | null;
|
sharePin?: string | null;
|
||||||
sessionId?: string | null;
|
sessionId?: string | null;
|
||||||
|
signal?: AbortSignal;
|
||||||
view?: 'full' | 'initial';
|
view?: 'full' | 'initial';
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@@ -2882,6 +2902,7 @@ export async function fetchChatShareSnapshot(
|
|||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
allowUnauthenticated: true,
|
allowUnauthenticated: true,
|
||||||
|
signal: options?.signal,
|
||||||
sharePin: options?.sharePin,
|
sharePin: options?.sharePin,
|
||||||
timeoutMs: 20000,
|
timeoutMs: 20000,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AppstoreOutlined, CheckOutlined, CloseOutlined, CommentOutlined, CopyOutlined, DeleteOutlined, DownOutlined, EditOutlined, EyeInvisibleOutlined, EyeOutlined, FileTextOutlined, FilterOutlined, FullscreenOutlined, LeftOutlined, MinusOutlined, PlusOutlined, ReloadOutlined, RightOutlined, SearchOutlined, SendOutlined, SettingOutlined, ThunderboltOutlined, UpOutlined } from '@ant-design/icons';
|
import { AppstoreOutlined, CheckOutlined, CloseOutlined, CommentOutlined, CopyOutlined, DeleteOutlined, DownOutlined, EditOutlined, EyeInvisibleOutlined, EyeOutlined, FileTextOutlined, FilterOutlined, FullscreenOutlined, LeftOutlined, MinusOutlined, PlusOutlined, ReloadOutlined, RightOutlined, SearchOutlined, SendOutlined, SettingOutlined, ThunderboltOutlined, UpOutlined } from '@ant-design/icons';
|
||||||
import { Alert, App, Button, Checkbox, Drawer, Dropdown, Input, Modal, Select, Spin, Tabs, Tag, Typography, type MenuProps } from 'antd';
|
import { Alert, App, Button, Checkbox, Drawer, Dropdown, Input, Modal, Select, Spin, Tabs, Tag, Typography, type MenuProps } from 'antd';
|
||||||
import type { TextAreaRef } from 'antd/es/input/TextArea';
|
import type { TextAreaRef } from 'antd/es/input/TextArea';
|
||||||
import { Suspense, lazy, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type ClipboardEvent, type CSSProperties, type FocusEvent, type KeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from 'react';
|
import { Suspense, lazy, startTransition, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type ClipboardEvent, type CSSProperties, type FocusEvent, type KeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { FullscreenPreviewModal } from '../../../components/previewer';
|
import { FullscreenPreviewModal } from '../../../components/previewer';
|
||||||
@@ -110,6 +110,8 @@ const SHARE_LAST_ROOM_STORAGE_KEY = 'codex-live-share-last-room-by-token';
|
|||||||
const SHARE_ROOM_SNAPSHOT_SESSION_INDEX_STORAGE_KEY = 'codex-live-share-room-snapshot-index:v1';
|
const SHARE_ROOM_SNAPSHOT_SESSION_INDEX_STORAGE_KEY = 'codex-live-share-room-snapshot-index:v1';
|
||||||
const SHARE_ROOM_SNAPSHOT_SESSION_STORAGE_KEY_PREFIX = 'codex-live-share-room-snapshot:v1';
|
const SHARE_ROOM_SNAPSHOT_SESSION_STORAGE_KEY_PREFIX = 'codex-live-share-room-snapshot:v1';
|
||||||
const SHARE_ROOM_SNAPSHOT_SESSION_CACHE_LIMIT = 6;
|
const SHARE_ROOM_SNAPSHOT_SESSION_CACHE_LIMIT = 6;
|
||||||
|
const SHARE_ROOM_SWITCH_CACHE_REQUEST_LIMIT = 12;
|
||||||
|
const SHARE_ROOM_SWITCH_CACHE_MESSAGE_LIMIT = 32;
|
||||||
const SHARE_IMMEDIATE_SEND_TOGGLE_HOLD_MS = 1000;
|
const SHARE_IMMEDIATE_SEND_TOGGLE_HOLD_MS = 1000;
|
||||||
const SHARE_EDGE_NAVIGATION_HOTZONE_PX = 28;
|
const SHARE_EDGE_NAVIGATION_HOTZONE_PX = 28;
|
||||||
const SHARE_APPS_EDGE_MIDDLE_BAND_RATIO = 0.2;
|
const SHARE_APPS_EDGE_MIDDLE_BAND_RATIO = 0.2;
|
||||||
@@ -140,6 +142,28 @@ type ShareMessageRenderPayload = ShareRenderedMessage & {
|
|||||||
previewItems: PreviewItem[];
|
previewItems: PreviewItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ShareRoomSwitchPerfStep = {
|
||||||
|
name: string;
|
||||||
|
at: number;
|
||||||
|
detailLevel?: ChatShareSnapshot['detailLevel'];
|
||||||
|
requestCount?: number;
|
||||||
|
messageCount?: number;
|
||||||
|
activityLogCount?: number;
|
||||||
|
hasCache?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ShareRoomSwitchPerfRun = {
|
||||||
|
id: string;
|
||||||
|
fromSessionId: string;
|
||||||
|
toSessionId: string;
|
||||||
|
startedAt: number;
|
||||||
|
steps: ShareRoomSwitchPerfStep[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ShareWindowWithPerf = Window & {
|
||||||
|
__chatShareRoomSwitchPerfRuns?: ShareRoomSwitchPerfRun[];
|
||||||
|
};
|
||||||
|
|
||||||
type ShareExpandMode = 'latest' | 'pending' | 'all';
|
type ShareExpandMode = 'latest' | 'pending' | 'all';
|
||||||
type PendingSharePromptSelection = PromptDraftSelection & {
|
type PendingSharePromptSelection = PromptDraftSelection & {
|
||||||
status: 'draft' | 'submitted';
|
status: 'draft' | 'submitted';
|
||||||
@@ -593,7 +617,11 @@ function readStoredShareRoomSnapshot(token: string, sessionId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cachedSessionId = resolveShareSnapshotCacheSessionId(snapshot);
|
const cachedSessionId = resolveShareSnapshotCacheSessionId(snapshot);
|
||||||
return cachedSessionId === normalizedSessionId ? snapshot : null;
|
if (cachedSessionId !== normalizedSessionId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildShareRoomSwitchCacheSnapshot(snapshot);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -643,9 +671,10 @@ function clearStoredShareRoomSnapshotCache(token: string) {
|
|||||||
|
|
||||||
function writeStoredShareRoomSnapshot(token: string, snapshot: ChatShareSnapshot | null | undefined) {
|
function writeStoredShareRoomSnapshot(token: string, snapshot: ChatShareSnapshot | null | undefined) {
|
||||||
const normalizedToken = token.trim();
|
const normalizedToken = token.trim();
|
||||||
const normalizedSessionId = resolveShareSnapshotCacheSessionId(snapshot);
|
const storedSnapshot = buildShareRoomSwitchCacheSnapshot(snapshot ?? null);
|
||||||
|
const normalizedSessionId = resolveShareSnapshotCacheSessionId(storedSnapshot);
|
||||||
|
|
||||||
if (!canUseShareRoomSnapshotSessionStorage() || !normalizedToken || !normalizedSessionId || !snapshot) {
|
if (!canUseShareRoomSnapshotSessionStorage() || !normalizedToken || !normalizedSessionId || !storedSnapshot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +685,7 @@ function writeStoredShareRoomSnapshot(token: string, snapshot: ChatShareSnapshot
|
|||||||
buildShareRoomSnapshotSessionStorageKey(normalizedToken, normalizedSessionId),
|
buildShareRoomSnapshotSessionStorageKey(normalizedToken, normalizedSessionId),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
savedAt: nextSavedAt,
|
savedAt: nextSavedAt,
|
||||||
snapshot,
|
snapshot: storedSnapshot,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const nextIndex = readStoredShareRoomSnapshotSessionIndex();
|
const nextIndex = readStoredShareRoomSnapshotSessionIndex();
|
||||||
@@ -681,6 +710,91 @@ function writeStoredShareRoomSnapshot(token: string, snapshot: ChatShareSnapshot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildShareRoomSwitchCacheSnapshot(snapshot: ChatShareSnapshot | null | undefined) {
|
||||||
|
if (!snapshot) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.detailLevel === 'initial') {
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedRequests = snapshot.requests.slice(-SHARE_ROOM_SWITCH_CACHE_REQUEST_LIMIT);
|
||||||
|
const requestIdSet = new Set(trimmedRequests.map((request) => request.requestId));
|
||||||
|
const trimmedMessages = snapshot.messages
|
||||||
|
.filter((message) => {
|
||||||
|
const requestId = message.requestId?.trim() ?? '';
|
||||||
|
return !requestId || requestIdSet.has(requestId);
|
||||||
|
})
|
||||||
|
.slice(-SHARE_ROOM_SWITCH_CACHE_MESSAGE_LIMIT);
|
||||||
|
const oldestLoadedMessageId =
|
||||||
|
trimmedMessages.length > 0
|
||||||
|
? trimmedMessages[0]?.id ?? snapshot.oldestLoadedMessageId
|
||||||
|
: snapshot.oldestLoadedMessageId;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...snapshot,
|
||||||
|
detailLevel: 'initial',
|
||||||
|
requests: trimmedRequests,
|
||||||
|
messages: trimmedMessages,
|
||||||
|
activityLogs: [],
|
||||||
|
oldestLoadedMessageId,
|
||||||
|
hasOlderMessages: true,
|
||||||
|
} satisfies ChatShareSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startShareRoomSwitchPerfRun(fromSessionId: string, toSessionId: string) {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const perfWindow = window as ShareWindowWithPerf;
|
||||||
|
const nextRun: ShareRoomSwitchPerfRun = {
|
||||||
|
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
|
fromSessionId: fromSessionId.trim(),
|
||||||
|
toSessionId: toSessionId.trim(),
|
||||||
|
startedAt: performance.now(),
|
||||||
|
steps: [],
|
||||||
|
};
|
||||||
|
const nextRuns = [...(perfWindow.__chatShareRoomSwitchPerfRuns ?? []).slice(-9), nextRun];
|
||||||
|
perfWindow.__chatShareRoomSwitchPerfRuns = nextRuns;
|
||||||
|
return nextRun.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordShareRoomSwitchPerfStep(
|
||||||
|
runId: string,
|
||||||
|
name: string,
|
||||||
|
snapshot?: ChatShareSnapshot | null,
|
||||||
|
extra?: Pick<ShareRoomSwitchPerfStep, 'hasCache'>,
|
||||||
|
) {
|
||||||
|
if (typeof window === 'undefined' || !runId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const perfWindow = window as ShareWindowWithPerf;
|
||||||
|
const runs = perfWindow.__chatShareRoomSwitchPerfRuns;
|
||||||
|
|
||||||
|
if (!runs?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetRun = runs.find((run) => run.id === runId);
|
||||||
|
|
||||||
|
if (!targetRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetRun.steps.push({
|
||||||
|
name,
|
||||||
|
at: performance.now(),
|
||||||
|
detailLevel: snapshot?.detailLevel,
|
||||||
|
requestCount: snapshot?.requests.length,
|
||||||
|
messageCount: snapshot?.messages.length,
|
||||||
|
activityLogCount: snapshot?.activityLogs.length,
|
||||||
|
hasCache: extra?.hasCache,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function readShareRoomSessionIdFromLocation() {
|
function readShareRoomSessionIdFromLocation() {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return '';
|
return '';
|
||||||
@@ -4074,6 +4188,7 @@ export function ChatSharePage() {
|
|||||||
typeof window === 'undefined' || !normalizedToken
|
typeof window === 'undefined' || !normalizedToken
|
||||||
? null
|
? null
|
||||||
: readStoredShareRoomSnapshot(normalizedToken, initialRequestedRoomSessionId);
|
: readStoredShareRoomSnapshot(normalizedToken, initialRequestedRoomSessionId);
|
||||||
|
const initialHasCachedSnapshot = initialCachedSnapshot != null;
|
||||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const pageRef = useRef<HTMLDivElement | null>(null);
|
const pageRef = useRef<HTMLDivElement | null>(null);
|
||||||
const requestAnchorRefs = useRef(new Map<string, HTMLElement>());
|
const requestAnchorRefs = useRef(new Map<string, HTMLElement>());
|
||||||
@@ -4083,6 +4198,11 @@ export function ChatSharePage() {
|
|||||||
const deferredSnapshotRef = useRef<ChatShareSnapshot | null>(null);
|
const deferredSnapshotRef = useRef<ChatShareSnapshot | null>(null);
|
||||||
const liveRefreshTimerRef = useRef<number | null>(null);
|
const liveRefreshTimerRef = useRef<number | null>(null);
|
||||||
const snapshotRefreshPromiseRef = useRef<Promise<boolean> | null>(null);
|
const snapshotRefreshPromiseRef = useRef<Promise<boolean> | null>(null);
|
||||||
|
const snapshotRefreshAbortControllerRef = useRef<AbortController | null>(null);
|
||||||
|
const snapshotRefreshInFlightRoomSessionIdRef = useRef('');
|
||||||
|
const snapshotRefreshInFlightViewRef = useRef<'initial' | 'full' | null>(null);
|
||||||
|
const roomSwitchPerfRunIdRef = useRef('');
|
||||||
|
const pendingSnapshotPerfStageRef = useRef<{ runId: string; stage: 'cache' | 'initial' | 'full' } | null>(null);
|
||||||
const pendingSilentRefreshRef = useRef(false);
|
const pendingSilentRefreshRef = useRef(false);
|
||||||
const scrollSyncFrameRef = useRef<number | null>(null);
|
const scrollSyncFrameRef = useRef<number | null>(null);
|
||||||
const scrollIdleTimerRef = useRef<number | null>(null);
|
const scrollIdleTimerRef = useRef<number | null>(null);
|
||||||
@@ -4232,6 +4352,21 @@ export function ChatSharePage() {
|
|||||||
const isRoomSwitchingRef = useRef(isRoomSwitching);
|
const isRoomSwitchingRef = useRef(isRoomSwitching);
|
||||||
isRoomSwitchingRef.current = isRoomSwitching;
|
isRoomSwitchingRef.current = isRoomSwitching;
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const pendingStage = pendingSnapshotPerfStageRef.current;
|
||||||
|
|
||||||
|
if (!pendingStage || !snapshot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingSnapshotPerfStageRef.current = null;
|
||||||
|
recordShareRoomSwitchPerfStep(pendingStage.runId, `${pendingStage.stage}-snapshot-commit`, snapshot);
|
||||||
|
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
recordShareRoomSwitchPerfStep(pendingStage.runId, `${pendingStage.stage}-first-frame`, snapshot);
|
||||||
|
});
|
||||||
|
}, [snapshot]);
|
||||||
|
|
||||||
const shareTokenSetting = snapshot?.share.tokenSetting ?? null;
|
const shareTokenSetting = snapshot?.share.tokenSetting ?? null;
|
||||||
const shareAllowedAppIdSet = useMemo(
|
const shareAllowedAppIdSet = useMemo(
|
||||||
() => buildShareAllowedAppIdSet(shareTokenSetting?.allowedAppIds, snapshot?.share.permissions),
|
() => buildShareAllowedAppIdSet(shareTokenSetting?.allowedAppIds, snapshot?.share.permissions),
|
||||||
@@ -4858,35 +4993,65 @@ export function ChatSharePage() {
|
|||||||
|
|
||||||
void refreshShareRuntime();
|
void refreshShareRuntime();
|
||||||
}, [isRoomSettingsOpen, refreshShareRuntime, roomSettingsTabKey]);
|
}, [isRoomSettingsOpen, refreshShareRuntime, roomSettingsTabKey]);
|
||||||
const refreshSnapshot = useCallback(async (options?: { initialLoad?: boolean; silent?: boolean; sharePin?: string | null }) => {
|
const refreshSnapshot = useCallback(async (options?: {
|
||||||
|
initialLoad?: boolean;
|
||||||
|
silent?: boolean;
|
||||||
|
sharePin?: string | null;
|
||||||
|
view?: 'initial' | 'full';
|
||||||
|
preserveVisibleSnapshot?: boolean;
|
||||||
|
}) => {
|
||||||
if (!normalizedToken) {
|
if (!normalizedToken) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialLoad = options?.initialLoad === true;
|
const initialLoad = options?.initialLoad === true;
|
||||||
const silent = options?.silent === true;
|
const silent = options?.silent === true;
|
||||||
|
const requestView = options?.view ?? (initialLoad ? 'initial' : 'full');
|
||||||
|
const preserveVisibleSnapshot = options?.preserveVisibleSnapshot === true;
|
||||||
|
const currentPerfRunId = roomSwitchPerfRunIdRef.current;
|
||||||
|
const requestedSessionId = requestedRoomSessionIdRef.current.trim();
|
||||||
|
|
||||||
if (snapshotRefreshPromiseRef.current) {
|
if (snapshotRefreshPromiseRef.current) {
|
||||||
|
const inFlightRoomSessionId = snapshotRefreshInFlightRoomSessionIdRef.current;
|
||||||
|
const inFlightView = snapshotRefreshInFlightViewRef.current;
|
||||||
|
const shouldReplaceInFlightRequest =
|
||||||
|
requestView === 'initial'
|
||||||
|
|| requestedSessionId !== inFlightRoomSessionId
|
||||||
|
|| inFlightView !== requestView;
|
||||||
|
|
||||||
|
if (shouldReplaceInFlightRequest) {
|
||||||
|
snapshotRefreshAbortControllerRef.current?.abort();
|
||||||
|
} else {
|
||||||
if (!initialLoad) {
|
if (!initialLoad) {
|
||||||
pendingSilentRefreshRef.current = pendingSilentRefreshRef.current || silent;
|
pendingSilentRefreshRef.current = pendingSilentRefreshRef.current || silent;
|
||||||
}
|
}
|
||||||
return snapshotRefreshPromiseRef.current;
|
return snapshotRefreshPromiseRef.current;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
snapshotRefreshAbortControllerRef.current = abortController;
|
||||||
|
snapshotRefreshInFlightRoomSessionIdRef.current = requestedSessionId;
|
||||||
|
snapshotRefreshInFlightViewRef.current = requestView;
|
||||||
|
|
||||||
if (options?.initialLoad) {
|
if (options?.initialLoad) {
|
||||||
|
if (!(preserveVisibleSnapshot && hasSnapshotRef.current)) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
} else {
|
}
|
||||||
|
} else if (!silent) {
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshTask = (async () => {
|
const refreshTaskPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
|
recordShareRoomSwitchPerfStep(currentPerfRunId, `${requestView}-fetch-start`);
|
||||||
const nextSnapshot = await fetchChatShareSnapshot(normalizedToken, {
|
const nextSnapshot = await fetchChatShareSnapshot(normalizedToken, {
|
||||||
sharePin: options?.sharePin,
|
sharePin: options?.sharePin,
|
||||||
sessionId: requestedRoomSessionIdRef.current || undefined,
|
sessionId: requestedSessionId || undefined,
|
||||||
view: initialLoad ? 'initial' : 'full',
|
signal: abortController.signal,
|
||||||
|
view: requestView,
|
||||||
});
|
});
|
||||||
const requestedSessionId = requestedRoomSessionIdRef.current.trim();
|
recordShareRoomSwitchPerfStep(currentPerfRunId, `${requestView}-fetch-end`, nextSnapshot);
|
||||||
const matchedRequestedRoom = doesShareSnapshotMatchRequestedRoom(nextSnapshot, requestedSessionId);
|
const matchedRequestedRoom = doesShareSnapshotMatchRequestedRoom(nextSnapshot, requestedSessionId);
|
||||||
|
|
||||||
const shouldApplyImmediately =
|
const shouldApplyImmediately =
|
||||||
@@ -4899,12 +5064,19 @@ export function ChatSharePage() {
|
|||||||
if (!shouldApplyImmediately) {
|
if (!shouldApplyImmediately) {
|
||||||
deferredSnapshotRef.current = nextSnapshot;
|
deferredSnapshotRef.current = nextSnapshot;
|
||||||
} else {
|
} else {
|
||||||
|
pendingSnapshotPerfStageRef.current = currentPerfRunId
|
||||||
|
? { runId: currentPerfRunId, stage: requestView }
|
||||||
|
: null;
|
||||||
|
if (!initialLoad && requestView === 'full' && hasSnapshotRef.current) {
|
||||||
|
startTransition(() => {
|
||||||
setSnapshot(nextSnapshot);
|
setSnapshot(nextSnapshot);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setSnapshot(nextSnapshot);
|
||||||
|
}
|
||||||
deferredSnapshotRef.current = null;
|
deferredSnapshotRef.current = null;
|
||||||
}
|
}
|
||||||
if (nextSnapshot.detailLevel !== 'initial') {
|
|
||||||
writeStoredShareRoomSnapshot(normalizedToken, nextSnapshot);
|
writeStoredShareRoomSnapshot(normalizedToken, nextSnapshot);
|
||||||
}
|
|
||||||
if (nextSnapshot.share.hasAccessPin) {
|
if (nextSnapshot.share.hasAccessPin) {
|
||||||
const resolvedPin = normalizeAccessPinInput(options?.sharePin ?? '');
|
const resolvedPin = normalizeAccessPinInput(options?.sharePin ?? '');
|
||||||
const persistedPin = resolvedPin || getStoredChatShareAccessPin(normalizedToken);
|
const persistedPin = resolvedPin || getStoredChatShareAccessPin(normalizedToken);
|
||||||
@@ -4919,6 +5091,7 @@ export function ChatSharePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (matchedRequestedRoom) {
|
if (matchedRequestedRoom) {
|
||||||
|
recordShareRoomSwitchPerfStep(currentPerfRunId, `${requestView}-matched-requested-room`, nextSnapshot);
|
||||||
setIsRoomSwitching(false);
|
setIsRoomSwitching(false);
|
||||||
} else if (requestedSessionId) {
|
} else if (requestedSessionId) {
|
||||||
pendingSilentRefreshRef.current = true;
|
pendingSilentRefreshRef.current = true;
|
||||||
@@ -4930,6 +5103,10 @@ export function ChatSharePage() {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof ChatApiError && error.status === 401 && (error.code === 'share_pin_required' || error.code === 'share_pin_invalid')) {
|
if (error instanceof ChatApiError && error.status === 401 && (error.code === 'share_pin_required' || error.code === 'share_pin_invalid')) {
|
||||||
setStoredChatShareAccessPin(normalizedToken, null);
|
setStoredChatShareAccessPin(normalizedToken, null);
|
||||||
clearStoredShareRoomSnapshotCache(normalizedToken);
|
clearStoredShareRoomSnapshotCache(normalizedToken);
|
||||||
@@ -4950,7 +5127,16 @@ export function ChatSharePage() {
|
|||||||
setIsRoomSwitching(false);
|
setIsRoomSwitching(false);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
|
if (snapshotRefreshAbortControllerRef.current === abortController) {
|
||||||
|
snapshotRefreshAbortControllerRef.current = null;
|
||||||
|
}
|
||||||
|
if (snapshotRefreshPromiseRef.current === refreshTaskPromise) {
|
||||||
snapshotRefreshPromiseRef.current = null;
|
snapshotRefreshPromiseRef.current = null;
|
||||||
|
}
|
||||||
|
if (snapshotRefreshInFlightRoomSessionIdRef.current === requestedSessionId) {
|
||||||
|
snapshotRefreshInFlightRoomSessionIdRef.current = '';
|
||||||
|
snapshotRefreshInFlightViewRef.current = null;
|
||||||
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setIsRefreshing(false);
|
setIsRefreshing(false);
|
||||||
|
|
||||||
@@ -4963,9 +5149,40 @@ export function ChatSharePage() {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
snapshotRefreshPromiseRef.current = refreshTask;
|
snapshotRefreshPromiseRef.current = refreshTaskPromise;
|
||||||
return refreshTask;
|
return refreshTaskPromise;
|
||||||
}, [normalizedToken]);
|
}, [normalizedToken]);
|
||||||
|
const ensureLatestShareRoomDetailLoaded = useCallback(
|
||||||
|
async (requestId?: string | null) => {
|
||||||
|
const normalizedRequestId = requestId?.trim() ?? '';
|
||||||
|
|
||||||
|
if (!normalizedToken || !snapshot?.conversation.sessionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
normalizedRequestId
|
||||||
|
&& snapshot.activityLogs.some((item) => item.requestId.trim() === normalizedRequestId)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoadingFullSnapshot(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const detailPage = await fetchChatShareConversationDetail(normalizedToken, {
|
||||||
|
sessionId: snapshot.conversation.sessionId,
|
||||||
|
limit: Math.max(SHARE_ROOM_SWITCH_CACHE_REQUEST_LIMIT, snapshot.requests.length || 0),
|
||||||
|
sharePin: getStoredChatShareAccessPin(normalizedToken) || undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSnapshot((current) => (current ? mergeChatShareSnapshotHistory(current, detailPage) : current));
|
||||||
|
} finally {
|
||||||
|
setIsLoadingFullSnapshot(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[normalizedToken, snapshot],
|
||||||
|
);
|
||||||
const handleLoadOlderShareHistory = useCallback(async () => {
|
const handleLoadOlderShareHistory = useCallback(async () => {
|
||||||
if (!normalizedToken || !snapshot?.conversation.sessionId || !snapshot.hasOlderMessages || !snapshot.oldestLoadedMessageId) {
|
if (!normalizedToken || !snapshot?.conversation.sessionId || !snapshot.hasOlderMessages || !snapshot.oldestLoadedMessageId) {
|
||||||
return;
|
return;
|
||||||
@@ -5676,15 +5893,27 @@ export function ChatSharePage() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const openProcessInspector = useCallback((requestId: string) => {
|
const openProcessInspector = useCallback(async (requestId: string) => {
|
||||||
if (!requestId.trim()) {
|
const normalizedRequestId = requestId.trim();
|
||||||
|
|
||||||
|
if (!normalizedRequestId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveProcessInspectorRequestId(requestId);
|
const hasActivityLog = snapshot?.activityLogs.some((item) => item.requestId.trim() === normalizedRequestId) ?? false;
|
||||||
|
|
||||||
|
if (!hasActivityLog) {
|
||||||
|
try {
|
||||||
|
await ensureLatestShareRoomDetailLoaded(normalizedRequestId);
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error instanceof Error ? error.message : '상세 활동 로그를 불러오지 못했습니다.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveProcessInspectorRequestId(normalizedRequestId);
|
||||||
setProcessInspectorMode('default');
|
setProcessInspectorMode('default');
|
||||||
setProcessInspectorExpandedSection('checklist');
|
setProcessInspectorExpandedSection('checklist');
|
||||||
}, []);
|
}, [ensureLatestShareRoomDetailLoaded, message, snapshot?.activityLogs]);
|
||||||
|
|
||||||
const closeProcessInspector = useCallback(() => {
|
const closeProcessInspector = useCallback(() => {
|
||||||
setActiveProcessInspectorRequestId('');
|
setActiveProcessInspectorRequestId('');
|
||||||
@@ -6083,9 +6312,13 @@ export function ChatSharePage() {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshSnapshot({ initialLoad: true });
|
void refreshSnapshot({
|
||||||
|
initialLoad: true,
|
||||||
|
view: 'initial',
|
||||||
|
preserveVisibleSnapshot: initialHasCachedSnapshot,
|
||||||
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [normalizedToken, refreshSnapshot]);
|
}, [initialHasCachedSnapshot, normalizedToken, refreshSnapshot]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return;
|
return;
|
||||||
@@ -6094,7 +6327,6 @@ export function ChatSharePage() {
|
|||||||
const urlRoomSessionId = readShareRoomSessionIdFromLocation();
|
const urlRoomSessionId = readShareRoomSessionIdFromLocation();
|
||||||
const restoredRoomSessionId = urlRoomSessionId || readStoredShareLastRoomSessionId(normalizedToken);
|
const restoredRoomSessionId = urlRoomSessionId || readStoredShareLastRoomSessionId(normalizedToken);
|
||||||
const cachedSnapshot = readStoredShareRoomSnapshot(normalizedToken, restoredRoomSessionId);
|
const cachedSnapshot = readStoredShareRoomSnapshot(normalizedToken, restoredRoomSessionId);
|
||||||
|
|
||||||
requestedRoomSessionIdRef.current = restoredRoomSessionId;
|
requestedRoomSessionIdRef.current = restoredRoomSessionId;
|
||||||
deferredSnapshotRef.current = null;
|
deferredSnapshotRef.current = null;
|
||||||
setSnapshot(cachedSnapshot);
|
setSnapshot(cachedSnapshot);
|
||||||
@@ -6116,7 +6348,7 @@ export function ChatSharePage() {
|
|||||||
if (cachedSnapshot) {
|
if (cachedSnapshot) {
|
||||||
setSnapshot(cachedSnapshot);
|
setSnapshot(cachedSnapshot);
|
||||||
}
|
}
|
||||||
setIsRoomSwitching(Boolean(nextRoomSessionId));
|
setIsRoomSwitching(Boolean(nextRoomSessionId) && !cachedSnapshot);
|
||||||
setRequestedRoomSessionId(nextRoomSessionId);
|
setRequestedRoomSessionId(nextRoomSessionId);
|
||||||
setIsShareRoomListVisible(false);
|
setIsShareRoomListVisible(false);
|
||||||
};
|
};
|
||||||
@@ -6137,8 +6369,15 @@ export function ChatSharePage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshSnapshot({ silent: true });
|
const requestedSessionId = requestedRoomSessionId.trim();
|
||||||
}, [normalizedToken, refreshSnapshot, requestedRoomSessionId]);
|
const hasMatchingSnapshot = doesShareSnapshotMatchRequestedRoom(snapshot, requestedSessionId);
|
||||||
|
|
||||||
|
if (hasMatchingSnapshot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshSnapshot({ silent: true, view: 'initial' });
|
||||||
|
}, [normalizedToken, refreshSnapshot, requestedRoomSessionId, snapshot]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!normalizedToken || !snapshot || snapshot.detailLevel === 'initial') {
|
if (!normalizedToken || !snapshot || snapshot.detailLevel === 'initial') {
|
||||||
return;
|
return;
|
||||||
@@ -6688,9 +6927,13 @@ export function ChatSharePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cachedSnapshot = readStoredShareRoomSnapshot(normalizedToken, normalizedSessionId);
|
const cachedSnapshot = readStoredShareRoomSnapshot(normalizedToken, normalizedSessionId);
|
||||||
|
const perfRunId = startShareRoomSwitchPerfRun(selectedShareRoomSessionId, normalizedSessionId);
|
||||||
|
|
||||||
deferredSnapshotRef.current = null;
|
deferredSnapshotRef.current = null;
|
||||||
|
roomSwitchPerfRunIdRef.current = perfRunId;
|
||||||
|
recordShareRoomSwitchPerfStep(perfRunId, 'room-select', undefined, { hasCache: Boolean(cachedSnapshot) });
|
||||||
if (cachedSnapshot) {
|
if (cachedSnapshot) {
|
||||||
|
pendingSnapshotPerfStageRef.current = { runId: perfRunId, stage: 'cache' };
|
||||||
setSnapshot(cachedSnapshot);
|
setSnapshot(cachedSnapshot);
|
||||||
}
|
}
|
||||||
setIsRoomSwitching(!cachedSnapshot);
|
setIsRoomSwitching(!cachedSnapshot);
|
||||||
@@ -8366,7 +8609,11 @@ export function ChatSharePage() {
|
|||||||
const normalizedRoomSessionId = restoreSnapshot.roomSessionId.trim();
|
const normalizedRoomSessionId = restoreSnapshot.roomSessionId.trim();
|
||||||
|
|
||||||
if (normalizedRoomSessionId && normalizedRoomSessionId !== selectedShareRoomSessionId) {
|
if (normalizedRoomSessionId && normalizedRoomSessionId !== selectedShareRoomSessionId) {
|
||||||
setIsRoomSwitching(true);
|
const cachedSnapshot = readStoredShareRoomSnapshot(normalizedToken, normalizedRoomSessionId);
|
||||||
|
if (cachedSnapshot) {
|
||||||
|
setSnapshot(cachedSnapshot);
|
||||||
|
}
|
||||||
|
setIsRoomSwitching(!cachedSnapshot);
|
||||||
requestedRoomSessionIdRef.current = normalizedRoomSessionId;
|
requestedRoomSessionIdRef.current = normalizedRoomSessionId;
|
||||||
writeStoredShareLastRoomSessionId(normalizedToken, normalizedRoomSessionId);
|
writeStoredShareLastRoomSessionId(normalizedToken, normalizedRoomSessionId);
|
||||||
writeShareRoomSessionIdToLocation(normalizedRoomSessionId, 'replace');
|
writeShareRoomSessionIdToLocation(normalizedRoomSessionId, 'replace');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import './FullscreenPreviewModal.css';
|
|||||||
|
|
||||||
type FullscreenPreviewModalProps = {
|
type FullscreenPreviewModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
afterOpenChange?: (open: boolean) => void;
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
meta?: ReactNode;
|
meta?: ReactNode;
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
@@ -25,6 +26,7 @@ type FullscreenPreviewModalProps = {
|
|||||||
|
|
||||||
export function FullscreenPreviewModal({
|
export function FullscreenPreviewModal({
|
||||||
open,
|
open,
|
||||||
|
afterOpenChange,
|
||||||
title,
|
title,
|
||||||
meta,
|
meta,
|
||||||
actions,
|
actions,
|
||||||
@@ -45,6 +47,7 @@ export function FullscreenPreviewModal({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
|
afterOpenChange={afterOpenChange}
|
||||||
footer={null}
|
footer={null}
|
||||||
title={null}
|
title={null}
|
||||||
width="100vw"
|
width="100vw"
|
||||||
|
|||||||
@@ -2056,11 +2056,6 @@ export function BaseballTicketBayPlayAppView({ onBack, launchContext = 'direct',
|
|||||||
<span>{alerts.length}건</span>
|
<span>{alerts.length}건</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isViewingAllClients || isSharedTokenScope ? (
|
|
||||||
<div className="baseball-ticket-bay-app__scope-note">
|
|
||||||
{isSharedTokenScope ? '현재 공유 리소스 별명 기준으로 목록을 표시합니다.' : '등록 토큰으로 전체 기기 목록을 보고 있습니다. 리소스 별명으로 확인하고 수정과 삭제도 바로 할 수 있습니다.'}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="baseball-ticket-bay-app__items">
|
<div className="baseball-ticket-bay-app__items">
|
||||||
{alerts.length ? (
|
{alerts.length ? (
|
||||||
alerts.map((item) => {
|
alerts.map((item) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user