基于 Dify 实现 AI 流式对话:组件设计思路(React)

在 AI 应用开发中,流式对话是提升用户体验的关键特性 ------ 它能让 AI 响应像人类聊天一样 "边想边说",而非等待完整结果后一次性展示。本文将以一个招聘场景的 AI 助手为例,详解如何基于 Dify 平台实现流式对话功能,包括组件设计、流程编排和核心技术细节。

涉及到的组件库:https://ant-design-x.antgroup.com/docs/react/introduce-cn

一、场景与核心需求

我们要实现的是一个 "招聘 AI 助手",核心功能包括:

  • 支持用户与 AI 的自然语言流式对话
  • 提供结构化表单输入(如职位信息填写)
  • 展示 AI 思考过程(节点步骤 + 推理内容)
  • 生成可编辑的职位描述(JD)并同步至第三方系统(如北森)

整个流程需要实现 "输入→流式响应→结果处理→第三方同步" 的完整闭环,而 Dify 作为 AI 中间件,负责处理对话逻辑和流式输出。

二、核心组件设计

为实现上述功能,我们将系统拆分为 4 个核心模块,各司其职又协同工作:

1. 主容器组件(HrChatMessageList)

作为页面入口,管理全局状态和核心流程,主要职责包括:

  • 维护聊天记录(messages状态)
  • 处理用户输入(文本 / 表单)
  • 协调 AI 响应的渲染
  • 管理加载状态和错误处理

核心状态设计:

复制代码
// 存储所有聊天消息(用户/AI/表单)
const [messages, setMessages] = useState([]);
// 输入框内容
const [inputValue, setInputValue] = useState("");
// 累积AI响应内容(普通消息/JD内容)
const assistantMessage = useRef("");
const jdMessage = useRef("");
// 思考链节点记录
const thoughtChainRef = useRef([]);

2. Dify 交互钩子(useDifyAgent)

封装与 Dify API 的通信逻辑,是连接前端与 AI 的核心桥梁。它的核心功能包括:

  • 初始化 Dify 客户端(配置 API 密钥和基础 URL)
  • 提供sendMessage方法,支持流式请求
  • 解析 Dify 的流式响应(区分节点事件、消息事件、结束事件)
  • 通过回调函数将数据传递给主组件

关键代码片段:

复制代码
const [agent] = useXAgent({
    baseURL: "https://...", dify链接
    dangerouslyApiKey: "Bearer dify密钥",
  });
const { sendMessage } = useXChat({ agent });

// 发送消息并处理流式响应
const sendMessage = async (params) => {
  agent.request(
    { response_mode: "streaming", ...params },
    {
      onUpdate: (messages) => {
        const data = JSON.parse(messages.data);
        // 处理节点开始事件(更新思考链)
        if (data.event === "node_started") {
          onNode(decodeURIComponent(data.data.title));
        }
        // 处理消息事件(实时更新UI)
        if (data.event === "message") {
          onMessage({ currentText: data.answer });
        }
        // 处理结束事件(完成响应)
        if (data.event === "message_end") {
          onSuccess();
        }
      }
    }
  );
};

3. 结构化表单组件(JdFormComponent)

提供职位信息的结构化输入,通过ref暴露验证和数据格式化能力:

复制代码
// 父组件通过ref调用表单验证
useImperativeHandle(ref, () => ({
  validateAndGetData: () => {
    return form.validateFields().then(values => {
      // 格式化表单数据(如将多行文本转为数组)
      return {
        MainResponsibilities: values.coreResponsibilities.split('\n'),
        ...
      };
    });
  }
}));

4. 思考链展示组件(ThoughtChainList)

可视化 AI 的思考过程,支持展开 / 收起,增强用户对 AI 决策的理解:

复制代码
// 展示节点步骤和思考内容
<ThoughtChain items={items} />
{think && <div dangerouslySetInnerHTML={{ __html: md.render(think) }} />}

三、核心状态与 Ref 说明(父组件关键数据)

状态 / Ref 作用
messages 存储所有聊天消息(用户 / AI / 表单)
inputValue 输入框内容
assistantMessage 累积 AI 的普通响应内容
jdMessage 累积 AI 生成的 JD 内容
thoughtChainRef 存储 AI 对话的节点步骤(思考链)
isCreateJdRef 标记是否处于「JD 生成」节点
difyConversationIdRef 存储 Dify 对话 ID,用于关联历史消息

四、关键回调函数流转

useDifyAgent 是回调流转的核心,各回调的作用:

  1. handleMessage(父组件):接收 Dify 的消息内容,更新 AI 回复的实时渲染。
  2. handleError(父组件):处理 API 错误,更新 UI 提示。
  3. handleSuccess(父组件):API 请求结束后,完善 AI 消息的 UI(添加表单 / JD 编辑框)。
  4. handleNode(父组件):更新思考链节点状态(新增、标记成功)。
  5. handleSavetotaTokens(父组件):记录对话消耗的 token 数。

五、完整流程详解

1. 初始化与历史加载

  • 页面加载时通过 URL 参数获取对话 ID
  • 调用getMessageInfoList接口拉取历史消息
  • 解析历史消息,区分用户消息和 AI 消息,重建思考链状态
  • 若为首次进入,自动触发欢迎消息的流式请求

