鸿蒙 Next 对接 AI API 实现文字对话功能指南
在智能化浪潮下,为用户提供便捷的 AI 文字对话功能,成为众多鸿蒙 Next 应用提升用户体验的关键。接下来,我们将深入剖析如何在鸿蒙 Next 系统中对接 AI API,打造流畅的文字对话交互。
前期搭建
环境部署
首先,安装 DevEco Studio 这一鸿蒙原生应用开发的核心工具。它能提供智能代码补全等功能,极大提高开发效率。安装过程需严格遵循官方文档,确保每个环节正确无误。
项目初始化
在 DevEco Studio 内创建新的鸿蒙 Next 应用项目。创建时,依据应用定位,精准选择合适的模板与配置选项。例如,若应用定位为知识问答类,可选择简洁的表单式页面模板,方便用户输入问题与查看答案。
网络权限申请
在应用的\entry\src\main\module.json5
配置文件中,明确声明网络访问权限,这是应用与 AI API 进行数据交互的基础。添加如下代码:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
对接流程
选定 AI API 服务
目前,市场上有多种 AI API 服务可供选择。像华为云自然语言处理服务,凭借其与鸿蒙系统的良好兼容性,能提供稳定高效的自然语言处理能力;还有百度文心一言 API,具备强大的语义理解与文本生成功能。开发者需综合考量服务的功能特性、价格、性能以及与鸿蒙 Next 系统的适配度等因素,做出合适的选择。
集成 AI API 可使用 nodejs 转发
- 在
\entry\src\main\resources\base\profile\main_pages.json
文件中配置页面:
{
"src": ["pages/Index"]
}
- 在
\entry\src\main\ets\pages
目录中添加Index.ets
import {
router } from '@kit.ArkUI';
import {
sendChat } from '../apis/chat';
const enterKeyCode = 2054;
const primaryColor = '#0ea5e9';
enum AiModel {
// 文生图
Text2Img = "ti",
// 文字对话
Text2Text = "tt",
}
interface IListItem<T> {
id: T;
name: string;
}
interface IMessage {
itemAlign: ItemAlign;
charts: string;
}
interface RoomParams {
model: AiModel;
}
interface IAiModelItem extends IListItem<AiModel> {
hello: string;
}
const aiModels: IAiModelItem[] = [
{
id: AiModel.Text2Text,
name: "聊天",
hello: '你好,我是文本生成图片的AI工具',
},
{
id: AiModel.Text2Img,
name: "文生图",
hello: '请输入绘画的内容',
}
];
@Entry
@Component
struct Room {
@State buttonScale: number = 1;
@State avatarUrl: string = '';
@State model: IAiModelItem | undefined = undefined;
@State title: string = '';
@State bone: number = 0;
@State messages: IMessage[] = [];
@State charts: string = '';
@State loading: boolean = false;
handleMessage: () => void = () => {
if (!this.charts) {
return;
}
this.messages.unshift({
itemAlign: ItemAlign.End,
charts: this.charts,
})
this.loading = true;
if (!this.model) {
return;
}
const chat = this.charts;
if (!chat) {
AlertDialog.show({
message: '内容输入异常,请联系管理员' });
return;
}
sendChat({
model: this.model.id,
chat: chat
}).then((res) => {
this.charts = '';
this.loading = false
this.messages.unshift({
itemAlign: ItemAlign.Start, charts: res })
}).catch((e: string) => {
console.log('发送失败' + e);
})
}
onPageShow(): void {
this.avatarUrl = '/images/logo.png';
const params = router.getParams() as RoomParams;
this.model = aiModels.find(item => item.id === params.model);
const storage = preferences.getPreferencesSync(getContext(), {
name: 'storage' });
const user = storage.getSync('user', null) as IUser;
if (user) {
this.bone = user.bone;
}
}
@Builder
renderTextChat(text: string) {
Text(text)
.alignSelf(ItemAlign.Start)
.backgroundColor('white')
.padding(8)
.borderRadius(8)
.fontColor('black')
}
scroller: Scroller = new Scroller();
build() {
Column() {
Row({
space: 12 }) {
Image('images/back.png').onClick(() => {
router.back()
}).width(24).height(24)
if (this.avatarUrl) {
Image(this.avatarUrl).width(32).height(32).borderRadius(50)
}
if (this.model) {
Column({
space: 4 }) {
Text(this.model.name).fontWeight(FontWeight.Medium).fontSize(16);
}.alignItems(HorizontalAlign.Start).flexGrow(1)
}
Text('举报').onClick(() => {
router.replaceUrl({
url: 'pages/Report'
})
})
}
.width('100%')
.backgroundColor('#ffffff')
.padding(8)
.alignItems(VerticalAlign.Center)
.position({
left: 0, top: 0 })
.zIndex(1)
Scroll() {
Column({
space: 12 }) {
if (this.loading) {
this.renderTextChat('...')
}
ForEach(this.messages, (item: IMessage) => {
if (item.itemAlign === ItemAlign.End) {
Text(item.charts)
.alignSelf(item.itemAlign)
.backgroundColor(primaryColor)
.padding(8)
.borderRadius(8)
.fontColor('white')
}
if (item.itemAlign === ItemAlign.Start) {
if (this.model?.id === AiModel.Text2Text) {
this.renderTextChat(item.charts);
}
if (this.model?.id === AiModel.Text2Img) {
Column() {
Image(item.charts).width(200).height(200).borderRadius(4)
}.alignSelf(ItemAlign.Start).borderRadius(8).backgroundColor('white').padding(8)
}
}
})
if (this.model) {
this.renderTextChat(this.model.hello)
}
}.width('100%')
}
.width('100%')
.padding({
top: 128,
bottom: 0,
left: 12,
right: 12
})
Column() {
Row() {
TextInput({
text: this.charts, placeholder: this.bone + ' (签到领取)' })
.onChange(v => {
this.charts = v;
})
.backgroundColor('transparent')
.defaultFocus(true)
.onKeyEvent(e => {
if (e.keyCode === enterKeyCode) {
this.handleMessage();
}
})
.flexGrow(1)
.height(60)
Image('/images/bone.svg')
.onClick(this.handleMessage)
.width(36)
.animation({
duration: 200, curve: Curve.Ease })
.clickEffect({
level: ClickEffectLevel.LIGHT, scale: 0.5 })
.position({
right: 0, top: 12 })
}
.width('100%')
.borderRadius(20)
.padding({
right: 20 })
.alignItems(VerticalAlign.Center)
.backgroundColor('#ffffff')
.justifyContent(FlexAlign.SpaceBetween)
.shadow(ShadowStyle.OUTER_DEFAULT_XS)
}
.padding({
left: 12,
right: 12,
top: 12,
bottom: 12
})
.position({
top: 48, left: 0 })
}.width('100%').height('100%')
}
}
- 在
\entry\src\main\ets\apis\chat
目录下添加如下代码
// 引入包名
import {
http } from "@kit.NetworkKit";
import {
BusinessError } from "@kit.BasicServicesKit";
import {
router } from "@kit.ArkUI";
const URL_BASE = "https://******/api";
// const URL_BASE = 'http://localhost:8081/api';
function sendHttp<R, T>(path: string, method: http.RequestMethod, data?: T): Promise<R> {
// 每一个httpRequest对应一个HTTP请求任务,不可复用
const httpRequest = http.createHttp();
return new Promise((res, rej) => {
httpRequest.request(
// 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定
URL_BASE + path,
{
method: method, // 可选,默认为http.RequestMethod.GET
// 当使用POST请求时此字段用于传递请求体内容,具体格式与服务端协商确定
extraData: data || null,
expectDataType: http.HttpDataType.STRING, // 可选,指定返回数据的类型
usingCache: true, // 可选,默认为true
priority: 1, // 可选,默认为1
// 开发者根据自身业务需要添加header字段
header: {
"content-type": "application/json", auth: "huawei" },
readTimeout: 60000, // 可选,默认为60000ms
connectTimeout: 60000, // 可选,默认为60000ms
usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定
usingProxy: false, //可选,默认不使用网络代理,自API 10开始支持该属性
},
(err: BusinessError, resp: http.HttpResponse) => {
httpRequest.destroy();
if (err) {
console.log(err.message);
rej(500);
return;
}
if (resp.responseCode === 401) {
router.replaceUrl({
url: "pages/Index" });
rej(401);
return;
}
if (resp.responseCode !== 200) {
console.log(resp.result + "");
rej(resp.responseCode);
httpRequest.destroy();
return;
}
try {
res(JSON.parse(resp.result as string));
} catch (e) {
res(resp.result as R);
}
}
);
});
}
interface IChatOption {
model: AiModel;
chat: string;
}
export function sendChat(data: IChatOption) {
return sendHttp<string, IChatOption>("/ai", http.RequestMethod.POST, data);
}
服务器代码 以 nextjs + api 为例
import {
NextApiRequest, NextApiResponse } from "next";
// *********************************************鸿蒙next********************************************
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") return res.status(405);
const {
chat = "" } = req.body;
if (!chat.trim()) {
return res.status(400).send("no chat");
}
const resp = await axios.post(
"https://ai.gitee.com/api/serverless/Qwen2.5-72B-Instruct/chat/completions",
{
stream: false,
max_tokens: 512,
temperature: 0.7,
frequency_penalty: 1,
messages: [
{
role: "user",
content: chat,
},
],
},
{
headers: {
Authorization: `Bearer 【此处是密钥】`,
"Content-Type": "application/json",
},
}
);
const content = resp.data.choices[0]?.message.content;
res.status(200).send(content);
res.status(405).send(405);
}
至此就从服务端、客户端实现了调用AI api的功能
功能测试与优化
完成功能开发后,要在不同设备与网络环境下对应用进行全面测试。检查用户输入的文字能否准确传递给 AI API,AI 回复是否符合预期,多轮对话时上下文信息的传递是否正确,以及应用在长时间对话中的性能表现等。针对测试中发现的问题,如响应延迟、回复不准确等,及时优化代码与调整配置,确保为用户提供流畅、高效的文字对话服务。
通过以上步骤,开发者能够在鸿蒙 Next 系统中成功对接 AI API,实现强大的文字对话功能,为用户带来智能化的交互体验。在开发过程中,需充分利用鸿蒙系统资源,严格遵循开发规范,持续打磨细节,才能打造出优质的应用。