#HarmonyOS NEXT体验官#梅科尔工作室HOS-自闭症智能问答助手 原创

梅科尔工作室HOS
发布于 2024-8-21 14:04
浏览
0收藏

本文会手把手教你实现如何基于API12构建一个自闭症智能问答助手。

作者:梅科尔宗月妍

目的

目前国内儿童自闭症已被列为精神残疾类疾病首位且患病群体庞大,但通过我们调研发现,社会以及家长对自闭症的认知远远不足,往往会忽视孩子早期所表现出的一些自闭症症状,并且,即使孩子确诊了自闭症,家长依然不知道该如何进行康复干预。
由此,我们基于API12开发了这款自闭症智能问答助手,并供大家学习交流。

效果展示

#HarmonyOS NEXT体验官#梅科尔工作室HOS-自闭症智能问答助手-鸿蒙开发者社区

实现思路

定义变量

其中,title为输入框内容,submitValue为发送按钮内容,timetext为加载弹窗倒计时,arr为对话列表,myid为发送次数,conversation_id为对话id,intervalID1为定时器id,buttonColor为发送按钮颜色

// 输入框内容
@State title: string = ''
@State submitValue: string = '' 
// 加载弹窗倒计时  
@State timetext:number = 5   
// 对话列表
@State arr: Array<Chat> = [new Chat(1,"您好,我是自闭症顾问米粒,请问您有什么问题?",true)  ];
// 发送次数,用于后续清空对话判断
@State myid:number=1
// 对话id
@State conversation_id:string=""
// 定时器id
@State intervalID1:number=0
@State buttonColor:string='#69A5FB'

定义聊天类

其中,id为聊天id,content为聊天内容,role为聊天角色。

export class Chat {
  id:number;
  content: string;  // 聊天内容
  role: Boolean; // 聊天角色



  constructor(id:number, content: string, role: Boolean) {
    this.id=id;
    this.content = content;
    this.role = role;

  }
}

构建头部机器人区域

构建头部组件。

@Builder Robot(){
    Row(){
      Image($r('app.media.ai1'))
        .width(135)
        .height(175)

      Column({space:25}){
        Column({space:5}){
          Text('您好')
            .fontSize(18)
            .fontColor('#1C74F2')

          Text('我是AI米粒-自闭症智能顾问')
            .fontSize(16)
            .fontColor('#101010')
        }
        .alignItems(HorizontalAlign.Start)

        Text('专注自闭症领域,您可以向我咨询自闭症的有关问题,我来为您提供建议,希望能为您提供帮助和支持')
          .width(200)
          .fontSize(12)
          .fontColor('#888888')
          .textAlign(TextAlign.Start)
      }
      .width('100%')
      .alignItems(HorizontalAlign.Start)
    }
    .width('96%')
    .padding(3)
    .height(150)
    .backgroundImage($r('app.media.beijing_ai'))
    .backgroundImageSize({width:'100%',height:'100%'})
    .borderRadius({topLeft: 100, topRight: 10, bottomLeft: 100, bottomRight: 10})
  }

构建提示案例区域

其中,用:this.title='孩子' + this.age + ',' + content,可以实现点击后,将内容赋值给title,从而实现输入框内容自动赋值。
此处提供三个推荐问的问题示例。

// 提示案例
  @Builder TipItem(content:string){
    Row(){
      Text('孩子'+this.age + ',')
        .fontSize(14)
        .fontColor('#1C74F2')
      Text(content)
        .fontSize(14)
        .fontColor('#101010')
        .width(200)
    }
    .width('100%')
    .borderRadius(15)
    .height(75)
    .padding(10)
    .backgroundColor('#F5F5F5')
    .onClick(()=>{
      this.title='孩子' + this.age + ',' + content
    })
  }


  // 提示区域
  @Builder Tips(){
    Column({space:15}){
      Row() {
        Text('请以“')
          .fontSize(14)
          .fontColor('#101010')
        Text('孩子xx岁xx个月')
          .fontSize(14)
          .fontColor('#1C74F2')
        Text('”开始描述问题,案例如下:')
          .fontSize(14)
          .fontColor('#101010')
      }

      this.TipItem('有轻度自闭症仅能简单表达,不会叙述一件事情的发生,不愿意配合,该怎么引导?')
      this.TipItem('外出总是跑来跑去,喜欢大喊大叫,做刻板行为,怎么引导安坐?')
      this.TipItem('现在在幼儿园做幼小衔接,孩子在课堂尖叫,离开座位,该如何引导?')
    }
    .alignItems(HorizontalAlign.Start)
    .width('93%')
  }

构建底部输入框和按钮区域

其中,通过判断输入框是否为空,从而实现发送按钮颜色变化,同时,通过点击发送按钮,实现发送请求,并实现加载弹窗的开关。

