鸿蒙原生 ArkTS AI 对话应用实战:从零构建「AI 万能手册」

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

一、引言

随着大语言模型(LLM)技术的快速发展,将 AI 对话能力集成到原生应用中已成为移动开发的刚需。从智能客服到个人助理,从学习辅导到情感陪伴,AI 对话正在改变用户与应用的交互方式。

本文将以一个完整的「AI 万能手册」应用为依托,详细讲解如何在 HarmonyOS NEXT(API 24)上使用 ArkTS 原生框架构建一个支持流式响应的 AI 对话应用。内容涵盖:

  • 项目架构设计与页面导航
  • 原生 HTTP 网络请求(@kit.NetworkKit
  • SSE(Server-Sent Events)流式响应解析
  • 声明式 UI 的聊天气泡组件设计
  • 系统提示词(System Prompt)工程
  • 状态管理与流式文本拼接
  • 编译错误排查与 ArkTS 语法避坑

二、项目架构与入口

2.1 应用入口 EntryAbility

鸿蒙应用的入口是 UIAbility 的子类。每个应用有且只有一个 EntryAbility,它负责应用的初始化、生命周期管理和页面加载。

// entry/src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'App', 'Ability onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(0x0000, 'App', 'Ability onWindowStageCreate');
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'App',
          'Failed to load content: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(0x0000, 'App', 'Succeeded in loading content.');
    });
  }

  onForeground(): void { /* 应用进入前台 */ }
  onBackground(): void { /* 应用进入后台 */ }
}

关键点

  • loadContent('pages/Index') 加载 Index.ets 页面。路径不含 .ets 后缀。
  • hilog 是鸿蒙的日志工具,来自 @kit.PerformanceAnalysisKit
  • 生命周期 onCreateonWindowStageCreateonForegroundonBackground 的顺序是固定的。

2.2 项目文件结构

entry/src/main/ets/
├── entryability/
│   └── EntryAbility.ets           # 应用入口
└── pages/
    ├── Index.ets                   # AI 聊天主界面
    ├── AIChatService.ets           # AI 服务层(网络请求 + SSE 解析)
    ├── RunnerPage.ets
    └── TabsBottomDemo.ets

2.3 应用架构分层

应用采用经典的三层架构:

Index.ets(UI 层)
    ↓ 调用 queryAI() / cancelAI()
AIChatService.ets(服务层)
    ↓ @kit.NetworkKit HTTP 请求
GitCode AI API(AI 模型层)

这种分层的好处是:

  • UI 层只关心渲染和用户交互,不关心网络细节
  • 服务层封装所有网络和解析逻辑,可独立测试和复用
  • AI 模型层可随时切换不同的 API 供应商

三、AI 服务层:AIChatService.ets

服务层是整个应用的「发动机」。它负责将用户的聊天消息通过 HTTP POST 请求发送到 AI API,并通过 SSE(Server-Sent Events)流式接收和解析 AI 的回复。

3.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~2)
  top_p: number;                          // 核采样参数
  frequency_penalty: number;              // 频率惩罚
  thinking_budget: number;                // 思考预算
}

/** SSE 解析结果回调 */
export interface AICallbacks {
  onData: (text: string) => void;         // 收到新 token
  onDone: () => void;                     // 响应结束
  onError: (errMsg: string) => void;      // 错误处理
}

设计考量

  • ChatMessagerole 字段遵循 OpenAI API 规范:system(系统提示词)、user(用户)、assistant(AI)
  • AICallbacks 使用回调模式而不是 Promise,因为 SSE 是流式的——数据会分多次到达,Promise 只能 resolve 一次
  • stream: true 是关键标记,告诉 API 以 SSE 格式返回响应

3.2 系统提示词工程

系统提示词(System Prompt)是决定 AI 行为和回答质量的关键。一个好的系统提示词应该包含:

  1. 角色定义:AI 是谁?
  2. 能力范围:能解决什么问题?
  3. 回答结构:如何组织回答?
  4. 风格约束:语气、长度、格式等
