JSON νλλ‘ λ§λλ νλ‘λμ λ λ μ±λ΄ μμ§ β React νκ²½μμ 볡μ‘ν λνν μΈν°νμ΄μ€λ₯Ό 5λΆ μμ ꡬμΆνμΈμ.
| κΈ°λ₯ | μ€λͺ | ν¨κ³Ό |
|---|---|---|
| ποΈ JSON κΈ°λ° μλλ¦¬μ€ | μ½λ μμ΄ λν νλ¦ μ€κ³ | κ°λ° μκ° 90% λ¨μΆ |
| π λ©ν° μΈμ κ΄λ¦¬ | ν μ¬μ©μκ° μ¬λ¬ μλ΄ μ§ν | μ¬μ©μ κ²½ν ν₯μ |
| π₯ νλ‘λμ λ λ | Firebase μ°λ + λΉμ© μ΅μ ν | μ΄μ λΉμ© 98% μ κ° |
npm install @nago730/chatbot-libraryconst SUPPORT_FLOW = {
start: {
id: 'start',
question: '무μμ λμλ릴κΉμ?',
type: 'button',
options: ['μ£Όλ¬Έ λ¬Έμ', 'λ°°μ‘ μ‘°ν', 'μ·¨μ/νλΆ'],
next: (answer) => {
if (answer === 'μ£Όλ¬Έ λ¬Έμ') return 'order';
if (answer === 'λ°°μ‘ μ‘°ν') return 'delivery';
return 'refund';
}
},
order: {
id: 'order',
question: 'μ£Όλ¬Έλ²νΈλ₯Ό μ
λ ₯ν΄μ£ΌμΈμ',
type: 'input',
next: 'complete'
},
complete: {
id: 'complete',
question: 'κ°μ¬ν©λλ€. κ³§ μ°λ½λλ¦¬κ² μ΅λλ€.',
next: '',
isEnd: true
}
};import { useChat } from '@nago730/chatbot-library';
function ChatBot() {
const { node, submitAnswer, submitInput, messages, isEnd } = useChat(
SUPPORT_FLOW,
'user_123'
);
if (isEnd) {
return <div>β
{node.question}</div>;
}
return (
<div>
{/* λν νμ€ν 리 */}
{messages.map((msg, i) => (
<div key={i}>
<p>π€ {msg.question}</p>
<p>π€ {msg.answer}</p>
</div>
))}
{/* νμ¬ μ§λ¬Έ */}
<p>{node.question}</p>
{/* λ²νΌν */}
{node.type === 'button' && node.options?.map(opt => (
<button key={opt} onClick={() => submitAnswer(opt)}>
{opt}
</button>
))}
{/* μ
λ ₯ν */}
{node.type === 'input' && (
<input onKeyDown={(e) => {
if (e.key === 'Enter') submitInput(e.currentTarget.value);
}} />
)}
</div>
);
}π μλ£! μ΄μ μλνλ μ±λ΄μ΄ μκ²Όμ΅λλ€.
Flowλ λ Έλ(Node)μ μ§ν©μ λλ€. κ° λ Έλλ μ§λ¬Έκ³Ό λ€μ λ¨κ³λ₯Ό μ μν©λλ€.
interface ChatNode {
id: string; // κ³ μ ID
question: string; // μ¬μ©μμκ² λ³΄μ¬μ€ μ§λ¬Έ
type?: 'button' | 'input'; // λ΅λ³ λ°λ λ°©μ (κΈ°λ³Έ: button)
options?: string[]; // μ νμ§ (type='button'μΌ λ)
next: string | ((answer) => string); // λ€μ λ
Έλ ID (λμ κ°λ₯)
isEnd?: boolean; // λν μ’
λ£ νμ
}ν μ¬μ©μκ° μ¬λ¬ λ² μλ΄μ μμν μ μμ΅λλ€.
const { sessionId, reset } = useChat(FLOW, userId, 'start', adapter, {
sessionId: 'auto' // 'auto' | 'new' | 'specific_id'
});
// μ μλ΄ μμ
<button onClick={() => reset()}>μ μλ΄</button>const chat = useChat(FLOW, userId, 'start', adapter, {
saveStrategy: 'onEnd' // 'always' | 'onEnd'
});| μ λ΅ | μ μ₯ μμ | μΆμ² λμ |
|---|---|---|
'always' |
λ§€ λ΅λ³λ§λ€ | λ°μ΄ν° 무결μ±μ΄ μ€μν κ²½μ° |
'onEnd' |
λν μ’ λ£ μ | λΉμ© μ κ° (κΆμ₯) |
import { createHybridFirebaseAdapter } from '@nago730/chatbot-library/examples';
import { getFirestore } from 'firebase/firestore';
const db = getFirestore(app);
const adapter = createHybridFirebaseAdapter(db, {
timeout: 5000,
fallbackToLocal: true,
debug: false
});
const chat = useChat(FLOW, userId, 'start', adapter, {
saveStrategy: 'onEnd' // λΉμ© 98% μ κ°!
});10λ§ μ¬μ©μ, μΌ 10ν λν κΈ°μ€ (Firestore)
| κ΅¬μ± | μ λΉμ© | μ κ°μ¨ |
|---|---|---|
| κΈ°λ³Έ μ€μ (always + μ 체 λ°μ΄ν°) | $2,700 | - |
| νμ΄λΈλ¦¬λ + onEnd β | $5.4 | 99.8% |
- β κΈ°κΈ° μ ν 볡ꡬ: PC β λͺ¨λ°μΌ λν μ΄μ΄κ°κΈ° 100%
- β λ€νΈμν¬ μμ μ±: νμμμ + μλ ν΄λ°±
- β νμ μμ : Firebase Timestamp μλ μ κ·ν
- β λΉμ© μ΅μ ν: μ€λ§νΈ μ μ₯ μ λ΅
π Firebase μμΈ κ°μ΄λ
ν μ¬μ©μκ° μ¬λ¬ μλ΄μ μ§ννκ³ μ΄μ λνλ₯Ό λΆλ¬μ¬ μ μμ΅λλ€.
const { sessionId, reset, isEnd } = useChat(FLOW, userId, 'start', adapter, {
sessionId: 'auto'
});
// UI μμ
<div>
<p>νμ¬ μΈμ
: {sessionId}</p>
{isEnd && (
<button onClick={() => reset()}>
μ μλ΄ μμ
</button>
)}
<button onClick={() => reset('session_1706000000_abc')}>
μ΄μ μλ΄ λ³΄κΈ°
</button>
</div>π λ©ν° μΈμ μλ²½ κ°μ΄λ
useChat(
flow: Record<string, ChatNode>,
userId: string,
initialNodeId?: string,
adapter?: StorageAdapter,
options?: ChatOptions
)| νλΌλ―Έν° | νμ | μ€λͺ |
|---|---|---|
flow |
Record<string, ChatNode> |
μλλ¦¬μ€ Flow κ°μ²΄ |
userId |
string |
μ¬μ©μ ID (μΈμ ν€λ‘ μ¬μ©) |
initialNodeId |
string |
μμ λ
Έλ ID (κΈ°λ³Έ: 'start') |
adapter |
StorageAdapter |
μ μ₯μ μ΄λν° (μ ν) |
options |
ChatOptions |
μΆκ° μ΅μ (μ ν) |
interface ChatOptions {
saveStrategy?: 'always' | 'onEnd'; // μ μ₯ μμ
scenarioId?: string; // μλλ¦¬μ€ ID
sessionId?: 'auto' | 'new' | string; // μΈμ
μ λ΅
}{
node: ChatNode; // νμ¬ λ
Έλ
submitAnswer: (value: any) => Promise<void>; // λ²νΌ λ΅λ³ μ μΆ
submitInput: (value: string) => Promise<void>; // ν
μ€νΈ λ΅λ³ μ μΆ
answers: Record<string, any>; // μμ§λ λ΅λ³
messages: ChatMessage[]; // λν νμ€ν 리
isEnd: boolean; // μ’
λ£ μ¬λΆ
sessionId: string; // νμ¬ μΈμ
ID
reset: (sessionId?: string) => void; // μΈμ
리μ
}interface StorageAdapter {
saveState: (userId: string, state: ChatState) => Promise<void>;
loadState: (userId: string) => Promise<ChatState | null>;
}- π Complete Guide - λͺ¨λ κΈ°λ₯ + μ€μ ν¨ν΄
- π₯ Firebase Adapter Guide
- π Multi-Session Guide
- β‘ Quick Reference
- β Best Practices - DO's & DON'Ts
- π‘ Examples - μ€μ μ½λ λͺ¨μ
- π§ μμ μ½λ
κ°λ° μ μμ£Ό λ°μνλ μ€μλ€:
- β sessionId μμ΄ λ©ν° μλ΄ κ΅¬ν β
reset()μ¬μ©νμΈμ - β saveStrategy: 'always' + μ€μκ° νμ΄ν β
'onEnd'μ¬μ© κΆμ₯ - β Firebase Timestamp μ κ·ν λλ½ β μ΄λν° μμ μ½λ μ¬μ©
- β μλ¬ νΈλ€λ§ μμ β
fallbackToLocal: trueμ€μ νμ
π μ 체 Best Practices 보기
const SUPPORT_FLOW = {
start: { /* ... */ },
order_inquiry: { /* ... */ },
delivery_status: { /* ... */ },
refund: { /* ... */ }
};
function CustomerSupport() {
const { node, submitAnswer, reset, sessionId } = useChat(
SUPPORT_FLOW,
customerId,
'start',
firebaseAdapter,
{ sessionId: 'auto', saveStrategy: 'onEnd' }
);
return <ChatUI node={node} onAnswer={submitAnswer} onReset={reset} />;
}λ λ§μ μμ : Examples
// ChatNode
interface ChatNode {
id: string;
question: string;
type?: 'button' | 'input';
options?: string[];
next: string | ((answer: any) => string);
isEnd?: boolean;
}
// ChatMessage
interface ChatMessage {
nodeId: string;
question: string;
answer: any;
timestamp: number;
}
// ChatState
interface ChatState {
answers: Record<string, any>;
currentStep: string;
messages: ChatMessage[];
flowHash: string;
updatedAt: number;
}μ΄ λΌμ΄λΈλ¬λ¦¬λ ν리λμ μΈμ£Ό μμ
μ νλ©° λ°λ³΅λλ μ±λ΄ ꡬνμ μ§μ³ λ§λ€μ΄μ‘μ΅λλ€.
AI κΈ°λ° κ°λ°μ μ΅μ νλ λ¬Έμλ₯Ό λͺ©νλ‘ νκ³ μμ΅λλ€.
- β Star νλκ° κ°λ° λκΈ°λΆμ¬κ° λ©λλ€!
- π λ²κ·Έ μ 보: Issues
- π‘ κΈ°λ₯ μ μ: Issues (κΈ°λ₯ μ μλ νμν©λλ€!)
MIT License
Made with β€οΈ for Vibe Coders β AI μλμ λ λμ κ°λ° κ²½νμ μν΄