AI 万能手册:从零构建 HarmonyOS 智能对话应用(API 24)

基于 ArkTS + DeepSeek API 实现流式 AI 对话,全面兼容 API 24
在这里插入图片描述


目录

  1. 项目背景与定位
  2. 技术选型与架构设计
  3. 页面布局与 UI 组件
  4. 网络层:AIChatService 详解
  5. SSE 流式响应的实现原理
  6. 状态管理与数据流
  7. API 24 兼容性攻关记录
  8. 错误处理与异常恢复
  9. 性能优化实践
  10. 完整代码解读
  11. 部署与运行指南
  12. 总结与未来规划

1. 项目背景与定位

1.1 为什么需要"AI 万能手册"

在日常生活和工作中,我们经常遇到各种需要专业建议的场景:

  • 写一段得体的工作邮件
  • 调试一段复杂的代码
  • 分析一段对话中的情感需求
  • 制定一份健身计划
  • 为孩子设计一份学习方案

传统的搜索引擎需要用户从大量结果中筛选信息,而 AI 大模型能够直接给出针对性回答。然而,市面上大多数 AI 应用都是通用型聊天界面,缺乏特定场景的引导和优化。

AI 万能手册 正是为解决这一痛点而生——它是一款全能的 AI 生活助手应用,覆盖八大领域,提供开箱即用的智能问答体验。

1.2 目标用户

  • 开发者:需要快速获取技术方案和代码示例
  • 职场人士:需要文案润色、邮件起草、沟通建议
  • 学生:需要学习辅导、知识答疑
  • 普通用户:需要生活建议、健康指导、情感分析

1.3 核心功能

功能模块 描述 AI 能力
智能对话 自然语言问答 DeepSeek-V3 大模型
流式响应 实时逐字输出 SSE Server-Sent Events
历史对话 上下文连续理解 消息数组维护
通用覆盖 8 大领域问题 系统提示词引导
错误兜底 网络异常恢复 双模式解析回退

1.4 技术约束

本项目基于 HarmonyOS SDK 24(API 24,对应 OpenHarmony 6.1.1),面临以下技术约束:

  • 不支持 .fill() 形状着色方法
  • 不支持 Color.fromArgb() 颜色构造
  • 不支持 .overlay() 直接传入组件
  • 不支持 CSS calc() 表达式
  • HTTP on 系列 API 已弃用但仍可用
  • 网络请求需显式申请 INTERNET 权限

2. 技术选型与架构设计

2.1 整体架构

AI 万能手册采用经典的分层架构,分为三层:

┌────────────────────────────────────────┐
│               UI 表现层                 │
│   Index.ets(页面组件 + 状态管理)        │
│   @Builder 气泡组件 / 加载指示器         │
├────────────────────────────────────────┤
│              业务逻辑层                  │
│   sendMessage() / scrollToBottom()      │
│   消息历史管理 / 流式响应更新             │
├────────────────────────────────────────┤
│              网络服务层                  │
│   AIChatService.ets                    │
│   queryAI() / cancelAI()               │
│   SSE 解析 / 非流式回退                 │
└────────────────────────────────────────┘

2.2 技术栈

层面 技术 版本
语言 ArkTS 5.0+ (API 24)
UI 框架 ArkUI 声明式 6.1.1
网络框架 @kit.NetworkKit (http) API 24
AI 模型 DeepSeek-V3 -
API 协议 OpenAI 兼容接口 REST + SSE
构建工具 Hvigor 5.x

2.3 设计原则

  1. 兼容优先:所有 API 调用都经过 API 24 验证
  2. 优雅降级:SSE 流式失败时自动回退到非流式解析
  3. 响应式 UI:使用 @State + .layoutWeight() 实现自适应
  4. 关注点分离:网络请求与 UI 渲染完全解耦

3. 页面布局与 UI 组件

3.1 整体布局结构

聊天界面采用经典的三段式结构:

┌──────────────────────────┐
│     顶部标题栏 (56vp)      │  ← Row + Text
├──────────────────────────┤
│                          │
│     消息列表 (自适应)      │  ← Scroll + Column
│                          │
│                          │
├──────────────────────────┤
│     底部输入区 (72vp)      │  ← Row + TextArea + Button
└──────────────────────────┘

