mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:14:03 +01:00
Nested template functions (#186)
This commit is contained in:
@@ -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 || <> </>}
|
||||
</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 || <> </>
|
||||
)}
|
||||
</InlineCode>
|
||||
)}
|
||||
</VStack>
|
||||
<Button color="primary" onClick={handleDone}>
|
||||
Done
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user