snapshot of https://github.com/mountain-loop/yaak.git (main, 2026-09-23; blobs > 131072B omitted)
This commit is contained in:
195
plugins/template-function-prompt/src/index.ts
Normal file
195
plugins/template-function-prompt/src/index.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from "@yaakapp/api";
|
||||
import slugify from "slugify";
|
||||
|
||||
const STORE_NONE = "none";
|
||||
const STORE_FOREVER = "forever";
|
||||
const STORE_EXPIRE = "expire";
|
||||
|
||||
interface Saved {
|
||||
value: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
templateFunctions: [
|
||||
{
|
||||
name: "prompt.text",
|
||||
description: "Prompt the user for input when sending a request",
|
||||
previewType: "click",
|
||||
previewArgs: ["label"],
|
||||
args: [
|
||||
{
|
||||
type: "text",
|
||||
name: "label",
|
||||
label: "Label",
|
||||
optional: true,
|
||||
dynamic(_ctx, args) {
|
||||
if (
|
||||
args.values.store === STORE_EXPIRE ||
|
||||
(args.values.store === STORE_FOREVER && !args.values.key)
|
||||
) {
|
||||
return { optional: false };
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "store",
|
||||
label: "Store Input",
|
||||
defaultValue: STORE_NONE,
|
||||
options: [
|
||||
{ label: "Never", value: STORE_NONE },
|
||||
{ label: "Expire", value: STORE_EXPIRE },
|
||||
{ label: "Forever", value: STORE_FOREVER },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "h_stack",
|
||||
dynamic(_ctx, args) {
|
||||
return { hidden: args.values.store === STORE_NONE };
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
type: "text",
|
||||
name: "namespace",
|
||||
label: "Namespace",
|
||||
// oxlint-disable-next-line no-template-curly-in-string -- Yaak template syntax
|
||||
defaultValue: "${[ctx.workspace()]}",
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "key",
|
||||
label: "Key (defaults to Label)",
|
||||
optional: true,
|
||||
dynamic(_ctx, args) {
|
||||
return { placeholder: String(args.values.label || "") };
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "ttl",
|
||||
label: "TTL (seconds)",
|
||||
placeholder: "0",
|
||||
defaultValue: "0",
|
||||
optional: true,
|
||||
dynamic(_ctx, args) {
|
||||
return { hidden: args.values.store !== STORE_EXPIRE };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "banner",
|
||||
color: "info",
|
||||
inputs: [],
|
||||
dynamic(_ctx, args) {
|
||||
let key: string;
|
||||
try {
|
||||
key = buildKey(args);
|
||||
} catch (err) {
|
||||
return { color: "danger", inputs: [{ type: "markdown", content: String(err) }] };
|
||||
}
|
||||
return {
|
||||
hidden: args.values.store === STORE_NONE,
|
||||
inputs: [
|
||||
{
|
||||
type: "markdown",
|
||||
content: [`Value will be saved under: \`${key}\``].join("\n\n"),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "accordion",
|
||||
label: "Advanced",
|
||||
inputs: [
|
||||
{
|
||||
type: "text",
|
||||
name: "title",
|
||||
label: "Prompt Title",
|
||||
optional: true,
|
||||
placeholder: "Enter Value",
|
||||
},
|
||||
{ type: "text", name: "defaultValue", label: "Default Value", optional: true },
|
||||
{ type: "text", name: "placeholder", label: "Input Placeholder", optional: true },
|
||||
{ type: "checkbox", name: "password", label: "Mask Value" },
|
||||
],
|
||||
},
|
||||
],
|
||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
if (args.purpose !== "send") return null;
|
||||
|
||||
if (args.values.store !== STORE_NONE && !args.values.namespace) {
|
||||
throw new Error("Namespace is required when storing values");
|
||||
}
|
||||
|
||||
const existing = await maybeGetValue(ctx, args);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const value = await ctx.prompt.text({
|
||||
id: `prompt-${args.values.label ?? "none"}`,
|
||||
label: String(args.values.label || "Value"),
|
||||
title: String(args.values.title ?? "Enter Value"),
|
||||
defaultValue: String(args.values.defaultValue ?? ""),
|
||||
placeholder: String(args.values.placeholder ?? ""),
|
||||
password: Boolean(args.values.password),
|
||||
required: false,
|
||||
});
|
||||
|
||||
if (value == null) {
|
||||
throw new Error("Prompt cancelled");
|
||||
}
|
||||
|
||||
if (args.values.store !== STORE_NONE) {
|
||||
await maybeSetValue(ctx, args, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function buildKey(args: CallTemplateFunctionArgs) {
|
||||
if (!args.values.key && !args.values.label) {
|
||||
throw new Error("A label or key is required when storing values");
|
||||
}
|
||||
return [args.values.namespace, args.values.key || args.values.label]
|
||||
.filter((v) => !!v)
|
||||
.map((v) => slugify(String(v), { lower: true, trim: true }))
|
||||
.join(".");
|
||||
}
|
||||
|
||||
async function maybeGetValue(ctx: Context, args: CallTemplateFunctionArgs) {
|
||||
if (args.values.store === STORE_NONE) return null;
|
||||
|
||||
const existing = await ctx.store.get<Saved>(buildKey(args));
|
||||
if (existing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (args.values.store === STORE_FOREVER) {
|
||||
return existing.value;
|
||||
}
|
||||
|
||||
const ttlSeconds = Number.parseInt(String(args.values.ttl), 10) || 0;
|
||||
const ageSeconds = (Date.now() - existing.createdAt) / 1000;
|
||||
if (ageSeconds > ttlSeconds) {
|
||||
ctx.store.delete(buildKey(args)).catch(console.error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return existing.value;
|
||||
}
|
||||
|
||||
async function maybeSetValue(ctx: Context, args: CallTemplateFunctionArgs, value: string) {
|
||||
if (args.values.store === STORE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.store.set<Saved>(buildKey(args), { value, createdAt: Date.now() });
|
||||
}
|
||||
Reference in New Issue
Block a user