mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-06 04:33:16 -05:00
Compare commits
3 Commits
main
...
dynamic-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3bd552b15 | ||
|
|
3ad57c125f | ||
|
|
09e78d9210 |
24
Cargo.lock
generated
24
Cargo.lock
generated
@@ -1316,12 +1316,12 @@ checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.5"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3446,9 +3446,9 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
@@ -6341,9 +6341,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
version = "0.3.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -6351,22 +6351,22 @@ dependencies = [
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.27"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
||||
@@ -12,7 +12,7 @@ use chrono::Utc;
|
||||
use cookie::Cookie;
|
||||
use log::error;
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime};
|
||||
use tauri::{AppHandle, Emitter, Listener, Manager, Runtime};
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use yaak_crypto::manager::EncryptionManager;
|
||||
@@ -59,7 +59,55 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
}
|
||||
InternalEventPayload::PromptFormRequest(_) => {
|
||||
let window = get_window_from_plugin_context(app_handle, &plugin_context)?;
|
||||
Ok(call_frontend(&window, event).await)
|
||||
if event.reply_id.is_some() {
|
||||
// Follow-up update from plugin runtime with resolved inputs — forward to frontend
|
||||
window.emit_to(window.label(), "plugin_event", event.clone())?;
|
||||
Ok(None)
|
||||
} else {
|
||||
// Initial request — set up bidirectional communication
|
||||
window.emit_to(window.label(), "plugin_event", event.clone()).unwrap();
|
||||
|
||||
let event_id = event.id.clone();
|
||||
let plugin_handle = plugin_handle.clone();
|
||||
let plugin_context = plugin_context.clone();
|
||||
let window = window.clone();
|
||||
|
||||
// Spawn async task to handle bidirectional form communication
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel::<InternalEvent>(128);
|
||||
|
||||
// Listen for replies from the frontend
|
||||
let listener_id = window.listen(event_id, move |ev: tauri::Event| {
|
||||
let resp: InternalEvent = serde_json::from_str(ev.payload()).unwrap();
|
||||
let _ = tx.try_send(resp);
|
||||
});
|
||||
|
||||
// Forward each reply to the plugin runtime
|
||||
while let Some(resp) = rx.recv().await {
|
||||
let is_done = matches!(
|
||||
&resp.payload,
|
||||
InternalEventPayload::PromptFormResponse(r) if r.done.unwrap_or(false)
|
||||
);
|
||||
|
||||
let event_to_send = plugin_handle.build_event_to_send(
|
||||
&plugin_context,
|
||||
&resp.payload,
|
||||
Some(resp.reply_id.unwrap_or_default()),
|
||||
);
|
||||
if let Err(e) = plugin_handle.send(&event_to_send).await {
|
||||
log::warn!("Failed to forward form response to plugin: {:?}", e);
|
||||
}
|
||||
|
||||
if is_done {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
window.unlisten(listener_id);
|
||||
});
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
||||
let http_responses = app_handle
|
||||
|
||||
14
crates/yaak-plugins/bindings/gen_events.ts
generated
14
crates/yaak-plugins/bindings/gen_events.ts
generated
@@ -66,7 +66,7 @@ export type DeleteModelRequest = { model: string, id: string, };
|
||||
|
||||
export type DeleteModelResponse = { model: AnyModel, };
|
||||
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown" | "c" | "clojure" | "csharp" | "go" | "java" | "kotlin" | "objective_c" | "ocaml" | "php" | "powershell" | "python" | "r" | "ruby" | "shell" | "swift";
|
||||
|
||||
export type EmptyPayload = {};
|
||||
|
||||
@@ -172,7 +172,11 @@ hideGutter?: boolean,
|
||||
/**
|
||||
* Language for syntax highlighting
|
||||
*/
|
||||
language?: EditorLanguage, readOnly?: boolean, completionOptions?: Array<GenericCompletionOption>,
|
||||
language?: EditorLanguage, readOnly?: boolean,
|
||||
/**
|
||||
* Fixed number of visible rows
|
||||
*/
|
||||
rows?: number, completionOptions?: Array<GenericCompletionOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
@@ -476,9 +480,11 @@ label: string, title?: string, size?: WindowSize, dataDirKey?: string, };
|
||||
|
||||
export type PluginContext = { id: string, label: string | null, workspaceId: string | null, };
|
||||
|
||||
export type PromptFormRequest = { id: string, title: string, description?: string, inputs: Array<FormInput>, confirmText?: string, cancelText?: string, };
|
||||
export type PromptFormRequest = { id: string, title: string, description?: string, inputs: Array<FormInput>, confirmText?: string, cancelText?: string, size?: PromptFormSize, };
|
||||
|
||||
export type PromptFormResponse = { values: { [key in string]?: JsonPrimitive } | null, };
|
||||
export type PromptFormResponse = { values: { [key in string]?: JsonPrimitive } | null, done?: boolean, };
|
||||
|
||||
export type PromptFormSize = "sm" | "md" | "lg" | "full" | "dynamic";
|
||||
|
||||
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
||||
/**
|
||||
|
||||
2
crates/yaak-plugins/bindings/gen_models.ts
generated
2
crates/yaak-plugins/bindings/gen_models.ts
generated
@@ -49,7 +49,7 @@ export type HttpResponseEvent = { model: "http_response_event", id: string, crea
|
||||
* This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support.
|
||||
* The `From` impl is in yaak-http to avoid circular dependencies.
|
||||
*/
|
||||
export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, } | { "type": "send_url", method: string, path: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, } | { "type": "dns_resolved", hostname: string, addresses: Array<string>, duration: bigint, overridden: boolean, };
|
||||
export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, } | { "type": "send_url", method: string, scheme: string, username: string, password: string, host: string, port: number, path: string, query: string, fragment: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, } | { "type": "dns_resolved", hostname: string, addresses: Array<string>, duration: bigint, overridden: boolean, };
|
||||
|
||||
export type HttpResponseHeader = { name: string, value: string, };
|
||||
|
||||
|
||||
@@ -587,6 +587,19 @@ pub struct PromptFormRequest {
|
||||
pub confirm_text: Option<String>,
|
||||
#[ts(optional)]
|
||||
pub cancel_text: Option<String>,
|
||||
#[ts(optional)]
|
||||
pub size: Option<PromptFormSize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum PromptFormSize {
|
||||
Sm,
|
||||
Md,
|
||||
Lg,
|
||||
Full,
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
@@ -594,6 +607,8 @@ pub struct PromptFormRequest {
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct PromptFormResponse {
|
||||
pub values: Option<HashMap<String, JsonPrimitive>>,
|
||||
#[ts(optional)]
|
||||
pub done: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
@@ -936,6 +951,21 @@ pub enum EditorLanguage {
|
||||
Xml,
|
||||
Graphql,
|
||||
Markdown,
|
||||
C,
|
||||
Clojure,
|
||||
Csharp,
|
||||
Go,
|
||||
Java,
|
||||
Kotlin,
|
||||
ObjectiveC,
|
||||
Ocaml,
|
||||
Php,
|
||||
Powershell,
|
||||
Python,
|
||||
R,
|
||||
Ruby,
|
||||
Shell,
|
||||
Swift,
|
||||
}
|
||||
|
||||
impl Default for EditorLanguage {
|
||||
@@ -966,6 +996,10 @@ pub struct FormInputEditor {
|
||||
#[ts(optional)]
|
||||
pub read_only: Option<bool>,
|
||||
|
||||
/// Fixed number of visible rows
|
||||
#[ts(optional)]
|
||||
pub rows: Option<i32>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub completion_options: Option<Vec<GenericCompletionOption>>,
|
||||
}
|
||||
|
||||
193
package-lock.json
generated
193
package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"packages/plugin-runtime-types",
|
||||
"plugins-external/mcp-server",
|
||||
"plugins-external/template-function-faker",
|
||||
"plugins-external/httpsnippet",
|
||||
"plugins/action-copy-curl",
|
||||
"plugins/action-copy-grpcurl",
|
||||
"plugins/action-send-folder",
|
||||
@@ -62,6 +63,13 @@
|
||||
"crates/yaak-ws",
|
||||
"src-web"
|
||||
],
|
||||
"dependencies": {
|
||||
"@codemirror/lang-go": "^6.0.1",
|
||||
"@codemirror/lang-java": "^6.0.2",
|
||||
"@codemirror/lang-php": "^6.0.2",
|
||||
"@codemirror/lang-python": "^6.2.1",
|
||||
"@codemirror/legacy-modes": "^6.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
@@ -736,6 +744,19 @@
|
||||
"@lezer/css": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-go": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz",
|
||||
"integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/go": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
@@ -753,6 +774,16 @@
|
||||
"@lezer/html": "^1.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-java": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz",
|
||||
"integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/java": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||
@@ -793,6 +824,32 @@
|
||||
"@lezer/markdown": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-php": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz",
|
||||
"integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/php": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-python": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz",
|
||||
"integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.3.2",
|
||||
"@codemirror/language": "^6.8.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/python": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-xml": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
|
||||
@@ -836,6 +893,15 @@
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/legacy-modes": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz",
|
||||
"integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz",
|
||||
@@ -1570,6 +1636,17 @@
|
||||
"lezer-generator": "src/lezer-generator.cjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/go": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz",
|
||||
"integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||
@@ -1590,6 +1667,17 @@
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/java": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz",
|
||||
"integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/javascript": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||
@@ -1631,6 +1719,28 @@
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/php": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz",
|
||||
"integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/python": {
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz",
|
||||
"integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/xml": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz",
|
||||
@@ -2022,6 +2132,19 @@
|
||||
"node": ">=16.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@readme/httpsnippet": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@readme/httpsnippet/-/httpsnippet-11.0.0.tgz",
|
||||
"integrity": "sha512-XSyaAsJkZfmMO9R4WDlVJARZgd4wlImftSkMkKclidniXA1h6DTya9iTqJenQo9mHQLh3u6kAC3CDRaIV+LbLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"qs": "^6.11.2",
|
||||
"stringify-object": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@replit/codemirror-emacs": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@replit/codemirror-emacs/-/codemirror-emacs-6.1.0.tgz",
|
||||
@@ -4045,6 +4168,10 @@
|
||||
"resolved": "plugins/filter-xpath",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaak/httpsnippet": {
|
||||
"resolved": "plugins-external/httpsnippet",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaak/importer-curl": {
|
||||
"resolved": "plugins/importer-curl",
|
||||
"link": true
|
||||
@@ -7411,6 +7538,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-own-enumerable-property-symbols": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
|
||||
"integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
@@ -8529,6 +8662,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-obj": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
|
||||
"integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-obj": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
|
||||
@@ -13789,6 +13931,29 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/stringify-object": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
|
||||
"integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"get-own-enumerable-property-symbols": "^3.0.0",
|
||||
"is-obj": "^1.0.1",
|
||||
"is-regexp": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/stringify-object/node_modules/is-regexp": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
|
||||
"integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
@@ -15788,6 +15953,34 @@
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"plugins-external/httpsnippet": {
|
||||
"name": "@yaak/httpsnippet",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@readme/httpsnippet": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"plugins-external/httpsnippet/node_modules/@types/node": {
|
||||
"version": "22.19.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.9.tgz",
|
||||
"integrity": "sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"plugins-external/httpsnippet/node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"plugins-external/mcp-server": {
|
||||
"name": "@yaak/mcp-server",
|
||||
"version": "0.1.7",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"packages/plugin-runtime-types",
|
||||
"plugins-external/mcp-server",
|
||||
"plugins-external/template-function-faker",
|
||||
"plugins-external/httpsnippet",
|
||||
"plugins/action-copy-curl",
|
||||
"plugins/action-copy-grpcurl",
|
||||
"plugins/action-send-folder",
|
||||
@@ -104,5 +105,12 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-go": "^6.0.1",
|
||||
"@codemirror/lang-java": "^6.0.2",
|
||||
"@codemirror/lang-php": "^6.0.2",
|
||||
"@codemirror/lang-python": "^6.2.1",
|
||||
"@codemirror/legacy-modes": "^6.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export type DeleteModelRequest = { model: string, id: string, };
|
||||
|
||||
export type DeleteModelResponse = { model: AnyModel, };
|
||||
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown" | "c" | "clojure" | "csharp" | "go" | "java" | "kotlin" | "objective_c" | "ocaml" | "php" | "powershell" | "python" | "r" | "ruby" | "shell" | "swift";
|
||||
|
||||
export type EmptyPayload = {};
|
||||
|
||||
@@ -172,7 +172,11 @@ hideGutter?: boolean,
|
||||
/**
|
||||
* Language for syntax highlighting
|
||||
*/
|
||||
language?: EditorLanguage, readOnly?: boolean, completionOptions?: Array<GenericCompletionOption>,
|
||||
language?: EditorLanguage, readOnly?: boolean,
|
||||
/**
|
||||
* Fixed number of visible rows
|
||||
*/
|
||||
rows?: number, completionOptions?: Array<GenericCompletionOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
@@ -476,9 +480,11 @@ label: string, title?: string, size?: WindowSize, dataDirKey?: string, };
|
||||
|
||||
export type PluginContext = { id: string, label: string | null, workspaceId: string | null, };
|
||||
|
||||
export type PromptFormRequest = { id: string, title: string, description?: string, inputs: Array<FormInput>, confirmText?: string, cancelText?: string, };
|
||||
export type PromptFormRequest = { id: string, title: string, description?: string, inputs: Array<FormInput>, confirmText?: string, cancelText?: string, size?: PromptFormSize, };
|
||||
|
||||
export type PromptFormResponse = { values: { [key in string]?: JsonPrimitive } | null, };
|
||||
export type PromptFormResponse = { values: { [key in string]?: JsonPrimitive } | null, done?: boolean, };
|
||||
|
||||
export type PromptFormSize = "sm" | "md" | "lg" | "full" | "dynamic";
|
||||
|
||||
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,7 @@ export type HttpResponseEvent = { model: "http_response_event", id: string, crea
|
||||
* This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support.
|
||||
* The `From` impl is in yaak-http to avoid circular dependencies.
|
||||
*/
|
||||
export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, } | { "type": "send_url", method: string, path: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, } | { "type": "dns_resolved", hostname: string, addresses: Array<string>, duration: bigint, overridden: boolean, };
|
||||
export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, } | { "type": "send_url", method: string, scheme: string, username: string, password: string, host: string, port: number, path: string, query: string, fragment: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, } | { "type": "dns_resolved", hostname: string, addresses: Array<string>, duration: bigint, overridden: boolean, };
|
||||
|
||||
export type HttpResponseHeader = { name: string, value: string, };
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ import type {
|
||||
} from '../bindings/gen_events.ts';
|
||||
import type { Folder, HttpRequest } from '../bindings/gen_models.ts';
|
||||
import type { JsonValue } from '../bindings/serde_json/JsonValue';
|
||||
import type { DynamicPromptFormArg } from './PromptFormPlugin';
|
||||
|
||||
type DynamicPromptFormRequest = Omit<PromptFormRequest, 'inputs'> & {
|
||||
inputs: DynamicPromptFormArg[];
|
||||
};
|
||||
|
||||
export type WorkspaceHandle = Pick<WorkspaceInfo, 'id' | 'name'>;
|
||||
|
||||
@@ -39,7 +44,7 @@ export interface Context {
|
||||
};
|
||||
prompt: {
|
||||
text(args: PromptTextRequest): Promise<PromptTextResponse['value']>;
|
||||
form(args: PromptFormRequest): Promise<PromptFormResponse['values']>;
|
||||
form(args: DynamicPromptFormRequest): Promise<PromptFormResponse['values']>;
|
||||
};
|
||||
store: {
|
||||
set<T>(key: string, value: T): Promise<void>;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { FormInput, JsonPrimitive } from '../bindings/gen_events';
|
||||
import type { MaybePromise } from '../helpers';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type CallPromptFormDynamicArgs = {
|
||||
values: { [key in string]?: JsonPrimitive };
|
||||
};
|
||||
|
||||
type AddDynamicMethod<T> = {
|
||||
dynamic?: (
|
||||
ctx: Context,
|
||||
args: CallPromptFormDynamicArgs,
|
||||
) => MaybePromise<Partial<T> | null | undefined>;
|
||||
};
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: distributive conditional type pattern
|
||||
type AddDynamic<T> = T extends any
|
||||
? T extends { inputs?: FormInput[] }
|
||||
? Omit<T, 'inputs'> & {
|
||||
inputs: Array<AddDynamic<FormInput>>;
|
||||
dynamic?: (
|
||||
ctx: Context,
|
||||
args: CallPromptFormDynamicArgs,
|
||||
) => MaybePromise<
|
||||
Partial<Omit<T, 'inputs'> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined
|
||||
>;
|
||||
}
|
||||
: T & AddDynamicMethod<T>
|
||||
: never;
|
||||
|
||||
export type DynamicPromptFormArg = AddDynamic<FormInput>;
|
||||
@@ -2,21 +2,22 @@ import type { AuthenticationPlugin } from './AuthenticationPlugin';
|
||||
|
||||
import type { Context } from './Context';
|
||||
import type { FilterPlugin } from './FilterPlugin';
|
||||
import type { FolderActionPlugin } from './FolderActionPlugin';
|
||||
import type { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
|
||||
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
||||
import type { WebsocketRequestActionPlugin } from './WebsocketRequestActionPlugin';
|
||||
import type { WorkspaceActionPlugin } from './WorkspaceActionPlugin';
|
||||
import type { FolderActionPlugin } from './FolderActionPlugin';
|
||||
import type { ImporterPlugin } from './ImporterPlugin';
|
||||
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
|
||||
import type { ThemePlugin } from './ThemePlugin';
|
||||
import type { WebsocketRequestActionPlugin } from './WebsocketRequestActionPlugin';
|
||||
import type { WorkspaceActionPlugin } from './WorkspaceActionPlugin';
|
||||
|
||||
export type { Context };
|
||||
export type { DynamicTemplateFunctionArg } from './TemplateFunctionPlugin';
|
||||
export type { DynamicAuthenticationArg } from './AuthenticationPlugin';
|
||||
export type { CallPromptFormDynamicArgs, DynamicPromptFormArg } from './PromptFormPlugin';
|
||||
export type { DynamicTemplateFunctionArg } from './TemplateFunctionPlugin';
|
||||
export type { TemplateFunctionPlugin };
|
||||
export type { WorkspaceActionPlugin } from './WorkspaceActionPlugin';
|
||||
export type { FolderActionPlugin } from './FolderActionPlugin';
|
||||
export type { WorkspaceActionPlugin } from './WorkspaceActionPlugin';
|
||||
|
||||
/**
|
||||
* The global structure of a Yaak plugin
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import console from 'node:console';
|
||||
import { type Stats, statSync, watch } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { Context, PluginDefinition } from '@yaakapp/api';
|
||||
import type {
|
||||
CallPromptFormDynamicArgs,
|
||||
Context,
|
||||
DynamicPromptFormArg,
|
||||
PluginDefinition,
|
||||
} from '@yaakapp/api';
|
||||
import {
|
||||
applyFormInputDefaults,
|
||||
validateTemplateFunctionArgs,
|
||||
@@ -12,6 +17,7 @@ import type {
|
||||
DeleteModelResponse,
|
||||
FindHttpResponsesResponse,
|
||||
Folder,
|
||||
FormInput,
|
||||
GetCookieValueRequest,
|
||||
GetCookieValueResponse,
|
||||
GetHttpRequestByIdResponse,
|
||||
@@ -55,6 +61,7 @@ export class PluginInstance {
|
||||
#mod: PluginDefinition;
|
||||
#pluginToAppEvents: EventChannel;
|
||||
#appToPluginEvents: EventChannel;
|
||||
#pendingDynamicForms = new Map<string, DynamicPromptFormArg[]>();
|
||||
|
||||
constructor(workerData: PluginWorkerData, pluginEvents: EventChannel) {
|
||||
this.#workerData = workerData;
|
||||
@@ -106,6 +113,7 @@ export class PluginInstance {
|
||||
|
||||
async terminate() {
|
||||
await this.#mod?.dispose?.();
|
||||
this.#pendingDynamicForms.clear();
|
||||
this.#unimportModule();
|
||||
}
|
||||
|
||||
@@ -664,10 +672,58 @@ export class PluginInstance {
|
||||
return reply.value;
|
||||
},
|
||||
form: async (args) => {
|
||||
const reply: PromptFormResponse = await this.#sendForReply(context, {
|
||||
type: 'prompt_form_request',
|
||||
...args,
|
||||
// Strip dynamic callbacks before serializing (they can't cross the wire)
|
||||
const strippedInputs = stripDynamicCallbacks(args.inputs);
|
||||
|
||||
// Build the event manually so we can get the event ID for keying
|
||||
const eventToSend = this.#buildEventToSend(
|
||||
context,
|
||||
{ type: 'prompt_form_request', ...args, inputs: strippedInputs },
|
||||
null,
|
||||
);
|
||||
|
||||
// Store original inputs (with dynamic callbacks) for later resolution
|
||||
this.#pendingDynamicForms.set(eventToSend.id, args.inputs);
|
||||
|
||||
const reply = await new Promise<PromptFormResponse>((resolve) => {
|
||||
const cb = (event: InternalEvent) => {
|
||||
if (event.replyId !== eventToSend.id) return;
|
||||
|
||||
if (event.payload.type === 'prompt_form_response') {
|
||||
const { done, values } = event.payload as PromptFormResponse;
|
||||
if (done) {
|
||||
// Final response — resolve the promise and clean up
|
||||
this.#appToPluginEvents.unlisten(cb);
|
||||
this.#pendingDynamicForms.delete(eventToSend.id);
|
||||
resolve({ values } as PromptFormResponse);
|
||||
} else {
|
||||
// Intermediate value change — resolve dynamic inputs and send back
|
||||
const storedInputs = this.#pendingDynamicForms.get(eventToSend.id);
|
||||
if (storedInputs && values) {
|
||||
const ctx = this.#newCtx(context);
|
||||
const callArgs: CallPromptFormDynamicArgs = { values };
|
||||
applyDynamicFormInput(ctx, storedInputs, callArgs)
|
||||
.then((resolvedInputs) => {
|
||||
const stripped = stripDynamicCallbacks(resolvedInputs);
|
||||
this.#sendPayload(
|
||||
context,
|
||||
{ type: 'prompt_form_request', ...args, inputs: stripped },
|
||||
eventToSend.id,
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to resolve dynamic form inputs', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this.#appToPluginEvents.listen(cb);
|
||||
|
||||
// Send the initial event after we start listening (to prevent race)
|
||||
this.#sendEvent(eventToSend);
|
||||
});
|
||||
|
||||
return reply.values;
|
||||
},
|
||||
},
|
||||
@@ -906,6 +962,17 @@ export class PluginInstance {
|
||||
}
|
||||
}
|
||||
|
||||
function stripDynamicCallbacks(inputs: DynamicPromptFormArg[]): FormInput[] {
|
||||
return inputs.map((input) => {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: stripping dynamic from union type
|
||||
const { dynamic, ...rest } = input as any;
|
||||
if ('inputs' in rest && Array.isArray(rest.inputs)) {
|
||||
rest.inputs = stripDynamicCallbacks(rest.inputs);
|
||||
}
|
||||
return rest as FormInput;
|
||||
});
|
||||
}
|
||||
|
||||
function genId(len = 5): string {
|
||||
const alphabet = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
let id = '';
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import type { Context, DynamicAuthenticationArg, DynamicTemplateFunctionArg } from '@yaakapp/api';
|
||||
import type {
|
||||
CallPromptFormDynamicArgs,
|
||||
Context,
|
||||
DynamicAuthenticationArg,
|
||||
DynamicPromptFormArg,
|
||||
DynamicTemplateFunctionArg,
|
||||
} from '@yaakapp/api';
|
||||
import type {
|
||||
CallHttpAuthenticationActionArgs,
|
||||
CallTemplateFunctionArgs,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
|
||||
type AnyDynamicArg = DynamicTemplateFunctionArg | DynamicAuthenticationArg | DynamicPromptFormArg;
|
||||
type AnyCallArgs =
|
||||
| CallTemplateFunctionArgs
|
||||
| CallHttpAuthenticationActionArgs
|
||||
| CallPromptFormDynamicArgs;
|
||||
|
||||
export async function applyDynamicFormInput(
|
||||
ctx: Context,
|
||||
args: DynamicTemplateFunctionArg[],
|
||||
@@ -18,30 +30,40 @@ export async function applyDynamicFormInput(
|
||||
|
||||
export async function applyDynamicFormInput(
|
||||
ctx: Context,
|
||||
args: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[],
|
||||
callArgs: CallTemplateFunctionArgs | CallHttpAuthenticationActionArgs,
|
||||
): Promise<(DynamicTemplateFunctionArg | DynamicAuthenticationArg)[]> {
|
||||
const resolvedArgs: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[] = [];
|
||||
args: DynamicPromptFormArg[],
|
||||
callArgs: CallPromptFormDynamicArgs,
|
||||
): Promise<DynamicPromptFormArg[]>;
|
||||
|
||||
export async function applyDynamicFormInput(
|
||||
ctx: Context,
|
||||
args: AnyDynamicArg[],
|
||||
callArgs: AnyCallArgs,
|
||||
): Promise<AnyDynamicArg[]> {
|
||||
const resolvedArgs: AnyDynamicArg[] = [];
|
||||
for (const { dynamic, ...arg } of args) {
|
||||
const dynamicResult =
|
||||
typeof dynamic === 'function'
|
||||
? await dynamic(
|
||||
ctx,
|
||||
callArgs as CallTemplateFunctionArgs & CallHttpAuthenticationActionArgs,
|
||||
callArgs as CallTemplateFunctionArgs &
|
||||
CallHttpAuthenticationActionArgs &
|
||||
CallPromptFormDynamicArgs,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const newArg = {
|
||||
...arg,
|
||||
...dynamicResult,
|
||||
} as DynamicTemplateFunctionArg | DynamicAuthenticationArg;
|
||||
} as AnyDynamicArg;
|
||||
|
||||
if ('inputs' in newArg && Array.isArray(newArg.inputs)) {
|
||||
try {
|
||||
newArg.inputs = await applyDynamicFormInput(
|
||||
ctx,
|
||||
newArg.inputs as DynamicTemplateFunctionArg[],
|
||||
callArgs as CallTemplateFunctionArgs & CallHttpAuthenticationActionArgs,
|
||||
callArgs as CallTemplateFunctionArgs &
|
||||
CallHttpAuthenticationActionArgs &
|
||||
CallPromptFormDynamicArgs,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Failed to apply dynamic form input', e);
|
||||
|
||||
24
plugins-external/httpsnippet/package.json
Normal file
24
plugins-external/httpsnippet/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@yaak/httpsnippet",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"displayName": "HTTP Snippet",
|
||||
"description": "Generate code snippets for HTTP requests in various languages and frameworks",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mountain-loop/yaak.git",
|
||||
"directory": "plugins-external/httpsnippet"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"test": "vitest --run tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@readme/httpsnippet": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
311
plugins-external/httpsnippet/src/index.ts
Normal file
311
plugins-external/httpsnippet/src/index.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
import { type HarRequest, availableTargets, HTTPSnippet } from '@readme/httpsnippet';
|
||||
import type { EditorLanguage, HttpRequest, PluginDefinition } from '@yaakapp/api';
|
||||
|
||||
// Get all available targets and build select options
|
||||
const targets = availableTargets();
|
||||
|
||||
// Build language (target) options
|
||||
const languageOptions = targets.map((target) => ({
|
||||
label: target.title,
|
||||
value: target.key,
|
||||
}));
|
||||
|
||||
// Preferred clients per target (shown first in the list)
|
||||
const preferredClients: Record<string, string> = {
|
||||
javascript: 'fetch',
|
||||
node: 'fetch',
|
||||
};
|
||||
|
||||
// Get client options for a given target key
|
||||
function getClientOptions(targetKey: string) {
|
||||
const target = targets.find((t) => t.key === targetKey);
|
||||
if (!target) return [];
|
||||
const preferred = preferredClients[targetKey];
|
||||
return target.clients
|
||||
.map((client) => ({
|
||||
label: client.title,
|
||||
value: client.key,
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
if (a.value === preferred) return -1;
|
||||
if (b.value === preferred) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Get default client for a target
|
||||
function getDefaultClient(targetKey: string): string {
|
||||
const options = getClientOptions(targetKey);
|
||||
return options[0]?.value ?? '';
|
||||
}
|
||||
|
||||
// Defaults
|
||||
const defaultTarget = 'javascript';
|
||||
const defaultClient = 'fetch';
|
||||
|
||||
// Map httpsnippet target key to editor language for syntax highlighting
|
||||
const editorLanguageMap: Record<string, EditorLanguage> = {
|
||||
c: 'c',
|
||||
clojure: 'clojure',
|
||||
csharp: 'csharp',
|
||||
go: 'go',
|
||||
http: 'text',
|
||||
java: 'java',
|
||||
javascript: 'javascript',
|
||||
json: 'json',
|
||||
kotlin: 'kotlin',
|
||||
node: 'javascript',
|
||||
objc: 'objective_c',
|
||||
ocaml: 'ocaml',
|
||||
php: 'php',
|
||||
powershell: 'powershell',
|
||||
python: 'python',
|
||||
r: 'r',
|
||||
ruby: 'ruby',
|
||||
shell: 'shell',
|
||||
swift: 'swift',
|
||||
};
|
||||
|
||||
function getEditorLanguage(targetKey: string): EditorLanguage {
|
||||
return editorLanguageMap[targetKey] ?? 'text';
|
||||
}
|
||||
|
||||
// Convert Yaak HttpRequest to HAR format
|
||||
function toHarRequest(request: Partial<HttpRequest>) {
|
||||
// Build URL with query parameters
|
||||
let finalUrl = request.url || '';
|
||||
const urlParams = (request.urlParameters ?? []).filter((p) => p.enabled !== false && !!p.name);
|
||||
if (urlParams.length > 0) {
|
||||
const [base, hash] = finalUrl.split('#');
|
||||
const separator = base?.includes('?') ? '&' : '?';
|
||||
const queryString = urlParams
|
||||
.map((p) => `${encodeURIComponent(p.name)}=${encodeURIComponent(p.value)}`)
|
||||
.join('&');
|
||||
finalUrl = base + separator + queryString + (hash ? `#${hash}` : '');
|
||||
}
|
||||
|
||||
// Build headers array
|
||||
const headers: Array<{ name: string; value: string }> = (request.headers ?? [])
|
||||
.filter((h) => h.enabled !== false && !!h.name)
|
||||
.map((h) => ({ name: h.name, value: h.value }));
|
||||
|
||||
// Handle authentication
|
||||
if (request.authentication?.disabled !== true) {
|
||||
if (request.authenticationType === 'basic') {
|
||||
const credentials = btoa(
|
||||
`${request.authentication?.username ?? ''}:${request.authentication?.password ?? ''}`,
|
||||
);
|
||||
headers.push({ name: 'Authorization', value: `Basic ${credentials}` });
|
||||
} else if (request.authenticationType === 'bearer') {
|
||||
const prefix = request.authentication?.prefix ?? 'Bearer';
|
||||
const token = request.authentication?.token ?? '';
|
||||
headers.push({ name: 'Authorization', value: `${prefix} ${token}`.trim() });
|
||||
} else if (request.authenticationType === 'apikey') {
|
||||
if (request.authentication?.location === 'header') {
|
||||
headers.push({
|
||||
name: request.authentication?.key ?? 'X-Api-Key',
|
||||
value: request.authentication?.value ?? '',
|
||||
});
|
||||
} else if (request.authentication?.location === 'query') {
|
||||
const sep = finalUrl.includes('?') ? '&' : '?';
|
||||
finalUrl = [
|
||||
finalUrl,
|
||||
sep,
|
||||
encodeURIComponent(request.authentication?.key ?? 'token'),
|
||||
'=',
|
||||
encodeURIComponent(request.authentication?.value ?? ''),
|
||||
].join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build HAR request object
|
||||
const har: Record<string, unknown> = {
|
||||
method: request.method || 'GET',
|
||||
url: finalUrl,
|
||||
headers,
|
||||
};
|
||||
|
||||
// Handle request body
|
||||
const bodyType = request.bodyType ?? 'none';
|
||||
if (bodyType !== 'none' && request.body) {
|
||||
if (bodyType === 'application/x-www-form-urlencoded' && Array.isArray(request.body.form)) {
|
||||
const params = request.body.form
|
||||
.filter((p: { enabled?: boolean; name?: string }) => p.enabled !== false && !!p.name)
|
||||
.map((p: { name: string; value: string }) => ({ name: p.name, value: p.value }));
|
||||
har.postData = {
|
||||
mimeType: 'application/x-www-form-urlencoded',
|
||||
params,
|
||||
};
|
||||
} else if (bodyType === 'multipart/form-data' && Array.isArray(request.body.form)) {
|
||||
const params = request.body.form
|
||||
.filter((p: { enabled?: boolean; name?: string }) => p.enabled !== false && !!p.name)
|
||||
.map((p: { name: string; value: string; file?: string; contentType?: string }) => {
|
||||
const param: Record<string, string> = { name: p.name, value: p.value || '' };
|
||||
if (p.file) param.fileName = p.file;
|
||||
if (p.contentType) param.contentType = p.contentType;
|
||||
return param;
|
||||
});
|
||||
har.postData = {
|
||||
mimeType: 'multipart/form-data',
|
||||
params,
|
||||
};
|
||||
} else if (bodyType === 'graphql' && typeof request.body.query === 'string') {
|
||||
const body = {
|
||||
query: request.body.query || '',
|
||||
variables: maybeParseJSON(request.body.variables, undefined),
|
||||
};
|
||||
har.postData = {
|
||||
mimeType: 'application/json',
|
||||
text: JSON.stringify(body),
|
||||
};
|
||||
} else if (typeof request.body.text === 'string') {
|
||||
har.postData = {
|
||||
mimeType: bodyType,
|
||||
text: request.body.text,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return har;
|
||||
}
|
||||
|
||||
function maybeParseJSON<T>(v: unknown, fallback: T): T | unknown {
|
||||
if (typeof v !== 'string') return fallback;
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
httpRequestActions: [
|
||||
{
|
||||
label: 'Generate Code Snippet',
|
||||
icon: 'copy',
|
||||
async onSelect(ctx, args) {
|
||||
// Render the request with variables resolved
|
||||
const renderedRequest = await ctx.httpRequest.render({
|
||||
httpRequest: args.httpRequest,
|
||||
purpose: 'send',
|
||||
});
|
||||
|
||||
// Convert to HAR format
|
||||
const harRequest = toHarRequest(renderedRequest) as HarRequest;
|
||||
|
||||
// Get previously selected language or use defaults
|
||||
const storedTarget = await ctx.store.get<string>('selectedTarget');
|
||||
const storedClient = await ctx.store.get<string>('selectedClient');
|
||||
const initialTarget = storedTarget || defaultTarget;
|
||||
const initialClient = storedClient || defaultClient;
|
||||
|
||||
// Create snippet generator
|
||||
const snippet = new HTTPSnippet(harRequest);
|
||||
const generateSnippet = (target: string, client: string): string => {
|
||||
const result = snippet.convert(target as any, client);
|
||||
return (Array.isArray(result) ? result.join('\n') : result || '').replace(/\r\n/g, '\n');
|
||||
};
|
||||
|
||||
// Generate initial code preview
|
||||
let initialCode = '';
|
||||
try {
|
||||
initialCode = generateSnippet(initialTarget, initialClient);
|
||||
} catch {
|
||||
initialCode = '// Error generating snippet';
|
||||
}
|
||||
|
||||
// Show dialog with language/library selectors and code preview
|
||||
const result = await ctx.prompt.form({
|
||||
id: 'httpsnippet',
|
||||
title: 'Generate Code Snippet',
|
||||
confirmText: 'Copy to Clipboard',
|
||||
cancelText: 'Cancel',
|
||||
size: 'md',
|
||||
inputs: [
|
||||
{
|
||||
type: 'h_stack',
|
||||
inputs: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'target',
|
||||
label: 'Language',
|
||||
defaultValue: initialTarget,
|
||||
options: languageOptions,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: `client-${initialTarget}`,
|
||||
label: 'Library',
|
||||
defaultValue: initialClient,
|
||||
options: getClientOptions(initialTarget),
|
||||
dynamic(_ctx, { values }) {
|
||||
const targetKey = String(values.target || defaultTarget);
|
||||
const options = getClientOptions(targetKey);
|
||||
return {
|
||||
name: `client-${targetKey}`,
|
||||
options,
|
||||
defaultValue: options[0]?.value ?? '',
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
name: 'code',
|
||||
label: 'Preview',
|
||||
language: getEditorLanguage(initialTarget),
|
||||
defaultValue: initialCode,
|
||||
readOnly: true,
|
||||
rows: 15,
|
||||
dynamic(_ctx, { values }) {
|
||||
const targetKey = String(values.target || defaultTarget);
|
||||
const clientKey = String(
|
||||
values[`client-${targetKey}`] || getDefaultClient(targetKey),
|
||||
);
|
||||
let code: string;
|
||||
try {
|
||||
code = generateSnippet(targetKey, clientKey);
|
||||
} catch {
|
||||
code = '// Error generating snippet';
|
||||
}
|
||||
return {
|
||||
defaultValue: code,
|
||||
language: getEditorLanguage(targetKey),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (result) {
|
||||
// Store the selected language and library for next time
|
||||
const selectedTarget = String(result.target || initialTarget);
|
||||
const selectedClient = String(
|
||||
result[`client-${selectedTarget}`] || getDefaultClient(selectedTarget),
|
||||
);
|
||||
await ctx.store.set('selectedTarget', selectedTarget);
|
||||
await ctx.store.set('selectedClient', selectedClient);
|
||||
|
||||
// Generate snippet for the selected language
|
||||
try {
|
||||
const codeText = generateSnippet(selectedTarget, selectedClient);
|
||||
await ctx.clipboard.copyText(codeText);
|
||||
await ctx.toast.show({
|
||||
message: 'Code snippet copied to clipboard',
|
||||
icon: 'copy',
|
||||
color: 'success',
|
||||
});
|
||||
} catch (err) {
|
||||
await ctx.toast.show({
|
||||
message: `Failed to generate snippet: ${err}`,
|
||||
icon: 'alert_triangle',
|
||||
color: 'danger',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -360,8 +360,9 @@ function EditorArg({
|
||||
className={classNames(
|
||||
'border border-border rounded-md overflow-hidden px-2 py-1',
|
||||
'focus-within:border-border-focus',
|
||||
'max-h-[10rem]', // So it doesn't take up too much space
|
||||
!arg.rows && 'max-h-[10rem]', // So it doesn't take up too much space
|
||||
)}
|
||||
style={arg.rows ? { height: `${arg.rows * 1.4 + 0.75}rem` } : undefined}
|
||||
>
|
||||
<Editor
|
||||
id={id}
|
||||
|
||||
@@ -5,11 +5,14 @@ import {
|
||||
completionKeymap,
|
||||
} from '@codemirror/autocomplete';
|
||||
import { history, historyKeymap } from '@codemirror/commands';
|
||||
import { go } from '@codemirror/lang-go';
|
||||
import { java } from '@codemirror/lang-java';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { php } from '@codemirror/lang-php';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { xml } from '@codemirror/lang-xml';
|
||||
import type { LanguageSupport } from '@codemirror/language';
|
||||
import {
|
||||
bracketMatching,
|
||||
codeFolding,
|
||||
@@ -17,8 +20,18 @@ import {
|
||||
foldKeymap,
|
||||
HighlightStyle,
|
||||
indentOnInput,
|
||||
LanguageSupport,
|
||||
StreamLanguage,
|
||||
syntaxHighlighting,
|
||||
} from '@codemirror/language';
|
||||
import { c, csharp, kotlin, objectiveC } from '@codemirror/legacy-modes/mode/clike';
|
||||
import { clojure } from '@codemirror/legacy-modes/mode/clojure';
|
||||
import { oCaml } from '@codemirror/legacy-modes/mode/mllike';
|
||||
import { powerShell } from '@codemirror/legacy-modes/mode/powershell';
|
||||
import { r } from '@codemirror/legacy-modes/mode/r';
|
||||
import { ruby } from '@codemirror/legacy-modes/mode/ruby';
|
||||
import { shell } from '@codemirror/legacy-modes/mode/shell';
|
||||
import { swift } from '@codemirror/legacy-modes/mode/swift';
|
||||
import { linter, lintGutter, lintKeymap } from '@codemirror/lint';
|
||||
|
||||
import { search, searchKeymap } from '@codemirror/search';
|
||||
@@ -83,6 +96,10 @@ const syntaxTheme = EditorView.theme({}, { dark: true });
|
||||
|
||||
const closeBracketsExtensions: Extension = [closeBrackets(), keymap.of([...closeBracketsKeymap])];
|
||||
|
||||
const legacyLang = (mode: Parameters<typeof StreamLanguage.define>[0]) => {
|
||||
return () => new LanguageSupport(StreamLanguage.define(mode));
|
||||
};
|
||||
|
||||
const syntaxExtensions: Record<
|
||||
NonNullable<EditorProps['language']>,
|
||||
null | (() => LanguageSupport)
|
||||
@@ -98,6 +115,21 @@ const syntaxExtensions: Record<
|
||||
text: text,
|
||||
timeline: timeline,
|
||||
markdown: markdown,
|
||||
c: legacyLang(c),
|
||||
clojure: legacyLang(clojure),
|
||||
csharp: legacyLang(csharp),
|
||||
go: go,
|
||||
java: java,
|
||||
kotlin: legacyLang(kotlin),
|
||||
objective_c: legacyLang(objectiveC),
|
||||
ocaml: legacyLang(oCaml),
|
||||
php: php,
|
||||
powershell: legacyLang(powerShell),
|
||||
python: python,
|
||||
r: legacyLang(r),
|
||||
ruby: legacyLang(ruby),
|
||||
shell: legacyLang(shell),
|
||||
swift: legacyLang(swift),
|
||||
};
|
||||
|
||||
const closeBracketsFor: (keyof typeof syntaxExtensions)[] = ['json', 'javascript', 'graphql'];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FormInput, JsonPrimitive } from '@yaakapp-internal/plugins';
|
||||
import type { FormEvent } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { generateId } from '../../lib/generateId';
|
||||
import { DynamicForm } from '../DynamicForm';
|
||||
import { Button } from './Button';
|
||||
@@ -12,16 +12,21 @@ export interface PromptProps {
|
||||
onResult: (value: Record<string, JsonPrimitive> | null) => void;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
onValuesChange?: (values: Record<string, JsonPrimitive>) => void;
|
||||
onInputsUpdated?: (cb: (inputs: FormInput[]) => void) => void;
|
||||
}
|
||||
|
||||
export function Prompt({
|
||||
onCancel,
|
||||
inputs,
|
||||
inputs: initialInputs,
|
||||
onResult,
|
||||
confirmText = 'Confirm',
|
||||
cancelText = 'Cancel',
|
||||
onValuesChange,
|
||||
onInputsUpdated,
|
||||
}: PromptProps) {
|
||||
const [value, setValue] = useState<Record<string, JsonPrimitive>>({});
|
||||
const [inputs, setInputs] = useState<FormInput[]>(initialInputs);
|
||||
const handleSubmit = useCallback(
|
||||
(e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
@@ -30,6 +35,16 @@ export function Prompt({
|
||||
[onResult, value],
|
||||
);
|
||||
|
||||
// Register callback for external input updates (from plugin dynamic resolution)
|
||||
useEffect(() => {
|
||||
onInputsUpdated?.(setInputs);
|
||||
}, [onInputsUpdated]);
|
||||
|
||||
// Notify of value changes for dynamic resolution
|
||||
useEffect(() => {
|
||||
onValuesChange?.(value);
|
||||
}, [value, onValuesChange]);
|
||||
|
||||
const id = `prompt.form.${useRef(generateId()).current}`;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import type { InternalEvent, ShowToastRequest } from '@yaakapp-internal/plugins';
|
||||
import { debounce } from '@yaakapp-internal/lib';
|
||||
import type {
|
||||
FormInput,
|
||||
InternalEvent,
|
||||
JsonPrimitive,
|
||||
ShowToastRequest,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { updateAllPlugins } from '@yaakapp-internal/plugins';
|
||||
import type {
|
||||
PluginUpdateNotification,
|
||||
@@ -32,6 +38,9 @@ export function initGlobalListeners() {
|
||||
|
||||
listenToTauriEvent('settings', () => openSettings.mutate(null));
|
||||
|
||||
// Track active dynamic form dialogs so follow-up input updates can reach them
|
||||
const activeForms = new Map<string, (inputs: FormInput[]) => void>();
|
||||
|
||||
// Listen for plugin events
|
||||
listenToTauriEvent<InternalEvent>('plugin_event', async ({ payload: event }) => {
|
||||
if (event.payload.type === 'prompt_text_request') {
|
||||
@@ -49,26 +58,47 @@ export function initGlobalListeners() {
|
||||
};
|
||||
await emit(event.id, result);
|
||||
} else if (event.payload.type === 'prompt_form_request') {
|
||||
if (event.replyId != null) {
|
||||
// Follow-up update from plugin runtime — update the active dialog's inputs
|
||||
const updateInputs = activeForms.get(event.replyId);
|
||||
if (updateInputs) {
|
||||
updateInputs(event.payload.inputs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial request — show the dialog with bidirectional support
|
||||
const emitFormResponse = (values: Record<string, JsonPrimitive> | null, done: boolean) => {
|
||||
const result: InternalEvent = {
|
||||
id: generateId(),
|
||||
replyId: event.id,
|
||||
pluginName: event.pluginName,
|
||||
pluginRefId: event.pluginRefId,
|
||||
context: event.context,
|
||||
payload: {
|
||||
type: 'prompt_form_response',
|
||||
values,
|
||||
done,
|
||||
},
|
||||
};
|
||||
emit(event.id, result);
|
||||
};
|
||||
|
||||
const values = await showPromptForm({
|
||||
id: event.payload.id,
|
||||
title: event.payload.title,
|
||||
description: event.payload.description,
|
||||
size: event.payload.size,
|
||||
inputs: event.payload.inputs,
|
||||
confirmText: event.payload.confirmText,
|
||||
cancelText: event.payload.cancelText,
|
||||
onValuesChange: debounce((values) => emitFormResponse(values, false), 150),
|
||||
onInputsUpdated: (cb) => activeForms.set(event.id, cb),
|
||||
});
|
||||
const result: InternalEvent = {
|
||||
id: generateId(),
|
||||
replyId: event.id,
|
||||
pluginName: event.pluginName,
|
||||
pluginRefId: event.pluginRefId,
|
||||
context: event.context,
|
||||
payload: {
|
||||
type: 'prompt_form_response',
|
||||
values,
|
||||
},
|
||||
};
|
||||
await emit(event.id, result);
|
||||
|
||||
// Clean up and send final response
|
||||
activeForms.delete(event.id);
|
||||
emitFormResponse(values, true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
import type { FormInput, JsonPrimitive } from '@yaakapp-internal/plugins';
|
||||
import type { DialogProps } from '../components/core/Dialog';
|
||||
import type { PromptProps } from '../components/core/Prompt';
|
||||
import { Prompt } from '../components/core/Prompt';
|
||||
import { showDialog } from './dialog';
|
||||
|
||||
type FormArgs = Pick<DialogProps, 'title' | 'description'> &
|
||||
type FormArgs = Pick<DialogProps, 'title' | 'description' | 'size'> &
|
||||
Omit<PromptProps, 'onClose' | 'onCancel' | 'onResult'> & {
|
||||
id: string;
|
||||
onValuesChange?: (values: Record<string, JsonPrimitive>) => void;
|
||||
onInputsUpdated?: (cb: (inputs: FormInput[]) => void) => void;
|
||||
};
|
||||
|
||||
export async function showPromptForm({ id, title, description, ...props }: FormArgs) {
|
||||
export async function showPromptForm({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
size,
|
||||
onValuesChange,
|
||||
onInputsUpdated,
|
||||
...props
|
||||
}: FormArgs) {
|
||||
return new Promise((resolve: PromptProps['onResult']) => {
|
||||
showDialog({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
hideX: true,
|
||||
size: 'sm',
|
||||
size: size ?? 'sm',
|
||||
disableBackdropClose: true, // Prevent accidental dismisses
|
||||
onClose: () => {
|
||||
// Click backdrop, close, or escape
|
||||
@@ -32,6 +43,8 @@ export async function showPromptForm({ id, title, description, ...props }: FormA
|
||||
resolve(v);
|
||||
hide();
|
||||
},
|
||||
onValuesChange,
|
||||
onInputsUpdated,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user