mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-23 15:06:50 +08:00
### What problem does this PR solve? Feat: Remove HMAC from the webhook #10427 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -25,3 +25,12 @@ const CopyToClipboard = ({ text }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default CopyToClipboard;
|
export default CopyToClipboard;
|
||||||
|
|
||||||
|
export function CopyToClipboardWithText({ text }: { text: string }) {
|
||||||
|
return (
|
||||||
|
<div className="bg-bg-card p-1 rounded-md flex gap-2">
|
||||||
|
<span className="flex-1 truncate">{text}</span>
|
||||||
|
<CopyToClipboard text={text}></CopyToClipboard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ interface NumberInputProps {
|
|||||||
onChange?: (value: number) => void;
|
onChange?: (value: number) => void;
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
min?: number;
|
min?: number;
|
||||||
|
max?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberInput: React.FC<NumberInputProps> = ({
|
const NumberInput: React.FC<NumberInputProps> = ({
|
||||||
@ -15,6 +16,7 @@ const NumberInput: React.FC<NumberInputProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
height,
|
height,
|
||||||
min = 0,
|
min = 0,
|
||||||
|
max = Infinity,
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState<number>(() => {
|
const [value, setValue] = useState<number>(() => {
|
||||||
return initialValue ?? 0;
|
return initialValue ?? 0;
|
||||||
@ -34,6 +36,9 @@ const NumberInput: React.FC<NumberInputProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleIncrement = () => {
|
const handleIncrement = () => {
|
||||||
|
if (value > max - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setValue(value + 1);
|
setValue(value + 1);
|
||||||
onChange?.(value + 1);
|
onChange?.(value + 1);
|
||||||
};
|
};
|
||||||
@ -41,6 +46,9 @@ const NumberInput: React.FC<NumberInputProps> = ({
|
|||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newValue = Number(e.target.value);
|
const newValue = Number(e.target.value);
|
||||||
if (!isNaN(newValue)) {
|
if (!isNaN(newValue)) {
|
||||||
|
if (newValue > max) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
onChange?.(newValue);
|
onChange?.(newValue);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,11 @@ import {
|
|||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { ReactNode, cloneElement, isValidElement } from 'react';
|
import { ReactNode, cloneElement, isValidElement } from 'react';
|
||||||
import { ControllerRenderProps, useFormContext } from 'react-hook-form';
|
import {
|
||||||
|
ControllerRenderProps,
|
||||||
|
UseControllerProps,
|
||||||
|
useFormContext,
|
||||||
|
} from 'react-hook-form';
|
||||||
|
|
||||||
type RAGFlowFormItemProps = {
|
type RAGFlowFormItemProps = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -18,7 +22,7 @@ type RAGFlowFormItemProps = {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
} & Pick<UseControllerProps<any>, 'rules'>;
|
||||||
|
|
||||||
export function RAGFlowFormItem({
|
export function RAGFlowFormItem({
|
||||||
name,
|
name,
|
||||||
@ -29,11 +33,13 @@ export function RAGFlowFormItem({
|
|||||||
required = false,
|
required = false,
|
||||||
labelClassName,
|
labelClassName,
|
||||||
className,
|
className,
|
||||||
|
rules,
|
||||||
}: RAGFlowFormItemProps) {
|
}: RAGFlowFormItemProps) {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
rules={rules}
|
||||||
name={name}
|
name={name}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|||||||
@ -196,7 +196,7 @@ export enum SwitchLogicOperator {
|
|||||||
Or = 'or',
|
Or = 'or',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WebhookAlgorithmList = [
|
export const WebhookJWTAlgorithmList = [
|
||||||
'hs256',
|
'hs256',
|
||||||
'hs384',
|
'hs384',
|
||||||
'hs512',
|
'hs512',
|
||||||
|
|||||||
@ -9,8 +9,12 @@ import {
|
|||||||
IFlowTemplate,
|
IFlowTemplate,
|
||||||
IPipeLineListRequest,
|
IPipeLineListRequest,
|
||||||
ITraceData,
|
ITraceData,
|
||||||
|
IWebhookTrace,
|
||||||
} from '@/interfaces/database/agent';
|
} from '@/interfaces/database/agent';
|
||||||
import { IDebugSingleRequestBody } from '@/interfaces/request/agent';
|
import {
|
||||||
|
IAgentWebhookTraceRequest,
|
||||||
|
IDebugSingleRequestBody,
|
||||||
|
} from '@/interfaces/request/agent';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { BeginId } from '@/pages/agent/constant';
|
import { BeginId } from '@/pages/agent/constant';
|
||||||
import { IInputs } from '@/pages/agent/interface';
|
import { IInputs } from '@/pages/agent/interface';
|
||||||
@ -19,6 +23,7 @@ import agentService, {
|
|||||||
fetchAgentLogsByCanvasId,
|
fetchAgentLogsByCanvasId,
|
||||||
fetchPipeLineList,
|
fetchPipeLineList,
|
||||||
fetchTrace,
|
fetchTrace,
|
||||||
|
fetchWebhookTrace,
|
||||||
} from '@/services/agent-service';
|
} from '@/services/agent-service';
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { buildMessageListWithUuid } from '@/utils/chat';
|
import { buildMessageListWithUuid } from '@/utils/chat';
|
||||||
@ -55,6 +60,7 @@ export const enum AgentApiAction {
|
|||||||
FetchPrompt = 'fetchPrompt',
|
FetchPrompt = 'fetchPrompt',
|
||||||
CancelDataflow = 'cancelDataflow',
|
CancelDataflow = 'cancelDataflow',
|
||||||
CancelCanvas = 'cancelCanvas',
|
CancelCanvas = 'cancelCanvas',
|
||||||
|
FetchWebhookTrace = 'fetchWebhookTrace',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyDsl = {
|
export const EmptyDsl = {
|
||||||
@ -786,3 +792,70 @@ export const useFetchFlowSSE = (): {
|
|||||||
|
|
||||||
return { data, loading, refetch };
|
return { data, loading, refetch };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useFetchWebhookTrace = (autoStart: boolean = true) => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const [currentWebhookId, setCurrentWebhookId] = useState<string>('');
|
||||||
|
const [currentNextSinceTs, setCurrentNextSinceTs] = useState<number>(0);
|
||||||
|
const [shouldPoll, setShouldPoll] = useState(autoStart);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isFetching: loading,
|
||||||
|
refetch,
|
||||||
|
} = useQuery<IWebhookTrace>({
|
||||||
|
queryKey: [AgentApiAction.FetchWebhookTrace, id],
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchIntervalInBackground: false,
|
||||||
|
gcTime: 0,
|
||||||
|
enabled: !!id && shouldPoll,
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!id) return {};
|
||||||
|
|
||||||
|
const payload: IAgentWebhookTraceRequest =
|
||||||
|
{} as IAgentWebhookTraceRequest;
|
||||||
|
|
||||||
|
if (currentNextSinceTs) {
|
||||||
|
payload['since_ts'] = currentNextSinceTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentWebhookId) {
|
||||||
|
payload['webhook_id'] = currentWebhookId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await fetchWebhookTrace(id, payload);
|
||||||
|
|
||||||
|
const result = data.data ?? {};
|
||||||
|
|
||||||
|
if (result.webhook_id && result.webhook_id !== currentWebhookId) {
|
||||||
|
setCurrentWebhookId(result.webhook_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentNextSinceTs === 0 &&
|
||||||
|
result.next_since_ts &&
|
||||||
|
result.next_since_ts !== currentNextSinceTs
|
||||||
|
) {
|
||||||
|
setCurrentNextSinceTs(result.next_since_ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.finished) {
|
||||||
|
setShouldPoll(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
refetchInterval: shouldPoll ? 3000 : false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
refetch,
|
||||||
|
isPolling: shouldPoll,
|
||||||
|
currentWebhookId,
|
||||||
|
currentNextSinceTs,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -291,3 +291,10 @@ export interface GlobalVariableType {
|
|||||||
description: string;
|
description: string;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IWebhookTrace {
|
||||||
|
webhook_id: null;
|
||||||
|
events: any[];
|
||||||
|
next_since_ts: number;
|
||||||
|
finished: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@ -2,3 +2,8 @@ export interface IDebugSingleRequestBody {
|
|||||||
component_id: string;
|
component_id: string;
|
||||||
params: Record<string, any>;
|
params: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAgentWebhookTraceRequest {
|
||||||
|
since_ts: number; // From the first request for return
|
||||||
|
webhook_id: string; // Each external request generates a unique webhook ID.
|
||||||
|
}
|
||||||
|
|||||||
@ -1050,12 +1050,18 @@ export enum WebhookSecurityAuthType {
|
|||||||
Token = 'token',
|
Token = 'token',
|
||||||
Basic = 'basic',
|
Basic = 'basic',
|
||||||
Jwt = 'jwt',
|
Jwt = 'jwt',
|
||||||
Hmac = 'hmac',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RateLimitPerList = ['minute', 'hour', 'day'];
|
export enum WebhookRateLimitPer {
|
||||||
|
Second = 'second',
|
||||||
|
Minute = 'minute',
|
||||||
|
Hour = 'hour',
|
||||||
|
Day = 'day',
|
||||||
|
}
|
||||||
|
|
||||||
export const WebhookMaxBodySize = ['10MB', '50MB', '100MB', '1000MB'];
|
export const RateLimitPerList = Object.values(WebhookRateLimitPer);
|
||||||
|
|
||||||
|
export const WebhookMaxBodySize = ['1MB', '5MB', '10MB'];
|
||||||
|
|
||||||
export enum WebhookRequestParameters {
|
export enum WebhookRequestParameters {
|
||||||
File = VariableType.File,
|
File = VariableType.File,
|
||||||
|
|||||||
@ -43,6 +43,7 @@ function BeginForm({ node }: INextOperatorForm) {
|
|||||||
const form = useForm({
|
const form = useForm({
|
||||||
defaultValues: values,
|
defaultValues: values,
|
||||||
resolver: zodResolver(BeginFormSchema),
|
resolver: zodResolver(BeginFormSchema),
|
||||||
|
mode: 'onChange',
|
||||||
});
|
});
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
useWatchFormChange(node?.id, form);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { WebhookAlgorithmList } from '@/constants/agent';
|
import { WebhookJWTAlgorithmList } from '@/constants/agent';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const BeginFormSchema = z.object({
|
export const BeginFormSchema = z.object({
|
||||||
@ -30,7 +30,14 @@ export const BeginFormSchema = z.object({
|
|||||||
max_body_size: z.string(),
|
max_body_size: z.string(),
|
||||||
jwt: z
|
jwt: z
|
||||||
.object({
|
.object({
|
||||||
algorithm: z.string().default(WebhookAlgorithmList[0]).optional(),
|
algorithm: z.string().default(WebhookJWTAlgorithmList[0]).optional(),
|
||||||
|
required_claims: z.array(z.object({ value: z.string() })),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
hmac: z
|
||||||
|
.object({
|
||||||
|
header: z.string().optional(),
|
||||||
|
secret: z.string().optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import { useCallback } from 'react';
|
|||||||
import { UseFormReturn } from 'react-hook-form';
|
import { UseFormReturn } from 'react-hook-form';
|
||||||
import {
|
import {
|
||||||
AgentDialogueMode,
|
AgentDialogueMode,
|
||||||
RateLimitPerList,
|
|
||||||
WebhookContentType,
|
WebhookContentType,
|
||||||
WebhookExecutionMode,
|
WebhookExecutionMode,
|
||||||
WebhookMaxBodySize,
|
WebhookMaxBodySize,
|
||||||
WebhookMethod,
|
WebhookMethod,
|
||||||
|
WebhookRateLimitPer,
|
||||||
WebhookSecurityAuthType,
|
WebhookSecurityAuthType,
|
||||||
} from '../../constant';
|
} from '../../constant';
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ const initialFormValuesMap = {
|
|||||||
methods: [WebhookMethod.Get],
|
methods: [WebhookMethod.Get],
|
||||||
schema: {},
|
schema: {},
|
||||||
'security.auth_type': WebhookSecurityAuthType.Basic,
|
'security.auth_type': WebhookSecurityAuthType.Basic,
|
||||||
'security.rate_limit.per': RateLimitPerList[0],
|
'security.rate_limit.per': WebhookRateLimitPer.Second,
|
||||||
'security.rate_limit.limit': 10,
|
'security.rate_limit.limit': 10,
|
||||||
'security.max_body_size': WebhookMaxBodySize[0],
|
'security.max_body_size': WebhookMaxBodySize[0],
|
||||||
'response.status': 200,
|
'response.status': 200,
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { WebhookAlgorithmList } from '@/constants/agent';
|
import { WebhookJWTAlgorithmList } from '@/constants/agent';
|
||||||
import { WebhookSecurityAuthType } from '@/pages/agent/constant';
|
import { WebhookSecurityAuthType } from '@/pages/agent/constant';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useFormContext, useWatch } from 'react-hook-form';
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DynamicStringForm } from '../../components/dynamic-string-form';
|
||||||
|
|
||||||
const AlgorithmOptions = buildOptions(WebhookAlgorithmList);
|
const AlgorithmOptions = buildOptions(WebhookJWTAlgorithmList);
|
||||||
|
|
||||||
const RequiredClaimsOptions = buildOptions(['exp', 'sub']);
|
|
||||||
|
|
||||||
export function Auth() {
|
export function Auth() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -88,38 +87,10 @@ export function Auth() {
|
|||||||
>
|
>
|
||||||
<Input></Input>
|
<Input></Input>
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
<RAGFlowFormItem
|
<DynamicStringForm
|
||||||
name="security.jwt.required_claims"
|
name="security.jwt.required_claims"
|
||||||
label={t('flow.webhook.requiredClaims')}
|
label={t('flow.webhook.requiredClaims')}
|
||||||
>
|
></DynamicStringForm>
|
||||||
<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],
|
[t],
|
||||||
@ -129,11 +100,14 @@ export function Auth() {
|
|||||||
[WebhookSecurityAuthType.Token]: renderTokenAuth,
|
[WebhookSecurityAuthType.Token]: renderTokenAuth,
|
||||||
[WebhookSecurityAuthType.Basic]: renderBasicAuth,
|
[WebhookSecurityAuthType.Basic]: renderBasicAuth,
|
||||||
[WebhookSecurityAuthType.Jwt]: renderJwtAuth,
|
[WebhookSecurityAuthType.Jwt]: renderJwtAuth,
|
||||||
[WebhookSecurityAuthType.Hmac]: renderHmacAuth,
|
|
||||||
[WebhookSecurityAuthType.None]: () => null,
|
[WebhookSecurityAuthType.None]: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return AuthMap[
|
return (
|
||||||
(authType ?? WebhookSecurityAuthType.None) as WebhookSecurityAuthType
|
<div key={`auth-${authType}`} className="space-y-5">
|
||||||
]();
|
{AuthMap[
|
||||||
|
(authType ?? WebhookSecurityAuthType.None) as WebhookSecurityAuthType
|
||||||
|
]()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,15 +6,10 @@ import { Separator } from '@/components/ui/separator';
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
import { loader } from '@monaco-editor/react';
|
import { loader } from '@monaco-editor/react';
|
||||||
import { omit } from 'lodash';
|
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
import {
|
import { TypesWithArray, WebhookRequestParameters } from '../../../constant';
|
||||||
TypesWithArray,
|
|
||||||
WebhookContentType,
|
|
||||||
WebhookRequestParameters,
|
|
||||||
} from '../../../constant';
|
|
||||||
import { DynamicFormHeader } from '../../components/dynamic-fom-header';
|
import { DynamicFormHeader } from '../../components/dynamic-fom-header';
|
||||||
|
|
||||||
loader.config({ paths: { vs: '/vs' } });
|
loader.config({ paths: { vs: '/vs' } });
|
||||||
@ -28,16 +23,9 @@ type SelectKeysProps = {
|
|||||||
requiredField?: string;
|
requiredField?: string;
|
||||||
nodeId?: string;
|
nodeId?: string;
|
||||||
isObject?: boolean;
|
isObject?: boolean;
|
||||||
|
operatorList: WebhookRequestParameters[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildParametersOptions(isObject: boolean) {
|
|
||||||
const list = isObject
|
|
||||||
? WebhookRequestParameters
|
|
||||||
: omit(WebhookRequestParameters, ['File']);
|
|
||||||
|
|
||||||
return buildOptions(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DynamicRequest({
|
export function DynamicRequest({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
@ -45,15 +33,9 @@ export function DynamicRequest({
|
|||||||
keyField = 'key',
|
keyField = 'key',
|
||||||
operatorField = 'type',
|
operatorField = 'type',
|
||||||
requiredField = 'required',
|
requiredField = 'required',
|
||||||
isObject = false,
|
operatorList,
|
||||||
}: SelectKeysProps) {
|
}: SelectKeysProps) {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const contentType = useWatch({
|
|
||||||
name: 'content_types',
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
const isFormDataContentType =
|
|
||||||
contentType === WebhookContentType.MultipartFormData;
|
|
||||||
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
const { fields, remove, append } = useFieldArray({
|
||||||
name: name,
|
name: name,
|
||||||
@ -94,9 +76,7 @@ export function DynamicRequest({
|
|||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
field.onChange(val);
|
field.onChange(val);
|
||||||
}}
|
}}
|
||||||
options={buildParametersOptions(
|
options={buildOptions(operatorList)}
|
||||||
isObject && isFormDataContentType,
|
|
||||||
)}
|
|
||||||
></SelectWithSearch>
|
></SelectWithSearch>
|
||||||
)}
|
)}
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
|
|||||||
@ -1,17 +1,20 @@
|
|||||||
import { Collapse } from '@/components/collapse';
|
import { Collapse } from '@/components/collapse';
|
||||||
import CopyToClipboard from '@/components/copy-to-clipboard';
|
import { CopyToClipboardWithText } from '@/components/copy-to-clipboard';
|
||||||
|
import NumberInput from '@/components/originui/number-input';
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { MultiSelect } from '@/components/ui/multi-select';
|
import { MultiSelect } from '@/components/ui/multi-select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
import {
|
import {
|
||||||
RateLimitPerList,
|
RateLimitPerList,
|
||||||
WebhookMaxBodySize,
|
WebhookMaxBodySize,
|
||||||
WebhookMethod,
|
WebhookMethod,
|
||||||
|
WebhookRateLimitPer,
|
||||||
WebhookSecurityAuthType,
|
WebhookSecurityAuthType,
|
||||||
} from '../../../constant';
|
} from '../../../constant';
|
||||||
import { DynamicStringForm } from '../../components/dynamic-string-form';
|
import { DynamicStringForm } from '../../components/dynamic-string-form';
|
||||||
@ -21,18 +24,32 @@ import { WebhookResponse } from './response';
|
|||||||
|
|
||||||
const RateLimitPerOptions = buildOptions(RateLimitPerList);
|
const RateLimitPerOptions = buildOptions(RateLimitPerList);
|
||||||
|
|
||||||
|
const RequestLimitMap = {
|
||||||
|
[WebhookRateLimitPer.Second]: 100,
|
||||||
|
[WebhookRateLimitPer.Minute]: 1000,
|
||||||
|
[WebhookRateLimitPer.Hour]: 10000,
|
||||||
|
[WebhookRateLimitPer.Day]: 100000,
|
||||||
|
};
|
||||||
|
|
||||||
export function WebHook() {
|
export function WebHook() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const form = useFormContext();
|
||||||
|
|
||||||
|
const rateLimitPer = useWatch({
|
||||||
|
name: 'security.rate_limit.per',
|
||||||
|
control: form.control,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getLimitRateLimitPerMax = useCallback((rateLimitPer: string) => {
|
||||||
|
return RequestLimitMap[rateLimitPer as keyof typeof RequestLimitMap] ?? 100;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const text = `${location.protocol}//${location.host}/api/v1/webhook/${id}`;
|
const text = `${location.protocol}//${location.host}/api/v1/webhook/${id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-bg-card p-1 rounded-md flex gap-2">
|
<CopyToClipboardWithText text={text}></CopyToClipboardWithText>
|
||||||
<span className="flex-1 truncate">{text}</span>
|
|
||||||
<CopyToClipboard text={text}></CopyToClipboard>
|
|
||||||
</div>
|
|
||||||
<RAGFlowFormItem name="methods" label={t('flow.webhook.methods')}>
|
<RAGFlowFormItem name="methods" label={t('flow.webhook.methods')}>
|
||||||
{(field) => (
|
{(field) => (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -61,13 +78,28 @@ export function WebHook() {
|
|||||||
name="security.rate_limit.limit"
|
name="security.rate_limit.limit"
|
||||||
label={t('flow.webhook.limit')}
|
label={t('flow.webhook.limit')}
|
||||||
>
|
>
|
||||||
<Input type="number"></Input>
|
<NumberInput
|
||||||
|
max={getLimitRateLimitPerMax(rateLimitPer)}
|
||||||
|
className="w-full"
|
||||||
|
></NumberInput>
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
<RAGFlowFormItem
|
<RAGFlowFormItem
|
||||||
name="security.rate_limit.per"
|
name="security.rate_limit.per"
|
||||||
label={t('flow.webhook.per')}
|
label={t('flow.webhook.per')}
|
||||||
>
|
>
|
||||||
<SelectWithSearch options={RateLimitPerOptions}></SelectWithSearch>
|
{(field) => (
|
||||||
|
<SelectWithSearch
|
||||||
|
options={RateLimitPerOptions}
|
||||||
|
value={field.value}
|
||||||
|
onChange={(val) => {
|
||||||
|
field.onChange(val);
|
||||||
|
form.setValue(
|
||||||
|
'security.rate_limit.limit',
|
||||||
|
getLimitRateLimitPerMax(val),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
></SelectWithSearch>
|
||||||
|
)}
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
<RAGFlowFormItem
|
<RAGFlowFormItem
|
||||||
name="security.max_body_size"
|
name="security.max_body_size"
|
||||||
|
|||||||
@ -1,13 +1,40 @@
|
|||||||
import { Collapse } from '@/components/collapse';
|
import { Collapse } from '@/components/collapse';
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { WebhookContentType } from '@/pages/agent/constant';
|
import {
|
||||||
|
WebhookContentType,
|
||||||
|
WebhookRequestParameters,
|
||||||
|
} from '@/pages/agent/constant';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DynamicRequest } from './dynamic-request';
|
import { DynamicRequest } from './dynamic-request';
|
||||||
|
|
||||||
export function WebhookRequestSchema() {
|
export function WebhookRequestSchema() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const form = useFormContext();
|
||||||
|
const contentType = useWatch({
|
||||||
|
name: 'content_types',
|
||||||
|
control: form.control,
|
||||||
|
});
|
||||||
|
const isFormDataContentType =
|
||||||
|
contentType === WebhookContentType.MultipartFormData;
|
||||||
|
|
||||||
|
const bodyOperatorList = useMemo(() => {
|
||||||
|
return isFormDataContentType
|
||||||
|
? [
|
||||||
|
WebhookRequestParameters.String,
|
||||||
|
WebhookRequestParameters.Number,
|
||||||
|
WebhookRequestParameters.Boolean,
|
||||||
|
WebhookRequestParameters.File,
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
WebhookRequestParameters.String,
|
||||||
|
WebhookRequestParameters.Number,
|
||||||
|
WebhookRequestParameters.Boolean,
|
||||||
|
];
|
||||||
|
}, [isFormDataContentType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse title={<div>{t('flow.webhook.schema')}</div>}>
|
<Collapse title={<div>{t('flow.webhook.schema')}</div>}>
|
||||||
@ -23,14 +50,20 @@ export function WebhookRequestSchema() {
|
|||||||
<DynamicRequest
|
<DynamicRequest
|
||||||
name="schema.query"
|
name="schema.query"
|
||||||
label={t('flow.webhook.queryParameters')}
|
label={t('flow.webhook.queryParameters')}
|
||||||
|
operatorList={[
|
||||||
|
WebhookRequestParameters.String,
|
||||||
|
WebhookRequestParameters.Number,
|
||||||
|
WebhookRequestParameters.Boolean,
|
||||||
|
]}
|
||||||
></DynamicRequest>
|
></DynamicRequest>
|
||||||
<DynamicRequest
|
<DynamicRequest
|
||||||
name="schema.headers"
|
name="schema.headers"
|
||||||
label={t('flow.webhook.headerParameters')}
|
label={t('flow.webhook.headerParameters')}
|
||||||
|
operatorList={[WebhookRequestParameters.String]}
|
||||||
></DynamicRequest>
|
></DynamicRequest>
|
||||||
<DynamicRequest
|
<DynamicRequest
|
||||||
name="schema.body"
|
name="schema.body"
|
||||||
isObject
|
operatorList={bodyOperatorList}
|
||||||
label={t('flow.webhook.requestBodyParameters')}
|
label={t('flow.webhook.requestBodyParameters')}
|
||||||
></DynamicRequest>
|
></DynamicRequest>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export function WebhookResponse() {
|
|||||||
name="response.body_template"
|
name="response.body_template"
|
||||||
label={t('flow.webhook.bodyTemplate')}
|
label={t('flow.webhook.bodyTemplate')}
|
||||||
>
|
>
|
||||||
<Textarea></Textarea>
|
<Textarea className="overflow-auto"></Textarea>
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
21
web/src/pages/agent/form/components/json-viewer.tsx
Normal file
21
web/src/pages/agent/form/components/json-viewer.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import JsonView from 'react18-json-view';
|
||||||
|
|
||||||
|
export function JsonViewer({
|
||||||
|
data,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
data: Record<string, any>;
|
||||||
|
title: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-2">
|
||||||
|
<div>{title}</div>
|
||||||
|
<JsonView
|
||||||
|
src={data}
|
||||||
|
displaySize
|
||||||
|
collapseStringsAfterLength={100000000000}
|
||||||
|
className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-muted"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
web/src/pages/agent/hooks/use-is-webhook.ts
Normal file
10
web/src/pages/agent/hooks/use-is-webhook.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { AgentDialogueMode, BeginId } from '../constant';
|
||||||
|
import useGraphStore from '../store';
|
||||||
|
|
||||||
|
export function useIsWebhookMode() {
|
||||||
|
const getNode = useGraphStore((state) => state.getNode);
|
||||||
|
|
||||||
|
const beginNode = getNode(BeginId);
|
||||||
|
|
||||||
|
return beginNode?.data.form?.mode === AgentDialogueMode.Webhook;
|
||||||
|
}
|
||||||
@ -46,6 +46,7 @@ import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
|||||||
import { useFetchPipelineLog } from './hooks/use-fetch-pipeline-log';
|
import { useFetchPipelineLog } from './hooks/use-fetch-pipeline-log';
|
||||||
import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query';
|
import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query';
|
||||||
import { useIsPipeline } from './hooks/use-is-pipeline';
|
import { useIsPipeline } from './hooks/use-is-pipeline';
|
||||||
|
import { useIsWebhookMode } from './hooks/use-is-webhook';
|
||||||
import { useRunDataflow } from './hooks/use-run-dataflow';
|
import { useRunDataflow } from './hooks/use-run-dataflow';
|
||||||
import {
|
import {
|
||||||
useSaveGraph,
|
useSaveGraph,
|
||||||
@ -58,6 +59,7 @@ import { SettingDialog } from './setting-dialog';
|
|||||||
import useGraphStore from './store';
|
import useGraphStore from './store';
|
||||||
import { useAgentHistoryManager } from './use-agent-history-manager';
|
import { useAgentHistoryManager } from './use-agent-history-manager';
|
||||||
import { VersionDialog } from './version-dialog';
|
import { VersionDialog } from './version-dialog';
|
||||||
|
import WebhookSheet from './webhook-sheet';
|
||||||
|
|
||||||
function AgentDropdownMenuItem({
|
function AgentDropdownMenuItem({
|
||||||
children,
|
children,
|
||||||
@ -110,6 +112,7 @@ export default function Agent() {
|
|||||||
useShowEmbedModal();
|
useShowEmbedModal();
|
||||||
const { navigateToAgentLogs } = useNavigatePage();
|
const { navigateToAgentLogs } = useNavigatePage();
|
||||||
const time = useWatchAgentChange(chatDrawerVisible);
|
const time = useWatchAgentChange(chatDrawerVisible);
|
||||||
|
const isWebhookMode = useIsWebhookMode();
|
||||||
|
|
||||||
// pipeline
|
// pipeline
|
||||||
|
|
||||||
@ -119,6 +122,12 @@ export default function Agent() {
|
|||||||
showModal: showPipelineRunSheet,
|
showModal: showPipelineRunSheet,
|
||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
|
|
||||||
|
const {
|
||||||
|
visible: webhookTestSheetVisible,
|
||||||
|
hideModal: hideWebhookTestSheet,
|
||||||
|
showModal: showWebhookTestSheet,
|
||||||
|
} = useSetModalState();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
visible: pipelineLogSheetVisible,
|
visible: pipelineLogSheetVisible,
|
||||||
showModal: showPipelineLogSheet,
|
showModal: showPipelineLogSheet,
|
||||||
@ -172,12 +181,20 @@ export default function Agent() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleButtonRunClick = useCallback(() => {
|
const handleButtonRunClick = useCallback(() => {
|
||||||
if (isPipeline) {
|
if (isWebhookMode) {
|
||||||
|
showWebhookTestSheet();
|
||||||
|
} else if (isPipeline) {
|
||||||
handleRunPipeline();
|
handleRunPipeline();
|
||||||
} else {
|
} else {
|
||||||
handleRunAgent();
|
handleRunAgent();
|
||||||
}
|
}
|
||||||
}, [handleRunAgent, handleRunPipeline, isPipeline]);
|
}, [
|
||||||
|
handleRunAgent,
|
||||||
|
handleRunPipeline,
|
||||||
|
isPipeline,
|
||||||
|
isWebhookMode,
|
||||||
|
showWebhookTestSheet,
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
run: runPipeline,
|
run: runPipeline,
|
||||||
@ -320,6 +337,9 @@ export default function Agent() {
|
|||||||
hideModal={hideGlobalParamSheet}
|
hideModal={hideGlobalParamSheet}
|
||||||
></GlobalParamSheet>
|
></GlobalParamSheet>
|
||||||
)}
|
)}
|
||||||
|
{webhookTestSheetVisible && (
|
||||||
|
<WebhookSheet hideModal={hideWebhookTestSheet}></WebhookSheet>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,243 +0,0 @@
|
|||||||
export const list = [
|
|
||||||
{
|
|
||||||
event: 'node_finished',
|
|
||||||
message_id: 'dce6c0c8466611f08e04047c16ec874f',
|
|
||||||
created_at: 1749606805,
|
|
||||||
task_id: 'db68eb0645ab11f0bbdc047c16ec874f',
|
|
||||||
data: {
|
|
||||||
inputs: {},
|
|
||||||
outputs: {
|
|
||||||
_elapsed_time: 0.000010083022061735392,
|
|
||||||
},
|
|
||||||
component_id: 'begin',
|
|
||||||
error: null,
|
|
||||||
elapsed_time: 0.000010083022061735392,
|
|
||||||
created_at: 1749606805,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
event: 'node_finished',
|
|
||||||
message_id: 'dce6c0c8466611f08e04047c16ec874f',
|
|
||||||
created_at: 1749606805,
|
|
||||||
task_id: 'db68eb0645ab11f0bbdc047c16ec874f',
|
|
||||||
data: {
|
|
||||||
inputs: {
|
|
||||||
query: '算法',
|
|
||||||
},
|
|
||||||
outputs: {
|
|
||||||
formalized_content:
|
|
||||||
'\nDocument: Mallat算法频3333率混叠原因及其改进模型.pdf \nRelevant fragments as following:\n---\nID: Retrieval:ClearHornetsClap_0\nFig. 6 The improved decomposition model of Ma llat algorithm\n图6 改进的Mallat分解算法模型\ncaj\nG\n→cdj+1\n↑2\nT\ncdj+1\n---\nID: Retrieval:ClearHornetsClap_1\n2 Mallat算法Mallat算法是StéPhanMallat将计算机视觉领域内的多分辨分析思想引入到小波分析中,推导出的小波分析快速算法但在具体使用时,Mallat算法是利用与尺度函数和小波函数相对应的小波低通滤波器H, h和小波高通滤波器G, g对信号进行低通和高通滤波来具体实现的为了叙述方便,在此把尺度函数称为低频子带,小波函数称为次高频子带Mallat算法如下\n---\nID: Retrieval:ClearHornetsClap_2\n4 改进算法模型经过对Mallat算法的深入研究可知,Mallat算法实现过程会不可避免地产生频率混叠现象可是,为什么现在小波分析的Mallat算法还能得到广泛的应用呢?这就是Mallat算法的精妙之处由小波分解算法和重构算法可知,小波分解过程是隔点采样的过程,重构过程是隔点插零的过程,实际上这两个过程都产生频率混叠,但是它们产生混叠的方向正好相反也就是说分解过程产生的混叠又在重构过程中得到了纠正[13 ]限于篇幅隔点插零产生频率混叠现象本文不做详细的讨论不过,这也给如何解决Mallat分解算法产生频率混叠现象提供了一个思路:在利用Mallat算法对信号分解,得到各尺度上小波系数后,再按照重构的方法重构至所需的小波空间系数cdj ,利用该重构系数cdj代替相应尺度上得到的小波系数cdj 来分析该尺度上的信号,这样就能较好地解决频率混叠所带来的影响,以达到预期的目的本文称这种算法为子带信号重构算法经改进的算法模型如图6所示\n---\nID: Retrieval:ClearHornetsClap_3\n关键词:小波分析;Mallat算法;频率混叠中图分类号: TN911. 6 文献标识码: A 文章编号: 10030972 (2007)04051104Reason andM eliorationModel of Generating Frequency A liasing of Ma llat A lgor ithmGUO Chaofeng, L IM e ilian(College of Computer Science &Technology, XuchangUniversity, Xuchang 461000, China)Abstract:Because of the design ofMallatA lgorithm, the phenomenon of frequency aliasing exists in the signal decomposition process. Based on research and analysis ofMallat algorithm, the reasons thatMallat algorithm generates frequency aliasing were found out, and an improved modle that can elim inate efficiently frequency aliasingwas given.\n---\nID: Retrieval:ClearHornetsClap_4\n·应用技术研究·Mallat算率混叠原因及其改进模型郭超峰,李梅莲(许昌学院计算机科学与技术学院,河南许昌461000)摘 要:Mallat算法由于自身设计的原因,在信号分解过程中,存在频率混叠现象在利用小波分析进行信号提取时,这种现象是一个不容忽视的问题. 通过分析Mallat算法,找出了造成Mallat算法产生频率混叠的原因,给出了一个能有效消除频率混叠的改进算法模型\n---\nID: Retrieval:ClearHornetsClap_5\nF ig. 4 Two 1-D miensiona l pagoda decomposition process ofMa llat a lgor ithm重构算法:2fsHz, J表示分解的深度A j [f (t)]=2{∑k h (t -2k)A j+1 [f (t)]+其中: j、J意义与式(2)相同, j =J -1, J -2, …, 0; h, g为小波重构; A j、D j 意义与式(2)相同Mallat二维塔式小波变换的重构过程如图5所示\n---\nID: Retrieval:ClearHornetsClap_6\n1 Mallat算法的频率混叠现象对最高频率为的带限信号进行离散化抽样,如果抽样周期比较大,或者说抽样频率比较小,那么抽样将会导致频率相邻的2个被延拓的频谱发生叠加而互相产生影响,这种现象称为混叠[78 ]下面是一个利用Mallat算法进行信号分解的例子[912 ]\n---\nID: Retrieval:ClearHornetsClap_7\n∑k g (t -2k)D j+1 [f (t)]}, \n',
|
|
||||||
_references: {
|
|
||||||
total: 30,
|
|
||||||
chunks: [
|
|
||||||
{
|
|
||||||
chunk_id: '64fe175ac75330dd',
|
|
||||||
content_ltks:
|
|
||||||
'fig 6 the improv decomposit model of ma llat algorithm 图 6 改进 的 mallat 分解 算法 模型 caj g cdj 12 t cdj 1',
|
|
||||||
content_with_weight:
|
|
||||||
'Fig. 6 The improved decomposition model of Ma llat algorithm\n图6 改进的Mallat分解算法模型\ncaj\nG\n→cdj+1\n↑2\nT\ncdj+1',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
docnm_kwd: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
kb_id: 'fd05dba641bf11f0a713047c16ec874f',
|
|
||||||
important_kwd: [],
|
|
||||||
image_id: 'fd05dba641bf11f0a713047c16ec874f-64fe175ac75330dd',
|
|
||||||
similarity: 0.8437627020510018,
|
|
||||||
vector_similarity: 0.47920900683667284,
|
|
||||||
term_similarity: 1,
|
|
||||||
positions: [[3, 303, 500, 513, 545]],
|
|
||||||
doc_type_kwd: 'image',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chunk_id: 'dad90c5ea1b0945b',
|
|
||||||
content_ltks:
|
|
||||||
'2 mallat 算法 mallat 算法 是 st é phanmallat 将 计算机 视觉 领域 内 的 多 分辨 分析 思想 引入 到 小波 分析 中 推导 出 的 小波 分析 快速 算法 但 在 具体 使用 时 mallat 算法 是 利用 与 尺度 函数 和 小波 函数 相对 应 的 小波 低通滤波器 h h 和 小波 高通 滤波器 g g 对 信号 进行 低 通和 高 通滤波 来 具体 实现 的 为了 叙述 方便 在 此 把 尺度 函数 称为 低频 子 带 小 波函数 称为 次 高频 子 带 mallat 算法 如下',
|
|
||||||
content_with_weight:
|
|
||||||
'2 Mallat算法Mallat算法是StéPhanMallat将计算机视觉领域内的多分辨分析思想引入到小波分析中,推导出的小波分析快速算法但在具体使用时,Mallat算法是利用与尺度函数和小波函数相对应的小波低通滤波器H, h和小波高通滤波器G, g对信号进行低通和高通滤波来具体实现的为了叙述方便,在此把尺度函数称为低频子带,小波函数称为次高频子带Mallat算法如下',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
docnm_kwd: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
kb_id: 'fd05dba641bf11f0a713047c16ec874f',
|
|
||||||
important_kwd: [],
|
|
||||||
image_id: 'fd05dba641bf11f0a713047c16ec874f-dad90c5ea1b0945b',
|
|
||||||
similarity: 0.8354827046851212,
|
|
||||||
vector_similarity: 0.4516090156170707,
|
|
||||||
term_similarity: 1,
|
|
||||||
positions: [[2, 38, 267, 673, 770]],
|
|
||||||
doc_type_kwd: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chunk_id: '28df4d0c894e3201',
|
|
||||||
content_ltks:
|
|
||||||
'4 改进 算法 模型 经过 对 mallat 算法 的 深入研究 可知 mallat 算法 实现 过程 会 不可避免 地 产生 频率 混 叠 现象 可是 为什么 现在 小波 分析 的 mallat 算法 还 能 得到 广泛 的 应用 呢 这 就是 mallat 算法 的 精妙 之处 由 小波 分解 算法 和 重构 算法 可知 小波 分解 过程 是 隔 点 采样 的 过程 重构 过程 是 隔 点 插 零 的 过程 实际上 这 两个 过程 都 产生 频率 混 叠 但是 它们 产生 混 叠 的 方向 正好 相反 也就是说 分解 过程 产生 的 混 叠 又 在 重构 过程 中 得到 了 纠正 13 限于 篇幅 隔 点 插 零 产生 频率 混 叠 现象 本文 不 做 详细 的 讨论 不过 这 也 给 如何 解决 mallat 分解 算法 产生 频率 混 叠 现象 提供 了 一个 思路 在 利用 mallat 算法 对 信号 分解 得到 各 尺度 上 小波 系数 后 再 按照 重构 的 方法 重构 至 所需 的 小波 空间 系数 cdj 利用 该 重构 系数 cdj 代替 相应 尺度 上 得到 的 小波 系数 cdj 来 分析 该 尺度 上 的 信号 这样 就 能 较 好地解决 频率 混 叠 所 带来 的 影响 以 达到 预期 的 目的 本文 称 这种 算法 为 子 带 信号 重构 算法 经 改进 的 算法 模型 如 图 6 所示',
|
|
||||||
content_with_weight:
|
|
||||||
'4 改进算法模型经过对Mallat算法的深入研究可知,Mallat算法实现过程会不可避免地产生频率混叠现象可是,为什么现在小波分析的Mallat算法还能得到广泛的应用呢?这就是Mallat算法的精妙之处由小波分解算法和重构算法可知,小波分解过程是隔点采样的过程,重构过程是隔点插零的过程,实际上这两个过程都产生频率混叠,但是它们产生混叠的方向正好相反也就是说分解过程产生的混叠又在重构过程中得到了纠正[13 ]限于篇幅隔点插零产生频率混叠现象本文不做详细的讨论不过,这也给如何解决Mallat分解算法产生频率混叠现象提供了一个思路:在利用Mallat算法对信号分解,得到各尺度上小波系数后,再按照重构的方法重构至所需的小波空间系数cdj ,利用该重构系数cdj代替相应尺度上得到的小波系数cdj 来分析该尺度上的信号,这样就能较好地解决频率混叠所带来的影响,以达到预期的目的本文称这种算法为子带信号重构算法经改进的算法模型如图6所示',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
docnm_kwd: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
kb_id: 'fd05dba641bf11f0a713047c16ec874f',
|
|
||||||
important_kwd: [],
|
|
||||||
image_id: 'fd05dba641bf11f0a713047c16ec874f-28df4d0c894e3201',
|
|
||||||
similarity: 0.8322061155631969,
|
|
||||||
vector_similarity: 0.4406870518773232,
|
|
||||||
term_similarity: 1,
|
|
||||||
positions: [
|
|
||||||
[3, 285, 518, 257, 272],
|
|
||||||
[3, 282, 515, 280, 504],
|
|
||||||
],
|
|
||||||
doc_type_kwd: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chunk_id: 'e79b07acbec9eb61',
|
|
||||||
content_ltks:
|
|
||||||
'关键词 小波 分析 mallat 算法 频率 混 叠 中图 分类号 tn911 6 文献 标识码 a 文章 编号 10030972 2007 04051104reason andm eliorationmodel of gener frequenc a lias of ma llat a lgor ithmguo chaofeng l im e ilian colleg of comput scienc technolog xuchangunivers xuchang 461000 china abstract becaus of the design ofmallata lgorithm the phenomenon of frequenc alias exist in the signal decomposit process base on research and analysi ofmallat algorithm the reason thatmallat algorithm gener frequenc alias were found out and an improv modl that can elim inat effici frequenc aliasingwa given',
|
|
||||||
content_with_weight:
|
|
||||||
'关键词:小波分析;Mallat算法;频率混叠中图分类号: TN911. 6 文献标识码: A 文章编号: 10030972 (2007)04051104Reason andM eliorationModel of Generating Frequency A liasing of Ma llat A lgor ithmGUO Chaofeng, L IM e ilian(College of Computer Science &Technology, XuchangUniversity, Xuchang 461000, China)Abstract:Because of the design ofMallatA lgorithm, the phenomenon of frequency aliasing exists in the signal decomposition process. Based on research and analysis ofMallat algorithm, the reasons thatMallat algorithm generates frequency aliasing were found out, and an improved modle that can elim inate efficiently frequency aliasingwas given.',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
docnm_kwd: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
kb_id: 'fd05dba641bf11f0a713047c16ec874f',
|
|
||||||
important_kwd: [],
|
|
||||||
image_id: 'fd05dba641bf11f0a713047c16ec874f-e79b07acbec9eb61',
|
|
||||||
similarity: 0.8294912664806687,
|
|
||||||
vector_similarity: 0.43163755493556244,
|
|
||||||
term_similarity: 1,
|
|
||||||
positions: [
|
|
||||||
[1, 81, 528, 240, 251],
|
|
||||||
[1, 81, 528, 255, 266],
|
|
||||||
[1, 54, 501, 287, 302],
|
|
||||||
[1, 211, 657, 305, 317],
|
|
||||||
[1, 113, 560, 319, 331],
|
|
||||||
[1, 65, 512, 334, 395],
|
|
||||||
],
|
|
||||||
doc_type_kwd: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chunk_id: '138908de860b111c',
|
|
||||||
content_ltks:
|
|
||||||
'应用 技术 研究 mallat 算 率 混 叠 原因 及其 改进 模型 郭 超 峰 李 梅 莲 许昌 学院 计算机科学 与 技术 学院 河南 许昌 461000 摘 要 mallat 算法 由于 自身 设计 的 原因 在 信号 分解 过程 中 存在 频率 混 叠 现象 在 利用 小波 分析 进行 信号 提取 时 这种 现象 是 一个 不容忽视 的 问题 通过 分析 mallat 算法 找出 了 造成 mallat 算法 产生 频率 混 叠 的 原因 给出 了 一个 能 有效 消除 频率 混 叠 的 改进 算法 模型',
|
|
||||||
content_with_weight:
|
|
||||||
'·应用技术研究·Mallat算率混叠原因及其改进模型郭超峰,李梅莲(许昌学院计算机科学与技术学院,河南许昌461000)摘 要:Mallat算法由于自身设计的原因,在信号分解过程中,存在频率混叠现象在利用小波分析进行信号提取时,这种现象是一个不容忽视的问题. 通过分析Mallat算法,找出了造成Mallat算法产生频率混叠的原因,给出了一个能有效消除频率混叠的改进算法模型',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
docnm_kwd: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
kb_id: 'fd05dba641bf11f0a713047c16ec874f',
|
|
||||||
important_kwd: [],
|
|
||||||
image_id: 'fd05dba641bf11f0a713047c16ec874f-138908de860b111c',
|
|
||||||
similarity: 0.827624678600734,
|
|
||||||
vector_similarity: 0.4254155953357798,
|
|
||||||
term_similarity: 1,
|
|
||||||
positions: [
|
|
||||||
[1, 47, 471, 80, 92],
|
|
||||||
[1, 75, 500, 112, 138],
|
|
||||||
[1, 224, 649, 154, 168],
|
|
||||||
[1, 172, 596, 179, 190],
|
|
||||||
[1, 63, 487, 196, 237],
|
|
||||||
],
|
|
||||||
doc_type_kwd: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chunk_id: '77951868ce3d1994',
|
|
||||||
content_ltks:
|
|
||||||
'f ig 4 two 1 d miensiona l pagoda decomposit process ofma llat a lgor ithm 重构 算法 2fshz j 表示 分解 的 深度 a j f t2 k h t 2k a j 1 f t 其中 j j 意义 与 式 2 相同 j j 1 j 20 h g 为 小波 重构 a j d j 意义 与 式 2 相同 mallat 二维 塔式 小波 变换 的 重构 过程 如 图 5 所示',
|
|
||||||
content_with_weight:
|
|
||||||
'F ig. 4 Two 1-D miensiona l pagoda decomposition process ofMa llat a lgor ithm重构算法:2fsHz, J表示分解的深度A j [f (t)]=2{∑k h (t -2k)A j+1 [f (t)]+其中: j、J意义与式(2)相同, j =J -1, J -2, …, 0; h, g为小波重构; A j、D j 意义与式(2)相同Mallat二维塔式小波变换的重构过程如图5所示',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
docnm_kwd: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
kb_id: 'fd05dba641bf11f0a713047c16ec874f',
|
|
||||||
important_kwd: [],
|
|
||||||
image_id: 'fd05dba641bf11f0a713047c16ec874f-77951868ce3d1994',
|
|
||||||
similarity: 0.8263513588843328,
|
|
||||||
vector_similarity: 0.42117119628110916,
|
|
||||||
term_similarity: 1,
|
|
||||||
positions: [
|
|
||||||
[2, 284, 514, 754, 765],
|
|
||||||
[3, 285, 515, 79, 91],
|
|
||||||
[3, 57, 287, 95, 107],
|
|
||||||
[3, 38, 268, 126, 167],
|
|
||||||
],
|
|
||||||
doc_type_kwd: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chunk_id: 'b8076c8ba1598567',
|
|
||||||
content_ltks:
|
|
||||||
'1 mallat 算法 的 频率 混 叠 现象 对 最高 频率 为 的 带 限 信号 进行 离散 化 抽样 如果 抽样 周期 比较 大 或者说 抽样 频率 比较 小 那么 抽样 将 会 导致 频率 相邻 的 2 个 被 延拓 的 频谱 发生 叠加 而 互相 产生 影响 这种 现象 称为 混 叠 78 下面 是 一个 利用 mallat 算法 进行 信号 分解 的 例子 912',
|
|
||||||
content_with_weight:
|
|
||||||
'1 Mallat算法的频率混叠现象对最高频率为的带限信号进行离散化抽样,如果抽样周期比较大,或者说抽样频率比较小,那么抽样将会导致频率相邻的2个被延拓的频谱发生叠加而互相产生影响,这种现象称为混叠[78 ]下面是一个利用Mallat算法进行信号分解的例子[912 ]',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
docnm_kwd: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
kb_id: 'fd05dba641bf11f0a713047c16ec874f',
|
|
||||||
important_kwd: [],
|
|
||||||
image_id: 'fd05dba641bf11f0a713047c16ec874f-b8076c8ba1598567',
|
|
||||||
similarity: 0.8260278445075276,
|
|
||||||
vector_similarity: 0.4200928150250923,
|
|
||||||
term_similarity: 1,
|
|
||||||
positions: [
|
|
||||||
[1, 287, 516, 430, 441],
|
|
||||||
[1, 284, 513, 448, 520],
|
|
||||||
],
|
|
||||||
doc_type_kwd: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chunk_id: '537d27bca0af2c0e',
|
|
||||||
content_ltks: 'k g t 2k d j 1 f t',
|
|
||||||
content_with_weight: '∑k g (t -2k)D j+1 [f (t)]}, ',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
docnm_kwd: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
kb_id: 'fd05dba641bf11f0a713047c16ec874f',
|
|
||||||
important_kwd: [],
|
|
||||||
image_id: 'fd05dba641bf11f0a713047c16ec874f-537d27bca0af2c0e',
|
|
||||||
similarity: 0.8255620344768997,
|
|
||||||
vector_similarity: 0.4185401149229989,
|
|
||||||
term_similarity: 1,
|
|
||||||
positions: [[3, 93, 187, 112, 124]],
|
|
||||||
doc_type_kwd: 'image',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
doc_aggs: [
|
|
||||||
{
|
|
||||||
doc_name: 'Mallat算法频3333率混叠原因及其改进模型.pdf',
|
|
||||||
doc_id: 'bf60855c41d911f09504047c16ec874f',
|
|
||||||
count: 8,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
component_id: 'Retrieval:ClearHornetsClap',
|
|
||||||
error: null,
|
|
||||||
elapsed_time: null,
|
|
||||||
created_at: 1749606806,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
event: 'node_finished',
|
|
||||||
message_id: 'dce6c0c8466611f08e04047c16ec874f',
|
|
||||||
created_at: 1749606805,
|
|
||||||
task_id: 'db68eb0645ab11f0bbdc047c16ec874f',
|
|
||||||
data: {
|
|
||||||
inputs: {},
|
|
||||||
outputs: {
|
|
||||||
content: null,
|
|
||||||
structured_output: null,
|
|
||||||
_elapsed_time: 0.009871692978776991,
|
|
||||||
},
|
|
||||||
component_id: 'Agent:EvilBobcatsWish',
|
|
||||||
error: null,
|
|
||||||
elapsed_time: 0.009871692978776991,
|
|
||||||
created_at: 1749606806,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
event: 'node_finished',
|
|
||||||
message_id: 'dce6c0c8466611f08e04047c16ec874f',
|
|
||||||
created_at: 1749606805,
|
|
||||||
task_id: 'db68eb0645ab11f0bbdc047c16ec874f',
|
|
||||||
data: {
|
|
||||||
inputs: {},
|
|
||||||
outputs: {
|
|
||||||
content:
|
|
||||||
'您好,根据您提供的知识库内容,以下是关于Mallat算法的一些信息:\n\n1. **Mallat算法的定义**:\n Mallat算法是由Stéphane Mallat将计算机视觉领域的多分辨率分析思想引入到小波分析中推导出的小波分析快速算法。该算法利用与尺度函数和小波函数相对应的小波低通滤波器(记为H, h)和小波高通滤波器(记为G, g)对信号进行低通和高通滤波来实现。\n\n2. **频率混叠现象**:\n 在具体使用时,Mallat算法实现过程中不可避免地会产生频率混叠现象。这种现象是由于小波分解过程是隔点采样的过程,而重构过程是隔点插零的过程,这两个过程都可能产生频率混叠。\n\n3. **改进模型**:\n 通过对Mallat算法的深入研究,找到了造成频率混叠的原因,并提出了一个能有效消除频率混叠的改进模型。这种改进的模型被称为子带信号重构算法。具体来说,在利用Mallat算法对信号分解得到各尺度上的小波系数后,再按照重构的方法重构至所需的小波空间系数cdj,并用该重构系数代替相应尺度上得到的小波系数来分析该尺度上的信号,以较好地解决频率混叠所带来的影响。\n\n4. **应用技术研究**:\n Mallat算法由于自身设计的原因,在信号分解过程中存在频率混叠现象。通过分析Mallat算法找出了造成这种现象的原因,并给出了一个能有效消除这种现象的改进模型。\n\n这些信息提供了对Mallat算法及其相关问题和解决方案的基本理解。如果您有更具体的问题或需要进一步的信息,请随时告知!',
|
|
||||||
_elapsed_time: 0.0001981810200959444,
|
|
||||||
},
|
|
||||||
component_id: 'Message:PurpleWordsBuy',
|
|
||||||
error: null,
|
|
||||||
elapsed_time: 0.0001981810200959444,
|
|
||||||
created_at: 1749606814,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -14,12 +14,9 @@ import {
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
|
import { JsonViewer } from '../form/components/json-viewer';
|
||||||
import OperatorIcon, { SVGIconMap } from '../operator-icon';
|
import OperatorIcon, { SVGIconMap } from '../operator-icon';
|
||||||
import {
|
import { toLowerCaseStringAndDeleteChar, typeMap } from './workflow-timeline';
|
||||||
JsonViewer,
|
|
||||||
toLowerCaseStringAndDeleteChar,
|
|
||||||
typeMap,
|
|
||||||
} from './workflow-timeline';
|
|
||||||
type IToolIcon =
|
type IToolIcon =
|
||||||
| Operator.ArXiv
|
| Operator.ArXiv
|
||||||
| Operator.GitHub
|
| Operator.GitHub
|
||||||
|
|||||||
@ -24,8 +24,8 @@ import { cn } from '@/lib/utils';
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { get, isEmpty, isEqual, uniqWith } from 'lodash';
|
import { get, isEmpty, isEqual, uniqWith } from 'lodash';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import JsonView from 'react18-json-view';
|
|
||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
|
import { JsonViewer } from '../form/components/json-viewer';
|
||||||
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
||||||
import OperatorIcon from '../operator-icon';
|
import OperatorIcon from '../operator-icon';
|
||||||
import ToolTimelineItem from './tool-timeline-item';
|
import ToolTimelineItem from './tool-timeline-item';
|
||||||
@ -37,25 +37,7 @@ type LogFlowTimelineProps = Pick<
|
|||||||
sendLoading: boolean;
|
sendLoading: boolean;
|
||||||
isShare?: boolean;
|
isShare?: boolean;
|
||||||
};
|
};
|
||||||
export function JsonViewer({
|
|
||||||
data,
|
|
||||||
title,
|
|
||||||
}: {
|
|
||||||
data: Record<string, any>;
|
|
||||||
title: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<section className="space-y-2">
|
|
||||||
<div>{title}</div>
|
|
||||||
<JsonView
|
|
||||||
src={data}
|
|
||||||
displaySize
|
|
||||||
collapseStringsAfterLength={100000000000}
|
|
||||||
className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-muted"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export const typeMap = {
|
export const typeMap = {
|
||||||
begin: t('flow.logTimeline.begin'),
|
begin: t('flow.logTimeline.begin'),
|
||||||
agent: t('flow.logTimeline.agent'),
|
agent: t('flow.logTimeline.agent'),
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
NodeHandleId,
|
NodeHandleId,
|
||||||
Operator,
|
Operator,
|
||||||
TypesWithArray,
|
TypesWithArray,
|
||||||
|
WebhookSecurityAuthType,
|
||||||
} from './constant';
|
} from './constant';
|
||||||
import { BeginFormSchemaType } from './form/begin-form/schema';
|
import { BeginFormSchemaType } from './form/begin-form/schema';
|
||||||
import { DataOperationsFormSchemaType } from './form/data-operations-form';
|
import { DataOperationsFormSchemaType } from './form/data-operations-form';
|
||||||
@ -353,13 +354,20 @@ function transformRequestSchemaToJsonschema(
|
|||||||
|
|
||||||
function transformBeginParams(params: BeginFormSchemaType) {
|
function transformBeginParams(params: BeginFormSchemaType) {
|
||||||
if (params.mode === AgentDialogueMode.Webhook) {
|
if (params.mode === AgentDialogueMode.Webhook) {
|
||||||
|
const nextSecurity: Record<string, any> = {
|
||||||
|
...params.security,
|
||||||
|
ip_whitelist: params.security?.ip_whitelist.map((x) => x.value),
|
||||||
|
};
|
||||||
|
if (params.security?.auth_type === WebhookSecurityAuthType.Jwt) {
|
||||||
|
nextSecurity.jwt = {
|
||||||
|
...nextSecurity.jwt,
|
||||||
|
required_claims: nextSecurity.jwt?.required_claims.map((x) => x.value),
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...params,
|
...params,
|
||||||
schema: transformRequestSchemaToJsonschema(params.schema),
|
schema: transformRequestSchemaToJsonschema(params.schema),
|
||||||
security: {
|
security: nextSecurity,
|
||||||
...params.security,
|
|
||||||
ip_whitelist: params.security?.ip_whitelist.map((x) => x.value),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
94
web/src/pages/agent/webhook-sheet/index.tsx
Normal file
94
web/src/pages/agent/webhook-sheet/index.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { CopyToClipboardWithText } from '@/components/copy-to-clipboard';
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
} from '@/components/ui/sheet';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
import { useFetchWebhookTrace } from '@/hooks/use-agent-request';
|
||||||
|
import { MessageEventType } from '@/hooks/use-send-message';
|
||||||
|
import { IModalProps } from '@/interfaces/common';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useParams } from 'umi';
|
||||||
|
import { BeginId } from '../constant';
|
||||||
|
import { JsonViewer } from '../form/components/json-viewer';
|
||||||
|
import { WorkFlowTimeline } from './timeline';
|
||||||
|
|
||||||
|
type RunSheetProps = IModalProps<any>;
|
||||||
|
|
||||||
|
enum WebhookTraceTabType {
|
||||||
|
Detail = 'detail',
|
||||||
|
Tracing = 'tracing',
|
||||||
|
}
|
||||||
|
|
||||||
|
const WebhookSheet = ({ hideModal }: RunSheetProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { id } = useParams();
|
||||||
|
const text = `${location.protocol}//${location.host}/api/v1/webhook_test/${id}`;
|
||||||
|
|
||||||
|
const { data } = useFetchWebhookTrace(true);
|
||||||
|
|
||||||
|
const firstInput = data?.events.find(
|
||||||
|
(event) =>
|
||||||
|
event.event === MessageEventType.NodeFinished &&
|
||||||
|
event.data.component_id === BeginId,
|
||||||
|
)?.data.inputs;
|
||||||
|
|
||||||
|
const latestOutput = data?.events?.findLast(
|
||||||
|
(event) =>
|
||||||
|
event.event === MessageEventType.NodeFinished &&
|
||||||
|
event.data.component_id !== BeginId,
|
||||||
|
)?.data.outputs;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet onOpenChange={hideModal} open modal={false}>
|
||||||
|
<SheetContent className={cn('top-20 p-2 space-y-5 flex flex-col pb-20')}>
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>{t('flow.testRun')}</SheetTitle>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-sm font-medium">Webhook URL:</div>
|
||||||
|
<CopyToClipboardWithText text={text}></CopyToClipboardWithText>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div className="text-state-success">
|
||||||
|
{data?.finished ? 'SUCCESS' : 'RUNNING'}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
defaultValue={WebhookTraceTabType.Detail}
|
||||||
|
className="flex-1 min-h-0 flex flex-col"
|
||||||
|
>
|
||||||
|
<TabsList className="w-fit">
|
||||||
|
<TabsTrigger value={WebhookTraceTabType.Detail}>Detail</TabsTrigger>
|
||||||
|
<TabsTrigger value={WebhookTraceTabType.Tracing}>
|
||||||
|
Tracing
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value={WebhookTraceTabType.Detail}>
|
||||||
|
<JsonViewer data={firstInput || {}} title={'Input'}></JsonViewer>
|
||||||
|
<JsonViewer data={latestOutput || {}} title={'Output'}></JsonViewer>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value={WebhookTraceTabType.Tracing}
|
||||||
|
className="overflow-auto flex-1"
|
||||||
|
>
|
||||||
|
<WorkFlowTimeline
|
||||||
|
currentEventListWithoutMessage={data?.events || []}
|
||||||
|
canvasId={id}
|
||||||
|
currentMessageId=""
|
||||||
|
sendLoading={false}
|
||||||
|
></WorkFlowTimeline>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WebhookSheet;
|
||||||
337
web/src/pages/agent/webhook-sheet/timeline.tsx
Normal file
337
web/src/pages/agent/webhook-sheet/timeline.tsx
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
import HighLightMarkdown from '@/components/highlight-markdown';
|
||||||
|
import {
|
||||||
|
Timeline,
|
||||||
|
TimelineContent,
|
||||||
|
TimelineHeader,
|
||||||
|
TimelineIndicator,
|
||||||
|
TimelineItem,
|
||||||
|
TimelineSeparator,
|
||||||
|
} from '@/components/originui/timeline';
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from '@/components/ui/accordion';
|
||||||
|
import { useFetchMessageTrace } from '@/hooks/use-agent-request';
|
||||||
|
import {
|
||||||
|
INodeData,
|
||||||
|
INodeEvent,
|
||||||
|
MessageEventType,
|
||||||
|
} from '@/hooks/use-send-message';
|
||||||
|
import { ITraceData } from '@/interfaces/database/agent';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { get, isEmpty, isEqual, uniqWith } from 'lodash';
|
||||||
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { Operator } from '../constant';
|
||||||
|
import { JsonViewer } from '../form/components/json-viewer';
|
||||||
|
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
||||||
|
import ToolTimelineItem from '../log-sheet/tool-timeline-item';
|
||||||
|
import OperatorIcon from '../operator-icon';
|
||||||
|
type LogFlowTimelineProps = Pick<
|
||||||
|
ReturnType<typeof useCacheChatLog>,
|
||||||
|
'currentEventListWithoutMessage' | 'currentMessageId'
|
||||||
|
> & {
|
||||||
|
canvasId?: string;
|
||||||
|
sendLoading: boolean;
|
||||||
|
isShare?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const typeMap = {
|
||||||
|
begin: t('flow.logTimeline.begin'),
|
||||||
|
agent: t('flow.logTimeline.agent'),
|
||||||
|
retrieval: t('flow.logTimeline.retrieval'),
|
||||||
|
message: t('flow.logTimeline.message'),
|
||||||
|
awaitResponse: t('flow.logTimeline.awaitResponse'),
|
||||||
|
switch: t('flow.logTimeline.switch'),
|
||||||
|
iteration: t('flow.logTimeline.iteration'),
|
||||||
|
categorize: t('flow.logTimeline.categorize'),
|
||||||
|
code: t('flow.logTimeline.code'),
|
||||||
|
textProcessing: t('flow.logTimeline.textProcessing'),
|
||||||
|
tavilySearch: t('flow.logTimeline.tavilySearch'),
|
||||||
|
tavilyExtract: t('flow.logTimeline.tavilyExtract'),
|
||||||
|
exeSQL: t('flow.logTimeline.exeSQL'),
|
||||||
|
google: t('flow.logTimeline.google'),
|
||||||
|
duckDuckGo: t('flow.logTimeline.google'),
|
||||||
|
wikipedia: t('flow.logTimeline.wikipedia'),
|
||||||
|
googleScholar: t('flow.logTimeline.googleScholar'),
|
||||||
|
arXiv: t('flow.logTimeline.googleScholar'),
|
||||||
|
pubMed: t('flow.logTimeline.googleScholar'),
|
||||||
|
gitHub: t('flow.logTimeline.gitHub'),
|
||||||
|
email: t('flow.logTimeline.email'),
|
||||||
|
httpRequest: t('flow.logTimeline.httpRequest'),
|
||||||
|
wenCai: t('flow.logTimeline.wenCai'),
|
||||||
|
yahooFinance: t('flow.logTimeline.yahooFinance'),
|
||||||
|
userFillUp: t('flow.logTimeline.userFillUp'),
|
||||||
|
};
|
||||||
|
export const toLowerCaseStringAndDeleteChar = (
|
||||||
|
str: string,
|
||||||
|
char: string = '_',
|
||||||
|
) => str.toLowerCase().replace(/ /g, '').replaceAll(char, '');
|
||||||
|
|
||||||
|
// Convert all keys in typeMap to lowercase and output the new typeMap
|
||||||
|
export const typeMapLowerCase = Object.fromEntries(
|
||||||
|
Object.entries(typeMap).map(([key, value]) => [
|
||||||
|
toLowerCaseStringAndDeleteChar(key),
|
||||||
|
value,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
function getInputsOrOutputs(
|
||||||
|
nodeEventList: INodeData[],
|
||||||
|
field: 'inputs' | 'outputs',
|
||||||
|
) {
|
||||||
|
const inputsOrOutputs = nodeEventList.map((x) => get(x, field, {}));
|
||||||
|
|
||||||
|
if (inputsOrOutputs.length < 2) {
|
||||||
|
return inputsOrOutputs[0] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniqWith(inputsOrOutputs, isEqual); // TODO: Violence should not be used to
|
||||||
|
}
|
||||||
|
export const WorkFlowTimeline = ({
|
||||||
|
currentEventListWithoutMessage,
|
||||||
|
currentMessageId,
|
||||||
|
canvasId,
|
||||||
|
sendLoading,
|
||||||
|
isShare,
|
||||||
|
}: LogFlowTimelineProps) => {
|
||||||
|
// const getNode = useGraphStore((state) => state.getNode);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: traceData,
|
||||||
|
setMessageId,
|
||||||
|
setISStopFetchTrace,
|
||||||
|
} = useFetchMessageTrace(canvasId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMessageId(currentMessageId);
|
||||||
|
}, [currentMessageId, setMessageId]);
|
||||||
|
const getNodeName = (nodeId: string) => {
|
||||||
|
if ('begin' === nodeId) return t('flow.begin');
|
||||||
|
return nodeId;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setISStopFetchTrace(!sendLoading);
|
||||||
|
}, [sendLoading, setISStopFetchTrace]);
|
||||||
|
|
||||||
|
const startedNodeList = useMemo(() => {
|
||||||
|
const finish = currentEventListWithoutMessage?.some(
|
||||||
|
(item) => item.event === MessageEventType.WorkflowFinished,
|
||||||
|
);
|
||||||
|
setISStopFetchTrace(finish || !sendLoading);
|
||||||
|
const duplicateList = currentEventListWithoutMessage?.filter(
|
||||||
|
(x) => x.event === MessageEventType.NodeStarted,
|
||||||
|
) as INodeEvent[];
|
||||||
|
|
||||||
|
// Remove duplicate nodes
|
||||||
|
return duplicateList?.reduce<Array<INodeEvent>>((pre, cur) => {
|
||||||
|
if (pre.every((x) => x.data.component_id !== cur.data.component_id)) {
|
||||||
|
pre.push(cur);
|
||||||
|
}
|
||||||
|
return pre;
|
||||||
|
}, []);
|
||||||
|
}, [currentEventListWithoutMessage, sendLoading, setISStopFetchTrace]);
|
||||||
|
|
||||||
|
const getElapsedTime = (nodeId: string) => {
|
||||||
|
if (nodeId === 'begin') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const data = currentEventListWithoutMessage?.find((x) => {
|
||||||
|
return (
|
||||||
|
x.data?.component_id === nodeId &&
|
||||||
|
x.event === MessageEventType.NodeFinished
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (!data || data?.data.elapsed_time < 0.000001) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return data?.data.elapsed_time || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasTrace = useCallback(
|
||||||
|
(componentId: string) => {
|
||||||
|
if (Array.isArray(traceData)) {
|
||||||
|
return traceData?.some((x) => x.component_id === componentId);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[traceData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterTrace = useCallback(
|
||||||
|
(componentId: string) => {
|
||||||
|
const trace = traceData
|
||||||
|
?.filter((x) => x.component_id === componentId)
|
||||||
|
.reduce<ITraceData['trace']>((pre, cur) => {
|
||||||
|
pre.push(...cur.trace);
|
||||||
|
|
||||||
|
return pre;
|
||||||
|
}, []);
|
||||||
|
return Array.isArray(trace) ? trace : [{}];
|
||||||
|
},
|
||||||
|
[traceData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterFinishedNodeList = useCallback(
|
||||||
|
(componentId: string) => {
|
||||||
|
const nodeEventList = currentEventListWithoutMessage
|
||||||
|
.filter(
|
||||||
|
(x) =>
|
||||||
|
x.event === MessageEventType.NodeFinished &&
|
||||||
|
(x.data as INodeData)?.component_id === componentId,
|
||||||
|
)
|
||||||
|
.map((x) => x.data);
|
||||||
|
|
||||||
|
return nodeEventList;
|
||||||
|
},
|
||||||
|
[currentEventListWithoutMessage],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Timeline>
|
||||||
|
{startedNodeList?.map((x, idx) => {
|
||||||
|
const nodeDataList = filterFinishedNodeList(x.data.component_id);
|
||||||
|
const finishNodeIds = nodeDataList.map(
|
||||||
|
(x: INodeData) => x.component_id,
|
||||||
|
);
|
||||||
|
const inputs = getInputsOrOutputs(nodeDataList, 'inputs');
|
||||||
|
const outputs = getInputsOrOutputs(nodeDataList, 'outputs');
|
||||||
|
const nodeLabel = x.data.component_type;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TimelineItem
|
||||||
|
key={idx}
|
||||||
|
step={idx}
|
||||||
|
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8"
|
||||||
|
>
|
||||||
|
<TimelineHeader>
|
||||||
|
<TimelineSeparator
|
||||||
|
className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-6.5 top-6 bg-accent-primary"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
x.data.component_type === 'Agent'
|
||||||
|
? 'repeating-linear-gradient( to bottom, rgba(76, 164, 231, 1), rgba(76, 164, 231, 1) 5px, transparent 5px, transparent 10px'
|
||||||
|
: '',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TimelineIndicator
|
||||||
|
className={cn(
|
||||||
|
' group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 p-1 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7',
|
||||||
|
{
|
||||||
|
'border border-blue-500': finishNodeIds.includes(
|
||||||
|
x.data.component_id,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='relative after:content-[""] after:absolute after:inset-0 after:z-10 after:bg-transparent after:transition-all after:duration-300'>
|
||||||
|
<div className="absolute inset-0 z-10 flex items-center justify-center ">
|
||||||
|
<div
|
||||||
|
className={cn('rounded-full w-6 h-6', {
|
||||||
|
' border-muted-foreground border-2 border-t-transparent animate-spin ':
|
||||||
|
!finishNodeIds.includes(x.data.component_id) &&
|
||||||
|
sendLoading,
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div className="size-6 flex items-center justify-center">
|
||||||
|
<OperatorIcon
|
||||||
|
className="size-4"
|
||||||
|
name={nodeLabel as Operator}
|
||||||
|
></OperatorIcon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TimelineIndicator>
|
||||||
|
</TimelineHeader>
|
||||||
|
<TimelineContent className="text-foreground rounded-lg border mb-5">
|
||||||
|
<section key={'content_' + idx}>
|
||||||
|
<Accordion
|
||||||
|
type="single"
|
||||||
|
collapsible
|
||||||
|
className="bg-bg-card px-3"
|
||||||
|
>
|
||||||
|
<AccordionItem value={idx.toString()}>
|
||||||
|
<AccordionTrigger
|
||||||
|
hideDownIcon={isShare && !x.data?.thoughts}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<span>
|
||||||
|
{!isShare && getNodeName(x.data?.component_name)}
|
||||||
|
{isShare &&
|
||||||
|
(typeMapLowerCase[
|
||||||
|
toLowerCaseStringAndDeleteChar(
|
||||||
|
nodeLabel,
|
||||||
|
) as keyof typeof typeMap
|
||||||
|
] ??
|
||||||
|
nodeLabel)}
|
||||||
|
</span>
|
||||||
|
<span className="text-text-secondary text-xs">
|
||||||
|
{getElapsedTime(x.data.component_id)
|
||||||
|
.toString()
|
||||||
|
.slice(0, 6)}
|
||||||
|
{getElapsedTime(x.data.component_id) ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'border-background -end-1 -top-1 size-2 rounded-full',
|
||||||
|
{ 'bg-state--success': isEmpty(x.data.error) },
|
||||||
|
{ 'bg-state--error': !isEmpty(x.data.error) },
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Online</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
{!isShare && (
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{!isShare && (
|
||||||
|
<>
|
||||||
|
<JsonViewer
|
||||||
|
data={inputs}
|
||||||
|
title="Input"
|
||||||
|
></JsonViewer>
|
||||||
|
|
||||||
|
<JsonViewer
|
||||||
|
data={outputs}
|
||||||
|
title={'Output'}
|
||||||
|
></JsonViewer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
)}
|
||||||
|
{isShare && x.data?.thoughts && (
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="w-full h-[200px] break-words overflow-auto scrollbar-auto p-2 bg-muted">
|
||||||
|
<HighLightMarkdown>
|
||||||
|
{x.data.thoughts || ''}
|
||||||
|
</HighLightMarkdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
)}
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</section>
|
||||||
|
</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
{hasTrace(x.data.component_id) && (
|
||||||
|
<ToolTimelineItem
|
||||||
|
key={'tool_' + idx}
|
||||||
|
tools={filterTrace(x.data.component_id)}
|
||||||
|
sendLoading={sendLoading}
|
||||||
|
isShare={isShare}
|
||||||
|
></ToolTimelineItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Timeline>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -2,6 +2,7 @@ import {
|
|||||||
IAgentLogsRequest,
|
IAgentLogsRequest,
|
||||||
IPipeLineListRequest,
|
IPipeLineListRequest,
|
||||||
} from '@/interfaces/database/agent';
|
} from '@/interfaces/database/agent';
|
||||||
|
import { IAgentWebhookTraceRequest } from '@/interfaces/request/agent';
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { registerNextServer } from '@/utils/register-server';
|
import { registerNextServer } from '@/utils/register-server';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
@ -143,4 +144,11 @@ export const fetchPipeLineList = (params: IPipeLineListRequest) => {
|
|||||||
return request.get(api.listCanvas, { params: params });
|
return request.get(api.listCanvas, { params: params });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchWebhookTrace = (
|
||||||
|
id: string,
|
||||||
|
params: IAgentWebhookTraceRequest,
|
||||||
|
) => {
|
||||||
|
return request.get(api.fetchWebhookTrace(id), { params: params });
|
||||||
|
};
|
||||||
|
|
||||||
export default agentService;
|
export default agentService;
|
||||||
|
|||||||
@ -202,6 +202,9 @@ export default {
|
|||||||
prompt: `${api_host}/canvas/prompts`,
|
prompt: `${api_host}/canvas/prompts`,
|
||||||
cancelDataflow: (id: string) => `${api_host}/canvas/cancel/${id}`,
|
cancelDataflow: (id: string) => `${api_host}/canvas/cancel/${id}`,
|
||||||
downloadFile: `${api_host}/canvas/download`,
|
downloadFile: `${api_host}/canvas/download`,
|
||||||
|
testWebhook: (id: string) => `${ExternalApi}${api_host}/webhook_test/${id}`,
|
||||||
|
fetchWebhookTrace: (id: string) =>
|
||||||
|
`${ExternalApi}${api_host}/webhook_trace/${id}`,
|
||||||
|
|
||||||
// mcp server
|
// mcp server
|
||||||
listMcpServer: `${api_host}/mcp_server/list`,
|
listMcpServer: `${api_host}/mcp_server/list`,
|
||||||
|
|||||||
Reference in New Issue
Block a user