This commit is contained in:
parent
f9b91d45c1
commit
7b89324981
|
|
@ -46,31 +46,7 @@ class Conversation(BaseModel):
|
|||
default=False, description="正在运行,True 表示正在运行,False 表示未正在运行"
|
||||
)
|
||||
dialogs: Dict[str, Dialog] = Field(default_factory=dict, description="对话列表")
|
||||
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
|
||||
|
||||
|
||||
class ConversationHistoryItem(BaseModel):
|
||||
"""
|
||||
会话历史项领域模型
|
||||
"""
|
||||
|
||||
id: str = Field(..., description="会话唯一标识")
|
||||
description: str = Field(..., description="会话描述")
|
||||
created_at: str = Field(..., description="会话创建时间")
|
||||
|
||||
|
||||
class DialogItem(BaseModel):
|
||||
"""
|
||||
对话项领域模型
|
||||
"""
|
||||
|
||||
id: str = Field(..., description="对话唯一标识")
|
||||
user_prompt: str = Field(..., description="用户提示词")
|
||||
thoughts: List[Thought] = Field(default_factory=list, description="思考列表")
|
||||
result_output: str = Field(default="", description="结果输出")
|
||||
is_thinking: bool = Field(
|
||||
default=False, description="正在思考,True 表示正在思考,False 表示未正在思考"
|
||||
)
|
||||
is_expanded: bool = Field(
|
||||
default=False, description="思考折叠面板展开状态,True 表示展开,False 表示折叠"
|
||||
created_at: str = Field(
|
||||
...,
|
||||
description="创建时间",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,37 +3,37 @@
|
|||
会话页面
|
||||
"""
|
||||
import reflex as rx
|
||||
from typing import Dict
|
||||
from typing import Tuple
|
||||
|
||||
from application.domain_models import (
|
||||
Conversation,
|
||||
DialogItem,
|
||||
Dialog,
|
||||
Thought,
|
||||
ConversationHistoryItem,
|
||||
)
|
||||
from application.states import ConversationState, AuthState
|
||||
|
||||
|
||||
def conversation_history_item_showing(
|
||||
item: ConversationHistoryItem,
|
||||
item: Tuple[str, Conversation],
|
||||
) -> rx.Component:
|
||||
"""
|
||||
会话历史项展示
|
||||
:param conversation: 会话历史项
|
||||
:param item: 会话历史项
|
||||
:return: Component
|
||||
"""
|
||||
conversation = item[1]
|
||||
|
||||
# 高亮:若为当前会话或显示更多则高亮
|
||||
highlight: bool = (item.id == ConversationState.shown_more_conversation_id) | (
|
||||
item.id == ConversationState.conversation_id
|
||||
)
|
||||
highlight: bool = (
|
||||
conversation.id == ConversationState.shown_more_conversation_id
|
||||
) | (conversation.id == ConversationState.conversation_id)
|
||||
|
||||
return rx.list.item(
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
# 对话描述
|
||||
rx.text(
|
||||
item.description,
|
||||
conversation.description,
|
||||
flex=1,
|
||||
height="22px",
|
||||
line_height="22px",
|
||||
|
|
@ -52,7 +52,7 @@ def conversation_history_item_showing(
|
|||
rx.spacer(),
|
||||
# 创建时间
|
||||
rx.text(
|
||||
item.created_at,
|
||||
conversation.created_at,
|
||||
line_height="20px",
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-8)",
|
||||
|
|
@ -64,7 +64,7 @@ def conversation_history_item_showing(
|
|||
margin_bottom="8px",
|
||||
),
|
||||
# 点击事件:切换会话
|
||||
on_click=lambda: ConversationState.switch_conversation(item.id),
|
||||
on_click=lambda: ConversationState.switch_conversation(conversation.id),
|
||||
width="100%",
|
||||
),
|
||||
# 更多按钮:点击更多按钮显示气泡卡片,可删除会话
|
||||
|
|
@ -97,7 +97,7 @@ def conversation_history_item_showing(
|
|||
"删除",
|
||||
# 点击事件:删除对话
|
||||
on_click=lambda: ConversationState.delete_conversation(
|
||||
item.id
|
||||
conversation.id
|
||||
),
|
||||
display="flex",
|
||||
align_items="center",
|
||||
|
|
@ -130,7 +130,7 @@ def conversation_history_item_showing(
|
|||
overflow="visible",
|
||||
),
|
||||
on_open_change=lambda is_shown: ConversationState.set_shown_more_conversation_id(
|
||||
item.id, is_shown
|
||||
conversation.id, is_shown
|
||||
),
|
||||
open_delay=0,
|
||||
),
|
||||
|
|
@ -195,7 +195,7 @@ def conversation_history_items_showing(
|
|||
),
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
ConversationState.conversation_history_items,
|
||||
ConversationState.conversations,
|
||||
conversation_history_item_showing,
|
||||
),
|
||||
flex="1",
|
||||
|
|
@ -331,19 +331,21 @@ def greeting_showing() -> rx.Component:
|
|||
)
|
||||
|
||||
|
||||
def thought_showing(item: Thought):
|
||||
def thought_showing(item: Tuple[int, Thought]):
|
||||
"""
|
||||
思考展示
|
||||
:param item: 思考实例
|
||||
:return: Component
|
||||
"""
|
||||
# 思考实例
|
||||
thought = item[1]
|
||||
return rx.vstack(
|
||||
rx.match(
|
||||
item.type,
|
||||
thought.type,
|
||||
(
|
||||
"thinking",
|
||||
rx.text(
|
||||
item.content,
|
||||
thought.content,
|
||||
line_height="22px",
|
||||
font_size="var(--prismui-font-size-2)",
|
||||
color="var(--prismui-color-3)",
|
||||
|
|
@ -356,16 +358,18 @@ def thought_showing(item: Thought):
|
|||
)
|
||||
|
||||
|
||||
def dialog_item_showing(item: DialogItem) -> rx.Component:
|
||||
def dialog_showing(item: Tuple[str, Dialog]) -> rx.Component:
|
||||
"""
|
||||
对话项展示
|
||||
:param item: 对话项实例
|
||||
对话展示
|
||||
:param item: 对话实例
|
||||
:return: Component
|
||||
"""
|
||||
# 对话实例
|
||||
dialog = item[1]
|
||||
# 推理状态
|
||||
is_thinking = item.is_thinking
|
||||
is_thinking = dialog.is_thinking
|
||||
# 思考折叠面板展开状态
|
||||
is_expanded = item.is_expanded
|
||||
is_expanded = dialog.is_expanded
|
||||
|
||||
return rx.vstack(
|
||||
rx.hstack(
|
||||
|
|
@ -373,7 +377,7 @@ def dialog_item_showing(item: DialogItem) -> rx.Component:
|
|||
rx.vstack(
|
||||
# 用户提示词
|
||||
rx.text(
|
||||
item.user_prompt,
|
||||
dialog.user_prompt,
|
||||
max_width="600px",
|
||||
padding="12px 16px",
|
||||
background_color="var(--prismui-background-color-3)",
|
||||
|
|
@ -439,13 +443,13 @@ def dialog_item_showing(item: DialogItem) -> rx.Component:
|
|||
),
|
||||
cursor="pointer",
|
||||
# 点击事件,展开/折叠思考折叠面板
|
||||
on_click=lambda: ConversationState.toggle_collapse(item.id),
|
||||
on_click=lambda: ConversationState.toggle_collapse(dialog.id),
|
||||
),
|
||||
rx.box(
|
||||
rx.box(
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
item.thoughts,
|
||||
dialog.thoughts,
|
||||
thought_showing,
|
||||
),
|
||||
width="100%",
|
||||
|
|
@ -467,7 +471,7 @@ def dialog_item_showing(item: DialogItem) -> rx.Component:
|
|||
),
|
||||
# 结果输出
|
||||
rx.markdown(
|
||||
item.result_output,
|
||||
dialog.result_output,
|
||||
component_map={
|
||||
"p": lambda text: rx.text(
|
||||
text,
|
||||
|
|
@ -545,14 +549,42 @@ def dialog_item_showing(item: DialogItem) -> rx.Component:
|
|||
"hr": lambda _: rx.divider(
|
||||
height="1px",
|
||||
margin="12px 0",
|
||||
background_color="var(--prismui-background-color-6)",
|
||||
background_color="transparent",
|
||||
),
|
||||
"table": lambda children: rx.el.table(
|
||||
children,
|
||||
style={
|
||||
"margin": "12px 0",
|
||||
"width": "100%",
|
||||
"border-collapse": "collapse",
|
||||
},
|
||||
),
|
||||
"thead": lambda children: rx.el.thead(children),
|
||||
"tbody": lambda children: rx.el.tbody(children),
|
||||
"tr": lambda children: rx.el.tr(children),
|
||||
"th": lambda text: rx.el.th(
|
||||
text,
|
||||
style={
|
||||
"padding": "10px 12px",
|
||||
"background": "var(--prismui-background-color-3)",
|
||||
"border": "1px solid var(--prismui-background-color-6)",
|
||||
"text-align": "left",
|
||||
},
|
||||
),
|
||||
"td": lambda text: rx.el.td(
|
||||
text,
|
||||
style={
|
||||
"padding": "10px 12px",
|
||||
"border": "1px solid var(--prismui-background-color-6)",
|
||||
"vertical-align": "top",
|
||||
},
|
||||
),
|
||||
},
|
||||
width="100%",
|
||||
),
|
||||
padding="0 16px",
|
||||
width="100%",
|
||||
key=item.id,
|
||||
key=dialog.id,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -563,21 +595,26 @@ def dialog_items_showing() -> rx.Component:
|
|||
"""
|
||||
|
||||
# 当前会话的对话列表
|
||||
dialog_items = ConversationState.dialog_items
|
||||
dialogs = ConversationState.dialogs
|
||||
# 若对话列表为空则显示欢迎,否则显示对话列表
|
||||
return rx.cond(
|
||||
dialog_items.length() == 0,
|
||||
dialogs.length() == 0,
|
||||
# 欢迎展示
|
||||
greeting_showing(),
|
||||
rx.vstack(
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
dialog_items,
|
||||
dialog_item_showing,
|
||||
dialogs,
|
||||
dialog_showing,
|
||||
),
|
||||
width="100%",
|
||||
padding="0 12px",
|
||||
padding_bottom="180px",
|
||||
style={
|
||||
"&::-webkit-scrollbar": {
|
||||
"display": "none",
|
||||
},
|
||||
},
|
||||
),
|
||||
flex="1",
|
||||
width="100%",
|
||||
|
|
@ -778,12 +815,14 @@ def user_prompt_sending() -> rx.Component:
|
|||
position="absolute",
|
||||
bottom="0",
|
||||
left="0",
|
||||
right="0",
|
||||
z_index="99",
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
padding="0 12px 12px",
|
||||
margin_x="auto",
|
||||
border="none",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,8 @@ import reflex as rx
|
|||
from application.states.database import DatabaseState
|
||||
from application.domain_models import (
|
||||
Conversation,
|
||||
DialogItem,
|
||||
Dialog,
|
||||
Thought,
|
||||
ConversationHistoryItem,
|
||||
)
|
||||
|
||||
instructions: str = """
|
||||
|
|
@ -117,34 +116,6 @@ class ConversationState(rx.State):
|
|||
"""
|
||||
self.is_conversation_history_shown = not self.is_conversation_history_shown
|
||||
|
||||
@rx.var
|
||||
def conversation_history_items(self) -> List[ConversationHistoryItem]:
|
||||
"""
|
||||
获取会话历史项列表,用于前端渲染会话历史
|
||||
:return: 会话历史项列表
|
||||
"""
|
||||
items: List[ConversationHistoryItem] = []
|
||||
for conversation in reversed(
|
||||
self.conversations.values()
|
||||
): # 按照会话唯一标识倒序排序
|
||||
# 格式化会话创建时间
|
||||
match (datetime.now().date() - conversation.created_at.date()).days:
|
||||
case 0:
|
||||
created_at = f"{conversation.created_at.strftime('%H:%M')}"
|
||||
case 1:
|
||||
created_at = f"昨天 {conversation.created_at.strftime('%H:%M')}"
|
||||
case _:
|
||||
created_at = conversation.created_at.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
items.append(
|
||||
ConversationHistoryItem(
|
||||
id=conversation.id,
|
||||
description=conversation.description,
|
||||
created_at=created_at,
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
@rx.event
|
||||
def set_shown_more_conversation_id(
|
||||
self, conversation_id: str, is_shown: bool
|
||||
|
|
@ -252,29 +223,16 @@ class ConversationState(rx.State):
|
|||
return conversation.is_running
|
||||
|
||||
@rx.var
|
||||
def dialog_items(self) -> List[DialogItem]:
|
||||
def dialogs(self) -> Dict[str, Dialog]:
|
||||
"""
|
||||
当前会话的对话项列表
|
||||
:return: 当前会话的对话项列表
|
||||
当前会话的对话字典
|
||||
:return: 当前会话的对话字典
|
||||
"""
|
||||
# 当前会话
|
||||
conversation = self.conversations.get(self.conversation_id)
|
||||
if not conversation:
|
||||
return []
|
||||
|
||||
items: List[DialogItem] = []
|
||||
for dialog in conversation.dialogs.values():
|
||||
items.append(
|
||||
DialogItem(
|
||||
id=dialog.id,
|
||||
user_prompt=dialog.user_prompt,
|
||||
thoughts=list(dialog.thoughts.values()),
|
||||
result_output=dialog.result_output,
|
||||
is_thinking=dialog.is_thinking,
|
||||
is_expanded=dialog.is_expanded,
|
||||
)
|
||||
)
|
||||
return items
|
||||
return {}
|
||||
return conversation.dialogs
|
||||
|
||||
@rx.event
|
||||
async def handle_user_prompt(self) -> AsyncGenerator[None]:
|
||||
|
|
@ -293,7 +251,6 @@ class ConversationState(rx.State):
|
|||
conversation = self.conversations[self.conversation_id]
|
||||
# 将运行状态设置为正在运行
|
||||
conversation.is_running = True
|
||||
yield
|
||||
|
||||
database_state = await self.get_state(DatabaseState)
|
||||
|
||||
|
|
@ -303,20 +260,20 @@ class ConversationState(rx.State):
|
|||
conversation_id=self.conversation_id, user_prompt=user_prompt
|
||||
)
|
||||
)
|
||||
# 强制更新会话并推送前端
|
||||
self.conversations[self.conversation_id] = conversation
|
||||
yield
|
||||
|
||||
# 将最后一个对话作为当前对话
|
||||
dialog = next(reversed(conversation.dialogs.values()))
|
||||
|
||||
# 获取消息历史
|
||||
message_history = await database_state.get_message_history(
|
||||
conversation_id=self.conversation_id
|
||||
)
|
||||
|
||||
# 初始化工具调用唯一标识和片段索引映射字典
|
||||
tool_call_ids: Dict[str, int] = {}
|
||||
async with agent.run_stream_events(
|
||||
conversation_id=self.conversation_id,
|
||||
user_prompt=user_prompt,
|
||||
message_history=message_history,
|
||||
message_history=await database_state.get_message_history(
|
||||
conversation_id=self.conversation_id
|
||||
),
|
||||
) as events:
|
||||
async for event in events:
|
||||
match event:
|
||||
|
|
@ -336,7 +293,6 @@ class ConversationState(rx.State):
|
|||
dialog.thoughts[index] = Thought(
|
||||
type="thinking", content=content
|
||||
)
|
||||
yield
|
||||
|
||||
# 工具检索分片开始事件
|
||||
case ToolSearchCallPart(tool_call_id=tool_call_id):
|
||||
|
|
@ -347,7 +303,6 @@ class ConversationState(rx.State):
|
|||
type="tool_search",
|
||||
content="正在生成检索关键词",
|
||||
)
|
||||
yield
|
||||
|
||||
# 能力加载分片开始事件
|
||||
case LoadCapabilityCallPart(tool_call_id=tool_call_id):
|
||||
|
|
@ -357,7 +312,6 @@ class ConversationState(rx.State):
|
|||
type="capability_load",
|
||||
content="正在生成加载参数",
|
||||
)
|
||||
yield
|
||||
|
||||
# 工具调用分片开始事件
|
||||
case ToolCallPart(tool_call_id=tool_call_id):
|
||||
|
|
@ -367,12 +321,10 @@ class ConversationState(rx.State):
|
|||
type="tool_call",
|
||||
content="正在生成调用参数",
|
||||
)
|
||||
yield
|
||||
|
||||
# 文本分片开始事件
|
||||
case TextPart(content=content):
|
||||
dialog.result_output = content
|
||||
yield
|
||||
|
||||
# ========== 增量事件 ==========
|
||||
case PartDeltaEvent(index=index, delta=delta):
|
||||
|
|
@ -382,14 +334,12 @@ class ConversationState(rx.State):
|
|||
content_delta=content_delta,
|
||||
):
|
||||
dialog.thoughts[index].content += content_delta or ""
|
||||
yield
|
||||
|
||||
# 文本分片增量事件
|
||||
case TextPartDelta(
|
||||
content_delta=content_delta,
|
||||
):
|
||||
dialog.result_output += content_delta
|
||||
yield
|
||||
|
||||
# ========== 结束事件 ==========
|
||||
case PartEndEvent(
|
||||
|
|
@ -399,12 +349,11 @@ class ConversationState(rx.State):
|
|||
):
|
||||
match part:
|
||||
# 思考分片结束事件
|
||||
case ThinkingPart(part_kind=part_kind, content=content):
|
||||
case ThinkingPart(content=content):
|
||||
# 若下一分片种类为文本则将思考状态设置为思考完成、思考面板展开状态设置为折叠
|
||||
if next_part_kind == "text":
|
||||
dialog.is_thinking = False
|
||||
dialog.is_expanded = False
|
||||
yield
|
||||
|
||||
# ========== 函数工具调用事件 ==========
|
||||
case FunctionToolCallEvent(tool_call_id=tool_call_id, part=part):
|
||||
|
|
@ -414,21 +363,18 @@ class ConversationState(rx.State):
|
|||
# 工具检索
|
||||
case "tool_search":
|
||||
dialog.thoughts[index].content = "正在检索"
|
||||
yield
|
||||
|
||||
# 能力加载
|
||||
case "capability_load":
|
||||
dialog.thoughts[index].content = (
|
||||
f"正在加载能力 {part.tool_name}"
|
||||
)
|
||||
yield
|
||||
|
||||
# 工具调用
|
||||
case "tool_call":
|
||||
dialog.thoughts[index].content = (
|
||||
f"正在调用工具 {part.tool_name}"
|
||||
)
|
||||
yield
|
||||
|
||||
# ========== 函数工具结果事件 ==========
|
||||
case FunctionToolResultEvent(
|
||||
|
|
@ -442,17 +388,14 @@ class ConversationState(rx.State):
|
|||
dialog.thoughts[index].content = (
|
||||
content if isinstance(content, str) else ""
|
||||
) # 暂仅考虑文本内容
|
||||
yield
|
||||
|
||||
# 能力加载
|
||||
case "capability_load":
|
||||
dialog.thoughts[index].content = "已加载"
|
||||
yield
|
||||
|
||||
# 工具调用
|
||||
case "tool_call":
|
||||
dialog.thoughts[index].content = f"已调用"
|
||||
yield
|
||||
|
||||
# ========== 智能体运行结果事件 ==========
|
||||
case AgentRunResultEvent(result=result):
|
||||
|
|
@ -470,7 +413,10 @@ class ConversationState(rx.State):
|
|||
)
|
||||
# 将当前会话的运行状态设置为运行完成
|
||||
conversation.is_running = False
|
||||
yield
|
||||
|
||||
# 强制更新会话并推送前端
|
||||
self.conversations[self.conversation_id] = conversation
|
||||
yield
|
||||
|
||||
@rx.event
|
||||
def toggle_collapse(self, dialog_id: str) -> None:
|
||||
|
|
|
|||
|
|
@ -122,6 +122,22 @@ class ResultRecord(SQLModel, table=True):
|
|||
new_messages: str = Field(description="新增消息")
|
||||
|
||||
|
||||
def format_at(at: datetime) -> str:
|
||||
"""
|
||||
格式化日期时间
|
||||
:param at: 日期时间
|
||||
:return: 格式化后的日期时间字符串
|
||||
"""
|
||||
match (datetime.now().date() - at.date()).days:
|
||||
case 0:
|
||||
formatted_at = f"{at.strftime('%H:%M')}"
|
||||
case 1:
|
||||
formatted_at = f"昨天 {at.strftime('%H:%M')}"
|
||||
case _:
|
||||
formatted_at = at.strftime("%Y-%m-%d %H:%M")
|
||||
return formatted_at
|
||||
|
||||
|
||||
# 思考领域模型类型适配器
|
||||
ThoughtTypeAdapter = TypeAdapter(Dict[int, Thought])
|
||||
|
||||
|
|
@ -221,12 +237,13 @@ class DatabaseState(rx.State):
|
|||
.order_by(ConversationRecord.id, DialogRecord.id)
|
||||
)
|
||||
for conversation_record, dialog_record in result.all():
|
||||
|
||||
record = records.setdefault(
|
||||
conversation_record.id,
|
||||
Conversation(
|
||||
id=conversation_record.id,
|
||||
description=conversation_record.description,
|
||||
created_at=conversation_record.created_at,
|
||||
created_at=format_at(conversation_record.created_at),
|
||||
),
|
||||
)
|
||||
if not dialog_record:
|
||||
|
|
@ -263,7 +280,7 @@ class DatabaseState(rx.State):
|
|||
record.id: Conversation(
|
||||
id=record.id,
|
||||
description=record.description,
|
||||
created_at=record.created_at,
|
||||
created_at=format_at(record.created_at),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue