在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

目录

  1. 前言:当修仙文化遇上 AI
  2. 系统提示词工程 —— 创造一个有灵魂的 AI 角色
  3. 整体架构设计
  4. 网络层深度剖析
  5. SSE 流式协议的手工实现
  6. 修仙主题 UI 设计体系
  7. 聊天界面布局详解
  8. @State 状态管理与数据流
  9. 标签云布局 —— FlexWrap 实战
  10. 消息气泡组件与流式打字效果
  11. 错误处理与边界情况全梳理
  12. 性能优化实践
  13. 编译部署与常见问题
  14. 总结与扩展方向

1. 前言:当修仙文化遇上 AI

在 HarmonyOS NEXT API 24 上构建 AI 聊天应用,已经不仅仅是调通一个 HTTP 接口那么简单。当我们将中国传统的修仙文化(Xianxia/Cultivation)与现代大语言模型结合时,开发者需要同时掌握:

  • 大模型提示词工程:如何用 System Prompt 塑造 AI 的角色人格
  • ArkTS 声明式 UI:如何用代码构建沉浸式的修仙界面
  • SSE 流式协议:如何解析大模型的逐 token 输出
  • 鸿蒙网络编程:如何使用 @kit.NetworkKit 的 http 模块
  • 状态管理:如何用 @State 驱动高频 UI 更新

「AI 修仙功法」是一个完全运行在鸿蒙设备上的 AI 聊天应用,AI 扮演一位来自上古修仙界的散仙,回答关于修炼境界、功法心法、丹药炼制、法器法宝等八大领域的修炼问题。

本文将从提示词工程、网络层实现、UI 主题体系、状态管理与流式渲染四个核心维度,完整剖析这个应用的每一行代码。


2. 系统提示词工程 —— 创造一个有灵魂的 AI 角色

2.1 什么是 System Prompt

System Prompt(系统提示词)是在每次 API 调用时随用户消息一起发送给大模型的指令。它定义了 AI 的角色身份、知识范围、回复风格和行为边界

在 OpenAI 兼容的 API 中,请求体呈如下结构:

{
  "messages": [
    {"role": "system", "content": "你是..."},
    {"role": "user", "content": "用户问题..."},
    {"role": "assistant", "content": "AI回复..."}
  ]
}

system 消息不在聊天界面中显示,但它从根本上决定了 AI 的行为方式

2.2 「AI 修仙功法」的 System Prompt 设计

const SYSTEM_PROMPT: string =
  '你是一位来自上古修仙界的资深散仙 "AI 修仙功法",' +
    '曾在九天十地游历万年,精通人、妖、魔三界各种功法秘籍。' +
    '你以指点后辈修士为己任,帮助修仙路上的道友解决各种修炼难题。' +
    '你覆盖但不限于以下领域:\n\n' +
    '1. **修炼境界**:炼气、筑基、金丹、元婴、化神、合体、大乘、渡劫等各境界突破要领\n' +
    // ... 2~7 省略 ...
    '8. **秘境探险**:秘境规则、寻宝技巧、禁制破解、逃命秘术\n\n' +
    '以下是你要遵循的回复原则:\n' +
    '1. 先感知:察觉道友当前的修炼状态和心境,给予理解与鼓励。\n' +
    '2. 再指路:分析问题根由,指出修炼方向或功法要领。\n' +
    '3. 授秘诀:提供 1~3 条具体的修炼法门或口诀,并说明适用阶段。\n' +
    '4. 语气需有仙风道骨,但不可过于晦涩,兼顾修真韵味和易懂。\n' +
    '5. 涉及突破大境界、渡劫等危险事项时,务必提醒做好万全准备。\n' +
    '6. 回复可适当引用上古传说、仙家典故以增信服力。\n\n' +
    '请用中文回复,保留修仙文风但勿过于文言。';

2.3 好 Prompt 的五个要素

从这份 Prompt 中,可以提炼出优秀 System Prompt 的五个核心要素:

要素 对应内容 作用
身份定义 「资深散仙」「曾在九天十地游历万年」 明确角色,赋予可信度
能力边界 「精通人、妖、魔三界各种功法秘籍」 让 AI 知道自己的知识范围
领域覆盖 8 个修炼领域的具体列表 引导用户提问方向
行为规范 6 条回复原则 控制输出格式和质量
风格指南 「仙风道骨但不可过于晦涩」 平衡沉浸感和可用性

2.4 为什么使用字符串拼接而非模板字符串

在代码中我们使用了 + 号拼接多行字符串,而非 ES6 的模板字符串(Template Literal)。这主要是因为:

  1. 格式控制+ 拼接可以精确控制每行的缩进和换行
  2. 可读性:每行一个句子,便于版本 diff 对比
  3. 兼容性:某些老版本的 ArkTS 编译器对模板字符串支持不完善

在实际的 API 24 项目中,两种方式均可使用,但推荐字符串拼接 + 明确的 \n 换行,因为你可以精确控制最终发给大模型的内容格式。


3. 整体架构设计

3.1 文件职责划分

entry/src/main/ets/pages/
├── Index.ets              # UI 层:界面渲染、交互、状态管理
├── AIChatService.ets      # 网络层:HTTP 请求、SSE 解析、数据回调
文件 行数 核心 API 职责
AIChatService.ets 341 行 http.createHttp() 网络通信、数据解析
Index.ets 476 行 @State@Builder UI 渲染、用户交互

3.2 数据流全景

道友输入问题 / 点击修炼标签
        │
        ▼
Index.ets: addUserMessage()
        │
        ▼
Index.ets: callAI()
        │  构建 ChatMessage[] 历史
        ▼
queryAI(callbacks, history)
        │
        ▼
AIChatService.ets → HTTP POST (SSE) → 服务器
        │                              │
        │    ┌─────────────────────────┘
        │    ▼
        │  on('dataReceive') 触发?── 是 ──→ SSE 流式逐行解析
        │    │                              │
        │    │                              ▼
        │    │                   每收到一个 token
        │    │                              │
        │    │                              ▼
        │    │             callbacks.onData(text)
        │    │                              │
        │    │                              ▼
        │    │              Index.ets: 追加到消息 + 刷新 UI
        │    │
        │    └── onData 未触发
        │         │
        │         └──→ resp.result 非流式回退解析
        │                    │
        │                    ▼
        │               callbacks.onData(完整文本)
        │
        ▼
callbacks.onDone() → isStreaming = false

4. 网络层深度剖析

4.1 http 模块的导入与使用

在 API 24 中,所有网络请求统一使用 @kit.NetworkKit 包:

import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

BusinessError 用于类型化错误处理。

4.2 请求体构建

const requestBody: ChatCompletionRequest = {
  model: 'deepseek-ai/DeepSeek-V3',
  messages: fullMessages,    // system + user + assistant
  stream: true,              // 启用流式输出
  max_tokens: 2048,          // 最大输出长度
  temperature: 0.6,          // 温度:越低越确定,越高越有创意
  top_p: 0.95,               // 核采样
  frequency_penalty: 0,      // 频率惩罚
  thinking_budget: 2048,     // 思考预算(deepseek 特有参数)
};

参数选择说明(API 24 环境下的最佳实践):

  • temperature: 0.6:介于 0(精确/确定性)和 1(创意/随机性)之间,既保证修仙回复的连贯性,又保留一些意外惊喜
  • max_tokens: 2048:足够覆盖大多数修炼问题的回复,过长会浪费 tokens
  • stream: true:必须开启,否则无法实现打字效果

4.3 请求头设置

header: {
  Authorization: `Bearer ${API_KEY}`,
  'Content-Type': 'application/json',
  Accept: 'text/event-stream',   // ★ 告知服务器我们希望 SSE 流式响应
}

Accept: text/event-stream 是请求 SSE 响应的关键头信息。如果不设置这个头,某些 API 服务器可能不会启用流式模式。

4.4 超时配置

connectTimeout: 30000,  // 连接超时:30 秒
readTimeout: 120000,    // 读取超时:120 秒(AI 回复可能较长)

