working new protocols
This commit is contained in:
parent
a791f973dc
commit
b6b1f3e894
8 changed files with 168 additions and 139 deletions
|
|
@ -6,22 +6,14 @@
|
||||||
|
|
||||||
<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: ["modifier", "content", "child"],
|
props: ["modifier", "content", "child"],
|
||||||
|
|
||||||
setup(props) {
|
setup() {
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,30 +30,18 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
margin-left: 15px;
|
|
||||||
margin-right: 15px;
|
|
||||||
margin-bottom: 4px;
|
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 {
|
.ai-bubble {
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-bubble {
|
.client-bubble {
|
||||||
color: white;
|
color: white;
|
||||||
background-color: #58C4FD;
|
background-color: #58C4FD;
|
||||||
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-first-child {
|
.ai-first-child {
|
||||||
|
|
@ -94,7 +74,6 @@
|
||||||
border-top-right-radius: 9px;
|
border-top-right-radius: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// .notify {
|
// .notify {
|
||||||
// position: absolute;
|
// position: absolute;
|
||||||
// width: 12px;
|
// width: 12px;
|
||||||
|
|
|
||||||
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, 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 () {
|
||||||
|
|
@ -93,7 +92,7 @@ export function annotateAnim (uid: string, val: string, contextRef: Ref) {
|
||||||
anime({
|
anime({
|
||||||
targets: `#${uid} span`,
|
targets: `#${uid} span`,
|
||||||
opacity: [1, 0],
|
opacity: [1, 0],
|
||||||
duration: 500,
|
duration: 250,
|
||||||
easing: 'easeOutExpo'
|
easing: 'easeOutExpo'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -105,7 +104,7 @@ export function annotateAnim (uid: string, val: string, contextRef: Ref) {
|
||||||
anime({
|
anime({
|
||||||
targets: `#${uid} span`,
|
targets: `#${uid} span`,
|
||||||
opacity: [0, 1],
|
opacity: [0, 1],
|
||||||
duration: 500,
|
duration: 250,
|
||||||
easing: 'easeOutExpo'
|
easing: 'easeOutExpo'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,21 @@
|
||||||
:child="calcChild(index, content.length)"
|
:child="calcChild(index, content.length)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span class="context">
|
<Context
|
||||||
<div v-if="modifier==='ai'" class="avatar"></div>
|
:modifier="modifier"
|
||||||
{{ contextRef }}
|
:context="context"
|
||||||
</span>
|
:uid="uid"
|
||||||
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang='ts'>
|
<script lang='ts'>
|
||||||
|
|
||||||
import { defineComponent, ref, Ref, onMounted, watch } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import Bubble from "@/render/components/bubble.vue";
|
import Bubble from "@/render/components/bubble.vue";
|
||||||
import { annotateAnim, calcChild } from "@/render/components/controllers/helpers";
|
import Context from "@/render/components/context.vue";
|
||||||
|
import { calcChild } from "@/render/components/controllers/helpers";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Message",
|
name: "Message",
|
||||||
|
|
@ -28,25 +30,14 @@
|
||||||
props: ["modifier", "content", "context", "uid"],
|
props: ["modifier", "content", "context", "uid"],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Bubble
|
Bubble,
|
||||||
|
Context
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup() {
|
||||||
|
|
||||||
const contextRef = ref(" ");
|
|
||||||
contextRef.value = props.context;
|
|
||||||
|
|
||||||
watch(() => props.context, (val: string, oldval: string) => {
|
|
||||||
annotateAnim(props.uid, val, contextRef);
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
calcChild,
|
calcChild
|
||||||
contextRef
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -57,85 +48,41 @@
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 9px;
|
padding-top: 9px;
|
||||||
padding-bottom: 9px;
|
padding-bottom: 9px;
|
||||||
animation-name: message-init-anim;
|
animation-name: message-init-anim;
|
||||||
animation-duration: 0.5s;
|
animation-duration: 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes message-init-anim {
|
@keyframes message-init-anim {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(50px);
|
} to {
|
||||||
} to {
|
opacity: 1;
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.message:first-child {
|
.message:first-child {
|
||||||
margin-top: 55px;
|
margin-top: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message:last-child {
|
.message:last-child {
|
||||||
margin-bottom: 18px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-message {
|
.client-message {
|
||||||
@extend .message;
|
@extend .message;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-message {
|
.ai-message {
|
||||||
@extend .message;
|
@extend .message;
|
||||||
align-items: flex-start;
|
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>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<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 Message from "@/render/components/message.vue";
|
import Message from "@/render/components/message.vue";
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,31 @@
|
||||||
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<Annotation> = (payload) => {
|
export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
|
||||||
const annotation = payload as Annotation;
|
const annotation = payload as Annotation;
|
||||||
|
|
||||||
for (let i in messages.value) {
|
for (const i in messages.value) {
|
||||||
|
|
||||||
if (messages.value[i].uid == annotation.uid) {
|
if (messages.value[i].uid == annotation.uid) {
|
||||||
|
|
||||||
|
|
@ -36,7 +47,7 @@ export const updateMessage: IpcListenerCallback<Annotation> = (payload) => {
|
||||||
export const deleteMessage: IpcListenerCallback<string> = (payload) => {
|
export const deleteMessage: IpcListenerCallback<string> = (payload) => {
|
||||||
const uid = payload as string;
|
const uid = payload as string;
|
||||||
|
|
||||||
for (var i = 0; i < messages.value.length; i++) {
|
for (let i = 0; i < messages.value.length; i++) {
|
||||||
if (messages.value[i].uid === uid) {
|
if (messages.value[i].uid === uid) {
|
||||||
messages.value.splice(i, 1);
|
messages.value.splice(i, 1);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue