client-py connected

This commit is contained in:
2026-03-27 16:33:40 -04:00
parent c76887ab92
commit c3a8fae132
55 changed files with 1598 additions and 426 deletions

View File

@@ -5,10 +5,10 @@ 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 { useIndicatorStore } from './stores/indicators'
import { useChannelStore } from './stores/channel'
import { useStateSync } from './composables/useStateSync'
import { wsManager } from './composables/useWebSocket'
import { authService } from './composables/useAuth'
@@ -30,10 +30,10 @@ const chartStore = useChartStore()
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
chartStore.symbol = null as any
chartStore.start_time = null
chartStore.end_time = null
chartStore.period = null as any
}
})
@@ -90,15 +90,15 @@ const handleAuthenticate = async (email: string, password: string) => {
const initializeApp = async () => {
// Initialize state sync after successful authentication
const orderStore = useOrderStore()
const chartStore = useChartStore()
const shapeStore = useShapeStore()
const indicatorStore = useIndicatorStore()
const channelStore = useChannelStore()
const stateSync = useStateSync({
OrderStore: orderStore,
ChartStore: chartStore,
ShapeStore: shapeStore,
IndicatorStore: indicatorStore
chartState: chartStore,
shapes: shapeStore,
indicators: indicatorStore,
channelState: channelStore
})
stateSyncCleanup = stateSync.cleanup
}

View File

@@ -29,9 +29,9 @@ onMounted(() => {
datafeed = createTradingViewDatafeed()
tvWidget = new window.TradingView.widget({
symbol: chartStore.chart_state.symbol, // Use symbol from store
symbol: chartStore.symbol, // Use symbol from store
datafeed: datafeed,
interval: chartStore.chart_state.interval as any,
interval: chartStore.period as any,
container: chartContainer.value!,
library_path: '/charting_library/',
locale: 'en',
@@ -167,8 +167,8 @@ function initializeVisibleRange() {
to: new Date(endTime * 1000).toISOString()
})
chartStore.chart_state.start_time = startTime
chartStore.chart_state.end_time = endTime
chartStore.start_time = startTime
chartStore.end_time = endTime
}
}
@@ -183,16 +183,16 @@ function setupChartListeners() {
if (symbolInfo && symbolInfo.ticker) {
console.log('[ChartView] Symbol changed to:', symbolInfo.ticker)
isUpdatingFromChart = true
chartStore.chart_state.symbol = symbolInfo.ticker
chartStore.symbol = symbolInfo.ticker
isUpdatingFromChart = false
}
})
// Listen for interval changes
// Listen for period changes
chart.onIntervalChanged().subscribe(null, (interval: string) => {
console.log('[ChartView] Interval changed to:', interval)
console.log('[ChartView] Period changed to:', interval)
isUpdatingFromChart = true
chartStore.chart_state.interval = interval
chartStore.period = interval
isUpdatingFromChart = false
})
@@ -210,8 +210,8 @@ function setupChartListeners() {
})
isUpdatingFromChart = true
chartStore.chart_state.start_time = startTime
chartStore.chart_state.end_time = endTime
chartStore.start_time = startTime
chartStore.end_time = endTime
isUpdatingFromChart = false
}
})
@@ -224,7 +224,7 @@ function setupStoreWatchers() {
// Watch for external changes to symbol (e.g., from backend/agent)
watch(
() => chartStore.chart_state.symbol,
() => chartStore.symbol,
(newSymbol) => {
if (isUpdatingFromChart) return // Ignore updates that came from the chart itself
@@ -238,16 +238,16 @@ function setupStoreWatchers() {
}
)
// Watch for external changes to interval
// Watch for external changes to period
watch(
() => chartStore.chart_state.interval,
(newInterval) => {
() => chartStore.period,
(newPeriod) => {
if (isUpdatingFromChart) return
console.log('[ChartView] Store interval changed externally to:', newInterval)
if (chart.resolution() !== newInterval) {
chart.setResolution(newInterval, () => {
console.log('[ChartView] Chart interval updated to:', newInterval)
console.log('[ChartView] Store period changed externally to:', newPeriod)
if (chart.resolution() !== newPeriod) {
chart.setResolution(newPeriod, () => {
console.log('[ChartView] Chart period updated to:', newPeriod)
})
}
}

View File

@@ -412,7 +412,7 @@ export function useTradingViewIndicators(tvWidget: IChartingLibraryWidget) {
return
}
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
// If studyId is actually an object, extract the real values
let actualStudyId = studyId
@@ -539,7 +539,7 @@ export function useTradingViewIndicators(tvWidget: IChartingLibraryWidget) {
return
}
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
if (studyId && typeof studyId === 'string') {
console.log('[Indicators] Study properties changed for ID:', studyId)
@@ -671,7 +671,7 @@ export function useTradingViewIndicators(tvWidget: IChartingLibraryWidget) {
const chart = tvWidget.activeChart()
if (!chart) return
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
const allStudies = chart.getAllStudies()
if (!allStudies) return
@@ -755,7 +755,7 @@ export function useTradingViewIndicators(tvWidget: IChartingLibraryWidget) {
return
}
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
// Find added indicators
for (const [id, indicator] of Object.entries(newIndicators)) {
@@ -815,7 +815,7 @@ export function useTradingViewIndicators(tvWidget: IChartingLibraryWidget) {
const chart = tvWidget.activeChart()
if (!chart) return
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
const tvName = ALL_BACKEND_TO_TV_NAMES[indicator.talib_name]
if (!tvName) {

View File

@@ -207,7 +207,7 @@ export function useTradingViewShapes(tvWidget: IChartingLibraryWidget) {
const chart = tvWidget.activeChart()
if (!chart) return
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
if (eventType === 'remove') {
isUpdatingStore = true
@@ -394,7 +394,7 @@ export function useTradingViewShapes(tvWidget: IChartingLibraryWidget) {
if (!allShapes) return
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
// Track which shape IDs we've seen
const seenIds = new Set<string>()
@@ -471,7 +471,7 @@ export function useTradingViewShapes(tvWidget: IChartingLibraryWidget) {
const chart = tvWidget.activeChart()
if (!chart) return
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
// Find added shapes
for (const [id, shape] of Object.entries(newShapes)) {
@@ -530,11 +530,11 @@ export function useTradingViewShapes(tvWidget: IChartingLibraryWidget) {
const chart = tvWidget.activeChart()
if (!chart) return
const currentSymbol = chartStore.chart_state.symbol
const currentSymbol = chartStore.symbol
// Get current chart interval and convert to seconds for timestamp canonicalization
const interval = chartStore.chart_state.interval
const intervalSeconds = intervalToSeconds(interval)
// Get current chart period and convert to seconds for timestamp canonicalization
const period = chartStore.period
const intervalSeconds = intervalToSeconds(period)
// Convert points to TradingView format and canonicalize timestamps to candle boundaries
const tvPoints = shape.points.map(p => {
@@ -715,7 +715,7 @@ export function useTradingViewShapes(tvWidget: IChartingLibraryWidget) {
.map((obj: any) => obj.id)
console.log('[TradingView Shapes] Selection changed:', selectedShapeIds)
chartStore.chart_state.selected_shapes = selectedShapeIds
chartStore.selected_shapes = selectedShapeIds
})
} catch (error) {

View File

@@ -14,6 +14,11 @@ class WebSocketManager {
public isConnected = ref(false)
public isAuthenticated = ref(false)
private token: string | null = null
private messageQueue: WebSocketMessage[] = []
private reconnectAttempts = 0
private maxReconnectAttempts = Infinity // Keep trying indefinitely
private reconnectDelay = 1000 // Start with 1 second
private maxReconnectDelay = 15000 // Max 15 seconds
/**
* Connect to WebSocket with JWT token for authentication
@@ -63,6 +68,7 @@ class WebSocketManager {
console.log('[WebSocket] Connected successfully')
this.isConnected.value = true
this.isAuthenticated.value = false // Wait for 'connected' message from server
this.reconnectAttempts = 0 // Reset reconnection counter
resolve()
}
@@ -76,6 +82,8 @@ class WebSocketManager {
if (message.type === 'connected') {
console.log('[WebSocket] Received connected message, marking as authenticated')
this.isAuthenticated.value = true
// Flush any queued messages now that we're authenticated
this.flushMessageQueue()
}
// Pass to all handlers
@@ -96,6 +104,11 @@ class WebSocketManager {
this.isConnected.value = false
this.isAuthenticated.value = false
console.log('WebSocket disconnected:', event.code, event.reason)
// Attempt to reconnect if we have a token
if (this.token && !event.wasClean) {
this.scheduleReconnect()
}
}
// Connection timeout
@@ -117,19 +130,69 @@ class WebSocketManager {
}
send(message: WebSocketMessage) {
console.log('[WebSocket] Attempting to send message:', message.type, 'readyState:', this.ws?.readyState)
if (this.ws?.readyState === WebSocket.OPEN) {
console.log('[WebSocket] Sending message:', JSON.stringify(message))
console.log('[WebSocket] Sending message:', message.type)
this.ws.send(JSON.stringify(message))
} else {
console.error('[WebSocket] Cannot send message - WebSocket not open. State:', this.ws?.readyState)
console.log('[WebSocket] Queuing message (not connected yet):', message.type, '- Queue size:', this.messageQueue.length + 1)
this.messageQueue.push(message)
// Trigger reconnection if not already in progress
if (this.token && !this.reconnectTimeout) {
this.scheduleReconnect()
}
}
}
private scheduleReconnect() {
if (this.reconnectTimeout) {
return // Reconnection already scheduled
}
// Exponential backoff with max delay
const delay = Math.min(
this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
this.maxReconnectDelay
)
console.log(`[WebSocket] Scheduling reconnect in ${delay}ms (attempt ${this.reconnectAttempts + 1})`)
this.reconnectTimeout = window.setTimeout(() => {
this.reconnectTimeout = null
this.reconnectAttempts++
if (this.token) {
console.log('[WebSocket] Attempting to reconnect...')
this.connect(this.token)
.then(() => {
console.log('[WebSocket] Reconnected successfully')
this.reconnectAttempts = 0 // Reset counter on success
this.flushMessageQueue()
})
.catch(err => {
console.error('[WebSocket] Reconnection failed:', err)
// scheduleReconnect will be called again via onclose
})
}
}, delay)
}
private flushMessageQueue() {
if (this.messageQueue.length > 0) {
console.log(`[WebSocket] Flushing ${this.messageQueue.length} queued messages`)
const queue = [...this.messageQueue]
this.messageQueue = []
queue.forEach(msg => this.send(msg))
}
}
disconnect() {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout)
this.reconnectTimeout = null
}
this.token = null // Clear token to prevent auto-reconnect
this.messageQueue = [] // Clear message queue
this.reconnectAttempts = 0
if (this.ws) {
this.ws.close()
this.ws = null

24
web/src/stores/channel.ts Normal file
View File

@@ -0,0 +1,24 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export interface ChannelInfo {
type: string
connectedAt: number
capabilities: {
supportsSync: boolean
supportsImages: boolean
supportsMarkdown: boolean
supportsStreaming: boolean
supportsTradingViewEmbed: boolean
}
}
export interface ChannelState {
connected: Record<string, ChannelInfo>
}
export const useChannelStore = defineStore('channelState', () => {
const connected = ref<Record<string, ChannelInfo>>({})
return { connected }
})

View File

@@ -5,18 +5,16 @@ export interface ChartState {
symbol: string
start_time: number | null
end_time: number | null
interval: string
period: string
selected_shapes: string[]
}
export const useChartStore = defineStore('ChartStore', () => {
const chart_state = ref<ChartState>({
symbol: 'BINANCE:BTC/USDT',
start_time: null,
end_time: null,
interval: '15',
selected_shapes: []
})
export const useChartStore = defineStore('chartState', () => {
const symbol = ref<string>('BINANCE:BTC/USDT')
const start_time = ref<number | null>(null)
const end_time = ref<number | null>(null)
const period = ref<string>('15')
const selected_shapes = ref<string[]>([])
return { chart_state }
return { symbol, start_time, end_time, period, selected_shapes }
})

View File

@@ -17,7 +17,7 @@ export interface IndicatorInstance {
original_id?: string
}
export const useIndicatorStore = defineStore('IndicatorStore', () => {
export const useIndicatorStore = defineStore('indicators', () => {
const indicators = ref<Record<string, IndicatorInstance>>({})
// Helper methods

View File

@@ -21,7 +21,7 @@ export interface Shape {
original_id?: string // Original ID from backend/agent before TradingView assigns its own ID
}
export const useShapeStore = defineStore('ShapeStore', () => {
export const useShapeStore = defineStore('shapes', () => {
const shapes = ref<Record<string, Shape>>({})
// Helper methods