refactor messageItem functionality + update design
add dynamic title bar updat Message object type
This commit is contained in:
parent
e1e60f7609
commit
f0f19687b0
9 changed files with 245 additions and 177 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
53
src/composables/audioRenderer/useAudioRenderer.ts
Normal file
53
src/composables/audioRenderer/useAudioRenderer.ts
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue