Files
ai-kefu-admin/admin/src/api/conversations.ts
T
浅念 ce668eaf2f feat: 会话与线索(列表/筛选/租户切换/详情消息流/推送日志/删除)
- 会话列表页签:分页表格 + 筛选(意向三值/站点精确/日期区间 daterange,
  筛选变化重置第一页)+ 行操作(详情抽屉/删除确认框,级联消息画像评估
  推送日志,末页删空自动回退一页)
- 租户切换器:超管专用(fetchTenantList 下拉,默认第一个,切换联动双页签
  并关抽屉);租户管理员固定登录态 tenant_slug,无切换器只有标签
- 详情抽屉:元数据(状态/来源/站点/IP 归国/session/设备)+ 画像 chips
  (ElTag 键值)+ 评估块(意向/理由/联系方式/更正次数)+ 消息流气泡
  (user 右 assistant 左,时间直显库内 naive UTC 不换算,与筛选同口径)
- 推送日志页签(列表平行):状态筛选(pending/success/failed/skipped)
  + 分页表格(类型/状态/尝试/outcome/HTTP/payload 大小/会话/错误)——
  列表接口不回大 payload,仅服务端 payload_size
- api/conversations.ts 实装调用函数并按 data_routes.py 实际响应修正类型
  (IntentLevel 三值/messages.time/详情扁平 evaluation+profile 块/
  PushLogItem 全字段),新增枚举中文映射(intentLabel 等,未知值回退原样)
- vitest +9(conversations API 契约:URL/参数/白名单/映射),总 45;
  后端不动(pytest 577 基线复核)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-13 17:31:12 +08:00

