各 Agent 计算力消耗排行榜
+
+
各 Agent 计算力消耗排行榜
+
+
+
+
+
@@ -108,10 +127,13 @@
任务级成本分析
-
-
-
-
+
+
+
@@ -167,7 +189,17 @@
-
@@ -214,9 +246,21 @@ const loading = ref(true)
const rawData = ref([])
const timeScale = ref('day') // hour, day, week, month
-const taskSort = ref('cost') // cost, tokens, time
const activeTab = ref('task') // 'trend', 'agent', 'task', 'log'
+// Global Time Range Filter
+const globalTimeRange = ref('7d')
+
+// Tab filters & sorters
+const searchAgent = ref('')
+const sortAgent = ref('tokens')
+
+const searchTask = ref('')
+const taskSort = ref('cost')
+
+const searchLog = ref('')
+const sortLog = ref('time')
+
onMounted(async () => {
try {
const res = await fetch('/api/agent/quota')
@@ -231,16 +275,27 @@ onMounted(async () => {
}
})
-// Overview stats
-const totalCost = computed(() => rawData.value.reduce((s, d) => s + (d.costUsd || 0), 0))
-const totalInputTokens = computed(() => rawData.value.reduce((s, d) => s + (d.usage?.input || 0), 0))
-const totalOutputTokens = computed(() => rawData.value.reduce((s, d) => s + (d.usage?.output || 0), 0))
-const totalCalls = computed(() => rawData.value.length)
+const filteredData = computed(() => {
+ if (globalTimeRange.value === 'all') return rawData.value
+ const now = Date.now()
+ let cutoff = 0
+ if (globalTimeRange.value === '1h') cutoff = now - 3600 * 1000
+ else if (globalTimeRange.value === '24h') cutoff = now - 24 * 3600 * 1000
+ else if (globalTimeRange.value === '7d') cutoff = now - 7 * 24 * 3600 * 1000
+ else if (globalTimeRange.value === '30d') cutoff = now - 30 * 24 * 3600 * 1000
+ return rawData.value.filter(d => new Date(d.timestamp).getTime() >= cutoff)
+})
+
+// Overview stats (derived from filteredData)
+const totalCost = computed(() => filteredData.value.reduce((s, d) => s + (d.costUsd || 0), 0))
+const totalInputTokens = computed(() => filteredData.value.reduce((s, d) => s + (d.usage?.input || 0), 0))
+const totalOutputTokens = computed(() => filteredData.value.reduce((s, d) => s + (d.usage?.output || 0), 0))
+const totalCalls = computed(() => filteredData.value.length)
// Time Breakdown
const timeStats = computed(() => {
const map = {}
- rawData.value.forEach(d => {
+ filteredData.value.forEach(d => {
const date = new Date(d.timestamp)
let label = ''
if (timeScale.value === 'hour') {
@@ -277,7 +332,7 @@ const maxTimeCalls = computed(() => {
// Agent Breakdown
const agentStats = computed(() => {
const map = {}
- rawData.value.forEach(d => {
+ filteredData.value.forEach(d => {
const id = d.agentId || 'unknown'
if (!map[id]) map[id] = { id, cost: 0, input: 0, output: 0, tokens: 0, calls: 0, model: d.model || 'claude-3-5-*' }
map[id].cost += (d.costUsd || 0)
@@ -287,7 +342,18 @@ const agentStats = computed(() => {
map[id].calls += 1
if (d.model && d.model !== 'unknown') map[id].model = d.model
})
- return Object.values(map).sort((a,b) => b.tokens - a.tokens)
+
+ let result = Object.values(map)
+ if (searchAgent.value) {
+ const q = searchAgent.value.toLowerCase()
+ result = result.filter(a => a.id.toLowerCase().includes(q) || a.model.toLowerCase().includes(q))
+ }
+
+ if (sortAgent.value === 'calls') result.sort((a,b) => b.calls - a.calls)
+ else if (sortAgent.value === 'cost') result.sort((a,b) => b.cost - a.cost)
+ else result.sort((a,b) => b.tokens - a.tokens)
+
+ return result
})
const maxAgentTokens = computed(() => {
if (agentStats.value.length === 0) return 1
@@ -297,14 +363,13 @@ const maxAgentTokens = computed(() => {
// Task-level Breakdown
const taskStats = computed(() => {
const map = {}
- rawData.value.forEach(d => {
- // 从 convId 中提取 taskId
+ filteredData.value.forEach(d => {
const convId = d.convId || ''
let taskId = null
const taskMatch = convId.match(/(?:task|callback)-?(T-[a-z0-9]+)/i)
if (taskMatch) taskId = taskMatch[1]
else if (convId.startsWith('tpl-train-')) taskId = convId
- else return // 跳过非任务类型的记录(如 conv-xxx 用户对话)
+ else return
if (!map[taskId]) map[taskId] = { taskId, cost: 0, input: 0, output: 0, calls: 0, duration: 0, agentSet: new Set(), details: [], expanded: false, firstTime: d.timestamp }
map[taskId].cost += (d.costUsd || 0)
@@ -317,15 +382,36 @@ const taskStats = computed(() => {
if (d.timestamp < map[taskId].firstTime) map[taskId].firstTime = d.timestamp
})
- const arr = Object.values(map).map(t => ({ ...t, agents: [...t.agentSet] }))
+ let arr = Object.values(map).map(t => ({ ...t, agents: [...t.agentSet] }))
+
+ if (searchTask.value) {
+ const q = searchTask.value.toLowerCase()
+ arr = arr.filter(t => t.taskId.toLowerCase().includes(q) || t.agents.some(a => a.toLowerCase().includes(q)))
+ }
+
if (taskSort.value === 'cost') arr.sort((a, b) => b.cost - a.cost)
else if (taskSort.value === 'tokens') arr.sort((a, b) => (b.input + b.output) - (a.input + a.output))
else arr.sort((a, b) => new Date(b.firstTime) - new Date(a.firstTime))
- return arr.slice(0, 50)
+ return arr.slice(0, 100) // 提升显示上限
})
// Recent Logs table
-const recentLogs = computed(() => rawData.value.slice(0, 50))
+const recentLogs = computed(() => {
+ let list = filteredData.value
+ if (searchLog.value) {
+ const q = searchLog.value.toLowerCase()
+ list = list.filter(d => (d.agentId && d.agentId.toLowerCase().includes(q)) ||
+ (d.model && d.model.toLowerCase().includes(q)) ||
+ (d.convId && d.convId.toLowerCase().includes(q)))
+ }
+
+ let sorted = [...list]
+ if (sortLog.value === 'cost') sorted.sort((a,b) => (b.costUsd||0) - (a.costUsd||0))
+ else if (sortLog.value === 'duration') sorted.sort((a,b) => (b.durationMs||0) - (a.durationMs||0))
+ else sorted.sort((a,b) => new Date(b.timestamp) - new Date(a.timestamp))
+
+ return sorted.slice(0, 200) // Increase limits
+})
function formatTime(iso) {
const d = new Date(iso)
@@ -351,7 +437,7 @@ function formatTime(iso) {
}
.page-header {
- margin-bottom: 32px;
+ margin-bottom: 24px;
}
.page-header h1 {
font-size: 28px;
@@ -362,6 +448,41 @@ function formatTime(iso) {
.subtitle {
color: var(--text-secondary);
font-size: 14px;
+ margin-bottom: 20px;
+}
+.global-time-filter {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ background: var(--bg-surface);
+ padding: 8px 16px;
+ border-radius: 8px;
+ border: 1px solid var(--border-color);
+ width: fit-content;
+}
+.filter-label {
+ font-size: 13px;
+ color: var(--text-secondary);
+ margin-right: 8px;
+}
+.global-time-filter button {
+ background: transparent;
+ border: 1px solid transparent;
+ font-size: 13px;
+ padding: 6px 16px;
+ border-radius: 6px;
+ color: var(--text-primary);
+ cursor: pointer;
+ transition: all 0.2s;
+}
+.global-time-filter button:hover {
+ background: var(--bg-secondary);
+}
+.global-time-filter button.active {
+ background: var(--accent-primary);
+ color: #fff;
+ border-color: rgba(0,0,0,0.1);
+ box-shadow: 0 2px 4px rgba(35, 131, 226, 0.2);
}
.overview-grid {
@@ -453,6 +574,42 @@ function formatTime(iso) {
gap: 8px;
}
+/* Tab Controls for Search and Sort */
+.tab-controls {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+.search-input {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 6px 12px;
+ border-radius: 6px;
+ font-size: 13px;
+ width: 220px;
+ outline: none;
+ transition: border-color 0.2s;
+}
+.search-input:focus {
+ border-color: var(--accent-primary);
+}
+.sort-select {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ color: var(--text-primary);
+ padding: 6px 30px 6px 12px;
+ border-radius: 6px;
+ font-size: 13px;
+ outline: none;
+ cursor: pointer;
+ appearance: none;
+ background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
+ background-repeat: no-repeat;
+ background-position: right 8px center;
+ background-size: 14px;
+}
+
/* 按时间过滤 */
.time-filters {
display: flex;
最近的大模型调用日志
+
+
最近的大模型调用日志 (明细)
+
+
+
+
+