欢迎光临
我们一直在努力

鸿蒙应用 AI 对话实战:用蓝耘 MaaS 给礼物 App 装上「多轮对话选礼大脑」

鸿蒙 AI 对话实战:用蓝耘 MaaS 给礼物 App 装上「多轮对话选礼大脑」

当别人还在用表单填参数调 AI 时,我们直接在鸿蒙里做了一个完整的聊天界面——多轮对话、模型热切换、快捷提示词、Markdown 清洗,全部原生 ArkTS 实现,后端只靠蓝耘 MaaS 一个 API Key。 在这里插入图片描述

一、为什么礼物 App 需要 AI?

送礼是门玄学。预算 500 送女友、300 送爸爸、800 送朋友结婚——每个人、每个场合、每个预算,最优解都不同。

传统礼物 App 只能做静态推荐:编辑写好的榜单、用户手动筛选。但送礼这件事本质是对话:

“她喜欢文艺和摄影” → “那拍立得怎么样?” → “会不会太普通?” → “加点手账本呢?”

这种来回沟通,表单做不到,只有多轮对话才能胜任。而多轮对话的核心是上下文记忆——AI 要记住前面聊了什么。

本文完整拆解:如何在 HarmonyOS 上用蓝耘 MaaS 实现一个真正的多轮对话式 AI 选礼助手。 在这里插入图片描述

在这里插入图片描述

二、先看效果:蓝耘 MaaS 真实调用

在这里插入图片描述

以下内容全部来自蓝耘 MaaS API 的真实返回。

蓝耘 MaaS(元生代 MaaS)模型即服务(Model-as-a-Service)平台,它以 OpenAI 兼容的 Chat Completions 协议统一封装了 DeepSeek、Kimi、Qwen、智谱 GLM、MiniMax 等多家主流大模型的 API 调用,开发者只需一个 API Key即可用同一套 SDK(如 openai Python 库)按需切换调用不同模型,无需各自对接各家厂商,本质是一个多模型聚合的一站式大模型推理网关。 https://maas.lanyun.net/#/model/modelSquare 在这里插入图片描述

2.1 调用方式

蓝耘 MaaS 兼容 OpenAI Chat Completions 协议:

curl -X POST https://maas-api.lanyun.net/v1/chat/completions \\
-H "Authorization: Bearer sk-你的Key" \\
-d '{
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "你是蓝耘AI选礼助手…"},
{"role": "user", "content": "我女朋友下周过生日,预算500元,她喜欢文艺和摄影,请推荐3个礼物方案。"}
],
"max_tokens": 2048,
"temperature": 0.8
}'

2.2 调用 1:deepseek-v4-flash — 送女友生日礼物

Prompt:我女朋友下周过生日,预算500元左右,她喜欢文艺和摄影,请推荐3个礼物方案

蓝耘 AI 真实返回:

好的!😊 根据你女朋友的文艺摄影爱好和500元预算,我为你精选了3个走心又实用的礼物方案:

方案一:Lomography 拍立得相机(如 Diana F+ 或 Lomo’Instant)

  • 推荐理由:文艺复古造型,自带柔焦与漏光效果,出片有胶片感,非常适合喜欢摄影的女生
  • 预估价格:¥380-480(含一盒相纸)

方案二:手工牛皮相机背带 + 文艺帆布相机包组合

  • 推荐理由:如果她已有相机,一条质感牛皮背带能瞬间提升文艺气质;搭配复古帆布包,日常出门装相机、笔记本都很方便
  • 预估价格:¥200-400

方案三:摄影画册《光影集》+ 复古胶卷钥匙扣

  • 推荐理由:一本大师级摄影集能让她在翻页中感受光影美学,附赠复古胶卷造型钥匙扣
  • 预估价格:¥150-250 + ¥30-50

usage:prompt_tokens: 75, completion_tokens: 458, reasoning_tokens: 34, total: 533

2.3 调用 2:kimi-k2.5 — 同一 Key 换模型,送朋友结婚

只改 "model": "kimi-k2.5",其余参数不动:

Prompt:好朋友要结婚了,预算800元,请推荐合适的结婚礼物

蓝耘 AI 真实返回:

恭喜你的朋友!🎉 800元预算可以选到既体面又有心意的结婚礼物:

🏠 实用家电类 | 礼物 | 参考价格 | 亮点 | | 戴森吹风机/卷发棒 | 约800元 | 高端实用,送礼有面子 | | 胶囊咖啡机 | 500-800元 | 提升生活幸福感 | | 智能扫拖机器人 | 700-800元 | 解放双手,懒人福音 |

🍽️ 品质餐具/厨具

  • 双立人/WMF锅具套装 — 600-800元,"锅"满幸福
  • Wedgwood骨瓷香槟杯 — 500-800元,仪式感强

💡 最稳妥选法:Wedgwood香槟杯一对(约600元)+ 红包200元 → 既有品牌质感,又有现金实用,寓意"一杯子一辈子"🥂

usage:prompt_tokens: 60, completion_tokens: 476, total: 536

2.4 关键发现

观察说明
同一个 API Key 两次调用用完全相同的 Key
同一个 URL 都是 https://maas-api.lanyun.net/v1/chat/completions
只改 model 字段 请求体其他参数不变
模型各有特色 DeepSeek 推理细致 · Kimi 表格输出规整
usage 透明 含 reasoning_tokens,成本精确可控

这就是蓝耘 MaaS 的核心价值:一次对接,六个模型随意切,代码零改动。

在这里插入图片描述

三、项目架构

在这里插入图片描述

64-gift-mgr/
├── entry/src/main/ets/
│ ├── common/
│ │ ├── LanYunAI.ets ← 蓝耘 MaaS 服务层 + Prompt + 快捷词
│ │ └── Theme.ets ← 暖橙主色 + 蓝耘品牌色
│ ├── pages/
│ │ ├── Index.ets ← 3 Tab(首页 · AI选礼 · 我的)
│ │ ├── HomeTab.ets ← 倒计时 + AI入口 + 创意/时间轴切换
│ │ ├── AITab.ets ← 对话式 AI 选礼助手(核心)
│ │ └── ProfileTab.ets ← 蓝耘平台信息展示
│ └── entryability/
│ └── EntryAbility.ets ← 安全区全区初始化
└── module.json5 ← 网络权限

3 Tab 架构:

Tab功能AI 集成
首页 倒计时 + 快捷功能 + 礼物创意 + 送礼时间轴 AI 入口大卡片(3 个快捷场景)
AI选礼 多轮对话聊天界面 核心页面,6 模型热切换
我的 统计 + 菜单 + 关于 蓝耘平台信息卡

四、核心:对话式 AI 选礼助手

4.1 交互设计

与表单式 AI 不同,这里做的是真正的聊天界面:

在这里插入图片描述

设计要点:

  • 用户消息:橙色右气泡(应用主色)
  • AI 消息:蓝耘蓝左气泡 + 蓝耘头像(品牌色区分)
  • Loading 状态:AI 气泡内显示「蓝耘 AI 正在思考…」+ 转圈动画
  • 快捷提示词:横滑标签,点击即发送
  • 底部输入栏:TextInput + 蓝耘渐变发送按钮

4.2 消息气泡实现

// 用户消息气泡(右对齐,橙色渐变)
@Builder
UserBubble(m: Msg) {
Row() {
Column() {
Text(m.content)
.fontSize(14).fontColor('#FFFFFF').lineHeight(22)
.padding({ left: 14, right: 14, top: 10, bottom: 10 })
.borderRadius(D.rMd)
.linearGradient(C.gradPrimary) // 暖橙渐变
}.constraintSize({ maxWidth: '78%' }) // 限制最大宽度

Blank().layoutWeight(1) // 右对齐
}.width('100%')
}

// AI 消息气泡(左对齐,蓝耘蓝)
@Builder
AIBubble(m: Msg) {
Row({ space: 8 }) {
// 蓝耘头像
Column() {
Text('耘').fontSize(12).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
}
.width(30).height(30).borderRadius(8)
.linearGradient(C.gradLanYun) // 蓝耘渐变
.justifyContent(FlexAlign.Center)
.alignSelf(ItemAlign.Start)

Column() {
if (m.loading) {
Row({ space: 6 }) {
LoadingProgress().width(16).height(16).color(C.lanYun)
Text('蓝耘 AI 正在思考…').fontSize(13).fontColor(C.textDim)
}
.padding(12).backgroundColor(C.lanYunBubble).borderRadius(D.rMd)
} else {
Text(this.stripMd(m.content))
.fontSize(14).fontColor(C.text).lineHeight(23)
.padding(12).backgroundColor(C.lanYunBubble).borderRadius(D.rMd)
}
}.constraintSize({ maxWidth: '80%' })
}.width('100%')
}

双色气泡策略:用户消息用 App 主色(暖橙),AI 消息用蓝耘品牌色(蓝),用户一眼能区分「这是我说的话」和「这是 AI 回的话」。 在这里插入图片描述

4.3 多轮对话:上下文记忆

这是对话式 AI 的核心。每次发送时,把最近 10 条消息作为上下文一起发给蓝耘 MaaS:

private doSend(userText: string): void {
// 1. 添加用户消息到列表
this.msgId++;
this.messages.push({ id: this.msgId, role: 'user', content: userText, loading: false });
this.messages = this.messages.slice();

// 2. 添加 AI loading 占位消息
this.msgId++;
const aiMsgId = this.msgId;
this.messages.push({ id: aiMsgId, role: 'assistant', content: '', loading: true });
this.messages = this.messages.slice();
this.sending = true;

// 3. 构建对话历史(System + 最近 10 条)
const history: ChatMessage[] = [
{ role: 'system', content: GIFT_SYSTEM_PROMPT }
];
const recent = this.messages
.filter(m => m.id < aiMsgId && m.content.length > 0)
.slice(10); // 只取最近 10 条,控制 token 消耗
for (const m of recent) {
history.push({ role: m.role, content: m.content });
}

// 4. 调用蓝耘 MaaS
const model = LAN_YUN_MODELS[this.currentModel];
chat(history, model).then((reply: string) => {
// 5. 替换 loading 消息为真实回复
const idx = this.messages.findIndex(m => m.id === aiMsgId);
if (idx >= 0) {
this.messages.splice(idx, 1, {
id: aiMsgId, role: 'assistant',
content: reply, loading: false,
});
this.messages = [this.messages]; // 触发 UI 更新
}
this.sending = false;
this.scrollToBottom();
});
}

为什么取最近 10 条而不是全部?

  • Token 消耗:每条历史都会计入 prompt_tokens,全量发送成本高
  • 上下文窗口:模型有最大上下文限制,10 条足够覆盖大多数对话场景
  • 响应速度:历史越短,首 token 延迟越低

4.4 System Prompt 设计

export const GIFT_SYSTEM_PROMPT: string =
'你是「蓝耘 AI 选礼助手」,集成在礼物管理 App 中,由蓝耘元生代 MaaS 平台驱动。' +
'你的任务是根据收礼人信息、场合、预算等,推荐合适的礼物方案。' +
'回答要实用、有条理,使用中文,适当使用 emoji。' +
'推荐礼物时请给出:礼物名称、推荐理由、预估价格。' +
'如果用户预算有限,请优先推荐性价比高的选择。';

5 句话设定了:身份、平台来源、任务、输出格式、特殊策略。简洁但完整。

4.5 快捷提示词

6 个预置提示词,横滑展示,点击即发送:

export const QUICK_PROMPTS: QuickPrompt[] = [
{ emoji: '👩', label: '送女友', prompt: '我女朋友下周过生日,预算500元左右…' },
{ emoji: '👨', label: '送爸爸', prompt: '父亲节快到了,预算300元,爸爸喜欢喝茶和钓鱼…' },
{ emoji: '👶', label: '送孩子', prompt: '侄子满月了,预算200元…' },
{ emoji: '🧑', label: '送朋友', prompt: '好朋友要结婚了,预算800元…' },
{ emoji: '💐', label: '写贺卡', prompt: '帮我写一段生日贺卡文案…' },
{ emoji: '💰', label: '预算规划', prompt: '这个月有3个人需要送礼…' },
];

用户不用打字,点一下就能开始对话。这对移动端体验至关重要。


在这里插入图片描述

五、蓝耘 MaaS 服务层

5.1 核心调用函数

export async function chat(
messages: ChatMessage[],
model: string = DEFAULT_MODEL,
maxTokens: number = 2048,
temperature: number = 0.8
): Promise<string> {
const client = http.createHttp();

// 超时兜底:90 秒
const timeoutPromise = new Promise<string>((_, reject) => {
setTimeout(() => reject(new Error('请求超时(90s)')), 90000);
});

const requestPromise = new Promise<string>(async (resolve, reject) => {
try {
const resp = await client.request(BASE_URL + '/chat/completions', {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + API_KEY,
},
extraData: JSON.stringify({
model, messages, max_tokens: maxTokens,
temperature, stream: false,
}),
connectTimeout: 30000,
readTimeout: 60000,
});

if (resp.responseCode !== 200) {
resolve('[请求失败] 状态码: ' + resp.responseCode);
return;
}

const json: ChatResponse = JSON.parse(`${resp.result}`);
const msg = json.choices?.[0]?.message;
if (!msg) { resolve('[蓝耘 AI 返回为空]'); return; }

// content 优先,reasoning_content 兜底
const content = msg.content ?? '';
const reasoning = msg.reasoning_content ?? '';
resolve(content.length > 0 ? content
: reasoning.length > 0 ? reasoning
: '[蓝耘 AI 返回为空]');
} catch (e) {
reject(e);
}
});

try {
return await Promise.race([requestPromise, timeoutPromise]);
} catch (e) {
return '[请求异常] ' + (e as Error).message;
} finally {
client.destroy(); // 关键:防止资源泄漏
}
}

5.2 鸿蒙特有的三个坑

坑原因解决
http.createHttp() 内存泄漏 每次调用创建新实例 finally { client.destroy(); }
请求卡死不返回 鸿蒙 http 超时机制不完善 Promise.race + 90s 兜底
DeepSeek 返回空 content 推理模型内容在 reasoning_content fallback 到 reasoning_content

5.3 模型列表与热切换

export const LAN_YUN_MODELS: string[] = [
'deepseek-v4-flash', // 快速响应 · 日常推荐
'/maas/deepseek-ai/DeepSeek-V3.2', // 深度推理 · 复杂选礼
'kimi-k2.5', // 超长上下文 · 详细方案
'qwen3.6-flash', // 极速生成 · 短文本
'/maas/zhipuai/GLM-5.2', // 中文优化 · 自然表达
'minimax-m3', // 创意发散 · 独特推荐
];

用户在聊天页点右上角 ⚙️ 弹出模型选择器,切换后下次对话即用新模型。同一个 chat() 函数,只改 model 参数,6 个模型随意切。


六、首页:AI 入口 + 创意/时间轴

6.1 AI 入口大卡片

在这里插入图片描述

首页核心是蓝耘 AI 入口卡片——主入口 + 3 个快捷场景:

@Builder
AIEntry() {
Column({ space: 0 }) {
// 主入口:点击跳转 AI 对话页
Row({ space: 14 }) {
Column() { Text('耘').fontSize(22).fontColor('#FFFFFF') }
.width(52).height(52).borderRadius(14)
.linearGradient(C.gradLanYun)

Column({ space: 4 }) {
Text('蓝耘 AI 选礼助手').fontSize(16).fontWeight(FontWeight.Bold)
Text('不知道送什么?问蓝耘 AI').fontSize(12).fontColor(C.textDim)
}.layoutWeight(1)

Text('→').fontSize(20).fontColor(C.lanYun)
}
.padding(16).onClick(() => { goAI(''); })

Divider().color(C.stroke)

// 三个快捷场景:点击跳转并预填提示词
Row() {
this.AISubEntry('👩', '送女友', '我女朋友下周过生日…')
this.AISubEntry('👨', '送爸爸', '父亲节快到了…')
this.AISubEntry('🧑', '送朋友', '好朋友要结婚了…')
}
}
.backgroundColor(C.card).borderRadius(D.rLg)
.border({ width: 1, color: C.lanYunSoft })
}

