# React Use Chat

[![npm version](https://badge.fury.io/js/react-use-chat.svg)](https://badge.fury.io/js/react-use-chat)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)

一个用于构建对话式引导流程的 React Hook，支持单选、多选和嵌套分支处理。

[English](./README.en.md) | 中文

## ✨ 特性

- 🎯 **对话式交互** - 以聊天形式引导用户完成复杂流程
- 🎛️ **多种选择类型** - 支持单选、多选和自动完成
- 🌳 **嵌套分支处理** - 支持多层嵌套的复杂对话树
- 🎨 **灵活配置** - 支持自定义执行顺序和互斥选项
- 📝 **历史记录** - 自动记录用户的选择历史
- 🎁 **计划推荐** - 基于用户选择生成个性化推荐
- 📱 **响应式** - 支持桌面和移动端
- 🔧 **TypeScript** - 完整的类型定义支持
- 🪝 **React Hooks** - 现代 React 开发模式
- ⚡ **轻量级** - 无额外依赖，体积小巧

## 📦 安装

```bash
npm install react-use-chat
```

```bash
yarn add react-use-chat
```

```bash
pnpm add react-use-chat
```

## 🚀 快速开始

### 基础用法

```tsx
import React from 'react';
import { useDialog } from 'react-use-chat';

const dialogData = [
  {
    node_id: 'welcome',
    question_text: '你想学习什么？',
    answer_type: 'single_select',
    answers: [
      {
        answer_id: 'math',
        answer_text: '数学',
        next_node_id: 'math_level',
        plan_trigger: null,
      },
      {
        answer_id: 'english',
        answer_text: '英语',
        next_node_id: null,
        plan_trigger: '英语学习计划',
      },
    ],
  },
  {
    node_id: 'math_level',
    question_text: '选择你的数学水平：',
    answer_type: 'multi_select',
    answers: [
      {
        answer_id: 'basic',
        answer_text: '基础',
        next_node_id: null,
        plan_trigger: '基础数学',
      },
      {
        answer_id: 'advanced',
        answer_text: '高级',
        next_node_id: null,
        plan_trigger: '高级数学',
      },
    ],
  },
];

function App() {
  const {
    currentNode,
    history,
    selectedAnswers,
    handleSelection,
    handleMultiSelect,
    confirmSelections,
    resetDialog,
  } = useDialog(dialogData);

  if (!currentNode) {
    return <div>对话已完成</div>;
  }

  return (
    <div>
      <h2>{currentNode.question_text}</h2>
      
      {currentNode.answer_type === 'single_select' && (
        <div>
          {currentNode.answers.map((answer) => (
            <button
              key={answer.answer_id}
              onClick={() => handleSelection(answer.answer_id)}
            >
              {answer.answer_text}
            </button>
          ))}
        </div>
      )}

      {currentNode.answer_type === 'multi_select' && (
        <div>
          {currentNode.answers.map((answer) => (
            <label key={answer.answer_id}>
              <input
                type="checkbox"
                checked={selectedAnswers.includes(answer.answer_id)}
                onChange={() => handleMultiSelect(answer.answer_id)}
              />
              {answer.answer_text}
            </label>
          ))}
          <button 
            onClick={confirmSelections}
            disabled={selectedAnswers.length === 0}
          >
            确认选择
          </button>
        </div>
      )}
    </div>
  );
}
```

### 使用 Context Provider（可选）

对于复杂的应用程序，你也可以使用 Context Provider 方式：

```tsx
import React from 'react';
import { DialogProvider, useDialogContext } from 'react-use-chat';

function DialogComponent() {
  const { currentNode, handleSelection } = useDialogContext();
  // ... 组件逻辑
}

function App() {
  return (
    <DialogProvider dialogData={dialogData}>
      <DialogComponent />
    </DialogProvider>
  );
}
```

## 📚 API 参考

### useDialog Hook

```tsx
const result = useDialog(dialogData, options);
```

#### 参数

| 参数 | 类型 | 必需 | 描述 |
| --- | --- | --- | --- |
| `dialogData` | `DialogNode[]` | ✅ | 对话节点数据数组 |
| `options` | `UseDialogOptions` | ❌ | 配置选项 |

#### 选项 (UseDialogOptions)

```tsx
interface UseDialogOptions {
  initialNodeId?: string;        // 初始节点 ID
  findNodeById?: (nodeId: string) => DialogNode | null; // 自定义节点查找函数
  autoCompleteDelay?: number;    // 自动完成延迟时间（毫秒）
}
```

#### 返回值 (UseDialogReturn)

```tsx
interface UseDialogReturn {
  currentNode: DialogNode | null;           // 当前节点
  history: HistoryItem[];                   // 历史记录
  learningPlan: string[];                   // 学习计划
  selectedAnswers: string[];                // 已选择的答案
  multiSelectBranchStack: SelectionBranch[][]; // 多选分支栈
  currentBranchIndices: number[];           // 分支索引
  pendingPlanTriggers: string[];            // 待处理的计划触发器
  handleSelection: (answerId: string) => void;      // 处理单选
  handleMultiSelect: (answerId: string) => void;    // 处理多选
  confirmSelections: () => void;            // 确认多选
  resetDialog: () => void;                  // 重置对话
}
```

### 数据结构

#### DialogNode

```tsx
interface DialogNode {
  node_id: string;                    // 节点唯一标识
  question_text: string;              // 问题文本
  answer_type: AnswerType;            // 答案类型
  answers: DialogAnswer[];            // 答案选项
  default_next_node_id?: string | null;     // 默认下一节点（auto_complete 类型）
  default_plan_trigger?: string | null;     // 默认计划触发器
  execute_by_config_order?: boolean;  // 是否按配置顺序执行
  is_branch_end?: boolean;            // 是否为分支结束节点
}
```

#### DialogAnswer

```tsx
interface DialogAnswer {
  answer_id: string;           // 答案唯一标识
  answer_text: string;         // 答案文本
  next_node_id: string | null; // 下一个节点 ID
  plan_trigger: string | null; // 计划触发器
  execution_order?: number;    // 执行顺序
  mutually_exclusive?: boolean; // 是否互斥
}
```

#### AnswerType

```tsx
type AnswerType = "single_select" | "multi_select" | "auto_complete";
```

## 🎯 高级用法

### 嵌套多选分支

系统支持复杂的嵌套多选场景：

```tsx
const complexDialogData = [
  {
    node_id: 'subjects',
    question_text: '选择你想学习的学科：',
    answer_type: 'multi_select',
    execute_by_config_order: true,
    answers: [
      {
        answer_id: 'math',
        answer_text: '数学',
        next_node_id: 'math_topics', // 指向另一个多选节点
        plan_trigger: null,
        execution_order: 0,
      },
      {
        answer_id: 'english',
        answer_text: '英语',
        next_node_id: 'english_skills', // 指向另一个多选节点
        plan_trigger: null,
        execution_order: 1,
      },
    ],
  },
  // 数学主题的多选节点
  {
    node_id: 'math_topics',
    question_text: '选择数学学习重点：',
    answer_type: 'multi_select',
    answers: [
      {
        answer_id: 'algebra',
        answer_text: '代数',
        next_node_id: null,
        plan_trigger: '代数学习计划',
      },
      {
        answer_id: 'geometry',
        answer_text: '几何',
        next_node_id: null,
        plan_trigger: '几何学习计划',
      },
    ],
  },
  // ... 更多节点
];
```

### 互斥选项

在多选模式下，可以设置互斥选项：

```tsx
{
  node_id: 'level_selection',
  question_text: '选择你的水平和学习偏好：',
  answer_type: 'multi_select',
  answers: [
    {
      answer_id: 'beginner',
      answer_text: '初学者',
      mutually_exclusive: true, // 与其他水平选项互斥
      next_node_id: null,
      plan_trigger: '初学者计划',
    },
    {
      answer_id: 'advanced',
      answer_text: '高级',
      mutually_exclusive: true, // 与其他水平选项互斥
      next_node_id: null,
      plan_trigger: '高级计划',
    },
    {
      answer_id: 'visual_learner',
      answer_text: '视觉学习者',
      next_node_id: null,
      plan_trigger: '视觉学习计划',
    },
  ],
}
```

### 自动完成节点

```tsx
{
  node_id: 'processing',
  question_text: '正在生成你的个性化学习计划...',
  answer_type: 'auto_complete',
  answers: [],
  default_next_node_id: 'results',
  default_plan_trigger: null,
}
```

### 自定义节点查找

```tsx
const { currentNode } = useDialog(dialogData, {
  findNodeById: (nodeId) => {
    // 自定义查找逻辑，比如从 API 获取
    return fetch(`/api/nodes/${nodeId}`).then(res => res.json());
  },
});
```

### 滚动事件监听

Hook 会在对话更新时派发滚动事件：

```tsx
import { SCROLL_TO_BOTTOM_EVENT } from 'react-use-chat';

useEffect(() => {
  const handleScroll = () => {
    window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
  };

  document.addEventListener(SCROLL_TO_BOTTOM_EVENT, handleScroll);
  return () => document.removeEventListener(SCROLL_TO_BOTTOM_EVENT, handleScroll);
}, []);
```

## 🎨 样式定制

Hook 本身不包含样式，你可以完全自定义 UI。参考示例项目中的 CSS：

```css
.dialog-container {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.question {
  margin-bottom: 20px;
  font-size: 18px;
  font-weight: 600;
}

.answer-button {
  display: block;
  width: 100%;
  margin-bottom: 10px;
  padding: 12px 16px;
  border: 2px solid #e1e1e1;
  background: white;
  cursor: pointer;
  transition: all 0.2s ease;
}

.answer-button:hover {
  border-color: #007bff;
  background: #f8f9fa;
}
```

## 🔧 开发

### 安装依赖

```bash
npm install
```

### 构建

```bash
npm run build
```

### 测试

```bash
npm test
```

### 运行示例

```bash
cd example
npm install
npm start
```

## 📄 许可证

MIT © [Your Name](https://github.com/defaultjacky)

## 🤝 贡献

欢迎贡献代码！请阅读 [贡献指南](./CONTRIBUTING.md) 了解详情。

### 贡献者

<a href="https://github.com/defaultjacky/react-use-chat/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=defaultjacky/react-use-chat" />
</a>

## 📮 支持

- 💬 [GitHub Discussions](https://github.com/defaultjacky/react-use-chat/discussions)
- 🐛 [GitHub Issues](https://github.com/defaultjacky/react-use-chat/issues)
- 📧 [邮件支持](mailto:your.email@example.com)

## 🔗 相关项目

- [react-hook-form](https://github.com/react-hook-form/react-hook-form) - 性能优异的表单库
- [react-query](https://github.com/TanStack/query) - 数据获取和状态管理
- [zustand](https://github.com/pmndrs/zustand) - 轻量级状态管理

---

如果这个项目对你有帮助，请给个 ⭐️ 支持一下！
