sk-voice-command 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +402 -0
- package/package.json +28 -0
- package/src/core/command-matcher.js +293 -0
- package/src/core/store.js +263 -0
- package/src/index.js +17 -0
- package/src/plugin.js +252 -0
- package/src/utils/constants.js +73 -0
- package/src/utils/helpers.js +137 -0
- package/src/utils/leven.js +144 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 sk-voice-command
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# sk-voice-command
|
|
2
|
+
|
|
3
|
+
> 阿里云语音识别指令模块 for uni-app
|
|
4
|
+
|
|
5
|
+
一个零依赖的 Vue 2 语音指令模块,基于阿里云实时语音识别技术,让你的 uni-app 应用支持语音控制。
|
|
6
|
+
|
|
7
|
+
## ✨ 特性
|
|
8
|
+
|
|
9
|
+
- 🎤 **实时语音识别** - 基于阿里云"一句话识别",低延迟高准确率
|
|
10
|
+
- 🎯 **智能指令匹配** - 支持模糊匹配,自动识别用户意图
|
|
11
|
+
- 🔌 **零依赖** - 不依赖 Vuex、uni-ui 等第三方库
|
|
12
|
+
- 📦 **即插即用** - 5 分钟完成集成
|
|
13
|
+
- 📱 **跨平台** - 支持微信小程序、H5、App
|
|
14
|
+
|
|
15
|
+
## 📦 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install sk-voice-command
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 🚀 快速开始
|
|
22
|
+
|
|
23
|
+
### 1. 安装插件
|
|
24
|
+
|
|
25
|
+
在 `main.js` 中注册插件:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
import Vue from 'vue'
|
|
29
|
+
import SkVoiceCommand from 'sk-voice-command'
|
|
30
|
+
|
|
31
|
+
Vue.use(SkVoiceCommand, {
|
|
32
|
+
// 必需配置
|
|
33
|
+
appkey: 'your-aliyun-appkey',
|
|
34
|
+
tokenUrl: 'https://your-api.com/api/aliyun/token',
|
|
35
|
+
|
|
36
|
+
// 可选配置
|
|
37
|
+
matchThreshold: 0.8, // 匹配阈值(默认 0.8)
|
|
38
|
+
confirmThreshold: 0.6, // 确认阈值(默认 0.6)
|
|
39
|
+
enableIntermediateResult: true,// 启用中间结果(默认 true)
|
|
40
|
+
debug: false // 调试模式(默认 false)
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. 注册指令
|
|
45
|
+
|
|
46
|
+
在页面组件中注册指令:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
export default {
|
|
50
|
+
mounted() {
|
|
51
|
+
// 注册页面级指令
|
|
52
|
+
this.$voice.registerCommands({
|
|
53
|
+
scope: 'page',
|
|
54
|
+
commands: [
|
|
55
|
+
{
|
|
56
|
+
id: 'submit-form',
|
|
57
|
+
keywords: ['提交', '确认提交', '发送'],
|
|
58
|
+
action: () => {
|
|
59
|
+
this.submitForm()
|
|
60
|
+
},
|
|
61
|
+
description: '提交当前表单'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'scroll-top',
|
|
65
|
+
keywords: ['滚动到顶部', '回到顶部'],
|
|
66
|
+
action: () => {
|
|
67
|
+
uni.pageScrollTo({ scrollTop: 0 })
|
|
68
|
+
},
|
|
69
|
+
description: '滚动到页面顶部'
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
beforeDestroy() {
|
|
76
|
+
// 页面卸载时自动清除指令(插件会自动处理,也可以手动调用)
|
|
77
|
+
// this.$voice.unregisterCommands('page')
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. 使用
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
export default {
|
|
86
|
+
methods: {
|
|
87
|
+
// 开始录音
|
|
88
|
+
async startVoice() {
|
|
89
|
+
try {
|
|
90
|
+
await this.$voice.start()
|
|
91
|
+
console.log('开始录音')
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('启动失败:', error)
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// 停止录音
|
|
98
|
+
async stopVoice() {
|
|
99
|
+
const result = await this.$voice.stop()
|
|
100
|
+
console.log('识别结果:', result)
|
|
101
|
+
// result.text - 识别文本
|
|
102
|
+
// result.matched - 是否匹配到指令
|
|
103
|
+
// result.command - 匹配的指令
|
|
104
|
+
// result.similarity - 相似度
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// 手动匹配指令(用于测试)
|
|
108
|
+
testMatch(text) {
|
|
109
|
+
const result = this.$voice.matchCommand(text)
|
|
110
|
+
if (result.matched) {
|
|
111
|
+
this.$voice.executeCommand(result.command)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 📚 API 文档
|
|
119
|
+
|
|
120
|
+
### 插件配置选项
|
|
121
|
+
|
|
122
|
+
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|
|
123
|
+
|------|------|------|--------|------|
|
|
124
|
+
| appkey | String | ✅ | - | 阿里云 AppKey |
|
|
125
|
+
| tokenUrl | String | ✅ | - | Token 获取接口地址 |
|
|
126
|
+
| matchThreshold | Number | ❌ | 0.8 | 自动执行阈值(0-1) |
|
|
127
|
+
| confirmThreshold | Number | ❌ | 0.6 | 确认执行阈值(0-1) |
|
|
128
|
+
| enableIntermediateResult | Boolean | ❌ | true | 是否启用中间结果 |
|
|
129
|
+
| debug | Boolean | ❌ | false | 调试模式 |
|
|
130
|
+
|
|
131
|
+
### 实例方法
|
|
132
|
+
|
|
133
|
+
#### registerCommands(options)
|
|
134
|
+
|
|
135
|
+
注册指令
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
this.$voice.registerCommands({
|
|
139
|
+
scope: 'page', // 'page' 或 'global'
|
|
140
|
+
commands: [
|
|
141
|
+
{
|
|
142
|
+
id: 'command-id', // 唯一标识(必需)
|
|
143
|
+
keywords: ['关键词1', '关键词2'], // 关键词数组(必需)
|
|
144
|
+
action: () => {}, // 执行函数(必需)
|
|
145
|
+
description: '指令描述', // 描述(可选)
|
|
146
|
+
weight: 10 // 权重(可选,默认 0)
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### unregisterCommands(scope)
|
|
153
|
+
|
|
154
|
+
注销指令
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
this.$voice.unregisterCommands('page') // 注销页面指令
|
|
158
|
+
this.$voice.unregisterCommands('global') // 注销全局指令
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### start()
|
|
162
|
+
|
|
163
|
+
开始语音识别
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
await this.$voice.start()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### stop()
|
|
170
|
+
|
|
171
|
+
停止语音识别
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
const result = await this.$voice.stop()
|
|
175
|
+
// result.text - 识别文本
|
|
176
|
+
// result.matched - 是否匹配
|
|
177
|
+
// result.command - 匹配的指令
|
|
178
|
+
// result.similarity - 相似度(0-1)
|
|
179
|
+
// result.weight - 指令权重
|
|
180
|
+
// result.action - 'auto' | 'confirm' | 'reject'
|
|
181
|
+
// result.matchedKeyword - 匹配的关键词
|
|
182
|
+
// result.allMatches - 所有匹配结果(调试用)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### matchCommand(text)
|
|
186
|
+
|
|
187
|
+
手动匹配指令(用于测试)
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
const result = this.$voice.matchCommand('测试文本')
|
|
191
|
+
if (result.matched) {
|
|
192
|
+
this.$voice.executeCommand(result.command)
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### executeCommand(command)
|
|
197
|
+
|
|
198
|
+
执行指令
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
this.$voice.executeCommand(command)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 状态对象
|
|
205
|
+
|
|
206
|
+
通过 `this.$voice.state` 访问:
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
{
|
|
210
|
+
isRecording: false, // 是否正在录音
|
|
211
|
+
recognitionText: '', // 识别文本
|
|
212
|
+
lastCommand: null, // 最后执行的指令
|
|
213
|
+
error: null, // 错误信息
|
|
214
|
+
status: 'idle', // 当前状态
|
|
215
|
+
recordDuration: 0, // 录音时长(毫秒)
|
|
216
|
+
isRecognizing: false // 是否正在识别
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 事件监听
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
// 监听状态变化
|
|
224
|
+
this.$voice.on('state-change', (state) => {
|
|
225
|
+
console.log('状态变化:', state)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// 监听录音开始
|
|
229
|
+
this.$voice.on('recording-started', () => {
|
|
230
|
+
console.log('开始录音')
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// 监听录音停止
|
|
234
|
+
this.$voice.on('recording-stopped', (result) => {
|
|
235
|
+
console.log('识别结果:', result)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// 移除监听
|
|
239
|
+
this.$voice.off('state-change', callback)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## 🔧 高级用法
|
|
243
|
+
|
|
244
|
+
### 全局指令
|
|
245
|
+
|
|
246
|
+
在 `App.vue` 中注册全局指令:
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
export default {
|
|
250
|
+
onLaunch() {
|
|
251
|
+
this.$voice.registerCommands({
|
|
252
|
+
scope: 'global',
|
|
253
|
+
commands: [
|
|
254
|
+
{
|
|
255
|
+
id: 'go-home',
|
|
256
|
+
keywords: ['返回首页', '回到主页'],
|
|
257
|
+
action: () => {
|
|
258
|
+
uni.switchTab({ url: '/pages/index/index' })
|
|
259
|
+
},
|
|
260
|
+
description: '返回首页'
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 指令优先级
|
|
269
|
+
|
|
270
|
+
- **页面指令 > 全局指令**
|
|
271
|
+
- 当页面指令和全局指令有相同关键词时,优先执行页面指令
|
|
272
|
+
- **权重优先级**:当多个指令相似度相同或相近(差异 ≤ 5%)时,优先执行权重高的指令
|
|
273
|
+
|
|
274
|
+
### 匹配策略
|
|
275
|
+
|
|
276
|
+
- **相似度 ≥ 80%**: 自动执行
|
|
277
|
+
- **60% ≤ 相似度 < 80%**: 弹出确认对话框
|
|
278
|
+
- **相似度 < 60%**: 提示未识别,不执行
|
|
279
|
+
|
|
280
|
+
### 权重系统
|
|
281
|
+
|
|
282
|
+
权重用于处理多个指令相似度接近的情况:
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
// 示例:三个指令相似度都在 85%-90% 之间
|
|
286
|
+
{
|
|
287
|
+
id: 'command-a',
|
|
288
|
+
keywords: ['提交'],
|
|
289
|
+
action: () => console.log('A'),
|
|
290
|
+
weight: 10 // 权重最高,优先执行
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
{
|
|
294
|
+
id: 'command-b',
|
|
295
|
+
keywords: ['确认'],
|
|
296
|
+
action: () => console.log('B'),
|
|
297
|
+
weight: 5
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
{
|
|
301
|
+
id: 'command-c',
|
|
302
|
+
keywords: ['发送'],
|
|
303
|
+
action: () => console.log('C'),
|
|
304
|
+
weight: 8
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**权重规则**:
|
|
309
|
+
1. 默认权重为 0
|
|
310
|
+
2. 当多个指令相似度差异 ≤ 5% 时,按权重降序排列
|
|
311
|
+
3. 权重越高,优先级越高
|
|
312
|
+
|
|
313
|
+
## 📝 配置 Token 获取接口
|
|
314
|
+
|
|
315
|
+
模块需要提供一个 Token 获取接口,返回阿里云访问 Token:
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
// 示例:后端接口
|
|
319
|
+
GET /api/aliyun/token
|
|
320
|
+
|
|
321
|
+
Response:
|
|
322
|
+
{
|
|
323
|
+
"token": "your-aliyun-token"
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
后端实现示例(Node.js):
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
const Core = require('@alicloud/pop-core')
|
|
331
|
+
|
|
332
|
+
// 获取 Token
|
|
333
|
+
async function getToken() {
|
|
334
|
+
const client = new Core({
|
|
335
|
+
accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID,
|
|
336
|
+
accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET,
|
|
337
|
+
endpoint: 'https://nls-meta.cn-shanghai.aliyuncs.com',
|
|
338
|
+
apiVersion: '2019-02-28'
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
const params = {
|
|
342
|
+
"Action": "CreateToken"
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const result = await client.request('POST', params)
|
|
346
|
+
return result.Token.Id
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## 🌐 微信小程序配置
|
|
351
|
+
|
|
352
|
+
在微信小程序后台配置以下合法域名:
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
request: https://nls-meta.cn-shanghai.aliyuncs.com
|
|
356
|
+
socket: wss://nls-gateway-cn-shanghai.aliyuncs.com
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## 🤝 贡献
|
|
360
|
+
|
|
361
|
+
欢迎提交 Issue 和 Pull Request!
|
|
362
|
+
|
|
363
|
+
## 📄 许可证
|
|
364
|
+
|
|
365
|
+
[MIT](LICENSE)
|
|
366
|
+
|
|
367
|
+
## 🔗 相关链接
|
|
368
|
+
|
|
369
|
+
- [阿里云智能语音交互](https://www.aliyun.com/product/ivh)
|
|
370
|
+
- [uni-app 官方文档](https://uniapp.dcloud.io/)
|
|
371
|
+
- [OpenSpec 提案](../../openspec/changes/add-aliyun-voice-command/)
|
|
372
|
+
|
|
373
|
+
## 💡 常见问题
|
|
374
|
+
|
|
375
|
+
### 1. 如何获取阿里云 AppKey?
|
|
376
|
+
|
|
377
|
+
1. 访问 [阿里云控制台](https://ecs.console.aliyun.com/)
|
|
378
|
+
2. 开通"智能语音交互"服务
|
|
379
|
+
3. 创建项目并获取 AppKey
|
|
380
|
+
|
|
381
|
+
### 2. Token 如何获取?
|
|
382
|
+
|
|
383
|
+
推荐方式:通过后端接口获取,避免暴露 AccessKey。
|
|
384
|
+
|
|
385
|
+
开发测试:可以使用 AccessKey 直接获取(不推荐生产环境)。
|
|
386
|
+
|
|
387
|
+
### 3. 支持哪些平台?
|
|
388
|
+
|
|
389
|
+
- ✅ 微信小程序
|
|
390
|
+
- ✅ H5
|
|
391
|
+
- ✅ Android App
|
|
392
|
+
- ✅ iOS App
|
|
393
|
+
|
|
394
|
+
### 4. 指令匹配不准确怎么办?
|
|
395
|
+
|
|
396
|
+
1. 调整 `matchThreshold` 和 `confirmThreshold` 配置
|
|
397
|
+
2. 为指令添加更多关键词同义词
|
|
398
|
+
3. 启用 `debug: true` 查看匹配详情
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
**Made with ❤️ by sk-voice-command team**
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sk-voice-command",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "语音指令控制和自动化操作",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"uni-app",
|
|
11
|
+
"voice",
|
|
12
|
+
"speech-recognition",
|
|
13
|
+
"voice-command",
|
|
14
|
+
"语音识别",
|
|
15
|
+
"语音指令"
|
|
16
|
+
],
|
|
17
|
+
"author": "Ding_D(1601202253@qq.com)",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": ""
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src/",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
]
|
|
28
|
+
}
|