3.2 顶部标题栏

使用 Row 容器 + 纯色背景实现:

Row() {
  Text('AI 万能手册')
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    .fontColor(Color.White)
}
.width('100%')
.height(56)
.backgroundColor('#2B8EFF')
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)

3.3 消息列表

采用 Scroll + Column 组合,支持无限滚动:

Scroll(this.scroller) {
  Column() {
    ForEach(this.messages, (msg, index) => {
      // 根据 role 渲染不同类型的气泡
    })
  }
}
.width('100%')
.layoutWeight(1)  // 自适应填充
.edgeEffect(EdgeEffect.Spring)  // 弹性滚动效果

3.4 用户消息气泡(右对齐蓝色)

用户消息采用右侧蓝色气泡设计,视觉上区分于 AI 消息:

Row() {
  Row().layoutWeight(1)     // 左侧弹性占位,将气泡推到右侧
  Text(content)
    .fontSize(16)
    .fontColor(Color.White)
    .backgroundColor('#2B8EFF')
    .borderRadius({
      topLeft: 16,
      topRight: 4,          // 右上角小圆角,模拟对话框尾巴
      bottomLeft: 16,
      bottomRight: 16
    })
    .padding({ left: 14, right: 14, top: 10, bottom: 10 })
}

3.5 AI 消息气泡(左对齐带头像)

AI 消息采用左侧白色气泡 + 圆形头像设计:

Row() {
  // AI 头像(Stack 组合实现,兼容 API 24)
  Stack() {
    Circle()
      .width(32)
      .height(32)
      .backgroundColor('#2B8EFF')  // API 24 兼容写法
    Text('AI')
      .fontSize(12)
      .fontColor(Color.White)
  }
  .width(32)
  .height(32)
  .margin({ right: 8 })

  Text(content)
    .fontSize(16)
    .fontColor('#333333')
    .backgroundColor(Color.White)
    .borderRadius({
      topLeft: 4,           // 左上角小圆角,模拟对话框尾巴
      topRight: 16,
      bottomLeft: 16,
      bottomRight: 16
    })
    // ...
}

API 24 要点:AI 头像使用 Stack(Circle + Text) 组合实现,而非 .overlay(Text(...))。后者在 API 24 中不被支持。

3.6 底部输入区

输入区包含 TextArea 输入框和 Button 发送按钮:

Row() {
  TextArea({
    placeholder: '输入你遇到的问题...',
    text: this.inputText
  })
    .width(0)
    .layoutWeight(1)
    .height(48)
    .fontSize(16)
    .placeholderColor('#999999')
    .backgroundColor(Color.White)
    .borderRadius(24)
    .padding({ left: 16, right: 16 })
    .onChange((value: string) => { this.inputText = value })

  Button() {
    Text('发送')
      .fontSize(16)
      .fontColor(Color.White)
  }
  .width(64)
  .height(48)
  .borderRadius(24)
  .backgroundColor(this.isLoading ? '#CCCCCC' : '#2B8EFF')
  .enabled(!this.isLoading && this.inputText.trim().length > 0)
  .onClick(() => { this.sendMessage() })
}

关键交互细节:

  • 加载时按钮变灰并禁用
  • 输入为空时按钮禁用
  • .layoutWeight(1) 让输入框自适应填充

3.7 加载指示器(Thinking 动画)

AI 思考时显示三个跳动圆点:

Row() {
  // AI 头像
  Stack() {
    Circle().width(32).height(32).backgroundColor('#2B8EFF')
    Text('AI').fontSize(12).fontColor(Color.White)
  }
  .width(32).height(32).margin({ right: 8 })

  // 三个跳动点
  Row() {
    ForEach([0, 1, 2], (idx) => {
      Circle()
        .width(8).height(8)
        .backgroundColor('#999999')
        .margin({ left: 3, right: 3 })
    })
  }
  .backgroundColor(Color.White)
  .borderRadius(16)
  .padding({ left: 12, right: 12, top: 8, bottom: 8 })
}

4. 网络层:AIChatService 详解

4.1 模块职责

AIChatService.ets 是应用的网络服务层,负责:

  • 构建 HTTP 请求和请求体
  • 发送 POST 请求到 AI API
  • 解析 SSE 流式响应
  • 提供非流式回退机制
  • 支持请求取消

4.2 API 配置

const API_URL = 'https://api-ai.gitcode.com/v1/chat/completions'
const API_KEY = "Bearer your_api_key_here"

安全提醒:实际部署时,API Key 不应硬编码在源码中,应通过配置文件或运行时注入。

4.3 消息与请求体结构

export interface ChatMessage {
  role: string    // 'system' | 'user' | 'assistant'
  content: string
}

export interface ChatCompletionRequest {
  model: string
  messages: ChatMessage[]
  stream: boolean
  max_tokens: number
  temperature: number
  top_p: number
  frequency_penalty: number
  thinking_budget: number
}

4.4 queryAI 函数核心流程

export function queryAI(callbacks: AICallbacks, messages: ChatMessage[]): void {
  // 1. 取消上一次未完成的请求
  cancelPreviousRequest()

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

  // 3. 构建完整消息列表(插入系统提示词)
  const fullMessages = [
    { role: 'system', content: SYSTEM_PROMPT },
    ...messages
  ]

  // 4. 注册 SSE 数据监听
  httpRequest.on('dataReceive', handleDataReceive)

  // 5. 注册完成监听
  httpRequest.on('dataEnd', handleDataEnd)

  // 6. 发起 POST 请求
  httpRequest.request(API_URL, { method: 'POST', ... }, handleResponse)
}

4.5 回调接口设计

export interface AICallbacks {
  onData: (text: string) => void   // 每个 token 到达
  onDone: () => void               // 响应结束
  onError: (errMsg: string) => void // 错误通知
}

采用回调而非 Promise 的原因:

  • 流式响应需要多次回调,Promise 只能 resolve 一次
  • 回调模式更贴近事件驱动的 SSE 模型
  • 不需要在 UI 层管理复杂的异步状态机

4.6 系统提示词设计

系统提示词是 AI 行为的核心控制器。本应用将其设计为万能手册助手

const SYSTEM_PROMPT =
  '你是一位全能的 AI 生活助手,能够帮助用户解决工作、学习、生活、情感、' +
  '健康、科技等各个领域的问题。\n\n' +
  '原则:\n' +
  '1. 全面覆盖:编程调试、文案写作、情感咨询、健康建议、日常知识\n' +
  '2. 实用优先:给出可落地的具体建议、步骤或代码\n' +
  '3. 结构清晰:复杂问题用分点、分段呈现\n' +
  '4. 语气友善:保持耐心和鼓励\n' +
  '5. 安全合规:不提供医疗诊断、法律意见等最终结论'

提示词设计的五个层次:

  1. 角色定义:告诉 AI"你是谁"
  2. 能力范围:列举可处理的问题类型
  3. 输出规范:约束回答的风格和结构
  4. 语气控制:设定情感基调
  5. 安全边界:防止 AI 越界提供危险建议

5. SSE 流式响应的实现原理

5.1 SSE 协议简介

SSE(Server-Sent Events)是一种服务器推送技术,允许服务器向客户端持续发送数据流。在 AI 对话场景中,SSE 实现了逐字输出的效果——用户无需等待完整响应,而是看到 AI 像真人一样"边想边写"。

标准 SSE 数据格式:

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

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

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

data: [DONE]

5.2 流式监听机制

在 HarmonyOS 的 HTTP 模块中,SSE 数据通过 on('dataReceive') 事件获取:

httpRequest.on('dataReceive', (data: ArrayBuffer) => {
  const text = arrayBufferToString(data)
  buffer += text

  // 按行拆解
  const lines = buffer.split('\n')
  buffer = lines.pop() ?? ''  // 保存不完整的最后一行

  for (const line of lines) {
    if (line.trim().startsWith('data:')) {
      if (line.trim() === 'data:[DONE]') {
        callbacks.onDone()
        continue
      }
      const content = parseSSEDataLine(line.trim())
      if (content) {
        callbacks.onData(content)
      }
    }
  }
})

