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

一、项目背景

1.1 为什么选择鸿蒙原生开发

随着华为鸿蒙操作系统(HarmonyOS)的快速发展,越来越多的开发者开始关注鸿蒙原生应用开发。HarmonyOS 作为面向全场景的分布式操作系统,不仅覆盖手机、平板、智能穿戴等设备,还提供了独特的 ArkTS 语言作为主要开发语言。ArkTS 基于 TypeScript 进行了扩展,保留了 TypeScript 的类型安全性和语法简洁性,同时融入了声明式 UI 编程范式,使得 UI 开发更加直观和高效。

本项目的诞生源于两个需求的交汇:一方面是对宇宙知识科普的需求日益增长,人们渴望用通俗易懂的方式了解天文学、宇宙学的奥秘;另一方面是探索鸿蒙原生应用开发的最佳实践,尤其是 ArkTS 语言在实际项目中的应用效果。于是,「宇宙知识AI问答」应运而生——一个基于 HarmonyOS 原生 ArkTS 开发的智能问答应用,集成了 AI 大模型能力,能够用专业且通俗的语言回答各种宇宙知识问题。

1.2 项目目标

本项目旨在打造一个面向普通用户的宇宙知识科普问答平台,具有以下核心目标:

  • 原生体验:充分利用 HarmonyOS 原生 API,提供流畅、稳定的用户体验。
  • 智能问答:集成大语言模型(DeepSeek-V3),实现高质量的宇宙知识问答。
  • 流式输出:采用 SSE(Server-Sent Events)流式传输技术,实现 AI 回复的逐字呈现,提升交互感。
  • 友好界面:采用 ArkTS 声明式 UI 架构,构建美观、直观的聊天界面。
  • 代码规范:遵循 ArkTS 最佳实践,展示模块化、可维护的代码结构。

1.3 技术选型

技术组件 选择 说明
开发语言 ArkTS 鸿蒙原生声明式 UI 语言
开发框架 HarmonyOS NEXT API 最新原生开发框架
AI 模型 DeepSeek-V3 高性能大语言模型
网络请求 @kit.NetworkKit 鸿蒙原生 HTTP 网络库
数据传输 SSE 流式传输 实时增量更新
UI 构建 @Builder 装饰器 组件化 UI 构建方案

二、项目结构全览

2.1 目录层次

本项目的目录结构遵循 HarmonyOS 标准工程规范,主要代码集中在 entry/src/main/ets/pages/ 目录下:

Demo0528/
├── entry/
│   └── src/
│       └── main/
│           └── ets/
│               └── pages/
│                   ├── Index.ets           # 主页面(聊天界面)
│                   ├── AIChatService.ets   # AI 服务层(网络请求+SSE解析)
│                   └── ColumnStartLayout.ets  # 其他布局组件
├── build-profile.json5      # 项目构建配置
├── hvigor/                  # 构建工具配置
├── AppScope/                # 应用级配置
└── oh-package.json5         # 包依赖管理

2.2 模块职责划分

项目采用了明确的职责分离架构,共分为三个核心层次:

UI 表现层(Index.ets):负责所有用户界面元素的构建和交互逻辑,包括聊天消息展示、输入框、按钮、加载动画等。通过 @Builder 装饰器将 UI 拆分为多个独立构建函数,每个函数负责一个独立的 UI 区域。

AI 服务层(AIChatService.ets):封装了所有与 AI API 通信的逻辑,包括 HTTP 请求的发起、SSE 流式数据的解析、非流式回退机制以及请求取消功能。该层对上层完全黑盒,仅通过定义清晰的接口(AICallbacksChatMessage)与 UI 层交互。

配置管理层(build-profile.json5、module.json5 等):管理网络权限、应用签名、模块配置等基础设施,确保应用能够正常访问网络接口。

这种分层设计使得代码具有良好的可测试性和可维护性。当需要更换 AI 模型提供商时,只需修改 AIChatService.ets 中的 API 配置和解析逻辑,UI 层完全无需改动。


