#HarmonyOS NEXT体验官#梅科尔工作室HOS-自闭症智能问答助手 原创
本文会手把手教你实现如何基于API12构建一个自闭症智能问答助手。
作者:梅科尔宗月妍
目的
目前国内儿童自闭症已被列为精神残疾类疾病首位且患病群体庞大,但通过我们调研发现,社会以及家长对自闭症的认知远远不足,往往会忽视孩子早期所表现出的一些自闭症症状,并且,即使孩子确诊了自闭症,家长依然不知道该如何进行康复干预。
由此,我们基于API12开发了这款自闭症智能问答助手,并供大家学习交流。
效果展示
实现思路
定义变量
其中,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的自闭症智能问答助手。主要提供聊天对话页面搭建思路和调用网络接口实现对话问答功能,希望这篇文章能帮到你~