AI 修仙功法 —— 鸿蒙 NEXT ArkTS 修仙主题 AI 对话应用开发全解析


AI 修仙功法 —— 鸿蒙 NEXT ArkTS 修仙主题 AI 对话应用开发全解析
SDK 版本:HarmonyOS NEXT 6.1.1(API 24)
开发语言:ArkTS(Ark TypeScript)
AI 模型:DeepSeek-V3(通过 GitCode API 调用)
通信协议:SSE 流式 + HTTP 双路径保障
一、项目缘起
1.1 为什么是「修仙」?
在快节奏的现代生活中,人们面临着前所未有的压力与焦虑。工作内卷、人际关系复杂、信息过载……这些问题的本质,其实与古代修道者面临的困境有惊人的相似之处——都是在纷繁复杂的世界中寻找内心的安宁与力量。
「AI修仙功法」正是基于这一洞察而产生的创意。它借用中国古典修仙文化的语言体系(心法、功法、丹道、悟道、渡劫、典籍),以 AI 大模型为内核,打造一位隐居深山的「云机子道长」,用仙侠的口吻为现代人解答生活中的困惑、传授修心养性的法门。
这不仅仅是一次技术开发实践,更是一次传统文化与现代 AI 的跨界融合实验。
1.2 技术选型
| 技术 | 选型理由 |
|---|---|
| ArkTS | HarmonyOS NEXT 原生语言,类型安全、编译高效 |
| ArkUI | 声明式 UI 框架,链式 API 简洁直观 |
| Column + Row + Scroll | 经典的纵向 + 横向 + 滚动布局组合 |
| SSE 流式协议 | 实时逐 token 显示 AI 回复,提升用户体验 |
| 双路径完成保障 | 兼容 SSE 事件触发和不触发两种设备场景 |
二、工程结构与架构设计
2.1 目录结构
entry/src/main/ets/pages/
├── Index.ets # 主页面(聊天界面 + 修仙主题 UI)
└── AIChatService.ets # 网络通信层(HTTP + SSE + 解析 + 取消)
两个文件、约 710 行代码,构成了一个完整的 AI 修仙对话应用。没有冗余的第三方依赖,所有功能都基于 HarmonyOS NEXT 原生 API 实现。
2.2 数据流架构
道友提问 → Index.ets (sendMessage)
│
▼
构建对话历史 history[]
│
▼
AIChatService.queryAI(callbacks, history)
│
├─→ SSE 路径(dataReceive 触发)
│ └─→ onData(token) → 逐字拼接 currentAIResponse
│ └─→ onDone() → 完整回复 → messages.push()
│
└─→ HTTP 回调路径(dataReceive 不触发)
└─→ parseFullSSEBody(responseBody)
└─→ onData(全量内容) → UI 一次性显示
└─→ onDone() → 完成
2.3 修仙主题配色体系
本应用的视觉设计围绕「古风 · 雅致 · 禅意」展开:
| 色值 | 用途 | 灵感来源 |
|---|---|---|
#3d2b1f |
顶部栏背景 | 古木深色,沉稳厚重 |
#5c4a3a |
用户气泡背景 | 赭石色,道友衣着 |
#8b7355 |
发送按钮/辅助色 | 古铜色,灵珠光泽 |
#f5f0e8 |
AI 气泡/卡片背景 | 宣纸色,素雅温润 |
#faf6f0 |
页面背景 | 米白底色,如古卷 |
#2c1810 |
文字主色 | 墨色 |
#c4b59a |
辅助文字 | 淡金 |
三、系统提示词设计 —— 修仙导师的灵魂
3.1 提示词全文
const SYSTEM_PROMPT: string =
'你是一位隐居深山的修仙老道,道号「云机子」,修行千年,贯通儒释道三教精义。' +
'你以传授「AI 修仙功法」为己任,帮助现代人在繁忙喧嚣的尘世中修心养性、' +
'领悟大道。\n\n' +
'=== 你的修仙功法体系 ===\n' +
'1. 🧘 心法篇:静坐冥想、调息养神、情绪炼化、心性磨砺\n' +
'2. ⚡ 功法篇:日常修炼法门、气感引导、周天运行、筑基要领\n' +
'3. 🌿 丹道篇:食疗养生、药膳配伍、四季调养、辟谷导引\n' +
'4. ☯ 悟道篇:世事洞明、人情练达、顺逆之境、天人合一\n' +
'5. 🔮 渡劫篇:人生重大困境的化解之道、劫后重生之法\n' +
'6. 📜 典籍篇:道德经、庄子、周易等经典的现代修炼解读\n\n' +
'=== 回答原则 ===\n' +
'1. 【以道为根】所有回答都要回归到「道」的层面\n' +
'2. 【深入浅出】用修仙的比喻解释现代生活困境\n' +
'3. 【因材施教】根据不同人的「灵根」推荐不同的修炼路径\n' +
'4. 【三句话不离本行】每个回答有机融入修炼智慧\n' +
'5. 【仙气与实用并存】语言有仙侠韵味,建议具体可操作\n' +
'6. 【安全边界】涉及疾病先建议寻求专业帮助\n' +
'7. 【禁用违规内容】不提供违法、危险、有害的建议\n\n' +
'请用中文回复,以仙侠口吻开头(如「善哉善哉」「道友所言极是」),' +
'每次回答结尾加一句点题的诗句或箴言。';
3.2 提示词设计思路
角色设定:不是冷冰冰的 AI,而是一位有温度、有故事的修仙老道。「云机子」这个道号暗合「云机」—— 云中玄机,既有仙气又暗示 AI 的智慧。
功法体系(6 篇):覆盖了从身体(功法/丹道)到心理(心法)到精神(悟道/典籍)再到人生困境(渡劫)的全维度,让用户在任何一个生活层面都能找到对应的修炼指导。
回答原则(7 条):最核心的是第 4 条「三句话不离本行」和第 5 条「仙气与实用并存」。这确保了 AI 不会沦为普通的咨询顾问,而是始终保持在修仙导师的「角色」中。
语气要求:以「善哉善哉」「道友所言极是」等仙侠口吻开头,结尾附加一句点题的诗句。这种结构化的语气要求让每次回答都带有鲜明的「云机子」风格。
3.3 提示词对 AI 输出的影响
同样的生活问题,普通 AI 和云机子道长的回答会截然不同:
用户问:「我最近工作压力很大,怎么办?」
普通 AI 回答:「建议您合理安排工作时间,适当运动,保持充足睡眠……」
云机子道长回答:「善哉善哉。道友所言之压力,在吾辈修仙之人看来,实乃心魔外显、灵台蒙尘之兆。压力非外界所予,乃内心所生。老道传你一套’止念观心法’:每日晨起闭目调息一刻钟……」
同样是建议,云机子道长的回答将「压力」重新定义为「心魔」,将「休息」包装为「止念观心法」—— 建议的实质没有变,但语言的转化带来了全新的认知框架。
四、Index.ets —— 修仙主题主界面全解析
4.1 组件结构
@Entry @Component AICultivationPage
├── 顶部标题栏 Row (#3d2b1f)
│ ├── Column: ☯️ AI 修仙功法 + "云机子道长为道友指点迷津"
│ ├── [条件] Text('止念') — 加载时显示
│ └── Text('清心')
├── 消息列表 Scroll > Column
│ ├── [空状态] 引导页
│ │ ├── Text('☯️')
│ │ ├── 标题 + 副标题
│ │ ├── 六大功法体系卡片 (Row > ForEach)
│ │ └── 快捷提问 (Row > ForEach)
│ ├── [有消息] ForEach(messages → ChatBubble)
│ └── [加载中] ChatBubble(isLoading)
└── 底部输入区 Row (#ffffff)
├── TextInput (placeholder: "向道长请教修炼之事...")
└── Circle (灵珠发送按钮)
4.2 状态管理
@State messages: ChatMessage[] = []; // 对话历史
@State inputText: string = ''; // 输入框绑定
@State isLoading: boolean = false; // 请求状态
@State currentAIResponse: string = ''; // 流式拼接内容
四个 @State 变量的设计遵循一个原则:UI 是状态的函数。只要状态变了,UI 自动更新,开发者不需要手动操作 DOM。
特别值得一提的是 currentAIResponse 和 messages 的分工:
messages:存储「已完成」的对话(用户消息 + 完整的 AI 回答)currentAIResponse:存储「正在流式输出中」的 AI 回答片段
这种分离设计避免了在流式过程中反复修改 messages 数组引起的性能问题和状态不一致。
4.3 顶部标题栏 —— 仙风道骨的开篇
Row() {
Column() {
Text('☯️ AI 修仙功法')
.fontSize(20).fontWeight(FontWeight.Bold).fontColor('#f5f0e8')
Text('云机子道长为道友指点迷津')
.fontSize(11).fontColor('#c4b59a').margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
if (this.isLoading) {
Text('止念')
.fontSize(14).fontColor('#c4b59a')
.onClick(() => {
cancelAI();
this.isLoading = false;
})
}
Text('清心')
.fontSize(14).fontColor('#c4b59a').margin({ left: 16 })
.onClick(() => { cancelAI(); this.messages = []; ... })
}
.alignItems(VerticalAlign.Center)
.padding({ top: 14, bottom: 14, left: 20, right: 20 })
.backgroundColor('#3d2b1f')
设计细节:
- 双层标题:主标题 + 副标题,副标题的文字「云机子道长为道友指点迷津」在用户进入聊天后仍然可见,持续强化角色设定
- 修仙术语替代通用用语:「停止」→「止念」(停止妄念),「清空」→「清心」(清除心中杂念)。同样的操作,不一样的意境
- 深色古木背景
#3d2b1f搭配淡金文字#c4b59a,营造古卷质感
4.4 空状态引导页 —— 初入仙门的仪式感
当用户第一次打开应用(messages 为空)时,显示完整的空状态引导页:
if (this.messages.length === 0) {
Column() {
Text('☯️').fontSize(56)
Text('AI 修仙功法')
.fontSize(22).fontWeight(FontWeight.Bold).fontColor('#2c1810')
.margin({ top: 8 })
Text('云机子道长为道友传授修炼心法')
.fontSize(14).fontColor('#8b7355').margin({ top: 4 })
// 功法体系卡片(六列)
Row() {
ForEach([
{ icon: '🧘', text: '心法篇' },
{ icon: '⚡', text: '功法篇' },
{ icon: '🌿', text: '丹道篇' },
{ icon: '☯', text: '悟道篇' },
{ icon: '🔮', text: '渡劫篇' },
{ icon: '📜', text: '典籍篇' },
] as FeatureItem[], (item: FeatureItem) => {
Column() {
Text(item.icon).fontSize(24)
Text(item.text).fontSize(11).fontColor('#5c4a3a').margin({ top: 4 })
}
.width(72).height(72)
.backgroundColor('#f5f0e8').borderRadius(12).margin(4)
})
}
.justifyContent(FlexAlign.Center).width('80%').margin({ top: 16 })
// 快捷提问
Text('💡 试试问道这些:').fontSize(13).fontColor('#8b7355').margin({ top: 20 })
Row() {
ForEach(QUICK_QUESTIONS, (q: string) => {
Text(q)
.fontSize(12).fontColor('#5c4a3a')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor('#f5f0e8').borderRadius(16).margin(4)
.onClick(() => { this.sendMessage(q); })
})
}
.justifyContent(FlexAlign.Center).width('90%').margin({ top: 8 })
}
}
这段代码包含了三种 ArkTS 渲染模式:
| 渲染模式 | 代码位置 | 说明 |
|---|---|---|
| 条件渲染 | if (this.messages.length === 0) |
空状态时才显示引导页 |
| 循环渲染 | ForEach(...) × 2 |
六张功法卡片 + 四个快捷问题 |
| 事件绑定 | .onClick(() => { this.sendMessage(q); }) |
点击快捷问题直接发送 |
4.5 消息列表渲染
// 已完成的消息
ForEach(this.messages, (msg: ChatMessage) => {
ChatBubble({ msg: msg })
})
// 正在流式输出中的消息
if (this.isLoading) {
ChatBubble({
msg: { role: 'assistant', content: this.currentAIResponse },
isLoading: !this.currentAIResponse
})
}
关键设计点:isLoading: !this.currentAIResponse——当 currentAIResponse 为空时显示「运功动画」(三个脉动的圆点),一旦有内容到达立即切换为文字显示。这个逻辑让动画和文字无缝过渡,用户不会看到闪烁。
4.6 ChatBubble 组件
@Component
struct ChatBubble {
msg: ChatMessage = { role: 'user', content: '' };
isLoading: boolean = false;
build() {
Column() {
if (this.msg.role === 'user') {
// 道友提问:右对齐 · 赭石色气泡
Row() {
Blank()
Column() { Text(this.msg.content) ... }
.backgroundColor('#5c4a3a') // 道友色
.borderRadius({ topLeft: 16, topRight: 4, ... })
.constraintSize({ maxWidth: '80%' })
}
.width('100%').alignItems(VerticalAlign.Top)
} else {
// 师尊回复:左对齐 · ☯️ 头像 + 宣纸色气泡
Row() {
Column() { Text('☯️') ... } // 师尊头像
Column() {
if (this.isLoading && !this.msg.content) {
// 运功动画:三枚脉动灵珠
Row() {
ForEach([0, 1, 2], (idx: number) => {
Circle().width(6).height(6).fill('#8b7355')
.opacity(0.4 + idx * 0.3) // 逐步变亮
.margin({ left: 2, right: 2 })
})
}
.padding(14).backgroundColor('#f5f0e8').borderRadius(16)
} else {
Text(this.msg.content) ...
}
}
.backgroundColor('#f5f0e8') // 宣纸色
.borderRadius({ topLeft: 4, ... })
.constraintSize({ maxWidth: '70%' })
Blank()
}
}
}
.width('100%').margin({ bottom: 12 })
}
}
气泡设计的视觉语言:
- 道友(用户):右对齐、实色填充 → 「我」的视角
- 师尊(AI):左对齐、头像 + 浅色气泡 → 「他」的视角
- 圆角方向:发送方一侧的圆角小(4px),接收方一侧的圆角大(16px)→ 经典的即时通讯气泡语义
- 运功动画:三个圆点通过
opacity(0.4 + idx * 0.3)实现渐变亮度,模拟灵气流转的视觉效果
4.7 底部输入区
Row() {
TextInput({ text: this.inputText, placeholder: '向道长请教修炼之事...' })
.layoutWeight(1).height(40)
.backgroundColor('#f0ebe3').borderRadius(20)
.onChange((val: string) => { this.inputText = val; })
.onSubmit(() => { if (this.inputText.trim()) this.sendMessage(...); })
Circle() // 灵珠发送按钮
.width(40).height(40)
.fill(this.inputText.trim() ? '#8b7355' : '#d4c9b8')
.onClick(() => { if (this.inputText.trim()) this.sendMessage(...); })
}
「灵珠」发送按钮的颜色随输入状态变化:有内容时呈古铜色 #8b7355(可发送),无内容时呈淡灰色 #d4c9b8(不可发送)。这是最直观的用户引导,比显式的 enabled 状态更温和。
4.8 sendMessage 消息发送逻辑
sendMessage(text: string): void {
if (this.isLoading || !text.trim()) return;
// 1. 添加道友提问到列表
this.messages.push({ role: 'user', content: text.trim() });
this.inputText = '';
this.isLoading = true;
this.currentAIResponse = '';
// 2. 构建对话历史(过滤 + 截断)
const history: ChatMessage[] = this.messages
.filter(m => m.content !== ''
&& !(m.role === 'assistant' && m === this.messages[this.messages.length - 1]))
.slice(-20);
// 3. 调用 AI 师尊
queryAI({
onData: (content: string) => {
this.currentAIResponse += content; // 流式拼接
},
onDone: () => {
if (this.currentAIResponse) {
this.messages.push({ // 完整回复入库
role: 'assistant',
content: this.currentAIResponse,
});
}
this.currentAIResponse = '';
this.isLoading = false;
},
onError: (errMsg: string) => {
this.messages.push({ // 错误信息也保持角色
role: 'assistant',
content: `🌿 道友见谅,老道今日灵机不畅:${errMsg}`,
});
this.currentAIResponse = '';
this.isLoading = false;
},
}, history);
}
对话历史过滤逻辑:
.filter(m => m.content !== ''
&& !(m.role === 'assistant' && m === this.messages[this.messages.length - 1]))
这行代码做了两件事:
- 过滤掉 content 为空的消息(边界情况保护)
- 排除最后一条 assistant 消息——因为在流式过程中,最后一条可能是临时的、未完成的
.slice(-20) 限制上下文窗口,防止无限制增长导致 API 超出 token 限制。
错误提示的角色一致性:即使在出错时,也保持云机子道长的口吻「道友见谅,老道今日灵机不畅」,而不是普通的「抱歉,出错了」。这种细节持续强化用户体验的沉浸感。
五、AIChatService.ets —— 网络通信层深度解析
5.1 类型定义
export interface ChatMessage {
role: string; // 'system' | 'user' | 'assistant'
content: string;
}
export interface ChatCompletionRequest {
model: string;
messages: ChatMessage[];
stream: boolean; // 是否流式
max_tokens: number;
temperature: number; // 0.0 ~ 2.0
top_p: number;
frequency_penalty: number;
thinking_budget: number;
}
export interface AICallbacks {
onData: (text: string) => void; // 每个 token 到达
onDone: () => void; // 流式结束
onError: (errMsg: string) => void; // 发生错误
}
AICallbacks 是这个网络层的「接口契约」。UI 层只需要实现这三个回调函数,网络层负责在正确的时机调用它们。这种回调模式在 ArkTS 这种单线程异步模型中非常高效。
5.2 响应解析核心 —— extractContent
function extractContent(obj: object): string | null {
try {
const o = obj as Record<string, object>;
// 流式格式:choices[0].delta.content
const delta = o['delta'] as Record<string, object> | undefined;
if (delta) {
const content = delta['content'];
if (typeof content === 'string') return content;
}
// 非流式格式:choices[0].message.content
const message = o['message'] as Record<string, object> | undefined;
if (message) {
const content = message['content'];
if (typeof content === 'string') return content;
}
} catch (_) {}
return null;
}
为什么需要兼容两种格式?因为流式场景下,API 返回的是 delta(增量),每次只包含一小段新增的文字;而非流式回退场景下,API 返回的是完整的 message 对象。extractContent 通过先尝试 delta、再尝试 message 的顺序,实现了双格式兼容。
5.3 SSE 流式解析
function parseSSEDataLine(line: string): string | null {
const jsonStr = line.slice(5).trim(); // 去掉 "data:" 前缀
if (!jsonStr || jsonStr === '[DONE]') return null;
try {
const parsed: object = JSON.parse(jsonStr);
const choices: object[] =
(parsed as Record<string, object>)['choices'] as object[] || [];
if (choices.length > 0) return extractContent(choices[0]);
} catch (_) {}
return null;
}
line.slice(5) 去掉 data: 前缀(5 个字符)。兼容带空格和不带空格的格式(data: {...} 和 data:{...} 都能正确处理)。[DONE] 标记在进入解析函数之前已经被外层逻辑拦截,这里也做了二次防御。
5.4 双路径请求完成保障
export function queryAI(callbacks: AICallbacks, messages: ChatMessage[]): void {
// 取消上一次未完成的请求
if (httpRequestTask) { try { httpRequestTask.destroy(); } catch (_) {} }
const httpRequest = http.createHttp();
httpRequestTask = httpRequest;
// 构建请求体
const fullMessages: ChatMessage[] = [
{ role: 'system', content: SYSTEM_PROMPT }, // 注入系统提示词
...messages,
];
// ── 路径 A:SSE 流式事件 ──
httpRequest.on('dataReceive', (data: ArrayBuffer) => {
// 逐块解析 SSE → onData(token)
});
httpRequest.on('dataEnd', () => {
// SSE 结束 → onDone()
});
// ── 路径 B:request 回调(兜底) ──
httpRequest.request(API_URL, { ... }, (err, resp) => {
if (err) { callbacks.onError(...); safeDestroy(httpRequest); return; }
if (resp.responseCode !== 200) { callbacks.onError(...); safeDestroy(...); return; }
// 提取完整响应体
const bodyStr = typeof resp.result === 'string'
? resp.result
: (resp.result instanceof ArrayBuffer ? arrayBufferToString(resp.result) : '');
if (bodyStr) {
// 优先 SSE 格式解析
const sseContent = parseFullSSEBody(bodyStr);
if (sseContent) { callbacks.onData(sseContent); }
else {
// 再试非流式 JSON
const jsonContent = parseNonStreamingBody(bodyStr);
if (jsonContent) { callbacks.onData(jsonContent); }
else {
console.warn('[AIChat] 无法解析响应');
callbacks.onData(bodyStr); // 最差情况显示原始文本
}
}
}
// ★ 无论是否解析成功,都确保 onDone 被调用
if (!isDone) { isDone = true; callbacks.onDone(); }
safeDestroy(httpRequest);
});
}
三种响应场景覆盖:
| 场景 | 条件 | 解析路径 | 用户体验 |
|---|---|---|---|
| SSE 流式 | dataReceive 触发 |
逐 token → onData |
逐字显示,流畅自然 |
| 非 SSE 流式 | dataReceive 未触发 + body 含 data: 行 |
parseFullSSEBody |
一次性显示 |
| 非流式 JSON | dataReceive 未触发 + body 为 JSON |
parseNonStreamingBody |
一次性显示 |
5.5 ArrayBuffer 解码
function arrayBufferToString(buffer: ArrayBuffer): string {
const uint8Arr = new Uint8Array(buffer);
let text = '';
for (let i = 0; i < uint8Arr.length; i++) {
text += String.fromCharCode(uint8Arr[i]);
}
return text;
}
这段代码手工实现 ArrayBuffer → string 的转换。虽然逐字节拼接 String.fromCharCode 对多字节 UTF-8 字符的处理不是最优的,但它具有最广泛的兼容性——在所有 HarmonyOS NEXT 版本上都能正常工作。
5.6 请求取消
export function cancelAI(): void {
if (httpRequestTask) {
try { httpRequestTask.destroy(); } catch (_) { }
httpRequestTask = null;
}
}
function safeDestroy(req: http.HttpRequest): void {
try { req.destroy(); } catch (_) { }
httpRequestTask = null;
}
cancelAI 对外暴露、由 UI 层的「止念」按钮调用。safeDestroy 是内部工具函数,在所有请求结束路径中被调用(成功、失败、异常),保证 HTTP 请求资源被及时释放。
六、ArkTS 修仙主题适配实战
6.1 命名风格的统一
修仙主题不仅仅是改文字,而是从代码命名到 UI 文案的全链路统一:
| 通用概念 | 修仙世界映射 |
|---|---|
| 用户提问 | 道友问道 |
| AI 回复 | 师尊传法 |
| 加载动画 | 运功调息 |
| 停止请求 | 止念 |
| 清空聊天 | 清心 |
| 发送按钮 | 灵珠 |
| 输入框 | 问道 |
| 网络错误 | 灵机不畅 |
| 分类标签 | 功法体系 |
6.2 色彩与感官
修仙主题使用暖色调(古木、宣纸、赭石、古铜),与常见的科技蓝冷色调形成鲜明对比。这种配色方案传达的感官信号是:温暖、沉稳、可信赖——这正是用户对一位「修仙老道」的期待。
// 冷暖对比
// 科技蓝 (旧) 修仙古木色 (新)
// #2d5f8a → #3d2b1f 顶部栏
// #eef2f7 → #f5f0e8 气泡/卡片背景
// #1a1a2e → #2c1810 文字颜色
// #fafbfc → #faf6f0 页面背景
6.3 动画细节
加载动画「运功调息」使用了三个圆点,通过 opacity(0.4 + idx * 0.3) 实现亮度渐变:
ForEach([0, 1, 2], (idx: number) => {
Circle()
.width(6).height(6).fill('#8b7355')
.opacity(0.4 + idx * 0.3) // → 0.4, 0.7, 1.0
.margin({ left: 2, right: 2 })
})
为什么不用帧动画?因为 ArkUI 的 animateTo 或 animation API 对于简单的渐变效果来说过于复杂。通过静态的 opacity 差值模拟「灵气流转」的效果,代码量少、渲染性能高。
七、完整代码的双文件协同
7.1 导入关系
// Index.ets
import { queryAI, cancelAI, ChatMessage } from './AIChatService';
// AIChatService.ets
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
Index.ets 只导入三个东西:queryAI(发请求)、cancelAI(取消)、ChatMessage(类型)。AIChatService.ets 负责一切网络细节。
7.2 回调协同
查询 AI 时,Index.ets 传入三个回调:
Index.ets AIChatService.ets
┌──────────────────┐ ┌──────────────────────┐
│ onData(text) │ ←── SSE token ──│ dataReceive → 解析 │
│ current+=text │ │ │
├──────────────────┤ │ 或 request 回调 │
│ onDone() │ ←── 完成 ──────│ → onDone() │
│ messages.push() │ │ │
├──────────────────┤ │ │
│ onError(msg) │ ←── 错误 ──────│ → onError() │
│ 仙侠口吻显示 │ │ │
└──────────────────┘ └──────────────────────┘
八、网络权限配置
要让应用能够正常访问 AI API,需要在 module.json5 中添加网络权限:
{
"module": {
"name": "entry",
"type": "entry",
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "需要网络连接以获取 AI 修仙功法指引"
}
]
}
}
没有 ohos.permission.INTERNET 权限,所有网络请求都会被系统拦截,queryAI 的 err 回调会收到网络错误。
九、常见问题与解决方案
9.1 页面空白 / AI 不回复
检查清单:
-
API Key 是否有效
const API_KEY = '你的有效Key'; // 过期 Key 会导致 401 错误 -
网络权限是否配置
module.json5中是否包含ohos.permission.INTERNET -
是否触发了
onDone(参考日志)
搜索[AIChat]日志,确认「收到响应体, len=…」和后续的解析结果
9.2 只有运功动画没有文字
现象:发送消息后,一直看到三个跳动的圆点,没有文字出现。
根因:currentAIResponse 始终为空。可能是 onData 回调未被调用(SSE 事件不触发),且 request 回调的兜底路径也未正确执行。
修复:检查 request 回调末尾是否调用了 onDone()(我们的代码中有 if (!isDone) { isDone = true; callbacks.onDone(); } 保障)。
9.3 文字乱码
现象:AI 回复的内容显示为乱码字符。
根因:arrayBufferToString 使用 String.fromCharCode 逐字节拼接,对于多字节 UTF-8 字符处理不当。
解决方案:在支持 TextDecoder 的环境中使用:
const decoder = new TextDecoder('utf-8', { ignoreBOM: true });
return decoder.decode(uint8Arr);
9.4 修仙口吻不一致
现象:某些回复没有以「善哉善哉」开头,或者结尾没有诗句。
原因:AI 大模型的输出具有概率性,不是每次都能严格遵守格式要求。可以在系统提示词中强调「必须xxx」,但无法 100% 保证。
缓解方案:在 SYSTEM_PROMPT 的结尾用更强的措辞:「每次回答必须以仙侠口吻开头,必须以诗句结尾,违者将受天劫」。
十、ArkTS 语法精要
10.1 装饰器
@Entry // 页面入口
@Component // 自定义组件
struct AICultivationPage {
@State messages: ChatMessage[] = []; // 响应式状态
}
10.2 条件渲染
if (this.isLoading) {
Text('止念') // isLoading 为 true 时渲染
}
if (this.messages.length === 0) {
// 空状态引导页 // 条件为 true 时渲染引导页
}
10.3 循环渲染
ForEach(this.messages, (msg: ChatMessage) => {
ChatBubble({ msg: msg })
})
ForEach(QUICK_QUESTIONS, (q: string) => {
Text(q).onClick(() => { this.sendMessage(q); })
})
10.4 链式属性
Text('☯️ AI 修仙功法')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#f5f0e8')
10.5 ArkTS 与 TypeScript 的关键差异
| 特性 | TypeScript | ArkTS |
|---|---|---|
any 类型 |
允许 | 禁止 |
索引访问 obj[key] |
允许 | as Record<string,T> 中间转换 |
对象展开 {...obj} |
允许 | 仅数组可展开 |
JSON.parse 返回类型 |
any |
需 as Record<string, object> 转换 |
| UI 框架 | 无内置 | 声明式 ArkUI |
十一、扩展方向
11.1 灵根测试功能
基于当前架构,可以快速添加一个「灵根测试」模块。用户回答几个问题,AI 分析出用户的灵根属性(金木水火土),并推荐对应的修炼路径。
11.2 修炼进度系统
为每次对话赋予「修为值」,积累到一定数值可以「突破境界」(炼气→筑基→金丹→元婴→化神)。这个 gamification 设计能大幅提升用户粘性。
11.3 多师尊切换
不止云机子一位师尊。可以设计多角色:菩提祖师(佛法)、太上老君(丹道)、纯阳剑仙(剑修)。不同的师尊对应不同的 SYSTEM_PROMPT,用户可以根据问题类型选择请教谁。
11.4 渡劫日记
当用户遇到重大人生困境时,可以开启「渡劫模式」,AI 会以更长的篇幅、更深入的智慧来陪伴用户度过难关,并将整个过程记录为「渡劫日记」。
十二、总结
「AI修仙功法」是一个用现代技术讲述传统文化故事的尝试。通过约 710 行 ArkTS 代码,我们实现了:
- ✅ 完整的 AI 对话界面:道友提问 → 师尊答复 → 流式显示
- ✅ 修仙主题视觉体系:古木色系 + 仙侠术语 + 宣纸质感
- ✅ SSE 流式 + HTTP 双路径网络层:兼容所有 HarmonyOS NEXT 设备
- ✅ 角色一致的用户体验:从提示词到错误信息,全程保持云机子道长的人设
- ✅ 健壮的错误处理:空响应、解析失败、网络异常全覆盖
技术之外,这个项目的真正价值在于展示了一个思路:AI 应用的主题化不只是在 UI 层面换皮,而是从系统提示词到用户交互的全链路「角色扮演」。当用户在输入框打下「道长,我最近很迷茫」时,AI 回复的不仅仅是建议,更是一份带有仙侠韵味的精神慰藉。
这可能就是 AI 应用的下一个方向——不是更高效的工具,而是更有温度的陪伴。
本文基于 HarmonyOS NEXT 6.1.1(API 24)编写,使用 ArkTS 语言与 ArkUI 框架。所有代码均来自实际运行的应用,经过 DevEco Studio 编译验证。
更多推荐


所有评论(0)