This commit is contained in:
parent
942c1bea71
commit
a7fead671a
|
|
@ -6,9 +6,8 @@ import reflex as rx
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from application.states.models import (
|
from application.states.models import (
|
||||||
Conversation,
|
MessageHistoryItem,
|
||||||
Dialog,
|
MessageType,
|
||||||
Thought,
|
|
||||||
ConversationHistoryItem,
|
ConversationHistoryItem,
|
||||||
)
|
)
|
||||||
from application.states import ConversationState, AuthState
|
from application.states import ConversationState, AuthState
|
||||||
|
|
@ -32,7 +31,7 @@ def conversation_history_item(
|
||||||
return rx.list.item(
|
return rx.list.item(
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
rx.hstack(
|
rx.hstack(
|
||||||
# 对话描述
|
# 会话历史项的会话描述
|
||||||
rx.text(
|
rx.text(
|
||||||
item.description,
|
item.description,
|
||||||
flex=1,
|
flex=1,
|
||||||
|
|
@ -51,7 +50,7 @@ def conversation_history_item(
|
||||||
),
|
),
|
||||||
rx.hstack(
|
rx.hstack(
|
||||||
rx.spacer(),
|
rx.spacer(),
|
||||||
# 创建时间
|
# 会话历史项的会话创建日期时间
|
||||||
rx.text(
|
rx.text(
|
||||||
item.created_at,
|
item.created_at,
|
||||||
line_height="20px",
|
line_height="20px",
|
||||||
|
|
@ -68,7 +67,7 @@ def conversation_history_item(
|
||||||
on_click=lambda: ConversationState.set_actived_conversation(item.id),
|
on_click=lambda: ConversationState.set_actived_conversation(item.id),
|
||||||
width="100%",
|
width="100%",
|
||||||
),
|
),
|
||||||
# 更多按钮:点击更多按钮显示气泡卡片,可删除会话
|
# 会话历史项的更多按钮
|
||||||
rx.box(
|
rx.box(
|
||||||
rx.popover.root(
|
rx.popover.root(
|
||||||
rx.popover.trigger(
|
rx.popover.trigger(
|
||||||
|
|
@ -143,9 +142,9 @@ def conversation_history_item(
|
||||||
z_index="99",
|
z_index="99",
|
||||||
min_width="14px",
|
min_width="14px",
|
||||||
cursor="pointer",
|
cursor="pointer",
|
||||||
opacity=rx.cond(highlight, "1", "0"),
|
opacity=rx.cond(is_highlight, "1", "0"),
|
||||||
transition="opacity 0.18s ease-in-out",
|
transition="opacity 0.18s ease-in-out",
|
||||||
pointer_events=rx.cond(highlight, "auto", "none"),
|
pointer_events=rx.cond(is_highlight, "auto", "none"),
|
||||||
),
|
),
|
||||||
display="flex",
|
display="flex",
|
||||||
position="relative",
|
position="relative",
|
||||||
|
|
@ -154,12 +153,12 @@ def conversation_history_item(
|
||||||
padding="16px",
|
padding="16px",
|
||||||
margin_bottom="8px",
|
margin_bottom="8px",
|
||||||
background=rx.cond(
|
background=rx.cond(
|
||||||
highlight,
|
is_highlight,
|
||||||
"var(--prismui-background-3)",
|
"var(--prismui-background-3)",
|
||||||
"var(--prismui-background-color-1)",
|
"var(--prismui-background-color-1)",
|
||||||
),
|
),
|
||||||
border_radius="var(--prismui-border-radius-3)",
|
border_radius="var(--prismui-border-radius-3)",
|
||||||
box_shadow=rx.cond(highlight, "var(--prismui-box-shadow-7)", "none"),
|
box_shadow=rx.cond(is_highlight, "var(--prismui-box-shadow-7)", "none"),
|
||||||
cursor="pointer",
|
cursor="pointer",
|
||||||
style={
|
style={
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
|
|
@ -175,16 +174,16 @@ def conversation_history_item(
|
||||||
|
|
||||||
|
|
||||||
def conversation_history(
|
def conversation_history(
|
||||||
is_shown: bool,
|
is_conversation_history_shown: bool,
|
||||||
) -> rx.Component:
|
) -> rx.Component:
|
||||||
"""
|
"""
|
||||||
会话历史
|
会话历史
|
||||||
:param is_shown: 展示 / 隐藏
|
:param is_conversation_history_shown: 展示 / 隐藏
|
||||||
:return: Component
|
:return: Component
|
||||||
"""
|
"""
|
||||||
return rx.box(
|
return rx.box(
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
# 标题
|
# 会话历史的标题
|
||||||
rx.hstack(
|
rx.hstack(
|
||||||
rx.text(
|
rx.text(
|
||||||
"会话历史",
|
"会话历史",
|
||||||
|
|
@ -219,9 +218,9 @@ def conversation_history(
|
||||||
backdrop_filter="blur(50px)",
|
backdrop_filter="blur(50px)",
|
||||||
gap="12px",
|
gap="12px",
|
||||||
),
|
),
|
||||||
width=rx.cond(is_shown, "25%", "0px"),
|
width=rx.cond(is_conversation_history_shown, "25%", "0px"),
|
||||||
min_width=rx.cond(is_shown, "240px", "0px"),
|
min_width=rx.cond(is_conversation_history_shown, "240px", "0px"),
|
||||||
max_width=rx.cond(is_shown, "380px", "0px"),
|
max_width=rx.cond(is_conversation_history_shown, "380px", "0px"),
|
||||||
height="100%",
|
height="100%",
|
||||||
transition="all 0.18s ease-in-out",
|
transition="all 0.18s ease-in-out",
|
||||||
overflow="hidden",
|
overflow="hidden",
|
||||||
|
|
@ -334,282 +333,251 @@ def guidance() -> rx.Component:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def thought_showing(item: Tuple[int, Thought]):
|
def user_prompt(item: MessageHistoryItem) -> rx.Component:
|
||||||
"""
|
"""
|
||||||
思考展示
|
用户提示词
|
||||||
:param item: 思考实例
|
:return: Component
|
||||||
|
"""
|
||||||
|
return rx.hstack(
|
||||||
|
rx.spacer(),
|
||||||
|
rx.vstack(
|
||||||
|
rx.text(
|
||||||
|
item.content,
|
||||||
|
max_width="600px",
|
||||||
|
padding="12px 16px",
|
||||||
|
background_color="var(--prismui-background-color-3)",
|
||||||
|
border_radius="var(--prismui-border-radius-3)",
|
||||||
|
line_height="1.5",
|
||||||
|
word_wrap="break-word",
|
||||||
|
word_break="break-all",
|
||||||
|
white_space="pre-line",
|
||||||
|
),
|
||||||
|
rx.hstack(
|
||||||
|
# 再次发送
|
||||||
|
rx.box(
|
||||||
|
rx.icon(
|
||||||
|
"rotate-ccw",
|
||||||
|
width="14px",
|
||||||
|
height="14px",
|
||||||
|
color="var(--prismui-color-2)",
|
||||||
|
),
|
||||||
|
padding="4px",
|
||||||
|
border_radius="var(--prismui-border-radius-1)",
|
||||||
|
cursor="pointer",
|
||||||
|
style={
|
||||||
|
"_hover": {
|
||||||
|
"background_color": "var(--prismui-background-color-2)",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
width="100%",
|
||||||
|
margin_top="8px",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
align_items="flex-start",
|
||||||
|
gap="4px",
|
||||||
|
width="100%",
|
||||||
|
margin_top="8px",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def thinking(item: MessageHistoryItem) -> rx.Component:
|
||||||
|
"""
|
||||||
|
思考
|
||||||
:return: Component
|
:return: Component
|
||||||
"""
|
"""
|
||||||
# 思考实例
|
|
||||||
thought = item[1]
|
|
||||||
return rx.vstack(
|
return rx.vstack(
|
||||||
rx.match(
|
# 思考的标题栏
|
||||||
thought.type,
|
rx.box(
|
||||||
(
|
rx.hstack(
|
||||||
"thinking",
|
|
||||||
rx.text(
|
rx.text(
|
||||||
thought.content,
|
item.title,
|
||||||
line_height="1.6",
|
|
||||||
font_size="var(--prismui-font-size-1)",
|
font_size="var(--prismui-font-size-1)",
|
||||||
color="var(--prismui-color-3)",
|
color="var(--prismui-color-3)",
|
||||||
),
|
),
|
||||||
|
rx.icon(
|
||||||
|
"chevron-right",
|
||||||
|
width="14px",
|
||||||
|
height="14px",
|
||||||
|
color="var(--prismui-color-3)",
|
||||||
|
transform=rx.cond(item.is_shown, "rotate(90deg)", "rotate(0deg)"),
|
||||||
|
transition="transform 0.18s ease-in-out",
|
||||||
|
),
|
||||||
|
align_items="center",
|
||||||
|
gap="4px",
|
||||||
|
margin_bottom="8px",
|
||||||
|
line_height="22px",
|
||||||
),
|
),
|
||||||
rx.fragment(),
|
# 点击事件,展示 / 隐藏消息历史项
|
||||||
|
on_click=lambda: ConversationState.toggle_message_history_item_shown(
|
||||||
|
item.id
|
||||||
|
),
|
||||||
|
cursor="pointer",
|
||||||
),
|
),
|
||||||
align_items="flex-start",
|
# 思考的内容
|
||||||
|
rx.text(
|
||||||
|
item.content,
|
||||||
|
line_height="1.6",
|
||||||
|
font_size="var(--prismui-font-size-1)",
|
||||||
|
color="var(--prismui-color-3)",
|
||||||
|
),
|
||||||
|
margin_bottom="8px",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def result_output(item: MessageHistoryItem) -> rx.Component:
|
||||||
|
"""
|
||||||
|
结果输出
|
||||||
|
:return: Component
|
||||||
|
"""
|
||||||
|
return rx.markdown(
|
||||||
|
item.content,
|
||||||
|
component_map={
|
||||||
|
"p": lambda text: rx.text(
|
||||||
|
text,
|
||||||
|
margin="4px 0",
|
||||||
|
line_height="1.75",
|
||||||
|
word_wrap="break-word",
|
||||||
|
word_break="break-all",
|
||||||
|
white_space="pre-line",
|
||||||
|
),
|
||||||
|
"strong": lambda text: rx.text(
|
||||||
|
text,
|
||||||
|
display="inline",
|
||||||
|
font_weight="var(--prismui-font-weight-3)",
|
||||||
|
),
|
||||||
|
"code": lambda text: rx.text(
|
||||||
|
text,
|
||||||
|
display="inline",
|
||||||
|
background_color="var(--prismui-background-color-6)",
|
||||||
|
border_radius="var(--prismui-border-radius-1)",
|
||||||
|
color="var(--prismui-color-5)",
|
||||||
|
),
|
||||||
|
"h1": lambda text: rx.text(
|
||||||
|
text,
|
||||||
|
margin="8px 0 4px",
|
||||||
|
line_height="1.75",
|
||||||
|
font_size="var(--prismui-font-size-4)",
|
||||||
|
font_weight="var(--prismui-font-weight-3)",
|
||||||
|
word_wrap="break-word",
|
||||||
|
word_break="break-all",
|
||||||
|
white_space="pre-line",
|
||||||
|
),
|
||||||
|
"h2": lambda text: rx.text(
|
||||||
|
text,
|
||||||
|
margin="6px 0 4px",
|
||||||
|
line_height="1.75",
|
||||||
|
font_size="var(--prismui-font-size-3)",
|
||||||
|
font_weight="var(--prismui-font-weight-3)",
|
||||||
|
word_wrap="break-word",
|
||||||
|
word_break="break-all",
|
||||||
|
white_space="pre-line",
|
||||||
|
),
|
||||||
|
"h3": lambda text: rx.text(
|
||||||
|
text,
|
||||||
|
margin="4px 0",
|
||||||
|
line_height="1.75",
|
||||||
|
font_weight="var(--prismui-font-weight-3)",
|
||||||
|
word_wrap="break-word",
|
||||||
|
word_break="break-all",
|
||||||
|
white_space="pre-line",
|
||||||
|
),
|
||||||
|
"ul": lambda children: rx.vstack(
|
||||||
|
children,
|
||||||
|
align_items="flex-start",
|
||||||
|
margin="4px 0",
|
||||||
|
style={"padding": "0 !important"},
|
||||||
|
),
|
||||||
|
"ol": lambda children: rx.vstack(
|
||||||
|
children,
|
||||||
|
align_items="flex-start",
|
||||||
|
margin="4px 0",
|
||||||
|
style={"padding": "0 !important"},
|
||||||
|
),
|
||||||
|
"li": lambda text: rx.hstack(
|
||||||
|
rx.box(
|
||||||
|
width="4.5px",
|
||||||
|
height="4.5px",
|
||||||
|
margin_right="8px",
|
||||||
|
background_color="var(--prismui-background-color-7)",
|
||||||
|
border_radius="100%",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
text,
|
||||||
|
line_height="1.75",
|
||||||
|
word_wrap="break-word",
|
||||||
|
word_break="break-all",
|
||||||
|
white_space="pre-line",
|
||||||
|
),
|
||||||
|
align_items="center",
|
||||||
|
justify_content="flex-start",
|
||||||
|
margin_bottom="4px",
|
||||||
|
),
|
||||||
|
"hr": lambda _: rx.divider(
|
||||||
|
height="1px",
|
||||||
|
margin="12px 0",
|
||||||
|
background_color="var(--prismui-background-color-2)",
|
||||||
|
),
|
||||||
|
"table": lambda children: rx.el.table(
|
||||||
|
children,
|
||||||
|
width="100%",
|
||||||
|
margin="8px 0",
|
||||||
|
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 children: rx.el.th(
|
||||||
|
children,
|
||||||
|
padding="8px 12px",
|
||||||
|
background_color="var(--prismui-background-color-6)",
|
||||||
|
border="1px solid var(--prismui-background-color-2)",
|
||||||
|
text_align="left",
|
||||||
|
),
|
||||||
|
"td": lambda children: rx.el.td(
|
||||||
|
children,
|
||||||
|
padding="8px 12px",
|
||||||
|
border="1px solid var(--prismui-background-color-2)",
|
||||||
|
text_align="left",
|
||||||
|
),
|
||||||
|
},
|
||||||
width="100%",
|
width="100%",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def message_history_item(item: Tuple[str, Dialog]) -> rx.Component:
|
def message_history_item(item: MessageHistoryItem) -> rx.Component:
|
||||||
"""
|
"""
|
||||||
消息历史项
|
消息历史项
|
||||||
:param item: 对话实例
|
:param item: 对话实例
|
||||||
:return: Component
|
:return: Component
|
||||||
"""
|
"""
|
||||||
# 对话实例
|
|
||||||
dialog = item[1]
|
|
||||||
# 推理状态
|
|
||||||
is_thinking = dialog.is_thinking
|
|
||||||
# 思考折叠面板展开状态
|
|
||||||
is_expanded = dialog.is_expanded
|
|
||||||
|
|
||||||
return rx.vstack(
|
return rx.vstack(
|
||||||
# 用户提示词
|
# 若等待流式输出则显示加载动效,否则根据消息类型渲染组件
|
||||||
rx.cond(
|
rx.cond(
|
||||||
dialog.user_prompt,
|
ConversationState.awaiting_stream,
|
||||||
rx.hstack(
|
rx.box(
|
||||||
rx.spacer(),
|
|
||||||
rx.vstack(
|
|
||||||
rx.text(
|
|
||||||
dialog.user_prompt,
|
|
||||||
max_width="600px",
|
|
||||||
padding="12px 16px",
|
|
||||||
background_color="var(--prismui-background-color-3)",
|
|
||||||
border_radius="var(--prismui-border-radius-3)",
|
|
||||||
line_height="1.5",
|
|
||||||
word_wrap="break-word",
|
|
||||||
word_break="break-all",
|
|
||||||
white_space="pre-line",
|
|
||||||
),
|
|
||||||
rx.hstack(
|
|
||||||
# 再次发送
|
|
||||||
rx.box(
|
|
||||||
rx.icon(
|
|
||||||
"rotate-ccw",
|
|
||||||
width="14px",
|
|
||||||
height="14px",
|
|
||||||
color="var(--prismui-color-2)",
|
|
||||||
),
|
|
||||||
padding="4px",
|
|
||||||
border_radius="var(--prismui-border-radius-1)",
|
|
||||||
cursor="pointer",
|
|
||||||
style={
|
|
||||||
"_hover": {
|
|
||||||
"background_color": "var(--prismui-background-color-2)",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
margin_top="8px",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
align_items="flex-start",
|
|
||||||
gap="4px",
|
|
||||||
width="100%",
|
|
||||||
margin_top="8px",
|
|
||||||
),
|
|
||||||
rx.fragment(),
|
|
||||||
),
|
|
||||||
# 思考折叠面板
|
|
||||||
rx.cond(
|
|
||||||
dialog.thoughts,
|
|
||||||
rx.vstack(
|
|
||||||
# 标题
|
|
||||||
rx.box(
|
rx.box(
|
||||||
rx.hstack(
|
class_name="loadin",
|
||||||
rx.text(
|
flex_shrink="0",
|
||||||
rx.cond(is_thinking, "思考中", "思考完成"),
|
|
||||||
font_size="var(--prismui-font-size-1)",
|
|
||||||
color="var(--prismui-color-3)",
|
|
||||||
),
|
|
||||||
rx.icon(
|
|
||||||
"chevron-right",
|
|
||||||
width="14px",
|
|
||||||
height="14px",
|
|
||||||
color="var(--prismui-color-3)",
|
|
||||||
transform=rx.cond(
|
|
||||||
is_expanded, "rotate(90deg)", "rotate(0deg)"
|
|
||||||
),
|
|
||||||
transition="transform 0.18s ease-in-out",
|
|
||||||
),
|
|
||||||
align_items="center",
|
|
||||||
gap="4px",
|
|
||||||
margin_bottom="8px",
|
|
||||||
line_height="22px",
|
|
||||||
),
|
|
||||||
# 点击事件,展开/折叠思考折叠面板
|
|
||||||
on_click=lambda: ConversationState.toggle_collapse(dialog.id),
|
|
||||||
cursor="pointer",
|
|
||||||
),
|
|
||||||
# 思考
|
|
||||||
rx.box(
|
|
||||||
rx.box(
|
|
||||||
rx.auto_scroll(
|
|
||||||
rx.foreach(
|
|
||||||
dialog.thoughts,
|
|
||||||
thought_showing,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
align_self="start",
|
|
||||||
),
|
|
||||||
style={"overflow": "hidden", "min-height": "0"},
|
|
||||||
),
|
|
||||||
display="grid",
|
|
||||||
grid_template_rows=rx.cond(is_expanded, "1fr", "0fr"),
|
|
||||||
width="100%",
|
|
||||||
opacity=rx.cond(is_expanded, "1", "0"),
|
|
||||||
overflow="hidden",
|
|
||||||
transition="grid-template-rows 0.18s ease-in-out, opacity 0.18s ease-in-out",
|
|
||||||
),
|
),
|
||||||
|
display="flex",
|
||||||
|
justify_content="center",
|
||||||
|
align_items="center",
|
||||||
|
width="32px",
|
||||||
|
height="18px",
|
||||||
margin_bottom="8px",
|
margin_bottom="8px",
|
||||||
|
overflow="hidden",
|
||||||
),
|
),
|
||||||
# 若思考列表为空且正在运行则显示加载动效
|
rx.match(
|
||||||
rx.cond(
|
item.type,
|
||||||
ConversationState.running_status,
|
(MessageType.USER_PROMPT, user_prompt(item)),
|
||||||
rx.box(
|
(MessageType.THINKING, thinking(item)),
|
||||||
rx.box(
|
(MessageType.RESULT_OUTPUT, result_output(item)),
|
||||||
class_name="loadin",
|
|
||||||
flex_shrink="0",
|
|
||||||
),
|
|
||||||
display="flex",
|
|
||||||
justify_content="center",
|
|
||||||
align_items="center",
|
|
||||||
width="32px",
|
|
||||||
height="18px",
|
|
||||||
margin_bottom="8px",
|
|
||||||
overflow="hidden",
|
|
||||||
),
|
|
||||||
rx.fragment(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
# 结果输出
|
|
||||||
rx.markdown(
|
|
||||||
dialog.result_output,
|
|
||||||
component_map={
|
|
||||||
"p": lambda text: rx.text(
|
|
||||||
text,
|
|
||||||
margin="4px 0",
|
|
||||||
line_height="1.75",
|
|
||||||
word_wrap="break-word",
|
|
||||||
word_break="break-all",
|
|
||||||
white_space="pre-line",
|
|
||||||
),
|
|
||||||
"strong": lambda text: rx.text(
|
|
||||||
text,
|
|
||||||
display="inline",
|
|
||||||
font_weight="var(--prismui-font-weight-3)",
|
|
||||||
),
|
|
||||||
"code": lambda text: rx.text(
|
|
||||||
text,
|
|
||||||
display="inline",
|
|
||||||
background_color="var(--prismui-background-color-6)",
|
|
||||||
border_radius="var(--prismui-border-radius-1)",
|
|
||||||
color="var(--prismui-color-5)",
|
|
||||||
),
|
|
||||||
"h1": lambda text: rx.text(
|
|
||||||
text,
|
|
||||||
margin="8px 0 4px",
|
|
||||||
line_height="1.75",
|
|
||||||
font_size="var(--prismui-font-size-4)",
|
|
||||||
font_weight="var(--prismui-font-weight-3)",
|
|
||||||
word_wrap="break-word",
|
|
||||||
word_break="break-all",
|
|
||||||
white_space="pre-line",
|
|
||||||
),
|
|
||||||
"h2": lambda text: rx.text(
|
|
||||||
text,
|
|
||||||
margin="6px 0 4px",
|
|
||||||
line_height="1.75",
|
|
||||||
font_size="var(--prismui-font-size-3)",
|
|
||||||
font_weight="var(--prismui-font-weight-3)",
|
|
||||||
word_wrap="break-word",
|
|
||||||
word_break="break-all",
|
|
||||||
white_space="pre-line",
|
|
||||||
),
|
|
||||||
"h3": lambda text: rx.text(
|
|
||||||
text,
|
|
||||||
margin="4px 0",
|
|
||||||
line_height="1.75",
|
|
||||||
font_weight="var(--prismui-font-weight-3)",
|
|
||||||
word_wrap="break-word",
|
|
||||||
word_break="break-all",
|
|
||||||
white_space="pre-line",
|
|
||||||
),
|
|
||||||
"ul": lambda children: rx.vstack(
|
|
||||||
children,
|
|
||||||
align_items="flex-start",
|
|
||||||
margin="4px 0",
|
|
||||||
style={"padding": "0 !important"},
|
|
||||||
),
|
|
||||||
"ol": lambda children: rx.vstack(
|
|
||||||
children,
|
|
||||||
align_items="flex-start",
|
|
||||||
margin="4px 0",
|
|
||||||
style={"padding": "0 !important"},
|
|
||||||
),
|
|
||||||
"li": lambda text: rx.hstack(
|
|
||||||
rx.box(
|
|
||||||
width="4.5px",
|
|
||||||
height="4.5px",
|
|
||||||
margin_right="8px",
|
|
||||||
background_color="var(--prismui-background-color-7)",
|
|
||||||
border_radius="100%",
|
|
||||||
),
|
|
||||||
rx.text(
|
|
||||||
text,
|
|
||||||
line_height="1.75",
|
|
||||||
word_wrap="break-word",
|
|
||||||
word_break="break-all",
|
|
||||||
white_space="pre-line",
|
|
||||||
),
|
|
||||||
align_items="center",
|
|
||||||
justify_content="flex-start",
|
|
||||||
margin_bottom="4px",
|
|
||||||
),
|
|
||||||
"hr": lambda _: rx.divider(
|
|
||||||
height="1px",
|
|
||||||
margin="12px 0",
|
|
||||||
background_color="var(--prismui-background-color-2)",
|
|
||||||
),
|
|
||||||
"table": lambda children: rx.el.table(
|
|
||||||
children,
|
|
||||||
width="100%",
|
|
||||||
margin="8px 0",
|
|
||||||
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 children: rx.el.th(
|
|
||||||
children,
|
|
||||||
padding="8px 12px",
|
|
||||||
background_color="var(--prismui-background-color-6)",
|
|
||||||
border="1px solid var(--prismui-background-color-2)",
|
|
||||||
text_align="left",
|
|
||||||
),
|
|
||||||
"td": lambda children: rx.el.td(
|
|
||||||
children,
|
|
||||||
padding="8px 12px",
|
|
||||||
border="1px solid var(--prismui-background-color-2)",
|
|
||||||
text_align="left",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
padding="0 16px",
|
padding="0 16px",
|
||||||
width="100%",
|
width="100%",
|
||||||
key=dialog.id,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -618,17 +586,15 @@ def message_history() -> rx.Component:
|
||||||
消息历史
|
消息历史
|
||||||
:return: Component
|
:return: Component
|
||||||
"""
|
"""
|
||||||
# 消息历史
|
|
||||||
message_history = ConversationState.message_history
|
|
||||||
return rx.box(
|
return rx.box(
|
||||||
# 若消息历史为空则显示引导,否则渲染消息历史项
|
# 若消息历史为空则显示引导,否则渲染消息历史项
|
||||||
rx.cond(
|
rx.cond(
|
||||||
message_history.length() == 0,
|
ConversationState.message_history.length() == 0,
|
||||||
guidance(),
|
guidance(),
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
rx.auto_scroll(
|
rx.auto_scroll(
|
||||||
rx.foreach(
|
rx.foreach(
|
||||||
message_history,
|
ConversationState.message_history,
|
||||||
message_history_item,
|
message_history_item,
|
||||||
),
|
),
|
||||||
width="100%",
|
width="100%",
|
||||||
|
|
@ -765,18 +731,19 @@ def user_prompt_sending() -> rx.Component:
|
||||||
line_height="21px",
|
line_height="21px",
|
||||||
color="var(--prismui-color-9)",
|
color="var(--prismui-color-9)",
|
||||||
),
|
),
|
||||||
loading=ConversationState.running_status,
|
# 加载状态:绑定当前会话的运行状态
|
||||||
# 是否禁用绑定用户提示词发送禁用状态
|
loading=ConversationState.is_running,
|
||||||
disabled=ConversationState.is_user_prompt_sending_disabled,
|
# 不可点击状态:绑定用户提示词发送按钮不可点击状态
|
||||||
# 点击事件:发送用户提示词
|
disabled=ConversationState.is_user_prompt_sending_button_disabled,
|
||||||
on_click=ConversationState.handle_user_prompt,
|
# 点击事件:运行
|
||||||
|
on_click=ConversationState.run,
|
||||||
display="inline-flex",
|
display="inline-flex",
|
||||||
position="relative",
|
position="relative",
|
||||||
justify_content="center",
|
justify_content="center",
|
||||||
align_items="center",
|
align_items="center",
|
||||||
padding="0 12px",
|
padding="0 12px",
|
||||||
background_color=rx.cond(
|
background_color=rx.cond(
|
||||||
ConversationState.is_user_prompt_sending_disabled,
|
ConversationState.is_user_prompt_sending_button_disabled,
|
||||||
"var(--prismui-color-11)",
|
"var(--prismui-color-11)",
|
||||||
"var(--prismui-color-5)",
|
"var(--prismui-color-5)",
|
||||||
),
|
),
|
||||||
|
|
@ -787,7 +754,7 @@ def user_prompt_sending() -> rx.Component:
|
||||||
white_space="nowrap",
|
white_space="nowrap",
|
||||||
overflow="hidden",
|
overflow="hidden",
|
||||||
cursor=rx.cond(
|
cursor=rx.cond(
|
||||||
ConversationState.is_user_prompt_sending_disabled,
|
ConversationState.is_user_prompt_sending_button_disabled,
|
||||||
"not-allowed",
|
"not-allowed",
|
||||||
"pointer",
|
"pointer",
|
||||||
),
|
),
|
||||||
|
|
@ -828,11 +795,11 @@ def user_prompt_sending() -> rx.Component:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def conversation_history_collapse_button(
|
def conversation_history_toggle_button(
|
||||||
is_conversation_history_shown: bool,
|
is_conversation_history_shown: bool,
|
||||||
) -> rx.Component:
|
) -> rx.Component:
|
||||||
"""
|
"""
|
||||||
会话历史折叠面板按钮
|
会话历史显示 / 隐藏按钮
|
||||||
:param is_conversation_history_shown: 会话历史展示状态
|
:param is_conversation_history_shown: 会话历史展示状态
|
||||||
:return: Component
|
:return: Component
|
||||||
"""
|
"""
|
||||||
|
|
@ -870,13 +837,10 @@ def conversation_page() -> rx.Component:
|
||||||
"""
|
"""
|
||||||
会话页面:布局参考 MetaChat,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。
|
会话页面:布局参考 MetaChat,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。
|
||||||
"""
|
"""
|
||||||
# 会话历史展示状态
|
|
||||||
is_shown = ConversationState.is_conversation_history_shown
|
|
||||||
|
|
||||||
return rx.box(
|
return rx.box(
|
||||||
rx.hstack(
|
rx.hstack(
|
||||||
# 会话历史
|
# 会话历史
|
||||||
conversation_history(is_shown),
|
conversation_history(ConversationState.is_conversation_history_shown),
|
||||||
# 工作区
|
# 工作区
|
||||||
rx.box(
|
rx.box(
|
||||||
# 对话历史
|
# 对话历史
|
||||||
|
|
@ -905,8 +869,10 @@ def conversation_page() -> rx.Component:
|
||||||
overflow="hidden",
|
overflow="hidden",
|
||||||
transition="all 0.18s ease-in-out",
|
transition="all 0.18s ease-in-out",
|
||||||
),
|
),
|
||||||
# 会话历史折叠面板按钮
|
# 会话历史显示 / 隐藏按钮
|
||||||
conversation_history_collapse_button(is_shown),
|
conversation_history_toggle_button(
|
||||||
|
ConversationState.is_conversation_history_shown
|
||||||
|
),
|
||||||
# 挂载事件:恢复会话状态
|
# 挂载事件:恢复会话状态
|
||||||
on_mount=AuthState.resume_conversation_state,
|
on_mount=AuthState.resume_conversation_state,
|
||||||
position="relative",
|
position="relative",
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ def nav_button(
|
||||||
is_actived = nav_button == AuthState.activated_nav_button
|
is_actived = nav_button == AuthState.activated_nav_button
|
||||||
|
|
||||||
return rx.vstack(
|
return rx.vstack(
|
||||||
# 图标
|
# 导航按钮的图标
|
||||||
rx.image(
|
rx.image(
|
||||||
nav_buttons[nav_button]["src"],
|
nav_buttons[nav_button]["src"],
|
||||||
width="36px",
|
width="36px",
|
||||||
|
|
@ -47,7 +47,7 @@ def nav_button(
|
||||||
border_radius="var(--prismui-border-radius-3)",
|
border_radius="var(--prismui-border-radius-3)",
|
||||||
box_shadow=rx.cond(is_actived, "var(--prismui-box-shadow-2)", "none"),
|
box_shadow=rx.cond(is_actived, "var(--prismui-box-shadow-2)", "none"),
|
||||||
),
|
),
|
||||||
# 标签
|
# 导航按钮的文本
|
||||||
rx.text(
|
rx.text(
|
||||||
nav_buttons[nav_button]["text"],
|
nav_buttons[nav_button]["text"],
|
||||||
line_height="20px",
|
line_height="20px",
|
||||||
|
|
@ -57,7 +57,7 @@ def nav_button(
|
||||||
align_items="center",
|
align_items="center",
|
||||||
gap="4px",
|
gap="4px",
|
||||||
cursor="pointer",
|
cursor="pointer",
|
||||||
# 点击事件:将指定导航按钮设置为激活的导航按钮
|
# 点击事件:设置激活导航按钮
|
||||||
on_click=AuthState.set_activated_nav_button(nav_button),
|
on_click=AuthState.set_activated_nav_button(nav_button),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ from application.states.models import (
|
||||||
Conversation,
|
Conversation,
|
||||||
Message,
|
Message,
|
||||||
MessageType,
|
MessageType,
|
||||||
Work,
|
WorkFlow,
|
||||||
WorkType,
|
WorkFlowType,
|
||||||
ConversationHistoryItem,
|
ConversationHistoryItem,
|
||||||
usage_validate_python,
|
usage_validate_python,
|
||||||
)
|
)
|
||||||
|
|
@ -149,7 +149,8 @@ class ConversationState(rx.State):
|
||||||
"""
|
"""
|
||||||
# 获取当前数据库状态
|
# 获取当前数据库状态
|
||||||
db_state = await self.get_db_state()
|
db_state = await self.get_db_state()
|
||||||
await db_state.delete_conversation_record(conversation_id)
|
# 更新会话记录
|
||||||
|
await db_state.update_conversation_record(conversation_id, is_deleted=True)
|
||||||
del self.conversations[conversation_id]
|
del self.conversations[conversation_id]
|
||||||
|
|
||||||
# 删除后,若当前用户的会话字典为空则创建会话
|
# 删除后,若当前用户的会话字典为空则创建会话
|
||||||
|
|
@ -224,7 +225,7 @@ class ConversationState(rx.State):
|
||||||
conversation.user_prompt = user_prompt.strip()
|
conversation.user_prompt = user_prompt.strip()
|
||||||
|
|
||||||
@rx.var
|
@rx.var
|
||||||
def is_user_prompt_send_button_disabled(self) -> bool:
|
def is_user_prompt_sending_button_disabled(self) -> bool:
|
||||||
"""
|
"""
|
||||||
用户提示词发送按钮不可点击状态
|
用户提示词发送按钮不可点击状态
|
||||||
:return: bool
|
:return: bool
|
||||||
|
|
@ -238,8 +239,8 @@ class ConversationState(rx.State):
|
||||||
@rx.var
|
@rx.var
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
"""
|
"""
|
||||||
获取当前会话的运行状态
|
当前会话正在运行
|
||||||
:return: 当前会话的运行状态(True 表示正在运行,False 表示运行完成)
|
:return: 当前会话正在运行(True 表示正在运行,False 表示运行完成)
|
||||||
"""
|
"""
|
||||||
# 当前会话
|
# 当前会话
|
||||||
conversation = self.conversations.get(self.actived_conversation_id)
|
conversation = self.conversations.get(self.actived_conversation_id)
|
||||||
|
|
@ -247,6 +248,18 @@ class ConversationState(rx.State):
|
||||||
return False
|
return False
|
||||||
return conversation.is_running
|
return conversation.is_running
|
||||||
|
|
||||||
|
@rx.var
|
||||||
|
def awaiting_stream(self) -> bool:
|
||||||
|
"""
|
||||||
|
当前会话正在等待流式输出
|
||||||
|
:return: 当前会话正在等待流式输出(True 表示等待流式输出,False 表示已开始流式输出或已完成)
|
||||||
|
"""
|
||||||
|
# 当前会话
|
||||||
|
conversation = self.conversations.get(self.actived_conversation_id)
|
||||||
|
if not conversation:
|
||||||
|
return False
|
||||||
|
return conversation.awaiting_stream
|
||||||
|
|
||||||
@rx.event
|
@rx.event
|
||||||
async def run(self) -> AsyncGenerator[None]:
|
async def run(self) -> AsyncGenerator[None]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -264,13 +277,22 @@ class ConversationState(rx.State):
|
||||||
|
|
||||||
# 将正在运行设置为是
|
# 将正在运行设置为是
|
||||||
conversation.is_running = True
|
conversation.is_running = True
|
||||||
# 构建用户提示词消息实例
|
# 将等待流式输出设置为是
|
||||||
message = Message(
|
conversation.awaiting_stream = True
|
||||||
type=MessageType.USER_PROMPT,
|
|
||||||
content=(user_prompt := conversation.user_prompt),
|
# 获取数据库状态
|
||||||
|
db_state = await self.get_db_state()
|
||||||
|
|
||||||
|
# 先创建消息记录再添加消息实例
|
||||||
|
conversation.messages.update(
|
||||||
|
await db_state.create_message_record(
|
||||||
|
conversation_id=conversation.id,
|
||||||
|
message=Message(
|
||||||
|
type=MessageType.USER_PROMPT,
|
||||||
|
content=(user_prompt := conversation.user_prompt),
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# 添加至消息字典
|
|
||||||
conversation.messages[message.id] = message
|
|
||||||
# 清空用户提示词
|
# 清空用户提示词
|
||||||
conversation.user_prompt = ""
|
conversation.user_prompt = ""
|
||||||
yield # 通知前端更新渲染
|
yield # 通知前端更新渲染
|
||||||
|
|
@ -280,40 +302,32 @@ class ConversationState(rx.State):
|
||||||
# 初始化工具调用唯一标识集合
|
# 初始化工具调用唯一标识集合
|
||||||
tool_call_ids: set[str] = set()
|
tool_call_ids: set[str] = set()
|
||||||
try:
|
try:
|
||||||
# 获取数据库状态
|
|
||||||
db_state = await self.get_db_state()
|
|
||||||
# 获取消息历史列表
|
|
||||||
message_history = await db_state.get_message_history(
|
|
||||||
conversation_id=self.actived_conversation_id
|
|
||||||
)
|
|
||||||
usage = usage_validate_python(conversation.usage)
|
|
||||||
# 匹配工作流类型
|
# 匹配工作流类型
|
||||||
match conversation.work_flow:
|
match conversation.work_flow:
|
||||||
# 预定航班
|
# 预定航班
|
||||||
case WorkType.BOOK_FLIGHT:
|
case WorkFlowType.BOOK_FLIGHT:
|
||||||
from application.workshop.book_flight import run_stream_events
|
from application.workshop.book_flight import run_stream_events
|
||||||
|
|
||||||
stream_events = run_stream_events(
|
|
||||||
work=conversation.work,
|
|
||||||
user_prompt=user_prompt,
|
|
||||||
message_history=message_history,
|
|
||||||
usage=usage,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 非结构化对话
|
# 非结构化对话
|
||||||
case _:
|
case _:
|
||||||
from application.workshop.unstructured_dialogue import (
|
from application.workshop.unstructured_dialogue import (
|
||||||
run_stream_events,
|
run_stream_events,
|
||||||
)
|
)
|
||||||
# 运行并流式输出事件
|
|
||||||
stream_events = run_stream_events(
|
# 运行并流式输出事件
|
||||||
user_prompt=user_prompt,
|
stream_events = run_stream_events(
|
||||||
message_history=message_history,
|
user_prompt=user_prompt,
|
||||||
usage=usage,
|
message_history=await db_state.get_message_history(
|
||||||
)
|
conversation_id=self.actived_conversation_id
|
||||||
|
),
|
||||||
|
usage=usage_validate_python(conversation.usage),
|
||||||
|
)
|
||||||
|
|
||||||
# 获取运行流式输出事件
|
# 获取运行流式输出事件
|
||||||
async for event in stream_events:
|
async for event in stream_events:
|
||||||
|
# 将等待流式输出设置为否
|
||||||
|
conversation.awaiting_stream = False
|
||||||
|
message: Message | None = None
|
||||||
match event:
|
match event:
|
||||||
# ========== 开始事件 ==========
|
# ========== 开始事件 ==========
|
||||||
case PartStartEvent(
|
case PartStartEvent(
|
||||||
|
|
@ -335,24 +349,6 @@ class ConversationState(rx.State):
|
||||||
# 将消息实例唯一标识与片段索引映射
|
# 将消息实例唯一标识与片段索引映射
|
||||||
index_map_to_message_id[index] = message.id
|
index_map_to_message_id[index] = message.id
|
||||||
|
|
||||||
# 工具调用分片开始事件
|
|
||||||
case ToolCallPart(
|
|
||||||
tool_name=tool_name, tool_call_id=tool_call_id
|
|
||||||
):
|
|
||||||
# 构建消息实例
|
|
||||||
message = Message(
|
|
||||||
type=MessageType.TOOL_CALL,
|
|
||||||
title=tool_name,
|
|
||||||
content=",",
|
|
||||||
is_running=True,
|
|
||||||
)
|
|
||||||
tool_call_ids.add(tool_call_id)
|
|
||||||
# 添加至消息字典
|
|
||||||
dialog.thoughts[index] = Thought(
|
|
||||||
type="tool_call",
|
|
||||||
content="正在生成调用参数",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 文本分片开始事件
|
# 文本分片开始事件
|
||||||
case TextPart(content=content):
|
case TextPart(content=content):
|
||||||
# 构建消息实例
|
# 构建消息实例
|
||||||
|
|
@ -399,102 +395,67 @@ class ConversationState(rx.State):
|
||||||
]
|
]
|
||||||
message.is_running = False
|
message.is_running = False
|
||||||
message.title = "思考完成"
|
message.title = "思考完成"
|
||||||
|
message.content = content
|
||||||
# ========== 函数工具调用事件 ==========
|
|
||||||
case FunctionToolCallEvent(tool_call_id=tool_call_id, part=part):
|
|
||||||
# 获取分片索引
|
|
||||||
index = tool_call_ids[tool_call_id]
|
|
||||||
match dialog.thoughts[index].type:
|
|
||||||
# 工具检索
|
|
||||||
case "tool_search":
|
|
||||||
dialog.thoughts[index].content = (
|
|
||||||
f"正在检索 {part.args_as_json_str()}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 能力加载
|
|
||||||
case "capability_load":
|
|
||||||
dialog.thoughts[index].content = (
|
|
||||||
f"正在加载能力 {part.tool_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 工具调用
|
|
||||||
case "tool_call":
|
|
||||||
dialog.thoughts[index].content = (
|
|
||||||
f"正在调用工具 {part.tool_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ========== 函数工具结果事件 ==========
|
|
||||||
case FunctionToolResultEvent(
|
|
||||||
tool_call_id=tool_call_id,
|
|
||||||
content=content,
|
|
||||||
):
|
|
||||||
index = tool_call_ids[tool_call_id]
|
|
||||||
match dialog.thoughts[index].type:
|
|
||||||
# 工具检索
|
|
||||||
case "tool_search":
|
|
||||||
dialog.thoughts[index].content = (
|
|
||||||
content if isinstance(content, str) else ""
|
|
||||||
) # 暂仅考虑文本内容
|
|
||||||
|
|
||||||
# 能力加载
|
|
||||||
case "capability_load":
|
|
||||||
dialog.thoughts[index].content = f"已加载 {content}"
|
|
||||||
|
|
||||||
# 工具调用
|
|
||||||
case "tool_call":
|
|
||||||
dialog.thoughts[index].content = f"已调用 {content}"
|
|
||||||
|
|
||||||
# ========== 智能体运行结果事件 ==========
|
# ========== 智能体运行结果事件 ==========
|
||||||
case AgentRunResultEvent(result=result):
|
case AgentRunResultEvent(result=result):
|
||||||
# 创建对话记录
|
# 更新使用量
|
||||||
await db_state.create_dialog_record(
|
await db_state.update_conversation_record(
|
||||||
conversation_id=self.conversation_id,
|
conversation.id, usage=result.usage
|
||||||
id=dialog.id,
|
|
||||||
user_prompt=dialog.user_prompt,
|
|
||||||
thoughts=thoughts_to_dict(dialog.thoughts),
|
|
||||||
result_output=dialog.result_output,
|
|
||||||
usage=usage_to_dict(result.usage),
|
|
||||||
)
|
)
|
||||||
# 创建结果记录
|
# 创建运行记录
|
||||||
await db_state.create_result_record(
|
await db_state.create_run_record(
|
||||||
conversation_id=self.conversation_id,
|
conversation_id=conversation.id,
|
||||||
_id=dialog.id,
|
|
||||||
new_messages=result.new_messages(),
|
new_messages=result.new_messages(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 强制更新会话并推送前端
|
if isinstance(message, Message):
|
||||||
self.conversations[self.actived_conversation_id] = conversation
|
# 强制更新会话并推送前端
|
||||||
yield
|
self.conversations[self.actived_conversation_id] = conversation
|
||||||
|
yield
|
||||||
|
# 创建对话记录
|
||||||
|
await db_state.create_message_record(
|
||||||
|
conversation_id=conversation.id,
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
...
|
...
|
||||||
finally:
|
finally:
|
||||||
# 将正在运行设置为否
|
# 将正在运行设置为否
|
||||||
conversation.is_running = False
|
conversation.is_running = False
|
||||||
|
# 将等待流式输出设置为否
|
||||||
|
conversation.awaiting_stream = False
|
||||||
|
|
||||||
self.conversations[self.actived_conversation_id] = conversation
|
self.conversations[self.actived_conversation_id] = conversation
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@rx.event
|
@rx.event
|
||||||
async def generate_prd(self) -> None:
|
async def init_work_flow(self, work_flow_type: WorkFlowType) -> None:
|
||||||
"""
|
"""
|
||||||
预订航班
|
初始化工作流
|
||||||
"""
|
|
||||||
from application.tasks.book_flight import init_task
|
|
||||||
|
|
||||||
# 当前会话
|
|
||||||
conversation = self.conversations[self.conversation_id]
|
|
||||||
# 初始化预定航班任务
|
|
||||||
conversation.task = init_task()
|
|
||||||
self.user_prompt = f"帮我找一班从 {conversation.task.deps.origin} 到 {conversation.task.deps.destination} 在 {conversation.task.deps.date} 的航班"
|
|
||||||
|
|
||||||
@rx.event
|
|
||||||
def toggle_message_collapse(self, message_id: str) -> None:
|
|
||||||
"""
|
|
||||||
展开/折叠消息组件
|
|
||||||
"""
|
"""
|
||||||
# 当前会话
|
# 当前会话
|
||||||
conversation = self.conversations.get(self.actived_conversation_id)
|
conversation = self.conversations.get(self.actived_conversation_id)
|
||||||
if not conversation:
|
if not conversation:
|
||||||
return
|
return
|
||||||
conversation.messages[message_id].is_expanded ^= True
|
|
||||||
|
match work_flow_type:
|
||||||
|
# 预定航班
|
||||||
|
case WorkFlowType.BOOK_FLIGHT:
|
||||||
|
from application.workshop.book_flight import init_work_flow
|
||||||
|
|
||||||
|
# 初始化预定航班任务
|
||||||
|
conversation.work_flow = init_work_flow()
|
||||||
|
conversation.user_prompt = f"帮我找一班从 {conversation.work_flow.deps.origin} 到 {conversation.work_flow.deps.destination} 在 {conversation.work_flow.deps.date} 的航班"
|
||||||
|
|
||||||
|
@rx.event
|
||||||
|
def toggle_message_history_item_shown(self, message_id: str) -> None:
|
||||||
|
"""
|
||||||
|
展示 / 隐藏消息历史项
|
||||||
|
"""
|
||||||
|
# 当前会话
|
||||||
|
conversation = self.conversations.get(self.actived_conversation_id)
|
||||||
|
if not conversation:
|
||||||
|
return
|
||||||
|
conversation.messages[message_id].is_shown ^= True
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,17 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from random import choices
|
from random import choices
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter, RunUsage
|
||||||
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter
|
|
||||||
from pydantic_ai._uuid import uuid7
|
from pydantic_ai._uuid import uuid7
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
from sqlalchemy import desc
|
|
||||||
from sqlmodel import Field, JSON, SQLModel, select, update
|
from sqlmodel import Field, JSON, SQLModel, select, update
|
||||||
|
|
||||||
from application.states.models import (
|
from application.states.models import (
|
||||||
Conversation,
|
Conversation,
|
||||||
TaskStatus,
|
|
||||||
RunStatus,
|
|
||||||
MessageType,
|
MessageType,
|
||||||
Run,
|
|
||||||
Message,
|
Message,
|
||||||
deps_to_object,
|
usage_dump_python,
|
||||||
usage_validate_python,
|
|
||||||
usage_limits_to_object,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -265,56 +259,56 @@ class DatabaseState(rx.State):
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async def delete_conversation_record(self, conversation_id: str) -> None:
|
async def update_conversation_record(
|
||||||
|
self,
|
||||||
|
conversation_id: str,
|
||||||
|
description: str | None = None,
|
||||||
|
is_deleted: bool | None = None,
|
||||||
|
usage: RunUsage | None = None,
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
删除会话记录(逻辑删除)
|
更新会话记录
|
||||||
:param conversation_id: 指定会话唯一标识
|
:param conversation_id: 指定会话唯一标识
|
||||||
|
:param description: 会话描述
|
||||||
|
:param is_deleted: 会话已删除
|
||||||
|
:param usage: 使用量
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
record = await session.get(ConversationRecord, conversation_id)
|
record = await session.get(ConversationRecord, conversation_id)
|
||||||
if not record:
|
if not record:
|
||||||
return
|
return
|
||||||
record.is_deleted = True
|
if isinstance(description, str):
|
||||||
|
record.description = description
|
||||||
|
if isinstance(is_deleted, bool):
|
||||||
|
record.is_deleted = is_deleted
|
||||||
|
if isinstance(usage, RunUsage):
|
||||||
|
record.usage = usage_dump_python(usage)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
async def save_new_messages_record1(
|
async def create_message_record(
|
||||||
self,
|
self,
|
||||||
conversation_id: str,
|
conversation_id: str,
|
||||||
run_id: str,
|
message: Message,
|
||||||
user_prompt: str,
|
|
||||||
thoughts: dict[int, Any],
|
|
||||||
result_output: str,
|
|
||||||
usage: dict[str, Any],
|
|
||||||
) -> dict[str, Message]:
|
) -> dict[str, Message]:
|
||||||
"""
|
"""
|
||||||
保存新增消息
|
创建消息记录
|
||||||
:param conversation_id: 会话唯一标识
|
:param conversation_id: 会话唯一标识
|
||||||
:param user_prompt: 用户提示词
|
:param message: 消息实例
|
||||||
:param result_output: 结果输出
|
:return: 消息实例
|
||||||
:return: 保存消息记录的唯一标识
|
|
||||||
"""
|
"""
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
record = MessageRecord(
|
record = MessageRecord(
|
||||||
id=id,
|
id=message.id,
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
user_prompt=user_prompt,
|
type=message.type,
|
||||||
thoughts=thoughts,
|
title=message.title,
|
||||||
result_output=result_output,
|
content=message.content,
|
||||||
usage=usage,
|
|
||||||
)
|
)
|
||||||
session.add(record)
|
session.add(record)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(record)
|
await session.refresh(record)
|
||||||
return {
|
return {message.id: message}
|
||||||
record.id: Dialog(
|
|
||||||
id=record.id,
|
|
||||||
user_prompt=record.user_prompt,
|
|
||||||
thoughts=record.thoughts,
|
|
||||||
result_output=record.result_output,
|
|
||||||
usage=record.usage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async def get_message_history(self, conversation_id: str) -> list[ModelMessage]:
|
async def get_message_history(self, conversation_id: str) -> list[ModelMessage]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -337,15 +331,13 @@ class DatabaseState(rx.State):
|
||||||
)
|
)
|
||||||
return message_history
|
return message_history
|
||||||
|
|
||||||
async def save_new_messages_record(
|
async def create_run_record(
|
||||||
self,
|
self,
|
||||||
id: str,
|
|
||||||
conversation_id: str,
|
conversation_id: str,
|
||||||
new_messages: list[ModelMessage],
|
new_messages: list[ModelMessage],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
保存新增消息
|
创建运行记录
|
||||||
:param id: 运行唯一标识
|
|
||||||
:param conversation_id: 会话唯一标识
|
:param conversation_id: 会话唯一标识
|
||||||
:param new_messages: 新增消息
|
:param new_messages: 新增消息
|
||||||
:return: None
|
:return: None
|
||||||
|
|
@ -353,7 +345,6 @@ class DatabaseState(rx.State):
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
session.add(
|
session.add(
|
||||||
RunRecord(
|
RunRecord(
|
||||||
id=id,
|
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
new_messages=ModelMessagesTypeAdapter.dump_json(
|
new_messages=ModelMessagesTypeAdapter.dump_json(
|
||||||
new_messages
|
new_messages
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ class Message(BaseModel):
|
||||||
is_running: bool = Field(
|
is_running: bool = Field(
|
||||||
default=False, description="正在运行,True 表示正在运行,False 表示运行完成"
|
default=False, description="正在运行,True 表示正在运行,False 表示运行完成"
|
||||||
)
|
)
|
||||||
is_expanded: bool = Field(
|
is_shown: bool = Field(
|
||||||
default=False, description="展开组件,True 表示展开,False 表示折叠"
|
default=False, description="展示组件,True 表示展示,False 表示隐藏"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -85,12 +85,12 @@ class WorkFlow(BaseModel):
|
||||||
工作流类
|
工作流类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: WorkFlowType = Field(..., description="工作类型")
|
type: WorkFlowType = Field(..., description="工作流类型")
|
||||||
deps: Deps | None = Field(default=None, description="工作依赖项")
|
deps: Deps | None = Field(default=None, description="工作流依赖项")
|
||||||
usage: RunUsage = Field(default=RunUsage(), description="工作使用量")
|
usage: RunUsage = Field(default=RunUsage(), description="工作流使用量")
|
||||||
usage_limits: UsageLimits | None = Field(
|
usage_limits: UsageLimits | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="任务使用量限制",
|
description="工作流使用量限制",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -109,6 +109,7 @@ class Conversation(BaseModel):
|
||||||
is_running: bool = Field(
|
is_running: bool = Field(
|
||||||
default=False, description="正在运行,True 表示正在运行, False 表示运行结束"
|
default=False, description="正在运行,True 表示正在运行, False 表示运行结束"
|
||||||
)
|
)
|
||||||
|
awaiting_stream: bool = Field(default=False, description="等待流式输出,True 表示等待流式输出,False 表示已开始流式输出或已完成")
|
||||||
|
|
||||||
|
|
||||||
class ConversationHistoryItem(BaseModel):
|
class ConversationHistoryItem(BaseModel):
|
||||||
|
|
@ -121,6 +122,23 @@ class ConversationHistoryItem(BaseModel):
|
||||||
created_at: str = Field(..., description="会话创建日期时间")
|
created_at: str = Field(..., description="会话创建日期时间")
|
||||||
|
|
||||||
|
|
||||||
|
class MessageHistoryItem(BaseModel):
|
||||||
|
"""
|
||||||
|
消息历史项类
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str = Field(..., description="消息唯一标识")
|
||||||
|
type: MessageType = Field(..., description="消息类型")
|
||||||
|
title: str = Field(default="", description="消息标题")
|
||||||
|
content: str = Field(default="", description="消息内容")
|
||||||
|
is_running: bool = Field(
|
||||||
|
default=False, description="正在运行,True 表示正在运行,False 表示运行完成"
|
||||||
|
)
|
||||||
|
is_shown: bool = Field(
|
||||||
|
default=False, description="展示组件,True 表示展示,False 表示隐藏"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Dpes 适配器
|
# Dpes 适配器
|
||||||
DepsAdapter = TypeAdapter(Deps)
|
DepsAdapter = TypeAdapter(Deps)
|
||||||
|
|
||||||
|
|
@ -149,16 +167,18 @@ UsageAdapter = TypeAdapter(RunUsage)
|
||||||
|
|
||||||
def usage_validate_python(usage: dict[str, Any]) -> RunUsage:
|
def usage_validate_python(usage: dict[str, Any]) -> RunUsage:
|
||||||
"""
|
"""
|
||||||
Usage 转为对象
|
将 Usage 反序列化
|
||||||
"""
|
"""
|
||||||
if not usage:
|
if not usage:
|
||||||
return RunUsage()
|
return RunUsage()
|
||||||
return UsageAdapter.validate_python(usage)
|
return UsageAdapter.validate_python(usage)
|
||||||
|
|
||||||
|
|
||||||
def usage_to_dict(usage: RunUsage) -> dict[str, Any]:
|
def usage_dump_python(usage: RunUsage) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Usage 转为字典
|
将 Usage 序列化
|
||||||
|
:param usage: 使用量
|
||||||
|
:return: python 字典
|
||||||
"""
|
"""
|
||||||
return UsageAdapter.dump_python(usage)
|
return UsageAdapter.dump_python(usage)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,21 @@ import datetime
|
||||||
from typing import AsyncGenerator, Literal
|
from typing import AsyncGenerator, Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from pydantic_ai import Agent, ModelMessage, ModelRetry, RunContext, UsageLimits
|
from pydantic_ai import Agent, ModelMessage, ModelRetry, RunContext, UsageLimits, RunUsage
|
||||||
from pydantic_ai.run import AgentRunResultEvent
|
from pydantic_ai.run import AgentRunResultEvent
|
||||||
|
|
||||||
from application.states.models import (
|
from application.states.models import (
|
||||||
Task,
|
WorkFlow,
|
||||||
TaskNodeResultEvent,
|
WorkFlowType,
|
||||||
TaskType,
|
|
||||||
usage_limits_to_object,
|
usage_limits_to_object,
|
||||||
usage_to_dict,
|
usage_to_dict,
|
||||||
usage_to_object,
|
usage_to_object,
|
||||||
|
Deps,
|
||||||
)
|
)
|
||||||
from application.workshop.models import DEEPSEEK_V4_FLASH_MODEL, MODEL_SETTINGS
|
from application.workshop.models import DEEPSEEK_V4_FLASH_MODEL, MODEL_SETTINGS
|
||||||
|
|
||||||
|
|
||||||
class Deps(BaseModel):
|
class Deps_(BaseModel):
|
||||||
"""
|
"""
|
||||||
依赖项类
|
依赖项类
|
||||||
"""
|
"""
|
||||||
|
|
@ -167,10 +167,9 @@ flight_info = """
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def init_task() -> Task:
|
def init_work_flow() -> WorkFlow:
|
||||||
return WorkFlow(
|
return WorkFlow(
|
||||||
type=WorkType.BOOK_FLIGHT,
|
type=WorkFlowType.BOOK_FLIGHT,
|
||||||
tools={"extract_flight_details": {"": ""}},
|
|
||||||
deps=Deps(
|
deps=Deps(
|
||||||
flight_info=flight_info,
|
flight_info=flight_info,
|
||||||
date=datetime.date(2025, 1, 10),
|
date=datetime.date(2025, 1, 10),
|
||||||
|
|
@ -185,8 +184,6 @@ async def run_stream_events(
|
||||||
usage: RunUsage,
|
usage: RunUsage,
|
||||||
user_prompt: str,
|
user_prompt: str,
|
||||||
message_history: list[ModelMessage],
|
message_history: list[ModelMessage],
|
||||||
work: WorkType | None = None,
|
|
||||||
|
|
||||||
) -> AsyncGenerator:
|
) -> AsyncGenerator:
|
||||||
result = None
|
result = None
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -264,3 +261,73 @@ async def run_stream_events(
|
||||||
)
|
)
|
||||||
yield event
|
yield event
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# 工具调用分片开始事件
|
||||||
|
case ToolCallPart(
|
||||||
|
tool_name=tool_name, tool_call_id=tool_call_id
|
||||||
|
):
|
||||||
|
# 构建消息实例
|
||||||
|
message = Message(
|
||||||
|
type=MessageType.TOOL_CALL,
|
||||||
|
title=tool_name,
|
||||||
|
content=",",
|
||||||
|
is_running=True,
|
||||||
|
)
|
||||||
|
tool_call_ids.add(tool_call_id)
|
||||||
|
# 添加至消息字典
|
||||||
|
dialog.thoughts[index] = Thought(
|
||||||
|
type="tool_call",
|
||||||
|
content="正在生成调用参数",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 函数工具调用事件 ==========
|
||||||
|
case FunctionToolCallEvent(tool_call_id=tool_call_id, part=part):
|
||||||
|
# 获取分片索引
|
||||||
|
index = tool_call_ids[tool_call_id]
|
||||||
|
match dialog.thoughts[index].type:
|
||||||
|
# 工具检索
|
||||||
|
case "tool_search":
|
||||||
|
dialog.thoughts[index].content = (
|
||||||
|
f"正在检索 {part.args_as_json_str()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 能力加载
|
||||||
|
case "capability_load":
|
||||||
|
dialog.thoughts[index].content = (
|
||||||
|
f"正在加载能力 {part.tool_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 工具调用
|
||||||
|
case "tool_call":
|
||||||
|
dialog.thoughts[index].content = (
|
||||||
|
f"正在调用工具 {part.tool_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 函数工具结果事件 ==========
|
||||||
|
case FunctionToolResultEvent(
|
||||||
|
tool_call_id=tool_call_id,
|
||||||
|
content=content,
|
||||||
|
):
|
||||||
|
index = tool_call_ids[tool_call_id]
|
||||||
|
match dialog.thoughts[index].type:
|
||||||
|
# 工具检索
|
||||||
|
case "tool_search":
|
||||||
|
dialog.thoughts[index].content = (
|
||||||
|
content if isinstance(content, str) else ""
|
||||||
|
) # 暂仅考虑文本内容
|
||||||
|
|
||||||
|
# 能力加载
|
||||||
|
case "capability_load":
|
||||||
|
dialog.thoughts[index].content = f"已加载 {content}"
|
||||||
|
|
||||||
|
# 工具调用
|
||||||
|
case "tool_call":
|
||||||
|
dialog.thoughts[index].content = f"已调用 {content}"
|
||||||
|
|
||||||
|
case _:
|
||||||
|
continue
|
||||||
Loading…
Reference in New Issue