From 58606b7eabe5ac82fc408abd4108e44d473248dd Mon Sep 17 00:00:00 2001 From: dsyoon Date: Sat, 27 Dec 2025 14:07:27 +0900 Subject: [PATCH] init --- .gitignore | 3 + .idea/.gitignore | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/ncuetalk_frontend.iml | 8 + .idea/vcs.xml | 6 + README.md | 97 +- favicon.ico | Bin 0 -> 17342 bytes index.html | 14 + package-lock.json | 1843 +++++++++++++++++ package.json | 20 + public/favicon.svg | 1 + src/App.jsx | 119 ++ src/components/Sidebar.jsx | 45 + src/config.js | 3 + src/context/AuthContext.jsx | 53 + src/context/ToolContext.jsx | 94 + src/main.jsx | 9 + src/pages/AiNewsPage.jsx | 171 ++ src/pages/ChatPage.jsx | 553 +++++ src/pages/HomePage.jsx | 275 +++ src/pages/LecturePage.jsx | 106 + src/pages/LoginPage.jsx | 39 + src/pages/QnaPage.jsx | 117 ++ src/pages/ToolsPage.jsx | 63 + src/services/openaiService.js | 72 + src/style.css | 12 + src/tools/dev_chatbot/AIService.js | 29 + src/tools/dev_chatbot/ChatHandler.js | 20 + src/tools/dev_chatbot/ChatInput.jsx | 158 ++ src/tools/dev_chatbot/OCRService.js | 14 + src/tools/dev_chatbot/constants.js | 3 + style.css | 1139 ++++++++++ vite.config.mjs | 17 + 35 files changed, 5133 insertions(+), 1 deletion(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/ncuetalk_frontend.iml create mode 100644 .idea/vcs.xml create mode 100644 favicon.ico create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/favicon.svg create mode 100644 src/App.jsx create mode 100644 src/components/Sidebar.jsx create mode 100644 src/config.js create mode 100644 src/context/AuthContext.jsx create mode 100644 src/context/ToolContext.jsx create mode 100644 src/main.jsx create mode 100644 src/pages/AiNewsPage.jsx create mode 100644 src/pages/ChatPage.jsx create mode 100644 src/pages/HomePage.jsx create mode 100644 src/pages/LecturePage.jsx create mode 100644 src/pages/LoginPage.jsx create mode 100644 src/pages/QnaPage.jsx create mode 100644 src/pages/ToolsPage.jsx create mode 100644 src/services/openaiService.js create mode 100644 src/style.css create mode 100644 src/tools/dev_chatbot/AIService.js create mode 100644 src/tools/dev_chatbot/ChatHandler.js create mode 100644 src/tools/dev_chatbot/ChatInput.jsx create mode 100644 src/tools/dev_chatbot/OCRService.js create mode 100644 src/tools/dev_chatbot/constants.js create mode 100644 style.css create mode 100644 vite.config.mjs diff --git a/.gitignore b/.gitignore index 0dbf2f2..8ef1703 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ # ---> Python +dist/* +node_modules/* + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..812ab5a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b6461af --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/ncuetalk_frontend.iml b/.idea/ncuetalk_frontend.iml new file mode 100644 index 0000000..8388dbc --- /dev/null +++ b/.idea/ncuetalk_frontend.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 3a624b5..93d5ea3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,97 @@ -# ncuetalk_frontend +# 엔큐톡 프론트엔드 (React + Vite) +엔큐톡(ncuetalk)은 다양한 AI 엔진(챗봇·PDF QA·Text-to-SQL 등)을 한 화면에서 사용할 수 있게 해 주는 통합 프론트엔드입니다. 기존 jQuery SPA 구조를 **React 18 / Vite 7** 기반으로 전면 개편하였습니다. + +## 폴더 구조 + +``` +ncuetalk_frontend/ +├── index.html # Vite 진입점 (root div 및 module script) +├── style.css # 전역 스타일 – 기존 CSS 그대로 유지 +├── package.json # 의존성 & 스크립트 +├── vite.config.js # Vite 설정 +├── src/ # React 소스 코드 +│ ├── main.jsx # React DOM 마운트 +│ ├── App.jsx # 라우팅·레이아웃 컨테이너 +│ ├── context/ +│ │ └── ToolContext.jsx # 도구 상태 전역 관리(Context API) +│ ├── components/ +│ │ └── Sidebar.jsx # 좌측 메뉴 바 +│ ├── pages/ # 상단 메뉴별 화면 +│ │ ├── ChatPage.jsx # 채팅 화면(일반/iframe/dev_chatbot) +│ │ ├── ToolsPage.jsx # 도구 목록/필터/즐겨찾기 +│ │ ├── LecturePage.jsx # 강의 모음(정적) +│ │ └── CommunityPage.jsx# 커뮤니티(정적) +│ └── tools/ # 개별 도구 전용 컴포넌트·헬퍼 +│ └── dev_chatbot/ +│ ├── constants.js # 백엔드 API base URL 등 +│ ├── AIService.js # /chat 호출 래퍼 +│ ├── OCRService.js # Tesseract.js 기반 OCR +│ ├── ChatHandler.js # 파일/OCR 처리 후 AIService 호출 +│ └── ChatInput.jsx # dev_chatbot 전용 채팅 UI +└── frontend/ # (구)폴더 – iframe 전용 도구의 README만 보관 + ├── lims_text2sql/ + └── research_qa/ +``` + +### 주요 흐름 +1. **ToolsPage** 가 `/tools` API를 조회해 카드 목록을 표시 +2. 카드 클릭 → 선택 도구가 `ToolContext` 에 저장되며 `ChatPage` 로 이동 +3. ChatPage + * dev_chatbot → `DevChatInput` 렌더링 (OCR + 멀티파트 업로드 지원) + * research_qa / lims_text2sql → 외부 URL을 iframe 으로 임베드 + * 그 외 → 일반 채팅 UI (textarea + API `/chat` 호출) + +## 환경 변수 설정 + +프로젝트 루트 또는 쉘 설정(`~/.zshrc`)에 다음 값을 지정하세요. + +```bash +# OpenAI Key (필수) – 브라우저에서 직접 호출할 때 사용 +OPENAI_API_KEY="sk-..." +``` + +Vite 설정에서 `envPrefix` 를 `OPENAI_` 로 포함했으므로 `VITE_` 접두어 없이도 노출됩니다. + +## 실행 +```bash +nvm install 20 +nvm use 20 +node -v # 20.x 확인 +npm install +npm run dev # 개발 서버 (http://localhost:5173) +``` + +## 주요 기능 +1. React 18 + Vite 7 SPA 구조 +2. ChatGPT 도구 + - 모델 선택: GPT-4o(기본), GPT-4.1-mini, o4-mini + - 긴 한국어 답변 + 이모지 + Markdown 렌더링 + - 입력 후 "생각중… (경과초)" 로딩 표시 +3. 개발챗봇 도구 + - PDF 파일 사이드바(업로드·삭제) + - 이미지 OCR(Tesseract.js) 지원 +4. 강의·커뮤니티 페이지 정적 콘텐츠 표시 + +## 의존성 주요 목록 +| 라이브러리 | 용도 | +|------------|------| +| React 18 | UI | +| Vite 7 | 번들러 | +| marked 5 | Markdown → HTML 변환 | +| tesseract.js| 이미지 OCR | + +## 빌드 & 미리보기 +```bash +npm run build +npm run preview # http://localhost:4173 +``` + +## 빌드 산출물 구조 +프로덕션 빌드시 `dist/` 폴더가 생성되며, 정적 파일을 Nginx·S3 등에 바로 서빙할 수 있습니다. + +## 기여 & 문의 +Pull Request 환영합니다. 버그·제안은 Issue로 등록해 주세요. + +--- +© 2025 Daewoong AI Lab – All rights reserved. diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..766b1a6806c4758bda8a43326587cd499ec0e761 GIT binary patch literal 17342 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 + + + + + NCue Recipe Engine + + + + +
+ + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a8a4fda --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1843 @@ +{ + "name": "ncuetalk-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ncuetalk-frontend", + "version": "1.0.0", + "dependencies": { + "marked": "^5.1.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tesseract.js": "^5.1.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.0.4", + "vite": "^7.0.5" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, + "node_modules/browserslist": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", + "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001733", + "electron-to-chromium": "^1.5.199", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.203", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz", + "integrity": "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/idb-keyval": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", + "license": "Apache-2.0" + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/marked": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-5.1.2.tgz", + "integrity": "sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tesseract.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-5.1.1.tgz", + "integrity": "sha512-lzVl/Ar3P3zhpUT31NjqeCo1f+D5+YfpZ5J62eo2S14QNVOmHBTtbchHm/YAbOOOzCegFnKf4B3Qih9LuldcYQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-electron": "^2.2.2", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^5.1.1", + "wasm-feature-detect": "^1.2.11", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-5.1.1.tgz", + "integrity": "sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ==", + "license": "Apache-2.0" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "license": "MIT", + "engines": { + "node": "*" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6685f69 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "ncuetalk-frontend", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite build && vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tesseract.js": "^5.1.0", + "marked": "^5.1.2" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.0.4", + "vite": "^7.0.5" + } +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..efe83d9 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,119 @@ +import React, { useEffect, useState } from 'react'; +import Sidebar from './components/Sidebar'; +import HomePage from './pages/HomePage'; +import ChatPage from './pages/ChatPage'; +import ToolsPage from './pages/ToolsPage'; +import LecturePage from './pages/LecturePage'; +import AiNewsPage from './pages/AiNewsPage'; +import QnaPage from './pages/QnaPage'; +import { ToolProvider } from './context/ToolContext'; +import { AuthProvider, useAuth } from './context/AuthContext'; +import LoginPage from './pages/LoginPage'; +import { useTool } from './context/ToolContext'; + +function TopbarTitle({ currentPage, onNavigate }) { + const { selectedTool } = useTool(); + const { user, logout } = useAuth(); + const titleMap = { + home: '', + chat: '데모', + tools: '도구', + lecture: '교육', + ai_news: '인사이트', + qna: 'FAQ', + login: '로그인', + }; + const title = currentPage === 'chat' ? (selectedTool?.name || titleMap.chat) : (titleMap[currentPage] || ''); + return ( +
+ {title} + +
+ ); +} + +function UserBadge({ user, onLogout, onNavigate }) { + const [open, setOpen] = React.useState(false); + if (!user) return
{ e.preventDefault(); onNavigate('login'); }}>로그인; + return ( +
+ + {open && ( +
+
{user.email}
+ +
+ )} +
+ ); +} + +export default function App() { + const [currentPage, setCurrentPage] = useState('home'); + + const PAGES = { + home: , + chat: , + tools: , + lecture: , + // community 제거 + login: { handleNavigate('chat'); }} />, + ai_news: , + qna: , + }; + + // URL → 상태 동기화 (초기 진입 및 뒤로가기 대응) + useEffect(() => { + const path = window.location.pathname.replace(/^\/+/, ''); + const menuPaths = { '': 'home', chatting: 'chat', tools: 'tools', lecture: 'lecture', ai_news: 'ai_news', qna: 'qna', login: 'login' }; + const maybeMenu = menuPaths[path]; + if (maybeMenu) { + setCurrentPage(maybeMenu); + } else { + // 카드 경로일 때는 채팅 페이지로 전환 + setCurrentPage('chat'); + } + }, []); + + // 브라우저 뒤/앞으로가기(popstate) 시 URL에 맞게 화면 전환 + useEffect(() => { + const onPopState = () => { + const path = window.location.pathname.replace(/^\/+/, ''); + const menuPaths = { '': 'home', chatting: 'chat', tools: 'tools', lecture: 'lecture', ai_news: 'ai_news', qna: 'qna', login: 'login' }; + const maybeMenu = menuPaths[path]; + if (maybeMenu) { + setCurrentPage(maybeMenu); + } else { + // 카드 경로는 채팅 화면으로 + setCurrentPage('chat'); + } + }; + window.addEventListener('popstate', onPopState); + return () => window.removeEventListener('popstate', onPopState); + }, []); + + // 메뉴 전환 시 URL 경로 갱신 + function handleNavigate(page) { + setCurrentPage(page); + const menuToPath = { home: '', chat: 'chatting', tools: 'tools', lecture: 'lecture', ai_news: 'ai_news', qna: 'qna', login: 'login' }; + const p = menuToPath[page] || ''; + const newPath = `/${p}`; + if (window.location.pathname !== newPath) { + window.history.pushState({}, '', newPath); + } + } + + return ( + + +
+ +
+ + {PAGES[currentPage]} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx new file mode 100644 index 0000000..19448ce --- /dev/null +++ b/src/components/Sidebar.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { useTool } from '../context/ToolContext'; + +const MENU = [ + { id: 'home', label: '홈', sub: 'Engine' }, + { id: 'chat', label: '데모', sub: 'Playground' }, + { id: 'tools', label: '도구', sub: 'Tools' }, + { id: 'lecture', label: '교육', sub: 'Workshops' }, + { id: 'ai_news', label: '인사이트', sub: 'Insights' }, + { id: 'qna', label: 'FAQ', sub: 'Support' }, +]; + +export default function Sidebar({ current, onChange }) { + const { setSelectedTool } = useTool(); + return ( + + ); +} \ No newline at end of file diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..3034528 --- /dev/null +++ b/src/config.js @@ -0,0 +1,3 @@ +export const API_BASE_URL = + import.meta.env.VITE_API_BASE_URL || + `${window.location.protocol}//${window.location.hostname}:8010`; diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx new file mode 100644 index 0000000..c42c2e0 --- /dev/null +++ b/src/context/AuthContext.jsx @@ -0,0 +1,53 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { API_BASE_URL } from '../config'; + +const AuthContext = createContext(); + +export function AuthProvider({ children }) { + const [user, setUser] = useState(null); // { user_id, email } + const [loading, setLoading] = useState(true); + + useEffect(() => { + try { + const saved = JSON.parse(localStorage.getItem('auth_user') || 'null'); + if (saved && saved.user_id) setUser(saved); + } catch {} + setLoading(false); + }, []); + + const login = async (email, password) => { + const res = await fetch(`${API_BASE_URL}/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ email, password }), + }); + if (!res.ok) { + // 서버가 에러 메시지를 JSON 으로 보낼 수도 있으므로 파싱 시도 + let msg = '로그인 실패'; + try { msg = (await res.json())?.detail || msg; } catch { /* ignore */ } + throw new Error(msg); + } + const data = await res.json(); // { user_id, email } + setUser(data); + try { localStorage.setItem('auth_user', JSON.stringify(data)); } catch {} + return data; + }; + + const logout = async () => { + try { + await fetch(`${API_BASE_URL}/auth/logout`, { method: 'POST', credentials: 'include' }); + } catch {} + setUser(null); + try { localStorage.removeItem('auth_user'); } catch {} + }; + + const value = { user, loading, login, logout }; + return {children}; +} + +export function useAuth() { + return useContext(AuthContext); +} + + diff --git a/src/context/ToolContext.jsx b/src/context/ToolContext.jsx new file mode 100644 index 0000000..82c9147 --- /dev/null +++ b/src/context/ToolContext.jsx @@ -0,0 +1,94 @@ +import React, { createContext, useState, useContext, useEffect } from 'react'; +import { API_BASE_URL } from '../config'; + +const ToolContext = createContext(); + +export function ToolProvider({ children }) { + const [tools, setTools] = useState([]); + const [selectedTool, setSelectedTool] = useState(null); + const [favorites, setFavorites] = useState(() => { + try { + return JSON.parse(localStorage.getItem('favorites')) || []; + } catch { + return []; + } + }); + + useEffect(() => { + async function load() { + try { + const res = await fetch(`${API_BASE_URL}/tools`); + const data = await res.json(); + // 'GxP 챗봇'과 'chatgpt' 카드는 도구 목록에서 제거 + const filtered = (Array.isArray(data) ? data : []).filter((tool) => !['chatbot_gxp','chatgpt','lims_text2sql','research_qa'].includes(tool.id)); + const mapped = filtered.map((tool) => { + const category = ['전체']; + if (['dev_chatbot', 'doc_translation'].includes(tool.id)) { + category.push('오픈AI'); + } + return { ...tool, category }; + }); + setTools(mapped); + // 선택된 도구 복원 (마지막 카드 유지) + try { + const savedId = localStorage.getItem('selectedToolId'); + if (savedId) { + const found = mapped.find((t) => t.id === savedId); + if (found) setSelectedTool(found); + } + // URL 경로가 카드 ID일 경우 해당 카드로 강제 설정 (예: /dev_chatbot) + const path = window.location.pathname.replace(/^\/+/, ''); + const menuPaths = new Set(['chatting', 'tools', 'lecture', 'community', '']); + if (path && !menuPaths.has(path)) { + const fromUrl = mapped.find((t) => t.id === path); + if (fromUrl) setSelectedTool(fromUrl); + } + } catch {} + // 즐겨찾기에 남아 있을 수 있는 제거 대상 정리 + setFavorites((prev) => prev.filter((id) => !['chatbot_gxp','chatgpt','lims_text2sql','research_qa'].includes(id))); + } catch (_) {} + } + load(); + }, []); + + useEffect(() => { + localStorage.setItem('favorites', JSON.stringify(favorites)); + }, [favorites]); + + // 선택된 도구 지속화: 메뉴 이동 후에도 마지막 카드가 유지되도록 저장 + useEffect(() => { + try { + if (selectedTool?.id) localStorage.setItem('selectedToolId', selectedTool.id); + } catch {} + }, [selectedTool?.id]); + + const toggleFavorite = (id) => { + setFavorites((prev) => (prev.includes(id) ? prev.filter((f) => f !== id) : [...prev, id])); + }; + + // 카드 선택 시 URL을 카드 경로로 반영 (메뉴 페이지가 아닌 경우) + useEffect(() => { + if (!selectedTool?.id) return; + const path = `/${selectedTool.id}`; + const menuPaths = new Set(['/chatting','/tools','/lecture','/community','/']); + if (!menuPaths.has(window.location.pathname) && window.location.pathname === path) return; + // 도구 화면에서 카드 선택 시 URL을 카드 경로로 업데이트 + if (window.location.pathname !== path) { + window.history.pushState({}, '', path); + } + }, [selectedTool?.id]); + + const value = { + tools, + selectedTool, + setSelectedTool, + favorites, + toggleFavorite, + }; + + return {children}; +} + +export function useTool() { + return useContext(ToolContext); +} \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..4f57b72 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +); \ No newline at end of file diff --git a/src/pages/AiNewsPage.jsx b/src/pages/AiNewsPage.jsx new file mode 100644 index 0000000..0dfd6bf --- /dev/null +++ b/src/pages/AiNewsPage.jsx @@ -0,0 +1,171 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useAuth } from '../context/AuthContext'; +import { API_BASE_URL } from '../config'; + +export default function AiNewsPage() { + const { user } = useAuth(); + const [news, setNews] = useState([]); + const [newsOffset, setNewsOffset] = useState(0); + const [newsLoading, setNewsLoading] = useState(false); + const [showNewsEditor, setShowNewsEditor] = useState(false); + const [newsUrl, setNewsUrl] = useState(''); + const contentRef = useRef(null); + const sentinelRef = useRef(null); + + useEffect(() => { + (async () => { + await loadNews(0, false); + // 최초 로드 후 최신 위치로 한 번 스크롤 + requestAnimationFrame(() => { + const el = contentRef.current; + if (el) el.scrollTop = el.scrollHeight; + }); + })(); + }, []); + + // IntersectionObserver로 상단 도달 시 로드 + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && !newsLoading) { + loadNews(newsOffset, true); + } + }, + { root: contentRef.current, threshold: 0.1 } + ); + if (sentinelRef.current) observer.observe(sentinelRef.current); + return () => observer.disconnect(); + }, [newsOffset, newsLoading]); + + const loadNews = async (offset = 0, prepend = false) => { + if (newsLoading) return; + setNewsLoading(true); + try { + const res = await fetch(`${API_BASE_URL}/community/ai_news?offset=${offset}&limit=10`); + if (!res.ok) return; + const data = await res.json(); + const items = (data.items || []).reverse(); + if (prepend) { + const el = contentRef.current; + const prevH = el ? el.scrollHeight : document.documentElement.scrollHeight; + setNews((prev) => [...items, ...prev]); + setTimeout(() => { + if (el) { + el.scrollTop = el.scrollHeight - prevH; + } else { + const newH = document.documentElement.scrollHeight; + window.scrollTo(0, newH - prevH); + } + }, 0); + } else { + setNews(items); + setTimeout(() => { + const el = contentRef.current; + if (el) el.scrollTop = el.scrollHeight; else window.scrollTo(0, document.documentElement.scrollHeight); + }, 0); + } + const nextOffset = data.nextOffset || 0; + setNewsOffset(nextOffset); + return nextOffset; + } finally { + setNewsLoading(false); + } + }; + + // 기존 스크롤 보정 루프 제거 – IntersectionObserver 사용 + + const submitNews = async () => { + if (!newsUrl.trim()) return; + await fetch(`${API_BASE_URL}/community/ai_news`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: newsUrl, author_id: String(user?.user_id || ''), author_email: user?.email || null }), + }); + setShowNewsEditor(false); + setNewsUrl(''); + loadNews(0); + }; + + return ( +
+
+
+

인사이트

+

업계 동향/레퍼런스 링크를 모아두고, 팀/고객과 빠르게 공유합니다.

+
+
+
+
+
+ {news.map((n) => ( +
+
+ {n.meta?.image ? ( + thumb + ) : null} +
+ + {n.meta?.title || n.url} + +
{n.meta?.description}
+
{new Date(n.created_at).toISOString().slice(0, 10)}
+
+
+
+ ))} +
+ {newsLoading && ( +
+ 뉴스 로딩중 + + + + + +
+ )} + {user && !showNewsEditor && ( + + )} + {showNewsEditor && user && ( +
+ setNewsUrl(e.target.value)} + placeholder="뉴스 URL을 입력하세요" + style={{ width: 'calc(100% - 25px)', marginBottom: 8, padding: '10px 12px', borderRadius: 8, border: '1px solid #ffd6c2' }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + submitNews(); + } + }} + /> +
+ + +
+
+ )} +
+
+ ); +} + + diff --git a/src/pages/ChatPage.jsx b/src/pages/ChatPage.jsx new file mode 100644 index 0000000..29b7a4a --- /dev/null +++ b/src/pages/ChatPage.jsx @@ -0,0 +1,553 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { useTool } from '../context/ToolContext'; +import DevChatInput from '../tools/dev_chatbot/ChatInput.jsx'; +import { marked } from 'marked'; +marked.setOptions({ mangle:false, headerIds:false }); +import { fetchOpenAIChat, fetchOpenAIImage, classifyRequest } from '../services/openaiService'; +import { useAuth } from '../context/AuthContext'; +import { API_BASE_URL } from '../config'; + +// === Chat History Panel 컴포넌트 추가 === +function ChatHistoryPanel({ toolId }) { + const [open, setOpen] = React.useState(false); + const [messages, setMessages] = React.useState([]); + + // 패널이 열릴 때마다 로컬스토리지에서 메시지 불러오기 + React.useEffect(() => { + if (!open) return; + try { + const saved = JSON.parse(localStorage.getItem(`messages_${toolId}`) || '[]'); + setMessages(Array.isArray(saved) ? saved : []); + } catch { + setMessages([]); + } + }, [toolId, open]); + + return ( + <> + +
+
대화 히스토리
+
    + {messages.length === 0 && ( +
  • 대화 기록이 없습니다.
  • + )} + {messages.map((m, idx) => ( +
  • +
    {String(m.content).slice(0, 36)}{String(m.content).length > 36 ? '…' : ''}
    +
    {m.time}
    +
  • + ))} +
