Merge pull request #746 from szmadd/feat/dashboard-agent-name
feat(dashboard): 数据总览智能体图表中将以 slug 展示的智能体ID替换为名称
This commit is contained in:
commit
6eb57663d9
@ -349,6 +349,10 @@ class AgentRepository:
|
|||||||
result = await self.db.execute(select(Agent).where(Agent.slug == slug))
|
result = await self.db.execute(select(Agent).where(Agent.slug == slug))
|
||||||
return result.scalar_one_or_none()
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
async def list_by_slugs(self, slugs: list[str]) -> list[Agent]:
|
||||||
|
result = await self.db.execute(select(Agent).where(Agent.slug.in_(slugs)))
|
||||||
|
return list(result.scalars().all())
|
||||||
|
|
||||||
async def get_visible_by_slug(self, *, slug: str, user: User, include_subagents: bool = False) -> Agent | None:
|
async def get_visible_by_slug(self, *, slug: str, user: User, include_subagents: bool = False) -> Agent | None:
|
||||||
agent = await self.get_by_slug(slug)
|
agent = await self.get_by_slug(slug)
|
||||||
if not agent or (agent.is_subagent and not include_subagents):
|
if not agent or (agent.is_subagent and not include_subagents):
|
||||||
|
|||||||
@ -16,6 +16,7 @@ from sqlalchemy import Integer, String, cast, distinct, func, select, text
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from server.utils.auth_middleware import get_admin_user, get_db
|
from server.utils.auth_middleware import get_admin_user, get_db
|
||||||
|
from yuxi.repositories.agent_repository import AgentRepository
|
||||||
from yuxi.repositories.conversation_repository import ConversationRepository
|
from yuxi.repositories.conversation_repository import ConversationRepository
|
||||||
from yuxi.storage.postgres.models_business import User
|
from yuxi.storage.postgres.models_business import User
|
||||||
from yuxi.utils.datetime_utils import UTC, ensure_shanghai, shanghai_now, utc_now
|
from yuxi.utils.datetime_utils import UTC, ensure_shanghai, shanghai_now, utc_now
|
||||||
@ -89,6 +90,7 @@ class AgentAnalytics(BaseModel):
|
|||||||
agent_satisfaction_rates: list[dict]
|
agent_satisfaction_rates: list[dict]
|
||||||
agent_tool_usage: list[dict]
|
agent_tool_usage: list[dict]
|
||||||
top_performing_agents: list[dict]
|
top_performing_agents: list[dict]
|
||||||
|
agent_names: dict[str, str] = {} # agent_id -> agent_name 映射
|
||||||
|
|
||||||
|
|
||||||
class ConversationListItem(BaseModel):
|
class ConversationListItem(BaseModel):
|
||||||
@ -557,12 +559,19 @@ async def get_agent_analytics(
|
|||||||
top_performing_agents.sort(key=lambda x: x["conversation_count"], reverse=True)
|
top_performing_agents.sort(key=lambda x: x["conversation_count"], reverse=True)
|
||||||
top_performing_agents = top_performing_agents[:5]
|
top_performing_agents = top_performing_agents[:5]
|
||||||
|
|
||||||
|
agent_slugs = [agent_id for agent_id, _ in agents if agent_id]
|
||||||
|
agent_names = {}
|
||||||
|
if agent_slugs:
|
||||||
|
agent_repo = AgentRepository(db)
|
||||||
|
agent_names = {agent.slug: agent.name for agent in await agent_repo.list_by_slugs(agent_slugs)}
|
||||||
|
|
||||||
return AgentAnalytics(
|
return AgentAnalytics(
|
||||||
total_agents=total_agents,
|
total_agents=total_agents,
|
||||||
agent_conversation_counts=agent_conversation_counts,
|
agent_conversation_counts=agent_conversation_counts,
|
||||||
agent_satisfaction_rates=agent_satisfaction,
|
agent_satisfaction_rates=agent_satisfaction,
|
||||||
agent_tool_usage=agent_tool_usage,
|
agent_tool_usage=agent_tool_usage,
|
||||||
top_performing_agents=top_performing_agents,
|
top_performing_agents=top_performing_agents,
|
||||||
|
agent_names=agent_names,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -718,6 +727,7 @@ class TimeSeriesStats(BaseModel):
|
|||||||
average_count: float
|
average_count: float
|
||||||
peak_count: int
|
peak_count: int
|
||||||
peak_date: str
|
peak_date: str
|
||||||
|
agent_names: dict[str, str] | None = None # agent_id -> agent_name 映射(仅 type=agents)
|
||||||
|
|
||||||
|
|
||||||
@dashboard.get("/stats/calls/timeseries", response_model=TimeSeriesStats)
|
@dashboard.get("/stats/calls/timeseries", response_model=TimeSeriesStats)
|
||||||
@ -887,6 +897,13 @@ async def get_call_timeseries_stats(
|
|||||||
|
|
||||||
categories = sorted(list(categories))
|
categories = sorted(list(categories))
|
||||||
|
|
||||||
|
agent_names = None
|
||||||
|
if type == "agents" and categories:
|
||||||
|
agent_slugs = [c for c in categories if c]
|
||||||
|
if agent_slugs:
|
||||||
|
agent_repo = AgentRepository(db)
|
||||||
|
agent_names = {agent.slug: agent.name for agent in await agent_repo.list_by_slugs(agent_slugs)}
|
||||||
|
|
||||||
# 重新组织数据:按时间点分组每个类别的数据
|
# 重新组织数据:按时间点分组每个类别的数据
|
||||||
time_data = {}
|
time_data = {}
|
||||||
|
|
||||||
@ -961,6 +978,7 @@ async def get_call_timeseries_stats(
|
|||||||
average_count=average_count,
|
average_count=average_count,
|
||||||
peak_count=peak_data["total"],
|
peak_count=peak_data["total"],
|
||||||
peak_date=peak_data["date"],
|
peak_date=peak_data["date"],
|
||||||
|
agent_names=agent_names,
|
||||||
)
|
)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
|
|||||||
@ -63,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'agent_id'">
|
<template v-if="column.key === 'agent_id'">
|
||||||
<a-tag color="blue">{{ record.agent_id }}</a-tag>
|
<a-tag color="blue">{{ resolveAgentName(record.agent_id) }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'satisfaction_rate'">
|
<template v-if="column.key === 'satisfaction_rate'">
|
||||||
<a-statistic
|
<a-statistic
|
||||||
@ -128,7 +128,7 @@ const performerColumns = [
|
|||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '智能体ID',
|
title: '智能体',
|
||||||
key: 'agent_id',
|
key: 'agent_id',
|
||||||
width: '30%'
|
width: '30%'
|
||||||
},
|
},
|
||||||
@ -161,6 +161,10 @@ const topPerformers = computed(() => {
|
|||||||
return props.agentStats?.top_performing_agents || []
|
return props.agentStats?.top_performing_agents || []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const agentNames = computed(() => props.agentStats?.agent_names || {})
|
||||||
|
|
||||||
|
const resolveAgentName = (agentId) => agentNames.value[agentId] || agentId
|
||||||
|
|
||||||
// 初始化对话数和工具调用数合并图表
|
// 初始化对话数和工具调用数合并图表
|
||||||
const initConversationToolChart = () => {
|
const initConversationToolChart = () => {
|
||||||
if (
|
if (
|
||||||
@ -235,7 +239,7 @@ const initConversationToolChart = () => {
|
|||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: topAgentIds,
|
data: topAgentIds.map(resolveAgentName),
|
||||||
axisLine: {
|
axisLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: getCSSVariable('--gray-200')
|
color: getCSSVariable('--gray-200')
|
||||||
|
|||||||
@ -169,8 +169,15 @@ const renderCallStatsChart = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const agentNames = callStatsData.value.agent_names || {}
|
||||||
|
|
||||||
|
const resolveCategoryLabel = (cat) => {
|
||||||
|
if (cat === 'None') return '未知模型'
|
||||||
|
return agentNames[cat] || cat
|
||||||
|
}
|
||||||
|
|
||||||
const series = categories.map((category, index) => ({
|
const series = categories.map((category, index) => ({
|
||||||
name: category === 'None' ? '未知模型' : category,
|
name: resolveCategoryLabel(category),
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
stack: 'total',
|
stack: 'total',
|
||||||
emphasis: { focus: 'series' },
|
emphasis: { focus: 'series' },
|
||||||
@ -234,7 +241,7 @@ const renderCallStatsChart = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: categories.map((cat) => (cat === 'None' ? '未知模型' : cat)),
|
data: categories.map(resolveCategoryLabel),
|
||||||
bottom: 5 /* 调整图例位置,从0改为5 */,
|
bottom: 5 /* 调整图例位置,从0改为5 */,
|
||||||
textStyle: { color: getCSSVariable('--gray-500'), fontSize: 12 },
|
textStyle: { color: getCSSVariable('--gray-500'), fontSize: 12 },
|
||||||
itemWidth: 14,
|
itemWidth: 14,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user