feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
"""
陪练功能相关Schema定义
"""
from typing import Optional, List
from datetime import datetime
from pydantic import BaseModel, Field, field_validator
# ==================== 枚举类型 ====================
class SceneType:
"""场景类型枚举"""
PHONE = "phone" # 电话销售
FACE = "face" # 面对面销售
COMPLAINT = "complaint" # 客户投诉
AFTER_SALES = "after-sales" # 售后服务
PRODUCT_INTRO = "product-intro" # 产品介绍
class Difficulty:
"""难度等级枚举"""
BEGINNER = "beginner" # 入门
JUNIOR = "junior" # 初级
INTERMEDIATE = "intermediate" # 中级
SENIOR = "senior" # 高级
EXPERT = "expert" # 专家
class SceneStatus:
"""场景状态枚举"""
ACTIVE = "active" # 启用
INACTIVE = "inactive" # 禁用
# ==================== 场景Schema ====================
class PracticeSceneBase(BaseModel):
"""陪练场景基础Schema"""
name: str = Field(..., max_length=200, description="场景名称")
description: Optional[str] = Field(None, description="场景描述")
type: str = Field(..., description="场景类型: phone/face/complaint/after-sales/product-intro")
difficulty: str = Field(..., description="难度等级: beginner/junior/intermediate/senior/expert")
status: str = Field(default="active", description="状态: active/inactive")
background: str = Field(..., description="场景背景设定")
ai_role: str = Field(..., description="AI角色描述")
objectives: List[str] = Field(..., description="练习目标数组")
keywords: Optional[List[str]] = Field(default=None, description="关键词数组")
duration: int = Field(default=10, ge=1, le=120, description="预计时长(分钟)")
@field_validator('type')
@classmethod
def validate_type(cls, v):
"""验证场景类型"""
valid_types = ['phone', 'face', 'complaint', 'after-sales', 'product-intro']
if v not in valid_types:
raise ValueError(f"场景类型必须是: {', '.join(valid_types)}")
return v
@field_validator('difficulty')
@classmethod
def validate_difficulty(cls, v):
"""验证难度等级"""
valid_difficulties = ['beginner', 'junior', 'intermediate', 'senior', 'expert']
if v not in valid_difficulties:
raise ValueError(f"难度等级必须是: {', '.join(valid_difficulties)}")
return v
@field_validator('status')
@classmethod
def validate_status(cls, v):
"""验证状态"""
valid_statuses = ['active', 'inactive']
if v not in valid_statuses:
raise ValueError(f"状态必须是: {', '.join(valid_statuses)}")
return v
@field_validator('objectives')
@classmethod
def validate_objectives(cls, v):
"""验证练习目标"""
if not v or len(v) < 1:
raise ValueError("至少需要1个练习目标")
if len(v) > 10:
raise ValueError("练习目标不能超过10个")
return v
class PracticeSceneCreate(PracticeSceneBase):
"""创建陪练场景Schema"""
pass
class PracticeSceneUpdate(BaseModel):
"""更新陪练场景Schema所有字段可选"""
name: Optional[str] = Field(None, max_length=200, description="场景名称")
description: Optional[str] = Field(None, description="场景描述")
type: Optional[str] = Field(None, description="场景类型")
difficulty: Optional[str] = Field(None, description="难度等级")
status: Optional[str] = Field(None, description="状态")
background: Optional[str] = Field(None, description="场景背景设定")
ai_role: Optional[str] = Field(None, description="AI角色描述")
objectives: Optional[List[str]] = Field(None, description="练习目标数组")
keywords: Optional[List[str]] = Field(None, description="关键词数组")
duration: Optional[int] = Field(None, ge=1, le=120, description="预计时长(分钟)")
class PracticeSceneResponse(PracticeSceneBase):
"""陪练场景响应Schema"""
id: int
usage_count: int
rating: float
created_by: Optional[int] = None
updated_by: Optional[int] = None
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
# ==================== 对话Schema ====================
class StartPracticeRequest(BaseModel):
"""开始陪练对话请求Schema"""
# 场景信息(首次消息必填,后续消息可选)
scene_id: Optional[int] = Field(None, description="场景ID可选")
scene_name: Optional[str] = Field(None, description="场景名称")
scene_description: Optional[str] = Field(None, description="场景描述")
scene_background: Optional[str] = Field(None, description="场景背景")
scene_ai_role: Optional[str] = Field(None, description="AI角色")
scene_objectives: Optional[List[str]] = Field(None, description="练习目标")
scene_keywords: Optional[List[str]] = Field(None, description="关键词")
# 对话信息
user_message: str = Field(..., description="用户消息")
conversation_id: Optional[str] = Field(None, description="对话ID续接对话时必填")
is_first: bool = Field(..., description="是否首次消息")
@field_validator('scene_name')
@classmethod
def validate_scene_name_for_first(cls, v, info):
"""首次消息时场景名称必填"""
if info.data.get('is_first') and not v:
raise ValueError("首次消息时场景名称必填")
return v
@field_validator('scene_background')
@classmethod
def validate_scene_background_for_first(cls, v, info):
"""首次消息时场景背景必填"""
if info.data.get('is_first') and not v:
raise ValueError("首次消息时场景背景必填")
return v
@field_validator('scene_ai_role')
@classmethod
def validate_scene_ai_role_for_first(cls, v, info):
"""首次消息时AI角色必填"""
if info.data.get('is_first') and not v:
raise ValueError("首次消息时AI角色必填")
return v
@field_validator('scene_objectives')
@classmethod
def validate_scene_objectives_for_first(cls, v, info):
"""首次消息时练习目标必填"""
if info.data.get('is_first') and (not v or len(v) == 0):
raise ValueError("首次消息时练习目标必填")
return v
class InterruptPracticeRequest(BaseModel):
"""中断对话请求Schema"""
conversation_id: str = Field(..., description="对话ID")
chat_id: str = Field(..., description="聊天ID")
class ConversationInfo(BaseModel):
"""对话信息Schema"""
id: str = Field(..., description="对话ID")
name: str = Field(..., description="对话名称")
created_at: int = Field(..., description="创建时间(时间戳)")
class ConversationsResponse(BaseModel):
"""对话列表响应Schema"""
items: List[ConversationInfo]
has_more: bool
page: int
size: int
# ==================== 场景提取Schema ====================
class ExtractSceneRequest(BaseModel):
"""提取场景请求Schema"""
course_id: int = Field(..., description="课程ID")
class ExtractedSceneData(BaseModel):
"""提取的场景数据Schema"""
name: str = Field(..., description="场景名称")
description: str = Field(..., description="场景描述")
type: str = Field(..., description="场景类型")
difficulty: str = Field(..., description="难度等级")
background: str = Field(..., description="场景背景")
ai_role: str = Field(..., description="AI角色描述")
objectives: List[str] = Field(..., description="练习目标数组")
keywords: Optional[List[str]] = Field(default=[], description="关键词数组")
class ExtractSceneResponse(BaseModel):
"""提取场景响应Schema"""
scene: ExtractedSceneData = Field(..., description="场景数据")
workflow_run_id: str = Field(..., description="工作流运行ID")
task_id: str = Field(..., description="任务ID")
# ==================== 陪练会话Schema ====================
class PracticeSessionCreate(BaseModel):
"""创建陪练会话请求Schema"""
scene_id: Optional[int] = Field(None, description="场景ID")
scene_name: str = Field(..., description="场景名称")
scene_type: Optional[str] = Field(None, description="场景类型")
conversation_id: Optional[str] = Field(None, description="Coze对话ID")
class PracticeSessionResponse(BaseModel):
"""陪练会话响应Schema"""
id: int
session_id: str
user_id: int
scene_id: Optional[int]
scene_name: str
scene_type: Optional[str]
conversation_id: Optional[str]
start_time: datetime
end_time: Optional[datetime]
duration_seconds: int
turns: int
status: str
created_at: datetime
class Config:
from_attributes = True
class SaveDialogueRequest(BaseModel):
"""保存对话记录请求Schema"""
session_id: str = Field(..., description="会话ID")
speaker: str = Field(..., description="说话人: user/ai")
content: str = Field(..., description="对话内容")
sequence: int = Field(..., ge=1, description="顺序号从1开始")
class PracticeDialogueResponse(BaseModel):
"""对话记录响应Schema"""
id: int
session_id: str
speaker: str
content: str
timestamp: datetime
sequence: int
class Config:
from_attributes = True
# ==================== 分析报告Schema ====================
class ScoreBreakdownItem(BaseModel):
"""分数细分项"""
name: str
score: int = Field(..., ge=0, le=100)
description: str
class AbilityDimensionItem(BaseModel):
"""能力维度项"""
name: str
score: int = Field(..., ge=0, le=100)
feedback: str
class DialogueReviewItem(BaseModel):
"""对话复盘项"""
speaker: str
time: str
content: str
tags: List[str] = Field(default_factory=list)
comment: str = Field(default="")
class SuggestionItem(BaseModel):
"""改进建议项"""
title: str
content: str
example: Optional[str] = None
class PracticeAnalysisResult(BaseModel):
"""陪练分析结果Schema"""
total_score: int = Field(..., ge=0, le=100, description="综合得分")
score_breakdown: List[ScoreBreakdownItem] = Field(..., description="分数细分")
ability_dimensions: List[AbilityDimensionItem] = Field(..., description="能力维度")
dialogue_review: List[DialogueReviewItem] = Field(..., description="对话复盘")
suggestions: List[SuggestionItem] = Field(..., description="改进建议")
class PracticeReportResponse(BaseModel):
"""陪练报告响应Schema"""
session_info: PracticeSessionResponse
analysis: PracticeAnalysisResult
class Config:
from_attributes = True