// 底部输入框和发送按钮
  @Builder BottomContent(){
    Row({space:10}){
      TextInput({placeholder:'请输入您的问题......',text: this.title})
        .width('80%')
        .height(46)
        .backgroundColor('#F5F5F5')
        .fontColor('#101010')
        .placeholderColor('#888888')
        .borderRadius(15)
        .onChange((value: string) => {
          this.title = value
          if (this.title != '') {
            this.buttonColor = '#1C74F2'
          }
          else {
            this.buttonColor ='#69A5FB'
          }
        })

      Button('发送')
        .width(60)
        .height(42)
        .backgroundColor(this.buttonColor)
        .borderRadius(5)
        .onClick(() => {
          this.buttonColor = '#69A5FB'
          
          // 判断输入框为空时点击发送显示提示弹窗
          
          if(this.title==''){
            promptAction.showToast({
              message: "请输入您的问题~",
              duration: 2000
            })
          }
          else {
            this.myid++ // 开始对话
            this.arr.push(new Chat(this.myid, this.title, false)) // 对话列表相应增加
            this.S_conversation() // 新建对话请求

            setTimeout(() => {
            
              this.S_login() // 模型问答请求
              
              // 等待一段时间,确保 S_login() 函数执行完毕
              setTimeout(() => {
                console.info(this.title);
              }, 500) // 假设 S_login() 需要 1 秒钟执行完毕
              
              this.title = '' // 清空输入框
              
            }, 10);


            this.loadingController.open();


            this.intervalID1 = setInterval(
              () => {
                this.timetext--
                if (this.timetext == 0) {
                  clearInterval(this.intervalID1)
                  this.loadingController.close(); // 加载弹窗开关
                }
              }, 1000)
            this.timetext = 5
          }
        })
    }
    .width('100%')
    .padding(5)
    .margin({bottom:7})
  }

构建对话列表

其中,通过判断role属性,判断是否为机AI回复,从而实现不同的布局,同时将列表容器的高度设为58%,以实现滚动功能。

// 对话列表
  @Builder ChatList(){
    List() {
      ForEach(this.arr, (item: Chat) => {
        ListItem() {
          if (item.role) {
            Row({ space: 8 }) {
              Image($r('app.media.ai1'))
                .width(48)
                .height(48)
                .borderRadius(90)

              Row() {
                Text(item.content)
                  .fontSize(16)
                  .copyOption(CopyOptions.LocalDevice)
                  .fontColor("#FFFFFF")
              }
              .width("60%")
              .globalCard()
              .backgroundColor("#69A5FB")
            }
            .width("100%")
            .justifyContent(FlexAlign.Start)
            .alignItems(VerticalAlign.Top)
          }

          else {
            Row({ space: 8 }) {
              Row() {
                Text(item.content)
                  .fontSize(16)
                  .copyOption(CopyOptions.LocalDevice)
              }
              .width("60%")
              .justifyContent(FlexAlign.End)
              .globalCard()
              .backgroundColor('#FFFFFF')

              Image($r("app.media.begin_logo"))
                .width(48)
                .height(48)
                .borderRadius(90)

            }
            .width("100%")
            .justifyContent(FlexAlign.End)
            .alignItems(VerticalAlign.Top)
            .margin({ top: 14, bottom: 14 })
          }
        }
      }, (item: Chat) => item.id.toString())
    }
    .scrollBar(BarState.Off) // 关闭滚动条
    .width("96%")
    .height("58%") // 此处将列表容器的高度设为58%,以实现滚动功能
    .padding(10)
    .margin({ left: 15 })
  }

构建页面主体

此处用myid判断是否已开始对话,若开始则隐藏提示案例区域,否则正常显示该区域。
这里TopBuilder是一个顶部组件,包含标题和返回键以及右侧图标和onConfirm()点击事件,用于清空对话。