+
+ + ); +} + +function FileSidebar() { + const [files, setFiles] = useState([]); + + const loadFiles = async () => { + try { + const res = await fetch(`${API_BASE_URL}/files`); + const data = await res.json(); + setFiles(data.files || []); + } catch (_) {} + }; + + useEffect(() => { + loadFiles(); + }, []); + + const handleDelete = async (fname) => { + if (!window.confirm(`${fname} 파일을 삭제하시겠습니까?`)) return; + await fetch(`${API_BASE_URL}/file?filename=${encodeURIComponent(fname)}`, { method: 'DELETE' }); + loadFiles(); + }; + + const handleUpload = async (e) => { + const files = Array.from(e.target.files || []); + if (!files.length) return; + const formData = new FormData(); + files.forEach((f) => formData.append('files', f)); + await fetch(`${API_BASE_URL}/upload_pdf`, { method: 'POST', body: formData }); + loadFiles(); + }; + + return ( +
+
+ 파일 리스트 + + +
+
    + {files.map((f) => ( +
  • + {f} + +
  • + ))} +
+
+ ); +} + +export default function ChatPage() { + const { selectedTool } = useTool(); + const { user } = useAuth(); + const [messages, setMessages] = useState(() => { + try { + const toolIdInitial = selectedTool?.id || 'chatgpt'; + const saved = JSON.parse(localStorage.getItem(`messages_${toolIdInitial}`) || '[]'); + return Array.isArray(saved) ? saved.filter(m=>m.type!=='loading') : []; + } catch { + return []; + } + }); + const [input, setInput] = useState(''); + const [chatModel, setChatModel] = useState('GPT-5'); + const inputRef = useRef(null); + const chatEndRef = useRef(null); + const [sending, setSending] = useState(false); + + // 기본 카드: ChatGPT (도구 목록에서 제거되었기 때문에 selectedTool 이 없으면 ChatGPT로 동작) + const defaultChatTool = { + id: 'chatgpt', + name: 'Recipe Engine Demo', + description: '영양/질환/약물 제약을 포함해 “설명 가능한” 레시피 추천을 만드는 데모입니다. (모델 선택 가능)', + }; + const activeTool = selectedTool || defaultChatTool; + + // 히스토리 패널용 공통 값 + const toolId = selectedTool?.id || 'chatgpt'; + const historyPanel = ; + + useEffect(() => { + // 카드 전환 시 해당 카드의 저장된 대화 불러오기 (기본: chatgpt) + const toolId = selectedTool?.id || 'chatgpt'; + try { + const key = `messages_${toolId}`; + const saved = JSON.parse(localStorage.getItem(key) || '[]'); + setMessages(Array.isArray(saved) ? saved.filter(m=>m.type!=='loading') : []); + } catch { + setMessages([]); + } + }, [selectedTool?.id]); + + useEffect(() => { + // scroll to bottom + chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + // 대화 변경 시 로컬스토리지에 저장 (기본: chatgpt) + useEffect(() => { + const toolId = selectedTool?.id || 'chatgpt'; + try { localStorage.setItem(`messages_${toolId}`, JSON.stringify(messages)); } catch {} + }, [messages, selectedTool?.id]); + + + // (iframe으로 제공하는 도구는 더 이상 없음) + + if (selectedTool && selectedTool.id === 'dev_chatbot') { + return ( +
+ {historyPanel} + + {/* DevChatInput renders header, messages, input */} + +
+ ); + } + + // 문서번역 도구: Word 파일 업로드/목록/다운로드/삭제 + 실시간 텍스트 번역 지원 + if (selectedTool && selectedTool.id === 'doc_translation') { + return ( +
+ {historyPanel} + + +
+ ); + } + + const sendMessage = async () => { + if (sending) return; + if (!user) { + alert('로그인이 필요합니다'); + return; + } + setSending(true); + if (!input.trim()) { setSending(false); return; } + const userMsg = { type: 'user', content: input.trim(), time: new Date().toLocaleTimeString() }; + setMessages((prev) => [...prev, userMsg]); + setInput(''); + if (inputRef.current) { + inputRef.current.style.height = '32px'; + } + + // loading placeholder + const loadingMsg = { type: 'loading', start: Date.now() }; + setMessages((prev) => [...prev, loadingMsg]); + + // interval to update elapsed seconds + const loadingInterval = setInterval(() => { + setMessages((prev) => { + const last = prev[prev.length - 1]; + if (last && last.type === 'loading') return [...prev]; // trigger rerender + clearInterval(loadingInterval); + return prev; + }); + }, 1000); + + // backend 호출 + try { + let botContent = ''; + if (activeTool.id === 'chatgpt') { + const modelMap = { + 'GPT-5': 'gpt-5', + 'GPT-5-mini': 'gpt-5-mini', + 'GPT-5-nano': 'gpt-5-nano', + 'GPT-4o': 'gpt-4o', + 'GPT-4.1-mini': 'gpt-4o-mini', + 'o4-mini': 'o4-mini', + }; + const model = modelMap[chatModel] || modelMap['GPT-5']; + const sysPrompt = `당신은 한국어로 답하는 뛰어난 AI 어시스턴트입니다. + +✅ 반드시 사실(팩트)에 기반하여 거짓 정보 없이 답하십시오. +✅ 먼저 내부적으로 생각(Chain-of-Thought)을 수행한 뒤, 이용자에게는 정리된 결과만 보여 주세요. +✅ 충분한 길이(최소 5문단)로, 필요 시 제목·소제목·순서/불릿 목록·코드 블록을 포함한 **Markdown** 형식을 사용하십시오. +✅ 이해를 돕기 위해 😃, 📌, 💡 등 적절한 이모지를 활용하십시오. +✅ 요약만 제시하지 말고, 예시·배경 설명·추가 정보를 상세히 제공합니다. +✅ 가능하다면 신뢰할 수 있는 공개 출처 URL 또는 참고문헌을 맨 아래에 제시하십시오.`; + // 이미지 생성 의도 간단 감지 (예: 이미지/그려줘/생성해줘/그림/사진 등 키워드) + // 1차: 키워드 룰기반, 2차: 모델 분류기로 보강 (샘플코드 방식) + let wantsImage = /\b(이미지|그려줘|그림|사진|image|draw|generate\s+image|그려|create\s+an?\s+image|render)\b/i.test(userMsg.content); + if (!wantsImage) { + try { + const cls = await classifyRequest(userMsg.content, 'gpt-5'); + wantsImage = cls === 'image'; + } catch (_) { /* ignore */ } + } + if (wantsImage) { + try { + const dataUrl = await fetchOpenAIImage(userMsg.content, 'gpt-image-1', { size: '1024x1024' }); + // 이미지 응답은 전용 타입으로 렌더링 + clearInterval(loadingInterval); + const botMsg = { type: 'image', content: dataUrl, time: new Date().toLocaleTimeString() }; + setMessages((prev) => { + const copy = [...prev]; + const idx = copy.findIndex((m) => m.type === 'loading'); + if (idx !== -1) copy[idx] = botMsg; else copy.push(botMsg); + return copy; + }); + return; // 조기 반환 (텍스트 처리 불필요) + } catch (err) { + // 이미지 생성 실패 시 텍스트 답변으로 폴백 + botContent = await fetchOpenAIChat([ + { role: 'system', content: sysPrompt }, + { role: 'user', content: userMsg.content }, + ], model, { }); + } + } else { + botContent = await fetchOpenAIChat([ + { role: 'system', content: sysPrompt }, + { role: 'user', content: userMsg.content }, + ], model, { }); + } + } else { + const formData = new FormData(); + formData.append('message', userMsg.content); + formData.append('tool_id', activeTool.id); + const res = await fetch(`${API_BASE_URL}/chat`, { method: 'POST', body: formData }); + const data = await res.json(); + botContent = data.response; + } + clearInterval(loadingInterval); + const botMsg = { type: 'bot', content: botContent, time: new Date().toLocaleTimeString(), markdown:true }; + // replace loading placeholder + setMessages((prev) => { + const copy = [...prev]; + const idx = copy.findIndex((m) => m.type === 'loading'); + if (idx !== -1) copy[idx] = botMsg; + return copy; + }); + } catch (e) { + setMessages((prev) => [...prev, { type: 'bot', content: '서버 오류', time: new Date().toLocaleTimeString() }]); + } + setSending(false); + }; + + return ( +
+ {historyPanel} +
+
+
{activeTool.name}
+
{activeTool.description}
+
+ {messages.map((m, idx) => ( +
+ {m.type==='loading' ? ( +
생각중...
+ ) : m.type==='image' ? ( +
generated
+ ) : m.markdown ? ( +
+ ) : ( +
{m.content}
+ )} +
{ m.type==='loading' ? `${Math.floor((Date.now()-m.start)/1000)}s` : m.time}
+
+ ))} +
+
+ +
+
+ {activeTool.id==='chatgpt' && ( + + )} +