add splash screen on load

This commit is contained in:
riqo 2021-02-28 14:27:51 -06:00
commit 346bfcba01
9 changed files with 218 additions and 243 deletions

View file

@ -1,5 +1,5 @@
<template>
<div id="app">
<div id="app" v-if="!loading">
<button class="titlebar">
@ -15,33 +15,32 @@
<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>

View file

@ -35,7 +35,9 @@ export const initAudioIO = () => {
});
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);
});
}
@ -54,7 +56,8 @@ export const updateRecorder = (_event, record: boolean): void => {
} else {
// stop mic data flow on space bar key up.
ai.pause();
sendAudio(Buffer.concat(audioInput));
const audio = Buffer.concat(audioInput);
sendAudio(audio);
audioInput = [];
}
}

View file

@ -30,10 +30,11 @@ let win: BrowserWindow | null;
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 => {
@ -58,6 +59,7 @@ export const createWindow = async (options: WindowSettings): Promise<void> => {
width: options.width,
height: options.height,
resizable: options.resizable,
backgroundColor: '#EBEBEB',
frame: false,
minWidth: 350,
minHeight: 500,
@ -82,8 +84,12 @@ export const createWindow = async (options: WindowSettings): Promise<void> => {
// 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();
});

View file

@ -0,0 +1,140 @@
<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>

View file

@ -1,57 +1,55 @@
<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>
@ -68,4 +66,4 @@ export default defineComponent({
}
</style>
</style>

View file

@ -1,4 +1,4 @@
import { reactive, toRefs } from 'vue';
import { reactive, toRefs, ref } from 'vue';
import { useIpc } from './ipc';
interface AuthState {
@ -11,6 +11,8 @@ const state = reactive<AuthState>({
error: undefined,
});
const loading = ref(true);
const AUTH_KEY = 'crimata_token';
const token = window.localStorage.getItem(AUTH_KEY);
@ -20,6 +22,7 @@ if (token) {
}
const authToken = async () => {
loading.value = true;
const { invoke } = useIpc();
try {
const res = await invoke('auth-session', token);
@ -32,6 +35,7 @@ const authToken = async () => {
window.localStorage.removeItem(AUTH_KEY);
state.accessToken = null;
}
loading.value = false;
}
// authenticate on auth event
@ -56,5 +60,6 @@ export const useAuth = () => {
setToken,
logout,
...toRefs(state), // accessToken, error
loading
}
}

View file

@ -5,7 +5,6 @@ import {
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.
@ -16,14 +15,6 @@ const routes: Array<RouteRecordRaw> = [
component: Home,
meta: { requiresAuth: true },
},
{
path: "/splash",
name: "splash",
component: Splash,
meta: {
requiresAuth: false
},
},
{
path: "/login",
name: "login",

View file

@ -3,74 +3,37 @@
<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(() => {
@ -82,6 +45,10 @@ export default defineComponent({
})
onUnmounted(() => {
window.ipcRenderer.removeAllListeners('render-message');
})
return {
messages,
}
@ -112,91 +79,4 @@ export default defineComponent({
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>

View file

@ -52,68 +52,21 @@ const ia = new portAudio.AudioIO({
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 buf = Buffer.from(chunk, 'base64');
audioInput.push(buf);
});
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
}
});
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({