import { AppstoreOutlined, FileMarkdownOutlined, MessageOutlined, ProfileOutlined } from '@ant-design/icons'; import { Badge } from 'antd'; import type { MenuProps } from 'antd'; import type { ReactNode } from 'react'; import type { PlanFilterStatus } from '../../features/planBoard'; export type TopMenuKey = 'docs' | 'apis' | 'plans' | 'chat' | 'play'; export type HeaderTopMenuKey = 'docs' | 'plans' | 'play'; export type ApiSectionKey = 'components' | 'widgets'; export type PlanSectionKey = | PlanFilterStatus | 'release' | 'release-review' | 'board' | 'charts' | 'schedule' | 'history' | 'automation-type' | 'automation-context' | 'token-setting' | 'shared-resource' | 'server-command'; export type ChatSectionKey = 'live' | 'rooms' | 'changes' | 'resources' | 'errors' | 'manage' | 'manage-defaults' | 'manage-share'; export type PlaySectionKey = 'layout' | 'draw' | 'apps' | 'test' | 'cbt'; export type PlaySidebarKey = PlaySectionKey | `layout-record:${string}`; export const CHAT_SECTION_GROUP_LABELS: Record = { live: 'Codex Live', rooms: '시스템 채팅', changes: 'Codex Live', resources: '리소스 관리', errors: '앱로그', manage: '채팅 관리', 'manage-defaults': '채팅 관리', 'manage-share': '채팅 관리', }; export const CHAT_SECTION_LABELS: Record = { live: 'Codex Live', rooms: '시스템 채팅', changes: '변경 이력', resources: '리소스 관리', errors: '에러 로그', manage: '유형 권한 관리', 'manage-defaults': '공통 문맥 관리', 'manage-share': '공유채팅 생성', }; export const DOCS_DEFAULT_FOLDER = 'project'; export const PLAY_LAYOUT_RECORD_PREFIX = 'layout-record:' as const; export const PLAN_MENU_STATUS_ORDER: PlanFilterStatus[] = ['all', 'in-progress', 'error', 'done']; export const PLAN_GROUP_LABEL = '작업'; export const DOCS_FOLDER_LABELS: Record = { project: '프로젝트 구조', }; export const PLAN_FILTER_LABELS: Record = { all: '자동화 현황', 'in-progress': '실행 중 (0)', done: '완료', error: '실패 (0)', }; export const PLAN_SIDEBAR_LABELS: Record = { ...PLAN_FILTER_LABELS, release: 'release (0)', 'release-review': 'release 검수', board: '작업 요청', charts: '차트', schedule: '스케줄', history: '이력', 'automation-type': '자동화 유형', 'automation-context': 'Context 유형', 'token-setting': '설정', 'shared-resource': '공유 리소스 관리', 'server-command': 'Command', }; export const PLAY_SIDEBAR_LABELS: Record = { layout: 'Layout Editor', draw: 'Layout Draw', apps: 'Apps', test: 'Test App', cbt: 'CBT', }; export const PLAN_MENU_ANCHOR_IDS: Partial> = { all: 'plan-menu-all', 'in-progress': 'plan-menu-in-progress', release: 'plan-menu-release', 'release-review': 'plan-menu-release-review', done: 'plan-menu-done', error: 'plan-menu-error', board: 'plan-menu-board', charts: 'plan-menu-charts', schedule: 'plan-menu-schedule', history: 'plan-menu-history', 'automation-type': 'plan-menu-automation-type', 'automation-context': 'plan-menu-automation-context', 'token-setting': 'plan-menu-token-setting', 'shared-resource': 'plan-menu-shared-resource', 'server-command': 'plan-menu-server-command', }; export function getDocsSectionLabel(section: string) { return DOCS_FOLDER_LABELS[section] ?? section; } export function resolveSavedLayoutMenuKey(layoutId: string): PlaySidebarKey { return `${PLAY_LAYOUT_RECORD_PREFIX}${layoutId}`; } export function resolveSavedLayoutIdFromMenuKey(key: PlaySidebarKey) { return key.startsWith(PLAY_LAYOUT_RECORD_PREFIX) ? key.slice(PLAY_LAYOUT_RECORD_PREFIX.length) : null; } export function resolvePlaySidebarLabel(selectedPlayMenu: PlaySidebarKey, savedLayouts: Array<{ id: string; name: string }>) { const savedLayoutId = resolveSavedLayoutIdFromMenuKey(selectedPlayMenu); if ( selectedPlayMenu === 'layout' || selectedPlayMenu === 'draw' || selectedPlayMenu === 'apps' || selectedPlayMenu === 'test' || selectedPlayMenu === 'cbt' ) { return PLAY_SIDEBAR_LABELS[selectedPlayMenu]; } return savedLayouts.find((record) => record.id === savedLayoutId)?.name ?? 'Saved Layout'; } export function renderPlanMenuLabel(menu: PlanSectionKey, label: string) { const anchorId = PLAN_MENU_ANCHOR_IDS[menu]; if (!anchorId) { return label; } return {label}; } export function buildDocsPath(folder = DOCS_DEFAULT_FOLDER) { return `/docs/${folder}`; } export function buildApisPath(section: ApiSectionKey = 'components') { return `/apis/${section}`; } export function buildPlansPath(section: PlanSectionKey = 'all') { return `/plans/${section}`; } export function buildChatPath(section: ChatSectionKey = 'live') { return `/chat/${section}`; } export function buildPlayPath(section: PlaySectionKey = 'layout') { return `/play/${section}`; } function normalizePlayAppReturnToPath(returnTo: string | null | undefined) { if (!returnTo || !returnTo.startsWith('/') || returnTo.startsWith('//')) { return null; } return returnTo; } export function buildPlayAppPath( appId: string, launchContext: 'direct' | 'embedded' = 'direct', returnTo?: string | null, ) { const searchParams = new URLSearchParams({ app: appId, }); if (launchContext === 'embedded') { searchParams.set('launchContext', launchContext); } const normalizedReturnTo = normalizePlayAppReturnToPath(returnTo); if (normalizedReturnTo) { searchParams.set('returnTo', normalizedReturnTo); } return `/play/apps?${searchParams.toString()}`; } export function buildSavedLayoutPath(layoutId: string) { return `/play/layout-record/${layoutId}`; } type RenderMenuLabelParams = { key: string; label: string; }; function renderMenuLabel({ key, label }: RenderMenuLabelParams) { return {label}; } export function buildDocsMenuItems(docFolders: string[]): MenuProps['items'] { return [ { key: 'docs-group', icon: , label: 'Docs', children: docFolders.map((folder) => ({ key: folder, label: DOCS_FOLDER_LABELS[folder] ?? folder, })), }, ]; } export function buildApiMenuItems(): MenuProps['items'] { return [ { key: 'api-group', icon: , label: 'APIs', children: [ { key: 'components', label: 'Components' }, { key: 'widgets', label: 'Widgets' }, ], }, ]; } export function buildPlanMenuItems(hasAccess = true): MenuProps['items'] { if (!hasAccess) { return []; } return [ { key: 'plan-group', icon: , label: PLAN_GROUP_LABEL, children: [ { key: 'all', label: renderPlanMenuLabel('all', PLAN_FILTER_LABELS.all), }, { key: 'board', label: renderPlanMenuLabel('board', PLAN_SIDEBAR_LABELS.board), }, { key: 'release-review', label: renderPlanMenuLabel('release-review', PLAN_SIDEBAR_LABELS['release-review']), }, { key: 'charts', label: renderPlanMenuLabel('charts', PLAN_SIDEBAR_LABELS.charts), }, { key: 'schedule', label: renderPlanMenuLabel('schedule', PLAN_SIDEBAR_LABELS.schedule), }, { key: 'history', label: renderPlanMenuLabel('history', PLAN_SIDEBAR_LABELS.history), }, { key: 'automation-type', label: renderPlanMenuLabel('automation-type', PLAN_SIDEBAR_LABELS['automation-type']), }, { key: 'automation-context', label: renderPlanMenuLabel('automation-context', PLAN_SIDEBAR_LABELS['automation-context']), }, ], }, { key: 'token-management-group', icon: , label: '토큰관리', children: [ { key: 'token-setting', label: renderPlanMenuLabel('token-setting', PLAN_SIDEBAR_LABELS['token-setting']), }, { key: 'shared-resource', label: renderPlanMenuLabel('shared-resource', PLAN_SIDEBAR_LABELS['shared-resource']), }, ], }, { key: 'server-group', icon: , label: 'Servers', children: [ { key: 'server-command', label: renderPlanMenuLabel('server-command', PLAN_SIDEBAR_LABELS['server-command']), }, ], }, ]; } function renderChatUnreadLabel(label: string, unreadCount: number) { if (unreadCount <= 0) { return label; } return ( {label} ); } export function buildChatMenuItems(_hasAccess = true, unreadCount = 0): MenuProps['items'] { return [ { key: 'chat-group', icon: , label: renderChatUnreadLabel('채팅', unreadCount), children: [ { key: 'live', label: 'Codex Live' }, { key: 'rooms', label: '시스템 채팅' }, { key: 'changes', label: '변경 이력' }, { key: 'resources', label: '리소스 관리' }, ], }, { key: 'app-log-group', icon: , label: '앱로그', children: [{ key: 'errors', label: '에러 로그' }], }, { key: 'chat-manage-group', icon: , label: '채팅 관리', children: [ { key: 'manage', label: '유형 권한 관리' }, { key: 'manage-defaults', label: '공통 문맥 관리' }, { key: 'manage-share', label: '공유채팅 생성' }, ], }, ]; } export function buildPlayMenuItems(savedLayouts: Array<{ id: string; name: string }>): MenuProps['items'] { return [ { key: 'play-group', icon: , label: 'Play', children: [ { key: 'play-layout-group', label: 'Layout', children: [ { key: 'layout', label: 'Layout Editor' }, { key: 'draw', label: 'Layout Draw' }, ...(savedLayouts.length ? savedLayouts.map((record) => ({ key: resolveSavedLayoutMenuKey(record.id), label: record.name, })) : [{ key: 'saved-layout-empty', label: '저장된 레이아웃 없음', disabled: true }]), ], }, { key: 'play-apps-group', label: 'Apps', children: [ { key: 'apps', label: 'Apps' }, { key: 'test', label: 'Test App' }, { key: 'play-apps-general-group', label: '일반', children: [{ key: 'cbt', label: 'CBT' }], }, ], }, ], }, ]; } export function resolvePlanOpenKeys() { return ['plan-group', 'token-management-group', 'server-group', 'chat-group', 'app-log-group', 'chat-manage-group']; } export function resolvePlayOpenKeys() { return ['play-group', 'play-layout-group', 'play-apps-group', 'play-apps-general-group']; } export function resolvePlanQuickFilterMenu(filter: 'working' | 'release-pending-main' | 'automation-failed') { if (filter === 'working') { return 'in-progress' as const; } return filter === 'release-pending-main' ? ('release' as const) : ('error' as const); } export function renderSidebarIntro(activeTopMenu: TopMenuKey) { const isDocsGroup = activeTopMenu === 'docs' || activeTopMenu === 'apis'; return { color: isDocsGroup ? 'gold' : activeTopMenu === 'play' ? 'cyan' : 'green', tag: isDocsGroup ? 'Docs' : activeTopMenu === 'play' ? 'Play' : '자동화', description: isDocsGroup ? '사이드바에서 Docs와 APIs를 함께 탐색합니다.' : activeTopMenu === 'play' ? '사이드바에서 Play 화면을 탐색합니다.' : '사이드바에서 작업, Codex Live, 앱로그를 함께 전환합니다.', }; } export function resolveCurrentPageDescriptor(params: { topMenu: TopMenuKey; docsMenu: string; apiMenu: ApiSectionKey; planMenu: PlanSectionKey; chatMenu: ChatSectionKey; playMenu: PlaySidebarKey; savedLayouts: Array<{ id: string; name: string }>; }) { const { topMenu, docsMenu, apiMenu, planMenu, chatMenu, playMenu, savedLayouts } = params; if (topMenu === 'docs') { return { id: `docs:${docsMenu}`, title: `Docs / ${getDocsSectionLabel(docsMenu)}`, topMenu, section: docsMenu, }; } if (topMenu === 'apis') { return { id: `apis:${apiMenu}`, title: apiMenu === 'components' ? 'APIs / Components' : 'APIs / Widgets', topMenu, section: apiMenu, }; } if (topMenu === 'plans') { const title = planMenu === 'server-command' ? `Servers / ${PLAN_SIDEBAR_LABELS[planMenu]}` : planMenu === 'token-setting' || planMenu === 'shared-resource' ? `토큰관리 / ${PLAN_SIDEBAR_LABELS[planMenu]}` : `${PLAN_GROUP_LABEL} / ${PLAN_SIDEBAR_LABELS[planMenu]}`; return { id: `plans:${planMenu}`, title, topMenu, section: planMenu, }; } if (topMenu === 'chat') { const title = `${CHAT_SECTION_GROUP_LABELS[chatMenu]} / ${CHAT_SECTION_LABELS[chatMenu]}`; return { id: `app-log:${chatMenu}`, title, topMenu, section: chatMenu, }; } return { id: `play:${playMenu}`, title: `Play / ${resolvePlaySidebarLabel(playMenu, savedLayouts)}`, topMenu, section: playMenu, }; } export function resolveTopMenuPath(menu: HeaderTopMenuKey, currentDocsFolder: string) { if (menu === 'docs') { return buildDocsPath(currentDocsFolder); } if (menu === 'plans') { return buildPlansPath('all'); } if (menu === 'play') { return buildPlayPath('layout'); } return buildPlansPath('all'); } export function createPageWindowId(topMenu: TopMenuKey, section: string) { return `page:${topMenu}:${section}`; } export function renderMenuAnchor(label: string, anchorId: string): ReactNode { return renderMenuLabel({ key: anchorId, label }); }