Nested template functions (#186)

This commit is contained in:
Gregory Schier
2025-03-18 12:49:19 -07:00
committed by GitHub
parent b9ed554aca
commit cb773babe1
9 changed files with 321 additions and 118 deletions

View File

@@ -5,10 +5,13 @@ import { useMemo, useState } from 'react';
import { useDebouncedValue } from '../hooks/useDebouncedValue';
import { useRenderTemplate } from '../hooks/useRenderTemplate';
import { useTemplateTokensToString } from '../hooks/useTemplateTokensToString';
import { useToggle } from '../hooks/useToggle';
import { Button } from './core/Button';
import { InlineCode } from './core/InlineCode';
import { VStack } from './core/Stacks';
import { HStack, VStack } from './core/Stacks';
import { DYNAMIC_FORM_NULL_ARG, DynamicForm } from './DynamicForm';
import { IconButton } from './core/IconButton';
import { Banner } from './core/Banner';
interface Props {
templateFunction: TemplateFunction;
@@ -18,6 +21,7 @@ interface Props {
}
export function TemplateFunctionDialog({ templateFunction, hide, initialTokens, onChange }: Props) {
const [showSecretsInPreview, toggleShowSecretsInPreview] = useToggle(false);
const [argValues, setArgValues] = useState<Record<string, string | boolean>>(() => {
const initial: Record<string, string> = {};
const initialArgs =
@@ -77,27 +81,65 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
const debouncedTagText = useDebouncedValue(tagText.data ?? '', 200);
const rendered = useRenderTemplate(debouncedTagText);
const tooLarge = (rendered.data ?? '').length > 10000;
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
const dataContainsSecrets = useMemo(() => {
for (const [name, value] of Object.entries(argValues)) {
const isPassword = templateFunction.args.some(
(a) => a.type === 'text' && a.password && a.name === name,
);
if (isPassword && typeof value === 'string' && value && rendered.data?.includes(value)) {
return true;
}
}
return false;
// Only update this on rendered data change to keep secrets hidden on input change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rendered.data]);
return (
<VStack className="pb-3" space={4}>
<h1 className="font-mono !text-base">{templateFunction.name}()</h1>
<DynamicForm
autocompleteVariables
autocompleteFunctions
inputs={templateFunction.args}
data={argValues}
onChange={setArgValues}
stateKey={`template_function.${templateFunction.name}`}
/>
<VStack className="w-full">
<div className="text-sm text-text-subtle">Preview</div>
<InlineCode
className={classNames(
'whitespace-pre select-text cursor-text max-h-[10rem] overflow-y-auto hide-scrollbars',
tooLarge && 'italic text-danger',
)}
>
{tooLarge ? 'too large to preview' : rendered.data || <>&nbsp;</>}
</InlineCode>
<VStack className="w-full" space={1}>
<HStack space={0.5}>
<div className="text-sm text-text-subtle">Rendered Preview</div>
<IconButton
size="xs"
iconSize="sm"
icon={showSecretsInPreview ? 'lock' : 'lock_open'}
title={showSecretsInPreview ? 'Show preview' : 'Hide preview'}
onClick={toggleShowSecretsInPreview}
className={classNames(
'ml-auto text-text-subtlest',
!dataContainsSecrets && 'invisible',
)}
/>
</HStack>
{rendered.error || tagText.error ? (
<Banner color="danger">{`${rendered.error || tagText.error}`}</Banner>
) : (
<InlineCode
className={classNames(
'whitespace-pre select-text cursor-text max-h-[10rem] overflow-y-auto hide-scrollbars',
tooLarge && 'italic text-danger',
)}
>
{dataContainsSecrets && !showSecretsInPreview ? (
<span className="italic text-text-subtle">------ sensitive values hidden ------</span>
) : tooLarge ? (
'too large to preview'
) : (
rendered.data || <>&nbsp;</>
)}
</InlineCode>
)}
</VStack>
<Button color="primary" onClick={handleDone}>
Done

View File

@@ -266,7 +266,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
async (fn: TemplateFunction, tagValue: string, startPos: number) => {
const initialTokens = await parseTemplate(tagValue);
showDialog({
id: 'template-function',
id: 'template-function-'+Math.random(), // Allow multiple at once
size: 'sm',
title: 'Configure Function',
description: fn.description,

View File

@@ -69,6 +69,7 @@ const icons = {
left_panel_hidden: lucide.PanelLeftOpenIcon,
left_panel_visible: lucide.PanelLeftCloseIcon,
lock: lucide.LockIcon,
lock_open: lucide.LockOpenIcon,
magic_wand: lucide.Wand2Icon,
merge: lucide.MergeIcon,
minus: lucide.MinusIcon,