feat: BossAi 老板AI助手初始版本

包含功能:
- 法务参谋/经营决策/生活助理/情绪中心四大AI对话模块
- 专家咨询系统(用户选定→专家确认→咨询→回复→评价)
- 角色登录(用户/专家/管理员)+ ProtectedRoute 权限控制
- 管理后台(用户管理/专家管理/AI使用统计/专家活跃度)
- 专家工作台(待确认服务/待回复咨询/已回复咨询/服务中用户)
- 服务关系取消(用户和专家均可取消,需自己确认)
- 全页面移动端响应式适配
- 知识库RAG检索
- 对话历史(普通/保密模式)

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-29 23:57:26 +08:00
co-authored by Devin
commit 5c21e8fcd0
101 changed files with 25273 additions and 0 deletions
@@ -0,0 +1,60 @@
import { useState } from 'react'
import { MessageSquare, Send } from 'lucide-react'
import { EmptyState } from '@/components/common/EmptyState'
import { Markdown } from '@/components/common/Markdown'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import type { Consultation } from '@/types'
import { useWorkbenchData, WorkbenchShell, ReplyDialog, maskPhone } from './shared'
/** 专家工作台 - 待回复咨询:用户发起的咨询,专家在此回复 */
export function WorkbenchPending() {
const { expert, consultations, loading, error, loadData } = useWorkbenchData()
const [replyTarget, setReplyTarget] = useState<Consultation | null>(null)
const pendingConsultations = consultations.filter(c => c.status === 'pending')
return (
<WorkbenchShell expert={expert} loading={loading} error={error} title="待回复咨询" description="用户发起的咨询,在此回复">
{pendingConsultations.length === 0 ? (
<EmptyState title="暂无待回复咨询" description="用户发起咨询后会显示在这里" icon={<MessageSquare className="h-8 w-8" />} />
) : (
<div className="space-y-3">
{pendingConsultations.map((c) => (
<Card key={c.id}>
<CardContent className="py-4">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<p className="truncate text-sm font-medium">{c.topic}</p>
<Badge variant="destructive" className="shrink-0 text-xs">待回复</Badge>
</div>
<p className="mt-0.5 text-xs text-muted-foreground">用户:{maskPhone(c.user_phone || '')}</p>
{c.question && (
<div className="mt-2 text-sm text-muted-foreground">
<Markdown content={c.question} />
</div>
)}
{c.context && (
<div className="mt-2 rounded-md bg-muted/30 p-2 text-xs text-muted-foreground">
<p className="mb-1 font-medium">AI 对话上下文:</p>
<Markdown content={c.context} />
</div>
)}
<p className="mt-2 text-xs text-muted-foreground">创建时间:{c.created_at}</p>
</div>
<Button size="sm" className="shrink-0" onClick={() => setReplyTarget(c)}>
<Send className="h-3.5 w-3.5" /> 回复
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)}
<ReplyDialog target={replyTarget} onClose={() => setReplyTarget(null)} onSuccess={loadData} />
</WorkbenchShell>
)
}