Improved prompt function add add ctx.* functions (#301)

This commit is contained in:
Gregory Schier
2025-11-15 08:19:58 -08:00
committed by GitHub
parent 7ced183b11
commit 84219571e8
29 changed files with 454 additions and 150 deletions

View File

@@ -26,7 +26,7 @@ import { IconButton } from './core/IconButton';
import { Input } from './core/Input';
import { Label } from './core/Label';
import { Select } from './core/Select';
import { HStack, VStack } from './core/Stacks';
import { VStack } from './core/Stacks';
import { Markdown } from './Markdown';
import { SelectFile } from './SelectFile';
@@ -216,7 +216,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
);
case 'h_stack':
return (
<HStack key={i + stateKey} alignItems="end" space={3}>
<div className="flex flex-wrap sm:flex-nowrap gap-3 items-end" key={i + stateKey}>
<FormInputs
data={data}
disabled={disabled}
@@ -226,7 +226,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
autocompleteFunctions={autocompleteFunctions || false}
autocompleteVariables={autocompleteVariables}
/>
</HStack>
</div>
);
case 'banner':
return (

View File

@@ -50,7 +50,6 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({
const newMethod = await showPrompt({
id: 'custom-method',
label: 'Http Method',
defaultValue: '',
title: 'Custom Method',
confirmText: 'Save',
description: 'Enter a custom method name',

View File

@@ -54,7 +54,6 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
initial.value = await convertTemplateToInsecure(template);
}
console.log('INITIAL', initial);
setInitialArgValues(initial);
})().catch(console.error);
}, [
@@ -78,7 +77,7 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
}
function InitializedTemplateFunctionDialog({
templateFunction: { name },
templateFunction: { name, previewType: ogPreviewType },
initialArgValues,
hide,
onChange,
@@ -86,7 +85,7 @@ function InitializedTemplateFunctionDialog({
}: Omit<Props, 'initialTokens'> & {
initialArgValues: Record<string, string | boolean>;
}) {
const enablePreview = name !== 'secure';
const previewType = ogPreviewType == null ? 'live' : ogPreviewType;
const [showSecretsInPreview, toggleShowSecretsInPreview] = useToggle(false);
const [argValues, setArgValues] = useState<Record<string, string | boolean>>(initialArgValues);
@@ -126,7 +125,14 @@ function InitializedTemplateFunctionDialog({
};
const debouncedTagText = useDebouncedValue(tagText.data ?? '', 400);
const rendered = useRenderTemplate(debouncedTagText);
const [renderKey, setRenderKey] = useState<string | null>(null);
const rendered = useRenderTemplate(
debouncedTagText,
previewType !== 'none',
previewType === 'click' ? 'send' : 'preview',
previewType === 'live' ? renderKey + debouncedTagText : renderKey,
);
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
const dataContainsSecrets = useMemo(() => {
for (const [name, value] of Object.entries(argValues)) {
@@ -174,12 +180,12 @@ function InitializedTemplateFunctionDialog({
)}
</div>
<div className="px-6 border-t border-t-border py-3 bg-surface-highlight w-full flex flex-col gap-4">
{enablePreview ? (
{previewType !== 'none' ? (
<VStack className="w-full">
<HStack space={0.5}>
<HStack className="text-sm text-text-subtle" space={1.5}>
Rendered Preview
{rendered.isPending && <LoadingIcon size="xs" />}
{rendered.isLoading && <LoadingIcon size="xs" />}
</HStack>
<IconButton
size="xs"
@@ -195,6 +201,7 @@ function InitializedTemplateFunctionDialog({
</HStack>
<InlineCode
className={classNames(
'relative',
'whitespace-pre-wrap !select-text cursor-text max-h-[10rem] overflow-y-auto hide-scrollbars !border-text-subtlest',
tooLarge && 'italic text-danger',
)}
@@ -212,6 +219,18 @@ function InitializedTemplateFunctionDialog({
) : (
rendered.data || <>&nbsp;</>
)}
<div className="absolute right-0 top-0 bottom-0 flex items-center">
<IconButton
size="xs"
icon="refresh"
className="text-text-subtle"
title="Refresh preview"
spin={rendered.isLoading}
onClick={() => {
setRenderKey(new Date().toISOString());
}}
/>
</div>
</InlineCode>
</VStack>
) : (
@@ -250,8 +269,16 @@ function collectArgumentValues(initialTokens: Tokens, templateFunction: Template
if (!('name' in arg)) return;
const initialArg = initialArgs.find((a) => a.name === arg.name);
const initialArgValue = initialArg?.value.type === 'str' ? initialArg?.value.text : undefined;
initial[arg.name] = initialArgValue ?? arg.defaultValue ?? DYNAMIC_FORM_NULL_ARG;
const initialArgValue =
initialArg?.value.type === 'str'
? initialArg?.value.text
: initialArg?.value.type === 'bool'
? initialArg.value.value
: undefined;
const value = initialArgValue ?? arg.defaultValue;
if (value != null) {
initial[arg.name] = value;
}
};
templateFunction.args.forEach(processArg);

View File

@@ -1,71 +0,0 @@
import type { Tokens } from '@yaakapp-internal/templates';
import { useCallback, useMemo, useState } from 'react';
import { useActiveEnvironmentVariables } from '../hooks/useActiveEnvironmentVariables';
import { useRenderTemplate } from '../hooks/useRenderTemplate';
import { useTemplateTokensToString } from '../hooks/useTemplateTokensToString';
import { Button } from './core/Button';
import { InlineCode } from './core/InlineCode';
import { Select } from './core/Select';
import { VStack } from './core/Stacks';
interface Props {
initialTokens: Tokens;
hide: () => void;
onChange: (rawTag: string) => void;
}
export function TemplateVariableDialog({ hide, onChange, initialTokens }: Props) {
const variables = useActiveEnvironmentVariables();
const [selectedVariableName, setSelectedVariableName] = useState<string>(() => {
return initialTokens.tokens[0]?.type === 'tag' && initialTokens.tokens[0]?.val.type === 'var'
? initialTokens.tokens[0]?.val.name
: ''; // Should never happen
});
const tokens: Tokens = useMemo(() => {
const selectedVariable = variables.find((v) => v.name === selectedVariableName);
return {
tokens: [
{
type: 'tag',
val: {
type: 'var',
name: selectedVariable?.name ?? '',
},
},
],
};
}, [selectedVariableName, variables]);
const tagText = useTemplateTokensToString(tokens);
const handleDone = useCallback(async () => {
if (tagText.data != null) {
onChange(tagText.data);
}
hide();
}, [hide, onChange, tagText.data]);
const rendered = useRenderTemplate(tagText.data ?? '');
return (
<VStack className="pb-3" space={4}>
<VStack space={2}>
<Select
name="variable"
label="Select Variable"
value={selectedVariableName}
options={variables.map((v) => ({ label: v.name, value: v.name }))}
onChange={setSelectedVariableName}
/>
</VStack>
<VStack>
<div className="text-sm text-text-subtle">Preview</div>
<InlineCode className="select-text cursor-text">{rendered.data}</InlineCode>
</VStack>
<Button color="primary" onClick={handleDone}>
Done
</Button>
</VStack>
);
}

View File

@@ -9,7 +9,7 @@ export interface BannerProps {
export function Banner({ children, className, color }: BannerProps) {
return (
<div className="w-full mb-auto grid grid-rows-1 max-h-full">
<div className="w-auto grid grid-rows-1 max-h-full flex-0">
<div
className={classNames(
className,
@@ -18,6 +18,7 @@ export function Banner({ children, className, color }: BannerProps) {
'border border-border border-dashed',
'px-4 py-2 rounded-lg select-auto',
'overflow-auto text-text',
'mb-auto', // Don't stretch all the way down if the parent is in grid or flexbox
)}
>
{children}

View File

@@ -723,6 +723,7 @@ function FileActionsDropdown({
id: 'content-type',
title: 'Override Content-Type',
label: 'Content-Type',
required: false,
placeholder: 'text/plain',
defaultValue: pair.contentType ?? '',
confirmText: 'Set',

View File

@@ -16,6 +16,7 @@ export function Prompt({
label,
defaultValue,
placeholder,
password,
onResult,
required,
confirmText,
@@ -36,10 +37,10 @@ export function Prompt({
onSubmit={handleSubmit}
>
<PlainInput
hideLabel
autoSelect
required={required}
placeholder={placeholder ?? 'Enter text'}
type={password ? 'password' : 'text'}
label={label}
defaultValue={defaultValue}
onChange={setValue}