feat: update main chat and system chat UI

This commit is contained in:
2026-05-25 17:26:37 +09:00
parent fb5ec649cd
commit f59522ffc4
120 changed files with 43262 additions and 3325 deletions

View File

@@ -0,0 +1,155 @@
export const ISOLATED_CHAT_ROOM_SESSION_PREFIX = 'chat-room-menu-';
export const MANAGED_CHAT_SHARE_SESSION_PREFIX = 'chat-share-room-';
export type IsolatedChatRoomScope = {
topMenu: string;
menuTitle: string;
featureTitle: string;
focusedComponentId: string | null;
pageUrl: string;
selectionSummary?: string | null;
selectionIds?: string[];
errorSummary?: string | null;
sourceAppId?: string | null;
launchedAt: string;
};
export type MainChatPanelMode = 'live' | 'rooms';
export function isIsolatedChatRoomSessionId(sessionId: string | null | undefined) {
return String(sessionId ?? '').trim().startsWith(ISOLATED_CHAT_ROOM_SESSION_PREFIX);
}
export function isManagedChatShareSessionId(sessionId: string | null | undefined) {
return String(sessionId ?? '').trim().startsWith(MANAGED_CHAT_SHARE_SESSION_PREFIX);
}
export function createIsolatedChatRoomSessionId() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return `${ISOLATED_CHAT_ROOM_SESSION_PREFIX}${crypto.randomUUID()}`;
}
return `${ISOLATED_CHAT_ROOM_SESSION_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
}
export function shouldShowConversationForMode(sessionId: string, mode: MainChatPanelMode) {
const isIsolatedRoom = isIsolatedChatRoomSessionId(sessionId);
const isManagedShareRoom = isManagedChatShareSessionId(sessionId);
return mode === 'rooms' ? isIsolatedRoom || isManagedShareRoom : !isIsolatedRoom && !isManagedShareRoom;
}
export function resolveChatPathForSession(sessionId: string) {
return isIsolatedChatRoomSessionId(sessionId) || isManagedChatShareSessionId(sessionId) ? '/chat/rooms' : '/chat/live';
}
export function normalizeIsolatedChatRoomScope(
scope: Partial<IsolatedChatRoomScope> | null | undefined,
): IsolatedChatRoomScope | null {
if (!scope) {
return null;
}
const menuTitle = String(scope.menuTitle ?? '').trim();
const featureTitle = String(scope.featureTitle ?? '').trim();
const pageUrl = String(scope.pageUrl ?? '').trim();
if (!menuTitle && !featureTitle && !pageUrl) {
return null;
}
return {
topMenu: String(scope.topMenu ?? '').trim() || 'unknown',
menuTitle: menuTitle || '현재 메뉴',
featureTitle: featureTitle || menuTitle || '현재 기능',
focusedComponentId: String(scope.focusedComponentId ?? '').trim() || null,
pageUrl,
selectionSummary: String(scope.selectionSummary ?? '').trim() || null,
selectionIds: Array.isArray(scope.selectionIds)
? scope.selectionIds.map((item) => String(item).trim()).filter(Boolean)
: [],
errorSummary: String(scope.errorSummary ?? '').trim() || null,
sourceAppId: String(scope.sourceAppId ?? '').trim() || null,
launchedAt: String(scope.launchedAt ?? '').trim() || new Date().toISOString(),
};
}
export function buildIsolatedChatRoomTitle(scope: IsolatedChatRoomScope | null | undefined) {
if (!scope) {
return '격리 채팅방';
}
return `${scope.menuTitle} · ${scope.featureTitle}`.trim();
}
export function buildIsolatedChatRoomRequestBadgeLabel(scope: IsolatedChatRoomScope | null | undefined) {
if (!scope) {
return '격리 요청';
}
return scope.focusedComponentId?.trim() || scope.featureTitle || scope.menuTitle || '격리 요청';
}
export function buildIsolatedChatRoomContextSupplement(scope: IsolatedChatRoomScope | null | undefined) {
if (!scope) {
return '';
}
const lines = [
'## 격리 채팅방 범위',
`- 현재 활성 메뉴: ${scope.menuTitle}`,
`- 현재 기능: ${scope.featureTitle}`,
`- topMenu: ${scope.topMenu || '없음'}`,
`- focusedComponentId: ${scope.focusedComponentId || '없음'}`,
`- pageUrl: ${scope.pageUrl || '없음'}`,
];
if (scope.selectionSummary) {
lines.push(`- 현재 선택: ${scope.selectionSummary}`);
}
if (scope.selectionIds && scope.selectionIds.length > 0) {
lines.push(`- 선택 ID: ${scope.selectionIds.join(', ')}`);
}
if (scope.errorSummary) {
lines.push('');
lines.push('## 최근 참조 에러');
lines.push(scope.errorSummary);
}
return lines.join('\n').trim();
}
function normalizeScopeCompareValue(value: string | null | undefined) {
return String(value ?? '').trim();
}
function normalizeScopeSelectionIds(value: string[] | null | undefined) {
return (value ?? []).map((item) => String(item).trim()).filter(Boolean).sort();
}
export function doesIsolatedChatRoomScopeMatch(
left: IsolatedChatRoomScope | null | undefined,
right: IsolatedChatRoomScope | null | undefined,
) {
const normalizedLeft = normalizeIsolatedChatRoomScope(left);
const normalizedRight = normalizeIsolatedChatRoomScope(right);
if (!normalizedLeft || !normalizedRight) {
return false;
}
const leftSelectionIds = normalizeScopeSelectionIds(normalizedLeft.selectionIds);
const rightSelectionIds = normalizeScopeSelectionIds(normalizedRight.selectionIds);
return (
normalizeScopeCompareValue(normalizedLeft.sourceAppId) === normalizeScopeCompareValue(normalizedRight.sourceAppId) &&
normalizeScopeCompareValue(normalizedLeft.topMenu) === normalizeScopeCompareValue(normalizedRight.topMenu) &&
normalizeScopeCompareValue(normalizedLeft.menuTitle) === normalizeScopeCompareValue(normalizedRight.menuTitle) &&
normalizeScopeCompareValue(normalizedLeft.featureTitle) === normalizeScopeCompareValue(normalizedRight.featureTitle) &&
normalizeScopeCompareValue(normalizedLeft.focusedComponentId) === normalizeScopeCompareValue(normalizedRight.focusedComponentId) &&
normalizeScopeCompareValue(normalizedLeft.pageUrl) === normalizeScopeCompareValue(normalizedRight.pageUrl) &&
leftSelectionIds.length === rightSelectionIds.length &&
leftSelectionIds.every((item, index) => item === rightSelectionIds[index])
);
}