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
|
import reflex as rx
|
||||||
from application.pages import index
|
from application.pages.index import render_index
|
||||||
|
|
||||||
app = rx.App(
|
app = rx.App(
|
||||||
style={
|
style={
|
||||||
|
|
@ -21,6 +21,7 @@ app = rx.App(
|
||||||
"box_sizing": "border-box",
|
"box_sizing": "border-box",
|
||||||
},
|
},
|
||||||
"--prismui-background-color": "#ffffff",
|
"--prismui-background-color": "#ffffff",
|
||||||
|
"--mc-float-block-shadow": "rgba(213, 213, 213, 0.25)",
|
||||||
"--devui-global-bg": "#f6f6f8",
|
"--devui-global-bg": "#f6f6f8",
|
||||||
"--mc-global-bg": "linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%)",
|
"--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)",
|
"--mc-box-shadow": "rgba(25, 25, 25, .06)",
|
||||||
|
|
@ -46,4 +47,4 @@ app = rx.App(
|
||||||
}, # 自定义主题颜色
|
}, # 自定义主题颜色
|
||||||
) # 此处变量名需使用 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",
|
line_height="20px",
|
||||||
font_size="11px",
|
font_size="11px",
|
||||||
font_weight="700",
|
font_weight="700",
|
||||||
color="var(--devui-color-text)",
|
color="var(--prismui-color-text)",
|
||||||
),
|
),
|
||||||
display="flex",
|
display="flex",
|
||||||
flex_direction="column",
|
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
|
from sqlmodel import Field, JSON, SQLModel
|
||||||
|
|
||||||
|
|
||||||
# 会话数据表模型
|
# 会话数据表
|
||||||
class Conversation(SQLModel, table=True):
|
class Conversations(SQLModel, table=True):
|
||||||
id: str = Field(
|
id: str = Field(
|
||||||
default_factory=lambda: str(uuid7()),
|
default_factory=lambda: str(uuid7()),
|
||||||
primary_key=True,
|
primary_key=True,
|
||||||
|
|
@ -21,8 +21,15 @@ class Conversation(SQLModel, table=True):
|
||||||
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
|
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(
|
id: str = Field(
|
||||||
default_factory=lambda: str(uuid7()),
|
default_factory=lambda: str(uuid7()),
|
||||||
primary_key=True,
|
primary_key=True,
|
||||||
|
|
@ -30,21 +37,14 @@ class Dialog(SQLModel, table=True):
|
||||||
)
|
)
|
||||||
conversation_id: str = Field(..., index=True, description="会话唯一标识")
|
conversation_id: str = Field(..., index=True, description="会话唯一标识")
|
||||||
question: str = Field(..., description="问题")
|
question: str = Field(..., description="问题")
|
||||||
thought_nodes: list[ThoughtNode] = Field(
|
thought_nodes: dict[int, ThoughtNodes] = Field(
|
||||||
default_factory=list, sa_type=JSON, description="思考节点列表"
|
default_factory=dict, sa_type=JSON, description="思考节点字典"
|
||||||
)
|
)
|
||||||
answer: str = Field(default="", description="回答")
|
answer: str = Field(default="", description="回答")
|
||||||
|
|
||||||
|
|
||||||
# 思考节点数据表模型
|
# 运行结果数据表
|
||||||
class ThoughtNode(SQLModel):
|
class RunResults(SQLModel, table=True):
|
||||||
|
|
||||||
kind: str = Field(description="思考节点类型")
|
|
||||||
content: str = Field(default="", description="思考节点内容")
|
|
||||||
|
|
||||||
|
|
||||||
# 运行结果数据表模型
|
|
||||||
class RunResult(SQLModel, table=True):
|
|
||||||
conversation_id: str = Field(primary_key=True, description="会话唯一标识")
|
conversation_id: str = Field(primary_key=True, description="会话唯一标识")
|
||||||
dialog_id: str = Field(primary_key=True, description="对话唯一标识")
|
dialog_id: str = Field(primary_key=True, description="对话唯一标识")
|
||||||
new_messages: str = Field(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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from application.pages.index import index
|
from application.pages.conversation import render_conversation
|
||||||
from application.pages.conversation import conversation
|
from application.pages.library import render_library
|
||||||
from application.pages.library import library
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["index", "conversation", "library"]
|
__all__ = ["render_conversation", "render_library"]
|
||||||
|
|
|
||||||
|
|
@ -4,31 +4,162 @@
|
||||||
"""
|
"""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
from application.components import (
|
from application.models import Conversation, Dialog, ThoughtNode
|
||||||
render_conversation_box,
|
from application.states import (
|
||||||
render_conversation_history_item,
|
ConversationHistoryState,
|
||||||
render_input_box,
|
ConversationState,
|
||||||
render_create_conversation_button,
|
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(
|
return rx.box(
|
||||||
rx.hstack(
|
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.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.vstack(
|
||||||
# 渲染标题
|
# 渲染标题
|
||||||
rx.hstack(
|
rx.hstack(
|
||||||
rx.text(
|
rx.text(
|
||||||
"对话历史",
|
"会话历史",
|
||||||
margin_bottom="8px",
|
margin_bottom="8px",
|
||||||
font_size="var(--devui-font-size-lg)",
|
font_size="var(--devui-font-size-lg)",
|
||||||
font_weight="700",
|
font_weight="700",
|
||||||
|
|
@ -42,9 +173,7 @@ def conversation() -> rx.Component:
|
||||||
rx.auto_scroll(
|
rx.auto_scroll(
|
||||||
rx.foreach(
|
rx.foreach(
|
||||||
ConversationState.conversation_history,
|
ConversationState.conversation_history,
|
||||||
lambda item, _: render_conversation_history_item(
|
lambda item, _: render_conversation_history_item(item[0], item[1]),
|
||||||
item[0], item[1]
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
flex="1",
|
flex="1",
|
||||||
margin_top="8px",
|
margin_top="8px",
|
||||||
|
|
@ -59,29 +188,483 @@ def conversation() -> rx.Component:
|
||||||
),
|
),
|
||||||
display="flex",
|
display="flex",
|
||||||
flex_direction="column",
|
flex_direction="column",
|
||||||
padding="12px",
|
|
||||||
height="100%",
|
height="100%",
|
||||||
|
padding="12px",
|
||||||
background_color="#f9f9f9cc",
|
background_color="#f9f9f9cc",
|
||||||
backdrop_filter="blur(50px)", # 背景模糊
|
backdrop_filter="blur(50px)",
|
||||||
gap="12px",
|
gap="12px",
|
||||||
color="var(--devui-text)",
|
color="var(--prismui-color-text)",
|
||||||
),
|
),
|
||||||
width=rx.cond(is_open, "25%", "0px"),
|
width=rx.cond(is_collapse_expanded, "25%", "0px"),
|
||||||
min_width=rx.cond(is_open, "240px", "0px"),
|
min_width=rx.cond(is_collapse_expanded, "240px", "0px"),
|
||||||
max_width=rx.cond(is_open, "380px", "0px"),
|
max_width=rx.cond(is_collapse_expanded, "380px", "0px"),
|
||||||
height="100%",
|
height="100%",
|
||||||
overflow="hidden",
|
overflow="hidden",
|
||||||
transition="all 0.3s ease-in-out",
|
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.box(
|
||||||
rx.vstack(
|
rx.vstack(
|
||||||
# 渲染对话框
|
rx.hstack(
|
||||||
render_conversation_box(),
|
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_create_conversation_button(),
|
||||||
# 渲染输入框
|
# 渲染输入框
|
||||||
render_input_box(),
|
render_input(),
|
||||||
display="flex",
|
display="flex",
|
||||||
flex_flow="column",
|
flex_flow="column",
|
||||||
width="100%",
|
width="100%",
|
||||||
|
|
@ -95,19 +678,20 @@ def conversation() -> rx.Component:
|
||||||
width="0",
|
width="0",
|
||||||
height="100%",
|
height="100%",
|
||||||
background="linear-gradient(180deg, #fffffff2, #f8fafff2 99%)",
|
background="linear-gradient(180deg, #fffffff2, #f8fafff2 99%)",
|
||||||
),
|
)
|
||||||
position="relative",
|
|
||||||
display="flex",
|
|
||||||
flex="1",
|
def render_conversation_history_collapse_button(
|
||||||
height="100%",
|
is_collapse_expanded: bool,
|
||||||
min_height="0",
|
) -> rx.Component:
|
||||||
overflow="hidden",
|
"""
|
||||||
transition="all 0.3s ease-in-out",
|
渲染会话历史折叠面板的打开/关闭按钮
|
||||||
),
|
:param is_collapse_expanded: 会话历史折叠面板展开状态
|
||||||
# 渲染打开/关闭对话历史折叠面板按钮
|
:return: Component
|
||||||
rx.button(
|
"""
|
||||||
|
return rx.button(
|
||||||
rx.cond(
|
rx.cond(
|
||||||
is_open,
|
is_collapse_expanded,
|
||||||
rx.icon(
|
rx.icon(
|
||||||
"chevron-left",
|
"chevron-left",
|
||||||
width="16px",
|
width="16px",
|
||||||
|
|
@ -123,21 +707,49 @@ def conversation() -> rx.Component:
|
||||||
),
|
),
|
||||||
position="absolute",
|
position="absolute",
|
||||||
top="50%",
|
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,
|
z_index=99,
|
||||||
width="16px",
|
width="16px",
|
||||||
height="40px",
|
height="40px",
|
||||||
background="var(--devui-base-bg)",
|
background="var(--prismui-background-color)",
|
||||||
box_shadow="var(--mc-float-block-shadow)",
|
box_shadow="2px 0 4px 0 var(--mc-float-block-shadow)",
|
||||||
border_radius=rx.cond(is_open, "6px", "0 6px 6px 0"),
|
border_radius=rx.cond(is_collapse_expanded, "6px", "0 6px 6px 0"),
|
||||||
transition="all 0.3s ease-in-out",
|
transition="all 0.3s ease-in-out",
|
||||||
cursor="pointer",
|
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",
|
position="relative",
|
||||||
width="100%",
|
width="100%",
|
||||||
height="100%",
|
height="100%",
|
||||||
border_radius="12px",
|
border_radius="12px",
|
||||||
overflow="hidden", # 溢出隐藏
|
overflow="hidden",
|
||||||
|
# 挂载事件:初始化会话历史
|
||||||
|
on_mount=ConversationState.on_mount,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
from application.components import sidebar
|
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
|
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(
|
rx.match(
|
||||||
SidebarState.get_activated_button,
|
SidebarState.get_activated_button,
|
||||||
# 渲染知识库页面
|
# 渲染知识库页面
|
||||||
("library", library()),
|
("library", render_library()),
|
||||||
# 渲染会话页面
|
# 渲染会话页面
|
||||||
conversation(),
|
render_conversation(),
|
||||||
),
|
),
|
||||||
width="100%",
|
width="100%",
|
||||||
height="100vh",
|
height="100vh",
|
||||||
|
|
@ -32,5 +33,5 @@ def index() -> rx.Component:
|
||||||
box_sizing="border-box",
|
box_sizing="border-box",
|
||||||
background="var(--mc-global-bg)",
|
background="var(--mc-global-bg)",
|
||||||
# 挂载事件:因测试需模拟已认证
|
# 挂载事件:因测试需模拟已认证
|
||||||
on_mount=AuthState.authenticate(),
|
on_mount=AuthState.authenticate,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
|
|
||||||
def library() -> rx.Component:
|
def render_library() -> rx.Component:
|
||||||
"""
|
"""
|
||||||
知识库页面
|
知识库页面
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from application.states.auth import AuthState
|
from application.states.auth import AuthState
|
||||||
from application.states.conversation import ConversationState
|
from application.states.conversation import ConversationState
|
||||||
from application.states.create_conversation import CreateConversationState
|
|
||||||
from application.states.conversation_history import ConversationHistoryState
|
from application.states.conversation_history import ConversationHistoryState
|
||||||
from application.states.database import (
|
from application.states.create_conversation import CreateConversationState
|
||||||
DatabaseState,
|
from application.states.database import DatabaseState
|
||||||
Conversations,
|
|
||||||
ConversationMemory as Conversation,
|
|
||||||
DialogMemory as Dialog,
|
|
||||||
ThoughtNodeMemory as ThoughtNode,
|
|
||||||
)
|
|
||||||
from application.states.sidebar import SidebarState
|
from application.states.sidebar import SidebarState
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AuthState",
|
"AuthState",
|
||||||
"ConversationState",
|
|
||||||
"Conversations",
|
|
||||||
"ConversationHistoryState",
|
"ConversationHistoryState",
|
||||||
|
"ConversationState",
|
||||||
"CreateConversationState",
|
"CreateConversationState",
|
||||||
"DatabaseState",
|
"DatabaseState",
|
||||||
"SidebarState",
|
"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
|
@rx.event
|
||||||
async def authenticate(self) -> None:
|
async def authenticate(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
智能体状态
|
会话状态
|
||||||
"""
|
"""
|
||||||
from typing import Any, AsyncGenerator, Dict
|
from typing import Any, AsyncGenerator, Dict
|
||||||
from typing import Dict, List
|
from typing import Dict
|
||||||
|
|
||||||
from pydantic_ai import Agent, ThinkingPartDelta
|
from pydantic_ai import Agent, ThinkingPartDelta
|
||||||
from pydantic_ai.messages import (
|
from pydantic_ai.messages import (
|
||||||
|
|
@ -24,15 +24,10 @@ from pydantic_ai.providers.openai import OpenAIProvider
|
||||||
from pydantic_ai.run import AgentRunResultEvent
|
from pydantic_ai.run import AgentRunResultEvent
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
from application.states import (
|
from application.models import Conversation, Dialog, ThoughtNode
|
||||||
AuthState,
|
from application.states.auth import AuthState
|
||||||
Conversation,
|
from application.states.create_conversation import CreateConversationState
|
||||||
Conversations,
|
from application.states.database import DatabaseState
|
||||||
CreateConversationState,
|
|
||||||
DatabaseState,
|
|
||||||
Dialog,
|
|
||||||
ThoughtNode,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
instructions: str = """
|
instructions: str = """
|
||||||
|
|
@ -74,7 +69,7 @@ class ConversationState(rx.State):
|
||||||
# 用户唯一标识
|
# 用户唯一标识
|
||||||
user_id: str = ""
|
user_id: str = ""
|
||||||
# 会话字典
|
# 会话字典
|
||||||
conversations: Conversations = {}
|
conversations: Dict[str, Conversation] = {}
|
||||||
# 当前会话唯一标识
|
# 当前会话唯一标识
|
||||||
conversation_id: str = ""
|
conversation_id: str = ""
|
||||||
|
|
||||||
|
|
@ -96,7 +91,6 @@ class ConversationState(rx.State):
|
||||||
self.conversations = await database_state.get_conversations(
|
self.conversations = await database_state.get_conversations(
|
||||||
user_id=self.user_id
|
user_id=self.user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# 若会话字典为空则新建会话
|
# 若会话字典为空则新建会话
|
||||||
if not self.conversations:
|
if not self.conversations:
|
||||||
# 先在数据库新建会话,再在会话字典新建会话
|
# 先在数据库新建会话,再在会话字典新建会话
|
||||||
|
|
@ -109,7 +103,7 @@ class ConversationState(rx.State):
|
||||||
self.conversation_id = next(reversed(self.conversations.keys()))
|
self.conversation_id = next(reversed(self.conversations.keys()))
|
||||||
|
|
||||||
@rx.var
|
@rx.var
|
||||||
def conversation_history(self) -> Conversations:
|
def conversation_history(self) -> Dict[str, Conversation]:
|
||||||
"""
|
"""
|
||||||
获取会话历史,用于前端渲染会话历史
|
获取会话历史,用于前端渲染会话历史
|
||||||
:return: 会话历史
|
:return: 会话历史
|
||||||
|
|
@ -384,6 +378,12 @@ class ConversationState(rx.State):
|
||||||
|
|
||||||
# ========== 智能体运行结果事件 ==========
|
# ========== 智能体运行结果事件 ==========
|
||||||
case AgentRunResultEvent(result=result):
|
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(
|
await database_state.save_new_messages(
|
||||||
conversation_id=self.conversation_id,
|
conversation_id=self.conversation_id,
|
||||||
|
|
@ -401,4 +401,4 @@ class ConversationState(rx.State):
|
||||||
"""
|
"""
|
||||||
# 指定运行
|
# 指定运行
|
||||||
dialog = self.conversations[self.conversation_id].dialogs[dialog_id]
|
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表示关闭
|
# 会话历史折叠面板展开状态,True表示展开,False表示收起
|
||||||
is_collapse_open: bool = False
|
is_collapse_expanded: bool = False
|
||||||
|
|
||||||
@rx.event
|
@rx.event
|
||||||
def toggle_collapse(self) -> None:
|
def toggle_collapse(self) -> None:
|
||||||
|
|
@ -19,4 +19,4 @@ class ConversationHistoryState(rx.State):
|
||||||
打开/关闭会话历史折叠面板
|
打开/关闭会话历史折叠面板
|
||||||
:return: None
|
: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 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
|
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
from sqlalchemy import ColumnElement, desc
|
from sqlalchemy import ColumnElement, desc
|
||||||
from sqlmodel import select
|
from sqlmodel import select
|
||||||
|
|
||||||
from application.models import Conversation, Dialog, RunResult
|
from application.models import (
|
||||||
|
Conversation,
|
||||||
|
Conversations,
|
||||||
class ConversationMemory(BaseModel):
|
Dialog,
|
||||||
"""
|
Dialogs,
|
||||||
会话内存模型
|
RunResults,
|
||||||
"""
|
ThoughtNode,
|
||||||
|
|
||||||
description: str = Field(default="新会话", description="会话描述")
|
|
||||||
is_running: bool = Field(
|
|
||||||
default=False, description="运行状态,True 表示运行中,False 表示运行完成"
|
|
||||||
)
|
)
|
||||||
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):
|
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: 用户唯一标识
|
:param user_id: 用户唯一标识
|
||||||
:return: 会话字典
|
:return: 会话字典
|
||||||
"""
|
"""
|
||||||
conversations: Conversations = {}
|
conversations: Dict[str, Conversation] = {}
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
result = await session.exec(
|
result = await session.exec(
|
||||||
select(Conversation, Dialog)
|
select(Conversations, Dialogs)
|
||||||
.outerjoin(
|
.outerjoin(
|
||||||
Dialog,
|
Dialogs,
|
||||||
cast(
|
cast(
|
||||||
ColumnElement[bool],
|
ColumnElement[bool],
|
||||||
Conversation.id == Dialog.conversation_id,
|
Conversations.id == Dialogs.conversation_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
Conversation.user_id == user_id,
|
Conversations.user_id == user_id,
|
||||||
Conversation.is_deleted == False,
|
Conversations.is_deleted == False,
|
||||||
)
|
)
|
||||||
.order_by(Conversation.id, Dialog.id)
|
.order_by(Conversations.id, Dialogs.id)
|
||||||
)
|
)
|
||||||
for conversation, dialog in result.all():
|
for conversation, dialog in result.all():
|
||||||
conversation_memory = conversations.setdefault(
|
conversation_memory = conversations.setdefault(
|
||||||
conversation.id,
|
conversation.id,
|
||||||
ConversationMemory(
|
Conversation(
|
||||||
description=conversation.description,
|
description=conversation.description,
|
||||||
created_at=conversation.created_at,
|
created_at=conversation.created_at,
|
||||||
),
|
),
|
||||||
|
|
@ -103,11 +64,11 @@ class DatabaseState(rx.State):
|
||||||
|
|
||||||
if not dialog:
|
if not dialog:
|
||||||
continue
|
continue
|
||||||
conversation_memory.dialogs[dialog.id] = DialogMemory(
|
conversation_memory.dialogs[dialog.id] = Dialog(
|
||||||
question=dialog.question,
|
question=dialog.question,
|
||||||
thought_nodes=ThoughtNodeMemoryTypeAdapter.validate_python(
|
thought_nodes=ThoughtNodeTypeAdapter.validate_python(
|
||||||
dialog.thought_nodes
|
dialog.thought_nodes
|
||||||
), # 反序列化为思考节点内存实例列表
|
), # 反序列化为思考节点内存实例字典
|
||||||
answer=dialog.answer,
|
answer=dialog.answer,
|
||||||
)
|
)
|
||||||
return conversations
|
return conversations
|
||||||
|
|
@ -122,7 +83,7 @@ class DatabaseState(rx.State):
|
||||||
:return: 新建会话唯一标识、创建时间
|
:return: 新建会话唯一标识、创建时间
|
||||||
"""
|
"""
|
||||||
async with rx.asession() as session:
|
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)
|
session.add(conversation)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(conversation)
|
await session.refresh(conversation)
|
||||||
|
|
@ -135,7 +96,7 @@ class DatabaseState(rx.State):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
conversation = await session.get(Conversation, conversation_id)
|
conversation = await session.get(Conversations, conversation_id)
|
||||||
if not conversation:
|
if not conversation:
|
||||||
return
|
return
|
||||||
conversation.is_deleted = True
|
conversation.is_deleted = True
|
||||||
|
|
@ -149,7 +110,7 @@ class DatabaseState(rx.State):
|
||||||
:return: 新建对话唯一标识
|
:return: 新建对话唯一标识
|
||||||
"""
|
"""
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
dialog = Dialog(
|
dialog = Dialogs(
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
question=question,
|
question=question,
|
||||||
)
|
)
|
||||||
|
|
@ -161,7 +122,7 @@ class DatabaseState(rx.State):
|
||||||
async def complete_dialog(
|
async def complete_dialog(
|
||||||
self,
|
self,
|
||||||
dialog_id: str,
|
dialog_id: str,
|
||||||
thought_nodes: list[ThoughtNodeMemory],
|
thought_nodes: dict[int, ThoughtNode],
|
||||||
answer: str,
|
answer: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -172,11 +133,11 @@ class DatabaseState(rx.State):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
dialog = await session.get(Dialog, dialog_id)
|
dialog = await session.get(Dialogs, dialog_id)
|
||||||
if not dialog:
|
if not dialog:
|
||||||
return
|
return
|
||||||
dialog.thought_nodes = ThoughtNodeMemoryTypeAdapter.dump_python(
|
dialog.thought_nodes = ThoughtNodeTypeAdapter.dump_python(
|
||||||
thought_nodes
|
{k: v for k, v in thought_nodes.items()}
|
||||||
)
|
)
|
||||||
dialog.answer = answer
|
dialog.answer = answer
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
@ -190,9 +151,9 @@ class DatabaseState(rx.State):
|
||||||
message_history: List[ModelMessage] = []
|
message_history: List[ModelMessage] = []
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
result = await session.exec(
|
result = await session.exec(
|
||||||
select(RunResult)
|
select(RunResults)
|
||||||
.where(RunResult.conversation_id == conversation_id)
|
.where(RunResults.conversation_id == conversation_id)
|
||||||
.order_by(desc(RunResult.dialog_id))
|
.order_by(desc(RunResults.dialog_id))
|
||||||
)
|
)
|
||||||
for record in result.all():
|
for record in result.all():
|
||||||
message_history.extend(
|
message_history.extend(
|
||||||
|
|
@ -215,7 +176,7 @@ class DatabaseState(rx.State):
|
||||||
"""
|
"""
|
||||||
async with rx.asession() as session:
|
async with rx.asession() as session:
|
||||||
session.add(
|
session.add(
|
||||||
RunResult(
|
RunResults(
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
dialog_id=dialog_id,
|
dialog_id=dialog_id,
|
||||||
new_messages=ModelMessagesTypeAdapter.dump_json(
|
new_messages=ModelMessagesTypeAdapter.dump_json(
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"""
|
"""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
from application.states import AuthState
|
from application.states.auth import AuthState
|
||||||
|
|
||||||
|
|
||||||
class SidebarState(rx.State):
|
class SidebarState(rx.State):
|
||||||
|
|
@ -24,7 +24,7 @@ class SidebarState(rx.State):
|
||||||
return f"sidebar_{user_id}"
|
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
|
@rx.var
|
||||||
def get_activated_button(self) -> str:
|
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