// });
//
-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.
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
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;
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;
}
});
- 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,
<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>
{
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;
\ No newline at end of file
xmlns:xlink="http://www.w3.org/1999/xlink"
@click="emittMessage"
>
- <AI />
+ <!-- <AI /> -->
<MessageRenderer />
<InputItem />
<!-- <TitleBar /> -->
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);
--- /dev/null
+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
+ }
+}
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));
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})`;
}
}
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) {
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,
export interface Message {
content: string;
- signature: string;
- outgoing: boolean;
+ context: string;
+ subContext: string;
modifier: string;
- imgURL: string;
id: string;
+ time: string;
}
interface MessageTransform {