#星计划#OpenHarmony+文心一言对话APP WenXinTalk 原创 精华
#星计划#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
2.选择模版
【OpenHarmony】Empty Ability
3.填写项目名,WenXinTalk,应用包名com.WenXinTalk,应用存储位置XXX(不要有中文,特殊字符,空格)
Compile SDK10,Model :Stage
Device Type 默认就可以。
node,建议16.20.1
完成以上配置以后点击Finish
4.选择在新窗口打开,等待依赖加载完毕。如图所示。
如果大家加载依赖报错的话,检查自己的网络。
二、运行HelloWord
1.给开发板供电,并连接开发板到电脑,如图所示
2.签名
签名的步骤:
点击File->Project struct
点击Apply或者ok就可以,。
现在点击按钮运行项目。
控制台打印信息
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
则需要去签名
三、修改图标和名称
修改应用图标和名称:
目录在AppScope/app.json5
{
"app": {
"bundleName": "com.myapplication",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.1",
"icon": "$media:iconAi",
"label": "$string:app_name"
}
}
修改完毕之后如图所示
修改成功之后,如图所示
打开方式,在设置-应用管理一栏。
修改桌面图标和名称
修改src/main/module.json5中如图所示的label和icon。
我们修改label的时候,修改中文目录下的就可以
{
"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"
]
}
]
}
]
}
}
修改中文目录的即可
修改结果如下
四、添加相关权限
因为需要用到网络数据,所以添加initent权限。
在src/main/module.json5中modele中添加配置,然后按sync就可以。
"requestPermissions": [{
"name": "ohos.permission.INTERNET"
}],
五、定义数据模型
1.在ets目录下新建model文件夹
2.新建TS File
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获取 |
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
测试接口
测试如图所示
返回数据
{
"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模块
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';
2.创建createHttp
let httpRequest = http.createHttp();
3.填写HTTP地址
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.对网络数据的处理
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.在应用程序里测试网络请求
build() {
Column() {
Button("测试网络请求").onClick(() => {
this.httpData()
})
}.width("100%").height("100%").justifyContent(FlexAlign.Center)
}
}
如图所示
测试成功
如果2300006错误码,请检查网络
如果201错误码,请检查module.json中网络权限是否配置!
七、UI
数据获取到之后,我们就剩在页面上显示了。
首页
U I布局如图所示。
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)
}
}
八、完整源码
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<MsgInput> = []
aboutToAppear() {
this.message = "请介绍一下你自己"
}
httpData(MsgArray: Array<MsgInput>) {
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
一、在创建应用时选择第一个
如图
二、签名
签名时选择support HarmonyOS
需要登录华为账号后继续签名
三、注释掉源码中不支持API 9的部分
网络声明时直接使用即可
其余部分与在开发板上运行都相同,直接运行即可
九、总结
本文我们学习使用了基本组件的使用,网络请求以及状态管理,使用了ForEach循环渲染来构建对话界面。
并且在HarmonyOS设备上成功运行
十、FAQ
2300006 域名解析失败
错误信息
Couldn’t resolve host name.
错误描述
服务器的域名无法解析。
可能原因
1.传入的服务器的URL不正确。
2.网络不通畅。
处理步骤
1.请检查输入的服务器的URL是否合理。
2.请检查网络连接情况
201错误码
检查moudle.json5的网络权限是否添加
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
],
很完整的讲解,感谢分享
必须给点赞
支持作者
这个厉害
好详细,点赞
大佬你好显示AccessTokenExpired这个报错要怎么处理呢
过期了,重新申请一个
有啥办法可以连kimi吗
很厉害的教学