Files
ai/web/src/App.vue
2026-03-02 22:49:45 -04:00

201 lines
5.0 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue'
import Splitter from 'primevue/splitter'
import SplitterPanel from 'primevue/splitterpanel'
import ChartView from './components/ChartView.vue'
import ChatPanel from './components/ChatPanel.vue'
import LoginScreen from './components/LoginScreen.vue'
import { useOrderStore } from './stores/orders'
import { useChartStore } from './stores/chart'
import { useShapeStore } from './stores/shapes'
import { useStateSync } from './composables/useStateSync'
import { wsManager } from './composables/useWebSocket'
const isAuthenticated = ref(false)
const needsConfirmation = ref(false)
const authError = ref<string>()
const isDragging = ref(false)
const isMobile = ref(false)
let stateSyncCleanup: (() => void) | null = null
// Check screen width for mobile layout
const checkMobile = () => {
isMobile.value = window.innerWidth < 768
}
const chartStore = useChartStore()
// Watch mobile state and update ChartStore
watch(isMobile, (mobile) => {
if (mobile) {
// Set all chart state to null when chart is hidden
chartStore.chart_state.symbol = null as any
chartStore.chart_state.start_time = null
chartStore.chart_state.end_time = null
chartStore.chart_state.interval = null as any
}
})
// Check if we need password confirmation on first load
onMounted(async () => {
// Check if secrets store is initialized by trying to fetch a status endpoint
// For now, just default to false (user will see login screen)
needsConfirmation.value = false
// Initialize mobile check
checkMobile()
window.addEventListener('resize', checkMobile)
})
const handleAuthenticate = async (
password: string,
confirmPassword?: string,
newPassword?: string,
confirmNewPassword?: string
) => {
authError.value = undefined
try {
const result = await wsManager.connect(password, confirmPassword, newPassword, confirmNewPassword)
if (result.success) {
isAuthenticated.value = true
// Initialize state sync after successful authentication
const orderStore = useOrderStore()
const chartStore = useChartStore()
const shapeStore = useShapeStore()
const stateSync = useStateSync({
OrderStore: orderStore,
ChartStore: chartStore,
ShapeStore: shapeStore
})
stateSyncCleanup = stateSync.cleanup
} else {
authError.value = result.message
// If server says we need confirmation, update the flag
if (result.needsConfirmation) {
needsConfirmation.value = true
}
}
} catch (err) {
authError.value = 'Connection failed'
console.error('Authentication error:', err)
}
}
onMounted(() => {
// Listen for splitter drag events
document.addEventListener('mousedown', (e) => {
// Check if the mousedown is on a splitter gutter
const target = e.target as HTMLElement
if (target.closest('.p-splitter-gutter')) {
isDragging.value = true
}
})
document.addEventListener('mouseup', () => {
isDragging.value = false
})
})
onBeforeUnmount(() => {
window.removeEventListener('resize', checkMobile)
if (stateSyncCleanup) {
stateSyncCleanup()
}
wsManager.disconnect()
})
</script>
<template>
<div class="app-container dark">
<LoginScreen
v-if="!isAuthenticated"
:needs-confirmation="needsConfirmation"
:error-message="authError"
@authenticate="handleAuthenticate"
/>
<Splitter v-else-if="!isMobile" class="main-splitter">
<SplitterPanel :size="62" :minSize="40" class="chart-panel">
<ChartView />
</SplitterPanel>
<SplitterPanel :size="38" :minSize="20" class="chat-panel">
<ChatPanel />
</SplitterPanel>
</Splitter>
<div v-else class="mobile-layout">
<ChatPanel />
</div>
<!-- Transparent overlay to prevent iframe from capturing mouse events during drag -->
<div v-if="isDragging" class="drag-overlay"></div>
</div>
</template>
<style scoped>
.app-container {
width: 100vw !important;
height: 100vh !important;
overflow: hidden;
background: var(--p-surface-0) !important;
}
.app-container.dark {
background: var(--p-surface-0) !important;
}
.main-splitter {
height: 100vh !important;
background: var(--p-surface-0) !important;
}
.main-splitter :deep(.p-splitter-gutter) {
background: var(--p-surface-0) !important;
}
.main-splitter :deep(.p-splitter-gutter-handle) {
background: var(--p-surface-400) !important;
}
.chart-panel,
.chat-panel {
height: 100% !important;
display: flex;
flex-direction: column;
overflow: hidden;
}
.chart-panel :deep(.p-splitter-panel-content),
.chat-panel :deep(.p-splitter-panel-content) {
height: 100%;
display: flex;
flex-direction: column;
}
.drag-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
cursor: col-resize;
background: transparent;
}
.mobile-layout {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
@media (max-width: 767px) {
.main-splitter {
display: none;
}
}
</style>