鸿蒙开发:实现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文本适配。


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


相关文章
|
6月前
|
人工智能 安全 API
20 万奖金池就位!Higress AI 网关开发挑战赛参赛指南
本次赛事共设三大赛题方向,参赛者可以任选一个方向参赛。本文是对每个赛题方向的参赛指南。
560 52
|
6月前
|
人工智能 运维 安全
加速智能体开发:从 Serverless 运行时到 Serverless AI 运行时
在云计算与人工智能深度融合的背景下,Serverless 技术作为云原生架构的集大成者,正加速向 AI 原生架构演进。阿里云函数计算(FC)率先提出并实践“Serverless AI 运行时”概念,通过技术创新与生态联动,为智能体(Agent)开发提供高效、安全、低成本的基础设施支持。本文从技术演进路径、核心能力及未来展望三方面解析 Serverless AI 的突破性价值。
|
6月前
|
人工智能 运维 Java
Spring AI Alibaba Admin 开源!以数据为中心的 Agent 开发平台
Spring AI Alibaba Admin 正式发布!一站式实现 Prompt 管理、动态热更新、评测集构建、自动化评估与全链路可观测,助力企业高效构建可信赖的 AI Agent 应用。开源共建,现已上线!
6997 91
|
人工智能 自然语言处理 前端开发
产品经理也能“开发”需求?淘宝信息流从需求到上线的AI端到端实践
淘宝推荐信息流业务,常年被“需求多、技术栈杂、协作慢”困扰,需求上线周期动辄一周。WaterFlow——一套 AI 驱动的端到端开发新实践,让部分需求两天内上线,甚至产品经理也能“自产自销”需求。短短数月,已落地 30+ 需求、自动生成 5.4 万行代码,大幅提升研发效率。接下来,我们将揭秘它是如何落地并改变协作模式的。
940 37
产品经理也能“开发”需求?淘宝信息流从需求到上线的AI端到端实践
|
6月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1055 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
6月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
837 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
6月前
|
人工智能 IDE 开发工具
从6人日到1人日:一次AI驱动的客户端需求开发实战
从6人日到1人日:一次AI驱动的客户端需求开发实战
从6人日到1人日:一次AI驱动的客户端需求开发实战
|
6月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
996 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
6月前
|
人工智能 小程序 开发者
【一步步开发AI运动APP】十二、自定义扩展新运动项目03
继【一步步开发AI运动小程序】后,我们推出新系列【一步步开发AI运动APP】,助开发者打造高性能、优体验的AI运动应用。本文详解自定义扩展运动分析器的统一管理实现,提升代码复用性与可维护性,涵盖APP与小程序插件差异及完整代码示例,助力AI运动场景深度拓展。
|
6月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
311 0

热门文章

最新文章