This commit is contained in:
parent
3f52e7bd61
commit
998a0fc572
|
|
@ -1,8 +1,8 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: a7968da75b72
|
||||
Revision ID: 4269426de639
|
||||
Revises:
|
||||
Create Date: 2026-07-26 23:23:19.558164
|
||||
Create Date: 2026-07-27 10:10:56.902466
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
|
@ -12,7 +12,7 @@ import sqlalchemy as sa
|
|||
import sqlmodel
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'a7968da75b72'
|
||||
revision: str = '4269426de639'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
|
@ -28,7 +28,9 @@ app = rx.App(
|
|||
"outline": "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)",
|
||||
"opacity": 1,
|
||||
}, # 覆盖占位符样式
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
领域模型
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Dict, List
|
||||
from pydantic_ai._uuid import uuid7
|
||||
from typing import Dict
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from dataclasses import dataclass
|
||||
from pydantic_ai._uuid import uuid7
|
||||
|
||||
|
||||
class Thought(BaseModel):
|
||||
|
|
@ -25,7 +25,7 @@ class Dialog(BaseModel):
|
|||
|
||||
id: str = Field(default_factory=lambda: str(uuid7()), 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="结果输出")
|
||||
is_thinking: bool = Field(
|
||||
default=False, description="正在思考,True 表示正在思考,False 表示未正在思考"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
会话页面
|
||||
"""
|
||||
import reflex as rx
|
||||
from typing import Dict
|
||||
|
||||
from application.domain_models import (
|
||||
Conversation,
|
||||
|
|
@ -14,36 +15,61 @@ from application.states import ConversationState, AuthState
|
|||
|
||||
|
||||
def conversation_history_item(
|
||||
item: ConversationHistoryItem,
|
||||
conversation: ConversationHistoryItem,
|
||||
) -> rx.Component:
|
||||
"""
|
||||
会话历史项
|
||||
:param item: 会话历史项
|
||||
:param conversation: 会话历史项
|
||||
: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(
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
# 对话描述
|
||||
rx.text(
|
||||
item.description,
|
||||
conversation.description,
|
||||
flex=1,
|
||||
height="22px",
|
||||
line_height="22px",
|
||||
padding_right="4px",
|
||||
padding_right="18px",
|
||||
color="var(--prismui-color-2)",
|
||||
overflow="hidden",
|
||||
text_overflow="ellipsis",
|
||||
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.popover.root(
|
||||
# 触发事件:点击更多按钮
|
||||
rx.popover.trigger(
|
||||
rx.box(
|
||||
rx.icon(
|
||||
|
|
@ -55,12 +81,11 @@ def conversation_history_item(
|
|||
cursor="pointer",
|
||||
)
|
||||
),
|
||||
# 气泡卡片
|
||||
rx.popover.content(
|
||||
rx.vstack(
|
||||
rx.box(
|
||||
position="absolute",
|
||||
top="-12px",
|
||||
top="-6px",
|
||||
left="50%",
|
||||
transform="translateX(-50%) rotate(45deg)",
|
||||
width="8px",
|
||||
|
|
@ -70,22 +95,26 @@ def conversation_history_item(
|
|||
rx.popover.close(
|
||||
rx.box(
|
||||
"删除",
|
||||
# 点击事件:删除对话
|
||||
on_click=lambda: ConversationState.delete_conversation(
|
||||
conversation.id
|
||||
),
|
||||
display="flex",
|
||||
align_items="center",
|
||||
justify_content="center",
|
||||
width="100%",
|
||||
height="24px",
|
||||
padding="4px",
|
||||
border_radius="var(--prismui-border-radius-1)",
|
||||
line_height="16px",
|
||||
font_family="var(--prismui-font-family)",
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-1)",
|
||||
style={
|
||||
"_hover": {
|
||||
"background_color": "var(--prismui-background-color-6)",
|
||||
}
|
||||
}, # 鼠标悬停显示背景颜色
|
||||
},
|
||||
# 点击事件:删除对话
|
||||
on_click=lambda: ConversationState.delete_conversation(
|
||||
item.id
|
||||
),
|
||||
cursor="pointer",
|
||||
),
|
||||
),
|
||||
position="relative",
|
||||
|
|
@ -95,62 +124,51 @@ def conversation_history_item(
|
|||
side_offset=8,
|
||||
position="relative",
|
||||
align="center",
|
||||
padding="8px",
|
||||
padding="4px 8px",
|
||||
border_radius="var(--prismui-border-radius-1)",
|
||||
box_shadow="0 2px 12px rgba(0,0,0,0.1)",
|
||||
font_family="var(--prismui-font-family)",
|
||||
box_shadow="var(--prismui-box-shadow-6)",
|
||||
overflow="visible",
|
||||
),
|
||||
on_open_change=lambda is_shown: ConversationState.set_shown_more_conversation_id(
|
||||
conversation.id, is_shown
|
||||
),
|
||||
open_delay=0,
|
||||
),
|
||||
position="absolute",
|
||||
top="27px",
|
||||
right="16px",
|
||||
transform="translateY(-50%)",
|
||||
z_index=99,
|
||||
min_width="14px",
|
||||
cursor="pointer",
|
||||
style={
|
||||
"opacity": rx.cond(
|
||||
is_actived, "1", "0"
|
||||
), # 若当前会话已激活则不透明,否则透明
|
||||
"transition": "opacity 0.18s ease",
|
||||
"pointer_events": rx.cond(is_actived, "auto", "none"),
|
||||
},
|
||||
opacity=rx.cond(highlight, "1", "0"),
|
||||
transition="opacity 0.18s ease",
|
||||
pointer_events=rx.cond(highlight, "auto", "none"),
|
||||
),
|
||||
display="flex",
|
||||
align_items="center",
|
||||
width="100%",
|
||||
margin_bottom="8px",
|
||||
cursor="pointer",
|
||||
),
|
||||
rx.hstack(
|
||||
rx.spacer(),
|
||||
# 创建时间
|
||||
rx.text(
|
||||
"1",
|
||||
margin_bottom="8px",
|
||||
),
|
||||
),
|
||||
),
|
||||
position="relative",
|
||||
align_items="flex-start",
|
||||
width="100%",
|
||||
padding="16px",
|
||||
margin_bottom="8px",
|
||||
background=rx.cond(
|
||||
is_actived,
|
||||
"linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)",
|
||||
highlight,
|
||||
"var(--prismui-background-3)",
|
||||
"var(--prismui-background-color-1)",
|
||||
),
|
||||
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={
|
||||
"&:hover": {
|
||||
"background": "linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)",
|
||||
"box_shadow": "2px 2px 8px #e9e9e9",
|
||||
"background": "var(--prismui-background-3)",
|
||||
"box_shadow": "var(--prismui-box-shadow-7)",
|
||||
}, # 鼠标悬停背景颜色和阴影
|
||||
"&:hover > div > div:last-child": {
|
||||
"opacity": "1 !important",
|
||||
"pointer_events": "auto !important",
|
||||
}, # 鼠标悬停强制显示右侧三点按钮
|
||||
"&:hover > div:last-child": {
|
||||
"opacity": "1",
|
||||
"pointer_events": "auto",
|
||||
}, # 鼠标悬停显示更多按钮
|
||||
},
|
||||
cursor="pointer",
|
||||
# 点击事件:切换会话
|
||||
on_click=ConversationState.switch_conversation(item.id),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -223,12 +241,11 @@ def greeting() -> rx.Component:
|
|||
src="/logo.png",
|
||||
width="64px",
|
||||
height="64px",
|
||||
object_fit="contain",
|
||||
),
|
||||
rx.box(
|
||||
"棱镜球",
|
||||
font_size="32px",
|
||||
font_weight="700",
|
||||
font_size="calc(var(--prismui-font-size-3) * 2)",
|
||||
font_weight="var(--prismui-font-weight-3)",
|
||||
letter_spacing="1px",
|
||||
),
|
||||
display="flex",
|
||||
|
|
@ -248,14 +265,12 @@ def greeting() -> rx.Component:
|
|||
),
|
||||
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(
|
||||
|
|
@ -263,23 +278,22 @@ def greeting() -> rx.Component:
|
|||
rx.text(
|
||||
"演示案例",
|
||||
line_height="24px",
|
||||
font_size="16px",
|
||||
font_weight="700",
|
||||
font_size="var(--prismui-font-size-3)",
|
||||
font_weight="var(--prismui-font-weight-3)",
|
||||
),
|
||||
display="flex",
|
||||
width="100%",
|
||||
justify_content="space-between",
|
||||
align_items="center",
|
||||
width="100%",
|
||||
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)",
|
||||
background_color="var(--prismui-background-color-6)",
|
||||
border_radius="var(--prismui-border-radius-9)",
|
||||
color="var(--prismui-color-10)",
|
||||
cursor="pointer",
|
||||
),
|
||||
display="flex",
|
||||
|
|
@ -289,26 +303,26 @@ def greeting() -> rx.Component:
|
|||
),
|
||||
width="100%",
|
||||
padding="24px",
|
||||
background_color="var(--devui-base-bg)",
|
||||
border_radius="24px",
|
||||
background_color="var(--prismui-background-color-1)",
|
||||
border_radius="var(--prismui-border-radius-7)",
|
||||
),
|
||||
display="flex",
|
||||
flex_direction="column",
|
||||
align_items="center",
|
||||
gap="24px",
|
||||
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",
|
||||
justify_content="flex-start",
|
||||
gap="24px",
|
||||
width="100%",
|
||||
max_width="1200px",
|
||||
padding="0 12px",
|
||||
justify_content="flex-start",
|
||||
gap="24px",
|
||||
overflow="auto",
|
||||
style={
|
||||
"&::-webkit-scrollbar": {
|
||||
|
|
@ -318,20 +332,20 @@ def greeting() -> rx.Component:
|
|||
)
|
||||
|
||||
|
||||
def thought(thought: Thought):
|
||||
def thought(thought):
|
||||
"""
|
||||
思考节点
|
||||
思考卡片
|
||||
|
||||
:param thought: 思考节点实例
|
||||
:param thought: 思考实例
|
||||
:return: Component
|
||||
"""
|
||||
return rx.vstack(
|
||||
rx.match(
|
||||
thought.type,
|
||||
thought[1].type,
|
||||
(
|
||||
"thinking",
|
||||
rx.text(
|
||||
thought.content,
|
||||
thought[1].content,
|
||||
line_height="22px",
|
||||
font_size="var(--prismui-font-size-2)",
|
||||
color="var(--prismui-color-3)",
|
||||
|
|
@ -354,28 +368,26 @@ def dialog(dialog: Dialog) -> rx.Component:
|
|||
is_thinking = dialog.is_thinking
|
||||
# 思考折叠面板展开状态
|
||||
is_expanded = dialog.is_expanded
|
||||
# 思考节点
|
||||
thoughts = dialog.thoughts
|
||||
|
||||
return rx.vstack(
|
||||
# 问题和操作栏
|
||||
rx.hstack(
|
||||
rx.spacer(),
|
||||
rx.vstack(
|
||||
# 问题
|
||||
# 用户提示词
|
||||
rx.text(
|
||||
dialog.user_prompt,
|
||||
max_width="600px",
|
||||
padding="12px 16px",
|
||||
background_color="var(--prismui-background-color-3)",
|
||||
border_radius="var(--prismui-border-radius-3)",
|
||||
line_height="1.5",
|
||||
color="var(--prismui-color-text)",
|
||||
word_wrap="break-word",
|
||||
word_break="break-all",
|
||||
white_space="pre-line",
|
||||
color="var(--prismui-color-text)",
|
||||
),
|
||||
# 操作栏
|
||||
rx.hstack(
|
||||
# 再次发送
|
||||
rx.box(
|
||||
rx.icon(
|
||||
"rotate-ccw",
|
||||
|
|
@ -396,11 +408,10 @@ def dialog(dialog: Dialog) -> rx.Component:
|
|||
margin_top="8px",
|
||||
),
|
||||
),
|
||||
width="100%",
|
||||
margin_top="8px",
|
||||
align_items="flex-start",
|
||||
gap="4px",
|
||||
font_size="var(--devui-font-size)",
|
||||
width="100%",
|
||||
margin_top="8px",
|
||||
),
|
||||
# 思考折叠面板
|
||||
rx.vstack(
|
||||
|
|
@ -437,7 +448,7 @@ def dialog(dialog: Dialog) -> rx.Component:
|
|||
rx.box(
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
thoughts,
|
||||
dialog.thoughts,
|
||||
thought,
|
||||
),
|
||||
width="100%",
|
||||
|
|
@ -464,35 +475,43 @@ def dialog(dialog: Dialog) -> rx.Component:
|
|||
"p": lambda text: rx.text(
|
||||
text,
|
||||
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_break="break-all",
|
||||
white_space="pre-line",
|
||||
line_height="24px",
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-1)",
|
||||
),
|
||||
"strong": lambda text: rx.text(
|
||||
text,
|
||||
display="inline",
|
||||
font_weight="var(--prismui-font-weight-1)",
|
||||
font_weight="var(--prismui-font-weight-3)",
|
||||
),
|
||||
"ul": lambda children: rx.vstack(
|
||||
children, margin_bottom="8px", gap="4px"
|
||||
),
|
||||
"li": lambda text: rx.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_break="break-all",
|
||||
white_space="pre-line",
|
||||
line_height="24px",
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-1)",
|
||||
style={
|
||||
"display": "list-item",
|
||||
"list_style_type": "disc",
|
||||
"list_style_position": "inside",
|
||||
},
|
||||
style={},
|
||||
),
|
||||
"hr": lambda _: rx.divider(margin="8px 0"),
|
||||
},
|
||||
width="100%",
|
||||
margin_top="8px",
|
||||
|
|
@ -503,21 +522,19 @@ def dialog(dialog: Dialog) -> rx.Component:
|
|||
)
|
||||
|
||||
|
||||
def dialogs() -> rx.Component:
|
||||
def dialogs_showing() -> rx.Component:
|
||||
"""
|
||||
对话列表
|
||||
对话列表展示
|
||||
:return: Component
|
||||
"""
|
||||
|
||||
# 获取当前会话的对话历史
|
||||
# 当前会话的对话列表
|
||||
dialogs = ConversationState.dialogs
|
||||
|
||||
# 若运行字典为空则品牌图标、名称和介绍、预设用户提示词等,否则遍历运行项
|
||||
# 若对话列表为空则显示欢迎,否则显示对话列表
|
||||
return rx.cond(
|
||||
dialogs.length() == 0,
|
||||
# 欢迎
|
||||
greeting(),
|
||||
# 遍历对话项
|
||||
rx.vstack(
|
||||
rx.auto_scroll(
|
||||
rx.foreach(
|
||||
|
|
@ -539,54 +556,53 @@ def dialogs() -> rx.Component:
|
|||
)
|
||||
|
||||
|
||||
def create_conversation_button() -> rx.Component:
|
||||
def create_conversation() -> rx.Component:
|
||||
"""
|
||||
新建会话按钮
|
||||
创建会话
|
||||
:return: Component
|
||||
"""
|
||||
return rx.hstack(
|
||||
rx.el.style(
|
||||
"""
|
||||
.rt-TooltipArrow polygon {
|
||||
fill: #ffffff !important;
|
||||
fill: var(--prismui-background-color-1) !important;
|
||||
}
|
||||
.rt-TooltipText {
|
||||
color: var(--prismui-color-text) !important;
|
||||
font-family: var(--prismui-font-family) !important;
|
||||
color: var(--prismui-color-1) !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)",
|
||||
width="14px",
|
||||
height="14px",
|
||||
style={
|
||||
"_hover": {
|
||||
"color": "var(--devui-brand)",
|
||||
"color": "var(--prismui-color-5)",
|
||||
}
|
||||
},
|
||||
),
|
||||
content="新建会话",
|
||||
background_color="#ffffff",
|
||||
box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)",
|
||||
color="#252b3a",
|
||||
side="top",
|
||||
side_offset=9,
|
||||
background_color="var(--prismui-background-color-1)",
|
||||
box_shadow="var(--prismui-box-shadow-3)",
|
||||
),
|
||||
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",
|
||||
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",
|
||||
)
|
||||
),
|
||||
|
|
@ -623,35 +639,34 @@ def create_conversation_button() -> rx.Component:
|
|||
)
|
||||
|
||||
|
||||
def operation() -> rx.Component:
|
||||
def user_prompt_sending() -> rx.Component:
|
||||
"""
|
||||
操作区
|
||||
用户提示词发送
|
||||
:return: Component
|
||||
"""
|
||||
return rx.vstack(
|
||||
# 自定义输入组件
|
||||
rx.form(
|
||||
rx.box(
|
||||
rx.vstack(
|
||||
rx.hstack(
|
||||
rx.text_area(
|
||||
name="question",
|
||||
# 值绑定用户提示词
|
||||
value=ConversationState.user_prompt,
|
||||
# 输入事件:设置用户提示词
|
||||
on_change=ConversationState.set_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(--prismui-color-text)",
|
||||
background_color="var(--prismui-background-color-1)",
|
||||
border="none !important",
|
||||
outline="none !important",
|
||||
box_shadow="none !important",
|
||||
style={
|
||||
".rt-TextAreaInput": {
|
||||
"border": "none !important",
|
||||
"outline": "none !important",
|
||||
"boxShadow": "none !important",
|
||||
"box-shadow": "none !important",
|
||||
},
|
||||
},
|
||||
),
|
||||
width="100%",
|
||||
padding="0 16px",
|
||||
),
|
||||
rx.hstack(
|
||||
rx.spacer(),
|
||||
|
|
@ -659,67 +674,69 @@ def operation() -> rx.Component:
|
|||
rx.icon(
|
||||
"send",
|
||||
margin_right="4px",
|
||||
width="12px",
|
||||
height="12px",
|
||||
width="15px",
|
||||
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,
|
||||
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%",
|
||||
justify_content="flex-end",
|
||||
align_items="center",
|
||||
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": {
|
||||
"fontFamily": "var(--font-family)",
|
||||
"fontSize": "var(--devui-font-size)",
|
||||
"color": "var(--placeholder)",
|
||||
"opacity": 1,
|
||||
},
|
||||
},
|
||||
# 免责声明
|
||||
rx.text(
|
||||
"内容由大模型生成,无法确保准确性和完整性,仅供参考",
|
||||
width="100%",
|
||||
text_align="center",
|
||||
margin_top="8px",
|
||||
line_height="18px",
|
||||
font_size="var(--prismui-font-size-1)",
|
||||
color="var(--prismui-color-10)",
|
||||
),
|
||||
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",
|
||||
border="none",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -774,12 +791,12 @@ def conversation() -> rx.Component:
|
|||
conversation_history(is_conversation_history_shown),
|
||||
rx.box(
|
||||
rx.vstack(
|
||||
# 对话历史
|
||||
dialogs(),
|
||||
# 新建对话按钮
|
||||
create_conversation_button(),
|
||||
# 操作区
|
||||
operation(),
|
||||
# 对话列表展示
|
||||
dialogs_showing(),
|
||||
# 创建对话
|
||||
create_conversation(),
|
||||
# 用户提示词发送
|
||||
user_prompt_sending(),
|
||||
display="flex",
|
||||
flex_flow="column",
|
||||
width="100%",
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ def login_popup() -> rx.Component:
|
|||
"登录解锁更多功能",
|
||||
margin_bottom="24px",
|
||||
line_height="32px",
|
||||
font_size="var(--prismui-font-size-3)",
|
||||
font_size="var(--prismui-font-size-4)",
|
||||
font_weight="var(--prismui-font-weight-3)",
|
||||
),
|
||||
# 邮箱输入框
|
||||
|
|
@ -281,7 +281,11 @@ def login_popup() -> rx.Component:
|
|||
"var(--prismui-color-4)",
|
||||
"var(--prismui-color-5)",
|
||||
),
|
||||
cursor="pointer",
|
||||
cursor=rx.cond(
|
||||
AuthState.is_captcha_sending_disabled,
|
||||
"not-allowed",
|
||||
"pointer",
|
||||
),
|
||||
),
|
||||
width="100%",
|
||||
height="45px",
|
||||
|
|
@ -390,7 +394,7 @@ def login_popup() -> rx.Component:
|
|||
padding="20px",
|
||||
background_color="var(--prismui-background-color-1)",
|
||||
border="var(--prismui-border-2)",
|
||||
border_radius="var(--prismui-border-radius-5)",
|
||||
border_radius="var(--prismui-border-radius-6)",
|
||||
),
|
||||
rx.text(
|
||||
"加微信好友",
|
||||
|
|
@ -407,7 +411,7 @@ def login_popup() -> rx.Component:
|
|||
),
|
||||
background="var(--prismui-background-2)",
|
||||
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",
|
||||
overflow="hidden",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -77,10 +77,15 @@ class ConversationState(rx.State):
|
|||
|
||||
# 会话历史展示状态,True表示展示,False表示隐藏
|
||||
is_conversation_history_shown: bool = False
|
||||
# 显示更多的会话唯一标识
|
||||
shown_more_conversation_id: str = ""
|
||||
|
||||
# 会话创建状态,True表示正在创建,False表示未正在创建
|
||||
is_conversation_creating: bool = False
|
||||
|
||||
# 用户提示词
|
||||
user_prompt: str = ""
|
||||
|
||||
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
|
||||
|
||||
@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
|
||||
def conversation_history_items(self) -> List[ConversationHistoryItem]:
|
||||
"""
|
||||
|
|
@ -138,17 +127,34 @@ class ConversationState(rx.State):
|
|||
for conversation in reversed(
|
||||
self.conversations.values()
|
||||
): # 按照会话唯一标识倒序排序
|
||||
# 格式化会话创建时间
|
||||
match (datetime.now().date() - conversation.created_at.date()).days:
|
||||
case 0:
|
||||
created_at = f"{conversation.created_at.strftime('%H:%M')}"
|
||||
case 1:
|
||||
created_at = f"昨天 {conversation.created_at.strftime('%H:%M')}"
|
||||
case _:
|
||||
created_at = conversation.created_at.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
items.append(
|
||||
ConversationHistoryItem(
|
||||
id=conversation.id,
|
||||
description=conversation.description,
|
||||
created_at=self.format_conversation_created_at(
|
||||
created_at=conversation.created_at
|
||||
),
|
||||
created_at=created_at,
|
||||
)
|
||||
)
|
||||
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
|
||||
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
|
||||
|
||||
@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
|
||||
def running_status(self) -> bool:
|
||||
"""
|
||||
|
|
@ -227,7 +254,7 @@ class ConversationState(rx.State):
|
|||
@rx.var
|
||||
def dialogs(self) -> List[Dialog]:
|
||||
"""
|
||||
获取当前会话的对话列表
|
||||
当前会话的对话列表
|
||||
:return: 当前会话的对话列表
|
||||
"""
|
||||
# 当前会话
|
||||
|
|
@ -237,17 +264,18 @@ class ConversationState(rx.State):
|
|||
return list(conversation.dialogs.values())
|
||||
|
||||
@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
|
||||
处理用户提示词
|
||||
:return: AsyncGenerator[None]
|
||||
"""
|
||||
# 解析用户提示词
|
||||
user_prompt = form_data["user_prompt"].strip()
|
||||
if not user_prompt:
|
||||
if not self.user_prompt:
|
||||
return
|
||||
|
||||
# 清空前端用户提示词
|
||||
user_prompt = self.user_prompt
|
||||
self.user_prompt = ""
|
||||
|
||||
# 当前会话
|
||||
conversation = self.conversations[self.conversation_id]
|
||||
# 将当前会话的运行状态设置为正在运行
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from random import choices
|
||||
from typing import Dict, List, Tuple
|
||||
from typing import Dict, List
|
||||
|
||||
from pydantic import TypeAdapter
|
||||
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter
|
||||
|
|
@ -106,8 +106,8 @@ class DialogRecord(SQLModel, table=True):
|
|||
)
|
||||
conversation_id: str = Field(..., index=True, description="会话唯一标识")
|
||||
user_prompt: str = Field(..., description="用户提示词")
|
||||
thoughts: List[ThoughtRecord] = Field(
|
||||
default_factory=List, sa_type=JSON, description="思考列表"
|
||||
thoughts: Dict[int, ThoughtRecord] = Field(
|
||||
default_factory=dict, sa_type=JSON, 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):
|
||||
|
|
@ -282,7 +282,7 @@ class DatabaseState(rx.State):
|
|||
|
||||
async def create_dialog_record(
|
||||
self, conversation_id: str, user_prompt: str
|
||||
) -> Dialog:
|
||||
) -> Dict[str, Dialog]:
|
||||
"""
|
||||
创建对话记录
|
||||
:param conversation_id: 会话唯一标识
|
||||
|
|
@ -297,12 +297,12 @@ class DatabaseState(rx.State):
|
|||
session.add(record)
|
||||
await session.commit()
|
||||
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(
|
||||
self,
|
||||
id: str,
|
||||
thoughts: List[Thought],
|
||||
thoughts: Dict[int, Thought],
|
||||
result_output: str,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
--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-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-size-1: 12px;
|
||||
--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-2: 500;
|
||||
--prismui-font-weight-3: 700;
|
||||
|
|
@ -21,17 +23,27 @@
|
|||
--prismui-color-5: #5e7ce0;
|
||||
--prismui-color-6: #f66f6a;
|
||||
--prismui-color-7: #f2f2f3;
|
||||
--prismui-color-8: #aeaeae;
|
||||
--prismui-color-9: #ffffff;
|
||||
--prismui-color-10: #71757f;
|
||||
--prismui-color-11: #beccfa;
|
||||
--prismui-border-1: 1px solid #060a260f;
|
||||
--prismui-border-radius-1: 4px;
|
||||
--prismui-border-radius-2: 6px;
|
||||
--prismui-border-radius-3: 8px;
|
||||
--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-2: 0 4px 12px #0000001a;
|
||||
--prismui-box-shadow-3: 0 2px 12px 0 #252b3a3d;
|
||||
--prismui-box-shadow-4: 2px 0 4px 0 #d5d5d540;
|
||||
--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);
|
||||
}
|
||||
Binary file not shown.
Loading…
Reference in New Issue