feat: add chat log step display

#324
#742
This commit is contained in:
ulleo
2026-01-28 18:54:25 +08:00
committed by ulleo
parent 6bb748a8b7
commit f8240acfcf
13 changed files with 141 additions and 39 deletions
+7
View File
@@ -98,6 +98,13 @@ async def chat_record_log(session: SessionDep, current_user: CurrentUser, chat_r
return await asyncio.to_thread(inner)
@router.get("/record/{chat_record_id}/usage", summary=f"{PLACEHOLDER_PREFIX}get_record_usage")
async def chat_record_usage(session: SessionDep, current_user: CurrentUser, chat_record_id: int):
def inner():
return get_chat_log_history(session, chat_record_id, current_user, True)
return await asyncio.to_thread(inner)
""" @router.post("/rename", response_model=str, summary=f"{PLACEHOLDER_PREFIX}rename_chat")
@system_log(LogConfig(
+44 -21
View File
@@ -514,7 +514,7 @@ def format_record(record: ChatRecordResult):
return _dict
def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user: CurrentUser) -> ChatLogHistory:
def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user: CurrentUser, without_steps: bool = False) -> ChatLogHistory:
"""
获取ChatRecord的详细历史记录
@@ -522,6 +522,7 @@ def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user:
session: 数据库会话
chat_record_id: ChatRecord的ID
current_user: 当前用户
without_steps
Returns:
ChatLogHistory: 包含历史步骤和时间信息的对象
@@ -536,7 +537,8 @@ def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user:
# 2. 查询与该ChatRecord相关的所有ChatLog记录
chat_logs = session.query(ChatLog).filter(
ChatLog.pid == chat_record_id
ChatLog.pid == chat_record_id,
ChatLog.operate != OperationEnum.GENERATE_RECOMMENDED_QUESTIONS
).order_by(ChatLog.start_time).all()
# 3. 计算总的时间和token信息
@@ -544,15 +546,6 @@ def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user:
steps = []
for log in chat_logs:
# 计算单条记录的耗时
duration = None
if log.start_time and log.finish_time:
try:
time_diff = log.finish_time - log.start_time
duration = time_diff.total_seconds()
except Exception:
duration = None
# 计算单条记录的token消耗
log_tokens = 0
if log.token_usage is not None:
@@ -567,16 +560,46 @@ def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user:
# 累加到总token消耗
total_tokens += log_tokens
# 创建ChatLogHistoryItem
history_item = ChatLogHistoryItem(
start_time=log.start_time,
finish_time=log.finish_time,
duration=duration,
total_tokens=log_tokens,
operate=log.operate,
local_operation=log.local_operation
)
steps.append(history_item)
if not without_steps:
# 计算单条记录的耗时
duration = None
if log.start_time and log.finish_time:
try:
time_diff = log.finish_time - log.start_time
duration = round(time_diff.total_seconds(), 2)
except Exception:
duration = None
# 获取操作类型的枚举名称
operate_name = None
if log.operate:
# 如果是OperationEnum枚举实例
if isinstance(log.operate, OperationEnum):
operate_name = log.operate.name
# 如果是字符串,尝试从枚举值获取名称
elif isinstance(log.operate, str):
try:
# 通过枚举值找到对应的枚举实例
for enum_item in OperationEnum:
if enum_item.value == log.operate:
operate_name = enum_item.name
break
except Exception:
operate_name = log.operate
else:
operate_name = str(log.operate)
# 创建ChatLogHistoryItem
history_item = ChatLogHistoryItem(
start_time=log.start_time,
finish_time=log.finish_time,
duration=duration,
total_tokens=log_tokens,
operate=operate_name,
local_operation=log.local_operation
)
steps.append(history_item)
# 4. 计算总耗时(使用ChatRecord的时间)
total_duration = None
+1 -1
View File
@@ -193,7 +193,7 @@ class ChatLogHistoryItem(BaseModel):
finish_time: Optional[datetime] = None
duration: Optional[float] = None # 耗时字段(单位:秒)
total_tokens: Optional[int] = None # token总消耗
operate: Optional[OperationEnum] = None
operate: Optional[str] = None
local_operation: Optional[bool] = False
class ChatLogHistory(BaseModel):
+1
View File
@@ -142,6 +142,7 @@
"get_chart_data": "Get Chart Data",
"get_chart_predict_data": "Get Chart Prediction Data",
"get_record_log": "Get Chart Record Log",
"get_record_usage": "Get Chart Record Token Usage & Duration",
"rename_chat": "Rename Chat",
"delete_chat": "Delete Chat",
"start_chat": "Create Chat",
+1
View File
@@ -142,6 +142,7 @@
"get_chart_data": "获取图表数据",
"get_chart_predict_data": "获取图表预测数据",
"get_record_log": "获取对话日志",
"get_record_usage": "获取对话Token使用量及耗时",
"rename_chat": "重命名对话",
"delete_chat": "删除对话",
"start_chat": "创建对话",
+7 -1
View File
@@ -1,5 +1,8 @@
import { request } from '@/utils/request'
import { getDate } from '@/utils/utils.ts'
import { i18n } from '@/i18n'
const { t } = i18n.global
export const questionApi = {
pager: (pageNumber: number, pageSize: number) =>
@@ -320,7 +323,7 @@ export class ChatLogHistoryItem {
this.finish_time = getDate(finish_time)
this.duration = duration
this.total_tokens = total_tokens
this.operate = operate
this.operate = t('chat.log.' + operate)
this.local_operation = !!local_operation
}
}
@@ -441,6 +444,9 @@ export const chatApi = {
get_chart_log_history: (record_id?: number): Promise<any> => {
return request.get(`/chat/record/${record_id}/log`)
},
get_chart_usage: (record_id?: number): Promise<any> => {
return request.get(`/chat/record/${record_id}/usage`)
},
startChat: (data: any): Promise<ChatInfo> => {
return request.post('/chat/start', data)
},
+17 -2
View File
@@ -709,7 +709,22 @@
"exec-sql-err": "Execute SQL failed",
"no_data": "No Data",
"loading_data": "Loading ...",
"show_error_detail": "Show error info"
"show_error_detail": "Show error info",
"log": {
"GENERATE_SQL": "Generate SQL",
"GENERATE_CHART": "Generate Chart Structure",
"ANALYSIS": "Analyze",
"PREDICT_DATA": "Predict",
"GENERATE_SQL_WITH_PERMISSIONS": "Apply Row Permissions to SQL",
"CHOOSE_DATASOURCE": "Match Datasource",
"GENERATE_DYNAMIC_SQL": "Generate Dynamic SQL",
"CHOOSE_TABLE": "Match Data Table (Schema)",
"FILTER_TERMS": "Match Terms",
"FILTER_SQL_EXAMPLE": "Match SQL Examples",
"FILTER_CUSTOM_PROMPT": "Match Custom Prompts",
"EXECUTE_SQL": "Execute SQL",
"GENERATE_PICTURE": "Generate Picture"
}
},
"about": {
"title": "About",
@@ -874,4 +889,4 @@
"to_doc": "View API",
"trigger_limit": "Supports up to {0} API Keys"
}
}
}
+17 -2
View File
@@ -709,7 +709,22 @@
"exec-sql-err": "SQL 실행 실패",
"no_data": "데이터가 없습니다",
"loading_data": "로딩 중 ...",
"show_error_detail": "구체적인 정보 보기"
"show_error_detail": "구체적인 정보 보기",
"log": {
"GENERATE_SQL": "SQL 생성",
"GENERATE_CHART": "차트 구조 생성",
"ANALYSIS": "분석",
"PREDICT_DATA": "예측",
"GENERATE_SQL_WITH_PERMISSIONS": "SQL에 행 권한 적용",
"CHOOSE_DATASOURCE": "데이터 소스 매칭",
"GENERATE_DYNAMIC_SQL": "동적 SQL 생성",
"CHOOSE_TABLE": "데이터 테이블 매칭 (스키마)",
"FILTER_TERMS": "용어 매칭",
"FILTER_SQL_EXAMPLE": "SQL 예시 매칭",
"FILTER_CUSTOM_PROMPT": "사용자 정의 프롬프트 매칭",
"EXECUTE_SQL": "SQL 실행",
"GENERATE_PICTURE": "이미지 생성"
}
},
"about": {
"title": "정보",
@@ -874,4 +889,4 @@
"to_doc": "API 보기",
"trigger_limit": "최대 {0}개의 API 키 생성 지원"
}
}
}
+17 -2
View File
@@ -709,7 +709,22 @@
"exec-sql-err": "执行SQL失败",
"no_data": "暂无数据",
"loading_data": "加载中...",
"show_error_detail": "查看具体信息"
"show_error_detail": "查看具体信息",
"log": {
"GENERATE_SQL": "生成 SQL",
"GENERATE_CHART": "生成图表结构",
"ANALYSIS": "分析",
"PREDICT_DATA": "预测",
"GENERATE_SQL_WITH_PERMISSIONS": "拼接 SQL 行权限",
"CHOOSE_DATASOURCE": "匹配数据源",
"GENERATE_DYNAMIC_SQL": "生成动态 SQL",
"CHOOSE_TABLE": "匹配数据表 (Schema)",
"FILTER_TERMS": "匹配术语",
"FILTER_SQL_EXAMPLE": "匹配 SQL 示例",
"FILTER_CUSTOM_PROMPT": "匹配自定义提示词",
"EXECUTE_SQL": "执行 SQL",
"GENERATE_PICTURE": "生成图片"
}
},
"about": {
"title": "关于",
@@ -874,4 +889,4 @@
"to_doc": "查看 API",
"trigger_limit": "最多支持创建 {0} 个 API Key"
}
}
}
@@ -158,7 +158,7 @@ const sendMessage = async () => {
break
case 'error':
currentRecord.error = data.content
emits('error')
emits('error', currentRecord.id)
break
case 'analysis-result':
analysis_answer += data.content
@@ -185,7 +185,7 @@ const sendMessage = async () => {
break
case 'error':
currentRecord.error = data.content
emits('error')
emits('error', currentRecord.id)
break
case 'sql-result':
sql_answer += data.reasoning_content
@@ -162,7 +162,7 @@ const sendMessage = async () => {
break
case 'error':
currentRecord.error = data.content
emits('error')
emits('error', currentRecord.id)
break
case 'predict-result':
predict_answer += data.reasoning_content
@@ -171,7 +171,7 @@ const sendMessage = async () => {
_currentChat.value.records[index.value].predict_content = predict_content
break
case 'predict-failed':
emits('error')
emits('error', currentRecord.id)
break
case 'predict-success':
//currentChat.value.records[_index].predict_data = data.content
+25 -6
View File
@@ -774,6 +774,7 @@ async function onChartAnswerFinish(id: number) {
getRecommendQuestionsLoading.value = true
loading.value = false
isTyping.value = false
getRecordUsage(id)
getRecommendQuestions(id)
}
@@ -781,10 +782,10 @@ const loadingOver = () => {
getRecommendQuestionsLoading.value = false
}
function onChartAnswerError() {
function onChartAnswerError(id: number) {
loading.value = false
isTyping.value = false
console.debug('onChartAnswerError')
getRecordUsage(id)
}
function onChatStop() {
@@ -864,12 +865,13 @@ const analysisAnswerRef = ref()
async function onAnalysisAnswerFinish(id: number) {
loading.value = false
isTyping.value = false
console.debug(id)
getRecordUsage(id)
//await getRecommendQuestions(id)
}
function onAnalysisAnswerError() {
function onAnalysisAnswerError(id: number) {
loading.value = false
isTyping.value = false
getRecordUsage(id)
}
function askAgain(message: ChatMessage) {
@@ -931,17 +933,34 @@ async function clickAnalysis(id?: number) {
return
}
function getRecordUsage(recordId: any) {
chatApi.get_chart_usage(recordId).then((res) => {
const logHistory = chatApi.toChatLogHistory(res)
if (logHistory) {
currentChat.value.records.forEach((record) => {
if (record.id === recordId) {
record.duration = logHistory.duration
record.finish_time = logHistory.finish_time
record.total_tokens = logHistory.total_tokens
}
})
}
})
}
const predictAnswerRef = ref()
async function onPredictAnswerFinish(id: number) {
loading.value = false
isTyping.value = false
console.debug('onPredictAnswerFinish: ', id)
// console.debug('onPredictAnswerFinish: ', id)
getRecordUsage(id)
//await getRecommendQuestions(id)
}
function onPredictAnswerError() {
function onPredictAnswerError(id: number) {
loading.value = false
isTyping.value = false
getRecordUsage(id)
}
async function clickPredict(id?: number) {