5.3 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 && choices.length > 0) {
      const choice = choices[0]
      // 流式格式使用 delta
      if (choice.delta) return choice.delta.content
      // 非流式格式使用 message
      if (choice.message) return choice.message.content
    }
  } catch (_) { /* JSON 解析失败,跳过 */ }
  return null
}

5.4 非流式回退机制

针对部分 HarmonyOS 版本 HTTP 模块在 SSE 场景下不触发 dataReceive 的问题,实现了两级回退:

第一级:SSE 完整体解析 parseFullSSEBody()
  ↓(失败)
第二级:非流式 JSON 解析 parseNonStreamingBody()
  ↓(失败)
最终:返回原始 body 前 200 字符作为错误提示
if (!receivedAnyData && resp.result) {
  const bodyStr = typeof resp.result === 'string'
    ? resp.result
    : arrayBufferToString(resp.result)

  const sseContent = parseFullSSEBody(bodyStr)
  if (sseContent) {
    callbacks.onData(sseContent)
  } else {
    const jsonContent = parseNonStreamingBody(bodyStr)
    if (jsonContent) {
      callbacks.onData(jsonContent)
    } else {
      callbacks.onError('无法解析响应: ' + preview)
    }
  }
}

5.5 请求取消

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

请求取消的作用场景:

  • 用户快速连续发送消息时,自动取消上一次请求
  • 页面退出时清理网络资源
  • AI 回复太长时间,用户主动停止

6. 状态管理与数据流

6.1 状态变量设计

@State private messages: ChatMessage[] = []     // 消息列表
@State private inputText: string = ''            // 输入框文本
@State private isLoading: boolean = false         // 是否请求中
@State private currentResponse: string = ''       // 当前累积的响应
private scroller: Scroller = new Scroller()       // 滚动控制器

6.2 数据流图

用户输入 → inputText(@State)
   ↓
点击发送 → sendMessage()
   ↓
添加用户消息到 messages(@State) → UI 刷新
   ↓
调用 queryAI(callbacks, history)
   ↓
onData(chunk) → 累积 currentResponse → 更新 messages → UI 刷新 → 滚动
   ↓
onDone() → isLoading = false → 最终确认消息内容
   ↓
onError(err) → 添加错误消息 → isLoading = false

6.3 核心消息发送逻辑

sendMessage(): void {
  const text = this.inputText.trim()
  if (!text || this.isLoading) return

  // 1. 添加用户消息
  this.messages.push({ role: 'user', content: text })
  this.inputText = ''
  this.isLoading = true
  this.scrollToBottom()

  // 2. 构建历史消息(排除欢迎语)
  const history: ChatMessage[] = []
  for (let i = 1; i < this.messages.length; i++) {
    history.push({ role: this.messages[i].role, content: this.messages[i].content })
  }
  // 移除最后一条用户消息(API 会自动带上)
  history.pop()

  // 3. 发起 AI 请求
  let response = ''
  queryAI({
    onData: (chunk) => {
      response += chunk
      // 更新或插入 AI 消息
      const last = this.messages[this.messages.length - 1]
      if (last.role === 'user') {
        this.messages.push({ role: 'assistant', content: response })
      } else {
        this.messages[this.messages.length - 1] = { role: 'assistant', content: response }
      }
      this.scrollToBottom()
    },
    onDone: () => {
      this.isLoading = false
      this.scrollToBottom()
    },
    onError: (err) => {
      this.isLoading = false
      this.messages.push({ role: 'assistant', content: `⚠️ ${err}` })
    }
  }, history)
}

6.4 消息更新的两种路径

onData 回调中,需要根据最后一条消息的角色决定更新方式:

条件 操作 说明
最后一条是 user push 新 AI 消息 首次收到 token
最后一条是 assistant assign 替换 已有流式内容,追加

这种设计确保了流式响应的实时更新不会产生重复消息。

6.5 自动滚动

scrollToBottom(): void {
  setTimeout(() => {
    this.scroller.scrollEdge(Edge.Bottom)
  }, 100)  // 延迟 100ms 确保新内容已渲染
}

延迟 100ms 的原因:

  • @State 变更后需要等待一个帧周期
  • 确保新消息的布局计算已完成
  • Edge.Bottom 是 ArkTS 标准的滚动目标

