refactor(tamagotchi): move chat to a standalone window (#174)
* chore(tamagotchi): remove unused dependencies * fix: sync mcp connection state between windows * refactor: move chat to a standalone window
This commit is contained in:
Generated
-2
@@ -95,8 +95,6 @@ name = "app"
|
||||
version = "0.4.27"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-log",
|
||||
|
||||
@@ -21,8 +21,6 @@ crate-type = [
|
||||
tauri-build = { version = "2.2.0", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.3.1", features = [
|
||||
'macos-private-api',
|
||||
|
||||
@@ -32,6 +32,7 @@ pub async fn open_chat_window(app: tauri::AppHandle) {
|
||||
WebviewUrl::App(Path::new("#/chat").to_path_buf()),
|
||||
)
|
||||
.title("chat")
|
||||
.inner_size(600.0, 800.0)
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ onTokenLiteral(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div py="1" flex="~ col" rounded="lg" overflow-hidden>
|
||||
<div py="1" flex="~ col" h-full rounded="lg" overflow-hidden>
|
||||
<div flex-1 /> <!-- spacer -->
|
||||
<div
|
||||
ref="chatHistoryRef" v-auto-animate max-h="30vh" flex="~ col"
|
||||
ref="chatHistoryRef" v-auto-animate flex="~ col"
|
||||
scrollbar="~ w-2 track-color-transparent thumb-color-primary-300 rounded" h-full w-full overflow-scroll
|
||||
>
|
||||
<div flex-1 /> <!-- spacer -->
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { ChatProvider } from '@xsai-ext/shared-providers'
|
||||
import { useMicVAD } from '@proj-airi/stage-ui/composables'
|
||||
import { useChatStore, useConsciousnessStore, useProvidersStore, useSettings } from '@proj-airi/stage-ui/stores'
|
||||
import { BasicTextarea } from '@proj-airi/ui'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -87,10 +86,6 @@ function handleTranscription(_buffer: Float32Array) {
|
||||
// selectedAudioDevice.value = found
|
||||
// }
|
||||
|
||||
function openSettings() {
|
||||
invoke('open_settings_window')
|
||||
}
|
||||
|
||||
watch(isAudioInputOn, async (value) => {
|
||||
if (value === 'false') {
|
||||
destroy()
|
||||
@@ -108,30 +103,20 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div relative w-full flex gap-1>
|
||||
<TamagotchiChatHistory transform="translate-y-[-100%]" absolute left-0 top-0 w-full />
|
||||
<div flex flex-1>
|
||||
<BasicTextarea
|
||||
v-model="messageInput"
|
||||
:placeholder="t('stage.message')"
|
||||
border="solid 2 primary-100"
|
||||
text="primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
|
||||
bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
|
||||
w-full resize-none overflow-y-scroll rounded-l-xl p-2 font-medium outline-none
|
||||
transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
|
||||
@submit="handleSend"
|
||||
/>
|
||||
<div h-full w-full flex="~ col gap-1">
|
||||
<div w-full flex-1>
|
||||
<TamagotchiChatHistory />
|
||||
</div>
|
||||
<div
|
||||
class="px-4 py-2.5"
|
||||
border="solid 2 primary-100 "
|
||||
text="lg primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
|
||||
<BasicTextarea
|
||||
v-model="messageInput"
|
||||
:placeholder="t('stage.message')"
|
||||
border="solid 2 primary-100"
|
||||
text="primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
|
||||
bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
|
||||
flex items-center justify-center rounded-r-xl
|
||||
@click="openSettings"
|
||||
>
|
||||
<div i-solar:settings-bold-duotone />
|
||||
</div>
|
||||
w-full resize-none overflow-y-scroll rounded-xl p-2 font-medium outline-none
|
||||
transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
|
||||
@submit="handleSend"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RouterView } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div px-4 py-8 flex="~ col gap-4">
|
||||
<div h-full w-full p-4 flex="~ col gap-4">
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -106,6 +106,7 @@ const routeHeaderMetadata = computed(() => routeHeaderMetadataMap.value[route.pa
|
||||
v-if="routeHeaderMetadata"
|
||||
:title="routeHeaderMetadata.title"
|
||||
:subtitle="routeHeaderMetadata.subtitle"
|
||||
:show-back-button="route.path !== '/settings'"
|
||||
/>
|
||||
<RouterView />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import InteractiveArea from '../components/InteractiveArea.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InteractiveArea
|
||||
class="interaction-area block"
|
||||
h-full w-full transition="opacity duration-250"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: stage
|
||||
</route>
|
||||
@@ -2,10 +2,10 @@
|
||||
import { WidgetStage } from '@proj-airi/stage-ui/components'
|
||||
import { useMcpStore } from '@proj-airi/stage-ui/stores'
|
||||
import { connectServer } from '@proj-airi/tauri-plugin-mcp'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted } from 'vue'
|
||||
|
||||
import InteractiveArea from '../components/InteractiveArea.vue'
|
||||
import { useWindowShortcuts } from '../composables/window-shortcuts'
|
||||
import { useWindowControlStore } from '../stores/window-controls'
|
||||
import { WindowControlMode } from '../types/window-controls'
|
||||
@@ -29,6 +29,14 @@ const modeIndicatorClass = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
function openSettings() {
|
||||
invoke('open_settings_window')
|
||||
}
|
||||
|
||||
function openChat() {
|
||||
invoke('open_chat_window')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (connected.value)
|
||||
return
|
||||
@@ -58,11 +66,30 @@ onMounted(async () => {
|
||||
>
|
||||
<div relative h-full w-full items-end gap-2 class="view">
|
||||
<WidgetStage h-full w-full flex-1 mb="<md:18" />
|
||||
<InteractiveArea
|
||||
class="interaction-area block"
|
||||
:class="{ 'pointer-events-none': !windowStore.isControlActive }"
|
||||
absolute bottom-0 w-full transition="opacity duration-250" op-0
|
||||
/>
|
||||
<div
|
||||
absolute bottom-4 left-4 flex gap-1 op-0 transition="opacity duration-250"
|
||||
class="interaction-area"
|
||||
:class="{ 'pointer-events-none': windowStore.isControlActive }"
|
||||
>
|
||||
<div
|
||||
border="solid 2 primary-100 "
|
||||
text="lg primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
|
||||
bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
|
||||
flex cursor-pointer items-center justify-center rounded-l-xl p-4 transition-colors
|
||||
@click="openChat"
|
||||
>
|
||||
<div i-solar:chat-line-bold-duotone />
|
||||
</div>
|
||||
<div
|
||||
border="solid 2 primary-100 "
|
||||
text="lg primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
|
||||
bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
|
||||
flex cursor-pointer items-center justify-center rounded-r-xl p-4 transition-colors
|
||||
@click="openSettings"
|
||||
>
|
||||
<div i-solar:settings-bold-duotone />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debug Mode UI -->
|
||||
|
||||
@@ -3,10 +3,13 @@ import { useMotion } from '@vueuse/motion'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
title: string
|
||||
subtitle?: string
|
||||
}>()
|
||||
showBackButton?: boolean
|
||||
}>(), {
|
||||
showBackButton: true,
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -54,7 +57,10 @@ watch([() => props.title, () => props.subtitle, route], async () => {
|
||||
bg="$bg-color"
|
||||
>
|
||||
<button @click="router.back()">
|
||||
<div i-solar:alt-arrow-left-line-duotone text-2xl />
|
||||
<div
|
||||
i-solar:alt-arrow-left-line-duotone text-2xl
|
||||
:class="{ 'pointer-events-none op-0': !showBackButton }"
|
||||
/>
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div v-if="subtitle" absolute left-0 top-0 translate-y="[-80%]">
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useMcpStore = defineStore('mcp', () => {
|
||||
const serverCmd = useLocalStorage('settings/mcp/server-cmd', '')
|
||||
const serverArgs = useLocalStorage('settings/mcp/server-args', '')
|
||||
const connected = ref(false)
|
||||
const connected = useLocalStorage('mcp/connected', false) // use local storage to sync between windows
|
||||
|
||||
return {
|
||||
serverCmd,
|
||||
|
||||
Reference in New Issue
Block a user