refactor messageItem functionality + update design

add dynamic title bar

updat Message object type
This commit is contained in:
riqo 2020-12-03 16:32:08 -06:00
commit f0f19687b0
9 changed files with 245 additions and 177 deletions

View file

@ -19,7 +19,7 @@ import { SpeechRecorder } from "speech-recorder";
// });
//
const recorder = new SpeechRecorder({ sampleRate: 16000, framesPerBuffer: 320 });
// const recorder = new SpeechRecorder({ sampleRate: 16000, framesPerBuffer: 320 });
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
@ -33,9 +33,9 @@ protocol.registerSchemesAsPrivileged([
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 350,
height: 525,
resizable: false,
width: 450,
height: 1025,
resizable: true,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
@ -56,24 +56,26 @@ function createWindow() {
win.loadURL("app://./index.html");
}
win.webContents.on('did-finish-load', () => {
if (win) {
recorder.start({
onAudio: (audio: object, speech: boolean) => {
if (win) {
win.webContents.send('render-audio', {
audio: audio,
speech: speech
})
}
if (speech) {
// TODO: send audio to crimata BE
// ws.send(audio);
}
}
});
}
})
// win.webContents.on('did-finish-load', () => {
// if (win) {
// recorder.start({
// onAudio: (audio: object, speech: boolean) => {
// if (win) {
// win.setTitle("Listening...");
// // win.webContents.send('render-audio', {
// // audio: audio,
// // speech: speech
// // })
// if (speech) {
// win.setTitle("Recording...");
// // TODO: send audio to crimata BE
// // ws.send(audio);
// }
// }
// }
// });
// }
// })
win.on("closed", () => {
win = null;

View file

@ -48,7 +48,7 @@ export default defineComponent({
let gradient;
const radius = ref(0);
const think = ref(false);
const aiOffset = ref(1.0);
const aiOffset = ref(0.7);
const anime = inject("animejs");
let thinkAnimation;
@ -100,24 +100,24 @@ export default defineComponent({
}
});
watch(think, async (think, prevThink) => {
if (think) {
anime({
targets: gradient,
cy: "0.7",
});
thinkAnimation.play();
} else {
thinkAnimation.pause();
// thinkAnimation.reset();
anime({
targets: gradient,
cy: "1",
});
// thinkAnimation.reset();
}
});
// watch(think, async (think, prevThink) => {
// if (think) {
// anime({
// targets: gradient,
// cy: "0.7",
// });
// thinkAnimation.play();
// } else {
// thinkAnimation.pause();
// // thinkAnimation.reset();
// anime({
// targets: gradient,
// cy: "1",
// });
//
// // thinkAnimation.reset();
// }
// });
return {
radius,
aiOffset,

View file

@ -1,21 +1,26 @@
<template>
<g :id="item.id" :class="item.modifier" :transform="startingOffset">
<rect ref="messageRect" x="30" y="10" width="196" height="63" rx="20" :fill="bubbleFill" />
<rect ref="messageRect" x="30" y="10" width="196" height="63" rx="10" :fill="bubbleFill" />
<text font-size="14" font-family="SF Pro" ref="messageText" :fill="textFill">
<tspan dy="16" x="0" v-for="(item, index) in textArr" :key="index">{{ item }}</tspan>
</text>
<g :transform="signatureTranslate">
<image xlink:href="profile.png" width="29" height="29" fill="#b5b5b5" clip-path="url(#clip)" />
<text
transform="translate(36, 17)"
fill="#888"
transform="translate(0, 5)"
fill="#000"
font-size="10"
font-family="SFCompactDisplay-Bold, SF Compact Display"
font-weight="700"
>
<tspan v-if="item.outgoing">You to</tspan>
<tspan v-else>From</tspan>
<tspan xml:space="preserve" fill="#000">{{ item.signature }}</tspan>
<tspan xml:space="preserve">{{ item.context }}</tspan>
</text>
<text
transform="translate(0, 20)"
fill="#6f6f6f"
font-size="9"
font-family="SFCompactDisplay, SF Compact Display"
>
<tspan v-if="item.subContext">{{item.subContext}}</tspan>
</text>
</g>
</g>

View file

@ -4,156 +4,156 @@ const Messages: Message[] = [
{
content:
"I can be there at 5 pm tomorrow to greet you at a Chick file with my almond and chive salad.",
signature: "Tommy McConville",
outgoing: true,
context: "You to Josie Wright",
subContext: "josie@gmail.com",
modifier: "sf",
imgURL: "../assets/logo.png",
id: "1"
id: "1",
time: ""
},
{
content: "Sounds good, see you Thursday.",
signature: "Josie Wright",
outgoing: false,
modifier: "fr",
imgURL: "@/assets/logo.png",
id: "2"
context: "Message from Josie Wright",
subContext: "Subject: Chick File",
modifier: "ai",
id: "2",
time: ""
},
{
content: "Hey Anna, did you get the paper done?",
signature: "Tommy McConville",
outgoing: false,
modifier: "fr",
imgURL: "@/assets/logo.png",
id: "3"
context: "Mesage from Tommy McConville",
subContext: "New Conversation",
modifier: "ai",
id: "3",
time: ""
},
{
content: "Yes posting it online soon.",
signature: "Tommy McConville",
outgoing: true,
modifier: "sf",
imgURL: "../../assets/logo.png",
id: "4"
context: "You to Tommy McConville",
subContext: "",
modifier: "ai",
id: "4",
time: ""
},
{
content: "Sweet, thanks!",
signature: "Tommy McConville",
outgoing: false,
modifier: "fr",
imgURL: "../../assets/logo.png",
id: "5"
context: "Message from Tommy McConville",
subContext: "",
modifier: "ai",
id: "5",
time: ""
},
{
content: "Hey do you know Enrique Hernandez?",
signature: "Crimata AI",
outgoing: false,
context: "Message from Crimata AI",
subContext: "",
modifier: "ai",
imgURL: "../../assets/logo.png",
id: "6"
id: "6",
time: ""
},
{
content: "Hey do you know Enrique Hernandez?",
signature: "Crimata AI",
outgoing: false,
context: "Message from Crimata",
subContext: "",
modifier: "ai",
imgURL: "../../assets/logo.png",
id: "7"
id: "7",
time: ""
},
{
content: "Hey do you know Enrique Hernandez?",
signature: "Crimata AI",
outgoing: false,
context: "Message from Crimata",
subContext: "",
modifier: "ai",
imgURL: "../../assets/logo.png",
id: "8"
id: "8",
time: ""
},
{
content: "Hey Anna, did you get the paper done?",
signature: "Tommy McConville",
outgoing: false,
modifier: "fr",
imgURL: "@/assets/logo.png",
id: "9"
context: "Message from Tommy McConville",
subContext: "",
modifier: "ai",
id: "9",
time: ""
},
{
content: "Hey Anna, did you get the paper done?",
signature: "Tommy McConville",
outgoing: false,
modifier: "fr",
imgURL: "@/assets/logo.png",
id: "10"
context: "Message from Tommy McConville",
subContext: "",
modifier: "ai",
id: "10",
time: ""
},
{
content: "Yes posting it online soon.",
signature: "Tommy McConville",
outgoing: true,
context: "Message from Tommy McConville",
subContext: "",
modifier: "sf",
imgURL: "../../assets/logo.png",
id: "11"
id: "11",
time: ""
},
{
content:
"I can be there at 5 pm tomorrow to greet you at a Chick file with my almond and chive salad.",
signature: "Tommy McConville",
outgoing: true,
context: "You to Tommy McConville",
subContext: "",
modifier: "sf",
imgURL: "../assets/logo.png",
id: "12"
id: "12",
time: ""
},
{
content: "Hey Anna, did you get the paper done?",
signature: "Tommy McConville",
outgoing: false,
modifier: "fr",
imgURL: "@/assets/logo.png",
id: "13"
context: "Message from Tommy McConville",
subContext: "",
modifier: "ai",
id: "13",
time: ""
},
{
content: "Hey Anna, did you get the paper done?",
signature: "Tommy McConville",
outgoing: false,
modifier: "fr",
imgURL: "@/assets/logo.png",
id: "14"
context: "Message from Tommy McConville",
subContext: "",
modifier: "ai",
id: "14",
time: ""
},
{
content: "Yes posting it online soon.",
signature: "Tommy McConville",
outgoing: true,
context: "You to Tommy McConville",
subContext: "",
modifier: "sf",
imgURL: "../../assets/logo.png",
id: "15"
id: "15",
time: ""
},
{
content: "Sweet, thanks!",
signature: "Tommy McConville",
outgoing: false,
modifier: "fr",
imgURL: "../../assets/logo.png",
id: "16"
context: "Message from Tommy McConville",
subContext: "",
modifier: "ai",
id: "16",
time: ""
},
{
content: "Hey do you know Enrique Hernandez?",
signature: "Crimata AI",
outgoing: false,
context: "Message from Crimata AI",
subContext: "",
modifier: "ai",
imgURL: "../../assets/logo.png",
id: "17"
id: "17",
time: ""
},
{
content: "Yes posting it online soon.",
signature: "Tommy McConville",
outgoing: true,
context: "You to Tommy McConville",
subContext: "",
modifier: "sf",
imgURL: "../../assets/logo.png",
id: "18"
id: "18",
time: ""
},
{
content: "Sweet, thanks!",
signature: "Tommy McConville",
outgoing: false,
modifier: "fr",
imgURL: "../../assets/logo.png",
id: "19"
context: "Message from Tommy McConville",
subContext: "",
modifier: "ai",
id: "19",
time: ""
},
];
export default Messages;
export default Messages;

View file

@ -6,7 +6,7 @@
xmlns:xlink="http://www.w3.org/1999/xlink"
@click="emittMessage"
>
<AI />
<!-- <AI /> -->
<MessageRenderer />
<InputItem />
<!-- <TitleBar /> -->
@ -23,7 +23,7 @@ import { Mitt } from "@/types/mitt/index";
import { defineComponent, ref, inject } from "vue";
export default defineComponent({
name: "UI",
components: { AI, MessageRenderer, InputItem, TitleBar },
components: { MessageRenderer, InputItem },
setup() {
const messages = Messages;
const count = ref(0);

View file

@ -0,0 +1,53 @@
export default function useAudioRenderer() {
const audioCtx = new (window.AudioContext)();
const analyzer = audioCtx.createAnalyser();
analyzer.fftSize = 2048;
const xInitial = 24;
const xFinal = 640;
// analyzer.minDecibels = -90;
analyzer.smoothingTimeConstant = 0.85;
const dataArray = new Uint8Array(analyzer.frequencyBinCount);
const sampleRate = 16000;
function convertBlock(buffer: ArrayBuffer): Float32Array {
// incoming data is an ArrayBuffer
const incomingData = new Uint8Array(buffer); // create a uint8 view on the ArrayBuffer
const l = incomingData.length; // length, we need this for the loop
const outputData = new Float32Array(incomingData.length); // create the Float32Array for output
for (let i = 0; i < l; i++) {
outputData[i] = (incomingData[i] - 128) / 128.0; // convert audio to float
}
return outputData; // return the Float32Array
}
const getAudioFrequencyData = () => {
}
const renderAudio = (payload) => {
// step 1: convert audio from Uint8 to Float32Array
const floatArray = convertBlock(payload.audio.buffer);
// step 2: source audio frequency {ffft} data
const buffer = audioCtx.createBuffer(1, floatArray.length, sampleRate);
buffer.copyToChannel(floatArray, 0, 0);
const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(analyzer);
source.start();
analyzer.getByteFrequencyData(dataArray);
// step 3: process audio frequency data
const dataView = dataArray.slice(xInitial, xFinal);
const sum = dataView.reduce((a, b) => a + b);
const average = sum / dataView.length;
const scaled = average / 255;
const zeroed = scaled - 0.6;
return zeroed;
}
return {
renderAudio
}
}

View file

@ -18,7 +18,7 @@ export default function useMessageItemAnims({
function adjustMessageRect(): void {
// must get dimensions of text box to fit bubble around
const padding = 20;
const padding = 12.5;
const textBox = messageText.getBBox();
messageRect.setAttribute("x", String(textBox.x - padding));
messageRect.setAttribute("y", String(textBox.y - padding));
@ -29,8 +29,7 @@ export default function useMessageItemAnims({
function translateMsgSignature(): void {
if (messageRect.getAttribute("height") !== null) {
const height = Number(messageRect.getAttribute("height"));
const p = -10;
signatureTranslate.value = `translate(-10 ${height + p})`;
signatureTranslate.value = `translate(-10 ${height})`;
}
}

View file

@ -1,35 +1,10 @@
import { computed, onMounted, ref } from "vue";
export default function useMessageItemComp(m: string, id: string) {
const x = ref(0)
const xOffset = async () => {
const paddingLeft = 40;
const messageMargin = 20;
const winW = 350;
let x: number;
const msg = await document.getElementById(id);
if (msg) {
const messageWidth = msg.getBoundingClientRect().width;
switch (m) {
case "sf":
x = winW - messageWidth;
break;
case "ai":
x = messageMargin + (winW -messageWidth) / 2;
break;
default:
x = paddingLeft;
}
return x;
}
}
onMounted(async () => {
const result = await xOffset();
if(result) {
x.value = result;
}
})
const paddingLeft = 30;
const winW = 350;
const messageWidth = ref(0);
const messageMarginTop = 75;
const textFill = computed(() => {
switch (m) {
@ -44,25 +19,59 @@ export default function useMessageItemComp(m: string, id: string) {
switch (m) {
case "sf":
return "#61b4f4";
case "ai":
return "#DDDDDD";
default:
default:
return "#fff";
}
});
const setMessageWidthRef = async () => {
const msg = await document.getElementById(id);
if (msg) {
messageWidth.value = msg.getBoundingClientRect().width;
}
}
const calculateXoffset = async () => {
switch (m) {
case "sf":
x.value = winW - messageWidth.value;
break;
default:
x.value = paddingLeft;
// to center a message
// x = messageMargin + (winW -messageWidth) / 2;
}
}
// TODO implement func in order to reduce dual renderer height call startingOffset computed prop
const getRendererHeight = () => {
const messageRenderer = document.getElementById('messageRenderer');
if (messageRenderer) {
const rect = messageRenderer.getBoundingClientRect();
return rect.height;
}
}
const startingOffset = computed(() => {
const messageRenderer = document.getElementById('messageRenderer')
const messageRenderer = document.getElementById('messageRenderer');
if (messageRenderer) {
const rect = messageRenderer.getBoundingClientRect();
let Y = 600;
if (rect.height > 500) {
Y = rect.height + 75;
Y = rect.height + messageMarginTop;
}
return `translate(${x.value} ${Y})`;
}
});
onMounted(async () => {
// to calculate X offset:
// First: set message width ref
await setMessageWidthRef();
// Then: calculate message X offset
calculateXoffset();
})
return {
textFill,
bubbleFill,

View file

@ -1,10 +1,10 @@
export interface Message {
content: string;
signature: string;
outgoing: boolean;
context: string;
subContext: string;
modifier: string;
imgURL: string;
id: string;
time: string;
}
interface MessageTransform {