# WenXinTalk **Repository Path**: qin-weijia/wen-xin-talk ## Basic Information - **Project Name**: WenXinTalk - **Description**: 基于openHarmoney和文心一言API接口开发聊天软件 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-01-10 - **Last Updated**: 2024-03-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: OpenHarmony, 文心一言 ## README # OpenHarmony+文心一言对话APP WenXinTalk ## 运行环境 (后面附有API9版本,可修改后在HarmonyOS4设备上运行) DAYU200:4.0.10.16 SDK:4.0.10.15 IDE:4.0.600 ## 在DAYU200:4.0.10.16上运行 ### 一、创建应用 1.点击File->new File->Create Progect ![image-20240112142948560](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112142948560.png) 2.选择模版 【OpenHarmony】Empty Ability ![image-20240112143008020](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112143008020.png) 3.填写项目名,WenXinTalk,应用包名com.WenXinTalk,应用存储位置XXX(不要有中文,特殊字符,空格) Compile SDK10,Model :Stage Device Type 默认就可以。 node,建议16.20.1 完成以上配置以后点击Finish ![image-20240112143105099](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112143105099.png) 4.选择在新窗口打开,等待依赖加载完毕。如图所示。 ![image-20240109105950521](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240109105950521.png) 如果大家加载依赖报错的话,检查自己的网络。 ### 二、运行HelloWord 1.给开发板供电,并连接开发板到电脑,如图所示 ![image-20240109110152111](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240109110152111.png) 2.签名 签名的步骤: 点击File->Project struct ![image-20240112160932077](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112160932077.png) 点击Apply或者ok就可以,。 现在点击按钮运行项目。 ![image-20240109110601209](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240109110601209.png) 控制台打印信息 ```shell 01/12 16:13:40: Launching com.myapplication $ hdc uninstall com.myapplication $ hdc shell mkdir data/local/tmp/39ca9a16978647c98d8ac8bdf4a45279 $ hdc file send "E:\study\HarmonyOS\project\WenXinTalk\entry\build\default\outputs\default\entry-default-signed.hap" "data/local/tmp/39ca9a16978647c98d8ac8bdf4a45279" $ hdc shell bm install -p data/local/tmp/39ca9a16978647c98d8ac8bdf4a45279 $ hdc shell rm -rf data/local/tmp/39ca9a16978647c98d8ac8bdf4a45279 $ hdc shell aa start -a EntryAbility -b com.myapplication ``` 证明项目已经成功运行在开发板上。 如果报错 ``` 01/09 14:38:39: Install Failed: error: failed to install bundle. code:9568320 error: no signature file. Open signing configs Error while Deploy Hap ``` 则需要去签名 ![image-20240109143947627](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240109143947627.png) ### 三、修改图标和名称 ### 修改应用图标和名称: 目录在AppScope/app.json5 ```json { "app": { "bundleName": "com.myapplication", "vendor": "example", "versionCode": 1000000, "versionName": "1.0.1", "icon": "$media:iconAi", "label": "$string:app_name" } } ``` 修改完毕之后如图所示 ![image-20240112215958960](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112215958960.png) 修改成功之后,如图所示 打开方式,在设置-应用管理一栏。 ![image-20240112220114623](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112220114623.png) ### 修改桌面图标和名称 修改src/main/module.json5中如图所示的label和icon。 我们修改label的时候,修改中文目录下的就可以 ```js { "module": { "requestPermissions": [ { "name": "ohos.permission.INTERNET" } ], "name": "entry", "type": "entry", "description": "$string:module_desc", "mainElement": "EntryAbility", "deviceTypes": [ "default", "tablet" ], "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", "icon": "$media:iconAi", "label": "$string:EntryAbility_label", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", "exported": true, "skills": [ { "entities": [ "entity.system.home" ], "actions": [ "action.system.home" ] } ] } ] } } ``` ![image-20240112220400442](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112220400442.png) 修改中文目录的即可 ![image-20240112220423307](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112220423307.png) 修改结果如下 ![image-20240112220501645](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112220501645.png) ### 四、添加相关权限 因为需要用到网络数据,所以添加initent权限。 在src/main/module.json5中modele中添加配置,然后按sync就可以。 ```js "requestPermissions": [{ "name": "ohos.permission.INTERNET" }], ``` ![image-20240109111759768](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240109111759768.png) ### 五、定义数据模型 1.在ets目录下新建model文件夹 ![image-20240109113219899](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240109113219899.png) 2.新建TS File ![image-20240109113244651](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240109113244651.png) ```js export class WenXinReturn { id: string = "" object: string = "" created: string = "" result: string = "" is_truncated: Boolean = false need_clear_history: Boolean = false finish_reason: string = "" usage: Usage = new Usage() } export class Usage { prompt_tokens: number = 0 completion_tokens: number = 0 total_tokens: number = 0 } export class MsgOutput { content: string = "" } export class MsgInput { id: string = "" role: string = "" content: string = "" } ``` ### 六、加载网络数据 ### 接口说明 准备好数据, 接口地址:https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro 请求方法:POST 请求参数: #### Header参数 | 名称 | 值 | | :----------- | :--------------- | | Content-Type | application/json | #### Query参数 | 名称 | 类型 | 必填 | 描述 | | :----------- | :----- | :--- | :----------------------------------------------------------- | | access_token | string | 是 | 通过API Key和Secret Key获取的access_token,参考[Access Token获取](https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5) | #### Body参数 | 名称 | 类型 | 必填 | 描述 | | :------------ | :------------ | :--- | :----------------------------------------------------------- | | messages | List(message) | 是 | 聊天上下文信息。说明: (1)messages成员不能为空,1个成员表示单轮对话,多个成员表示多轮对话 (2)最后一个message为当前请求的信息,前面的message为历史对话信息 (3)必须为奇数个成员,成员中message的role必须依次为user(or function)、assistant (4)最后一个message的content长度(即此轮对话的问题)不能超过2400 token;如果messages中content总长度大于2400 token,系统会依次遗忘最早的历史会话,直到content的总长度不超过2400 token | | temperature | float | 否 | 说明: (1)较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 (2)默认0.95,范围 (0, 1.0],不能为0 (3)建议该参数和top_p只设置1个 (4)建议top_p和temperature不要同时更改 | | top_p | float | 否 | 说明: (1)影响输出文本的多样性,取值越大,生成文本的多样性越强 (2)默认0.8,取值范围 [0, 1.0] (3)建议该参数和temperature只设置1个 (4)建议top_p和temperature不要同时更改 | | penalty_score | float | 否 | 通过对已生成的token增加惩罚,减少重复生成的现象。说明: (1)值越大表示惩罚越大 (2)默认1.0,取值范围:[1.0, 2.0] | | stream | bool | 否 | 是否以流式接口的形式返回数据,默认false | | system | string | 否 | 模型人设,主要用于人设设定,例如,你是xxx公司制作的AI助手,说明: (1)长度限制1024个字符 (2)如果使用functions参数,不支持设定人设system | | user_id | string | 否 | 表示最终用户的唯一标识符,可以监视和检测滥用行为,防止接口恶意调用 | 在这个WenXinTalk App中我们主要使用参数messages ### 测试接口 测试如图所示 ![image-20240112221313449](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112221313449.png) 返回数据 ```json { "id": "as-7gxj35bftb", "object": "chat.completion", "created": 1705068746, "result": "您好,我是文心一言,英文名是ERNIE Bot。我能够与人对话互动,回答问题,协助创作,高效便捷地帮助人们获取信息、知识和灵感。", "is_truncated": false, "need_clear_history": false, "finish_reason": "normal", "usage": { "prompt_tokens": 3, "completion_tokens": 33, "total_tokens": 36 } } ``` ### 创建HTTP请求 1.导入http模块 ```js import http from '@ohos.net.http'; import { BusinessError } from '@ohos.base'; ``` 2.创建createHttp ``` let httpRequest = http.createHttp(); ``` 3.填写HTTP地址 ```js httpRequest.request( `https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro?access_token=${access_token}`, { method: http.RequestMethod.POST, header: [{ 'Content-Type': 'application/json' }], extraData: { "messages": MsgArray } } ``` 4.对网络数据的处理 ```js if (!err) { let MsgReturn: WenXinReturn = JSON.parse(data.result.toString()) this.MsgResult.content = MsgReturn.result let MsgIn: MsgInput = new MsgInput() MsgIn.role = "assistant" //文心一言返回的结果中令role为"assistant" MsgIn.content = this.MsgResult.content MsgIn.id = this.MsgArray.length.toString() //id作为MsgArray中的标识,用以后续循环渲染 this.MsgArray.push(MsgIn) //将文心一言返回的结果同样作为参数加入到MsgArray的末尾 httpRequest.destroy(); } else { this.message = JSON.stringify(err) console.error('error:' + JSON.stringify(err)); httpRequest.off('headersReceive'); // 当该请求使用完毕时,调用destroy方法主动销毁 httpRequest.destroy(); } ``` 完成以上配置以后 5.在应用程序里测试网络请求 ```js build() { Column() { Button("测试网络请求").onClick(() => { this.httpData() }) }.width("100%").height("100%").justifyContent(FlexAlign.Center) } } ``` 如图所示 ![image-20240112222023411](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112222023411.png) 测试成功 如果2300006错误码,请检查网络 **如果201错误码,请检查module.json中网络权限是否配置!** ### 七、UI 数据获取到之后,我们就剩在页面上显示了。 ### 首页 U I布局如图所示。 ```js build() { Column() { Column() { Scroll() { Column() { ForEach(this.MsgArray, (item: MsgInput, index: number) => { if (item.role == "user") { Row() { Text(item.content) .width("50%") .padding(15.5) .backgroundColor("#95EC69") .borderRadius(4) Image($r("app.media.user")) .backgroundColor(Color.White) .objectFit(ImageFit.Fill) .width(50) .height(50) .borderRadius(4) .margin({ left: 8, right: 8 }) } .margin({ top: index === 0 ? 0 : 12 }) .width("100%") .alignItems(VerticalAlign.Top) .justifyContent(FlexAlign.End) } else if (item.role == "assistant") { Row() { Image($r("app.media.iconAi")) .backgroundColor(Color.White) .objectFit(ImageFit.Fill) .width(50) .height(50) .borderRadius(4) .margin({ left: 8, right: 8 }) Text(item.content) .width("50%") .padding(15.5) .backgroundColor(Color.White) .borderRadius(4) .margin({ right: 0 }) } .margin({ top: index === 0 ? 0 : 12 }) .width("100%") .alignItems(VerticalAlign.Top) .justifyContent(FlexAlign.Start) } }, (item: MsgInput) => item.id) //循环渲染时,键值匹配规则使用item.id } .padding({ top: 12, bottom: 12 }) } .width("100%") } .backgroundColor("#EDEDED") .layoutWeight(1) Row() { TextInput({ placeholder: "来和文心一言聊天吧", text: this.message }) .backgroundColor(Color.White) .layoutWeight(8) .height(60) .onChange((msg: string) => { this.message = msg }) .margin({ top: 10, bottom: 10, left: 4, right: 4 }) Button("提交") .margin(4) .layoutWeight(2) .onClick(() => { let MsgIn: MsgInput = new MsgInput() MsgIn.id = this.MsgArray.length.toString() MsgIn.role = "user" MsgIn.content = this.message this.MsgArray.push(MsgIn) this.httpData(this.MsgArray) this.message = "" }) } .backgroundColor("#F7F7F7") } .width("100%") .height("100%") .justifyContent(FlexAlign.SpaceBetween) } } ``` ![image-20240112222518159](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112222518159.png) ### 八、完整源码 ```js import { MsgInput, MsgOutput, WenXinReturn } from '../model/WexXinClass'; import http from '@ohos.net.http'; import { BusinessError } from '@ohos.base'; @Entry @Component struct Index { @State message: string = 'Hello World'; @State WXReturn: WenXinReturn = new WenXinReturn() @State MsgResult: MsgOutput = new MsgOutput() @State MsgInputSample: MsgInput = new MsgInput() @State MsgArray: Array = [] aboutToAppear() { this.message = "请介绍一下你自己" } httpData(MsgArray: Array) { let httpRequest = http.createHttp(); httpRequest.request( "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro?access_token=24.c8909e4016b8a223464e02f463f1d1ac.2592000.1707464478.282335-46746495", { method: http.RequestMethod.POST, header: [{ 'Content-Type': 'application/json' }], extraData: { "messages": MsgArray } }, (err: BusinessError, data: http.HttpResponse) => { if (!err) { let MsgReturn: WenXinReturn = JSON.parse(data.result.toString()) this.MsgResult.content = MsgReturn.result let MsgIn: MsgInput = new MsgInput() MsgIn.role = "assistant" MsgIn.content = this.MsgResult.content MsgIn.id = this.MsgArray.length.toString() this.MsgArray.push(MsgIn) httpRequest.destroy(); } else { this.message = JSON.stringify(err) console.error('error:' + JSON.stringify(err)); httpRequest.off('headersReceive'); // 当该请求使用完毕时,调用destroy方法主动销毁 httpRequest.destroy(); } } ) } build() { Column() { Column() { Scroll() { Column() { ForEach(this.MsgArray, (item: MsgInput, index: number) => { if (item.role == "user") { Row() { Text(item.content) .width("50%") .padding(15.5) .backgroundColor("#95EC69") .borderRadius(4) Image($r("app.media.user")) .backgroundColor(Color.White) .objectFit(ImageFit.Fill) .width(50) .height(50) .borderRadius(4) .margin({ left: 8, right: 8 }) } .margin({ top: index === 0 ? 0 : 12 }) .width("100%") .alignItems(VerticalAlign.Top) .justifyContent(FlexAlign.End) } else if (item.role == "assistant") { Row() { Image($r("app.media.iconAi")) .backgroundColor(Color.White) .objectFit(ImageFit.Fill) .width(50) .height(50) .borderRadius(4) .margin({ left: 8, right: 8 }) Text(item.content) .width("50%") .padding(15.5) .backgroundColor(Color.White) .borderRadius(4) .margin({ right: 0 }) } .margin({ top: index === 0 ? 0 : 12 }) .width("100%") .alignItems(VerticalAlign.Top) .justifyContent(FlexAlign.Start) } }, (item: MsgInput) => item.id) } .padding({ top: 12, bottom: 12 }) } .width("100%") } .backgroundColor("#EDEDED") .layoutWeight(1) Row() { TextInput({ placeholder: "来和文心一言聊天吧", text: this.message }) .backgroundColor(Color.White) .layoutWeight(8) .height(60) .onChange((msg: string) => { this.message = msg }) .margin({ top: 10, bottom: 10, left: 4, right: 4 }) Button("提交") .margin(4) .layoutWeight(2) .onClick(() => { let MsgIn: MsgInput = new MsgInput() MsgIn.id = this.MsgArray.length.toString() MsgIn.role = "user" MsgIn.content = this.message this.MsgArray.push(MsgIn) this.httpData(this.MsgArray) this.message = "" }) } .backgroundColor("#F7F7F7") } .width("100%") .height("100%") .justifyContent(FlexAlign.SpaceBetween) } } ``` ## 在HarmonyOS 4.0设备上运行 **(主要介绍与在OpenHarmony中开发时的不同,相同点不在赘述)** ### 运行环境 HUAWEI Mate 40 : HarmonyOS 4.0.0 SDK:3.1.0(API 9) IDE:4.0.600 ### 一、在创建应用时选择第一个 如图 ![image-20240112224005507](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112224005507.png) ![image-20240112224034240](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112224034240.png) ### 二、签名 签名时选择support HarmonyOS ![image-20240112224437824](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112224437824.png) 需要登录华为账号后继续签名 ![image-20240112224543923](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112224543923.png) ### 三、注释掉源码中不支持API 9的部分 ![image-20240112224641164](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112224641164.png) 网络声明时直接使用即可 ![image-20240112224726844](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112224726844.png) ### 其余部分与在开发板上运行都相同,直接运行即可 ![image-20240112224825309](https://qwj-for-typora.oss-cn-beijing.aliyuncs.com/for-typora/image-20240112224825309.png) ## 九、总结 本文我们学习使用了基本组件的使用,网络请求以及状态管理,使用了ForEach循环渲染来构建对话界面。 并且在HarmonyOS设备上成功运行 ## 十、FAQ ### 2300006 域名解析失败 **错误信息** Couldn’t resolve host name. **错误描述** 服务器的域名无法解析。 **可能原因** 1.传入的服务器的URL不正确。 2.网络不通畅。 **处理步骤** 1.请检查输入的服务器的URL是否合理。 2.请检查网络连接情况 ### 201错误码 检查moudle.json5的网络权限是否添加 ```js "requestPermissions": [ { "name": "ohos.permission.INTERNET" } ], ``` ## 参考链接 [错误码](https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/reference/errorcodes/errorcode-net-http.md/) [ForEach](https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/quick-start/arkts-rendering-control-foreach.md/)