AI 大模型的推理速度通常较慢,尤其是复杂问题可能需要 10~30 秒才能开始返回内容。readTimeout 设为 120 秒是一个合理的上限。


5. SSE 流式协议的手工实现

5.1 SSE 协议格式

SSE(Server-Sent Events)的每一行格式如下:

data: {"choices":[{"delta":{"content":"修炼"}}]}

data: {"choices":[{"delta":{"content":"之"}}]}

data: {"choices":[{"delta":{"content":"道"}}]}

data: [DONE]

关键规则:

  • 每行以 data: 开头(包含冒号后的空格)
  • data: [DONE] 表示流结束
  • 不同事件之间用空行 \n 分隔

5.2 ArrayBuffer 解码

httpRequest.on('dataReceive') 返回的是 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;
}

性能提示:对于大块数据,逐字节拼接字符串可能较慢。如果遇到性能瓶颈,可以考虑使用 TextDecoder API(API 24 已支持)。

5.3 缓冲区管理算法

流式数据是分块(chunk)到达的,每个 chunk 可能包含不完整的行。缓冲区管理是 SSE 解析的核心:

let buffer = '';

httpRequest.on('dataReceive', (data: ArrayBuffer) => {
  const text = arrayBufferToString(data);
  buffer += text;                           // ★ 追加到缓冲区

  const lines = buffer.split('\n');
  buffer = lines.pop() ?? '';               // ★ 最后一行可能不完整

  for (const line of lines) {
    const trimmed = line.trim();
    if (!trimmed.startsWith('data:')) continue;
    if (trimmed === 'data:[DONE]') { /* end */ continue; }
    const content = parseSSEDataLine(trimmed);
    if (content) callbacks.onData(content);
  }
});

为什么最后一行要 pop 出来?

考虑两个连续 chunk:

chunk 1: "data: {"choices":[{"delta":{"conte"
chunk 2: "nt":"修炼"}}]}\n\ndata: [DONE]\n"