const SYSTEM_PROMPT: string =
  '你是一位名叫「AI 万能手册」的智能生活助手,擅长解决用户生活中遇到的各种问题。' +
  '你的回答应遵循以下原则:\n\n' +
  '1. 领域覆盖:涵盖但不限于以下生活场景——\n' +
  '   · 情感关系:恋爱沟通、家庭矛盾、友谊维护\n' +
  '   · 职场工作:职业规划、沟通技巧、压力管理\n' +
  '   · 学习成长:学习方法、时间管理、技能提升\n' +
  '   · 健康生活:饮食营养、运动健身、睡眠调节\n' +
  '   · 日常生活:家务技巧、旅行攻略、消费决策\n' +
  '   · 心理情绪:焦虑缓解、情绪管理、自我调节\n' +
  '   · 社交人际:社交技巧、聚会礼仪、沟通话术\n' +
  '   · 法律常识:消费维权、租房合同、劳动权益(仅作参考,建议咨询专业律师)\n\n' +
  '2. 回答结构:每个回答包含——\n' +
  '   · 共情理解:先理解用户的处境和情绪\n' +
  '   · 问题分析:简要分析问题的关键点\n' +
  '   · 实用建议:提供 1~3 条具体可执行的建议\n' +
  '   · 温馨提示:如有需要,补充注意事项\n\n' +
  '3. 回答风格:\n' +
  '   · 语气温和亲切,像一位有经验的朋友在聊天\n' +
  '   · 用词通俗易懂,避免过于专业的术语\n' +
  '   · 建议具体可行,不空泛不鸡汤\n' +
  '   · 涉及法律/医疗等专业领域时,务必注明「建议咨询专业人士」\n\n' +
  '4. 格式要求:\n' +
  '   · 使用中文回答\n' +
  '   · 适当使用 emoji 让回答更生动\n' +
  '   · 分点排列,方便阅读\n' +
  '   · 保持回答简洁,一般不超过 500 字\n\n' +
  '请记住:你的使命是用智慧和温度,帮助用户解决生活中的每一个问题。';

提示词工程要点

  1. 角色明确:定义 AI 的名字和使命,让回答有「人格」
  2. 范围清晰:明确列出 8 大生活领域,避免 AI 回答跑题
  3. 结构约束:要求「共情→分析→建议→提示」的四段式结构
  4. 风格约束:温和亲切、通俗易懂、具体可行
  5. 安全边界:法律/医疗领域必须标注「建议咨询专业人士」
  6. 格式约束:中文 + emoji + 分点 + 500 字上限

3.3 SSE 流式响应解析

SSE(Server-Sent Events)是一种服务器推送协议,允许服务器向客户端持续发送数据流。AI 对话中的「打字机效果」就是通过 SSE 实现的。

SSE 数据格式如下:

data: {"choices":[{"delta":{"content":"你好"}}]}

data: {"choices":[{"delta":{"content":",我"}}]}

data: {"choices":[{"delta":{"content":"是AI"}}]}

data: [DONE]

每条消息以 data: 开头,以 \n\n 结尾。[DONE] 标记流结束。

/**
 * 解析 SSE data 行,提取 content 增量
 */
function parseSSEDataLine(line: string): string | null {
  const jsonStr = line.slice(5).trim();      // 去掉 "data:" 前缀
  if (!jsonStr) return null;

  try {
    const parsed = JSON.parse(jsonStr) as Record<string, Object>;
    const choices = parsed.choices as Object[];
    if (choices && choices.length > 0) {
      const choice = choices[0] as Record<string, Object>;
      // 兼容 delta(流式)和 message(非流式)
      const delta = choice.delta as Record<string, Object>;
      if (delta) return delta.content as string;
      const message = choice.message as Record<string, Object>;
      if (message) return message.content as string;
    }
  } catch (_) { /* JSON 解析失败,跳过 */ }
  return null;
}

解析逻辑

  1. 去掉 data: 前缀(前 5 个字符)
  2. 将剩余部分解析为 JSON
  3. 提取 choices[0].delta.content(流式格式)
  4. 如果 delta 不存在,尝试 message.content(非流式回退)

3.4 非流式回退机制

并非所有的 HarmonyOS 设备都能正确触发 dataReceive 事件。为了应对这种情况,我们设计了「非流式回退」机制:

// ---- 非流式回退 ----
if (!receivedAnyData && resp.result) {
  const bodyStr = typeof resp.result === 'string'
    ? resp.result
    : arrayBufferToString(resp.result as ArrayBuffer);

  // 先尝试按 SSE 格式解析
  const sseContent = parseFullSSEBody(bodyStr);
  if (sseContent) {
    callbacks.onData(sseContent);
  } else {
    // 再尝试非流式 JSON 格式
    const jsonContent = parseNonStreamingBody(bodyStr);
    if (jsonContent) {
      callbacks.onData(jsonContent);
    } else {
      callbacks.onError(`无法解析响应: ${preview}`);
    }
  }
}

