HarmonyOS NEXT实现文字和图片的方式和AI对话

打印 上一主题 下一主题

主题 997|帖子 997|积分 2991

作品效果:

大模子的回复:


各人根据上面的图片就大概知道了这是一个怎么样的业务逻辑
总的来说:首先设计好数据结构
这里是两个人在对话,一个是用户User另外一个是机器人Agent
设计好ViewModel
现在分析怎样实现上述效果:
STEP1:点击右边的十字,表现更多工具

这里形貌的是右边的十字按钮,点击时改变ViewModel isMoreFunction 
  1. // 这里描述的是右边的十字按钮,点击时改变ViewModel isMoreFunction
  2.           Row() {
  3.             Image($r("app.media.ic_public_agent_more_plus_mark"))
  4.               .width(25)
  5.               .height(25)
  6.               .borderRadius(25)
  7.               .fillColor(Color.Black)
  8.               .rotate({ angle: this.rotateAngle })
  9.           }
  10.           .width(40)
  11.           .height(40)
  12.           .borderRadius(40)
  13.           .justifyContent(FlexAlign.Center)
  14.           .backgroundColor(Color.White)
  15.           .shadow({
  16.             radius: 10,
  17.             color: $r("app.color.agent_btm_func_item_icon_gray"),
  18.             offsetX: 0,
  19.             offsetY: 0
  20.           })
  21.           .onClick(() => {
  22.             // 设置属性动画
  23.             this.getUIContext()?.animateTo({ curve: curves.springMotion() }, async () => {
  24.               // 打开任务栏
  25.               this.isMoreFunction = !this.isMoreFunction
  26.               this.rotateAngle = this.isMoreFunction ? 45 : 0
  27.               if (this.isMoreFunction) {
  28.                 // 关闭键盘
  29.                 await inputMethod.getController().stopInputSession()
  30.               }
  31.             })
  32.           })
复制代码
这里使用if条件渲染下面的结构:

代码如下:

  1. @State isMoreFunction: boolean = false // 是否展现底部的多功能栏
  2. ......
  3. // 底部结构
  4.       if (this.isMoreFunction) {
  5.         Row() {
  6.           GridRow({ columns: 4 }) {
  7.             ForEach(this.bottomFunctionList, (item: PopupComp) => {
  8.               GridCol() {
  9.                 this.getBottomCard(item)
  10.               }.height(100)
  11.             })
  12.           }
  13.           .width('100%')
  14.         }
  15.         .margin({ top: 20 })
  16.         .width($r("app.string.full_percent"))
  17.         .padding({ left: 10, right: 10 })
  18.       }
复制代码

点击图片按钮的时间就会弹出弹窗,这里我使用的是我之前封装好的工具类MediaOperate的getImageUrisFromAlbum方法,打开体系相册,获取照片
  1.   /**
  2.    * 选择照片
  3.    * @param photoNum
  4.    * @returns
  5.    */
  6.   static async getImageUrisFromAlbum(photoNum: number = 1): Promise<string> {
  7.     try {
  8.       const photo = new picker.PhotoViewPicker()
  9.       const result = await photo.select({
  10.         maxSelectNumber: photoNum,
  11.         MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE
  12.       })
  13.       return result.photoUris[0]
  14.     } catch (error) {
  15.       AlertDialog.show({
  16.         message: '选取相册图片uris失败:\n' + JSON.stringify(error, null, 2)
  17.       })
  18.       return ''
  19.     }
  20.   }
复制代码
并且将照片存储到状态变量中去
  1. @State @Watch('getPlaceHolder') selectedUri: string = '' // 当前选中的图片的地址
  2. //......
  3. this.selectedUri = await MediaOperate.getImageUrisFromAlbum(1)
复制代码
然后我们就拿到了请求大模子的照片了
如图:

然后就是老生常谈的输入文字了,这里我不外多赘述了
可以参考官网文档:文本与输入-ArkTS组件-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开辟者 (huawei.com)
只需要将输入的文字也存储到状态变量内里去即可:下面的代码仅供参考
  1.   // 输入文字的系统组件
  2.             TextArea({
  3.               placeholder: this.placeHolder,
  4.               text: this.inputTextContent
  5.             })
  6.               .height(50)
  7.               .textOverflow(TextOverflow.Ellipsis)
  8.               .layoutWeight(1)
  9.               .borderRadius(14)
  10.               .backgroundColor($r("app.color.agent_textarea_background_color"))
  11.               .id('input')
  12.               .focusable(true)
  13.               .defaultFocus(true)
  14.               .onFocus(() => {
  15.                 // 聚焦键盘的时候,隐藏底部功能栏
  16.                 this.isMoreFunction = false
  17.                 this.rotateAngle = 0
  18.                 // 消息列表滚动到底部
  19.                 this.scroller.scrollEdge(Edge.Bottom)
  20.               })
  21.               .onChange((value) => {
  22.                 this.inputTextContent = value
  23.               })
  24.               .onSubmit(() => {
  25.                 this.sendTextMessage(this.inputTextContent)
  26.                 this.inputTextContent = ''
  27.               })
复制代码
就如许我们拿到了图片用户输入的文字
STEP2:发送请求:

这里我使用的是原生的http请求(参考官方文档:@ohos.net.http (数据请求)-ArkTS API-Network Kit(网络服务)-网络-体系 - 华为HarmonyOS开辟者 (huawei.com))
这里呢,我封装了一个请求工具类:
贴给各人参考一下:(涉及我的保密信息,部分不表现代码太长了:仅供参考!!!




然后我们就能拿到用户发送的信息和大模子的相应信息了:
最后我们渲染一下就可以了:
效果:

首先是将用户的的大模子的信息更新到状态变量内里去,这里怎么更新前面讲了,也不做过多形貌。
  1.   @State rcdMsgList: MessageInfoModel[] = [] // 记录生活信息数据
  2.   @State qstMsgList: MessageInfoModel[] = [] // 问题模式信息数据
  3.   @State showMsgList: MessageInfoModel[] = [] // 需要展示的数据信息
复制代码
  1.   // 发送信息给大模型
  2.   async sendTextMessage(inputContent: string) {
  3.     if (this.agentMode === AgentModeEnum.Record) {
  4.       const tempUri = this.selectedUri
  5.       // 删除选中的图片
  6.       this.selectedUri = ''
  7.       // 分享生活
  8.       const msgStored = new MessageInfoModel({
  9.         sender: currentUser,
  10.         receiver: currentAgent,
  11.         content: inputContent,
  12.         sendTimeStamp: Date.now(),
  13.         sourceFilePath: tempUri, // 照片的地址
  14.         type: tempUri === '' ? MessageTypeEnum.TEXT : MessageTypeEnum.IMAGE // 照片
  15.       } as MessageInfo)
  16.       hilog.info(0, 'AgentMessage', `AgentMessage Info: ${JSON.stringify(msgStored, null, 2)}`)
  17.       // 更新到记录生活的列表里面去,展示UI界面
  18.       this.rcdMsgList.push(msgStored)
  19.       // 添加信息到数据库里面去
  20.       await relationalDatabase.addMessage(msgStored, TABLE_RECORD_MESSAGE)
  21.       this.scroller.scrollEdge(Edge.Bottom)
  22.       let requestContent = inputContent
  23.       // 辅助记录生活的语句
  24.       requestContent =
  25.         '帮我记录我的生活:\n' + `现在是` + getCurrentTimeStringFormat(Date.now()) + ',\n' + inputContent
  26.       // 用于辅助询问信息的对象
  27.       const msgAsked = new MessageInfoModel({
  28.         sender: currentUser,
  29.         receiver: currentAgent,
  30.         content: requestContent,
  31.         sendTimeStamp: Date.now(),
  32.         sourceFilePath: tempUri, // 照片的地址
  33.         type: tempUri === '' ? MessageTypeEnum.TEXT : MessageTypeEnum.IMAGE // 照片
  34.       } as MessageInfo)
  35.       // 更新UI
  36.       this.showMsgList = this.rcdMsgList
  37.       this.getResponseMessage(msgAsked)
  38.     } else {
  39.       const tempUri = this.selectedUri
  40.       // 删除选中的图片
  41.       this.selectedUri = ''
  42.       // 解答疑惑
  43.       const msg = new MessageInfoModel({
  44.         sender: currentUser,
  45.         receiver: currentAgent,
  46.         content: inputContent,
  47.         sendTimeStamp: Date.now(),
  48.         sourceFilePath: tempUri, // 照片的地址
  49.         type: tempUri === '' ? MessageTypeEnum.TEXT : MessageTypeEnum.IMAGE // 照片
  50.       } as MessageInfo)
  51.       // 更新到解答疑问的列表里面去,展示UI界面
  52.       this.qstMsgList.push(msg)
  53.       // 添加信息到数据库里面去
  54.       await relationalDatabase.addMessage(msg, TABLE_QUESTION_MESSAGE)
  55.       // 滑动到最低端
  56.       this.scroller.scrollEdge(Edge.Bottom)
  57.       // 构建问题语句,这个是根据用户输入的信息进行优化的prompt
  58.       // 用户原始的输入的信息将通过inputContent存储到数据库中去
  59.       // 问题语句
  60.       let requestContent = inputContent
  61.       requestContent = '我有一个问题想问你:\n' + inputContent
  62.       // 用于辅助询问信息的对象
  63.       const msgAsked = new MessageInfoModel({
  64.         sender: currentUser,
  65.         receiver: currentAgent,
  66.         content: requestContent,
  67.         sendTimeStamp: Date.now(),
  68.         sourceFilePath: tempUri, // 照片的地址
  69.         type: tempUri === '' ? MessageTypeEnum.TEXT : MessageTypeEnum.IMAGE // 照片
  70.       } as MessageInfo)
  71.       // 更新UI
  72.       this.showMsgList = this.qstMsgList
  73.       // 获取Agent响应
  74.       await this.getResponseMessage(msgAsked)
  75.     }
  76.   }
复制代码

在这里紧张讲讲封装信息的组件:
信息列表:这里就是通过showMsgList内里的信息渲染出来的
  1. //聊天内容
  2.       List({ space: 20, scroller: this.scroller }) {
  3.         ForEach(this.showMsgList, (item: MessageInfoModel) => {
  4.           ListItem() {
  5.             // 单个消息UI                                        解决this丢失问题
  6.             MessageComp({ item, popupBuilder: this.popupBuilder.bind(this), showPopup: this.showPopup })
  7.           }
  8.         })
  9.       }
  10.       .layoutWeight(1)
  11.       .width('100%')
  12.       .scrollBar(BarState.Off)
  13.       .padding({ top: 20, bottom: 60 })
  14.       .onClick(async () => {
  15.         // 关闭键盘
  16.         await inputMethod.getController().stopInputSession()
  17.       })
复制代码
最后就是自定义封装的Message了:
  1. @Component
  2. struct MessageComp {
  3.   @State item: MessageInfoModel = new MessageInfoModel({} as MessageInfo)
  4.   @State showPopup: boolean = false
  5.   @State popupCompList: PopupComp[] = []
  6.   // 使用自定义组件的自定义构建函数初始化@BuilderParam
  7.   @BuilderParam popupBuilder: () => void = this.doNothingBuilder; // list: PopupComp[]
  8.   @Builder
  9.   doNothingBuilder() {
  10.   };
  11.   build() {
  12.     Row() {
  13.       Row() {
  14.         Column({ space: 10 }) {
  15.           if (this.item.sourceFilePath && this.item.sender.username === currentUser.username) {
  16.             Image(this.item.sourceFilePath)
  17.               .width(230)
  18.               .borderRadius(10)
  19.               .margin({ left: 25, right: 25 })
  20.           }
  21.           // 消息类型渲染
  22.           Text(this.item.content)
  23.             .backgroundColor(this.item.sender.user_id === currentUser.user_id ? $r('app.color.user_massage_color') :
  24.             $r('app.color.agent_message_color'))
  25.             .fontColor(this.item.sender.user_id === currentUser.user_id ? Color.White : Color.Black)
  26.             .margin({ left: 10, right: 10 })
  27.             .borderRadius({
  28.               topLeft: this.item.sender.user_id === currentUser.user_id ? 16 : 0,
  29.               topRight: this.item.sender.user_id === currentUser.user_id ? 0 : 16,
  30.               bottomLeft: 16,
  31.               bottomRight: 16
  32.             })
  33.             .lineHeight(24)
  34.             .padding(10)
  35.             .bindPopup(this.showPopup, {
  36.               builder: this.popupBuilder,
  37.               popupColor: Color.White,
  38.               backgroundBlurStyle: BlurStyle.NONE,
  39.               onStateChange: (event) => {
  40.                 // 当手指点了其他位置 关闭状态
  41.                 this.showPopup = event.isVisible
  42.               }
  43.             })
  44.         }
  45.       }
  46.       .layoutWeight(4)
  47.       .justifyContent(this.item.sender.user_id === currentUser.user_id ? FlexAlign.End : FlexAlign.Start)
  48.       // 站位
  49.       Text().layoutWeight(1)
  50.     }
  51.     .width('100%')
  52.     .padding({ left: 10, right: 10 })
  53.     .direction(this.item.sender.user_id === currentUser.user_id ? Direction.Rtl : Direction.Ltr)
  54.     .gesture(
  55.       // 长按显示弹层,操作信息
  56.       LongPressGesture()
  57.         .onAction(() => {
  58.           this.showPopup = true
  59.         }))
  60.   }
  61. }
  62. export { MessageComp }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

守听

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表