[Haik]: removed app input functionality entirely as this will no longer be used

This commit is contained in:
haikdc
2026-04-05 16:49:44 -07:00
parent 6fbd766bbe
commit 61212c648d
6 changed files with 17 additions and 172 deletions
-13
View File
@@ -9,11 +9,6 @@ class App(BaseModel):
name: str
description: str = ""
icon: str = "view_quilt"
input_schema: dict[str, Any] = Field(default_factory=lambda: {
"type": "object",
"properties": {},
"required": [],
})
files: dict[str, str] = Field(default_factory=dict)
thumbnail: Optional[str] = None
created_at: str = Field(default_factory=lambda: datetime.now().isoformat())
@@ -24,11 +19,6 @@ class AppCreate(BaseModel):
name: str
description: str = ""
icon: str = "view_quilt"
input_schema: dict[str, Any] = Field(default_factory=lambda: {
"type": "object",
"properties": {},
"required": [],
})
files: dict[str, str] = Field(default_factory=dict)
thumbnail: Optional[str] = None
@@ -37,21 +27,18 @@ class AppUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
icon: Optional[str] = None
input_schema: Optional[dict[str, Any]] = None
files: Optional[dict[str, str]] = None
thumbnail: Optional[str] = None
class AppExecute(BaseModel):
app_id: str
input_data: dict[str, Any] = Field(default_factory=dict)
class AppExecuteResult(BaseModel):
app_id: str
app_name: str
frontend_code: str
input_data: dict[str, Any]
backend_result: Optional[dict[str, Any]] = None
stdout: Optional[str] = None
stderr: Optional[str] = None
+6 -23
View File
@@ -18,9 +18,7 @@ from backend.apps.app_builder.App import (
from backend.config.paths import DB_ROOT
from backend.apps.app_builder.executor import execute_backend_code
from backend.apps.app_builder.templates import APP_BUILDER_SKILL, APP_BUILDER_TEMPLATE_FILES
from backend.apps.app_builder.helpers import (
validate_against_schema, inject_data_into_html, decode_data_param, walk_directory,
)
from backend.apps.app_builder.helpers import walk_directory
APP_BUILDER_DIR = os.path.join(DB_ROOT, "app_builder")
APP_BUILDER_WORKSPACE_DIR = os.path.join(APP_BUILDER_DIR, "workspace")
@@ -42,7 +40,7 @@ _store = PydanticStore[App](model_cls=App, data_dir=APP_BUILDER_DIR, not_found_d
# ---------------------------------------------------------------------------
@app_builder.router.get("/workspace/{workspace_id}/serve/{filepath:path}")
async def serve_workspace_file(workspace_id: str, filepath: str, _d: str = ""):
async def serve_workspace_file(workspace_id: str, filepath: str):
folder = os.path.join(APP_BUILDER_WORKSPACE_DIR, workspace_id)
full_path = os.path.normpath(os.path.join(folder, filepath))
if not full_path.startswith(os.path.normpath(folder)):
@@ -51,22 +49,16 @@ async def serve_workspace_file(workspace_id: str, filepath: str, _d: str = ""):
raise HTTPException(status_code=404, detail="File not found")
with open(full_path) as f:
content = f.read()
if filepath == "index.html":
input_json, result_json = decode_data_param(_d) if _d else ("{}", "null")
content = inject_data_into_html(content, input_json, result_json)
mime, _ = mimetypes.guess_type(filepath)
return Response(content=content, media_type=mime or "text/plain")
@app_builder.router.get("/{app_id}/serve/{filepath:path}")
async def serve_app_file(app_id: str, filepath: str, _d: str = ""):
async def serve_app_file(app_id: str, filepath: str):
app = _store.load(app_id)
content = app.files.get(filepath)
if content is None:
raise HTTPException(status_code=404, detail="File not found in app")
if filepath == "index.html":
input_json, result_json = decode_data_param(_d) if _d else ("{}", "null")
content = inject_data_into_html(content, input_json, result_json)
mime, _ = mimetypes.guess_type(filepath)
return Response(content=content, media_type=mime or "text/plain")
@@ -168,8 +160,8 @@ async def create_app(body: AppCreate):
now = datetime.now().isoformat()
app = App(
name=body.name, description=body.description, icon=body.icon,
input_schema=body.input_schema, files=body.files,
thumbnail=body.thumbnail, created_at=now, updated_at=now,
files=body.files, thumbnail=body.thumbnail,
created_at=now, updated_at=now,
)
_store.save(app)
return {"ok": True, "app": app.model_dump()}
@@ -199,14 +191,6 @@ async def delete_app(app_id: str):
@app_builder.router.post("/execute")
async def execute_app(body: AppExecute):
app = _store.load(body.app_id)
validation_err = validate_against_schema(body.input_data, app.input_schema)
if validation_err:
return AppExecuteResult(
app_id=app.id, app_name=app.name,
frontend_code=app.files.get("index.html", ""),
input_data=body.input_data,
backend_result=None, error=validation_err,
).model_dump()
backend_code = app.files.get("backend.py")
backend_result = None
@@ -215,7 +199,7 @@ async def execute_app(body: AppExecute):
error = None
if backend_code:
try:
exec_result = await execute_backend_code(backend_code, body.input_data)
exec_result = await execute_backend_code(backend_code)
backend_result = exec_result.result
stdout_text = exec_result.stdout
stderr_text = exec_result.stderr
@@ -225,7 +209,6 @@ async def execute_app(body: AppExecute):
return AppExecuteResult(
app_id=app.id, app_name=app.name,
frontend_code=app.files.get("index.html", ""),
input_data=body.input_data,
backend_result=backend_result, stdout=stdout_text,
stderr=stderr_text, error=error,
).model_dump()
+5 -66
View File
@@ -12,7 +12,6 @@ here is served directly to the live preview.
|------|----------|---------|
| `index.html` | **Yes** | Entry point. Must be a complete HTML document. This is the ONLY file the preview iframe loads — never rename it. |
| `meta.json` | **Yes** | `{"name":"…","description":"…"}` — displayed in the UI header. Always write this. |
| `schema.json` | Recommended | JSON Schema defining the input form (the "Test Input" tab). |
| `backend.py` | Optional | Server-side Python executed before rendering. |
| Everything else | Optional | JS, CSS, images, subdirectories — referenced from `index.html` via relative paths. |
@@ -20,77 +19,24 @@ here is served directly to the live preview.
- Name the main HTML file anything other than `index.html` — the platform
will not find it and the preview will be blank.
- Use `document.write()` — it breaks the injected data globals.
- Assume any external server or API is available unless the user provides one.
---
## Injected globals
Before `index.html` loads, the platform injects two globals:
```javascript
window.APP_BUILDER_INPUT // Object — structured input from the schema form
window.APP_BUILDER_BACKEND_RESULT // Object | null — result from backend.py execution
```
These are available immediately in any `<script>` tag. You can also listen for
live updates when the user changes input:
```javascript
window.addEventListener('app-builder-data-ready', () => {
const input = window.APP_BUILDER_INPUT;
const result = window.APP_BUILDER_BACKEND_RESULT;
// re-render with new data
});
```
---
## schema.json format
Standard JSON Schema. The platform renders a form from this automatically.
```json
{
"type": "object",
"properties": {
"title": { "type": "string", "default": "My Dashboard" },
"count": { "type": "number", "default": 10 },
"enabled": { "type": "boolean", "default": true },
"items": {
"type": "array",
"items": { "type": "string" },
"default": ["alpha", "beta"]
}
},
"required": ["title"]
}
```
Supported types: `string`, `number`, `integer`, `boolean`, `array`, `object`.
Use `"default"` values so the preview works without manual input.
---
## backend.py
Optional server-side Python that runs before the frontend renders.
It receives a global `input_data` dict (the schema form values) and must
assign its result to a global `result` dict.
It must assign its result to a global `result` dict.
```python
# input_data is pre-populated from the schema form
import json
result = {
"processed_items": [item.upper() for item in input_data.get("items", [])],
"items": ["alpha", "beta", "gamma"],
"timestamp": "2024-01-01T00:00:00Z",
}
```
The `result` dict becomes `window.APP_BUILDER_BACKEND_RESULT` in the frontend.
---
## Multi-file projects
@@ -102,7 +48,6 @@ workspace root, so relative imports work naturally:
workspace/
├── index.html
├── meta.json
├── schema.json
├── styles/
│ └── main.css
├── components/
@@ -146,9 +91,8 @@ import React from 'react';
import { createRoot } from 'react-dom/client';
function App() {
const input = window.APP_BUILDER_INPUT || {};
return React.createElement('div', null,
React.createElement('h1', null, input.title || 'Hello')
React.createElement('h1', null, 'Hello')
);
}
@@ -210,14 +154,9 @@ Other CDN libraries work too — use `https://esm.sh/` or `https://cdn.jsdelivr.
</head>
<body>
<div class="card">
<h1 id="title">Loading…</h1>
<p id="desc"></p>
<h1>Hello</h1>
<p>Describe what you want to build and the agent will update this app.</p>
</div>
<script>
const input = window.APP_BUILDER_INPUT || {};
document.getElementById('title').textContent = input.title || 'Untitled';
document.getElementById('desc').textContent = input.description || 'No description provided.';
</script>
</body>
</html>
```
+5 -7
View File
@@ -16,12 +16,12 @@ class BackendExecResult:
stderr: str
async def execute_backend_code(code: str, input_data: dict) -> BackendExecResult:
async def execute_backend_code(code: str) -> BackendExecResult:
"""Execute user-provided Python code in a subprocess.
The code receives ``input_data`` as a global dict and must assign its
result to a global ``result`` dict. User print() calls are captured
separately from the result via an in-process StringIO redirect.
The code must assign its result to a global ``result`` dict.
User print() calls are captured separately from the result via
an in-process StringIO redirect.
"""
preamble = (
@@ -29,7 +29,6 @@ async def execute_backend_code(code: str, input_data: dict) -> BackendExecResult
"_orig_stdout = sys.stdout\n"
"_capture = io.StringIO()\n"
"sys.stdout = _capture\n"
"input_data = json.loads(sys.stdin.read())\n"
"result = {}\n"
)
postamble = (
@@ -40,14 +39,13 @@ async def execute_backend_code(code: str, input_data: dict) -> BackendExecResult
proc = await asyncio.create_subprocess_exec(
sys.executable, "-c", wrapper,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
try:
stdout, stderr = await asyncio.wait_for(
proc.communicate(input=json.dumps(input_data).encode()),
proc.communicate(),
timeout=TIMEOUT_SECONDS,
)
except asyncio.TimeoutError:
+1 -52
View File
@@ -1,60 +1,9 @@
"""Pure helpers for data injection, validation, and directory walking."""
"""Pure helpers for the app builder."""
from __future__ import annotations
import base64
import json
import os
from jsonschema import validate as schema_validate, ValidationError as SchemaValidationError
def validate_against_schema(data: dict, schema: dict) -> str | None:
"""Validate *data* against *schema*. Return an error string or None."""
try:
schema_validate(instance=data, schema=schema)
return None
except SchemaValidationError as exc:
path = " -> ".join(str(p) for p in exc.absolute_path) if exc.absolute_path else "(root)"
return f"Schema validation failed at {path}: {exc.message}"
def build_data_injection(input_json: str, result_json: str) -> str:
return (
"<script>\n"
"(function() {\n"
" window.APP_BUILDER_INPUT = " + input_json + ";\n"
" window.APP_BUILDER_BACKEND_RESULT = " + result_json + ";\n"
" window.addEventListener('message', function(e) {\n"
" if (e.data && e.data.type === 'APP_BUILDER_DATA') {\n"
" window.APP_BUILDER_INPUT = e.data.input || {};\n"
" window.APP_BUILDER_BACKEND_RESULT = e.data.backendResult || null;\n"
" window.dispatchEvent(new CustomEvent('app-builder-data-ready'));\n"
" }\n"
" });\n"
"})();\n"
"</script>"
)
def inject_data_into_html(html: str, input_json: str = "{}", result_json: str = "null") -> str:
injection = build_data_injection(input_json, result_json)
if "</head>" in html:
return html.replace("</head>", f"{injection}\n</head>", 1)
if "<body" in html:
return html.replace("<body", f"{injection}\n<body", 1)
return f"{injection}\n{html}"
def decode_data_param(d: str) -> tuple[str, str]:
try:
decoded = json.loads(base64.b64decode(d))
input_json = json.dumps(decoded.get("i", {}))
result_json = json.dumps(decoded.get("r", None))
return input_json, result_json
except Exception:
return "{}", "null"
def walk_directory(folder: str) -> dict[str, str]:
files: dict[str, str] = {}
-11
View File
@@ -45,21 +45,11 @@ APP_BUILDER_TEMPLATE_INDEX = """\
<p id="desc">Describe what you want to build and the agent will update this app.</p>
</div>
<script>
const input = window.APP_BUILDER_INPUT || {};
const result = window.APP_BUILDER_BACKEND_RESULT || null;
</script>
</body>
</html>
"""
APP_BUILDER_TEMPLATE_SCHEMA = """\
{
"type": "object",
"properties": {},
"required": []
}
"""
APP_BUILDER_TEMPLATE_META = """\
{
"name": "",
@@ -69,6 +59,5 @@ APP_BUILDER_TEMPLATE_META = """\
APP_BUILDER_TEMPLATE_FILES = {
"index.html": APP_BUILDER_TEMPLATE_INDEX,
"schema.json": APP_BUILDER_TEMPLATE_SCHEMA,
"meta.json": APP_BUILDER_TEMPLATE_META,
}