回退优先级

  1. 流式 dataReceive(最佳体验,逐字显示)
  2. SSE 格式解析(dataReceive 未触发时,从完整响应体中解析 SSE)
  3. 非流式 JSON 解析(API 返回完整 JSON)
  4. 错误提示(以上全部失败)

3.5 完整请求流程

export function queryAI(
  callbacks: AICallbacks,
  messages: ChatMessage[],
): void {
  // 1. 取消上一次未完成的请求
  if (httpRequestTask) {
    httpRequestTask.destroy();
    httpRequestTask = null;
  }

  // 2. 创建 HTTP 请求
  const httpRequest = http.createHttp();
  httpRequestTask = httpRequest;

  // 3. 构建完整消息列表(系统提示词 + 对话历史)
  const fullMessages: ChatMessage[] = [
    { role: 'system', content: SYSTEM_PROMPT },
    ...messages,
  ];

  // 4. 构建请求体
  const requestBody: ChatCompletionRequest = {
    model: 'deepseek-ai/DeepSeek-V3',
    messages: fullMessages,
    stream: true,
    max_tokens: 2048,
    temperature: 0.6,
  };

  // 5. 注册 SSE 监听
  httpRequest.on('dataReceive', (data: ArrayBuffer) => {
    // 逐块接收 SSE 数据
  });

  httpRequest.on('dataEnd', () => {
    // 数据接收完毕
  });

  // 6. 发起 POST 请求
  httpRequest.request(API_URL, {
    method: http.RequestMethod.POST,
    header: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      Accept: 'text/event-stream',   // ← 关键:要求 SSE 流式响应
    },
    extraData: JSON.stringify(requestBody),
    connectTimeout: 30000,
    readTimeout: 120000,
  }, callback);
}

四、UI 层:Index.ets 聊天界面

UI 层负责渲染聊天界面和处理用户交互。主要包含三个区域:顶部标题栏、消息列表区、底部输入区。

4.1 聊天气泡组件 ChatBubble

聊天气泡是聊天界面的核心视觉元素。我们需要区分用户消息和 AI 消息:

@Component
struct ChatBubble {
  private msg: ChatMessage = { role: 'user', content: '' };
  private isLoading: boolean = false;

  build() {
    Column() {
      if (this.msg.role === 'user') {
        // ── 用户消息:蓝色气泡,右对齐 ──
        Row() {
          Blank()                          // 左侧弹簧,将气泡推到右侧
          Column() {
            Text(this.msg.content)
              .fontSize(15).fontColor('#ffffff')
          }
          .padding(14)
          .backgroundColor('#2d5f8a')      // 蓝色气泡
          .borderRadius({
            topLeft: 16, topRight: 4,       // 右上角小圆角 → "尾巴"效果
            bottomLeft: 16, bottomRight: 16
          })
          .constraintSize({ maxWidth: '80%' })
        }
        .width('100%')
      } else {
        // ── AI 消息:灰色气泡 + 🤖 头像,左对齐 ──
        Row() {
          // AI 头像
          Column() { Text('🤖').fontSize(20) }
            .width(36).height(36)
            .backgroundColor('#eef2f7').borderRadius(18)
            .margin({ right: 8 })

          // 气泡内容
          Column() {
            if (this.isLoading && !this.msg.content) {
              // ★ 正在输入动画:三个小圆点依次闪烁 ★
              Row() {
                ForEach([0, 1, 2], (idx: number) => {
                  Circle()
                    .width(6).height(6).fill('#2d5f8a')
                    .opacity(0.4 + idx * 0.3)     // 透明度依次递增
                    .margin({ left: 2, right: 2 })
                })
              }
              .padding(14).backgroundColor('#f0f4f8').borderRadius(16)
            } else {
              Text(this.msg.content)
                .fontSize(15).fontColor('#1a1a2e')
            }
          }
          .padding(14)
          .backgroundColor('#f0f4f8')      // 灰色气泡
          .borderRadius({
            topLeft: 4, topRight: 16,       // 左上角小圆角 → "尾巴"效果
            bottomLeft: 16, bottomRight: 16
          })
          .constraintSize({ maxWidth: '70%' })

          Blank()                          // 右侧弹簧
        }
        .width('100%')
      }
    }
    .margin({ bottom: 12 })
  }
}

