# Task: 대시보드 집단지성 탭 추가

## 개요
대시보드에 5번째 탭 "집단지성"을 추가하여, AI집단지성 그룹챗의 요약/인사이트/원본 대화를 열람하고
/정리 기능을 대시보드에서 트리거할 수 있게 한다.

## 대상 파일
- `/home/jay/workspace/dashboard/server.py` — API 추가
- `/home/jay/workspace/dashboard/index.html` — 5번째 탭 UI 추가

## 데이터 소스 경로
- JSONL 원본: `/home/jay/workspace/memory/groupchat/*.jsonl` (날짜별)
- 요약: `/home/jay/workspace/memory/groupchat/summaries/*.json`
- 인사이트: `/home/jay/workspace/memory/groupchat/insights/*.md`

## Part 1: server.py API 추가

### 1-1. GET /api/groupchat/summaries
summaries/ 디렉토리의 JSON 파일들을 읽어 목록 반환.
query param: `date` (선택, 기본값 오늘)

응답 예시:
```json
{
  "status": "ok",
  "date": "2026-03-15",
  "summaries": [
    {
      "filename": "2026-03-15_general_001.json",
      "timestamp": "2026-03-15T14:30:00",
      "summary": "요약 텍스트...",
      "key_topics": ["insuwiki", "firebase"],
      "topic_tag": "general",
      "key_decisions": ["Firebase Auth 방식 결정"],
      "consensus_level": "agreed",
      "participants": ["제이회장님", "잼민이", "코덱스", "클로디"],
      "message_range": {"from": 1, "to": 50}
    }
  ],
  "available_dates": ["2026-03-15", "2026-03-14"]
}
```

구현:
```python
if self.path.startswith("/api/groupchat/summaries"):
    params = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
    date = params.get("date", [datetime.now().strftime("%Y-%m-%d")])[0]
    groupchat_dir = self.server.data_loader.memory_dir / "groupchat"
    summaries_dir = groupchat_dir / "summaries"

    summaries = []
    if summaries_dir.exists():
        for f in sorted(summaries_dir.glob(f"{date}_*.json")):
            try:
                data = json.loads(f.read_text(encoding="utf-8"))
                data["filename"] = f.name
                summaries.append(data)
            except: pass

    # 사용 가능한 날짜 목록 (JSONL 파일명에서 추출)
    available_dates = sorted(
        [f.stem for f in groupchat_dir.glob("*.jsonl")],
        reverse=True
    )[:30]

    self._json_response({"status": "ok", "date": date, "summaries": summaries, "available_dates": available_dates})
    return
```

### 1-2. GET /api/groupchat/insights
insights/ 디렉토리의 .md 파일들을 읽어 목록 반환.
query param: `date` (선택, 기본값 오늘)

응답 예시:
```json
{
  "status": "ok",
  "insights": [
    {
      "filename": "2026-03-15_insight_001.md",
      "content": "# 핵심 논점\n...",
      "timestamp": "2026-03-15T15:00:00"
    }
  ]
}
```

구현: insights/ 디렉토리 glob → .md 파일 읽기 → 파일 mtime을 timestamp로.

### 1-3. GET /api/groupchat/messages
특정 날짜의 JSONL 원본 메시지 반환. 최근 100개 제한.
query param: `date` (선택, 기본값 오늘), `limit` (선택, 기본값 100)

응답:
```json
{
  "status": "ok",
  "messages": [
    {"sender": "제이회장님", "text": "...", "timestamp": "...", "is_bot": false}
  ],
  "total_count": 450
}
```

구현: JSONL 파일 읽기 → 최근 N개 반환. chat_id 필드는 응답에서 제외.

### 1-4. POST /api/groupchat/organize
/정리 트리거. 대시보드에서 버튼 클릭 시 트리거 파일을 생성한다.
봇의 group_chat.py 메인 루프에서 이 파일을 감지하여 generate_insight()를 실행.

구현:
```python
if self.path == "/api/groupchat/organize":
    trigger_path = self.server.data_loader.memory_dir / "events" / "dashboard-organize-trigger.json"
    trigger_data = {
        "type": "organize",
        "source": "dashboard",
        "timestamp": datetime.now().isoformat()
    }
    trigger_path.parent.mkdir(parents=True, exist_ok=True)
    trigger_path.write_text(json.dumps(trigger_data, ensure_ascii=False), encoding="utf-8")
    self._json_response({"status": "ok", "message": "정리 요청이 전송되었습니다. 봇이 처리 후 결과가 표시됩니다."})
    return
```

### 1-5. SSE 감시 대상 추가
server.py의 SSE 파일 감시 목록에 groupchat 관련 경로 추가:
- `memory/groupchat/summaries/` 디렉토리 변경 감시
- `memory/groupchat/insights/` 디렉토리 변경 감시

## Part 2: index.html UI 추가

### 2-1. 탭 추가
기존 탭 배열에 집단지성 탭 추가:
```javascript
const tabs = [
    { id: 'org', label: '조직뷰' },
    { id: 'project', label: '프로젝트뷰' },
    { id: 'system', label: '시스템뷰' },
    { id: 'archive', label: '기록' },
    { id: 'groupchat', label: '집단지성' },
];
```

탭 렌더링 영역에 추가:
```jsx
{activeTab === 'groupchat' && <GroupChatView />}
```

