"use client"; import {ChangeEvent,FormEvent,useEffect,useMemo,useState} from "react"; import {ArrowUpRight,Bot,FilePlus2,Paperclip,Send,Sparkles,UserRound} from "lucide-react"; import AgentNav from "./AgentNav"; import IdentitySelector,{ROLE_LABEL} from "./IdentitySelector"; import {newRequestId} from "@/lib/request-id"; import "./business-assistant.css"; const ROOT="/api/risk-agent/project-assistant"; type Message={id:string;side:"assistant"|"user";text:string;url?:string;objectId?:string}; function headers(json=true){ const token=sessionStorage.getItem("xingbang-access-token"),role=sessionStorage.getItem("xingbang-demo-role"); return {...(json?{"Content-Type":"application/json"}:{}),...(token?{Authorization:`Bearer ${token}`}:{}) ,...(role?{"X-Xingbang-Demo-Role":role}:{})}; } export default function BusinessAssistant(){ const [projects,setProjects]=useState([]),[identity,setIdentity]=useState(null),[projectId,setProjectId]=useState(""); const [input,setInput]=useState(""),[busy,setBusy]=useState(false),[error,setError]=useState(""); const [messages,setMessages]=useState([{id:"hello",side:"assistant",text:"你好,我是兴邦对公业务助手。选择项目后,你可以直接告诉我查状态、发起AI预审、生成报告或补充材料。"}]); const role=identity?.role||"manager",project=projects.find(p=>p.id===projectId); const quick=useMemo(()=>role==="reviewer"?["查看项目状态","发起独立评审","生成评审报告"]:role==="admin"?["查看项目状态"]:["查看项目状态","我有几个 Skills?","哪些金融相关 Skills?","发起AI预审","生成预审报告"],[role]); async function load(){ setError(""); try{const response=await fetch(`${ROOT}/projects`,{headers:headers()});const body=await response.json();if(!response.ok)throw Error(body.detail||"身份或项目读取失败");setProjects(body.projects);setIdentity(body.identity);const requested=new URLSearchParams(location.search).get("project");setProjectId(current=>current||(requested&&body.projects.some((p:any)=>p.id===requested)?requested:body.projects[0]?.id||""));} catch(e:any){setError(e.message)} } useEffect(()=>{load()},[]); async function send(text:string){ const message=text.trim();if(!message||!projectId||busy)return; if(message.includes("提交")&&message.includes("评审")&&!window.confirm(`确认将 ${projectId} 的AI预审报告提交正式评审?提交后保留当前版本。`))return; setMessages(old=>[...old,{id:newRequestId(),side:"user",text:message}]);setInput("");setBusy(true);setError(""); try{const catalogQuery=/skill|rules?|技能|规则|数据卡|模板/i.test(message);const response=await fetch(`${ROOT}/${catalogQuery?"library/inventory":"commands"}`,{method:"POST",headers:{...headers(),"Idempotency-Key":newRequestId()},body:JSON.stringify({project_id:projectId,message})});const body=await response.json();if(!response.ok)throw Error(body.detail||"业务指令执行失败");setMessages(old=>[...old,{id:newRequestId(),side:"assistant",text:body.message,url:body.url,objectId:body.object_id}]);await load();} catch(e:any){setError(e.message);setMessages(old=>[...old,{id:newRequestId(),side:"assistant",text:`未执行:${e.message}`}]);} finally{setBusy(false)} } async function attach(event:ChangeEvent){ const file=event.target.files?.[0];event.target.value="";if(!file||!projectId||busy)return; setMessages(old=>[...old,{id:newRequestId(),side:"user",text:`把材料“${file.name}”补充到 ${projectId}`}]);setBusy(true);setError(""); try{const form=new FormData();form.append("file",file);const response=await fetch(`${ROOT}/projects/${encodeURIComponent(projectId)}/documents`,{method:"POST",headers:headers(false),body:form});const body=await response.json();if(!response.ok)throw Error(body.detail||"材料上传失败");setMessages(old=>[...old,{id:newRequestId(),side:"assistant",text:`材料已归档并解析出 ${body.candidates?.length||0} 个候选字段。为避免误改事实,请到项目页确认来源。`,url:`/xingbang-workbench?project=${encodeURIComponent(projectId)}&tab=findings`,objectId:body.id}]);await load();} catch(e:any){setError(e.message);setMessages(old=>[...old,{id:newRequestId(),side:"assistant",text:`材料未补充:${e.message}`}]);} finally{setBusy(false)} } function submit(event:FormEvent){event.preventDefault();send(input)} const conversation=
{messages.map(message=>
{message.side==="assistant"&&}

{message.text}

{message.side==="user"&&}
{message.objectId&&操作凭证 · {message.objectId}}{message.url&&查看项目中的结果}
)}{busy&&

正在执行并生成可追溯记录…

}
{error&&
{error}
}
setInput(e.target.value)} placeholder={`例如:帮我做一次 ${projectId||"当前项目"} 风险研判`}/>
当前身份只会调用本项目授权的 Skills、规则和业务工具,并保留操作凭证。
; return

XINGBANG BUSINESS ASSISTANT

一句话,推进对公业务

对话和页面按钮共用同一套 Skills、规则、权限与留痕。
正在处理
{project&&<>
当前阶段{project.stage}
下一步{project.next_action}
进入项目}
{conversation}
; }