data fixes; indicator=>workspace sync
This commit is contained in:
@@ -6,6 +6,14 @@ import { useTradingViewShapes } from '../composables/useTradingViewShapes'
|
||||
import { useTradingViewIndicators } from '../composables/useTradingViewIndicators'
|
||||
import { useChartStore } from '../stores/chart'
|
||||
import type { IChartingLibraryWidget } from '../types/tradingview'
|
||||
import { intervalToSeconds } from '../utils'
|
||||
|
||||
// Convert seconds to TradingView interval string
|
||||
function secondsToInterval(seconds: number): string {
|
||||
if (seconds % 86400 === 0) return `${seconds / 86400}D`
|
||||
if (seconds % 3600 === 0) return `${seconds / 3600}H`
|
||||
return `${seconds / 60}` // plain number = minutes
|
||||
}
|
||||
|
||||
const chartContainer = ref<HTMLDivElement | null>(null)
|
||||
const chartStore = useChartStore()
|
||||
@@ -31,7 +39,7 @@ onMounted(() => {
|
||||
tvWidget = new window.TradingView.widget({
|
||||
symbol: chartStore.symbol, // Use symbol from store
|
||||
datafeed: datafeed,
|
||||
interval: chartStore.period as any,
|
||||
interval: secondsToInterval(chartStore.period) as any,
|
||||
container: chartContainer.value!,
|
||||
library_path: '/charting_library/',
|
||||
locale: 'en',
|
||||
@@ -190,9 +198,10 @@ function setupChartListeners() {
|
||||
|
||||
// Listen for period changes
|
||||
chart.onIntervalChanged().subscribe(null, (interval: string) => {
|
||||
console.log('[ChartView] Period changed to:', interval)
|
||||
const seconds = intervalToSeconds(interval)
|
||||
console.log('[ChartView] Period changed to:', interval, `(${seconds}s)`)
|
||||
isUpdatingFromChart = true
|
||||
chartStore.period = interval
|
||||
chartStore.period = seconds
|
||||
isUpdatingFromChart = false
|
||||
})
|
||||
|
||||
@@ -244,10 +253,11 @@ function setupStoreWatchers() {
|
||||
(newPeriod) => {
|
||||
if (isUpdatingFromChart) return
|
||||
|
||||
console.log('[ChartView] Store period changed externally to:', newPeriod)
|
||||
if (chart.resolution() !== newPeriod) {
|
||||
chart.setResolution(newPeriod, () => {
|
||||
console.log('[ChartView] Chart period updated to:', newPeriod)
|
||||
const tvInterval = secondsToInterval(newPeriod)
|
||||
console.log('[ChartView] Store period changed externally to:', newPeriod, `-> ${tvInterval}`)
|
||||
if (chart.resolution() !== tvInterval) {
|
||||
chart.setResolution(tvInterval, () => {
|
||||
console.log('[ChartView] Chart period updated to:', tvInterval)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,35 @@ const rooms = computed(() => [{
|
||||
|
||||
// Streaming state
|
||||
let currentStreamingMessageId: string | null = null
|
||||
let toolCallMessageId: string | null = null
|
||||
let lastSentMessageId: string | null = null
|
||||
let streamingBuffer = ''
|
||||
const isAgentProcessing = ref(false)
|
||||
const toolCallStatus = ref<string | null>(null)
|
||||
|
||||
const addToolCallBubble = (label: string) => {
|
||||
removeToolCallBubble()
|
||||
toolCallMessageId = `tool-call-${Date.now()}`
|
||||
const timestamp = new Date().toTimeString().split(' ')[0].slice(0, 5)
|
||||
messages.value = [...messages.value, {
|
||||
_id: toolCallMessageId,
|
||||
content: `⚙ ${label}`,
|
||||
senderId: AGENT_ID,
|
||||
timestamp,
|
||||
date: new Date().toLocaleDateString(),
|
||||
saved: false,
|
||||
distributed: false,
|
||||
seen: false,
|
||||
files: [],
|
||||
toolCall: true
|
||||
}]
|
||||
}
|
||||
|
||||
const removeToolCallBubble = () => {
|
||||
if (toolCallMessageId) {
|
||||
messages.value = messages.value.filter(m => m._id !== toolCallMessageId)
|
||||
toolCallMessageId = null
|
||||
}
|
||||
}
|
||||
|
||||
// Generate message ID
|
||||
const generateMessageId = () => `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
@@ -52,7 +77,7 @@ const handleMessage = (data: WebSocketMessage) => {
|
||||
console.log('[ChatPanel] Received message:', data)
|
||||
|
||||
if (data.type === 'agent_tool_call') {
|
||||
toolCallStatus.value = data.label ?? data.toolName ?? null
|
||||
addToolCallBubble(data.label ?? data.toolName ?? 'Tool call...')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -99,12 +124,13 @@ const handleMessage = (data: WebSocketMessage) => {
|
||||
|
||||
if (!currentStreamingMessageId) {
|
||||
console.log('[ChatPanel] Starting new streaming message')
|
||||
// Remove any ephemeral tool-call bubble before starting the real response
|
||||
removeToolCallBubble()
|
||||
// Set up streaming state and mark user message as seen
|
||||
isAgentProcessing.value = true
|
||||
currentStreamingMessageId = generateMessageId()
|
||||
streamingBuffer = data.content
|
||||
streamingImages.value = []
|
||||
toolCallStatus.value = null
|
||||
|
||||
// Mark the last sent user message as seen (double-checkmark)
|
||||
if (lastSentMessageId) {
|
||||
@@ -205,7 +231,7 @@ const handleMessage = (data: WebSocketMessage) => {
|
||||
streamingBuffer = ''
|
||||
streamingImages.value = []
|
||||
isAgentProcessing.value = false
|
||||
toolCallStatus.value = null
|
||||
removeToolCallBubble()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +247,7 @@ const stopAgent = () => {
|
||||
}
|
||||
wsManager.send(wsMessage)
|
||||
isAgentProcessing.value = false
|
||||
toolCallStatus.value = null
|
||||
removeToolCallBubble()
|
||||
lastSentMessageId = null
|
||||
}
|
||||
|
||||
@@ -336,34 +362,137 @@ const chatTheme = 'dark'
|
||||
// Styles to match TradingView dark theme
|
||||
const chatStyles = computed(() => JSON.stringify({
|
||||
general: {
|
||||
color: '#d1d4dc',
|
||||
colorSpinner: '#2962ff',
|
||||
borderStyle: '1px solid #2a2e39'
|
||||
color: '#D3D4DC',
|
||||
colorButtonClear: '#D3D4DC',
|
||||
colorButton: '#131722',
|
||||
backgroundColorButton: '#26A69A',
|
||||
backgroundInput: '#131722',
|
||||
colorPlaceholder: '#787B86',
|
||||
colorCaret: '#D3D4DC',
|
||||
colorSpinner: '#26A69A',
|
||||
borderStyle: '1px solid #2A2E39',
|
||||
backgroundScrollIcon: '#2A2E39'
|
||||
},
|
||||
container: {
|
||||
background: '#131722'
|
||||
border: 'none',
|
||||
borderRadius: '0',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
header: {
|
||||
background: '#1e222d',
|
||||
colorRoomName: '#d1d4dc',
|
||||
colorRoomInfo: '#787b86'
|
||||
background: '#2A2E39',
|
||||
colorRoomName: '#D3D4DC',
|
||||
colorRoomInfo: '#787B86',
|
||||
position: 'absolute',
|
||||
width: '100%'
|
||||
},
|
||||
footer: {
|
||||
background: '#1e222d',
|
||||
borderStyleInput: '1px solid #2a2e39',
|
||||
backgroundInput: '#1e222d',
|
||||
colorInput: '#d1d4dc',
|
||||
colorPlaceholder: '#787b86',
|
||||
colorIcons: '#787b86'
|
||||
background: '#2A2E39',
|
||||
borderStyleInput: '1px solid #2A2E39',
|
||||
borderInputSelected: '#26A69A',
|
||||
backgroundReply: '#2A2E39',
|
||||
backgroundTagActive: '#2A2E39',
|
||||
backgroundTag: '#1E222D'
|
||||
},
|
||||
content: {
|
||||
background: '#131722'
|
||||
},
|
||||
sidemenu: {
|
||||
background: '#131722',
|
||||
backgroundHover: '#1E222D',
|
||||
backgroundActive: '#2A2E39',
|
||||
colorActive: '#D3D4DC',
|
||||
borderColorSearch: '#2A2E39'
|
||||
},
|
||||
dropdown: {
|
||||
background: '#2A2E39',
|
||||
backgroundHover: '#363B4A'
|
||||
},
|
||||
message: {
|
||||
background: '#1e222d',
|
||||
backgroundMe: '#2962ff',
|
||||
color: '#d1d4dc',
|
||||
colorMe: '#ffffff'
|
||||
background: '#1E222D',
|
||||
backgroundMe: '#26A69A',
|
||||
color: '#D3D4DC',
|
||||
colorStarted: '#787B86',
|
||||
backgroundDeleted: '#131722',
|
||||
backgroundSelected: '#2A2E39',
|
||||
colorDeleted: '#787B86',
|
||||
colorUsername: '#787B86',
|
||||
colorTimestamp: '#787B86',
|
||||
backgroundDate: 'rgba(0, 0, 0, 0.3)',
|
||||
colorDate: '#787B86',
|
||||
backgroundSystem: 'rgba(0, 0, 0, 0.3)',
|
||||
colorSystem: '#787B86',
|
||||
backgroundMedia: 'rgba(0, 0, 0, 0.18)',
|
||||
backgroundReply: 'rgba(0, 0, 0, 0.18)',
|
||||
colorReplyUsername: '#D3D4DC',
|
||||
colorReply: '#B2B5BE',
|
||||
colorTag: '#26A69A',
|
||||
backgroundImage: '#2A2E39',
|
||||
colorNewMessages: '#26A69A',
|
||||
backgroundScrollCounter: '#26A69A',
|
||||
colorScrollCounter: '#131722',
|
||||
backgroundReaction: 'none',
|
||||
borderStyleReaction: 'none',
|
||||
backgroundReactionHover: '#2A2E39',
|
||||
borderStyleReactionHover: 'none',
|
||||
colorReactionCounter: '#D3D4DC',
|
||||
backgroundReactionMe: '#26A69A',
|
||||
borderStyleReactionMe: 'none',
|
||||
backgroundReactionHoverMe: '#26A69A',
|
||||
borderStyleReactionHoverMe: 'none',
|
||||
colorReactionCounterMe: '#131722',
|
||||
backgroundAudioRecord: '#EF5350',
|
||||
backgroundAudioLine: 'rgba(255, 255, 255, 0.15)',
|
||||
backgroundAudioProgress: '#26A69A',
|
||||
backgroundAudioProgressSelector: '#26A69A',
|
||||
colorFileExtension: '#787B86'
|
||||
},
|
||||
markdown: {
|
||||
background: 'rgba(42, 46, 57, 0.8)',
|
||||
border: 'rgba(55, 60, 74, 0.9)',
|
||||
color: '#26A69A',
|
||||
colorMulti: '#D3D4DC'
|
||||
},
|
||||
room: {
|
||||
colorUsername: '#D3D4DC',
|
||||
colorMessage: '#787B86',
|
||||
colorTimestamp: '#787B86',
|
||||
colorStateOnline: '#26A69A',
|
||||
colorStateOffline: '#787B86',
|
||||
backgroundCounterBadge: '#26A69A',
|
||||
colorCounterBadge: '#131722'
|
||||
},
|
||||
emoji: {
|
||||
background: '#2A2E39'
|
||||
},
|
||||
icons: {
|
||||
search: '#787B86',
|
||||
add: '#D3D4DC',
|
||||
toggle: '#D3D4DC',
|
||||
menu: '#D3D4DC',
|
||||
close: '#787B86',
|
||||
closeImage: '#D3D4DC',
|
||||
file: '#26A69A',
|
||||
paperclip: '#787B86',
|
||||
closeOutline: '#D3D4DC',
|
||||
closePreview: '#D3D4DC',
|
||||
send: '#26A69A',
|
||||
sendDisabled: '#787B86',
|
||||
emoji: '#787B86',
|
||||
emojiReaction: '#787B86',
|
||||
document: '#26A69A',
|
||||
pencil: '#787B86',
|
||||
checkmark: '#787B86',
|
||||
checkmarkSeen: '#26A69A',
|
||||
eye: '#D3D4DC',
|
||||
dropdownMessage: '#D3D4DC',
|
||||
dropdownMessageBackground: 'rgba(0, 0, 0, 0.3)',
|
||||
dropdownRoom: '#D3D4DC',
|
||||
dropdownScroll: '#2A2E39',
|
||||
microphone: '#787B86',
|
||||
audioPlay: '#26A69A',
|
||||
audioPause: '#26A69A',
|
||||
audioCancel: '#EF5350',
|
||||
audioConfirm: '#26A69A'
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -429,7 +558,6 @@ onUnmounted(() => {
|
||||
|
||||
<!-- Stop button overlay -->
|
||||
<div v-if="isAgentProcessing" class="stop-button-container">
|
||||
<div v-if="toolCallStatus" class="tool-call-status">{{ toolCallStatus }}</div>
|
||||
<Button
|
||||
icon="pi pi-stop-circle"
|
||||
label="Stop"
|
||||
@@ -459,12 +587,12 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
background: #131722;
|
||||
color: #787b86;
|
||||
color: #787B86;
|
||||
}
|
||||
|
||||
.workspace-loading-spinner {
|
||||
font-size: 2rem;
|
||||
color: #787b86;
|
||||
color: #787B86;
|
||||
}
|
||||
|
||||
.workspace-loading-message {
|
||||
@@ -485,6 +613,7 @@ onUnmounted(() => {
|
||||
max-width: 80% !important;
|
||||
}
|
||||
|
||||
|
||||
.chat-header-custom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -504,21 +633,10 @@ onUnmounted(() => {
|
||||
.stop-button-container {
|
||||
position: absolute;
|
||||
bottom: 80px;
|
||||
right: 20px;
|
||||
left: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.tool-call-status {
|
||||
background: rgba(30, 34, 45, 0.92);
|
||||
color: #787b86;
|
||||
font-size: 0.75rem;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 6px;
|
||||
text-align: center;
|
||||
border: 1px solid #2a2e39;
|
||||
}
|
||||
|
||||
.stop-button {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
animation: pulse 2s infinite;
|
||||
|
||||
Reference in New Issue
Block a user