190 lines
4.9 KiB
Python
190 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
import os
|
|
|
|
# Read the existing file
|
|
with open('frontend/admin/components/AiLogsTile.vue', 'r') as f:
|
|
content = f.read()
|
|
|
|
# Find where the file is truncated
|
|
if 'case \'success\': return \'green' in content and not 'case \'warning\': return \'orange\'' in content:
|
|
# The file is truncated at the getLogColor function
|
|
# Let's find the exact position and complete it
|
|
lines = content.split('\n')
|
|
|
|
# Find the line with getLogColor
|
|
for i, line in enumerate(lines):
|
|
if 'const getLogColor = (type: AiLogEntry[\'type\']) => {' in line:
|
|
start_idx = i
|
|
# Find where this function ends (look for the next function or end of file)
|
|
for j in range(i+1, len(lines)):
|
|
if lines[j].strip().startswith('const ') or lines[j].strip().startswith('//'):
|
|
# Found next function or comment
|
|
end_idx = j
|
|
break
|
|
else:
|
|
end_idx = len(lines)
|
|
|
|
# Replace the incomplete function with complete version
|
|
new_function = '''const getLogColor = (type: AiLogEntry['type']) => {
|
|
switch (type) {
|
|
case 'info': return 'blue'
|
|
case 'success': return 'green'
|
|
case 'warning': return 'orange'
|
|
case 'error': return 'red'
|
|
case 'gold': return 'amber'
|
|
default: return 'grey'
|
|
}
|
|
}
|
|
|
|
const getLogIcon = (type: AiLogEntry['type']) => {
|
|
switch (type) {
|
|
case 'info': return 'mdi-information'
|
|
case 'success': return 'mdi-check-circle'
|
|
case 'warning': return 'mdi-alert'
|
|
case 'error': return 'mdi-alert-circle'
|
|
case 'gold': return 'mdi-star'
|
|
default: return 'mdi-help-circle'
|
|
}
|
|
}
|
|
|
|
const getRobotColor = (robotName: string) => {
|
|
const robot = robots.value.find(r => r.name === robotName)
|
|
return robot?.statusColor || 'grey'
|
|
}
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status.toLowerCase()) {
|
|
case 'running': return 'success'
|
|
case 'idle': return 'warning'
|
|
case 'error': return 'error'
|
|
case 'paused': return 'grey'
|
|
default: return 'grey'
|
|
}
|
|
}
|
|
|
|
const formatTime = (timestamp: Date) => {
|
|
const now = new Date()
|
|
const diff = now.getTime() - timestamp.getTime()
|
|
|
|
if (diff < 60000) return 'Just now'
|
|
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`
|
|
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`
|
|
return timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
}
|
|
|
|
// Data fetching and polling
|
|
const fetchLogs = async () => {
|
|
if (isRefreshing.value) return
|
|
|
|
isRefreshing.value = true
|
|
try {
|
|
// Simulate API call
|
|
await new Promise(resolve => setTimeout(resolve, 500))
|
|
|
|
// Add new mock log
|
|
const newLog = generateMockLog()
|
|
logs.value.push(newLog)
|
|
|
|
// Keep only last 50 logs
|
|
if (logs.value.length > 50) {
|
|
logs.value = logs.value.slice(-50)
|
|
}
|
|
|
|
// Mark old logs as not new
|
|
setTimeout(() => {
|
|
logs.value.forEach(log => {
|
|
if (log.isNew && Date.now() - log.timestamp.getTime() > 5000) {
|
|
log.isNew = false
|
|
}
|
|
})
|
|
}, 5000)
|
|
|
|
// Update connection status randomly
|
|
if (Math.random() > 0.95) {
|
|
connectionStatus.value = 'disconnected'
|
|
} else if (Math.random() > 0.98) {
|
|
connectionStatus.value = 'error'
|
|
} else {
|
|
connectionStatus.value = 'connected'
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Failed to fetch AI logs:', error)
|
|
connectionStatus.value = 'error'
|
|
} finally {
|
|
isRefreshing.value = false
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const forceRefresh = () => {
|
|
fetchLogs()
|
|
}
|
|
|
|
const toggleAutoScroll = () => {
|
|
autoScroll.value = !autoScroll.value
|
|
}
|
|
|
|
const clearLogs = () => {
|
|
logs.value = []
|
|
}
|
|
|
|
const scrollToBottom = () => {
|
|
if (logContainer.value && autoScroll.value) {
|
|
nextTick(() => {
|
|
logContainer.value!.scrollTop = logContainer.value!.scrollHeight
|
|
})
|
|
}
|
|
}
|
|
|
|
// Polling management
|
|
let pollInterval: number | null = null
|
|
|
|
const startPolling = () => {
|
|
if (pollInterval) clearInterval(pollInterval)
|
|
pollInterval = setInterval(() => {
|
|
fetchLogs()
|
|
scrollToBottom()
|
|
}, pollingInterval.value) as unknown as number
|
|
}
|
|
|
|
const stopPolling = () => {
|
|
if (pollInterval) {
|
|
clearInterval(pollInterval)
|
|
pollInterval = null
|
|
}
|
|
}
|
|
|
|
// Lifecycle hooks
|
|
onMounted(() => {
|
|
// Initial load
|
|
fetchLogs()
|
|
|
|
// Start polling
|
|
startPolling()
|
|
|
|
// Generate initial logs
|
|
for (let i = 0; i < 10; i++) {
|
|
const log = generateMockLog()
|
|
log.timestamp = new Date(Date.now() - (10 - i) * 60000) // Staggered times
|
|
log.isNew = false
|
|
logs.value.push(log)
|
|
}
|
|
|
|
isLoading.value = false
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
stopPolling()
|
|
})'''
|
|
|
|
# Replace the lines
|
|
new_lines = lines[:start_idx] + new_function.split('\n')
|
|
content = '\n'.join(new_lines)
|
|
break
|
|
|
|
# Write the complete file
|
|
with open('frontend/admin/components/AiLogsTile.vue', 'w') as f:
|
|
f.write(content)
|
|
|
|
print("File completed successfully") |