@nago730/chatbot-library 1.1.1 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +392 -392
- package/package.json +50 -36
- package/src/engine.ts +24 -24
- package/src/examples/firebaseAdapter.example.ts +421 -421
- package/src/index.ts +2 -2
- package/src/types.ts +39 -39
- package/src/useChat.ts +338 -338
package/README.md
CHANGED
|
@@ -1,392 +1,392 @@
|
|
|
1
|
-
# @nago730/chatbot-library
|
|
2
|
-
|
|
3
|
-
> **JSON 하나로 만드는 프로덕션 레디 챗봇 엔진** — React 환경에서 복잡한 대화형 인터페이스를 5분 안에 구축하세요.
|
|
4
|
-
|
|
5
|
-
<p align="left">
|
|
6
|
-
<img src="https://img.shields.io/npm/v/@nago730/chatbot-library" alt="npm version" />
|
|
7
|
-
<img src="https://img.shields.io/github/license/Nago730/chatbot-library" alt="license" />
|
|
8
|
-
<img src="https://img.shields.io/npm/dm/@nago730/chatbot-library" alt="downloads" />
|
|
9
|
-
</p>
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## 🎯 핵심 기능 3가지
|
|
14
|
-
|
|
15
|
-
| 기능 | 설명 | 효과 |
|
|
16
|
-
|------|------|------|
|
|
17
|
-
| 🗂️ **JSON 기반 시나리오** | 코드 없이 대화 흐름 설계 | 개발 시간 **90% 단축** |
|
|
18
|
-
| 🔄 **멀티 세션 관리** | 한 사용자가 여러 상담 진행 | 사용자 경험 **향상** |
|
|
19
|
-
| 🔥 **프로덕션 레디** | Firebase 연동 + 비용 최적화 | 운영 비용 **98% 절감** |
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## ⚡ 5분 빠른 시작
|
|
24
|
-
|
|
25
|
-
### 1. 설치
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm install @nago730/chatbot-library
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 2. Flow 정의 (JSON)
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
const SUPPORT_FLOW = {
|
|
35
|
-
start: {
|
|
36
|
-
id: 'start',
|
|
37
|
-
question: '무엇을 도와드릴까요?',
|
|
38
|
-
type: 'button',
|
|
39
|
-
options: ['주문 문의', '배송 조회', '취소/환불'],
|
|
40
|
-
next: (answer) => {
|
|
41
|
-
if (answer === '주문 문의') return 'order';
|
|
42
|
-
if (answer === '배송 조회') return 'delivery';
|
|
43
|
-
return 'refund';
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
order: {
|
|
47
|
-
id: 'order',
|
|
48
|
-
question: '주문번호를 입력해주세요',
|
|
49
|
-
type: 'input',
|
|
50
|
-
next: 'complete'
|
|
51
|
-
},
|
|
52
|
-
complete: {
|
|
53
|
-
id: 'complete',
|
|
54
|
-
question: '감사합니다. 곧 연락드리겠습니다.',
|
|
55
|
-
next: '',
|
|
56
|
-
isEnd: true
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 3. 컴포넌트에서 사용
|
|
62
|
-
|
|
63
|
-
```tsx
|
|
64
|
-
import { useChat } from '@nago730/chatbot-library';
|
|
65
|
-
|
|
66
|
-
function ChatBot() {
|
|
67
|
-
const { node, submitAnswer, submitInput, messages, isEnd } = useChat(
|
|
68
|
-
SUPPORT_FLOW,
|
|
69
|
-
'user_123'
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
if (isEnd) {
|
|
73
|
-
return <div>✅ {node.question}</div>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<div>
|
|
78
|
-
{/* 대화 히스토리 */}
|
|
79
|
-
{messages.map((msg, i) => (
|
|
80
|
-
<div key={i}>
|
|
81
|
-
<p>🤖 {msg.question}</p>
|
|
82
|
-
<p>👤 {msg.answer}</p>
|
|
83
|
-
</div>
|
|
84
|
-
))}
|
|
85
|
-
|
|
86
|
-
{/* 현재 질문 */}
|
|
87
|
-
<p>{node.question}</p>
|
|
88
|
-
|
|
89
|
-
{/* 버튼형 */}
|
|
90
|
-
{node.type === 'button' && node.options?.map(opt => (
|
|
91
|
-
<button key={opt} onClick={() => submitAnswer(opt)}>
|
|
92
|
-
{opt}
|
|
93
|
-
</button>
|
|
94
|
-
))}
|
|
95
|
-
|
|
96
|
-
{/* 입력형 */}
|
|
97
|
-
{node.type === 'input' && (
|
|
98
|
-
<input onKeyDown={(e) => {
|
|
99
|
-
if (e.key === 'Enter') submitInput(e.currentTarget.value);
|
|
100
|
-
}} />
|
|
101
|
-
)}
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
**🎉 완료!** 이제 작동하는 챗봇이 생겼습니다.
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## 📚 핵심 개념
|
|
112
|
-
|
|
113
|
-
### Flow 구조
|
|
114
|
-
|
|
115
|
-
Flow는 **노드(Node)의 집합**입니다. 각 노드는 질문과 다음 단계를 정의합니다.
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
interface ChatNode {
|
|
119
|
-
id: string; // 고유 ID
|
|
120
|
-
question: string; // 사용자에게 보여줄 질문
|
|
121
|
-
type?: 'button' | 'input'; // 답변 받는 방식 (기본: button)
|
|
122
|
-
options?: string[]; // 선택지 (type='button'일 때)
|
|
123
|
-
next: string | ((answer) => string); // 다음 노드 ID (동적 가능)
|
|
124
|
-
isEnd?: boolean; // 대화 종료 표시
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### 세션 관리
|
|
129
|
-
|
|
130
|
-
한 사용자가 **여러 번 상담**을 시작할 수 있습니다.
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
const { sessionId, reset } = useChat(FLOW, userId, 'start', adapter, {
|
|
134
|
-
sessionId: 'auto' // 'auto' | 'new' | 'specific_id'
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// 새 상담 시작
|
|
138
|
-
<button onClick={() => reset()}>새 상담</button>
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### 저장 전략
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
const chat = useChat(FLOW, userId, 'start', adapter, {
|
|
145
|
-
saveStrategy: 'onEnd' // 'always' | 'onEnd'
|
|
146
|
-
});
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
| 전략 | 저장 시점 | 추천 대상 |
|
|
150
|
-
|------|-----------|-----------|
|
|
151
|
-
| `'always'` | 매 답변마다 | 데이터 무결성이 중요한 경우 |
|
|
152
|
-
| `'onEnd'` | 대화 종료 시 | **비용 절감** (권장) |
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## 🔥 Firebase 연동 (프로덕션)
|
|
157
|
-
|
|
158
|
-
### Quick Start
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
import { createHybridFirebaseAdapter } from '@nago730/chatbot-library/examples';
|
|
162
|
-
import { getFirestore } from 'firebase/firestore';
|
|
163
|
-
|
|
164
|
-
const db = getFirestore(app);
|
|
165
|
-
const adapter = createHybridFirebaseAdapter(db, {
|
|
166
|
-
timeout: 5000,
|
|
167
|
-
fallbackToLocal: true,
|
|
168
|
-
debug: false
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const chat = useChat(FLOW, userId, 'start', adapter, {
|
|
172
|
-
saveStrategy: 'onEnd' // 비용 98% 절감!
|
|
173
|
-
});
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### 비용 최적화
|
|
177
|
-
|
|
178
|
-
**10만 사용자, 일 10회 대화 기준 (Firestore)**
|
|
179
|
-
|
|
180
|
-
| 구성 | 월 비용 | 절감율 |
|
|
181
|
-
|------|---------|--------|
|
|
182
|
-
| 기본 설정 (always + 전체 데이터) | $2,700 | - |
|
|
183
|
-
| **하이브리드 + onEnd** ⭐ | **$5.4** | **99.8%** |
|
|
184
|
-
|
|
185
|
-
### 핵심 개선사항
|
|
186
|
-
|
|
187
|
-
- ✅ **기기 전환 복구**: PC → 모바일 대화 이어가기 100%
|
|
188
|
-
- ✅ **네트워크 안정성**: 타임아웃 + 자동 폴백
|
|
189
|
-
- ✅ **타입 안전**: Firebase Timestamp 자동 정규화
|
|
190
|
-
- ✅ **비용 최적화**: 스마트 저장 전략
|
|
191
|
-
|
|
192
|
-
📖 [Firebase 상세 가이드](./docs/firebase-adapter-guide.md)
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
## 🔄 멀티 세션
|
|
197
|
-
|
|
198
|
-
한 사용자가 **여러 상담을 진행**하고 **이전 대화를 불러올** 수 있습니다.
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
const { sessionId, reset, isEnd } = useChat(FLOW, userId, 'start', adapter, {
|
|
202
|
-
sessionId: 'auto'
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// UI 예제
|
|
206
|
-
<div>
|
|
207
|
-
<p>현재 세션: {sessionId}</p>
|
|
208
|
-
|
|
209
|
-
{isEnd && (
|
|
210
|
-
<button onClick={() => reset()}>
|
|
211
|
-
새 상담 시작
|
|
212
|
-
</button>
|
|
213
|
-
)}
|
|
214
|
-
|
|
215
|
-
<button onClick={() => reset('session_1706000000_abc')}>
|
|
216
|
-
이전 상담 보기
|
|
217
|
-
</button>
|
|
218
|
-
</div>
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
📖 [멀티 세션 완벽 가이드](./docs/multi-session-guide.md)
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## 📖 API Reference
|
|
226
|
-
|
|
227
|
-
### useChat
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
useChat(
|
|
231
|
-
flow: Record<string, ChatNode>,
|
|
232
|
-
userId: string,
|
|
233
|
-
initialNodeId?: string,
|
|
234
|
-
adapter?: StorageAdapter,
|
|
235
|
-
options?: ChatOptions
|
|
236
|
-
)
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
#### Parameters
|
|
240
|
-
|
|
241
|
-
| 파라미터 | 타입 | 설명 |
|
|
242
|
-
|----------|------|------|
|
|
243
|
-
| `flow` | `Record<string, ChatNode>` | 시나리오 Flow 객체 |
|
|
244
|
-
| `userId` | `string` | 사용자 ID (세션 키로 사용) |
|
|
245
|
-
| `initialNodeId` | `string` | 시작 노드 ID (기본: `'start'`) |
|
|
246
|
-
| `adapter` | `StorageAdapter` | 저장소 어댑터 (선택) |
|
|
247
|
-
| `options` | `ChatOptions` | 추가 옵션 (선택) |
|
|
248
|
-
|
|
249
|
-
#### ChatOptions
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
interface ChatOptions {
|
|
253
|
-
saveStrategy?: 'always' | 'onEnd'; // 저장 시점
|
|
254
|
-
scenarioId?: string; // 시나리오 ID
|
|
255
|
-
sessionId?: 'auto' | 'new' | string; // 세션 전략
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
#### Return Values
|
|
260
|
-
|
|
261
|
-
```typescript
|
|
262
|
-
{
|
|
263
|
-
node: ChatNode; // 현재 노드
|
|
264
|
-
submitAnswer: (value: any) => Promise<void>; // 버튼 답변 제출
|
|
265
|
-
submitInput: (value: string) => Promise<void>; // 텍스트 답변 제출
|
|
266
|
-
answers: Record<string, any>; // 수집된 답변
|
|
267
|
-
messages: ChatMessage[]; // 대화 히스토리
|
|
268
|
-
isEnd: boolean; // 종료 여부
|
|
269
|
-
sessionId: string; // 현재 세션 ID
|
|
270
|
-
reset: (sessionId?: string) => void; // 세션 리셋
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### StorageAdapter
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
interface StorageAdapter {
|
|
278
|
-
saveState: (userId: string, state: ChatState) => Promise<void>;
|
|
279
|
-
loadState: (userId: string) => Promise<ChatState | null>;
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## 📚 전체 문서
|
|
286
|
-
|
|
287
|
-
### 가이드
|
|
288
|
-
- 📘 [**Complete Guide**](./docs/complete-guide.md) - 모든 기능 + 실전 패턴
|
|
289
|
-
- 🔥 [Firebase Adapter Guide](./docs/firebase-adapter-guide.md)
|
|
290
|
-
- 🔄 [Multi-Session Guide](./docs/multi-session-guide.md)
|
|
291
|
-
- ⚡ [Quick Reference](./docs/firebase-quick-reference.md)
|
|
292
|
-
|
|
293
|
-
### 학습 자료
|
|
294
|
-
- ✅ [Best Practices](./docs/best-practices.md) - DO's & DON'Ts
|
|
295
|
-
- 💡 [Examples](./docs/examples.md) - 실전 코드 모음
|
|
296
|
-
- 🔧 [예제 코드](./src/examples/)
|
|
297
|
-
|
|
298
|
-
---
|
|
299
|
-
|
|
300
|
-
## ⚠️ Common Pitfalls
|
|
301
|
-
|
|
302
|
-
개발 시 자주 발생하는 실수들:
|
|
303
|
-
|
|
304
|
-
1. ❌ **sessionId 없이 멀티 상담 구현** → `reset()` 사용하세요
|
|
305
|
-
2. ❌ **saveStrategy: 'always' + 실시간 타이핑** → `'onEnd'` 사용 권장
|
|
306
|
-
3. ❌ **Firebase Timestamp 정규화 누락** → 어댑터 예제 코드 사용
|
|
307
|
-
4. ❌ **에러 핸들링 없음** → `fallbackToLocal: true` 설정 필수
|
|
308
|
-
|
|
309
|
-
📖 [전체 Best Practices 보기](./docs/best-practices.md)
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
## 🚀 실전 예제
|
|
314
|
-
|
|
315
|
-
### 고객 지원 챗봇
|
|
316
|
-
|
|
317
|
-
```typescript
|
|
318
|
-
const SUPPORT_FLOW = {
|
|
319
|
-
start: { /* ... */ },
|
|
320
|
-
order_inquiry: { /* ... */ },
|
|
321
|
-
delivery_status: { /* ... */ },
|
|
322
|
-
refund: { /* ... */ }
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
function CustomerSupport() {
|
|
326
|
-
const { node, submitAnswer, reset, sessionId } = useChat(
|
|
327
|
-
SUPPORT_FLOW,
|
|
328
|
-
customerId,
|
|
329
|
-
'start',
|
|
330
|
-
firebaseAdapter,
|
|
331
|
-
{ sessionId: 'auto', saveStrategy: 'onEnd' }
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
return <ChatUI node={node} onAnswer={submitAnswer} onReset={reset} />;
|
|
335
|
-
}
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
더 많은 예제: [Examples](./docs/examples.md)
|
|
339
|
-
|
|
340
|
-
---
|
|
341
|
-
|
|
342
|
-
## 🛠️ 타입 정의
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
// ChatNode
|
|
346
|
-
interface ChatNode {
|
|
347
|
-
id: string;
|
|
348
|
-
question: string;
|
|
349
|
-
type?: 'button' | 'input';
|
|
350
|
-
options?: string[];
|
|
351
|
-
next: string | ((answer: any) => string);
|
|
352
|
-
isEnd?: boolean;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// ChatMessage
|
|
356
|
-
interface ChatMessage {
|
|
357
|
-
nodeId: string;
|
|
358
|
-
question: string;
|
|
359
|
-
answer: any;
|
|
360
|
-
timestamp: number;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// ChatState
|
|
364
|
-
interface ChatState {
|
|
365
|
-
answers: Record<string, any>;
|
|
366
|
-
currentStep: string;
|
|
367
|
-
messages: ChatMessage[];
|
|
368
|
-
flowHash: string;
|
|
369
|
-
updatedAt: number;
|
|
370
|
-
}
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
---
|
|
374
|
-
|
|
375
|
-
## 🤝 기여하기
|
|
376
|
-
|
|
377
|
-
이 라이브러리는 프리랜서 외주 작업을 하며 반복되는 챗봇 구현에 지쳐 만들어졌습니다.
|
|
378
|
-
AI 기반 개발에 최적화된 문서를 목표로 하고 있습니다.
|
|
379
|
-
|
|
380
|
-
- ⭐ **Star** 하나가 개발 동기부여가 됩니다!
|
|
381
|
-
- 🐛 버그 제보: [Issues](https://github.com/Nago730/chatbot-library/issues)
|
|
382
|
-
- 💡 기능 제안: [Issues](https://github.com/Nago730/chatbot-library/issues) (기능 제안도 환영합니다!)
|
|
383
|
-
|
|
384
|
-
---
|
|
385
|
-
|
|
386
|
-
## 📄 라이선스
|
|
387
|
-
|
|
388
|
-
MIT License
|
|
389
|
-
|
|
390
|
-
---
|
|
391
|
-
|
|
392
|
-
**Made with ❤️ for Vibe Coders** — AI 시대의 더 나은 개발 경험을 위해
|
|
1
|
+
# @nago730/chatbot-library
|
|
2
|
+
|
|
3
|
+
> **JSON 하나로 만드는 프로덕션 레디 챗봇 엔진** — React 환경에서 복잡한 대화형 인터페이스를 5분 안에 구축하세요.
|
|
4
|
+
|
|
5
|
+
<p align="left">
|
|
6
|
+
<img src="https://img.shields.io/npm/v/@nago730/chatbot-library" alt="npm version" />
|
|
7
|
+
<img src="https://img.shields.io/github/license/Nago730/chatbot-library" alt="license" />
|
|
8
|
+
<img src="https://img.shields.io/npm/dm/@nago730/chatbot-library" alt="downloads" />
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🎯 핵심 기능 3가지
|
|
14
|
+
|
|
15
|
+
| 기능 | 설명 | 효과 |
|
|
16
|
+
|------|------|------|
|
|
17
|
+
| 🗂️ **JSON 기반 시나리오** | 코드 없이 대화 흐름 설계 | 개발 시간 **90% 단축** |
|
|
18
|
+
| 🔄 **멀티 세션 관리** | 한 사용자가 여러 상담 진행 | 사용자 경험 **향상** |
|
|
19
|
+
| 🔥 **프로덕션 레디** | Firebase 연동 + 비용 최적화 | 운영 비용 **98% 절감** |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ⚡ 5분 빠른 시작
|
|
24
|
+
|
|
25
|
+
### 1. 설치
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @nago730/chatbot-library
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Flow 정의 (JSON)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const SUPPORT_FLOW = {
|
|
35
|
+
start: {
|
|
36
|
+
id: 'start',
|
|
37
|
+
question: '무엇을 도와드릴까요?',
|
|
38
|
+
type: 'button',
|
|
39
|
+
options: ['주문 문의', '배송 조회', '취소/환불'],
|
|
40
|
+
next: (answer) => {
|
|
41
|
+
if (answer === '주문 문의') return 'order';
|
|
42
|
+
if (answer === '배송 조회') return 'delivery';
|
|
43
|
+
return 'refund';
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
order: {
|
|
47
|
+
id: 'order',
|
|
48
|
+
question: '주문번호를 입력해주세요',
|
|
49
|
+
type: 'input',
|
|
50
|
+
next: 'complete'
|
|
51
|
+
},
|
|
52
|
+
complete: {
|
|
53
|
+
id: 'complete',
|
|
54
|
+
question: '감사합니다. 곧 연락드리겠습니다.',
|
|
55
|
+
next: '',
|
|
56
|
+
isEnd: true
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. 컴포넌트에서 사용
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { useChat } from '@nago730/chatbot-library';
|
|
65
|
+
|
|
66
|
+
function ChatBot() {
|
|
67
|
+
const { node, submitAnswer, submitInput, messages, isEnd } = useChat(
|
|
68
|
+
SUPPORT_FLOW,
|
|
69
|
+
'user_123'
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (isEnd) {
|
|
73
|
+
return <div>✅ {node.question}</div>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div>
|
|
78
|
+
{/* 대화 히스토리 */}
|
|
79
|
+
{messages.map((msg, i) => (
|
|
80
|
+
<div key={i}>
|
|
81
|
+
<p>🤖 {msg.question}</p>
|
|
82
|
+
<p>👤 {msg.answer}</p>
|
|
83
|
+
</div>
|
|
84
|
+
))}
|
|
85
|
+
|
|
86
|
+
{/* 현재 질문 */}
|
|
87
|
+
<p>{node.question}</p>
|
|
88
|
+
|
|
89
|
+
{/* 버튼형 */}
|
|
90
|
+
{node.type === 'button' && node.options?.map(opt => (
|
|
91
|
+
<button key={opt} onClick={() => submitAnswer(opt)}>
|
|
92
|
+
{opt}
|
|
93
|
+
</button>
|
|
94
|
+
))}
|
|
95
|
+
|
|
96
|
+
{/* 입력형 */}
|
|
97
|
+
{node.type === 'input' && (
|
|
98
|
+
<input onKeyDown={(e) => {
|
|
99
|
+
if (e.key === 'Enter') submitInput(e.currentTarget.value);
|
|
100
|
+
}} />
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**🎉 완료!** 이제 작동하는 챗봇이 생겼습니다.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 📚 핵심 개념
|
|
112
|
+
|
|
113
|
+
### Flow 구조
|
|
114
|
+
|
|
115
|
+
Flow는 **노드(Node)의 집합**입니다. 각 노드는 질문과 다음 단계를 정의합니다.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
interface ChatNode {
|
|
119
|
+
id: string; // 고유 ID
|
|
120
|
+
question: string; // 사용자에게 보여줄 질문
|
|
121
|
+
type?: 'button' | 'input'; // 답변 받는 방식 (기본: button)
|
|
122
|
+
options?: string[]; // 선택지 (type='button'일 때)
|
|
123
|
+
next: string | ((answer) => string); // 다음 노드 ID (동적 가능)
|
|
124
|
+
isEnd?: boolean; // 대화 종료 표시
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 세션 관리
|
|
129
|
+
|
|
130
|
+
한 사용자가 **여러 번 상담**을 시작할 수 있습니다.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const { sessionId, reset } = useChat(FLOW, userId, 'start', adapter, {
|
|
134
|
+
sessionId: 'auto' // 'auto' | 'new' | 'specific_id'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// 새 상담 시작
|
|
138
|
+
<button onClick={() => reset()}>새 상담</button>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 저장 전략
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const chat = useChat(FLOW, userId, 'start', adapter, {
|
|
145
|
+
saveStrategy: 'onEnd' // 'always' | 'onEnd'
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| 전략 | 저장 시점 | 추천 대상 |
|
|
150
|
+
|------|-----------|-----------|
|
|
151
|
+
| `'always'` | 매 답변마다 | 데이터 무결성이 중요한 경우 |
|
|
152
|
+
| `'onEnd'` | 대화 종료 시 | **비용 절감** (권장) |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 🔥 Firebase 연동 (프로덕션)
|
|
157
|
+
|
|
158
|
+
### Quick Start
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { createHybridFirebaseAdapter } from '@nago730/chatbot-library/examples';
|
|
162
|
+
import { getFirestore } from 'firebase/firestore';
|
|
163
|
+
|
|
164
|
+
const db = getFirestore(app);
|
|
165
|
+
const adapter = createHybridFirebaseAdapter(db, {
|
|
166
|
+
timeout: 5000,
|
|
167
|
+
fallbackToLocal: true,
|
|
168
|
+
debug: false
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const chat = useChat(FLOW, userId, 'start', adapter, {
|
|
172
|
+
saveStrategy: 'onEnd' // 비용 98% 절감!
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 비용 최적화
|
|
177
|
+
|
|
178
|
+
**10만 사용자, 일 10회 대화 기준 (Firestore)**
|
|
179
|
+
|
|
180
|
+
| 구성 | 월 비용 | 절감율 |
|
|
181
|
+
|------|---------|--------|
|
|
182
|
+
| 기본 설정 (always + 전체 데이터) | $2,700 | - |
|
|
183
|
+
| **하이브리드 + onEnd** ⭐ | **$5.4** | **99.8%** |
|
|
184
|
+
|
|
185
|
+
### 핵심 개선사항
|
|
186
|
+
|
|
187
|
+
- ✅ **기기 전환 복구**: PC → 모바일 대화 이어가기 100%
|
|
188
|
+
- ✅ **네트워크 안정성**: 타임아웃 + 자동 폴백
|
|
189
|
+
- ✅ **타입 안전**: Firebase Timestamp 자동 정규화
|
|
190
|
+
- ✅ **비용 최적화**: 스마트 저장 전략
|
|
191
|
+
|
|
192
|
+
📖 [Firebase 상세 가이드](./docs/firebase-adapter-guide.md)
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 🔄 멀티 세션
|
|
197
|
+
|
|
198
|
+
한 사용자가 **여러 상담을 진행**하고 **이전 대화를 불러올** 수 있습니다.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const { sessionId, reset, isEnd } = useChat(FLOW, userId, 'start', adapter, {
|
|
202
|
+
sessionId: 'auto'
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// UI 예제
|
|
206
|
+
<div>
|
|
207
|
+
<p>현재 세션: {sessionId}</p>
|
|
208
|
+
|
|
209
|
+
{isEnd && (
|
|
210
|
+
<button onClick={() => reset()}>
|
|
211
|
+
새 상담 시작
|
|
212
|
+
</button>
|
|
213
|
+
)}
|
|
214
|
+
|
|
215
|
+
<button onClick={() => reset('session_1706000000_abc')}>
|
|
216
|
+
이전 상담 보기
|
|
217
|
+
</button>
|
|
218
|
+
</div>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
📖 [멀티 세션 완벽 가이드](./docs/multi-session-guide.md)
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 📖 API Reference
|
|
226
|
+
|
|
227
|
+
### useChat
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
useChat(
|
|
231
|
+
flow: Record<string, ChatNode>,
|
|
232
|
+
userId: string,
|
|
233
|
+
initialNodeId?: string,
|
|
234
|
+
adapter?: StorageAdapter,
|
|
235
|
+
options?: ChatOptions
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### Parameters
|
|
240
|
+
|
|
241
|
+
| 파라미터 | 타입 | 설명 |
|
|
242
|
+
|----------|------|------|
|
|
243
|
+
| `flow` | `Record<string, ChatNode>` | 시나리오 Flow 객체 |
|
|
244
|
+
| `userId` | `string` | 사용자 ID (세션 키로 사용) |
|
|
245
|
+
| `initialNodeId` | `string` | 시작 노드 ID (기본: `'start'`) |
|
|
246
|
+
| `adapter` | `StorageAdapter` | 저장소 어댑터 (선택) |
|
|
247
|
+
| `options` | `ChatOptions` | 추가 옵션 (선택) |
|
|
248
|
+
|
|
249
|
+
#### ChatOptions
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
interface ChatOptions {
|
|
253
|
+
saveStrategy?: 'always' | 'onEnd'; // 저장 시점
|
|
254
|
+
scenarioId?: string; // 시나리오 ID
|
|
255
|
+
sessionId?: 'auto' | 'new' | string; // 세션 전략
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Return Values
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
{
|
|
263
|
+
node: ChatNode; // 현재 노드
|
|
264
|
+
submitAnswer: (value: any) => Promise<void>; // 버튼 답변 제출
|
|
265
|
+
submitInput: (value: string) => Promise<void>; // 텍스트 답변 제출
|
|
266
|
+
answers: Record<string, any>; // 수집된 답변
|
|
267
|
+
messages: ChatMessage[]; // 대화 히스토리
|
|
268
|
+
isEnd: boolean; // 종료 여부
|
|
269
|
+
sessionId: string; // 현재 세션 ID
|
|
270
|
+
reset: (sessionId?: string) => void; // 세션 리셋
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### StorageAdapter
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
interface StorageAdapter {
|
|
278
|
+
saveState: (userId: string, state: ChatState) => Promise<void>;
|
|
279
|
+
loadState: (userId: string) => Promise<ChatState | null>;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 📚 전체 문서
|
|
286
|
+
|
|
287
|
+
### 가이드
|
|
288
|
+
- 📘 [**Complete Guide**](./docs/complete-guide.md) - 모든 기능 + 실전 패턴
|
|
289
|
+
- 🔥 [Firebase Adapter Guide](./docs/firebase-adapter-guide.md)
|
|
290
|
+
- 🔄 [Multi-Session Guide](./docs/multi-session-guide.md)
|
|
291
|
+
- ⚡ [Quick Reference](./docs/firebase-quick-reference.md)
|
|
292
|
+
|
|
293
|
+
### 학습 자료
|
|
294
|
+
- ✅ [Best Practices](./docs/best-practices.md) - DO's & DON'Ts
|
|
295
|
+
- 💡 [Examples](./docs/examples.md) - 실전 코드 모음
|
|
296
|
+
- 🔧 [예제 코드](./src/examples/)
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## ⚠️ Common Pitfalls
|
|
301
|
+
|
|
302
|
+
개발 시 자주 발생하는 실수들:
|
|
303
|
+
|
|
304
|
+
1. ❌ **sessionId 없이 멀티 상담 구현** → `reset()` 사용하세요
|
|
305
|
+
2. ❌ **saveStrategy: 'always' + 실시간 타이핑** → `'onEnd'` 사용 권장
|
|
306
|
+
3. ❌ **Firebase Timestamp 정규화 누락** → 어댑터 예제 코드 사용
|
|
307
|
+
4. ❌ **에러 핸들링 없음** → `fallbackToLocal: true` 설정 필수
|
|
308
|
+
|
|
309
|
+
📖 [전체 Best Practices 보기](./docs/best-practices.md)
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## 🚀 실전 예제
|
|
314
|
+
|
|
315
|
+
### 고객 지원 챗봇
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const SUPPORT_FLOW = {
|
|
319
|
+
start: { /* ... */ },
|
|
320
|
+
order_inquiry: { /* ... */ },
|
|
321
|
+
delivery_status: { /* ... */ },
|
|
322
|
+
refund: { /* ... */ }
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
function CustomerSupport() {
|
|
326
|
+
const { node, submitAnswer, reset, sessionId } = useChat(
|
|
327
|
+
SUPPORT_FLOW,
|
|
328
|
+
customerId,
|
|
329
|
+
'start',
|
|
330
|
+
firebaseAdapter,
|
|
331
|
+
{ sessionId: 'auto', saveStrategy: 'onEnd' }
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
return <ChatUI node={node} onAnswer={submitAnswer} onReset={reset} />;
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
더 많은 예제: [Examples](./docs/examples.md)
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 🛠️ 타입 정의
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
// ChatNode
|
|
346
|
+
interface ChatNode {
|
|
347
|
+
id: string;
|
|
348
|
+
question: string;
|
|
349
|
+
type?: 'button' | 'input';
|
|
350
|
+
options?: string[];
|
|
351
|
+
next: string | ((answer: any) => string);
|
|
352
|
+
isEnd?: boolean;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ChatMessage
|
|
356
|
+
interface ChatMessage {
|
|
357
|
+
nodeId: string;
|
|
358
|
+
question: string;
|
|
359
|
+
answer: any;
|
|
360
|
+
timestamp: number;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ChatState
|
|
364
|
+
interface ChatState {
|
|
365
|
+
answers: Record<string, any>;
|
|
366
|
+
currentStep: string;
|
|
367
|
+
messages: ChatMessage[];
|
|
368
|
+
flowHash: string;
|
|
369
|
+
updatedAt: number;
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 🤝 기여하기
|
|
376
|
+
|
|
377
|
+
이 라이브러리는 프리랜서 외주 작업을 하며 반복되는 챗봇 구현에 지쳐 만들어졌습니다.
|
|
378
|
+
AI 기반 개발에 최적화된 문서를 목표로 하고 있습니다.
|
|
379
|
+
|
|
380
|
+
- ⭐ **Star** 하나가 개발 동기부여가 됩니다!
|
|
381
|
+
- 🐛 버그 제보: [Issues](https://github.com/Nago730/chatbot-library/issues)
|
|
382
|
+
- 💡 기능 제안: [Issues](https://github.com/Nago730/chatbot-library/issues) (기능 제안도 환영합니다!)
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 📄 라이선스
|
|
387
|
+
|
|
388
|
+
MIT License
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
**Made with ❤️ for Vibe Coders** — AI 시대의 더 나은 개발 경험을 위해
|