feat: add chat log step display

#324
#742
This commit is contained in:
ulleo
2026-01-29 16:53:47 +08:00
committed by ulleo
parent ecf9e0b18d
commit 8a1a9e01d3
6 changed files with 96 additions and 19 deletions
@@ -0,0 +1,36 @@
"""063_update_chat_log_dll
Revision ID: c8751179a8de
Revises: c9ab05247503
Create Date: 2026-01-29 14:41:21.022781
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'c8751179a8de'
down_revision = 'c9ab05247503'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('chat_log', sa.Column('error', sa.Boolean(), nullable=True))
sql = '''
UPDATE chat_log SET error = false
'''
op.execute(sql)
op.alter_column('chat_log', 'error',
existing_type=sa.BOOLEAN(),
nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('chat_log', 'error')
# ### end Alembic commands ###
+17 -3
View File
@@ -515,7 +515,8 @@ def format_record(record: ChatRecordResult):
return _dict
def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user: CurrentUser, without_steps: bool = False) -> ChatLogHistory:
def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user: CurrentUser,
without_steps: bool = False) -> ChatLogHistory:
"""
获取ChatRecord的详细历史记录
@@ -573,6 +574,7 @@ def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user:
# 获取操作类型的枚举名称
operate_name = None
message = None
if log.operate:
# 如果是OperationEnum枚举实例
if isinstance(log.operate, OperationEnum):
@@ -590,6 +592,14 @@ def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user:
else:
operate_name = str(log.operate)
if log.messages is not None:
message = log.messages
if not log.operate == OperationEnum.CHOOSE_TABLE:
try:
message = orjson.loads(log.messages)
except Exception:
pass
# 创建ChatLogHistoryItem
history_item = ChatLogHistoryItem(
start_time=log.start_time,
@@ -597,7 +607,9 @@ def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user:
duration=duration,
total_tokens=log_tokens,
operate=operate_name,
local_operation=log.local_operation
local_operation=log.local_operation,
error=log.error,
message=message,
)
steps.append(history_item)
@@ -622,6 +634,7 @@ def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user:
return chat_log_history
def get_chat_brief_generate(session: SessionDep, chat_id: int):
chat = get_chat(session=session, chat_id=chat_id)
if chat is not None and chat.brief_generate is not None:
@@ -1055,7 +1068,8 @@ def save_error_message(session: SessionDep, record_id: int, message: str) -> Cha
# log error finish
stmt = update(ChatLog).where(and_(ChatLog.pid == record.id, ChatLog.finish_time.is_(None))).values(
finish_time=record.finish_time
finish_time=record.finish_time,
error=True
)
session.execute(stmt)
session.commit()
+5
View File
@@ -47,6 +47,7 @@ class OperationEnum(Enum):
EXECUTE_SQL = '12'
GENERATE_PICTURE = '13'
class ChatFinishStep(Enum):
GENERATE_SQL = 1
QUERY_DATA = 2
@@ -77,6 +78,7 @@ class ChatLog(SQLModel, table=True):
finish_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True))
token_usage: Optional[dict | None | int] = Field(sa_column=Column(JSONB))
local_operation: bool = Field(default=False)
error: bool = Field(default=False)
class Chat(SQLModel, table=True):
@@ -195,6 +197,9 @@ class ChatLogHistoryItem(BaseModel):
total_tokens: Optional[int] = None # token总消耗
operate: Optional[str] = None
local_operation: Optional[bool] = False
message: Optional[str | dict | list[dict]] = None
error: Optional[bool] = False
class ChatLogHistory(BaseModel):
start_time: Optional[datetime] = None
+14 -8
View File
@@ -220,7 +220,6 @@ class LLMService:
self.sql_message.append(SystemMessage(
content=self.chat_question.sql_sys_question(self.ds.type, self.enable_sql_row_limit)))
if last_sql_messages is not None and len(last_sql_messages) > 0:
# 获取最后3轮对话
last_rounds = get_last_conversation_rounds(last_sql_messages, rounds=count_limit)
for _msg_dict in last_rounds:
@@ -234,20 +233,27 @@ class LLMService:
last_chart_messages: List[dict[str, Any]] = self.generate_chart_logs[-1].messages if len(
self.generate_chart_logs) > 0 else []
if self.chat_question.regenerate_record_id:
# filter record before regenerate_record_id
_temp_log = next(
filter(lambda obj: obj.pid == self.chat_question.regenerate_record_id, self.generate_chart_logs), None)
last_chart_messages: List[dict[str, Any]] = _temp_log.messages if _temp_log else []
count_chart_limit = self.base_message_round_count_limit
self.chart_message = []
# add sys prompt
self.chart_message.append(SystemMessage(content=self.chat_question.chart_sys_question()))
if last_chart_messages is not None and len(last_chart_messages) > 0:
# limit count
for last_chart_message in last_chart_messages:
last_rounds = get_last_conversation_rounds(last_chart_messages, rounds=count_chart_limit)
for _msg_dict in last_rounds:
_msg: BaseMessage
if last_chart_message.get('type') == 'human':
_msg = HumanMessage(content=last_chart_message.get('content'))
if _msg_dict.get('type') == 'human':
_msg = HumanMessage(content=_msg_dict.get('content'))
self.chart_message.append(_msg)
elif last_chart_message.get('type') == 'ai':
_msg = AIMessage(content=last_chart_message.get('content'))
elif _msg_dict.get('type') == 'ai':
_msg = AIMessage(content=_msg_dict.get('content'))
self.chart_message.append(_msg)
def init_record(self, session: Session) -> ChatRecord:
+8 -3
View File
@@ -301,6 +301,7 @@ export class ChatLogHistoryItem {
total_tokens?: number | undefined
operate?: string | undefined
local_operation?: boolean | undefined
error?: boolean | undefined
constructor()
constructor(
@@ -309,7 +310,8 @@ export class ChatLogHistoryItem {
duration: number | undefined,
total_tokens: number | undefined,
operate: string | undefined,
local_operation: boolean | undefined
local_operation: boolean | undefined,
error: boolean | undefined
)
constructor(
start_time?: Date | string,
@@ -317,7 +319,8 @@ export class ChatLogHistoryItem {
duration?: number | undefined,
total_tokens?: number | undefined,
operate?: string | undefined,
local_operation?: boolean | undefined
local_operation?: boolean | undefined,
error?: boolean | undefined
) {
this.start_time = getDate(start_time)
this.finish_time = getDate(finish_time)
@@ -325,6 +328,7 @@ export class ChatLogHistoryItem {
this.total_tokens = total_tokens
this.operate = t('chat.log.' + operate)
this.local_operation = !!local_operation
this.error = !!error
}
}
@@ -368,7 +372,8 @@ const toChatLogHistoryItem = (data?: any): any | undefined => {
data.duration,
data.total_tokens,
data.operate,
data.local_operation
data.local_operation,
data.error
)
}
+16 -5
View File
@@ -51,13 +51,22 @@ defineExpose({
<div class="title">{{ t('parameter.execution_details') }}</div>
<div class="list">
<div class="list-item" :key="ele.duration" v-for="ele in logHistory.steps">
<div class="name">{{ ele.operate }}</div>
<div v-for="ele in logHistory.steps" :key="ele.duration" class="list-item">
<div class="name">
{{ ele.operate }}
</div>
<div class="status">
<div
v-if="ele.total_tokens && ele.total_tokens > 0"
class="time"
style="margin-right: 12px"
>
{{ ele.total_tokens }} tokens
</div>
<div class="time">{{ ele.duration }}s</div>
<el-icon size="16">
<gou_icon v-if="true"></gou_icon>
<icon_error v-else></icon_error>
<icon_error v-if="ele.error"></icon_error>
<gou_icon v-else></gou_icon>
</el-icon>
</div>
</div>
@@ -94,7 +103,6 @@ defineExpose({
.name {
float: left;
color: #646a73;
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
line-height: 22px;
@@ -132,6 +140,9 @@ defineExpose({
font-weight: 500;
font-size: 14px;
line-height: 22px;
display: flex;
flex-direction: row;
gap: 12px;
}
.time {