实现一个智能生活信息查询的小秘书功能,支持查天气、新闻、日历、汇率、笑话、故事、百科、诗词、邮编、区号、菜谱、股票、节目预告,还支持闲聊、算24点、数学计算、单位换算、购物、搜索等功能。
使用方式:
新版上线支持语音识别,按下说话,松开发送。
老版本上支持摇一摇、点界面按钮、手动输入、下拉刷新这四种方式。
扫码试用(左右皆可)
界面展示
开发资源
- 免费开放语义接口平台 olami.ai
- 微信小程序平台
- js, css
- 我自己搭建的https的语音识别API
源码分析
这里主要介绍新版本首页相关的代码,其它部分代码在 微信小程序——智能小秘“遥知之”源码分享(语义理解基于olami)(注:这个是原来不支持语音识别的版本) 的基础上,变化不怎么大,具体可参考那篇文章。
asr.js源码:
- /**
- * 作者:happycxz
- * 时间:2017.09.19
- * 源码分享链接:https://blog.csdn.net/happycxz/article/details/78024986
- *
- * https的silk语音识别API(专供微信小程序调用):https://api.happycxz.com/test/silk2asr/olami/asr
- * 该API服务搭建全过程解析及源码分享贴:https://blog.csdn.net/happycxz/article/details/78016299
- * 需要使用此API请联系作者QQ:404499164
- *
- * 遵循开放、分享、自由、免费的精神,把开源坚持到底
- */
- //获取应用实例
- var app = getApp()
- var UTIL = require('../../utils/util.js');
- var GUID = require('../../utils/GUID.js');
- var NLI = require('../../utils/NLI.js');
- const appkey = require('../../config').appkey
- const appsecret = require('../../config').appsecret
- //弹幕定时器
- var timer;
- var pageSelf = undefined;
- var doommList = [];
- class Doomm {
- constructor() {
- this.text = UTIL.getRandomItem(app.globalData.corpus);
- this.top = Math.ceil(Math.random() * 40);
- this.time = Math.ceil(Math.random() * 8 + 6);
- this.color = getRandomColor();
- this.display = true;
- let that = this;
- setTimeout(function () {
- doommList.splice(doommList.indexOf(that), 1);
- doommList.push(new Doomm());
- pageSelf.setData({
- doommData: doommList
- })
- }, this.time * 1000)
- }
- }
- function getRandomColor() {
- let rgb = []
- for (let i = 0; i < 3; ++i) {
- let color = Math.floor(Math.random() * 256).toString(16)
- color = color.length == 1 ? '0' + color : color
- rgb.push(color)
- }
- return '#' + rgb.join('')
- }
- Page({
- data: {
- j: 1,//帧动画初始图片
- isSpeaking: false,//是否正在说话
- outputTxt : "", //输出识别结果
- doommData: []
- },
- initDoomm: function () {
- doommList.push(new Doomm());
- doommList.push(new Doomm());
- doommList.push(new Doomm());
- this.setData({
- doommData: doommList
- })
- },
- onLoad: function () {
- pageSelf = this;
- this.initDoomm();
- },
- //手指按下
- touchdown: function () {
- UTIL.log("手指按下了... new date : " + new Date)
- var _this = this;
- speaking.call(this);
- this.setData({
- isSpeaking: true
- })
- //开始录音
- wx.startRecord({
- success: function (res) {
- //临时路径,下次进入小程序时无法正常使用
- var tempFilePath = res.tempFilePath;
- UTIL.log('record SUCCESS file path:' + tempFilePath)
- _this.setData({
- recordPath: tempFilePath
- });
- },
- fail: function (res) {
- //录音失败
- wx.showModal({
- title: '提示',
- content: '录音的姿势不对!',
- showCancel: false,
- success: function (res) {
- if (res.confirm) {
- UTIL.log('用户点击确定')
- return
- }
- }
- })
- }
- })
- },
- //手指抬起
- touchup: function () {
- UTIL.log("手指抬起了...")
- this.setData({
- isSpeaking: false,
- })
- clearInterval(this.timer)
- wx.stopRecord()
- var _this = this
- setTimeout(function () {
- var urls = "https://api.happycxz.com/test/silk2asr/olami/asr";
- UTIL.log(_this.data.recordPath);
- wx.uploadFile({
- url: urls,
- filePath: _this.data.recordPath,
- name: 'file',
- formData: { "appKey": appkey, "appSecret": appsecret, "userId": UTIL.getUserUnique() },
- header: { 'content-type': 'multipart/form-data' },
- success: function (res) {
- UTIL.log('res.data:' + res.data);
- var nliResult = getNliFromResult(res.data);
- UTIL.log('nliResult:' + nliResult);
- var stt = getSttFromResult(res.data);
- UTIL.log('stt:' + stt);
- var sentenceResult;
- try {
- sentenceResult = NLI.getSentenceFromNliResult(nliResult);
- } catch (e) {
- UTIL.log('touchup() 错误' + e.message + '发生在' + e.lineNumber + '行');
- sentenceResult = '没明白你说的,换个话题?'
- }
- var lastOutput = "==>语音识别结果:\n" + stt + "\n\n==>语义处理结果:\n" + sentenceResult;
- _this.setData({
- outputTxt: lastOutput,
- });
- wx.hideToast();
- },
- fail: function (res) {
- UTIL.log(res);
- wx.showModal({
- title: '提示',
- content: "网络请求失败,请确保网络是否正常",
- showCancel: false,
- success: function (res) {
- }
- });
- wx.hideToast();
- }
- });
- }, 1000)
- },
- //切换到老版本
- turnToOld: function() {
- wx.navigateTo({
- url: '../index/index',
- })
- },
- })
- function getNliFromResult(res_data) {
- var res_data_json = JSON.parse(res_data);
- var res_data_result_json = JSON.parse(res_data_json.result);
- return res_data_result_json.nli;
- }
- function getSttFromResult(res_data) {
- var res_data_json = JSON.parse(res_data);
- var res_data_result_json = JSON.parse(res_data_json.result);
- return res_data_result_json.asr.result;
- }
- //麦克风帧动画
- function speaking() {
- var _this = this;
- //话筒帧动画
- var i = 1;
- this.timer = setInterval(function () {
- i++;
- i = i % 5;
- _this.setData({
- j: i
- })
- }, 200);
- }
这部分主要实现录音按钮被按下和松开触发话筒录音及结束录音,当按钮被按下后,触发调用话筒动画特效(其实是四五个图片轮流显示的效果),同时调用wx.startRecord开始录音。
当按钮松开时停止录音,然后将录音临时文件往 https://api.happycxz.com/test/silk2asr/olami/asr 上发送,同时发olami上注册申请的appKey和appSecret,以及用户唯一识别号。
从语音识别接口返回的结果是原封不动的olami官方输出结果,我们只需要取一下语音识别结果以及语义理解结果即可。 语义理解结果在原来 微信小程序——智能小秘“遥知之”源码分享(语义理解基于olami)(注:这个是原来不支持语音识别的版本) 中已经有方法解析,为了代码复用性强些,把解析nli结果的方法简单改了下,即适用新版语音识别的,也适用以前老版本的手动输入的。
代码逻辑很简单,看看就明白了,这里不再多述。
asr.json源码:
- {
- "window": {
- "enablePullDownRefresh": false
- }
- }
因为老版项目中我开启了下拉刷新,新界面上不需要了,所以在asr页面的.json这里特意关闭了此功能。
asr.wxml源码:
- <view class="container">
- <view class="page-section">
- <view class="text-box" scroll-y="true">
- <text style="max-width:200px;overflow-y:auto;height:200px;" selectable="true">{{outputTxt}}</text>
- </view>
- </view>
- <view class="page-section">
- <text selectable="true" class="text-head">语义理解基于olami.ai,作者QQ:404499164</text>
- </view>
- <view class="little-gap-top button-selection2 button-show bottom-button">
- <button size="mini" type="default" open-type="contact">联系作者</button>
- <button size="mini" type="default" bindtap="turnToOld">切老版本</button>
- <button size="mini" type="default" open-type="share">帮忙分享</button>
- </view>
- <view class="page-section">
- <view class="doommview">
- <block wx:for="{{doommData}}" wx:key="id">
- <text wx:if="{{item.display}}" class="aon" style="animation: first {{item.time}}s linear infinite;top:{{item.top}}%;color:{{item.color}};">
- {{item.text}}
- </text>
- </block>
- </view>
- </view>
- <view wx:if="{{isSpeaking}}" class="speak-style">
- <image class="sound-style" src="../../pics/voice_icon_speech_sound_1.png" ></image>
- <image wx:if="{{j==2}}" class="sound-style" src="../../pics/voice_icon_speech_sound_2.png" ></image>
- <image wx:if="{{j==3}}" class="sound-style" src="../../pics/voice_icon_speech_sound_3.png" ></image>
- <image wx:if="{{j==4}}" class="sound-style" src="../../pics/voice_icon_speech_sound_4.png" ></image>
- <image wx:if="{{j==5}}"class="sound-style" src="../../pics/voice_icon_speech_sound_5.png" ></image>
- </view>
- </view>
- <view class="record-style">
- <button type="primary" class="btn-style" bindtouchstart="touchdown" bindtouchend="touchup">按下录音,松开结束</button>
- </view>
布局调了半天,还是没有达到我想要的效果,前端布局我没系统学习过,基本就是凑凑拼拼,望有基本审美观的各位看官理解……
asr.wxss源码:
- /* pages/asr/asr.wxss */
- page{
- background-color:beige;
- background-image: url(https://img.blog.csdn.net/20170720105808995?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGFwcHljeHo=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast);
- background-size: cover;
- }
- .page-section{
- display: flex;
- flex-direction: column;
- margin-bottom: 10rpx;
- }
- .text-head{
- color: #ff0000;
- font-size: 28rpx;
- align-items: center;
- margin-top: 5rpx;
- }
- .button-selection {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- }
- .button-selection2 {
- justify-content: space-between;
- align-content: space-between;
- flex-shrink:1;
- }
- .little-gap-top {
- margin-top: 10rpx;
- }
- .big-gap-top {
- margin-top: 100rpx;
- }
- .button-show {
- display: flex;
- align-self: center;
- justify-content: center;
- }
- .bottom-button {
- justify-content: space-around;
- flex-shrink:0;
- }
- .text-box{
- margin-bottom: 0rpx;
- margin-left: 50rpx;
- margin-right: 50rpx;
- padding: 40rpx 0;
- display: flex;
- min-height: 650rpx;
- max-width: 600rpx;
- width:600rpx;
- background-color: #ffffff;
- justify-content: center;
- align-items: center;
- text-align: left;
- font-size: 30rpx;
- color: #353535;
- line-height: 2em;
- border: 1px solid cornflowerblue;
- }
- /* 录音 */
- .speak-style{
- position: relative;
- height: 240rpx;
- width: 240rpx;
- border-radius: 20rpx;
- margin: 0 auto;
- background: #26A5FF;
- }
- .record-style{
- position: fixed;
- bottom: 0;
- left: 0;
- height: 120rpx;
- width: 100%;
- }
- .btn-style{
- margin-left: 30rpx;
- margin-right: 30rpx;
- }
- .sound-style{
- position: absolute;
- width: 74rpx;
- height:150rpx;
- margin-top: 45rpx;
- margin-left: 83rpx;
- }
- /* 弹幕 */
- .button{
- position: absolute;
- bottom: 0;
- width: 100%;
- }
- .aon{
- position: absolute;
- white-space:nowrap;
- animation-timing-function: linear;
- animation-fill-mode: none;
- }
- .doommview{
- z-index: 3;
- height: 80%;
- width: 100%;
- /* position: absolute; */
- }
- @keyframes first{
- from{left: 100%; }
- to{left: -100%;}
- }
弹幕部分原先是硬代码实现的,后来在小程序联盟里请教后才得知,css里有可以实现动画特效的功能,就顺便修改了一下。还是有点显示方面BUG的,不折腾了。
其它代码还是参照我原来的那个文章里介绍的吧:微信小程序——智能小秘“遥知之”源码分享(语义理解基于olami)(注:这个是原来不支持语音识别的版本) ,基本变动比较少。
“遥知之”微信小程序完整源码下载:
码云:https://git.oschina.net/happycxz/ ... chat_littleapp_demo
github: https://github.com/happycxz/nlp_ ... chat_littleapp_demo
写在最后
这次小程序的版本更新,还是上一次的延续,上次老版本未能支持上语音识别,用起来很不方便,网上也找不到相应的免费的接口,于是索性凑了点时间专门自己搭个HTTPS服务出来,方便同样想在微信小程序上DEBUG语音识别功能的伙伴们和兴趣开发者们调试和做些小玩意,好在,总算是一路走过来了,特别感谢kn007大神提供的silk decoder源码以及ffmpeg转码脚本,关于此议题(解码转换QQ微信的SILK v3编码音频为MP3或其他格式)在他本人的博客中火热地讨论了一年多了,感兴趣的也可以去膜拜一下这位大神。