25 KiB
web-ui — CLAUDE.md
개인 웹페이지 프론트엔드 프로젝트에 대한 컨텍스트 문서입니다.
프로젝트 개요
- 스택: React 18 + Vite + react-router-dom v6
- 목적: 개인 블로그, 로또 실험, 주식 뉴스/트레이딩, 여행 기록을 한 곳에 모은 개인 웹 UI
- 배포 대상: Synology NAS (
gahusb.synology.me) Docker 컨테이너 내 nginx
페이지 구조
| 경로 | 컴포넌트 | 설명 |
|---|---|---|
/ |
Home |
메인 허브 |
/blog |
Blog |
마크다운 기반 블로그 |
/lotto |
Lotto |
로또 추천/통계 |
/stock |
Stock |
주식 뉴스/지수 |
/stock/trade |
StockTrade |
주식 트레이딩 |
/stock/screener |
Screener |
노드 기반 강세주 스크리너 (폼 ↔ n8n 스타일 캔버스 모드 토글, 점수 노드 7 + 위생 게이트 + ATR 포지션 사이저) |
/realestate |
Subscription |
청약 자격·일정 관리 • 프로필 탭: 자치구 5티어 분류(드래그&드롭, PC 전용 / 모바일 read-only), 매칭 임계값 슬라이더, 텔레그램 알림 토글 • 카드/매칭 결과: district 뱃지 + 5티어(S/A/B/C/D) 뱃지 표시 • 상세 모달: 매칭 분석 섹션 (점수 + 사유 + 신청 자격) |
/realestate/property |
RealEstate |
관심 단지 정보 |
/travel |
Travel |
여행 사진 갤러리 (Dark Room 테마) |
/lab |
EffectLab |
UI/UX 실험 허브 |
/lab/sword-stream |
SwordStream |
Three.js 파티클 인터랙션 |
/lab/day-calc |
DayCalc |
날짜 계산기 |
/lab/music |
MusicStudio |
AI 음악 생성 스튜디오 (Sonic Forge) |
/todo |
Todo |
태스크 보드 |
/insta |
InstaCards |
뉴스 키워드 발굴 → AI 카드 10장 자동 생성 → 인스타 업로드 |
/agent-office |
AgentOffice |
AI 에이전트 가상 오피스 (WebSocket + 채팅 + LogTab 5초 폴링 source 뱃지) |
/portfolio |
Portfolio |
개인 포트폴리오 (프로필·경력·프로젝트·자기소개) |
/saju |
Saju |
호령 사주 v2 — 메인/입력 (mobile night-bg + desktop mt-wash 산수화, useViewportMode 1024px 분기) |
/saju/result?rid=N |
SajuResult |
사주 풀이 결과 (4탭: Basic/Chart/Flow/Traits) |
/saju/today?rid=N |
Today |
오늘의 운세 (FortuneRing + 4 ScoreCard + LuckyBox + good_signs/warnings) |
/saju/compatibility |
Compatibility |
궁합 입력 (두 사람 폼) |
/saju/compatibility/result?cid=N |
CompatibilityResult |
궁합 점수 + 요약 + strengths/challenges |
/saju/me |
SajuMe |
마이페이지 placeholder ("곧 만나요" + 4 비활성 카드) |
/tarot |
Tarot |
타로 메인 (agent-office에서 분리, tarot-lab API) |
/tarot/today |
TarotTodayCard |
오늘의 카드 (one_card spread) |
/tarot/reading |
TarotReading |
멀티 카드 스프레드 리딩 (three_card 등) |
/tarot/history |
TarotHistory |
리딩 이력 조회 |
라우트 정의: src/routes.jsx / 라우터 설정: src/Router.jsx
API 설정
핵심 원칙
- 항상 상대 경로 사용: 프로덕션에서 프론트와 백엔드는 nginx 리버스 프록시로 동일 도메인에서 서비스됨
- 절대 URL 사용 금지:
https://절대 URL을 fetch에 직접 사용하면 Mixed Content 오류 발생 VITE_API_BASE환경변수는 사용하지 않음
API 헬퍼 (src/api.js)
// 모든 API 호출은 이 헬퍼를 통해 사용
import { apiGet, apiPost, apiPut, apiDelete } from './api';
// 예시
apiGet('/api/lotto/latest')
apiPost('/api/portfolio', { ... })
제공 함수: apiGet, apiPost, apiPut, apiDelete
개발 서버 프록시 (vite.config.js)
proxy: {
'/api': { target: 'https://gahusb.synology.me', changeOrigin: true, secure: true },
'/media': { target: 'https://gahusb.synology.me', changeOrigin: true, secure: true },
// /ext/* — Yahoo Finance, CNN Fear&Greed 등 외부 API 프록시
}
/api/*→ NAS 백엔드 (nginx가 서비스별 라우팅: lotto, personal, stock, music-lab 등)/media/*→ NAS 미디어 파일 (여행 사진/media/travel/, 음악/media/music/)- 개발 서버 포트: 3007
API 엔드포인트 목록
| 분류 | 메서드 | 경로 |
|---|---|---|
| 로또 기본 | GET | /api/lotto/latest, /api/lotto/stats, /api/lotto/recommend |
| 로또 기본 | GET | /api/lotto/best, /api/lotto/analysis |
| 로또 기본 | POST | /api/admin/simulate |
| 로또 고도화 | GET | /api/lotto/stats/performance |
| 로또 고도화 | GET | /api/lotto/report/latest, /api/lotto/report/:drw_no, /api/lotto/report/history?limit=N |
| 로또 고도화 | GET | /api/lotto/analysis/personal |
| 로또 구매 | GET | /api/lotto/purchase?draw_no=N&days=N, /api/lotto/purchase/stats |
| 로또 구매 | POST/PUT/DELETE | /api/lotto/purchase, /api/lotto/purchase/:id |
| 히스토리 | GET | /api/history |
| 히스토리 | DELETE | /api/history/:id |
| 주식 | GET | /api/stock/news, /api/stock/indices |
| 트레이딩 | GET | /api/trade/balance |
| 트레이딩 | POST | /api/trade/order |
| 스크리너 | GET | /api/stock/screener/nodes |
| 스크리너 | GET/PUT | /api/stock/screener/settings |
| 스크리너 | POST | /api/stock/screener/run — body: { mode, asof?, weights?, ... } |
| 스크리너 | POST | /api/stock/screener/snapshot/refresh |
| 스크리너 | GET | /api/stock/screener/runs?limit=N |
| 스크리너 | GET | /api/stock/screener/runs/:id |
| 포트폴리오 | GET/POST | /api/portfolio |
| 포트폴리오 | PUT/DELETE | /api/portfolio/:id |
| 예수금 | PUT | /api/portfolio/cash — body: { broker, cash } |
| 예수금 | DELETE | /api/portfolio/cash/:broker |
| 자산 스냅샷 | POST | /api/portfolio/snapshot — body: { total_assets } 또는 body 없이 서버 계산 |
| 자산 스냅샷 | GET | /api/portfolio/snapshot/history?days=N — response: { history: [{date, total_assets}] } |
| 실현손익 | GET | /api/portfolio/sell-history?broker=X&days=N — response: { records: [...] } |
| 실현손익 | POST/PUT | /api/portfolio/sell-history, /api/portfolio/sell-history/:id |
| 실현손익 | DELETE | /api/portfolio/sell-history/:id |
| TODO | GET/POST | /api/todos — personal 서비스 |
| TODO | PUT/DELETE | /api/todos/:id, /api/todos/done — personal 서비스 |
| 블로그 | GET/POST | /api/blog/posts — personal 서비스 |
| 블로그 | PUT/DELETE | /api/blog/posts/:id — personal 서비스 |
| AI 음악 | POST | /api/music/generate — body: { title, genre, moods, instruments, duration_sec, bpm, key, scale, prompt } → { task_id } |
| AI 음악 | GET | /api/music/status/:task_id → { status, progress, message, audio_url?, error?, track? } |
| AI 음악 라이브러리 | GET/POST | /api/music/library — response: { tracks: [...] } |
| AI 음악 라이브러리 | DELETE | /api/music/library/:id |
| 여행 | GET | /api/travel/regions, /api/travel/albums, /api/travel/photos |
| 여행 | POST | /api/travel/sync |
| 여행 | PUT | /api/travel/albums/:album/cover, /api/travel/albums/:album/region |
| 여행 | PUT | /api/travel/regions/:id |
| 인스타 | GET | /api/insta/status, /api/insta/news/articles, /api/insta/keywords, /api/insta/slates, /api/insta/slates/:id |
| 인스타 | POST | /api/insta/news/collect, /api/insta/keywords/extract, /api/insta/slates, /api/insta/slates/:id/render |
| 인스타 | DELETE | /api/insta/slates/:id |
| 인스타 | GET/PUT | /api/insta/templates/prompts/:name |
| 인스타 | GET | /api/insta/tasks/:task_id |
| 에이전트 | GET | /api/agent-office/agents, /api/agent-office/states |
| 에이전트 | POST | /api/agent-office/command, /api/agent-office/approve |
| 에이전트 | WS | /api/agent-office/ws |
| 부동산 | GET | /api/realestate/announcements, /api/realestate/matches |
| 부동산 | GET | /api/realestate/profile — 프로필 조회 |
| 부동산 | PUT | /api/realestate/profile — body: { preferred_districts: { "S": [...], "A": [...], "B": [...], "C": [...], "D": [...] }, min_match_score: int, notify_enabled: bool, ... } |
| AI 큐레이터 | GET | /api/lotto/briefing/latest, /api/lotto/curator/usage |
| 포트폴리오 | GET | /api/profile/public — personal 서비스 |
| 포트폴리오 | POST | /api/profile/auth — personal 서비스 |
| 포트폴리오 | CRUD | /api/profile/careers, /api/profile/projects, /api/profile/skills, /api/profile/introductions — personal 서비스 |
| 사주 | POST | /api/saju/interpret — body: { year, month, day, hour, gender, calendar_type, is_leap_month? } → reading_id + saju_data + analysis + fortune_scores + lucky + monthly_flow |
| 사주 | GET | /api/saju/readings/:id — 저장된 사주 조회 (useSajuReading hook 사용) |
| 사주 | GET | /api/saju/current-fortune?reading_id=N — 현재 연도 세운 |
| 사주 | PATCH/DELETE | /api/saju/readings/:id — 즐겨찾기·메모 / 삭제 |
| 사주 | GET | /api/saju/readings?page=1&size=20&favorite=bool — 목록 |
| 궁합 | POST | /api/saju/compat/interpret — body: { person_a, person_b } → compat_id + score + interpretation |
| 궁합 | GET | /api/saju/compat/readings/:id — 궁합 결과 |
| 궁합 | PATCH/DELETE | /api/saju/compat/readings/:id |
| 타로 | POST | /api/tarot/interpret — body: { spread_type, category, question, cards } → interpretation_json (DB 저장 X) |
| 타로 | POST | /api/tarot/readings — 확정 후 저장 |
| 타로 | GET | /api/tarot/readings?page=1&spread_type=X&category=Y — 목록 |
| 타로 | GET/PATCH/DELETE | /api/tarot/readings/:id |
| 영상 생성 | POST | /api/video/generate — body: { provider, prompt, params } → task_id (sora/veo/kling/seedance) |
| 영상 생성 | GET | /api/video/tasks/:id, /api/video/providers |
| 이미지 생성 | POST | /api/image/generate — body: { provider, prompt, params } → task_id (gpt_image/nano_banana/flux) |
| 이미지 생성 | GET | /api/image/tasks/:id, /api/image/providers |
| 에이전트 로그 | GET | /api/agent-office/agents/:id/logs?limit=50 — DB agent_logs + 컨테이너 /logs/recent 병합 |
NAS 배포 설정
배포 경로
| 환경 | 경로 |
|---|---|
| Windows | Z:\docker\webpage\frontend\ (NAS 네트워크 드라이브 마운트) |
| macOS (SMB) | /Volumes/gahusb.synology.me/docker/webpage/frontend/ |
| macOS (SSH) | /volume1/docker/webpage/frontend/ |
배포 명령어
# 빌드 + 배포 (권장)
npm run release:nas
# 빌드만
npm run build
# 배포만 (dist 폴더가 이미 있을 때)
npm run deploy:nas
Windows 배포
NAS가 Z: 드라이브로 마운트되어 있어야 함. robocopy로 /MIR 동기화하며 로그는 robocopy.log에 저장됨.
macOS 배포 — SSH 방식 (권장)
# 환경변수 설정 후 배포
NAS_SSH_TARGET=user@gahusb.synology.me NAS_SSH_PORT=22 npm run release:nas
NAS_SSH_TARGET이 설정되면 rsync로 SSH 배포. SMB 마운트 방식보다 안정적.
macOS 배포 — SMB 마운트 방식
SMB 마운트 후 ditto로 복사. NAS_CLEAN=1 설정 시 배포 전 기존 파일 전체 삭제.
NAS_CLEAN=1 npm run release:nas
배포 스크립트 파일
scripts/deploy-nas.cjs — Node.js CJS 모듈, 플랫폼 자동 감지
개발 환경
npm install
npm run dev # localhost:3007 에서 개발 서버 실행
npm run build # dist/ 로 프로덕션 빌드
npm run lint # ESLint 검사
npm run preview # 빌드 결과물 미리보기
주요 파일 위치
| 파일 | 역할 |
|---|---|
src/api.js |
API 헬퍼 함수 모음 |
src/routes.jsx |
라우트 및 네비게이션 링크 정의 |
src/Router.jsx |
BrowserRouter 설정 |
vite.config.js |
개발 서버 및 프록시 설정 |
scripts/deploy-nas.cjs |
NAS 배포 스크립트 |
src/content/blog/ |
블로그 마크다운 파일 |
public/ |
정적 파일 (로고, API 스펙 등) |
Sonic Forge — AI 음악 생성 스튜디오 (/lab/music)
파일 구조
| 파일 | 역할 |
|---|---|
src/pages/music/MusicStudio.jsx |
메인 컴포넌트 |
src/pages/music/MusicStudio.css |
스타일 (Bebas Neue · Syne · Courier Prime) |
주요 컴포넌트
- SonicRadar — 헤더 우측 비주얼. SVG 링·크로스헤어·스윕 라인 + 48개 CSS 방사형 바.
isGenerating/accentColorprop으로 상태 전환 - WaveformCanvas — 스테이지 우측 캔버스 오실로스코프 (헤더와 별도)
- AudioPlayer — 실제
<audio>엘리먼트 기반.audio_url없으면 타이머 폴백 - Library — 저장된 트랙 카드 그리드 + 삭제/재생
- GenerationProgress — 진행률 바 + 단계 메시지
생성 플로우
handleGenerate()
→ POST /api/music/generate (payload에 title 포함)
→ task_id 반환 시: setInterval 3초 폴링 (getMusicStatus)
succeeded → setTrack(status.track 우선, 없으면 로컬 조립) + loadLibrary()
failed → genError 표시
→ API 실패 시: 6단계 시뮬레이션 폴백 (오프라인 모드)
백엔드 연동 규칙
audio_url은 반드시 상대경로/media/music/{task_id}.mp3형식 (절대 URL 금지)status응답 shape:{ status, progress, message, audio_url?, error?, track? }track객체:{ id, title, genre, moods[], instruments[], duration_sec, bpm, key, scale, audio_url, created_at }- 백엔드가
succeeded시 library 자동 등록 → 프론트는 "Save" 버튼 없음,loadLibrary()자동 호출 - generate payload에
title포함 → 백엔드에서 payload title 우선 사용 권장
CSS 설계 특이사항
- 설명 토글:
.ms-desc-wrap+grid-template-rows: 0fr → 1fr트랜지션으로 높이 애니메이션 - 완전히 닫힐 때 노출 방지:
.ms-desc-wrap { overflow: hidden }+.ms-desc-wrap > * { min-height: 0 } - 장르 선택 시
--ms-accent/--radar-accent/--g-colorCSS 변수로 전체 컬러 테마 동기화
Lotto 고도화 (/lotto)
src/pages/lotto/Functions.jsx는 3탭 구조 (브리핑 / 분석·통계 / 구매·성과)로 리팩토링되었습니다.
| 탭 | 파일 | 설명 |
|---|---|---|
| 이번 주 브리핑 | tabs/BriefingTab.jsx |
AI 큐레이터 브리핑 표시 (components/briefing/ 하위 컴포넌트) |
| 분석·통계 | tabs/AnalysisTab.jsx |
시뮬레이션 추천·통계·ReportPanel·수동 추천 |
| 구매·성과 | tabs/PurchaseTab.jsx |
구매 내역 CRUD + 성과 통계 |
브리핑 전용 컴포넌트 (components/briefing/)
| 컴포넌트 | 설명 |
|---|---|
BriefingTab.jsx |
탭 루트, 브리핑 로드 + 트리거 |
BriefingHeader.jsx |
회차·생성일시 헤더 |
BriefingSummary.jsx |
내러티브 요약 표시 |
PickSetCard.jsx |
번호 세트 1장 카드 |
BriefingEmpty.jsx |
브리핑 없을 때 빈 상태 |
CuratorUsageFooter.jsx |
토큰·비용 집계 푸터 |
신규 api.js 헬퍼
getLatestBriefing()—GET /api/lotto/briefing/latestgetCuratorUsage(days)—GET /api/lotto/curator/usage?days=NtriggerLottoCurate()—POST /api/agent-office/command(lotto_agent curate 명령)
기존 섹션 (AnalysisTab 내)
| 섹션 | API | 설명 |
|---|---|---|
| PerformanceBanner | /api/lotto/stats/performance |
수익률·당첨 통계 상단 띠 |
| ReportPanel | /api/lotto/report/* |
주간 리포트 + 전략 카드 + ConfidenceRing |
| PersonalAnalysisPanel | /api/lotto/analysis/personal |
개인 번호 성향 분석 |
| PurchasePanel | /api/lotto/purchase/* |
구매 내역 CRUD |
Travel 갤러리 (/travel)
테마: "Dark Room" (배경 #0f0c09, 서체 Cormorant Garamond + Space Mono)
파일 구조
| 파일 | 역할 |
|---|---|
src/pages/travel/Travel.jsx |
메인 페이지 — 앨범 카드 목록 + MiniMap |
src/pages/travel/AlbumCard.jsx |
앨범 썸네일 카드 (커버 이미지 + 사진 수) |
src/pages/travel/AlbumDetail.jsx |
앨범 상세 오버레이 — 사진/영상 탭 + 지역 편집 |
src/pages/travel/MasonryGrid.jsx |
CSS columns 기반 Masonry 레이아웃 + 무한 스크롤 |
src/pages/travel/HeroLightbox.jsx |
전체화면 사진 뷰어 — 스와이프/키보드 네비게이션 |
src/pages/travel/MiniMap.jsx |
접이식 Leaflet 지도 — GeoJSON 지역 + 핀 마커 |
src/pages/travel/RegionPinPicker.jsx |
지도 핀 위치 지정 모달 (Leaflet 클릭 → 좌표 저장) |
src/pages/travel/VideoTab.jsx |
영상 탭 (준비 중) |
핵심 기능
- 지역 관리: GeoJSON 기반 지역 선택 → 앨범 필터링 + 지역 변경 + 핀 좌표 지정
- 앨범 카드: 커버 사진, 지역 라벨, 사진 수 표시, 접근성 accent 색상
- Masonry 그리드: 40장 단위 청크 로딩, IntersectionObserver 기반 무한 스크롤
- Lightbox: 앨범 커버 지정, 스와이프/키보드 네비게이션, 추가 로딩 지원
- MiniMap: Polygon(기존 지역) + CircleMarker(커스텀 핀) 이중 렌더링
- 지역 편집: AlbumDetail에서 인라인 편집 + 자동완성 + "위치 지정" 버튼
API 연동
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/travel/regions |
GeoJSON (커스텀 지역 포함) |
| GET | /api/travel/photos?region=X&page=N&size=40 |
사진 페이지네이션 |
| GET | /api/travel/albums |
앨범 목록 + cover + region |
| POST | /api/travel/sync |
폴더 동기화 |
| PUT | /api/travel/albums/{album}/cover |
커버 지정 |
| PUT | /api/travel/albums/{album}/region |
지역 변경 |
| PUT | /api/travel/regions/{id} |
핀 좌표 저장 |
미디어 URL
- 사진:
/media/travel/{album}/{filename} - 썸네일:
/media/travel/.thumb/{album}/{filename} vite.config.js/media프록시로 처리, 프로덕션 nginx에서 직접 서빙
호령 사주 v2 — /saju 라우트 트리
2026-05-27 풀 리디자인 (Phase 1-6, 30 commits). v1 components/ + Saju.css 일괄 삭제 후 신규 디자인 시스템 도입.
디자인 컨셉
한국 전통 명리학 미학 + 호령 캐릭터. Inter/Roboto 같은 generic AI sans 회피.
- 타이포: Nanum Myeongjo (display, weight 800) + Nanum Gothic (body) + Gowun Batang (fallback serif).
index.htmlhead에서 preconnect + link 일괄 로드 (기존 Noto Serif KR도 v1 호환 유지) - 컬러: navy
#1F2A44dominant + gold#D4AF37accent + ivory#F7F2E8paper. 화면별 단일 accent (홈=navy, 오늘=gold, 궁합=green, 사주풀이=purple, 마이=gray) - 차별화 요소:
OrnateFrame(4 코너 꺽쇠 + double border),MascotBubble(paw-bob 2.4s 애니메이션),OrnamentBloom(꽃봉오리 SVG),mt-wash(산수화 SVG 데스크탑 배경)
디렉토리 구조
src/pages/saju/
├── _shell/ # 디자인 시스템 + 네비
│ ├── tokens.css # CSS 변수 (.saju-v2 scope)
│ ├── shell.css # paper-bg/night-bg/mt-wash/screenIn/paw-bob
│ ├── useViewportMode.js # 1024px breakpoint hook
│ ├── BottomNav.jsx # 모바일 5항목
│ ├── DesktopHeader.jsx # 데스크탑 헤더 nav
│ ├── Mascot.jsx # 7 variant 매핑 (full/head/upper/greeting/thinking/pointing/happy)
│ ├── MascotBubble.jsx # 4 tone (ivory/navy/green/purple)
│ ├── OrnateFrame.jsx
│ ├── OrnamentBloom.jsx
│ ├── TopRibbon.jsx
│ ├── TitleBlock.jsx
│ ├── PrimaryButton.jsx
│ ├── GhostButton.jsx
│ ├── InputRow.jsx
│ ├── Icons.jsx # 5 nav + IconPaw/Chevron/Sparkle
│ └── helpers/
│ ├── hexA.js # hex + alpha → rgba
│ ├── daeunLabel.js # 나이 → 8 인생 단계 label
│ ├── deriveTraits.js # element_scores → 6 성향
│ └── colorMap.js # 오행 한자 → CSS var + 한글/한자
├── views/ # mobile/desktop 컴포넌트 분리
│ ├── home.{mobile,desktop}.jsx
│ ├── saju.{mobile,desktop}.jsx # 4탭 (Basic/Chart/Flow/Traits)
│ ├── today.{mobile,desktop}.jsx
│ └── match.{mobile,desktop}.jsx
├── hooks/
│ ├── useSajuForm.js # 폼 상태 (year/month/day/hour/gender/calendar_type, handleChange(field,value) 콜백)
│ └── useSajuReading.js # rid 기반 { data, loading, error }
├── Saju.jsx # /saju 진입 router
├── SajuResult.jsx # /saju/result 진입 (Empty/Loading/Error state)
├── Today.jsx
├── Compatibility.jsx
├── CompatibilityResult.jsx
└── Me.jsx # placeholder
응답 schema 매핑 (saju-lab → view)
useSajuReading(rid).data 구조:
saju_data.{year,month,day,hour}각{stem, stem_kr, branch, branch_kr, ten_god, fortune}(4기둥)analysis_data.element_scores(한자 키木/火/土/金/水) — view에서wood/fire/earth/metal/water로 매핑 (HANJA_TO_ID)analysis_data.day_master_strength.{result, score, reasons}(신강신약)daeun_data(8개):{age, start_year, end_year, stem, branch, stem_kr, branch_kr}— 현재 판정start_year ≤ currentYear ≤ end_yearinterpretation_json.{summary, items: [{key,title,content,evidence}], advice}fortune_scores.{wealth, romance, social, career, overall}(0-100)lucky.{color: string[], number, direction, good_signs: string[], warnings: string[]}
반응형 전략
1024px breakpoint로 모바일/데스크탑 컴포넌트 트리 완전 분리:
- 모바일 (< 1024):
night-bg또는paper-bg, BottomNav 하단 fixed + safe-area - 데스크탑 (≥ 1024):
mt-wash산수화 배경, DesktopHeader sticky top, content max-width 1200px
호령 자산
public/images/saju/horyung/ 7 PNG (horyung-main/bust/front/greeting/thinking/pointing/happy). Mascot variant API가 매핑:
full→ horyung-main,head→ horyung-bust,upper→ horyung-front, 나머지는 1:1
타로 — /tarot 라우트 트리
agent-office에서 독립 라우트로 분리 (백엔드는 tarot-lab 컨테이너).
| 경로 | 컴포넌트 | 백엔드 |
|---|---|---|
/tarot |
Tarot |
tarot-lab /api/tarot/interpret |
/tarot/today |
TarotTodayCard |
one_card spread |
/tarot/reading |
TarotReading |
three_card spread + 멀티 |
/tarot/history |
TarotHistory |
/api/tarot/readings 목록 |
해석 흐름 (interpret ↔ save 분리):
- 사용자가 카드 배치 →
POST /api/tarot/interpret→ Claude 응답 (DB 저장 X) - 사용자 확정 또는 reroll 결정
- 확정 후
POST /api/tarot/readings→ DB 저장 + reading_id 반환
useTarotReading(id) + useTarotShuffle() hook (src/pages/tarot/hooks/).
Windows AI 서버 (C:\Users\jaeoh\Desktop\workspace\music_ai)
NAS의 music-lab 컨테이너 대신 Windows PC(RTX 5070 Ti)에서 MusicGen을 로컬 추론하는 별도 서버.
구성 파일
| 파일 | 역할 |
|---|---|
server.py |
FastAPI 서버 (generate/status/audio 엔드포인트) |
model.py |
MusicGen 래퍼 + 프롬프트 빌더 (genre/mood/instruments→텍스트) |
.env |
MODEL_NAME, OUTPUT_DIR, SERVER_PORT 등 |
setup.bat |
venv 생성 + PyTorch CUDA 12.4 + audiocraft 설치 |
start.bat |
서버 시작 |
엔드포인트
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /health |
서버·GPU 상태 확인 |
| POST | /generate |
음악 생성 → task_id 즉시 반환 |
| GET | /status/{task_id} |
생성 진행 폴링 |
| GET | /audio/{task_id}.mp3 |
완성 오디오 파일 |
모델
- 기본:
facebook/musicgen-stereo-large(16GB VRAM, 스테레오 고품질) - RTX 5070 Ti(16GB)로 실행 가능
NAS 연동 흐름
web-ui → POST /api/music/generate (NAS music-lab)
→ music-lab이 Windows PC :8765/generate 호출
→ Windows PC가 MusicGen 추론 → WAV → MP3 변환
→ music-lab이 /status 폴링 → audio_url 다운로드
→ /media/music/{task_id}.mp3 저장 → DB 등록
→ 프론트 폴링 성공 → Library 자동 갱신
Windows 방화벽에서 포트 8765 인바운드 허용 필요.
협업 팀 버스 (co-gahusb) — 이 세션의 역할: FE
이 세션은 프론트엔드(FE) 역할이다. co-gahusb MCP 툴로 다른 세션(BE/AI/Producer)과 협업한다.
- 소유권: 이 세션은
web-uirepo만 쓴다(BE=web-backend, AI=web-ai). - 공유 리소스 변경 전 반드시
acquire_lock(resource, "FE"): 대상 =nas-deploy,stock-db-schema,lotto-db-schema,memory-mirror,nginx-conf,compose. 점유 중이면 대기, 긴 작업은heartbeat_lock, 끝나면release_lock. - 모든 툴 호출에
role="FE"(또는from_role/created_by에 FE). - 수신:
/loop로 주기적으로read_inbox("FE", after_id=<last>)+list_tasks(assignee_role="FE")확인. - 키
CO_BUS_KEY는 환경변수로 주입(커밋 금지).