diff --git a/backend/apps/outputs/app_builder_skill.md b/backend/apps/outputs/app_builder_skill.md index ed740f0b..5ac73fd0 100644 --- a/backend/apps/outputs/app_builder_skill.md +++ b/backend/apps/outputs/app_builder_skill.md @@ -325,6 +325,43 @@ export const JOBS_LIST = '/api/jobs/list'; --- +## Publishable AI + compute — `window.OUTPUT_LLM` / `window.OUTPUT_COMPUTE` + +The FastAPI backend above runs in preview but is **not hosted when an app is +published** to the web. For features that should keep working on a published +`{slug}.openswarm.dev` link, use these two runtime calls instead of a backend. +They behave the same in preview and when published. + +**AI (Claude):** call `window.OUTPUT_LLM` with an Anthropic-style messages body. +The model is chosen for you (a cheap default), so don't pass one. + +```ts +const res = await window.OUTPUT_LLM({ + messages: [{ role: 'user', content: prompt }], + max_tokens: 512, +}); +const data = await res.json(); +const text = data.content[0].text; +``` + +**Data-shaping compute:** put pure Python (json/math/csv/datetime only — no +network, no files) in a top-level `backend.py` that reads `input_data` and assigns +`result`, then call `window.OUTPUT_COMPUTE(input)`: + +```python +# backend.py +result = {"total": sum(input_data["nums"])} +``` +```ts +const out = await window.OUTPUT_COMPUTE({ nums: [1, 2, 3] }); // -> { total: 6 } +``` + +Rule of thumb: if the app should be publishable, reach for `OUTPUT_LLM` / +`OUTPUT_COMPUTE` first; only use the FastAPI backend for preview-only tools or +things those two can't do (it won't be there once published). + +--- + ## Debugging — use `swarm_debug`, not `print()` The backend has `swarm_debug` pre-installed. It's a colored frame-aware diff --git a/backend/apps/outputs/html_inject.py b/backend/apps/outputs/html_inject.py index c9fd8d1b..a579d645 100644 --- a/backend/apps/outputs/html_inject.py +++ b/backend/apps/outputs/html_inject.py @@ -54,20 +54,50 @@ def _validate_against_schema(data: dict, schema: dict) -> str | None: return f"Schema validation failed at {path}: {exc.message}" -def _build_data_injection(input_json: str, result_json: str, backend_url_json: str = "null") -> str: +def _runtime_helpers_js(token: str, output_id: str | None) -> str: + """OUTPUT_COMPUTE / OUTPUT_LLM: the same runtime API the published edge injects, + pointed at this install's backend so an app works in preview AND when published. + The install token rides in the header (the iframe already exposes it via the + relative-URL rewrite; this is the same local-only credential, not a cloud secret).""" + auth = json.dumps(f"Bearer {token}") + js = "" + if output_id: + oid = json.dumps(output_id) + js += ( + " window.OUTPUT_COMPUTE = async function (input) {\n" + " var r = await fetch('/api/outputs/execute', {method:'POST', headers:{'Content-Type':'application/json','Authorization': " + auth + "}, body: JSON.stringify({output_id: " + oid + ", input_data: input || {}, force: true})});\n" + " var d = await r.json();\n" + " if (d.error) throw new Error(d.error);\n" + " return d.backend_result;\n" + " };\n" + ) + js += ( + " window.OUTPUT_LLM = async function (body) {\n" + " return fetch('/api/outputs/llm', {method:'POST', headers:{'Content-Type':'application/json','Authorization': " + auth + "}, body: JSON.stringify(body || {})});\n" + " };\n" + ) + return js + + +def _build_data_injection(input_json: str, result_json: str, backend_url_json: str = "null", runtime: dict | None = None) -> str: """Build a