client-py connected
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
24
web/src/stores/channel.ts
Normal 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 }
|
||||
})
|
||||
@@ -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 }
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user