feat: 更新时间范围为14天和14小时,优化满意度计算逻辑
This commit is contained in:
parent
0835ab708e
commit
71cf6830e4
@ -8,7 +8,6 @@
|
||||
## Bugs
|
||||
|
||||
- [ ] 前端工具调用渲染出现问题
|
||||
- [ ] 智能体的加载状态有问题:(1)智能体加载没有动画;(2)切换对话和加载中,使用同一个loading状态。
|
||||
|
||||
## Next
|
||||
|
||||
@ -43,4 +42,5 @@
|
||||
- [x] v1 版本的 LangGraph 的工具渲染有问题
|
||||
- [x] upload 接口会阻塞主进程
|
||||
- [x] LightRAG 知识库查看不了解析后的文本,偶然出现,未复现
|
||||
- [x] LightRAG 知识库应该可以支持修改 LLM
|
||||
- [x] LightRAG 知识库应该可以支持修改 LLM
|
||||
- [x] 智能体的加载状态有问题:(1)智能体加载没有动画;(2)切换对话和加载中,使用同一个loading状态。
|
||||
@ -540,7 +540,7 @@ async def get_agent_analytics(
|
||||
or 0
|
||||
)
|
||||
|
||||
satisfaction_rate = round((positive_feedbacks / total_feedbacks * 100), 2) if total_feedbacks > 0 else 0
|
||||
satisfaction_rate = round((positive_feedbacks / total_feedbacks * 100), 2) if total_feedbacks > 0 else 100
|
||||
|
||||
agent_satisfaction.append(
|
||||
{"agent_id": agent_id, "satisfaction_rate": satisfaction_rate, "total_feedbacks": total_feedbacks}
|
||||
@ -624,7 +624,7 @@ async def get_dashboard_stats(
|
||||
like_count = db.query(func.count(MessageFeedback.id)).filter(MessageFeedback.rating == "like").scalar() or 0
|
||||
|
||||
# Calculate satisfaction rate
|
||||
satisfaction_rate = round((like_count / total_feedbacks * 100), 2) if total_feedbacks > 0 else 0
|
||||
satisfaction_rate = round((like_count / total_feedbacks * 100), 2) if total_feedbacks > 0 else 100
|
||||
|
||||
return {
|
||||
"total_conversations": total_conversations,
|
||||
@ -738,7 +738,7 @@ class TimeSeriesStats(BaseModel):
|
||||
@dashboard.get("/stats/calls/timeseries", response_model=TimeSeriesStats)
|
||||
async def get_call_timeseries_stats(
|
||||
type: str = "models", # models/agents/tokens/tools
|
||||
time_range: str = "7days", # 7hours/7days/7weeks
|
||||
time_range: str = "14days", # 14hours/14days/14weeks
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
):
|
||||
@ -750,24 +750,24 @@ async def get_call_timeseries_stats(
|
||||
now = utc_now()
|
||||
local_now = shanghai_now()
|
||||
|
||||
if time_range == "7hours":
|
||||
intervals = 7
|
||||
# 包含当前小时:从6小时前开始
|
||||
if time_range == "14hours":
|
||||
intervals = 14
|
||||
# 包含当前小时:从13小时前开始
|
||||
start_time = now - timedelta(hours=intervals - 1)
|
||||
group_format = func.strftime("%Y-%m-%d %H:00", func.datetime(Message.created_at, "+8 hours"))
|
||||
base_local_time = ensure_shanghai(start_time)
|
||||
elif time_range == "7weeks":
|
||||
intervals = 7
|
||||
# 包含当前周:从6周前开始,并对齐到当周周一 00:00
|
||||
elif time_range == "14weeks":
|
||||
intervals = 14
|
||||
# 包含当前周:从13周前开始,并对齐到当周周一 00:00
|
||||
local_start = local_now - timedelta(weeks=intervals - 1)
|
||||
local_start = local_start - timedelta(days=local_start.weekday())
|
||||
local_start = local_start.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_time = local_start.astimezone(UTC)
|
||||
group_format = func.strftime("%Y-%W", func.datetime(Message.created_at, "+8 hours"))
|
||||
base_local_time = local_start
|
||||
else: # 7days (default)
|
||||
intervals = 7
|
||||
# 包含当前天:从6天前开始
|
||||
else: # 14days (default)
|
||||
intervals = 14
|
||||
# 包含当前天:从13天前开始
|
||||
start_time = now - timedelta(days=intervals - 1)
|
||||
group_format = func.strftime("%Y-%m-%d", func.datetime(Message.created_at, "+8 hours"))
|
||||
base_local_time = ensure_shanghai(start_time)
|
||||
@ -790,11 +790,11 @@ async def get_call_timeseries_stats(
|
||||
elif type == "agents":
|
||||
# 智能体调用统计(基于对话更新时间,按智能体分组)
|
||||
# 为对话创建独立的时间格式化器
|
||||
if time_range == "7hours":
|
||||
if time_range == "14hours":
|
||||
conv_group_format = func.strftime("%Y-%m-%d %H:00", func.datetime(Conversation.updated_at, "+8 hours"))
|
||||
elif time_range == "7weeks":
|
||||
elif time_range == "14weeks":
|
||||
conv_group_format = func.strftime("%Y-%W", func.datetime(Conversation.updated_at, "+8 hours"))
|
||||
else: # 7days
|
||||
else: # 14days
|
||||
conv_group_format = func.strftime("%Y-%m-%d", func.datetime(Conversation.updated_at, "+8 hours"))
|
||||
|
||||
query = (
|
||||
@ -855,11 +855,11 @@ async def get_call_timeseries_stats(
|
||||
elif type == "tools":
|
||||
# 工具调用统计(按工具名称分组)
|
||||
# 为工具调用创建独立的时间格式化器
|
||||
if time_range == "7hours":
|
||||
if time_range == "14hours":
|
||||
tool_group_format = func.strftime("%Y-%m-%d %H:00", func.datetime(ToolCall.created_at, "+8 hours"))
|
||||
elif time_range == "7weeks":
|
||||
elif time_range == "14weeks":
|
||||
tool_group_format = func.strftime("%Y-%W", func.datetime(ToolCall.created_at, "+8 hours"))
|
||||
else: # 7days
|
||||
else: # 14days
|
||||
tool_group_format = func.strftime("%Y-%m-%d", func.datetime(ToolCall.created_at, "+8 hours"))
|
||||
|
||||
query = (
|
||||
@ -908,7 +908,7 @@ async def get_call_timeseries_stats(
|
||||
|
||||
for result in results:
|
||||
date_key = result.date
|
||||
if time_range == "7weeks":
|
||||
if time_range == "14weeks":
|
||||
date_key = normalize_week_key(date_key)
|
||||
category = getattr(result, "category", "unknown")
|
||||
count = result.count
|
||||
@ -923,17 +923,17 @@ async def get_call_timeseries_stats(
|
||||
# 从起始点开始(北京时间)
|
||||
current_time = base_local_time
|
||||
|
||||
if time_range == "7hours":
|
||||
if time_range == "14hours":
|
||||
delta = timedelta(hours=1)
|
||||
elif time_range == "7weeks":
|
||||
elif time_range == "14weeks":
|
||||
delta = timedelta(weeks=1)
|
||||
else:
|
||||
delta = timedelta(days=1)
|
||||
|
||||
for i in range(intervals):
|
||||
if time_range == "7hours":
|
||||
if time_range == "14hours":
|
||||
date_key = current_time.strftime("%Y-%m-%d %H:00")
|
||||
elif time_range == "7weeks":
|
||||
elif time_range == "14weeks":
|
||||
iso_year, iso_week, _ = current_time.isocalendar()
|
||||
date_key = f"{iso_year}-{iso_week:02d}"
|
||||
else:
|
||||
|
||||
@ -123,10 +123,10 @@ export const dashboardApi = {
|
||||
/**
|
||||
* 获取调用统计时间序列数据
|
||||
* @param {string} type - 数据类型 (models/agents/tokens/tools)
|
||||
* @param {string} timeRange - 时间范围 (7hours/7days/7weeks)
|
||||
* @param {string} timeRange - 时间范围 (14hours/14days/14weeks)
|
||||
* @returns {Promise<Object>} - 时间序列统计数据
|
||||
*/
|
||||
getCallTimeseries: (type = 'models', timeRange = '7days') => {
|
||||
getCallTimeseries: (type = 'models', timeRange = '14days') => {
|
||||
return apiAdminGet(`/api/dashboard/stats/calls/timeseries?type=${type}&time_range=${timeRange}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,9 +45,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoadingThreads || isLoadingMessages" class="chat-loading">
|
||||
<LoadingOutlined />
|
||||
<span>正在加载历史记录...</span>
|
||||
<!-- 加载状态:新建对话或加载消息 -->
|
||||
<div v-if="chatState.creatingNewChat" class="chat-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>正在创建新对话...</span>
|
||||
</div>
|
||||
<div v-else-if="isLoadingMessages" class="chat-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>正在加载消息...</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!conversations.length" class="chat-examples">
|
||||
@ -304,7 +309,7 @@ const isStreaming = computed(() => {
|
||||
const threadState = currentThreadState.value;
|
||||
return threadState ? threadState.isStreaming : false;
|
||||
});
|
||||
const isProcessing = computed(() => isStreaming.value || chatState.creatingNewChat);
|
||||
const isProcessing = computed(() => isStreaming.value);
|
||||
const isSmallContainer = computed(() => uiState.containerWidth <= 520);
|
||||
const isMediumContainer = computed(() => uiState.containerWidth <= 768);
|
||||
|
||||
@ -1218,10 +1223,23 @@ watch(conversations, () => {
|
||||
width: 100%;
|
||||
z-index: 9;
|
||||
animation: slideInUp 0.5s ease-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
span {
|
||||
margin-left: 8px;
|
||||
color: var(--gray-700);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--gray-200);
|
||||
border-top-color: var(--main-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -51,12 +51,12 @@ const props = defineProps({
|
||||
// state
|
||||
const callStatsData = ref(null)
|
||||
const callStatsLoading = ref(false)
|
||||
const callTimeRange = ref('7days')
|
||||
const callTimeRange = ref('14days')
|
||||
const callDataType = ref('models')
|
||||
const timeRangeOptions = [
|
||||
{ value: '7hours', label: '近7小时' },
|
||||
{ value: '7days', label: '近7天' },
|
||||
{ value: '7weeks', label: '近7周' },
|
||||
{ value: '14hours', label: '近14小时' },
|
||||
{ value: '14days', label: '近14天' },
|
||||
{ value: '14weeks', label: '近14周' },
|
||||
]
|
||||
const dataTypeOptions = [
|
||||
{ value: 'models', label: '模型调用' },
|
||||
@ -64,6 +64,27 @@ const dataTypeOptions = [
|
||||
{ value: 'tokens', label: 'Token消耗' },
|
||||
{ value: 'tools', label: '工具调用' },
|
||||
]
|
||||
const isTokenView = computed(() => callDataType.value === 'tokens')
|
||||
|
||||
const formatTokenValue = (value) => {
|
||||
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||
return '0M'
|
||||
}
|
||||
const millionValue = value / 1_000_000
|
||||
const absMillion = Math.abs(millionValue)
|
||||
const decimalPlaces = absMillion >= 100 ? 0 : absMillion >= 10 ? 1 : 2
|
||||
return `${millionValue.toFixed(decimalPlaces)}M`
|
||||
}
|
||||
|
||||
const formatValueForDisplay = (value) => {
|
||||
if (isTokenView.value) {
|
||||
return formatTokenValue(value)
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return value.toLocaleString()
|
||||
}
|
||||
return (value ?? 0).toString()
|
||||
}
|
||||
|
||||
const switchTimeRange = (val) => {
|
||||
if (callTimeRange.value === val) return
|
||||
@ -131,9 +152,9 @@ const renderCallStatsChart = () => {
|
||||
|
||||
const xAxisData = data.map(item => {
|
||||
const date = item.date
|
||||
if (callTimeRange.value === '7hours') {
|
||||
if (callTimeRange.value === '14hours') {
|
||||
return date.split(' ')[1]
|
||||
} else if (callTimeRange.value === '7weeks') {
|
||||
} else if (callTimeRange.value === '14weeks') {
|
||||
return `第${date.split('-')[1]}周`
|
||||
} else {
|
||||
return date.split('-').slice(1).join('-')
|
||||
@ -171,7 +192,11 @@ const renderCallStatsChart = () => {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: '#6b7280', fontSize: 12 },
|
||||
axisLabel: {
|
||||
color: '#6b7280',
|
||||
fontSize: 12,
|
||||
formatter: (value) => (isTokenView.value ? formatTokenValue(value) : value),
|
||||
},
|
||||
splitLine: { lineStyle: { color: '#f3f4f6' } }
|
||||
},
|
||||
tooltip: {
|
||||
@ -181,16 +206,18 @@ const renderCallStatsChart = () => {
|
||||
borderWidth: 1,
|
||||
textStyle: { color: '#374151', fontSize: 12 },
|
||||
formatter: (params) => {
|
||||
if (!params?.length) return ''
|
||||
let total = 0
|
||||
let result = `${params[0].name}<br/>`
|
||||
params.forEach(param => {
|
||||
total += param.value
|
||||
const truncatedName = truncateLegend(param.seriesName)
|
||||
result += `<span style=\"display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${param.color}\"></span>`
|
||||
result += `${truncatedName}: ${param.value}<br/>`
|
||||
result += `${truncatedName}: ${formatValueForDisplay(param.value)}<br/>`
|
||||
})
|
||||
const labelMap = { models: '模型调用', agents: '智能体调用', tokens: 'Token消耗', tools: '工具调用' }
|
||||
return `<div style=\"font-weight:bold;margin-bottom:5px\">${labelMap[callDataType.value]}</div>${result}<strong>总计: ${total}</strong>`
|
||||
const formattedTotal = formatValueForDisplay(total)
|
||||
return `<div style=\"font-weight:bold;margin-bottom:5px\">${labelMap[callDataType.value]}</div>${result}<strong>总计: ${formattedTotal}</strong>`
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user