This commit is contained in:
parent
345b443975
commit
b2d78e73b3
|
|
@ -1,21 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from application.models.tables import (
|
||||
Captchas,
|
||||
Conversations,
|
||||
Dialogs,
|
||||
RunResults,
|
||||
Users,
|
||||
CaptchaRecord,
|
||||
ConversationRecord,
|
||||
DialogRecord,
|
||||
RunResultRecord,
|
||||
UserRecord,
|
||||
)
|
||||
from application.models.domains import (
|
||||
Conversation,
|
||||
Dialog,
|
||||
ThoughtNode,
|
||||
)
|
||||
from application.models.domains import Conversation, Dialog, ThoughtNode
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Captchas",
|
||||
"CaptchaRecord",
|
||||
"Conversation",
|
||||
"Conversations",
|
||||
"ConversationRecord",
|
||||
"Dialog",
|
||||
"Dialogs",
|
||||
"RunResults",
|
||||
"DialogRecord",
|
||||
"RunResultRecord",
|
||||
"ThoughtNode",
|
||||
"Users",
|
||||
"UserRecord",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
领域模型
|
||||
reflex运行时领域模型
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
|
|
|||
|
|
@ -9,21 +9,26 @@ from pydantic_ai._uuid import uuid7
|
|||
from sqlmodel import Field, JSON, SQLModel
|
||||
|
||||
|
||||
class CaptchaTable(SQLModel, table=True):
|
||||
class CaptchaRecord(SQLModel, table=True):
|
||||
"""
|
||||
验证码表
|
||||
验证码记录表
|
||||
邮箱和创建时间联合作为主键
|
||||
"""
|
||||
|
||||
email: str = Field(..., primary_key=True, description="邮箱")
|
||||
captcha: str = Field(
|
||||
default_factory=lambda: "".join(choices("0123456789", k=6)),
|
||||
primary_key=True,
|
||||
description="验证码",
|
||||
)
|
||||
is_valid: bool = Field(
|
||||
default=True,
|
||||
index=True,
|
||||
description="验证码有效:True 表示有效,False 表示无效",
|
||||
)
|
||||
is_verified: bool = Field(
|
||||
default=False,
|
||||
index=True,
|
||||
description="已核验:True 表示已核验,False 表示未核验",
|
||||
description="验证码已核验:True 表示已核验,False 表示未核验",
|
||||
)
|
||||
expired_at: datetime = Field(
|
||||
default_factory=lambda: datetime.now() + timedelta(minutes=30),
|
||||
|
|
@ -33,7 +38,7 @@ class CaptchaTable(SQLModel, table=True):
|
|||
|
||||
|
||||
# 用户表
|
||||
class UserTable(SQLModel, table=True):
|
||||
class UserRecord(SQLModel, table=True):
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid7()),
|
||||
primary_key=True,
|
||||
|
|
@ -43,7 +48,7 @@ class UserTable(SQLModel, table=True):
|
|||
|
||||
|
||||
# 会话表
|
||||
class ConversationTable(SQLModel, table=True):
|
||||
class ConversationRecord(SQLModel, table=True):
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid7()),
|
||||
primary_key=True,
|
||||
|
|
@ -52,18 +57,22 @@ class ConversationTable(SQLModel, table=True):
|
|||
user_id: str = Field(..., index=True, description="用户唯一标识")
|
||||
description: str = Field(..., description="会话描述")
|
||||
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
|
||||
is_deleted: bool = Field(default=False, index=True, description="已删除:True 表示已删除,False 表示未删除")
|
||||
is_deleted: bool = Field(
|
||||
default=False,
|
||||
index=True,
|
||||
description="已删除:True 表示已删除,False 表示未删除",
|
||||
)
|
||||
|
||||
|
||||
# 思考节点表(仅定义)
|
||||
class ThoughtNodes(SQLModel):
|
||||
class ThoughtNodeRecord(SQLModel):
|
||||
|
||||
kind: str = Field(description="思考节点类型")
|
||||
content: str = Field(default="", description="思考节点内容")
|
||||
|
||||
|
||||
# 对话表
|
||||
class Dialogs(SQLModel, table=True):
|
||||
class DialogRecord(SQLModel, table=True):
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid7()),
|
||||
primary_key=True,
|
||||
|
|
@ -71,14 +80,14 @@ class Dialogs(SQLModel, table=True):
|
|||
)
|
||||
conversation_id: str = Field(..., index=True, description="会话唯一标识")
|
||||
question: str = Field(..., description="问题")
|
||||
thought_nodes: dict[int, ThoughtNodes] = Field(
|
||||
thought_nodes: dict[int, ThoughtNodeRecord] = Field(
|
||||
default_factory=dict, sa_type=JSON, description="思考节点字典"
|
||||
)
|
||||
answer: str = Field(default="", description="回答")
|
||||
|
||||
|
||||
# 运行结果表
|
||||
class RunResults(SQLModel, table=True):
|
||||
class RunResultRecord(SQLModel, table=True):
|
||||
conversation_id: str = Field(primary_key=True, description="会话唯一标识")
|
||||
dialog_id: str = Field(primary_key=True, description="对话唯一标识")
|
||||
new_messages: str = Field(description="新增消息")
|
||||
|
|
|
|||
|
|
@ -225,9 +225,7 @@ class AuthState(rx.State):
|
|||
|
||||
# 更新验证码记录
|
||||
database_state = await self.get_state(DatabaseState)
|
||||
if not await database_state.update_captcha(
|
||||
email=self.email, captcha=self.captcha
|
||||
):
|
||||
if not await database_state.get_captcha(email=self.email, captcha=self.captcha):
|
||||
self.is_logging_in = False
|
||||
self.login_error_message = "验证码错误"
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
"""
|
||||
会话状态
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Any, AsyncGenerator, Dict
|
||||
from typing import Dict
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic_ai import Agent, ThinkingPartDelta
|
||||
from pydantic_ai.messages import (
|
||||
FunctionToolCallEvent,
|
||||
|
|
@ -24,10 +25,8 @@ from pydantic_ai.providers.openai import OpenAIProvider
|
|||
from pydantic_ai.run import AgentRunResultEvent
|
||||
import reflex as rx
|
||||
|
||||
from application.models import Conversation, Dialog, ThoughtNode
|
||||
from application.states.database import DatabaseState
|
||||
|
||||
|
||||
instructions: str = """
|
||||
# 角色
|
||||
专业友好AI助手,结构化解答各类问题。
|
||||
|
|
@ -60,6 +59,46 @@ agent: Agent = Agent(
|
|||
)
|
||||
|
||||
|
||||
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="创建时间")
|
||||
|
||||
|
||||
class ConversationState(rx.State):
|
||||
"""
|
||||
会话状态
|
||||
|
|
@ -68,7 +107,7 @@ class ConversationState(rx.State):
|
|||
# 当前用户唯一标识
|
||||
user_id: str = ""
|
||||
|
||||
# 会话字典
|
||||
# 当前用户的会话字典
|
||||
conversations: Dict[str, Conversation] = {}
|
||||
# 当前会话唯一标识
|
||||
conversation_id: str = ""
|
||||
|
|
@ -93,7 +132,7 @@ class ConversationState(rx.State):
|
|||
database_state = await self.get_state(DatabaseState)
|
||||
|
||||
# 获取会话字典
|
||||
self.conversations = await database_state.retrieve_conversations(
|
||||
self.conversations = await database_state.get_conversations(
|
||||
user_id=self.user_id
|
||||
)
|
||||
# 若会话字典为空则先创建会话记录再在会话字典中添加
|
||||
|
|
|
|||
|
|
@ -3,25 +3,27 @@
|
|||
数据库状态
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, cast
|
||||
from typing import Dict, List
|
||||
|
||||
from pydantic import TypeAdapter
|
||||
from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter
|
||||
import reflex as rx
|
||||
from sqlalchemy import ColumnElement, desc
|
||||
from sqlalchemy import desc
|
||||
from sqlmodel import select, update
|
||||
|
||||
from application.models import (
|
||||
Captchas,
|
||||
CaptchaRecord,
|
||||
Conversation,
|
||||
Conversations,
|
||||
ConversationRecord,
|
||||
Dialog,
|
||||
Dialogs,
|
||||
RunResults,
|
||||
DialogRecord,
|
||||
RunResultRecord,
|
||||
ThoughtNode,
|
||||
Users,
|
||||
UserRecord,
|
||||
)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# 思考节点领域模型类型适配器
|
||||
ThoughtNodeTypeAdapter = TypeAdapter(dict[int, ThoughtNode])
|
||||
|
|
@ -32,26 +34,26 @@ class DatabaseState(rx.State):
|
|||
数据库状态
|
||||
"""
|
||||
|
||||
async def create_captchas_record(self, email: str) -> str:
|
||||
async def create_captcha_record(self, email: str) -> str:
|
||||
"""
|
||||
创建验证码记录
|
||||
:param email: 邮箱
|
||||
:return: 验证码
|
||||
:return: 所创建记录的验证码
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
# 前置处理:将该邮箱有效且未使用的验证码记录的是否有效设置为无效
|
||||
# 前置操作:将该邮箱有效且未核验的验证码记录设置为无效
|
||||
await session.exec(
|
||||
update(Captchas)
|
||||
update(CaptchaRecord)
|
||||
.where(
|
||||
Captchas.email == email, # type: ignore
|
||||
Captchas.is_valid == True, # type: ignore
|
||||
Captchas.is_unused == True, # type: ignore
|
||||
CaptchaRecord.email == email, # type: ignore
|
||||
CaptchaRecord.is_valid == True, # type: ignore
|
||||
CaptchaRecord.is_verified == False, # type: ignore
|
||||
)
|
||||
.values(is_valid=False)
|
||||
)
|
||||
await session.flush()
|
||||
# 创建验证码记录
|
||||
record = Captchas(
|
||||
# 创建记录
|
||||
record = CaptchaRecord(
|
||||
email=email,
|
||||
)
|
||||
session.add(record)
|
||||
|
|
@ -59,27 +61,27 @@ class DatabaseState(rx.State):
|
|||
await session.refresh(record)
|
||||
return record.captcha
|
||||
|
||||
async def update_captcha(self, email: str, captcha: str) -> bool:
|
||||
async def verify_captcha(self, email: str, captcha: str) -> bool:
|
||||
"""
|
||||
更新验证码记录
|
||||
核验验证码
|
||||
:param email: 邮箱
|
||||
:param captcha: 验证码
|
||||
:return: 是否更新成功,True 表示更新成功,False 表示更新失败
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
result = await session.exec(
|
||||
select(Captchas).where(
|
||||
Captchas.email == email,
|
||||
Captchas.captcha == captcha,
|
||||
Captchas.is_valid == True,
|
||||
Captchas.is_unused == True,
|
||||
Captchas.expired_at > datetime.now(),
|
||||
select(CaptchaRecord).where(
|
||||
CaptchaRecord.email == email,
|
||||
CaptchaRecord.captcha == captcha,
|
||||
CaptchaRecord.is_valid == True,
|
||||
CaptchaRecord.is_verified == False,
|
||||
CaptchaRecord.expired_at > datetime.now(), # 不区分是否过期
|
||||
)
|
||||
)
|
||||
record = result.first()
|
||||
if not record:
|
||||
return False
|
||||
record.is_unused = False
|
||||
record.is_verified = True
|
||||
await session.commit()
|
||||
await session.refresh(record)
|
||||
return True
|
||||
|
|
@ -91,18 +93,20 @@ class DatabaseState(rx.State):
|
|||
:return: 创建用户记录的唯一标识
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
result = await session.exec(select(Users).where(Users.email == email))
|
||||
result = await session.exec(
|
||||
select(UserRecord).where(UserRecord.email == email)
|
||||
)
|
||||
record = result.first()
|
||||
# 若用户记录存在则返回用户唯一标识,否则创建新用户记录并返回所创建的用户唯一标识
|
||||
if record:
|
||||
return record.id
|
||||
new_user = Users(email=email)
|
||||
new_user = UserRecord(email=email)
|
||||
session.add(new_user)
|
||||
await session.commit()
|
||||
await session.refresh(new_user)
|
||||
return new_user.id
|
||||
|
||||
async def retrieve_conversations(self, user_id: str) -> Dict[str, Conversation]:
|
||||
async def get_conversations(self, user_id: str) -> Dict[str, Conversation]:
|
||||
"""
|
||||
获取会话字典
|
||||
:param user_id: 用户唯一标识
|
||||
|
|
@ -111,16 +115,16 @@ class DatabaseState(rx.State):
|
|||
records: Dict[str, Conversation] = {}
|
||||
async with rx.asession() as session:
|
||||
result = await session.exec(
|
||||
select(Conversations, Dialogs)
|
||||
select(ConversationRecord, DialogRecord)
|
||||
.outerjoin(
|
||||
Dialogs,
|
||||
Conversations.id == Dialogs.conversation_id, # type: ignore
|
||||
DialogRecord,
|
||||
ConversationRecord.id == DialogRecord.conversation_id, # type: ignore
|
||||
)
|
||||
.where(
|
||||
Conversations.user_id == user_id,
|
||||
Conversations.is_deleted == False,
|
||||
ConversationRecord.user_id == user_id,
|
||||
ConversationRecord.is_deleted == False,
|
||||
)
|
||||
.order_by(Conversations.id, Dialogs.id)
|
||||
.order_by(ConversationRecord.id, DialogRecord.id)
|
||||
)
|
||||
for conversation, dialog in result.all():
|
||||
record = records.setdefault(
|
||||
|
|
@ -151,7 +155,7 @@ class DatabaseState(rx.State):
|
|||
:return: 创建会话记录的唯一标识和创建时间
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
record = Conversations(user_id=user_id, description=description)
|
||||
record = ConversationRecord(user_id=user_id, description=description)
|
||||
session.add(record)
|
||||
await session.commit()
|
||||
await session.refresh(record)
|
||||
|
|
@ -164,7 +168,7 @@ class DatabaseState(rx.State):
|
|||
:return: None
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
record = await session.get(Conversations, conversation_id)
|
||||
record = await session.get(ConversationRecord, conversation_id)
|
||||
if not record:
|
||||
return
|
||||
record.is_deleted = True
|
||||
|
|
@ -178,7 +182,7 @@ class DatabaseState(rx.State):
|
|||
:return: 创建对话记录的唯一标识
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
record = Dialogs(
|
||||
record = DialogRecord(
|
||||
conversation_id=conversation_id,
|
||||
question=question,
|
||||
)
|
||||
|
|
@ -201,7 +205,7 @@ class DatabaseState(rx.State):
|
|||
:return: None
|
||||
"""
|
||||
async with rx.asession() as session:
|
||||
record = await session.get(Dialogs, dialog_id)
|
||||
record = await session.get(DialogRecord, dialog_id)
|
||||
if not record:
|
||||
return
|
||||
record.thought_nodes = ThoughtNodeTypeAdapter.dump_python(
|
||||
|
|
@ -225,7 +229,7 @@ class DatabaseState(rx.State):
|
|||
"""
|
||||
async with rx.asession() as session:
|
||||
session.add(
|
||||
RunResults(
|
||||
RunResultRecord(
|
||||
conversation_id=conversation_id,
|
||||
dialog_id=dialog_id,
|
||||
new_messages=ModelMessagesTypeAdapter.dump_json(
|
||||
|
|
@ -248,9 +252,9 @@ class DatabaseState(rx.State):
|
|||
records: List[ModelMessage] = []
|
||||
async with rx.asession() as session:
|
||||
result = await session.exec(
|
||||
select(RunResults)
|
||||
.where(RunResults.conversation_id == conversation_id)
|
||||
.order_by(desc(RunResults.dialog_id))
|
||||
select(RunResultRecord)
|
||||
.where(RunResultRecord.conversation_id == conversation_id)
|
||||
.order_by(desc(RunResultRecord.dialog_id))
|
||||
)
|
||||
for record in result.all():
|
||||
records.extend(
|
||||
|
|
|
|||
Loading…
Reference in New Issue