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,9 @@
export interface BroadcastInfo {
has_broadcast: boolean
mp3_url?: string
generated_at?: string
}
export interface GenerateBroadcastResponse {
message: string
}

View File

@@ -0,0 +1,129 @@
/**
* 课程资料相关类型定义
*/
/**
* 课程资料信息
*/
export interface Material {
id: number
course_id: number
name: string
description?: string
file_url: string
file_type: string
file_size: number
created_at: string
updated_at?: string
}
/**
* 预览类型
* 支持格式TXT、Markdown、MDX、PDF、HTML、Excel、Word、CSV、VTT、Properties、视频、音频、图片
*/
export type PreviewType = 'pdf' | 'text' | 'html' | 'download' | 'video' | 'audio' | 'image'
/**
* 预览信息
*/
export interface PreviewInfo {
preview_type: PreviewType
preview_url?: string
content?: string // 文本内容仅text类型
file_name: string
original_url: string
file_size: number
is_converted?: boolean // 是否经过转换Office文档
}
/**
* 文件类型图标映射
* 支持格式TXT、Markdown、MDX、PDF、HTML、Excel、Word、CSV、VTT、Properties
*/
export const FILE_TYPE_ICONS: Record<string, string> = {
// PDF
pdf: 'document',
// Office文档
doc: 'document',
docx: 'document',
xls: 'document',
xlsx: 'document',
// 文本
txt: 'document',
md: 'document',
mdx: 'document',
csv: 'tickets',
vtt: 'document',
properties: 'setting',
// HTML
html: 'document',
htm: 'document',
}
/**
* 获取文件类型图标
*/
export function getFileTypeIcon(fileName: string): string {
const ext = fileName.split('.').pop()?.toLowerCase() || ''
return FILE_TYPE_ICONS[ext] || 'document'
}
/**
* 格式化文件大小
*/
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
}
/**
* 获取文件扩展名
*/
export function getFileExtension(fileName: string): string {
return fileName.split('.').pop()?.toLowerCase() || ''
}
/**
* 判断文件类型
* 支持格式TXT、Markdown、MDX、PDF、HTML、Excel、Word、CSV、VTT、Properties、视频、音频、图片
*/
export function getFileCategory(fileName: string): 'document' | 'text' | 'video' | 'audio' | 'image' | 'other' {
const ext = getFileExtension(fileName)
// 视频格式
const videoFormats = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv']
if (videoFormats.includes(ext)) {
return 'video'
}
// 音频格式
const audioFormats = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a']
if (audioFormats.includes(ext)) {
return 'audio'
}
// 图片格式
const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp']
if (imageFormats.includes(ext)) {
return 'image'
}
// 文档格式
const documentFormats = [
'pdf', 'doc', 'docx', 'xls', 'xlsx',
'txt', 'md', 'mdx', 'csv', 'vtt', 'properties',
'html', 'htm'
]
if (documentFormats.includes(ext)) {
return 'document'
}
return 'other'
}

View File

@@ -0,0 +1,149 @@
/**
* 陪练功能类型定义
*/
// ==================== 枚举类型 ====================
/** 场景类型 */
export type SceneType = 'phone' | 'face' | 'complaint' | 'after-sales' | 'product-intro'
/** 难度等级 */
export type Difficulty = 'beginner' | 'junior' | 'intermediate' | 'senior' | 'expert'
/** 场景状态 */
export type SceneStatus = 'active' | 'inactive'
/** 聊天模式 */
export enum ChatModel {
TEXT = 'text',
VOICE = 'voice'
}
/** 连接状态 */
export enum ConnectionStatus {
UNCONNECTED = 'unconnected',
CONNECTING = 'connecting',
CONNECTED = 'connected',
DISCONNECTED = 'disconnected',
ERROR = 'error',
WAITING = 'waiting',
LISTENING = 'listening'
}
// ==================== 数据模型 ====================
/** 陪练场景 */
export interface PracticeScene {
id: number
name: string
description?: string
type: SceneType
difficulty: Difficulty
status: SceneStatus
background: string
ai_role: string
objectives: string[]
keywords?: string[]
duration: number
usage_count: number
rating: number
created_by?: number
updated_by?: number
created_at: string
updated_at: string
}
/** 消息对象 */
export interface Message {
id: string
role: 'user' | 'assistant'
content: string
timestamp: number
loading?: boolean
prologue?: boolean
}
/** Bot信息 */
export interface BotInfo {
bot_id: string
name: string
description: string
icon_url: string
onboarding_info?: {
prologue: string
suggested_questions: string[]
}
}
// ==================== 请求/响应 ====================
/** 获取场景列表请求参数 */
export interface GetScenesParams {
page?: number
size?: number
type?: SceneType
difficulty?: Difficulty
search?: string
}
/** 开始陪练请求参数 */
export interface StartPracticeParams {
scene_id?: number
scene_name?: string
scene_description?: string
scene_background?: string
scene_ai_role?: string
scene_objectives?: string[]
scene_keywords?: string[]
user_message: string
conversation_id?: string
is_first: boolean
}
/** 中断对话请求参数 */
export interface InterruptPracticeParams {
conversation_id: string
chat_id: string
}
/** 分页响应 */
export interface PaginatedResponse<T> {
items: T[]
total: number
page: number
page_size: number
pages: number
}
/** 统一响应格式 */
export interface ResponseModel<T = any> {
code: number
message: string
data: T
request_id?: string
}
// ==================== SSE事件 ====================
/** SSE事件类型 */
export type SSEEventType =
| 'conversation.chat.created'
| 'message.delta'
| 'message.completed'
| 'conversation.completed'
| 'error'
| 'done'
/** SSE事件数据 */
export interface SSEEventData {
conversation_id?: string
chat_id?: string
content?: string
token_count?: number
input_count?: number
output_count?: number
error?: string
}