6.2 跨页面预填提示词

点击「送女友」等快捷场景,跳转到 AI 对话页并自动填充对应提示词:

// HomeTab — 点击快捷场景
function goAI(prompt: string): void {
AppStorage.setOrCreate('aiPresetPrompt', prompt);
AppStorage.setOrCreate('currentTab', 1); // 切到 AI Tab
}

// AITab — 进入时读取预填
aboutToAppear(): void {
if (this.presetPrompt.length > 0) {
this.inputText = this.presetPrompt; // 自动填入输入框
}
}

用户点「送女友」→ 跳到 AI 页 → 输入框已填好提示词 → 直接点发送 → AI 开始推荐。三步变一步。 在这里插入图片描述

6.3 创意/时间轴切换

首页底部用 Tab 切换两种视图:

  • 礼物创意:卡片列表,带爱心收藏,点击切换 liked 状态
  • 送礼时间轴:竖向时间轴 + 汇总统计(待送/总预算/本周)

@Builder
TimelineList() {
Column() {
// 汇总统计
Row() {
Column() { Text('4').fontSize(20).fontColor(C.primary); Text('待送礼物') }
Column() { Text('¥1800').fontSize(20).fontColor(C.accent); Text('总预算') }
Column() { Text('1').fontSize(20).fontColor(C.danger); Text('本周') }
}

// 时间轴
ForEach(this.timeline, (e: TimelineEvent, idx: number) => {
Row({ space: 12 }) {
Column() {
Row().width(12).height(12).backgroundColor(C.primary).borderRadius(6) // 圆点
if (idx < this.timeline.length 1) {
Column().width(2).height(48).backgroundColor(C.stroke) // 连线
}
}
// 事件卡片…
}
})
}
}


七、主题设计:暖橙 + 蓝耘蓝双色系

在这里插入图片描述

export class C {
// 应用主色:暖橙(礼物温暖)
static readonly primary: string = '#FF6B35';
static readonly accent: string = '#FF9558';
static readonly gradPrimary: LinearGradient = {
angle: 135, colors: [['#FF6B35', 0.0], ['#FF9558', 1.0]]
};

// 蓝耘品牌色(AI 组件专用)
static readonly lanYun: string = '#2563EB';
static readonly lanYunDeep: string = '#1E40AF';
static readonly lanYunBubble: string = '#EFF6FF'; // AI 气泡背景
static readonly gradLanYun: LinearGradient = {
angle: 135, colors: [['#2563EB', 0.0], ['#3B82F6', 0.5], ['#0EA5E9', 1.0]]
};
}

组件色系效果
用户消息气泡 暖橙渐变 右对齐,代表「我说的」
AI 消息气泡 蓝耘蓝 左对齐 + 蓝耘头像,代表「AI 回的」
AI 入口卡片 蓝耘蓝边框 与普通功能卡区分
倒计时卡片 暖橙渐变 App 原生功能
发送按钮 蓝耘渐变 AI 操作入口

八、Markdown 清洗

大模型返回带 Markdown 标记,鸿蒙 Text 组件不解析,需要清洗:

