chore: test deploy snapshot
This commit is contained in:
1
.tmp-chatshare-full.json
Normal file
1
.tmp-chatshare-full.json
Normal file
File diff suppressed because one or more lines are too long
1
.tmp-chatshare-initial.json
Normal file
1
.tmp-chatshare-initial.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -66,6 +66,7 @@ import { getTokenSettingById, type TokenSettingRecord } from '../services/token-
|
|||||||
const CHAT_ATTACHMENT_FILE_SIZE_LIMIT = 300 * 1024 * 1024;
|
const CHAT_ATTACHMENT_FILE_SIZE_LIMIT = 300 * 1024 * 1024;
|
||||||
const CHAT_ATTACHMENT_ROUTE_BODY_LIMIT = 450 * 1024 * 1024;
|
const CHAT_ATTACHMENT_ROUTE_BODY_LIMIT = 450 * 1024 * 1024;
|
||||||
const CHAT_PUBLIC_ROUTE_PREFIX = '/.codex_chat/';
|
const CHAT_PUBLIC_ROUTE_PREFIX = '/.codex_chat/';
|
||||||
|
const CHAT_LEGACY_PUBLIC_ROUTE_PREFIX = '/public/.codex_chat/';
|
||||||
const CHAT_API_RESOURCE_ROUTE_PREFIX = '/api/chat/resources';
|
const CHAT_API_RESOURCE_ROUTE_PREFIX = '/api/chat/resources';
|
||||||
const CHAT_SHARE_ROUTE_PREFIX = '/api/chat/shares';
|
const CHAT_SHARE_ROUTE_PREFIX = '/api/chat/shares';
|
||||||
const CHAT_SHARE_TOKEN_VERSION = 1;
|
const CHAT_SHARE_TOKEN_VERSION = 1;
|
||||||
@@ -1235,7 +1236,11 @@ async function buildChatShareSnapshot(
|
|||||||
tokenPayload.kind === 'request-bundle' && normalizedSessionId.startsWith(MANAGED_CHAT_SHARE_SESSION_PREFIX);
|
tokenPayload.kind === 'request-bundle' && normalizedSessionId.startsWith(MANAGED_CHAT_SHARE_SESSION_PREFIX);
|
||||||
const useInitialManagedShareRoomView = isManagedShareRoomSession && detailLevel === 'initial';
|
const useInitialManagedShareRoomView = isManagedShareRoomSession && detailLevel === 'initial';
|
||||||
const detailPage = useInitialManagedShareRoomView
|
const detailPage = useInitialManagedShareRoomView
|
||||||
? await listChatConversationDetailPage(normalizedSessionId, { limit: 12 })
|
? await listChatConversationDetailPage(normalizedSessionId, {
|
||||||
|
limit: 12,
|
||||||
|
includeActivityLogs: false,
|
||||||
|
includePagination: false,
|
||||||
|
})
|
||||||
: null;
|
: null;
|
||||||
const [requests, messages] = detailPage
|
const [requests, messages] = detailPage
|
||||||
? [detailPage.requests, detailPage.messages]
|
? [detailPage.requests, detailPage.messages]
|
||||||
@@ -1571,7 +1576,7 @@ async function ensureManagedShareAccessPin(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (pinStatus.status === 'ok' || pinStatus.status === 'not-configured') {
|
if (pinStatus.status === 'ok' || pinStatus.status === 'not-configured') {
|
||||||
return true;
|
return pinStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pinStatus.status === 'required') {
|
if (pinStatus.status === 'required') {
|
||||||
@@ -1579,7 +1584,7 @@ async function ensureManagedShareAccessPin(
|
|||||||
code: 'share_pin_required',
|
code: 'share_pin_required',
|
||||||
message: '이 공유 채팅방은 4자리 비밀번호 입력이 필요합니다.',
|
message: '이 공유 채팅방은 4자리 비밀번호 입력이 필요합니다.',
|
||||||
});
|
});
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pinStatus.status === 'invalid') {
|
if (pinStatus.status === 'invalid') {
|
||||||
@@ -1587,17 +1592,17 @@ async function ensureManagedShareAccessPin(
|
|||||||
code: 'share_pin_invalid',
|
code: 'share_pin_invalid',
|
||||||
message: '공유 채팅방 비밀번호가 올바르지 않습니다.',
|
message: '공유 채팅방 비밀번호가 올바르지 않습니다.',
|
||||||
});
|
});
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pinStatus.status === 'missing') {
|
if (pinStatus.status === 'missing') {
|
||||||
reply.code(404).send({
|
reply.code(404).send({
|
||||||
message: '공유 링크를 찾을 수 없습니다.',
|
message: '공유 링크를 찾을 수 없습니다.',
|
||||||
});
|
});
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasManagedSharePermission(
|
function hasManagedSharePermission(
|
||||||
@@ -1641,6 +1646,11 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
|||||||
return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
|
return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get(`${CHAT_LEGACY_PUBLIC_ROUTE_PREFIX}*`, async (request, reply) => {
|
||||||
|
const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
|
||||||
|
return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
|
||||||
|
});
|
||||||
|
|
||||||
app.get(`${CHAT_API_RESOURCE_ROUTE_PREFIX}/*`, async (request, reply) => {
|
app.get(`${CHAT_API_RESOURCE_ROUTE_PREFIX}/*`, async (request, reply) => {
|
||||||
const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
|
const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
|
||||||
return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
|
return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
|
||||||
@@ -1668,7 +1678,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
|
const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);
|
||||||
|
|
||||||
|
if (!accessPinStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1898,7 +1910,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
|
const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);
|
||||||
|
|
||||||
|
if (!accessPinStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2033,7 +2047,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
|
const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);
|
||||||
|
|
||||||
|
if (!accessPinStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2467,7 +2483,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
|
const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);
|
||||||
|
|
||||||
|
if (!accessPinStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2497,15 +2515,19 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessPinStatus = await validateSharedResourceAccessPinBySharePath(managedContext.sharePath, getRequestChatSharePin(request), {
|
|
||||||
clientId: getRequestClientId(request),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (managedContext.managedResource) {
|
if (managedContext.managedResource) {
|
||||||
await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
|
void recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
|
||||||
actorLabel: 'share-viewer',
|
actorLabel: 'share-viewer',
|
||||||
summary: '공유 채팅 링크를 열었습니다.',
|
summary: '공유 채팅 링크를 열었습니다.',
|
||||||
detail: managedContext.managedResource.token.resourceLabel,
|
detail: managedContext.managedResource.token.resourceLabel,
|
||||||
|
}).catch((error) => {
|
||||||
|
request.log.warn(
|
||||||
|
{
|
||||||
|
err: error,
|
||||||
|
managedResourceTokenId: managedContext.managedResource?.token.id,
|
||||||
|
},
|
||||||
|
'Failed to record shared chat view usage',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { extractChatMessageParts, parseChatMessageParts } from './chat-message-parts.js';
|
||||||
|
|
||||||
|
test('extractChatMessageParts normalizes absolute legacy dot-codex prompt preview urls to api chat resource urls', () => {
|
||||||
|
const input = [
|
||||||
|
'문서 미리보기',
|
||||||
|
'[[prompt:{"title":"확인","options":[{"label":"legacy","value":"legacy","preview":{"type":"resource","url":"https://preview.sm-home.cloud/public/.codex_chat/chat-room/resource/source/chat-room-reference.md"}}]}]]',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const parsed = extractChatMessageParts(input);
|
||||||
|
const prompt = parsed.parts.find((part): part is Extract<(typeof parsed.parts)[number], { type: 'prompt' }> => part.type === 'prompt');
|
||||||
|
|
||||||
|
assert.ok(prompt);
|
||||||
|
assert.equal(
|
||||||
|
prompt.options[0]?.preview?.url,
|
||||||
|
'/api/chat/resources/chat-room/resource/source/chat-room-reference.md',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parseChatMessageParts normalizes absolute legacy link card urls to api chat resource urls', () => {
|
||||||
|
const parsed = parseChatMessageParts([
|
||||||
|
{
|
||||||
|
type: 'link_card',
|
||||||
|
title: 'legacy resource',
|
||||||
|
url: 'https://preview.sm-home.cloud/.codex_chat/chat-room/resource/uploads/spec.png',
|
||||||
|
actionLabel: '열기',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(parsed, [
|
||||||
|
{
|
||||||
|
type: 'link_card',
|
||||||
|
title: 'legacy resource',
|
||||||
|
url: '/api/chat/resources/chat-room/resource/uploads/spec.png',
|
||||||
|
actionLabel: '열기',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -237,6 +237,14 @@ function normalizeUrl(value: string) {
|
|||||||
if (pathname.startsWith(CHAT_API_RESOURCE_MARKER) || pathname.startsWith(RESOURCE_MANAGER_PREVIEW_MARKER)) {
|
if (pathname.startsWith(CHAT_API_RESOURCE_MARKER) || pathname.startsWith(RESOURCE_MANAGER_PREVIEW_MARKER)) {
|
||||||
return normalizePreviewPathHash(pathname);
|
return normalizePreviewPathHash(pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) {
|
||||||
|
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) {
|
||||||
|
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_DOT_CODEX_MARKER.length)}`;
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Fall through to handle relative and embedded resource paths below.
|
// Fall through to handle relative and embedded resource paths below.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3570,12 +3570,16 @@ export async function listChatConversationDetailPage(
|
|||||||
options: {
|
options: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
beforeMessageId?: number | null;
|
beforeMessageId?: number | null;
|
||||||
|
includeActivityLogs?: boolean;
|
||||||
|
includePagination?: boolean;
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<ChatConversationDetailPage> {
|
): Promise<ChatConversationDetailPage> {
|
||||||
const normalizedSessionId = sessionId.trim();
|
const normalizedSessionId = sessionId.trim();
|
||||||
await getChatConversation(normalizedSessionId, null);
|
await getChatConversation(normalizedSessionId, null);
|
||||||
const conversation = await db(CHAT_CONVERSATION_TABLE).where({ session_id: normalizedSessionId }).first();
|
const conversation = await db(CHAT_CONVERSATION_TABLE).where({ session_id: normalizedSessionId }).first();
|
||||||
const normalizedLimit = Math.max(1, Math.min(100, Math.round(options.limit ?? 10)));
|
const normalizedLimit = Math.max(1, Math.min(100, Math.round(options.limit ?? 10)));
|
||||||
|
const includeActivityLogs = options.includeActivityLogs !== false;
|
||||||
|
const includePagination = options.includePagination !== false;
|
||||||
const normalizedBeforeMessageId =
|
const normalizedBeforeMessageId =
|
||||||
Number.isFinite(options.beforeMessageId) && (options.beforeMessageId ?? 0) > 0
|
Number.isFinite(options.beforeMessageId) && (options.beforeMessageId ?? 0) > 0
|
||||||
? Math.trunc(options.beforeMessageId as number)
|
? Math.trunc(options.beforeMessageId as number)
|
||||||
@@ -3629,21 +3633,26 @@ export async function listChatConversationDetailPage(
|
|||||||
.orderBy('message_id', 'asc')
|
.orderBy('message_id', 'asc')
|
||||||
.orderBy('id', 'asc');
|
.orderBy('id', 'asc');
|
||||||
const messages = messageRows.map((row: Parameters<typeof mapMessageRow>[0]) => mapMessageRow(row));
|
const messages = messageRows.map((row: Parameters<typeof mapMessageRow>[0]) => mapMessageRow(row));
|
||||||
const activityLogs = await listChatConversationActivityLogsByRequestIds(normalizedSessionId, requestIds);
|
const activityLogs = includeActivityLogs
|
||||||
const oldestLoadedMessageId =
|
? await listChatConversationActivityLogsByRequestIds(normalizedSessionId, requestIds)
|
||||||
requests.reduce<number | null>((oldestId, request) => {
|
: [];
|
||||||
const candidateIds = [request.userMessageId, request.responseMessageId].filter(
|
const oldestLoadedMessageId = includePagination
|
||||||
(value): value is number => typeof value === 'number' && Number.isInteger(value) && value > 0,
|
? (
|
||||||
);
|
requests.reduce<number | null>((oldestId, request) => {
|
||||||
|
const candidateIds = [request.userMessageId, request.responseMessageId].filter(
|
||||||
|
(value): value is number => typeof value === 'number' && Number.isInteger(value) && value > 0,
|
||||||
|
);
|
||||||
|
|
||||||
if (candidateIds.length === 0) {
|
if (candidateIds.length === 0) {
|
||||||
return oldestId;
|
return oldestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextCandidateId = Math.min(...candidateIds);
|
const nextCandidateId = Math.min(...candidateIds);
|
||||||
return oldestId == null ? nextCandidateId : Math.min(oldestId, nextCandidateId);
|
return oldestId == null ? nextCandidateId : Math.min(oldestId, nextCandidateId);
|
||||||
}, null) ?? messages[0]?.id ?? null;
|
}, null) ?? messages[0]?.id ?? null
|
||||||
const oldestRequest = requests[0] ?? null;
|
)
|
||||||
|
: null;
|
||||||
|
const oldestRequest = includePagination ? requests[0] ?? null : null;
|
||||||
const hasOlderMessages = oldestRequest
|
const hasOlderMessages = oldestRequest
|
||||||
? Boolean(
|
? Boolean(
|
||||||
await db(CHAT_CONVERSATION_REQUEST_TABLE)
|
await db(CHAT_CONVERSATION_REQUEST_TABLE)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const port = Number(process.env.PORT ?? 5173);
|
|||||||
const distDirName = process.env.APP_DIST_DIR ?? 'app-dist';
|
const distDirName = process.env.APP_DIST_DIR ?? 'app-dist';
|
||||||
const rootDir = normalize(isAbsolute(distDirName) ? distDirName : join(process.cwd(), distDirName));
|
const rootDir = normalize(isAbsolute(distDirName) ? distDirName : join(process.cwd(), distDirName));
|
||||||
const workServerUrl = new URL(process.env.WORK_SERVER_URL ?? 'http://127.0.0.1:3100');
|
const workServerUrl = new URL(process.env.WORK_SERVER_URL ?? 'http://127.0.0.1:3100');
|
||||||
const proxyPrefixes = ['/api', '/.codex_chat', '/ws/chat'];
|
const proxyPrefixes = ['/api', '/.codex_chat', '/public/.codex_chat', '/ws/chat'];
|
||||||
|
|
||||||
const mimeTypes = {
|
const mimeTypes = {
|
||||||
'.css': 'text/css; charset=utf-8',
|
'.css': 'text/css; charset=utf-8',
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ async function resetServiceWorkersAndReload() {
|
|||||||
reloadAppWithCacheBuster();
|
reloadAppWithCacheBuster();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function forceReloadApp() {
|
||||||
|
await resetServiceWorkersAndReload();
|
||||||
|
}
|
||||||
|
|
||||||
async function unregisterServiceWorkers() {
|
async function unregisterServiceWorkers() {
|
||||||
if (!isAppUpdateSupported()) {
|
if (!isAppUpdateSupported()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -196,6 +196,14 @@ function extractKnownPreviewPath(value: string) {
|
|||||||
return normalizePreviewPathHash(pathname);
|
return normalizePreviewPathHash(pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) {
|
||||||
|
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) {
|
||||||
|
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_DOT_CODEX_MARKER.length)}`;
|
||||||
|
}
|
||||||
|
|
||||||
return normalized;
|
return normalized;
|
||||||
} catch {
|
} catch {
|
||||||
return normalizePreviewPathHash(extractEmbeddedResourcePath(normalized));
|
return normalizePreviewPathHash(extractEmbeddedResourcePath(normalized));
|
||||||
|
|||||||
@@ -173,6 +173,14 @@ function extractKnownPreviewPath(value: string) {
|
|||||||
return normalizePreviewPathHash(pathname);
|
return normalizePreviewPathHash(pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) {
|
||||||
|
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) {
|
||||||
|
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_DOT_CODEX_MARKER.length)}`;
|
||||||
|
}
|
||||||
|
|
||||||
return normalized;
|
return normalized;
|
||||||
} catch {
|
} catch {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -224,7 +224,7 @@
|
|||||||
|
|
||||||
.chat-share-page__room-list-panel--floating {
|
.chat-share-page__room-list-panel--floating {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1300;
|
z-index: 1700;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 18px 42px rgba(15, 23, 42, 0.18),
|
0 18px 42px rgba(15, 23, 42, 0.18),
|
||||||
@@ -886,15 +886,29 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
align-self: flex-start;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__conversation-title.ant-typography {
|
||||||
|
margin: 0;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.35;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__header-summary.ant-typography {
|
.chat-share-page__header-summary.ant-typography {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
white-space: nowrap;
|
white-space: normal;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,6 +928,55 @@
|
|||||||
background: #ef4444;
|
background: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-share-page__status-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: min(100%, 96px);
|
||||||
|
min-height: 18px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #334155;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__status-badge--processing {
|
||||||
|
background: rgba(219, 234, 254, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__status-badge--warning {
|
||||||
|
background: rgba(254, 243, 199, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__status-badge--success {
|
||||||
|
background: rgba(220, 252, 231, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__status-badge--error {
|
||||||
|
background: rgba(254, 226, 226, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__status-badge--default {
|
||||||
|
background: rgba(241, 245, 249, 0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__status-badge-label {
|
||||||
|
display: block;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__section-action--title-edit.ant-btn {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-share-page__section-actions {
|
.chat-share-page__section-actions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -929,7 +992,7 @@
|
|||||||
.chat-share-page__request-nav {
|
.chat-share-page__request-nav {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__section-action--icon.ant-btn {
|
.chat-share-page__section-action--icon.ant-btn {
|
||||||
@@ -999,6 +1062,34 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-share-page__section-action--inline.ant-btn {
|
||||||
|
height: 28px;
|
||||||
|
padding-inline: 8px;
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__section-action--inline.ant-btn .ant-btn-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: rgba(226, 232, 240, 0.72);
|
||||||
|
color: #475569;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__section-action--inline.ant-btn:hover,
|
||||||
|
.chat-share-page__section-action--inline.ant-btn:focus-visible {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__section-action--inline.ant-btn:hover .ant-btn-icon,
|
||||||
|
.chat-share-page__section-action--inline.ant-btn:focus-visible .ant-btn-icon {
|
||||||
|
background: rgba(219, 234, 254, 0.92);
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-share-page__tool-button-label {
|
.chat-share-page__tool-button-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -1211,20 +1302,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__expand-mode-filter.ant-btn {
|
.chat-share-page__expand-mode-filter.ant-btn {
|
||||||
|
min-width: 34px;
|
||||||
color: #475569;
|
color: #475569;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
padding-inline: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__expand-mode-filter--active.ant-btn {
|
.chat-share-page__expand-mode-filter--active.ant-btn {
|
||||||
background: linear-gradient(180deg, rgba(219, 234, 254, 0.98) 0%, rgba(191, 219, 254, 0.94) 100%);
|
|
||||||
color: #1d4ed8;
|
color: #1d4ed8;
|
||||||
box-shadow:
|
background: rgba(219, 234, 254, 0.38);
|
||||||
inset 0 0 0 1px rgba(96, 165, 250, 0.28),
|
box-shadow: inset 0 0 0 1px rgba(96, 165, 250, 0.18);
|
||||||
0 8px 18px rgba(96, 165, 250, 0.14);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__expand-mode-filter.ant-btn:hover {
|
.chat-share-page__expand-mode-filter.ant-btn:hover,
|
||||||
|
.chat-share-page__expand-mode-filter.ant-btn:focus-visible {
|
||||||
color: #1d4ed8;
|
color: #1d4ed8;
|
||||||
background: rgba(226, 232, 240, 0.72);
|
background: rgba(226, 232, 240, 0.5);
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__expand-mode-filter.ant-btn:active {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__expand-mode-filter.ant-btn .chat-share-page__tool-button-label {
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__expand-mode-filter.ant-btn {
|
.chat-share-page__expand-mode-filter.ant-btn {
|
||||||
@@ -1232,8 +1336,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__expand-mode-filter.ant-btn .ant-btn-icon {
|
.chat-share-page__expand-mode-filter.ant-btn .ant-btn-icon {
|
||||||
color: #1d4ed8;
|
color: currentColor;
|
||||||
background: rgba(219, 234, 254, 0.96);
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__expand-mode-filter.ant-btn .anticon {
|
.chat-share-page__expand-mode-filter.ant-btn .anticon {
|
||||||
@@ -2656,67 +2761,72 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.chat-share-page__first-inquiry {
|
.chat-share-page__conversation-toolbar {
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 2px 0 12px;
|
|
||||||
border-bottom: 1px solid rgba(148, 163, 184, 0.28);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-share-page__first-inquiry-head {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
|
||||||
align-items: start;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-share-page__first-inquiry-copy {
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-share-page__first-inquiry-title-row {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
justify-content: flex-end;
|
||||||
gap: 6px;
|
margin: 0 -10px 8px;
|
||||||
min-width: 0;
|
padding: 2px 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__first-inquiry-title-row .chat-share-page__section-action.ant-btn {
|
.chat-share-page__conversation-toolbar {
|
||||||
flex: 0 0 auto;
|
position: sticky;
|
||||||
margin-top: -2px;
|
top: var(--chat-share-page-conversation-toolbar-top, 52px);
|
||||||
|
z-index: 18;
|
||||||
|
background: transparent;
|
||||||
|
backdrop-filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__first-inquiry-menu-badge {
|
.chat-share-page__conversation-toolbar-group {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: fit-content;
|
justify-content: flex-end;
|
||||||
|
gap: 4px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-height: 28px;
|
|
||||||
padding: 0 12px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(219, 234, 254, 0.94);
|
|
||||||
color: #1d4ed8;
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(96, 165, 250, 0.22);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__first-inquiry-title.ant-typography {
|
.chat-share-page__conversation-toolbar-button.ant-btn {
|
||||||
margin: 0;
|
min-width: 34px;
|
||||||
min-width: 0;
|
height: 34px;
|
||||||
font-size: 18px;
|
padding-inline: 8px;
|
||||||
line-height: 1.45;
|
color: #475569;
|
||||||
font-weight: 700;
|
background: transparent;
|
||||||
|
border-color: rgba(148, 163, 184, 0.22);
|
||||||
|
box-shadow: none;
|
||||||
|
backdrop-filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__conversation-toolbar-button.ant-btn .ant-btn-icon {
|
||||||
|
color: currentColor;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__conversation-toolbar-button.ant-btn:hover,
|
||||||
|
.chat-share-page__conversation-toolbar-button.ant-btn:focus-visible {
|
||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
overflow: hidden;
|
background: transparent;
|
||||||
|
border-color: rgba(100, 116, 139, 0.34);
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__first-inquiry-head .chat-share-page__expand-mode-filter.ant-btn {
|
.chat-share-page__conversation-toolbar-button.ant-btn:active {
|
||||||
justify-self: end;
|
background: transparent;
|
||||||
margin-top: 1px;
|
border-color: rgba(71, 85, 105, 0.42);
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__conversation-toolbar-button.ant-btn:disabled,
|
||||||
|
.chat-share-page__conversation-toolbar-button.ant-btn:disabled:hover,
|
||||||
|
.chat-share-page__conversation-toolbar-button.ant-btn:disabled:focus-visible {
|
||||||
|
color: rgba(100, 116, 139, 0.68);
|
||||||
|
background: transparent;
|
||||||
|
border-color: rgba(148, 163, 184, 0.16);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-share-page__conversation-toolbar .chat-share-page__conversation-toolbar-button.ant-btn .chat-share-page__tool-button-label {
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-share-page__bundle-list {
|
.chat-share-page__bundle-list {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import { extractPreviewItems, type PreviewItem } from '../mainChatPanel/previewI
|
|||||||
import { buildChatPath, buildPlayAppPath } from '../routes';
|
import { buildChatPath, buildPlayAppPath } from '../routes';
|
||||||
import type { PreviewKind } from '../mainChatPanel/previewKind';
|
import type { PreviewKind } from '../mainChatPanel/previewKind';
|
||||||
import { normalizeChatResourceUrl } from '../mainChatPanel/chatResourceUrl';
|
import { normalizeChatResourceUrl } from '../mainChatPanel/chatResourceUrl';
|
||||||
|
import { forceReloadApp } from '../appUpdate';
|
||||||
import type {
|
import type {
|
||||||
ChatComposerAttachment,
|
ChatComposerAttachment,
|
||||||
ChatConversationSummary,
|
ChatConversationSummary,
|
||||||
@@ -185,6 +186,7 @@ type ShareNotificationClientStatus = {
|
|||||||
tone: ShareNotificationStatusTone;
|
tone: ShareNotificationStatusTone;
|
||||||
};
|
};
|
||||||
type ShareProcessInspectorMode = 'default' | 'fullscreen' | 'minimized';
|
type ShareProcessInspectorMode = 'default' | 'fullscreen' | 'minimized';
|
||||||
|
type ShareProcessInspectorExpandedSection = 'summary' | 'narratives' | null;
|
||||||
type ShareProcessChecklistStep = {
|
type ShareProcessChecklistStep = {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -4030,6 +4032,7 @@ export function ChatSharePage() {
|
|||||||
const [pendingRequestRetryIds, setPendingRequestRetryIds] = useState<string[]>([]);
|
const [pendingRequestRetryIds, setPendingRequestRetryIds] = useState<string[]>([]);
|
||||||
const [isShareRoomListVisible, setIsShareRoomListVisible] = useState(false);
|
const [isShareRoomListVisible, setIsShareRoomListVisible] = useState(false);
|
||||||
const [shareRoomListLayerStyle, setShareRoomListLayerStyle] = useState<CSSProperties | null>(null);
|
const [shareRoomListLayerStyle, setShareRoomListLayerStyle] = useState<CSSProperties | null>(null);
|
||||||
|
const [conversationToolbarStickyTop, setConversationToolbarStickyTop] = useState(52);
|
||||||
const [isRoomSwitching, setIsRoomSwitching] = useState(false);
|
const [isRoomSwitching, setIsRoomSwitching] = useState(false);
|
||||||
const [replyReferenceRequestId, setReplyReferenceRequestId] = useState('');
|
const [replyReferenceRequestId, setReplyReferenceRequestId] = useState('');
|
||||||
const [previousQuestionModalRequestId, setPreviousQuestionModalRequestId] = useState('');
|
const [previousQuestionModalRequestId, setPreviousQuestionModalRequestId] = useState('');
|
||||||
@@ -4080,8 +4083,7 @@ export function ChatSharePage() {
|
|||||||
const [pendingShareRuntimeRequestIds, setPendingShareRuntimeRequestIds] = useState<string[]>([]);
|
const [pendingShareRuntimeRequestIds, setPendingShareRuntimeRequestIds] = useState<string[]>([]);
|
||||||
const [activeProcessInspectorRequestId, setActiveProcessInspectorRequestId] = useState('');
|
const [activeProcessInspectorRequestId, setActiveProcessInspectorRequestId] = useState('');
|
||||||
const [processInspectorMode, setProcessInspectorMode] = useState<ShareProcessInspectorMode>('default');
|
const [processInspectorMode, setProcessInspectorMode] = useState<ShareProcessInspectorMode>('default');
|
||||||
const [isProcessInspectorSummaryCollapsed, setIsProcessInspectorSummaryCollapsed] = useState(true);
|
const [processInspectorExpandedSection, setProcessInspectorExpandedSection] = useState<ShareProcessInspectorExpandedSection>(null);
|
||||||
const [isProcessInspectorNarrativesCollapsed, setIsProcessInspectorNarrativesCollapsed] = useState(true);
|
|
||||||
const [optimisticShareRooms, setOptimisticShareRooms] = useState<ChatShareRoomSummary[]>([]);
|
const [optimisticShareRooms, setOptimisticShareRooms] = useState<ChatShareRoomSummary[]>([]);
|
||||||
const [shareRoomPendingCountsBySessionId, setShareRoomPendingCountsBySessionId] = useState<Record<string, ShareRoomPendingCounts>>({});
|
const [shareRoomPendingCountsBySessionId, setShareRoomPendingCountsBySessionId] = useState<Record<string, ShareRoomPendingCounts>>({});
|
||||||
const [isLoadingShareRoomPendingCounts, setIsLoadingShareRoomPendingCounts] = useState(false);
|
const [isLoadingShareRoomPendingCounts, setIsLoadingShareRoomPendingCounts] = useState(false);
|
||||||
@@ -4102,6 +4104,7 @@ export function ChatSharePage() {
|
|||||||
const shareRoomPendingCountRefreshPromiseBySessionIdRef = useRef<Record<string, Promise<void> | null>>({});
|
const shareRoomPendingCountRefreshPromiseBySessionIdRef = useRef<Record<string, Promise<void> | null>>({});
|
||||||
const shareRoomPendingCountRefreshQueuedBySessionIdRef = useRef<Record<string, boolean>>({});
|
const shareRoomPendingCountRefreshQueuedBySessionIdRef = useRef<Record<string, boolean>>({});
|
||||||
const conversationHeaderRef = useRef<HTMLDivElement | null>(null);
|
const conversationHeaderRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const conversationToolbarRef = useRef<HTMLDivElement | null>(null);
|
||||||
const roomListTriggerButtonRef = useRef<HTMLButtonElement | null>(null);
|
const roomListTriggerButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||||
const roomListPanelRef = useRef<HTMLElement | null>(null);
|
const roomListPanelRef = useRef<HTMLElement | null>(null);
|
||||||
const processInspectorCardRef = useRef<HTMLDivElement | null>(null);
|
const processInspectorCardRef = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -5428,6 +5431,37 @@ export function ChatSharePage() {
|
|||||||
};
|
};
|
||||||
}, [minimizedPrograms.length]);
|
}, [minimizedPrograms.length]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const headerElement = conversationHeaderRef.current;
|
||||||
|
|
||||||
|
if (!headerElement) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStickyOffset = () => {
|
||||||
|
const headerHeight = headerElement.getBoundingClientRect().height;
|
||||||
|
const nextStickyTop = Math.max(48, Math.ceil(headerHeight) + 8);
|
||||||
|
|
||||||
|
setConversationToolbarStickyTop((current) => (current === nextStickyTop ? current : nextStickyTop));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeObserver =
|
||||||
|
typeof ResizeObserver === 'undefined'
|
||||||
|
? null
|
||||||
|
: new ResizeObserver(() => {
|
||||||
|
updateStickyOffset();
|
||||||
|
});
|
||||||
|
|
||||||
|
updateStickyOffset();
|
||||||
|
window.addEventListener('resize', updateStickyOffset);
|
||||||
|
resizeObserver?.observe(headerElement);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', updateStickyOffset);
|
||||||
|
resizeObserver?.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!isShareRoomListVisible) {
|
if (!isShareRoomListVisible) {
|
||||||
setShareRoomListLayerStyle(null);
|
setShareRoomListLayerStyle(null);
|
||||||
@@ -5437,7 +5471,8 @@ export function ChatSharePage() {
|
|||||||
const updateLayerPosition = () => {
|
const updateLayerPosition = () => {
|
||||||
const triggerRect = roomListTriggerButtonRef.current?.getBoundingClientRect() ?? null;
|
const triggerRect = roomListTriggerButtonRef.current?.getBoundingClientRect() ?? null;
|
||||||
const headerRect = conversationHeaderRef.current?.getBoundingClientRect() ?? null;
|
const headerRect = conversationHeaderRef.current?.getBoundingClientRect() ?? null;
|
||||||
const anchorRect = headerRect ?? triggerRect;
|
const toolbarRect = conversationToolbarRef.current?.getBoundingClientRect() ?? null;
|
||||||
|
const anchorRect = triggerRect ?? headerRect ?? toolbarRect;
|
||||||
|
|
||||||
if (!anchorRect) {
|
if (!anchorRect) {
|
||||||
return;
|
return;
|
||||||
@@ -5445,14 +5480,17 @@ export function ChatSharePage() {
|
|||||||
|
|
||||||
const viewportPadding = 8;
|
const viewportPadding = 8;
|
||||||
const availableWidth = Math.max(280, window.innerWidth - (viewportPadding * 2));
|
const availableWidth = Math.max(280, window.innerWidth - (viewportPadding * 2));
|
||||||
const preferredWidth = headerRect
|
const preferredWidth = toolbarRect
|
||||||
? Math.min(Math.max(headerRect.width, 280), 420)
|
? Math.min(Math.max(toolbarRect.width, 280), 420)
|
||||||
|
: headerRect
|
||||||
|
? Math.min(Math.max(headerRect.width, 280), 420)
|
||||||
: Math.min(360, availableWidth);
|
: Math.min(360, availableWidth);
|
||||||
const width = Math.min(preferredWidth, availableWidth);
|
const width = Math.min(preferredWidth, availableWidth);
|
||||||
const preferredLeft = headerRect?.left ?? triggerRect?.left ?? viewportPadding;
|
const preferredRight = triggerRect?.right ?? toolbarRect?.right ?? headerRect?.right ?? (viewportPadding + width);
|
||||||
|
const minLeft = viewportPadding;
|
||||||
const maxLeft = Math.max(viewportPadding, window.innerWidth - viewportPadding - width);
|
const maxLeft = Math.max(viewportPadding, window.innerWidth - viewportPadding - width);
|
||||||
const left = Math.min(Math.max(preferredLeft, viewportPadding), maxLeft);
|
const left = Math.min(Math.max(preferredRight - width, minLeft), maxLeft);
|
||||||
const top = Math.max(anchorRect.bottom, triggerRect?.bottom ?? 0) + 8;
|
const top = anchorRect.bottom + 8;
|
||||||
const maxHeight = Math.max(220, window.innerHeight - top - viewportPadding);
|
const maxHeight = Math.max(220, window.innerHeight - top - viewportPadding);
|
||||||
|
|
||||||
setShareRoomListLayerStyle({
|
setShareRoomListLayerStyle({
|
||||||
@@ -5527,14 +5565,20 @@ export function ChatSharePage() {
|
|||||||
|
|
||||||
setActiveProcessInspectorRequestId(requestId);
|
setActiveProcessInspectorRequestId(requestId);
|
||||||
setProcessInspectorMode('default');
|
setProcessInspectorMode('default');
|
||||||
setIsProcessInspectorSummaryCollapsed(true);
|
setProcessInspectorExpandedSection(null);
|
||||||
setIsProcessInspectorNarrativesCollapsed(true);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeProcessInspector = useCallback(() => {
|
const closeProcessInspector = useCallback(() => {
|
||||||
setActiveProcessInspectorRequestId('');
|
setActiveProcessInspectorRequestId('');
|
||||||
setIsProcessInspectorSummaryCollapsed(true);
|
setProcessInspectorExpandedSection(null);
|
||||||
setIsProcessInspectorNarrativesCollapsed(true);
|
}, []);
|
||||||
|
|
||||||
|
const handleToggleProcessInspectorSummary = useCallback(() => {
|
||||||
|
setProcessInspectorExpandedSection((current) => (current === 'summary' ? null : 'summary'));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleToggleProcessInspectorNarratives = useCallback(() => {
|
||||||
|
setProcessInspectorExpandedSection((current) => (current === 'narratives' ? null : 'narratives'));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleProgramMinimizedPointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
|
const handleProgramMinimizedPointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
|
||||||
@@ -5583,7 +5627,7 @@ export function ChatSharePage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.reload();
|
void forceReloadApp();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleReloadProgram = useCallback(() => {
|
const handleReloadProgram = useCallback(() => {
|
||||||
@@ -7461,6 +7505,8 @@ export function ChatSharePage() {
|
|||||||
() => (activeProcessInspectorRequestId.trim() ? requestById.get(activeProcessInspectorRequestId.trim()) ?? null : null),
|
() => (activeProcessInspectorRequestId.trim() ? requestById.get(activeProcessInspectorRequestId.trim()) ?? null : null),
|
||||||
[activeProcessInspectorRequestId, requestById],
|
[activeProcessInspectorRequestId, requestById],
|
||||||
);
|
);
|
||||||
|
const isProcessInspectorSummaryCollapsed = processInspectorExpandedSection !== 'summary';
|
||||||
|
const isProcessInspectorNarrativesCollapsed = processInspectorExpandedSection !== 'narratives';
|
||||||
const activeProcessInspectorPayload = useMemo(() => {
|
const activeProcessInspectorPayload = useMemo(() => {
|
||||||
if (!activeProcessInspectorRequest) {
|
if (!activeProcessInspectorRequest) {
|
||||||
return null;
|
return null;
|
||||||
@@ -7638,9 +7684,7 @@ export function ChatSharePage() {
|
|||||||
size="small"
|
size="small"
|
||||||
className="chat-share-page__process-inspector-summary-toggle"
|
className="chat-share-page__process-inspector-summary-toggle"
|
||||||
icon={isProcessInspectorSummaryCollapsed ? <DownOutlined /> : <UpOutlined />}
|
icon={isProcessInspectorSummaryCollapsed ? <DownOutlined /> : <UpOutlined />}
|
||||||
onClick={() => {
|
onClick={handleToggleProcessInspectorSummary}
|
||||||
setIsProcessInspectorSummaryCollapsed((current) => !current);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{isProcessInspectorSummaryCollapsed ? '요청 정보 보기' : '요청 정보 접기'}
|
{isProcessInspectorSummaryCollapsed ? '요청 정보 보기' : '요청 정보 접기'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -7705,9 +7749,7 @@ export function ChatSharePage() {
|
|||||||
size="small"
|
size="small"
|
||||||
className="chat-share-page__process-inspector-summary-toggle"
|
className="chat-share-page__process-inspector-summary-toggle"
|
||||||
icon={isProcessInspectorNarrativesCollapsed ? <DownOutlined /> : <UpOutlined />}
|
icon={isProcessInspectorNarrativesCollapsed ? <DownOutlined /> : <UpOutlined />}
|
||||||
onClick={() => {
|
onClick={handleToggleProcessInspectorNarratives}
|
||||||
setIsProcessInspectorNarrativesCollapsed((current) => !current);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{isProcessInspectorNarrativesCollapsed ? '보기' : '접기'}
|
{isProcessInspectorNarrativesCollapsed ? '보기' : '접기'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -8686,8 +8728,8 @@ export function ChatSharePage() {
|
|||||||
key: 'conversation-refresh',
|
key: 'conversation-refresh',
|
||||||
label: (
|
label: (
|
||||||
<span className="chat-share-page__settings-item">
|
<span className="chat-share-page__settings-item">
|
||||||
<span className="chat-share-page__settings-item-title">화면 새로고침</span>
|
<span className="chat-share-page__settings-item-title">강력 새로고침</span>
|
||||||
<span className="chat-share-page__settings-item-description">PWA 반영이 늦을 때 현재 공유채팅방 화면을 다시 불러옵니다.</span>
|
<span className="chat-share-page__settings-item-description">서비스워커와 캐시를 정리한 뒤 현재 공유채팅방 화면을 다시 불러옵니다.</span>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
icon: <ReloadOutlined />,
|
icon: <ReloadOutlined />,
|
||||||
@@ -9249,19 +9291,46 @@ export function ChatSharePage() {
|
|||||||
<div className={contentLayoutClassName}>
|
<div className={contentLayoutClassName}>
|
||||||
<section className="chat-share-page__panel chat-share-page__conversation-panel">
|
<section className="chat-share-page__panel chat-share-page__conversation-panel">
|
||||||
<div ref={conversationHeaderRef} className="chat-share-page__section-head">
|
<div ref={conversationHeaderRef} className="chat-share-page__section-head">
|
||||||
<div className="chat-share-page__section-copy">
|
<div className="chat-share-page__section-copy">
|
||||||
<div className="chat-share-page__section-title-row">
|
<div className="chat-share-page__section-title-row">
|
||||||
<Title level={5}>채팅</Title>
|
<Title
|
||||||
|
level={5}
|
||||||
|
className="chat-share-page__conversation-title"
|
||||||
|
ellipsis={{ rows: 1, tooltip: headerTitleText.trim() || '채팅' }}
|
||||||
|
>
|
||||||
|
{headerTitleText.trim() || '채팅'}
|
||||||
|
</Title>
|
||||||
|
{canOpenSharedRoomSettings ? (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
className="chat-share-page__section-action chat-share-page__section-action--tool chat-share-page__section-action--inline chat-share-page__section-action--title-edit"
|
||||||
|
aria-label="채팅방 이름 및 설정 편집"
|
||||||
|
title="채팅방 이름 및 설정 편집"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
openSharedRoomSettings();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<span
|
<span
|
||||||
className={`chat-share-page__live-dot ${isLiveConnected ? 'chat-share-page__live-dot--connected' : 'chat-share-page__live-dot--disconnected'}`}
|
className={`chat-share-page__live-dot ${isLiveConnected ? 'chat-share-page__live-dot--connected' : 'chat-share-page__live-dot--disconnected'}`}
|
||||||
aria-label={isLiveConnected ? '웹소켓 연결됨' : '웹소켓 연결 끊김'}
|
aria-label={isLiveConnected ? '웹소켓 연결됨' : '웹소켓 연결 끊김'}
|
||||||
title={isLiveConnected ? '웹소켓 연결됨' : '웹소켓 연결 끊김'}
|
title={isLiveConnected ? '웹소켓 연결됨' : '웹소켓 연결 끊김'}
|
||||||
/>
|
/>
|
||||||
{aggregateStatusTag ? <Tag color={aggregateStatusTag.color}>{aggregateStatusTag.label}</Tag> : null}
|
{aggregateStatusTag ? (
|
||||||
<Text type="secondary" className="chat-share-page__header-summary">
|
<span
|
||||||
{headerSummaryLabel}
|
className={`chat-share-page__status-badge chat-share-page__status-badge--${aggregateStatusTag.color}`}
|
||||||
</Text>
|
aria-label={aggregateStatusTag.label}
|
||||||
|
title={aggregateStatusTag.label}
|
||||||
|
>
|
||||||
|
<span className="chat-share-page__status-badge-label">{aggregateStatusTag.label}</span>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
<Text type="secondary" className="chat-share-page__header-summary">
|
||||||
|
{headerSummaryLabel}
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="chat-share-page__section-actions">
|
<div className="chat-share-page__section-actions">
|
||||||
{canToggleShareRoomList ? (
|
{canToggleShareRoomList ? (
|
||||||
@@ -9278,29 +9347,6 @@ export function ChatSharePage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="chat-share-page__request-nav" aria-label="요청 이동">
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
className="chat-share-page__section-action"
|
|
||||||
icon={<LeftOutlined />}
|
|
||||||
disabled={!canMoveToPreviousRequest}
|
|
||||||
onClick={handleMoveToPreviousRequest}
|
|
||||||
>
|
|
||||||
이전
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
className="chat-share-page__section-action"
|
|
||||||
icon={<RightOutlined />}
|
|
||||||
iconPosition="end"
|
|
||||||
disabled={!canMoveToNextRequest}
|
|
||||||
onClick={handleMoveToNextRequest}
|
|
||||||
>
|
|
||||||
다음
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
menu={{
|
menu={{
|
||||||
@@ -9323,6 +9369,64 @@ export function ChatSharePage() {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
ref={conversationToolbarRef}
|
||||||
|
className="chat-share-page__conversation-toolbar"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
'--chat-share-page-conversation-toolbar-top': `${conversationToolbarStickyTop}px`,
|
||||||
|
} as CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="chat-share-page__conversation-toolbar-group" aria-label="요청 이동 및 필터">
|
||||||
|
<div className="chat-share-page__request-nav" aria-label="요청 이동">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
className="chat-share-page__section-action chat-share-page__section-action--tool chat-share-page__conversation-toolbar-button"
|
||||||
|
icon={<LeftOutlined />}
|
||||||
|
disabled={!canMoveToPreviousRequest}
|
||||||
|
onClick={handleMoveToPreviousRequest}
|
||||||
|
>
|
||||||
|
<span className="chat-share-page__tool-button-label">이전</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
className="chat-share-page__section-action chat-share-page__section-action--tool chat-share-page__conversation-toolbar-button"
|
||||||
|
icon={<RightOutlined />}
|
||||||
|
iconPosition="end"
|
||||||
|
disabled={!canMoveToNextRequest}
|
||||||
|
onClick={handleMoveToNextRequest}
|
||||||
|
>
|
||||||
|
<span className="chat-share-page__tool-button-label">다음</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Dropdown
|
||||||
|
trigger={['click']}
|
||||||
|
placement="bottomRight"
|
||||||
|
menu={{
|
||||||
|
items: shareExpandModeMenuItems,
|
||||||
|
selectable: true,
|
||||||
|
selectedKeys: [expandMode],
|
||||||
|
onClick: handleSelectExpandMode,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
className={`chat-share-page__section-action chat-share-page__section-action--tool chat-share-page__conversation-toolbar-button chat-share-page__expand-mode-filter${expandMode !== 'latest' ? ' chat-share-page__expand-mode-filter--active' : ''}`}
|
||||||
|
aria-label={`공유 채팅 펼치기 필터: ${getShareExpandModeLabel(expandMode)} ${requestProgressLabel}`.trim()}
|
||||||
|
title={`공유 채팅 펼치기 필터: ${getShareExpandModeLabel(expandMode)} ${requestProgressLabel}`.trim()}
|
||||||
|
icon={<FilterOutlined />}
|
||||||
|
>
|
||||||
|
<span className="chat-share-page__tool-button-label">
|
||||||
|
{expandMode === 'latest' ? '필터' : getShareExpandModeLabel(expandMode)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{showRoomSwitchingSkeleton ? (
|
{showRoomSwitchingSkeleton ? (
|
||||||
<div className="chat-share-page__conversation-loading-block" role="status" aria-live="polite">
|
<div className="chat-share-page__conversation-loading-block" role="status" aria-live="polite">
|
||||||
<Spin size="large" />
|
<Spin size="large" />
|
||||||
@@ -9339,59 +9443,6 @@ export function ChatSharePage() {
|
|||||||
<Text type="secondary">{`${roomSwitchingStatusLabel} 불러오는 중`}</Text>
|
<Text type="secondary">{`${roomSwitchingStatusLabel} 불러오는 중`}</Text>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{headerInquiryRequest ? (
|
|
||||||
<section className="chat-share-page__first-inquiry">
|
|
||||||
<div className="chat-share-page__first-inquiry-head">
|
|
||||||
<div className="chat-share-page__first-inquiry-copy">
|
|
||||||
<div className="chat-share-page__first-inquiry-title-row">
|
|
||||||
<Title
|
|
||||||
level={5}
|
|
||||||
className="chat-share-page__first-inquiry-title"
|
|
||||||
ellipsis={{ rows: 1, tooltip: headerTitleText }}
|
|
||||||
>
|
|
||||||
{headerTitleText}
|
|
||||||
</Title>
|
|
||||||
{canOpenSharedRoomSettings ? (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
className="chat-share-page__section-action chat-share-page__section-action--tool"
|
|
||||||
aria-label="채팅방 이름 및 설정 편집"
|
|
||||||
title="채팅방 이름 및 설정 편집"
|
|
||||||
icon={<EditOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
openSharedRoomSettings();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Dropdown
|
|
||||||
trigger={['click']}
|
|
||||||
placement="bottomRight"
|
|
||||||
menu={{
|
|
||||||
items: shareExpandModeMenuItems,
|
|
||||||
selectable: true,
|
|
||||||
selectedKeys: [expandMode],
|
|
||||||
onClick: handleSelectExpandMode,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
className={`chat-share-page__section-action chat-share-page__section-action--tool chat-share-page__expand-mode-filter${expandMode !== 'latest' ? ' chat-share-page__expand-mode-filter--active' : ''}`}
|
|
||||||
aria-label={`공유 채팅 펼치기 필터: ${getShareExpandModeLabel(expandMode)} ${requestProgressLabel}`.trim()}
|
|
||||||
title={`공유 채팅 펼치기 필터: ${getShareExpandModeLabel(expandMode)} ${requestProgressLabel}`.trim()}
|
|
||||||
icon={<FilterOutlined />}
|
|
||||||
>
|
|
||||||
<span className="chat-share-page__tool-button-label">
|
|
||||||
{expandMode === 'latest' ? '필터' : getShareExpandModeLabel(expandMode)}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
) : null}
|
|
||||||
{expandMode === 'latest' && hiddenBeforeCount > 0 ? (
|
{expandMode === 'latest' && hiddenBeforeCount > 0 ? (
|
||||||
<div className="chat-share-page__omission chat-share-page__omission--collapsed chat-share-page__omission--before" aria-label={`위쪽 채팅 ${hiddenBeforeCount}건 숨김`}>
|
<div className="chat-share-page__omission chat-share-page__omission--collapsed chat-share-page__omission--before" aria-label={`위쪽 채팅 ${hiddenBeforeCount}건 숨김`}>
|
||||||
<span className="chat-share-page__omission-line" aria-hidden="true" />
|
<span className="chat-share-page__omission-line" aria-hidden="true" />
|
||||||
|
|||||||
@@ -217,6 +217,10 @@ export default defineConfig({
|
|||||||
target: WORK_SERVER_HTTP_TARGET,
|
target: WORK_SERVER_HTTP_TARGET,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
|
'/public/.codex_chat': {
|
||||||
|
target: WORK_SERVER_HTTP_TARGET,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
|
|||||||
Reference in New Issue
Block a user