build() {
    Stack({alignContent:Alignment.Bottom}){
      Column({ space: 25 }) {
        
        // 此处onComfirm()为清空对话函数
        
        TopBuilder({ title: 'AI米粒', color: '#101010', fanhuiimg: $r('app.media.fanhui'),img:$r('app.media.qingkong'),onConfirm:()=>{this.onComfirm()}})
        this.Robot()
        
        // 判断当前对话是否已为空
        
        if(this.myid==1) {
          this.Tips()
          this.ChatList()
        }
        else {
          this.ChatList()
        }
      }
      .width('100%')
      .height('100%')

      this.BottomContent()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }

清空对话函数

包括再次确认弹窗以及判断当前对话是否为空。

// 清空当前对话
  onComfirm(){
  
  // 增加再次确认弹窗
  
    promptAction.showDialog({
      title: '',
      message: '是否清空当前对话?',
      buttons: [
        {
          text: '确认',
          color: '#1C74F2',
        },
        {
          text: '取消',
          color: '#101010'
        }
      ],
    })
      .then(data => {
        if(data.index==0){
          if(this.myid==1){
            promptAction.showToast({
              message:'当前对话已为空~',
              duration:2000
            })
          }
          else {
          
          //
          
            this.arr = [new Chat(1, "您好,我是自闭症顾问米粒,请问您有什么问题?", true)];
            this.myid = 1

            promptAction.showToast({
              message: '已清空当前对话',
              duration: 2000
            })
          }

        }
      })
  }

Loading弹窗

封装

为了效果更符合实际,我们用Loading弹窗,并设置定时器,在5秒后关闭弹窗。
这里为了省事,直接使用loading动图,如果你想用动画来实现loading动效也可以~

@CustomDialog
export struct Loading {
  controller: CustomDialogController;

  build() {
    Column({ space: 20}) {
      Image($r('app.media.loading'))
        .width(81)
        .height(81)
    }
    .width('96%')
    .height(100)
    .borderRadius(20)
    .justifyContent(FlexAlign.Center)
  }
}

主页面调用

loadingController: CustomDialogController = new CustomDialogController({
    builder: Loading({})
  })

新建对话请求

"X-Appbuilder-Authorization"为密钥
传递成功后,将返回的conversation_id赋值给this.conversation_id,用于后续请求

// 新建对话请求
S_conversation(){
  let jsondata="";
  let httpRequest = http.createHttp();
  let url = "https://qianfan.baidubce.com/v2/app/conversation"
  httpRequest.request(
    url,
    {
      method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET
      // 开发者根据自身业务需要添加header字段123
                        header: {
        'Content-Type': 'application/json',
        'X-Appbuilder-Authorization':'Bearer bce-v3/ALTAK-tVElPRmgqkSjJslUpWfrz/44774debb4851a425b89f91ae4933a3b8516a71f' // 密钥
      },
      // 当使用POST请求时此字段用于传递内容
      extraData:{
        "app_id": "437b75b2-6c93-496f-85b3-5327bcd3d325" // 应用ID
      },
      connectTimeout: 60000, // 可选,默认为60000ms
      readTimeout: 60000, // 可选,默认为60000ms
    }, (err, data) => {
    if (!err) {
      jsondata = data.result.toString()
      jsondata = JSON.parse(jsondata)
      // data.result为HTTP响应内容,可根据业务需要进行解析
      if (data.responseCode == 200) {
        console.info("传递成功")
        this.conversation_id=JSON.parse(data.result.toString()).conversation_id
        console.info("对话id为:"+JSON.parse(data.result.toString()).conversation_id)
      }
    }
  }
  )
}

模型问答请求

"X-Appbuilder-Authorization"为密钥
聊天对话发送次数自增,即此时页面中隐藏提示案例区域,并且在对话数组中添加接收到的回答。

// 模型问答请求
S_login() {
  let jsondata="";
  let httpRequest = http.createHttp();
  let url = "https://qianfan.baidubce.com/v2/app/conversation/runs"
  httpRequest.request(
    // 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定
    url,
    {
      method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET
      // 开发者根据自身业务需要添加header字段123
      header: {
        'Content-Type': 'application/json',
        'X-Appbuilder-Authorization':'Bearer bce-v3/ALTAK-tVElPRmgqkSjJslUpWfrz/44774debb4851a425b89f91ae4933a3b8516a71f'
      },
      // 当使用POST请求时此字段用于传递内容
      extraData:{
        "app_id": "437b75b2-6c93-496f-85b3-5327bcd3d325",
        "query": this.title, // 用户问题
        "stream": false,
        "conversation_id": this.conversation_id // 上一个请求获取到的当前对话ID
      },
      connectTimeout: 60000, // 可选,默认为60000ms
      readTimeout: 60000, // 可选,默认为60000ms
    }, (err, data) => {

    if (!err) {
      jsondata = data.result.toString()
      jsondata = JSON.parse(jsondata)
      // data.result为HTTP响应内容,可根据业务需要进行解析
      if (data.responseCode == 200) {
        console.info("传递成功")
        this.myid++ // 聊天对话发送次数自增
        this.arr.push(new Chat(this.myid,JSON.parse(data.result.toString()).answer,true))  // 增加新的聊天对话
        console.info("从服务器返回接口返回数据成功,传递参数" + JSON.parse(data.result.toString()).answer)
        console.info(this.arr[2].content.toString())
      }
    }
  }
  );
}

到这里运行的时候你会发现,发送问题后等待一段时间却没有返回回答数据。
为解决此bug,我们在aboutAppear()函数中调用新建对话请求,即在页面刚开始加载的时候默认获取一次对话id,这样就可以保证每次发送问题都能有一个新的对话id包含此问题,从而可以获取到回答数据。

aboutToAppear(){
    this.S_conversation()
  }

至此我们就可以完美实现基于API12构建一个自闭症智能问答助手了。

总结

本文详细介绍了如何构建一个基于API12的自闭症智能问答助手。主要提供聊天对话页面搭建思路和调用网络接口实现对话问答功能,希望这篇文章能帮到你~

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-8-21 14:06:21修改
1
收藏
回复
举报
回复
    相关推荐