|
|
|
|
@@ -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 { 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 { 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 { useParams } from 'react-router-dom';
|
|
|
|
|
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_STORAGE_KEY_PREFIX = 'codex-live-share-room-snapshot:v1';
|
|
|
|
|
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_EDGE_NAVIGATION_HOTZONE_PX = 28;
|
|
|
|
|
const SHARE_APPS_EDGE_MIDDLE_BAND_RATIO = 0.2;
|
|
|
|
|
@@ -140,6 +142,28 @@ type ShareMessageRenderPayload = ShareRenderedMessage & {
|
|
|
|
|
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 PendingSharePromptSelection = PromptDraftSelection & {
|
|
|
|
|
status: 'draft' | 'submitted';
|
|
|
|
|
@@ -593,7 +617,11 @@ function readStoredShareRoomSnapshot(token: string, sessionId: string) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cachedSessionId = resolveShareSnapshotCacheSessionId(snapshot);
|
|
|
|
|
return cachedSessionId === normalizedSessionId ? snapshot : null;
|
|
|
|
|
if (cachedSessionId !== normalizedSessionId) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buildShareRoomSwitchCacheSnapshot(snapshot);
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
@@ -643,9 +671,10 @@ function clearStoredShareRoomSnapshotCache(token: string) {
|
|
|
|
|
|
|
|
|
|
function writeStoredShareRoomSnapshot(token: string, snapshot: ChatShareSnapshot | null | undefined) {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -656,7 +685,7 @@ function writeStoredShareRoomSnapshot(token: string, snapshot: ChatShareSnapshot
|
|
|
|
|
buildShareRoomSnapshotSessionStorageKey(normalizedToken, normalizedSessionId),
|
|
|
|
|
JSON.stringify({
|
|
|
|
|
savedAt: nextSavedAt,
|
|
|
|
|
snapshot,
|
|
|
|
|
snapshot: storedSnapshot,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
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() {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return '';
|
|
|
|
|
@@ -4074,6 +4188,7 @@ export function ChatSharePage() {
|
|
|
|
|
typeof window === 'undefined' || !normalizedToken
|
|
|
|
|
? null
|
|
|
|
|
: readStoredShareRoomSnapshot(normalizedToken, initialRequestedRoomSessionId);
|
|
|
|
|
const initialHasCachedSnapshot = initialCachedSnapshot != null;
|
|
|
|
|
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
const pageRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
const requestAnchorRefs = useRef(new Map<string, HTMLElement>());
|
|
|
|
|
@@ -4083,6 +4198,11 @@ export function ChatSharePage() {
|
|
|
|
|
const deferredSnapshotRef = useRef<ChatShareSnapshot | null>(null);
|
|
|
|
|
const liveRefreshTimerRef = useRef<number | 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 scrollSyncFrameRef = useRef<number | null>(null);
|
|
|
|
|
const scrollIdleTimerRef = useRef<number | null>(null);
|
|
|
|
|
@@ -4232,6 +4352,21 @@ export function ChatSharePage() {
|
|
|
|
|
const isRoomSwitchingRef = useRef(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 shareAllowedAppIdSet = useMemo(
|
|
|
|
|
() => buildShareAllowedAppIdSet(shareTokenSetting?.allowedAppIds, snapshot?.share.permissions),
|
|
|
|
|
@@ -4858,35 +4993,65 @@ export function ChatSharePage() {
|
|
|
|
|
|
|
|
|
|
void refreshShareRuntime();
|
|
|
|
|
}, [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) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const initialLoad = options?.initialLoad === 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 (!initialLoad) {
|
|
|
|
|
pendingSilentRefreshRef.current = pendingSilentRefreshRef.current || silent;
|
|
|
|
|
const inFlightRoomSessionId = snapshotRefreshInFlightRoomSessionIdRef.current;
|
|
|
|
|
const inFlightView = snapshotRefreshInFlightViewRef.current;
|
|
|
|
|
const shouldReplaceInFlightRequest =
|
|
|
|
|
requestView === 'initial'
|
|
|
|
|
|| requestedSessionId !== inFlightRoomSessionId
|
|
|
|
|
|| inFlightView !== requestView;
|
|
|
|
|
|
|
|
|
|
if (shouldReplaceInFlightRequest) {
|
|
|
|
|
snapshotRefreshAbortControllerRef.current?.abort();
|
|
|
|
|
} else {
|
|
|
|
|
if (!initialLoad) {
|
|
|
|
|
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) {
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
} else {
|
|
|
|
|
if (!(preserveVisibleSnapshot && hasSnapshotRef.current)) {
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
}
|
|
|
|
|
} else if (!silent) {
|
|
|
|
|
setIsRefreshing(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshTask = (async () => {
|
|
|
|
|
const refreshTaskPromise = (async () => {
|
|
|
|
|
try {
|
|
|
|
|
recordShareRoomSwitchPerfStep(currentPerfRunId, `${requestView}-fetch-start`);
|
|
|
|
|
const nextSnapshot = await fetchChatShareSnapshot(normalizedToken, {
|
|
|
|
|
sharePin: options?.sharePin,
|
|
|
|
|
sessionId: requestedRoomSessionIdRef.current || undefined,
|
|
|
|
|
view: initialLoad ? 'initial' : 'full',
|
|
|
|
|
sessionId: requestedSessionId || undefined,
|
|
|
|
|
signal: abortController.signal,
|
|
|
|
|
view: requestView,
|
|
|
|
|
});
|
|
|
|
|
const requestedSessionId = requestedRoomSessionIdRef.current.trim();
|
|
|
|
|
recordShareRoomSwitchPerfStep(currentPerfRunId, `${requestView}-fetch-end`, nextSnapshot);
|
|
|
|
|
const matchedRequestedRoom = doesShareSnapshotMatchRequestedRoom(nextSnapshot, requestedSessionId);
|
|
|
|
|
|
|
|
|
|
const shouldApplyImmediately =
|
|
|
|
|
@@ -4899,12 +5064,19 @@ export function ChatSharePage() {
|
|
|
|
|
if (!shouldApplyImmediately) {
|
|
|
|
|
deferredSnapshotRef.current = nextSnapshot;
|
|
|
|
|
} else {
|
|
|
|
|
setSnapshot(nextSnapshot);
|
|
|
|
|
pendingSnapshotPerfStageRef.current = currentPerfRunId
|
|
|
|
|
? { runId: currentPerfRunId, stage: requestView }
|
|
|
|
|
: null;
|
|
|
|
|
if (!initialLoad && requestView === 'full' && hasSnapshotRef.current) {
|
|
|
|
|
startTransition(() => {
|
|
|
|
|
setSnapshot(nextSnapshot);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
setSnapshot(nextSnapshot);
|
|
|
|
|
}
|
|
|
|
|
deferredSnapshotRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
if (nextSnapshot.detailLevel !== 'initial') {
|
|
|
|
|
writeStoredShareRoomSnapshot(normalizedToken, nextSnapshot);
|
|
|
|
|
}
|
|
|
|
|
writeStoredShareRoomSnapshot(normalizedToken, nextSnapshot);
|
|
|
|
|
if (nextSnapshot.share.hasAccessPin) {
|
|
|
|
|
const resolvedPin = normalizeAccessPinInput(options?.sharePin ?? '');
|
|
|
|
|
const persistedPin = resolvedPin || getStoredChatShareAccessPin(normalizedToken);
|
|
|
|
|
@@ -4919,6 +5091,7 @@ export function ChatSharePage() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (matchedRequestedRoom) {
|
|
|
|
|
recordShareRoomSwitchPerfStep(currentPerfRunId, `${requestView}-matched-requested-room`, nextSnapshot);
|
|
|
|
|
setIsRoomSwitching(false);
|
|
|
|
|
} else if (requestedSessionId) {
|
|
|
|
|
pendingSilentRefreshRef.current = true;
|
|
|
|
|
@@ -4930,6 +5103,10 @@ export function ChatSharePage() {
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
} 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')) {
|
|
|
|
|
setStoredChatShareAccessPin(normalizedToken, null);
|
|
|
|
|
clearStoredShareRoomSnapshotCache(normalizedToken);
|
|
|
|
|
@@ -4950,7 +5127,16 @@ export function ChatSharePage() {
|
|
|
|
|
setIsRoomSwitching(false);
|
|
|
|
|
return false;
|
|
|
|
|
} finally {
|
|
|
|
|
snapshotRefreshPromiseRef.current = null;
|
|
|
|
|
if (snapshotRefreshAbortControllerRef.current === abortController) {
|
|
|
|
|
snapshotRefreshAbortControllerRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
if (snapshotRefreshPromiseRef.current === refreshTaskPromise) {
|
|
|
|
|
snapshotRefreshPromiseRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
if (snapshotRefreshInFlightRoomSessionIdRef.current === requestedSessionId) {
|
|
|
|
|
snapshotRefreshInFlightRoomSessionIdRef.current = '';
|
|
|
|
|
snapshotRefreshInFlightViewRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
setIsRefreshing(false);
|
|
|
|
|
|
|
|
|
|
@@ -4963,9 +5149,40 @@ export function ChatSharePage() {
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
snapshotRefreshPromiseRef.current = refreshTask;
|
|
|
|
|
return refreshTask;
|
|
|
|
|
snapshotRefreshPromiseRef.current = refreshTaskPromise;
|
|
|
|
|
return refreshTaskPromise;
|
|
|
|
|
}, [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 () => {
|
|
|
|
|
if (!normalizedToken || !snapshot?.conversation.sessionId || !snapshot.hasOlderMessages || !snapshot.oldestLoadedMessageId) {
|
|
|
|
|
return;
|
|
|
|
|
@@ -5676,15 +5893,27 @@ export function ChatSharePage() {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const openProcessInspector = useCallback((requestId: string) => {
|
|
|
|
|
if (!requestId.trim()) {
|
|
|
|
|
const openProcessInspector = useCallback(async (requestId: string) => {
|
|
|
|
|
const normalizedRequestId = requestId.trim();
|
|
|
|
|
|
|
|
|
|
if (!normalizedRequestId) {
|
|
|
|
|
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');
|
|
|
|
|
setProcessInspectorExpandedSection('checklist');
|
|
|
|
|
}, []);
|
|
|
|
|
}, [ensureLatestShareRoomDetailLoaded, message, snapshot?.activityLogs]);
|
|
|
|
|
|
|
|
|
|
const closeProcessInspector = useCallback(() => {
|
|
|
|
|
setActiveProcessInspectorRequestId('');
|
|
|
|
|
@@ -6083,9 +6312,13 @@ export function ChatSharePage() {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void refreshSnapshot({ initialLoad: true });
|
|
|
|
|
void refreshSnapshot({
|
|
|
|
|
initialLoad: true,
|
|
|
|
|
view: 'initial',
|
|
|
|
|
preserveVisibleSnapshot: initialHasCachedSnapshot,
|
|
|
|
|
});
|
|
|
|
|
return undefined;
|
|
|
|
|
}, [normalizedToken, refreshSnapshot]);
|
|
|
|
|
}, [initialHasCachedSnapshot, normalizedToken, refreshSnapshot]);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return;
|
|
|
|
|
@@ -6094,7 +6327,6 @@ export function ChatSharePage() {
|
|
|
|
|
const urlRoomSessionId = readShareRoomSessionIdFromLocation();
|
|
|
|
|
const restoredRoomSessionId = urlRoomSessionId || readStoredShareLastRoomSessionId(normalizedToken);
|
|
|
|
|
const cachedSnapshot = readStoredShareRoomSnapshot(normalizedToken, restoredRoomSessionId);
|
|
|
|
|
|
|
|
|
|
requestedRoomSessionIdRef.current = restoredRoomSessionId;
|
|
|
|
|
deferredSnapshotRef.current = null;
|
|
|
|
|
setSnapshot(cachedSnapshot);
|
|
|
|
|
@@ -6116,7 +6348,7 @@ export function ChatSharePage() {
|
|
|
|
|
if (cachedSnapshot) {
|
|
|
|
|
setSnapshot(cachedSnapshot);
|
|
|
|
|
}
|
|
|
|
|
setIsRoomSwitching(Boolean(nextRoomSessionId));
|
|
|
|
|
setIsRoomSwitching(Boolean(nextRoomSessionId) && !cachedSnapshot);
|
|
|
|
|
setRequestedRoomSessionId(nextRoomSessionId);
|
|
|
|
|
setIsShareRoomListVisible(false);
|
|
|
|
|
};
|
|
|
|
|
@@ -6137,8 +6369,15 @@ export function ChatSharePage() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void refreshSnapshot({ silent: true });
|
|
|
|
|
}, [normalizedToken, refreshSnapshot, requestedRoomSessionId]);
|
|
|
|
|
const requestedSessionId = requestedRoomSessionId.trim();
|
|
|
|
|
const hasMatchingSnapshot = doesShareSnapshotMatchRequestedRoom(snapshot, requestedSessionId);
|
|
|
|
|
|
|
|
|
|
if (hasMatchingSnapshot) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void refreshSnapshot({ silent: true, view: 'initial' });
|
|
|
|
|
}, [normalizedToken, refreshSnapshot, requestedRoomSessionId, snapshot]);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!normalizedToken || !snapshot || snapshot.detailLevel === 'initial') {
|
|
|
|
|
return;
|
|
|
|
|
@@ -6688,9 +6927,13 @@ export function ChatSharePage() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cachedSnapshot = readStoredShareRoomSnapshot(normalizedToken, normalizedSessionId);
|
|
|
|
|
const perfRunId = startShareRoomSwitchPerfRun(selectedShareRoomSessionId, normalizedSessionId);
|
|
|
|
|
|
|
|
|
|
deferredSnapshotRef.current = null;
|
|
|
|
|
roomSwitchPerfRunIdRef.current = perfRunId;
|
|
|
|
|
recordShareRoomSwitchPerfStep(perfRunId, 'room-select', undefined, { hasCache: Boolean(cachedSnapshot) });
|
|
|
|
|
if (cachedSnapshot) {
|
|
|
|
|
pendingSnapshotPerfStageRef.current = { runId: perfRunId, stage: 'cache' };
|
|
|
|
|
setSnapshot(cachedSnapshot);
|
|
|
|
|
}
|
|
|
|
|
setIsRoomSwitching(!cachedSnapshot);
|
|
|
|
|
@@ -8366,7 +8609,11 @@ export function ChatSharePage() {
|
|
|
|
|
const normalizedRoomSessionId = restoreSnapshot.roomSessionId.trim();
|
|
|
|
|
|
|
|
|
|
if (normalizedRoomSessionId && normalizedRoomSessionId !== selectedShareRoomSessionId) {
|
|
|
|
|
setIsRoomSwitching(true);
|
|
|
|
|
const cachedSnapshot = readStoredShareRoomSnapshot(normalizedToken, normalizedRoomSessionId);
|
|
|
|
|
if (cachedSnapshot) {
|
|
|
|
|
setSnapshot(cachedSnapshot);
|
|
|
|
|
}
|
|
|
|
|
setIsRoomSwitching(!cachedSnapshot);
|
|
|
|
|
requestedRoomSessionIdRef.current = normalizedRoomSessionId;
|
|
|
|
|
writeStoredShareLastRoomSessionId(normalizedToken, normalizedRoomSessionId);
|
|
|
|
|
writeShareRoomSessionIdToLocation(normalizedRoomSessionId, 'replace');
|
|
|
|
|
|