7. API 24 兼容性攻关记录

7.1 问题全景

在 API 24 环境中,我们遇到了以下兼容性问题:

问题 尝试的解决方案 最终方案
.fill() 不可用 backgroundColor / Container 包裹 backgroundColor()
.overlay() 不支持组件 改用字符串 overlay Stack 嵌套
Color.fromArgb() 不存在 尝试 Color.fromRgba() 十六进制字符串 '#AARRGGBB'
Color.Purple 未定义 尝试其他命名颜色 '#800080' 字符串
calc() 无效 寻找替代 API 代码中手动计算
BUILD ERROR 信息不明确 逐段注释排查 对照编译日志定位

7.2 .fill() 问题

错误信息

Property 'fill' does not exist on type 'CircleAttribute'.

根因:API 24 中,CircleRect 等形状组件不支持 .fill() 方法,该方法在 API 26 才引入。

解决方案:使用 .backgroundColor() 替代:

// ❌ API 24 不允许
Circle().fill('#2B8EFF')

// ✅ API 24 兼容
Circle().backgroundColor('#2B8EFF')

原理

  • .fill() 是形状组件的填充着色方法
  • .backgroundColor() 是通用组件的背景色方法
  • 在视觉上两者效果一致,但底层实现不同

7.3 .overlay() 问题

错误信息

Unexpected token

(Rollup 无法解析,因为 .overlay(Text(...)) 不是合法语法)

根因:API 24 中 overlay 方法签名仅接受 stringCustomBuilder,不能直接传组件表达式。

解决方案:使用 Stack 嵌套替代 overlay:

// ❌ API 24 不允许
Circle().overlay(Text('AI').fontSize(12))

// ✅ API 24 兼容
Stack() {
  Circle().width(32).height(32).backgroundColor('#2B8EFF')
  Text('AI').fontSize(12).fontColor(Color.White)
}
.width(32).height(32)

7.4 Color.fromArgb() 问题

错误信息

Property 'fromArgb' does not exist on type 'typeof Color'.

根因:API 24 的 Color 类没有静态工厂方法。

解决方案:使用十六进制字符串:

// ❌ API 24 不允许
Color.fromArgb(255, 128, 0, 128)

// ✅ API 24 兼容
'#800080'   // 紫色
'#2B8EFF'   // 自定义蓝色
'#FF800080' // 带透明度

7.5 颜色使用对照表

目标颜色 API 24 写法
红色 Color.Red'#FF0000' 命名颜色可用
蓝色 Color.Blue'#0000FF' 命名颜色可用
紫色 '#800080' 无命名颜色
浅灰 Color.Gray'#A0A0A0' LightGray 不存在
粉色 Color.Pink'#FFC0CB' 命名颜色可用
橙色 Color.Orange'#FFA500' 命名颜色可用
棕色 Color.Brown'#A52A2A' 命名颜色可用
透明 Color.Transparent'#00000000' 命名颜色可用
自定义蓝 '#2B8EFF' 推荐字符串格式

7.6 HTTP API 弃用问题

警告信息

'on' has been deprecated

虽然 httpRequest.on() 在 API 24 中被标记为弃用,但没有替代方法。在 API 24 中,这是监听 SSE 流式数据的唯一手段。因此我们保留使用,待 SDK 升级后再迁移到新的 API。

7.7 INTERNET 权限配置

API 24 要求显式声明网络权限。在 module.json5 中添加:

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

如果缺少此权限,HTTP 请求会静默失败,且不触发任何回调。

7.8 ArkTS 特有语法约束

约束 说明 解决方案
无声明合并 不能重复声明相同名称的接口/类型 使用唯一名称如 Offset2D
import 必须在顶部 装饰器之前 将 import 放在文件开头
@BuilderParam 需初始化 必须有默认值 提供默认空 Builder
箭头函数不能作为组件 不能直接调用 this.child() 在 @Builder 中使用 this.child()

8. 错误处理与异常恢复

8.1 网络错误处理

// HTTP 请求失败
callbacks.onError(`请求失败: ${JSON.stringify(err)}`)

// HTTP 状态码非 200
callbacks.onError(`服务器返回错误 (${resp.responseCode}): ${errBody}`)

UI 层展示:

onError: (errMsg) => {
  this.isLoading = false
  this.messages.push({
    role: 'assistant',
    content: `⚠️ 出错了:${errMsg}`
  })
}

8.2 响应解析失败

三级解析 + 最终降级:

SSE 行解析 → 完整体 SSE 解析 → 非流式 JSON → 原始内容预览

8.3 空输入保护

if (!text || this.isLoading) return

8.4 双重提交防护

通过 isLoading 状态锁防止用户在请求进行中再次发送:

.enabled(!this.isLoading && this.inputText.trim().length > 0)

8.5 请求资源清理

每次新请求前取消旧请求,防止资源泄漏:

if (httpRequestTask) {
  try { httpRequestTask.destroy() } catch (_) {}
  httpRequestTask = null
}

9. 性能优化实践

9.1 布局优化

  1. 使用 .layoutWeight(1) 替代固定高度

    // 自适应填充剩余空间
    Scroll().width('100%').height(1).layoutWeight(1)
    
  2. 避免不必要的 @State 刷新

    将不需要触发 UI 刷新的变量用普通成员变量而非 @State 装饰

  3. 使用 key 优化 ForEach

    ForEach(this.messages, (msg, index) => {
      // 使用稳定的 index 作为 key
    }, (msg, index) => index.toString())
    

9.2 网络优化

  1. 连接超时控制

    connectTimeout: 30000  // 30s
    readTimeout: 120000    // 120s
    
  2. 请求取消机制

    防止无效请求占用网络资源

  3. SSE 缓冲优化

    使用缓冲区 + 分行处理,避免单次 dataReceive 处理过长字符串

9.3 内存优化

  1. 消息历史管理

    当前版本保留全部历史。大规模使用时,建议限制历史长度(如最近 50 条)

  2. 字符串累积优化

    流式响应使用 += 累加,在消息量大时建议使用数组 join() 方式

9.4 滚动性能

  1. 延迟滚动

    使用 setTimeout(100ms) 确保新内容渲染完成后再滚动

  2. 避免频繁 scrollToBottom

    仅在收到新 token 和请求完成时触发


10. 完整代码解读

10.1 文件结构

entry/src/main/ets/
├── pages/
│   ├── Index.ets          # 主页面(入口)
│   └── AIChatService.ets  # AI 聊天服务层
├── entryability/
│   └── EntryAbility.ets   # Ability 生命周期
└── entrybackupability/
    └── EntryBackupAbility.ets

10.2 Index.ets 主要符号表

符号 类型 作用
messages @State ChatMessage[] 消息列表
inputText @State string 输入框内容
isLoading @State boolean 请求状态
currentResponse @State string 当前累积响应
scroller Scroller 滚动控制器
sendMessage() 方法 发送消息
scrollToBottom() 方法 自动滚动
userBubble() @Builder 用户气泡
aiBubble() @Builder AI 气泡
loadingIndicator() @Builder 加载指示器

10.3 AIChatService.ets 主要符号表

符号 类型 作用
ChatMessage interface 消息结构
ChatCompletionRequest interface 请求体结构
AICallbacks interface 回调接口
queryAI() function 发起 AI 请求
cancelAI() function 取消请求
parseSSEDataLine() function 单行 SSE 解析
parseFullSSEBody() function 完整 SSE 解析
parseNonStreamingBody() function 非流式 JSON 解析

10.4 完整 Index.ets 源码(约 200 行)

import { queryAI, ChatMessage } from '../pages/AIChatService'

@Entry
@Component
struct Index {
  @State private messages: ChatMessage[] = []
  @State private inputText: string = ''
  @State private isLoading: boolean = false
  @State private currentResponse: string = ''
  private scroller: Scroller = new Scroller()

  aboutToAppear(): void {
    this.messages.push({
      role: 'assistant',
      content: '你好!我是 AI 万能手册助手 📖\n\n我可以帮你解决各种问题:\n' +
        '• 💻 编程与技术\n• ✍️ 文案与写作\n• 💬 情感与沟通\n' +
        '• 🏠 生活与家居\n• 📚 学习与教育\n• 💪 健康与运动\n\n' +
        '有什么我可以帮你的吗?'
    })
  }

  build() {
    Column() {
      // 顶部标题栏
      Row() {
        Text('AI 万能手册')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
      }
      .width('100%')
      .height(56)
      .backgroundColor('#2B8EFF')
      .padding({ left: 16, right: 16 })
      .alignItems(VerticalAlign.Center)

      // 消息列表
      Scroll(this.scroller) {
        Column() {
          ForEach(this.messages, (msg: ChatMessage, index: number) => {
            if (msg.role === 'user') {
              this.userBubble(msg.content)
            } else {
              this.aiBubble(msg.content, index)
            }
          }, (msg: ChatMessage, idx: number) => idx.toString())
          if (this.isLoading) { this.loadingIndicator() }
        }
        .width('100%')
        .padding({ left: 12, right: 12, top: 8, bottom: 8 })
      }
      .width('100%')
      .height(1)
      .layoutWeight(1)
      .edgeEffect(EdgeEffect.Spring)

      // 底部输入区
      Row() {
        TextArea({ placeholder: '输入你遇到的问题...', text: this.inputText })
          .width(0).layoutWeight(1).height(48)
          .fontSize(16).placeholderColor('#999999')
          .backgroundColor(Color.White).borderRadius(24)
          .padding({ left: 16, right: 16 })
          .onChange((v: string) => { this.inputText = v })

        Button() {
          Text('发送').fontSize(16).fontColor(Color.White)
        }
        .width(64).height(48).borderRadius(24)
        .backgroundColor(this.isLoading ? '#CCCCCC' : '#2B8EFF')
        .margin({ left: 8 })
        .enabled(!this.isLoading && this.inputText.trim().length > 0)
        .onClick(() => { this.sendMessage() })
      }
      .width('100%')
      .padding({ left: 12, right: 12, top: 8, bottom: 16 })
      .backgroundColor('#F5F5F5')
      .alignItems(VerticalAlign.Center)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F0F0')
  }

  @Builder
  userBubble(content: string) {
    Row() {
      Row().layoutWeight(1)
      Text(content)
        .fontSize(16).fontColor(Color.White)
        .backgroundColor('#2B8EFF')
        .borderRadius({ topLeft: 16, topRight: 4, bottomLeft: 16, bottomRight: 16 })
        .padding({ left: 14, right: 14, top: 10, bottom: 10 })
        .maxLines(100).lineHeight(24)
    }
    .width('100%').margin({ top: 6, bottom: 6 })
  }

  @Builder
  aiBubble(content: string, index: number) {
    Row() {
      Stack() {
        Circle().width(32).height(32).backgroundColor('#2B8EFF')
        Text('AI').fontSize(12).fontColor(Color.White)
      }
      .width(32).height(32).margin({ right: 8 })

      Text(content)
        .fontSize(16).fontColor('#333333')
        .backgroundColor(Color.White)
        .borderRadius({ topLeft: 4, topRight: 16, bottomLeft: 16, bottomRight: 16 })
        .padding({ left: 14, right: 14, top: 10, bottom: 10 })
        .maxLines(200).lineHeight(24)

      Row().layoutWeight(1)
    }
    .width('100%').margin({ top: 6, bottom: 6 })
    .alignItems(VerticalAlign.Top)
  }

  @Builder
  loadingIndicator() {
    Row() {
      Stack() {
        Circle().width(32).height(32).backgroundColor('#2B8EFF')
        Text('AI').fontSize(12).fontColor(Color.White)
      }
      .width(32).height(32).margin({ right: 8 })

      Row() {
        ForEach([0, 1, 2], (idx: number) => {
          Circle().width(8).height(8).backgroundColor('#999999')
            .margin({ left: 3, right: 3 })
        }, (idx: number) => idx.toString())
      }
      .backgroundColor(Color.White).borderRadius(16)
      .padding({ left: 12, right: 12, top: 8, bottom: 8 })
    }
    .width('100%').margin({ top: 6, bottom: 6 })
    .alignItems(VerticalAlign.Top)
  }

  sendMessage(): void {
    const text = this.inputText.trim()
    if (!text || this.isLoading) return
    this.messages.push({ role: 'user', content: text })
    this.inputText = ''
    this.isLoading = true
    this.scrollToBottom()

    const history: ChatMessage[] = []
    for (let i = 1; i < this.messages.length - 1; i++) {
      history.push({ role: this.messages[i].role, content: this.messages[i].content })
    }

    let response = ''
    queryAI({
      onData: (chunk) => {
        response += chunk
        const last = this.messages[this.messages.length - 1]
        if (last.role === 'user') {
          this.messages.push({ role: 'assistant', content: response })
        } else {
          this.messages[this.messages.length - 1] = { role: 'assistant', content: response }
        }
        this.scrollToBottom()
      },
      onDone: () => { this.isLoading = false; this.scrollToBottom() },
      onError: (err) => {
        this.isLoading = false
        this.messages.push({ role: 'assistant', content: `⚠️ ${err}` })
      }
    }, history)
  }

  scrollToBottom(): void {
    setTimeout(() => { this.scroller.scrollEdge(Edge.Bottom) }, 100)
  }
}

11. 部署与运行指南

11.1 环境要求

项目 要求
HarmonyOS SDK API 24(OpenHarmony 6.1.1)
DevEco Studio 5.0+
Node.js 18+
Hvigor 5.x
真机/模拟器 API 24 以上

11.2 快速开始

# 1. 克隆项目
cd D:\hongmeng\design20

# 2. 修改 API Key(必做)
# 编辑 AIChatService.ets,替换 API_KEY

# 3. 配置网络权限
# 确认 module.json5 中包含 ohos.permission.INTERNET

# 4. 编译构建
hvigorw assembleHap --mode module -p module=entry -p product=default

# 5. 安装到设备
hvigorw install --mode module -p module=entry -p product=default

11.3 常见构建错误

错误 原因 解决
Identifier already declared 重复声明 检查接口名称是否与 SDK 内置冲突
Unexpected token 语法不支持 检查 .overlay() 等 API 24 不支持的调用
Property does not exist API 版本不匹配 查阅 API 24 文档,使用替代方法
Failed to compile 编译错误 根据错误行号逐行排查

12. 总结与未来规划

12.1 项目亮点

