import { getToken, setToken, setProfile, getProfile, clearStore } from "./store";
import { parseAuthRes } from "./auth";
import { ipcEmit } from "@/composables/useEmitter";
+import { messages } from "@/messages";
export const accountAuth = async (): Promise<Error | AuthState> => {
ipcEmit("set-profile", profile);
- // ipcEmit('messages') etc
+ ipcEmit("init-messages", messages)
}
--- /dev/null
+import { ipcEmit } from "@/composables/useEmitter";
+
+export let messages: Message[] = [];
+
+export const setMessages = (init: {messages: Message[]}) => {
+ messages = init.messages;
+ ipcEmit("init-messages", messages);
+}
+
+export const newMessage = (message: Message) => {
+ messages.push(message);
+ ipcEmit("add-message", message);
+}
+
+export const annotateMessage = (annotation: Annotation) => {
+ for (let i in messages) {
+ if (messages[i].uid == annotation.uid) {
+ messages[i].context = annotation.context;
+ ipcEmit("update-message", annotation)
+ break;
+ }
+ }
+}
+
<template>
- <div :class="`${type}-message`">
-
- <!-- 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 :class="`bubble ${modifier}-bubble ${modifier}-${child}`">
+ {{ content }}
</div>
</template>
export default defineComponent({
name: "Bubble",
- props: ["text", "context", "type", "position"],
-
- setup() {
+ props: ["modifier", "content", "child"],
- const seen = ref(false);
- /* initialize seen state */
- if (document.visibilityState === "visible") {
- seen.value = true;
- } else seen.value = false;
+ setup(props) {
onMounted(() => {
- if(seen.value)
- document.addEventListener("visibilitychange", () => {
- seen.value = true
- });
-
});
return {
- seen
+
};
}
<style lang="scss" scoped>
- .message {
- width: 100vw;
- display: flex;
- flex-direction: column;
- padding-top: 9px;
- padding-bottom: 9px;
- }
-
- .message:first-child {
- margin-top: 55px;
- }
-
- .message:last-child {
- margin-bottom: 18px;
- }
-
- .sf-message {
- @extend .message;
- justify-content: flex-end;
- }
-
- .ai-message, .fr-message {
- @extend .message;
- justify-content: flex-start;
- }
-
- .bubble {
- position: relative;
- max-width: 66vw;
- font-family: "SF Pro Text";
- font-size: 14px;
- padding: 10px;
- border-radius: 18px;
- }
-
- .sf-bubble {
- @extend .bubble;
- background-color: #58c4fd;
- color: white;
- }
-
-
-
-
-
-
-
-
- .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>
-
-
+.bubble {
+ position: relative;
+ max-width: 66vw;
+ font-family: "SF Pro Text";
+ font-size: 14px;
+ padding: 10px;
+ border-radius: 18px;
+ margin-left: 15px;
+ margin-right: 15px;
+ margin-bottom: 4px;
+ animation-name: bubble-init-anim;
+ animation-duration: 0.5s;
+}
+
+@keyframes bubble-init-anim {
+ from {
+ opacity: 0;
+ transform: translateY(25px);
+ } to {
+ opacity: 1;
+ transform: translateY(0px);
+ }
+}
+
+.ai-bubble {
+ background-color: #FFFFFF;
+}
+
+.client-bubble {
+ color: white;
+ background-color: #58C4FD;
+}
+
+.ai-first-child {
+ border-bottom-left-radius: 9px;
+}
+
+.ai-middle-child {
+ border-top-left-radius: 9px;
+ border-bottom-left-radius: 9px;
+}
+
+.ai-last-child {
+ border-top-left-radius: 9px;
+}
+
+.client-first-child {
+ border-bottom-right-radius: 9px;
+}
+
+.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;
+}
+
+
+// .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);
+// }
+// }
+
+</style>
\ No newline at end of file
-import { ref } from "vue";
+import { ref, Ref } from "vue";
import anime from "animejs";
import { v4 as uuidv4 } from 'uuid';
}
-
-export function newMessage ({
- text=false,
- audio=false,
- context=false,
- uid=uuidv4()
-}) {
- return {
- text: text,
- audio: audio,
- context: context,
- uid: uid
- };
+// animate a message annotation.
+//! probably could be improved.
+export function annotateAnim (uid: string, val: string, contextRef: Ref) {
+
+ anime({
+ targets: `#${uid} span`,
+ opacity: [1, 0],
+ duration: 500,
+ easing: 'easeOutExpo'
+ })
+
+ .finished.then(() => {
+ contextRef.value = val;
+ })
+
+ .then(() => {
+ anime({
+ targets: `#${uid} span`,
+ opacity: [0, 1],
+ duration: 500,
+ 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";
+ }
+}
\ No newline at end of file
import { onMounted, onUnmounted, ref, Ref } from "vue";
import { postMessage } from "@/render/ipc";
-import { newMessage, animateAudioInput } from "./helpers";
+import { animateAudioInput } from "./helpers";
import { invokeReturnAudio, postAudioChunk } from "@/render/ipc";
export default function useAudioInputController (typing: Ref) {
import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
import { postMessage } from "@/render/ipc";
-import { newMessage, animateTextInput } from "./helpers";
+import { animateTextInput } from "./helpers";
export default function useTextInputController(elementX: Ref) {
if (textInput) {
// Send it to the backend for processing.
- const message = newMessage({
- text: false
- });
+ const message: Raw = {
+ text: textInput.value,
+ audio: false
+ };
- // postMessage(message);
+ postMessage(message);
clearInput()
}
--- /dev/null
+<template>
+ <div :id="uid" :class="`${modifier}-message`">
+
+ <Bubble
+ v-for="(val, index) in content"
+ :modifier="modifier"
+ :content="val"
+ :child="calcChild(index, content.length)"
+ />
+
+ <span class="context">
+ <div v-if="modifier==='ai'" class="avatar"></div>
+ {{ contextRef }}
+ </span>
+
+ </div>
+</template>
+
+<script lang='ts'>
+
+ import { defineComponent, ref, Ref, onMounted, watch } from 'vue';
+ import Bubble from "@/render/components/bubble.vue";
+ import { annotateAnim, calcChild } from "@/render/components/controllers/helpers";
+
+ export default defineComponent({
+ name: "Message",
+
+ props: ["modifier", "content", "context", "uid"],
+
+ components: {
+ Bubble
+ },
+
+ setup(props) {
+
+ const contextRef = ref(" ");
+ contextRef.value = props.context;
+
+ watch(() => props.context, (val: string, oldval: string) => {
+ annotateAnim(props.uid, val, contextRef);
+ });
+
+ onMounted(() => {
+
+ });
+
+ return {
+ calcChild,
+ contextRef
+ };
+
+ }
+
+ })
+
+</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.5s;
+ }
+
+ @keyframes message-init-anim {
+ from {
+ opacity: 0;
+ transform: translateY(50px);
+ } to {
+ opacity: 1;
+ transform: translateY(0px);
+ }
+ }
+
+ .message:first-child {
+ margin-top: 55px;
+ }
+
+ .message:last-child {
+ margin-bottom: 18px;
+ }
+
+ .client-message {
+ @extend .message;
+ align-items: flex-end;
+ }
+
+ .ai-message {
+ @extend .message;
+ align-items: flex-start;
+ }
+
+ .context {
+ position: relative;
+ display: flex;
+ align-items: center;
+ font-family: "SF Compact Display";
+ font-size: 12px;
+ font-weight: bold;
+ margin-top: 5px;
+ margin-right: 15px;
+ margin-left: 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>
+
+
<!-- List of message bubbles. -->
<div id="messenger">
- <Bubble
+ <Message
v-for="message in messages"
- :text="message.content.text"
+ :modifier="message.modifier"
+ :content="message.content"
:context="message.context"
- :key="message[0]"
+ :uid="message.uid"
/>
</div>
import { defineComponent, onMounted, onUnmounted } from "vue";
import InputItem from "@/render/components/inputItem.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 { messages } from "@/render/shared/messages";
components: {
InputItem,
Settings,
- Bubble
+ Message
},
setup() {
*
*/
-export const postMessage = (payload: Message): void => (
+export const postMessage = (payload: Raw): void => (
post('post-session-send', payload)
);
export const messages: Ref<Array<Message>> = ref([]);
export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
- messages.value = payload as Array<Message>;
+ // 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;
+export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
+ const annotation = payload as Annotation;
let targetMessage = messages.value.filter((m: Message) => {
- return m.uid = message.uid;
+ return m.uid === annotation.uid;
})[0];
if (targetMessage) {
- targetMessage = message;
+ targetMessage.content = annotation.content;
+ targetMessage.context = annotation.context;
}
-
}
+messages.value = [
+{
+ content: ["Welcome Andrew"],
+ context: "Crimata",
+ modifier: "ai",
+ uid: "b5dd3d1b-cef0-4057-aa0f-c72e5c4cd67d"
+},
+{
+ content: ["show contacts"],
+ context: "show contacts",
+ modifier: "client",
+ uid: "ee2819a3-1125-4881-9942-ca5b3d0ad502"
+}]
+
+setTimeout(() => {
+
+ console.log("updating message");
+
+ updateMessage({
+ content: ["show contacts", "msg"],
+ context: "show contacts",
+ uid: "ee2819a3-1125-4881-9942-ca5b3d0ad502"
+ });
+
+
+
+}, 2000);
+
export default {
messages,
setMessages,
// import useAudio from "@/audio";
-import { ipcEmit } from "@/composables/useEmitter";
+import { setMessages, newMessage, annotateMessage } from "./messages";
import useWebsockets from "./composables/useWebsockets";
import {config} from "@/config";
-/* data structure of messages that's tied to the UI */
-const uiState: any | null = null;
-
/* start and stop audio functionality */
// const { initAudio, closeAudio } = useAudio();
-const isInitMessage = (message: any): boolean => {
- return true;
+const isInitMessage = (object: any): object is Message[] => {
+ return 'messages' in object;
};
-const deauthenticate = (): void => {
- console.log('deauthenticating')
+const isNewMessage = (object: any): object is Message => {
+ return 'content' in object;
};
-const isAddMessage = (message: any): boolean => {
- return true;
+const isAnnotation = (object: any): object is Annotation => {
+ return 'messages' in object;
+};
+
+const deauthenticate = (): void => {
+ console.log('deauthenticating')
};
/**
* Takes an onMessage callback which we define below.
*/
-
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") {
deauthenticate();
- return;
}
- ipcEmit("add-message", message)
- return
-
- /* on init, platform sends state, used to init canvas */
- // if (isInitMessage(message)) {
- // ipcEmit("init-messages", message)
- // }
-
+ else if (isInitMessage(message)) {
+ setMessages(message);
+ }
- // else if (isAddMessage(message)) {
- // ipcEmit("add-messages", message)
- // }
+ else if (isNewMessage(message)) {
+ newMessage(message);
+ }
- // else {
- // ipcEmit("update-messages", message)
- // }
+ else if (isAnnotation(message)) {
+ annotateMessage(message);
+ }
}
interface Message {
- text: boolean | string;
+ modifier: string;
+ content: string[];
context: boolean | string;
- audio: boolean | string;
- type: 1 | 2 | 3;
uid: string;
}
-interface ViewMessage extends Message {
- child: string;
+interface Annotation {
+ content: string[];
+ context: string;
+ uid: string;
+}
+
+interface Raw {
+ text: string | boolean;
+ audio: string | boolean;
}
interface WindowState {