diff --git a/docs/changelog/roadmap.md b/docs/changelog/roadmap.md index edb489b4..3cd79722 100644 --- a/docs/changelog/roadmap.md +++ b/docs/changelog/roadmap.md @@ -8,7 +8,6 @@ ## Bugs - [ ] 前端工具调用渲染出现问题 -- [ ] 智能体的加载状态有问题:(1)智能体加载没有动画;(2)切换对话和加载中,使用同一个loading状态。 ## Next @@ -43,4 +42,5 @@ - [x] v1 版本的 LangGraph 的工具渲染有问题 - [x] upload 接口会阻塞主进程 - [x] LightRAG 知识库查看不了解析后的文本,偶然出现,未复现 -- [x] LightRAG 知识库应该可以支持修改 LLM \ No newline at end of file +- [x] LightRAG 知识库应该可以支持修改 LLM +- [x] 智能体的加载状态有问题:(1)智能体加载没有动画;(2)切换对话和加载中,使用同一个loading状态。 \ No newline at end of file diff --git a/server/routers/dashboard_router.py b/server/routers/dashboard_router.py index 06411356..0dec596e 100644 --- a/server/routers/dashboard_router.py +++ b/server/routers/dashboard_router.py @@ -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: diff --git a/web/src/apis/dashboard_api.js b/web/src/apis/dashboard_api.js index 0ae66c7a..3c3e7169 100644 --- a/web/src/apis/dashboard_api.js +++ b/web/src/apis/dashboard_api.js @@ -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} - 时间序列统计数据 */ - getCallTimeseries: (type = 'models', timeRange = '7days') => { + getCallTimeseries: (type = 'models', timeRange = '14days') => { return apiAdminGet(`/api/dashboard/stats/calls/timeseries?type=${type}&time_range=${timeRange}`) } } diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 55b781fc..d9a04db2 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -45,9 +45,14 @@ -
- - 正在加载历史记录... + +
+
+ 正在创建新对话... +
+
+
+ 正在加载消息...
@@ -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; } } diff --git a/web/src/components/dashboard/CallStatsComponent.vue b/web/src/components/dashboard/CallStatsComponent.vue index a24df3b4..03b20f01 100644 --- a/web/src/components/dashboard/CallStatsComponent.vue +++ b/web/src/components/dashboard/CallStatsComponent.vue @@ -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}
` params.forEach(param => { total += param.value const truncatedName = truncateLegend(param.seriesName) result += `` - result += `${truncatedName}: ${param.value}
` + result += `${truncatedName}: ${formatValueForDisplay(param.value)}
` }) const labelMap = { models: '模型调用', agents: '智能体调用', tokens: 'Token消耗', tools: '工具调用' } - return `
${labelMap[callDataType.value]}
${result}总计: ${total}` + const formattedTotal = formatValueForDisplay(total) + return `
${labelMap[callDataType.value]}
${result}总计: ${formattedTotal}` } }, legend: {