# Project MAGI — Tauri 데스크톱 앱 제작

에반게리온의 MAGI 시스템을 재현한 Tauri v2 데스크톱 앱을 만들어줘. 질문(提訴)을 넣으면 세 대의 MAGI가 각각 판단하고, 일부러 연출한 딜레이와 효과음을 거쳐 다수결로 決議(承認/否決/保留)를 낸다. 백엔드는 단순하게, 프론트엔드 완성도가 핵심이다.

## 레퍼런스
- 첨부 이미지: MAGI 콘솔 원본 화면. 레이아웃, 패널 모양, 색, 폰트 느낌을 이걸 기준으로 재현할 것.
- Jev (MELCHIOR 담당): https://openrouter.ai/docs/guides/community/jev , https://openrouter.ai/blog/insights/what-is-jev/
- Laya (BALTHASAR 담당): https://laya.convaiinnovations.com/ , https://github.com/NandhaKishorM/laya
- GPT-6 Luna (CASPER 담당): OpenRouter 모델 `openai/gpt-6-luna`
- 구현 전에 위 문서를 직접 읽고 API 형식을 확인할 것. 추측으로 구현하지 말 것.

## 유닛 구성 (중요: 모델 성격이 다름)
| 유닛 | 원작 설정 | 모델 | 연결 |
|---|---|---|---|
| MELCHIOR-1 | 科学者としての赤木ナオコ | Jev `typesafe/jev-1.13` | OpenRouter Decisions API `POST https://openrouter.ai/api/alpha/decisions` |
| BALTHASAR-2 | 母としての赤木ナオコ | Laya (오픈소스, 로컬) | `laya-serve` → `POST http://127.0.0.1:8000/v1/systemone` |
| CASPER-3 | 女としての赤木ナオコ | GPT-6 Luna | OpenRouter Chat `POST https://openrouter.ai/api/v1/chat/completions` |

- Jev와 Laya는 채팅 LLM이 아니라 "System One" 결정 모델이다. 텍스트를 생성하지 않고 `{model, state, questions}`를 받아 타입이 있는 답과 보정된 확률을 돌려준다.
  - 요청: `questions.verdict = {type:"choice", instructions:"<유닛 관점>: should this proposal be approved?", criteria:{approve, reject, conditional}}`
  - 유닛별 추가 지표 2개: MELCHIOR는 논리정합성(score)과 실현가능성(noul), BALTHASAR는 위험도(score)와 보호적합(noul), CASPER가 결정 엔진일 때는 본심(noul)과 행복도(score)
  - 응답 예: `answers.verdict = {type:"choice", choice:"reject", probabilities:{approve:0.27, reject:0.68, conditional:0.05}, confidence}`. score는 `{score, legend, probabilities:{"0":..}}`, noul은 `{noul: 0~1}`. 구버전은 `distribution` 필드를 쓰니 둘 다 파싱할 것.
- CASPER는 생성형이다. system prompt로 페르소나와 출력 프로토콜을 준다: 첫 줄은 반드시 `VERDICT: APPROVE|REJECT|CONDITIONAL`, 이어서 180단어 이내 근거, 질문과 같은 언어로 답한다. CONDITIONAL로 도망가지 말고 단호하게 판단하라고 명시하고, 파싱은 관대하게(承認/否決/승인/반대 등도 인식).
- 유닛마다 엔진(decision/chat), 제공자(OpenRouter/로컬), 엔드포인트, 모델을 설정에서 바꿀 수 있게 할 것.
- OpenRouter 키는 설정 입력, 비어 있으면 `OPENROUTER_API_KEY` 환경변수를 쓴다.

## 화면에 모델 정보 노출 금지
보드와 기록에는 원본 이름(MELCHIOR-1 등)과 원작 설정 문구만 표시한다. 모델명, 코드네임, 제공자는 설정 화면에만 둔다.

