diff --git a/agent/application/agents/__init__.py b/agent/application/agents/__init__.py new file mode 100644 index 0000000..52476ef --- /dev/null +++ b/agent/application/agents/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from application.agents.chat_agent import chat_agent + + +__all__ = [ + "chat_agent", +] diff --git a/agent/application/agents/chat_agent.py b/agent/application/agents/chat_agent.py new file mode 100644 index 0000000..5a908e6 --- /dev/null +++ b/agent/application/agents/chat_agent.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +开放式对话智能体 +""" +from pydantic_ai import Agent +from pydantic_ai.models.openai import OpenAIChatModel +from pydantic_ai.providers.openai import OpenAIProvider + +instructions: str = """ +# 角色 +专业友好AI助手,结构化解答各类问题。 + +# 输出硬性规则 +1. 全文强制标准Markdown,禁止纯文本;不要额外说明排版格式,直接输出内容; +2. 层级使用 `#/##/###`,列表用 `-` 无序列表或数字有序列表; +3. 代码块用 ```语言名``` 包裹; +4. 重点内容标注 **粗体**/*斜体*; +5. 思考、工具日志仅输出文本,适配前端折叠面板,禁止输出HTML标签; +6. 内容分点拆分,排版整洁适配前端Markdown渲染。 + +# 行文要求 +语言通俗,逻辑完整简洁,无多余废话。 +""" + +# 实例化智能体 +chat_agent: Agent = Agent( + name="chat_agent", + model=OpenAIChatModel( + model_name="deepseek-v4-flash", + provider=OpenAIProvider( + base_url="https://tokenhub.tencentmaas.com/v1", + api_key="sk-D9Y1mCe8VlvNqLuSC4mAjqEwxJ2nW4C0h8a7EPn8kg9RLsHq", + ), + ), + instructions=instructions, + capabilities=None, + output_type=str, + retries=1, +) diff --git a/agent/application/agents/generating_prd_agent.py b/agent/application/agents/generating_prd_agent.py new file mode 100644 index 0000000..2793f8d --- /dev/null +++ b/agent/application/agents/generating_prd_agent.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +生成产品需求文档智能体 +""" +from pydantic_ai import Agent +from pydantic_ai.models.openai import OpenAIChatModel +from pydantic_ai.providers.openai import OpenAIProvider + +instructions: str = """ +# 角色 +专业友好AI助手,结构化解答各类问题。 + +# 输出硬性规则 +1. 全文强制标准Markdown,禁止纯文本;不要额外说明排版格式,直接输出内容; +2. 层级使用 `#/##/###`,列表用 `-` 无序列表或数字有序列表; +3. 代码块用 ```语言名``` 包裹; +4. 重点内容标注 **粗体**/*斜体*; +5. 思考、工具日志仅输出文本,适配前端折叠面板,禁止输出HTML标签; +6. 内容分点拆分,排版整洁适配前端Markdown渲染。 + +# 行文要求 +语言通俗,逻辑完整简洁,无多余废话。 +""" + +# 实例化智能体 +chat_agent: Agent = Agent( + name="chat_agent", + model=OpenAIChatModel( + model_name="deepseek-v4-flash", + provider=OpenAIProvider( + base_url="https://tokenhub.tencentmaas.com/v1", + api_key="sk-D9Y1mCe8VlvNqLuSC4mAjqEwxJ2nW4C0h8a7EPn8kg9RLsHq", + ), + ), + instructions=instructions, + capabilities=None, + output_type=str, + retries=1, +) diff --git a/agent/application/domain_models.py b/agent/application/domain_models.py index 749bee6..d9a6966 100644 --- a/agent/application/domain_models.py +++ b/agent/application/domain_models.py @@ -7,6 +7,7 @@ from typing import Dict, List from pydantic import BaseModel, Field from pydantic_ai._uuid import uuid7 +from enum import StrEnum class Thought(BaseModel): @@ -24,7 +25,7 @@ class Dialog(BaseModel): """ id: str = Field(default_factory=lambda: str(uuid7()), description="对话唯一标识") - user_prompt: str = Field(..., description="用户提示词") + user_prompt: str = Field(default="", description="用户提示词") thoughts: Dict[int, Thought] = Field(default_factory=dict, description="思考列表") result_output: str = Field(default="", description="结果输出") is_thinking: bool = Field( @@ -50,3 +51,12 @@ class Conversation(BaseModel): ..., description="创建时间", ) + + +class TaskType(StrEnum): + """ + 任务类型枚举 + """ + + CHAT = "chat" + GENERATING_PRD = "generating_prd" diff --git a/agent/application/pages/conversation.py b/agent/application/pages/conversation.py index 9d073f0..72e92bc 100644 --- a/agent/application/pages/conversation.py +++ b/agent/application/pages/conversation.py @@ -288,7 +288,8 @@ def greeting_showing() -> rx.Component: ), rx.hstack( rx.box( - "智能客服", + "生成产品需求文档", + on_click=ConversationState.generate_prd, padding="10px 16px", background_color="var(--prismui-background-color-6)", border_radius="var(--prismui-border-radius-9)", @@ -372,96 +373,122 @@ def dialog_showing(item: Tuple[str, Dialog]) -> rx.Component: is_expanded = dialog.is_expanded 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", - word_wrap="break-word", - word_break="break-all", - white_space="pre-line", - ), - rx.hstack( - # 再次发送 - rx.box( - rx.icon( - "rotate-ccw", - width="14px", - height="14px", - color="var(--prismui-color-2)", - ), - padding="4px", - border_radius="var(--prismui-border-radius-1)", - cursor="pointer", - style={ - "_hover": { - "background_color": "var(--prismui-background-color-2)", - } - }, - ), - width="100%", - margin_top="8px", - ), - ), - align_items="flex-start", - gap="4px", - width="100%", - margin_top="8px", - ), - # 思考折叠面板 - rx.vstack( - # 标题 - rx.box( - rx.hstack( + # 用户提示词 + rx.cond( + dialog.user_prompt, + rx.hstack( + rx.spacer(), + rx.vstack( rx.text( - rx.cond(is_thinking, "思考中", "思考完成"), - font_size="var(--prismui-font-size-1)", - color="var(--prismui-color-3)", + dialog.user_prompt, + max_width="600px", + padding="12px 16px", + background_color="var(--prismui-background-color-3)", + border_radius="var(--prismui-border-radius-3)", + line_height="1.5", + word_wrap="break-word", + word_break="break-all", + white_space="pre-line", ), - rx.icon( - "chevron-right", - width="14px", - height="14px", - color="var(--prismui-color-3)", - transform=rx.cond(is_expanded, "rotate(90deg)", "rotate(0deg)"), - transition="transform 0.18s ease-in-out", - ), - align_items="center", - gap="4px", - margin_bottom="8px", - line_height="22px", - ), - # 点击事件,展开/折叠思考折叠面板 - on_click=lambda: ConversationState.toggle_collapse(dialog.id), - cursor="pointer", - ), - # 思考 - rx.box( - rx.box( - rx.auto_scroll( - rx.foreach( - dialog.thoughts, - thought_showing, + rx.hstack( + # 再次发送 + rx.box( + rx.icon( + "rotate-ccw", + width="14px", + height="14px", + color="var(--prismui-color-2)", + ), + padding="4px", + border_radius="var(--prismui-border-radius-1)", + cursor="pointer", + style={ + "_hover": { + "background_color": "var(--prismui-background-color-2)", + } + }, ), width="100%", margin_top="8px", - align_self="start", ), - style={"overflow": "hidden", "min-height": "0"}, ), - display="grid", - grid_template_rows=rx.cond(is_expanded, "1fr", "0fr"), + align_items="flex-start", + gap="4px", width="100%", + margin_top="8px", + ), + rx.fragment(), + ), + # 思考折叠面板 + rx.cond( + dialog.thoughts, + rx.vstack( + # 标题 + rx.box( + rx.hstack( + rx.text( + rx.cond(is_thinking, "思考中", "思考完成"), + font_size="var(--prismui-font-size-1)", + color="var(--prismui-color-3)", + ), + rx.icon( + "chevron-right", + width="14px", + height="14px", + color="var(--prismui-color-3)", + transform=rx.cond( + is_expanded, "rotate(90deg)", "rotate(0deg)" + ), + transition="transform 0.18s ease-in-out", + ), + align_items="center", + gap="4px", + margin_bottom="8px", + line_height="22px", + ), + # 点击事件,展开/折叠思考折叠面板 + on_click=lambda: ConversationState.toggle_collapse(dialog.id), + cursor="pointer", + ), + # 思考 + rx.box( + rx.box( + rx.auto_scroll( + rx.foreach( + dialog.thoughts, + thought_showing, + ), + width="100%", + align_self="start", + ), + style={"overflow": "hidden", "min-height": "0"}, + ), + display="grid", + grid_template_rows=rx.cond(is_expanded, "1fr", "0fr"), + width="100%", + opacity=rx.cond(is_expanded, "1", "0"), + overflow="hidden", + transition="grid-template-rows 0.18s ease-in-out, opacity 0.18s ease-in-out", + ), margin_bottom="8px", - opacity=rx.cond(is_expanded, "1", "0"), - overflow="hidden", - transition="grid-template-rows 0.18s ease-in-out, opacity 0.18s ease-in-out", + ), + # 若思考列表为空且正在运行则显示加载动效 + rx.cond( + ConversationState.running_status, + rx.box( + rx.box( + class_name="loadin", + flex_shrink="0", + ), + display="flex", + justify_content="center", + align_items="center", + width="32px", + height="18px", + margin_bottom="8px", + overflow="hidden", + ), + rx.fragment(), ), ), # 结果输出 @@ -643,58 +670,34 @@ def conversation_creating() -> rx.Component: """ ), rx.spacer(), - rx.dialog.root( - rx.dialog.trigger( - rx.box( - rx.tooltip( - rx.icon( - "plus", - width="14px", - height="14px", - style={ - "_hover": { - "color": "var(--prismui-color-5)", - } - }, - ), - content="新建会话", - side="top", - side_offset=9, - background_color="var(--prismui-background-color-1)", - box_shadow="var(--prismui-box-shadow-3)", - ), - display="flex", - 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", - ) - ), - 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, + rx.box( + rx.tooltip( + rx.icon( + "plus", + on_click=ConversationState.create_conversation, + width="14px", + height="14px", + style={ + "_hover": { + "color": "var(--prismui-color-5)", + } + }, ), - background_color=rx.color("mauve", 1), + content="创建会话", + side="top", + side_offset=9, + background_color="var(--prismui-background-color-1)", + box_shadow="var(--prismui-box-shadow-3)", ), - open=ConversationState.is_conversation_creating, - on_open_change=ConversationState.toggle_conversation_creating, + display="flex", + 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", ), display="flex", justify_content="flex-end", diff --git a/agent/application/states/auth.py b/agent/application/states/auth.py index f679624..fe85e03 100644 --- a/agent/application/states/auth.py +++ b/agent/application/states/auth.py @@ -6,7 +6,7 @@ from asyncio import sleep from email.header import Header from email.mime.text import MIMEText import re -from typing import AsyncGenerator +from typing import AsyncGenerator, Optional from aiosmtplib import SMTP, SMTPException import reflex as rx @@ -50,6 +50,9 @@ class AuthState(rx.State): # 激活的导航按钮 activated_nav_button: str = "conversation" + # 数据库状态 + _db_state: Optional[DatabaseState] = None + @rx.event def reset_login_error_message(self) -> None: """ @@ -91,6 +94,7 @@ class AuthState(rx.State): ): self.login_error_message = "邮箱格式不正确,请重新输入" return False + self.login_error_message = "" return True @@ -123,10 +127,12 @@ class AuthState(rx.State): 发送验证码(后台任务) :return: None """ + if not self._db_state: + return + async with self: # 创建验证码记录并获取验证码 - database_state = await self.get_state(DatabaseState) - captcha = await database_state.create_captcha_record(email=self.email) + captcha = await self._db_state.create_captcha_record(email=self.email) # 构建验证码邮件 message = MIMEText( @@ -166,6 +172,9 @@ class AuthState(rx.State): self.is_captcha_sent = True + # 获取数据库状态 + self._db_state = await self.get_state(DatabaseState) + # 将倒计时和发送验证码事件添加至后台任务队列 yield type(self).countdown() yield type(self).send_captcha_background() @@ -221,19 +230,20 @@ class AuthState(rx.State): self.login_error_message = "请先阅读并同意协议和政策" return + if not self._db_state: + return + self.is_logging_in = True - # 获取数据库状态 - database_state = await self.get_state(DatabaseState) # 核验验证码 - if not await database_state.verify_captcha( + if not await self._db_state.verify_captcha( email=self.email, captcha=self.captcha ): self.is_logging_in = False self.login_error_message = "验证码错误" return # 创建用户记录 - user_id = await database_state.create_user_record(email=self.email) + user_id = await self._db_state.create_user_record(email=self.email) # 恢复当前用户会话状态 conversation_state = await self.get_state(ConversationState) @@ -271,7 +281,6 @@ class AuthState(rx.State): conversation_state.conversations = {} conversation_state.conversation_id = "" conversation_state.is_conversation_history_shown = False - conversation_state.is_conversation_creating = False self.user_id = "" diff --git a/agent/application/states/conversation.py b/agent/application/states/conversation.py index fe6c8ed..e217133 100644 --- a/agent/application/states/conversation.py +++ b/agent/application/states/conversation.py @@ -3,8 +3,8 @@ 会话状态 """ from datetime import datetime -from typing import Any, AsyncGenerator, Dict, List -from pydantic_ai import Agent, ThinkingPartDelta +from typing import Any, AsyncGenerator, Dict, List, Optional +from pydantic_ai import Agent, ThinkingPartDelta, ModelMessage from pydantic_ai.messages import ( FunctionToolCallEvent, FunctionToolResultEvent, @@ -18,48 +18,19 @@ from pydantic_ai.messages import ( ToolCallPart, ToolSearchCallPart, ) -from pydantic_ai.models.openai import OpenAIChatModel -from pydantic_ai.providers.openai import OpenAIProvider + from pydantic_ai.run import AgentRunResultEvent import reflex as rx + from application.states.database import DatabaseState from application.domain_models import ( Conversation, Dialog, Thought, + TaskType, ) - -instructions: str = """ -# 角色 -专业友好AI助手,结构化解答各类问题。 - -# 输出硬性规则 -1. 全文强制标准Markdown,禁止纯文本;不要额外说明排版格式,直接输出内容; -2. 层级使用 `#/##/###`,列表用 `-` 无序列表或数字有序列表; -3. 代码块用 ```语言名``` 包裹; -4. 重点内容标注 **粗体**/*斜体*; -5. 思考、工具日志仅输出文本,适配前端折叠面板,禁止输出HTML标签; -6. 内容分点拆分,排版整洁适配前端Markdown渲染。 - -# 行文要求 -语言通俗,逻辑完整简洁,无多余废话。 -""" - -# 实例化智能体(因无法序列化故剥离出状态管理) -agent: Agent = Agent( - model=OpenAIChatModel( - model_name="deepseek-v4-flash", - provider=OpenAIProvider( - base_url="https://tokenhub.tencentmaas.com/v1", - api_key="sk-D9Y1mCe8VlvNqLuSC4mAjqEwxJ2nW4C0h8a7EPn8kg9RLsHq", - ), - ), - instructions=instructions, - capabilities=None, - output_type=str, - retries=1, -) +from application.agents import chat_agent class ConversationState(rx.State): @@ -79,12 +50,14 @@ class ConversationState(rx.State): # 显示更多的会话唯一标识 shown_more_conversation_id: str = "" - # 会话创建状态,True表示正在创建,False表示未正在创建 - is_conversation_creating: bool = False - + # 任务类型 + task_type: TaskType = TaskType.CHAT # 用户提示词 user_prompt: str = "" + # 数据库状态(私有变量,不予序列化) + _db_state: Optional[DatabaseState] = None + async def resume(self, user_id: str) -> None: """ 恢复当前用户会话状态 @@ -95,18 +68,19 @@ class ConversationState(rx.State): if not self.user_id: return - database_state = await self.get_state(DatabaseState) + self._db_state = await self.get_state(DatabaseState) + # 获取会话字典 - self.conversations = await database_state.get_conversations( + self.conversations = await self._db_state.get_conversations( user_id=self.user_id ) # 若会话字典为空则先创建会话记录再在会话字典中添加会话实例 if not self.conversations: self.conversations.update( - await database_state.create_conversations_record(user_id=self.user_id) + await self._db_state.create_conversations_record(user_id=self.user_id) ) # 将最后一个会话作为当前会话并更新会话唯一标识 - self.conversation_id = next(reversed(self.conversations)) + self.conversation_id = next(reversed(self.conversations.keys())) @rx.event def toggle_conversation_history_shown(self) -> None: @@ -133,9 +107,11 @@ class ConversationState(rx.State): :param conversation_id: 需删除的会话唯一标识 :return: None """ + if not self._db_state: + return + # 先设置会话记录为已删除再在会话字典中删除会话实例 - database_state = await self.get_state(DatabaseState) - await database_state.set_conversations_record_deleted( + await self._db_state.set_conversations_record_deleted( conversation_id=conversation_id ) del self.conversations[conversation_id] @@ -143,12 +119,12 @@ class ConversationState(rx.State): # 删除后,若会话字典为空则先创建会话记录再添加会话实例 if not self.conversations: self.conversations.update( - await database_state.create_conversations_record(user_id=self.user_id) + await self._db_state.create_conversations_record(user_id=self.user_id) ) # 删除后,若当前会话不存在则将最后一个会话作为当前会话并更新会话唯一标识 if self.conversation_id not in self.conversations: - self.conversation_id = next(reversed(self.conversations)) + self.conversation_id = next(reversed(self.conversations.keys())) @rx.event def switch_conversation(self, conversation_id: str) -> None: @@ -160,34 +136,22 @@ class ConversationState(rx.State): self.conversation_id = conversation_id @rx.event - async def create_conversation(self, form_data: Dict[str, Any]) -> None: + async def create_conversation(self) -> None: """ 创建会话 - :param form_data: 表单数据 :return: None """ - # 解析会话描述 - description = form_data["description"].strip() or "新会话" - # 创建会话记录再添加会话实例 - database_state = await self.get_state(DatabaseState) + if not self._db_state: + return + self.conversations.update( - await database_state.create_conversations_record( - user_id=self.user_id, description=description + await self._db_state.create_conversations_record( + user_id=self.user_id, description="新会话" ) ) # 将最后一个会话作为当前会话并更新会话唯一标识 - self.conversation_id = next(reversed(self.conversations)) - - # 会话创建状态设置为未正在创建 - self.is_conversation_creating = False - - @rx.event - def toggle_conversation_creating(self) -> None: - """ - 切换会话创建状态 - """ - self.is_conversation_creating = not self.is_conversation_creating + self.conversation_id = next(reversed(self.conversations.keys())) @rx.event def set_user_prompt(self, user_prompt: str) -> None: @@ -234,46 +198,30 @@ class ConversationState(rx.State): return {} return conversation.dialogs - @rx.event - async def handle_user_prompt(self) -> AsyncGenerator[None]: + async def run_stream_events( + self, + conversation: Conversation, + dialog: Dialog, + agent: Agent, + message_history: Optional[List[ModelMessage]] = None, + ) -> AsyncGenerator[None]: """ - 处理用户提示词 + 运行并处理流式事件 + :param agent: 代理 + :param conversation: 当前会话 + :param dialog: 当前对话 + :param message_history: 消息历史列表 :return: AsyncGenerator[None] """ - if not self.user_prompt: + if not self._db_state: return - # 清空前端用户提示词 - user_prompt = self.user_prompt - self.user_prompt = "" - - # 当前会话 - conversation = self.conversations[self.conversation_id] - # 将运行状态设置为正在运行 - conversation.is_running = True - - database_state = await self.get_state(DatabaseState) - - # 创建对话记录再添加对话实例 - conversation.dialogs.update( - await database_state.create_dialog_record( - conversation_id=self.conversation_id, user_prompt=user_prompt - ) - ) - # 强制更新会话并推送前端 - self.conversations[self.conversation_id] = conversation - yield - - # 将最后一个对话作为当前对话 - dialog = next(reversed(conversation.dialogs.values())) # 初始化工具调用唯一标识和片段索引映射字典 tool_call_ids: Dict[str, int] = {} async with agent.run_stream_events( conversation_id=self.conversation_id, - user_prompt=user_prompt, - message_history=await database_state.get_message_history( - conversation_id=self.conversation_id - ), + user_prompt=dialog.user_prompt, + message_history=message_history, ) as events: async for event in events: match event: @@ -397,25 +345,91 @@ class ConversationState(rx.State): # ========== 智能体运行结果事件 ========== case AgentRunResultEvent(result=result): + # 获取数据库状态 # 补全对话记录 - await database_state.complete_dialog_record( + await self._db_state.complete_dialog_record( id=dialog.id, thoughts=dialog.thoughts, result_output=dialog.result_output, ) # 创建结果记录 - await database_state.create_result_record( + await self._db_state.create_result_record( conversation_id=self.conversation_id, dialog_id=dialog.id, new_messages=result.new_messages(), ) - # 将当前会话的运行状态设置为运行完成 + # 将正在运行设置为否 conversation.is_running = False # 强制更新会话并推送前端 self.conversations[self.conversation_id] = conversation yield + @rx.event + async def handle_user_prompt(self) -> AsyncGenerator[None]: + """ + 处理用户提示词 + :return: AsyncGenerator[None] + """ + if not self.user_prompt: + return + + if not self._db_state: + return + + # 当前会话 + conversation = self.conversations[self.conversation_id] + # 将正在运行设置为是 + conversation.is_running = True + # 创建对话记录再添加对话实例 + conversation.dialogs.update( + await self._db_state.create_dialog_record( + conversation_id=self.conversation_id, user_prompt=self.user_prompt + ) + ) + # 将最后一个对话作为当前对话 + dialog = next(reversed(conversation.dialogs.values())) + # 清空前端用户提示词 + self.user_prompt = "" + # 强制更新会话并推送前端 + self.conversations[self.conversation_id] = conversation + yield + + match self.task_type: + # 开放式对话 + case TaskType.CHAT: + async for _ in self.run_stream_events( + conversation=conversation, + dialog=dialog, + agent=chat_agent, + message_history=await self._db_state.get_message_history( + conversation_id=self.conversation_id + ), + ): + yield + + case TaskType.GENERATING_PRD: + ... + + @rx.event + async def generate_prd(self) -> AsyncGenerator[None]: + """ + 生成产品需求文档 + """ + if not self._db_state: + return + + # 当前会话 + conversation = self.conversations[self.conversation_id] + + # 创建对话记录再添加对话实例 + conversation.dialogs.update( + await self._db_state.create_dialog_record( + conversation_id=self.conversation_id, result_output="请输入产品需求" + ) + ) + yield + @rx.event def toggle_collapse(self, dialog_id: str) -> None: """ diff --git a/agent/application/states/database.py b/agent/application/states/database.py index b1b6701..2758e3e 100644 --- a/agent/application/states/database.py +++ b/agent/application/states/database.py @@ -298,23 +298,29 @@ class DatabaseState(rx.State): await session.commit() async def create_dialog_record( - self, conversation_id: str, user_prompt: str + self, conversation_id: str, user_prompt: str = "", result_output: str = "" ) -> Dict[str, Dialog]: """ 创建对话记录 :param conversation_id: 会话唯一标识 :param user_prompt: 用户提示词 + :param result_output: 结果输出 :return: 创建对话记录的唯一标识 """ async with rx.asession() as session: record = DialogRecord( conversation_id=conversation_id, user_prompt=user_prompt, + result_output=result_output, ) session.add(record) await session.commit() await session.refresh(record) - return {record.id: Dialog(id=record.id, user_prompt=user_prompt)} + return { + record.id: Dialog( + id=record.id, user_prompt=user_prompt, result_output=result_output + ) + } async def complete_dialog_record( self, @@ -366,7 +372,7 @@ class DatabaseState(rx.State): async def get_message_history(self, conversation_id: str) -> List[ModelMessage]: """ - 获取消息历史 + 获取消息历史列表 :param conversation_id: 会话唯一标识 :return: 消息历史 """ diff --git a/agent/assets/styles.css b/agent/assets/styles.css index 2d45ce1..73a043b 100644 --- a/agent/assets/styles.css +++ b/agent/assets/styles.css @@ -59,4 +59,29 @@ pre { pre, pre code { font-family: Consolas, "Microsoft YaHei", sans-serif !important; +} + +.loadin { + width: 6px; + aspect-ratio: 1; + border-radius: 50%; + animation: loadin 0.9s infinite ease-in-out; +} +@keyframes loadin { + 0% { + box-shadow: 12px 0 var(--prismui-color-5), -12px 0 var(--prismui-color-9); + background: var(--prismui-color-5); + } + 33% { + box-shadow: 12px 0 var(--prismui-color-5), -12px 0 var(--prismui-color-9); + background: var(--prismui-color-9); + } + 66% { + box-shadow: 12px 0 var(--prismui-color-9), -12px 0 var(--prismui-color-5); + background: var(--prismui-color-9); + } + 100% { + box-shadow: 12px 0 var(--prismui-color-9), -12px 0 var(--prismui-color-5); + background: var(--prismui-color-5); + } } \ No newline at end of file diff --git a/agent/database.db b/agent/database.db index b4433d4..9efe3dd 100644 Binary files a/agent/database.db and b/agent/database.db differ diff --git a/agent/reflex.lock/bun.lock b/agent/reflex.lock/bun.lock index e8e3f43..25f70a0 100644 --- a/agent/reflex.lock/bun.lock +++ b/agent/reflex.lock/bun.lock @@ -5,7 +5,6 @@ "": { "name": "reflex", "dependencies": { - "@radix-ui/react-form": "0.1.8", "@radix-ui/themes": "3.3.0", "@react-router/node": "7.15.0", "isbot": "5.1.40", diff --git a/agent/reflex.lock/package.json b/agent/reflex.lock/package.json index 47cf4cd..bea6203 100644 --- a/agent/reflex.lock/package.json +++ b/agent/reflex.lock/package.json @@ -6,7 +6,6 @@ "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",