一个基于 Dify 工作流引擎,专注于宪法鉴定式案例分析的智能助教 MVP。
本项目是对 ask-ucass
项目的深度定制修改,旨在探索构建一个面向法学教育的、专注的、生产级的AI应用。遵循“从最简单的解决方案开始,只在必要时增加复杂性”的原则,当前MVP版本聚焦于单一核心功能:宪法案例的鉴定式分析。
它被设计为一个单轮交互的分析工具,而非一个通用的多轮对话机器人,以确保在演示阶段的稳定性和专业性。
- 🎯 核心功能: 对用户输入的宪法案例,进行结构化的鉴定式分析。
- 📝 统一入口: 所有新对话和已清空的对话,都从一个功能完备的“对话设置”表单开始,清晰地引导用户输入。
- 🔄 单轮交互: 完成一次分析后,禁用后续追问,并引导用户开启新的对话,保证了MVP阶段交互流程的清晰可靠。
- ⚙️ Dify驱动: 核心AI逻辑完全由本地部署的Dify工作流(Chatflow)驱动。
- ✨ 现代UI: 采用
shadcn/ui
组件库,实现了专业、美观的用户界面。 - 🏃 状态反馈: 在AI处理请求时,提供清晰的加载和流式输出动画,优化用户等待体验。
- 应用框架: Next.js
- 前端界面: React
- UI 组件库: shadcn/ui & Tailwind CSS
- 类型系统: TypeScript
- AI 引擎: Dify (本地部署)
- 前端状态管理: Zustand
- 后端API: Next.js App Router (作为Dify的安全代理)
本项目依赖 Bun 作为包管理器和运行时。
- 克隆仓库
git clone <your-repo-url> cd <your-repo-name>
- 安装依赖
bun install
- 配置环境变量
- 在项目根目录创建一个名为
.env
的文件。 - 填入您Dify服务的地址和API密钥:
DIFY_URL=<Your Dify Server URL> DIFY_KEY=<Your Dify API Key>
- 在项目根目录创建一个名为
- 运行开发服务器
应用将在
bun run dev
http://localhost:3000
上运行。
- 开启新对话: 访问
http://localhost:3000/chat
,应用会自动跳转到一个新的对话页面。 - 输入案例: 在页面中央的“对话设置”表单中,输入您需要分析的案例描述及其他选填信息。
- 开始分析: 点击“开始分析”按钮,等待AI返回分析结果。
- 开始下一次分析: 在AI回答完毕后,页面底部会提示并引导您“开启新对话”以进行下一次分析。
应用通过一个内部API路由作为前后端交互的桥梁,并作为Dify API的安全代理。
-
路径:
/api/[id]/chat
-
方法:
POST
-
说明:
[id]
是客户端生成的用于追踪对话的UUID。 -
请求体 (Request Body):
{ "message": "用户输入的主要案例描述 (string)", "question_type": "部门法类型 (string, e.g., '宪法')", "answer": "用户自己的答案 (string, optional)", "answer_idea": "用户的分析思路 (string, optional)", "isNewConversation": "是否为新对话 (boolean, optional)", "userId": "用户ID (string, optional)" }
-
响应 (Response): Server-Sent Events (SSE) 流,包含以下事件类型:
-
content: 消息内容片段(在“伪流式”模式下,可能一次性返回全部内容)。
-
metadata: 会话元数据(令牌使用量、处理时间、Dify返回的conversation_id等)。
-
error: 错误信息。
-
done: 结束标记。
-
系统与Dify的集成是本应用的核心,其设计遵循了安全和体验优化的原则。
- 安全代理模式: 前端应用从不直接接触
DIFY_KEY
。所有对Dify的请求都由Next.js后端 (route.ts
) 发起,后端负责从环境变量中读取并附加API密钥,这遵循了API密钥不应暴露在客户端的最佳实践。 - 工作流调用: 应用调用的是Dify的工作流(Chatflow),而非简单的对话模型。这允许我们在Dify端编排复杂的逻辑,如参数提取、工具调用、条件分支等。
- 对话ID管理:
- 创建: 当发起一个新对话时,前端会向后端发送
isNewConversation: true
信号。后端据此向Dify发送一个空的conversation_id
来创建新会话。 - 获取与存储: 后端从Dify返回的数据流中捕获由Dify生成的真实
conversation_id
,并将其传回前端。前端的Zustand状态管理器 (chat-store.ts
) 负责将这个真实ID与当前对话进行关联和持久化存储。 - 延续: 在后续的(未来可能实现的)追问中,将使用这个已存储的真实ID来保持上下文。
- 创建: 当发起一个新对话时,前端会向后端发送
- 流式响应:
- **后端 (
route.ts
)**负责处理与 Dify 的流式通信。它被设计为具备高兼容性:既能实时转发 Dify 工作流中标准的 message 事件,也能在工作流本身不支持流式输出时,智能地从最终的 workflow_finished 事件中提取完整答案,并将其作为单次的 content 事件向下游发送。确保了前端能以统一的方式处理不同类型的后端响应。 - **前端 (
useChatHandler.ts
)**完全基于 Server-Sent Events (SSE) 进行设计。它监听来自后端代理的事件流,并在接收到任何 content 事件时,立刻将文本片段追加到UI界面上,实现内容的实时渲染。确保用户能够即时看到AI的输出,提供了流畅、高效的交互体验。
- **后端 (
在当前稳定的MVP版本基础上,未来可以从以下方向进行迭代:
-
实现对用户答案的深度评价功能
- 这不仅是简单地指出对错,而是要成为一个真正的“助教”。AI需要能够:
- 识别优点与不足:清晰地指出用户答案中值得肯定的地方和存在的逻辑缺陷或知识盲点。
- 提供修改建议:给出具体的、可操作的修改意见,甚至提供重写后的“范例”段落供学生对比学习。
- 推荐学习资源:根据用户答案中暴露出的问题,动态地从知识库中检索并推荐相关的法条、理论解释或经典案例,引导学生进行针对性的巩固学习。
- 这不仅是简单地指出对错,而是要成为一个真正的“助教”。AI需要能够:
-
在Dify工作流中构建多轮对话与追问逻辑
- 这不仅仅是为了补充或完善单次分析结果,更是为了创造一种苏格拉底式的教学体验。
- 通过在工作流中构建“意图识别”和分支逻辑,AI将能够:
- 理解追问意图:区分用户是在要求“澄清概念”、“补充事实”还是“挑战结论”。
- 引导式提问:在评价完用户答案后,主动提出启发性问题,引导学生自己发现答案中的漏洞。
- 动态调整分析:根据用户在追问中提供的新信息或新视角,对最初的分析结果进行动态的补充和修正。
-
逐步支持其他部门法的案例分析
- 在Dify中为民法、刑法等创建新的知识库和处理分支,将应用扩展为一个更全面的法学案例分析平台。
- 实现“中止生成”按钮
- 启用并完善反馈 (
FeedbackForm
) 功能
- 引入用户认证系统,为不同用户保存独立的对话历史。
- 完善应用的错误处理和重试机制。
当前状态: 当后端请求出错时,前端界面会显示一个统一且模糊的提示:“出现了一些错误:但是我也不知道为什么”。虽然应用左下角可能存在一个全局的 issue 反馈入口,但这并不能在当前对话的上下文中为用户提供具体的错误原因。
规划的实现效果: 目标是提供与上下文相关的、具体的、对用户友好的错误信息。这与左下角的全局 issue 反馈功能并不冲突,二者可以互补:
- 全局 Issue 反馈 (左下角): 用于处理系统级、非预期的前端 Bug,由用户主动上报。
- 上下文错误提示 (本规划): 用于处理单次 AI 请求中可预期的后端错误,由系统主动告知用户。
效果举例:
-
场景一:Dify API 密钥错误或服务不通
- 当前效果: 用户点击“开始分析”后,AI 头像持续旋转,最终显示“出现了一些错误...”。
- 期望效果: 用户点击后,在原本应该出现答案的对话气泡中,直接显示一条清晰的错误信息,例如:“请求失败:无法连接到 AI 分析服务。请稍后重试或联系管理员。(错误码: SERVICE_UNAVAILABLE)”
-
场景二:用户的输入触发了内容安全策略
- 当前效果: 同样是显示模糊的通用错误。
- 期望效果: 在对话气泡中明确告知用户原因,例如:“内容安全提醒:您的输入因包含敏感词已被拦截。请检查并修改您提交的案例描述后重试。”
-
场景三:分析任务超时
- 当前效果: 长时间等待后,可能出现通用错误或无响应。
- 期望效果: 在对话气泡中提示:“分析超时:本次案例分析任务执行时间过长,已自动中断。您可以尝试简化问题或稍后重试。”
当前状态: 在本次品牌重塑过程中,我们直接修改了多个文件中的硬编码字符串,例如:
src/components/chat/chat-header.tsx
中的项目名称 "案研社"。src/components/chat-messages.tsx
中的 AI 显示名称 "Assistant"。src/app/layout.tsx
中的页面标题 "案研社 - AI 案例分析助教"。
规划的实现效果:
将所有这些分散的、与品牌或配置相关的“魔法字符串”提取出来,统一存放在一个或多个专门的配置文件中(例如 src/config/brand.ts
)。在整个应用中,任何需要这些值的地方都从这个统一的来源导入并使用。
带来的好处举例:
-
场景:未来决定将项目名从“案研社”改为“法析社”
- 当前做法: 需要在整个项目中搜索“案研社”,逐一检查并修改
chat-header.tsx
,layout.tsx
等多个文件,容易遗漏。 - 期望做法: 只需在
src/config/brand.ts
文件中修改一行代码export const APP_NAME = "法析社";
,整个应用的所有相关显示会自动更新,包括页面标题、页头名称等。
- 当前做法: 需要在整个项目中搜索“案研社”,逐一检查并修改
-
场景:更换 AI 头像文件
- 当前做法: 需要直接修改
chat-messages.tsx
中的图片路径。 - 期望做法: 只需在配置文件中修改
export const ASSISTANT_AVATAR_PATH = "/new_logo.svg";
,即可完成更换。
- 当前做法: 需要直接修改
欢迎各种形式的贡献!您可以:
- [提交一个 Issue](/issues) 来报告BUG或提出功能建议。
- Fork 本仓库并提交 Pull Request。