设计要点

  • 用户气泡右对齐:使用 Blank() 占据左侧空间,将气泡推到右侧
  • AI 气泡左对齐:左侧放头像,右侧放气泡,使用 Blank() 占据右侧空间
  • 圆角方向差异:用户气泡的右上角小圆角(4px),AI 气泡的左上角小圆角(4px),模拟聊天气泡的「尾巴」
  • 加载动画isLoading 为 true 且内容为空时,显示三个渐变透明度的圆点

4.2 状态管理

页面级状态使用 @State 装饰器管理:

@Entry
@Component
struct AIUniversalManualPage {
  @State messages: ChatMessage[] = [];       // 聊天消息列表
  @State inputText: string = '';             // 输入框文本
  @State isLoading: boolean = false;          // 是否正在请求 AI
  @State currentAIResponse: string = '';      // 流式拼接中的 AI 回复
}

@State 的作用域规则

  • @State 变量变化时,所在组件的 build() 方法会重新执行
  • 只有被 @State 装饰的变量才能触发 UI 更新
  • 普通成员变量变化不会触发重渲染

4.3 发送消息流程

sendMessage(text: string): void {
  if (this.isLoading || !text.trim()) return;

  // 1. 添加用户消息到列表
  const userMsg: ChatMessage = { role: 'user', content: text.trim() };
  this.messages.push(userMsg);
  this.inputText = '';           // 清空输入框
  this.isLoading = true;
  this.currentAIResponse = '';   // 清空之前的回复

  // 2. 构建对话历史(保留最近 20 条)
  const history: ChatMessage[] = this.messages
    .filter(m => m.content !== '')
    .slice(-20);

  // 3. 调用 AI 服务
  queryAI(
    {
      onData: (content: string) => {
        // 流式拼接:每收到一个 token 就追加到 currentAIResponse
        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.isLoading = false;
      },
    },
    history,
  );
}

关键流程

  1. 用户消息立即加入 messages 列表 → 气泡立即显示
  2. isLoading = true → 显示「正在输入」动画
  3. onData 每次被调用时,currentAIResponse 更新 → UI 中流式显示文字
  4. onDone 被调用时,将完整回复加入 messages → 固化为静态气泡
  5. onError 时显示错误信息

4.4 空白引导页

当用户首次打开应用时,显示引导页而不是空列表:

if (this.messages.length === 0) {
  Column() {
    Text('🤖').fontSize(56)
    Text('AI 万能手册').fontSize(22).fontWeight(FontWeight.Bold)
    Text('解决你生活中的各种问题').fontSize(14).fontColor('#666')

    // 6 大能力卡片
    Row() {
      ForEach(features, (item: FeatureItem) => {
        Column() {
          Text(item.icon).fontSize(24)
          Text(item.text).fontSize(11).fontColor('#555')
        }
        .width(72).height(72)
        .backgroundColor('#f5f7fa').borderRadius(12)
      })
    }
    .justifyContent(FlexAlign.Center)

    // 4 个快捷问题按钮
    Row() {
      ForEach(QUICK_QUESTIONS, (q: string) => {
        Text(q).padding(12)
          .backgroundColor('#eef2f7').borderRadius(16)
          .onClick(() => { this.sendMessage(q); })
      })
    }
  }
}

五、编译错误排查与 ArkTS 语法避坑

在开发过程中,我们遇到并修复了多个编译错误。以下是最常见的 7 类错误及其解决方案。

5.1 类型推断错误

错误信息

Use explicit types instead of "any", "unknown" (arkts-no-any-unknown)

原因:ArkTS 默认禁止 any 类型,要求显式类型注解。

修复

// ❌ 错误:隐式 any
ForEach(data, (item) => { ... })

// ✅ 正确:显式类型
ForEach(data, (item: FeatureItem) => { ... })

// 或使用 as 断言
ForEach([
  { icon: '💕', text: '情感关系' },
] as FeatureItem[], (item: FeatureItem) => { ... })

5.2 属性不存在

错误信息

Property 'maxWidth' does not exist on type 'ColumnAttribute'

原因:ArkTS 组件的属性名可能与预期不同。

修复

// ❌ 错误:maxWidth 不存在
Column() { }
  .maxWidth('80%')

// ✅ 正确:使用 constraintSize
Column() { }
  .constraintSize({ maxWidth: '80%' })

5.3 Flex 容器属性差异

错误信息

Property 'flexDirection' does not exist on type 'FlexAttribute'. Did you mean 'direction'?
Property 'flexWrap' does not exist on type 'FlexAttribute'
Property 'justifyContent' does not exist on type 'FlexAttribute'

