Compare commits

..
46 changed files with 646 additions and 1010 deletions

View file

View file

@ -66,9 +66,25 @@
"spectron": "11.0.0", "spectron": "11.0.0",
"typescript": "~3.9.3", "typescript": "~3.9.3",
"vue-cli-plugin-electron-builder": "~2.0.0-rc.6", "vue-cli-plugin-electron-builder": "~2.0.0-rc.6",
"vue-jest": "^5.0.0-0", "vue-jest": "^5.0.0-0"
"worker-loader": "^3.0.8", },
"worklet-loader": "^1.0.0" "vue": {
"lintOnSave": false,
"pluginOptions": {
"electronBuilder": {
"mainProcessFile": "./src/init.ts",
"rendererProcessFile": "./src/render/main.ts",
"preload": "./src/render/preload.ts",
"builderOptions": {
"appId": "com.crimata.ElectronUpdaterApp",
"artifactName": "${productName}-${version}.${ext}",
"publish": {
"provider": "generic",
"url": "https://gitlab.com/api/v4/projects/25637892/jobs/artifacts/main/raw/dist_electron?job=build"
}
}
}
}
}, },
"gitHooks": { "gitHooks": {
"pre-commit": "lint-staged" "pre-commit": "lint-staged"

View file

@ -1,12 +0,0 @@
class AudioProcessor extends AudioWorkletProcessor {
process (inputs, outputs, parameters) {
console.log(inputs);
console.log(outputs);
console.log(parameters);
return true
}
}
registerProcessor('processor', AudioProcessor)

View file

@ -1,33 +0,0 @@
interface AudioWorkletProcessor {
readonly port: MessagePort;
process(
inputs: Float32Array[][],
outputs: Float32Array[][],
parameters: Record<string, Float32Array>
): boolean;
}
declare let AudioWorkletProcessor: {
prototype: AudioWorkletProcessor;
new (options?: AudioWorkletNodeOptions): AudioWorkletProcessor;
};
declare function registerProcessor(
name: string,
processorCtor: (new (
options?: AudioWorkletNodeOptions
) => AudioWorkletProcessor) & {
parameterDescriptors?: AudioParamDescriptor[];
}
): undefined;
class AudioProcessor extends AudioWorkletProcessor {
process (inputs: Float32Array[][], outputs: Float32Array[][], parameters: Record<string, Float32Array>) {
console.log(inputs);
console.log(outputs);
console.log(parameters);
return true
}
}
registerProcessor('audio-processor', AudioProcessor)

View file

@ -1,7 +1,7 @@
import { postAuth, postLogin, postLogout } from "@/api/account"; import { postAuth, postLogin, postLogout } from "@/api/account";
import { endSession, launchSession } from "@/session"; import { endSession, launchSession } from "@/session";
import { getToken, setToken, setProfile, clearStore } from "./store"; import { getToken, setToken, setProfile, getProfile, clearStore } from "./store";
import { parseAuthRes } from "./auth"; import { parseAuthRes } from "./auth";
import { ipcEmit } from "@/composables/useEmitter"; import { ipcEmit } from "@/composables/useEmitter";
@ -83,4 +83,13 @@ export const accountLogout = async (): Promise<Error | void> => {
} }
export const updateAppState = (): void => {
const profile = getProfile();
ipcEmit("set-profile", profile);
// ipcEmit('messages') etc
}

View file

@ -71,10 +71,12 @@ export class IpcListener<InputParam> implements IIpcListener<InputParam> {
ipcMain.removeAllListeners(this.channel); ipcMain.removeAllListeners(this.channel);
} }
private _onPost = (_e: IpcMainEvent, payload: InputParam): void => { private _onPost = (_e: IpcMainEvent, payload?: string | null): void => {
console.log(`[IPC] Post: ${this.channel}`); console.log(`[IPC] Post: ${this.channel}`);
this._listenerCallback(payload);
const params = payload ? JSON.parse(payload) : null;
this._listenerCallback(params);
} }
} }

View file