### 2-2. GroupChatView 컴포넌트

#### 전체 레이아웃
```
┌─────────────────────────────────────────────┐
│ 📅 날짜선택    [2026-03-15 ▼]   [🔄 정리요청] │
├─────────────────────────────────────────────┤
│ ■ 요약 (3건)                                 │
│ ┌─────────────────────────────────────────┐ │
│ │ #1 general — AI집단지성 메모리 설계        │ │
│ │ 참여: 제이회장님, 잼민이, 코덱스, 클로디    │ │
│ │ 결정: Firebase Auth 방식 결정             │ │
│ │ 상태: agreed ✅                           │ │
│ │ 요약: 주제 전환 감지 방법에 대해 논의...     │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ #2 insuwiki — InsuWiki 기능 논의          │ │
│ │ ...                                      │ │
│ └─────────────────────────────────────────┘ │
├─────────────────────────────────────────────┤
│ ■ 인사이트 (1건)                             │
│ ┌─────────────────────────────────────────┐ │
│ │ 2026-03-15_insight_001.md                │ │
│ │ (마크다운 렌더링된 내용)                   │ │
│ └─────────────────────────────────────────┘ │
├─────────────────────────────────────────────┤
│ ■ 원본 대화 (최근 100건)        [펼치기/접기] │
│ ┌─────────────────────────────────────────┐ │
│ │ 14:30 제이회장님: AI집단지성 좋다!!!        │ │
│ │ 14:31 잼민이: 동의합니다. 특히...          │ │
│ │ 14:32 코덱스: 구현 관점에서...             │ │
│ │ ...                                      │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
```

#### 구현 세부

**날짜 선택**: `available_dates` 배열로 `<select>` 드롭다운. 변경 시 해당 날짜 데이터 fetch.

**정리요청 버튼**:
```jsx
const handleOrganize = async () => {
    setOrganizing(true);
    try {
        const res = await fetch('/api/groupchat/organize', { method: 'POST' });
        const data = await res.json();
        if (data.status === 'ok') {
            // 성공 메시지 표시
        }
    } finally {
        setOrganizing(false);
    }
};
```
버튼 UI: 파란색, 클릭 시 로딩 상태, 처리 완료 후 성공 메시지.

**요약 카드**:
- topic_tag 뱃지 (색상 구분)
- key_topics 태그 목록
- key_decisions 리스트
- consensus_level 상태 표시 (exploratory 🔍 / tentative 🤔 / agreed ✅ / decided 🏆)
- participants 아바타/이름
- summary 텍스트
- message_range 표시

**인사이트 섹션**:
- insights 목록. 마크다운 렌더링 (`marked` + `DOMPurify` — 이미 CDN 로드됨).
- 접기/펼치기 지원.

**원본 대화 섹션**:
- 기본 접힌 상태. "펼치기" 클릭 시 최근 100건 표시.
- 채팅 버블 스타일: 봇 메시지(좌측, 회색 배경) vs 사용자 메시지(우측, 파란 배경).
- 타임스탬프 표시.

### 2-3. 데이터 fetch
GroupChatView 내부에서 독립적으로 fetch:
```javascript
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
const [summaries, setSummaries] = useState([]);
const [insights, setInsights] = useState([]);
const [messages, setMessages] = useState([]);
const [availableDates, setAvailableDates] = useState([]);
const [showMessages, setShowMessages] = useState(false);

useEffect(() => {
    fetchGroupchatData(selectedDate);
}, [selectedDate]);

const fetchGroupchatData = async (date) => {
    const [sumRes, insRes] = await Promise.all([
        fetch(`/api/groupchat/summaries?date=${date}`),
        fetch(`/api/groupchat/insights?date=${date}`)
    ]);
    // ... set state
};
```
원본 대화는 "펼치기" 클릭 시에만 fetch (lazy load).

### 2-4. SSE 연동
기존 SSE 이벤트 수신 시 groupchat 탭이 활성화되어 있으면 데이터 재조회.

## 디자인 가이드
- 기존 대시보드 스타일(Tailwind, Inter 폰트, slate 색상 팔레트) 통일
- 요약 카드: `bg-white rounded-xl shadow-sm border border-slate-200 p-4`
- topic_tag 뱃지 색상: `bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full text-xs`
- consensus_level 뱃지:
  - exploratory: `bg-gray-100 text-gray-600`
  - tentative: `bg-yellow-100 text-yellow-700`
  - agreed: `bg-green-100 text-green-700`
  - decided: `bg-purple-100 text-purple-700`
- 채팅 버블: 봇=좌측 `bg-slate-100`, 사용자=우측 `bg-blue-500 text-white`
- 정리요청 버튼: `bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg`

## 테스트 요구사항
- server.py 4개 신규 API가 정상 응답하는지 확인
- summaries/insights 디렉토리가 없을 때 빈 배열 반환 확인
- JSONL 파일이 없을 때 빈 배열 반환 확인
- organize API 호출 시 trigger 파일 생성 확인

## 절대 규칙
- 기존 4개 탭의 동작을 절대 변경하지 말 것
- 기존 API에 영향 주지 말 것
- server.py의 기존 코드 구조(DataLoader, DashboardHandler) 패턴을 따를 것
- index.html의 기존 React 컴포넌트 패턴 따를 것
