This commit is contained in:
liubiren 2026-07-27 20:13:11 +08:00
parent 3f52e7bd61
commit 998a0fc572
9 changed files with 359 additions and 296 deletions

View File

@ -1,8 +1,8 @@
"""empty message """empty message
Revision ID: a7968da75b72 Revision ID: 4269426de639
Revises: Revises:
Create Date: 2026-07-26 23:23:19.558164 Create Date: 2026-07-27 10:10:56.902466
""" """
from typing import Sequence, Union from typing import Sequence, Union
@ -12,7 +12,7 @@ import sqlalchemy as sa
import sqlmodel import sqlmodel
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'a7968da75b72' revision: str = '4269426de639'
down_revision: Union[str, Sequence[str], None] = None down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None

View File

@ -28,7 +28,9 @@ app = rx.App(
"outline": "none", "outline": "none",
"box-shadow": "none", "box-shadow": "none",
}, },
"*::placeholder": { ".rt-TextAreaInput::placeholder, .rt-TextFieldInput::placeholder": {
"font-family": "var(--font-family)",
"font-size": "var(--prismui-font-size-2)",
"color": "var(--prismui-color-3)", "color": "var(--prismui-color-3)",
"opacity": 1, "opacity": 1,
}, # 覆盖占位符样式 }, # 覆盖占位符样式

View File

@ -3,10 +3,10 @@
领域模型 领域模型
""" """
from datetime import datetime from datetime import datetime
from typing import Dict, List from typing import Dict
from pydantic_ai._uuid import uuid7
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from dataclasses import dataclass from pydantic_ai._uuid import uuid7
class Thought(BaseModel): class Thought(BaseModel):
@ -25,7 +25,7 @@ class Dialog(BaseModel):
id: str = Field(default_factory=lambda: str(uuid7()), description="对话唯一标识") id: str = Field(default_factory=lambda: str(uuid7()), description="对话唯一标识")
user_prompt: str = Field(..., description="用户提示词") user_prompt: str = Field(..., description="用户提示词")
thoughts: List[Thought] = Field(default_factory=list, description="思考列表") thoughts: Dict[int, Thought] = Field(default_factory=dict, description="思考列表")
result_output: str = Field(default="", description="结果输出") result_output: str = Field(default="", description="结果输出")
is_thinking: bool = Field( is_thinking: bool = Field(
default=False, description="正在思考True 表示正在思考False 表示未正在思考" default=False, description="正在思考True 表示正在思考False 表示未正在思考"

View File

@ -3,6 +3,7 @@
会话页面 会话页面
""" """
import reflex as rx import reflex as rx
from typing import Dict
from application.domain_models import ( from application.domain_models import (
Conversation, Conversation,
@ -14,36 +15,61 @@ from application.states import ConversationState, AuthState
def conversation_history_item( def conversation_history_item(
item: ConversationHistoryItem, conversation: ConversationHistoryItem,
) -> rx.Component: ) -> rx.Component:
""" """
会话历史项 会话历史项
:param item: 会话历史项 :param conversation: 会话历史项
:return: Component :return: Component
""" """
# 会话激活状态 # 高亮:若为当前会话或显示更多则高亮
is_actived: bool = item.id == ConversationState.conversation_id highlight: bool = (
conversation.id == ConversationState.shown_more_conversation_id
) | (conversation.id == ConversationState.conversation_id)
return rx.list.item( return rx.list.item(
rx.vstack( rx.vstack(
rx.hstack( rx.hstack(
# 对话描述 # 对话描述
rx.text( rx.text(
item.description, conversation.description,
flex=1, flex=1,
height="22px", height="22px",
line_height="22px", line_height="22px",
padding_right="4px", padding_right="18px",
color="var(--prismui-color-2)", color="var(--prismui-color-2)",
overflow="hidden",
text_overflow="ellipsis", text_overflow="ellipsis",
white_space="nowrap", white_space="nowrap",
overflow="hidden",
), ),
# 更多按钮 display="flex",
align_items="center",
width="100%",
margin_bottom="8px",
),
rx.hstack(
rx.spacer(),
# 创建时间
rx.text(
conversation.created_at,
line_height="20px",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-8)",
),
justify_content="space-between",
align_items="center",
width="100%",
height="20px",
margin_bottom="8px",
),
# 点击事件:切换会话
on_click=lambda: ConversationState.switch_conversation(conversation.id),
width="100%",
),
# 更多按钮:点击更多按钮显示气泡卡片,可删除会话
rx.box( rx.box(
rx.popover.root( rx.popover.root(
# 触发事件:点击更多按钮
rx.popover.trigger( rx.popover.trigger(
rx.box( rx.box(
rx.icon( rx.icon(
@ -55,12 +81,11 @@ def conversation_history_item(
cursor="pointer", cursor="pointer",
) )
), ),
# 气泡卡片
rx.popover.content( rx.popover.content(
rx.vstack( rx.vstack(
rx.box( rx.box(
position="absolute", position="absolute",
top="-12px", top="-6px",
left="50%", left="50%",
transform="translateX(-50%) rotate(45deg)", transform="translateX(-50%) rotate(45deg)",
width="8px", width="8px",
@ -70,22 +95,26 @@ def conversation_history_item(
rx.popover.close( rx.popover.close(
rx.box( rx.box(
"删除", "删除",
# 点击事件:删除对话
on_click=lambda: ConversationState.delete_conversation(
conversation.id
),
display="flex",
align_items="center",
justify_content="center",
width="100%", width="100%",
height="24px", height="24px",
padding="4px",
border_radius="var(--prismui-border-radius-1)", border_radius="var(--prismui-border-radius-1)",
line_height="16px", line_height="16px",
font_family="var(--prismui-font-family)",
font_size="var(--prismui-font-size-1)", font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-1)", color="var(--prismui-color-1)",
style={ style={
"_hover": { "_hover": {
"background_color": "var(--prismui-background-color-6)", "background_color": "var(--prismui-background-color-6)",
} }, # 鼠标悬停显示背景颜色
}, },
# 点击事件:删除对话 cursor="pointer",
on_click=lambda: ConversationState.delete_conversation(
item.id
),
), ),
), ),
position="relative", position="relative",
@ -95,62 +124,51 @@ def conversation_history_item(
side_offset=8, side_offset=8,
position="relative", position="relative",
align="center", align="center",
padding="8px", padding="4px 8px",
border_radius="var(--prismui-border-radius-1)", border_radius="var(--prismui-border-radius-1)",
box_shadow="0 2px 12px rgba(0,0,0,0.1)", box_shadow="var(--prismui-box-shadow-6)",
font_family="var(--prismui-font-family)",
overflow="visible", overflow="visible",
), ),
on_open_change=lambda is_shown: ConversationState.set_shown_more_conversation_id(
conversation.id, is_shown
),
open_delay=0, open_delay=0,
), ),
position="absolute",
top="27px",
right="16px",
transform="translateY(-50%)",
z_index=99,
min_width="14px", min_width="14px",
cursor="pointer", cursor="pointer",
style={ opacity=rx.cond(highlight, "1", "0"),
"opacity": rx.cond( transition="opacity 0.18s ease",
is_actived, "1", "0" pointer_events=rx.cond(highlight, "auto", "none"),
), # 若当前会话已激活则不透明,否则透明
"transition": "opacity 0.18s ease",
"pointer_events": rx.cond(is_actived, "auto", "none"),
},
), ),
display="flex", display="flex",
align_items="center", position="relative",
width="100%", align_items="flex-start",
margin_bottom="8px",
cursor="pointer",
),
rx.hstack(
rx.spacer(),
# 创建时间
rx.text(
"1",
margin_bottom="8px",
),
),
),
width="100%", width="100%",
padding="16px", padding="16px",
margin_bottom="8px", margin_bottom="8px",
background=rx.cond( background=rx.cond(
is_actived, highlight,
"linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)", "var(--prismui-background-3)",
"var(--prismui-background-color-1)", "var(--prismui-background-color-1)",
), ),
border_radius="var(--prismui-border-radius-3)", border_radius="var(--prismui-border-radius-3)",
box_shadow=rx.cond(is_actived, "2px 2px 8px #e9e9e9", "none"), box_shadow=rx.cond(highlight, "var(--prismui-box-shadow-7)", "none"),
cursor="pointer",
style={ style={
"&:hover": { "&:hover": {
"background": "linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)", "background": "var(--prismui-background-3)",
"box_shadow": "2px 2px 8px #e9e9e9", "box_shadow": "var(--prismui-box-shadow-7)",
}, # 鼠标悬停背景颜色和阴影 }, # 鼠标悬停背景颜色和阴影
"&:hover > div > div:last-child": { "&:hover > div:last-child": {
"opacity": "1 !important", "opacity": "1",
"pointer_events": "auto !important", "pointer_events": "auto",
}, # 鼠标悬停强制显示右侧三点按钮 }, # 鼠标悬停显示更多按钮
}, },
cursor="pointer",
# 点击事件:切换会话
on_click=ConversationState.switch_conversation(item.id),
) )
@ -223,12 +241,11 @@ def greeting() -> rx.Component:
src="/logo.png", src="/logo.png",
width="64px", width="64px",
height="64px", height="64px",
object_fit="contain",
), ),
rx.box( rx.box(
"棱镜球", "棱镜球",
font_size="32px", font_size="calc(var(--prismui-font-size-3) * 2)",
font_weight="700", font_weight="var(--prismui-font-weight-3)",
letter_spacing="1px", letter_spacing="1px",
), ),
display="flex", display="flex",
@ -248,14 +265,12 @@ def greeting() -> rx.Component:
), ),
width="100%", width="100%",
line_height="1.5", line_height="1.5",
font_size="var(--devui-font-size)",
), ),
display="flex", display="flex",
flex_direction="column", flex_direction="column",
width="100%", width="100%",
align_items="center", align_items="center",
gap="12px", gap="12px",
color="var(--prismui-color-text)",
), ),
# 演示案例 # 演示案例
rx.vstack( rx.vstack(
@ -263,23 +278,22 @@ def greeting() -> rx.Component:
rx.text( rx.text(
"演示案例", "演示案例",
line_height="24px", line_height="24px",
font_size="16px", font_size="var(--prismui-font-size-3)",
font_weight="700", font_weight="var(--prismui-font-weight-3)",
), ),
display="flex", display="flex",
width="100%",
justify_content="space-between", justify_content="space-between",
align_items="center", align_items="center",
width="100%",
margin_bottom="16px", margin_bottom="16px",
), ),
rx.hstack( rx.hstack(
rx.box( rx.box(
"智能客服", "智能客服",
padding="10px 16px", padding="10px 16px",
background_color="var(--devui-dividing-line)", background_color="var(--prismui-background-color-6)",
border_radius="var(--devui-border-radius-full)", border_radius="var(--prismui-border-radius-9)",
font_size="var(--devui-font-size)", color="var(--prismui-color-10)",
color="var(--devui-aide-text)",
cursor="pointer", cursor="pointer",
), ),
display="flex", display="flex",
@ -289,26 +303,26 @@ def greeting() -> rx.Component:
), ),
width="100%", width="100%",
padding="24px", padding="24px",
background_color="var(--devui-base-bg)", background_color="var(--prismui-background-color-1)",
border_radius="24px", border_radius="var(--prismui-border-radius-7)",
), ),
display="flex", display="flex",
flex_direction="column", flex_direction="column",
align_items="center",
gap="24px",
width="100%", width="100%",
min_height="0", min_height="0",
margin="auto 0", margin="auto 0",
align_items="center",
gap="24px",
color="var(--prismui-color-text)", color="var(--prismui-color-text)",
), ),
display="flex", display="flex",
flex="1", flex="1",
flex_direction="column", flex_direction="column",
justify_content="flex-start",
gap="24px",
width="100%", width="100%",
max_width="1200px", max_width="1200px",
padding="0 12px", padding="0 12px",
justify_content="flex-start",
gap="24px",
overflow="auto", overflow="auto",
style={ style={
"&::-webkit-scrollbar": { "&::-webkit-scrollbar": {
@ -318,20 +332,20 @@ def greeting() -> rx.Component:
) )
def thought(thought: Thought): def thought(thought):
""" """
思考节点 思考卡片
:param thought: 思考节点实例 :param thought: 思考实例
:return: Component :return: Component
""" """
return rx.vstack( return rx.vstack(
rx.match( rx.match(
thought.type, thought[1].type,
( (
"thinking", "thinking",
rx.text( rx.text(
thought.content, thought[1].content,
line_height="22px", line_height="22px",
font_size="var(--prismui-font-size-2)", font_size="var(--prismui-font-size-2)",
color="var(--prismui-color-3)", color="var(--prismui-color-3)",
@ -354,28 +368,26 @@ def dialog(dialog: Dialog) -> rx.Component:
is_thinking = dialog.is_thinking is_thinking = dialog.is_thinking
# 思考折叠面板展开状态 # 思考折叠面板展开状态
is_expanded = dialog.is_expanded is_expanded = dialog.is_expanded
# 思考节点
thoughts = dialog.thoughts
return rx.vstack( return rx.vstack(
# 问题和操作栏
rx.hstack( rx.hstack(
rx.spacer(), rx.spacer(),
rx.vstack( rx.vstack(
# 问题 # 用户提示词
rx.text( rx.text(
dialog.user_prompt, dialog.user_prompt,
max_width="600px", max_width="600px",
padding="12px 16px", padding="12px 16px",
background_color="var(--prismui-background-color-3)", background_color="var(--prismui-background-color-3)",
border_radius="var(--prismui-border-radius-3)", border_radius="var(--prismui-border-radius-3)",
line_height="1.5",
color="var(--prismui-color-text)",
word_wrap="break-word", word_wrap="break-word",
word_break="break-all", word_break="break-all",
white_space="pre-line", white_space="pre-line",
color="var(--prismui-color-text)",
), ),
# 操作栏
rx.hstack( rx.hstack(
# 再次发送
rx.box( rx.box(
rx.icon( rx.icon(
"rotate-ccw", "rotate-ccw",
@ -396,11 +408,10 @@ def dialog(dialog: Dialog) -> rx.Component:
margin_top="8px", margin_top="8px",
), ),
), ),
width="100%",
margin_top="8px",
align_items="flex-start", align_items="flex-start",
gap="4px", gap="4px",
font_size="var(--devui-font-size)", width="100%",
margin_top="8px",
), ),
# 思考折叠面板 # 思考折叠面板
rx.vstack( rx.vstack(
@ -437,7 +448,7 @@ def dialog(dialog: Dialog) -> rx.Component:
rx.box( rx.box(
rx.auto_scroll( rx.auto_scroll(
rx.foreach( rx.foreach(
thoughts, dialog.thoughts,
thought, thought,
), ),
width="100%", width="100%",
@ -464,35 +475,43 @@ def dialog(dialog: Dialog) -> rx.Component:
"p": lambda text: rx.text( "p": lambda text: rx.text(
text, text,
margin_bottom="8px", margin_bottom="8px",
line_height="1.5",
word_wrap="break-word",
word_break="break-all",
white_space="pre-line",
),
"h2": lambda text: rx.text(
text,
margin_bottom="8px",
line_height="1.5",
font_weight="var(--prismui-font-weight-3)",
word_wrap="break-word", word_wrap="break-word",
word_break="break-all", word_break="break-all",
white_space="pre-line", white_space="pre-line",
line_height="24px",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-1)",
), ),
"strong": lambda text: rx.text( "strong": lambda text: rx.text(
text, text,
display="inline", display="inline",
font_weight="var(--prismui-font-weight-1)", font_weight="var(--prismui-font-weight-3)",
), ),
"ul": lambda children: rx.vstack( "ul": lambda children: rx.vstack(
children, margin_bottom="8px", gap="4px" children, margin_bottom="8px", gap="4px"
), ),
"li": lambda text: rx.text( "li": lambda text: rx.text(
text, text,
display="list-item",
list_style_type="disc",
list_style_position="outside",
margin="0 0 4px 16px",
line_height="1.5",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-1)",
word_wrap="break-word", word_wrap="break-word",
word_break="break-all", word_break="break-all",
white_space="pre-line", white_space="pre-line",
line_height="24px", style={},
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-1)",
style={
"display": "list-item",
"list_style_type": "disc",
"list_style_position": "inside",
},
), ),
"hr": lambda _: rx.divider(margin="8px 0"),
}, },
width="100%", width="100%",
margin_top="8px", margin_top="8px",
@ -503,21 +522,19 @@ def dialog(dialog: Dialog) -> rx.Component:
) )
def dialogs() -> rx.Component: def dialogs_showing() -> rx.Component:
""" """
对话列表 对话列表展示
:return: Component :return: Component
""" """
# 获取当前会话的对话历史 # 当前会话的对话列表
dialogs = ConversationState.dialogs dialogs = ConversationState.dialogs
# 若对话列表为空则显示欢迎,否则显示对话列表
# 若运行字典为空则品牌图标、名称和介绍、预设用户提示词等,否则遍历运行项
return rx.cond( return rx.cond(
dialogs.length() == 0, dialogs.length() == 0,
# 欢迎 # 欢迎
greeting(), greeting(),
# 遍历对话项
rx.vstack( rx.vstack(
rx.auto_scroll( rx.auto_scroll(
rx.foreach( rx.foreach(
@ -539,54 +556,53 @@ def dialogs() -> rx.Component:
) )
def create_conversation_button() -> rx.Component: def create_conversation() -> rx.Component:
""" """
新建会话按钮 创建会话
:return: Component :return: Component
""" """
return rx.hstack( return rx.hstack(
rx.el.style( rx.el.style(
""" """
.rt-TooltipArrow polygon { .rt-TooltipArrow polygon {
fill: #ffffff !important; fill: var(--prismui-background-color-1) !important;
} }
.rt-TooltipText { .rt-TooltipText {
color: var(--prismui-color-text) !important; font-family: var(--prismui-font-family) !important;
color: var(--prismui-color-1) !important;
opacity: 1 !important; opacity: 1 !important;
} }
""" """
), ),
rx.spacer(), rx.spacer(),
rx.dialog.root( rx.dialog.root(
# 触发事件:点击图标
rx.dialog.trigger( rx.dialog.trigger(
rx.box( rx.box(
rx.tooltip( rx.tooltip(
rx.icon( rx.icon(
"plus", "plus",
size=14, width="14px",
color="var(--prismui-color-text)", height="14px",
style={ style={
"_hover": { "_hover": {
"color": "var(--devui-brand)", "color": "var(--prismui-color-5)",
} }
}, },
), ),
content="新建会话", content="新建会话",
background_color="#ffffff",
box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)",
color="#252b3a",
side="top", side="top",
side_offset=9, side_offset=9,
background_color="var(--prismui-background-color-1)",
box_shadow="var(--prismui-box-shadow-3)",
), ),
display="flex", 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", justify_content="center",
align_items="center", align_items="center",
width="24px",
height="24px",
background_color="var(--prismui-background-color-1)",
box_shadow="var(--prismui-box-shadow-8)",
border_radius="var(--prismui-border-radius-9)",
cursor="pointer", cursor="pointer",
) )
), ),
@ -623,35 +639,34 @@ def create_conversation_button() -> rx.Component:
) )
def operation() -> rx.Component: def user_prompt_sending() -> rx.Component:
""" """
操作区 用户提示词发送
:return: Component :return: Component
""" """
return rx.vstack( return rx.vstack(
# 自定义输入组件
rx.form(
rx.box(
rx.vstack( rx.vstack(
rx.hstack(
rx.text_area( rx.text_area(
name="question", # 值绑定用户提示词
value=ConversationState.user_prompt,
# 输入事件:设置用户提示词
on_change=ConversationState.set_user_prompt,
placeholder="请输入您的问题按Enter换行", placeholder="请输入您的问题按Enter换行",
vertical_align="middle", vertical_align="middle",
width="100%", width="100%",
height="64px", height="64px",
padding="4px 0", padding="4px 0",
background_color="var(--devui-base-bg)", background_color="var(--prismui-background-color-1)",
font_size="var(--devui-font-size)", border="none !important",
color="var(--prismui-color-text)", outline="none !important",
box_shadow="none !important",
style={ style={
".rt-TextAreaInput": {
"border": "none !important", "border": "none !important",
"outline": "none !important", "outline": "none !important",
"boxShadow": "none !important", "box-shadow": "none !important",
},
}, },
),
width="100%",
padding="0 16px",
), ),
rx.hstack( rx.hstack(
rx.spacer(), rx.spacer(),
@ -659,67 +674,69 @@ def operation() -> rx.Component:
rx.icon( rx.icon(
"send", "send",
margin_right="4px", margin_right="4px",
width="12px", width="15px",
height="12px", height="15px",
color="var(--prismui-color-9)",
),
rx.text(
"发送",
line_height="21px",
color="var(--prismui-color-9)",
), ),
rx.text("发送"),
position="relative",
display="inline-flex",
padding="0 12px",
background_color="var(--devui-primary)",
border="none",
border_radius="20pxvar(--devui-border-radius-4)",
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, loading=ConversationState.running_status,
disabled=ConversationState.running_status, # 是否禁用绑定用户提示词发送禁用状态
disabled=ConversationState.is_user_prompt_sending_disabled,
# 点击事件:发送用户提示词
on_click=ConversationState.handle_user_prompt,
display="inline-flex",
position="relative",
justify_content="center",
align_items="center",
padding="0 12px",
background_color=rx.cond(
ConversationState.is_user_prompt_sending_disabled,
"var(--prismui-color-11)",
"var(--prismui-color-5)",
),
border="none",
border_radius="var(--prismui-border-radius-5)",
inline_height="1.5",
font_size="var(--prismui-font-size-2)",
white_space="nowrap",
overflow="hidden",
cursor=rx.cond(
ConversationState.is_user_prompt_sending_disabled,
"not-allowed",
"pointer",
),
), ),
width="100%", width="100%",
justify_content="flex-end", justify_content="flex-end",
align_items="center", align_items="center",
height="32px", height="32px",
padding="0 16px",
), ),
width="100%",
padding="12px 16px",
background_color="var(--prismui-background-color-1)",
border_radius="var(--prismui-border-radius-6)",
box_shadow="var(--prismui-box-shadow-9)",
), ),
style={ # 免责声明
"* textarea::placeholder": { rx.text(
"fontFamily": "var(--font-family)", "内容由大模型生成,无法确保准确性和完整性,仅供参考",
"fontSize": "var(--devui-font-size)", width="100%",
"color": "var(--placeholder)", text_align="center",
"opacity": 1, margin_top="8px",
}, line_height="18px",
}, font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-10)",
), ),
display="flex", display="flex",
flex_direction="column", flex_direction="column",
width="100%", 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", max_width="1200px",
padding="0 12px 12px", padding="0 12px 12px",
align_items="center", border="none",
) )
@ -774,12 +791,12 @@ def conversation() -> rx.Component:
conversation_history(is_conversation_history_shown), conversation_history(is_conversation_history_shown),
rx.box( rx.box(
rx.vstack( rx.vstack(
# 对话历史 # 对话列表展示
dialogs(), dialogs_showing(),
# 新建对话按钮 # 创建对话
create_conversation_button(), create_conversation(),
# 操作区 # 用户提示词发送
operation(), user_prompt_sending(),
display="flex", display="flex",
flex_flow="column", flex_flow="column",
width="100%", width="100%",

View File

@ -224,7 +224,7 @@ def login_popup() -> rx.Component:
"登录解锁更多功能", "登录解锁更多功能",
margin_bottom="24px", margin_bottom="24px",
line_height="32px", line_height="32px",
font_size="var(--prismui-font-size-3)", font_size="var(--prismui-font-size-4)",
font_weight="var(--prismui-font-weight-3)", font_weight="var(--prismui-font-weight-3)",
), ),
# 邮箱输入框 # 邮箱输入框
@ -281,7 +281,11 @@ def login_popup() -> rx.Component:
"var(--prismui-color-4)", "var(--prismui-color-4)",
"var(--prismui-color-5)", "var(--prismui-color-5)",
), ),
cursor="pointer", cursor=rx.cond(
AuthState.is_captcha_sending_disabled,
"not-allowed",
"pointer",
),
), ),
width="100%", width="100%",
height="45px", height="45px",
@ -390,7 +394,7 @@ def login_popup() -> rx.Component:
padding="20px", padding="20px",
background_color="var(--prismui-background-color-1)", background_color="var(--prismui-background-color-1)",
border="var(--prismui-border-2)", border="var(--prismui-border-2)",
border_radius="var(--prismui-border-radius-5)", border_radius="var(--prismui-border-radius-6)",
), ),
rx.text( rx.text(
"加微信好友", "加微信好友",
@ -407,7 +411,7 @@ def login_popup() -> rx.Component:
), ),
background="var(--prismui-background-2)", background="var(--prismui-background-2)",
border="1px solid #2222220f", border="1px solid #2222220f",
border_radius="var(--prismui-border-radius-5)", border_radius="var(--prismui-border-radius-6)",
box_shadow="0 4px 64px 0 #0000001a", box_shadow="0 4px 64px 0 #0000001a",
overflow="hidden", overflow="hidden",
), ),

View File

@ -77,10 +77,15 @@ class ConversationState(rx.State):
# 会话历史展示状态True表示展示False表示隐藏 # 会话历史展示状态True表示展示False表示隐藏
is_conversation_history_shown: bool = False is_conversation_history_shown: bool = False
# 显示更多的会话唯一标识
shown_more_conversation_id: str = ""
# 会话创建状态True表示正在创建False表示未正在创建 # 会话创建状态True表示正在创建False表示未正在创建
is_conversation_creating: bool = False is_conversation_creating: bool = False
# 用户提示词
user_prompt: str = ""
async def resume(self, user_id: str) -> None: async def resume(self, user_id: str) -> None:
""" """
恢复当前用户会话状态 恢复当前用户会话状态
@ -112,22 +117,6 @@ class ConversationState(rx.State):
""" """
self.is_conversation_history_shown = not self.is_conversation_history_shown self.is_conversation_history_shown = not self.is_conversation_history_shown
@staticmethod
def format_conversation_created_at(created_at: datetime) -> str:
"""
格式化会话创建时间
:param created_at: 会话创建时间
:return: 格式化后的会话创建时间
"""
match (datetime.now().date() - created_at.date()).days:
case 0:
formated_created_at = f"今天 {created_at.strftime('%H:%M')}"
case 1:
formated_created_at = f"昨天 {created_at.strftime('%H:%M')}"
case _:
formated_created_at = created_at.strftime("%Y-%m-%d %H:%M")
return formated_created_at
@rx.var @rx.var
def conversation_history_items(self) -> List[ConversationHistoryItem]: def conversation_history_items(self) -> List[ConversationHistoryItem]:
""" """
@ -138,17 +127,34 @@ class ConversationState(rx.State):
for conversation in reversed( for conversation in reversed(
self.conversations.values() self.conversations.values()
): # 按照会话唯一标识倒序排序 ): # 按照会话唯一标识倒序排序
# 格式化会话创建时间
match (datetime.now().date() - conversation.created_at.date()).days:
case 0:
created_at = f"{conversation.created_at.strftime('%H:%M')}"
case 1:
created_at = f"昨天 {conversation.created_at.strftime('%H:%M')}"
case _:
created_at = conversation.created_at.strftime("%Y-%m-%d %H:%M")
items.append( items.append(
ConversationHistoryItem( ConversationHistoryItem(
id=conversation.id, id=conversation.id,
description=conversation.description, description=conversation.description,
created_at=self.format_conversation_created_at( created_at=created_at,
created_at=conversation.created_at
),
) )
) )
return items return items
@rx.event
def set_shown_more_conversation_id(
self, conversation_id: str, is_shown: bool
) -> None:
"""
设置显示更多的会话唯一标识
:return: None
"""
self.shown_more_conversation_id = conversation_id if is_shown else ""
@rx.event @rx.event
async def delete_conversation(self, conversation_id: str) -> None: async def delete_conversation(self, conversation_id: str) -> None:
""" """
@ -212,6 +218,27 @@ class ConversationState(rx.State):
""" """
self.is_conversation_creating = not self.is_conversation_creating self.is_conversation_creating = not self.is_conversation_creating
@rx.event
def set_user_prompt(self, user_prompt: str) -> None:
"""
设置用户提示词
:param user_prompt: 用户提示词
:return: None
"""
self.user_prompt = user_prompt.strip()
@rx.var
def is_user_prompt_sending_disabled(self) -> bool:
"""
用户提示词发送禁用状态
:return: bool
"""
# 当前会话
conversation = self.conversations.get(self.conversation_id)
if not conversation:
return True
return conversation.is_running or not self.user_prompt
@rx.var @rx.var
def running_status(self) -> bool: def running_status(self) -> bool:
""" """
@ -227,7 +254,7 @@ class ConversationState(rx.State):
@rx.var @rx.var
def dialogs(self) -> List[Dialog]: def dialogs(self) -> List[Dialog]:
""" """
获取当前会话的对话列表 当前会话的对话列表
:return: 当前会话的对话列表 :return: 当前会话的对话列表
""" """
# 当前会话 # 当前会话
@ -237,17 +264,18 @@ class ConversationState(rx.State):
return list(conversation.dialogs.values()) return list(conversation.dialogs.values())
@rx.event @rx.event
async def run(self, form_data: dict[str, Any]) -> AsyncGenerator[None]: async def handle_user_prompt(self) -> AsyncGenerator[None]:
""" """
运行 处理用户提示词
:param form_data: 表单数据 :return: AsyncGenerator[None]
:return: AsyncGenerator
""" """
# 解析用户提示词 if not self.user_prompt:
user_prompt = form_data["user_prompt"].strip()
if not user_prompt:
return return
# 清空前端用户提示词
user_prompt = self.user_prompt
self.user_prompt = ""
# 当前会话 # 当前会话
conversation = self.conversations[self.conversation_id] conversation = self.conversations[self.conversation_id]
# 将当前会话的运行状态设置为正在运行 # 将当前会话的运行状态设置为正在运行

View File

@ -4,7 +4,7 @@
""" """
from datetime import datetime, timedelta from datetime import datetime, timedelta
from random import choices from random import choices
from typing import Dict, List, Tuple from typing import Dict, List
from pydantic import TypeAdapter from pydantic import TypeAdapter
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter
@ -106,8 +106,8 @@ class DialogRecord(SQLModel, table=True):
) )
conversation_id: str = Field(..., index=True, description="会话唯一标识") conversation_id: str = Field(..., index=True, description="会话唯一标识")
user_prompt: str = Field(..., description="用户提示词") user_prompt: str = Field(..., description="用户提示词")
thoughts: List[ThoughtRecord] = Field( thoughts: Dict[int, ThoughtRecord] = Field(
default_factory=List, sa_type=JSON, description="思考列表" default_factory=dict, sa_type=JSON, description="思考列表"
) )
result_output: str = Field(default="", description="结果输出") result_output: str = Field(default="", description="结果输出")
@ -123,7 +123,7 @@ class ResultRecord(SQLModel, table=True):
# 思考领域模型类型适配器 # 思考领域模型类型适配器
ThoughtTypeAdapter = TypeAdapter(List[Thought]) ThoughtTypeAdapter = TypeAdapter(Dict[int, Thought])
class DatabaseState(rx.State): class DatabaseState(rx.State):
@ -282,7 +282,7 @@ class DatabaseState(rx.State):
async def create_dialog_record( async def create_dialog_record(
self, conversation_id: str, user_prompt: str self, conversation_id: str, user_prompt: str
) -> Dialog: ) -> Dict[str, Dialog]:
""" """
创建对话记录 创建对话记录
:param conversation_id: 会话唯一标识 :param conversation_id: 会话唯一标识
@ -297,12 +297,12 @@ class DatabaseState(rx.State):
session.add(record) session.add(record)
await session.commit() await session.commit()
await session.refresh(record) await session.refresh(record)
return Dialog(id=record.id, user_prompt=user_prompt) return {record.id: Dialog(id=record.id, user_prompt=user_prompt)}
async def complete_dialog_record( async def complete_dialog_record(
self, self,
id: str, id: str,
thoughts: List[Thought], thoughts: Dict[int, Thought],
result_output: str, result_output: str,
) -> None: ) -> None:
""" """

View File

@ -7,10 +7,12 @@
--prismui-background-color-6: #f2f2f3; --prismui-background-color-6: #f2f2f3;
--prismui-background-1: linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%); --prismui-background-1: linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%);
--prismui-background-2: linear-gradient(180deg, #fffffff2, #f8fafff2 99%); --prismui-background-2: linear-gradient(180deg, #fffffff2, #f8fafff2 99%);
--prismui-background-3: linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd);
--prismui-font-family: HuaweiFont,Helvetica,Arial,PingFangSC-Regular,Hiragino Sans GB,Microsoft YaHei,微软雅黑,Microsoft JhengHei; --prismui-font-family: HuaweiFont,Helvetica,Arial,PingFangSC-Regular,Hiragino Sans GB,Microsoft YaHei,微软雅黑,Microsoft JhengHei;
--prismui-font-size-1: 12px; --prismui-font-size-1: 12px;
--prismui-font-size-2: 14px; --prismui-font-size-2: 14px;
--prismui-font-size-3: 20px; --prismui-font-size-3: 16px;
--prismui-font-size-4: 20px;
--prismui-font-weight-1: normal; --prismui-font-weight-1: normal;
--prismui-font-weight-2: 500; --prismui-font-weight-2: 500;
--prismui-font-weight-3: 700; --prismui-font-weight-3: 700;
@ -21,17 +23,27 @@
--prismui-color-5: #5e7ce0; --prismui-color-5: #5e7ce0;
--prismui-color-6: #f66f6a; --prismui-color-6: #f66f6a;
--prismui-color-7: #f2f2f3; --prismui-color-7: #f2f2f3;
--prismui-color-8: #aeaeae;
--prismui-color-9: #ffffff; --prismui-color-9: #ffffff;
--prismui-color-10: #71757f;
--prismui-color-11: #beccfa;
--prismui-border-1: 1px solid #060a260f; --prismui-border-1: 1px solid #060a260f;
--prismui-border-radius-1: 4px; --prismui-border-radius-1: 4px;
--prismui-border-radius-2: 6px; --prismui-border-radius-2: 6px;
--prismui-border-radius-3: 8px; --prismui-border-radius-3: 8px;
--prismui-border-radius-4: 12px; --prismui-border-radius-4: 12px;
--prismui-border-radius-5: 20px; --prismui-border-radius-5: 16px;
--prismui-border-radius-6: 20px;
--prismui-border-radius-7: 24px;
--prismui-border-radius-9: 100px;
--prismui-box-shadow-1: 0 4px 64px 0 #0000001a; --prismui-box-shadow-1: 0 4px 64px 0 #0000001a;
--prismui-box-shadow-2: 0 4px 12px #0000001a; --prismui-box-shadow-2: 0 4px 12px #0000001a;
--prismui-box-shadow-3: 0 2px 12px 0 #252b3a3d; --prismui-box-shadow-3: 0 2px 12px 0 #252b3a3d;
--prismui-box-shadow-4: 2px 0 4px 0 #d5d5d540; --prismui-box-shadow-4: 2px 0 4px 0 #d5d5d540;
--prismui-box-shadow-5: -2px -2px 4px #0000000d; --prismui-box-shadow-5: -2px -2px 4px #0000000d;
--prismui-box-shadow-6: 0 2px 12px #00000019;
--prismui-box-shadow-7: 2px 2px 8px #e9e9e9;
--prismui-box-shadow-8:0 1px 8px #1919190f;
--prismui-box-shadow-9: 0 1px 8px 0 #1919190f;
background-color: var(--prismui-background-color-1); background-color: var(--prismui-background-color-1);
} }

Binary file not shown.