跳转到内容

用户体验

3 篇包含标签 "用户体验" 的文章

渐进式披露:如何用少即是多的理念改进 AI 产品的人机交互

渐进式披露:如何用”少即是多”的理念改进 AI 产品的人机交互

Section titled “渐进式披露:如何用”少即是多”的理念改进 AI 产品的人机交互”

在 AI 产品设计中,用户输入的质量往往决定了输出的质量。本文分享我们在 HagiCode 项目中实践的一套”渐进式披露”交互方案,通过分步引导、智能补全和即时反馈,将用户简短模糊的输入转化为结构化的技术提案,显著提升了人机交互效率。

做 AI 产品的同学应该都遇到过这样的场景:用户打开你的应用,兴致勃勃地输入一行需求,结果 AI 返回的内容完全不搭边。不是 AI 不聪明,只是用户给的信息太少了,毕竟猜心这种事,谁也做不好。

这种现象在我们开发 HagiCode 的过程中尤为明显。HagiCode 是一个 AI 驱动的代码助手,用户通过自然语言描述需求来创建技术提案和会话。可在实际使用中,我们发现用户输入的内容往往存在这些问题:

  • 输入质量参差不齐:有的用户只输入几个字,比如”优化登录”、“修复 bug”,缺乏必要的上下文
  • 技术术语不统一:不同用户用不同的词说同一件事,有人说”前端”有人说”FE”
  • 缺少结构化信息:没有项目背景、没有仓库范围、没有影响范围这些关键信息
  • 重复性问题:相同类型的需求反复出现,每次都要从头解释

这些问题导致的直接后果就是:AI 理解困难、生成的提案质量不稳定、用户体验差。用户觉得”这 AI 不行啊”,我们也很委屈——你只给一句话,让我怎么猜你想要啥?

其实这也没办法,毕竟人和人之间的理解都需要时间,更何况是机器呢?

为了解决这些痛点,我们做了一个大胆的决定:引入”渐进式披露”的设计理念来改进人机交互。这个决定带来的变化,可能比你想象的还要大,只是当时我们也没想到会这么有效罢了。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个开源的 AI 代码助手项目,旨在通过自然语言交互帮助开发者完成代码编写、技术提案生成、代码审查等任务。项目地址:github.com/HagiCode-org/site

这套渐进式披露方案是我们在实际开发过程中,经过多次迭代和优化总结出来的。如果你觉得这套方案有价值,说明我们的工程实力还不错——那么 HagiCode 本身也值得关注一下,毕竟好东西是值得分享的。

“渐进式披露”(Progressive Disclosure)是一个源自 HCI(人机交互)领域的设计原则,核心思想很简单:不要一次性把所有信息和选项都展示给用户,而是根据用户的操作和需求,逐步展示必要的内容

这个原则特别适合 AI 产品,因为 AI 交互天然就是渐进式的——用户说一点,AI 理解一点,然后补充一点,再理解更多。就像人与人之间的交流一样,总得慢慢来,毕竟谁也不能一见面就把心掏出来不是?

具体到 HagiCode 的场景,我们从四个方面实施了渐进式披露:

1. 描述优化机制:让 AI 帮你把话说清楚

Section titled “1. 描述优化机制:让 AI 帮你把话说清楚”

当用户输入简短描述时,我们不是直接让 AI 去理解,而是先触发一个”描述优化”流程。这个流程的核心是”结构化输出”——把用户的自由文本转化为标准格式。就像把散落一地的珍珠串成项链,看起来也就不那么乱了。

优化后的描述必须包含以下几个标准章节:

  • 背景:问题背景和上下文
  • 分析:技术分析和思考过程
  • 解决:解决方案和实施步骤
  • 实践:实际代码示例和注意事项

同时,我们还会自动生成一个 Markdown 表格,展示目标仓库、路径、编辑权限等信息,方便 AI 后续操作。毕竟有个清晰的目录,找起东西来也方便。

下面是实际的代码实现:

// ProposalDescriptionMemoryService.cs 中的核心方法
public async Task<string> OptimizeDescriptionAsync(
string title,
string description,
string locale = "zh-CN",
DescriptionOptimizationMemoryContext? memoryContext = null,
CancellationToken cancellationToken = default)
{
// 构建查询参数
var queryContext = BuildQueryContext(title, description);
// 检索历史上下文
var memoryContext = await RetrieveHistoricalContextAsync(queryContext, cancellationToken);
// 生成结构化提示词
var prompt = await BuildOptimizationPromptAsync(
title,
description,
memoryContext,
cancellationToken);
// 调用 AI 进行优化
return await _aiService.CompleteAsync(prompt, cancellationToken);
}

这个流程的关键在于”记忆注入”——我们会把项目惯例、相似案例、负面模式等历史上下文注入到提示词中,让 AI 在优化时能够参考过去的经验。毕竟吃一堑长一智,过去的经验总不能白白浪费了不是?

注意事项

  • 确保当前输入优先于历史记忆,避免覆盖用户显式指定的信息
  • HagIndex 引用必须作为事实来源,不得被历史案例修改
  • 低置信度的纠错建议不应作为强约束注入

2. 语音输入能力:说话比打字更自然

Section titled “2. 语音输入能力:说话比打字更自然”

除了文本输入,我们还支持语音输入。这在描述复杂需求时特别有用——你想想,打一段技术需求可能要几分钟,但说可能几十秒就完事了,毕竟嘴总是比手快。

语音输入的设计重点是”状态管理”,用户必须清楚当前系统处于什么状态。我们定义了以下几种状态:

  • 空闲:系统就绪,可以开始录制
  • 等待上游:正在连接后端服务
  • 录制中:正在录制用户语音
  • 处理中:正在将语音转换为文本
  • 错误:发生错误,需要用户处理

前端的状态模型大概是这样的:

interface VoiceInputState {
status: 'idle' | 'waiting-upstream' | 'recording' | 'processing' | 'error';
duration: number;
error?: string;
deletedSet: Set<string>; // 已删除结果的指纹集合
}
// 开始录制时的状态转换
const handleVoiceInputStart = async () => {
// 先进入等待状态,显示加载动画
setState({ status: 'waiting-upstream' });
// 等待后端就绪确认
const isReady = await waitForBackendReady();
if (!isReady) {
setState({ status: 'error', error: '后端服务未就绪' });
return;
}
// 开始录制
setState({ status: 'recording', startTime: Date.now() });
};
// 处理识别结果
const handleRecognitionResult = (result: RecognitionResult) => {
const fingerprint = normalizeFingerprint(result.text);
// 检查是否已被删除
if (state.deletedSet.has(fingerprint)) {
return; // 跳过已删除的内容
}
// 合并结果到文本框
appendResult(result);
};

这里有个细节:我们用”指纹集合”来管理删除同步。当语音识别返回多条结果时,用户可能会删除其中一些。我们把已删除内容的指纹存起来,后续如果相同内容再出现就自动跳过。这就像记住了哪些菜不爱吃,下次就不会再点了,毕竟谁也不想被同样的问题困扰两次。

3. 提示词管理系统:把 AI 的”脑子”外置

Section titled “3. 提示词管理系统:把 AI 的”脑子”外置”

HagiCode 有一个灵活的提示词管理系统,所有提示词都以文件形式存储:

prompts/
├── metadata/
│ ├── optimize-description.zh-CN.json
│ └── optimize-description.en-US.json
└── templates/
├── optimize-description.zh-CN.hbs
└── optimize-description.en-US.hbs

每个提示词由两部分组成:

  • 元数据文件(.json):定义提示词的场景、版本、参数等信息
  • 模板文件(.hbs):使用 Handlebars 语法的实际提示词内容

元数据文件的格式是这样的:

{
"scenario": "optimize-description",
"locale": "zh-CN",
"version": "1.0.0",
"syntax": "handlebars",
"syntaxVersion": "1.0",
"parameters": [
{
"name": "title",
"type": "string",
"required": true,
"description": "提案标题"
},
{
"name": "description",
"type": "string",
"required": true,
"description": "原始描述"
}
],
"author": "HagiCode Team",
"description": "优化用户输入的技术提案描述",
"lastModified": "2026-04-05",
"tags": ["optimization", "nlp"]
}

模板文件使用 Handlebars 语法,支持参数注入:

你是一个技术提案专家。
<task>
根据以下信息生成结构化的技术提案描述。
</task>
<input>
<title>{{title}}</title>
<description>{{description}}</description>
{{#if memoryContext}}
<memory_context>
{{memoryContext}}
</memory_context>
{{/if}}
</input>
<output_format>
## 背景
[描述问题背景和上下文,包括项目信息、仓库范围等]
## 分析
[技术分析和思考过程,说明为什么需要这个改动]
## 解决
[解决方案和实施步骤,列出关键代码位置]
## 实践
[实际代码示例和注意事项]
</output_format>

这种设计的好处是:

  • 提示词可以像代码一样版本管理
  • 支持多语言,根据用户偏好自动切换
  • 参数化设计,可以动态注入上下文
  • 启动时验证完备性,避免运行时出错

毕竟脑子里的东西不写下来,谁也不知道什么时候就忘了,与其到时候懊悔,不如一开始就做好记录罢了。

4. 渐进式向导:复杂任务拆成小步

Section titled “4. 渐进式向导:复杂任务拆成小步”

对于复杂任务(比如首次安装配置),我们使用了多步骤向导的设计。每个步骤只请求必要信息,并提供清晰的进度指示。生活也是这样嘛,一口吃不成胖子,一步一步来反而更稳妥。

向导的状态模型:

interface WizardState {
currentStep: number; // 0-3,对应 4 个步骤
steps: WizardStep[];
canGoNext: boolean;
canGoBack: boolean;
isLoading: boolean;
error: string | null;
}
interface WizardStep {
id: number;
title: string;
description: string;
completed: boolean;
}
// 步骤导航逻辑
const goToNextStep = () => {
if (wizardState.currentStep < wizardState.steps.length - 1) {
// 验证当前步骤的输入
if (validateCurrentStep()) {
wizardState.currentStep++;
wizardState.steps[wizardState.currentStep - 1].completed = true;
}
}
};
const goToPreviousStep = () => {
if (wizardState.currentStep > 0) {
wizardState.currentStep--;
}
};

每个步骤都有独立的验证逻辑,已完成步骤会有清晰的视觉标记。取消操作会弹出确认对话框,防止用户误操作丢失进度。毕竟走错路可以回头,但如果把路都拆了,那就真的没辙了。

回顾 HagiCode 的渐进式披露实践,我们可以总结出几个核心原则:

  1. 分步引导:把复杂任务拆成小步,每步只请求必要信息
  2. 智能补全:利用历史上下文和项目知识自动补全信息
  3. 即时反馈:每个操作都有清晰的视觉反馈和状态提示
  4. 容错机制:允许用户撤销、重置,避免错误造成不可逆损失
  5. 输入多样化:支持文本、语音等多种输入方式

这套方案在 HagiCode 中的实际效果是:用户输入的平均长度从不到 20 字提升到了结构化的 200-300 字,AI 生成的提案质量显著提高,用户满意度也跟着上来了。

其实这也不奇怪,毕竟你给的信息越多,AI 理解得越准确,返回的结果自然就越好,这和人与人之间的交流也没什么两样。

如果你也在做 AI 相关的产品,希望这些经验能给你带来一些启发。记住:用户不是不想给信息,而是你还没问对问题。渐进式披露的核心,就是找到问问题的最佳时机和方式,只是这个时机和方式,需要一点耐心去摸索罢了。


如果本文对你有帮助,欢迎来 GitHub 给个 Star,关注 HagiCode 项目的后续发展。公测已经开始,现在安装即可体验完整功能:

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

在 React 项目中优雅实现新用户引导:HagiCode 的 driver.js 实践

在 React 项目中优雅实现新用户引导:HagiCode 的 driver.js 实践

Section titled “在 React 项目中优雅实现新用户引导:HagiCode 的 driver.js 实践”

当用户第一次打开你的产品时,他们真的知道该从哪里开始吗?这篇文章聊聊我们在 HagiCode 项目里用 driver.js 做新用户引导的那些事儿,也算是抛砖引玉罢了。

你有没有遇到过这样的场景:新用户注册了你的产品,打开页面后一脸茫然,东张西望,不知道该点哪里、该做什么。作为开发者,我们总以为用户会”自己探索”,毕竟人的好奇心是无限的嘛。可现实是——大部分用户会在几分钟内因为找不到入口而悄悄离开,就像故事开始得突然,结束得也自然。

新用户引导是解决这个问题的重要手段,只是实现起来也不那么简单。一个好的引导系统需要:

  • 能够精准定位页面元素并高亮显示
  • 支持多步骤引导流程
  • 能够记住用户的选择(完成/跳过)
  • 不影响页面性能和正常交互
  • 代码结构清晰,易于维护

在开发 HagiCode 的过程中,我们也遇到了同样的挑战。HagiCode 是一个 AI 代码助手项目,核心工作流是”用户创建提案 → AI 生成计划 → 用户审核 → AI 执行”这样一套 OpenSpec 流程。对于第一次接触这个概念的用户来说,这套流程是全新的,必须有一个好的引导来帮助他们快速上手。毕竟,新事物总是需要一点时间的。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个基于 Claude 的 AI 代码助手,通过 OpenSpec 工作流帮助开发者更高效地完成代码任务。你可以在 GitHub 上查看我们的开源代码。

在技术选型阶段,我们评估了几个主流的引导库,怎么说呢,每个都有自己的特点:

  • Intro.js:功能强大但体积较大,样式定制相对复杂
  • Shepherd.js:API 设计很好,但对于我们的场景来说有点”重”
  • driver.js:轻量、简洁、API 直观,且支持 React 生态

最终我们选择了 driver.js,其实也没什么特别的理由,主要基于以下几点考虑:

  1. 轻量级:核心库体积小,不会显著增加打包体积
  2. API 简洁:配置项清晰直观,上手快
  3. 灵活性:支持自定义定位、样式和交互行为
  4. 动态导入:可以按需加载,不影响首屏性能

选型这件事,其实没有最好的,只有最合适的罢了。

driver.js 的配置非常直观,以下是 HagiCode 项目中的核心配置:

import { driver } from 'driver.js';
import 'driver.js/dist/driver.css';
const newConversationDriver = driver({
allowClose: true, // 允许用户关闭引导
animate: true, // 启用动画效果
overlayClickBehavior: 'close', // 点击遮罩层关闭引导
disableActiveInteraction: false, // 保持元素可交互
showProgress: false, // 不显示进度条(我们有自定义进度管理)
steps: guideSteps // 引导步骤数组
});

这些配置背后的考虑是:

  • allowClose: true - 尊重用户选择,不强制完成引导,毕竟强扭的瓜不甜
  • disableActiveInteraction: false - 某些步骤需要用户实际操作(如输入文字),所以不能禁用交互
  • overlayClickBehavior: 'close' - 给用户一个快速的退出方式

引导状态的持久化是关键——我们不希望每次刷新页面都重新引导,那样挺烦人的。HagiCode 使用 localStorage 来管理引导状态:

export type GuideState = 'pending' | 'dismissed' | 'completed';
export interface UserGuideState {
session: GuideState;
detailGuides: Record<string, GuideState>;
}
// 读取状态
export const getUserGuideState = (): UserGuideState => {
const state = localStorage.getItem('userGuideState');
return state ? JSON.parse(state) : { session: 'pending', detailGuides: {} };
};
// 更新状态
export const setUserGuideState = (state: UserGuideState) => {
localStorage.setItem('userGuideState', JSON.stringify(state));
};

我们定义了三种状态:

  • pending:引导进行中,用户还未完成或跳过
  • dismissed:用户主动关闭了引导
  • completed:用户完成了所有步骤

对于提案详情页的引导,我们还支持更细粒度的状态追踪(通过 detailGuides 字典),因为一个提案可能会经历多个阶段(草稿、审核、执行完成),每个阶段都需要不同的引导。毕竟,事情的状态总是在变化的。

driver.js 使用 CSS 选择器来定位目标元素。HagiCode 采用了一个约定:使用 data-guide 自定义属性来标记引导目标:

const steps = [
{
element: '[data-guide="launch"]',
popover: {
title: '开始新对话',
description: '点击这里创建一个新的对话会话...'
}
}
];

在组件中这样使用:

<button data-guide="launch" onClick={handleLaunch}>
新建对话
</button>

这种做法的好处是:

  • 避免与业务样式类名冲突
  • 语义清晰,一眼就能看出这个元素与引导相关
  • 便于统一管理和维护

因为引导功能只在特定场景下才需要(比如新用户第一次访问),我们采用动态导入来优化初始加载性能:

const initNewUserGuide = async () => {
// 动态导入 driver.js
const { driver } = await import('driver.js');
await import('driver.js/dist/driver.css');
// 初始化引导
const newConversationDriver = driver({
// ...配置
});
newConversationDriver.drive();
};

这样 driver.js 及其样式文件只会在需要时才加载,不会影响首屏性能。毕竟,谁愿意为暂时用不到的东西付出等待的代价呢?

HagiCode 实现了两条引导路径,覆盖了用户的核心使用场景。

这条引导帮助用户完成从创建对话到提交第一个完整提案的整个流程:

  1. launch - 启动引导,介绍”新建对话”按钮
  2. compose - 引导用户在输入框中输入请求
  3. send - 引导点击发送按钮
  4. proposal-launch-readme - 引导创建 README 提案
  5. proposal-compose-readme - 引导编辑 README 请求内容
  6. proposal-submit-readme - 引导提交 README 提案
  7. proposal-launch-agents - 引导创建 AGENTS.md 提案
  8. proposal-compose-agents - 引导编辑 AGENTS.md 请求
  9. proposal-submit-agents - 引导提交 AGENTS.md 提案
  10. proposal-wait - 说明 AI 正在处理,请稍候

这条引导的设计思路是:通过两个实际的提案创建任务(README 和 AGENTS.md),让用户亲手体验 HagiCode 的核心工作流。毕竟,纸上得来终觉浅,绝知此事要躬行。

下面这几张图,对应的就是会话引导里的几个关键节点:

会话引导:从创建普通会话开始

会话引导的第一步,先把用户带到“新建普通会话”的入口上。

会话引导:输入第一句请求

接着引导用户在输入框里写下第一句请求,降低第一次开口的门槛。

会话引导:发送第一条消息

输入完成后,再明确提示用户发送第一条消息,让操作路径更连贯。

会话引导:等待会话列表继续执行

当两个提案都创建完成后,引导会回到会话列表,让用户知道接下来只需要等待系统继续执行和刷新。

当用户进入提案详情页时,根据提案的当前状态触发对应的引导:

  1. drafting(草稿阶段)- 引导用户查看 AI 生成的计划
  2. reviewing(审核阶段)- 引导用户执行计划
  3. executionCompleted(完成阶段)- 引导用户归档计划

这条引导的特点是状态驱动——根据提案的实际状态动态决定显示哪个引导步骤。事物总是在变化,引导也应该跟着变化才是。

下面这张图展示的是提案详情页在“起草阶段”的引导状态:

提案详情引导:起草阶段先生成规划

在这个阶段,引导会把用户注意力聚焦到“生成规划”这个关键动作上,避免第一次进入详情页时不知道该先做什么。

在 React 应用中,引导目标元素可能还没渲染完成(比如等待异步数据加载)。为了处理这种情况,HagiCode 实现了一个重试机制:

const waitForElement = (selector: string, maxRetries = 10, interval = 100) => {
let retries = 0;
return new Promise<HTMLElement>((resolve, reject) => {
const checkElement = () => {
const element = document.querySelector(selector) as HTMLElement;
if (element) {
resolve(element);
} else if (retries < maxRetries) {
retries++;
setTimeout(checkElement, interval);
} else {
reject(new Error(`Element not found: ${selector}`));
}
};
checkElement();
});
};

在初始化引导前调用这个函数,确保目标元素已经存在。有时候,多等待一下也是值得的。

基于 HagiCode 的实践经验,这里分享几个关键的最佳实践:

不要强制用户完成引导。有些用户是探索型的,他们更喜欢自己摸索。提供清晰的”跳过”按钮,并记住用户的选择,下次不再打扰。毕竟,美的事物或人,不一定要占有,只要她还是美的,自己好好看着她的美就好了。

每个引导步骤应该聚焦于单一目标:

  • Title:简短清晰,不超过 10 个字
  • Description:直击要点,告诉用户”这是啥”和”为啥要用”

避免长篇大论的说明——用户在引导阶段的注意力是很有限的。话说多了,反而没人愿意看。

使用稳定的、不频繁变化的元素标记方式。data-guide 自定义属性是一个好选择,避免依赖 class 名或 DOM 结构,因为这些很容易在重构中变化。代码总是在变化的,但有些东西应该尽量保持稳定。

HagiCode 为引导功能编写了完整的测试用例:

describe('NewUserConversationGuide', () => {
it('应该正确初始化引导状态', () => {
const state = getUserGuideState();
expect(state.session).toBe('pending');
});
it('应该正确更新引导状态', () => {
setUserGuideState({ session: 'completed', detailGuides: {} });
const state = getUserGuideState();
expect(state.session).toBe('completed');
});
});

测试可以确保在重构代码时不会不小心破坏引导功能。毕竟,谁也不希望改点代码就把之前的功能搞坏了。

  • 使用动态导入延迟加载引导库
  • 避免在用户已经完成引导后仍然初始化引导逻辑
  • 考虑引导动画的性能影响,低端设备上可以关闭动画

性能这东西,就像生活一样,该省的地方还是要省的。

新用户引导是提升产品用户体验的重要环节。在 HagiCode 项目中,我们使用 driver.js 构建了一套完整的引导系统,覆盖了从会话创建到提案执行的整个工作流。

通过本文的分享,我们希望传达的核心观点是:

  1. 技术选型要匹配需求:driver.js 不是最强的,但对我们来说是最合适的
  2. 状态管理很关键:用 localStorage 持久化引导状态,避免重复打扰用户
  3. 引导设计要聚焦:每个步骤解决一个问题,不要贪多
  4. 代码结构要清晰:分离引导配置、状态管理和 UI 逻辑,便于维护

如果你正在为自己的项目添加新用户引导功能,希望本文的实践经验能对你有所帮助。其实技术这东西,也没什么神秘的,多尝试,多总结,慢慢就好了…

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

如何用游戏化设计让 AI 编程变得更好玩

如何用游戏化设计让 AI 编程变得更好玩

Section titled “如何用游戏化设计让 AI 编程变得更好玩”

其实传统的 AI 编程工具功能挺强大的,就是少了点温度。我们在做 HagiCode 的时候就想,既然都要写代码,为什么不把它变成一场游戏呢?

用过 AI 编程助手的朋友应该都有这种体验:刚开始觉得挺新鲜,用着用着就感觉少了点什么。工具本身功能很强大,代码生成、自动补全、Bug 修复样样都能做,只是……没什么温度,用久了会觉得有些单调乏味。

这也罢了,毕竟谁愿意每天对着冷冰冰的工具呢。

这就好比打游戏,如果只是单纯地完成任务列表,没有角色成长、没有成就感解锁、没有团队配合,那很快就会觉得没意思。美的事物或人,不一定要占有,只是她还是美的,自己好好看着她的美就好了。可编程工具连这种美都没有,难免让人心灰意冷。

我们在开发 HagiCode 的过程中就遇到了这个问题。HagiCode 作为一个多 AI 助手协作平台,需要让用户长期保持使用热情。但现实是,再好的工具,如果缺乏情感连接,用户也很难坚持下去。

为了解决这个痛点,我们做了一个大胆的决定:把编程变成一场游戏。不是那种简单的积分排行榜,而是真正的角色扮演式的游戏化体验。这个决定带来的变化,可能比你想象的还要大。

毕竟,人嘛,总是需要点仪式感的。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个多 AI 助手协作平台,支持 Claude Code、Codex、Copilot、OpenCode 等多种 AI 助手协同工作。如果你对多 AI 协作、游戏化编程感兴趣,可以访问 github.com/HagiCode-org/site 了解更多。

其实也没什么特别的,只是我们把编程变成了一场冒险而已。

游戏化的本质不是”加个积分榜”,而是建立一套完整的激励体系,让用户在做任务的过程中体验到成长感、成就感和社交认同。

HagiCode 的游戏化设计围绕一个核心概念展开:每个 AI 助手都是一名”英雄”,用户就是这支英雄团队的队长。你带领这些英雄去征服各种”地牢”(编程任务),在这个过程中,英雄会获得经验、升级解锁能力,你和你的团队也会获得各种成就。

这不是什么噱头,而是基于人类行为心理学的精心设计。当任务被赋予意义和进度反馈时,人的投入度和坚持程度会显著提升。

就像古人说的,“此情可待成追忆,只是当时已惘然”。我们把这种情感体验融入到工具中,让编程不再只是敲代码,而是一段值得回忆的旅程。

Hero 是 HagiCode 游化系统的核心概念。每个 Hero 代表一个 AI 助手,比如 Claude Code 是一个 Hero,Codex 也是一个 Hero。

Hero 有三个装备槽位,这个设计其实还挺巧妙的:

  1. CLI 槽位(主要职业):决定 Hero 的基础能力,比如是 Claude Code 还是 Codex
  2. Model 槽位(次要职业):决定使用的模型,比如 Claude 4.5 还是 Claude 4.6
  3. Style 槽位(风格):决定 Hero 的行事风格,比如是”风落策略家”还是”其他风格”

三个槽位的组合可以创造出独特的 Hero 配置。就像游戏里配装一样,你需要根据任务特点选择合适的搭配。毕竟适合自己的才是最好的,这和生活选路差不多,条条大路通罗马,只是有的路好走一点,有的路稍微曲折一点罢了。

每个 Hero 都有自己的 XP(经验值)和等级:

type HeroProgressionSnapshot = {
currentLevel: number; // 当前等级
totalExperience: number; // 总经验值
currentLevelStartExperience: number; // 当前等级起始经验
nextLevelExperience: number; // 下一等级所需经验
experienceProgressPercent: number; // 进度百分比
remainingExperienceToNextLevel: number; // 距离下一级还需要多少经验
lastExperienceGain: number; // 最近一次获得的经验
lastExperienceGainAtUtc?: string | null; // 获得经验的时间
};

等级分为四个阶段,每个阶段的命名都很有代入感:

export const resolveHeroProgressionStage = (level?: number | null): HeroProgressionStage => {
const normalizedLevel = Math.max(1, level ?? 1);
if (normalizedLevel <= 100) return 'rookieSprint'; // 新人冲刺
if (normalizedLevel <= 300) return 'growthRun'; // 成长跑
if (normalizedLevel <= 700) return 'veteranClimb'; // 老兵攀登
return 'legendMarathon'; // 传奇马拉松
};

从”新人”到”传奇”,这个成长路径让用户有明确的目标感和成就感。就像人生的成长,总要经历从懵懂到成熟的过程,只是这里把这种过程具象化了而已。

创建 Hero 时需要配置三个槽位:

const heroDraft: HeroDraft = {
name: 'Athena',
icon: 'hero-avatar:storm-03',
description: '智谋过人的策略家',
executorType: AIProviderType.CLAUDE_CODE_CLI,
slots: {
cli: {
id: 'profession-claude-code',
parameters: { /* CLI 相关参数 */ }
},
model: {
id: 'secondary-claude-4-sonnet',
parameters: { /* 模型相关参数 */ }
},
style: {
id: 'fengluo-strategist',
parameters: { /* 风格相关参数 */ }
}
}
};

每个 Hero 都有独特的头像、描述和专业定位,这让冰冷的 AI 助手变得有个性、有温度。毕竟谁愿意跟没有性格的工具打交道呢?

“地牢”是游戏中的经典概念,代表着需要组队攻略的挑战。在 HagiCode 中,每个工作流程就是一个 Dungeon。

Dungeon 将工作流程组织成不同的”地牢”:

  • 提案生成地牢:负责生成技术提案
  • 提案执行地牢:负责执行提案中的任务
  • 提案归档地牢:负责整理和归档完成的提案

每个地牢都有自己的 Captain(队长)Hero,队长自动从启用的 Hero 中选择第一个。

其实这就像生活中的分工,每个人都有自己的角色,只是这里把这种分工变成了游戏机制而已。

你可以为不同的地牢配置不同的 Hero 小队:

const dungeonRoster: HeroDungeonRoster = {
scriptKey: 'proposal.generate',
displayName: '提案生成',
members: [
{ heroId: 'hero-1', name: 'Athena', executorType: 'ClaudeCode' },
{ heroId: 'hero-2', name: 'Apollo', executorType: 'Codex' }
]
};

比如生成提案时用 Athena(擅长策略),执行代码时用 Apollo(擅长实现),这样每个英雄都能发挥自己的专长。就像组建一支乐队,每个人都有自己的乐器,合起来才能奏出动听的旋律。

Dungeon 使用固定的 scriptKey 来标识不同的流程:

// 脚本键值对应不同的工作流程
const dungeonScripts = {
'proposal.generate': '提案生成',
'proposal.execute': '提案执行',
'proposal.archive': '提案归档'
};

任务状态流转是:queued(排队中)→ dispatching(分发中)→ dispatched(已分发)。整个过程自动化,不需要手动干预。这也是我们偷懒的小心思,毕竟谁愿意手动管这些事呢。

XP(经验值)是游戏化系统中最核心的反馈机制。用户通过完成任务获得 XP,XP 让英雄升级,升级解锁新的能力,形成正向循环。

在 HagiCode 中,XP 可以通过以下活动获得:

  • 代码执行完成
  • 工具调用成功
  • 提案生成
  • 会话管理操作
  • 项目操作

每完成一次有效操作,对应的 Hero 就会获得 XP。就像生活中的成长,每一步都算数,只是这里把这种成长量化了而已。

XP 和等级的进度是实时可视化的:

type HeroDungeonMember = {
heroId: string;
name: string;
icon?: string | null;
executorType: PCode_Models_AIProviderType;
currentLevel?: number; // 当前等级
totalExperience?: number; // 总经验值
experienceProgressPercent?: number; // 进度百分比
};

用户可以随时看到每个 Hero 的等级和进度,这种即时反馈是游戏化设计的关键。毕竟人总是需要点反馈,不然怎么知道自己进步了呢?

成就是游戏化中的另一个重要元素,它提供了长期目标和里程碑式的成就感。

HagiCode 支持多种成就类型:

  • 代码生成类:生成 X 行代码、生成 Y 个文件
  • 会话管理类:完成 Z 次对话
  • 项目操作类:在 W 个项目中工作过

其实这些成就就像人生中的里程碑,只是我们把它们变成了一种游戏机制而已。

成就有三种状态:

type AchievementStatus = 'unlocked' | 'in-progress' | 'locked';

三种状态有明显的视觉区分:

  • 已解锁:金色渐变 + 光晕效果
  • 进行中:蓝色脉冲动画
  • 未解锁:灰色,显示解锁条件

每个成就都清晰展示触发条件,让用户知道下一步该做什么。毕竟迷茫的时候,有个指引总是好的。

当成就解锁时,会触发庆祝动画。这种正向强化会让用户产生”我做到了”的满足感,激励他们继续前进。就像生活中小小的奖励,虽然不大,却能让人开心很久。

Battle Report 是 HagiCode 的一个特色功能,每天结束时生成一份全屏展示的战斗风格报告。

Battle Report 显示以下信息:

type HeroBattleReport = {
reportDate: string;
summary: {
totalHeroCount: number; // 总英雄数
activeHeroCount: number; // 活跃英雄数
totalBattleScore: number; // 总战斗分数
mvp: HeroBattleHero; // 最有价值英雄
};
heroes: HeroBattleHero[]; // 所有英雄的详细数据
};
  • 队伍总分数
  • 活跃 Hero 数量
  • 工具调用次数
  • 总工作时长
  • MVP(最有价值英雄)
  • 每个 Hero 的详细卡片

MVP 是当天表现最好的 Hero,会在报告中高亮显示。这不仅是数据统计,更是一种荣誉认可。毕竟谁不希望自己被认可呢?

每个 Hero 的卡片包含:

  • 等级进度
  • XP 获得量
  • 执行次数
  • 使用时长

这些数据让用户清楚地了解团队的工作状态。毕竟了解自己的努力成果,也是一种满足感。

HagiCode 的游戏化系统采用了现代化的技术栈和设计模式。其实也没什么特别的,只是选了一些趁手的工具而已。

// 前端使用 React + TypeScript
import React from 'react';
// 动画使用 Framer Motion
import { AnimatePresence, motion } from 'framer-motion';
// 状态管理使用 Redux Toolkit
import { useAppDispatch, useAppSelector } from '@/store';
// UI 组件使用 shadcn/ui
import { Dialog, DialogContent } from '@/components/ui/dialog';

Framer Motion 负责所有动画效果,shadcn/ui 提供基础的 UI 组件,Redux Toolkit 管理复杂的游戏化状态。毕竟工欲善其事,必先利其器。

HagiCode 采用了 Glassmorphism + Tech Dark 的设计风格:

/* 主渐变色 */
background: linear-gradient(135deg, #22C55E 0%, #25c2a0 50%, #06b6d4 100%);
/* 玻璃态效果 */
backdrop-filter: blur(12px);
/* 光辉效果 */
background: radial-gradient(circle at center, rgba(34, 197, 94, 0.15) 0%, transparent 70%);

绿色系的渐变配合玻璃态效果,营造出科技感和未来感。毕竟视觉上的美感,也是用户体验的一部分。

使用 Framer Motion 实现流畅的进场动画:

<motion.div
animate={{ opacity: 1, y: 0 }}
initial={{ opacity: 0, y: 18 }}
transition={{ duration: 0.35, ease: 'easeOut', delay: index * 0.08 }}
className="card"
>
{/* 卡片内容 */}
</motion.div>

每个卡片依次进场,延迟 0.08 秒,创造出流畅的视觉效果。毕竟流畅的动画能让体验更好,这是毋庸置疑的。

游戏化数据使用 Grain 存储系统,确保状态一致性。即使是 Hero 的 XP 累积这种细粒度数据,也能准确持久化。毕竟谁也不想让辛苦积累的经验丢失。

创建第一个 Hero 其实挺简单的:

  1. 进入 Hero 管理页面
  2. 点击”创建 Hero”按钮
  3. 配置三个槽位(CLI、Model、Style)
  4. 给 Hero 起个名字和描述
  5. 保存,你的第一个 Hero 就诞生了

就像认识新朋友一样,你需要给他一个名字、了解他的特点,然后你们就可以一起冒险了。

组建团队也很简单:

  1. 进入 Dungeon 管理页面
  2. 选择要配置的地牢(如”提案生成”)
  3. 从你的 Hero 列表中选择成员
  4. 系统自动选择第一个启用的 Hero 作为队长
  5. 保存配置

其实这就是一种组队的过程,就像生活中组建一个团队,每个人都有自己的角色。

每天结束后,你可以查看当日的 Battle Report:

  1. 点击”战斗报告”按钮
  2. 全屏展示当天的工作成果
  3. 查看 MVP 和每个 Hero 的详细数据
  4. 分享给团队成员(可选)

这也是一种仪式感,让自己知道今天努力了多少,离目标还有多远。

使用 React.memo 避免不必要的重渲染:

const HeroCard = React.memo(({ hero }: { hero: HeroDungeonMember }) => {
// 组件实现
});

毕竟性能也很重要,谁愿意用卡顿的工具呢?

检测用户的运动偏好设置,为敏感用户提供简化体验:

const prefersReducedMotion = useReducedMotion();
const duration = prefersReducedMotion ? 0 : 0.35;

毕竟不是所有人都喜欢动画,尊重用户的偏好也是设计的一部分。

保留 legacyIds 支持旧版本迁移:

type HeroDungeonMember = {
heroId: string;
legacyIds?: string[]; // 支持旧版本 ID 映射
// ...
};

毕竟谁也不希望因为版本升级就丢失数据。

所有文本使用 i18n 翻译键,方便多语言支持:

const displayName = t(`dungeon.${scriptKey}`, { defaultValue: displayName });

毕竟语言不应该成为使用的障碍。

游戏化不是简单的积分排行榜,而是一套完整的激励体系。HagiCode 通过 Hero 系统、Dungeon 系统、XP/等级系统、成就系统和 Battle Report,将编程工作转化为一场充满冒险精神的英雄之旅。

这套系统的核心价值在于:

  • 情感连接:让冰冷的 AI 助手变得有个性
  • 正向反馈:每次操作都有即时反馈
  • 长期目标:等级和成就提供成长路径
  • 团队认同:Dungeon 团队协作感
  • 荣誉认可:Battle Report 和 MVP 展示

游戏化设计让编程不再枯燥,而是一场有趣的冒险。用户在完成代码任务的同时,体验到角色成长、团队协作和成就解锁的乐趣,从而提高使用粘性和活跃度。

其实编程本身就是一种创造,只是我们把这种创造过程变得更有趣了一点而已。

如果本文对你有帮助:


感谢您的阅读,如果您觉得本文有用,快点击下方点赞按钮,让更多的人看到本文。

本内容采用人工智能辅助协作,经本人审核,符合本人观点与立场。