鸿蒙开发:实现AI打字机效果

简介: 具体的效果,根据业务情况而定,有两种模式,一种主动的流式输出,也就是数据以流式的形式进行返回,前端直接用组件加载即可,第二种就是刻意的流式展示,也就是在拿到数据之后,前端实现流式输出,进行打字机展示。

前言


代码案例基于Api13。

目前哪个行业最火,非AI莫属,deepseek发布之后,可以说,又把AI推上了一个新高度,在和AI进行询问会话的时候,我们可以发现,AI的回答都是以流式的效果进行展示的,也就是类似于打字机的效果,那么针对这种效果在实际的开发中是如何实现的呢?


具体的效果,根据业务情况而定,有两种模式,一种主动的流式输出,也就是数据以流式的形式进行返回,前端直接用组件加载即可,第二种就是刻意的流式展示,也就是在拿到数据之后,前端实现流式输出,进行打字机展示。


打字机的效果,一般都是在会话聊天之中,也就是列表之中,在实际的开发中,还要兼顾到,流式输出的数据加载是否会影响性能,页面闪烁,最新的聊天信息可展示等问题。


主动的流式输出


在一般的AI会话中,实现一个流式输出,一般会采用SSE或者WebSocket协议,像OpenAI官网,DeepSeek官网是采用SSE协议,当然,在实际的开发中,大家可以选择自己适用的技术即可。客户端发送问题之后,服务端检索到内容,就会时时的返回内容,具体是返回连接式的内容,还是逐字返回,需要和服务端进行定义。连接式返回,客户端只管加载,逐字返回,需要客户端拼接。


@Entry
@Component
struct Index {
  @State message?: string = ""
  intervalID?: number
  /**
   *AUTHOR:AbnerMing
   *INTRODUCE:模拟请求网络接口
   */
  doHttp(success: (message: string) => void) {
    let data = "具体的实现效果,根据业务情况而定,有两种模式,一种主动的流式输出,也就是数据以流式的形式进行返回,前端直接用组件加载即可,第二种就是刻意的流式展示,也就是在拿到数据之后,前端实现流式输出,进行打字机展示。"
    let position: number = 0
    //模拟请求流式输出
    this.intervalID = setInterval(() => {
      position = position + 2
      let message = data.substring(0, position)
      if (success != undefined) {
        success(message)
      }
      if (message.length >= data.length) {
        clearInterval(this.intervalID)
      }
    }, 100)
  }
  build() {
    Column() {
      Button("加载")
        .margin({ top: 10 })
        .onClick(() => {
          this.doHttp((message: string) => {
            this.message = message
          })
        })
      Text(this.message)
        .margin({ top: 20 })
        .width("100%")
    }.width("100%")
    .height("100%")
    .padding(10)
  }
}


我们可以看下演示的效果,实际的开发中,前端无须关注每次的返回字的长度。


0001.gif

被动的流式展示


所谓的被动,就是在已有数据的情况下,如何实现打字机的效果,这个比较的简单,无非是开启一个定时,以每隔多少时间,输出多少字为主,时间和输出字的长度都可以自己调节,简单案例如下,当然了,这种方式一般很少应用于实际的开发,不过在客户端有类似打字机效果的情况下可以使用。


@Entry
@Component
struct Index {
  @State message?: string = ""
  intervalID?: number
  build() {
    Column() {
      Button("加载")
        .margin({ top: 10 })
        .onClick(() => {
          this.start()
        })
      Text(this.message)
        .margin({ top: 20 })
        .width("100%")
    }.width("100%")
    .height("100%")
    .padding(10)
  }
  private start() {
    let data = "具体的实现效果,根据业务情况而定,有两种模式,一种主动的流式输出,也就是数据以流式的形式进行返回,前端直接用组件加载即可,第二种就是刻意的流式展示,也就是在拿到数据之后,前端实现流式输出,进行打字机展示。"
    let position: number = 0
    this.intervalID = setInterval(() => {
      position = position + 2
      this.message = data.substring(0, position)
      if (this.message.length >= data.length) {
        clearInterval(this.intervalID)
      }
      console.log("======定时")
    }, 100)
  }
}


我们看下输出效果,是不是有那种打字机的效果了,需要注意的是,定时关闭。


00002.gif

列表打印机效果


以上的效果都是以一个Text组件展示的情况,在实际的开发中,更多的是以左右会话形式,这时需要考虑的就多一点,比如会话定位在底部,流式展示时,不让列表闪烁等等问题,那么都是需要考虑的。


下面简单的实现一下聊天会话模式,所有的数据都是模拟的,UI简单的绘制了一下,在实际的开发中,肯定比较精细。


import { KeyboardAvoidMode } from '@kit.ArkUI'
@Observed
export class MessageBean {
  leftMessage?: string
  rightMessage?: string
}
@Component
struct ChatView {
  @ObjectLink messageBean: MessageBean;
  build() {
    Column() {
      if (this.messageBean.rightMessage != undefined) {
        Row() {
          Text(this.messageBean.rightMessage)
            .margin({ right: 10 })
          Image($r("app.media.startIcon"))
            .id("user_logo")
            .border({ color: Color.Red, width: 1, radius: 20 })
            .width(20)
            .height(20)
        }.width("100%")
        .justifyContent(FlexAlign.End)
        .padding({ right: 10 })
      } else {
        Row() {
          Image($r("app.media.startIcon"))
            .border({ color: Color.Red, width: 1, radius: 20 })
            .width(20)
            .height(20)
          Text(this.messageBean.leftMessage)
            .margin({ left: 10 })
        }.alignItems(VerticalAlign.Top)
        .padding({ right: 20 })
      }
    }
  }
}
@Entry
@Component
struct Index {
  private scroller: Scroller = new Scroller()
  @State sendMessage: string = ""
  @State messageList: MessageBean[] = []
  intervalID?: number
  private isEnd: boolean = true
  aboutToAppear(): void {
    this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE)
  }
  /**
   *AUTHOR:AbnerMing
   *INTRODUCE:模拟请求网络接口
   */
  doHttp(success: (message: string) => void) {
    let data = "具体的实现效果,根据业务情况而定,有两种模式,一种主动的流式输出,也就是数据以流式的形式进行返回,前端直接用组件加载即可,第二种就是刻意的流式展示,也就是在拿到数据之后,前端实现流式输出,进行打字机展示。"
    let position: number = 0
    //模拟请求流式输出
    this.intervalID = setInterval(() => {
      position = position + 2
      let message = data.substring(0, position)
      if (success != undefined) {
        success(message)
      }
      if (message.length >= data.length) {
        this.isEnd = true //模拟结束
        clearInterval(this.intervalID)
      }
    }, 100)
  }
  build() {
    RelativeContainer() {
      List({ space: 10, scroller: this.scroller }) {
        ForEach(this.messageList, (item: MessageBean) => {
          ListItem() {
            ChatView({ messageBean: item })
          }
        })
      }.margin({ top: 10 })
      .alignRules({
        top: {
          anchor: "__container__",
          align: VerticalAlign.Top
        },
        bottom: {
          anchor: "layout_send",
          align: VerticalAlign.Top
        }
      })
      RelativeContainer() {
        TextInput({ placeholder: "请输入问题" })
          .onChange((text) => {
            this.sendMessage = text
          })
          .alignRules({
            left: {
              anchor: "__container__",
              align: HorizontalAlign.Start
            },
            right: {
              anchor: "btn_send",
              align: HorizontalAlign.Start
            }
          })
        Button("发送")
          .id("btn_send")
          .margin({ left: 10 })
          .alignRules({
            right: {
              anchor: "__container__",
              align: HorizontalAlign.End
            }
          }).onClick(() => {
          //发送
          if (this.isEnd) {
            this.isEnd = false
            let bean = new MessageBean()
            bean.rightMessage = this.sendMessage
            this.messageList.push(bean)
            this.scroller.scrollEdge(Edge.Bottom)
            //模拟接口返回数据
            let leftBean = new MessageBean()
            this.messageList.push(leftBean)
            this.doHttp((content) => {
              leftBean.leftMessage = content
            })
          }
        })
      }
      .height(40)
      .id("layout_send")
      .padding({ left: 10, right: 10 })
      .backgroundColor(Color.White)
      .alignRules({
        bottom: {
          anchor: "__container__",
          align: VerticalAlign.Bottom
        }
      })
    }.width("100%")
    .height("100%")
  }
}


运行之后,我们简单测试一下:


00003.gif


一般会话列表的形式,有一点需要注意,那就是历史记录。


相关总结


需要注意的是,内容一般都是以markdown的形式输出,也就是真实的数据中,内容都是有样式的,比如加粗,图片,表格等等,所以,不能以单一的Text组件进行展示,需要针对markdown文本适配。


打字机的效果,更多的是在服务端的数据输出,客户端,最主要的是针对数据的渲染。


