<|start_filename|>yzz_weixinapp/utils/NLI.js<|end_filename|>
/*
  对接olami免费开放语义平台的NLI接口
*/

//获取应用实例
var app = getApp()

const appkey = require('../config').appkey
const appsecret = require('../config').appsecret

var MD5 = require('./MD5.js');
var UTIL = require('./util.js');

const requestUrl = "https://cn.olami.ai/cloudservice/api";
const api = "nli";


function getLocationJson() {
  var lat = app.globalData.latitude;
  var lng = app.globalData.longitude;
  if (lat < 180 && lng < 180) {
    lat *= 1000000;
    lng *= 1000000;
  }

  return { "position_type": 0, "longitude": lng, "latitude": lat };
}

//NLI接口访问
//参数：corpus为句子；cb为回调函数（带两个参数，第一个参数表示调用NLI是否成功，第二个参数是NLI的返回数据）
function process(corpus, cb) {
  var timestamp = new Date().getTime();
  var originalSign = appsecret + "api=" + api + "appkey=" + appkey + "timestamp=" + timestamp + appsecret;
  var sign = MD5.md5(originalSign);

  var locationJson = getLocationJson();
  var rqdataJson = { "data": { "input_type": 1, "text": corpus, "location": locationJson }, "data_type": "stt" };
  var rqdata = JSON.stringify(rqdataJson);

  UTIL.log('input:' + rqdata + '\r\n, custId:' + app.globalData.custId + ', originalSign:' + originalSign);

  wx.request({
    url: requestUrl,
    data: {
      appkey: appkey,
      api: api,
      timestamp: timestamp,
      sign: sign,
      rq: rqdata,
      cusid: app.globalData.custId,
    },
    header: {
      'content-type': 'application/x-www-form-urlencoded'
    },
    method: 'POST',
    success: function (result) {
      var data = result.data.data;
      var jsonData = JSON.stringify(data);
      typeof cb == "function" && cb(true, jsonData)
    },
    fail: function ({errMsg}) {
      typeof cb == "function" && cb(false, jsonData)
    }
  })
}

//解析NLI接口返回的数据，从语义结果中筛选出适合显示的文本内容
function getSentenceFromNliResult(nliArray) {
  var sentence;
  try {
    for (var i = 0; i < nliArray.length; i++) {
      var singleResult = nliArray[i];
      sentence = singleResult.desc_obj.result;

      var content = '';
      var appName = singleResult.type;
      //0: normal, 1: selection, 9 : openweb
      var tagType = 0;
      if (appName === 'selection') {
        appName = singleResult.desc_obj.type;
        tagType = 1;
      } else if (appName === 'openweb') {
        //appName = singleResult.desc_obj.type;
        tagType = 9;
      }
      switch (appName) {
        case 'joke':
        case 'story':
          sentence = singleResult.data_obj[0].content;
          break;
        case 'poem':
          if (tagType === 1) {
            for (var k = 0; k < singleResult.data_obj.length; k++) {
              content += '第' + (k + 1) + '个： ' + singleResult.data_obj[k].author + '   ' + singleResult.data_obj[k].poem_name + '\n';
            }
          }
          break;
        case 'cooking':
          if (tagType === 1) {
            for (var k = 0; k < singleResult.data_obj.length; k++) {
              content += '第' + (k + 1) + '个： ' + singleResult.data_obj[k].name + '\n';
            }
          } else if (tagType === 0) {
            content = singleResult.data_obj[0].content;
          }
          break;
        case 'baike':
          var filedNames = singleResult.data_obj[0].field_name;
          var filedValues = singleResult.data_obj[0].field_value;
          for (var k = 0; k < filedNames.length; k++) {
            content += filedNames[k] + ' : ' + filedValues[k] + '\n';
          }
          break;
        case 'news':
          if (tagType === 1) {
            for (var k = 0; k < singleResult.data_obj.length; k++) {
              content += singleResult.data_obj[k].title + '\n';
              content += singleResult.data_obj[k].detail + '…………\n';
              content += '【欲看此条新闻详情请说第' + (k + 1) + '个，或拷此链接到浏览器中打开：' + singleResult.data_obj[k].ref_url + '】\n\n';
            }
          } else if (tagType === 0) {
            content = singleResult.data_obj[0].detail;
          }
          break;
        case 'stock':
          var nowHour = new Date().getHours();
          var isKaiPan = (nowHour >= 9) && (nowHour <= 15);
          for (var k = 0; k < singleResult.data_obj.length; k++) {
            content += singleResult.data_obj[k].name + ' : ' + singleResult.data_obj[k].id + '\n';
            content += '开盘：' + singleResult.data_obj[k].price_start + '\n';
            if (isKaiPan) {
              content += '现价（' + UTIL.getHHMMSS(singleResult.data_obj[k].time) + '）：' + singleResult.data_obj[k].cur_price + '\n';
            } else {
              content += '收盘：' + singleResult.data_obj[k].price_end + '\n';
            }
            content += '成交量：' + singleResult.data_obj[k].volume + '\n';
            content += '成交额：' + singleResult.data_obj[k].amount + '\n';
            content += '最高价：' + singleResult.data_obj[k].price_high + '\n\n';
          }
          break;
        case 'tvprogram':
          for (var k = 0; k < singleResult.data_obj.length; k++) {
            content += singleResult.data_obj[k].time + '   ' + singleResult.data_obj[k].name + '\n';
          }
          break;
        case 'openweb':
          content += '【拷此链接到浏览器中打开：' + singleResult.data_obj[0].url + '】\n';
          break;
          defalt:
          break;
      }
      if (content !== '') {
        sentence += '\n\n' + content;
      }
      UTIL.log('NLI返回sentence:' + sentence)
    }
  } catch (e) {
    UTIL.log('getSentenceFromNliResult() 错误' + e.message + '发生在' + e.lineNumber + '行');
    sentence = '没明白你说的，换个话题？'
  }

  if (typeof sentence === 'undefined' || sentence === '') {
    sentence = '没明白你说的什么意思';
  }

  return sentence;
}


module.exports = {
  process: process,
  getSentenceFromNliResult: getSentenceFromNliResult,
}

<|start_filename|>yzz_weixinapp/pages/index/index.js<|end_filename|>
//获取应用实例
var app = getApp()

var UTIL = require('../../utils/util.js');
var GUID = require('../../utils/GUID.js');
var NLI = require('../../utils/NLI.js');

var cursor = 0;
var lastYYYTime = new Date().getTime();

var domainCorpus = '';
var lastCorpus = '';


Page({

  isShow: false,

  /**
   * 页面的初始数据
   */
  data: {
    //调试后门
    isDbg: false,
    //输入框文本
    inputTxt: '',
    //输出框文本
    outputTxt: ''
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    UTIL.log('index.onLoad')
    //调用应用实例的方法获取全局数据
    app.getUserInfo(function (userInfo2) {
      UTIL.log('user unique 1: ' + UTIL.getUserUnique(userInfo2))
    })
    UTIL.log('user unique 2: ' + UTIL.getUserUnique(app.globalData.userInfo))
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {
    UTIL.log('index.onReady')
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    UTIL.log('index.onShow')

    var that = this;
    that.isShow = true;

    //调用重力加速度传感API模拟摇一摇接口
    wx.onAccelerometerChange(function (e) {
      
      if (!that.isShow) {
        //当前界面不显示时不应该调用
        return
      }

      if (isBong(e.x) && isBong(e.y)) {
        if (new Date().getTime() - lastYYYTime <= 2000) {
          //1秒限制摇一次，避免摇一下触发多次请求
          UTIL.log('摇的太频繁啦，请等2秒再摇！' + e.x + ', '+ e.y + ', ' + e.z);
          return;
        }

        //更新最后一次成功摇的时间戳
        lastYYYTime = new Date().getTime()

        //从语料库中挑选语料运行语义理解，显示结果
        var selectedCorpus = selectCorpusRunNli(that);

        //弹Toast窗提示当前刷到哪句语料
        wx.showToast({
          title: selectedCorpus,
          icon: 'success',
          duration: 1500
        });
      }
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {
    UTIL.log('index.onHide')
    //页面隐藏后，关掉摇一摇检测
    wx.stopAccelerometer();
  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
    UTIL.log('index.onUnload')
  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
    UTIL.log('index.onPullDownRefresh')
    
    wx.stopPullDownRefresh();

    //页面下拉，触发轮换语料理解
    selectCorpusRunNli(this)
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    UTIL.log('index.onReachBottom')
  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {
    UTIL.log('index.onShareAppMessage')

    wx.showToast({
      title: '谢谢分享！',
      icon: 'success',
      duration: 1500
    });
  },

  switchChange: function (e) {
    UTIL.log('index.switchChange by:' + e.detail.value)
  },

  //输入文本框聚焦触发清除输入框中的内容
  bindFocusClear: function(e) {
    UTIL.log('index.bindFocusClear')
    if (e.detail.value === '') {
      return;
    }

    UTIL.log('clear: ' + e.detail.value)
    var self = this;
    self.setData({
      inputTxt: ''
    });
  },

  //点击完成按钮时触发
  bindConfirmControl: function(e) {
    var inputTxt = e.detail.value;
    UTIL.log('index.bindConfirmControl input string: ' + inputTxt);

    //手动打字输入语料运行语义理解
    singleCorpusRunNli(inputTxt, this)
  },

  //测试按钮触发事件
  bindTest: function () {
    UTIL.log('index.bindTest')

    //测试按钮替代摇一摇，从语料库中选语料测试
    selectCorpusRunNli(this)
  },

  //快捷按钮触发语料运行语义理解
  bindCorpusGenerator: function (e) {
    UTIL.log('index.bindCorpusGenerator')
    //获取"data-cp"中的语料
    var corpusList = e.target.dataset.cp.split('|');

    //默认头一次（或新切换时）点击，选用第一句语料
    var corpus = corpusList[0];
    if (domainCorpus !== corpusList[0]) {
      domainCorpus = corpusList[0];
    } else {
      //否则在语料表中随机挑选一个
      corpus = UTIL.getRandomItem(corpusList);

      //与上一句重复就换一句
      if (lastCorpus === corpus) {
        corpus = UTIL.getRandomItem(corpusList);
        if (lastCorpus === corpus) {
          corpus = UTIL.getRandomItem(corpusList);
          if (lastCorpus === corpus) {
            corpus = UTIL.getRandomItem(corpusList);
          }
        }
      }
    }

    //记录最近一次使用的语料
    lastCorpus = corpus;

    singleCorpusRunNli(corpus, this);
  },

  turnToNew: function() {
    wx.navigateBack({
    })
  }
})


//处理NLI语义结果
function NliProcess(corpus, self) {
  var nliResult;
  NLI.process(corpus, function (isSuccess, result) {
    if (isSuccess) {
    } else {
      
    }

    var sentenceResult;
    try {
      var resultJson = JSON.parse(result);
      nliResult = resultJson.nli;
      UTIL.log("NLI RESULT IS:" + nliResult);
      sentenceResult = NLI.getSentenceFromNliResult(nliResult);
    } catch (e) {
      UTIL.log('NliProcess() 错误' + e.message + '发生在' + e.lineNumber + '行');
      sentenceResult = '没明白你说的，换个话题？'
    }

    typeof self !== 'undefined' && self.setData({
      outputTxt: sentenceResult
    })
  })
}

//如输入是dbg且确认，则切换到debug模式，多显示两个按钮
function singleCorpusRunNli(inputTxt, self) {
  if (inputTxt === 'dbg' || inputTxt === 'DBG' || inputTxt === 'Dbg') {
    self.setData({
      //打开调试按钮
      isDbg: true,
      inputTxt: ''
    });
    // 打开调试
    wx.setEnableDebug({
      enableDebug: true
    })
    return;
  } else if(inputTxt === 'gbd' || inputTxt === 'GBD' || inputTxt === 'Gbd') {
    self.setData({
      //关闭调试按钮
      isDbg: false,
      inputTxt: ''
    });
    // 关闭调试
    wx.setEnableDebug({
      enableDebug: false,
    })
    return;
  }

  //正常用户输入文本
  if (inputTxt !== '') {
    self.setData({
      inputTxt: inputTxt
    });
    NliProcess(inputTxt, self);
  }
}

//选择预置语料运行语义理解
function selectCorpusRunNli(self) {
  var corpus = app.globalData.corpus;

  //顺序选择一句语料
  var corpusSelected = corpus[cursor]

  self.setData({
    //更新页面input框显示
    inputTxt: corpusSelected
  })

  UTIL.log('selected corpus:' + corpusSelected)
  //调用语料处理，刷新输出框结果
  NliProcess(corpusSelected, self);

  //光标后移，备下次挑选下一条语料
  if (cursor++ >= corpus.length - 1) {
    cursor = 0;
  }

  return corpusSelected;
}

//检测加速度传感器灵敏度
function isBong(val) {
  //目前灵敏度设置为0.8，且摇一摇只监测了x和y轴变动，具体见调用此函数的地方
  if (val >= 0.8 || val <= -0.8) {
    return true;
  }

  return false;
}

<|start_filename|>yzz_weixinapp/pages/logs/logs.js<|end_filename|>
//logs.js
var util = require('../../utils/util.js')
Page({
  data: {
    logs: []
  },
  onLoad: function () {
    this.setData({
      logs: (wx.getStorageSync('logs') || []).map(function (log) {
        var DATE = new Date(log);
        if (typeof DATE === 'date') {
          //当log是一个毫秒数，延用官方的案例，转成对应时间存LOG中（util.formatTime被我改成只输出时分秒）
          return util.formatTime(new Date(log))
        }
        
        //非毫秒数时，记录LOG原始内容，不作转换
        return log;
      })
    })
  }
})


<|start_filename|>yzz_weixinapp/app.js<|end_filename|>
//app.js

const corpusList = require('./config').corpus
var UTIL = require('./utils/util.js');

App({
  onShow: function () {
    UTIL.log('App Show')
  },
  onHide: function () {
    UTIL.log('App Hide')
  },
  onLaunch: function () {
    UTIL.log('App Launch')
    this.updateUserLocation()
  },

  updateUserLocation: function() {
    var that = this
    wx.getLocation({
      //type: 'wgs84',  // gps原始坐标
      type: 'gcj02', //国家标准加密坐标
      success: function (res) {
        that.globalData.latitude = res.latitude
        that.globalData.longitude = res.longitude
        that.globalData.speed = res.speed
        //var accuracy = res.accuracy
        UTIL.log('REFRESH LOCATION: ' + that.globalData.latitude + ' | ' + that.globalData.longitude + ' , speed: ' + that.globalData.speed)
      },
      fail: function(res) {
        UTIL.log('REFRESH LOCATION FAILED...')
      }
    })
  },

  getUserInfo:function(cb){
    var that = this
    if(this.globalData.userInfo){
      typeof cb == "function" && cb(this.globalData.userInfo)
    }else{
      //调用登录接口
      wx.login({
        success: function () {
          wx.getUserInfo({
            success: function (res) {
              that.globalData.userInfo = res.userInfo
              that.globalData.custId = UTIL.getUserUnique(that.globalData.userInfo);
              typeof cb == "function" && cb(that.globalData.userInfo)
            }
          })
        },
        fail: function () {
          UTIL.log('登录WX失败了！')
        }
      })
    }
  },

  clearUserInfo: function() {
    var that = this
    that.globalData.userInfo = null;
    that.globalData.hasLogin = false;
  },

  globalData:{
    userInfo:null,
    corpus: corpusList,
    custId: '',
    latitude: 0.0,
    longitude: 0.0,
    speed: 0,
  }
})


<|start_filename|>yzz_weixinapp/pages/asr/asr.js<|end_filename|>
/**
 * 作者：happycxz
 * 最后更新时间：2017.11.09
 * 源码分享链接：http://www.happycxz.com/m/?p=125
 *
 * https的silk语音识别API（专供微信小程序调用）：https://api.happycxz.com/wxapp/silk2asr
 * https的mp3语音识别API（专供微信小程序调用）：https://api.happycxz.com/wxapp/mp32asr
 * 该API服务搭建全过程解析及源码分享贴：http://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

//微信小程序新录音接口，录出来的是aac或者mp3，这里要录成mp3
const mp3Recorder = wx.getRecorderManager()
const mp3RecoderOptions = {
  duration: 60000,
  sampleRate: 16000,
  numberOfChannels: 1,
  encodeBitRate: 48000,
  format: 'mp3',
  //frameSize: 50
}

//弹幕定时器
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();

    //onLoad中为录音接口注册两个回调函数，主要是onStop，拿到录音mp3文件的文件名（不用在意文件后辍是.dat还是.mp3，后辍不决定音频格式）
    mp3Recorder.onStart(() => {
      UTIL.log('mp3Recorder.onStart()...')
    })
    mp3Recorder.onStop((res) => {
      UTIL.log('mp3Recorder.onStop() ' + res)
      const { tempFilePath } = res
      var urls = "https://api.happycxz.com/wxapp/mp32asr";
      UTIL.log('mp3Recorder.onStop() tempFilePath:' + tempFilePath)
      processFileUploadForAsr(urls, tempFilePath, this);
    })
  },

  /////////////////////////////////////////////////////////////// 以下是调用新接口实现的录音，录出来的是 mp3
  touchdown: function () {
  //touchdown_mp3: function () {
    UTIL.log("mp3Recorder.start with" + mp3RecoderOptions)
    var _this = this;
    speaking.call(this);
    this.setData({
      isSpeaking: true
    })
    mp3Recorder.start(mp3RecoderOptions);
  },
  touchup: function () {
  //touchup_mp3: function () {
    UTIL.log("mp3Recorder.stop")
    this.setData({
      isSpeaking: false,
    })
    mp3Recorder.stop();
  },


  //切换到老版本
  turnToOld: function () {
    wx.navigateTo({
      url: '../index/index',
    })
  },

  /////////////////////////////////////////////////////////////// 以下是调用老接口实现的录音，录出来的是 silk_v3
  //手指按下 
  touchdown_silk: function () {
  //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_silk: function () {
  //touchup: function () {
    UTIL.log("手指抬起了...")
    this.setData({
      isSpeaking: false,
    })
    clearInterval(this.timer)
    wx.stopRecord()

    var _this = this
    setTimeout(function () {
      var urls = "https://api.happycxz.com/wxapp/silk2asr/";
      UTIL.log(_this.data.recordPath);
      processFileUploadForAsr(urls, _this.data.recordPath, _this);
    }, 1000)
  },


})

//上传录音文件到 api.happycxz.com 接口，处理语音识别和语义，结果输出到界面
function processFileUploadForAsr(urls, filePath, _this) {
  wx.uploadFile({
    url: urls,
    filePath: filePath,
    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();
    }
  });
}

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);
}