240 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 会话与线索 API(Phase 3 Task 3 骨架;Task 5 实装调用函数并按实际响应修正类型)
*
* 端点与响应事实源:app/platform/routes/data_routes.py
* - 列表:{total, page, page_size, items},行含 preview/intent_level/contact_info/
* email_sent/message_count/country/site 等;筛选 intent 白名单(interested/
* not_interested/pending,非法 400)/site 精确/client_info["site"]/from-to 闭区间
* (日期-only 的 to 含当日全天)
* - 详情:扁平结构(元数据 + evaluation/profile/messages 三块),消息行字段为
* time(非 created_at);profile.chips 为 raw_info 去 _ 前缀键
* - 删除:{ok: true};不存在 404(级联消息/画像/评估/推送日志)
* - push-logs:列表不回大 payload(load_only 排除 request_payload),带
* payload_size 服务端长度标记;status 白名单 pending/success/failed/skipped
*/
import { http } from './client'
/** 意向层级(Evaluation.intent_level 枚举;列表筛选白名单同值) */
export type IntentLevel = 'interested' | 'not_interested' | 'pending'
/** 推送状态(LeadPushLog.status;筛选白名单同值) */
export type PushLogStatus = 'pending' | 'success' | 'failed' | 'skipped'
/** 联系方式(评估产出,留资三选一) */
export interface ContactInfo {
email?: string | null
whatsapp?: string | null
phone?: string | null
}
/** 会话列表行(GET /api/platform/tenants/{slug}/conversations) */
export interface ConversationListItem {
id: string
session_id: string
status: string
source: string
site: string | null
ip: string
device: string
country: string | null
intent_level: IntentLevel | null
reasoning: string | null
contact_info: ContactInfo | null
email_sent: boolean
message_count: number
preview: string
created_at: string | null
}
/** 会话列表查询参数(intent 白名单校验在后端;from/to 为闭区间日期 YYYY-MM-DD) */
export interface ConversationListParams {
intent?: IntentLevel
site?: string
from?: string
to?: string
page?: number
page_size?: number
}
/** 会话列表响应 */
export interface ConversationListResponse {
total: number
page: number
page_size: number
items: ConversationListItem[]
}
/** 会话详情消息行(时间升序;字段为 time) */
export interface ConversationMessage {
role: 'user' | 'assistant' | 'system' | string
content: string
time: string | null
}
/** 评估块(详情内嵌,未评估为 null) */
export interface ConversationEvaluation {
intent_level: IntentLevel | null
reasoning: string | null
contact_info: ContactInfo | null
email_sent: boolean
correction_count: number | null
evaluated_at: string | null
}
/** 会话详情响应(GET .../conversations/{convId}——扁平,与列表行不同构) */
export interface ConversationDetail {
id: string
session_id: string
status: string
source: string
client_info: Record<string, unknown>
site: string | null
ip: string
device: string
country: string | null
created_at: string | null
updated_at: string | null
evaluation: ConversationEvaluation | null
profile: {
chips: Record<string, unknown>
summary: string | null
} | null
messages: ConversationMessage[]
}
/** 推送日志行(GET .../push-logs——无 request_payload,仅 payload_size 长度标记) */
export interface PushLogItem {
id: string
evaluation_id: string
conversation_id: string
push_type: string
status: PushLogStatus | string
attempts: number
outcome: string | null
response_status: number | null
error_message: string | null
payload_size: number | null
created_at: string | null
updated_at: string | null
}
/** 推送日志查询参数(status 白名单校验在后端) */
export interface PushLogParams {
status?: PushLogStatus
conversation_id?: string
page?: number
page_size?: number
}
/** 推送日志响应 */
export interface PushLogResponse {
total: number
page: number
page_size: number
items: PushLogItem[]
}
/** 会话与线索端点 */
export const CONVERSATION_ENDPOINTS = {
list: (slug: string) => `/api/platform/tenants/${slug}/conversations`,
detail: (slug: string, convId: string) =>
`/api/platform/tenants/${slug}/conversations/${convId}`,
remove: (slug: string, convId: string) =>
`/api/platform/tenants/${slug}/conversations/${convId}`,
pushLogs: (slug: string) => `/api/platform/tenants/${slug}/push-logs`
} as const
// ---- 展示映射(枚举值事实源 data_routes.py INTENTS/PUSH_STATUSES 与 models.py) ----
/** 意向筛选/展示选项 */
export const INTENT_OPTIONS: Array<{ value: IntentLevel; label: string }> = [
{ value: 'interested', label: '有意向' },
{ value: 'not_interested', label: '无意向' },
{ value: 'pending', label: '待定' }
]
/** 意向展示文案(未知值回退原样) */
export function intentLabel(level: string | null | undefined): string {
if (!level) return '未评估'
return INTENT_OPTIONS.find((item) => item.value === level)?.label ?? level
}
/** 推送状态筛选/展示选项 */
export const PUSH_STATUS_OPTIONS: Array<{ value: PushLogStatus; label: string }> = [
{ value: 'pending', label: '待推送' },
{ value: 'success', label: '成功' },
{ value: 'failed', label: '失败' },
{ value: 'skipped', label: '跳过' }
]
/** 推送状态展示文案 */
export function pushStatusLabel(status: string | null | undefined): string {
if (!status) return '—'
return PUSH_STATUS_OPTIONS.find((item) => item.value === status)?.label ?? status
}
/** 推送类型展示文案(LeadPushLog.push_type:lead/returning/correction) */
export function pushTypeLabel(type: string | null | undefined): string {
const labels: Record<string, string> = {
lead: '新线索',
returning: '回访',
correction: '更正'
}
if (!type) return '—'
return labels[type] ?? type
}
/** 会话状态展示文案(ConversationStatus) */
export function conversationStatusLabel(status: string | null | undefined): string {
const labels: Record<string, string> = {
active: '进行中',
completed: '已完成',
abandoned: '已放弃'
}
if (!status) return '—'
return labels[status] ?? status
}
/** 会话来源展示文案(ConversationSource) */
export function conversationSourceLabel(source: string | null | undefined): string {
const labels: Record<string, string> = {
standalone: '独立站',
embedded: '内嵌页',
widget: '挂件',
app: '应用'
}
if (!source) return '—'
return labels[source] ?? source
}
// ---- 调用函数 -------------------------------------------------------------------
/** 会话列表(分页 + intent/site/日期区间筛选) */
export function fetchConversations(
slug: string,
params?: ConversationListParams
): Promise<ConversationListResponse> {
return http.get<ConversationListResponse>(CONVERSATION_ENDPOINTS.list(slug), { params })
}
/** 会话详情(元数据 + 画像 chips + 评估 + 消息全量) */
export function fetchConversationDetail(
slug: string,
convId: string
): Promise<ConversationDetail> {
return http.get<ConversationDetail>(CONVERSATION_ENDPOINTS.detail(slug, convId))
}
/** 删除会话(级联消息/画像/评估/推送日志;不存在 404) */
export function deleteConversation(
slug: string,
convId: string
): Promise<{ ok: boolean }> {
return http.del<{ ok: boolean }>(CONVERSATION_ENDPOINTS.remove(slug, convId))
}
/** 推送日志列表(status/conversation_id 筛选 + 分页;不回大 payload) */
export function fetchPushLogs(slug: string, params?: PushLogParams): Promise<PushLogResponse> {
return http.get<PushLogResponse>(CONVERSATION_ENDPOINTS.pushLogs(slug), { params })
}