<template>
- <div id="app">
+ <div id="app" v-if="!loading">
<button class="titlebar">
<router-view/>
</div>
+ <Splash v-else />
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import { useIpc } from "@/modules/ipc";
+import { useAuth } from "@/modules/auth";
+import Splash from "@/components/splash.vue"
export default defineComponent({
+ components: { Splash },
- setup() {
+ setup() {
const { invoke } = useIpc();
+ const { loading } = useAuth();
const callNavbar = (action: string) => {
- switch(action) {
- case 'close':
- invoke('nav-bar', 'close');
- break;
- case 'min':
- invoke('nav-bar', 'min');
- break;
- }
- }
-
- return {
- callNavbar
- }
+ invoke('nav-bar', action);
}
+
+ return {
+ callNavbar,
+ loading
+ }
+ }
})
</script>
});
ai.on('data', (chunk: string) => {
// turn string base64 data into buffer object.
- audioInput.push(Buffer.from(chunk, encoding));
+ console.log('received string chunk of length', chunk.length)
+ const buf = Buffer.from(chunk, encoding);
+ audioInput.push(buf);
});
}
} else {
// stop mic data flow on space bar key up.
ai.pause();
- sendAudio(Buffer.concat(audioInput));
+ const audio = Buffer.concat(audioInput);
+ sendAudio(audio);
audioInput = [];
}
}
const windowMount = (): void => {
backgroundMitt.emit('window-active', true);
+ ipcMain.removeAllListeners('update-recorder');
ipcMain.on('update-recorder', updateRecorder);
- // handle renderer auth-token event
- ipcMain.removeHandler('nav-bar'); // avoid setting duplicate handlers
- ipcMain.handle('nav-bar', navBarHandler);
+ // handle renderer auth-token event
+ ipcMain.removeHandler('nav-bar'); // avoid setting duplicate handlers
+ ipcMain.handle('nav-bar', navBarHandler);
}
const windowDismount = (): void => {
width: options.width,
height: options.height,
resizable: options.resizable,
+ backgroundColor: '#EBEBEB',
frame: false,
minWidth: 350,
minHeight: 500,
// Handle window close.
win.on("closed", windowDismount);
+ win.once('ready-to-show', () => {
+ if (win) win.show()
+ })
+
win.webContents.on('did-finish-load', () => {
- windowMount();
+ windowMount();
resolve();
});
--- /dev/null
+<template>
+ <div
+ class="message"
+ :id="`${message.modifier}Message`"
+ :key="message.id"
+ >
+
+ <!-- Includes bubble and context. -->
+ <div class="messageBox" :id="`${message.modifier}MessageBox`">
+
+ <!-- message bubble -->
+ <div
+ class="bubble"
+ :id="`${message.modifier}Bubble`"
+ >
+ {{ message.content }}
+ </div>
+
+ <!-- message context -->
+ <span class="context">
+
+ <!-- Render Crimata icon or initials. -->
+ <div v-if="message.avatar == 'crimata'" class="photo">
+ <img src="@/assets/crimata.svg"/>
+ </div>
+
+ <div v-else class="photo">
+ {{ message.avatar }}
+ </div>
+
+ {{ message.context }}
+
+ </span>
+
+ </div>
+
+ </div>
+</template>
+
+<script lang='ts'>
+ import {defineComponent} from 'vue';
+ export default defineComponent({
+ name: "MessageItem",
+ props: {
+ message: {
+ type: Object,
+ required: true
+ }
+ }
+ })
+</script>
+
+<style lang="scss" scoped>
+
+ .message {
+ width: 100vw;
+
+ display: flex;
+ flex-direction: column;
+
+ padding-top: 9px;
+ padding-bottom: 9px;
+
+ }
+
+ .message:first-child {
+ margin-top: 55px;
+ }
+
+ .message:last-child {
+ margin-bottom: 18px;
+ }
+
+ #aiMessage {
+ align-items: flex-start;
+ }
+
+ #sfMessage {
+ align-items: flex-end;
+ }
+
+ #aiMessageBox {
+ margin-left: 20px;
+ }
+
+ #sfMessageBox {
+ margin-right: 20px;
+ }
+
+ .bubble {
+ max-width: 66vw;
+
+ display: flex;
+ flex-direction: column;
+
+ font-family: "SF Pro Text";
+ font-size: 14px;
+
+ padding: 10px;
+
+ border-radius: 10px;
+ }
+
+ #aiBubble {
+ background-color: white;
+ }
+
+ #sfBubble {
+ background-color: #58c4fd;
+ color: white;
+ }
+
+ .context {
+ display: flex;
+ align-items: center;
+
+ font-family: "SF Compact Display";
+ font-size: 12px;
+ font-weight: bold;
+ margin-left: 7px;
+ margin-top: 5px;
+ }
+
+ .photo {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ margin-right: 5px;
+
+ width: 30px;
+ height: 30px;
+
+ font-size: 14px;
+
+ background-color: white;
+ border-radius: 15px;
+ }
+
+</style>
\ No newline at end of file
<template>
<div id="splash">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="57.046"
- height="52.759"
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="57.046"
+ height="52.759"
viewBox="0 0 57.046 52.759"
>
<g transform="translate(-4127 -507)">
<g transform="translate(3949 332.206)">
<g transform="translate(178 174.794)">
<g transform="translate(0 27.405)">
- <circle
- cx="12.677"
- cy="12.677"
- r="12.677"
- transform="translate(0 0)"
+ <circle
+ cx="12.677"
+ cy="12.677"
+ r="12.677"
+ transform="translate(0 0)"
fill="#383838"
/>
- <circle
- cx="12.677"
- cy="12.677"
- r="12.677"
- transform="translate(31.692 0)"
+ <circle
+ cx="12.677"
+ cy="12.677"
+ r="12.677"
+ transform="translate(31.692 0)"
fill="#383838"
/>
</g>
- <circle
- cx="12.677"
- cy="12.677"
- r="12.677"
- transform="translate(15.822 0)"
+ <circle
+ cx="12.677"
+ cy="12.677"
+ r="12.677"
+ transform="translate(15.822 0)"
fill="#383838"
/>
- <path
- d="M36.27,83.67"
- transform="translate(-23.569 -43.588)"
+ <path
+ d="M36.27,83.67"
+ transform="translate(-23.569 -43.588)"
fill="#ff0"
/>
</g>
</g>
</g>
- </svg>
+ </svg>
</div>
</template>
<script lang="ts">
-
+
import { defineComponent } from "vue";
export default defineComponent({
-
name: "Splash",
-
});
</script>
}
-</style>
\ No newline at end of file
+</style>
-import { reactive, toRefs } from 'vue';
+import { reactive, toRefs, ref } from 'vue';
import { useIpc } from './ipc';
interface AuthState {
error: undefined,
});
+const loading = ref(true);
+
const AUTH_KEY = 'crimata_token';
const token = window.localStorage.getItem(AUTH_KEY);
}
const authToken = async () => {
+ loading.value = true;
const { invoke } = useIpc();
try {
const res = await invoke('auth-session', token);
window.localStorage.removeItem(AUTH_KEY);
state.accessToken = null;
}
+ loading.value = false;
}
// authenticate on auth event
setToken,
logout,
...toRefs(state), // accessToken, error
+ loading
}
}
RouteRecordRaw
} from "vue-router";
import Home from "@/views/messenger.vue";
-import Splash from "@/views/splash.vue";
import { useAuth } from '@/modules/auth';
// Define the routes (/*) for the app here.
component: Home,
meta: { requiresAuth: true },
},
- {
- path: "/splash",
- name: "splash",
- component: Splash,
- meta: {
- requiresAuth: false
- },
- },
{
path: "/login",
name: "login",
<TextAudioInput />
<!-- List of message bubbles. -->
- <div id="messenger">
-
- <!-- message box (view width) -->
- <div
- class="message"
- :id="`${message.modifier}Message`"
- v-for="message in messages"
- >
-
- <!-- Includes bubble and context. -->
- <div class="messageBox" :id="`${message.modifier}MessageBox`">
-
- <!-- message bubble -->
- <div
- class="bubble"
- :id="`${message.modifier}Bubble`"
- >
- {{ message.content }}
- </div>
-
- <!-- message context -->
- <span class="context">
-
- <!-- Render Crimata icon or initials. -->
- <div v-if="message.avatar == 'crimata'" class="photo">
- <img src="@/assets/crimata.svg"/>
- </div>
-
- <div v-else class="photo">
- {{ message.avatar }}
- </div>
-
- {{ message.context }}
-
- </span>
-
- </div>
-
- </div>
-
+ <div id="messenger" >
+ <MessageItem v-for="message in messages" :message="message" :key="message.id"/>
</div>
-
+
</template>
<script lang="ts">
-import { defineComponent, computed, reactive, onMounted, ref } from "vue";
-
+import { defineComponent, reactive, onMounted, ref, onUnmounted } from "vue";
import TextAudioInput from "@/components/taInput/index.vue";
import { Message } from "@/types/message/index";
+import MessageItem from '@/components/messageItem.vue'
export default defineComponent({
name: "Messenger",
components: {
- TextAudioInput
+ TextAudioInput,
+ MessageItem,
},
- setup(props) {
+ setup() {
// List of messages in the view.
// Oldest message first.
- const messages: Message[] = reactive([])
+ const messages: Message[] = reactive([]);
// Render a message.
const render = (message: Message) => {
- messages.push(message)
- console.log(`Pushing: ${message.modifier}: ${message.avatar}, ${message.content}`)
+ messages.push(message);
+ console.log(`Pushing: ${message.modifier}: ${message.avatar}, ${message.content}`);
}
onMounted(() => {
})
+ onUnmounted(() => {
+ window.ipcRenderer.removeAllListeners('render-message');
+ })
+
return {
messages,
}
display: none;
}
- .message {
- width: 100vw;
-
- display: flex;
- flex-direction: column;
-
- padding-top: 9px;
- padding-bottom: 9px;
-
- }
-
- .message:first-child {
- margin-top: 55px;
- }
-
- .message:last-child {
- margin-bottom: 18px;
- }
-
- #aiMessage {
- align-items: flex-start;
- }
-
- #sfMessage {
- align-items: flex-end;
- }
-
- #aiMessageBox {
- margin-left: 20px;
- }
-
- #sfMessageBox {
- margin-right: 20px;
- }
-
- .bubble {
- max-width: 66vw;
-
- display: flex;
- flex-direction: column;
-
- font-family: "SF Pro Text";
- font-size: 14px;
-
- padding: 10px;
-
- border-radius: 10px;
- }
-
- #aiBubble {
- background-color: white;
- }
-
- #sfBubble {
- background-color: #58c4fd;
- color: white;
- }
-
- .context {
- display: flex;
- align-items: center;
-
- font-family: "SF Compact Display";
- font-size: 12px;
- font-weight: bold;
- margin-left: 7px;
- margin-top: 5px;
- }
-
- .photo {
- display: flex;
- justify-content: center;
- align-items: center;
-
- margin-right: 5px;
-
- width: 30px;
- height: 30px;
-
- font-size: 14px;
-
- background-color: white;
- border-radius: 15px;
- }
-
-
-
</style>
closeOnError: false,
},
});
-// ia.setEncoding('base64');
+ia.setEncoding('base64');
ia.start();
ia.on('error', (e) => {
console.log('error recording audio', + e);
});
ia.on('data', (chunk) => {
- audioInput.push(chunk);
-});
-const ao = new portAudio.AudioIO({
- outOptions: {
- channelCount: 1,
- sampleFormat: portAudio.SampleFormat16Bit,
- sampleRate: 16000,
- deviceId: -1, // Use -1 or omit the deviceId to select the default device
- closeOnError: true // Close the stream if an audio error is detected, if set false then just log the error
- }
+ const buf = Buffer.from(chunk, 'base64');
+ audioInput.push(buf);
});
-ao.start();
-
-const callback = () => {
- console.log('yayayay')
-}
-
-const play = (audio) => {
-
- console.log('audio length', audio.length);
- let i = 0;
- write();
-
- function write() {
- let ok = true;
- do {
- const chunk = audio[i]
- console.log(chunk, i)
-
- i++;
- if (i === audio.length - 1) {
- // Last time!
- ao.write(chunk, null, callback);
- } else {
- // See if we should continue, or wait.
- // Don't pass the callback, because we're not done yet.
- ok = ao.write(chunk, null);
- }
- } while (i < audio.length - 1 && ok);
- if (i < audio.length - 1) {
- // Had to stop early!
- // Write some more once it drains.
- ao.on('drain', write);
- }
- }
-
-}
-
async function processAudio() {
const buf = Buffer.concat(audioInput);
- play(audioInput);
-
audioInput = [];
transcribeSpeech({