@nago730/chatbot-library 1.0.0
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/README.md +113 -0
- package/dist/index.d.mts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
### 실제 사용 예시: `App.tsx`
|
|
2
|
+
|
|
3
|
+
이 코드는 당신이 만든 라이브러리를 `npm install` 했다고 가정하고 작성되었습니다.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import React from 'react';
|
|
7
|
+
// 1. 당신이 만든 라이브러리에서 필요한 부품들을 가져옵니다.
|
|
8
|
+
import { useChat, ChatNode } from '@your-id/chatbot-library';
|
|
9
|
+
|
|
10
|
+
// 2. 청소업체 전용 대화 시나리오(Flow) 정의
|
|
11
|
+
const CLEANING_FLOW: Record<string, ChatNode> = {
|
|
12
|
+
start: {
|
|
13
|
+
id: "serviceType",
|
|
14
|
+
question: "안녕하세요! 어떤 청소 서비스가 필요하신가요?",
|
|
15
|
+
options: ["이사청소", "거주청소", "사무실청소"],
|
|
16
|
+
next: (val) => (val === "거주청소" ? "isVacant" : "spaceSize"),
|
|
17
|
+
},
|
|
18
|
+
isVacant: {
|
|
19
|
+
id: "isVacant",
|
|
20
|
+
question: "현재 짐이 있는 상태인가요?",
|
|
21
|
+
options: ["네, 비어있어요", "아니오, 짐이 있어요"],
|
|
22
|
+
next: "spaceSize",
|
|
23
|
+
},
|
|
24
|
+
spaceSize: {
|
|
25
|
+
id: "spaceSize",
|
|
26
|
+
question: "공간의 평수는 어떻게 되나요? (숫자만 입력)",
|
|
27
|
+
next: "complete",
|
|
28
|
+
},
|
|
29
|
+
complete: {
|
|
30
|
+
id: "complete",
|
|
31
|
+
question: "모든 정보가 수집되었습니다. 곧 상담원이 연락드릴게요!",
|
|
32
|
+
isEnd: true,
|
|
33
|
+
next: ""
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default function App() {
|
|
38
|
+
// 3. 라이브러리의 useChat 훅 사용
|
|
39
|
+
// userId는 실제 서비스라면 로그인한 사용자의 ID를 넣으면 됩니다.
|
|
40
|
+
const { node, submitAnswer, answers, isEnd } = useChat(CLEANING_FLOW, "customer_001");
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div style={{ maxWidth: '500px', margin: '40px auto', fontFamily: 'sans-serif' }}>
|
|
44
|
+
<div style={{ padding: '20px', border: '1px solid #eee', borderRadius: '15px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)' }}>
|
|
45
|
+
<h2>🧹 청소 견적 도우미</h2>
|
|
46
|
+
<hr />
|
|
47
|
+
|
|
48
|
+
{/* 질문 영역 */}
|
|
49
|
+
<div style={{ margin: '20px 0', minHeight: '100px' }}>
|
|
50
|
+
<p style={{ fontSize: '18px', fontWeight: 'bold', color: '#333' }}>
|
|
51
|
+
{node.question}
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
{/* 선택지 버튼 (이사청소, 거주청소 등) */}
|
|
55
|
+
{!isEnd && node.options && (
|
|
56
|
+
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
|
57
|
+
{node.options.map((opt) => (
|
|
58
|
+
<button
|
|
59
|
+
key={opt}
|
|
60
|
+
onClick={() => submitAnswer(opt)}
|
|
61
|
+
style={{ padding: '10px 20px', cursor: 'pointer', borderRadius: '8px', border: '1px solid #007bff', background: 'white', color: '#007bff' }}
|
|
62
|
+
>
|
|
63
|
+
{opt}
|
|
64
|
+
</button>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{/* 주관식 입력창 (평수 입력 등) */}
|
|
70
|
+
{!isEnd && !node.options && (
|
|
71
|
+
<input
|
|
72
|
+
type="text"
|
|
73
|
+
placeholder="답변을 입력하고 Enter를 누르세요"
|
|
74
|
+
onKeyDown={(e) => {
|
|
75
|
+
if (e.key === 'Enter') {
|
|
76
|
+
submitAnswer((e.target as HTMLInputElement).value);
|
|
77
|
+
(e.target as HTMLInputElement).value = ""; // 입력창 비우기
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
80
|
+
style={{ width: '100%', padding: '10px', boxSizing: 'border-box', borderRadius: '8px', border: '1px solid #ccc' }}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{/* 4. 실시간 데이터 요약 영역 (사용자에게 현재까지 입력한 정보를 보여줌) */}
|
|
86
|
+
<div style={{ marginTop: '30px', padding: '15px', backgroundColor: '#f8f9fa', borderRadius: '10px' }}>
|
|
87
|
+
<h4 style={{ marginTop: 0 }}>📋 현재까지 수집된 정보</h4>
|
|
88
|
+
<ul style={{ fontSize: '14px', color: '#666' }}>
|
|
89
|
+
<li>서비스 종류: {answers.serviceType || '-'}</li>
|
|
90
|
+
{answers.isVacant && <li>공실 여부: {answers.isVacant}</li>}
|
|
91
|
+
<li>평수: {answers.spaceSize ? `${answers.spaceSize}평` : '-'}</li>
|
|
92
|
+
</ul>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{isEnd && (
|
|
96
|
+
<div style={{ textAlign: 'center', color: 'green', fontWeight: 'bold', marginTop: '20px' }}>
|
|
97
|
+
✅ 신청이 완료되었습니다!
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### 💡 이 `App.tsx` 코드가 특별한 이유
|
|
110
|
+
|
|
111
|
+
1. **실시간 요약:** 하단에 `answers` 객체를 이용해 사용자가 입력한 내용을 바로 보여줍니다. 이는 고객에게 신뢰감을 줍니다.
|
|
112
|
+
2. **동적 질문 처리:** `CLEANING_FLOW`를 보시면 `serviceType`이 무엇이냐에 따라 `isVacant` 질문을 건너뛰거나 포함하는 로직이 적용되어 있습니다.
|
|
113
|
+
3. **UI와 로직의 분리:** 질문 내용이나 순서를 바꾸고 싶을 때, UI 코드를 건드릴 필요 없이 `CLEANING_FLOW` 객체의 내용만 수정하면 됩니다.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
interface ChatNode {
|
|
2
|
+
id: string;
|
|
3
|
+
question: string;
|
|
4
|
+
options?: string[];
|
|
5
|
+
next: string | ((answer: any) => string);
|
|
6
|
+
isEnd?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface ChatState {
|
|
9
|
+
answers: Record<string, any>;
|
|
10
|
+
currentStep: string;
|
|
11
|
+
}
|
|
12
|
+
interface StorageAdapter {
|
|
13
|
+
save: (userId: string, data: any) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare class ChatEngine {
|
|
17
|
+
private flow;
|
|
18
|
+
constructor(flow: Record<string, ChatNode>);
|
|
19
|
+
getCurrentNode(stepId: string): ChatNode;
|
|
20
|
+
getNextStep(currentStepId: string, answer: any): string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare function useChat(flow: Record<string, ChatNode>, userId: string, adapter?: StorageAdapter): {
|
|
24
|
+
node: ChatNode;
|
|
25
|
+
submitAnswer: (value: any) => Promise<void>;
|
|
26
|
+
answers: Record<string, any>;
|
|
27
|
+
isEnd: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { ChatEngine, type ChatNode, type ChatState, type StorageAdapter, useChat };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
interface ChatNode {
|
|
2
|
+
id: string;
|
|
3
|
+
question: string;
|
|
4
|
+
options?: string[];
|
|
5
|
+
next: string | ((answer: any) => string);
|
|
6
|
+
isEnd?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface ChatState {
|
|
9
|
+
answers: Record<string, any>;
|
|
10
|
+
currentStep: string;
|
|
11
|
+
}
|
|
12
|
+
interface StorageAdapter {
|
|
13
|
+
save: (userId: string, data: any) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare class ChatEngine {
|
|
17
|
+
private flow;
|
|
18
|
+
constructor(flow: Record<string, ChatNode>);
|
|
19
|
+
getCurrentNode(stepId: string): ChatNode;
|
|
20
|
+
getNextStep(currentStepId: string, answer: any): string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare function useChat(flow: Record<string, ChatNode>, userId: string, adapter?: StorageAdapter): {
|
|
24
|
+
node: ChatNode;
|
|
25
|
+
submitAnswer: (value: any) => Promise<void>;
|
|
26
|
+
answers: Record<string, any>;
|
|
27
|
+
isEnd: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { ChatEngine, type ChatNode, type ChatState, type StorageAdapter, useChat };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var c=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var C=(e,t)=>{for(var r in t)c(e,r,{get:t[r],enumerable:!0})},E=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of N(t))!w.call(e,o)&&o!==r&&c(e,o,{get:()=>t[o],enumerable:!(n=x(t,o))||n.enumerable});return e};var y=e=>E(c({},"__esModule",{value:!0}),e);var A={};C(A,{ChatEngine:()=>i,useChat:()=>S});module.exports=y(A);var i=class{constructor(t){this.flow=t}getCurrentNode(t){let r=this.flow[t];if(!r)throw new Error(`ChatEngineError: Node with id "${t}" not found in flow.`);return r}getNextStep(t,r){let n=this.flow[t];if(!n)throw new Error(`ChatEngineError: Cannot calculate next step from missing node "${t}".`);return typeof n.next=="function"?n.next(r):n.next}};var s=require("react");function S(e,t,r){let n=(0,s.useMemo)(()=>new i(e),[e]),[o,m]=(0,s.useState)("start"),[a,p]=(0,s.useState)({}),u=(0,s.useCallback)(async g=>{try{let d=n.getCurrentNode(o),f=n.getNextStep(o,g),h={...a,[d.id]:g};p(h),m(f),e[f]?.isEnd&&r&&await r.save(t,h)}catch(d){throw d}},[o,n,a,e,t,r]);return{node:n.getCurrentNode(o),submitAnswer:u,answers:a,isEnd:!!e[o]?.isEnd}}0&&(module.exports={ChatEngine,useChat});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var s=class{constructor(t){this.flow=t}getCurrentNode(t){let r=this.flow[t];if(!r)throw new Error(`ChatEngineError: Node with id "${t}" not found in flow.`);return r}getNextStep(t,r){let e=this.flow[t];if(!e)throw new Error(`ChatEngineError: Cannot calculate next step from missing node "${t}".`);return typeof e.next=="function"?e.next(r):e.next}};import{useState as f,useCallback as u,useMemo as x}from"react";function y(n,t,r){let e=x(()=>new s(n),[n]),[o,h]=f("start"),[i,m]=f({}),p=u(async d=>{try{let a=e.getCurrentNode(o),c=e.getNextStep(o,d),g={...i,[a.id]:d};m(g),h(c),n[c]?.isEnd&&r&&await r.save(t,g)}catch(a){throw a}},[o,e,i,n,t,r]);return{node:e.getCurrentNode(o),submitAnswer:p,answers:i,isEnd:!!n[o]?.isEnd}}export{s as ChatEngine,y as useChat};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nago730/chatbot-library",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "청소업체 등 고객 정보 수집을 위한 실시간 계산 챗봇 엔진",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean --minify",
|
|
13
|
+
"dev": "tsup src/index.ts --format cjs,esm --watch --dts",
|
|
14
|
+
"lint": "tsc"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["chatbot", "headless", "react"],
|
|
17
|
+
"author": "Your Name",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": ">=16.8.0",
|
|
21
|
+
"react-dom": ">=16.8.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/react": "^19.0.0",
|
|
25
|
+
"@types/react-dom": "^19.0.0",
|
|
26
|
+
"react": "^19.0.0",
|
|
27
|
+
"react-dom": "^19.0.0",
|
|
28
|
+
"tsup": "^8.5.1",
|
|
29
|
+
"typescript": "^5.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|