三、ArkTS 声明式 UI 深入解析

3.1 声明式 UI 编程范式

ArkTS 采用的声明式 UI 编程范式,与传统的命令式 UI(如 Java Android 的 XML+Activity)有本质区别。在声明式 UI 中,开发者描述 UI “应该是什么样”,而非"如何一步步构建"。状态驱动视图更新——当 @State 装饰的变量发生变化时,框架自动重新渲染相关组件。

以本项目的聊天消息列表为例:

@State messageList: ChatMessage[] = [];
@State isLoading: boolean = false;
@State streamingContent: string = '';
@State inputText: string = '';
@State errorMsg: string = '';

build() {
    Column() {
        this.buildHeader();        // 标题栏
        Column() {
            this.buildChatArea()   // 聊天区
        }
        .layoutWeight(1);
        this.buildInputArea();     // 输入区
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#E8F0FF')
}

messageList 新增一条消息、isLoading 变为 truestreamingContent 收到新的 token 时,框架会自动触发 UI 重新渲染,开发者无需手动操作 DOM。

3.2 @Builder 组件化构建

ArkTS 的 @Builder 装饰器是实现组件化 UI 的核心工具。它允许将 UI 片段的构建逻辑封装为独立函数,类似其他框架中的"组件"概念。本项目共使用了 7 个 @Builder 函数:

@Builder 函数 职责 渲染条件
buildHeader() 顶部标题栏(标题+副标题) 始终显示
buildChatArea() 聊天消息列表容器 始终显示
buildWelcomeMessage() 欢迎界面(初始状态) messageList.length === 0
buildUserBubble(msg) 用户消息气泡 每条 role === ‘user’ 的消息
buildAIBubble(msg) AI 消息气泡 每条 role === ‘assistant’ 的消息
buildLoadingIndicator() AI 正在回复的加载动画 isLoading && !streamingContent
buildErrorBubble(err) 错误提示气泡 errorMsg 不为空
buildInputArea() 底部输入区域 始终显示

这种拆分带来了以下好处:

  • 关注点分离:每个 Builder 只负责一个 UI 区域的构建逻辑,代码清晰可读。
  • 条件渲染:通过 if 条件判断,灵活控制每个 Builder 的显示/隐藏。
  • 状态驱动:Builder 内部直接引用 @State 变量,状态变化时自动更新。
  • 复用性:同一个 Builder(如 buildAIBubble)可以在多个地方调用,如展示完整回复和流式中间状态。

3.3 聊天消息区的渲染策略

聊天消息区是整个应用最复杂的 UI 区域,需要同时处理多种状态:

@Builder
buildChatArea() {
    Scroll() {
        Column() {
            // 第一阶段:空状态 → 显示欢迎界面
            if (this.messageList.length === 0) {
                this.buildWelcomeMessage();
            }

            // 第二阶段:历史消息 → 逐条渲染气泡
            ForEach(this.messageList, (msg: ChatMessage) => {
                if (msg.role === 'user') {
                    this.buildUserBubble(msg.content);
                } else {
                    this.buildAIBubble(msg.content);
                }
            }, (msg: ChatMessage) => msg.role + '#' + msg.content)

            // 第三阶段:流式输出中 → 显示实时内容
            if (this.isLoading && this.streamingContent) {
                this.buildAIBubble(this.streamingContent + ' ▍');
            }

            // 第四阶段:等待中 → 显示加载动画
            if (this.isLoading && !this.streamingContent) {
                this.buildLoadingIndicator();
            }

            // 第五阶段:出错 → 显示错误提示
            if (this.errorMsg) {
                this.buildErrorBubble(this.errorMsg);
            }

            Blank().height(12)
        }
    }
}

这种渲染策略确保了所有 UI 状态都有对应的呈现方式,不会出现空白屏幕或状态残留。


四、界面设计与交互体验

4.1 视觉风格

应用采用深蓝星空主题色系,营造出宇宙探索的氛围感:

UI 元素 颜色值 色系说明
页面背景 #E8F0FF 淡蓝星空
主色调 #3B6CE8 深邃蓝
强调色 #2B5CC8 夜空蓝
辅助色 #7BA0F0 星辰蓝
边框色 #C5D8F8 淡蓝辉光
按钮底色 #EFF4FF 极浅蓝
文字深色 #3D2B1F 深棕(阅读舒适)

4.2 标题栏设计

标题栏采用纯白背景,中央展示应用名称和副标题:

┌─────────────────────┐
│    🌌 宇宙知识AI问答    │  ← 22sp 加粗
│   探索星辰大海,解答宇宙奥秘  │  ← 13sp 辅助文字
└─────────────────────┘

标题使用 Emoji 🌌 作为图标替代,避免了加载图片资源的开销,同时视觉效果鲜明。副标题的提示文字采用辅助色 #7BA0F0,与主标题形成层次感。

4.3 欢迎界面设计

当用户首次打开应用时,聊天区域显示精心设计的欢迎界面,包含引导文案和示例问题按钮:

         🌍                ← 48sp 大号 Emoji
        
    你对宇宙的哪方面感到好奇?  ← 引导文案
    
    输入你的问题,我来用科学、
    通俗的方式为你解答 🌟   ← 副引导
    
    试试这些问题 👇
    
    ┌────────────────────────┐
    │ 宇宙的年龄有多大?       │  ← 示例问题按钮1
    └────────────────────────┘
    ┌────────────────────────┐
    │ 黑洞里面是什么?        │  ← 示例问题按钮2
    └────────────────────────┘
    ┌────────────────────────┐
    │ 人类能移民火星吗?      │  ← 示例问题按钮3
    └────────────────────────┘
    
          🎲 换一批话题       ← 随机轮换按钮

欢迎界面的设计理念是"零门槛入门"——用户不需要学习如何使用,点击示例按钮或直接输入即可开始对话。这是良好用户体验的典型实践。

4.4 示例问题轮换机制

示例问题共 6 个,覆盖了宇宙知识的多个维度:

  1. “宇宙的年龄有多大?”——宇宙学基础
  2. “黑洞里面是什么?”——天体物理热门
  3. “人类能移民火星吗?需要多久?”——航天探索
  4. “暗物质和暗能量到底是什么?”——前沿理论
  5. “宇宙大爆炸之前发生了什么?”——哲学+科学
  6. “太阳系外有适合居住的星球吗?”——系外行星探索

"换一批话题"按钮通过 rotateSamples() 方法实现问题的随机排列:

rotateSamples(): void {
    const arr: string[] = this.sampleQuestions;
    for (let i: number = 0; i < arr.length; i++) {
        const j: number = (i * 7 + 3) % arr.length;
        const tmp: string = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    this.sampleQuestions = [...arr];
}

这是一种简单的伪随机洗牌算法,避免了复杂数学运算,在 ArkTS 中高效执行。使用 [...arr] 展开运算符创建新数组,触发 ArkTS 的状态更新检测。

4.5 对话气泡设计

用户消息和 AI 回复采用不同的气泡样式,通过视觉差异帮助用户快速区分消息来源:

用户气泡:深蓝底色、白色文字、右侧对齐、右上角小圆角("尾巴"在右上)。

Text(content)
    .backgroundColor('#3B6CE8')
    .borderRadius({ topLeft: 18, topRight: 4, bottomLeft: 18, bottomRight: 18 })

AI 气泡:白色底色、深色文字、左侧对齐、左上角小圆角("尾巴"在左上)、蓝色边框。

Text(content)
    .backgroundColor('#FFFFFF')
    .border({ width: 1, color: '#C5D8F8' })
    .borderRadius({ topLeft: 4, topRight: 18, bottomLeft: 18, bottomRight: 18 })

气泡的圆角设计模仿了即时通讯软件的常见样式,topRight: 4(用户)和 topLeft: 4(AI)营造出聊天气泡的"尾巴"效果,使对话界面更加自然。

4.6 底部输入区域

底部输入区固定在屏幕最下方,包含文本输入框和发送按钮:

┌──────────────────────────────────────┐
│  ┌────────────────────┐ ┌────────┐   │
│  │ 输入你想了解的...    │ │  发送  │   │
│  └────────────────────┘ └────────┘   │
│  (加载时显示:⏹ 取消回复)            │
└──────────────────────────────────────┘

输入框采用圆角胶囊样式(borderRadius: 22),高度 44vp,适合单手操作。发送按钮在输入框为空或 AI 正在回复时自动禁用(enabled 属性),这是一种常见的防误触设计。

支持键盘回车提交(onSubmit 回调),点击和回车两种方式均可发送消息。

4.7 加载状态与交互反馈

当 AI 正在回复时,应用通过三种方式提供反馈:

  1. 加载动画:在 AI 尚未返回任何内容时,显示 “🚀 正在探索宇宙…” 的加载气泡。
  2. 流式输出:当 AI 开始返回数据时,实时显示正在生成的文字,末尾带 光标符号模拟打字效果。
  3. 取消按钮:底部输入区域出现 “⏹ 取消回复” 按钮,用户可以随时中断 AI 回复。

取消操作的处理逻辑也很细致:

handleCancel(): void {
    cancelAI();
    this.isLoading = false;
    // 保留已收到的流式内容作为 AI 回复
    if (this.streamingContent) {
        const aiMsg: ChatMessage = { role: 'assistant', content: this.streamingContent };
        this.messageList = [...this.messageList, aiMsg];
    }
    this.streamingContent = '';
}

即使取消,之前收到的部分 AI 回复也不会丢失,而是作为完整的 AI 消息保存到聊天历史中——这体现了良好的用户体验设计。


五、AI 服务层深度剖析

5.1 架构设计

AIChatService.ets 是应用的核心服务层,负责与 AI API 的通信。其设计遵循以下原则:

  • 单一职责:只负责网络请求和数据解析,不涉及任何 UI 逻辑。
  • 接口隔离:通过 AICallbacks 接口定义与 UI 层的通信契约。
  • 防御性编程:多种回退机制确保在各种网络环境下都能工作。
  • 可取消性:支持运行时取消请求,避免资源浪费。

5.2 类型定义

export interface ChatMessage {
    role: string;
    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;
}

export interface AICallbacks {
    onData: (text: string) => void;
    onDone: () => void;
    onError: (errMsg: string) => void;
}

ChatMessage 采用与 OpenAI API 兼容的消息格式,role 可以是 systemuserassistantAICallbacks 定义了三个回调,覆盖了 AI 交互的全部生命周期:数据到达 → 完成 → 出错。

5.3 系统提示词设计

系统提示词(System Prompt)是决定 AI 回答质量的关键因素。本项目精心设计了提示词,确保 AI 的回答既专业又易懂:

const SYSTEM_PROMPT: string =
    '你是一位资深的宇宙知识专家,擅长解答关于天文学、宇宙学、天体物理等领域的科学问题。\n\n' +
    '以下是你要遵循的原则:\n' +
    '1. 严谨准确:基于公认的科学理论和观测数据回答问题,不传播伪科学或未经证实的假说。\n' +
    '2. 通俗易懂:用简洁明了的语言解释复杂概念,避免过度使用专业术语而不加说明。\n' +
    '3. 深入浅出:在保证准确性的前提下,适当使用类比帮助非专业用户理解。\n' +
    '4. 鼓励探索:激发用户对宇宙的好奇心,推荐进一步阅读的方向或相关话题。\n' +
    '5. 区分确定与不确定:对于前沿理论或尚存争议的话题,明确说明当前科学界的共识程度。\n\n' +
    '请用中文回复,保持专业且友好。';

五个原则分别对应准确度、可理解性、教学效果、用户粘性和诚实度——这些是科普类 AI 助手的核心品质。

5.4 DeepSeek-V3 模型配置

请求体配置了 DeepSeek-V3 模型的各项参数:

参数 作用
model deepseek-ai/DeepSeek-V3 指定模型
stream true 启用流式输出
max_tokens 2048 最大回复长度
temperature 0.6 创造性/确定性平衡
top_p 0.95 采样范围
frequency_penalty 0 频率惩罚
thinking_budget 2048 思考预算

temperature: 0.6 是一个经过调优的值——相比默认的 0.7 稍微降低了一些随机性,使得科学类回答更加稳定可靠。

5.5 SSE 流式传输实现

SSE(Server-Sent Events)是一种基于 HTTP 的实时数据传输协议。AI 模型逐个 token 生成回复内容,通过 SSE 实时推送给客户端,实现"逐字输出"的效果。

数据接收流程

HTTP POST → 建立连接 → data: {"choices":[{"delta":{"content":"宇宙"}}]}
                      → data: {"choices":[{"delta":{"content":"的"}}]}
                      → data: {"choices":[{"delta":{"content":"年龄"}}]}
                      → data: {"choices":[{"delta":{"content":"约"}}]}
                      → data: [DONE](结束标记)
                      → 连接关闭

ArkTS 实现

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]') {
            isDone = true;
            callbacks.onDone();
            continue;
        }

        const content = parseSSEDataLine(trimmed);
        if (content) {
            callbacks.onData(content);
        }
    }
});

这里有一个关键细节:buffer = lines.pop() ?? ''。由于网络传输是分片进行的,最后一行可能不完整(只收到了半个 JSON),所以需要将其保存到下次 dataReceive 事件再处理。这是 SSE 解析的标准实践。

5.6 双模式回退机制

HarmonyOS 不同版本和不同设备上的 http 模块行为存在差异。某些版本在 SSE 场景下不会触发 dataReceive 事件。为了解决这个兼容性问题,本项目实现了双模式回退机制:

模式一(流式)dataReceive 事件正常触发,逐个 token 解析并回调 onData

模式二(非流式回退):如果 dataReceive 从未被触发(receivedAnyData 仍为 false),从 request 回调的完整响应体中解析结果:

if (!receivedAnyData && resp.result) {
    // 先尝试按 SSE 格式解析(多个 data: 行)
    const sseContent = parseFullSSEBody(bodyStr);
    if (sseContent) {
        callbacks.onData(sseContent);
    } else {
        // 再尝试非流式 JSON 格式
        const jsonContent = parseNonStreamingBody(bodyStr);
        if (jsonContent) {
            callbacks.onData(jsonContent);
        } else {
            // 解析失败,返回错误提示
            callbacks.onError(`无法解析响应: ${preview}`);
        }
    }
    callbacks.onDone();
}

这种设计确保了应用在不同 HarmonyOS 版本上都能正常工作——不支持 SSE 的设备也能获得完整的 AI 回复,只是缺少逐字输出的视觉效果。

5.7 SSE 数据行解析

parseSSEDataLine 函数负责从单行 SSE 数据中提取 content:

function parseSSEDataLine(line: string): string | null {
    const jsonStr = line.slice(5).trim();
    if (!jsonStr) return null;
    try {
        const parsed = JSON.parse(jsonStr);
        const choices = parsed.choices;
        if (choices && choices.length > 0) {
            const choice = choices[0];
            // 兼容 delta 和 message 两种格式
            const delta = choice.delta;
            if (delta) return delta.content;
            const message = choice.message;
            if (message) return message.content;
        }
    } catch (_) { /* JSON 解析失败,跳过 */ }
    return null;
}

注意 line.slice(5) 的作用是去掉 SSE 行开头的 “data:” 前缀(5 个字符)。同时兼容了 delta(流式格式)和 message(非流式格式)两种结构,增加了鲁棒性。

5.8 取消请求机制

为了支持用户随时中断 AI 回复,代码维护了一个全局的 HTTP 请求引用:

let httpRequestTask: http.HttpRequest | null = null;

export function queryAI(callbacks, messages) {
    // 先取消上一次请求
    if (httpRequestTask) {
        httpRequestTask.destroy();
        httpRequestTask = null;
    }
    
    const httpRequest = http.createHttp();
    httpRequestTask = httpRequest;
    // ... 发起请求 ...
}

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