原因:在 HarmonyOS NEXT API 24 中,Flex 组件的属性集非常有限。justifyContentflexDirection 等属性仅在 RowColumn 上可用。

修复

// ❌ 错误:Flex 没有 justifyContent
Flex() { }
  .justifyContent(FlexAlign.Center)

// ✅ 正确:使用 Row 替代
Row() { }
  .justifyContent(FlexAlign.Center)

总结:ArkUI 中 FlexRowColumn 的关系:

  • Row = 弹性水平容器(支持 justifyContent、alignItems、layoutWeight)
  • Column = 弹性垂直容器(支持 justifyContent、alignItems、layoutWeight)
  • Flex = 底层弹性容器(API 24 中属性有限,大部分场景用 Row/Column 替代)

5.4 overlay 回调格式

错误信息

Argument of type '{ builder: () => TextAttribute; }' is not assignable to parameter of type 'string | CustomBuilder | ComponentContent<Object>'

原因overlay() 方法接受一个 CustomBuilder 函数,而不是一个包含 builder 属性的对象。

修复

// ❌ 错误:对象格式
Circle()
  .overlay({
    builder: () => Text('↑').fontColor('#ffffff')
  })

// ✅ 正确:直接传函数
Circle()
  .overlay((): void => {
    Text('↑').fontSize(20).fontColor('#ffffff')
  })

5.5 私有属性初始化

警告信息

Property 'msg' is private and can not be initialized through the component constructor

原因:在 ArkTS 中,组件的 private 属性不能通过构造函数参数初始化。

修复:这只是一个警告,不影响运行。可以将 private 改为 public 或移除访问修饰符:

// 有警告:private 属性不能通过构造函数传值
@Component
struct ChatBubble {
  private msg: ChatMessage = { role: 'user', content: '' };
}

// 无警告:移除 private
@Component
struct ChatBubble {
  msg: ChatMessage = { role: 'user', content: '' };
}

5.6 文本字符区分

问题描述:发送按钮显示汉字「上」而非箭头符号「↑」。

原因:在代码中写了 Text("上") 而不是 Text('↑')

修复

// ❌ 显示汉字
Text("上").fontSize(20)

// ✅ 显示箭头
Text('↑').fontSize(20)

Unicode 箭头符号需要用单引号包裹,"上" 是汉字,'↑' 是箭头符号。

5.7 权限配置

警告信息

To use this API, you need to apply for the permissions: ohos.permission.INTERNET

原因:网络请求需要 ohos.permission.INTERNET 权限。

修复:在 module.json5 中添加:

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

六、ArkTS 组件设计模式

6.1 @Builder 自定义构建器

@Builder 是 ArkTS 中复用 UI 片段的核心机制。它类似于一个不返回值的函数,可以在 build() 方法中直接被调用:

@Component
struct MyComponent {
  @State count: number = 0;

  build() {
    Column() {
      this.MyButton('点击', '#2d5f8a')
    }
  }

  @Builder
  MyButton(label: string, color: string) {
    Button(label)
      .backgroundColor(color)
      .fontColor('#ffffff')
      .onClick(() => { this.count++ })
  }
}

在底部 Tabs 导航中,我们大量使用 @Builder 来自定义 Tab 样式:

.tabBar(this.BottomTabBuilder(idx, tab))

@Builder
BottomTabBuilder(index: number, tab: TabData) {
  Column() {
    Stack() {
      Text(tab.icon).fontSize(22)
      if (tab.badge > 0) {
        Text(`${tab.badge}`)
          .backgroundColor('#e74c3c').borderRadius(8)
      }
    }
    Text(tab.label).fontSize(10)
  }
}

6.2 @BuilderParam 插槽模式

@BuilderParam 是 ArkTS 的「插槽」机制,允许父组件向子组件注入 UI 片段:

@Component
struct SectionCard {
  @BuilderParam content?: () => void;

  build() {
    Column() {
      // ... 标题和描述
      if (this.content) {
        this.content()     // ← 渲染注入的内容
      }
      // ... 代码块
    }
  }
}

// 使用
SectionCard({
  content: MyCustomContent()    // ← 注入自定义内容
})

这种模式在演示页面中广泛使用,实现了「通用卡片容器 + 可变内容」的优雅分离。

6.3 @State + @Link 数据驱动

@State 是组件的内部状态,@Link 是父组件向子组件传递的引用状态:

@Component
struct Child {
  @Link value: number;

  build() {
    Button(`值: ${this.value}`)
      .onClick(() => { this.value++ })  // ★ 修改 @Link 会同步到父组件 ★
  }
}