相关文章
|
7天前
|
人工智能 开发框架 决策智能
谷歌开源多智能体开发框架 Agent Development Kit:百行代码构建复杂AI代理,覆盖整个开发周期!
谷歌开源的Agent Development Kit(ADK)是首个代码优先的Python工具包,通过多智能体架构和灵活编排系统,支持开发者在百行代码内构建复杂AI代理,提供预置工具库与动态工作流定义能力。
115 3
谷歌开源多智能体开发框架 Agent Development Kit:百行代码构建复杂AI代理,覆盖整个开发周期!
|
3天前
|
人工智能 JSON 小程序
【一步步开发AI运动APP】七、自定义姿态动作识别检测——之规则配置检测
本文介绍了如何通过【一步步开发AI运动APP】系列博文,利用自定义姿态识别检测技术开发高性能的AI运动应用。核心内容包括:1) 自定义姿态识别检测,满足人像入镜、动作开始/停止等需求;2) Pose-Calc引擎详解,支持角度匹配、逻辑运算等多种人体分析规则;3) 姿态检测规则编写与执行方法;4) 完整示例展示左右手平举姿态检测。通过这些技术,开发者可轻松实现定制化运动分析功能。
|
10天前
|
人工智能 Rust 自然语言处理
37.1K star!AI模型全能工具箱,这个开源项目让智能体开发更简单!
"Awesome MCP Servers 是当前最全面的模型上下文协议服务器集合,为AI开发者提供开箱即用的工具链支持。通过标准化协议实现AI模型与各类资源的无缝对接,堪称智能体开发的瑞士军刀!"
|
7天前
|
人工智能 程序员 测试技术
AI 时代,为什么编程能力≠ 开发门槛
在 2.0 阶段,我们目标是实现面向任务的协同编码模式,人的主要职责转变为任务的下发、干预以及最后结果的审查。在这个过程中,人的实际工作量开始减轻,AI 工作的占比显著提升。目前的 2.0 版本是我们最近上线的。
160 65
|
16天前
|
存储 人工智能 监控
一键部署 Dify + MCP Server,高效开发 AI 智能体应用
本文将着重介绍如何通过 SAE 快速搭建 Dify AI 研发平台,依托 Serverless 架构提供全托管、免运维的解决方案,高效开发 AI 智能体应用。
2183 63
|
18天前
|
机器学习/深度学习 人工智能 并行计算
AI部署架构:A100、H100、A800、H800、H20的差异以及如何选型?开发、测试、生产环境如何进行AI大模型部署架构?
AI部署架构:A100、H100、A800、H800、H20的差异以及如何选型?开发、测试、生产环境如何进行AI大模型部署架构?
AI部署架构:A100、H100、A800、H800、H20的差异以及如何选型?开发、测试、生产环境如何进行AI大模型部署架构?
|
20天前
|
人工智能 JavaScript 数据安全/隐私保护
鸿蒙开发难题多到崩溃?然而 10 亿终端暗藏财富密码-卓伊凡
鸿蒙开发难题多到崩溃?然而 10 亿终端暗藏财富密码-卓伊凡
47 5
鸿蒙开发难题多到崩溃?然而 10 亿终端暗藏财富密码-卓伊凡
|
9天前
|
人工智能 前端开发 物联网
【05】20250416优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-增加告警中心相关卡片页面WarningCardWidget相关-增加Canvas 绘制折线图-Canvas 绘制柱状图-首页-优雅草卓伊凡
【05】20250416优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-增加告警中心相关卡片页面WarningCardWidget相关-增加Canvas 绘制折线图-Canvas 绘制柱状图-首页-优雅草卓伊凡
29 0
【05】20250416优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-增加告警中心相关卡片页面WarningCardWidget相关-增加Canvas 绘制折线图-Canvas 绘制柱状图-首页-优雅草卓伊凡
|
15天前
|
人工智能 开发框架 小程序
工会成立100周年纪念,开发职工健身AI运动小程序、APP方案推荐
为庆祝中华全国总工会成立100周年,特推出基于AI技术的智能健身系统,以小程序和APP形式呈现,助力职工健康生活。方案包括:1) 小程序插件,支持多种运动识别,开箱即用;2) APP插件,提供更高精度的运动检测;3) 成熟的「AI乐运动」系统,支持赛事活动管理。这些方案满足不同需求,推动全民健身体验升级,彰显工会对职工健康的关怀。
|
16天前
|
数据采集 SQL 人工智能
长文详解|DataWorks Data+AI一体化开发实战图谱
DataWorks是一站式智能大数据开发治理平台,内置阿里巴巴15年大数据建设方法论,深度适配阿里云MaxCompute、EMR、Hologres、Flink、PAI 等数十种大数据和AI计算服务,为数仓、数据湖、OpenLake湖仓一体数据架构提供智能化ETL开发、数据分析与主动式数据资产治理服务,助力“Data+AI”全生命周期的数据管理。
下一篇
oss创建bucket