chore: test deploy snapshot
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { DeleteOutlined, LinkOutlined, PlusOutlined, QrcodeOutlined, ReloadOutlined, SaveOutlined, StopOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||
import { Alert, App, Button, Card, Checkbox, Empty, Flex, Form, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tabs, Tag, Typography } from 'antd';
|
||||
import { useEffect, useMemo, useState, type Key, type MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { Alert, App, Button, Card, Checkbox, Drawer, Empty, Flex, Form, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tabs, Tag, Typography } from 'antd';
|
||||
import { useEffect, useLayoutEffect, useMemo, useState, type Key, type MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { getReadyPlayAppEntries } from '../../views/play/apps/apps/appsRegistry';
|
||||
import { copyTextToClipboard } from '../../utils/clipboard';
|
||||
import { openExternalLinkInNewWindow } from './mainChatPanel/linkNavigation';
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
type SharedResourceTokenRecord,
|
||||
type SharedResourceType,
|
||||
} from './sharedResourceTokenAccess';
|
||||
import { createInstallManifestObjectUrl, swapInstallDocumentMetadata } from './pwa/installManifest';
|
||||
import './SharedResourceManagementPage.css';
|
||||
|
||||
const { Paragraph, Text, Title } = Typography;
|
||||
@@ -45,6 +46,7 @@ const RESOURCE_TYPE_OPTIONS: Array<{ value: SharedResourceType; label: string }>
|
||||
const MANAGEMENT_APP_OPTIONS = [
|
||||
{ value: 'chat-live', label: 'Codex Live', description: '채팅방 조회와 응답 흐름 진입', category: '관리' },
|
||||
{ value: 'chat-room-settings', label: '채팅방 설정', description: 'Codex Live 채팅방 Context 설정 편집 접근', category: '관리' },
|
||||
{ value: 'text-memo-widget', label: '메모', description: '공유채팅 Apps에서 메모 컴포넌트 실행', category: '관리' },
|
||||
{ value: 'token-setting', label: '토큰관리 설정', description: '토큰 설정 목록과 상세 편집 접근', category: '관리' },
|
||||
{ value: 'shared-resource', label: '공유 리소스 관리', description: '공유 링크와 권한, 활동 이력 관리', category: '관리' },
|
||||
{ value: 'plan-board', label: '자동화 현황', description: '작업 현황과 요청 보드 접근', category: '관리' },
|
||||
@@ -83,6 +85,12 @@ type SharedResourceManagementSharedAccess = {
|
||||
managedResourceTokenId?: string | null;
|
||||
};
|
||||
|
||||
type ConversationDrawerState = {
|
||||
tokenId: string;
|
||||
tokenName: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
function isChatShareToken<T extends Pick<SharedResourceTokenRecord, 'resourceType'>>(
|
||||
item: T | null | undefined,
|
||||
): item is T & { resourceType: 'chat-share' } {
|
||||
@@ -105,6 +113,14 @@ type SharedResourceTokenFormValue = {
|
||||
usageLimit: number;
|
||||
};
|
||||
|
||||
const SHARED_RESOURCE_INSTALL_THEME_COLOR = '#0f766e';
|
||||
const SHARED_RESOURCE_INSTALL_BACKGROUND_COLOR = '#f3fbf9';
|
||||
const SHARED_RESOURCE_INSTALL_TITLE = '공유 리소스 관리';
|
||||
|
||||
function buildSharedResourceInstallTitle(isSharedManageMode: boolean) {
|
||||
return isSharedManageMode ? '공유 리소스 관리 링크' : SHARED_RESOURCE_INSTALL_TITLE;
|
||||
}
|
||||
|
||||
const EMPTY_FORM_VALUE: SharedResourceTokenFormValue = {
|
||||
name: '',
|
||||
description: '',
|
||||
@@ -417,6 +433,12 @@ function buildChatConversationUrl(item: Pick<SharedResourceTokenRecord, 'resourc
|
||||
return null;
|
||||
}
|
||||
|
||||
const shareUrl = buildAbsoluteShareUrl(item.sharePath);
|
||||
|
||||
if (shareUrl !== '-') {
|
||||
return shareUrl;
|
||||
}
|
||||
|
||||
const sessionId = resolveChatShareSessionId(item.sharePath || item.resourcePath);
|
||||
|
||||
if (!sessionId) {
|
||||
@@ -470,9 +492,11 @@ function SharedResourceQrPanel({
|
||||
export function SharedResourceManagementPage({
|
||||
sharedPreview = null,
|
||||
sharedAccess = null,
|
||||
disableInstallMetadata = false,
|
||||
}: {
|
||||
sharedPreview?: SharedResourceManagementSharedPreview | null;
|
||||
sharedAccess?: SharedResourceManagementSharedAccess | null;
|
||||
disableInstallMetadata?: boolean;
|
||||
}) {
|
||||
const { message } = App.useApp();
|
||||
const { hasAccess } = useTokenAccess();
|
||||
@@ -491,9 +515,43 @@ export function SharedResourceManagementPage({
|
||||
const [activeDetailTab, setActiveDetailTab] = useState<'basic' | 'settings' | 'history'>('basic');
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
|
||||
const [qrPreviewTokenId, setQrPreviewTokenId] = useState<string | null>(null);
|
||||
const [conversationDrawer, setConversationDrawer] = useState<ConversationDrawerState | null>(null);
|
||||
const [conversationDrawerKey, setConversationDrawerKey] = useState(0);
|
||||
const [form] = Form.useForm<SharedResourceTokenFormValue>();
|
||||
const [modalApi, modalContextHolder] = Modal.useModal();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (disableInstallMetadata || typeof window === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const startPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
const installTitle = buildSharedResourceInstallTitle(isSharedManageMode);
|
||||
const manifestObjectUrl = createInstallManifestObjectUrl({
|
||||
startPath,
|
||||
scope: window.location.pathname,
|
||||
name: installTitle,
|
||||
shortName: '공유 리소스',
|
||||
description: isSharedManageMode
|
||||
? '공유 리소스 관리 링크를 홈 화면 앱으로 바로 엽니다.'
|
||||
: '공유 리소스 관리 화면을 홈 화면 앱으로 바로 엽니다.',
|
||||
themeColor: SHARED_RESOURCE_INSTALL_THEME_COLOR,
|
||||
backgroundColor: SHARED_RESOURCE_INSTALL_BACKGROUND_COLOR,
|
||||
});
|
||||
const restoreManifest = swapInstallDocumentMetadata({
|
||||
manifestHref: manifestObjectUrl,
|
||||
title: installTitle,
|
||||
themeColor: SHARED_RESOURCE_INSTALL_THEME_COLOR,
|
||||
});
|
||||
|
||||
return () => {
|
||||
restoreManifest();
|
||||
if (manifestObjectUrl) {
|
||||
window.URL.revokeObjectURL(manifestObjectUrl);
|
||||
}
|
||||
};
|
||||
}, [disableInstallMetadata, isSharedManageMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = window.setInterval(() => {
|
||||
setNowMs(Date.now());
|
||||
@@ -632,17 +690,26 @@ export function SharedResourceManagementPage({
|
||||
const openConversationWindow = (url: string, event?: ReactMouseEvent<HTMLElement>) => {
|
||||
openExternalLinkInNewWindow(url, {
|
||||
event,
|
||||
allowSameTabFallback: false,
|
||||
onUnsupportedStandalone: (fallbackUrl) => {
|
||||
void copyTextToClipboard(fallbackUrl)
|
||||
.then(() => {
|
||||
message.info('현재 모바일 PWA에서는 preview 앱 열기도 막혀 URL을 복사했습니다. 브라우저에서 붙여 열어 주세요.');
|
||||
message.info('현재 창은 유지하고 공유채팅 URL만 복사했습니다. 브라우저나 새 PWA 창에서 붙여 열어 주세요.');
|
||||
})
|
||||
.catch(() => {
|
||||
message.info('현재 모바일 PWA에서는 새 창과 preview 앱 열기가 막힐 수 있습니다. QR 코드나 URL 복사로 이어서 열어 주세요.');
|
||||
message.info('현재 창은 유지했습니다. 새 창 열기가 막히면 QR 코드나 URL 복사로 이어서 열어 주세요.');
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
const openConversationDrawer = (tokenId: string, tokenName: string, url: string) => {
|
||||
setConversationDrawer({
|
||||
tokenId,
|
||||
tokenName,
|
||||
url,
|
||||
});
|
||||
setConversationDrawerKey((current) => current + 1);
|
||||
};
|
||||
|
||||
const listColumns = useMemo(
|
||||
() => [
|
||||
@@ -740,7 +807,7 @@ export function SharedResourceManagementPage({
|
||||
return;
|
||||
}
|
||||
|
||||
openConversationWindow(conversationUrl, event);
|
||||
openConversationDrawer(item.id, item.name, conversationUrl);
|
||||
}}
|
||||
>
|
||||
열기
|
||||
@@ -796,6 +863,21 @@ export function SharedResourceManagementPage({
|
||||
key: 'detail',
|
||||
render: (value: string | null) => sanitizeActivityDetail(value) ?? '-',
|
||||
},
|
||||
{
|
||||
title: '접속 정보',
|
||||
key: 'ip',
|
||||
render: (_value: unknown, item: SharedResourceTokenActivityRecord) => {
|
||||
const lines = [
|
||||
item.externalIp ? `외부 ${item.externalIp}` : null,
|
||||
item.clientIp ? `서버 ${item.clientIp}` : null,
|
||||
item.forwardedFor ? `XFF ${item.forwardedFor}` : null,
|
||||
item.clientId ? `client ${item.clientId}` : null,
|
||||
].filter(Boolean);
|
||||
return lines.length > 0 ? (
|
||||
<Typography.Text style={{ whiteSpace: 'pre-line' }}>{lines.join('\n')}</Typography.Text>
|
||||
) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '사용량',
|
||||
dataIndex: 'usageDelta',
|
||||
@@ -1148,6 +1230,38 @@ export function SharedResourceManagementPage({
|
||||
<Empty description="QR 코드를 표시할 공유 URL이 없습니다." />
|
||||
)}
|
||||
</Modal>
|
||||
<Drawer
|
||||
open={Boolean(conversationDrawer)}
|
||||
title={conversationDrawer ? `${conversationDrawer.tokenName} 채팅` : '공유 채팅'}
|
||||
placement="right"
|
||||
width="100vw"
|
||||
rootClassName="shared-resource-management-page__conversation-drawer"
|
||||
onClose={() => {
|
||||
setConversationDrawer(null);
|
||||
}}
|
||||
extra={
|
||||
conversationDrawer ? (
|
||||
<Space size={8}>
|
||||
<Button onClick={() => setConversationDrawerKey((current) => current + 1)}>새로고침</Button>
|
||||
<Button onClick={(event) => openConversationWindow(conversationDrawer.url, event)}>새 창</Button>
|
||||
</Space>
|
||||
) : null
|
||||
}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{conversationDrawer ? (
|
||||
<iframe
|
||||
key={`${conversationDrawer.tokenId}-${conversationDrawerKey}`}
|
||||
title={`${conversationDrawer.tokenName} 공유 채팅`}
|
||||
src={conversationDrawer.url}
|
||||
className="shared-resource-management-page__conversation-frame"
|
||||
/>
|
||||
) : null}
|
||||
</Drawer>
|
||||
{detailMode === 'list' ? (
|
||||
<Card
|
||||
title="공유 리소스 관리"
|
||||
@@ -1422,9 +1536,9 @@ export function SharedResourceManagementPage({
|
||||
type="link"
|
||||
icon={<LinkOutlined />}
|
||||
style={{ paddingInline: 0, marginTop: 4 }}
|
||||
onClick={(event) => openConversationWindow(detailConversationUrl, event)}
|
||||
onClick={() => openConversationDrawer(detailData.token.id, detailData.token.name, detailConversationUrl)}
|
||||
>
|
||||
채팅창 새 창 열기
|
||||
채팅창 열기
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user