@Component
struct Parent {
  @State count: number = 0;

  build() {
    Column() {
      Child({ value: this.count })      // 传递 @State 给 @Link
    }
  }
}

七、AI 对话应用最佳实践

7.1 对话历史管理

// 保留最近 N 条消息,避免超出 token 上限
const history = messages
  .filter(m => m.content !== '')       // 过滤空消息
  .slice(-20);                          // 最多保留 20 条

原则

  • 保留系统提示词 + 最近 20 条对话
  • 过滤掉空的 AI 回复(流式拼接过程中产生的临时空字符串)
  • 系统提示词始终在消息列表最前面

7.2 请求取消机制

当用户快速发送多条消息时,需要取消上一次未完成的请求:

let httpRequestTask: http.HttpRequest | null = null;

export function cancelAI(): void {
  if (httpRequestTask) {
    try {
      httpRequestTask.destroy();
    } catch (_) { /* ignore */ }
    httpRequestTask = null;
  }
}

在 UI 层,提供「停止」按钮和「清空」按钮都调用 cancelAI()

// 停止按钮(仅加载中可见)
if (this.isLoading) {
  Text('停止').onClick(() => {
    cancelAI();
    this.isLoading = false;
  })
}

// 清空按钮
Text('清空').onClick(() => {
  cancelAI();
  this.messages = [];
  this.currentAIResponse = '';
  this.isLoading = false;
})

7.3 错误处理策略

onError: (errMsg: string) => {
  // 向用户显示友好错误信息,而非崩溃或静默失败
  this.messages.push({
    role: 'assistant',
    content: `😅 抱歉,我遇到了问题:${errMsg}`,
  });
  this.isLoading = false;
}

错误处理原则

  1. 绝不崩溃:所有网络错误都应捕获并显示
  2. 友好提示:使用 emoji 和口语化表达
  3. 显示错误详情:帮助用户判断是网络问题还是 API 问题
  4. 恢复状态:确保 isLoading 被重置,UI 恢复正常

八、性能优化

8.1 流式渲染 vs 全量渲染

流式渲染(SSE)的核心优势是「逐字显示」,用户无需等待完整的 AI 回复即可开始阅读。这在长回复场景下大幅提升了用户体验。

实现原理:

  1. AI 服务器将回复拆成多个 token
  2. 每个 token 到达后,onData 回调被触发
  3. currentAIResponse 累加新内容
  4. @State 触发 UI 重渲染
  5. 气泡中的文字实时更新

8.2 对话历史截断

AI 模型的 token 上限是有限的(DeepSeek-V3 支持 32K tokens)。如果不截断历史,过长的对话会导致 API 调用失败。我们的策略是保留最近 20 条消息,这在实际使用中已足够维持上下文连贯性。

8.3 请求超时配置

connectTimeout: 30000,    // 连接超时 30 秒
readTimeout: 120000,      // 读取超时 120 秒

较长的读取超时时间是因为 AI 生成完整回复可能需要 10-60 秒(取决于回复长度和服务器负载)。


九、ArkTS vs TypeScript/JavaScript 差异总结

特性TypeScriptArkTS
any 类型允许❌ 禁止
unknown 类型允许❌ 禁止
联合类型支持有限支持
Flex 组件属性仅支持基础属性
Row/Column支持 justifyContent/alignItems
overlay()对象格式函数格式
@State必须用于响应式数据
私有属性传参允许警告
模块导入ES Module@kit.xxx 格式

十、总结与展望

本文通过一个完整的「AI 万能手册」应用,详细讲解了在 HarmonyOS NEXT(API 24)上使用 ArkTS 构建 AI 对话应用的完整流程。从项目架构、服务层设计、SSE 流式解析、UI 组件设计到编译错误排查,覆盖了全链路开发要点。

核心技术点回顾:

  1. 网络层:使用 @kit.NetworkKithttp 模块发起 SSE 请求
  2. 数据层:通过回调模式实现流式数据拼接
  3. UI 层@State 驱动消息列表实时更新
  4. 提示词工程:通过 System Prompt 精确控制 AI 回答行为和质量
  5. 错误处理:三层回退机制保障网络异常时的可用性

AI 对话应用只是鸿蒙原生 AI 集成的起点。后续可以扩展的方向包括:

  • 加入 TTS(文本转语音)能力,让 AI「说」出回复
  • 接入图片理解模型,支持多模态交互
  • 结合本地数据库,实现对话持久化
  • 加入语音输入,提升输入效率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值