This commit is contained in:
parent
5a68247656
commit
7d6d1d64a8
|
|
@ -1,46 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 54a15eecb10a
|
||||
Revises:
|
||||
Create Date: 2026-07-10 14:17:15.523102
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '54a15eecb10a'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('messagehistory',
|
||||
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('conversation_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('run_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('new_message', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('messagehistory', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_messagehistory_conversation_id'), ['conversation_id'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_messagehistory_run_id'), ['run_id'], unique=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('messagehistory', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_messagehistory_run_id'))
|
||||
batch_op.drop_index(batch_op.f('ix_messagehistory_conversation_id'))
|
||||
|
||||
op.drop_table('messagehistory')
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 6cd382f48060
|
||||
Revises:
|
||||
Create Date: 2026-07-13 17:24:40.090382
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '6cd382f48060'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('conversations',
|
||||
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('user_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('is_deleted', sa.Boolean(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('conversations', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_conversations_user_id'), ['user_id'], unique=False)
|
||||
|
||||
op.create_table('dialogs',
|
||||
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('conversation_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('question', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('thought_nodes', sa.JSON(), nullable=False),
|
||||
sa.Column('answer', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('dialogs', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_dialogs_conversation_id'), ['conversation_id'], unique=False)
|
||||
|
||||
op.create_table('runresults',
|
||||
sa.Column('conversation_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('dialog_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('new_messages', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('conversation_id', 'dialog_id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('runresults')
|
||||
with op.batch_alter_table('dialogs', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_dialogs_conversation_id'))
|
||||
|
||||
op.drop_table('dialogs')
|
||||
with op.batch_alter_table('conversations', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_conversations_user_id'))
|
||||
|
||||
op.drop_table('conversations')
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
应用主入口
|
||||
"""
|
||||
import reflex as rx
|
||||
from application.pages import index
|
||||
from application.pages.index import render_index
|
||||
|
||||
app = rx.App(
|
||||
style={
|
||||
|
|
@ -21,6 +21,7 @@ app = rx.App(
|
|||
"box_sizing": "border-box",
|
||||
},
|
||||
"--prismui-background-color": "#ffffff",
|
||||
"--mc-float-block-shadow": "rgba(213, 213, 213, 0.25)",
|
||||
"--devui-global-bg": "#f6f6f8",
|
||||
"--mc-global-bg": "linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%)",
|
||||
"--mc-box-shadow": "rgba(25, 25, 25, .06)",
|
||||
|
|
@ -46,4 +47,4 @@ app = rx.App(
|
|||
}, # 自定义主题颜色
|
||||
) # 此处变量名需使用 app ,具体原因目前尚不清楚
|
||||
# 注册首页路由
|
||||
app.add_page(component=index, route="/")
|
||||
app.add_page(component=render_index(), route="/")
|
||||
|
|
|
|||
|
|
@ -1,622 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
渲染对话相关组件
|
||||
"""
|
||||
import reflex as rx
|
||||
|
||||
from application.models import Run, Reasoning, Conversation
|
||||
from application.state.conversation import ConversationState
|
||||
from application.state.create_conversation_modal import CreateConversationModalState
|
||||
|
||||
|
||||
def render_conversation_history_item(
|
||||
conversation_id: str, conversation: Conversation
|
||||
) -> rx.Component:
|
||||
"""
|
||||
渲染对话历史的对话卡片
|
||||
:param conversation_id: 对话唯一标识
|
||||
:param conversation: 对话实例
|
||||
:return: Component
|
||||
"""
|
||||
|
||||
def more_button(
|
||||
conversation_id: str,
|
||||
) -> rx.Component:
|
||||
"""
|
||||
渲染更多按钮
|
||||
:param conversation_id: 对话唯一标识
|
||||
:return: Component
|
||||
"""
|
||||
return rx.popover.root(
|
||||
# 触发事件:点击更多按钮
|
||||
rx.popover.trigger(
|
||||
rx.box(
|
||||
rx.icon("ellipsis", size=14, color="var(--devui-text-weak)"),
|
||||
cursor="pointer", # 鼠标光标显示为手指
|
||||
)
|
||||
),
|
||||
# 渲染气泡卡片
|
||||
rx.popover.content(
|
||||
rx.vstack(
|
||||
rx.box(
|
||||
position="absolute", # 绝对定位
|
||||
width="8px", # 宽度
|
||||
height="8px", # 高度
|
||||
top="-12px", # 向上移动
|
||||
left="50%", # 向左移动
|
||||
transform="translateX(-50%) rotate(45deg)", # 旋转45度
|
||||
background_color="#ffffff", # 背景颜色
|
||||
box_shadow="-2px -2px 4px rgba(0,0,0,0.05)", # 阴影
|
||||
),
|
||||
rx.popover.close(
|
||||
rx.box(
|
||||
"删除",
|
||||
width="100%", # 宽度
|
||||
height="24px", # 高度
|
||||
padding="4px", # 内边距
|
||||
border_radius="4px", # 圆角
|
||||
line_height="16px", # 行高
|
||||
font_family="HuaweiFont,Helvetica,Arial,PingFangSC-Regular,Hiragino Sans GB,Microsoft YaHei,微软雅黑,Microsoft JhengHei", # 字体
|
||||
font_size="12px", # 字体大小
|
||||
color="#252b3a", # 字体颜色
|
||||
style={
|
||||
"_hover": {
|
||||
"background_color": "#f2f2f3", # 鼠标悬停显示背景颜色
|
||||
}
|
||||
},
|
||||
# 点击事件:删除对话
|
||||
on_click=lambda: ConversationState.delete_conversation(
|
||||
conversation_id
|
||||
),
|
||||
)
|
||||
),
|
||||
position="relative", # 相对定位
|
||||
width="100%", # 宽度
|
||||
),
|
||||
position="relative", # 相对定位
|
||||
align="center", # 水平居中对齐
|
||||
padding="8px", # 内边距
|
||||
border_radius="4px", # 圆角
|
||||
background_color="#ffffff", # 背景颜色
|
||||
box_shadow="0 2px 12px rgba(0,0,0,0.1)", # 阴影
|
||||
overflow="visible", # 溢出可见
|
||||
side="bottom", # 气泡卡片位于底部
|
||||
side_offset=9, # 偏移量
|
||||
),
|
||||
open_delay=0,
|
||||
)
|
||||
|
||||
# 指定对话的激活状态
|
||||
is_actived = conversation_id == ConversationState.conversation_id
|
||||
|
||||
return rx.box(
|
||||
rx.hstack(
|
||||
# 渲染对话描述
|
||||
rx.text(
|
||||
conversation.description,
|
||||
flex=1,
|
||||
height="22px",
|
||||
line_height="22px",
|
||||
font_size="var(--devui-font-size)",
|
||||
padding_right="4px",
|
||||
overflow="hidden",
|
||||
text_overflow="ellipsis",
|
||||
white_space="nowrap",
|
||||
),
|
||||
# 渲染更多按钮
|
||||
rx.box(
|
||||
more_button(conversation_id=conversation_id),
|
||||
min_width="14px",
|
||||
cursor="pointer", # 鼠标光标显示为手指
|
||||
style={
|
||||
"opacity": rx.cond(
|
||||
is_actived, "1", "0"
|
||||
), # 若已激活则不透明,否则透明
|
||||
"pointer_events": rx.cond(
|
||||
is_actived, "auto", "none"
|
||||
), # 若已激活则可点击,否则不可点击
|
||||
"transition": "opacity 0.18s ease",
|
||||
},
|
||||
),
|
||||
display="flex", # 弹性布局
|
||||
align_items="center", # 子元素垂直居中
|
||||
width="100%", # 宽度
|
||||
margin_bottom="8px", # 底部外边距
|
||||
),
|
||||
line_height="1.5", # 行高
|
||||
color="var(--devui-text-weak)", # 字体颜色
|
||||
cursor="pointer", # 鼠标悬停显示手指
|
||||
margin_bottom="8px", # 底部外边距
|
||||
width="100%", # 宽度
|
||||
padding="16px", # 内边距
|
||||
border_radius="8px", # 圆角
|
||||
style={
|
||||
"background": rx.cond(
|
||||
is_actived,
|
||||
"linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)",
|
||||
"var(--devui-base-bg)",
|
||||
),
|
||||
"box_shadow": rx.cond(is_actived, "2px 2px 8px #e9e9e9", "none"),
|
||||
# 鼠标悬停时渲染背景颜色和阴影
|
||||
"&:hover": {
|
||||
"background": "linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)",
|
||||
"box_shadow": "2px 2px 8px #e9e9e9",
|
||||
},
|
||||
# 鼠标悬停时强制显示右侧三点按钮,覆盖默认隐藏
|
||||
"&:hover > div > div:last-child": {
|
||||
"opacity": "1 !important",
|
||||
"pointer_events": "auto !important",
|
||||
},
|
||||
},
|
||||
# 点击事件:将指定对话唯一标识设置为当前对话唯一标识
|
||||
on_click=lambda: ConversationState.switch_conversation(conversation_id),
|
||||
)
|
||||
|
||||
|
||||
def render_reasoning_item_component(reasoning_id: int, reasoning: Reasoning):
|
||||
"""
|
||||
渲染推理项组件
|
||||
:param reasoning_id: 推理唯一标识
|
||||
:param reasoning: 推理实例
|
||||
:return: Component
|
||||
"""
|
||||
return rx.hstack(
|
||||
rx.vstack(
|
||||
rx.box(
|
||||
width="8px",
|
||||
height="8px",
|
||||
background_color=rx.color("blue", 7),
|
||||
),
|
||||
rx.box(width="2px", flex=1, background_color=rx.color("blue", 3)),
|
||||
align_items="center",
|
||||
spacing="0",
|
||||
height="100%",
|
||||
),
|
||||
rx.markdown(reasoning.content, color=rx.color("gray", 11), padding_y="4px"),
|
||||
align_items="flex-start",
|
||||
spacing="2",
|
||||
width="100%",
|
||||
key=reasoning_id,
|
||||
)
|
||||
|
||||
|
||||
def render_conversation_box() -> rx.Component:
|
||||
"""
|
||||
渲染对话框
|
||||
:return: Component
|
||||
"""
|
||||
|
||||
def item(run_id: str, run: Run) -> rx.Component:
|
||||
"""
|
||||
对话项
|
||||
:param run: 对话实例
|
||||
:return: Component
|
||||
"""
|
||||
# 推理字典
|
||||
reasonings = run.reasonings
|
||||
# 推理状态
|
||||
is_reasoning = run.is_reasoning
|
||||
# 推理面板展开状态
|
||||
is_reasoning_panel_open = run.is_reasoning_panel_open
|
||||
|
||||
return rx.vstack(
|
||||
rx.hstack(
|
||||
rx.spacer(),
|
||||
rx.vstack(
|
||||
# 用户问题
|
||||
rx.markdown(
|
||||
run.user_prompt,
|
||||
max_width="600px",
|
||||
padding="12px 16px",
|
||||
background_color="var(--devui-global-bg)",
|
||||
border_radius="12px",
|
||||
word_wrap="break-word",
|
||||
word_break="break-all",
|
||||
style={
|
||||
"& p.rt-Text": {
|
||||
"padding": "0",
|
||||
"margin": "0 !important",
|
||||
},
|
||||
},
|
||||
),
|
||||
# 再次发送按钮
|
||||
rx.hstack(
|
||||
rx.box(
|
||||
rx.icon(
|
||||
"rotate-ccw",
|
||||
width="14px",
|
||||
height="14px",
|
||||
color="var(--devui-text-weak)",
|
||||
),
|
||||
padding="4px",
|
||||
border_radius="4px",
|
||||
cursor="pointer",
|
||||
),
|
||||
width="100%",
|
||||
margin_top="8px",
|
||||
),
|
||||
),
|
||||
# 用户身份标识
|
||||
rx.avatar(
|
||||
src="/user_avatar.svg",
|
||||
align_self="flex-start",
|
||||
width="32px",
|
||||
height="32px",
|
||||
border_radius="100%",
|
||||
border="none",
|
||||
),
|
||||
width="100%",
|
||||
margin_top="8px",
|
||||
align_items="flex-start",
|
||||
gap="4px",
|
||||
font_size="var(--devui-font-size)",
|
||||
),
|
||||
# 智能体回答
|
||||
rx.hstack(
|
||||
# 智能体身份标识
|
||||
rx.avatar(
|
||||
src="/logo.svg",
|
||||
align_self="flex-start",
|
||||
width="32px",
|
||||
height="32px",
|
||||
border_radius="100%",
|
||||
border="none",
|
||||
),
|
||||
rx.box(
|
||||
rx.vstack(
|
||||
# 标题栏
|
||||
rx.cond(
|
||||
is_reasoning,
|
||||
rx.hstack(
|
||||
rx.icon("loader"),
|
||||
rx.text("推理中..."),
|
||||
rx.icon("chevron-down"),
|
||||
),
|
||||
rx.hstack(
|
||||
rx.icon("check"),
|
||||
rx.text("已推理"),
|
||||
rx.icon("chevron-up"),
|
||||
),
|
||||
),
|
||||
|
||||
)
|
||||
),
|
||||
# 标题栏
|
||||
text_align="left",
|
||||
width="100%",
|
||||
margin_bottom="8px",
|
||||
),
|
||||
# 渲染回复正文组件,若正在推理则不渲染,否则渲染回复正文组件
|
||||
rx.cond(
|
||||
is_reasoning,
|
||||
rx.fragment(),
|
||||
rx.box(
|
||||
rx.markdown(
|
||||
run.assistant_content,
|
||||
color=rx.color("gray", 12), # 文字颜色
|
||||
background_color=rx.color("gray", 2), # 背景颜色
|
||||
display="inline-block", # 布局模式:自适应文本宽度
|
||||
max_width="85%", # 最大宽度
|
||||
padding_x="1.25em", # 水平内边距
|
||||
padding_y="0.5em", # 垂直内边距
|
||||
margin_left="auto", # 左侧外边距自动调整
|
||||
margin_bottom="8px", # 底部外边距
|
||||
border_radius="12px", # 圆角
|
||||
),
|
||||
),
|
||||
),
|
||||
width="100%",
|
||||
key=run_id,
|
||||
)
|
||||
|
||||
# 运行字典
|
||||
runs = ConversationState.get_runs
|
||||
|
||||
# 若运行字典为空则渲染品牌图标、名称和介绍、预设用户提示词等,否则遍历渲染运行项
|
||||
return rx.cond(
|
||||
runs.length() == 0,
|
||||
rx.vstack(
|
||||
rx.vstack(
|
||||
# 品牌图标、名称和介绍
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
rx.image(
|
||||
src="/logo.png",
|
||||
width="64px",
|
||||
height="64px",
|
||||
object_fit="contain",
|
||||
),
|
||||
rx.box(
|
||||
"棱镜球",
|
||||
font_size="32px",
|
||||
font_weight="700",
|
||||
letter_spacing="1px",
|
||||
),
|
||||
display="flex",
|
||||
align_items="center",
|
||||
gap="8px",
|
||||
),
|
||||
rx.vstack(
|
||||
rx.text(
|
||||
"产品汪的棱镜球,精准捕捉各类业务需求",
|
||||
width="100%",
|
||||
text_align="center",
|
||||
),
|
||||
rx.text(
|
||||
"可帮助检索业务资料,协助生成产品方案、输出 PRD 、流程图和原型图等",
|
||||
width="100%",
|
||||
text_align="center",
|
||||
),
|
||||
width="100%",
|
||||
line_height="1.5",
|
||||
font_size="var(--devui-font-size)",
|
||||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
align_items="center",
|
||||
gap="12px",
|
||||
color="var(--devui-text)",
|
||||
),
|
||||
# 猜你想问
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
rx.text(
|
||||
"猜你想问",
|
||||
line_height="24px",
|
||||
font_size="16px",
|
||||
font_weight="700",
|
||||
),
|
||||
display="flex",
|
||||
width="100%",
|
||||
justify_content="space-between",
|
||||
align_items="center",
|
||||
margin_bottom="16px",
|
||||
),
|
||||
rx.hstack(
|
||||
rx.box(
|
||||
"需求梳理",
|
||||
padding="10px 16px",
|
||||
background_color="var(--devui-dividing-line)",
|
||||
border_radius="var(--devui-border-radius-full)",
|
||||
font_size="var(--devui-font-size)",
|
||||
color="var(--devui-aide-text)",
|
||||
cursor="pointer",
|
||||
),
|
||||
display="flex",
|
||||
flex_wrap="wrap",
|
||||
align_items="center",
|
||||
gap="12px",
|
||||
),
|
||||
width="100%",
|
||||
padding="24px",
|
||||
background_color="var(--devui-base-bg)",
|
||||
border_radius="24px",
|
||||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
min_height="0",
|
||||
margin="auto 0",
|
||||
align_items="center",
|
||||
gap="24px",
|
||||
color="var(--devui-text)",
|
||||
),
|
||||
display="flex",
|
||||
flex="1",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
padding="0 12px",
|
||||
justify_content="flex-start",
|
||||
gap="24px",
|
||||
overflow="auto",
|
||||
style={
|
||||
"&::-webkit-scrollbar": {
|
||||
"display": "none",
|
||||
},
|
||||
},
|
||||
),
|
||||
# 遍历渲染对话项
|
||||
rx.vstack(
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
runs,
|
||||
lambda i, _: item(i[0], i[1]),
|
||||
),
|
||||
width="100%",
|
||||
padding="0 12px",
|
||||
),
|
||||
flex="1",
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
margin_x="auto",
|
||||
padding_top="20px",
|
||||
justify_content="flex-start",
|
||||
overflow_x="hidden",
|
||||
overflow_y="auto",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def render_create_conversation_button() -> rx.Component:
|
||||
"""
|
||||
渲染新建对话按钮
|
||||
:return: Component
|
||||
"""
|
||||
return rx.hstack(
|
||||
rx.el.style(
|
||||
"""
|
||||
.rt-TooltipArrow polygon {
|
||||
fill: #ffffff !important;
|
||||
}
|
||||
.rt-TooltipText {
|
||||
color: var(--devui-text) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
"""
|
||||
),
|
||||
rx.spacer(), # 占位符
|
||||
rx.dialog.root(
|
||||
# 触发事件:点击图标
|
||||
rx.dialog.trigger(
|
||||
rx.box(
|
||||
rx.tooltip(
|
||||
rx.icon(
|
||||
"plus",
|
||||
size=14,
|
||||
color="var(--devui-text)",
|
||||
style={
|
||||
"_hover": {
|
||||
"color": "var(--devui-brand)",
|
||||
}
|
||||
},
|
||||
),
|
||||
content="新建对话",
|
||||
background_color="#ffffff",
|
||||
box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)",
|
||||
color="#252b3a",
|
||||
side="top",
|
||||
side_offset=9,
|
||||
),
|
||||
display="flex",
|
||||
width="24px",
|
||||
height="24px",
|
||||
background_color="var(--devui-base-bg)",
|
||||
box_shadow="0 1px 8px #1919190f",
|
||||
border_radius="var(--devui-border-radius-full)",
|
||||
justify_content="center",
|
||||
align_items="center",
|
||||
cursor="pointer",
|
||||
)
|
||||
),
|
||||
rx.dialog.content(
|
||||
rx.form(
|
||||
rx.hstack(
|
||||
rx.input(
|
||||
name="description",
|
||||
placeholder="请输入对话描述",
|
||||
flex="auto",
|
||||
min_width="20ch",
|
||||
),
|
||||
rx.button("创建"),
|
||||
spacing="2",
|
||||
wrap="wrap",
|
||||
width="100%",
|
||||
),
|
||||
# 提交事件:新建对话
|
||||
on_submit=ConversationState.create_conversation,
|
||||
),
|
||||
background_color=rx.color("mauve", 1),
|
||||
),
|
||||
open=CreateConversationModalState.is_open,
|
||||
on_open_change=CreateConversationModalState.toggle,
|
||||
),
|
||||
display="flex",
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
height="39px",
|
||||
padding="0 12px",
|
||||
justify_content="flex-end",
|
||||
align_items="center",
|
||||
gap="4px",
|
||||
)
|
||||
|
||||
|
||||
def render_input_box() -> rx.Component:
|
||||
"""
|
||||
渲染输入框
|
||||
"""
|
||||
return rx.vstack(
|
||||
# 渲染自定义输入组件
|
||||
rx.form(
|
||||
rx.box(
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
rx.text_area(
|
||||
name="user_prompt",
|
||||
placeholder="请输入您的问题,按Enter换行",
|
||||
vertical_align="middle",
|
||||
width="100%",
|
||||
height="64px",
|
||||
padding="4px 0",
|
||||
background_color="var(--devui-base-bg)",
|
||||
font_size="var(--devui-font-size)",
|
||||
color="var(--devui-text)",
|
||||
style={
|
||||
"border": "none !important",
|
||||
"outline": "none !important",
|
||||
"boxShadow": "none !important",
|
||||
},
|
||||
),
|
||||
width="100%",
|
||||
padding="0 16px",
|
||||
),
|
||||
rx.hstack(
|
||||
rx.spacer(), # 占位符
|
||||
rx.button(
|
||||
rx.icon(
|
||||
"send",
|
||||
margin_right="4px",
|
||||
width="12px",
|
||||
height="12px",
|
||||
),
|
||||
rx.text("发送"),
|
||||
position="relative",
|
||||
display="inline-flex",
|
||||
padding="0 12px",
|
||||
background_color="var(--devui-primary)",
|
||||
border="none",
|
||||
border_radius="20px",
|
||||
align_items="center",
|
||||
justify_content="center",
|
||||
white_space="nowrap",
|
||||
inline_height="1.5",
|
||||
font_size="var(--devui-font-size)",
|
||||
color="var(--devui-light-text)",
|
||||
overflow="hidden",
|
||||
cursor="pointer",
|
||||
type="submit",
|
||||
loading=ConversationState.get_running_status,
|
||||
disabled=ConversationState.get_running_status,
|
||||
),
|
||||
width="100%",
|
||||
justify_content="flex-end",
|
||||
align_items="center",
|
||||
height="32px",
|
||||
padding="0 16px",
|
||||
),
|
||||
),
|
||||
style={
|
||||
"* textarea::placeholder": {
|
||||
"fontFamily": "var(--font-family)",
|
||||
"fontSize": "var(--devui-font-size)",
|
||||
"color": "var(--placeholder)",
|
||||
"opacity": 1,
|
||||
},
|
||||
},
|
||||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
padding="12px 0",
|
||||
background_color="var(--devui-base-bg)",
|
||||
border="none",
|
||||
border_radius="16px",
|
||||
box_shadow="0 1px 8px 0 var(--mc-box-shadow)",
|
||||
on_submit=ConversationState.run,
|
||||
reset_on_submit=True,
|
||||
),
|
||||
# 渲染底部文案
|
||||
rx.text(
|
||||
"内容由大模型生成,无法确保准确性和完整性,仅供参考",
|
||||
margin_top="8px",
|
||||
text_align="center",
|
||||
font_size="12px",
|
||||
color="var(--devui-aide-text)",
|
||||
),
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
padding="0 12px 12px",
|
||||
align_items="center",
|
||||
)
|
||||
|
|
@ -85,7 +85,7 @@ def sidebar() -> rx.Component:
|
|||
line_height="20px",
|
||||
font_size="11px",
|
||||
font_weight="700",
|
||||
color="var(--devui-color-text)",
|
||||
color="var(--prismui-color-text)",
|
||||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from application.models.datatables import Conversations, Dialogs, RunResults
|
||||
from application.models.memories import Conversation, Dialog, ThoughtNode
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Conversation",
|
||||
"Conversations",
|
||||
"Dialog",
|
||||
"Dialogs",
|
||||
"RunResults",
|
||||
"ThoughtNode",
|
||||
]
|
||||
|
|
@ -8,8 +8,8 @@ from pydantic_ai._uuid import uuid7
|
|||
from sqlmodel import Field, JSON, SQLModel
|
||||
|
||||
|
||||
# 会话数据表模型
|
||||
class Conversation(SQLModel, table=True):
|
||||
# 会话数据表
|
||||
class Conversations(SQLModel, table=True):
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid7()),
|
||||
primary_key=True,
|
||||
|
|
@ -21,8 +21,15 @@ class Conversation(SQLModel, table=True):
|
|||
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
|
||||
|
||||
|
||||
# 对话数据表模型
|
||||
class Dialog(SQLModel, table=True):
|
||||
# 思考节点数据表(仅用于定义)
|
||||
class ThoughtNodes(SQLModel):
|
||||
|
||||
kind: str = Field(description="思考节点类型")
|
||||
content: str = Field(default="", description="思考节点内容")
|
||||
|
||||
|
||||
# 对话数据表
|
||||
class Dialogs(SQLModel, table=True):
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid7()),
|
||||
primary_key=True,
|
||||
|
|
@ -30,21 +37,14 @@ class Dialog(SQLModel, table=True):
|
|||
)
|
||||
conversation_id: str = Field(..., index=True, description="会话唯一标识")
|
||||
question: str = Field(..., description="问题")
|
||||
thought_nodes: list[ThoughtNode] = Field(
|
||||
default_factory=list, sa_type=JSON, description="思考节点列表"
|
||||
thought_nodes: dict[int, ThoughtNodes] = Field(
|
||||
default_factory=dict, sa_type=JSON, description="思考节点字典"
|
||||
)
|
||||
answer: str = Field(default="", description="回答")
|
||||
|
||||
|
||||
# 思考节点数据表模型
|
||||
class ThoughtNode(SQLModel):
|
||||
|
||||
kind: str = Field(description="思考节点类型")
|
||||
content: str = Field(default="", description="思考节点内容")
|
||||
|
||||
|
||||
# 运行结果数据表模型
|
||||
class RunResult(SQLModel, table=True):
|
||||
# 运行结果数据表
|
||||
class RunResults(SQLModel, table=True):
|
||||
conversation_id: str = Field(primary_key=True, description="会话唯一标识")
|
||||
dialog_id: str = Field(primary_key=True, description="对话唯一标识")
|
||||
new_messages: str = Field(description="新增消息")
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
内存模型
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ThoughtNode(BaseModel):
|
||||
"""
|
||||
思考节点内存模型
|
||||
"""
|
||||
|
||||
kind: str
|
||||
content: str
|
||||
|
||||
|
||||
class Dialog(BaseModel):
|
||||
"""
|
||||
对话内存模型
|
||||
"""
|
||||
|
||||
question: str = Field(..., description="问题")
|
||||
is_thinking: bool = Field(
|
||||
default=False, description="思考状态,True 表示思考中,False 表示思考完成"
|
||||
)
|
||||
is_collapse_expanded: bool = Field(
|
||||
default=False, description="思考折叠面板展开状态,True 表示展开,False 表示折叠"
|
||||
)
|
||||
thought_nodes: dict[int, ThoughtNode] = Field(
|
||||
default_factory=dict, description="思考节点字典"
|
||||
)
|
||||
answer: str = Field(default="", description="回答")
|
||||
|
||||
|
||||
class Conversation(BaseModel):
|
||||
"""
|
||||
会话内存模型
|
||||
"""
|
||||
|
||||
description: str = Field(default="新会话", description="会话描述")
|
||||
is_running: bool = Field(
|
||||
default=False, description="运行状态,True 表示运行中,False 表示运行完成"
|
||||
)
|
||||
dialogs: Dict[str, Dialog] = Field(default_factory=dict, description="对话字典")
|
||||
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from application.pages.index import index
|
||||
from application.pages.conversation import conversation
|
||||
from application.pages.library import library
|
||||
from application.pages.conversation import render_conversation
|
||||
from application.pages.library import render_library
|
||||
|
||||
|
||||
__all__ = ["index", "conversation", "library"]
|
||||
__all__ = ["render_conversation", "render_library"]
|
||||
|
|
|
|||
|
|
@ -4,31 +4,162 @@
|
|||
"""
|
||||
import reflex as rx
|
||||
|
||||
from application.components import (
|
||||
render_conversation_box,
|
||||
render_conversation_history_item,
|
||||
render_input_box,
|
||||
render_create_conversation_button,
|
||||
from application.models import Conversation, Dialog, ThoughtNode
|
||||
from application.states import (
|
||||
ConversationHistoryState,
|
||||
ConversationState,
|
||||
CreateConversationState,
|
||||
)
|
||||
from application.states import ConversationState, ConversationHistoryState
|
||||
|
||||
|
||||
def conversation() -> rx.Component:
|
||||
def render_conversation_history_item(
|
||||
conversation_id: str, conversation: Conversation
|
||||
) -> rx.Component:
|
||||
"""
|
||||
会话页面:布局参考 MetaChat,左侧为会话历史,右侧为会话。其中,会话历史为折叠面板。
|
||||
渲染会话历史条目
|
||||
:param conversation_id: 会话唯一标识
|
||||
:param conversation: 会话实例
|
||||
:return: Component
|
||||
"""
|
||||
# 会话历史折叠面板打开状态
|
||||
is_open = ConversationHistoryState.is_open
|
||||
|
||||
# 会话激活状态
|
||||
is_conversation_actived = conversation_id == ConversationState.conversation_id
|
||||
|
||||
return rx.box(
|
||||
rx.hstack(
|
||||
# 渲染对话历史折叠面板
|
||||
# 渲染对话描述
|
||||
rx.text(
|
||||
conversation.description,
|
||||
flex=1,
|
||||
height="22px",
|
||||
line_height="22px",
|
||||
font_size="var(--devui-font-size)",
|
||||
padding_right="4px",
|
||||
overflow="hidden",
|
||||
text_overflow="ellipsis",
|
||||
white_space="nowrap",
|
||||
),
|
||||
# 渲染更多按钮
|
||||
rx.box(
|
||||
rx.popover.root(
|
||||
# 触发事件:点击更多按钮
|
||||
rx.popover.trigger(
|
||||
rx.box(
|
||||
rx.icon(
|
||||
"ellipsis", size=14, color="var(--devui-text-weak)"
|
||||
),
|
||||
cursor="pointer",
|
||||
)
|
||||
),
|
||||
# 渲染气泡卡片
|
||||
rx.popover.content(
|
||||
rx.vstack(
|
||||
rx.box(
|
||||
position="absolute",
|
||||
top="-12px",
|
||||
left="50%",
|
||||
transform="translateX(-50%) rotate(45deg)",
|
||||
width="8px",
|
||||
height="8px",
|
||||
background_color="#ffffff",
|
||||
box_shadow="-2px -2px 4px rgba(0,0,0,0.05)",
|
||||
),
|
||||
rx.popover.close(
|
||||
rx.box(
|
||||
"删除",
|
||||
width="100%",
|
||||
height="24px",
|
||||
padding="4px",
|
||||
border_radius="4px",
|
||||
line_height="16px",
|
||||
font_family="HuaweiFont,Helvetica,Arial,PingFangSC-Regular,Hiragino Sans GB,Microsoft YaHei,微软雅黑,Microsoft JhengHei",
|
||||
font_size="12px",
|
||||
color="#252b3a",
|
||||
style={
|
||||
"_hover": {
|
||||
"background_color": "#f2f2f3",
|
||||
}
|
||||
},
|
||||
# 点击事件:删除对话
|
||||
on_click=lambda: ConversationState.delete_conversation(
|
||||
conversation_id
|
||||
),
|
||||
)
|
||||
),
|
||||
position="relative",
|
||||
width="100%",
|
||||
),
|
||||
position="relative",
|
||||
align="center",
|
||||
padding="8px",
|
||||
border_radius="4px",
|
||||
background_color="#ffffff",
|
||||
box_shadow="0 2px 12px rgba(0,0,0,0.1)",
|
||||
overflow="visible",
|
||||
side="bottom",
|
||||
side_offset=9,
|
||||
),
|
||||
open_delay=0,
|
||||
),
|
||||
min_width="14px",
|
||||
cursor="pointer",
|
||||
style={
|
||||
"opacity": rx.cond(
|
||||
is_conversation_actived, "1", "0"
|
||||
), # 若当前会话已激活则不透明,否则透明
|
||||
"pointer_events": rx.cond(
|
||||
is_conversation_actived, "auto", "none"
|
||||
), # 若当前会话已激活则可点击,否则不可点击
|
||||
"transition": "opacity 0.18s ease",
|
||||
},
|
||||
),
|
||||
display="flex",
|
||||
align_items="center",
|
||||
width="100%",
|
||||
margin_bottom="8px",
|
||||
),
|
||||
width="100%",
|
||||
padding="16px",
|
||||
margin_bottom="8px",
|
||||
border_radius="8px",
|
||||
line_height="1.5",
|
||||
color="var(--devui-text-weak)",
|
||||
cursor="pointer",
|
||||
style={
|
||||
"background": rx.cond(
|
||||
is_conversation_actived,
|
||||
"linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)",
|
||||
"var(--devui-base-bg)",
|
||||
),
|
||||
"box_shadow": rx.cond(
|
||||
is_conversation_actived, "2px 2px 8px #e9e9e9", "none"
|
||||
),
|
||||
"&:hover": {
|
||||
"background": "linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)",
|
||||
"box_shadow": "2px 2px 8px #e9e9e9",
|
||||
}, # 鼠标悬停渲染背景颜色和阴影
|
||||
"&:hover > div > div:last-child": {
|
||||
"opacity": "1 !important",
|
||||
"pointer_events": "auto !important",
|
||||
}, # 鼠标悬停强制显示右侧三点按钮
|
||||
},
|
||||
# 点击事件:将指定对话唯一标识设置为当前对话唯一标识
|
||||
on_click=ConversationState.switch_conversation(conversation_id),
|
||||
)
|
||||
|
||||
|
||||
def render_conversation_history_collapse(is_collapse_expanded: bool) -> rx.Component:
|
||||
"""
|
||||
渲染对话历史折叠面板
|
||||
:param is_collapse_expanded: 对话历史折叠面板展开状态
|
||||
:return: Component
|
||||
"""
|
||||
return rx.box(
|
||||
rx.vstack(
|
||||
# 渲染标题
|
||||
rx.hstack(
|
||||
rx.text(
|
||||
"对话历史",
|
||||
"会话历史",
|
||||
margin_bottom="8px",
|
||||
font_size="var(--devui-font-size-lg)",
|
||||
font_weight="700",
|
||||
|
|
@ -42,9 +173,7 @@ def conversation() -> rx.Component:
|
|||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
ConversationState.conversation_history,
|
||||
lambda item, _: render_conversation_history_item(
|
||||
item[0], item[1]
|
||||
),
|
||||
lambda item, _: render_conversation_history_item(item[0], item[1]),
|
||||
),
|
||||
flex="1",
|
||||
margin_top="8px",
|
||||
|
|
@ -59,29 +188,483 @@ def conversation() -> rx.Component:
|
|||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
padding="12px",
|
||||
height="100%",
|
||||
padding="12px",
|
||||
background_color="#f9f9f9cc",
|
||||
backdrop_filter="blur(50px)", # 背景模糊
|
||||
backdrop_filter="blur(50px)",
|
||||
gap="12px",
|
||||
color="var(--devui-text)",
|
||||
color="var(--prismui-color-text)",
|
||||
),
|
||||
width=rx.cond(is_open, "25%", "0px"),
|
||||
min_width=rx.cond(is_open, "240px", "0px"),
|
||||
max_width=rx.cond(is_open, "380px", "0px"),
|
||||
width=rx.cond(is_collapse_expanded, "25%", "0px"),
|
||||
min_width=rx.cond(is_collapse_expanded, "240px", "0px"),
|
||||
max_width=rx.cond(is_collapse_expanded, "380px", "0px"),
|
||||
height="100%",
|
||||
overflow="hidden",
|
||||
transition="all 0.3s ease-in-out",
|
||||
)
|
||||
|
||||
|
||||
def render_greeting() -> rx.Component:
|
||||
"""
|
||||
渲染问候
|
||||
:return: Component
|
||||
"""
|
||||
return rx.vstack(
|
||||
rx.vstack(
|
||||
# 品牌图标、名称和介绍
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
rx.image(
|
||||
src="/logo.png",
|
||||
width="64px",
|
||||
height="64px",
|
||||
object_fit="contain",
|
||||
),
|
||||
# 渲染对话区域
|
||||
rx.box(
|
||||
"棱镜球",
|
||||
font_size="32px",
|
||||
font_weight="700",
|
||||
letter_spacing="1px",
|
||||
),
|
||||
display="flex",
|
||||
align_items="center",
|
||||
gap="8px",
|
||||
),
|
||||
rx.vstack(
|
||||
rx.text(
|
||||
"产品汪的棱镜球,精准捕捉各类业务需求",
|
||||
width="100%",
|
||||
text_align="center",
|
||||
),
|
||||
rx.text(
|
||||
"可帮助检索业务资料,协助生成产品方案、输出 PRD 、流程图和原型图等",
|
||||
width="100%",
|
||||
text_align="center",
|
||||
),
|
||||
width="100%",
|
||||
line_height="1.5",
|
||||
font_size="var(--devui-font-size)",
|
||||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
align_items="center",
|
||||
gap="12px",
|
||||
color="var(--prismui-color-text)",
|
||||
),
|
||||
# 猜你想问
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
rx.text(
|
||||
"猜你想问",
|
||||
line_height="24px",
|
||||
font_size="16px",
|
||||
font_weight="700",
|
||||
),
|
||||
display="flex",
|
||||
width="100%",
|
||||
justify_content="space-between",
|
||||
align_items="center",
|
||||
margin_bottom="16px",
|
||||
),
|
||||
rx.hstack(
|
||||
rx.box(
|
||||
"需求梳理",
|
||||
padding="10px 16px",
|
||||
background_color="var(--devui-dividing-line)",
|
||||
border_radius="var(--devui-border-radius-full)",
|
||||
font_size="var(--devui-font-size)",
|
||||
color="var(--devui-aide-text)",
|
||||
cursor="pointer",
|
||||
),
|
||||
display="flex",
|
||||
flex_wrap="wrap",
|
||||
align_items="center",
|
||||
gap="12px",
|
||||
),
|
||||
width="100%",
|
||||
padding="24px",
|
||||
background_color="var(--devui-base-bg)",
|
||||
border_radius="24px",
|
||||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
min_height="0",
|
||||
margin="auto 0",
|
||||
align_items="center",
|
||||
gap="24px",
|
||||
color="var(--prismui-color-text)",
|
||||
),
|
||||
display="flex",
|
||||
flex="1",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
padding="0 12px",
|
||||
justify_content="flex-start",
|
||||
gap="24px",
|
||||
overflow="auto",
|
||||
style={
|
||||
"&::-webkit-scrollbar": {
|
||||
"display": "none",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def render_reasoning_thought_node(thought_node_id: int, thought_node: ThoughtNode):
|
||||
"""
|
||||
渲染思考节点
|
||||
:param thought_node_id: 思考节点唯一标识
|
||||
:param thought_node: 思考节点实例
|
||||
:return: Component
|
||||
"""
|
||||
return rx.hstack(
|
||||
rx.vstack(
|
||||
rx.box(
|
||||
width="8px",
|
||||
height="8px",
|
||||
background_color=rx.color("blue", 7),
|
||||
),
|
||||
rx.box(width="2px", flex=1, background_color=rx.color("blue", 3)),
|
||||
align_items="center",
|
||||
spacing="0",
|
||||
height="100%",
|
||||
),
|
||||
rx.markdown(thought_node.content, color=rx.color("gray", 11), padding_y="4px"),
|
||||
align_items="flex-start",
|
||||
spacing="2",
|
||||
width="100%",
|
||||
key=thought_node_id,
|
||||
)
|
||||
|
||||
|
||||
def render_dialog_history_item(dialog_id: str, dialog: Dialog) -> rx.Component:
|
||||
"""
|
||||
渲染对话历史条目
|
||||
:param run: 对话实例
|
||||
:return: Component
|
||||
"""
|
||||
# 推理状态
|
||||
is_thinking = dialog.is_thinking
|
||||
# 思考节点
|
||||
thought_nodes = dialog.thought_nodes
|
||||
|
||||
# 推理折叠面板展开状态
|
||||
is_reasoning_panel_open = dialog.is_collapse_expanded
|
||||
|
||||
return rx.vstack(
|
||||
# 渲染问题和再次发送按钮
|
||||
rx.hstack(
|
||||
rx.spacer(),
|
||||
rx.vstack(
|
||||
# 渲染问题
|
||||
rx.markdown(
|
||||
dialog.question,
|
||||
max_width="600px",
|
||||
padding="12px 16px",
|
||||
background_color="var(--devui-global-bg)",
|
||||
border_radius="12px",
|
||||
word_wrap="break-word",
|
||||
word_break="break-all",
|
||||
color="var(--prismui-color-text)",
|
||||
style={
|
||||
"& p.rt-Text": {
|
||||
"padding": "0",
|
||||
"margin": "0 !important",
|
||||
},
|
||||
},
|
||||
),
|
||||
# 再次发送按钮
|
||||
rx.hstack(
|
||||
rx.box(
|
||||
rx.icon(
|
||||
"rotate-ccw",
|
||||
width="14px",
|
||||
height="14px",
|
||||
color="var(--devui-text-weak)",
|
||||
),
|
||||
padding="4px",
|
||||
border_radius="4px",
|
||||
cursor="pointer",
|
||||
),
|
||||
width="100%",
|
||||
margin_top="8px",
|
||||
),
|
||||
),
|
||||
width="100%",
|
||||
margin_top="8px",
|
||||
align_items="flex-start",
|
||||
gap="4px",
|
||||
font_size="var(--devui-font-size)",
|
||||
),
|
||||
# 渲染智能体思考链面板和回答
|
||||
rx.vstack(
|
||||
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
thought_nodes,
|
||||
lambda i, _: render_reasoning_thought_node(i[0], i[1]),
|
||||
),
|
||||
width="100%",
|
||||
padding="0 12px",
|
||||
),
|
||||
# 渲染回答
|
||||
rx.markdown(
|
||||
dialog.answer,
|
||||
display="inline-block",
|
||||
max_width="85%",
|
||||
padding_x="1.25em",
|
||||
padding_y="0.5em",
|
||||
margin_left="auto",
|
||||
margin_bottom="8px",
|
||||
background_color=rx.color("gray", 2),
|
||||
border_radius="12px",
|
||||
color="var(--prismui-color-text)",
|
||||
),
|
||||
text_align="left",
|
||||
width="100%",
|
||||
margin_bottom="8px",
|
||||
),
|
||||
width="100%",
|
||||
key=dialog_id,
|
||||
)
|
||||
|
||||
|
||||
def render_dialog_history() -> rx.Component:
|
||||
"""
|
||||
渲染对话历史
|
||||
:return: Component
|
||||
"""
|
||||
|
||||
# 获取当前会话的对话历史
|
||||
dialog_history = ConversationState.dialog_history
|
||||
|
||||
# 若运行字典为空则渲染品牌图标、名称和介绍、预设用户提示词等,否则遍历渲染运行项
|
||||
return rx.cond(
|
||||
dialog_history.length() == 0,
|
||||
# 渲染问候
|
||||
render_greeting(),
|
||||
# 遍历渲染对话项
|
||||
rx.vstack(
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
dialog_history,
|
||||
lambda i, _: render_dialog_history_item(i[0], i[1]),
|
||||
),
|
||||
width="100%",
|
||||
padding="0 12px",
|
||||
),
|
||||
flex="1",
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
margin_x="auto",
|
||||
padding_top="20px",
|
||||
justify_content="flex-start",
|
||||
overflow_x="hidden",
|
||||
overflow_y="auto",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def render_create_conversation_button() -> rx.Component:
|
||||
"""
|
||||
渲染新建对话按钮
|
||||
:return: Component
|
||||
"""
|
||||
return rx.hstack(
|
||||
rx.el.style(
|
||||
"""
|
||||
.rt-TooltipArrow polygon {
|
||||
fill: #ffffff !important;
|
||||
}
|
||||
.rt-TooltipText {
|
||||
color: var(--prismui-color-text) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
"""
|
||||
),
|
||||
rx.spacer(),
|
||||
rx.dialog.root(
|
||||
# 触发事件:点击图标
|
||||
rx.dialog.trigger(
|
||||
rx.box(
|
||||
rx.tooltip(
|
||||
rx.icon(
|
||||
"plus",
|
||||
size=14,
|
||||
color="var(--prismui-color-text)",
|
||||
style={
|
||||
"_hover": {
|
||||
"color": "var(--devui-brand)",
|
||||
}
|
||||
},
|
||||
),
|
||||
content="新建对话",
|
||||
background_color="#ffffff",
|
||||
box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)",
|
||||
color="#252b3a",
|
||||
side="top",
|
||||
side_offset=9,
|
||||
),
|
||||
display="flex",
|
||||
width="24px",
|
||||
height="24px",
|
||||
background_color="var(--devui-base-bg)",
|
||||
box_shadow="0 1px 8px #1919190f",
|
||||
border_radius="var(--devui-border-radius-full)",
|
||||
justify_content="center",
|
||||
align_items="center",
|
||||
cursor="pointer",
|
||||
)
|
||||
),
|
||||
rx.dialog.content(
|
||||
rx.form(
|
||||
rx.hstack(
|
||||
rx.input(
|
||||
name="description",
|
||||
placeholder="请输入对话描述",
|
||||
flex="auto",
|
||||
min_width="20ch",
|
||||
),
|
||||
rx.button("创建"),
|
||||
spacing="2",
|
||||
wrap="wrap",
|
||||
width="100%",
|
||||
),
|
||||
# 提交事件:新建对话
|
||||
on_submit=ConversationState.create_conversation,
|
||||
),
|
||||
background_color=rx.color("mauve", 1),
|
||||
),
|
||||
open=CreateConversationState.is_modal_open,
|
||||
on_open_change=CreateConversationState.toggle_modal,
|
||||
),
|
||||
display="flex",
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
height="39px",
|
||||
padding="0 12px",
|
||||
justify_content="flex-end",
|
||||
align_items="center",
|
||||
gap="4px",
|
||||
)
|
||||
|
||||
|
||||
def render_input() -> rx.Component:
|
||||
"""
|
||||
渲染输入框
|
||||
:return: Component
|
||||
"""
|
||||
return rx.vstack(
|
||||
# 渲染自定义输入组件
|
||||
rx.form(
|
||||
rx.box(
|
||||
rx.vstack(
|
||||
# 渲染对话框
|
||||
render_conversation_box(),
|
||||
rx.hstack(
|
||||
rx.text_area(
|
||||
name="question",
|
||||
placeholder="请输入您的问题,按Enter换行",
|
||||
vertical_align="middle",
|
||||
width="100%",
|
||||
height="64px",
|
||||
padding="4px 0",
|
||||
background_color="var(--devui-base-bg)",
|
||||
font_size="var(--devui-font-size)",
|
||||
color="var(--prismui-color-text)",
|
||||
style={
|
||||
"border": "none !important",
|
||||
"outline": "none !important",
|
||||
"boxShadow": "none !important",
|
||||
},
|
||||
),
|
||||
width="100%",
|
||||
padding="0 16px",
|
||||
),
|
||||
rx.hstack(
|
||||
rx.spacer(),
|
||||
rx.button(
|
||||
rx.icon(
|
||||
"send",
|
||||
margin_right="4px",
|
||||
width="12px",
|
||||
height="12px",
|
||||
),
|
||||
rx.text("发送"),
|
||||
position="relative",
|
||||
display="inline-flex",
|
||||
padding="0 12px",
|
||||
background_color="var(--devui-primary)",
|
||||
border="none",
|
||||
border_radius="20px",
|
||||
align_items="center",
|
||||
justify_content="center",
|
||||
white_space="nowrap",
|
||||
inline_height="1.5",
|
||||
font_size="var(--devui-font-size)",
|
||||
color="var(--devui-light-text)",
|
||||
overflow="hidden",
|
||||
cursor="pointer",
|
||||
type="submit",
|
||||
loading=ConversationState.running_status,
|
||||
disabled=ConversationState.running_status,
|
||||
),
|
||||
width="100%",
|
||||
justify_content="flex-end",
|
||||
align_items="center",
|
||||
height="32px",
|
||||
padding="0 16px",
|
||||
),
|
||||
),
|
||||
style={
|
||||
"* textarea::placeholder": {
|
||||
"fontFamily": "var(--font-family)",
|
||||
"fontSize": "var(--devui-font-size)",
|
||||
"color": "var(--placeholder)",
|
||||
"opacity": 1,
|
||||
},
|
||||
},
|
||||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
width="100%",
|
||||
padding="12px 0",
|
||||
background_color="var(--devui-base-bg)",
|
||||
border="none",
|
||||
border_radius="16px",
|
||||
box_shadow="0 1px 8px 0 var(--mc-box-shadow)",
|
||||
on_submit=ConversationState.run,
|
||||
reset_on_submit=True,
|
||||
),
|
||||
# 渲染底部文案
|
||||
rx.text(
|
||||
"内容由大模型生成,无法确保准确性和完整性,仅供参考",
|
||||
margin_top="8px",
|
||||
text_align="center",
|
||||
font_size="12px",
|
||||
color="var(--devui-aide-text)",
|
||||
),
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
padding="0 12px 12px",
|
||||
align_items="center",
|
||||
)
|
||||
|
||||
|
||||
def render_conversation_workplace() -> rx.Component:
|
||||
"""
|
||||
渲染会话工作区
|
||||
:return: Component
|
||||
"""
|
||||
return rx.box(
|
||||
rx.vstack(
|
||||
# 渲染对话历史
|
||||
render_dialog_history(),
|
||||
# 渲染新建对话按钮
|
||||
render_create_conversation_button(),
|
||||
# 渲染输入框
|
||||
render_input_box(),
|
||||
render_input(),
|
||||
display="flex",
|
||||
flex_flow="column",
|
||||
width="100%",
|
||||
|
|
@ -95,19 +678,20 @@ def conversation() -> rx.Component:
|
|||
width="0",
|
||||
height="100%",
|
||||
background="linear-gradient(180deg, #fffffff2, #f8fafff2 99%)",
|
||||
),
|
||||
position="relative",
|
||||
display="flex",
|
||||
flex="1",
|
||||
height="100%",
|
||||
min_height="0",
|
||||
overflow="hidden",
|
||||
transition="all 0.3s ease-in-out",
|
||||
),
|
||||
# 渲染打开/关闭对话历史折叠面板按钮
|
||||
rx.button(
|
||||
)
|
||||
|
||||
|
||||
def render_conversation_history_collapse_button(
|
||||
is_collapse_expanded: bool,
|
||||
) -> rx.Component:
|
||||
"""
|
||||
渲染会话历史折叠面板的打开/关闭按钮
|
||||
:param is_collapse_expanded: 会话历史折叠面板展开状态
|
||||
:return: Component
|
||||
"""
|
||||
return rx.button(
|
||||
rx.cond(
|
||||
is_open,
|
||||
is_collapse_expanded,
|
||||
rx.icon(
|
||||
"chevron-left",
|
||||
width="16px",
|
||||
|
|
@ -123,21 +707,49 @@ def conversation() -> rx.Component:
|
|||
),
|
||||
position="absolute",
|
||||
top="50%",
|
||||
left=rx.cond(is_open, "calc(clamp(240px, 25%, 380px) - 8px)", "0"),
|
||||
left=rx.cond(is_collapse_expanded, "calc(clamp(240px, 25%, 380px) - 8px)", "0"),
|
||||
z_index=99,
|
||||
width="16px",
|
||||
height="40px",
|
||||
background="var(--devui-base-bg)",
|
||||
box_shadow="var(--mc-float-block-shadow)",
|
||||
border_radius=rx.cond(is_open, "6px", "0 6px 6px 0"),
|
||||
background="var(--prismui-background-color)",
|
||||
box_shadow="2px 0 4px 0 var(--mc-float-block-shadow)",
|
||||
border_radius=rx.cond(is_collapse_expanded, "6px", "0 6px 6px 0"),
|
||||
transition="all 0.3s ease-in-out",
|
||||
cursor="pointer",
|
||||
# 点击事件:打开/关闭对话历史折叠面板
|
||||
on_click=ConversationHistoryCollapseState.toggle,
|
||||
on_click=ConversationHistoryState.toggle_collapse,
|
||||
)
|
||||
|
||||
|
||||
def render_conversation() -> rx.Component:
|
||||
"""
|
||||
渲染会话页面:布局参考 MetaChat,左侧为会话历史,右侧为对话历史和输入框
|
||||
"""
|
||||
# 会话历史折叠面板展开状态
|
||||
is_collapse_expanded = ConversationHistoryState.is_collapse_expanded
|
||||
|
||||
return rx.box(
|
||||
# 渲染对话历史折叠面板
|
||||
rx.hstack(
|
||||
# 渲染对话历史折叠面板
|
||||
render_conversation_history_collapse(is_collapse_expanded),
|
||||
# 渲染会话工作区
|
||||
render_conversation_workplace(),
|
||||
position="relative",
|
||||
display="flex",
|
||||
flex="1",
|
||||
height="100%",
|
||||
min_height="0",
|
||||
overflow="hidden",
|
||||
transition="all 0.3s ease-in-out",
|
||||
),
|
||||
# 渲染对话历史折叠面板打开/关闭按钮
|
||||
render_conversation_history_collapse_button(is_collapse_expanded),
|
||||
position="relative",
|
||||
width="100%",
|
||||
height="100%",
|
||||
border_radius="12px",
|
||||
overflow="hidden", # 溢出隐藏
|
||||
overflow="hidden",
|
||||
# 挂载事件:初始化会话历史
|
||||
on_mount=ConversationState.on_mount,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@
|
|||
import reflex as rx
|
||||
|
||||
from application.components import sidebar
|
||||
from application.pages import conversation, library
|
||||
from application.pages import render_conversation, render_library
|
||||
from application.pages.conversation import render_conversation
|
||||
from application.states import AuthState, SidebarState
|
||||
|
||||
|
||||
def index() -> rx.Component:
|
||||
def render_index() -> rx.Component:
|
||||
"""
|
||||
首页
|
||||
"""
|
||||
|
|
@ -21,9 +22,9 @@ def index() -> rx.Component:
|
|||
rx.match(
|
||||
SidebarState.get_activated_button,
|
||||
# 渲染知识库页面
|
||||
("library", library()),
|
||||
("library", render_library()),
|
||||
# 渲染会话页面
|
||||
conversation(),
|
||||
render_conversation(),
|
||||
),
|
||||
width="100%",
|
||||
height="100vh",
|
||||
|
|
@ -32,5 +33,5 @@ def index() -> rx.Component:
|
|||
box_sizing="border-box",
|
||||
background="var(--mc-global-bg)",
|
||||
# 挂载事件:因测试需模拟已认证
|
||||
on_mount=AuthState.authenticate(),
|
||||
on_mount=AuthState.authenticate,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import reflex as rx
|
||||
|
||||
|
||||
def library() -> rx.Component:
|
||||
def render_library() -> rx.Component:
|
||||
"""
|
||||
知识库页面
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,27 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from application.states.auth import AuthState
|
||||
from application.states.conversation import ConversationState
|
||||
from application.states.create_conversation import CreateConversationState
|
||||
from application.states.conversation_history import ConversationHistoryState
|
||||
from application.states.database import (
|
||||
DatabaseState,
|
||||
Conversations,
|
||||
ConversationMemory as Conversation,
|
||||
DialogMemory as Dialog,
|
||||
ThoughtNodeMemory as ThoughtNode,
|
||||
)
|
||||
from application.states.create_conversation import CreateConversationState
|
||||
from application.states.database import DatabaseState
|
||||
from application.states.sidebar import SidebarState
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AuthState",
|
||||
"ConversationState",
|
||||
"Conversations",
|
||||
"ConversationHistoryState",
|
||||
"ConversationState",
|
||||
"CreateConversationState",
|
||||
"DatabaseState",
|
||||
"SidebarState",
|
||||
"Conversation",
|
||||
"Dialog",
|
||||
"ThoughtNode",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class AuthState(rx.State):
|
|||
"""
|
||||
|
||||
# 当前登录的用户唯一标识(通过本地存储同步)
|
||||
user_id = rx.LocalStorage("user_id", sync=True)
|
||||
user_id: str = rx.LocalStorage("user_id", sync=True)
|
||||
|
||||
@rx.event
|
||||
async def authenticate(self) -> None:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
智能体状态
|
||||
会话状态
|
||||
"""
|
||||
from typing import Any, AsyncGenerator, Dict
|
||||
from typing import Dict, List
|
||||
from typing import Dict
|
||||
|
||||
from pydantic_ai import Agent, ThinkingPartDelta
|
||||
from pydantic_ai.messages import (
|
||||
|
|
@ -24,15 +24,10 @@ from pydantic_ai.providers.openai import OpenAIProvider
|
|||
from pydantic_ai.run import AgentRunResultEvent
|
||||
import reflex as rx
|
||||
|
||||
from application.states import (
|
||||
AuthState,
|
||||
Conversation,
|
||||
Conversations,
|
||||
CreateConversationState,
|
||||
DatabaseState,
|
||||
Dialog,
|
||||
ThoughtNode,
|
||||
)
|
||||
from application.models import Conversation, Dialog, ThoughtNode
|
||||
from application.states.auth import AuthState
|
||||
from application.states.create_conversation import CreateConversationState
|
||||
from application.states.database import DatabaseState
|
||||
|
||||
|
||||
instructions: str = """
|
||||
|
|
@ -74,7 +69,7 @@ class ConversationState(rx.State):
|
|||
# 用户唯一标识
|
||||
user_id: str = ""
|
||||
# 会话字典
|
||||
conversations: Conversations = {}
|
||||
conversations: Dict[str, Conversation] = {}
|
||||
# 当前会话唯一标识
|
||||
conversation_id: str = ""
|
||||
|
||||
|
|
@ -96,7 +91,6 @@ class ConversationState(rx.State):
|
|||
self.conversations = await database_state.get_conversations(
|
||||
user_id=self.user_id
|
||||
)
|
||||
|
||||
# 若会话字典为空则新建会话
|
||||
if not self.conversations:
|
||||
# 先在数据库新建会话,再在会话字典新建会话
|
||||
|
|
@ -109,7 +103,7 @@ class ConversationState(rx.State):
|
|||
self.conversation_id = next(reversed(self.conversations.keys()))
|
||||
|
||||
@rx.var
|
||||
def conversation_history(self) -> Conversations:
|
||||
def conversation_history(self) -> Dict[str, Conversation]:
|
||||
"""
|
||||
获取会话历史,用于前端渲染会话历史
|
||||
:return: 会话历史
|
||||
|
|
@ -384,6 +378,12 @@ class ConversationState(rx.State):
|
|||
|
||||
# ========== 智能体运行结果事件 ==========
|
||||
case AgentRunResultEvent(result=result):
|
||||
# 完成对话
|
||||
await database_state.complete_dialog(
|
||||
dialog_id=dialog_id,
|
||||
thought_nodes=dialog.thought_nodes,
|
||||
answer=dialog.answer,
|
||||
)
|
||||
# 保存新增消息
|
||||
await database_state.save_new_messages(
|
||||
conversation_id=self.conversation_id,
|
||||
|
|
@ -401,4 +401,4 @@ class ConversationState(rx.State):
|
|||
"""
|
||||
# 指定运行
|
||||
dialog = self.conversations[self.conversation_id].dialogs[dialog_id]
|
||||
dialog.is_collapse_open = not dialog.is_collapse_open
|
||||
dialog.is_collapse_expanded = not dialog.is_collapse_expanded
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ class ConversationHistoryState(rx.State):
|
|||
会话历史状态
|
||||
"""
|
||||
|
||||
# 会话历史折叠面板打开状态,True表示打开,False表示关闭
|
||||
is_collapse_open: bool = False
|
||||
# 会话历史折叠面板展开状态,True表示展开,False表示收起
|
||||
is_collapse_expanded: bool = False
|
||||
|
||||
@rx.event
|
||||
def toggle_collapse(self) -> None:
|
||||
|
|
@ -19,4 +19,4 @@ class ConversationHistoryState(rx.State):
|
|||
打开/关闭会话历史折叠面板
|
||||
:return: None
|
||||
"""
|
||||
self.is_collapse_open = not self.is_collapse_open
|
||||
self.is_collapse_expanded = not self.is_collapse_expanded
|
||||
|
|
|
|||
|
|
@ -2,66 +2,27 @@
|
|||
"""
|
||||
数据库状态
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, cast, TypeAlias
|
||||
from typing import Dict, List, cast
|
||||
|
||||
from pydantic import BaseModel, Field, TypeAdapter
|
||||
from pydantic import TypeAdapter
|
||||
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter
|
||||
import reflex as rx
|
||||
from sqlalchemy import ColumnElement, desc
|
||||
from sqlmodel import select
|
||||
|
||||
from application.models import Conversation, Dialog, RunResult
|
||||
|
||||
|
||||
class ConversationMemory(BaseModel):
|
||||
"""
|
||||
会话内存模型
|
||||
"""
|
||||
|
||||
description: str = Field(default="新会话", description="会话描述")
|
||||
is_running: bool = Field(
|
||||
default=False, description="运行状态,True 表示运行中,False 表示运行完成"
|
||||
from application.models import (
|
||||
Conversation,
|
||||
Conversations,
|
||||
Dialog,
|
||||
Dialogs,
|
||||
RunResults,
|
||||
ThoughtNode,
|
||||
)
|
||||
dialogs: Dict[str, DialogMemory] = Field(
|
||||
default_factory=dict, description="对话字典"
|
||||
)
|
||||
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
|
||||
|
||||
|
||||
class DialogMemory(BaseModel):
|
||||
"""
|
||||
对话内存模型
|
||||
"""
|
||||
|
||||
question: str = Field(..., description="问题")
|
||||
is_thinking: bool = Field(
|
||||
default=False, description="思考状态,True 表示思考中,False 表示思考完成"
|
||||
)
|
||||
is_collapse_expanded: bool = Field(
|
||||
default=False, description="思考折叠面板展开状态,True 表示展开,False 表示折叠"
|
||||
)
|
||||
thought_nodes: list[ThoughtNodeMemory] = Field(
|
||||
default_factory=list, description="思考节点列表"
|
||||
)
|
||||
answer: str = Field(default="", description="回答")
|
||||
|
||||
|
||||
class ThoughtNodeMemory(BaseModel):
|
||||
"""
|
||||
思考节点内存模型
|
||||
"""
|
||||
|
||||
kind: str
|
||||
content: str
|
||||
|
||||
|
||||
# 会话字典类型别名
|
||||
Conversations: TypeAlias = Dict[str, ConversationMemory]
|
||||
|
||||
# 思考节点内存模型类型适配器
|
||||
ThoughtNodeMemoryTypeAdapter = TypeAdapter(list[ThoughtNodeMemory])
|
||||
ThoughtNodeTypeAdapter = TypeAdapter(dict[int, ThoughtNode])
|
||||
|
||||
|
||||
class DatabaseState(rx.State):
|
||||
|
|
@ -69,33 +30,33 @@ class DatabaseState(rx.State):
|
|||
数据库状态
|
||||
"""
|
||||
|
||||
async def get_conversations(self, user_id: str) -> Conversations:
|
||||
async def get_conversations(self, user_id: str) -> Dict[str, Conversation]:
|
||||
"""
|
||||
获取会话字典
|
||||
:param user_id: 用户唯一标识
|
||||
:return: 会话字典
|
||||
"""
|
||||
conversations: Conversations = {}
|
||||
conversations: Dict[str, Conversation] = {}
|
||||
async with rx.asession() as session:
|
||||
result = await session.exec(
|
||||
select(Conversation, Dialog)
|
||||
select(Conversations, Dialogs)
|
||||
.outerjoin(
|
||||
Dialog,
|
||||
Dialogs,
|
||||
cast(
|
||||
ColumnElement[bool],
|
||||
Conversation.id == Dialog.conversation_id,
|
||||
Conversations.id == Dialogs.conversation_id,
|
||||
),
|
||||
)
|
||||
.where(
|
||||
Conversation.user_id == user_id,
|
||||
Conversation.is_deleted == False,
|
||||
Conversations.user_id == user_id,
|
||||
Conversations.is_deleted == False,
|
||||
)
|
||||
.order_by(Conversation.id, Dialog.id)
|
||||
.order_by(Conversations.id, Dialogs.id)
|
||||
)
|
||||
for conversation, dialog in result.all():
|
||||
conversation_memory = conversations.setdefault(
|
||||
conversation.id,
|
||||
ConversationMemory(
|
||||
Conversation(
|
||||
description=conversation.description,
|
||||
created_at=conversation.created_at,
|
||||
),
|
||||
|
|
@ -103,11 +64,11 @@ class DatabaseState(rx.State):
|
|||
|
||||
if not dialog:
|
||||
continue
|
||||
conversation_memory.dialogs[dialog.id] = DialogMemory(
|
||||
conversation_memory.dialogs[dialog.id] = Dialog(
|
||||
question=dialog.question,
|
||||
thought_nodes=ThoughtNodeMemoryTypeAdapter.validate_python(
|
||||
thought_nodes=ThoughtNodeTypeAdapter.validate_python(
|
||||
dialog.thought_nodes
|
||||
), # 反序列化为思考节点内存实例列表
|
||||
), # 反序列化为思考节点内存实例字典
|
||||
answer=dialog.answer,
|
||||
)
|
||||
return conversations
|
||||
|
|
@ -122,7 +83,7 @@ class DatabaseState(rx.State):
|
|||
:return: 新建会话唯一标识、创建时间
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
conversation = Conversation(user_id=user_id, description=description)
|
||||
conversation = Conversations(user_id=user_id, description=description)
|
||||
session.add(conversation)
|
||||
await session.commit()
|
||||
await session.refresh(conversation)
|
||||
|
|
@ -135,7 +96,7 @@ class DatabaseState(rx.State):
|
|||
:return: None
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
conversation = await session.get(Conversation, conversation_id)
|
||||
conversation = await session.get(Conversations, conversation_id)
|
||||
if not conversation:
|
||||
return
|
||||
conversation.is_deleted = True
|
||||
|
|
@ -149,7 +110,7 @@ class DatabaseState(rx.State):
|
|||
:return: 新建对话唯一标识
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
dialog = Dialog(
|
||||
dialog = Dialogs(
|
||||
conversation_id=conversation_id,
|
||||
question=question,
|
||||
)
|
||||
|
|
@ -161,7 +122,7 @@ class DatabaseState(rx.State):
|
|||
async def complete_dialog(
|
||||
self,
|
||||
dialog_id: str,
|
||||
thought_nodes: list[ThoughtNodeMemory],
|
||||
thought_nodes: dict[int, ThoughtNode],
|
||||
answer: str,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -172,11 +133,11 @@ class DatabaseState(rx.State):
|
|||
:return: None
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
dialog = await session.get(Dialog, dialog_id)
|
||||
dialog = await session.get(Dialogs, dialog_id)
|
||||
if not dialog:
|
||||
return
|
||||
dialog.thought_nodes = ThoughtNodeMemoryTypeAdapter.dump_python(
|
||||
thought_nodes
|
||||
dialog.thought_nodes = ThoughtNodeTypeAdapter.dump_python(
|
||||
{k: v for k, v in thought_nodes.items()}
|
||||
)
|
||||
dialog.answer = answer
|
||||
await session.commit()
|
||||
|
|
@ -190,9 +151,9 @@ class DatabaseState(rx.State):
|
|||
message_history: List[ModelMessage] = []
|
||||
async with rx.asession() as session:
|
||||
result = await session.exec(
|
||||
select(RunResult)
|
||||
.where(RunResult.conversation_id == conversation_id)
|
||||
.order_by(desc(RunResult.dialog_id))
|
||||
select(RunResults)
|
||||
.where(RunResults.conversation_id == conversation_id)
|
||||
.order_by(desc(RunResults.dialog_id))
|
||||
)
|
||||
for record in result.all():
|
||||
message_history.extend(
|
||||
|
|
@ -215,7 +176,7 @@ class DatabaseState(rx.State):
|
|||
"""
|
||||
async with rx.asession() as session:
|
||||
session.add(
|
||||
RunResult(
|
||||
RunResults(
|
||||
conversation_id=conversation_id,
|
||||
dialog_id=dialog_id,
|
||||
new_messages=ModelMessagesTypeAdapter.dump_json(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"""
|
||||
import reflex as rx
|
||||
|
||||
from application.states import AuthState
|
||||
from application.states.auth import AuthState
|
||||
|
||||
|
||||
class SidebarState(rx.State):
|
||||
|
|
@ -24,7 +24,7 @@ class SidebarState(rx.State):
|
|||
return f"sidebar_{user_id}"
|
||||
|
||||
# 侧边栏中激活的图标导航按钮
|
||||
activated_button = rx.LocalStorage(generate_storage_key, sync=True)
|
||||
activated_button: str = rx.LocalStorage(generate_storage_key, sync=True)
|
||||
|
||||
@rx.var
|
||||
def get_activated_button(self) -> str:
|
||||
|
|
|
|||
|
|
@ -1 +1,43 @@
|
|||
{"name": "reflex", "type": "module", "scripts": {"dev": "react-router dev --host", "export": "react-router build"}, "dependencies": {"@radix-ui/react-form": "0.1.8", "@radix-ui/themes": "3.3.0", "@react-router/node": "7.15.0", "isbot": "5.1.40", "lucide-react": "1.14.0", "react": "19.2.6", "react-dom": "19.2.6", "react-error-boundary": "6.1.1", "react-helmet": "6.1.0", "react-markdown": "10.1.0", "react-router": "7.15.0", "react-router-dom": "7.15.0", "react-syntax-highlighter": "16.1.1", "rehype-katex": "7.0.1", "rehype-raw": "7.0.0", "rehype-unwrap-images": "1.0.0", "remark-gfm": "4.0.1", "remark-math": "6.0.0", "socket.io-client": "4.8.3", "sonner": "2.0.7", "universal-cookie": "7.2.2"}, "devDependencies": {"@emotion/react": "11.14.0", "@react-router/dev": "7.15.0", "@react-router/fs-routes": "7.15.0", "autoprefixer": "10.5.0", "postcss": "8.5.14", "postcss-import": "16.1.1", "vite": "8.0.16"}, "overrides": {"cookie": "1.1.1"}}
|
||||
{
|
||||
"name": "reflex",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "react-router dev --host",
|
||||
"export": "react-router build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-form": "0.1.8",
|
||||
"@radix-ui/themes": "3.3.0",
|
||||
"@react-router/node": "7.15.0",
|
||||
"isbot": "5.1.40",
|
||||
"lucide-react": "1.14.0",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
"react-error-boundary": "6.1.1",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-router": "7.15.0",
|
||||
"react-router-dom": "7.15.0",
|
||||
"react-syntax-highlighter": "16.1.1",
|
||||
"rehype-katex": "7.0.1",
|
||||
"rehype-raw": "7.0.0",
|
||||
"rehype-unwrap-images": "1.0.0",
|
||||
"remark-gfm": "4.0.1",
|
||||
"remark-math": "6.0.0",
|
||||
"socket.io-client": "4.8.3",
|
||||
"sonner": "2.0.7",
|
||||
"universal-cookie": "7.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emotion/react": "11.14.0",
|
||||
"@react-router/dev": "7.15.0",
|
||||
"@react-router/fs-routes": "7.15.0",
|
||||
"autoprefixer": "10.5.0",
|
||||
"postcss": "8.5.14",
|
||||
"postcss-import": "16.1.1",
|
||||
"vite": "8.0.16"
|
||||
},
|
||||
"overrides": {
|
||||
"cookie": "1.1.1"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue