当前位置:首页>鸿蒙APP>鸿蒙仓颉编程语言挑战赛三等奖作品:MeetWise智能会议助手

鸿蒙仓颉编程语言挑战赛三等奖作品:MeetWise智能会议助手

  • 2026-10-31 14:12:29
鸿蒙仓颉编程语言挑战赛三等奖作品:MeetWise智能会议助手

01

一、 团队介绍

我们是来自金陵科技学院的五人开发团队,在本次鸿蒙仓颉挑战赛中凭借MeetWise智能会议助手项目获得佳绩。

团队成员:

  • 徐州(队长):项目总体规划与核心业务全流程开发,负责AI服务层(IFlyTekService.cj、LLMService.cj)的对接与封装,以及录音详情页、待办模块和笔记模块的全栈开发

  • 朱欣龙(全栈开发工程师):聚焦录音交互与数据基础设施,主导Recording.ets页面的交互逻辑实现,管理录音状态机,开发登录/注册模块及用户数据管理,设计基于RdbStore(SQLite)的本地存储方案

  • 宋忠呈(前端与动效设计师):负责个人中心模块开发(MinePage.ets、ProfilePage.ets、SettingsPage.ets),基于ArkUI Canvas组件优化录音时的实时声波纹动效,提升用户视觉体验

  • 唐志颖(核心逻辑与优化工程师):参与RecordingLogic.cj的开发,优化AudioCapturer音频流采集逻辑,针对讯飞转写结果进行去重算法优化,提升实时文字上屏速度与准确率,负责录音功能的稳定性测试与错误修复

  • 吴仔琪(产品与设计师):负责UI/UX高保真原型设计,制定应用视觉规范,梳理项目技术架构,总结ArkTS与Cangjie混合开发的最佳实践,制作项目路演PPT,剪辑与后期制作产品演示视频

02

二、项目核心

Why:开发初衷

在数字化办公时代,会议记录与信息整理成为职场痛点。传统录音工具缺乏智能化处理能力,无法满足高效会议管理需求。我们致力于打造一款集实时转写、智能摘要、任务管理于一体的智能会议助手,解决以下核心问题:

  1. 会议记录效率低下:传统手写记录方式耗时费力,容易遗漏关键信息

  2. 信息检索困难:纯音频文件难以快速定位特定内容

  3. 会议成果转化慢:会后整理会议纪要需要大量人工操作

  4. 任务跟进缺失:会议中产生的待办事项难以有效跟踪

What:核心功能

实时语音转写:集成讯飞开放平台实时转写能力,准确率达95%以上

AI智能摘要:基于智谱GLM-4大模型生成会议摘要,自动提取关键信息点

笔记编辑:以瀑布流或列表形式展示用户创建的会议笔记。

待办事项管理: 支持“全部”、“进行中”、“已完成”标签切换。

已关注
关注
重播 分享 赞

How:关键技术

项目采用HarmonyOS原生开发架构,融合ArkTS与Cangjie语言优势:

AI服务层封装(IFlyTekService.cj)

 // 讯飞实时转写服务封装示例 public class IFlyTekService {    private var socketClient: WebSocketClient;    private var transcriptionBuffer: MutableList<String>;    public func initialize(): Result<Void, Error> {        // 初始化WebSocket连接        this.socketClient = new WebSocketClient();        this.transcriptionBuffer = newMutableList<String>();        // 配置连接参数        this.socketClient.setUrl("wss://api.iflyrec.com/ws/v1");        this.socketClient.setOnMessageListener((message:String) -> {            // 处理转写结果this.handleTranscriptionResult(message);        });        return Result.success(());    }    public func startRealTimeTranscription(audioStream: AudioStream):Result<Void, Error> {        // 开始实时转写逻辑        try {            this.socketClient.connect();            // 启动音频流采集            audioStream.startCapture((data:AudioData) -> {                // 发送音频数据到服务器this.sendAudioData(data);            });            return Result.success(());        } catch (error) {            return Result.failure(error);        }    }    private func sendAudioData(data: AudioData): Void {        // 将音频数据转换为Base64并发送        var base64Data = Base64.encode(data.buffer);        this.socketClient.send(base64Data);    }    private func handleTranscriptionResult(message: String): Void {        // 解析转写结果并更新UI        var result = Json.parse(message);        var text = result.get("text").asString();        if (!text.isEmpty()) {            this.transcriptionBuffer.add(text);            // 通知UI更新            EventManager.emit("transcriptionUpdate",text);        }    } }