如果单纯按 \n 分割,chunk 1 最后一行 data: {"choices":[{"delta":{"conte" 是不完整的 JSON,直接解析会报错。缓冲区方案将不完整的最后一行留到下一个 chunk 到达时再处理。

5.4 SSE 行解析

function parseSSEDataLine(line: string): string | null {
  const jsonStr = line.slice(5).trim();   // 去掉 "data:"
  if (!jsonStr) return null;

  try {
    const parsed = JSON.parse(jsonStr);
    const choices = parsed.choices;
    if (choices?.length > 0) {
      const delta = choices[0].delta;
      if (delta) return delta.content;
      const message = choices[0].message;
      if (message) return message.content;
    }
  } catch (_) { /* ignore */ }
  return null;
}

注意 line.slice(5) 截取从第 5 个字符开始的内容——即去掉 data: 后(data: 占 5 个字符,包括冒号)。

5.5 双路径保障机制

在 HarmonyOS 的不同版本上,httpRequest.on('dataReceive') 的行为存在差异。某些版本在 SSE 场景下不会触发该事件。为此,我们设计了双路径保障

// 路径 A:流式(dataReceive 事件)
httpRequest.on('dataReceive', (data: ArrayBuffer) => { /* SSE 解析 */ });

// 路径 B:非流式回退(request 回调)
if (!receivedAnyData && resp.result) {
  // 从完整响应体解析
  const bodyStr = typeof resp.result === 'string'
    ? resp.result
    : arrayBufferToString(resp.result);

  const sseContent = parseFullSSEBody(bodyStr);     // 尝试 SSE
  if (!sseContent) {
    const jsonContent = parseNonStreamingBody(bodyStr); // 尝试 JSON
  }
}

这种设计确保了应用在任何 HarmonyOS 版本上都能正常工作。


6. 修仙主题 UI 设计体系

6.1 配色系统

「AI 修仙功法」使用了一套完整的紫金配色体系

用途 色值 说明
页面背景 #0d001a 深邃夜空般的暗紫色
标题栏背景 linear-gradient(135deg, #1a0033, #2d1b69, #1a0033) 紫金色渐变
标题文字 #FFD700 金色,象征仙界
输入框背景 #1a0a2e 深紫,金色边框点缀
用户气泡 rgba(123,47,190,0.6) 半透明紫色(金色边框)
AI 气泡 rgba(26,10,46,0.85) 深紫(金色边框)
AI 文字 #E8D5B7 古风米白色
错误条 #8B0000 暗红色
次级文字 #B8966B / #A08060 古铜色

6.2 在 ArkTS 中实现渐变色背景

在 API 24 中,渐变色通过 backgroundImage 属性实现:

Row()
  .backgroundImage('linear-gradient(135deg, #1a0033, #2d1b69, #1a0033)')
  .backgroundImageSize({ width: '100%', height: '100%' })

关键点

  • backgroundImage 支持 CSS 格式的渐变表达式
  • 必须配合 backgroundImageSize({ width: '100%', height: '100%' }) 才能正确填充
  • API 24 不支持 linearGradient 对象语法,只能使用 CSS 字符串

6.3 修仙风格文案设计

场景 普通文案 修仙文案
清空对话 新对话 重开法坛
加载中 思考中… 仙尊沉思中…
发送按钮 发送 问道
输入框占位 输入你的生活难题… 描述你的修炼难题…
空回复兜底 抱歉,我不明白 此事颇为玄妙,可否细细道来?
底部箴言 「修仙之道,贵在持之以恒……」

这种文案替换看似简单,但对用户体验的影响是巨大的——它让用户从打开应用的第一秒就进入了修仙世界的沉浸感


7. 聊天界面布局详解

7.1 三段式布局

┌──────────────────────────────────────┐
│  ☯ AI 修仙功法                        │  ← 紫金渐变标题栏 (60vp)
│     问道求仙 · 指掌之间  [🔄 重开法坛] │
├──────────────────────────────────────┤
│                                      │
│          聊天消息 / 欢迎界面            │  ← Scroll + Column
│          (layoutWeight: 1)            │     (深紫背景)
│                                      │
├──────────────────────────────────────┤
│     [错误提示条 - 仅出错时显示]         │  ← 条件渲染
├──────────────────────────────────────┤
│  ┌──────────────────┐ ┌──────┐       │
│  │ 描述你的修炼难题... │ │ 问道 │       │  ← 底部输入栏
│  └──────────────────┘ └──────┘       │
└──────────────────────────────────────┘

7.2 layoutWeight 的核心作用

Column() {
  // 顶部标题栏(固定高度 60vp)

  Scroll() {
    // 消息列表
  }
  .layoutWeight(1)     // ★ 撑满剩余空间
  .backgroundColor('#0d001a')

  // 错误提示条(条件渲染)

  // 底部输入栏(固定高度 ~60vp)
}

layoutWeight(1) 是 ArkTS 中最实用的布局属性之一。它让 Scroll 占据标题栏和输入栏之间的全部剩余空间,无需手动计算高度。当软键盘弹出时,这个区域会自动压缩,保障输入栏始终可见。

7.3 条件渲染的空状态 / 聊天态切换

Scroll() {
  Column() {
    if (this.messages.length === 0 && !this.isLoading) {
      // 空状态 → 欢迎界面(含场景标签云 + 底部箴言)
      this.buildWelcomeView();
    } else {
      // 有消息 → 渲染气泡列表
      ForEach(this.messages, (msg) => { this.buildMessageBubble(msg); });
      // 加载中 → 显示「仙尊沉思中...」
      if (this.isLoading) { this.buildLoadingIndicator(); }
    }
  }
}

这种写法的好处是单一数据源——this.messages 的长度决定了显示欢迎页还是聊天页,不存在「当前页面模式」这样的冗余状态。


8. @State 状态管理与数据流

8.1 @State 状态定义

@State messages: ChatItem[] = [];    // 消息列表(UI 核心数据)
@State inputText: string = '';       // 输入框内容
@State isLoading: boolean = false;   // 加载标志
@State errorMsg: string = '';        // 错误信息

这四个 @State 驱动了整个 UI 的变化:

@State 变化 UI 响应
messages 增加/修改 消息气泡列表重新渲染
inputText 变化 输入框内容更新、发送按钮颜色变化
isLoading 变为 true 按钮显示 Loading、发送按钮禁用、出现「仙尊沉思中」
isLoading 变为 false 按钮恢复、隐藏加载指示
errorMsg 不为空 显示红色错误提示条

8.2 @State 的浅比较机制

这是 ArkTS 开发中最容易被忽视的关键点。在 API 24 中,@State 使用**浅比较(Shallow Comparison)**来判定数据是否变化。

// ❌ 这样不会触发 UI 刷新!
this.messages[idx].content += '新的文字';
// messages 数组的引用没有变化,@State 检测不到修改

// ✅ 必须创建新数组!
this.messages[idx].content += '新的文字';
this.messages = [...this.messages];   // 新数组 → 触发刷新

原理@State 装饰器在每次赋值操作时,比较新旧值的引用(而非内容)。只有引用发生变化时,才会触发 build() 重新执行。[...arr] 创建了数组的浅拷贝,引用变了,UI 随之刷新。

8.3 流式数据的不可变更新

每次 AI 返回一个 token,都需要执行一次「读→改→写」操作:

onData: (text: string) => {
  const idx = this.messages.findIndex(m => m.id === aiMsgId);
  if (idx !== -1) {
    this.messages[idx].content += text;      // 改
    this.messages = [...this.messages];       // 写(触发刷新)
  }
}

对于几十到几百次更新(一次 AI 回复的 token 数),这种模式的性能完全足够。如果 token 数量极大(如生成 8000+ tokens),可以每 3~5 个 token 刷新一次来优化。

8.4 聊天历史的正确构建

发送给 API 的消息历史必须排除正在流式输出的那条消息:

const history: ChatMessage[] = [];
for (const msg of this.messages) {
  if (msg.id === aiMsgId) continue;          // 跳过当前 AI 消息
  if (msg.role === 'user') {
    history.push({ role: 'user', content: msg.content });
  } else if (msg.role === 'ai' && !msg.isStreaming) {
    history.push({ role: 'assistant', content: msg.content });
  }
}

9. 标签云布局 —— FlexWrap 实战

9.1 场景标签的设计

8 个修炼标签的排列需求:

  1. 水平排列,从左到右
  2. 宽度由内容自适应(不同长度的「丹药炼制」与「天劫渡劫」宽度不同)
  3. 一行满后自动换行
  4. 居中对齐

9.2 Flex + FlexWrap.Wrap 实现

Flex({
  wrap: FlexWrap.Wrap,                    // ★ 允许换行
  justifyContent: FlexAlign.Center,       // ★ 居中对齐
  alignContent: FlexAlign.Start,
}) {
  ForEach(this.scenes, (scene: SceneEntry) => {
    Button() {
      Row() {
        Text(scene.icon)
        Text(scene.label)
      }
    }
    .backgroundColor(scene.color)
    .borderRadius(18)
    .margin({ left: 4, right: 4, bottom: 8 })
    .onClick(() => { this.sendQuestion(scene.question); })
  })
}

9.3 为什么不用 GridRow

GridRow 要求所有子项等宽——但在标签云中,每个标签的宽度由其文本长度决定(「天劫渡劫」和「灵兽御兽」宽度显然不同)。Flex + Wrap 是标签云的唯一正确选择。

9.4 标签的 onCick

每个标签的 onClick 都绑定了一个完整的问题,点击后直接发送:

.onClick(() => {
  this.sendQuestion(scene.question);
})

这样用户不需要打字,一键就能开始对话。降低交互门槛是提高用户留存率的关键手段


10. 消息气泡组件与流式打字效果

10.1 道友消息气泡(右对齐)

Row() {
  Row() {}.layoutWeight(1)                    // 将气泡推到右侧

  Column() {
    Text(msg.content)
      .fontSize(15)
      .fontColor('#FFE4B5')
      .lineHeight(22)
  }
  .constraintSize({ maxWidth: 300 })
  .padding(12)
  .backgroundColor('rgba(123,47,190,0.6)')
  .border({ width: 1, color: 'rgba(255,215,0,0.2)' })
  .borderRadius(16)
  .borderRadius({ bottomRight: 4 })           // ★ 尖角效果

  Text('🧑‍🦰').fontSize(22)
}

10.2 仙尊消息气泡(左对齐)

Row() {
  Text('🧙').fontSize(24)

  Column() {
    Text(msg.isStreaming ? msg.content + '▌' : msg.content)
      .fontSize(15)
      .fontColor('#E8D5B7')
      .lineHeight(22)
  }
  .constraintSize({ maxWidth: 300 })
  .padding(12)
  .backgroundColor('rgba(26,10,46,0.85)')
  .border({ width: 1, color: 'rgba(255,215,0,0.3)' })
  .borderRadius(16)
  .borderRadius({ bottomLeft: 4 })
  .shadow({ radius: 4, color: 'rgba(255,215,0,0.08)', offsetX: 0, offsetY: 2 })

  Row() {}.layoutWeight(1)
}

10.3 流式光标效果

// 流式输出中 → 追加 ▌光标
Text(msg.isStreaming ? msg.content + '▌' : msg.content)

// 流式结束 → isStreaming = false → 光标消失
onDone: () => {
  this.messages[idx].isStreaming = false;
}

(U+258C)字符在文字最后显示为一个闪烁的方块光标,模拟打字效果。真正实现「闪烁」效果需要配合定时器交替显示空格和 ,但对于 200~500ms 一次的 token 更新,光标本身的闪烁感已经足够自然。


11. 错误处理与边界情况全梳理

11.1 六种错误场景

场景 触发条件 UI 表现
网络不通 DNS 解析失败/无网络 红色错误条:「请求失败: XXX」
鉴权失败 API Key 错误/过期 红色错误条:「服务器返回错误 (401): …」
限流 请求频率过高 红色错误条:「服务器返回错误 (429): …」
响应格式异常 服务器返回了意外的数据格式 红色错误条:「无法解析响应: …」
AI 返回空内容 模型未输出任何实质内容 兜底文本:「此事颇为玄妙,可否细细道来?」
流式未触发 平台差异导致 dataReceive 未触发 自动切换到非流式回退路径

11.2 请求取消(Cancel)

当用户点击「重开法坛」时,需要取消正在进行的请求:

// Index.ets
clearConversation(): void {
  cancelAI();     // ★ 先取消 HTTP 请求
  this.messages = [];
  this.isLoading = false;
  this.errorMsg = '';
}

// AIChatService.ets
export function cancelAI(): void {
  if (httpRequestTask) {
    httpRequestTask.destroy();
    httpRequestTask = null;
  }
}

为什么需要 cancel?因为如果不取消,onDataonDone 回调仍然会执行,试图更新已经被清空的 messages 数组,可能导致 findIndex 找不到消息而出错。

11.3 防重复点击

sendMessage(): void {
  const text = this.inputText.trim();
  if (!text || this.isLoading) return;   // ★ 正在加载中禁止发送
  // ...
}

同时,发送按钮在加载态下 enabled(false) 并显示 LoadingProgress,从 UI 层面阻止用户操作。


12. 性能优化实践

12.1 LazyForEach 替代 ForEach

当消息列表超过 50 条时,推荐使用 LazyForEach 替代 ForEach

// 需要先实现 IDataSource
class MessageDataSource implements IDataSource {
  private data: ChatItem[] = [];
  // ... 实现 totalCount / getData / registerDataChangeListener 等方法
}

LazyForEach(this.dataSource, (msg: ChatItem) => {
  this.buildMessageBubble(msg);
}, (msg: ChatItem) => msg.id.toString())

LazyForEach 只渲染视口内的组件,离开视口的组件会被回收。对于数百条消息的场景,性能差距可达 5~10 倍

12.2 控制 UI 刷新频率

在极高频率的流式更新场景(每次 onData 都刷新),UI 线程可能跟不上渲染。可以通过节流来控制刷新频率:

let renderCount = 0;

onData: (text: string) => {
  this.messages[idx].content += text;
  renderCount++;
  if (renderCount % 3 === 0) {    // 每 3 个 token 刷新一次
    this.messages = [...this.messages];
  }
}

onDone: () => {
  this.messages = [...this.messages];  // 最终确保刷新
}

12.3 @Builder 避免重复代码

将可复用的 UI 片段抽取为 @Builder,减少代码重复:

@Builder
buildMessageBubble(msg: ChatItem) { /* ... */ }

@Builder
buildWelcomeView() { /* ... */ }

@Builder
buildLoadingIndicator() { /* ... */ }

13. 编译部署与常见问题

13.1 完整编译命令

hvigorw --mode module -p module=entry -p product=default assembleHap

13.2 常见编译错误及解决方案

错误信息 原因 解决方案
Cannot find name 'ResourceColor' 类型未识别 API 24 可直接使用 string 色值,或在文件头确认无 import 遗漏
Property 'maxWidth' does not exist on type 'ColumnAttribute' 属性不存在 改用 constraintSize({ maxWidth: 300 })
Argument of type 'string' is not assignable to parameter of type 'ResourceStr' 类型不匹配 字符串直接作为 ResourceStr 使用,或通过 $r() 引用资源
Module has no exported member 导入路径错误 确认使用 @kit.* 的 kit 路径,而非 @ohos.*

13.3 权限配置

entry/src/main/module.json5 中添加:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
      }
    ]
  }
}

如果漏掉这一步,http 请求会在运行时静默失败,且不会触发 onError 回调(因为请求根本没发出去)。


14. 总结与扩展方向

14.1 本文要点回顾

本文通过「AI 修仙功法」这个完整的项目,系统讲解了在 HarmonyOS NEXT API 24 中使用 ArkTS 构建 AI 聊天应用的 8 个核心技术点:

技术点 关键内容 实战价值
提示词工程 System Prompt 五要素设计 任何 AI 应用的核心
HTTP 网络层 http 模块 + SSE 流式 连接大模型的基础
SSE 解析 缓冲区管理 + 双路径保障 解决平台兼容问题
主题 UI 体系 渐变色、配色、文案系统 提升产品沉浸感
三段式布局 Column + Scroll + layoutWeight 所有聊天界面的模板
@State 管理 浅比较机制 + 不可变更新 避免「改了但不刷新」bug
FlexWrap 流式标签云布局 快速场景入口
错误处理 6 种场景 + 请求取消 保障应用稳定性

14.2 ArkTS 开发的三个黄金法则

  1. @State 是不可变数据:永远通过创建新数组/新对象来触发刷新
  2. 属性链在闭包之前Component().attr() { content } 而非 Component() { content }.attr()
  3. 网络请求永远准备 B 计划:流式 + 非流式双路径保障

14.3 扩展方向

功能 实现思路 涉及 API
历史记录持久化 使用 Preferences 存储对话 JSON @kit.StorageKit
语音输入 集成语音识别,将用户语音转为文字 @kit.VoiceKit
Markdown 渲染 AI 回复中的 **粗体**、列表等格式渲染 RichText 或自定义解析
多轮对话记忆 当前已实现,只需增加持久化
主题切换 修仙/科幻/古风等多套主题 @Styles + @Extend
流式音频播放 AI 回复以语音形式朗读 @kit.AudioKit
对话导出 导出为文本或图片分享 @kit.DialogKit + @kit.MediaKit

14.4 写在最后

「AI 修仙功法」不仅是一个娱乐化的 AI 聊天应用,更是一份完整的 HarmonyOS NEXT AI 应用开发参考实现

从提示词工程到 SSE 流式解析,从紫金主题 UI 到 @State 不可变更新,每一个技术点都可以直接迁移到其他类型的 AI 应用中——无论是情感陪伴、知识问答、代码助手,还是本文主题的修仙向导。

修仙之道,贵在持之以恒。An developer’s path is also one of persistent cultivation.


本文所有代码基于 HarmonyOS NEXT API 24(ArkTS 声明式开发范式)。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