Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View File

@@ -0,0 +1,898 @@
<!-- Real-time AI News System Monitoring Dashboard -->
<template>
<view class="dashboard-container">
<!-- Header -->
<view class="dashboard-header">
<text class="dashboard-title">AI News System Dashboard</text>
<view class="status-indicator" :class="systemHealth.status">
<text class="status-text">{{ systemHealth.status.toUpperCase() }}</text>
<text class="health-score">{{ systemHealth.score }}/100</text>
</view>
</view>
<!-- Quick Stats -->
<view class="quick-stats">
<view class="stat-card">
<text class="stat-value">{{ formatNumber(stats.requests.total) }}</text>
<text class="stat-label">Total Requests</text>
<text class="stat-change" :class="getChangeClass(requestsChange)">
{{ formatChange(requestsChange) }}
</text>
</view>
<view class="stat-card">
<text class="stat-value">{{ (stats.requests.successRate * 100).toFixed(1) }}%</text>
<text class="stat-label">Success Rate</text>
<text class="stat-change" :class="getChangeClass(successRateChange)">
{{ formatChange(successRateChange) }}
</text>
</view>
<view class="stat-card">
<text class="stat-value">{{ stats.timing.averageLatency.toFixed(0) }}ms</text>
<text class="stat-label">Avg Latency</text>
<text class="stat-change" :class="getChangeClass(-latencyChange)">
{{ formatChange(latencyChange) }}
</text>
</view>
<view class="stat-card">
<text class="stat-value">${{ stats.costs.total.toFixed(2) }}</text>
<text class="stat-label">Total Cost</text>
<text class="stat-change" :class="getChangeClass(-costChange)">
${{ costChange.toFixed(2) }}
</text>
</view>
</view>
<!-- Health Checks -->
<view class="health-section">
<text class="section-title">System Health Checks</text>
<view class="health-checks">
<view class="health-check"
v-for="(check, key) in systemHealth.checks"
:key="key"
:class="getHealthCheckClass(key, check)">
<text class="check-name">{{ formatCheckName(key) }}</text>
<text class="check-value">{{ formatCheckValue(key, check) }}</text>
<view class="check-indicator" :class="getHealthCheckClass(key, check)"></view>
</view>
</view>
</view>
<!-- Active Alerts -->
<view class="alerts-section" v-if="systemHealth.alerts.length > 0">
<text class="section-title">Active Alerts ({{ systemHealth.alerts.length }})</text>
<scroll-view class="alerts-list" scroll-y="true">
<view class="alert-item"
v-for="alert in systemHealth.alerts"
:key="alert.id"
:class="alert.severity">
<view class="alert-header">
<text class="alert-severity">{{ alert.severity.toUpperCase() }}</text>
<text class="alert-time">{{ formatTime(alert.timestamp) }}</text>
</view>
<text class="alert-message">{{ alert.message }}</text>
<text class="alert-source">Source: {{ alert.source }}</text>
</view>
</scroll-view>
</view>
<!-- Performance Charts -->
<view class="charts-section">
<text class="section-title">Performance Trends</text>
<!-- Response Time Chart -->
<view class="chart-container">
<text class="chart-title">Response Time (Last Hour)</text>
<view class="chart-area">
<canvas class="chart-canvas"
canvas-id="responseTimeChart"
@touchstart="onChartTouch"
@touchend="onChartTouchEnd"></canvas>
</view>
</view>
<!-- Request Volume Chart -->
<view class="chart-container">
<text class="chart-title">Request Volume</text>
<view class="chart-area">
<canvas class="chart-canvas"
canvas-id="requestVolumeChart"></canvas>
</view>
</view>
<!-- Cost Distribution -->
<view class="chart-container">
<text class="chart-title">Cost by Provider</text>
<view class="cost-breakdown">
<view class="cost-item" v-for="(cost, provider) in stats.costs.byProvider" :key="provider">
<view class="cost-bar-container">
<text class="provider-name">{{ provider }}</text>
<view class="cost-bar">
<view class="cost-fill"
:style="{ width: (cost / stats.costs.total * 100) + '%' }"></view>
</view>
<text class="cost-value">${{ cost.toFixed(2) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- Optimization Recommendations -->
<view class="recommendations-section" v-if="recommendations.length > 0">
<text class="section-title">Optimization Recommendations</text>
<scroll-view class="recommendations-list" scroll-y="true">
<view class="recommendation-item"
v-for="(rec, index) in recommendations"
:key="index"
:class="rec.priority">
<view class="rec-header">
<text class="rec-type">{{ rec.type.toUpperCase() }}</text>
<text class="rec-priority">{{ rec.priority.toUpperCase() }}</text>
</view>
<text class="rec-description">{{ rec.description }}</text>
<view class="rec-impact">
<text class="impact-title">Expected Impact:</text>
<text v-if="rec.expectedImpact.performanceGain" class="impact-item">
🚀 {{ rec.expectedImpact.performanceGain }}
</text>
<text v-if="rec.expectedImpact.costSaving" class="impact-item">
💰 {{ rec.expectedImpact.costSaving }}
</text>
<text v-if="rec.expectedImpact.reliabilityImprovement" class="impact-item">
🛡 {{ rec.expectedImpact.reliabilityImprovement }}
</text>
</view>
<view class="rec-actions">
<button class="btn-apply" @click="applyRecommendation(rec)" :disabled="rec.applying">
{{ rec.applying ? 'Applying...' : 'Apply' }}
</button>
<button class="btn-dismiss" @click="dismissRecommendation(rec)">
Dismiss
</button>
</view>
</view>
</scroll-view>
</view>
<!-- Control Panel -->
<view class="controls-section">
<text class="section-title">Controls</text>
<view class="control-buttons">
<button class="control-btn" @click="refreshData" :disabled="isRefreshing">
{{ isRefreshing ? 'Refreshing...' : 'Refresh Data' }}
</button>
<button class="control-btn" @click="exportData">
Export Metrics
</button>
<button class="control-btn" @click="toggleAutoRefresh">
{{ autoRefresh ? 'Stop Auto-Refresh' : 'Start Auto-Refresh' }}
</button>
<button class="control-btn danger" @click="clearAlerts">
Clear Alerts
</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
import {
AIPerformanceMonitor,
type PerformanceStats,
type SystemHealth,
type OptimizationRecommendation,
defaultPerformanceConfig
} from '../services/AIPerformanceMonitor.uts'
// Reactive data
const monitor = new AIPerformanceMonitor(defaultPerformanceConfig)
const stats = ref<PerformanceStats>({
timeRange: { start: 0, end: 0, duration: 0 },
requests: { total: 0, successful: 0, failed: 0, successRate: 0 },
timing: { averageLatency: 0, medianLatency: 0, p95Latency: 0, p99Latency: 0 },
costs: { total: 0, average: 0, byProvider: {} },
cache: { hitRate: 0, totalRequests: 0, hits: 0, misses: 0 },
errors: { byType: {}, byProvider: {}, topErrors: [] }
})
const systemHealth = ref<SystemHealth>({
status: 'healthy',
score: 100,
checks: {
apiConnectivity: true,
memoryUsage: 0,
errorRate: 0,
responseTime: 0,
costBudget: 0,
cacheEfficiency: 0
},
alerts: []
})
const recommendations = ref<OptimizationRecommendation[]>([])
const isRefreshing = ref(false)
const autoRefresh = ref(true)
let refreshInterval: number | null = null
// Previous values for change calculation
const previousStats = ref<PerformanceStats | null>(null)
// Computed properties for changes
const requestsChange = computed(() => {
if (!previousStats.value) return 0
return stats.value.requests.total - previousStats.value.requests.total
})
const successRateChange = computed(() => {
if (!previousStats.value) return 0
return stats.value.requests.successRate - previousStats.value.requests.successRate
})
const latencyChange = computed(() => {
if (!previousStats.value) return 0
return stats.value.timing.averageLatency - previousStats.value.timing.averageLatency
})
const costChange = computed(() => {
if (!previousStats.value) return 0
return stats.value.costs.total - previousStats.value.costs.total
})
// Lifecycle hooks
onMounted(async () => {
console.log('🚀 Starting monitoring dashboard...')
monitor.startMonitoring()
await refreshData()
if (autoRefresh.value) {
startAutoRefresh()
}
})
onUnmounted(() => {
console.log('🛑 Stopping monitoring dashboard...')
stopAutoRefresh()
monitor.stopMonitoring()
})
// Methods
const refreshData = async () => {
if (isRefreshing.value) return
isRefreshing.value = true
try {
// Store previous stats for change calculation
previousStats.value = { ...stats.value }
// Get fresh data
const now = Date.now()
const oneHourAgo = now - 3600000
stats.value = monitor.getPerformanceStats(oneHourAgo, now)
systemHealth.value = monitor.getSystemHealth()
recommendations.value = monitor.getOptimizationRecommendations()
console.log('📊 Dashboard data refreshed')
} catch (error) {
console.error('❌ Failed to refresh dashboard data:', error)
} finally {
isRefreshing.value = false
}
}
const startAutoRefresh = () => {
if (refreshInterval) return
refreshInterval = setInterval(async () => {
await refreshData()
}, 30000) // Refresh every 30 seconds
console.log('🔄 Auto-refresh started')
}
const stopAutoRefresh = () => {
if (refreshInterval) {
clearInterval(refreshInterval)
refreshInterval = null
console.log('⏹️ Auto-refresh stopped')
}
}
const toggleAutoRefresh = () => {
autoRefresh.value = !autoRefresh.value
if (autoRefresh.value) {
startAutoRefresh()
} else {
stopAutoRefresh()
}
}
const applyRecommendation = async (rec: OptimizationRecommendation) => {
rec.applying = true
try {
const result = await monitor.applyOptimizations([rec])
if (result.applied > 0) {
console.log('✅ Recommendation applied successfully')
// Remove from list
const index = recommendations.value.indexOf(rec)
if (index > -1) {
recommendations.value.splice(index, 1)
}
} else {
console.error('❌ Failed to apply recommendation')
}
} catch (error) {
console.error('💥 Error applying recommendation:', error)
} finally {
rec.applying = false
}
}
const dismissRecommendation = (rec: OptimizationRecommendation) => {
const index = recommendations.value.indexOf(rec)
if (index > -1) {
recommendations.value.splice(index, 1)
}
}
const exportData = () => {
try {
const data = monitor.exportPerformanceData('json')
// In real implementation, this would trigger a download
console.log('📤 Exporting performance data...')
console.log(data)
// For uni-app, you might want to save to local storage or share
uni.setStorageSync('ai-news-performance-data', data)
uni.showToast({
title: 'Data exported to storage',
icon: 'success'
})
} catch (error) {
console.error('❌ Failed to export data:', error)
uni.showToast({
title: 'Export failed',
icon: 'error'
})
}
}
const clearAlerts = () => {
systemHealth.value.alerts = []
uni.showToast({
title: 'Alerts cleared',
icon: 'success'
})
}
// Utility methods
const formatNumber = (num: number): string => {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M'
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K'
}
return num.toString()
}
const formatChange = (change: number): string => {
if (change === 0) return '0'
const sign = change > 0 ? '+' : ''
return `${sign}${change.toFixed(1)}`
}
const getChangeClass = (change: number): string => {
if (change > 0) return 'positive'
if (change < 0) return 'negative'
return 'neutral'
}
const formatCheckName = (key: string): string => {
return key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())
}
const formatCheckValue = (key: string, value: any): string => {
switch (key) {
case 'apiConnectivity':
return value ? 'Connected' : 'Disconnected'
case 'memoryUsage':
return (value * 100).toFixed(1) + '%'
case 'errorRate':
return (value * 100).toFixed(2) + '%'
case 'responseTime':
return value.toFixed(0) + 'ms'
case 'costBudget':
return (value * 100).toFixed(1) + '%'
case 'cacheEfficiency':
return (value * 100).toFixed(1) + '%'
default:
return String(value)
}
}
const getHealthCheckClass = (key: string, value: any): string => {
switch (key) {
case 'apiConnectivity':
return value ? 'healthy' : 'critical'
case 'memoryUsage':
return value > 0.8 ? 'critical' : value > 0.6 ? 'warning' : 'healthy'
case 'errorRate':
return value > 0.1 ? 'critical' : value > 0.05 ? 'warning' : 'healthy'
case 'responseTime':
return value > 5000 ? 'critical' : value > 3000 ? 'warning' : 'healthy'
case 'costBudget':
return value > 0.9 ? 'critical' : value > 0.7 ? 'warning' : 'healthy'
case 'cacheEfficiency':
return value < 0.3 ? 'warning' : value < 0.5 ? 'healthy' : 'excellent'
default:
return 'neutral'
}
}
const formatTime = (timestamp: number): string => {
const now = Date.now()
const diff = now - timestamp
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 new Date(timestamp).toLocaleDateString()
}
// Chart event handlers
const onChartTouch = (e: any) => {
console.log('Chart touched:', e)
}
const onChartTouchEnd = (e: any) => {
console.log('Chart touch ended:', e)
}
</script>
<style scoped>
.dashboard-container {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.dashboard-title {
font-size: 24px;
font-weight: bold;
color: #333;
}
.status-indicator {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 20px;
border-radius: 8px;
min-width: 100px;
}
.status-indicator.healthy {
background-color: #e8f5e8;
border: 2px solid #4caf50;
}
.status-indicator.warning {
background-color: #fff3e0;
border: 2px solid #ff9800;
}
.status-indicator.critical {
background-color: #ffebee;
border: 2px solid #f44336;
}
.status-text {
font-weight: bold;
font-size: 14px;
}
.health-score {
font-size: 18px;
font-weight: bold;
margin-top: 4px;
}
.quick-stats {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.stat-card {
flex: 1;
min-width: 200px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
}
.stat-value {
display: block;
font-size: 28px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.stat-label {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.stat-change {
display: block;
font-size: 12px;
font-weight: bold;
}
.stat-change.positive {
color: #4caf50;
}
.stat-change.negative {
color: #f44336;
}
.stat-change.neutral {
color: #666;
}
.health-section,
.alerts-section,
.charts-section,
.recommendations-section,
.controls-section {
margin-bottom: 20px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.section-title {
display: block;
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.health-checks {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.health-check {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #ddd;
}
.health-check.healthy {
background-color: #e8f5e8;
border-left-color: #4caf50;
}
.health-check.warning {
background-color: #fff3e0;
border-left-color: #ff9800;
}
.health-check.critical {
background-color: #ffebee;
border-left-color: #f44336;
}
.check-name {
font-weight: bold;
color: #333;
}
.check-value {
color: #666;
}
.alerts-list {
max-height: 300px;
}
.alert-item {
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
border-left: 4px solid #ddd;
}
.alert-item.info {
background-color: #e3f2fd;
border-left-color: #2196f3;
}
.alert-item.warning {
background-color: #fff3e0;
border-left-color: #ff9800;
}
.alert-item.error {
background-color: #ffebee;
border-left-color: #f44336;
}
.alert-item.critical {
background-color: #fce4ec;
border-left-color: #e91e63;
}
.alert-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.alert-severity {
font-weight: bold;
font-size: 12px;
}
.alert-time {
font-size: 12px;
color: #666;
}
.alert-message {
display: block;
margin-bottom: 4px;
color: #333;
}
.alert-source {
display: block;
font-size: 12px;
color: #666;
}
.chart-container {
margin-bottom: 30px;
}
.chart-title {
display: block;
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.chart-area {
height: 200px;
position: relative;
}
.chart-canvas {
width: 100%;
height: 100%;
}
.cost-breakdown {
display: flex;
flex-direction: column;
gap: 10px;
}
.cost-item {
display: flex;
align-items: center;
gap: 15px;
}
.cost-bar-container {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
}
.provider-name {
min-width: 80px;
font-weight: bold;
color: #333;
}
.cost-bar {
flex: 1;
height: 20px;
background-color: #f0f0f0;
border-radius: 10px;
overflow: hidden;
}
.cost-fill {
height: 100%;
background: linear-gradient(90deg, #4caf50, #2196f3);
transition: width 0.3s ease;
}
.cost-value {
min-width: 60px;
text-align: right;
font-weight: bold;
color: #333;
}
.recommendations-list {
max-height: 400px;
}
.recommendation-item {
padding: 15px;
margin-bottom: 15px;
border-radius: 8px;
border-left: 4px solid #ddd;
}
.recommendation-item.low {
background-color: #f9f9f9;
border-left-color: #9e9e9e;
}
.recommendation-item.medium {
background-color: #fff3e0;
border-left-color: #ff9800;
}
.recommendation-item.high {
background-color: #ffebee;
border-left-color: #f44336;
}
.recommendation-item.critical {
background-color: #fce4ec;
border-left-color: #e91e63;
}
.rec-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.rec-type {
font-weight: bold;
font-size: 12px;
padding: 4px 8px;
background-color: #e0e0e0;
border-radius: 4px;
}
.rec-priority {
font-weight: bold;
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
}
.rec-description {
display: block;
margin-bottom: 10px;
color: #333;
line-height: 1.4;
}
.rec-impact {
margin-bottom: 15px;
}
.impact-title {
display: block;
font-weight: bold;
color: #333;
margin-bottom: 5px;
}
.impact-item {
display: block;
margin-bottom: 2px;
color: #666;
font-size: 14px;
}
.rec-actions {
display: flex;
gap: 10px;
}
.btn-apply,
.btn-dismiss {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
}
.btn-apply {
background-color: #4caf50;
color: white;
}
.btn-apply:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.btn-dismiss {
background-color: #f5f5f5;
color: #333;
}
.control-buttons {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.control-btn {
padding: 12px 24px;
border: 2px solid #2196f3;
background-color: #2196f3;
color: white;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.control-btn:hover {
background-color: #1976d2;
border-color: #1976d2;
}
.control-btn:disabled {
background-color: #ccc;
border-color: #ccc;
cursor: not-allowed;
}
.control-btn.danger {
background-color: #f44336;
border-color: #f44336;
}
.control-btn.danger:hover {
background-color: #d32f2f;
border-color: #d32f2f;
}
</style>