AI sections fall back to fetch with no Qt bridge, for non-React web frontends
ai_sections.html fetches {API_BASE}+path when window.qt is absent; a page-check case routes those requests to server_stub.dispatch(). AI SKILL.md step 6 and frontend.md send plain-HTML/Vue/Angular/Svelte projects to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
344718007e
commit
461798127a
6 files changed
+100
-13
No files matched your search
@@ -15,7 +15,7 @@ means `../rpa-dashboard-no-ai/templates/`.
|
||||
| `templates/web/pricing.py` | The pricing table (with its source and as-of date) and `cost_for_call`. The backend imports it. |
|
||||
| `templates/web/server_stub.py` | Sample-data backend for runs, review rows and the approve gate. Standard library only. |
|
||||
| `templates/web/bands.py` | The band cut points and rules. The one source of truth: the backend imports it. |
|
||||
| `templates/desktop/ai_sections.html` | Desktop: the Review tab (band legend, review split pane, drafts, Verify, approve gate) and the Cost tab in plain HTML, pasted into `<no-ai>dashboard.html`. |
|
||||
| `templates/desktop/ai_sections.html` | Desktop, and web projects with a non-React frontend: the Review tab (band legend, review split pane, drafts, Verify, approve gate) and the Cost tab in plain HTML, pasted into `<no-ai>dashboard.html`. Talks over the Qt bridge, or with no Qt, fetches the `templates/contract.md` endpoints. |
|
||||
| `templates/desktop/ai_bridge.py` | Desktop: the no-AI `Bridge` plus the `ai_request` slot that hands every AI request to the Python review backend. |
|
||||
| `templates/contract.md` | The AI contract: runs, review rows, endpoints and the gate, over HTTP or the Qt bridge. Extends `<no-ai>contract.md`. |
|
||||
| `templates/reference/` | Superbill Review extracts, marked EXAMPLE ONLY. Use them to port extra polish, never copy their project names. |
|
||||
@@ -59,7 +59,8 @@ Skip this if the user chose keep. Offer:
|
||||
|
||||
Ask only if there is no frontend. If there is one, use its stack. If the project
|
||||
uses PyQt, choose desktop and say so.
|
||||
- **Web:** the React starter in `templates/web/`, served by the backend.
|
||||
- **Web:** the React starter in `templates/web/`, served by the backend. An existing
|
||||
non-React frontend gets the plain-HTML page instead (step 6, web).
|
||||
- **Desktop:** the no-AI PyQt page with `templates/desktop/ai_sections.html` pasted
|
||||
in, hosted by `templates/desktop/ai_bridge.py`. No web server, no npm.
|
||||
|
||||
@@ -83,9 +84,17 @@ Both need the endpoints in `templates/contract.md`; add them to the backend (and
|
||||
|
||||
## 6. Build (web)
|
||||
|
||||
1. Copy `templates/web/` into the project as `dashboard/` (an existing frontend:
|
||||
see `<no-ai>frontend.md` section 3; in an existing React app, port the
|
||||
`src/` components into it). Don't copy `node_modules/` or `dist/`.
|
||||
**Existing plain-HTML, Vue, Angular or Svelte frontend:** don't use the React starter.
|
||||
Build the no-AI page as `../rpa-dashboard-no-ai/SKILL.md` step 6 says (web, with
|
||||
`API_BASE` filled, for example `/api`), paste the whole of `templates/desktop/ai_sections.html`
|
||||
into it just before `</body>`, and put it where `<no-ai>frontend.md` section 3 says. With no
|
||||
Qt bridge, its AI sections fetch `{API_BASE}` + the paths in `templates/contract.md`, so
|
||||
the backend serves those (step 7). Port it into the app's components only if the user asks,
|
||||
with `ai_sections.html` as the reference. Then skip to step 7.
|
||||
|
||||
1. Copy `templates/web/` into the project as `dashboard/` (an existing React app:
|
||||
port the `src/` components into it, see `<no-ai>frontend.md` section 3).
|
||||
Don't copy `node_modules/` or `dist/`.
|
||||
2. Fill every `{{...}}` in `dashboard/index.html`'s CONFIG block:
|
||||
|
||||
| Placeholder | Value |
|
||||
@@ -122,7 +131,7 @@ Both need the endpoints in `templates/contract.md`; add them to the backend (and
|
||||
The contracts: `<no-ai>contract.md` for the shell, `templates/contract.md` for runs,
|
||||
review rows and the gate.
|
||||
- **Sample data (no backend for these endpoints yet):** keep `SAMPLE_DATA = true` in
|
||||
`src/store.ts` and use `server_stub.py`. Replace its `SAMPLE_FIELDS` with the
|
||||
`src/store.ts` (a plain-HTML page: in `dashboard.html`) and use `server_stub.py`. Replace its `SAMPLE_FIELDS` with the
|
||||
fields the project's extraction really returns (name, a sample value, word scores,
|
||||
mandatory). Tell the user which endpoints their backend must serve later.
|
||||
- **Backend exists:** add the endpoints from `templates/contract.md` to it. Copy
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
Paste this whole block into the no-AI dashboard.html, just before </body>. It adds a Review
|
||||
tab: band legend, band filters, the source document beside its extracted fields, drafts,
|
||||
Verify, and Approve. Plain HTML, the page's own theme tokens, no build step.
|
||||
Every request goes over the Qt bridge as one slot call, ai_request(id, method, path, body),
|
||||
and comes back as an `ai_response` envelope (see ../contract.md, "Desktop"). The approve
|
||||
gate is the Python backend's: this page only asks, and shows the refusal.
|
||||
In PyQt, every request goes over the Qt bridge as one slot call, ai_request(id, method, path,
|
||||
body), and comes back as an `ai_response` envelope (see ../contract.md, "Desktop"). With no
|
||||
Qt (a plain-HTML web project), it fetches {API_BASE}{path} from the backend instead, the
|
||||
web endpoints in ../contract.md. The approve gate is the backend's: this page only asks,
|
||||
and shows the refusal.
|
||||
-->
|
||||
<style>
|
||||
/* BAND COLOURS: semantic only (swatches, the rule down a field). The same in every preset and both modes. */
|
||||
@@ -117,10 +119,19 @@ showTab=t=>{
|
||||
};
|
||||
window.addEventListener('beforeunload',e=>{ if(dirty().length) e.preventDefault(); });
|
||||
|
||||
// ----- transport: one bridge slot out, one `ai_response` envelope back -----
|
||||
// ----- transport: in PyQt, one bridge slot out, one `ai_response` envelope back;
|
||||
// with no Qt (a plain web page), fetch {API_BASE}{path} from the backend -----
|
||||
const pending={}; let seq=0;
|
||||
async function aiFetch(method,path,body){
|
||||
const r=await fetch(API_BASE+path,{method,headers:{'Content-Type':'application/json'},
|
||||
body:method==='GET'?undefined:JSON.stringify(body||{})});
|
||||
const b=await r.json().catch(()=>({}));
|
||||
if(!r.ok) throw new Error(b.detail||`Error ${r.status}`);
|
||||
return b;
|
||||
}
|
||||
function ai(method,path,body){
|
||||
if(!bridge) return Promise.reject(new Error('Review needs the desktop app (no Qt bridge here).'));
|
||||
if(!window.qt) return aiFetch(method,path,body);
|
||||
if(!bridge) return Promise.reject(new Error('The Qt bridge is not ready yet. Try again.'));
|
||||
return new Promise((ok,fail)=>{ const id=++seq; pending[id]=[ok,fail];
|
||||
bridge.ai_request(id,method,path,JSON.stringify(body||{})); });
|
||||
}
|
||||
@@ -149,7 +160,8 @@ function openRun(id){
|
||||
clearTimeout(timer);
|
||||
jobId=id; run=null; rows=[]; drafts={}; refusal=null;
|
||||
$('rv-doc-pick').value=id;
|
||||
ai('GET',`/runs/${id}/document`).then(doc=>{ if(id===jobId) $('rv-doc').src=
|
||||
if(!window.qt) $('rv-doc').src=`${API_BASE}/runs/${encodeURIComponent(id)}/document`; // web: the URL itself
|
||||
else ai('GET',`/runs/${id}/document`).then(doc=>{ if(id===jobId) $('rv-doc').src=
|
||||
String(doc).startsWith('<') ? 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(doc) : doc; }).catch(()=>{});
|
||||
render(); tick(id);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user