Merge branch 'protocols' into 'dev'
Working protocols with platform See merge request crimata/electron-app!6
This commit is contained in:
commit
21687f4b7f
20 changed files with 461 additions and 460 deletions
|
|
@ -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, getProfile, clearStore } from "./store";
|
import { getToken, setToken, setProfile, clearStore } from "./store";
|
||||||
import { parseAuthRes } from "./auth";
|
import { parseAuthRes } from "./auth";
|
||||||
import { ipcEmit } from "@/composables/useEmitter";
|
import { ipcEmit } from "@/composables/useEmitter";
|
||||||
|
|
||||||
|
|
@ -83,13 +83,4 @@ export const accountLogout = async (): Promise<Error | void> => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAppState = (): void => {
|
|
||||||
|
|
||||||
const profile = getProfile();
|
|
||||||
|
|
||||||
ipcEmit("set-profile", profile);
|
|
||||||
|
|
||||||
// ipcEmit('messages') etc
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
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 "@/account";
|
import { updateAppState } from "@/session";
|
||||||
import { onNavBar } from "@/window";
|
import { onNavBar } from "@/window";
|
||||||
|
|
||||||
const CLIENT_MESSAGE_CHANNEL = "post-session-send"
|
const CLIENT_MESSAGE_CHANNEL = "post-session-send"
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import initIpcMain from "@/ipc/index";
|
import initIpcMain from "@/ipc/index";
|
||||||
import { accountAuth, updateAppState } from "./account";
|
import { accountAuth } from "./account";
|
||||||
import { launchSession } from "./session";
|
import { launchSession, updateAppState } from "./session";
|
||||||
import createWindow from "./window";
|
import createWindow from "./window";
|
||||||
|
|
||||||
let authState: AuthState | null;
|
let authState: AuthState | null;
|
||||||
|
|
|
||||||
67
src/messages.ts
Normal file
67
src/messages.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,53 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="`${type}-message`">
|
<div :class="`bubble ${modifier}-bubble ${modifier}-${child}`">
|
||||||
|
{{ 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, ref, onMounted } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Bubble",
|
name: "Bubble",
|
||||||
|
|
||||||
props: ["text", "context", "type", "position"],
|
props: ["modifier", "content", "child"],
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -55,277 +19,55 @@
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
.message {
|
.bubble {
|
||||||
width: 100vw;
|
position: relative;
|
||||||
display: flex;
|
max-width: 66vw;
|
||||||
flex-direction: column;
|
font-family: "SF Pro Text";
|
||||||
padding-top: 9px;
|
font-size: 14px;
|
||||||
padding-bottom: 9px;
|
padding: 10px;
|
||||||
}
|
border-radius: 18px;
|
||||||
|
margin-bottom: 4px;
|
||||||
.message:first-child {
|
}
|
||||||
margin-top: 55px;
|
|
||||||
}
|
.ai-bubble {
|
||||||
|
background-color: #FFFFFF;
|
||||||
.message:last-child {
|
margin-left: 15px;
|
||||||
margin-bottom: 18px;
|
}
|
||||||
}
|
|
||||||
|
.client-bubble {
|
||||||
.sf-message {
|
color: white;
|
||||||
@extend .message;
|
background-color: #58C4FD;
|
||||||
justify-content: flex-end;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-message, .fr-message {
|
.ai-first-child {
|
||||||
@extend .message;
|
border-bottom-left-radius: 9px;
|
||||||
justify-content: flex-start;
|
}
|
||||||
}
|
|
||||||
|
.ai-middle-child {
|
||||||
.bubble {
|
border-top-left-radius: 9px;
|
||||||
position: relative;
|
border-bottom-left-radius: 9px;
|
||||||
max-width: 66vw;
|
}
|
||||||
font-family: "SF Pro Text";
|
|
||||||
font-size: 14px;
|
.ai-last-child {
|
||||||
padding: 10px;
|
border-top-left-radius: 9px;
|
||||||
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 {
|
||||||
.firstChildMessage {
|
border-top-right-radius: 9px;
|
||||||
padding-bottom: 2px;
|
}
|
||||||
}
|
|
||||||
|
</style>
|
||||||
.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>
|
|
||||||
|
|
||||||
|
|
||||||
88
src/render/components/context.vue
Normal file
88
src/render/components/context.vue
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<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>
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { ref } from "vue";
|
import { ref, Ref } from "vue";
|
||||||
import anime from "animejs";
|
import anime from "animejs";
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
|
|
||||||
export function animateTextInput () {
|
export function animateTextInput () {
|
||||||
|
|
@ -86,17 +85,40 @@ export function animateAudioInput () {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// animate a message annotation.
|
||||||
|
//! probably could be improved.
|
||||||
|
export function annotateAnim (uid: string, val: string, contextRef: Ref) {
|
||||||
|
|
||||||
export function newMessage ({
|
anime({
|
||||||
text=false,
|
targets: `#${uid} span`,
|
||||||
audio=false,
|
opacity: [1, 0],
|
||||||
context=false,
|
duration: 250,
|
||||||
uid=uuidv4()
|
easing: 'easeOutExpo'
|
||||||
}) {
|
})
|
||||||
return {
|
|
||||||
text: text,
|
.finished.then(() => {
|
||||||
audio: audio,
|
contextRef.value = val;
|
||||||
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 { newMessage, animateAudioInput } from "./helpers";
|
import { 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) {
|
||||||
|
|
|
||||||
|
|
@ -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 { newMessage, animateTextInput } from "./helpers";
|
import { animateTextInput } from "./helpers";
|
||||||
|
|
||||||
|
|
||||||
export default function useTextInputController(elementX: Ref) {
|
export default function useTextInputController(elementX: Ref) {
|
||||||
|
|
@ -36,11 +36,12 @@ 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 = newMessage({
|
const message: Raw = {
|
||||||
text: false
|
text: textInput.value,
|
||||||
});
|
audio: false
|
||||||
|
};
|
||||||
|
|
||||||
// postMessage(message);
|
postMessage(message);
|
||||||
|
|
||||||
clearInput()
|
clearInput()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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,7 +51,8 @@ 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,
|
||||||
|
|
|
||||||
89
src/render/components/message.vue
Normal file
89
src/render/components/message.vue
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -7,11 +7,12 @@
|
||||||
|
|
||||||
<!-- List of message bubbles. -->
|
<!-- List of message bubbles. -->
|
||||||
<div id="messenger">
|
<div id="messenger">
|
||||||
<Bubble
|
<Message
|
||||||
v-for="message in messages"
|
v-for="message in messages"
|
||||||
:text="message.content.text"
|
:modifier="message.modifier"
|
||||||
|
:content="message.content"
|
||||||
:context="message.context"
|
:context="message.context"
|
||||||
:key="message[0]"
|
:uid="message.uid"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -19,10 +20,10 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent, onMounted, onUnmounted } from "vue";
|
import { defineComponent } 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 Bubble from "@/render/components/bubble.vue";
|
import Message from "@/render/components/message.vue";
|
||||||
|
|
||||||
import { profile } from "@/render/shared/profile";
|
import { profile } from "@/render/shared/profile";
|
||||||
import { messages } from "@/render/shared/messages";
|
import { messages } from "@/render/shared/messages";
|
||||||
|
|
@ -33,7 +34,7 @@ export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
InputItem,
|
InputItem,
|
||||||
Settings,
|
Settings,
|
||||||
Bubble
|
Message
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
|
|
||||||
|
|
@ -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}`);
|
console.log(`[IPC] Post: ${this.channel}`, payload);
|
||||||
this._listenerCallback(payload);
|
this._listenerCallback(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,32 @@
|
||||||
|
|
||||||
|
|
||||||
export default function useScroll(element: string) {
|
export default function useScroll(elementId: string) {
|
||||||
|
|
||||||
let isScrolledToBottom: boolean;
|
|
||||||
const view = document.getElementById(element)
|
|
||||||
|
|
||||||
// Update isScrolledToBottom
|
// Update isScrolledToBottom
|
||||||
const updateScrollRef = () => {
|
const isScrolledToBottom = () => {
|
||||||
if (view) isScrolledToBottom = view.scrollHeight - view.clientHeight <= view.scrollTop + 1;
|
const el = document.getElementById(elementId);
|
||||||
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 = () => {
|
||||||
if (view) {
|
const el = document.getElementById(elementId);
|
||||||
view.scrollTo({
|
|
||||||
top: view.scrollHeight - view.clientHeight,
|
if (el) {
|
||||||
|
el.scrollTo({
|
||||||
|
top: el.scrollHeight - el.clientHeight,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateScrollRef,
|
isScrolledToBottom,
|
||||||
adjustScroll,
|
adjustScroll,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export const invokeReturnAudio = async (): Promise<ArrayBuffer[] | Error> => (
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const postMessage = (payload: Message): void => (
|
export const postMessage = (payload: Raw): void => (
|
||||||
post('post-session-send', payload)
|
post('post-session-send', payload)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { IpcRendererListener } from "./composables/useIpcRend"
|
||||||
* Shared state imports
|
* Shared state imports
|
||||||
* */
|
* */
|
||||||
import { setProfile } from "./shared/profile";
|
import { setProfile } from "./shared/profile";
|
||||||
import { setMessages, addMessage, updateMessage } from "./shared/messages";
|
import { setMessages, addMessage, updateMessage, deleteMessage } from "./shared/messages";
|
||||||
import { setConnectionStatus } from "./shared/connectionStatus";
|
import { setConnectionStatus } from "./shared/connectionStatus";
|
||||||
|
|
||||||
const SET_PROFILE_CHANNEL = "set-profile";
|
const SET_PROFILE_CHANNEL = "set-profile";
|
||||||
|
|
@ -12,6 +12,7 @@ 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";
|
const CONNECTION_STATUS_CHANNEL = "set-connection-status";
|
||||||
|
|
||||||
export const setProfileListener = new IpcRendererListener({
|
export const setProfileListener = new IpcRendererListener({
|
||||||
|
|
@ -35,8 +36,12 @@ 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({
|
export const connectionStatusListener = new IpcRendererListener({
|
||||||
channel: CONNECTION_STATUS_CHANNEL,
|
channel: CONNECTION_STATUS_CHANNEL,
|
||||||
listenerCallback: setConnectionStatus
|
listenerCallback: setConnectionStatus
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,56 @@
|
||||||
import { ref, Ref } from "vue";
|
import { ref, Ref } from "vue";
|
||||||
import useScroll from "@/render/composables/useScroll";
|
import useScroll from "@/render/composables/useScroll";
|
||||||
|
|
||||||
|
const { isScrolledToBottom, adjustScroll } = useScroll("messenger");
|
||||||
|
|
||||||
export const messages: Ref<Array<Message>> = ref([]);
|
export const messages: Ref<Array<Message>> = ref([]);
|
||||||
|
|
||||||
export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
|
export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
|
||||||
messages.value = payload as Array<Message>;
|
messages.value = payload as Array<Message>;
|
||||||
|
setTimeout(() => {
|
||||||
|
adjustScroll();
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addMessage: IpcListenerCallback<Message> = (payload) => {
|
export const addMessage: IpcListenerCallback<Message> = (payload) => {
|
||||||
|
const bottom = isScrolledToBottom();
|
||||||
messages.value.push(payload as Message);
|
messages.value.push(payload as Message);
|
||||||
|
if (bottom) {
|
||||||
|
setTimeout(() => {
|
||||||
|
adjustScroll();
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateMessage: IpcListenerCallback<Message> = (payload) => {
|
export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
|
||||||
const message = payload as Message;
|
const annotation = payload as Annotation;
|
||||||
|
|
||||||
let targetMessage = messages.value.filter((m: Message) => {
|
for (const i in messages.value) {
|
||||||
return m.uid = message.uid;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (targetMessage) {
|
if (messages.value[i].uid == annotation.uid) {
|
||||||
targetMessage = message;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,60 +3,35 @@
|
||||||
|
|
||||||
|
|
||||||
// import useAudio from "@/audio";
|
// import useAudio from "@/audio";
|
||||||
import { ipcEmit } from "@/composables/useEmitter";
|
import Messages from "./messages";
|
||||||
|
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();
|
||||||
|
|
||||||
const isInitMessage = (message: any): boolean => {
|
let messages: Messages;
|
||||||
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);
|
||||||
|
|
||||||
/* if the platform fails to authenticate, we must back down */
|
|
||||||
if (message === "CLOSE_AUTH_FAIL") {
|
if (message === "CLOSE_AUTH_FAIL") {
|
||||||
deauthenticate();
|
console.log("deauthenticating");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcEmit("add-message", message)
|
else if (message.header === "init") {
|
||||||
return
|
messages = new Messages(message.body);
|
||||||
|
}
|
||||||
|
|
||||||
/* on init, platform sends state, used to init canvas */
|
else {
|
||||||
// if (isInitMessage(message)) {
|
if (messages) {
|
||||||
// ipcEmit("init-messages", message)
|
messages.update(message.body);
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// else if (isAddMessage(message)) {
|
|
||||||
// ipcEmit("add-messages", message)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// else {
|
|
||||||
// ipcEmit("update-messages", message)
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,6 +39,17 @@ const connectionStatusCallback = (status: string) => {
|
||||||
ipcEmit('set-connection-status', status);
|
ipcEmit('set-connection-status', status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const updateAppState = (): void => {
|
||||||
|
const profile = getProfile();
|
||||||
|
|
||||||
|
ipcEmit("set-profile", profile);
|
||||||
|
|
||||||
|
if (messages)
|
||||||
|
messages.emit();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const statusOptions = {
|
const statusOptions = {
|
||||||
openMessage: "Connected",
|
openMessage: "Connected",
|
||||||
pongMessage: "Nominal",
|
pongMessage: "Nominal",
|
||||||
|
|
|
||||||
21
src/types.ts
21
src/types.ts
|
|
@ -1,14 +1,25 @@
|
||||||
|
|
||||||
|
interface Update {
|
||||||
|
name: string;
|
||||||
|
data: Message[] | Message | Annotation | string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
text: boolean | string;
|
modifier: string;
|
||||||
|
content: string[];
|
||||||
context: boolean | string;
|
context: boolean | string;
|
||||||
audio: boolean | string;
|
|
||||||
type: 1 | 2 | 3;
|
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ViewMessage extends Message {
|
interface Annotation {
|
||||||
child: string;
|
name: string;
|
||||||
|
data: string | string[];
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Raw {
|
||||||
|
text: string | boolean;
|
||||||
|
audio: string | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WindowState {
|
interface WindowState {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue