Skip to content

Commit ff9df4c

Browse files
authored
Merge pull request #243 from Deep-Blue-2013/master
Add Web Speech #242
2 parents 3655935 + 5921701 commit ff9df4c

File tree

15 files changed

+165
-9
lines changed

15 files changed

+165
-9
lines changed

src/web-live-chat/src/lib/helpers/typedefs.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ IRichContent.prototype.text;
118118
* @param {ChatResponseModel} message
119119
*/
120120

121+
/**
122+
* Invoked when speech to text is detected.
123+
*
124+
* @callback OnSpeechToTextDetected
125+
* @param {string} text
126+
*/
127+
128+
121129
// having to export an empty object here is annoying,
122130
// but required for vscode to pass on your types.
123131
export default {};

src/web-live-chat/src/lib/services/agent-service.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { agentListUrl, agentDetailUrl } from '$lib/services/api-endpoints.js';
2+
import { setAuthorization } from '$lib/helpers/http';
23
import axios from 'axios';
34

45
/**
56
* @returns {Promise<import('$typedefs').AgentModel[]>}
67
*/
78
export async function getAgents() {
9+
setAuthorization();
810
const response = await axios.get(agentListUrl);
911
return response.data;
1012
}
@@ -14,6 +16,7 @@ export async function getAgents() {
1416
* @returns {Promise<import('$typedefs').AgentModel>}
1517
*/
1618
export async function getAgent(id) {
19+
setAuthorization();
1720
let url = agentDetailUrl.replace("{id}", id);
1821
const response = await axios.get(url);
1922
return response.data;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API
2+
const SpeechRecognition =
3+
window.SpeechRecognition || window.webkitSpeechRecognition;
4+
const SpeechRecognitionEvent =
5+
window.SpeechRecognitionEvent || window.webkitSpeechRecognitionEvent;
6+
7+
const recognition = new SpeechRecognition();
8+
recognition.continuous = false;
9+
recognition.lang = "en-US";
10+
recognition.interimResults = false;
11+
recognition.maxAlternatives = 1;
12+
13+
const synth = window.speechSynthesis;
14+
15+
16+
const utterThis = new SpeechSynthesisUtterance();
17+
utterThis.pitch = 1;
18+
utterThis.rate = 1;
19+
20+
export const webSpeech = {
21+
/** @type {import('$typedefs').OnSpeechToTextDetected} */
22+
onSpeechToTextDetected: () => {},
23+
24+
start() {
25+
recognition.start();
26+
console.log("Ready to receive a voice command.");
27+
},
28+
29+
/** @param {string} transcript */
30+
utter(transcript) {
31+
setVoiceSynthesis();
32+
utterThis.text = transcript
33+
synth.speak(utterThis);
34+
}
35+
}
36+
37+
function setVoiceSynthesis() {
38+
if (utterThis.voice == null) {
39+
const voices = synth.getVoices();
40+
for (let i = 0; i < voices.length; i++) {
41+
if (voices[i].name === "Microsoft Michelle Online (Natural) - English (United States)") {
42+
utterThis.voice = voices[i];
43+
break;
44+
}
45+
}
46+
}
47+
}
48+
49+
recognition.onresult = (event) => {
50+
const text = event.results[0][0].transcript;
51+
console.log(`Confidence: ${text} ${event.results[0][0].confidence}`);
52+
webSpeech.onSpeechToTextDetected(text);
53+
};
54+
55+
recognition.onnomatch = (event) => {
56+
console.log("I didn't recognize that color.");
57+
};
58+
59+
recognition.onerror = (event) => {
60+
console.log(`Error occurred in recognition: ${event.error}`);
61+
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="row" style="margin-top:150px;margin-left:25px;margin-right:25px;">
22
<div class="col-lg-8 offset-lg-2">
33
<p class="section-subtitle text-muted text-center pt-4 font-secondary">We craft digital, graphic and dimensional thinking, to create category leading brand experiences that have meaning and add a value for our clients.</p>
4-
<div class="d-flex justify-content-center"><a href="/login" class="btn btn-primary">New Conversation</a></div>
4+
<div class="d-flex justify-content-center"><a href="/login" class="btn btn-primary">Get Started</a></div>
55
</div>
66
</div>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<script>
2+
import { Container, Row, Col } from "sveltestrap";
3+
4+
// This page is used to initialize a new conversation for client
5+
import { page } from '$app/stores';
6+
import { onMount } from 'svelte';
7+
import { getAgents } from '$lib/services/agent-service.js'
8+
9+
const params = $page.params;
10+
let agentId = "undefined";
11+
/** @type {import('$typedefs').AgentModel[]} */
12+
let agents = [];
13+
14+
onMount(async () => {
15+
agents = await getAgents();
16+
agentId = agents[0].id;
17+
});
18+
</script>
19+
20+
<Container fluid>
21+
<Row>
22+
<div class="col-12">
23+
<div style="margin-top: 10vh; margin-left:10vw;">
24+
{#each agents as agent}
25+
<div>
26+
<input
27+
class="form-check-input m-1"
28+
type="radio"
29+
name="agents"
30+
id={agent.id}
31+
value={agent.id}
32+
checked = {agentId == agent.id}
33+
on:click={() => agentId = agent.id}
34+
/>
35+
<label class="form-check-label" for={agent.id}>
36+
{agent.name}
37+
</label>
38+
<div class="mx-4">{agent.description}</div>
39+
</div>
40+
{/each}
41+
</div>
42+
</div>
43+
</Row>
44+
<Row class="text-center">
45+
<Col>
46+
<p class="section-subtitle text-muted text-center pt-4 font-secondary">We craft digital, graphic and dimensional thinking, to create category leading brand experiences that have meaning and add a value for our clients.</p>
47+
<div class="d-flex justify-content-center">
48+
<a href="/chat/{agentId}" class="btn btn-primary">
49+
<i class="mdi mdi-chat" />
50+
<span>Start Conversation</span>
51+
</a>
52+
</div>
53+
</Col>
54+
</Row>
55+
</Container>

src/web-live-chat/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import { onMount } from 'svelte';
1616
import Link from 'svelte-link';
1717
import { signalr } from '$lib/services/signalr-service.js';
18+
import { webSpeech } from '$lib/services/web-speech.js';
1819
import { sendMessageToHub, GetDialogs } from '$lib/services/conversation-service.js';
1920
import { format } from '$lib/helpers/datetime';
2021
import RcText from './rc-text.svelte';
@@ -43,6 +44,7 @@
4344
4445
// @ts-ignore
4546
let scrollbar;
47+
let microphoneIcon = "microphone-off";
4648
4749
/** @type {import('$typedefs').ChatResponseModel[]} */
4850
let dialogs = [];
@@ -78,17 +80,36 @@
7880
7981
/** @param {import('$typedefs').ChatResponseModel} message */
8082
function onMessageReceivedFromAssistant(message) {
81-
dialogs.push(message);
82-
refresh();
83+
webSpeech.utter(message.text);
84+
// clean rich content elements
85+
dialogs.forEach(dialog => {
86+
if (dialog.rich_content.messaging_type == "quick_reply") {
87+
dialog.rich_content.quick_repies = [];
88+
}
89+
});
90+
91+
dialogs.push(message);
92+
refresh();
8393
}
8494
85-
async function sendMessage() {
95+
async function sendTextMessage() {
8696
await sendMessageToHub(params.agentId, params.conversationId, text);
8797
}
8898
99+
async function startListen() {
100+
microphoneIcon = "microphone";
101+
webSpeech.onSpeechToTextDetected = async (transcript) => {
102+
text = transcript;
103+
await sendTextMessage();
104+
microphoneIcon = "microphone-off";
105+
}
106+
webSpeech.start();
107+
}
108+
89109
function refresh() {
90110
// trigger UI render
91111
dialogs = dialogs;
112+
92113
setTimeout(() => {
93114
const { viewport } = scrollbar.elements();
94115
viewport.scrollTo({ top: viewport.scrollHeight, behavior: 'smooth' }); // set scroll offset
@@ -194,8 +215,16 @@
194215

195216
<div class="p-3 chat-input-section" style="height: 10vh">
196217
<div class="row">
218+
<div class="col-auto">
219+
<button
220+
type="submit"
221+
class="btn btn-primary btn-rounded waves-effect waves-light"
222+
on:click={startListen}>
223+
<i class="mdi mdi-{microphoneIcon} md-36" />
224+
</button>
225+
</div>
197226
<div class="col">
198-
<div class="position-relative">
227+
<div class="position-relative">
199228
<input type="text" class="form-control chat-input" bind:value={text} placeholder="Enter Message..." />
200229
<div class="chat-input-links" id="tooltip-container">
201230
<ul class="list-inline mb-0">
@@ -210,10 +239,10 @@
210239
<button
211240
type="submit"
212241
class="btn btn-primary btn-rounded chat-send w-md waves-effect waves-light"
213-
on:click={sendMessage}
242+
on:click={sendTextMessage}
214243
><span class="d-none d-sm-inline-block me-2">Send</span>
215-
<i class="mdi mdi-send" /></button
216-
>
244+
<i class="mdi mdi-send" />
245+
</button>
217246
</div>
218247
</div>
219248
</div>

src/web-live-chat/src/routes/login/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
isOpen = true;
2929
msg = 'Authentication success';
3030
status = 'success';
31-
goto(`/chat/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a`);
31+
goto(`/chat`);
3232
});
3333
}
3434
</script>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)