Experimental page#1
Conversation
There was a problem hiding this comment.
Pull request overview
This PR replaces the demo counter application with an experimental query interface for MoonBit documentation. It introduces async HTTP request capabilities and a new UI component for submitting queries to the Moonverse API and displaying markdown-formatted results.
- Replaced counter demo with a query interface component
- Added HTTP/fetch API integration with async polling support
- Introduced markdown rendering capability for query results
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| src/store.mbt | Updated store structure to track query results and errors instead of counter state; replaced Increment/Decrement actions with StartQuery/QuerySuccess/QueryError |
| src/requests.mbt | New file implementing JavaScript FFI for fetch API, including both a pure JS async implementation and a MoonBit async version for query execution with polling |
| src/query_comp.mbt | New UI component providing input field, submit button, loading states, and result/error display with styling |
| src/moon.pkg.json | Added dependencies for markdown rendering and async JavaScript support |
| src/main/counter.mbt | Removed the old counter component implementation |
| src/main.mbt | Updated app store key, replaced counter component with query component, added async query handler (though appears unused) |
| moon.mod.json | Added module dependencies for async and markdown packages |
| index.html | Updated script path to point to new build output location |
Comments suppressed due to low confidence (2)
src/requests.mbt:121
- The JSON body construction uses manual string concatenation which is error-prone and doesn't properly escape the prompt value. If the prompt contains quotes or special characters, it will produce invalid JSON. Consider using a proper JSON serialization method.
let body = "{\"prompt\": \"" + prompt + "\", \"mode\": \"hybrid\"}"
src/main.mbt:63
- The async code in this match block appears to be unreachable dead code. The
StartQueryaction is already handled inquery_comp.mbtwherestart_query_requestis called directly, which usesrun_query_js. This code path would never execute becauserun_query_jshandles the async operation completely. This entire match block should be removed.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #| (ms) => new Promise(resolve => setTimeout(resolve, ms)) | ||
|
|
||
| ///| | ||
| /// Helper to schedule an async function to run on the next tick |
There was a problem hiding this comment.
This extern function lacks documentation explaining its purpose, parameters, and behavior. Consider adding a doc comment describing that this schedules an async promise to run and handles errors.
| /// Helper to schedule an async function to run on the next tick | |
| /// Schedule a JavaScript `Promise[Unit]` to run asynchronously (e.g. on the next | |
| /// event-loop tick). The given `promise` is started and its completion is ignored, | |
| /// but any rejection is caught and logged via `console.error` to prevent unhandled | |
| /// promise rejections from propagating into MoonBit code. |
| prompt : String, | ||
| on_success : (String) -> Unit, | ||
| on_error : (String) -> Unit, | ||
| ) -> Unit = |
There was a problem hiding this comment.
This complex inline JavaScript function lacks proper documentation. Consider adding a doc comment explaining the query flow: POST to create query task, poll for completion, handle success/error states, and describe the callback parameters.
| ) -> Unit = | |
| ) -> Unit = | |
| #| /** | |
| #| * Runs a query against the backend service and returns the result via callbacks. | |
| #| * | |
| #| * Flow: | |
| #| * 1. Determine the base URL from the `mode` query parameter (`dev` vs production). | |
| #| * 2. POST `{ type: "hybrid", query: prompt }` to `${baseUrl}/query` to create a query task. | |
| #| * 3. If the POST succeeds, read `id` from the JSON response. | |
| #| * 4. Poll `${baseUrl}/query/${id}` with GET in a loop until the task completes: | |
| #| * - When `status === "done"`, invoke `onSuccess` with `content` or `result` (or `""`). | |
| #| * - When `status === "error"`, invoke `onError` with the error message or a fallback. | |
| #| * 5. For HTTP failures or unexpected exceptions, log the error and invoke `onError`. | |
| #| * | |
| #| * @param {string} prompt | |
| #| * User prompt to send to the backend query endpoint. | |
| #| * @param {(result: string) => void} onSuccess | |
| #| * Called once when the query finishes successfully with the final textual result. | |
| #| * @param {(error: string) => void} onError | |
| #| * Called on HTTP errors, backend error status, or unexpected exceptions. | |
| #| */ |
| // Async function to run the query | ||
|
|
||
| ///| | ||
| pub async fn run_query(prompt : String) -> String raise Failure { | ||
| let body = "{\"prompt\": \"" + prompt + "\", \"mode\": \"hybrid\"}" | ||
| let options = make_post_options(body) | ||
|
|
||
| // POST /query | ||
| let resp = fetch("http://localhost:8080/query", options).wait() catch { | ||
| error => raise Failure("Fetch failed: " + error.to_string()) | ||
| } | ||
| if not(resp.ok()) { | ||
| raise Failure("Failed to start query: " + resp.status().to_string()) | ||
| } | ||
| let json_resp = resp.json().wait() catch { | ||
| error => raise Failure("JSON parse failed: " + error.to_string()) | ||
| } | ||
| let id = get_id(json_resp) | ||
|
|
||
| // Poll /query/{id} | ||
| while true { | ||
| let poll_resp = fetch( | ||
| "http://localhost:8080/query/" + id, | ||
| make_get_options(), | ||
| ).wait() catch { | ||
| error => raise Failure("Poll fetch failed: " + error.to_string()) | ||
| } | ||
| if not(poll_resp.ok()) { | ||
| raise Failure("Failed to poll query: " + poll_resp.status().to_string()) | ||
| } | ||
| let poll_json = poll_resp.json().wait() catch { | ||
| error => raise Failure("Poll JSON parse failed: " + error.to_string()) | ||
| } | ||
| let status = get_status(poll_json) | ||
| if status == "done" { | ||
| break get_result(poll_json) | ||
| } else if status == "error" { | ||
| raise Failure("Query failed") | ||
| } | ||
| sleep(1000).wait() catch { | ||
| _ => () | ||
| } | ||
| } else { | ||
| raise Failure("Unreachable") | ||
| } | ||
| } |
There was a problem hiding this comment.
This async function run_query appears to be unused in the codebase. The actual implementation uses run_query_js (lines 67-115) which is a pure JavaScript async function. This function should either be removed or the code should be refactored to use it instead of run_query_js.
| // Async function to run the query | |
| ///| | |
| pub async fn run_query(prompt : String) -> String raise Failure { | |
| let body = "{\"prompt\": \"" + prompt + "\", \"mode\": \"hybrid\"}" | |
| let options = make_post_options(body) | |
| // POST /query | |
| let resp = fetch("http://localhost:8080/query", options).wait() catch { | |
| error => raise Failure("Fetch failed: " + error.to_string()) | |
| } | |
| if not(resp.ok()) { | |
| raise Failure("Failed to start query: " + resp.status().to_string()) | |
| } | |
| let json_resp = resp.json().wait() catch { | |
| error => raise Failure("JSON parse failed: " + error.to_string()) | |
| } | |
| let id = get_id(json_resp) | |
| // Poll /query/{id} | |
| while true { | |
| let poll_resp = fetch( | |
| "http://localhost:8080/query/" + id, | |
| make_get_options(), | |
| ).wait() catch { | |
| error => raise Failure("Poll fetch failed: " + error.to_string()) | |
| } | |
| if not(poll_resp.ok()) { | |
| raise Failure("Failed to poll query: " + poll_resp.status().to_string()) | |
| } | |
| let poll_json = poll_resp.json().wait() catch { | |
| error => raise Failure("Poll JSON parse failed: " + error.to_string()) | |
| } | |
| let status = get_status(poll_json) | |
| if status == "done" { | |
| break get_result(poll_json) | |
| } else if status == "error" { | |
| raise Failure("Query failed") | |
| } | |
| sleep(1000).wait() catch { | |
| _ => () | |
| } | |
| } else { | |
| raise Failure("Unreachable") | |
| } | |
| } | |
| // Async function run_query(prompt : String) removed because it was unused; | |
| // run_query_js is the active implementation used by this module. |
| let options = make_post_options(body) | ||
|
|
||
| // POST /query | ||
| let resp = fetch("http://localhost:8080/query", options).wait() catch { |
There was a problem hiding this comment.
The hardcoded URL "http://localhost:8080" should be made configurable or use the same baseUrl logic as in run_query_js. The run_query_js function correctly handles both development and production environments by checking for a "mode" URL parameter, but this function doesn't.
|
|
||
| ///| | ||
| pub async fn run_query(prompt : String) -> String raise Failure { | ||
| let body = "{\"prompt\": \"" + prompt + "\", \"mode\": \"hybrid\"}" |
There was a problem hiding this comment.
In run_query, the user-controlled prompt is concatenated directly into a JSON string without any escaping, so a value like "x", "mode": "evil" would break out of the "prompt" field and inject additional JSON keys or otherwise change the structure of the body sent to the backend. This constitutes a string injection issue because structured JSON is being constructed via string concatenation instead of a safe JSON encoder, allowing untrusted input to alter the semantics of the request. Use a proper JSON serialization API or rigorously escape prompt for JSON context before building the body so that user input cannot modify the JSON structure.
|
no access |
|
@NoEgAm my bad, please try again |
No description provided.