Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:42:53.138Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

web_examples.rs

339 lines · 11.1 KB · rust
1#![allow(clippy::disallowed_methods, reason = "tooling is exempt")]
2
3use std::io::Write;
4use std::path::Path;
5use std::process::Command;
6
7use anyhow::{Context as _, Result, bail};
8use clap::Parser;
9
10#[derive(Parser)]
11pub struct WebExamplesArgs {
12    #[arg(long)]
13    pub release: bool,
14    #[arg(long, default_value = "8080")]
15    pub port: u16,
16    #[arg(long)]
17    pub no_serve: bool,
18}
19
20fn check_program(binary: &str, install_hint: &str) -> Result<()> {
21    match Command::new(binary).arg("--version").output() {
22        Ok(output) if output.status.success() => Ok(()),
23        _ => bail!("`{binary}` not found. Install with: {install_hint}"),
24    }
25}
26
27fn discover_examples() -> Result<Vec<String>> {
28    let examples_dir = Path::new("crates/gpui/examples");
29    let mut names = Vec::new();
30
31    for entry in std::fs::read_dir(examples_dir).context("failed to read crates/gpui/examples")? {
32        let path = entry?.path();
33        if path.extension().and_then(|e| e.to_str()) == Some("rs") {
34            if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
35                names.push(stem.to_string());
36            }
37        }
38    }
39
40    if names.is_empty() {
41        bail!("no examples found in crates/gpui/examples");
42    }
43
44    names.sort();
45    Ok(names)
46}
47
48pub fn run_web_examples(args: WebExamplesArgs) -> Result<()> {
49    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
50    let profile = if args.release { "release" } else { "debug" };
51    let out_dir = "target/web-examples";
52
53    check_program("wasm-bindgen", "cargo install wasm-bindgen-cli")?;
54
55    let examples = discover_examples()?;
56    eprintln!(
57        "Building {} example(s) for wasm32-unknown-unknown ({profile})...\n",
58        examples.len()
59    );
60
61    std::fs::create_dir_all(out_dir).context("failed to create output directory")?;
62
63    eprintln!("Building all examples...");
64
65    let mut cmd = Command::new(&cargo);
66    cmd.args([
67        "build",
68        "--target",
69        "wasm32-unknown-unknown",
70        "-p",
71        "gpui",
72        "--keep-going",
73    ]);
74    // 🙈
75    cmd.env("RUSTC_BOOTSTRAP", "1");
76    for name in &examples {
77        cmd.args(["--example", name]);
78    }
79    if args.release {
80        cmd.arg("--release");
81    }
82
83    let _ = cmd.status().context("failed to run cargo build")?;
84
85    // Run wasm-bindgen on each .wasm that was produced.
86    let mut succeeded: Vec<String> = Vec::new();
87    let mut failed: Vec<String> = Vec::new();
88
89    for name in &examples {
90        let wasm_path = format!("target/wasm32-unknown-unknown/{profile}/examples/{name}.wasm");
91        if !Path::new(&wasm_path).exists() {
92            eprintln!("[{name}] SKIPPED (build failed)");
93            failed.push(name.clone());
94            continue;
95        }
96
97        eprintln!("[{name}] Running wasm-bindgen...");
98
99        let example_dir = format!("{out_dir}/{name}");
100        std::fs::create_dir_all(&example_dir)
101            .with_context(|| format!("failed to create {example_dir}"))?;
102
103        let status = Command::new("wasm-bindgen")
104            .args([
105                &wasm_path,
106                "--target",
107                "web",
108                "--no-typescript",
109                "--out-dir",
110                &example_dir,
111                "--out-name",
112                name,
113            ])
114            // 🙈
115            .env("RUSTC_BOOTSTRAP", "1")
116            .status()
117            .context("failed to run wasm-bindgen")?;
118        if !status.success() {
119            eprintln!("[{name}] SKIPPED (wasm-bindgen failed)");
120            failed.push(name.clone());
121            continue;
122        }
123
124        // Write per-example index.html.
125        let html_path = format!("{example_dir}/index.html");
126        std::fs::File::create(&html_path)
127            .and_then(|mut file| file.write_all(make_example_html(name).as_bytes()))
128            .with_context(|| format!("failed to write {html_path}"))?;
129
130        eprintln!("[{name}] OK");
131        succeeded.push(name.clone());
132    }
133
134    if succeeded.is_empty() {
135        bail!("all {} examples failed to build", examples.len());
136    }
137
138    let example_names: Vec<&str> = succeeded.iter().map(|s| s.as_str()).collect();
139    let index_path = format!("{out_dir}/index.html");
140    std::fs::File::create(&index_path)
141        .and_then(|mut file| file.write_all(make_gallery_html(&example_names).as_bytes()))
142        .context("failed to write index.html")?;
143
144    if args.no_serve {
145        return Ok(());
146    }
147
148    // Serve with COEP/COOP headers required for WebGPU / SharedArrayBuffer.
149    eprintln!("Serving on http://127.0.0.1:{}...", args.port);
150
151    let server_script = format!(
152        r#"
153import http.server
154class Handler(http.server.SimpleHTTPRequestHandler):
155    def __init__(self, *args, **kwargs):
156        super().__init__(*args, directory="{out_dir}", **kwargs)
157    def end_headers(self):
158        self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
159        self.send_header("Cross-Origin-Opener-Policy", "same-origin")
160        super().end_headers()
161http.server.HTTPServer(("127.0.0.1", {port}), Handler).serve_forever()
162"#,
163        port = args.port,
164    );
165
166    let status = Command::new("python3")
167        .args(["-c", &server_script])
168        .status()
169        .context("failed to run python3 http server (is python3 installed?)")?;
170    if !status.success() {
171        bail!("python3 http server exited with: {status}");
172    }
173
174    Ok(())
175}
176
177fn make_example_html(name: &str) -> String {
178    format!(
179        r#"<!DOCTYPE html>
180<html lang="en">
181<head>
182    <meta charset="utf-8">
183    <meta name="viewport" content="width=device-width, initial-scale=1.0">
184    <title>GPUI Web: {name}</title>
185    <style>
186        * {{ margin: 0; padding: 0; box-sizing: border-box; }}
187        html, body {{
188            width: 100%; height: 100%; overflow: hidden;
189            background: #1e1e2e; color: #cdd6f4;
190            font-family: system-ui, -apple-system, sans-serif;
191        }}
192        canvas {{ display: block; width: 100%; height: 100%; }}
193        #loading {{
194            position: fixed; inset: 0;
195            display: flex; align-items: center; justify-content: center;
196            font-size: 1.25rem; opacity: 0.6;
197        }}
198        #loading.hidden {{ display: none; }}
199    </style>
200</head>
201<body>
202    <div id="loading">Loading {name}…</div>
203    <script type="module">
204        import init from './{name}.js';
205        await init();
206        document.getElementById('loading').classList.add('hidden');
207    </script>
208</body>
209</html>
210"#
211    )
212}
213
214fn make_gallery_html(examples: &[&str]) -> String {
215    let mut buttons = String::new();
216    for name in examples {
217        buttons.push_str(&format!(
218            "                <button class=\"example-btn\" data-name=\"{name}\">{name}</button>\n"
219        ));
220    }
221
222    let first = examples.first().copied().unwrap_or("hello_web");
223
224    format!(
225        r##"<!DOCTYPE html>
226<html lang="en">
227<head>
228    <meta charset="utf-8">
229    <meta name="viewport" content="width=device-width, initial-scale=1.0">
230    <title>GPUI Web Examples</title>
231    <style>
232        * {{ margin: 0; padding: 0; box-sizing: border-box; }}
233        html, body {{
234            width: 100%; height: 100%; overflow: hidden;
235            background: #1e1e2e; color: #cdd6f4;
236            font-family: system-ui, -apple-system, sans-serif;
237        }}
238        #app {{ display: flex; width: 100%; height: 100%; }}
239
240        #sidebar {{
241            width: 240px; min-width: 240px;
242            background: #181825;
243            border-right: 1px solid #313244;
244            display: flex; flex-direction: column;
245        }}
246        #sidebar-header {{
247            padding: 16px 14px 12px;
248            font-size: 0.8rem; font-weight: 700;
249            text-transform: uppercase; letter-spacing: 0.08em;
250            color: #a6adc8; border-bottom: 1px solid #313244;
251        }}
252        #sidebar-header span {{
253            font-size: 1rem; text-transform: none; letter-spacing: normal;
254            color: #cdd6f4; display: block; margin-top: 2px;
255        }}
256        #example-list {{
257            flex: 1; overflow-y: auto; padding: 8px 0;
258        }}
259        .example-btn {{
260            display: block; width: 100%;
261            padding: 8px 14px; border: none;
262            background: transparent; color: #bac2de;
263            font-size: 0.85rem; text-align: left;
264            cursor: pointer;
265            font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
266        }}
267        .example-btn:hover {{ background: #313244; color: #cdd6f4; }}
268        .example-btn.active {{ background: #45475a; color: #f5e0dc; font-weight: 600; }}
269
270        #main {{ flex: 1; display: flex; flex-direction: column; min-width: 0; }}
271        #toolbar {{
272            height: 40px; display: flex; align-items: center;
273            padding: 0 16px; gap: 12px;
274            background: #1e1e2e; border-bottom: 1px solid #313244;
275            font-size: 0.8rem; color: #a6adc8;
276        }}
277        #current-name {{
278            font-weight: 600; color: #cdd6f4;
279            font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
280        }}
281        #open-tab {{
282            margin-left: auto; padding: 4px 10px;
283            border: 1px solid #585b70; border-radius: 4px;
284            background: transparent; color: #a6adc8;
285            font-size: 0.75rem; cursor: pointer;
286            text-decoration: none;
287        }}
288        #open-tab:hover {{ background: #313244; color: #cdd6f4; }}
289        #viewer {{ flex: 1; border: none; width: 100%; background: #11111b; }}
290    </style>
291</head>
292<body>
293    <div id="app">
294        <div id="sidebar">
295            <div id="sidebar-header">
296                GPUI Examples
297                <span>{count} available</span>
298            </div>
299            <div id="example-list">
300{buttons}            </div>
301        </div>
302        <div id="main">
303            <div id="toolbar">
304                <span id="current-name">{first}</span>
305                <a id="open-tab" href="./{first}/" target="_blank">Open in new tab ↗</a>
306            </div>
307            <iframe id="viewer" src="./{first}/"></iframe>
308        </div>
309    </div>
310    <script>
311        const buttons = document.querySelectorAll('.example-btn');
312        const viewer  = document.getElementById('viewer');
313        const nameEl  = document.getElementById('current-name');
314        const openEl  = document.getElementById('open-tab');
315
316        function select(name) {{
317            buttons.forEach(b => b.classList.toggle('active', b.dataset.name === name));
318            viewer.src = './' + name + '/';
319            nameEl.textContent = name;
320            openEl.href = './' + name + '/';
321            history.replaceState(null, '', '#' + name);
322        }}
323
324        buttons.forEach(b => b.addEventListener('click', () => select(b.dataset.name)));
325
326        const hash = location.hash.slice(1);
327        if (hash && [...buttons].some(b => b.dataset.name === hash)) {{
328            select(hash);
329        }} else {{
330            select('{first}');
331        }}
332    </script>
333</body>
334</html>
335"##,
336        count = examples.len(),
337    )
338}
339
Served at tenant.openagents/omega Member data and write actions are omitted.