@ -11,13 +11,7 @@ let _connectionCheckInterval: ReturnType<typeof setTimeout>;
export default function useWebSockets( export default function useWebSockets(
messageCallback: (message: string) => void, messageCallback: (message: string) => void,
connectionStatusCallback: (status: string) => void, connectionStatusCallback: (alive: boolean) => void,
statusOptions?: {
openMessage: string,
pongMessage: string,
closeMessage: string,
pingErrorMessage: string,
},
) { ) {
let socket: WebSocket; let socket: WebSocket;
@ -47,15 +41,13 @@ export default function useWebSockets(
socket.send(JSON.stringify({key: secret})); socket.send(JSON.stringify({key: secret}));
connectionStatusCallback(statusOptions ? statusOptions.openMessage : "Connection Opened");
// ping server // ping server
_connectionCheckInterval = setInterval(() => { _connectionCheckInterval = setInterval(() => {
socket.ping(null, true, (e: Error) => { socket.ping(null, true, (e: Error) => {
if (e) { if (e) {
socket.close(); socket.close();
connectionStatusCallback(statusOptions ? statusOptions.pingErrorMessage : "Connection Lost"); connectionStatusCallback(false);
setTimeout(() => connect(socketUrl, secret), _reconnectTimeout); setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
} }
}); });
@ -70,20 +62,15 @@ export default function useWebSockets(
}); });
socket.on("close", (event: WebSocket.CloseEvent) => { socket.on("close", (event: WebSocket.CloseEvent) => {
connectionStatusCallback(statusOptions ? statusOptions.closeMessage : "Connection Closed"); connectionStatusCallback(false);
clearInterval(_connectionCheckInterval); clearInterval(_connectionCheckInterval);
if (!event.wasClean) { if (!event.wasClean) {
setTimeout(() => { setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
connect(socketUrl, secret), _reconnectTimeout
});
} }
}); });
socket.on("pong", () => connectionStatusCallback(statusOptions ? statusOptions.pongMessage : "Pong")); socket.on("pong", () => connectionStatusCallback(true));
socket.on('error', () => {});
} }
@ -91,7 +78,7 @@ export default function useWebSockets(
if (socket) { if (socket) {
socket.close(); socket.close();
} }
}; }
return { return {
connect, connect,

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
import { accountLogin, accountLogout } from "@/account"; import { accountLogin, accountLogout, accountProfile } from "@/account";
import { IpcHandler } from "@/composables/useIpcMain"; import { IpcHandler } from "@/composables/useIpcMain";
import { flush } from "@/audio"; import { flush } from "@/audio";

View file

@ -2,14 +2,11 @@
import { IpcListener } from "@/composables/useIpcMain" import { IpcListener } from "@/composables/useIpcMain"
import { sendMessage } from '@/session'; import { sendMessage } from '@/session';
import { collect } from "@/audio"; import { collect } from "@/audio";
import { updateAppState } from "@/session"; import { updateAppState } from "@/account";
import { onNavBar } from "@/window";
const CLIENT_MESSAGE_CHANNEL = "post-session-send" const CLIENT_MESSAGE_CHANNEL = "post-session-send"
const GET_AUDIO_CHANNEL = "post-audio-collect"; const GET_AUDIO_CHANNEL = "post-audio-collect";
const APP_MOUNT_CHANNEL = "post-app-mount"; const APP_MOUNT_CHANNEL = "post-app-mount";
const NAV_BAR_CHANNEL = "post-nav-bar";
const WINDOW_BLUR_CHANNEL = "post-window-focus";
export const messageListener = new IpcListener<Message>({ export const messageListener = new IpcListener<Message>({
channel: CLIENT_MESSAGE_CHANNEL, channel: CLIENT_MESSAGE_CHANNEL,
@ -25,22 +22,3 @@ export const appMountListener = new IpcListener<null>({
channel: APP_MOUNT_CHANNEL, channel: APP_MOUNT_CHANNEL,
listenerCallback: updateAppState listenerCallback: updateAppState
}); });
export const navBarListener = new IpcListener({
channel: NAV_BAR_CHANNEL,
listenerCallback: onNavBar
});
const onWindowFocus: IpcListenerCallback<{ isFocused: boolean }> = (payload) => {
if (payload.isFocused) {
console.log('window is focused')
} else {
console.log('window is blurred')
}
}
export const windowFocusListener = new IpcListener({
channel: WINDOW_BLUR_CHANNEL,
listenerCallback: onWindowFocus
});

View file

@ -10,8 +10,8 @@
*/ */
import initIpcMain from "@/ipc/index"; import initIpcMain from "@/ipc/index";
import { accountAuth } from "./account"; import { accountAuth, updateAppState } from "./account";
import { launchSession, updateAppState } from "./session"; import { launchSession } from "./session";
import createWindow from "./window"; import createWindow from "./window";
let authState: AuthState | null; let authState: AuthState | null;

View file

@ -1,67 +0,0 @@
import { ipcEmit } from "@/composables/useEmitter";
export default class Messages {
messages: Message[];
constructor(messages: Message[]) {
console.log(messages);
this.messages = messages;
this.emit();
}
emit() {
ipcEmit("init-messages", this.messages);
}
update(update: Update) {
if (update.name === "add") {
this.messages.push(update.data as Message);
ipcEmit("add-message", update.data);
}
else if (update.name === "annotate") {
this._annotate(update.data as Annotation);
ipcEmit("update-message", update.data);
}
else {
this._delete(update.data as string);
ipcEmit("delete-message", update.data);
}
}
_annotate(annotation: Annotation) {
for (let i in this.messages) {
if (this.messages[i].uid == annotation.uid) {
if (annotation.name == "content") {
this.messages[i].content = annotation.data as string[];
}
if (annotation.name == "context") {
this.messages[i].context = annotation.data as string;
}
ipcEmit("update-message", annotation);
}
}
}
_delete(uid: string) {
for (var i = 0; i < this.messages.length; i++) {
if (this.messages[i].uid === uid) {
this.messages.splice(i, 1);
break;
}
}
}
}

View file

@ -21,7 +21,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, onMounted, onUnmounted } from "vue"; import { defineComponent, onMounted } from "vue";
import { IpcRendererEvent } from "electron"; import { IpcRendererEvent } from "electron";
import Splash from "@/render/components/splash.vue"; import Splash from "@/render/components/splash.vue";
@ -29,7 +29,7 @@ import Messenger from "@/render/components/messenger.vue";
import Login from "@/render/components/login.vue"; import Login from "@/render/components/login.vue";
import Header from "@/render/components/header.vue"; import Header from "@/render/components/header.vue";
import { profile, authComplete } from "@/render/shared/profile"; import { profile, authComplete } from "@/render/composables/useProfile";
import { postAppMount } from "@/render/ipc"; import { postAppMount } from "@/render/ipc";
export default defineComponent({ export default defineComponent({
@ -43,26 +43,7 @@ export default defineComponent({
setup() { setup() {
const onFocus = () => { onMounted(() => postAppMount());
console.log('window is focused');
}
const onBlur = () => {
console.log('window is blurred');
}
onMounted(() => {
// subscribe to visibility change events
window.addEventListener('blur', onBlur);
window.addEventListener('focus', onFocus);
postAppMount()
});
onUnmounted(() => {
window.removeEventListener("blur", onBlur);
window.removeEventListener("focus", onFocus);
});
return { return {
authComplete, authComplete,

View file

@ -1,33 +0,0 @@
interface AudioWorkletProcessor {
readonly port: MessagePort;
process(
inputs: Float32Array[][],
outputs: Float32Array[][],
parameters: Record<string, Float32Array>
): boolean;
}
declare let AudioWorkletProcessor: {
prototype: AudioWorkletProcessor;
new (options?: AudioWorkletNodeOptions): AudioWorkletProcessor;
};
declare function registerProcessor(
name: string,
processorCtor: (new (
options?: AudioWorkletNodeOptions
) => AudioWorkletProcessor) & {
parameterDescriptors?: AudioParamDescriptor[];
}
): undefined;
class AudioProcessor extends AudioWorkletProcessor {
process (inputs: Float32Array[][], outputs: Float32Array[][], parameters: Record<string, Float32Array>) {
console.log(inputs);
console.log(outputs);
console.log(parameters);
return true
}
}
registerProcessor('audio-processor', AudioProcessor)

View file

@ -1,9 +0,0 @@
// MyWorkletNode.js
export default class MyWorkletNode extends AudioWorkletNode {
constructor(context) {
super(context, '-processor')
console.log(this.channelCount)
}
}

View file

@ -1,74 +0,0 @@
import AudioProcessor from "./audio.worklet.ts";
console.log('YAYAYAYYA', AudioProcessor)
//TODO: need to set export directory for audio worklet
// webpack!!!
const constraints = {
audio: {
echoCancellation: true,
autoGainControl: true,
noiseSuppression: true,
channelCount: 1
},
video: false
}
async function getWebAudioMediaStream() {
if (!window.navigator.mediaDevices) {
throw new Error(
"This browser does not support web audio or it is not enabled."
);
}
try {
const result = await window.navigator.mediaDevices.getUserMedia(constraints);
return result;
} catch (e) {
switch (e.name) {
case "NotAllowedError":
throw new Error(
"A recording device was found but has been disallowed for this application. Enable the device in the browser settings."
);
case "NotFoundError":
throw new Error(
"No recording device was found. Please attach a microphone and click Retry."
);
default:
throw e;
}
}
}
export async function setupAudio() {
// Get the browser audio. Awaits user "allowing" it for the current tab.
const mediaStream = await getWebAudioMediaStream();
const context = new window.AudioContext();
const audioSource = context.createMediaStreamSource(mediaStream);
let node;
// Add our audio processor worklet to the context.
try {
await context.audioWorklet.addModule("audio.worklet.js");
} catch (e) {
throw new Error(
`Failed to load audio analyzer worklet. Further info: ${e.message}`
);
}
node = new AudioWorkletNode(context, 'audio-processor')
// Connect the audio source (microphone output) to our analysis node.
audioSource.connect(node);
// Connect our analysis node to the output. Required even though we do not
// output any audio. Allows further downstream audio processing or output to
// occur.
node.connect(context.destination);
return { context, node };
}

View file

@ -1,17 +1,53 @@
<template> <template>
<div :class="`bubble ${modifier}-bubble ${modifier}-${child}`"> <div :class="`${type}-message`">
{{ content }}
<!-- message bubble -->
<div :class="`${type}-${child}-bubble`">
<div class="notification-dot"/>
<div class="error-dot"/>
{{ text }}
</div>
<!-- message context -->
<span class="context">
<div :class="`${type}-icon`"/>
{{ context }}
</span>
</div> </div>
</template> </template>
<script lang='ts'> <script lang='ts'>
import { defineComponent } from 'vue'; import { defineComponent, ref, onMounted } from 'vue';
export default defineComponent({ export default defineComponent({
name: "Bubble", name: "Bubble",
props: ["modifier", "content", "child"], props: ["text", "context", "type", "position"],
setup() {
const seen = ref(false);
/* initialize seen state */
if (document.visibilityState === "visible") {
seen.value = true;
} else seen.value = false;
onMounted(() => {
if(seen.value)
document.addEventListener("visibilitychange", () => {
seen.value = true
});
});
return {
seen
};
}
}) })
@ -19,55 +55,277 @@
<style lang="scss" scoped> <style lang="scss" scoped>
.bubble { .message {
position: relative; width: 100vw;
max-width: 66vw; display: flex;
font-family: "SF Pro Text"; flex-direction: column;
font-size: 14px; padding-top: 9px;
padding: 10px; padding-bottom: 9px;
border-radius: 18px; }
margin-bottom: 4px;
}
.ai-bubble { .message:first-child {
background-color: #FFFFFF; margin-top: 55px;
margin-left: 15px; }
}
.client-bubble { .message:last-child {
color: white; margin-bottom: 18px;
background-color: #58C4FD; }
margin-right: 15px;
}
.ai-first-child { .sf-message {
border-bottom-left-radius: 9px; @extend .message;
} justify-content: flex-end;
}
.ai-middle-child { .ai-message, .fr-message {
border-top-left-radius: 9px; @extend .message;
border-bottom-left-radius: 9px; justify-content: flex-start;
} }
.ai-last-child { .bubble {
border-top-left-radius: 9px; position: relative;
} max-width: 66vw;
font-family: "SF Pro Text";
font-size: 14px;
padding: 10px;
border-radius: 18px;
}
.client-first-child { .sf-bubble {
border-bottom-right-radius: 9px; @extend .bubble;
} background-color: #58c4fd;
color: white;
}
.client-middle-child {
border-top-right-radius: 9px;
border-bottom-right-radius: 9px;
}
.client-last-child {
border-top-right-radius: 9px;
}
.ai-none-child, .client-none-child {
border-top-right-radius: 9px;
}
</style>
.firstChildMessage {
padding-bottom: 2px;
}
.middleChildMessage {
padding-top: 2px;
padding-bottom: 2px;
}
.lastChildMessage {
padding-top: 2px;
}
#aiMessage {
align-items: flex-start;
}
#sfMessage {
align-items: flex-end;
}
#frMessage {
align-items: flex-start;
}
.messageBox {
position: relative;
display: flex;
flex-direction: column;
// Animate on render.
animation-name: appear;
animation-duration: 0.25s;
}
#aiMessageBox {
margin-left: 20px;
align-items: flex-start;
}
#sfMessageBox {
margin-right: 20px;
align-items: flex-end;
}
#frMessageBox {
margin-left: 20px;
align-items: flex-start;
}
.bubble {
position: relative;
max-width: 66vw;
display: flex;
flex-direction: column;
font-family: "SF Pro Text";
font-size: 14px;
padding: 10px;
border-radius: 18px;
}
@keyframes appear {
10% {
transform: scale(0.3);
}
100% {
transform: scale(1);
}
}
#aiBubble {
background-color: white;
}
#sfBubble {
background-color: #58c4fd;
color: white;
}
#frBubble {
background-color: white;
}
.sf-firstChildBubble {
border-bottom-right-radius: 9px;
}
.sf-middleChildBubble {
border-top-right-radius: 9px;
border-bottom-right-radius: 9px;
}
.sf-lastChildBubble {
border-top-right-radius: 9px;
}
.ai-firstChildBubble, .fr-firstChildBubble {
border-bottom-left-radius: 9px;
}
.ai-middleChildBubble, .fr-middleChildBubble {
border-top-left-radius: 9px;
border-bottom-left-radius: 9px;
}
.ai-lastChildBubble, .fr-lastChildBubble {
border-top-left-radius: 9px;
}
.notify {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #58D9FF;
top: -5px;
left: -5px;
border: 2px solid #EBEBEB;
transform: scale(0);
animation-name: notify-anim;
animation-duration: 5s;
}
@keyframes notify-anim {
0%, 90% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
.context {
position: relative;
display: flex;
align-items: center;
font-family: "SF Compact Display";
font-size: 12px;
font-weight: bold;
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;
}
// Apply for audio message playback.
.playing {
animation-name: circle1;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes circle1 {
0%,
100% {
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
}
25% {
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
}
50% {
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
}
75% {
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
}
}
.contentLoader {
display: flex;
}
.contentLoaderDot {
width: 5px;
height: 5px;
margin: 2px;
border-radius: 2.5px;
background-color: #357CA2;
}
.questionMark {
font-weight: 900;
color: #357CA2;
}
// .divider {
// width: 100vw;
// display: flex;
// justify-content: center;
// align-items: center;
// font-family: "SF Compact Display";
// font-size: 12px;
// font-weight: bold;
// color: #9B9B9B;
// margin-bottom: 18px;
// }
</style>

View file

@ -1,37 +0,0 @@
<template>
<div class="statusContainer">
<div>{{status}}</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { status } from "@/render/shared/connectionStatus";
export default defineComponent({
name: "ConnectionStatus",
setup() {
return {
status
}
}
});
</script>
<style lang="scss">
.statusContainer {
width: 100vw;
display: flex;
justify-content: center;
:first-child {
margin-right: 2px;
}
}
</style>

View file

@ -1,88 +0,0 @@
<template>
<span :class="`context ${modifier}-context`">
<div v-if="modifier==='ai'" class="avatar"></div>
{{ contextRef }}
</span>
</template>
<script lang='ts'>
import { defineComponent, ref, watch } from 'vue';
import { annotateAnim } from "@/render/components/controllers/helpers";
export default defineComponent({
name: "Context",
props: ["modifier", "context", "uid"],
setup(props) {
const contextRef = ref(" ");
contextRef.value = props.context;
watch(() => props.context, (val: string, _oldval: string) => {
annotateAnim(props.uid, val, contextRef);
});
return {
contextRef
};
}
})
</script>
<style lang="scss" scoped>
.context {
position: relative;
min-height: 14px;
display: flex;
align-items: center;
font-family: "SF Compact Display";
font-size: 12px;
font-weight: bold;
margin-top: 5px;
}
.ai-context {
margin-left: 15px;
}
.client-context {
margin-right: 15px;
}
.avatar {
margin-right: 5px;
width: 30px;
height: 30px;
background-color: white;
border-radius: 15px;
}
.playing {
animation-name: message-playback-anim;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes message-playback-anim {
0%,
100% {
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
}
25% {
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
}
50% {
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
}
75% {
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
}
}
</style>

View file

@ -1,5 +1,6 @@
import { ref, Ref } from "vue"; import { ref } from "vue";
import anime from "animejs"; import anime from "animejs";
import { v4 as uuidv4 } from 'uuid';
export function animateTextInput () { export function animateTextInput () {
@ -85,40 +86,17 @@ export function animateAudioInput () {
} }
// animate a message annotation.
//! probably could be improved.
export function annotateAnim (uid: string, val: string, contextRef: Ref) {
anime({ export function newMessage ({
targets: `#${uid} span`, text=false,
opacity: [1, 0], audio=false,
duration: 250, context=false,
easing: 'easeOutExpo' uid=uuidv4()
}) }) {
return {
.finished.then(() => { text: text,
contextRef.value = val; audio: audio,
}) context: context,
uid: uid
.then(() => { };
anime({
targets: `#${uid} span`,
opacity: [0, 1],
duration: 250,
easing: 'easeOutExpo'
})
})
} }
// Given index and length of content, return message child status.
export function calcChild (index: number, len: number) {
if (len === 1) {
return "none";
} else if (index === 0) {
return "first-child";
} else if (index === len - 1) {
return "last-child";
} else {
return "middle-child";
}
}

View file

@ -1,6 +1,6 @@
import { onMounted, onUnmounted, ref, Ref } from "vue"; import { onMounted, onUnmounted, ref, Ref } from "vue";
import { postMessage } from "@/render/ipc"; import { postMessage } from "@/render/ipc";
import { animateAudioInput } from "./helpers"; import { newMessage, animateAudioInput } from "./helpers";
import { invokeReturnAudio, postAudioChunk } from "@/render/ipc"; import { invokeReturnAudio, postAudioChunk } from "@/render/ipc";
export default function useAudioInputController (typing: Ref) { export default function useAudioInputController (typing: Ref) {

View file

@ -1,6 +1,6 @@
import { Ref, ref, watch, onMounted, onUnmounted } from "vue"; import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
import { postMessage } from "@/render/ipc"; import { postMessage } from "@/render/ipc";
import { animateTextInput } from "./helpers"; import { newMessage, animateTextInput } from "./helpers";
export default function useTextInputController(elementX: Ref) { export default function useTextInputController(elementX: Ref) {
@ -36,12 +36,11 @@ export default function useTextInputController(elementX: Ref) {
if (textInput) { if (textInput) {
// Send it to the backend for processing. // Send it to the backend for processing.
const message: Raw = { const message = newMessage({
text: textInput.value, text: false
audio: false });
};
postMessage(message); // postMessage(message);
clearInput() clearInput()
} }

View file

@ -0,0 +1,38 @@
import { ref } from 'vue';
import useScroll from "@/render/composables/useScroll";
const messagesRef = ref();
/* seed the canvas with messages */
const seedCanvas = (messages: Message[]) => {
messagesRef.value = messages;
}
const addMessage = (message: Message) => {
messagesRef.value.push(message);
}
const updateMessage = (message: Message) => {
let target_message = messagesRef.value.filter((m: Message) => {
return m.uid = message.uid;
})[0];
if (target_message) {
target_message = message;
}
}
export default function useMessages() {
const { updateScrollRef, adjustScroll } = useScroll("messenger");
return {
messagesRef,
seedCanvas,
addMessage,
updateMessage
};
}

View file

@ -9,7 +9,6 @@
class="menuButton minimizeButton" class="menuButton minimizeButton"
@click.prevent="postNavBarMin" @click.prevent="postNavBarMin"
/> />
<connection-status />
</span> </span>
</template> </template>
@ -17,20 +16,16 @@
// import { postNavBarExit, postNavBarMin } from "@/render/ipc"; // import { postNavBarExit, postNavBarMin } from "@/render/ipc";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { postNavBar } from "@/render/ipc";
import ConnectionStatus from "./connectionStatus.vue";
export default defineComponent({ export default defineComponent({
name: "Header", name: "Header",
components: { ConnectionStatus },
setup() { setup() {
const postNavBarExit = () => postNavBar('close'); const postNavBarExit = () => {};
const postNavBarMin = () => postNavBar('min'); const postNavBarMin = () => {};
return { return {
postNavBarExit, postNavBarExit,
postNavBarMin postNavBarMin
} }
} }
}); });

View file

@ -32,7 +32,7 @@ import draggify from "@/render/composables/useDraggify";
import useTextInputController from import useTextInputController from
"@/render/components/controllers/inputItem.control.text"; "@/render/components/controllers/inputItem.control.text";
// import useAudioInputController from import useAudioInputController from
"@/render/components/controllers/inputItem.control.audio"; "@/render/components/controllers/inputItem.control.audio";
export default defineComponent({ export default defineComponent({
@ -51,8 +51,7 @@ export default defineComponent({
// Controllers for text and audio. // Controllers for text and audio.
const { typing } = useTextInputController(elementX) const { typing } = useTextInputController(elementX)
// const { recording } = useAudioInputController(typing) const { recording } = useAudioInputController(typing)
const recording = false;
return { return {
elementX, elementX,

View file

@ -35,7 +35,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from "vue"; import { defineComponent, ref } from "vue";
import { setProfile } from '@/render/shared/profile'; import { setProfile } from '@/render/composables/useProfile';
import { invokeLogin } from "@/render/ipc"; import { invokeLogin } from "@/render/ipc";
export default defineComponent({ export default defineComponent({

View file

@ -1,89 +0,0 @@
<template>
<div :id="uid" :class="`${modifier}-message`">
<Bubble
v-for="(val, index) in content"
:modifier="modifier"
:content="val"
:child="calcChild(index, content.length)"
/>
<Context
:modifier="modifier"
:context="context"
:uid="uid"
/>
</div>
</template>
<script lang='ts'>
import { defineComponent } from 'vue';
import Bubble from "@/render/components/bubble.vue";
import Context from "@/render/components/context.vue";
import { calcChild } from "@/render/components/controllers/helpers";
export default defineComponent({
name: "Message",
props: ["modifier", "content", "context", "uid"],
components: {
Bubble,
Context
},
setup() {
return {
calcChild
};
}
})
</script>
<style lang="scss" scoped>
.message {
width: 100vw;
display: flex;
flex-direction: column;
padding-top: 9px;
padding-bottom: 9px;
animation-name: message-init-anim;
animation-duration: 0.25s;
}
@keyframes message-init-anim {
from {
opacity: 0;
} to {
opacity: 1;
}
}
.message:first-child {
margin-top: 55px;
}
.message:last-child {
margin-bottom: 6px;
}
.client-message {
@extend .message;
align-items: flex-end;
}
.ai-message {
@extend .message;
align-items: flex-start;
}
</style>

View file

@ -7,12 +7,11 @@
<!-- List of message bubbles. --> <!-- List of message bubbles. -->
<div id="messenger"> <div id="messenger">
<Message <Bubble
v-for="message in messages" v-for="message in messages"
:modifier="message.modifier" :text="message.content.text"
:content="message.content"
:context="message.context" :context="message.context"
:uid="message.uid" :key="message[0]"
/> />
</div> </div>
@ -20,13 +19,13 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; import { defineComponent, onMounted, onUnmounted } from "vue";
import InputItem from "@/render/components/inputItem.vue"; import InputItem from "@/render/components/inputItem.vue";
import Settings from "@/render/components/settings.vue"; import Settings from "@/render/components/settings.vue";
import Message from "@/render/components/message.vue"; import Bubble from "@/render/components/bubble.vue";
import { profile } from "@/render/shared/profile"; import { profile } from "@/render/composables/useProfile";
import { messages } from "@/render/shared/messages"; import { messages } from "@/render/composables/useMessages";
export default defineComponent({ export default defineComponent({
name: "Messenger", name: "Messenger",
@ -34,7 +33,7 @@ export default defineComponent({
components: { components: {
InputItem, InputItem,
Settings, Settings,
Message Bubble
}, },
setup() { setup() {

View file

@ -28,7 +28,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from "vue"; import { defineComponent, ref } from "vue";
import { clearProfile } from "@/render/shared/profile" import { clearProfile } from "@/render/composables/useProfile"
import { invokeLogout } from "@/render/ipc"; import { invokeLogout } from "@/render/ipc";
export default defineComponent({ export default defineComponent({

View file

@ -25,7 +25,7 @@ export class IpcRendererListener<InputParam> implements IIpcListener<InputParam>
} }
private _onPost = (_e: IpcRendererEvent, payload: InputParam): void => { private _onPost = (_e: IpcRendererEvent, payload: InputParam): void => {
console.log(`[IPC] Post: ${this.channel}`, payload); console.log(`[IPC] Post: ${this.channel}`);
this._listenerCallback(payload); this._listenerCallback(payload);
} }
@ -34,16 +34,16 @@ export class IpcRendererListener<InputParam> implements IIpcListener<InputParam>
export default function useIpcRenderer () { export default function useIpcRenderer () {
const invoke = async <T>(endpoint: string, payload: T) => { const invoke = async (endpoint: string, payload: any) => {
try { try {
const res = await window.ipcRenderer.invoke(endpoint, payload? JSON.stringify(payload) : null); const res = await window.ipcRenderer.invoke(endpoint, payload);
return res; return res;
} catch (e) { } catch (e) {
throw e; throw e;
} }
} }
const post = <T>(endpoint: string, payload: T) => { const post = (endpoint: string, payload: any) => {
window.ipcRenderer.send(endpoint, payload); window.ipcRenderer.send(endpoint, payload);
}; };

View file

@ -0,0 +1,33 @@
// shared
import { ref, Ref } from "vue";
import useScroll from "@/render/composables/useScroll";
export const messages: Ref<Array<Message>> = ref([]);
export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
messages.value = payload as Array<Message>;
}
export const addMessage: IpcListenerCallback<Message> = (payload) => {
messages.value.push(payload as Message);
}
export const updateMessage: IpcListenerCallback<Message> = (payload) => {
const message = payload as Message;
let targetMessage = messages.value.filter((m: Message) => {
return m.uid = message.uid;
})[0];
if (targetMessage) {
targetMessage = message;
}
}
export default {
messages,
setMessages,
addMessage,
updateMessage
};

View file

@ -1,32 +1,28 @@
export default function useScroll(elementId: string) { export default function useScroll(element: string) {
let isScrolledToBottom: boolean;
const view = document.getElementById(element)
// Update isScrolledToBottom // Update isScrolledToBottom
const isScrolledToBottom = () => { const updateScrollRef = () => {
const el = document.getElementById(elementId); if (view) isScrolledToBottom = view.scrollHeight - view.clientHeight <= view.scrollTop + 1;
return isScrolledToBottom;
if (el) {
return el.scrollHeight - el.clientHeight <= el.scrollTop + 30;
}
} }
// Adjust scroll after we add content to the messenger. // Adjust scroll after we add content to the messenger.
const adjustScroll = () => { const adjustScroll = () => {
const el = document.getElementById(elementId); if (view) {
view.scrollTo({
if (el) { top: view.scrollHeight - view.clientHeight,
el.scrollTo({
top: el.scrollHeight - el.clientHeight,
behavior: 'smooth' behavior: 'smooth'
}); });
} }
} }
return { return {
isScrolledToBottom, updateScrollRef,
adjustScroll, adjustScroll,
}; };

View file

@ -14,7 +14,7 @@ const { post, invoke } = useIpc();
export const invokeLogin = async ( export const invokeLogin = async (
payload: LoginPayload payload: LoginPayload
): Promise<Profile | Error> => ( ): Promise<Profile | Error> => (
await invoke('invoke-account-login', payload) await invoke('invoke-account-login', JSON.stringify(payload))
); );
export const invokeLogout = async (): Promise<void> => ( export const invokeLogout = async (): Promise<void> => (
@ -41,7 +41,11 @@ export const invokeReturnAudio = async (): Promise<ArrayBuffer[] | Error> => (
* *
*/ */
export const postMessage = (payload: Raw): void => ( export const invokeSession = async (cid: string): Promise<Profile | Error> => (
await invoke("messenger-init", cid)
);
export const postMessage = (payload: Message): void => (
post('post-session-send', payload) post('post-session-send', payload)
); );
@ -49,25 +53,6 @@ export const postAppMount = (): void => (
post('post-app-mount', null) post('post-app-mount', null)
); );
/**
*
* Nav bar endpoints
*
*/
export const postNavBar = (payload: string): void => (
post('post-nav-bar', payload)
);
/**
*
* Window focus endpoint
*
*/
export const postWindowBlur = (payload: { isFocused: boolean }): void => (
post('post-window-focus', payload)
);
/** /**
* *

View file

@ -1,19 +1,13 @@
import { IpcRendererListener } from "./composables/useIpcRend" import { IpcRendererListener } from "./composables/useIpcRend"
/* import { setProfile } from "./composables/useProfile";
* Shared state imports import { setMessages, addMessage, updateMessage } from "./composables/useMessages";
* */
import { setProfile } from "./shared/profile";
import { setMessages, addMessage, updateMessage, deleteMessage } from "./shared/messages";
import { setConnectionStatus } from "./shared/connectionStatus";
const SET_PROFILE_CHANNEL = "set-profile"; const SET_PROFILE_CHANNEL = "set-profile";
const INIT_MESSAGES_CHANNEL = "init-messages"; const INIT_MESSAGES_CHANNEL = "init-messages";
const ADD_MESSAGE_CHANNEL = "add-message"; const ADD_MESSAGE_CHANNEL = "add-message";
const UPDATE_MESSAGE_CHANNEL = "update-message"; const UPDATE_MESSAGE_CHANNEL = "update-message";
const DELETE_MESSAGE_CHANNEL = "delete-message";
const CONNECTION_STATUS_CHANNEL = "set-connection-status";
export const setProfileListener = new IpcRendererListener({ export const setProfileListener = new IpcRendererListener({
channel: SET_PROFILE_CHANNEL, channel: SET_PROFILE_CHANNEL,
@ -36,12 +30,3 @@ export const updateMessagesListener = new IpcRendererListener({
listenerCallback: updateMessage listenerCallback: updateMessage
}); });
export const deleteMessagesListener = new IpcRendererListener({
channel: DELETE_MESSAGE_CHANNEL,
listenerCallback: deleteMessage
});
export const connectionStatusListener = new IpcRendererListener({
channel: CONNECTION_STATUS_CHANNEL,
listenerCallback: setConnectionStatus
});

View file

@ -3,7 +3,6 @@
import App from "./App.vue"; import App from "./App.vue";
import { setupAudio } from "./audio/setupAudio";
import mitt from "mitt"; import mitt from "mitt";
import { createApp } from "vue"; import { createApp } from "vue";
@ -13,8 +12,6 @@ import { initIpcRendererListeners } from "./ipc"
// Handle ipcMain events. // Handle ipcMain events.
initIpcRendererListeners(); initIpcRendererListeners();
setupAudio();
// Handle events. // Handle events.
const emitter = mitt(); const emitter = mitt();

View file

@ -1,12 +0,0 @@
// shared
import { ref } from "vue";
export const status = ref("");
export const setConnectionStatus: IpcListenerCallback<string> = (payload) => {
status.value = payload;
};
export default {
status
};

View file

@ -1,64 +0,0 @@
// shared
import { ref, Ref } from "vue";
import useScroll from "@/render/composables/useScroll";
const { isScrolledToBottom, adjustScroll } = useScroll("messenger");
export const messages: Ref<Array<Message>> = ref([]);
export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
messages.value = payload as Array<Message>;
setTimeout(() => {
adjustScroll();
}, 1000);
}
export const addMessage: IpcListenerCallback<Message> = (payload) => {
const bottom = isScrolledToBottom();
messages.value.push(payload as Message);
if (bottom) {
setTimeout(() => {
adjustScroll();
}, 10);
}
}
export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
const annotation = payload as Annotation;
for (const i in messages.value) {
if (messages.value[i].uid == annotation.uid) {
if (annotation.name == "content") {
messages.value[i].content = annotation.data as string[];
}
if (annotation.name == "context") {
messages.value[i].context = annotation.data as string;
}
}
}
}
export const deleteMessage: IpcListenerCallback<string> = (payload) => {
const uid = payload as string;
for (let i = 0; i < messages.value.length; i++) {
if (messages.value[i].uid === uid) {
messages.value.splice(i, 1);
break;
}
}
}
export default {
messages,
setMessages,
addMessage,
updateMessage
};

View file

@ -11,8 +11,3 @@ declare module "anime-js" {
export = anime; export = anime;
} }
} }
declare module "*.worklet.ts" {
const exportString: string;
export default exportString;
}

View file

@ -3,64 +3,72 @@
// import useAudio from "@/audio"; // import useAudio from "@/audio";
import Messages from "./messages"; import { ipcEmit } from "@/composables/useEmitter";
import { getProfile } from "./store";
import useWebsockets from "./composables/useWebsockets"; import useWebsockets from "./composables/useWebsockets";
import {config} from "@/config"; import {config} from "@/config";
import { ipcEmit } from "@/composables/useEmitter";
/* data structure of messages that's tied to the UI */
const uiState: any | null = null;
/* start and stop audio functionality */ /* start and stop audio functionality */
// const { initAudio, closeAudio } = useAudio(); // const { initAudio, closeAudio } = useAudio();
let messages: Messages; const isInitMessage = (message: any): boolean => {
return true;
};
const deauthenticate = (): void => {
console.log('deauthenticating')
};
const isAddMessage = (message: any): boolean => {
return true;
};
/**
* Controls for interfacing with the platform.
* Takes an onMessage callback which we define below.
*/
const onMessageCallback = (payload: string) => { const onMessageCallback = (payload: string) => {
const message = JSON.parse(payload); const message = JSON.parse(payload);
console.log(typeof message);
/* if the platform fails to authenticate, we must back down */
if (message === "CLOSE_AUTH_FAIL") { if (message === "CLOSE_AUTH_FAIL") {
console.log("deauthenticating"); deauthenticate();
return;
} }
else if (message.header === "init") { ipcEmit("add-message", message)
messages = new Messages(message.body); return
/* on init, platform sends state, used to init canvas */
if (isInitMessage(message)) {
ipcEmit("init-messages", message)
}
else if (isAddMessage(message)) {
ipcEmit("add-messages", message)
} }
else { else {
if (messages) { ipcEmit("update-messages", message)
messages.update(message.body);
}
} }
} }
const connectionStatusCallback = (status: string) => { const onConnectionStatusCallback = (alive: boolean) => {
ipcEmit('set-connection-status', status); // console.log('[Session]: Connection Alive: ', alive);
} // ipcEmit('connection-state', alive);
export const updateAppState = (): void => {
const profile = getProfile();
ipcEmit("set-profile", profile);
if (messages)
messages.emit();
}
const statusOptions = {
openMessage: "Connected",
pongMessage: "Nominal",
closeMessage: "Reconnecting",
pingErrorMessage: "Connection Lost",
} }
const { connect, send, close } = useWebsockets( const { connect, send, close } = useWebsockets(
onMessageCallback, onMessageCallback,
connectionStatusCallback, onConnectionStatusCallback
statusOptions
); );
/* send a message to the platform */ /* send a message to the platform */

View file

@ -1,25 +1,14 @@
interface Update {
name: string;
data: Message[] | Message | Annotation | string;
}
interface Message { interface Message {
modifier: string; text: boolean | string;
content: string[];
context: boolean | string; context: boolean | string;
audio: boolean | string;
type: 1 | 2 | 3;
uid: string; uid: string;
} }
interface Annotation { interface ViewMessage extends Message {
name: string; child: string;
data: string | string[];
uid: string;
}
interface Raw {
text: string | boolean;
audio: string | boolean;
} }
interface WindowState { interface WindowState {
@ -58,7 +47,7 @@ interface IpcHandlerCallback<I, O> {
} }
interface IpcListenerCallback<T> { interface IpcListenerCallback<T> {
(payload: T): void; (payload: T | null): void;
} }
interface IIpcHandler<I, O> { interface IIpcHandler<I, O> {

View file

@ -33,20 +33,20 @@ const loadWinState = (fileName: string): WindowState => {
} }
// Called when a NavBar button is pressed. // Called when a NavBar button is pressed.
export const onNavBar: IpcListenerCallback<string> = (payload): void => { const onNavBar = (_event: any, action: string): void => {
if (win) { if (win) {
if (payload === "close") { if (action === "close") {
win.close(); win.close()
} else { } else {
win.minimize(); win.minimize()
} }
} }
} }
// Util function to render message on ipc-renderer event. // Util function to render message on ipc-renderer event.
const postToWindow = <T>(e: IpcRendererEvent<T>): void => { const postToWindow = <T>(event: IpcRendererEvent<T>): void => {
if (win) { if (win) {
win.webContents.send(e.channel, e.payload); win.webContents.send(event.channel, event.payload);
} }
} }
@ -75,6 +75,10 @@ const onWindowMount = (): void => {
// Must tell initApp that window exists. // Must tell initApp that window exists.
backgroundMitt.emit('window-active', true); backgroundMitt.emit('window-active', true);
// Handle win nav-bar event.
ipcMain.removeAllListeners("nav-bar") // avoid setting duplicate handlers
ipcMain.on("nav-bar", onNavBar);
// Gateway for messages to the frontend. // Gateway for messages to the frontend.
backgroundMitt.removeAllListeners("ipc-renderer") backgroundMitt.removeAllListeners("ipc-renderer")
backgroundMitt.on("ipc-renderer", postToWindow); backgroundMitt.on("ipc-renderer", postToWindow);

View file

@ -1,38 +1,84 @@
// const speech = require('@google-cloud/speech'); const speech = require('@google-cloud/speech');
const portAudio = require('naudiodon'); const portAudio = require('naudiodon');
// const rs = fs.createReadStream('rawAudio.wav'); // const rs = fs.createReadStream('rawAudio.wav');
// Creates a client
const client = new speech.SpeechClient();
const encoding = 'LINEAR16';
const sampleRateHertz = 16000;
const languageCode = 'en-US';
const audioContainer = { const audioContainer = {
input: '', input: '',
buffers: [] buffers: []
} }
const config = {
encoding: encoding,
sampleRateHertz: sampleRateHertz,
languageCode: languageCode,
};
/**
* Note that transcription is limited to 60 seconds audio.
* Use a GCS file for audio longer than 1 minute.
*/
async function transcribeSpeech (audio) {
const request = {
config: config,
audio: audio,
};
// Detects speech in the audio file. This creates a recognition job that you
// can wait for now, or get its result later.
const [operation] = await client.longRunningRecognize(request);
// Get a Promise representation of the final result of the job
const [response] = await operation.promise();
const transcription = response.results
.map(result => result.alternatives[0].transcript)
.join('\n');
console.log(`Transcription: ${transcription}`);
}
let record = true; let record = true;
// Create an instance of AudioIO with inOptions (defaults are as below), which will return a ReadableStream // Create an instance of AudioIO with inOptions (defaults are as below), which will return a ReadableStream
const aio = new portAudio.AudioIO({ const ia = new portAudio.AudioIO({
inOptions: { inOptions: {
channelCount: 2, channelCount: 1,
sampleFormat: portAudio.SampleFormat16Bit, sampleFormat: 16,
sampleRate: 44100, sampleRate: 16000,
deviceId: -1 // Use -1 or omit the deviceId to select the default device deviceId: -1,
closeOnError: false,
}, },
outOptions: { });
channelCount: 2, ia.setEncoding('base64');
sampleFormat: portAudio.SampleFormat16Bit, ia.start();
sampleRate: 44100, ia.on('data', (chunk) => {
deviceId: -1 // Use -1 or omit the deviceId to select the default device if (record) {
} console.log('recording data');
audioContainer.input += chunk;
} else {
if (audioContainer.input.length) audioContainer.input = "";
}
}); });
const ao = new portAudio.AudioIO({
outOptions: {
sampleFormat: 16,
channelCount: 1,
sampleRate: 16000,
deviceId: -1,
closeOnError: false,
}
});
ao.start();
aio.start() let counter = 0;
aio.read()
aio.on('data', buf => console.log(buf.timestamp));
const counter = 0;
const tests = []; const tests = [];
function testCallback() { function testCallback() {
@ -97,6 +143,13 @@ function bufSplit(input){
} }
async function test() { async function test() {
transcribeSpeech({
content: Buffer.from(audioContainer.input, 'base64')
});
tests.push(audioContainer.input)
counter++;
console.log('audio string length:', audioContainer.input.length)
console.log('buffers written: ', audioContainer.buffers.length)
} }
setTimeout(async () => { setTimeout(async () => {
@ -104,6 +157,26 @@ setTimeout(async () => {
test() test()
}, 4000); }, 4000);
// setTimeout(() => {
// record = true;
// }, 6000)
//
// setTimeout(async () => {
// record = false;
// test();
// }, 9000);
//
// setTimeout(() => {
// record = true;
// }, 11000)
//
// setTimeout(async () => {
// record = false;
// test();
// }, 14000);
//
setTimeout(async () => { setTimeout(async () => {
ia.quit();
testCallback()
return; return;
}, 6000); }, 6000);

View file

@ -1,38 +0,0 @@
const speech = require('@google-cloud/speech');
// Creates a client
const client = new speech.SpeechClient();
const encoding = 'LINEAR16';
const sampleRateHertz = 16000;
const languageCode = 'en-US';
const config = {
encoding: encoding,
sampleRateHertz: sampleRateHertz,
languageCode: languageCode,
};
/**
* Note that transcription is limited to 60 seconds audio.
* Use a GCS file for audio longer than 1 minute.
*/
async function transcribeSpeech (audio) {
const request = {
config: config,
audio: audio,
};
// Detects speech in the audio file. This creates a recognition job that you
// can wait for now, or get its result later.
const [operation] = await client.longRunningRecognize(request);
// Get a Promise representation of the final result of the job
const [response] = await operation.promise();
const transcription = response.results
.map(result => result.alternatives[0].transcript)
.join('\n');
console.log(`Transcription: ${transcription}`);
}

View file

@ -1,23 +0,0 @@
{
"compilerOptions": {
"target": "ES2018",
"module": "esnext",
"strict": true,
"importHelpers": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": ["webpack-env"],
"paths": {
"@/*": ["src/*"]
},
"lib": ["esnext", "scripthost"]
},
"include": ["src/**/*.worklet.ts"],
"exclude": ["node_modules"]
}

View file

@ -1,50 +0,0 @@
const path = require("path");
module.exports = {
assetsDir: "../../static/SPA"
}
module.exports = {
lintOnSave: false,
pluginOptions: {
electronBuilder: {
mainProcessFile: "./src/init.ts",
rendererProcessFile: "./src/render/main.ts",
preload: "./src/render/preload.ts",
builderOptions: {
"appId": "com.crimata.ElectronUpdaterApp",
"artifactName": "${productName}-${version}.${ext}",
"publish": {
"provider": "generic",
"url": "https://gitlab.com/api/v4/projects/25637892/jobs/artifacts/main/raw/dist_electron?job=build"
}
},
chainWebpackRendererProcess: (config) => {
// Chain webpack config for electron renderer process only (won't be applied to web builds)
config.outputDir = path.resolve(__dirnamei, "js");
config.module
.rule('worklet')
.test(/\.worklet\.ts$/)
.use('worklet-loader')
.loader('worklet-loader')
.tap(options => {
options.name = "js/[hash].worklet.js";
return options
})
.end()
// Add another loader
.use('ts-loader')
.loader('ts-loader')
.tap(options => {
options.configFile = "tsconfig.worklet.json";
return options
})
.end()
},
}
}
};

View file

@ -1456,11 +1456,6 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
"@types/json-schema@^7.0.6":
version "7.0.7"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/long@^4.0.0", "@types/long@^4.0.1": "@types/long@^4.0.0", "@types/long@^4.0.1":
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
@ -2372,7 +2367,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4:
version "6.12.6" version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@ -4903,6 +4898,11 @@ dotenv-expand@^5.1.0:
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
dotenv@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dotenv@^8.2.0: dotenv@^8.2.0:
version "8.2.0" version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
@ -6649,11 +6649,6 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1" minimalistic-crypto-utils "^1.0.1"
hoek@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
hoopy@^0.1.4: hoopy@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
@ -8452,7 +8447,7 @@ loader-utils@^0.2.16:
json5 "^0.5.0" json5 "^0.5.0"
object-assign "^4.0.1" object-assign "^4.0.1"
loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
@ -11159,14 +11154,6 @@ schema-utils@2.7.0:
ajv "^6.12.2" ajv "^6.12.2"
ajv-keywords "^3.4.1" ajv-keywords "^3.4.1"
schema-utils@^0.4.0:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
schema-utils@^1.0.0: schema-utils@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
@ -11185,15 +11172,6 @@ schema-utils@^2.0.0, schema-utils@^2.5.0, schema-utils@^2.6.1, schema-utils@^2.6
ajv "^6.12.4" ajv "^6.12.4"
ajv-keywords "^3.5.2" ajv-keywords "^3.5.2"
schema-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
dependencies:
"@types/json-schema" "^7.0.6"
ajv "^6.12.5"
ajv-keywords "^3.5.2"
scss-tokenizer@^0.2.3: scss-tokenizer@^0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@ -13322,14 +13300,6 @@ worker-farm@^1.7.0:
dependencies: dependencies:
errno "~0.1.7" errno "~0.1.7"
worker-loader@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37"
integrity sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
worker-rpc@^0.1.0: worker-rpc@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5"
@ -13337,15 +13307,6 @@ worker-rpc@^0.1.0:
dependencies: dependencies:
microevent.ts "~0.1.1" microevent.ts "~0.1.1"
worklet-loader@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/worklet-loader/-/worklet-loader-1.0.0.tgz#17e2eef75981de469c1e1e200ad1ffb54efe7a29"
integrity sha512-4yFqiGDwICoJB4ZbWHzCzyTyDrRnCU1XfvSJtjiBBDreuWDYpA6wf8yqQjNckcjL1jm/sT9ocSvP5tJnmsMOLA==
dependencies:
hoek "^4.2.1"
loader-utils "^1.0.0"
schema-utils "^0.4.0"
wrap-ansi@^3.0.1: wrap-ansi@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba"