admin / Apex
publicBid Management and Orchestration Tool with AI Capability
Apex / Synapse-Apexv2 / frontend / src / pages / Capabilities.tsx
3964 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | import { useEffect, useState } from "react"; import { api } from "../lib/api"; export default function Capabilities() { const [caps, setCaps] = useState<any[]>([]); const [frameworks, setFrameworks] = useState<any[]>([]); const [selFw, setSelFw] = useState<number[]>([]); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState(""); async function load() { setCaps(await api.get("/capabilities")); } useEffect(() => { load(); api.get("/bids/meta/frameworks").then(setFrameworks); }, []); async function upload(e: React.ChangeEvent<HTMLInputElement>) { if (!e.target.files?.length) return; setBusy(true); setMsg(""); const fd = new FormData(); fd.append("kind", "service"); fd.append("framework_ids", selFw.join(",")); Array.from(e.target.files).forEach((f) => fd.append("files", f)); try { await api.postForm("/capabilities/upload", fd); setMsg("Extraction started — capabilities will appear shortly. Click Refresh."); } catch (err: any) { setMsg(err.message); } setBusy(false); } async function approve(id: number) { await api.post(`/capabilities/${id}/approve`); load(); } async function del(id: number) { await api.del(`/capabilities/${id}`); load(); } async function saveResp(rid: number, text: string) { await api.patch(`/capabilities/responses/${rid}`, { response_text: text }); } async function regen(rid: number) { await api.post(`/capabilities/responses/${rid}/regenerate`); setMsg("Regeneration queued."); } return ( <div> <div className="topbar"><div><h1>Capability library</h1><span className="muted">Module 1 — service & architecture ingestion</span></div></div> <div className="card"> <h2>Ingest documents</h2> <label>Align generated responses to frameworks</label> <select multiple value={selFw.map(String)} onChange={(e) => setSelFw(Array.from(e.target.selectedOptions).map((o) => Number(o.value)))} style={{ maxWidth: 320 }}> {frameworks.map((f) => <option key={f.id} value={f.id}>{f.name}</option>)} </select> <div className="row" style={{ marginTop: 10 }}> <label className="secondary" style={{ border: "1px solid var(--apex-indigo)", padding: "8px 14px", borderRadius: 8, color: "var(--apex-indigo)", cursor: "pointer", width: "auto", margin: 0 }}> {busy ? "Uploading…" : "Upload service / architecture docs"} <input type="file" multiple hidden onChange={upload} accept=".pdf,.docx,.xlsx,.txt,.md" /> </label> <button className="ghost" onClick={load}>Refresh</button> </div> {msg && <p className="muted">{msg}</p>} </div> {caps.map((c) => ( <div className="card" key={c.id}> <div className="spread"> <div><b>{c.name}</b> <span className="chip">{c.category}</span> <span className={"chip " + (c.status === "approved" ? "ok" : "")}>{c.status}</span></div> <div className="row"> {c.status !== "approved" && <button className="secondary" onClick={() => approve(c.id)}>Approve for reuse</button>} <button className="iconbtn danger" onClick={() => del(c.id)}>✕</button> </div> </div> <p className="muted" style={{ fontSize: 14 }}>{c.description}</p> {c.responses.map((r: any) => ( <div key={r.id} style={{ marginTop: 8 }}> <label>Framework-aligned response {r.approved && <span className="chip ok">approved</span>}</label> <textarea rows={4} defaultValue={r.response_text} onBlur={(e) => saveResp(r.id, e.target.value)} /> <button className="ghost" style={{ marginTop: 4 }} onClick={() => regen(r.id)}>Regenerate</button> </div> ))} </div> ))} {caps.length === 0 && <div className="card muted">No capabilities yet. Upload documents to extract them.</div>} </div> ); } |