chore: initial baseline with P0-safety .gitignore
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
classDiagram
|
||||
class Agent {
|
||||
+str id
|
||||
+str user_id
|
||||
+str name
|
||||
+str status
|
||||
+str role
|
||||
+list skill_tags
|
||||
+int current_load
|
||||
+int max_load
|
||||
+datetime created_at
|
||||
+datetime updated_at
|
||||
}
|
||||
|
||||
class SystemConfig {
|
||||
+str id
|
||||
+str config_key
|
||||
+str config_value
|
||||
+str description
|
||||
+datetime updated_at
|
||||
}
|
||||
|
||||
class ConfigChangeLog {
|
||||
+str id
|
||||
+str config_key
|
||||
+str old_value
|
||||
+str new_value
|
||||
+str changed_by
|
||||
+datetime changed_at
|
||||
}
|
||||
|
||||
class QuickReplyTemplate {
|
||||
+str id
|
||||
+str category
|
||||
+str title
|
||||
+str content
|
||||
+list variables
|
||||
+int sort_order
|
||||
+str status
|
||||
+int version
|
||||
+str submitted_by
|
||||
+datetime created_at
|
||||
+datetime updated_at
|
||||
}
|
||||
|
||||
class Conversation {
|
||||
+str id
|
||||
+str employee_id
|
||||
+str employee_name
|
||||
+str status
|
||||
+int urgency_score
|
||||
+str assigned_agent_id
|
||||
+datetime created_at
|
||||
}
|
||||
|
||||
ConfigChangeLog --> SystemConfig : tracks changes to
|
||||
ConfigChangeLog --> Agent : changed_by
|
||||
QuickReplyTemplate --> Agent : submitted_by
|
||||
Conversation --> Agent : assigned_agent_id
|
||||
@@ -0,0 +1,19 @@
|
||||
sequenceDiagram
|
||||
participant U as 管理员
|
||||
participant FE as frontend-admin
|
||||
participant API as /api/admin/configs/{key}
|
||||
participant SVC as admin_service
|
||||
participant DB as PostgreSQL
|
||||
|
||||
U->>FE: 切换应急模式开关
|
||||
FE->>API: PUT /api/admin/configs/emergency_mode
|
||||
API->>API: require_admin 校验权限
|
||||
API->>SVC: update_config(key, value, agent_id)
|
||||
SVC->>DB: SELECT SystemConfig WHERE key=emergency_mode
|
||||
DB-->>SVC: 当前值 "false"
|
||||
SVC->>DB: INSERT ConfigChangeLog(old="false", new="true", by=agent_id)
|
||||
SVC->>DB: UPDATE SystemConfig SET value="true"
|
||||
DB-->>SVC: 更新成功
|
||||
SVC-->>API: {key, old_value, new_value, changed_at}
|
||||
API-->>FE: 返回变更结果
|
||||
FE->>FE: 显示变更成功提示
|
||||
@@ -0,0 +1,16 @@
|
||||
sequenceDiagram
|
||||
participant U as 管理员(组长)
|
||||
participant FE as frontend-admin
|
||||
participant API as /api/agents/login
|
||||
participant Redis as Redis
|
||||
participant DB as PostgreSQL
|
||||
|
||||
U->>FE: 输入 user_id + name 登录
|
||||
FE->>API: POST /api/agents/login
|
||||
API->>DB: 查询 Agent (user_id)
|
||||
DB-->>API: Agent 记录(含 role 字段)
|
||||
API->>Redis: 存储 token → user_id 映射
|
||||
API-->>FE: {agent_info, token, role: "admin"}
|
||||
FE->>FE: 检查 role === "admin"
|
||||
FE->>FE: 存储 admin_token 到 localStorage
|
||||
FE->>FE: 跳转到 /admin/dashboard
|
||||
@@ -0,0 +1,23 @@
|
||||
sequenceDiagram
|
||||
participant A as 坐席(王丽)
|
||||
participant AG as /api/quick-replies
|
||||
participant U as 管理员(宋献)
|
||||
participant ADM as /api/admin/quick-replies
|
||||
participant DB as PostgreSQL
|
||||
|
||||
A->>AG: POST /api/quick-replies (创建模板, status=draft)
|
||||
A->>AG: PUT /api/quick-replies/{id} (提交审核, status→pending_review)
|
||||
AG->>DB: UPDATE QuickReplyTemplate SET status='pending_review', submitted_by='wang_li'
|
||||
|
||||
U->>ADM: GET /api/admin/quick-replies/pending
|
||||
ADM->>DB: SELECT WHERE status='pending_review'
|
||||
DB-->>ADM: 待审核列表
|
||||
ADM-->>U: 显示待审核模板
|
||||
|
||||
U->>ADM: PUT /api/admin/quick-replies/{id}/review (action=approve)
|
||||
ADM->>DB: UPDATE SET status='approved', version=version+1
|
||||
DB-->>ADM: 更新成功
|
||||
|
||||
A->>AG: GET /api/quick-replies (获取可见模板)
|
||||
AG->>DB: SELECT WHERE status='approved' OR (status='pending_review' AND submitted_by=自己)
|
||||
DB-->>AG: 全员可见(approved) + 仅自己(pending_review)
|
||||
@@ -0,0 +1,77 @@
|
||||
classDiagram
|
||||
direction TB
|
||||
|
||||
class Employee {
|
||||
+str id
|
||||
+str corp_id
|
||||
+str employee_id
|
||||
+str name
|
||||
+str department
|
||||
+str position
|
||||
+str mobile
|
||||
+str email
|
||||
+str avatar
|
||||
+int status
|
||||
+str it_level ★NEW
|
||||
+str it_level_source ★NEW
|
||||
+dict notes ★NEW
|
||||
+datetime last_login_at
|
||||
+datetime created_at
|
||||
+datetime updated_at
|
||||
}
|
||||
|
||||
class Conversation {
|
||||
+str id
|
||||
+str corp_id
|
||||
+str employee_id
|
||||
+str employee_name
|
||||
+str department
|
||||
+str status
|
||||
+bool is_vip
|
||||
+bool is_pinned
|
||||
+bool is_todo
|
||||
+int urgency_score
|
||||
+dict tags
|
||||
+str assigned_agent_id
|
||||
+list collaborating_agent_ids
|
||||
+int impact_scope ★NEW
|
||||
+bool is_blocking ★NEW
|
||||
+str emotion_state ★NEW
|
||||
+datetime last_message_at
|
||||
+str last_message_summary
|
||||
+datetime created_at
|
||||
+datetime updated_at
|
||||
}
|
||||
|
||||
class TodoItem {
|
||||
+str id
|
||||
+str type
|
||||
+str title
|
||||
+str priority
|
||||
+dict description
|
||||
+str status
|
||||
+str assigned_agent_id
|
||||
+str corp_id
|
||||
+datetime created_at
|
||||
+datetime updated_at
|
||||
}
|
||||
|
||||
class TroubleshootingTemplate {
|
||||
+str id
|
||||
+str name
|
||||
+str category
|
||||
+list path_steps
|
||||
+dict flowchart
|
||||
+bool is_active
|
||||
+datetime created_at
|
||||
+datetime updated_at
|
||||
}
|
||||
|
||||
Employee "1" --> "*" Conversation : has
|
||||
Conversation "1" --> "*" TodoItem : may generate
|
||||
TroubleshootingTemplate "1" --> "0..1" Conversation : applied to
|
||||
|
||||
note for Employee "it_level: bronze|silver|gold|platinum|diamond|star|king\nit_level_source: system|manual"
|
||||
note for Conversation "impact_scope: 受影响人数\nis_blocking: 是否阻断性\nemotion_state: normal|anxious|angry|urgent"
|
||||
note for TodoItem "type: ticket|approval|device\npriority: urgent|high|normal\nstatus: pending|processing|resolved"
|
||||
note for TroubleshootingTemplate "category: vpn|email|system|account\npath_steps: [{label, status}]\nflowchart: 递归树结构"
|
||||
@@ -0,0 +1,22 @@
|
||||
sequenceDiagram
|
||||
participant U as 坐席
|
||||
participant TB as TopBar.vue
|
||||
participant TS as useThemeStore
|
||||
participant UT as useTheme.ts
|
||||
participant DOM as document.documentElement
|
||||
participant LS as localStorage
|
||||
|
||||
U->>TB: 点击 ☀️/🌙 切换开关
|
||||
TB->>TS: toggleTheme()
|
||||
TS->>TS: currentTheme = currentTheme === 'light' ? 'dark' : 'light'
|
||||
TS->>UT: applyTheme(currentTheme)
|
||||
UT->>DOM: setAttribute('data-theme', theme)
|
||||
UT->>LS: setItem('theme', theme)
|
||||
DOM-->>DOM: CSS 变量自动切换(:root / [data-theme="dark"])
|
||||
DOM-->>U: 300ms 过渡动画,界面变色
|
||||
|
||||
Note over U,LS: 页面加载时
|
||||
U->>UT: 首次进入页面
|
||||
UT->>LS: getItem('theme')
|
||||
LS-->>UT: 'dark' | 'light' | null
|
||||
UT->>DOM: setAttribute('data-theme', theme || 'light')
|
||||
@@ -0,0 +1,62 @@
|
||||
sequenceDiagram
|
||||
participant Browser as 坐席浏览器
|
||||
participant App as Vue3 App
|
||||
participant Store as Pinia Store
|
||||
participant API as Backend API
|
||||
participant DB as PostgreSQL
|
||||
|
||||
Note over Browser,App: 页面加载
|
||||
|
||||
Browser->>App: 挂载 Workspace.vue
|
||||
App->>Store: 初始化 conversationStore
|
||||
Store->>API: GET /api/conversations
|
||||
API->>DB: SELECT * FROM conversations WHERE status IN ('queued','serving') ORDER BY ...
|
||||
DB-->>API: 会话列表
|
||||
API-->>Store: 会话数据
|
||||
Store-->>App: 渲染会话列表
|
||||
|
||||
loop 每3秒 setInterval
|
||||
App->>Store: pollConversations()
|
||||
Store->>API: GET /api/conversations?page=1&page_size=50
|
||||
API->>DB: SELECT ... (同上)
|
||||
DB-->>API: 最新会话列表
|
||||
API-->>Store: 最新数据
|
||||
|
||||
alt 数据有变化
|
||||
Store->>Store: diff 比较,更新变化的会话
|
||||
Store-->>App: 触发响应式更新
|
||||
App->>App: 更新列表项标签/排序/未读数
|
||||
else 数据无变化
|
||||
Store-->>App: 无需更新
|
||||
end
|
||||
end
|
||||
|
||||
Note over App: 用户点击某个会话
|
||||
|
||||
App->>Store: selectConversation(id)
|
||||
Store->>API: GET /api/conversations/{id}/messages?limit=50
|
||||
API->>DB: SELECT * FROM messages WHERE conversation_id=... ORDER BY created_at
|
||||
DB-->>API: 消息列表
|
||||
API-->>Store: messages
|
||||
Store-->>App: 渲染对话区
|
||||
|
||||
loop 选中会话的消息轮询
|
||||
App->>Store: pollMessages(conv_id)
|
||||
Store->>API: GET /api/conversations/{id}/messages?limit=20&before=latest_id
|
||||
API->>DB: SELECT ... WHERE created_at > latest
|
||||
DB-->>API: 新消息
|
||||
API-->>Store: 新消息列表
|
||||
alt 有新消息
|
||||
Store->>Store: 追加消息到列表
|
||||
Store-->>App: 滚动到底部,显示新消息
|
||||
end
|
||||
end
|
||||
|
||||
Note over App: 坐席发送回复
|
||||
|
||||
App->>Store: sendMessage(conv_id, content)
|
||||
Store->>API: POST /api/conversations/{id}/messages {content}
|
||||
API->>DB: INSERT INTO messages ...
|
||||
API-->>Store: 发送的消息对象
|
||||
Store->>Store: 追加到消息列表
|
||||
Store-->>App: 显示在对话区
|
||||
@@ -0,0 +1,69 @@
|
||||
sequenceDiagram
|
||||
participant WX as 企微消息
|
||||
participant API as FastAPI
|
||||
participant Router as MessageRouter
|
||||
participant Score as ScoringService
|
||||
participant Vip as VipService
|
||||
participant DB as PostgreSQL
|
||||
participant Redis as Redis
|
||||
|
||||
WX->>API: 员工消息回调
|
||||
API->>Router: route_message(msg)
|
||||
|
||||
rect rgb(255, 240, 240)
|
||||
Note over Router,Score: Step 1: VIP检测
|
||||
Router->>Vip: is_vip(employee_id)
|
||||
Vip->>Redis: GET vip_cache:{employee_id}
|
||||
alt 缓存命中
|
||||
Redis-->>Vip: is_vip=True/False
|
||||
else 缓存未命中
|
||||
Vip->>Vip: get_user_info(employee_id)
|
||||
Vip->>Vip: _check_vip_rules(user_info)
|
||||
Note over Vip: 规则: 总监及以上 或 关键部门
|
||||
Vip->>Redis: SET vip_cache:{employee_id} EX 3600
|
||||
end
|
||||
Vip-->>Router: is_vip=True/False
|
||||
end
|
||||
|
||||
rect rgb(255, 255, 220)
|
||||
Note over Router,Score: Step 2: 情绪关键词检测
|
||||
Router->>Score: detect_emotion(msg)
|
||||
Score->>DB: SELECT FROM system_configs WHERE key LIKE 'emotion_keywords_%'
|
||||
DB-->>Score: 关键词列表
|
||||
Score->>Score: 遍历关键词匹配消息内容
|
||||
Score-->>Router: emotion="urgent"
|
||||
end
|
||||
|
||||
rect rgb(220, 255, 220)
|
||||
Note over Router,Score: Step 3: 举手检测
|
||||
Router->>Score: detect_hand_raise(msg)
|
||||
Score->>DB: SELECT FROM system_configs WHERE key='hand_raise_keywords'
|
||||
DB-->>Score: ["转人工","人工",...]
|
||||
Score->>Score: 遍历关键词匹配
|
||||
Score-->>Router: hand_raise=True
|
||||
end
|
||||
|
||||
rect rgb(220, 220, 255)
|
||||
Note over Router,Score: Step 4: 需介入检测
|
||||
Router->>Score: detect_need_intervene(conv)
|
||||
Score->>DB: SELECT COUNT FROM messages WHERE conversation_id=... AND sender_type='employee'
|
||||
DB-->>Score: 员工消息数
|
||||
Score->>DB: SELECT FROM system_configs WHERE key='intervene_round_threshold'
|
||||
DB-->>Score: 3
|
||||
Score->>Score: 员工连续追问 > 3轮?
|
||||
Score-->>Router: need_intervene=True
|
||||
end
|
||||
|
||||
rect rgb(255, 220, 255)
|
||||
Note over Router,Score: Step 5: 紧急度计算
|
||||
Router->>Score: calculate_urgency(conv, msg)
|
||||
Score->>Score: base = keyword_score(1)
|
||||
Score->>Score: + emotion_bonus(1)
|
||||
Score->>Score: + vip_bonus(1)
|
||||
Score->>Score: + repeat_bonus(1)
|
||||
Score->>Score: total = min(5, base+emotion+vip+repeat)
|
||||
Score-->>Router: urgency_score=4
|
||||
end
|
||||
|
||||
Router->>DB: UPDATE conversations SET tags=..., urgency_score=4, is_vip=...
|
||||
Router-->>API: 更新后的会话
|
||||
@@ -0,0 +1,43 @@
|
||||
sequenceDiagram
|
||||
participant Emp as 员工(H5页面)
|
||||
participant API as FastAPI
|
||||
participant ConvSvc as ConversationService
|
||||
participant Score as ScoringService
|
||||
participant WXSvc as WecomService
|
||||
participant Agent as 坐席工作台
|
||||
participant DB as PostgreSQL
|
||||
|
||||
Emp->>API: POST /api/h5/conversation/shake
|
||||
API->>ConvSvc: find_or_create_conversation(employee_id)
|
||||
ConvSvc->>DB: 查询/创建 conversation
|
||||
DB-->>ConvSvc: conversation
|
||||
API->>Score: detect_hand_raise("摇人")
|
||||
Score-->>API: hand_raise=True
|
||||
API->>ConvSvc: update_conversation(tags={hand_raise:true})
|
||||
ConvSvc->>DB: UPDATE conversations SET tags=...
|
||||
API->>ConvSvc: get_funny_phrase(scene='shake')
|
||||
ConvSvc->>DB: SELECT FROM funny_phrases WHERE scene='shake'
|
||||
DB-->>ConvSvc: "大哥,俺这就去摇人,稍等..."
|
||||
ConvSvc->>ConvSvc: send_message(conv_id, 'system', 趣味话术)
|
||||
ConvSvc->>WXSvc: send_text_message(employee_id, 趣味话术)
|
||||
API-->>Emp: {conversation, funny_phrase}
|
||||
|
||||
Note over Emp: H5显示摇人动画 + 趣味话术
|
||||
|
||||
loop 坐席轮询
|
||||
Agent->>API: GET /api/conversations
|
||||
API->>ConvSvc: get_conversations()
|
||||
ConvSvc->>DB: SELECT ... ORDER BY urgency DESC
|
||||
DB-->>ConvSvc: 列表(举手会话靠前)
|
||||
API-->>Agent: 举手标记的会话(黄色标签)
|
||||
end
|
||||
|
||||
Agent->>API: POST /api/conversations/{id}/assign {agent_id}
|
||||
API->>ConvSvc: update_conversation(status='serving', assigned_agent_id)
|
||||
ConvSvc->>DB: UPDATE conversations SET status='serving'
|
||||
ConvSvc->>ConvSvc: send_message(conv_id, 'system', '人摇来了!IT坐席为您服务')
|
||||
ConvSvc->>WXSvc: send_text_message(employee_id, 接入话术)
|
||||
ConvSvc-->>API: updated conversation
|
||||
API-->>Agent: 接单成功
|
||||
|
||||
WXSvc-->>Emp: 企微推送"人摇来了!IT坐席为您服务"
|
||||
Reference in New Issue
Block a user