每次发起新请求前先销毁旧请求,防止多次请求的资源泄露。cancelAI() 由 UI 层的取消按钮触发。

5.9 ArrayBuffer 解码

由于 http 模块的 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;
}

这个函数将二进制数据逐个字节转换为字符。对于 UTF-8 编码内容,需要注意中文等多字节字符可能跨越两个分片,这也是 SSE 解析时需要保留不完整行的原因之一。


六、网络层配置

6.1 网络权限

在 HarmonyOS 中,应用访问网络需要在 module.json5build-profile.json5 中声明网络权限。本项目需要访问 api-ai.gitcode.com 域名,配置如下:

{
  "deviceTypes": ["phone", "tablet"],
  "permissions": [
    "ohos.permission.INTERNET"
  ]
}

6.2 API 鉴权

AI API 通过 Bearer Token 进行鉴权:

header: {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'text/event-stream',
}

Accept: text/event-stream 是告知服务器我们期望 SSE 格式的响应,这是 HTTP 协议中的内容协商机制。


七、状态管理与数据流

7.1 状态变量概览

项目使用了 5 个核心状态变量:

变量 类型 初始值 用途
messageList ChatMessage[] [] 聊天历史消息
inputText string '' 输入框内容
isLoading boolean false AI 是否正在回复
streamingContent string '' 流式累积内容
errorMsg string '' 错误提示

