add splash screen on load
This commit is contained in:
parent
aa4b87d39d
commit
346bfcba01
9 changed files with 218 additions and 243 deletions
29
src/App.vue
29
src/App.vue
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
|||
140
src/components/messageItem.vue
Normal file
140
src/components/messageItem.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in a new issue