> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sajuapi.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 일일 운세 스트리밍

Server-Sent Events(SSE)를 통해 운세를 실시간으로 스트리밍합니다.

AI 응답이 생성되는 대로 클라이언트에 전달되어, 사용자에게 더 빠른 피드백을 제공할 수 있습니다.

***

## Request Body

<ParamField body="saju" type="SajuResult" required>
  클라이언트에서 계산한 사주 데이터 객체입니다.
</ParamField>

<ParamField body="userName" type="string" required>
  사용자 이름입니다.
</ParamField>

<ParamField body="date" type="string" required>
  운세를 볼 날짜입니다. YYYY-MM-DD 형식입니다.
</ParamField>

<ParamField body="model" type="string" default="haiku">
  사용할 AI 모델입니다.
</ParamField>

***

## Response

### 성공

SSE 스트림이 시작됩니다. Content-Type은 `text/event-stream`입니다.

### 이벤트 형식

```
data: {"chunk": "오늘은 "}

data: {"chunk": "투자에 "}

data: {"chunk": "좋은 날입니다."}

data: {"done": true, "fortune": {...}}
```

***

## 요청 예시

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch('/api/daily-fortune-stream', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      saju,
      userName: '김철수',
      date: '2025-01-16',
      model: 'haiku'
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split('\n');

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));

        if (data.chunk) {
          // 텍스트 청크 - UI에 추가
          console.log(data.chunk);
        }

        if (data.done) {
          // 스트리밍 완료 - 전체 데이터 사용 가능
          console.log('완료:', data.fortune);
        }
      }
    }
  }
  ```

  ```javascript React Hook theme={null}
  import { useState, useCallback } from 'react';

  function useFortuneStream() {
    const [text, setText] = useState('');
    const [isStreaming, setIsStreaming] = useState(false);
    const [fortune, setFortune] = useState(null);

    const streamFortune = useCallback(async (saju, userName, date, model) => {
      setIsStreaming(true);
      setText('');
      setFortune(null);

      const response = await fetch('/api/daily-fortune-stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ saju, userName, date, model })
      });

      const reader = response.body.getReader();
      const decoder = new TextDecoder();

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value);
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = JSON.parse(line.slice(6));

            if (data.chunk) {
              setText(prev => prev + data.chunk);
            }

            if (data.done) {
              setFortune(data.fortune);
            }
          }
        }
      }

      setIsStreaming(false);
    }, []);

    return { text, isStreaming, fortune, streamFortune };
  }
  ```
</CodeGroup>

***

## SSE 이벤트 구조

### 청크 이벤트

AI가 텍스트를 생성할 때마다 전송됩니다.

```json theme={null}
{
  "chunk": "오늘은 투자에 "
}
```

### 완료 이벤트

스트리밍이 완료되면 전체 운세 데이터와 함께 전송됩니다.

```json theme={null}
{
  "done": true,
  "fortune": {
    "todayFortune": {
      "score": 78,
      "overallAnalysis": "...",
      "actionItems": [...]
    },
    "investmentFortune": {...}
  }
}
```

***

## 에러 처리

스트리밍 중 에러가 발생하면 에러 이벤트가 전송됩니다.

```json theme={null}
{
  "error": true,
  "message": "AI model timeout"
}
```

```javascript theme={null}
// 에러 처리 예시
for (const line of lines) {
  if (line.startsWith('data: ')) {
    const data = JSON.parse(line.slice(6));

    if (data.error) {
      console.error('스트리밍 에러:', data.message);
      // 폴백: 일반 API 호출
      const fallback = await fetch('/api/daily-fortune', {...});
    }
  }
}
```

<Note>
  스트리밍은 긴 응답에 특히 유용합니다. 짧은 응답(character, tendency 등)은 일반 API를 사용하는 것이 더 효율적입니다.
</Note>
