mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-21 13:32:49 +08:00
### What problem does this PR solve? Feat: Add configuration for webhook to the begin node. #10427 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -20,6 +20,7 @@ import { CirclePlus, HelpCircle, Info } from 'lucide-react';
|
|||||||
import { useId, useState, type FC, type FormEvent } from 'react';
|
import { useId, useState, type FC, type FormEvent } from 'react';
|
||||||
import { useTranslation } from '../../hooks/use-translation';
|
import { useTranslation } from '../../hooks/use-translation';
|
||||||
import type { NewField, SchemaType } from '../../types/json-schema';
|
import type { NewField, SchemaType } from '../../types/json-schema';
|
||||||
|
import { KeyInputProps } from './interface';
|
||||||
import SchemaTypeSelector from './schema-type-selector';
|
import SchemaTypeSelector from './schema-type-selector';
|
||||||
|
|
||||||
interface AddFieldButtonProps {
|
interface AddFieldButtonProps {
|
||||||
@ -27,9 +28,10 @@ interface AddFieldButtonProps {
|
|||||||
variant?: 'primary' | 'secondary';
|
variant?: 'primary' | 'secondary';
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddFieldButton: FC<AddFieldButtonProps> = ({
|
const AddFieldButton: FC<AddFieldButtonProps & KeyInputProps> = ({
|
||||||
onAddField,
|
onAddField,
|
||||||
variant = 'primary',
|
variant = 'primary',
|
||||||
|
pattern,
|
||||||
}) => {
|
}) => {
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [fieldName, setFieldName] = useState('');
|
const [fieldName, setFieldName] = useState('');
|
||||||
@ -120,6 +122,7 @@ const AddFieldButton: FC<AddFieldButtonProps> = ({
|
|||||||
placeholder={t.fieldNamePlaceholder}
|
placeholder={t.fieldNamePlaceholder}
|
||||||
className="font-mono text-sm w-full"
|
className="font-mono text-sm w-full"
|
||||||
required
|
required
|
||||||
|
searchValue={pattern}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { KeyInputProps } from './interface';
|
||||||
|
|
||||||
|
export const KeyInputContext = React.createContext<KeyInputProps>({});
|
||||||
|
|
||||||
|
export function useInputPattern() {
|
||||||
|
const x = useContext(KeyInputContext);
|
||||||
|
return x.pattern;
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export type KeyInputProps = { pattern?: RegExp | string };
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
withObjectSchema,
|
withObjectSchema,
|
||||||
} from '../../types/json-schema';
|
} from '../../types/json-schema';
|
||||||
import type { ValidationTreeNode } from '../../types/validation';
|
import type { ValidationTreeNode } from '../../types/validation';
|
||||||
|
import { useInputPattern } from './context';
|
||||||
import TypeDropdown from './type-dropdown';
|
import TypeDropdown from './type-dropdown';
|
||||||
import TypeEditor from './type-editor';
|
import TypeEditor from './type-editor';
|
||||||
|
|
||||||
@ -54,6 +55,8 @@ export const SchemaPropertyEditor: React.FC<SchemaPropertyEditorProps> = ({
|
|||||||
'object' as SchemaType,
|
'object' as SchemaType,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const pattern = useInputPattern();
|
||||||
|
|
||||||
// Update temp values when props change
|
// Update temp values when props change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTempName(name);
|
setTempName(name);
|
||||||
@ -123,6 +126,7 @@ export const SchemaPropertyEditor: React.FC<SchemaPropertyEditorProps> = ({
|
|||||||
className="h-8 text-sm font-medium min-w-[120px] max-w-full z-10"
|
className="h-8 text-sm font-medium min-w-[120px] max-w-full z-10"
|
||||||
autoFocus
|
autoFocus
|
||||||
onFocus={(e) => e.target.select()}
|
onFocus={(e) => e.target.select()}
|
||||||
|
searchValue={pattern}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import {
|
|||||||
import type { JSONSchema, NewField } from '../../types/json-schema';
|
import type { JSONSchema, NewField } from '../../types/json-schema';
|
||||||
import { asObjectSchema, isBooleanSchema } from '../../types/json-schema';
|
import { asObjectSchema, isBooleanSchema } from '../../types/json-schema';
|
||||||
import AddFieldButton from './add-field-button';
|
import AddFieldButton from './add-field-button';
|
||||||
|
import { KeyInputContext } from './context';
|
||||||
|
import { KeyInputProps } from './interface';
|
||||||
import SchemaFieldList from './schema-field-list';
|
import SchemaFieldList from './schema-field-list';
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
@ -17,9 +19,10 @@ export interface SchemaVisualEditorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
const SchemaVisualEditor: FC<SchemaVisualEditorProps> = ({
|
const SchemaVisualEditor: FC<SchemaVisualEditorProps & KeyInputProps> = ({
|
||||||
schema,
|
schema,
|
||||||
onChange,
|
onChange,
|
||||||
|
pattern,
|
||||||
}) => {
|
}) => {
|
||||||
const t = useTranslation();
|
const t = useTranslation();
|
||||||
// Handle adding a top-level field
|
// Handle adding a top-level field
|
||||||
@ -121,7 +124,7 @@ const SchemaVisualEditor: FC<SchemaVisualEditorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="p-4 h-full flex flex-col overflow-auto jsonjoy">
|
<div className="p-4 h-full flex flex-col overflow-auto jsonjoy">
|
||||||
<div className="mb-6 shrink-0">
|
<div className="mb-6 shrink-0">
|
||||||
<AddFieldButton onAddField={handleAddField} />
|
<AddFieldButton onAddField={handleAddField} pattern={pattern} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grow overflow-auto">
|
<div className="grow overflow-auto">
|
||||||
@ -131,12 +134,14 @@ const SchemaVisualEditor: FC<SchemaVisualEditorProps> = ({
|
|||||||
<p className="text-sm">{t.visualEditorNoFieldsHint2}</p>
|
<p className="text-sm">{t.visualEditorNoFieldsHint2}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<SchemaFieldList
|
<KeyInputContext.Provider value={{ pattern }}>
|
||||||
schema={schema}
|
<SchemaFieldList
|
||||||
onAddField={handleAddField}
|
schema={schema}
|
||||||
onEditField={handleEditField}
|
onAddField={handleAddField}
|
||||||
onDeleteField={handleDeleteField}
|
onEditField={handleEditField}
|
||||||
/>
|
onDeleteField={handleDeleteField}
|
||||||
|
/>
|
||||||
|
</KeyInputContext.Provider>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -193,3 +193,19 @@ export enum SwitchLogicOperator {
|
|||||||
And = 'and',
|
And = 'and',
|
||||||
Or = 'or',
|
Or = 'or',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const WebhookAlgorithmList = [
|
||||||
|
'hs256',
|
||||||
|
'hs384',
|
||||||
|
'hs512',
|
||||||
|
'rs256',
|
||||||
|
'rs384',
|
||||||
|
'rs512',
|
||||||
|
'es256',
|
||||||
|
'es384',
|
||||||
|
'es512',
|
||||||
|
'ps256',
|
||||||
|
'ps384',
|
||||||
|
'ps512',
|
||||||
|
'none',
|
||||||
|
] as const;
|
||||||
|
|||||||
@ -1961,6 +1961,37 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
removeFirst: 'Remove first',
|
removeFirst: 'Remove first',
|
||||||
removeLast: 'Remove last',
|
removeLast: 'Remove last',
|
||||||
},
|
},
|
||||||
|
webhook: {
|
||||||
|
name: 'Webhook',
|
||||||
|
methods: 'Methods',
|
||||||
|
contentTypes: 'Content types',
|
||||||
|
security: 'Security',
|
||||||
|
schema: 'Schema',
|
||||||
|
response: 'Response',
|
||||||
|
executionMode: 'Execution mode',
|
||||||
|
authMethods: 'Authentication Methods',
|
||||||
|
authType: 'Authentication Type',
|
||||||
|
limit: 'Request Limit',
|
||||||
|
per: 'Time Period',
|
||||||
|
maxBodySize: 'Maximum Body Size',
|
||||||
|
ipWhitelist: 'IP Whitelist',
|
||||||
|
tokenHeader: 'Token Header',
|
||||||
|
tokenValue: 'Token Value',
|
||||||
|
username: 'Username',
|
||||||
|
password: 'Password',
|
||||||
|
algorithm: 'Algorithm',
|
||||||
|
secret: 'Secret',
|
||||||
|
issuer: 'Issuer',
|
||||||
|
audience: 'Audience',
|
||||||
|
requiredClaims: 'Required Claims',
|
||||||
|
header: 'Header',
|
||||||
|
status: 'Status',
|
||||||
|
headersTemplate: 'Headers Template',
|
||||||
|
bodyTemplate: 'Body Template',
|
||||||
|
basic: 'Basic',
|
||||||
|
bearer: 'Bearer',
|
||||||
|
apiKey: 'Api Key',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
llmTools: {
|
llmTools: {
|
||||||
bad_calculator: {
|
bad_calculator: {
|
||||||
|
|||||||
@ -1755,6 +1755,37 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
|
|||||||
removeFirst: '移除第一个',
|
removeFirst: '移除第一个',
|
||||||
removeLast: '移除最后一个',
|
removeLast: '移除最后一个',
|
||||||
},
|
},
|
||||||
|
webhook: {
|
||||||
|
name: '网络钩子',
|
||||||
|
methods: '方法',
|
||||||
|
contentTypes: '内容类型',
|
||||||
|
security: '安全配置',
|
||||||
|
schema: '模式',
|
||||||
|
response: '响应',
|
||||||
|
executionMode: '执行模式',
|
||||||
|
authMethods: '认证方法',
|
||||||
|
authType: '认证类型',
|
||||||
|
limit: '请求限制',
|
||||||
|
per: '时间周期',
|
||||||
|
maxBodySize: '最大主体大小',
|
||||||
|
ipWhitelist: 'IP白名单',
|
||||||
|
tokenHeader: '令牌头部',
|
||||||
|
tokenValue: '令牌值',
|
||||||
|
username: '用户名',
|
||||||
|
password: '密码',
|
||||||
|
algorithm: '算法',
|
||||||
|
secret: '密钥',
|
||||||
|
issuer: '签发者',
|
||||||
|
audience: '受众',
|
||||||
|
requiredClaims: '必需声明',
|
||||||
|
header: '头部',
|
||||||
|
status: '状态',
|
||||||
|
headersTemplate: '头部模板',
|
||||||
|
bodyTemplate: '主体模板',
|
||||||
|
basic: '基础认证',
|
||||||
|
bearer: '承载令牌',
|
||||||
|
apiKey: 'API密钥',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
|||||||
@ -1,32 +1,14 @@
|
|||||||
import i18n from '@/locales/config';
|
|
||||||
import { BeginId } from '@/pages/agent/constant';
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
const prefix = BeginId + '@';
|
|
||||||
|
|
||||||
interface VariableDisplayProps {
|
interface VariableDisplayProps {
|
||||||
content: string;
|
content: string;
|
||||||
getLabel?: (value?: string) => string | ReactNode;
|
getLabel?: (value?: string) => string | ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This component mimics the VariableNode's decorate function from PromptEditor
|
// This component mimics the VariableNode's decorate function from PromptEditor
|
||||||
function VariableNodeDisplay({
|
function VariableNodeDisplay({ label }: { label: ReactNode }) {
|
||||||
value,
|
|
||||||
label,
|
|
||||||
}: {
|
|
||||||
value: string;
|
|
||||||
label: ReactNode;
|
|
||||||
}) {
|
|
||||||
let content: ReactNode = <span className="text-accent-primary">{label}</span>;
|
let content: ReactNode = <span className="text-accent-primary">{label}</span>;
|
||||||
|
|
||||||
if (value.startsWith(prefix)) {
|
|
||||||
content = (
|
|
||||||
<div>
|
|
||||||
<span>{i18n.t(`flow.begin`)}</span> / {content}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="inline-flex items-center mr-1">{content}</div>;
|
return <div className="inline-flex items-center mr-1">{content}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,11 +45,7 @@ export function VariableDisplay({ content, getLabel }: VariableDisplayProps) {
|
|||||||
if (label && label !== variableValue) {
|
if (label && label !== variableValue) {
|
||||||
// If we found a valid label, render as variable node
|
// If we found a valid label, render as variable node
|
||||||
elements.push(
|
elements.push(
|
||||||
<VariableNodeDisplay
|
<VariableNodeDisplay key={`variable-${index}`} label={label} />,
|
||||||
key={`variable-${index}`}
|
|
||||||
value={variableValue}
|
|
||||||
label={label}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// If no label found, keep as original text
|
// If no label found, keep as original text
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export * from './pipeline';
|
|||||||
export enum AgentDialogueMode {
|
export enum AgentDialogueMode {
|
||||||
Conversational = 'conversational',
|
Conversational = 'conversational',
|
||||||
Task = 'task',
|
Task = 'task',
|
||||||
|
Webhook = 'Webhook',
|
||||||
}
|
}
|
||||||
|
|
||||||
import { ModelVariableType } from '@/constants/knowledge';
|
import { ModelVariableType } from '@/constants/knowledge';
|
||||||
@ -930,3 +931,37 @@ export enum AgentVariableType {
|
|||||||
Begin = 'begin',
|
Begin = 'begin',
|
||||||
Conversation = 'conversation',
|
Conversation = 'conversation',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum WebhookMethod {
|
||||||
|
Post = 'POST',
|
||||||
|
Get = 'GET',
|
||||||
|
Put = 'PUT',
|
||||||
|
Patch = 'PATCH',
|
||||||
|
Delete = 'DELETE',
|
||||||
|
Head = 'HEAD',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WebhookContentType {
|
||||||
|
ApplicationJson = 'application/json',
|
||||||
|
MultipartFormData = 'multipart/form-data',
|
||||||
|
ApplicationXWwwFormUrlencoded = 'application/x-www-form-urlencoded',
|
||||||
|
TextPlain = 'text/plain',
|
||||||
|
ApplicationOctetStream = 'application/octet-stream',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WebhookExecutionMode {
|
||||||
|
Immediately = 'Immediately',
|
||||||
|
Streaming = 'Streaming',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WebhookSecurityAuthType {
|
||||||
|
None = 'none',
|
||||||
|
Token = 'token',
|
||||||
|
Basic = 'basic',
|
||||||
|
Jwt = 'jwt',
|
||||||
|
Hmac = 'hmac',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RateLimitPerList = ['minute', 'hour', 'day'];
|
||||||
|
|
||||||
|
export const WebhookMaxBodySize = ['10MB', '50MB', '100MB', '1000MB'];
|
||||||
|
|||||||
@ -42,9 +42,9 @@ import { FormWrapper } from '../components/form-wrapper';
|
|||||||
import { Output } from '../components/output';
|
import { Output } from '../components/output';
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
import { PromptEditor } from '../components/prompt-editor';
|
||||||
import { QueryVariable } from '../components/query-variable';
|
import { QueryVariable } from '../components/query-variable';
|
||||||
|
import { SchemaDialog } from '../components/schema-dialog';
|
||||||
|
import { SchemaPanel } from '../components/schema-panel';
|
||||||
import { AgentTools, Agents } from './agent-tools';
|
import { AgentTools, Agents } from './agent-tools';
|
||||||
import { StructuredOutputDialog } from './structured-output-dialog';
|
|
||||||
import { StructuredOutputPanel } from './structured-output-panel';
|
|
||||||
import { useBuildPromptExtraPromptOptions } from './use-build-prompt-options';
|
import { useBuildPromptExtraPromptOptions } from './use-build-prompt-options';
|
||||||
import {
|
import {
|
||||||
useHandleShowStructuredOutput,
|
useHandleShowStructuredOutput,
|
||||||
@ -327,19 +327,17 @@ function AgentForm({ node }: INextOperatorForm) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StructuredOutputPanel
|
<SchemaPanel value={structuredOutput}></SchemaPanel>
|
||||||
value={structuredOutput}
|
|
||||||
></StructuredOutputPanel>
|
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
</Form>
|
</Form>
|
||||||
{structuredOutputDialogVisible && (
|
{structuredOutputDialogVisible && (
|
||||||
<StructuredOutputDialog
|
<SchemaDialog
|
||||||
hideModal={hideStructuredOutputDialog}
|
hideModal={hideStructuredOutputDialog}
|
||||||
onOk={handleStructuredOutputDialogOk}
|
onOk={handleStructuredOutputDialogOk}
|
||||||
initialValues={structuredOutput}
|
initialValues={structuredOutput}
|
||||||
></StructuredOutputDialog>
|
></SchemaDialog>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { RAGFlowSelect } from '@/components/ui/select';
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { FormTooltip } from '@/components/ui/tooltip';
|
import { FormTooltip } from '@/components/ui/tooltip';
|
||||||
|
import { WebhookAlgorithmList } from '@/constants/agent';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
@ -24,37 +25,71 @@ import { INextOperatorForm } from '../../interface';
|
|||||||
import { ParameterDialog } from './parameter-dialog';
|
import { ParameterDialog } from './parameter-dialog';
|
||||||
import { QueryTable } from './query-table';
|
import { QueryTable } from './query-table';
|
||||||
import { useEditQueryRecord } from './use-edit-query';
|
import { useEditQueryRecord } from './use-edit-query';
|
||||||
|
import { useHandleModeChange } from './use-handle-mode-change';
|
||||||
import { useValues } from './use-values';
|
import { useValues } from './use-values';
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
import { useWatchFormChange } from './use-watch-change';
|
||||||
|
import { WebHook } from './webhook';
|
||||||
|
|
||||||
const ModeOptions = [
|
const ModeOptions = [
|
||||||
{ value: AgentDialogueMode.Conversational, label: t('flow.conversational') },
|
{ value: AgentDialogueMode.Conversational, label: t('flow.conversational') },
|
||||||
{ value: AgentDialogueMode.Task, label: t('flow.task') },
|
{ value: AgentDialogueMode.Task, label: t('flow.task') },
|
||||||
|
{ value: AgentDialogueMode.Webhook, label: t('flow.webhook.name') },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
enablePrologue: z.boolean().optional(),
|
||||||
|
prologue: z.string().trim().optional(),
|
||||||
|
mode: z.string(),
|
||||||
|
inputs: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
key: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
optional: z.boolean(),
|
||||||
|
name: z.string(),
|
||||||
|
options: z.array(z.union([z.number(), z.string(), z.boolean()])),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
methods: z.string().optional(),
|
||||||
|
content_types: z.string().optional(),
|
||||||
|
security: z
|
||||||
|
.object({
|
||||||
|
auth_type: z.string(),
|
||||||
|
ip_whitelist: z.array(z.object({ value: z.string() })),
|
||||||
|
rate_limit: z.object({
|
||||||
|
limit: z.number(),
|
||||||
|
per: z.string().optional(),
|
||||||
|
}),
|
||||||
|
max_body_size: z.string(),
|
||||||
|
jwt: z
|
||||||
|
.object({
|
||||||
|
algorithm: z.string().default(WebhookAlgorithmList[0]).optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
schema: z.record(z.any()).optional(),
|
||||||
|
response: z
|
||||||
|
.object({
|
||||||
|
status: z.number(),
|
||||||
|
headers_template: z.array(
|
||||||
|
z.object({ key: z.string(), value: z.string() }),
|
||||||
|
),
|
||||||
|
body_template: z.array(z.object({ key: z.string(), value: z.string() })),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
execution_mode: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type BeginFormSchemaType = z.infer<typeof FormSchema>;
|
||||||
|
|
||||||
function BeginForm({ node }: INextOperatorForm) {
|
function BeginForm({ node }: INextOperatorForm) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const values = useValues(node);
|
const values = useValues(node);
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
enablePrologue: z.boolean().optional(),
|
|
||||||
prologue: z.string().trim().optional(),
|
|
||||||
mode: z.string(),
|
|
||||||
inputs: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
key: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
value: z.string(),
|
|
||||||
optional: z.boolean(),
|
|
||||||
name: z.string(),
|
|
||||||
options: z.array(z.union([z.number(), z.string(), z.boolean()])),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
defaultValues: values,
|
defaultValues: values,
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
@ -72,6 +107,8 @@ function BeginForm({ node }: INextOperatorForm) {
|
|||||||
|
|
||||||
const previousModeRef = useRef(mode);
|
const previousModeRef = useRef(mode);
|
||||||
|
|
||||||
|
const { handleModeChange } = useHandleModeChange(form);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
previousModeRef.current === AgentDialogueMode.Task &&
|
previousModeRef.current === AgentDialogueMode.Task &&
|
||||||
@ -111,6 +148,10 @@ function BeginForm({ node }: INextOperatorForm) {
|
|||||||
placeholder={t('common.pleaseSelect')}
|
placeholder={t('common.pleaseSelect')}
|
||||||
options={ModeOptions}
|
options={ModeOptions}
|
||||||
{...field}
|
{...field}
|
||||||
|
onChange={(val) => {
|
||||||
|
handleModeChange(val);
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
></RAGFlowSelect>
|
></RAGFlowSelect>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -158,44 +199,49 @@ function BeginForm({ node }: INextOperatorForm) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* Create a hidden field to make Form instance record this */}
|
{mode === AgentDialogueMode.Webhook && <WebHook></WebHook>}
|
||||||
<FormField
|
{mode !== AgentDialogueMode.Webhook && (
|
||||||
control={form.control}
|
<>
|
||||||
name={'inputs'}
|
{/* Create a hidden field to make Form instance record this */}
|
||||||
render={() => <div></div>}
|
<FormField
|
||||||
/>
|
control={form.control}
|
||||||
<Collapse
|
name={'inputs'}
|
||||||
title={
|
render={() => <div></div>}
|
||||||
<div>
|
/>
|
||||||
{t('flow.input')}
|
<Collapse
|
||||||
<FormTooltip tooltip={t('flow.beginInputTip')}></FormTooltip>
|
title={
|
||||||
</div>
|
<div>
|
||||||
}
|
{t('flow.input')}
|
||||||
rightContent={
|
<FormTooltip tooltip={t('flow.beginInputTip')}></FormTooltip>
|
||||||
<Button
|
</div>
|
||||||
variant={'ghost'}
|
}
|
||||||
onClick={(e) => {
|
rightContent={
|
||||||
e.preventDefault();
|
<Button
|
||||||
showModal();
|
variant={'ghost'}
|
||||||
}}
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
showModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Plus />
|
<QueryTable
|
||||||
</Button>
|
data={inputs}
|
||||||
}
|
showModal={showModal}
|
||||||
>
|
deleteRecord={handleDeleteRecord}
|
||||||
<QueryTable
|
></QueryTable>
|
||||||
data={inputs}
|
</Collapse>
|
||||||
showModal={showModal}
|
{visible && (
|
||||||
deleteRecord={handleDeleteRecord}
|
<ParameterDialog
|
||||||
></QueryTable>
|
hideModal={hideModal}
|
||||||
</Collapse>
|
initialValue={currentRecord}
|
||||||
{visible && (
|
otherThanCurrentQuery={otherThanCurrentQuery}
|
||||||
<ParameterDialog
|
submit={ok}
|
||||||
hideModal={hideModal}
|
></ParameterDialog>
|
||||||
initialValue={currentRecord}
|
)}
|
||||||
otherThanCurrentQuery={otherThanCurrentQuery}
|
</>
|
||||||
submit={ok}
|
|
||||||
></ParameterDialog>
|
|
||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -0,0 +1,76 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { UseFormReturn } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
AgentDialogueMode,
|
||||||
|
RateLimitPerList,
|
||||||
|
WebhookExecutionMode,
|
||||||
|
WebhookMaxBodySize,
|
||||||
|
WebhookSecurityAuthType,
|
||||||
|
} from '../../constant';
|
||||||
|
|
||||||
|
// const WebhookSchema = {
|
||||||
|
// query: {
|
||||||
|
// type: 'object',
|
||||||
|
// required: [],
|
||||||
|
// properties: {
|
||||||
|
// // debug: { type: 'boolean' },
|
||||||
|
// // event: { type: 'string' },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// headers: {
|
||||||
|
// type: 'object',
|
||||||
|
// required: [],
|
||||||
|
// properties: {
|
||||||
|
// // 'X-Trace-ID': { type: 'string' },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// body: {
|
||||||
|
// type: 'object',
|
||||||
|
// required: [],
|
||||||
|
// properties: {
|
||||||
|
// id: { type: 'string' },
|
||||||
|
// payload: { type: 'object' },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
properties: {
|
||||||
|
query: {
|
||||||
|
type: 'object',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
type: 'object',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialFormValuesMap = {
|
||||||
|
schema: schema,
|
||||||
|
'security.auth_type': WebhookSecurityAuthType.Basic,
|
||||||
|
'security.rate_limit.per': RateLimitPerList[0],
|
||||||
|
'security.max_body_size': WebhookMaxBodySize[0],
|
||||||
|
execution_mode: WebhookExecutionMode.Immediately,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useHandleModeChange(form: UseFormReturn<any>) {
|
||||||
|
const handleModeChange = useCallback(
|
||||||
|
(mode: AgentDialogueMode) => {
|
||||||
|
if (mode === AgentDialogueMode.Webhook) {
|
||||||
|
Object.entries(initialFormValuesMap).forEach(([key, value]) => {
|
||||||
|
form.setValue(key, value, { shouldDirty: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
return { handleModeChange };
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { JSONSchema } from '@/components/jsonjoy-builder';
|
||||||
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { UseFormReturn } from 'react-hook-form';
|
||||||
|
|
||||||
|
export function useShowSchemaDialog(form: UseFormReturn<any>) {
|
||||||
|
const {
|
||||||
|
visible: schemaDialogVisible,
|
||||||
|
showModal: showSchemaDialog,
|
||||||
|
hideModal: hideSchemaDialog,
|
||||||
|
} = useSetModalState();
|
||||||
|
|
||||||
|
const handleSchemaDialogOk = useCallback(
|
||||||
|
(values: JSONSchema) => {
|
||||||
|
// Sync data to canvas
|
||||||
|
form.setValue('schema', values);
|
||||||
|
hideSchemaDialog();
|
||||||
|
},
|
||||||
|
[form, hideSchemaDialog],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
schemaDialogVisible,
|
||||||
|
showSchemaDialog,
|
||||||
|
hideSchemaDialog,
|
||||||
|
handleSchemaDialogOk,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||||
|
import { AgentDialogueMode } from '../../constant';
|
||||||
import { BeginQuery } from '../../interface';
|
import { BeginQuery } from '../../interface';
|
||||||
import useGraphStore from '../../store';
|
import useGraphStore from '../../store';
|
||||||
|
|
||||||
@ -20,9 +21,21 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn) {
|
|||||||
if (id) {
|
if (id) {
|
||||||
values = form?.getValues() || {};
|
values = form?.getValues() || {};
|
||||||
|
|
||||||
|
let outputs: Record<string, any> = {};
|
||||||
|
|
||||||
|
// For webhook mode, use schema properties as direct outputs
|
||||||
|
// Each property (body, headers, query) should be able to show secondary menu
|
||||||
|
if (
|
||||||
|
values.mode === AgentDialogueMode.Webhook &&
|
||||||
|
values.schema?.properties
|
||||||
|
) {
|
||||||
|
outputs = { ...values.schema.properties };
|
||||||
|
}
|
||||||
|
|
||||||
const nextValues = {
|
const nextValues = {
|
||||||
...values,
|
...values,
|
||||||
inputs: transferInputsArrayToObject(values.inputs),
|
inputs: transferInputsArrayToObject(values.inputs),
|
||||||
|
outputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
updateNodeForm(id, nextValues);
|
||||||
|
|||||||
139
web/src/pages/agent/form/begin-form/webhook/auth.tsx
Normal file
139
web/src/pages/agent/form/begin-form/webhook/auth.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { WebhookAlgorithmList } from '@/constants/agent';
|
||||||
|
import { WebhookSecurityAuthType } from '@/pages/agent/constant';
|
||||||
|
import { buildOptions } from '@/utils/form';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const AlgorithmOptions = buildOptions(WebhookAlgorithmList);
|
||||||
|
|
||||||
|
const RequiredClaimsOptions = buildOptions(['exp', 'sub']);
|
||||||
|
|
||||||
|
export function Auth() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const form = useFormContext();
|
||||||
|
|
||||||
|
const authType = useWatch({
|
||||||
|
name: 'security.auth_type',
|
||||||
|
control: form.control,
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderTokenAuth = useCallback(
|
||||||
|
() => (
|
||||||
|
<>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.token.token_header"
|
||||||
|
label={t('flow.webhook.tokenHeader')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.token.token_value"
|
||||||
|
label={t('flow.webhook.tokenValue')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderBasicAuth = useCallback(
|
||||||
|
() => (
|
||||||
|
<>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.basic_auth.username"
|
||||||
|
label={t('flow.webhook.username')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.basic_auth.password"
|
||||||
|
label={t('flow.webhook.password')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderJwtAuth = useCallback(
|
||||||
|
() => (
|
||||||
|
<>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.jwt.algorithm"
|
||||||
|
label={t('flow.webhook.algorithm')}
|
||||||
|
>
|
||||||
|
<SelectWithSearch options={AlgorithmOptions}></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.jwt.secret"
|
||||||
|
label={t('flow.webhook.secret')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.jwt.issuer"
|
||||||
|
label={t('flow.webhook.issuer')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.jwt.audience"
|
||||||
|
label={t('flow.webhook.audience')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.jwt.required_claims"
|
||||||
|
label={t('flow.webhook.requiredClaims')}
|
||||||
|
>
|
||||||
|
<SelectWithSearch options={RequiredClaimsOptions}></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderHmacAuth = useCallback(
|
||||||
|
() => (
|
||||||
|
<>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.hmac.header"
|
||||||
|
label={t('flow.webhook.header')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.hmac.secret"
|
||||||
|
label={t('flow.webhook.secret')}
|
||||||
|
>
|
||||||
|
<Input></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.hmac.algorithm"
|
||||||
|
label={t('flow.webhook.algorithm')}
|
||||||
|
>
|
||||||
|
<SelectWithSearch options={AlgorithmOptions}></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const AuthMap = {
|
||||||
|
[WebhookSecurityAuthType.Token]: renderTokenAuth,
|
||||||
|
[WebhookSecurityAuthType.Basic]: renderBasicAuth,
|
||||||
|
[WebhookSecurityAuthType.Jwt]: renderJwtAuth,
|
||||||
|
[WebhookSecurityAuthType.Hmac]: renderHmacAuth,
|
||||||
|
[WebhookSecurityAuthType.None]: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return AuthMap[
|
||||||
|
(authType ?? WebhookSecurityAuthType.None) as WebhookSecurityAuthType
|
||||||
|
]();
|
||||||
|
}
|
||||||
213
web/src/pages/agent/form/begin-form/webhook/dynamic-response.tsx
Normal file
213
web/src/pages/agent/form/begin-form/webhook/dynamic-response.tsx
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { BoolSegmented } from '@/components/bool-segmented';
|
||||||
|
import { KeyInput } from '@/components/key-input';
|
||||||
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { Editor, loader } from '@monaco-editor/react';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
import { ReactNode, useCallback } from 'react';
|
||||||
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
|
import { InputMode, TypesWithArray } from '../../../constant';
|
||||||
|
import {
|
||||||
|
InputModeOptions,
|
||||||
|
buildConversationVariableSelectOptions,
|
||||||
|
} from '../../../utils';
|
||||||
|
import { DynamicFormHeader } from '../../components/dynamic-fom-header';
|
||||||
|
import { QueryVariable } from '../../components/query-variable';
|
||||||
|
|
||||||
|
loader.config({ paths: { vs: '/vs' } });
|
||||||
|
|
||||||
|
type SelectKeysProps = {
|
||||||
|
name: string;
|
||||||
|
label: ReactNode;
|
||||||
|
tooltip?: string;
|
||||||
|
keyField?: string;
|
||||||
|
valueField?: string;
|
||||||
|
operatorField?: string;
|
||||||
|
nodeId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VariableTypeOptions = buildConversationVariableSelectOptions();
|
||||||
|
|
||||||
|
const modeField = 'input_mode';
|
||||||
|
|
||||||
|
const ConstantValueMap = {
|
||||||
|
[TypesWithArray.Boolean]: true,
|
||||||
|
[TypesWithArray.Number]: 0,
|
||||||
|
[TypesWithArray.String]: '',
|
||||||
|
[TypesWithArray.ArrayBoolean]: '[]',
|
||||||
|
[TypesWithArray.ArrayNumber]: '[]',
|
||||||
|
[TypesWithArray.ArrayString]: '[]',
|
||||||
|
[TypesWithArray.ArrayObject]: '[]',
|
||||||
|
[TypesWithArray.Object]: '{}',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DynamicResponse({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
tooltip,
|
||||||
|
keyField = 'key',
|
||||||
|
valueField = 'value',
|
||||||
|
operatorField = 'type',
|
||||||
|
}: SelectKeysProps) {
|
||||||
|
const form = useFormContext();
|
||||||
|
const isDarkTheme = useIsDarkTheme();
|
||||||
|
|
||||||
|
const { fields, remove, append } = useFieldArray({
|
||||||
|
name: name,
|
||||||
|
control: form.control,
|
||||||
|
});
|
||||||
|
|
||||||
|
const initializeValue = useCallback(
|
||||||
|
(mode: string, variableType: string, valueFieldAlias: string) => {
|
||||||
|
if (mode === InputMode.Variable) {
|
||||||
|
form.setValue(valueFieldAlias, '', { shouldDirty: true });
|
||||||
|
} else {
|
||||||
|
const val = ConstantValueMap[variableType as TypesWithArray];
|
||||||
|
form.setValue(valueFieldAlias, val, { shouldDirty: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleModeChange = useCallback(
|
||||||
|
(mode: string, valueFieldAlias: string, operatorFieldAlias: string) => {
|
||||||
|
const variableType = form.getValues(operatorFieldAlias);
|
||||||
|
initializeValue(mode, variableType, valueFieldAlias);
|
||||||
|
},
|
||||||
|
[form, initializeValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleVariableTypeChange = useCallback(
|
||||||
|
(variableType: string, valueFieldAlias: string, modeFieldAlias: string) => {
|
||||||
|
const mode = form.getValues(modeFieldAlias);
|
||||||
|
|
||||||
|
initializeValue(mode, variableType, valueFieldAlias);
|
||||||
|
},
|
||||||
|
[form, initializeValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderParameter = useCallback(
|
||||||
|
(operatorFieldName: string, modeFieldName: string) => {
|
||||||
|
const mode = form.getValues(modeFieldName);
|
||||||
|
const logicalOperator = form.getValues(operatorFieldName);
|
||||||
|
|
||||||
|
if (mode === InputMode.Constant) {
|
||||||
|
if (logicalOperator === TypesWithArray.Boolean) {
|
||||||
|
return <BoolSegmented></BoolSegmented>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logicalOperator === TypesWithArray.Number) {
|
||||||
|
return <Input className="w-full" type="number"></Input>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logicalOperator === TypesWithArray.String) {
|
||||||
|
return <Textarea></Textarea>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
height={300}
|
||||||
|
theme={isDarkTheme ? 'vs-dark' : 'vs'}
|
||||||
|
language={'json'}
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
automaticLayout: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryVariable
|
||||||
|
types={[logicalOperator]}
|
||||||
|
hideLabel
|
||||||
|
pureQuery
|
||||||
|
></QueryVariable>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[form, isDarkTheme],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="space-y-2">
|
||||||
|
<DynamicFormHeader
|
||||||
|
label={label}
|
||||||
|
tooltip={tooltip}
|
||||||
|
onClick={() =>
|
||||||
|
append({
|
||||||
|
[keyField]: '',
|
||||||
|
[valueField]: '',
|
||||||
|
[modeField]: InputMode.Constant,
|
||||||
|
[operatorField]: TypesWithArray.String,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
></DynamicFormHeader>
|
||||||
|
<div className="space-y-5">
|
||||||
|
{fields.map((field, index) => {
|
||||||
|
const keyFieldAlias = `${name}.${index}.${keyField}`;
|
||||||
|
const valueFieldAlias = `${name}.${index}.${valueField}`;
|
||||||
|
const operatorFieldAlias = `${name}.${index}.${operatorField}`;
|
||||||
|
const modeFieldAlias = `${name}.${index}.${modeField}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section key={field.id} className="flex gap-2">
|
||||||
|
<div className="flex-1 space-y-3 min-w-0">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<RAGFlowFormItem name={keyFieldAlias} className="flex-1 ">
|
||||||
|
<KeyInput></KeyInput>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<Separator className="w-2" />
|
||||||
|
<RAGFlowFormItem name={operatorFieldAlias} className="flex-1">
|
||||||
|
{(field) => (
|
||||||
|
<SelectWithSearch
|
||||||
|
value={field.value}
|
||||||
|
onChange={(val) => {
|
||||||
|
handleVariableTypeChange(
|
||||||
|
val,
|
||||||
|
valueFieldAlias,
|
||||||
|
modeFieldAlias,
|
||||||
|
);
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
|
options={VariableTypeOptions}
|
||||||
|
></SelectWithSearch>
|
||||||
|
)}
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<Separator className="w-2" />
|
||||||
|
<RAGFlowFormItem name={modeFieldAlias} className="flex-1">
|
||||||
|
{(field) => (
|
||||||
|
<SelectWithSearch
|
||||||
|
value={field.value}
|
||||||
|
onChange={(val) => {
|
||||||
|
handleModeChange(
|
||||||
|
val,
|
||||||
|
valueFieldAlias,
|
||||||
|
operatorFieldAlias,
|
||||||
|
);
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
|
options={InputModeOptions}
|
||||||
|
></SelectWithSearch>
|
||||||
|
)}
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</div>
|
||||||
|
<RAGFlowFormItem name={valueFieldAlias} className="w-full">
|
||||||
|
{renderParameter(operatorFieldAlias, modeFieldAlias)}
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant={'ghost'} onClick={() => remove(index)}>
|
||||||
|
<X />
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
134
web/src/pages/agent/form/begin-form/webhook/index.tsx
Normal file
134
web/src/pages/agent/form/begin-form/webhook/index.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { Collapse } from '@/components/collapse';
|
||||||
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { buildOptions } from '@/utils/form';
|
||||||
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
RateLimitPerList,
|
||||||
|
WebhookContentType,
|
||||||
|
WebhookExecutionMode,
|
||||||
|
WebhookMaxBodySize,
|
||||||
|
WebhookMethod,
|
||||||
|
WebhookSecurityAuthType,
|
||||||
|
} from '../../../constant';
|
||||||
|
import { DynamicStringForm } from '../../components/dynamic-string-form';
|
||||||
|
import { SchemaDialog } from '../../components/schema-dialog';
|
||||||
|
import { SchemaPanel } from '../../components/schema-panel';
|
||||||
|
import { useShowSchemaDialog } from '../use-show-schema-dialog';
|
||||||
|
import { Auth } from './auth';
|
||||||
|
import { WebhookResponse } from './response';
|
||||||
|
|
||||||
|
const RateLimitPerOptions = buildOptions(RateLimitPerList);
|
||||||
|
|
||||||
|
export function WebHook() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const form = useFormContext();
|
||||||
|
|
||||||
|
const executionMode = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: 'execution_mode',
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
showSchemaDialog,
|
||||||
|
schemaDialogVisible,
|
||||||
|
hideSchemaDialog,
|
||||||
|
handleSchemaDialogOk,
|
||||||
|
} = useShowSchemaDialog(form);
|
||||||
|
|
||||||
|
const schema = form.getValues('schema');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RAGFlowFormItem name="methods" label={t('flow.webhook.methods')}>
|
||||||
|
<SelectWithSearch
|
||||||
|
options={buildOptions(WebhookMethod)}
|
||||||
|
></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="content_types"
|
||||||
|
label={t('flow.webhook.contentTypes')}
|
||||||
|
>
|
||||||
|
<SelectWithSearch
|
||||||
|
options={buildOptions(WebhookContentType)}
|
||||||
|
></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<Collapse title={<div>Security</div>}>
|
||||||
|
<section className="space-y-4">
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.auth_type"
|
||||||
|
label={t('flow.webhook.authType')}
|
||||||
|
>
|
||||||
|
<SelectWithSearch
|
||||||
|
options={buildOptions(WebhookSecurityAuthType)}
|
||||||
|
></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<Auth></Auth>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.rate_limit.limit"
|
||||||
|
label={t('flow.webhook.limit')}
|
||||||
|
>
|
||||||
|
<Input type="number"></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.rate_limit.per"
|
||||||
|
label={t('flow.webhook.per')}
|
||||||
|
>
|
||||||
|
<SelectWithSearch options={RateLimitPerOptions}></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="security.max_body_size"
|
||||||
|
label={t('flow.webhook.maxBodySize')}
|
||||||
|
>
|
||||||
|
<SelectWithSearch
|
||||||
|
options={buildOptions(WebhookMaxBodySize)}
|
||||||
|
></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<DynamicStringForm
|
||||||
|
name="security.ip_whitelist"
|
||||||
|
label={t('flow.webhook.ipWhitelist')}
|
||||||
|
></DynamicStringForm>
|
||||||
|
</section>
|
||||||
|
</Collapse>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="schema"
|
||||||
|
label={t('flow.webhook.schema')}
|
||||||
|
className="hidden"
|
||||||
|
>
|
||||||
|
<Textarea></Textarea>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="execution_mode"
|
||||||
|
label={t('flow.webhook.executionMode')}
|
||||||
|
>
|
||||||
|
<SelectWithSearch
|
||||||
|
options={buildOptions(WebhookExecutionMode)}
|
||||||
|
></SelectWithSearch>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
{executionMode === WebhookExecutionMode.Immediately && (
|
||||||
|
<WebhookResponse></WebhookResponse>
|
||||||
|
)}
|
||||||
|
<Separator></Separator>
|
||||||
|
<section className="flex justify-between items-center">
|
||||||
|
Schema
|
||||||
|
<Button variant={'ghost'} onClick={showSchemaDialog}>
|
||||||
|
{t('flow.structuredOutput.configuration')}
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
<SchemaPanel value={schema}></SchemaPanel>
|
||||||
|
{schemaDialogVisible && (
|
||||||
|
<SchemaDialog
|
||||||
|
initialValues={schema}
|
||||||
|
hideModal={hideSchemaDialog}
|
||||||
|
onOk={handleSchemaDialogOk}
|
||||||
|
pattern={''}
|
||||||
|
></SchemaDialog>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
web/src/pages/agent/form/begin-form/webhook/response.tsx
Normal file
30
web/src/pages/agent/form/begin-form/webhook/response.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Collapse } from '@/components/collapse';
|
||||||
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DynamicResponse } from './dynamic-response';
|
||||||
|
|
||||||
|
export function WebhookResponse() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapse title={<div>Response</div>}>
|
||||||
|
<section className="space-y-4">
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name={'response.status'}
|
||||||
|
label={t('flow.webhook.status')}
|
||||||
|
>
|
||||||
|
<Input type="number"></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<DynamicResponse
|
||||||
|
name="response.headers_template"
|
||||||
|
label={t('flow.webhook.headersTemplate')}
|
||||||
|
></DynamicResponse>
|
||||||
|
<DynamicResponse
|
||||||
|
name="response.body_template"
|
||||||
|
label={t('flow.webhook.bodyTemplate')}
|
||||||
|
></DynamicResponse>
|
||||||
|
</section>
|
||||||
|
</Collapse>
|
||||||
|
);
|
||||||
|
}
|
||||||
46
web/src/pages/agent/form/components/dynamic-string-form.tsx
Normal file
46
web/src/pages/agent/form/components/dynamic-string-form.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Trash2 } from 'lucide-react';
|
||||||
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
|
import { DynamicFormHeader, FormListHeaderProps } from './dynamic-fom-header';
|
||||||
|
|
||||||
|
type DynamicStringFormProps = { name: string } & FormListHeaderProps;
|
||||||
|
export function DynamicStringForm({ name, label }: DynamicStringFormProps) {
|
||||||
|
const form = useFormContext();
|
||||||
|
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
name: name,
|
||||||
|
control: form.control,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<DynamicFormHeader
|
||||||
|
label={label}
|
||||||
|
onClick={() => append({ value: '' })}
|
||||||
|
></DynamicFormHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<div key={field.id} className="flex items-center gap-2">
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name={`${name}.${index}.value`}
|
||||||
|
label="delimiter"
|
||||||
|
labelClassName="!hidden"
|
||||||
|
className="flex-1 !m-0"
|
||||||
|
>
|
||||||
|
<Input className="!m-0"></Input>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={'ghost'}
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ import {
|
|||||||
JsonSchemaVisualizer,
|
JsonSchemaVisualizer,
|
||||||
SchemaVisualEditor,
|
SchemaVisualEditor,
|
||||||
} from '@/components/jsonjoy-builder';
|
} from '@/components/jsonjoy-builder';
|
||||||
|
import { KeyInputProps } from '@/components/jsonjoy-builder/components/schema-editor/interface';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -16,11 +17,12 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export function StructuredOutputDialog({
|
export function SchemaDialog({
|
||||||
hideModal,
|
hideModal,
|
||||||
onOk,
|
onOk,
|
||||||
initialValues,
|
initialValues,
|
||||||
}: IModalProps<any>) {
|
pattern,
|
||||||
|
}: IModalProps<any> & KeyInputProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [schema, setSchema] = useState<JSONSchema>(initialValues);
|
const [schema, setSchema] = useState<JSONSchema>(initialValues);
|
||||||
|
|
||||||
@ -36,7 +38,11 @@ export function StructuredOutputDialog({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<section className="flex overflow-auto">
|
<section className="flex overflow-auto">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<SchemaVisualEditor schema={schema} onChange={setSchema} />
|
<SchemaVisualEditor
|
||||||
|
schema={schema}
|
||||||
|
onChange={setSchema}
|
||||||
|
pattern={pattern}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<JsonSchemaVisualizer schema={schema} onChange={setSchema} />
|
<JsonSchemaVisualizer schema={schema} onChange={setSchema} />
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { JSONSchema, JsonSchemaVisualizer } from '@/components/jsonjoy-builder';
|
import { JSONSchema, JsonSchemaVisualizer } from '@/components/jsonjoy-builder';
|
||||||
|
|
||||||
export function StructuredOutputPanel({ value }: { value: JSONSchema }) {
|
export function SchemaPanel({ value }: { value: JSONSchema }) {
|
||||||
return (
|
return (
|
||||||
<section className="h-48">
|
<section className="h-48">
|
||||||
<JsonSchemaVisualizer
|
<JsonSchemaVisualizer
|
||||||
@ -2,7 +2,9 @@ import { getStructuredDatatype } from '@/utils/canvas-util';
|
|||||||
import { get, isPlainObject } from 'lodash';
|
import { get, isPlainObject } from 'lodash';
|
||||||
import { ReactNode, useCallback } from 'react';
|
import { ReactNode, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
|
AgentDialogueMode,
|
||||||
AgentStructuredOutputField,
|
AgentStructuredOutputField,
|
||||||
|
BeginId,
|
||||||
JsonSchemaDataType,
|
JsonSchemaDataType,
|
||||||
Operator,
|
Operator,
|
||||||
} from '../constant';
|
} from '../constant';
|
||||||
@ -16,36 +18,94 @@ function getNodeId(value: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useShowSecondaryMenu() {
|
export function useShowSecondaryMenu() {
|
||||||
const { getOperatorTypeFromId } = useGraphStore((state) => state);
|
const { getOperatorTypeFromId, getNode } = useGraphStore((state) => state);
|
||||||
|
|
||||||
const showSecondaryMenu = useCallback(
|
const showSecondaryMenu = useCallback(
|
||||||
(value: string, outputLabel: string) => {
|
(value: string, outputLabel: string) => {
|
||||||
const nodeId = getNodeId(value);
|
const nodeId = getNodeId(value);
|
||||||
return (
|
const operatorType = getOperatorTypeFromId(nodeId);
|
||||||
getOperatorTypeFromId(nodeId) === Operator.Agent &&
|
|
||||||
|
// For Agent nodes, show secondary menu for 'structured' field
|
||||||
|
if (
|
||||||
|
operatorType === Operator.Agent &&
|
||||||
outputLabel === AgentStructuredOutputField
|
outputLabel === AgentStructuredOutputField
|
||||||
);
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Begin nodes in webhook mode, show secondary menu for schema properties (body, headers, query, etc.)
|
||||||
|
if (operatorType === Operator.Begin) {
|
||||||
|
const node = getNode(nodeId);
|
||||||
|
const mode = get(node, 'data.form.mode');
|
||||||
|
if (mode === AgentDialogueMode.Webhook) {
|
||||||
|
// Check if this output field is from the schema
|
||||||
|
const outputs = get(node, 'data.form.outputs', {});
|
||||||
|
const outputField = outputs[outputLabel];
|
||||||
|
// Show secondary menu if the field is an object or has properties
|
||||||
|
return (
|
||||||
|
outputField &&
|
||||||
|
(outputField.type === 'object' ||
|
||||||
|
(outputField.properties &&
|
||||||
|
Object.keys(outputField.properties).length > 0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
[getOperatorTypeFromId],
|
[getOperatorTypeFromId, getNode],
|
||||||
);
|
);
|
||||||
|
|
||||||
return showSecondaryMenu;
|
return showSecondaryMenu;
|
||||||
}
|
}
|
||||||
|
function useGetBeginOutputsOrSchema() {
|
||||||
|
const { getNode } = useGraphStore((state) => state);
|
||||||
|
|
||||||
|
const getBeginOutputs = useCallback(() => {
|
||||||
|
const node = getNode(BeginId);
|
||||||
|
const outputs = get(node, 'data.form.outputs', {});
|
||||||
|
return outputs;
|
||||||
|
}, [getNode]);
|
||||||
|
|
||||||
|
const getBeginSchema = useCallback(() => {
|
||||||
|
const node = getNode(BeginId);
|
||||||
|
const outputs = get(node, 'data.form.schema', {});
|
||||||
|
return outputs;
|
||||||
|
}, [getNode]);
|
||||||
|
|
||||||
|
return { getBeginOutputs, getBeginSchema };
|
||||||
|
}
|
||||||
|
|
||||||
export function useGetStructuredOutputByValue() {
|
export function useGetStructuredOutputByValue() {
|
||||||
const { getNode } = useGraphStore((state) => state);
|
const { getNode, getOperatorTypeFromId } = useGraphStore((state) => state);
|
||||||
|
|
||||||
|
const { getBeginOutputs } = useGetBeginOutputsOrSchema();
|
||||||
|
|
||||||
const getStructuredOutput = useCallback(
|
const getStructuredOutput = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
const node = getNode(getNodeId(value));
|
const nodeId = getNodeId(value);
|
||||||
const structuredOutput = get(
|
const node = getNode(nodeId);
|
||||||
node,
|
const operatorType = getOperatorTypeFromId(nodeId);
|
||||||
`data.form.outputs.${AgentStructuredOutputField}`,
|
const fields = splitValue(value);
|
||||||
);
|
const outputLabel = fields.at(1);
|
||||||
|
|
||||||
|
let structuredOutput;
|
||||||
|
if (operatorType === Operator.Agent) {
|
||||||
|
structuredOutput = get(
|
||||||
|
node,
|
||||||
|
`data.form.outputs.${AgentStructuredOutputField}`,
|
||||||
|
);
|
||||||
|
} else if (operatorType === Operator.Begin) {
|
||||||
|
// For Begin nodes in webhook mode, get the specific schema property
|
||||||
|
const outputs = getBeginOutputs();
|
||||||
|
if (outputLabel) {
|
||||||
|
structuredOutput = outputs[outputLabel];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return structuredOutput;
|
return structuredOutput;
|
||||||
},
|
},
|
||||||
[getNode],
|
[getBeginOutputs, getNode, getOperatorTypeFromId],
|
||||||
);
|
);
|
||||||
|
|
||||||
return getStructuredOutput;
|
return getStructuredOutput;
|
||||||
@ -66,13 +126,14 @@ export function useFindAgentStructuredOutputLabel() {
|
|||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
}>,
|
}>,
|
||||||
) => {
|
) => {
|
||||||
// agent structured output
|
|
||||||
const fields = splitValue(value);
|
const fields = splitValue(value);
|
||||||
|
const operatorType = getOperatorTypeFromId(fields.at(0));
|
||||||
|
|
||||||
|
// Handle Agent structured fields
|
||||||
if (
|
if (
|
||||||
getOperatorTypeFromId(fields.at(0)) === Operator.Agent &&
|
operatorType === Operator.Agent &&
|
||||||
fields.at(1)?.startsWith(AgentStructuredOutputField)
|
fields.at(1)?.startsWith(AgentStructuredOutputField)
|
||||||
) {
|
) {
|
||||||
// is agent structured output
|
|
||||||
const agentOption = options.find((x) => value.includes(x.value));
|
const agentOption = options.find((x) => value.includes(x.value));
|
||||||
const jsonSchemaFields = fields
|
const jsonSchemaFields = fields
|
||||||
.at(1)
|
.at(1)
|
||||||
@ -84,6 +145,19 @@ export function useFindAgentStructuredOutputLabel() {
|
|||||||
value: value,
|
value: value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Begin webhook fields
|
||||||
|
if (operatorType === Operator.Begin && fields.at(1)) {
|
||||||
|
const fieldOption = options
|
||||||
|
.filter((x) => x.parentLabel === BeginId)
|
||||||
|
.find((x) => value.startsWith(x.value));
|
||||||
|
|
||||||
|
return {
|
||||||
|
...fieldOption,
|
||||||
|
label: fields.at(1),
|
||||||
|
value: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[getOperatorTypeFromId],
|
[getOperatorTypeFromId],
|
||||||
);
|
);
|
||||||
@ -94,6 +168,7 @@ export function useFindAgentStructuredOutputLabel() {
|
|||||||
export function useFindAgentStructuredOutputTypeByValue() {
|
export function useFindAgentStructuredOutputTypeByValue() {
|
||||||
const { getOperatorTypeFromId } = useGraphStore((state) => state);
|
const { getOperatorTypeFromId } = useGraphStore((state) => state);
|
||||||
const filterStructuredOutput = useGetStructuredOutputByValue();
|
const filterStructuredOutput = useGetStructuredOutputByValue();
|
||||||
|
const { getBeginSchema } = useGetBeginOutputsOrSchema();
|
||||||
|
|
||||||
const findTypeByValue = useCallback(
|
const findTypeByValue = useCallback(
|
||||||
(
|
(
|
||||||
@ -136,10 +211,12 @@ export function useFindAgentStructuredOutputTypeByValue() {
|
|||||||
}
|
}
|
||||||
const fields = splitValue(value);
|
const fields = splitValue(value);
|
||||||
const nodeId = fields.at(0);
|
const nodeId = fields.at(0);
|
||||||
|
const operatorType = getOperatorTypeFromId(nodeId);
|
||||||
const jsonSchema = filterStructuredOutput(value);
|
const jsonSchema = filterStructuredOutput(value);
|
||||||
|
|
||||||
|
// Handle Agent structured fields
|
||||||
if (
|
if (
|
||||||
getOperatorTypeFromId(nodeId) === Operator.Agent &&
|
operatorType === Operator.Agent &&
|
||||||
fields.at(1)?.startsWith(AgentStructuredOutputField)
|
fields.at(1)?.startsWith(AgentStructuredOutputField)
|
||||||
) {
|
) {
|
||||||
const jsonSchemaFields = fields
|
const jsonSchemaFields = fields
|
||||||
@ -151,13 +228,32 @@ export function useFindAgentStructuredOutputTypeByValue() {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Begin webhook fields (body, headers, query, etc.)
|
||||||
|
if (operatorType === Operator.Begin) {
|
||||||
|
const outputLabel = fields.at(1);
|
||||||
|
const schema = getBeginSchema();
|
||||||
|
if (outputLabel && schema) {
|
||||||
|
const jsonSchemaFields = fields.at(1);
|
||||||
|
if (jsonSchemaFields) {
|
||||||
|
const type = findTypeByValue(schema, jsonSchemaFields);
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[filterStructuredOutput, findTypeByValue, getOperatorTypeFromId],
|
[
|
||||||
|
filterStructuredOutput,
|
||||||
|
findTypeByValue,
|
||||||
|
getBeginSchema,
|
||||||
|
getOperatorTypeFromId,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return findAgentStructuredOutputTypeByValue;
|
return findAgentStructuredOutputTypeByValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Consider merging with useFindAgentStructuredOutputLabel
|
||||||
export function useFindAgentStructuredOutputLabelByValue() {
|
export function useFindAgentStructuredOutputLabelByValue() {
|
||||||
const { getNode } = useGraphStore((state) => state);
|
const { getNode } = useGraphStore((state) => state);
|
||||||
|
|
||||||
|
|||||||
@ -314,10 +314,12 @@ export function useFilterQueryVariableOptionsByTypes({
|
|||||||
? toLower(y.type).includes(toLower(x))
|
? toLower(y.type).includes(toLower(x))
|
||||||
: toLower(y.type) === toLower(x),
|
: toLower(y.type) === toLower(x),
|
||||||
) ||
|
) ||
|
||||||
|
// agent structured output
|
||||||
isAgentStructured(
|
isAgentStructured(
|
||||||
y.value,
|
y.value,
|
||||||
y.value.slice(-AgentStructuredOutputField.length),
|
y.value.slice(-AgentStructuredOutputField.length),
|
||||||
), // agent structured output
|
) ||
|
||||||
|
y.value.startsWith(BeginId), // begin node outputs
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import {
|
|||||||
import pipe from 'lodash/fp/pipe';
|
import pipe from 'lodash/fp/pipe';
|
||||||
import isObject from 'lodash/isObject';
|
import isObject from 'lodash/isObject';
|
||||||
import {
|
import {
|
||||||
|
AgentDialogueMode,
|
||||||
CategorizeAnchorPointPositions,
|
CategorizeAnchorPointPositions,
|
||||||
FileType,
|
FileType,
|
||||||
FileTypeSuffixMap,
|
FileTypeSuffixMap,
|
||||||
@ -34,6 +35,7 @@ import {
|
|||||||
Operator,
|
Operator,
|
||||||
TypesWithArray,
|
TypesWithArray,
|
||||||
} from './constant';
|
} from './constant';
|
||||||
|
import { BeginFormSchemaType } from './form/begin-form';
|
||||||
import { DataOperationsFormSchemaType } from './form/data-operations-form';
|
import { DataOperationsFormSchemaType } from './form/data-operations-form';
|
||||||
import { ExtractorFormSchemaType } from './form/extractor-form';
|
import { ExtractorFormSchemaType } from './form/extractor-form';
|
||||||
import { HierarchicalMergerFormSchemaType } from './form/hierarchical-merger-form';
|
import { HierarchicalMergerFormSchemaType } from './form/hierarchical-merger-form';
|
||||||
@ -312,6 +314,41 @@ function transformDataOperationsParams(params: DataOperationsFormSchemaType) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transformArrayToObject(
|
||||||
|
list?: Array<{ key: string; value: string }>,
|
||||||
|
) {
|
||||||
|
if (!Array.isArray(list)) return {};
|
||||||
|
return list?.reduce<Record<string, any>>((pre, cur) => {
|
||||||
|
if (cur.key) {
|
||||||
|
pre[cur.key] = cur.value;
|
||||||
|
}
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformBeginParams(params: BeginFormSchemaType) {
|
||||||
|
if (params.mode === AgentDialogueMode.Webhook) {
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
security: {
|
||||||
|
...params.security,
|
||||||
|
ip_whitelist: params.security?.ip_whitelist.map((x) => x.value),
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
...params.response,
|
||||||
|
headers_template: transformArrayToObject(
|
||||||
|
params.response?.headers_template,
|
||||||
|
),
|
||||||
|
body_template: transformArrayToObject(params.response?.body_template),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// construct a dsl based on the node information of the graph
|
// construct a dsl based on the node information of the graph
|
||||||
export const buildDslComponentsByGraph = (
|
export const buildDslComponentsByGraph = (
|
||||||
nodes: RAGFlowNodeType[],
|
nodes: RAGFlowNodeType[],
|
||||||
@ -361,6 +398,9 @@ export const buildDslComponentsByGraph = (
|
|||||||
case Operator.DataOperations:
|
case Operator.DataOperations:
|
||||||
params = transformDataOperationsParams(params);
|
params = transformDataOperationsParams(params);
|
||||||
break;
|
break;
|
||||||
|
case Operator.Begin:
|
||||||
|
params = transformBeginParams(params);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user