  1. 完整的 AI 对话体验:从 UI 到网络层,实现了开箱即用的智能问答应用
  2. 流式响应:通过 SSE 协议实现逐字输出,交互体验流畅
  3. API 24 全面兼容:解决了形状着色、overlay 等多个兼容性问题
  4. 优雅降级:SSE 失败时自动回退到非流式解析
  5. 响应式布局:使用 .layoutWeight() 实现自适应填充

12.2 技术积累

通过本项目,我们沉淀了以下可复用的经验:

  • API 24 兼容性方案速查表
  • SSE 流式解析完整实现
  • 非流式回退机制设计
  • ArkTS 声明式 UI 最佳实践

12.3 未来规划

方向 具体计划 优先级
AI 能力 支持多模型切换(DeepSeek / GPT / 文心) ⭐⭐⭐
用户体验 语音输入、图片识别 ⭐⭐⭐
对话管理 对话历史存储、会话切换 ⭐⭐
个性化 自定义系统提示词 ⭐⭐
国际化 多语言支持
性能 虚拟列表、消息历史截断 ⭐⭐⭐

12.4 给 API 24 开发者的建议

  1. 开发前先检查 API 兼容性

    查阅 HarmonyOS SDK API Diff,确认目标 API 版本支持的方法

  2. 建立兼容性清单

    记录每个 API 版本不支持的接口和替代方案

  3. 使用字符串颜色格式

    '#AARRGGBB' 格式在所有 API 版本中通用

  4. 优先使用 Stack 替代 overlay

    Stack 嵌套比 .overlay() 更灵活,兼容性更好

  5. 编译日志是最好的老师

    HarmonyOS 的编译错误信息通常精确到行号,逐行排查效率最高


本文基于 HarmonyOS SDK 24(API 24)编写,对应 OpenHarmony 6.1.1。
文中所有代码均经过编译验证,可直接在 API 24 环境中运行。

项目源代码地址:D:\hongmeng\design20

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