7.2 数据流时序

一次完整的"用户提问→AI 回复"流程如下:

用户点击"发送"
  ↓
handleSend() 被调用
  ↓
① inputText = ''          // 清空输入框
② messageList += 用户消息  // 立即显示用户消息
③ streamingContent = ''    // 重置流式内容
④ errorMsg = ''            // 清除旧错误
⑤ isLoading = true         // 开启加载状态
  ↓
queryAI() 被调用
  ↓
[网络请求阶段]
  ↓
dataReceive 触发 → onData(text) → streamingContent += text
  ↓ (重复多个 token)
dataEnd 触发 → onDone()
  ↓
⑥ messageList += AI 回复   // 完整回复加入历史
⑦ streamingContent = ''    // 清空流式缓冲区
⑧ isLoading = false        // 关闭加载状态

在整个过程中,ArkTS 的声明式 UI 框架自动处理了视图更新:

  • 步骤②:用户消息气泡自动出现
  • 步骤⑤:加载动画自动显示
  • 步骤⑥→⑧:AI 消息气泡自动替换加载状态

7.3 状态更新最佳实践

ArkTS 的状态更新遵循"不可变数据"原则。当需要修改数组时,必须创建新的数组引用:

// ✅ 正确:创建新数组,触发 UI 更新
this.messageList = [...this.messageList, userMsg];

// ❌ 错误:直接修改数组,不会触发 UI 更新
this.messageList.push(userMsg);

字符串和基本类型则直接赋值即可触发更新,因为 ArkTS 会检测 @State 变量的 setter 调用。


八、错误处理体系

8.1 错误类型与处理策略

应用覆盖了三种主要的错误场景:

错误场景 检测方式 用户提示
网络连接失败 err 回调参数非空 “请求失败: {错误详情}”
服务器返回错误 resp.responseCode !== 200 “服务器返回错误 (4xx/5xx): 详情”
数据解析失败 parseSSEDataLine 返回 null “无法解析响应: 原始数据…”

8.2 错误提示气泡

错误信息通过 buildErrorBubble 展示为红色的聊天气泡:

@Builder
buildErrorBubble(errMsg: string) {
    Column() {
        Row() {
            Text('⚠️ ' + errMsg)
                .fontSize(13)
                .fontColor('#C94A4A')
                .padding(12)
                .backgroundColor('#FFF0F0')
                .border({ width: 1, color: '#F5C0C0' })
                .borderRadius(12)
            Blank()
        }
    }
}

