Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions web/src/pages/agent/canvas/node/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,17 @@ export function CardWithForm() {

type LabelCardProps = {
className?: string;
} & PropsWithChildren;
} & PropsWithChildren &
React.HTMLAttributes<HTMLElement>;

export function LabelCard({ children, className }: LabelCardProps) {
export function LabelCard({ children, className, ...props }: LabelCardProps) {
return (
<div
className={cn(
'bg-bg-card rounded-sm p-1 text-text-secondary text-xs',
className,
)}
{...props}
>
{children}
</div>
Expand Down
1 change: 1 addition & 0 deletions web/src/pages/agent/form/components/query-variable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export function QueryVariable({
options={finalOptions}
{...field}
// allowClear
type={type}
></GroupedSelectWithSecondaryMenu>
</FormControl>
<FormMessage />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ChevronDownIcon, XIcon } from 'lucide-react';
import * as React from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { VariableType } from '../../constant';
import {
useFilterStructuredOutputByValue,
useFindAgentStructuredOutputLabel,
Expand Down Expand Up @@ -52,13 +53,15 @@ interface GroupedSelectWithSecondaryMenuProps {
value?: string;
onChange?: (value: string) => void;
placeholder?: string;
type?: VariableType;
}

export function GroupedSelectWithSecondaryMenu({
options,
value,
onChange,
placeholder,
type,
}: GroupedSelectWithSecondaryMenuProps) {
const { t } = useTranslation();
const [open, setOpen] = React.useState(false);
Expand All @@ -69,6 +72,7 @@ export function GroupedSelectWithSecondaryMenu({

// Find the label of the selected item
const flattenedOptions = options.flatMap((g) => g.options);

let selectedItem = flattenedOptions
.flatMap((o) => [o, ...(o.children || [])])
.find((o) => o.value === value);
Expand Down Expand Up @@ -140,7 +144,7 @@ export function GroupedSelectWithSecondaryMenu({
<PopoverContent className="p-0" align="start">
<Command value={value}>
<CommandInput placeholder="Search..." />
<CommandList className="overflow-visible">
<CommandList className="overflow-auto">
{options.map((group, idx) => (
<CommandGroup key={idx} heading={group.label}>
{group.options.map((option) => {
Expand All @@ -159,6 +163,7 @@ export function GroupedSelectWithSecondaryMenu({
data={option}
click={handleSecondaryMenuClick}
filteredStructuredOutput={filteredStructuredOutput}
type={type}
></StructuredOutputSecondaryMenu>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,32 @@ import { cn } from '@/lib/utils';
import { get, isPlainObject } from 'lodash';
import { ChevronRight } from 'lucide-react';
import { PropsWithChildren, ReactNode, useCallback } from 'react';
import { VariableType } from '../../constant';

type DataItem = { label: ReactNode; value: string; parentLabel?: ReactNode };

type StructuredOutputSecondaryMenuProps = {
data: DataItem;
click(option: { label: ReactNode; value: string }): void;
filteredStructuredOutput: JSONSchema;
type?: VariableType;
} & PropsWithChildren;
export function StructuredOutputSecondaryMenu({
data,
click,
filteredStructuredOutput,
type,
}: StructuredOutputSecondaryMenuProps) {
const handleSubMenuClick = useCallback(
(option: { label: ReactNode; value: string }, dataType?: string) => () => {
// The query variable of the iteration operator can only select array type data.
if ((type && type === dataType) || !type) {
click(option);
}
},
[click, type],
);

const renderAgentStructuredOutput = useCallback(
(values: any, option: { label: ReactNode; value: string }) => {
if (isPlainObject(values) && 'properties' in values) {
Expand All @@ -37,7 +50,7 @@ export function StructuredOutputSecondaryMenu({
return (
<li key={key} className="pl-1">
<div
onClick={() => click(nextOption)}
onClick={handleSubMenuClick(nextOption, dataType)}
className="hover:bg-bg-card p-1 text-text-primary rounded-sm flex justify-between"
>
{key}
Expand All @@ -54,13 +67,16 @@ export function StructuredOutputSecondaryMenu({

return <div></div>;
},
[click],
[handleSubMenuClick],
);

return (
<HoverCard key={data.value} openDelay={100} closeDelay={100}>
<HoverCardTrigger asChild>
<li className="hover:bg-bg-card py-1 px-2 text-text-primary rounded-sm text-sm flex justify-between items-center">
<li
onClick={() => click(data)}
className="hover:bg-bg-card py-1 px-2 text-text-primary rounded-sm text-sm flex justify-between items-center"
>
{data.label} <ChevronRight className="size-3.5 text-text-secondary" />
</li>
</HoverCardTrigger>
Expand Down
89 changes: 66 additions & 23 deletions web/src/pages/agent/utils/filter-agent-structured-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@ import { JSONSchema } from '@/components/jsonjoy-builder';
import { Operator } from '@/constants/agent';
import { isPlainObject } from 'lodash';

// Loop operators can only accept variables of type list.

// Recursively traverse the JSON schema, keeping attributes with type "array" and discarding others.

export function filterLoopOperatorInput(
structuredOutput: JSONSchema,
path = [],
) {
if (typeof structuredOutput === 'boolean') {
return structuredOutput;
}
if (
structuredOutput.properties &&
isPlainObject(structuredOutput.properties)
) {
const properties = Object.entries({
...structuredOutput.properties,
}).reduce(
(pre, [key, value]) => {
if (
typeof value !== 'boolean' &&
(value.type === 'array' || hasArrayChild(value))
) {
pre[key] = filterLoopOperatorInput(value, path);
}
return pre;
},
{} as Record<string, JSONSchema>,
);

return { ...structuredOutput, properties };
}

return structuredOutput;
}

export function filterAgentStructuredOutput(
structuredOutput: JSONSchema,
operator?: string,
Expand All @@ -13,32 +49,39 @@ export function filterAgentStructuredOutput(
structuredOutput.properties &&
isPlainObject(structuredOutput.properties)
) {
const filterByPredicate = (predicate: (value: JSONSchema) => boolean) => {
const properties = Object.entries({
...structuredOutput.properties,
}).reduce(
(pre, [key, value]) => {
if (predicate(value)) {
pre[key] = value;
}
return pre;
},
{} as Record<string, JSONSchema>,
);

return { ...structuredOutput, properties };
};

if (operator === Operator.Iteration) {
return filterByPredicate(
(value) => typeof value !== 'boolean' && value.type === 'array',
);
} else {
return filterByPredicate(
(value) => typeof value !== 'boolean' && value.type !== 'array',
);
return filterLoopOperatorInput(structuredOutput);
}

return structuredOutput;
}

return structuredOutput;
}

export function hasArrayChild(data: Record<string, any> | Array<any>) {
if (Array.isArray(data)) {
for (const value of data) {
if (isPlainObject(value) && value.type === 'array') {
return true;
}
if (hasArrayChild(value)) {
return true;
}
}
}

if (isPlainObject(data)) {
for (const value of Object.values(data)) {
if (isPlainObject(value) && value.type === 'array') {
return true;
}

if (hasArrayChild(value)) {
return true;
}
}
}

return false;
}