2. 用户输入与消息发送

支持两种输入方式,最终都通过sendMessage提交给 Dify:

方式 1:文本输入

复制代码
const sendDifyMessage = (value) => {
  // 添加用户消息和AI加载状态到聊天列表
  addUserMessage([{ content: value, role: "user" }, loadingMessage]);
  // 保存用户消息到本地
  saveUserConversation({ content: value, ... });
  // 调用Dify接口
  sendMessage({ query: value, ... });
};

方式 2:表单提交

复制代码
const handleSubmit = async () => {
  // 验证并获取表单数据
  const formData = await formRef.current.validateAndGetData();
  // 格式化并发送表单数据
  addSubmittedFormMessage(formData);
  sendMessage({ query: formattedContent, ... });
};

3. 流式响应处理

这是整个流程的核心,通过 Dify 的流式输出实时更新 UI:

  1. 节点开始事件 :当 Dify 开始处理某个节点(如 "分析需求"、"生成 JD"),前端收到node_started事件,更新思考链节点状态:

    const handleNode = (value) => {
    thoughtChainRef.current.push({
    title: value,
    status: "pending",
    icon: <LoadingOutlined />
    });
    // 更新UI展示新节点
    setMessages(prev => [...prev.slice(0, -1), newAiMessage]);
    };

  2. 消息内容事件:Dify 返回实时生成的文本,前端累积内容并渲染:

    const handleMessage = (msg) => {
    // 累积AI响应内容
    assistantMessage.current += msg.currentText;
    // 实时更新UI
    setMessages(prev => {
    const lastMsg = prev[prev.length - 1];
    return [...prev.slice(0, -1), { ...lastMsg, content: renderedContent }];
    });
    };

  3. 响应结束事件:当 Dify 完成所有处理,前端完善 UI(如添加操作按钮):

    const handleSuccess = () => {
    // 更新最后一条AI消息,添加JD编辑框和同步按钮
    setMessages(prev => {
    const lastMsg = prev[prev.length - 1];
    return [...prev.slice(0, -1), { ...lastMsg, footer: syncButton }];
    });
    };

4. 结果处理与第三方同步

以 JD 生成为例,完成 AI 响应后支持:

  • 手动编辑 AI 生成的 JD 内容

  • 生成 Word 文档并上传至 OSS

  • 同步至北森系统:

    const createJdFile = async () => {
    // 1. 生成Word文档
    const doc = new Document({ ... });
    const blob = await Packer.toBlob(doc);

    复制代码
    // 2. 上传至OSS
    const formData = new FormData();
    formData.append('file', new File([blob], 'JD.docx'));
    await uploadToOSS(formData);
    
    // 3. 同步至北森
    await saveJobDescription({ url: ossUrl, ... });

    };

六、技术亮点与注意事项

  1. 状态管理技巧 :使用ref存储临时累积的内容(如 AI 响应、思考链),避免频繁触发重渲染;用useState管理需要展示的状态(如messages)。

  2. 流式渲染优化 :通过dangerouslySetInnerHTML配合 markdown 解析器,支持富文本实时渲染;控制更新频率,避免性能问题。

  3. 错误处理 :在onError回调中统一处理 API 错误,更新 UI 提示用户,同时清理临时状态。

  4. 第三方集成:通过签名 URL 方式安全上传文件至 OSS,避免前端暴露密钥;封装同步接口,便于替换不同的第三方系统。

七、效果

完整流程(历史对话)

对话过程中(收集进度未完成)

所有完成之后会走到同步至北森逻辑

总结

基于 Dify 实现流式对话的核心是:通过封装 API 交互层(useDifyAgent)分离业务逻辑与 UI 渲染,同时设计合理的状态管理策略,让前端既能实时响应 AI 的流式输出,又能保持良好的用户体验。

这种架构不仅适用于招聘场景,还可推广到客服、教育等需要 AI 实时交互的领域。关键在于理解流式响应的事件机制,以及如何设计组件间的通信方式,让整个流程清晰可维护。

相关推荐
这个昵称也不能用吗?1 小时前
【安卓 - 小组件 - app进程与桌面进程】
前端
kuilaurence1 小时前
CSS `border-image` 给文字加可拉伸边框
前端·css
一 乐2 小时前
校园墙|校园社区|基于Java+vue的校园墙小程序系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·小程序
一只小阿乐2 小时前
前端react 开发 图书列表分页
前端·react.js·react·ant-
IT古董2 小时前
在 React 项目中使用 Ky 与 TanStack Query 构建现代化数据请求层
前端·react.js·前端框架
小程故事多_802 小时前
LangGraph系列:多智能体终极方案,ReAct+MCP工业级供应链系统
人工智能·react.js·langchain
夏日不想说话2 小时前
一文搞懂 AI 流式响应
前端·node.js·openai
顾安r3 小时前
11.14 脚本网页 青蛙过河
服务器·前端·python·游戏·html
不爱吃糖的程序媛3 小时前
Electron 智能文件分析器开发实战适配鸿蒙
前端·javascript·electron