大语言模型服务封装(LLMService.cj)

  // 智谱GLM-4摘要服务封装 public class LLMService {    private var httpClient: HttpClient;    private var apiKey: String;    public func initialize(apiKey: String): Result<Void, Error> {        this.apiKey = apiKey;        this.httpClient = new HttpClient();        return Result.success(());    }    public func generateSummary(transcript: String): Result<String, Error> {        var request = new HttpRequest();        request.setMethod(HttpMethod.POST);        request.setUrl("https://open.bigmodel.cn/api/paas/v4/chat/completions");        // 构建请求体        var requestBody = Json.object()            .set("model", "glm-4")            .set("messages", Json.array(                Json.object()                    .set("role", "system")                    .set("content", "你是会议纪要助手,请根据会议内容生成简洁准确的摘要"),                Json.object()                    .set("role", "user")                    .set("content", "请根据以下会议内容生成摘要:\n\n" + transcript)            ))            .set("temperature", 0.7);        request.setBody(requestBody.toString());        request.setHeader("Authorization", "Bearer " + this.apiKey);        request.setHeader("Content-Type", "application/json");        try {            var response = this.httpClient.execute(request);            var result = Json.parse(response.getBody());            var summary = result.get("choices").get(0).get("message").get("content").asString();            return Result.success(summary);        } catch (error) {            return Result.failure(error);        }    } }

数据库访问对象(MeetingRecordDAO.ets)

import { RdbStore, RdbPredicates } from '@kit.ArkData';import { MeetingRecord } from '../model/MeetingRecord';const TABLE_NAME = 'meeting_records';const CREATE_TABLE_SQL = `   CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (     id INTEGER PRIMARY KEY AUTOINCREMENT,     title TEXT NOT NULL,     content TEXT,     createTime INTEGER,     audioPath TEXT,     summary TEXT   ) `;export class MeetingRecordDAO {private rdbStore: RdbStore;constructor(rdbStore: RdbStore) {this.rdbStore = rdbStore; }async createTable(): Promise<void> {await this.rdbStore.executeSql(CREATE_TABLE_SQL, []); }async insert(record: MeetingRecord): Promise<number> {     const valuesBucket = {       'title': record.title,       'content': record.content,       'createTime': record.createTime,       'audioPath': record.audioPath,       'summary': record.summary || ''    };     return await this.rdbStore.insert(TABLE_NAME, valuesBucket);  }async queryAll(): Promise<MeetingRecord[]> {const predicates = new dataRdb.RdbPredicates(TABLE_NAME);const resultSet = await this.rdbStore.query(predicates);return this.mapResultSetToRecords(resultSet);  }async queryById(id: number): Promise<MeetingRecord | null> {const predicates = new dataRdb.RdbPredicates(TABLE_NAME) .equalTo('id', id);const resultSet = await this.rdbStore.query(predicates);     const records = this.mapResultSetToRecords(resultSet);     return records.length > 0 ? records[0] : null;  }async update(record: MeetingRecord): Promise<number> {const valuesBucket = {'title': record.title,       'content': record.content,       'createTime': record.createTime,       'audioPath': record.audioPath,       'summary': record.summary    };     const predicates = new dataRdb.RdbPredicates(TABLE_NAME)      .equalTo('id', record.id);     return await this.rdbStore.update(valuesBucket, predicates);  }async delete(id: number): Promise<number> {const predicates = new dataRdb.RdbPredicates(TABLE_NAME) .equalTo('id', id);return await this.rdbStore.delete(predicates);  }private mapResultSetToRecords(resultSet: dataRdb.ResultSet): MeetingRecord[] {const records: MeetingRecord[] = [];if (resultSet.rowCount > 0) {while (resultSet.goToNextRow()) {const record: MeetingRecord = {           id: resultSet.getLong(resultSet.getColumnIndex('id')),           title: resultSet.getString(resultSet.getColumnIndex('title')),           content: resultSet.getString(resultSet.getColumnIndex('content')),           createTime: resultSet.getLong(resultSet.getColumnIndex('createTime')),           audioPath: resultSet.getString(resultSet.getColumnIndex('audioPath')),           summary: resultSet.getString(resultSet.getColumnIndex('summary'))        };         records.push(record);      }    }     return records;  } }

音频播放器(AudioPlayer.cj)

// 音频播放器封装 public class AudioPlayer {    private var player: AVPlayer;    private var currentPosition: Int64;    private var duration: Int64;    public func initialize(): Result<Void, Error> {        this.player = new AVPlayer();        this.currentPosition = 0;        this.duration = 0;        // 设置播放状态监听        this.player.setOnPlayEventListener((event: PlayEvent) -> {            switch (event.getType()) {                case PlayEvent.Type.PLAY:                    EventManager.emit("playStarted", ());                    break;                case PlayEvent.Type.PAUSE:                    EventManager.emit("playPaused", ());                    break;                case PlayEvent.Type.STOP:                    EventManager.emit("playStopped", ());                    break;                case PlayEvent.Type.COMPLETION:                    EventManager.emit("playCompleted", ());                    break;            }        });        return Result.success(());    }    public func loadAudio(filePath: String): Result<Void, Error> {        try {            var source = new AVSource();            source.setSourceFromUri(filePath);            this.player.setAVSource(source);            this.player.prepare();            this.duration = this.player.getDuration();            return Result.success(());        } catch (error) {            return Result.failure(error);        }    }    public func play(): Result<Void, Error> {        try {            this.player.play();            return Result.success(());        } catch (error) {            return Result.failure(error);        }    }    public func pause(): Result<Void, Error> {        try {            this.player.pause();            return Result.success(());        } catch (error) {            return Result.failure(error);        }    }    public func stop(): Result<Void, Error> {        try {            this.player.stop();            return Result.success(());        } catch (error) {            return Result.failure(error);        }    }    public func seekTo(position: Int64): Result<Void, Error> {        try {            this.player.seek(position, SeekMode.EXACTLY);            this.currentPosition = position;            return Result.success(());        } catch (error) {            return Result.failure(error);        }    }    public func getCurrentPosition(): Int64 {        return this.player.getCurrentTimeMs();    }    public func getDuration(): Int64 {        return this.duration;    } }

03

三、项目创新点

1. HarmonyOS NEXT 原生“双引擎”架构

首创 ArkTS(声明式 UI)与 Cangjie(高性能逻辑)混合架构。ArkTS 确保交互丝滑,仓颉处理高并发逻辑,实现了表现层与逻辑层的彻底解耦。

2. 智能会议全链路生产力闭环

打通了“高清录音 → 实时精准转写 → AI 摘要生成 → 结构化笔记/待办持久化”的端到端自动化流程,将传统碎片化的会议整理工作效率提升 10 倍以上。

3. 基于物理信号的非线性波形渲染

基于 PCM 采样数据,通过 RMS 均方根计算与对数分贝(dB)转换算法驱动视觉波形,提供工业级、真实起伏的交互反馈。

04

四、核心难点攻克

1. 软硬件协同下的架构转型

挑战 :初期尝试全仓颉开发录音功能,但受限于生态初期仓颉对底层硬件 API(AudioCapturer)的调用限制。

攻克 :果断切换为“硬件接口层(ArkTS)+ 核心处理层(仓颉)”架构。由 ArkTS 稳定采集流数据,透传给仓颉引擎进行高性能分贝计算、加密及 AI 处理,确保了功能的完整性与运行的高效率。

2. 跨语言高频数据同步与性能优化

挑战 :每秒上万个采样点的分贝数据及转写片段跨语言传输易导致 UI 卡顿。

攻克 :在 RecordingLogic.cj 中设计了节流回调机制,利用仓颉轻量级线程处理计算任务,确保了在复杂后台逻辑运行时,UI 列表滑动依然维持在 60 帧。

3. 实时转写结果的“去重与智能合并”算法

挑战 :网络波动导致的 WebSocket 乱序与重复推送会造成界面文字“跳变”闪烁。

攻克 :设计了基于 seg_id 的状态机管理策略,通过智能判断“中间态”与“最终态”文本进行按需刷新,将 UI 无效刷新率降低了 60%。

4. 极端环境下的“录音不丢失”稳定性保障

挑战 :应对断网、内存溢出或意外崩溃等极端场景,确保用户核心数据资产安全。

攻克 :采用“物理写入与在线转写分离”技术,配合指数退避重连算法。即使网络彻底中断, MeetingRecordDAO.ets 也能确保本地音频文件安全存盘。

5. 应用级全局状态的响应式同步

挑战 :多页面间用户信息、录音状态、业务数据的一致性同步难题。

攻克 :基于鸿蒙原生 StorageLink 构建应用级状态总线,建立单源数据池。实现了个人资料、业务状态在全应用内的实时感知与无感同步。

05

五、 感悟经验

1.  技术选型的重要性:

ArkTS+Cangjie的组合为项目提供了强大的性能基础,合理的技术架构是项目成功的关键。HarmonyOS的分布式能力也为我们的应用扩展提供了想象空间。

2.  团队协作的力量:

清晰的职责分工与高效的沟通机制,让我们能够在短时间内完成复杂功能的开发。每周的代码评审和进度同步确保了项目质量与进度。

3.  用户体验导向:

从用户实际需求出发,不断迭代优化功能,才是产品价值的核心所在。我们在开发过程中多次进行用户测试,根据反馈调整产品方向。

4.  持续学习精神:

面对新技术栈和复杂的AI集成,团队成员始终保持学习热情,通过官方文档、社区交流等方式快速掌握所需技能。

06

六、项目源码地址:

https://openatom.tech/cangjiechallenge/06ecb1ed6d7c99e118db3b63e3339ba5.git

领取仓颉专属红包封面!

发放时间2月12日 20:00

定好闹钟~扫码领取~

发放时间2月13日 12:00

定好闹钟~扫码领取~

发放时间2月13日 20:00

定好闹钟~扫码领取~

往期文章:

鸿蒙仓颉编程语言挑战赛圆满收官:创新应用引领智慧办公新生态

鸿蒙仓颉编程语言挑战赛一等奖作品:MeetAI-基于Cangjie的智能会后整理助手

鸿蒙仓颉编程语言挑战赛二等奖作品 :以仓颉之码,筑智慧学园——基于仓颉与OpenHarmony的智慧校园协同管控系统实践

鸿蒙仓颉编程语言挑战赛二等奖作品:TaskGenie 打造基于仓颉语言的智能办公“任务中枢”

鸿蒙仓颉编程语言挑战赛三等奖作品:基于仓颉语言的AI智能办公协同平台实战

鸿蒙仓颉编程语言挑战赛三等奖作品:安全会议助手

鸿蒙仓颉编程语言挑战赛获奖作品:基于仓颉语言开发的鸿蒙原生应用——《仓颉迹》开发实践

最新文章

随机文章