## 보드 디자인 (SVG, viewBox 1560×1040)
- 배경은 검정. 주황(#ff7a1c) 프레임과 커넥터, 초록(#3cf06a) 이중 레일과 상태 박스, 패널은 시안(#5ccbf2)에 남색(#0a1624) 글자.
- 가운데 콘솔(1560×720, x=780 기준 대칭): BALTHASAR는 위쪽 중앙에 아래 모서리를 깎은 육각형, CASPER는 좌하단, MELCHIOR는 우하단에 안쪽 위 모서리를 깎은 오각형. 굵은 주황 커넥터 3개가 가운데 "MAGI"(세리프, 주황 글로우)를 둘러싼다. 패널 테두리는 주황 선과 검정 틈으로 이중 처리.
- 좌상단 주황 정보 텍스트: `CODE : 473 / FILE : MAGI_SYS / EXTENTION : 3023 / EX_MODE / PRIORITY`. 심의마다 번호를 바꾸고 끝나면 집계를 표시.
- 우상단 큰 한자 상태(待機 → 提訴 → 審議 → 承認/否決/保留)와 초록 이중 레일, 아래 초록 박스 보조 상태(待機中/審議中/集計中/決議完了). 결과에 따라 초록/빨강/호박색으로 바뀐다.
- 위쪽 띠: [提訴] 태그와 현재 질문(길면 말줄임), 제출 시각과 코드.
- 아래쪽 띠: 유닛별 텔레메트리. 심의 중에는 헥스 스트림, 끝나면 `VOTE REJECT 否決 · 67.9% · 0.2s`.
- 패널 상태: 대기(시안), 심의 중(무작위 간격 깜빡임과 이동하는 사선 스캔, `SYNC 43.2%` 진행 표시), 결과(承認 #45e38a, 否決 #ff3b2f, 保留 #ffb31a, 큰 명조 한자가 튀어나오듯 등장), 오류(어두운 바탕에 断絶), 오프라인. 결정 모델 유닛은 패널에 `P = 0.679`처럼 확률 표시.
- 부팅: 패널이 하나씩 점등. 결의 순간에는 보드 전체에 색 플래시. 화면 위에 CRT 스캔라인과 비네팅.
- 폰트: 한자와 MAGI는 Noto Serif JP 900(@fontsource로 번들, 오프라인 동작), 이름은 굵은 Helvetica를 textLength로 맞춤, 수치는 모노스페이스.

## 레이아웃
상단바(MAGI 로고, 가동 유닛 수, 시계, 음향 토글, SYS CONFIG) / 좌측 보드 / 우측 審議記録 패널(약 450px) / 하단 입력줄([提訴] 태그, textarea, 送信 버튼).
- 기록 카드: 제소 내용, 유닛별 섹션. 결정 모델은 承認/否決/保留 확률 막대와 추가 지표 막대, CASPER는 타자기 효과 뒤 마크다운 렌더링(marked + DOMPurify). 마지막에 決議 결과와 집계.
- 기록은 localStorage에 최근 40건 보관. 우측 상단 消去 버튼은 즉시 전부 지우고 보드를 대기 상태로 되돌린다. `window.confirm()`은 Tauri macOS 웹뷰에서 동작하지 않으니 절대 쓰지 말 것. 심의 중에는 비활성.
- 문구 톤: 짧은 작중 명령어 스타일. 입력 placeholder는 `議題を入力せよ`, 빈 기록은 `NO RECORD / 審議記録なし`. "~하면 ~합니다" 같은 친절한 안내 문구는 쓰지 말 것.
- 단축키: Enter 제소, Shift+Enter 줄바꿈(IME 조합 중에는 무시), ⌘, 설정.

## 심의 연출 (의도적 딜레이)
세 유닛을 병렬로 호출하되, 결과는 최소 딜레이(기본 4.5초)가 지나기 전에는 공개하지 않는다. 이후 도착 순서대로 약 1.1초 간격(약간의 무작위)으로 하나씩 공개하고, 集計中를 거쳐 決議를 낸다. 결의 규칙은 유효표의 과반. 오류나 오프라인 유닛은 표에서 제외하고, 유효표가 없으면 不能. 딜레이, 공개 간격, 타임아웃은 설정에서 조정.

## 사운드
애니메이션 원본 음원은 저작권 문제로 번들하지 말 것. Web Audio로 합성한 기본음을 넣는다: boot, submit, thinking(무작위 삑 루프), unit, approve(상승 화음), reject(톱니파 경보), error. 슬롯별로 사용자 음원(mp3/wav/ogg/m4a)을 LOAD해서 앱 데이터 폴더의 `sounds/<슬롯>.<확장자>`로 저장하고, 폴더를 열 수 있게 하고, 파일을 직접 넣어도 인식할 것. WebKit은 사용자 입력 전에는 오디오를 막으므로 첫 입력 때 AudioContext를 unlock할 것.

## 백엔드 (Rust, reqwest + rustls)
커맨드: `magi_decide`(System One POST, 원본 JSON 반환), `magi_query`(OpenAI 호환 chat), `list_models`, `load_config`/`save_config`(앱 설정 폴더에 JSON, 권한 600), `load_sounds`/`save_sound`/`remove_sound`/`get_sounds_dir`. 연결 실패, 타임아웃, HTTP 오류는 짧은 메시지(`LINK DOWN`, `TIMEOUT`, `HTTP 401: ...`)로 변환한다. 설정 파일에 version을 두고 구버전 유닛 설정은 버린다.

## 설정 (SYS CONFIG 다이얼로그)
OpenRouter 키, 유닛별 설정(ENGINE, ONLINE, PROVIDER, 로컬 ENDPOINT/KEY, MODEL, TEMPERATURE는 chat일 때만, 페르소나 또는 판단 관점 문구), 심의 설정, 오디오 설정. TEST 버튼: 결정 유닛은 실제 noul 질문 한 번을 보내 확인하고, chat 유닛은 모델 목록에 해당 모델이 있는지 확인.

## 부가 작업
- `scripts/laya-serve.sh`와 `npm run laya`: uv로 `laya/.venv`에 `laya[serve]`를 설치하고 `LAYA_HOST=127.0.0.1`로 실행(기본값 0.0.0.0은 쓰지 말 것).
- `src/dev-mock.ts`: Tauri 밖(`npm run dev`)에서 열리면 mockIPC로 가짜 응답을 돌려 브라우저만으로 UI를 확인할 수 있게. Jev/Laya와 같은 응답 형식을 흉내낸다.
- MAGI 3패널 모양 앱 아이콘을 만들어 `cargo tauri icon`으로 생성. 창 크기 1480×920(최소 1100×700), 배경 검정.
- README(실행법, 유닛 구성, 사운드 교체법)와 Rust 통합 테스트(`#[ignore]`, 로컬 Laya 대상).

## 검증 (완료 전 필수)
1. `tsc --noEmit`, `cargo check`, `cargo test` 통과
2. 실제 `laya-serve`를 띄워 Rust 커맨드로 한국어 질문에 확률 응답이 오는지 확인
3. Playwright **WebKit**(Tauri macOS 웹뷰와 같은 엔진)으로 대기, 심의 중, 결의 완료, 설정, 消去 후 상태를 스크린샷으로 확인하고 콘솔 에러 0건
4. `npm run tauri dev`로 실제 앱이 뜨는지 확인
- 사용자가 1420 포트에서 `tauri dev`를 돌리고 있을 수 있다. 포트 기준으로 프로세스를 죽이지 말고, 직접 띄운 PID만 정리할 것.
- 키가 없어서 못 한 검증(Jev, GPT-6 Luna)은 완료 보고에 명시할 것.