feat: add ctx.prompt.form() API for multi-field form dialogs

Add PromptFormRequest and PromptFormResponse types to enable plugins to
display forms with multiple input fields. Implement the form() method in
the prompt context and wire up frontend event handling to show and collect
form responses from users.
This commit is contained in:
Gregory Schier
2026-01-09 19:35:47 -08:00
parent 4c8f768624
commit fa3e6e6508
6 changed files with 68 additions and 2 deletions

File diff suppressed because one or more lines are too long

View File

@@ -157,6 +157,9 @@ pub enum InternalEventPayload {
PromptTextRequest(PromptTextRequest), PromptTextRequest(PromptTextRequest),
PromptTextResponse(PromptTextResponse), PromptTextResponse(PromptTextResponse),
PromptFormRequest(PromptFormRequest),
PromptFormResponse(PromptFormResponse),
WindowInfoRequest(WindowInfoRequest), WindowInfoRequest(WindowInfoRequest),
WindowInfoResponse(WindowInfoResponse), WindowInfoResponse(WindowInfoResponse),
@@ -571,6 +574,28 @@ pub struct PromptTextResponse {
pub value: Option<String>, pub value: Option<String>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct PromptFormRequest {
pub id: String,
pub title: String,
#[ts(optional)]
pub description: Option<String>,
pub inputs: Vec<FormInput>,
#[ts(optional)]
pub confirm_text: Option<String>,
#[ts(optional)]
pub cancel_text: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct PromptFormResponse {
pub values: Option<HashMap<String, JsonPrimitive>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,8 @@ import type {
ListHttpRequestsRequest, ListHttpRequestsRequest,
ListHttpRequestsResponse, ListHttpRequestsResponse,
OpenWindowRequest, OpenWindowRequest,
PromptFormRequest,
PromptFormResponse,
PromptTextRequest, PromptTextRequest,
PromptTextResponse, PromptTextResponse,
RenderGrpcRequestRequest, RenderGrpcRequestRequest,
@@ -37,6 +39,7 @@ export interface Context {
}; };
prompt: { prompt: {
text(args: PromptTextRequest): Promise<PromptTextResponse['value']>; text(args: PromptTextRequest): Promise<PromptTextResponse['value']>;
form(args: PromptFormRequest): Promise<PromptFormResponse['values']>;
}; };
store: { store: {
set<T>(key: string, value: T): Promise<void>; set<T>(key: string, value: T): Promise<void>;

View File

@@ -28,6 +28,7 @@ import type {
ListHttpRequestsResponse, ListHttpRequestsResponse,
ListWorkspacesResponse, ListWorkspacesResponse,
PluginContext, PluginContext,
PromptFormResponse,
PromptTextResponse, PromptTextResponse,
RenderGrpcRequestResponse, RenderGrpcRequestResponse,
RenderHttpRequestResponse, RenderHttpRequestResponse,
@@ -661,6 +662,13 @@ export class PluginInstance {
}); });
return reply.value; return reply.value;
}, },
form: async (args) => {
const reply: PromptFormResponse = await this.#sendForReply(context, {
type: 'prompt_form_request',
...args,
});
return reply.values;
},
}, },
httpResponse: { httpResponse: {
find: async (args) => { find: async (args) => {

View File

@@ -21,6 +21,7 @@ import { stringToColor } from './color';
import { generateId } from './generateId'; import { generateId } from './generateId';
import { jotaiStore } from './jotai'; import { jotaiStore } from './jotai';
import { showPrompt } from './prompt'; import { showPrompt } from './prompt';
import { showPromptForm } from './prompt-form';
import { invokeCmd } from './tauri'; import { invokeCmd } from './tauri';
import { showToast } from './toast'; import { showToast } from './toast';
@@ -47,6 +48,27 @@ export function initGlobalListeners() {
}, },
}; };
await emit(event.id, result); await emit(event.id, result);
} else if (event.payload.type === 'prompt_form_request') {
const values = await showPromptForm({
id: event.payload.id,
title: event.payload.title,
description: event.payload.description,
inputs: event.payload.inputs,
confirmText: event.payload.confirmText,
cancelText: event.payload.cancelText,
});
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);
} }
}); });