private stripMd(md: string): string {
let t = md;
t = t.replace(/\\*\\*(.+?)\\*\\*/g, '$1'); // **粗体**
t = t.replace(/\\*(.+?)\\*/g, '$1'); // *斜体*
t = t.replace(/^#{1,6}\\s+/gm, ''); // # 标题
t = t.replace(/^(\\d+)\\.\\s+/gm, '$1. '); // 有序列表
t = t.replace(/^[-*]\\s+/gm, '· '); // 无序列表 → ·
t = t.replace(/`(.+?)`/g, '$1'); // `代码`
return t;
}

清洗后 **方案一** 变成 方案一,- 推荐理由 变成 · 推荐理由,在 Text 组件中显示干净整洁。


九、模型选择器

底部弹层,6 个模型带描述:

@Builder
ModelSheet() {
Stack({ alignContent: Alignment.Bottom }) {
// 遮罩
Column().width('100%').height('100%').backgroundColor('#80000000')
.onClick(() => { this.showModelSheet = false; })

// 选择器
Column({ space: 16 }) {
Row() {
Text('切换蓝耘 MaaS 模型').fontSize(16).fontWeight(FontWeight.Bold)
Text('✕').onClick(() => { this.showModelSheet = false; })
}.justifyContent(FlexAlign.SpaceBetween)

ForEach(LAN_YUN_MODELS, (m: string, idx: number) => {
Row({ space: 10 }) {
Column({ space: 2 }) {
Text(m).fontSize(13)
.fontColor(this.currentModel === idx ? C.lanYun : C.textSub)
Text(this.modelDesc(idx)).fontSize(10).fontColor(C.textDim)
}.layoutWeight(1)

if (this.currentModel === idx) {
Text('✓').fontSize(16).fontColor(C.lanYun).fontWeight(FontWeight.Bold)
}
}
.padding(12).borderRadius(D.rSm)
.backgroundColor(this.currentModel === idx ? C.lanYunSoft : C.cardSoft)
.onClick(() => {
this.currentModel = idx;
this.showModelSheet = false;
promptAction.showToast({ message: '已切换至 ' + m });
})
})
}
.padding(20).backgroundColor(C.card)
.borderRadius({ topLeft: D.rXl, topRight: D.rXl })
}
}

每个模型带一行描述(快速响应 · 日常推荐 / 深度推理 · 复杂选礼),用户不用懂技术也能选。


十、完整调用链路

在这里插入图片描述

十一、踩坑总结

坑原因解决方案
http.createHttp() 内存泄漏 每次创建新实例 finally { client.destroy(); }
请求卡死不返回 鸿蒙 http 超时不完善 Promise.race + 90s 兜底
DeepSeek 返回空 content 推理模型内容在 reasoning_content fallback 读取
@State 无法跨 Tab 组件内部状态不共享 @StorageLink + AppStorage
Markdown 显示混乱 Text 不解析 Markdown stripMd() 正则清洗
splice 后 UI 不更新 ArkUI 数组监听机制 splice 后 […arr] 重新赋值
对话历史过长 token 消耗大 只取最近 10 条
气泡宽度溢出 长文本撑满屏幕 constraintSize({ maxWidth: '80%' })

十二、与表单式 AI 的对比

对比项表单式 AI(如纪念日 App)对话式 AI(本文)
交互方式 选参数 → 点生成 → 看结果 自然对话,来回追问
上下文记忆 无,每次独立 有,最近 10 轮
用户体验 像填表 像聊天
适用场景 标准化任务(祝福语/选礼) 探索性任务(不知道送什么)
实现复杂度 中(需管理消息列表 + 滚动)
AI 能力利用 单轮 多轮,充分发挥大模型能力

对话式的核心优势:用户可以追问。第一次推荐不满意,直接说「太贵了,有没有便宜点的」,AI 记住上下文继续推荐。这是表单式做不到的。


十三、总结

这个礼物 App 展示了蓝耘 MaaS 在鸿蒙上的另一种集成范式——对话式 AI:

  • 多轮对话 — 取最近 10 条历史构建上下文,AI 能记住前面聊了什么
  • 6 模型热切换 — 同一个 chat() 函数,只改 model 参数,DeepSeek/Kimi/Qwen/GLM/MiniMax 随意切
  • 聊天界面 — 双色气泡(橙=用户,蓝=AI)、Loading 动画、快捷提示词、自动滚动
  • 跨页面联动 — 首页快捷场景 → AI 对话页预填提示词,三步变一步
  • 鸿蒙原生 — 零第三方依赖,只用 @kit.NetworkKit 的 http 模块
  • 蓝耘 MaaS 的价值在于:你不需要为每个模型写不同的代码。一个 Key、一个 URL、一个函数,六个模型随意切。 对话式 AI 的复杂度在 UI 层(消息管理、气泡渲染、滚动控制),而不在 API 层——因为蓝耘 MaaS 把 API 层简化到了极致。

    赞(0)
    未经允许不得转载:171主机测评 » 鸿蒙应用 AI 对话实战:用蓝耘 MaaS 给礼物 App 装上「多轮对话选礼大脑」
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址