红色系视觉(#C94A4A 文字 + #FFF0F0 背景)符合常见的错误提示约定,用户一眼就能识别。

8.3 异常保护

在多个关键函数中使用 try-catch 包裹可能抛出异常的操作:

// 销毁请求时忽略异常
try {
    httpRequestTask.destroy();
} catch (_) { /* ignore */ }

// JSON 解析失败时静默跳过
try {
    const parsed = JSON.parse(jsonStr);
    // ...
} catch (_) { /* 不是合法 JSON,跳过 */ }

这种防御性编程确保了应用的稳定性,不会因为某次数据格式异常而崩溃。


九、总结与展望

9.1 项目成果

「宇宙知识AI问答」作为一个鸿蒙原生 ArkTS 应用,成功实现了以下功能:

  • 完整的 AI 聊天界面:包括消息展示、输入交互、加载状态、错误处理等。
  • 流式 AI 回复:基于 SSE 协议实现逐字输出,提升用户体验。
  • 双模式回退:兼容不同 HarmonyOS 版本上的网络行为差异。
  • 干净的架构:UI 层与服务层完全分离,代码可维护性高。
  • 美学的 UI:深蓝星空主题配色,统一的设计语言。

9.2 技术亮点总结

亮点 说明
声明式 UI 利用 ArkTS @State + @Builder 实现状态驱动视图更新
SSE 流式 实现逐 token 解析,支持实时显示 AI 回复
回退机制 流式/非流式双模式兼容,确保稳路运行
组件化 7个 Builder 函数覆盖所有 UI 区域,关注点分离
防御编程 try-catch 包裹异常操作,应用不崩溃
响应式设计 提供/禁用各交互元素,防止用户在加载中误操作

9.3 未来改进方向

功能扩展

  • 支持多轮上下文记忆的深度对话
  • 添加语音输入/输出支持
  • 支持图片识别(如星座图、星系照片)

性能优化

  • 消息列表虚拟滚动(大量消息时优化性能)
  • 请求队列管理(多并发请求场景)
  • 本地缓存(历史对话持久化)

用户体验

  • 深色模式支持
  • 字体大小调节
  • 消息复制/分享功能
  • 回答内容中的 Markdown 渲染(数学公式、列表等)

平台扩展

  • 适配折叠屏设备
  • 支持平板横竖屏布局自适应
  • 鸿蒙超级终端联动

9.4 开发反思

在项目开发过程中,遇到了几个值得记录的技术挑战:

SSE 兼容性:HarmonyOS 不同版本的 http 模块对 SSE 的支持程度不同,这是最大的技术风险。通过双模式回退机制解决了这个问题。

ArkTS 数组更新:由于 ArkTS 采用引用比较检测状态变化,直接修改数组元素不会触发 UI 更新。必须创建新数组。

类型转换ArrayBuffer 到字符串的转换在 ArkTS 中需要手动实现,通过 String.fromCharCode 逐字节转换,注意多字节字符的边界情况。

取消请求httpRequest.destroy() 在某些情况下可能抛出异常,需要 try-catch 保护。


十、结语

「宇宙知识AI问答」不仅是一个功能完整的 AI 问答应用,更是一个展示鸿蒙原生 ArkTS 开发最佳实践的示例项目。从声明式 UI 的优雅表达,到 SSE 流式传输的实时交互,再到双模式回退的兼容性保障,每个技术决策都体现了对用户体验和代码质量的追求。

宇宙浩瀚,知识无垠。这个应用以科技的视角探索宇宙的奥秘,希望每一位使用者都能在问答中发现宇宙的壮美与科学的魅力。如果你对鸿蒙开发感兴趣,或者对宇宙知识有更多好奇,欢迎 fork 本项目,在探索代码与星辰大海的道路上一起前行。

项目地址:查看项目根目录源码,运行 hvigorw assembleHap --mode module 即可编译体验。


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