mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Remove useless files from the data flow #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,48 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { useCreateCategorizeFormSchema } from '../../form/categorize-form/use-form-schema';
|
|
||||||
|
|
||||||
export const useBuildCategorizeHandlePositions = ({
|
|
||||||
data,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
data: RAGFlowNodeType['data'];
|
|
||||||
}) => {
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
|
|
||||||
const FormSchema = useCreateCategorizeFormSchema();
|
|
||||||
|
|
||||||
type FormSchemaType = z.infer<typeof FormSchema>;
|
|
||||||
|
|
||||||
const items: Required<FormSchemaType['items']> = useMemo(() => {
|
|
||||||
return get(data, `form.items`, []);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const positions = useMemo(() => {
|
|
||||||
const list: Array<{
|
|
||||||
top: number;
|
|
||||||
name: string;
|
|
||||||
uuid: string;
|
|
||||||
}> &
|
|
||||||
Required<FormSchemaType['items']> = [];
|
|
||||||
|
|
||||||
items.forEach((x, idx) => {
|
|
||||||
list.push({
|
|
||||||
...x,
|
|
||||||
top: idx === 0 ? 86 : list[idx - 1].top + 8 + 24,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}, [items]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateNodeInternals(id);
|
|
||||||
}, [id, updateNodeInternals, items]);
|
|
||||||
|
|
||||||
return { positions };
|
|
||||||
};
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
import { ISwitchCondition, RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
import { SwitchElseTo } from '../../constant';
|
|
||||||
import { generateSwitchHandleText } from '../../utils';
|
|
||||||
|
|
||||||
export const useBuildSwitchHandlePositions = ({
|
|
||||||
data,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
data: RAGFlowNodeType['data'];
|
|
||||||
}) => {
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
|
|
||||||
const conditions: ISwitchCondition[] = useMemo(() => {
|
|
||||||
return get(data, 'form.conditions', []);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const positions = useMemo(() => {
|
|
||||||
const list: Array<{
|
|
||||||
text: string;
|
|
||||||
top: number;
|
|
||||||
idx: number;
|
|
||||||
condition?: ISwitchCondition;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
[...conditions, ''].forEach((x, idx) => {
|
|
||||||
let top = idx === 0 ? 53 : list[idx - 1].top + 10 + 14 + 16 + 16; // case number (Case 1) height + flex gap
|
|
||||||
if (idx >= 1) {
|
|
||||||
const previousItems = conditions[idx - 1]?.items ?? [];
|
|
||||||
if (previousItems.length > 0) {
|
|
||||||
// top += 12; // ConditionBlock padding
|
|
||||||
top += previousItems.length * 26; // condition variable height
|
|
||||||
// top += (previousItems.length - 1) * 25; // operator height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
text:
|
|
||||||
idx < conditions.length
|
|
||||||
? generateSwitchHandleText(idx)
|
|
||||||
: SwitchElseTo,
|
|
||||||
idx,
|
|
||||||
top,
|
|
||||||
condition: typeof x === 'string' ? undefined : x,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}, [conditions]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateNodeInternals(id);
|
|
||||||
}, [id, updateNodeInternals, conditions]);
|
|
||||||
|
|
||||||
return { positions };
|
|
||||||
};
|
|
||||||
@ -1,21 +1,8 @@
|
|||||||
import {
|
|
||||||
initialKeywordsSimilarityWeightValue,
|
|
||||||
initialSimilarityThresholdValue,
|
|
||||||
} from '@/components/similarity-slider';
|
|
||||||
import {
|
|
||||||
AgentGlobals,
|
|
||||||
CodeTemplateStrMap,
|
|
||||||
ProgrammingLanguage,
|
|
||||||
} from '@/constants/agent';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChatVariableEnabledField,
|
ChatVariableEnabledField,
|
||||||
variableEnabledFieldMap,
|
variableEnabledFieldMap,
|
||||||
} from '@/constants/chat';
|
} from '@/constants/chat';
|
||||||
import { ModelVariableType } from '@/constants/knowledge';
|
|
||||||
import i18n from '@/locales/config';
|
|
||||||
import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat';
|
import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat';
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Circle,
|
Circle,
|
||||||
@ -89,29 +76,6 @@ export enum ImageParseMethod {
|
|||||||
OCR = 'ocr',
|
OCR = 'ocr',
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialQueryBaseValues = {
|
|
||||||
query: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialRetrievalValues = {
|
|
||||||
query: AgentGlobals.SysQuery,
|
|
||||||
top_n: 8,
|
|
||||||
top_k: 1024,
|
|
||||||
kb_ids: [],
|
|
||||||
rerank_id: '',
|
|
||||||
empty_response: '',
|
|
||||||
...initialSimilarityThresholdValue,
|
|
||||||
...initialKeywordsSimilarityWeightValue,
|
|
||||||
use_kg: false,
|
|
||||||
cross_languages: [],
|
|
||||||
outputs: {
|
|
||||||
formalized_content: {
|
|
||||||
type: 'string',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialBeginValues = {
|
export const initialBeginValues = {
|
||||||
mode: AgentDialogueMode.Conversational,
|
mode: AgentDialogueMode.Conversational,
|
||||||
prologue: `Hi! I'm your assistant. What can I do for you?`,
|
prologue: `Hi! I'm your assistant. What can I do for you?`,
|
||||||
@ -126,175 +90,10 @@ export const variableCheckBoxFieldMap = Object.keys(
|
|||||||
return pre;
|
return pre;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const initialLlmBaseValues = {
|
|
||||||
...variableCheckBoxFieldMap,
|
|
||||||
temperature: 0.1,
|
|
||||||
top_p: 0.3,
|
|
||||||
frequency_penalty: 0.7,
|
|
||||||
presence_penalty: 0.4,
|
|
||||||
max_tokens: 256,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialGenerateValues = {
|
|
||||||
...initialLlmBaseValues,
|
|
||||||
prompt: i18n.t('flow.promptText'),
|
|
||||||
cite: true,
|
|
||||||
message_history_window_size: 12,
|
|
||||||
parameters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialRewriteQuestionValues = {
|
|
||||||
...initialLlmBaseValues,
|
|
||||||
language: '',
|
|
||||||
message_history_window_size: 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialRelevantValues = {
|
|
||||||
...initialLlmBaseValues,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialCategorizeValues = {
|
|
||||||
...initialLlmBaseValues,
|
|
||||||
query: AgentGlobals.SysQuery,
|
|
||||||
parameter: ModelVariableType.Precise,
|
|
||||||
message_history_window_size: 1,
|
|
||||||
items: [],
|
|
||||||
outputs: {
|
|
||||||
category_name: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialMessageValues = {
|
|
||||||
content: [''],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialKeywordExtractValues = {
|
|
||||||
...initialLlmBaseValues,
|
|
||||||
top_n: 3,
|
|
||||||
...initialQueryBaseValues,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialExeSqlValues = {
|
|
||||||
sql: '',
|
|
||||||
db_type: 'mysql',
|
|
||||||
database: '',
|
|
||||||
username: '',
|
|
||||||
host: '',
|
|
||||||
port: 3306,
|
|
||||||
password: '',
|
|
||||||
max_records: 1024,
|
|
||||||
outputs: {
|
|
||||||
formalized_content: {
|
|
||||||
value: '',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
json: {
|
|
||||||
value: [],
|
|
||||||
type: 'Array<Object>',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialSwitchValues = {
|
|
||||||
conditions: [
|
|
||||||
{
|
|
||||||
logical_operator: SwitchLogicOperatorOptions[0],
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
operator: SwitchOperatorOptions[0].value,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
to: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[SwitchElseTo]: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialConcentratorValues = {};
|
|
||||||
|
|
||||||
export const initialNoteValues = {
|
export const initialNoteValues = {
|
||||||
text: '',
|
text: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialCrawlerValues = {
|
|
||||||
extract_type: 'markdown',
|
|
||||||
query: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialInvokeValues = {
|
|
||||||
url: '',
|
|
||||||
method: 'GET',
|
|
||||||
timeout: 60,
|
|
||||||
headers: `{
|
|
||||||
"Accept": "*/*",
|
|
||||||
"Cache-Control": "no-cache",
|
|
||||||
"Connection": "keep-alive"
|
|
||||||
}`,
|
|
||||||
proxy: '',
|
|
||||||
clean_html: false,
|
|
||||||
variables: [],
|
|
||||||
outputs: {
|
|
||||||
result: {
|
|
||||||
value: '',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialTemplateValues = {
|
|
||||||
content: '',
|
|
||||||
parameters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialEmailValues = {
|
|
||||||
smtp_server: '',
|
|
||||||
smtp_port: 465,
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
sender_name: '',
|
|
||||||
to_email: '',
|
|
||||||
cc_email: '',
|
|
||||||
subject: '',
|
|
||||||
content: '',
|
|
||||||
outputs: {
|
|
||||||
success: {
|
|
||||||
value: true,
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialIterationValues = {
|
|
||||||
items_ref: '',
|
|
||||||
outputs: {},
|
|
||||||
};
|
|
||||||
export const initialIterationStartValues = {
|
|
||||||
outputs: {
|
|
||||||
item: {
|
|
||||||
type: 'unkown',
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: 'integer',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialCodeValues = {
|
|
||||||
lang: ProgrammingLanguage.Python,
|
|
||||||
script: CodeTemplateStrMap[ProgrammingLanguage.Python],
|
|
||||||
arguments: {
|
|
||||||
arg1: '',
|
|
||||||
arg2: '',
|
|
||||||
},
|
|
||||||
outputs: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialWaitingDialogueValues = {};
|
|
||||||
|
|
||||||
export const initialChunkerValues = { outputs: {} };
|
|
||||||
|
|
||||||
export const initialTokenizerValues = {
|
export const initialTokenizerValues = {
|
||||||
search_method: [
|
search_method: [
|
||||||
TokenizerSearchMethod.Embedding,
|
TokenizerSearchMethod.Embedding,
|
||||||
@ -305,47 +104,6 @@ export const initialTokenizerValues = {
|
|||||||
outputs: {},
|
outputs: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialAgentValues = {
|
|
||||||
...initialLlmBaseValues,
|
|
||||||
description: '',
|
|
||||||
user_prompt: '',
|
|
||||||
sys_prompt: t('flow.sysPromptDefultValue'),
|
|
||||||
prompts: [{ role: PromptRole.User, content: `{${AgentGlobals.SysQuery}}` }],
|
|
||||||
message_history_window_size: 12,
|
|
||||||
max_retries: 3,
|
|
||||||
delay_after_error: 1,
|
|
||||||
visual_files_var: '',
|
|
||||||
max_rounds: 1,
|
|
||||||
exception_method: '',
|
|
||||||
exception_goto: [],
|
|
||||||
exception_default_value: '',
|
|
||||||
tools: [],
|
|
||||||
mcp: [],
|
|
||||||
cite: true,
|
|
||||||
outputs: {
|
|
||||||
// structured_output: {
|
|
||||||
// topic: {
|
|
||||||
// type: 'string',
|
|
||||||
// description:
|
|
||||||
// 'default:general. The category of the search.news is useful for retrieving real-time updates, particularly about politics, sports, and major current events covered by mainstream media sources. general is for broader, more general-purpose searches that may include a wide range of sources.',
|
|
||||||
// enum: ['general', 'news'],
|
|
||||||
// default: 'general',
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
content: {
|
|
||||||
type: 'string',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialUserFillUpValues = {
|
|
||||||
enable_tips: true,
|
|
||||||
tips: '',
|
|
||||||
inputs: [],
|
|
||||||
outputs: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum StringTransformMethod {
|
export enum StringTransformMethod {
|
||||||
Merge = 'merge',
|
Merge = 'merge',
|
||||||
Split = 'split',
|
Split = 'split',
|
||||||
@ -360,18 +118,6 @@ export enum StringTransformDelimiter {
|
|||||||
Space = ' ',
|
Space = ' ',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialStringTransformValues = {
|
|
||||||
method: StringTransformMethod.Merge,
|
|
||||||
split_ref: '',
|
|
||||||
script: '',
|
|
||||||
delimiters: [StringTransformDelimiter.Comma],
|
|
||||||
outputs: {
|
|
||||||
result: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialParserValues = { outputs: {}, setups: [] };
|
export const initialParserValues = { outputs: {}, setups: [] };
|
||||||
|
|
||||||
export const initialSplitterValues = {
|
export const initialSplitterValues = {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
import BeginForm from '../form/begin-form';
|
|
||||||
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
||||||
import ParserForm from '../form/parser-form';
|
import ParserForm from '../form/parser-form';
|
||||||
import SplitterForm from '../form/splitter-form';
|
import SplitterForm from '../form/splitter-form';
|
||||||
@ -7,7 +6,7 @@ import TokenizerForm from '../form/tokenizer-form';
|
|||||||
|
|
||||||
export const FormConfigMap = {
|
export const FormConfigMap = {
|
||||||
[Operator.Begin]: {
|
[Operator.Begin]: {
|
||||||
component: BeginForm,
|
component: () => <></>,
|
||||||
},
|
},
|
||||||
[Operator.Note]: {
|
[Operator.Note]: {
|
||||||
component: () => <></>,
|
component: () => <></>,
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { AgentFormContext } from '../context';
|
|||||||
import { useHandleNodeNameChange } from '../hooks/use-change-node-name';
|
import { useHandleNodeNameChange } from '../hooks/use-change-node-name';
|
||||||
import OperatorIcon from '../operator-icon';
|
import OperatorIcon from '../operator-icon';
|
||||||
import { FormConfigMap } from './form-config-map';
|
import { FormConfigMap } from './form-config-map';
|
||||||
import SingleDebugSheet from './single-debug-sheet';
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node?: RAGFlowNodeType;
|
node?: RAGFlowNodeType;
|
||||||
@ -31,9 +30,7 @@ const FormSheet = ({
|
|||||||
visible,
|
visible,
|
||||||
hideModal,
|
hideModal,
|
||||||
node,
|
node,
|
||||||
singleDebugDrawerVisible,
|
|
||||||
chatVisible,
|
chatVisible,
|
||||||
hideSingleDebugDrawer,
|
|
||||||
}: IModalProps<any> & IProps) => {
|
}: IModalProps<any> & IProps) => {
|
||||||
const operatorName: Operator = node?.data.label as Operator;
|
const operatorName: Operator = node?.data.label as Operator;
|
||||||
// const clickedToolId = useGraphStore((state) => state.clickedToolId);
|
// const clickedToolId = useGraphStore((state) => state.clickedToolId);
|
||||||
@ -94,13 +91,6 @@ const FormSheet = ({
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
{singleDebugDrawerVisible && (
|
|
||||||
<SingleDebugSheet
|
|
||||||
visible={singleDebugDrawerVisible}
|
|
||||||
hideModal={hideSingleDebugDrawer}
|
|
||||||
componentId={node?.id}
|
|
||||||
></SingleDebugSheet>
|
|
||||||
)}
|
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,89 +0,0 @@
|
|||||||
import CopyToClipboard from '@/components/copy-to-clipboard';
|
|
||||||
import { Sheet, SheetContent, SheetHeader } from '@/components/ui/sheet';
|
|
||||||
import { useDebugSingle, useFetchInputForm } from '@/hooks/use-agent-request';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import JsonView from 'react18-json-view';
|
|
||||||
import 'react18-json-view/src/style.css';
|
|
||||||
import DebugContent from '../../debug-content';
|
|
||||||
import { transferInputsArrayToObject } from '../../form/begin-form/use-watch-change';
|
|
||||||
import { buildBeginInputListFromObject } from '../../form/begin-form/utils';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
componentId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SingleDebugSheet = ({
|
|
||||||
componentId,
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
}: IModalProps<any> & IProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const inputForm = useFetchInputForm(componentId);
|
|
||||||
const { debugSingle, data, loading } = useDebugSingle();
|
|
||||||
|
|
||||||
const list = useMemo(() => {
|
|
||||||
return buildBeginInputListFromObject(inputForm);
|
|
||||||
}, [inputForm]);
|
|
||||||
|
|
||||||
const onOk = useCallback(
|
|
||||||
(nextValues: any[]) => {
|
|
||||||
if (componentId) {
|
|
||||||
debugSingle({
|
|
||||||
component_id: componentId,
|
|
||||||
params: transferInputsArrayToObject(nextValues),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[componentId, debugSingle],
|
|
||||||
);
|
|
||||||
|
|
||||||
const content = JSON.stringify(data, null, 2);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sheet open={visible} modal={false}>
|
|
||||||
<SheetContent className="top-20 p-0" closeIcon={false}>
|
|
||||||
<SheetHeader className="py-2 px-5">
|
|
||||||
<div className="flex justify-between ">
|
|
||||||
{t('flow.testRun')}
|
|
||||||
<X onClick={hideModal} className="cursor-pointer" />
|
|
||||||
</div>
|
|
||||||
</SheetHeader>
|
|
||||||
<section className="overflow-y-auto pt-4 px-5">
|
|
||||||
<DebugContent
|
|
||||||
parameters={list}
|
|
||||||
ok={onOk}
|
|
||||||
isNext={false}
|
|
||||||
loading={loading}
|
|
||||||
submitButtonDisabled={list.length === 0}
|
|
||||||
></DebugContent>
|
|
||||||
{!isEmpty(data) ? (
|
|
||||||
<div
|
|
||||||
className={cn('mt-4 rounded-md border', {
|
|
||||||
[`border-state-error`]: !isEmpty(data._ERROR),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between p-2">
|
|
||||||
<span>JSON</span>
|
|
||||||
<CopyToClipboard text={content}></CopyToClipboard>
|
|
||||||
</div>
|
|
||||||
<JsonView
|
|
||||||
src={data}
|
|
||||||
displaySize
|
|
||||||
collapseStringsAfterLength={100000000000}
|
|
||||||
className="w-full h-[800px] break-words overflow-auto p-2"
|
|
||||||
dark
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</section>
|
|
||||||
</SheetContent>
|
|
||||||
</Sheet>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SingleDebugSheet;
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export function BeginDynamicOptions() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const form = useFormContext();
|
|
||||||
const name = 'options';
|
|
||||||
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
|
||||||
name: name,
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-5">
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
const typeField = `${name}.${index}.value`;
|
|
||||||
return (
|
|
||||||
<div key={field.id} className="flex items-center gap-2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={typeField}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder={t('common.pleaseInput')}
|
|
||||||
></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button variant={'ghost'} onClick={() => remove(index)}>
|
|
||||||
<X className="text-text-sub-title-invert " />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<BlockButton onClick={() => append({ value: '' })} type="button">
|
|
||||||
{t('flow.addField')}
|
|
||||||
</BlockButton>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,205 +0,0 @@
|
|||||||
import { Collapse } from '@/components/collapse';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import { FormTooltip } from '@/components/ui/tooltip';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { Plus } from 'lucide-react';
|
|
||||||
import { memo, useEffect, useRef } from 'react';
|
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { AgentDialogueMode } from '../../constant';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { ParameterDialog } from './parameter-dialog';
|
|
||||||
import { QueryTable } from './query-table';
|
|
||||||
import { useEditQueryRecord } from './use-edit-query';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
|
||||||
|
|
||||||
const ModeOptions = [
|
|
||||||
{ value: AgentDialogueMode.Conversational, label: t('flow.conversational') },
|
|
||||||
{ value: AgentDialogueMode.Task, label: t('flow.task') },
|
|
||||||
];
|
|
||||||
|
|
||||||
function BeginForm({ node }: INextOperatorForm) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
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({
|
|
||||||
defaultValues: values,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
const inputs = useWatch({ control: form.control, name: 'inputs' });
|
|
||||||
const mode = useWatch({ control: form.control, name: 'mode' });
|
|
||||||
|
|
||||||
const enablePrologue = useWatch({
|
|
||||||
control: form.control,
|
|
||||||
name: 'enablePrologue',
|
|
||||||
});
|
|
||||||
|
|
||||||
const previousModeRef = useRef(mode);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
previousModeRef.current === AgentDialogueMode.Task &&
|
|
||||||
mode === AgentDialogueMode.Conversational
|
|
||||||
) {
|
|
||||||
form.setValue('enablePrologue', true);
|
|
||||||
}
|
|
||||||
previousModeRef.current = mode;
|
|
||||||
}, [mode, form]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
ok,
|
|
||||||
currentRecord,
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
showModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
handleDeleteRecord,
|
|
||||||
} = useEditQueryRecord({
|
|
||||||
form,
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="px-5 space-y-5">
|
|
||||||
<Form {...form}>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'mode'}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('flow.modeTip')}>
|
|
||||||
{t('flow.mode')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
options={ModeOptions}
|
|
||||||
{...field}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{mode === AgentDialogueMode.Conversational && (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'enablePrologue'}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('flow.openingSwitchTip')}>
|
|
||||||
{t('flow.openingSwitch')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{mode === AgentDialogueMode.Conversational && enablePrologue && (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'prologue'}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('chat.setAnOpenerTip')}>
|
|
||||||
{t('flow.openingCopy')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea
|
|
||||||
rows={5}
|
|
||||||
{...field}
|
|
||||||
placeholder={t('common.pleaseInput')}
|
|
||||||
></Textarea>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* Create a hidden field to make Form instance record this */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'inputs'}
|
|
||||||
render={() => <div></div>}
|
|
||||||
/>
|
|
||||||
<Collapse
|
|
||||||
title={
|
|
||||||
<div>
|
|
||||||
{t('flow.input')}
|
|
||||||
<FormTooltip tooltip={t('flow.beginInputTip')}></FormTooltip>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
rightContent={
|
|
||||||
<Button
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
showModal();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<QueryTable
|
|
||||||
data={inputs}
|
|
||||||
showModal={showModal}
|
|
||||||
deleteRecord={handleDeleteRecord}
|
|
||||||
></QueryTable>
|
|
||||||
</Collapse>
|
|
||||||
{visible && (
|
|
||||||
<ParameterDialog
|
|
||||||
hideModal={hideModal}
|
|
||||||
initialValue={currentRecord}
|
|
||||||
otherThanCurrentQuery={otherThanCurrentQuery}
|
|
||||||
submit={ok}
|
|
||||||
></ParameterDialog>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(BeginForm);
|
|
||||||
@ -1,226 +0,0 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { ChangeEvent, useEffect, useMemo } from 'react';
|
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { BeginQueryType, BeginQueryTypeIconMap } from '../../constant';
|
|
||||||
import { BeginQuery } from '../../interface';
|
|
||||||
import { BeginDynamicOptions } from './begin-dynamic-options';
|
|
||||||
|
|
||||||
type ModalFormProps = {
|
|
||||||
initialValue: BeginQuery;
|
|
||||||
otherThanCurrentQuery: BeginQuery[];
|
|
||||||
submit(values: any): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormId = 'BeginParameterForm';
|
|
||||||
|
|
||||||
function ParameterForm({
|
|
||||||
initialValue,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
submit,
|
|
||||||
}: ModalFormProps) {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const FormSchema = z.object({
|
|
||||||
type: z.string(),
|
|
||||||
key: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1)
|
|
||||||
.refine(
|
|
||||||
(value) =>
|
|
||||||
!value || !otherThanCurrentQuery.some((x) => x.key === value),
|
|
||||||
{ message: 'The key cannot be repeated!' },
|
|
||||||
),
|
|
||||||
optional: z.boolean(),
|
|
||||||
name: z.string().trim().min(1),
|
|
||||||
options: z
|
|
||||||
.array(z.object({ value: z.string().or(z.boolean()).or(z.number()) }))
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
mode: 'onChange',
|
|
||||||
defaultValues: {
|
|
||||||
type: BeginQueryType.Line,
|
|
||||||
optional: false,
|
|
||||||
key: '',
|
|
||||||
name: '',
|
|
||||||
options: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return Object.values(BeginQueryType).reduce<RAGFlowSelectOptionType[]>(
|
|
||||||
(pre, cur) => {
|
|
||||||
const Icon = BeginQueryTypeIconMap[cur];
|
|
||||||
|
|
||||||
return [
|
|
||||||
...pre,
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Icon
|
|
||||||
className={`size-${cur === BeginQueryType.Options ? 4 : 5}`}
|
|
||||||
></Icon>
|
|
||||||
{t(cur.toLowerCase())}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
value: cur,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const type = useWatch({
|
|
||||||
control: form.control,
|
|
||||||
name: 'type',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isEmpty(initialValue)) {
|
|
||||||
form.reset({
|
|
||||||
...initialValue,
|
|
||||||
options: initialValue.options?.map((x) => ({ value: x })),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [form, initialValue]);
|
|
||||||
|
|
||||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
|
||||||
const values = { ...data, options: data.options?.map((x) => x.value) };
|
|
||||||
console.log('🚀 ~ onSubmit ~ values:', values);
|
|
||||||
|
|
||||||
submit(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const name = form.getValues().name || '';
|
|
||||||
form.setValue('key', e.target.value.trim());
|
|
||||||
if (!name) {
|
|
||||||
form.setValue('name', e.target.value.trim());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
id={FormId}
|
|
||||||
className="space-y-5"
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
name="type"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('type')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect {...field} options={options} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
name="key"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('key')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} autoComplete="off" onBlur={handleKeyChange} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
name="name"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('name')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
name="optional"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('optional')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{type === BeginQueryType.Options && (
|
|
||||||
<BeginDynamicOptions></BeginDynamicOptions>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ParameterDialog({
|
|
||||||
initialValue,
|
|
||||||
hideModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
submit,
|
|
||||||
}: ModalFormProps & IModalProps<BeginQuery>) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open onOpenChange={hideModal}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>{t('flow.variableSettings')}</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<ParameterForm
|
|
||||||
initialValue={initialValue}
|
|
||||||
otherThanCurrentQuery={otherThanCurrentQuery}
|
|
||||||
submit={submit}
|
|
||||||
></ParameterForm>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button type="submit" form={FormId}>
|
|
||||||
{t('modal.okText')}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from '@tanstack/react-table';
|
|
||||||
import { Pencil, Trash2 } from 'lucide-react';
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { TableEmpty } from '@/components/table-skeleton';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table';
|
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@/components/ui/tooltip';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { BeginQuery } from '../../interface';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
data: BeginQuery[];
|
|
||||||
deleteRecord(index: number): void;
|
|
||||||
showModal(index: number, record: BeginQuery): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
|
|
||||||
const columns: ColumnDef<BeginQuery>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: 'key',
|
|
||||||
header: t('flow.key'),
|
|
||||||
meta: { cellClassName: 'max-w-30' },
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const key: string = row.getValue('key');
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div className="truncate ">{key}</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{key}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'name',
|
|
||||||
header: t('flow.name'),
|
|
||||||
meta: { cellClassName: 'max-w-30' },
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const name: string = row.getValue('name');
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div className="truncate">{name}</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{name}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'type',
|
|
||||||
header: t('flow.type'),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div>
|
|
||||||
{t(`flow.${(row.getValue('type')?.toString() || '').toLowerCase()}`)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'optional',
|
|
||||||
header: t('flow.optional'),
|
|
||||||
cell: ({ row }) => <div>{row.getValue('optional') ? 'Yes' : 'No'}</div>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'actions',
|
|
||||||
enableHiding: false,
|
|
||||||
header: t('common.action'),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const record = row.original;
|
|
||||||
const idx = row.index;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
className="bg-transparent text-foreground hover:bg-muted-foreground hover:text-foreground"
|
|
||||||
onClick={() => showModal(idx, record)}
|
|
||||||
>
|
|
||||||
<Pencil />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className="bg-transparent text-foreground hover:bg-muted-foreground hover:text-foreground"
|
|
||||||
onClick={() => deleteRecord(idx)}
|
|
||||||
>
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="rounded-md border">
|
|
||||||
<Table rootClassName="rounded-md">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id}>
|
|
||||||
{headerGroup.headers.map((header) => {
|
|
||||||
return (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext(),
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && 'selected'}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell
|
|
||||||
key={cell.id}
|
|
||||||
className={cn(cell.column.columnDef.meta?.cellClassName)}
|
|
||||||
>
|
|
||||||
{flexRender(
|
|
||||||
cell.column.columnDef.cell,
|
|
||||||
cell.getContext(),
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableEmpty columnsLength={columns.length}></TableEmpty>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import { BeginQuery, INextOperatorForm } from '../../interface';
|
|
||||||
|
|
||||||
export const useEditQueryRecord = ({
|
|
||||||
form,
|
|
||||||
}: INextOperatorForm & { form: UseFormReturn }) => {
|
|
||||||
const { setRecord, currentRecord } = useSetSelectedRecord<BeginQuery>();
|
|
||||||
const { visible, hideModal, showModal } = useSetModalState();
|
|
||||||
const [index, setIndex] = useState(-1);
|
|
||||||
const inputs: BeginQuery[] = useWatch({
|
|
||||||
control: form.control,
|
|
||||||
name: 'inputs',
|
|
||||||
});
|
|
||||||
|
|
||||||
const otherThanCurrentQuery = useMemo(() => {
|
|
||||||
return inputs.filter((item, idx) => idx !== index);
|
|
||||||
}, [index, inputs]);
|
|
||||||
|
|
||||||
const handleEditRecord = useCallback(
|
|
||||||
(record: BeginQuery) => {
|
|
||||||
const inputs: BeginQuery[] = form?.getValues('inputs') || [];
|
|
||||||
|
|
||||||
const nextQuery: BeginQuery[] =
|
|
||||||
index > -1 ? inputs.toSpliced(index, 1, record) : [...inputs, record];
|
|
||||||
|
|
||||||
form.setValue('inputs', nextQuery);
|
|
||||||
|
|
||||||
hideModal();
|
|
||||||
},
|
|
||||||
[form, hideModal, index],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleShowModal = useCallback(
|
|
||||||
(idx?: number, record?: BeginQuery) => {
|
|
||||||
setIndex(idx ?? -1);
|
|
||||||
setRecord(record ?? ({} as BeginQuery));
|
|
||||||
showModal();
|
|
||||||
},
|
|
||||||
[setRecord, showModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDeleteRecord = useCallback(
|
|
||||||
(idx: number) => {
|
|
||||||
const inputs = form?.getValues('inputs') || [];
|
|
||||||
const nextInputs = inputs.filter(
|
|
||||||
(item: BeginQuery, index: number) => index !== idx,
|
|
||||||
);
|
|
||||||
|
|
||||||
form.setValue('inputs', nextInputs);
|
|
||||||
},
|
|
||||||
[form],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: handleEditRecord,
|
|
||||||
currentRecord,
|
|
||||||
setRecord,
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
showModal: handleShowModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
handleDeleteRecord,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { AgentDialogueMode } from '../../constant';
|
|
||||||
import { buildBeginInputListFromObject } from './utils';
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const defaultValues = useMemo(
|
|
||||||
() => ({
|
|
||||||
enablePrologue: true,
|
|
||||||
prologue: t('chat.setAnOpenerInitial'),
|
|
||||||
mode: AgentDialogueMode.Conversational,
|
|
||||||
inputs: [],
|
|
||||||
}),
|
|
||||||
[t],
|
|
||||||
);
|
|
||||||
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return defaultValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputs = buildBeginInputListFromObject(formData?.inputs);
|
|
||||||
|
|
||||||
return { ...(formData || {}), inputs };
|
|
||||||
}, [defaultValues, node?.data?.form]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { omit } from 'lodash';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import { BeginQuery } from '../../interface';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export function transferInputsArrayToObject(inputs: BeginQuery[] = []) {
|
|
||||||
return inputs.reduce<Record<string, Omit<BeginQuery, 'key'>>>((pre, cur) => {
|
|
||||||
pre[cur.key] = omit(cur, 'key');
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWatchFormChange(id?: string, form?: UseFormReturn) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (id) {
|
|
||||||
values = form?.getValues() || {};
|
|
||||||
|
|
||||||
const nextValues = {
|
|
||||||
...values,
|
|
||||||
inputs: transferInputsArrayToObject(values.inputs),
|
|
||||||
};
|
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
|
||||||
}
|
|
||||||
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import { BeginQuery } from '../../interface';
|
|
||||||
|
|
||||||
export function buildBeginInputListFromObject(
|
|
||||||
inputs: Record<string, Omit<BeginQuery, 'key'>>,
|
|
||||||
) {
|
|
||||||
return Object.entries(inputs || {}).reduce<BeginQuery[]>(
|
|
||||||
(pre, [key, value]) => {
|
|
||||||
pre.push({ ...(value || {}), key });
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { useFormContext } from 'react-hook-form';
|
|
||||||
|
|
||||||
interface IApiKeyFieldProps {
|
|
||||||
placeholder?: string;
|
|
||||||
}
|
|
||||||
export function ApiKeyField({ placeholder }: IApiKeyFieldProps) {
|
|
||||||
const form = useFormContext();
|
|
||||||
return (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="api_key"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.apiKey')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input type="password" {...field} placeholder={placeholder}></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { useFormContext } from 'react-hook-form';
|
|
||||||
|
|
||||||
export function DescriptionField() {
|
|
||||||
const form = useFormContext();
|
|
||||||
return (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`description`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('flow.description')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea {...field}></Textarea>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Collapse, Flex, Form, Input, Select } from 'antd';
|
|
||||||
import { PropsWithChildren, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useBuildVariableOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VariableType {
|
|
||||||
Reference = 'reference',
|
|
||||||
Input = 'input',
|
|
||||||
}
|
|
||||||
|
|
||||||
const getVariableName = (type: string) =>
|
|
||||||
type === VariableType.Reference ? 'component_id' : 'value';
|
|
||||||
|
|
||||||
const DynamicVariableForm = ({ node }: IProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const valueOptions = useBuildVariableOptions(node?.id, node?.parentId);
|
|
||||||
const form = Form.useFormInstance();
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
{ value: VariableType.Reference, label: t('flow.reference') },
|
|
||||||
{ value: VariableType.Input, label: t('flow.text') },
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleTypeChange = useCallback(
|
|
||||||
(name: number) => () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
form.setFieldValue(['query', name, 'component_id'], undefined);
|
|
||||||
form.setFieldValue(['query', name, 'value'], undefined);
|
|
||||||
}, 0);
|
|
||||||
},
|
|
||||||
[form],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.List name="query">
|
|
||||||
{(fields, { add, remove }) => (
|
|
||||||
<>
|
|
||||||
{fields.map(({ key, name, ...restField }) => (
|
|
||||||
<Flex key={key} gap={10} align={'baseline'}>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'type']}
|
|
||||||
className={styles.variableType}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
options={options}
|
|
||||||
onChange={handleTypeChange(name)}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item noStyle dependencies={[name, 'type']}>
|
|
||||||
{({ getFieldValue }) => {
|
|
||||||
const type = getFieldValue(['query', name, 'type']);
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, getVariableName(type)]}
|
|
||||||
className={styles.variableValue}
|
|
||||||
>
|
|
||||||
{type === VariableType.Reference ? (
|
|
||||||
<Select
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
options={valueOptions}
|
|
||||||
></Select>
|
|
||||||
) : (
|
|
||||||
<Input placeholder={t('common.pleaseInput')} />
|
|
||||||
)}
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
|
||||||
</Flex>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => add({ type: VariableType.Reference })}
|
|
||||||
block
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
className={styles.addButton}
|
|
||||||
>
|
|
||||||
{t('flow.addVariable')}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function FormCollapse({
|
|
||||||
children,
|
|
||||||
title,
|
|
||||||
}: PropsWithChildren<{ title: string }>) {
|
|
||||||
return (
|
|
||||||
<Collapse
|
|
||||||
className={styles.dynamicInputVariable}
|
|
||||||
defaultActiveKey={['1']}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: <span className={styles.title}>{title}</span>,
|
|
||||||
children,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const DynamicInputVariable = ({ node }: IProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<FormCollapse title={t('flow.input')}>
|
|
||||||
<DynamicVariableForm node={node}></DynamicVariableForm>
|
|
||||||
</FormCollapse>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DynamicInputVariable;
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { SideDown } from '@/assets/icon/next-icon';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from '@/components/ui/collapsible';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { Plus, Trash2 } from 'lucide-react';
|
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useBuildVariableOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VariableType {
|
|
||||||
Reference = 'reference',
|
|
||||||
Input = 'input',
|
|
||||||
}
|
|
||||||
|
|
||||||
const getVariableName = (type: string) =>
|
|
||||||
type === VariableType.Reference ? 'component_id' : 'value';
|
|
||||||
|
|
||||||
export function DynamicVariableForm({ node }: IProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const form = useFormContext();
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
|
||||||
name: 'query',
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
const valueOptions = useBuildVariableOptions(node?.id, node?.parentId);
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
{ value: VariableType.Reference, label: t('flow.reference') },
|
|
||||||
{ value: VariableType.Input, label: t('flow.text') },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
const typeField = `query.${index}.type`;
|
|
||||||
const typeValue = form.watch(typeField);
|
|
||||||
return (
|
|
||||||
<div key={field.id} className="flex items-center gap-1">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={typeField}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="w-2/5">
|
|
||||||
<FormDescription />
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
{...field}
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
options={options}
|
|
||||||
onChange={(val) => {
|
|
||||||
field.onChange(val);
|
|
||||||
form.resetField(`query.${index}.value`);
|
|
||||||
form.resetField(`query.${index}.component_id`);
|
|
||||||
}}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`query.${index}.${getVariableName(typeValue)}`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormDescription />
|
|
||||||
<FormControl>
|
|
||||||
{typeValue === VariableType.Reference ? (
|
|
||||||
<RAGFlowSelect
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
{...field}
|
|
||||||
options={valueOptions}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
) : (
|
|
||||||
<Input placeholder={t('common.pleaseInput')} {...field} />
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Trash2
|
|
||||||
className="cursor-pointer mx-3 size-4 text-colors-text-functional-danger"
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<Button onClick={append} className="mt-4" variant={'outline'} size={'sm'}>
|
|
||||||
<Plus />
|
|
||||||
{t('flow.addVariable')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DynamicInputVariable({ node }: IProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Collapsible defaultOpen className="group/collapsible">
|
|
||||||
<CollapsibleTrigger className="flex justify-between w-full pb-2">
|
|
||||||
<span className="font-bold text-2xl text-colors-text-neutral-strong">
|
|
||||||
{t('flow.input')}
|
|
||||||
</span>
|
|
||||||
<Button variant={'icon'} size={'icon'}>
|
|
||||||
<SideDown />
|
|
||||||
</Button>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<DynamicVariableForm node={node}></DynamicVariableForm>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export const ProgrammaticTag = 'programmatic';
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
.typeahead-popover {
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3);
|
|
||||||
border-radius: 8px;
|
|
||||||
position: fixed;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover ul::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover ul {
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover ul li {
|
|
||||||
margin: 0;
|
|
||||||
min-width: 180px;
|
|
||||||
font-size: 14px;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover ul li.selected {
|
|
||||||
background: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover li {
|
|
||||||
margin: 0 8px 0 8px;
|
|
||||||
color: #050505;
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 16px;
|
|
||||||
font-size: 15px;
|
|
||||||
display: flex;
|
|
||||||
align-content: center;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover li.active {
|
|
||||||
display: flex;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background-size: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover li .text {
|
|
||||||
display: flex;
|
|
||||||
line-height: 20px;
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeahead-popover li .icon {
|
|
||||||
display: flex;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
user-select: none;
|
|
||||||
margin-right: 8px;
|
|
||||||
line-height: 16px;
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
@ -1,181 +0,0 @@
|
|||||||
import { CodeHighlightNode, CodeNode } from '@lexical/code';
|
|
||||||
import {
|
|
||||||
InitialConfigType,
|
|
||||||
LexicalComposer,
|
|
||||||
} from '@lexical/react/LexicalComposer';
|
|
||||||
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
|
||||||
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
|
|
||||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
|
||||||
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
|
|
||||||
import {
|
|
||||||
$getRoot,
|
|
||||||
$getSelection,
|
|
||||||
EditorState,
|
|
||||||
Klass,
|
|
||||||
LexicalNode,
|
|
||||||
} from 'lexical';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@/components/ui/tooltip';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
||||||
import { Variable } from 'lucide-react';
|
|
||||||
import { ReactNode, useCallback, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { PasteHandlerPlugin } from './paste-handler-plugin';
|
|
||||||
import theme from './theme';
|
|
||||||
import { VariableNode } from './variable-node';
|
|
||||||
import { VariableOnChangePlugin } from './variable-on-change-plugin';
|
|
||||||
import VariablePickerMenuPlugin from './variable-picker-plugin';
|
|
||||||
|
|
||||||
// Catch any errors that occur during Lexical updates and log them
|
|
||||||
// or throw them as needed. If you don't throw them, Lexical will
|
|
||||||
// try to recover gracefully without losing user data.
|
|
||||||
function onError(error: Error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Nodes: Array<Klass<LexicalNode>> = [
|
|
||||||
HeadingNode,
|
|
||||||
QuoteNode,
|
|
||||||
CodeHighlightNode,
|
|
||||||
CodeNode,
|
|
||||||
VariableNode,
|
|
||||||
];
|
|
||||||
|
|
||||||
type PromptContentProps = { showToolbar?: boolean; multiLine?: boolean };
|
|
||||||
|
|
||||||
type IProps = {
|
|
||||||
value?: string;
|
|
||||||
onChange?: (value?: string) => void;
|
|
||||||
placeholder?: ReactNode;
|
|
||||||
} & PromptContentProps;
|
|
||||||
|
|
||||||
function PromptContent({
|
|
||||||
showToolbar = true,
|
|
||||||
multiLine = true,
|
|
||||||
}: PromptContentProps) {
|
|
||||||
const [editor] = useLexicalComposerContext();
|
|
||||||
const [isBlur, setIsBlur] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const insertTextAtCursor = useCallback(() => {
|
|
||||||
editor.update(() => {
|
|
||||||
const selection = $getSelection();
|
|
||||||
|
|
||||||
if (selection !== null) {
|
|
||||||
selection.insertText(' /');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [editor]);
|
|
||||||
|
|
||||||
const handleVariableIconClick = useCallback(() => {
|
|
||||||
insertTextAtCursor();
|
|
||||||
}, [insertTextAtCursor]);
|
|
||||||
|
|
||||||
const handleBlur = useCallback(() => {
|
|
||||||
setIsBlur(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
|
||||||
setIsBlur(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={cn('border rounded-sm ', { 'border-blue-400': !isBlur })}
|
|
||||||
>
|
|
||||||
{showToolbar && (
|
|
||||||
<div className="border-b px-2 py-2 justify-end flex">
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span className="inline-block cursor-pointer cursor p-0.5 hover:bg-gray-100 dark:hover:bg-slate-800 rounded-sm">
|
|
||||||
<Variable size={16} onClick={handleVariableIconClick} />
|
|
||||||
</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t('flow.insertVariableTip')}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ContentEditable
|
|
||||||
className={cn(
|
|
||||||
'relative px-2 py-1 focus-visible:outline-none max-h-[50vh] overflow-auto',
|
|
||||||
{
|
|
||||||
'min-h-40': multiLine,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PromptEditor({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
placeholder,
|
|
||||||
showToolbar,
|
|
||||||
multiLine = true,
|
|
||||||
}: IProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const initialConfig: InitialConfigType = {
|
|
||||||
namespace: 'PromptEditor',
|
|
||||||
theme,
|
|
||||||
onError,
|
|
||||||
nodes: Nodes,
|
|
||||||
};
|
|
||||||
|
|
||||||
const onValueChange = useCallback(
|
|
||||||
(editorState: EditorState) => {
|
|
||||||
editorState?.read(() => {
|
|
||||||
// const listNodes = $nodesOfType(VariableNode); // to be removed
|
|
||||||
// const allNodes = $dfs();
|
|
||||||
|
|
||||||
const text = $getRoot().getTextContent();
|
|
||||||
|
|
||||||
onChange?.(text);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[onChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative">
|
|
||||||
<LexicalComposer initialConfig={initialConfig}>
|
|
||||||
<RichTextPlugin
|
|
||||||
contentEditable={
|
|
||||||
<PromptContent
|
|
||||||
showToolbar={showToolbar}
|
|
||||||
multiLine={multiLine}
|
|
||||||
></PromptContent>
|
|
||||||
}
|
|
||||||
placeholder={
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'absolute top-1 left-2 text-text-secondary pointer-events-none',
|
|
||||||
{
|
|
||||||
'truncate w-[90%]': !multiLine,
|
|
||||||
'translate-y-10': multiLine,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{placeholder || t('common.promptPlaceholder')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
ErrorBoundary={LexicalErrorBoundary}
|
|
||||||
/>
|
|
||||||
<VariablePickerMenuPlugin value={value}></VariablePickerMenuPlugin>
|
|
||||||
<PasteHandlerPlugin />
|
|
||||||
<VariableOnChangePlugin
|
|
||||||
onChange={onValueChange}
|
|
||||||
></VariableOnChangePlugin>
|
|
||||||
</LexicalComposer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
||||||
import {
|
|
||||||
$createParagraphNode,
|
|
||||||
$createTextNode,
|
|
||||||
$getSelection,
|
|
||||||
$isRangeSelection,
|
|
||||||
PASTE_COMMAND,
|
|
||||||
} from 'lexical';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
function PasteHandlerPlugin() {
|
|
||||||
const [editor] = useLexicalComposerContext();
|
|
||||||
useEffect(() => {
|
|
||||||
const removeListener = editor.registerCommand(
|
|
||||||
PASTE_COMMAND,
|
|
||||||
(clipboardEvent: ClipboardEvent) => {
|
|
||||||
const clipboardData = clipboardEvent.clipboardData;
|
|
||||||
if (!clipboardData) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = clipboardData.getData('text/plain');
|
|
||||||
if (!text) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if text contains line breaks
|
|
||||||
if (text.includes('\n')) {
|
|
||||||
editor.update(() => {
|
|
||||||
const selection = $getSelection();
|
|
||||||
if (selection && $isRangeSelection(selection)) {
|
|
||||||
// Normalize line breaks, merge multiple consecutive line breaks into a single line break
|
|
||||||
const normalizedText = text.replace(/\n{2,}/g, '\n');
|
|
||||||
|
|
||||||
// Clear current selection
|
|
||||||
selection.removeText();
|
|
||||||
|
|
||||||
// Create a paragraph node to contain all content
|
|
||||||
const paragraph = $createParagraphNode();
|
|
||||||
|
|
||||||
// Split text by line breaks
|
|
||||||
const lines = normalizedText.split('\n');
|
|
||||||
|
|
||||||
// Process each line
|
|
||||||
lines.forEach((lineText, index) => {
|
|
||||||
// Add line text (if any)
|
|
||||||
if (lineText) {
|
|
||||||
const textNode = $createTextNode(lineText);
|
|
||||||
paragraph.append(textNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not the last line, add a line break
|
|
||||||
if (index < lines.length - 1) {
|
|
||||||
const lineBreak = $createTextNode('\n');
|
|
||||||
paragraph.append(lineBreak);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Insert paragraph
|
|
||||||
selection.insertNodes([paragraph]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prevent default paste behavior
|
|
||||||
clipboardEvent.preventDefault();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no line breaks, use default behavior
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
4,
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
removeListener();
|
|
||||||
};
|
|
||||||
}, [editor]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { PasteHandlerPlugin };
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default {
|
|
||||||
code: 'editor-code',
|
|
||||||
heading: {
|
|
||||||
h1: 'editor-heading-h1',
|
|
||||||
h2: 'editor-heading-h2',
|
|
||||||
h3: 'editor-heading-h3',
|
|
||||||
h4: 'editor-heading-h4',
|
|
||||||
h5: 'editor-heading-h5',
|
|
||||||
},
|
|
||||||
image: 'editor-image',
|
|
||||||
link: 'editor-link',
|
|
||||||
list: {
|
|
||||||
listitem: 'editor-listitem',
|
|
||||||
nested: {
|
|
||||||
listitem: 'editor-nested-listitem',
|
|
||||||
},
|
|
||||||
ol: 'editor-list-ol',
|
|
||||||
ul: 'editor-list-ul',
|
|
||||||
},
|
|
||||||
ltr: 'ltr',
|
|
||||||
paragraph: 'editor-paragraph',
|
|
||||||
placeholder: 'editor-placeholder',
|
|
||||||
quote: 'editor-quote',
|
|
||||||
rtl: 'rtl',
|
|
||||||
text: {
|
|
||||||
bold: 'editor-text-bold',
|
|
||||||
code: 'editor-text-code',
|
|
||||||
hashtag: 'editor-text-hashtag',
|
|
||||||
italic: 'editor-text-italic',
|
|
||||||
overflowed: 'editor-text-overflowed',
|
|
||||||
strikethrough: 'editor-text-strikethrough',
|
|
||||||
underline: 'editor-text-underline',
|
|
||||||
underlineStrikethrough: 'editor-text-underlineStrikethrough',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
import { BeginId } from '@/pages/flow/constant';
|
|
||||||
import { DecoratorNode, LexicalNode, NodeKey } from 'lexical';
|
|
||||||
import { ReactNode } from 'react';
|
|
||||||
const prefix = BeginId + '@';
|
|
||||||
|
|
||||||
export class VariableNode extends DecoratorNode<ReactNode> {
|
|
||||||
__value: string;
|
|
||||||
__label: string;
|
|
||||||
key?: NodeKey;
|
|
||||||
__parentLabel?: string | ReactNode;
|
|
||||||
__icon?: ReactNode;
|
|
||||||
|
|
||||||
static getType(): string {
|
|
||||||
return 'variable';
|
|
||||||
}
|
|
||||||
|
|
||||||
static clone(node: VariableNode): VariableNode {
|
|
||||||
return new VariableNode(
|
|
||||||
node.__value,
|
|
||||||
node.__label,
|
|
||||||
node.__key,
|
|
||||||
node.__parentLabel,
|
|
||||||
node.__icon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
value: string,
|
|
||||||
label: string,
|
|
||||||
key?: NodeKey,
|
|
||||||
parent?: string | ReactNode,
|
|
||||||
icon?: ReactNode,
|
|
||||||
) {
|
|
||||||
super(key);
|
|
||||||
this.__value = value;
|
|
||||||
this.__label = label;
|
|
||||||
this.__parentLabel = parent;
|
|
||||||
this.__icon = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
createDOM(): HTMLElement {
|
|
||||||
const dom = document.createElement('span');
|
|
||||||
dom.className = 'mr-1';
|
|
||||||
|
|
||||||
return dom;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDOM(): false {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
decorate(): ReactNode {
|
|
||||||
let content: ReactNode = (
|
|
||||||
<div className="text-blue-600">{this.__label}</div>
|
|
||||||
);
|
|
||||||
if (this.__parentLabel) {
|
|
||||||
content = (
|
|
||||||
<div className="flex items-center gap-1 text-text-primary ">
|
|
||||||
<div>{this.__icon}</div>
|
|
||||||
<div>{this.__parentLabel}</div>
|
|
||||||
<div className="text-text-disabled mr-1">/</div>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="bg-gray-200 dark:bg-gray-400 text-sm inline-flex items-center rounded-md px-2 py-1">
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTextContent(): string {
|
|
||||||
return `{${this.__value}}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $createVariableNode(
|
|
||||||
value: string,
|
|
||||||
label: string,
|
|
||||||
parentLabel: string | ReactNode,
|
|
||||||
icon?: ReactNode,
|
|
||||||
): VariableNode {
|
|
||||||
return new VariableNode(value, label, undefined, parentLabel, icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $isVariableNode(
|
|
||||||
node: LexicalNode | null | undefined,
|
|
||||||
): node is VariableNode {
|
|
||||||
return node instanceof VariableNode;
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
||||||
import { EditorState, LexicalEditor } from 'lexical';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { ProgrammaticTag } from './constant';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
onChange: (
|
|
||||||
editorState: EditorState,
|
|
||||||
editor?: LexicalEditor,
|
|
||||||
tags?: Set<string>,
|
|
||||||
) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VariableOnChangePlugin({ onChange }: IProps) {
|
|
||||||
// Access the editor through the LexicalComposerContext
|
|
||||||
const [editor] = useLexicalComposerContext();
|
|
||||||
// Wrap our listener in useEffect to handle the teardown and avoid stale references.
|
|
||||||
useEffect(() => {
|
|
||||||
// most listeners return a teardown function that can be called to clean them up.
|
|
||||||
return editor.registerUpdateListener(
|
|
||||||
({ editorState, tags, dirtyElements }) => {
|
|
||||||
// Check if there is a "programmatic" tag
|
|
||||||
const isProgrammaticUpdate = tags.has(ProgrammaticTag);
|
|
||||||
|
|
||||||
// The onchange event is only triggered when the data is manually updated
|
|
||||||
// Otherwise, the content will be displayed incorrectly.
|
|
||||||
if (dirtyElements.size > 0 && !isProgrammaticUpdate) {
|
|
||||||
onChange(editorState);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}, [editor, onChange]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@ -1,297 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
||||||
import {
|
|
||||||
LexicalTypeaheadMenuPlugin,
|
|
||||||
MenuOption,
|
|
||||||
useBasicTypeaheadTriggerMatch,
|
|
||||||
} from '@lexical/react/LexicalTypeaheadMenuPlugin';
|
|
||||||
import {
|
|
||||||
$createParagraphNode,
|
|
||||||
$createTextNode,
|
|
||||||
$getRoot,
|
|
||||||
$getSelection,
|
|
||||||
$isRangeSelection,
|
|
||||||
TextNode,
|
|
||||||
} from 'lexical';
|
|
||||||
import React, {
|
|
||||||
ReactElement,
|
|
||||||
ReactNode,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
} from 'react';
|
|
||||||
import * as ReactDOM from 'react-dom';
|
|
||||||
|
|
||||||
import { $createVariableNode } from './variable-node';
|
|
||||||
|
|
||||||
import { useBuildQueryVariableOptions } from '@/pages/agent/hooks/use-get-begin-query';
|
|
||||||
import { ProgrammaticTag } from './constant';
|
|
||||||
import './index.css';
|
|
||||||
class VariableInnerOption extends MenuOption {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
parentLabel: string | JSX.Element;
|
|
||||||
icon?: ReactNode;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
label: string,
|
|
||||||
value: string,
|
|
||||||
parentLabel: string | JSX.Element,
|
|
||||||
icon?: ReactNode,
|
|
||||||
) {
|
|
||||||
super(value);
|
|
||||||
this.label = label;
|
|
||||||
this.value = value;
|
|
||||||
this.parentLabel = parentLabel;
|
|
||||||
this.icon = icon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VariableOption extends MenuOption {
|
|
||||||
label: ReactElement | string;
|
|
||||||
title: string;
|
|
||||||
options: VariableInnerOption[];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
label: ReactElement | string,
|
|
||||||
title: string,
|
|
||||||
options: VariableInnerOption[],
|
|
||||||
) {
|
|
||||||
super(title);
|
|
||||||
this.label = label;
|
|
||||||
this.title = title;
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function VariablePickerMenuItem({
|
|
||||||
index,
|
|
||||||
option,
|
|
||||||
selectOptionAndCleanUp,
|
|
||||||
}: {
|
|
||||||
index: number;
|
|
||||||
option: VariableOption;
|
|
||||||
selectOptionAndCleanUp: (
|
|
||||||
option: VariableOption | VariableInnerOption,
|
|
||||||
) => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={option.key}
|
|
||||||
tabIndex={-1}
|
|
||||||
ref={option.setRefElement}
|
|
||||||
role="option"
|
|
||||||
id={'typeahead-item-' + index}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<span className="text text-slate-500">{option.title}</span>
|
|
||||||
<ul className="pl-2 py-1">
|
|
||||||
{option.options.map((x) => (
|
|
||||||
<li
|
|
||||||
key={x.value}
|
|
||||||
onClick={() => selectOptionAndCleanUp(x)}
|
|
||||||
className="hover:bg-slate-300 p-1"
|
|
||||||
>
|
|
||||||
{x.label}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function VariablePickerMenuPlugin({
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
value?: string;
|
|
||||||
}): JSX.Element {
|
|
||||||
const [editor] = useLexicalComposerContext();
|
|
||||||
const isFirstRender = useRef(true);
|
|
||||||
|
|
||||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
|
|
||||||
minLength: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [queryString, setQueryString] = React.useState<string | null>('');
|
|
||||||
|
|
||||||
const options = useBuildQueryVariableOptions();
|
|
||||||
|
|
||||||
const buildNextOptions = useCallback(() => {
|
|
||||||
let filteredOptions = options;
|
|
||||||
if (queryString) {
|
|
||||||
const lowerQuery = queryString.toLowerCase();
|
|
||||||
filteredOptions = options
|
|
||||||
.map((x) => ({
|
|
||||||
...x,
|
|
||||||
options: x.options.filter(
|
|
||||||
(y) =>
|
|
||||||
y.label.toLowerCase().includes(lowerQuery) ||
|
|
||||||
y.value.toLowerCase().includes(lowerQuery),
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
.filter((x) => x.options.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextOptions: VariableOption[] = filteredOptions.map(
|
|
||||||
(x) =>
|
|
||||||
new VariableOption(
|
|
||||||
x.label,
|
|
||||||
x.title,
|
|
||||||
x.options.map((y) => {
|
|
||||||
return new VariableInnerOption(y.label, y.value, x.label, y.icon);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return nextOptions;
|
|
||||||
}, [options, queryString]);
|
|
||||||
|
|
||||||
const findItemByValue = useCallback(
|
|
||||||
(value: string) => {
|
|
||||||
const children = options.reduce<
|
|
||||||
Array<{
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
parentLabel?: string | ReactNode;
|
|
||||||
icon?: ReactNode;
|
|
||||||
}>
|
|
||||||
>((pre, cur) => {
|
|
||||||
return pre.concat(cur.options);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return children.find((x) => x.value === value);
|
|
||||||
},
|
|
||||||
[options],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSelectOption = useCallback(
|
|
||||||
(
|
|
||||||
selectedOption: VariableOption | VariableInnerOption,
|
|
||||||
nodeToRemove: TextNode | null,
|
|
||||||
closeMenu: () => void,
|
|
||||||
) => {
|
|
||||||
editor.update(() => {
|
|
||||||
const selection = $getSelection();
|
|
||||||
|
|
||||||
if (!$isRangeSelection(selection) || selectedOption === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeToRemove) {
|
|
||||||
nodeToRemove.remove();
|
|
||||||
}
|
|
||||||
const variableNode = $createVariableNode(
|
|
||||||
(selectedOption as VariableInnerOption).value,
|
|
||||||
selectedOption.label as string,
|
|
||||||
selectedOption.parentLabel as string | ReactNode,
|
|
||||||
selectedOption.icon as ReactNode,
|
|
||||||
);
|
|
||||||
selection.insertNodes([variableNode]);
|
|
||||||
|
|
||||||
closeMenu();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[editor],
|
|
||||||
);
|
|
||||||
|
|
||||||
const parseTextToVariableNodes = useCallback(
|
|
||||||
(text: string) => {
|
|
||||||
const paragraph = $createParagraphNode();
|
|
||||||
|
|
||||||
// Regular expression to match content within {}
|
|
||||||
const regex = /{([^}]*)}/g;
|
|
||||||
let match;
|
|
||||||
let lastIndex = 0;
|
|
||||||
while ((match = regex.exec(text)) !== null) {
|
|
||||||
const { 1: content, index, 0: template } = match;
|
|
||||||
|
|
||||||
// Add the previous text part (if any)
|
|
||||||
if (index > lastIndex) {
|
|
||||||
const textNode = $createTextNode(text.slice(lastIndex, index));
|
|
||||||
|
|
||||||
paragraph.append(textNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add variable node or text node
|
|
||||||
const nodeItem = findItemByValue(content);
|
|
||||||
|
|
||||||
if (nodeItem) {
|
|
||||||
paragraph.append(
|
|
||||||
$createVariableNode(
|
|
||||||
content,
|
|
||||||
nodeItem.label,
|
|
||||||
nodeItem.parentLabel,
|
|
||||||
nodeItem.icon,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
paragraph.append($createTextNode(template));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update index
|
|
||||||
lastIndex = regex.lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the last part of text (if any)
|
|
||||||
if (lastIndex < text.length) {
|
|
||||||
const textNode = $createTextNode(text.slice(lastIndex));
|
|
||||||
paragraph.append(textNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
$getRoot().clear().append(paragraph);
|
|
||||||
|
|
||||||
if ($isRangeSelection($getSelection())) {
|
|
||||||
$getRoot().selectEnd();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[findItemByValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (editor && value && isFirstRender.current) {
|
|
||||||
isFirstRender.current = false;
|
|
||||||
editor.update(
|
|
||||||
() => {
|
|
||||||
parseTextToVariableNodes(value);
|
|
||||||
},
|
|
||||||
{ tag: ProgrammaticTag },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [parseTextToVariableNodes, editor, value]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LexicalTypeaheadMenuPlugin<VariableOption | VariableInnerOption>
|
|
||||||
onQueryChange={setQueryString}
|
|
||||||
onSelectOption={onSelectOption}
|
|
||||||
triggerFn={checkForTriggerMatch}
|
|
||||||
options={buildNextOptions()}
|
|
||||||
menuRenderFn={(anchorElementRef, { selectOptionAndCleanUp }) => {
|
|
||||||
const nextOptions = buildNextOptions();
|
|
||||||
return anchorElementRef.current && nextOptions.length
|
|
||||||
? ReactDOM.createPortal(
|
|
||||||
<div className="typeahead-popover w-[200px] p-2">
|
|
||||||
<ul className="overflow-y-auto !scrollbar-thin overflow-x-hidden">
|
|
||||||
{nextOptions.map((option, i: number) => (
|
|
||||||
<VariablePickerMenuItem
|
|
||||||
index={i}
|
|
||||||
key={option.key}
|
|
||||||
option={option}
|
|
||||||
selectOptionAndCleanUp={selectOptionAndCleanUp}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>,
|
|
||||||
anchorElementRef.current,
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { toLower } from 'lodash';
|
|
||||||
import { ReactNode, useMemo } from 'react';
|
|
||||||
import { useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { VariableType } from '../../constant';
|
|
||||||
import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
|
|
||||||
type QueryVariableProps = {
|
|
||||||
name?: string;
|
|
||||||
type?: VariableType;
|
|
||||||
label?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function QueryVariable({
|
|
||||||
name = 'query',
|
|
||||||
type,
|
|
||||||
label,
|
|
||||||
}: QueryVariableProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
const nextOptions = useBuildQueryVariableOptions();
|
|
||||||
|
|
||||||
const finalOptions = useMemo(() => {
|
|
||||||
return type
|
|
||||||
? nextOptions.map((x) => {
|
|
||||||
return {
|
|
||||||
...x,
|
|
||||||
options: x.options.filter((y) => toLower(y.type).includes(type)),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: nextOptions;
|
|
||||||
}, [nextOptions, type]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={name}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
{label || (
|
|
||||||
<FormLabel tooltip={t('flow.queryTip')}>
|
|
||||||
{t('flow.query')}
|
|
||||||
</FormLabel>
|
|
||||||
)}
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch
|
|
||||||
options={finalOptions}
|
|
||||||
{...field}
|
|
||||||
allowClear
|
|
||||||
></SelectWithSearch>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -65,26 +65,28 @@ const SplitterForm = ({ node }: INextOperatorForm) => {
|
|||||||
></SliderInputFormField>
|
></SliderInputFormField>
|
||||||
<section>
|
<section>
|
||||||
<span className="mb-2 inline-block">{t('flow.delimiters')}</span>
|
<span className="mb-2 inline-block">{t('flow.delimiters')}</span>
|
||||||
{fields.map((field, index) => (
|
<div className="space-y-4">
|
||||||
<div key={field.id} className="flex items-center gap-2">
|
{fields.map((field, index) => (
|
||||||
<div className="space-y-2 flex-1">
|
<div key={field.id} className="flex items-center gap-2">
|
||||||
<RAGFlowFormItem
|
<div className="space-y-2 flex-1">
|
||||||
name={`${name}.${index}.value`}
|
<RAGFlowFormItem
|
||||||
label="delimiter"
|
name={`${name}.${index}.value`}
|
||||||
labelClassName="!hidden"
|
label="delimiter"
|
||||||
|
labelClassName="!hidden"
|
||||||
|
>
|
||||||
|
<DelimiterInput className="!m-0"></DelimiterInput>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={'ghost'}
|
||||||
|
onClick={() => remove(index)}
|
||||||
>
|
>
|
||||||
<DelimiterInput className="!m-0"></DelimiterInput>
|
<Trash2 />
|
||||||
</RAGFlowFormItem>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
))}
|
||||||
type="button"
|
</div>
|
||||||
variant={'ghost'}
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
>
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</section>
|
</section>
|
||||||
<BlockButton onClick={() => append({ value: '\n' })}>
|
<BlockButton onClick={() => append({ value: '\n' })}>
|
||||||
{t('common.add')}
|
{t('common.add')}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { useFetchModelId } from '@/hooks/logic-hooks';
|
|
||||||
import { Connection, Node, Position, ReactFlowInstance } from '@xyflow/react';
|
import { Connection, Node, Position, ReactFlowInstance } from '@xyflow/react';
|
||||||
import humanId from 'human-id';
|
import humanId from 'human-id';
|
||||||
import { t } from 'i18next';
|
|
||||||
import { lowerFirst } from 'lodash';
|
import { lowerFirst } from 'lodash';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -9,32 +7,12 @@ import {
|
|||||||
NodeHandleId,
|
NodeHandleId,
|
||||||
NodeMap,
|
NodeMap,
|
||||||
Operator,
|
Operator,
|
||||||
initialAgentValues,
|
|
||||||
initialBeginValues,
|
initialBeginValues,
|
||||||
initialCategorizeValues,
|
|
||||||
initialChunkerValues,
|
|
||||||
initialCodeValues,
|
|
||||||
initialConcentratorValues,
|
|
||||||
initialCrawlerValues,
|
|
||||||
initialEmailValues,
|
|
||||||
initialExeSqlValues,
|
|
||||||
initialHierarchicalMergerValues,
|
initialHierarchicalMergerValues,
|
||||||
initialInvokeValues,
|
|
||||||
initialIterationStartValues,
|
|
||||||
initialIterationValues,
|
|
||||||
initialKeywordExtractValues,
|
|
||||||
initialMessageValues,
|
|
||||||
initialNoteValues,
|
initialNoteValues,
|
||||||
initialParserValues,
|
initialParserValues,
|
||||||
initialRelevantValues,
|
|
||||||
initialRetrievalValues,
|
|
||||||
initialRewriteQuestionValues,
|
|
||||||
initialSplitterValues,
|
initialSplitterValues,
|
||||||
initialStringTransformValues,
|
|
||||||
initialSwitchValues,
|
|
||||||
initialTokenizerValues,
|
initialTokenizerValues,
|
||||||
initialUserFillUpValues,
|
|
||||||
initialWaitingDialogueValues,
|
|
||||||
} from '../constant';
|
} from '../constant';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
import {
|
import {
|
||||||
@ -42,63 +20,21 @@ import {
|
|||||||
getNodeDragHandle,
|
getNodeDragHandle,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
|
||||||
function isBottomSubAgent(type: string, position: Position) {
|
|
||||||
return (
|
|
||||||
(type === Operator.Agent && position === Position.Bottom) ||
|
|
||||||
type === Operator.Tool
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export const useInitializeOperatorParams = () => {
|
export const useInitializeOperatorParams = () => {
|
||||||
const llmId = useFetchModelId();
|
|
||||||
|
|
||||||
const initialFormValuesMap = useMemo(() => {
|
const initialFormValuesMap = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
[Operator.Begin]: initialBeginValues,
|
[Operator.Begin]: initialBeginValues,
|
||||||
[Operator.Retrieval]: initialRetrievalValues,
|
|
||||||
[Operator.Categorize]: { ...initialCategorizeValues, llm_id: llmId },
|
|
||||||
[Operator.Relevant]: { ...initialRelevantValues, llm_id: llmId },
|
|
||||||
[Operator.RewriteQuestion]: {
|
|
||||||
...initialRewriteQuestionValues,
|
|
||||||
llm_id: llmId,
|
|
||||||
},
|
|
||||||
[Operator.Message]: initialMessageValues,
|
|
||||||
[Operator.KeywordExtract]: {
|
|
||||||
...initialKeywordExtractValues,
|
|
||||||
llm_id: llmId,
|
|
||||||
},
|
|
||||||
[Operator.ExeSQL]: initialExeSqlValues,
|
|
||||||
[Operator.Switch]: initialSwitchValues,
|
|
||||||
[Operator.Concentrator]: initialConcentratorValues,
|
|
||||||
[Operator.Note]: initialNoteValues,
|
[Operator.Note]: initialNoteValues,
|
||||||
[Operator.Crawler]: initialCrawlerValues,
|
|
||||||
[Operator.Invoke]: initialInvokeValues,
|
|
||||||
[Operator.Email]: initialEmailValues,
|
|
||||||
[Operator.Iteration]: initialIterationValues,
|
|
||||||
[Operator.IterationStart]: initialIterationStartValues,
|
|
||||||
[Operator.Code]: initialCodeValues,
|
|
||||||
[Operator.WaitingDialogue]: initialWaitingDialogueValues,
|
|
||||||
[Operator.Agent]: { ...initialAgentValues, llm_id: llmId },
|
|
||||||
[Operator.Tool]: {},
|
|
||||||
[Operator.UserFillUp]: initialUserFillUpValues,
|
|
||||||
[Operator.StringTransform]: initialStringTransformValues,
|
|
||||||
[Operator.Parser]: initialParserValues,
|
[Operator.Parser]: initialParserValues,
|
||||||
[Operator.Chunker]: initialChunkerValues,
|
|
||||||
[Operator.Tokenizer]: initialTokenizerValues,
|
[Operator.Tokenizer]: initialTokenizerValues,
|
||||||
[Operator.Splitter]: initialSplitterValues,
|
[Operator.Splitter]: initialSplitterValues,
|
||||||
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
|
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
|
||||||
};
|
};
|
||||||
}, [llmId]);
|
}, []);
|
||||||
|
|
||||||
const initializeOperatorParams = useCallback(
|
const initializeOperatorParams = useCallback(
|
||||||
(operatorName: Operator, position: Position) => {
|
(operatorName: Operator) => {
|
||||||
const initialValues = initialFormValuesMap[operatorName];
|
const initialValues = initialFormValuesMap[operatorName];
|
||||||
if (isBottomSubAgent(operatorName, position)) {
|
|
||||||
return {
|
|
||||||
...initialValues,
|
|
||||||
description: t('flow.descriptionMessage'),
|
|
||||||
user_prompt: t('flow.userPromptDefaultValue'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return initialValues;
|
return initialValues;
|
||||||
},
|
},
|
||||||
@ -175,95 +111,17 @@ function useAddChildEdge() {
|
|||||||
return { addChildEdge };
|
return { addChildEdge };
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAddToolNode() {
|
|
||||||
const { nodes, edges, addEdge, getNode, addNode } = useGraphStore(
|
|
||||||
(state) => state,
|
|
||||||
);
|
|
||||||
|
|
||||||
const addToolNode = useCallback(
|
|
||||||
(newNode: Node<any>, nodeId?: string): boolean => {
|
|
||||||
const agentNode = getNode(nodeId);
|
|
||||||
|
|
||||||
if (agentNode) {
|
|
||||||
const childToolNodeIds = edges
|
|
||||||
.filter(
|
|
||||||
(x) => x.source === nodeId && x.sourceHandle === NodeHandleId.Tool,
|
|
||||||
)
|
|
||||||
.map((x) => x.target);
|
|
||||||
|
|
||||||
if (
|
|
||||||
childToolNodeIds.length > 0 &&
|
|
||||||
nodes.some((x) => x.id === childToolNodeIds[0])
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
newNode.position = {
|
|
||||||
x: agentNode.position.x - 82,
|
|
||||||
y: agentNode.position.y + 140,
|
|
||||||
};
|
|
||||||
|
|
||||||
addNode(newNode);
|
|
||||||
if (nodeId) {
|
|
||||||
addEdge({
|
|
||||||
source: nodeId,
|
|
||||||
target: newNode.id,
|
|
||||||
sourceHandle: NodeHandleId.Tool,
|
|
||||||
targetHandle: NodeHandleId.End,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
[addEdge, addNode, edges, getNode, nodes],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { addToolNode };
|
|
||||||
}
|
|
||||||
|
|
||||||
function useResizeIterationNode() {
|
|
||||||
const { getNode, nodes, updateNode } = useGraphStore((state) => state);
|
|
||||||
|
|
||||||
const resizeIterationNode = useCallback(
|
|
||||||
(type: string, position: Position, parentId?: string) => {
|
|
||||||
const parentNode = getNode(parentId);
|
|
||||||
if (parentNode && !isBottomSubAgent(type, position)) {
|
|
||||||
const MoveRightDistance = 310;
|
|
||||||
const childNodeList = nodes.filter((x) => x.parentId === parentId);
|
|
||||||
const maxX = Math.max(...childNodeList.map((x) => x.position.x));
|
|
||||||
if (maxX + MoveRightDistance > parentNode.position.x) {
|
|
||||||
updateNode({
|
|
||||||
...parentNode,
|
|
||||||
width: (parentNode.width || 0) + MoveRightDistance,
|
|
||||||
position: {
|
|
||||||
x: parentNode.position.x + MoveRightDistance / 2,
|
|
||||||
y: parentNode.position.y,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[getNode, nodes, updateNode],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { resizeIterationNode };
|
|
||||||
}
|
|
||||||
type CanvasMouseEvent = Pick<
|
type CanvasMouseEvent = Pick<
|
||||||
React.MouseEvent<HTMLElement>,
|
React.MouseEvent<HTMLElement>,
|
||||||
'clientX' | 'clientY'
|
'clientX' | 'clientY'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||||
const { edges, nodes, addEdge, addNode, getNode } = useGraphStore(
|
const { nodes, addNode } = useGraphStore((state) => state);
|
||||||
(state) => state,
|
|
||||||
);
|
|
||||||
const getNodeName = useGetNodeName();
|
const getNodeName = useGetNodeName();
|
||||||
const { initializeOperatorParams } = useInitializeOperatorParams();
|
const { initializeOperatorParams } = useInitializeOperatorParams();
|
||||||
const { calculateNewlyBackChildPosition } = useCalculateNewlyChildPosition();
|
const { calculateNewlyBackChildPosition } = useCalculateNewlyChildPosition();
|
||||||
const { addChildEdge } = useAddChildEdge();
|
const { addChildEdge } = useAddChildEdge();
|
||||||
const { addToolNode } = useAddToolNode();
|
|
||||||
const { resizeIterationNode } = useResizeIterationNode();
|
|
||||||
// const [reactFlowInstance, setReactFlowInstance] =
|
// const [reactFlowInstance, setReactFlowInstance] =
|
||||||
// useState<ReactFlowInstance<any, any>>();
|
// useState<ReactFlowInstance<any, any>>();
|
||||||
|
|
||||||
@ -281,7 +139,6 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
|||||||
) =>
|
) =>
|
||||||
(event?: CanvasMouseEvent): string | undefined => {
|
(event?: CanvasMouseEvent): string | undefined => {
|
||||||
const nodeId = params.nodeId;
|
const nodeId = params.nodeId;
|
||||||
const node = getNode(nodeId);
|
|
||||||
|
|
||||||
// reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition
|
// reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition
|
||||||
// and you don't need to subtract the reactFlowBounds.left/top anymore
|
// and you don't need to subtract the reactFlowBounds.left/top anymore
|
||||||
@ -312,112 +169,30 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
|||||||
getNodeName(type),
|
getNodeName(type),
|
||||||
nodes,
|
nodes,
|
||||||
),
|
),
|
||||||
form: initializeOperatorParams(type as Operator, params.position),
|
form: initializeOperatorParams(type as Operator),
|
||||||
},
|
},
|
||||||
sourcePosition: Position.Right,
|
sourcePosition: Position.Right,
|
||||||
targetPosition: Position.Left,
|
targetPosition: Position.Left,
|
||||||
dragHandle: getNodeDragHandle(type),
|
dragHandle: getNodeDragHandle(type),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (node && node.parentId) {
|
addNode(newNode);
|
||||||
newNode.parentId = node.parentId;
|
addChildEdge(params.position, {
|
||||||
newNode.extent = 'parent';
|
source: params.nodeId,
|
||||||
const parentNode = getNode(node.parentId);
|
target: newNode.id,
|
||||||
if (parentNode && !isBottomSubAgent(type, params.position)) {
|
sourceHandle: params.id,
|
||||||
resizeIterationNode(type, params.position, node.parentId);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === Operator.Iteration) {
|
|
||||||
newNode.width = 500;
|
|
||||||
newNode.height = 250;
|
|
||||||
const iterationStartNode: Node<any> = {
|
|
||||||
id: `${Operator.IterationStart}:${humanId()}`,
|
|
||||||
type: 'iterationStartNode',
|
|
||||||
position: { x: 50, y: 100 },
|
|
||||||
// draggable: false,
|
|
||||||
data: {
|
|
||||||
label: Operator.IterationStart,
|
|
||||||
name: Operator.IterationStart,
|
|
||||||
form: initialIterationStartValues,
|
|
||||||
},
|
|
||||||
parentId: newNode.id,
|
|
||||||
extent: 'parent',
|
|
||||||
};
|
|
||||||
addNode(newNode);
|
|
||||||
addNode(iterationStartNode);
|
|
||||||
if (nodeId) {
|
|
||||||
addEdge({
|
|
||||||
source: nodeId,
|
|
||||||
target: newNode.id,
|
|
||||||
sourceHandle: NodeHandleId.Start,
|
|
||||||
targetHandle: NodeHandleId.End,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return newNode.id;
|
|
||||||
} else if (
|
|
||||||
type === Operator.Agent &&
|
|
||||||
params.position === Position.Bottom
|
|
||||||
) {
|
|
||||||
const agentNode = getNode(nodeId);
|
|
||||||
if (agentNode) {
|
|
||||||
// Calculate the coordinates of child nodes to prevent newly added child nodes from covering other child nodes
|
|
||||||
const allChildAgentNodeIds = edges
|
|
||||||
.filter(
|
|
||||||
(x) =>
|
|
||||||
x.source === nodeId &&
|
|
||||||
x.sourceHandle === NodeHandleId.AgentBottom,
|
|
||||||
)
|
|
||||||
.map((x) => x.target);
|
|
||||||
|
|
||||||
const xAxises = nodes
|
|
||||||
.filter((x) => allChildAgentNodeIds.some((y) => y === x.id))
|
|
||||||
.map((x) => x.position.x);
|
|
||||||
|
|
||||||
const maxX = Math.max(...xAxises);
|
|
||||||
|
|
||||||
newNode.position = {
|
|
||||||
x: xAxises.length > 0 ? maxX + 262 : agentNode.position.x + 82,
|
|
||||||
y: agentNode.position.y + 140,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
addNode(newNode);
|
|
||||||
if (nodeId) {
|
|
||||||
addEdge({
|
|
||||||
source: nodeId,
|
|
||||||
target: newNode.id,
|
|
||||||
sourceHandle: NodeHandleId.AgentBottom,
|
|
||||||
targetHandle: NodeHandleId.AgentTop,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return newNode.id;
|
|
||||||
} else if (type === Operator.Tool) {
|
|
||||||
const toolNodeAdded = addToolNode(newNode, params.nodeId);
|
|
||||||
return toolNodeAdded ? newNode.id : undefined;
|
|
||||||
} else {
|
|
||||||
addNode(newNode);
|
|
||||||
addChildEdge(params.position, {
|
|
||||||
source: params.nodeId,
|
|
||||||
target: newNode.id,
|
|
||||||
sourceHandle: params.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return newNode.id;
|
return newNode.id;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
addChildEdge,
|
addChildEdge,
|
||||||
addEdge,
|
|
||||||
addNode,
|
addNode,
|
||||||
addToolNode,
|
|
||||||
calculateNewlyBackChildPosition,
|
calculateNewlyBackChildPosition,
|
||||||
edges,
|
|
||||||
getNode,
|
|
||||||
getNodeName,
|
getNodeName,
|
||||||
initializeOperatorParams,
|
initializeOperatorParams,
|
||||||
nodes,
|
nodes,
|
||||||
reactFlowInstance,
|
reactFlowInstance,
|
||||||
resizeIterationNode,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,12 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { Node, OnBeforeDelete } from '@xyflow/react';
|
import { OnBeforeDelete } from '@xyflow/react';
|
||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
import { deleteAllDownstreamAgentsAndTool } from '../utils/delete-node';
|
|
||||||
|
|
||||||
const UndeletableNodes = [Operator.Begin, Operator.IterationStart];
|
const UndeletableNodes = [Operator.Begin];
|
||||||
|
|
||||||
export function useBeforeDelete() {
|
export function useBeforeDelete() {
|
||||||
const { getOperatorTypeFromId, getNode } = useGraphStore((state) => state);
|
const { getOperatorTypeFromId } = useGraphStore((state) => state);
|
||||||
|
|
||||||
const agentPredicate = (node: Node) => {
|
|
||||||
return getOperatorTypeFromId(node.id) === Operator.Agent;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBeforeDelete: OnBeforeDelete<RAGFlowNodeType> = async ({
|
const handleBeforeDelete: OnBeforeDelete<RAGFlowNodeType> = async ({
|
||||||
nodes, // Nodes to be deleted
|
nodes, // Nodes to be deleted
|
||||||
@ -23,13 +18,6 @@ export function useBeforeDelete() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
operatorType === Operator.IterationStart &&
|
|
||||||
!nodes.some((x) => x.id === node.parentId)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,27 +39,6 @@ export function useBeforeDelete() {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete the agent and tool nodes downstream of the agent node
|
|
||||||
if (nodes.some(agentPredicate)) {
|
|
||||||
nodes.filter(agentPredicate).forEach((node) => {
|
|
||||||
const { downstreamAgentAndToolEdges, downstreamAgentAndToolNodeIds } =
|
|
||||||
deleteAllDownstreamAgentsAndTool(node.id, edges);
|
|
||||||
|
|
||||||
downstreamAgentAndToolNodeIds.forEach((nodeId) => {
|
|
||||||
const currentNode = getNode(nodeId);
|
|
||||||
if (toBeDeletedNodes.every((x) => x.id !== nodeId) && currentNode) {
|
|
||||||
toBeDeletedNodes.push(currentNode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
downstreamAgentAndToolEdges.forEach((edge) => {
|
|
||||||
if (toBeDeletedEdges.every((x) => x.id !== edge.id)) {
|
|
||||||
toBeDeletedEdges.push(edge);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: toBeDeletedNodes,
|
nodes: toBeDeletedNodes,
|
||||||
edges: toBeDeletedEdges,
|
edges: toBeDeletedEdges,
|
||||||
|
|||||||
@ -1,60 +0,0 @@
|
|||||||
import { MessageType } from '@/constants/chat';
|
|
||||||
import { Message } from '@/interfaces/database/chat';
|
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { BeginQuery } from '../interface';
|
|
||||||
import { buildBeginQueryWithObject } from '../utils';
|
|
||||||
type IAwaitCompentData = {
|
|
||||||
derivedMessages: IMessage[];
|
|
||||||
sendFormMessage: (params: {
|
|
||||||
inputs: Record<string, BeginQuery>;
|
|
||||||
id: string;
|
|
||||||
}) => void;
|
|
||||||
canvasId: string;
|
|
||||||
};
|
|
||||||
const useAwaitCompentData = (props: IAwaitCompentData) => {
|
|
||||||
const { derivedMessages, sendFormMessage, canvasId } = props;
|
|
||||||
|
|
||||||
const getInputs = useCallback((message: Message) => {
|
|
||||||
return get(message, 'data.inputs', {}) as Record<string, BeginQuery>;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const buildInputList = useCallback(
|
|
||||||
(message: Message) => {
|
|
||||||
return Object.entries(getInputs(message)).map(([key, val]) => {
|
|
||||||
return {
|
|
||||||
...val,
|
|
||||||
key,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[getInputs],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOk = useCallback(
|
|
||||||
(message: Message) => (values: BeginQuery[]) => {
|
|
||||||
const inputs = getInputs(message);
|
|
||||||
const nextInputs = buildBeginQueryWithObject(inputs, values);
|
|
||||||
sendFormMessage({
|
|
||||||
inputs: nextInputs,
|
|
||||||
id: canvasId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[getInputs, sendFormMessage, canvasId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const isWaitting = useMemo(() => {
|
|
||||||
const temp = derivedMessages?.some((message, i) => {
|
|
||||||
const flag =
|
|
||||||
message.role === MessageType.Assistant &&
|
|
||||||
derivedMessages.length - 1 === i &&
|
|
||||||
message.data;
|
|
||||||
return flag;
|
|
||||||
});
|
|
||||||
return temp;
|
|
||||||
}, [derivedMessages]);
|
|
||||||
return { getInputs, buildInputList, handleOk, isWaitting };
|
|
||||||
};
|
|
||||||
|
|
||||||
export { useAwaitCompentData };
|
|
||||||
@ -1,71 +1,17 @@
|
|||||||
import { useToast } from '@/components/hooks/use-toast';
|
|
||||||
import { FileMimeType, Platform } from '@/constants/common';
|
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { IGraph } from '@/interfaces/database/flow';
|
|
||||||
import { downloadJsonFile } from '@/utils/file-util';
|
import { downloadJsonFile } from '@/utils/file-util';
|
||||||
import { message } from 'antd';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useBuildDslData } from './use-build-dsl';
|
import { useBuildDslData } from './use-build-dsl';
|
||||||
import { useSetGraphInfo } from './use-set-graph';
|
|
||||||
|
|
||||||
export const useHandleExportOrImportJsonFile = () => {
|
export const useHandleExportOrImportJsonFile = () => {
|
||||||
const { buildDslData } = useBuildDslData();
|
const { buildDslData } = useBuildDslData();
|
||||||
const {
|
|
||||||
visible: fileUploadVisible,
|
|
||||||
hideModal: hideFileUploadModal,
|
|
||||||
showModal: showFileUploadModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
const setGraphInfo = useSetGraphInfo();
|
|
||||||
const { data } = useFetchAgent();
|
const { data } = useFetchAgent();
|
||||||
const { t } = useTranslation();
|
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
const onFileUploadOk = useCallback(
|
|
||||||
async ({
|
|
||||||
fileList,
|
|
||||||
platform,
|
|
||||||
}: {
|
|
||||||
fileList: File[];
|
|
||||||
platform: Platform;
|
|
||||||
}) => {
|
|
||||||
console.log('🚀 ~ useHandleExportOrImportJsonFile ~ platform:', platform);
|
|
||||||
if (fileList.length > 0) {
|
|
||||||
const file = fileList[0];
|
|
||||||
if (file.type !== FileMimeType.Json) {
|
|
||||||
toast({ title: t('flow.jsonUploadTypeErrorMessage') });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const graphStr = await file.text();
|
|
||||||
const errorMessage = t('flow.jsonUploadContentErrorMessage');
|
|
||||||
try {
|
|
||||||
const graph = JSON.parse(graphStr);
|
|
||||||
if (graphStr && !isEmpty(graph) && Array.isArray(graph?.nodes)) {
|
|
||||||
setGraphInfo(graph ?? ({} as IGraph));
|
|
||||||
hideFileUploadModal();
|
|
||||||
} else {
|
|
||||||
message.error(errorMessage);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[hideFileUploadModal, setGraphInfo, t, toast],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleExportJson = useCallback(() => {
|
const handleExportJson = useCallback(() => {
|
||||||
downloadJsonFile(buildDslData().graph, `${data.title}.json`);
|
downloadJsonFile(buildDslData().graph, `${data.title}.json`);
|
||||||
}, [buildDslData, data.title]);
|
}, [buildDslData, data.title]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fileUploadVisible,
|
|
||||||
handleExportJson,
|
handleExportJson,
|
||||||
handleImportJson: showFileUploadModal,
|
|
||||||
hideFileUploadModal,
|
|
||||||
onFileUploadOk,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
import { useListMcpServer } from '@/hooks/use-mcp-request';
|
|
||||||
|
|
||||||
export function useFindMcpById() {
|
|
||||||
const { data } = useListMcpServer();
|
|
||||||
|
|
||||||
const findMcpById = (id: string) =>
|
|
||||||
data.mcp_servers.find((item) => item.id === id);
|
|
||||||
|
|
||||||
return {
|
|
||||||
findMcpById,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,317 +0,0 @@
|
|||||||
import { AgentGlobals } from '@/constants/agent';
|
|
||||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { Edge } from '@xyflow/react';
|
|
||||||
import { DefaultOptionType } from 'antd/es/select';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import {
|
|
||||||
ReactNode,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import {
|
|
||||||
AgentDialogueMode,
|
|
||||||
BeginId,
|
|
||||||
BeginQueryType,
|
|
||||||
Operator,
|
|
||||||
VariableType,
|
|
||||||
} from '../constant';
|
|
||||||
import { AgentFormContext } from '../context';
|
|
||||||
import { buildBeginInputListFromObject } from '../form/begin-form/utils';
|
|
||||||
import { BeginQuery } from '../interface';
|
|
||||||
import OperatorIcon from '../operator-icon';
|
|
||||||
import useGraphStore from '../store';
|
|
||||||
|
|
||||||
export function useSelectBeginNodeDataInputs() {
|
|
||||||
const getNode = useGraphStore((state) => state.getNode);
|
|
||||||
|
|
||||||
return buildBeginInputListFromObject(
|
|
||||||
getNode(BeginId)?.data?.form?.inputs ?? {},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useIsTaskMode() {
|
|
||||||
const getNode = useGraphStore((state) => state.getNode);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const node = getNode(BeginId);
|
|
||||||
return node?.data?.form?.mode === AgentDialogueMode.Task;
|
|
||||||
}, [getNode]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useGetBeginNodeDataQuery = () => {
|
|
||||||
const getNode = useGraphStore((state) => state.getNode);
|
|
||||||
|
|
||||||
const getBeginNodeDataQuery = useCallback(() => {
|
|
||||||
return buildBeginInputListFromObject(
|
|
||||||
get(getNode(BeginId), 'data.form.inputs', {}),
|
|
||||||
);
|
|
||||||
}, [getNode]);
|
|
||||||
|
|
||||||
return getBeginNodeDataQuery;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetBeginNodeDataInputs = () => {
|
|
||||||
const getNode = useGraphStore((state) => state.getNode);
|
|
||||||
|
|
||||||
const inputs = get(getNode(BeginId), 'data.form.inputs', {});
|
|
||||||
|
|
||||||
const beginNodeDataInputs = useMemo(() => {
|
|
||||||
return buildBeginInputListFromObject(inputs);
|
|
||||||
}, [inputs]);
|
|
||||||
|
|
||||||
return beginNodeDataInputs;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetBeginNodeDataQueryIsSafe = () => {
|
|
||||||
const [isBeginNodeDataQuerySafe, setIsBeginNodeDataQuerySafe] =
|
|
||||||
useState(false);
|
|
||||||
const inputs = useSelectBeginNodeDataInputs();
|
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const query: BeginQuery[] = inputs;
|
|
||||||
const isSafe = !query.some((q) => !q.optional && q.type === 'file');
|
|
||||||
setIsBeginNodeDataQuerySafe(isSafe);
|
|
||||||
}, [inputs, nodes]);
|
|
||||||
|
|
||||||
return isBeginNodeDataQuerySafe;
|
|
||||||
};
|
|
||||||
|
|
||||||
function filterAllUpstreamNodeIds(edges: Edge[], nodeIds: string[]) {
|
|
||||||
return nodeIds.reduce<string[]>((pre, nodeId) => {
|
|
||||||
const currentEdges = edges.filter((x) => x.target === nodeId);
|
|
||||||
|
|
||||||
const upstreamNodeIds: string[] = currentEdges.map((x) => x.source);
|
|
||||||
|
|
||||||
const ids = upstreamNodeIds.concat(
|
|
||||||
filterAllUpstreamNodeIds(edges, upstreamNodeIds),
|
|
||||||
);
|
|
||||||
|
|
||||||
ids.forEach((x) => {
|
|
||||||
if (pre.every((y) => y !== x)) {
|
|
||||||
pre.push(x);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildOutputOptions(
|
|
||||||
outputs: Record<string, any> = {},
|
|
||||||
nodeId?: string,
|
|
||||||
parentLabel?: string | ReactNode,
|
|
||||||
icon?: ReactNode,
|
|
||||||
) {
|
|
||||||
return Object.keys(outputs).map((x) => ({
|
|
||||||
label: x,
|
|
||||||
value: `${nodeId}@${x}`,
|
|
||||||
parentLabel,
|
|
||||||
icon,
|
|
||||||
type: outputs[x]?.type,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBuildNodeOutputOptions(nodeId?: string) {
|
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
|
||||||
const edges = useGraphStore((state) => state.edges);
|
|
||||||
|
|
||||||
const nodeOutputOptions = useMemo(() => {
|
|
||||||
if (!nodeId) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const upstreamIds = filterAllUpstreamNodeIds(edges, [nodeId]);
|
|
||||||
|
|
||||||
const nodeWithOutputList = nodes.filter(
|
|
||||||
(x) =>
|
|
||||||
upstreamIds.some((y) => y === x.id) && !isEmpty(x.data?.form?.outputs),
|
|
||||||
);
|
|
||||||
|
|
||||||
return nodeWithOutputList
|
|
||||||
.filter((x) => x.id !== nodeId)
|
|
||||||
.map((x) => ({
|
|
||||||
label: x.data.name,
|
|
||||||
value: x.id,
|
|
||||||
title: x.data.name,
|
|
||||||
options: buildOutputOptions(
|
|
||||||
x.data.form.outputs,
|
|
||||||
x.id,
|
|
||||||
x.data.name,
|
|
||||||
<OperatorIcon name={x.data.label as Operator} />,
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}, [edges, nodeId, nodes]);
|
|
||||||
|
|
||||||
return nodeOutputOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// exclude nodes with branches
|
|
||||||
const ExcludedNodes = [
|
|
||||||
Operator.Categorize,
|
|
||||||
Operator.Relevant,
|
|
||||||
Operator.Begin,
|
|
||||||
Operator.Note,
|
|
||||||
];
|
|
||||||
|
|
||||||
const StringList = [
|
|
||||||
BeginQueryType.Line,
|
|
||||||
BeginQueryType.Paragraph,
|
|
||||||
BeginQueryType.Options,
|
|
||||||
];
|
|
||||||
|
|
||||||
function transferToVariableType(type: string) {
|
|
||||||
if (StringList.some((x) => x === type)) {
|
|
||||||
return VariableType.String;
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBuildBeginVariableOptions() {
|
|
||||||
const inputs = useSelectBeginNodeDataInputs();
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: <span>{t('flow.beginInput')}</span>,
|
|
||||||
title: t('flow.beginInput'),
|
|
||||||
options: inputs.map((x) => ({
|
|
||||||
label: x.name,
|
|
||||||
parentLabel: <span>{t('flow.beginInput')}</span>,
|
|
||||||
icon: <OperatorIcon name={Operator.Begin} className="block" />,
|
|
||||||
value: `begin@${x.key}`,
|
|
||||||
type: transferToVariableType(x.type),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [inputs]);
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useBuildVariableOptions = (nodeId?: string, parentId?: string) => {
|
|
||||||
const nodeOutputOptions = useBuildNodeOutputOptions(nodeId);
|
|
||||||
const parentNodeOutputOptions = useBuildNodeOutputOptions(parentId);
|
|
||||||
const beginOptions = useBuildBeginVariableOptions();
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return [...beginOptions, ...nodeOutputOptions, ...parentNodeOutputOptions];
|
|
||||||
}, [beginOptions, nodeOutputOptions, parentNodeOutputOptions]);
|
|
||||||
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useBuildQueryVariableOptions(n?: RAGFlowNodeType) {
|
|
||||||
const { data } = useFetchAgent();
|
|
||||||
const node = useContext(AgentFormContext) || n;
|
|
||||||
const options = useBuildVariableOptions(node?.id, node?.parentId);
|
|
||||||
const nextOptions = useMemo(() => {
|
|
||||||
const globals = data?.dsl?.globals ?? {};
|
|
||||||
const globalOptions = Object.entries(globals).map(([key, value]) => ({
|
|
||||||
label: key,
|
|
||||||
value: key,
|
|
||||||
icon: <OperatorIcon name={Operator.Begin} className="block" />,
|
|
||||||
parentLabel: <span>{t('flow.beginInput')}</span>,
|
|
||||||
type: Array.isArray(value)
|
|
||||||
? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '<file>' : ''}`
|
|
||||||
: typeof value,
|
|
||||||
}));
|
|
||||||
return [
|
|
||||||
{ ...options[0], options: [...options[0]?.options, ...globalOptions] },
|
|
||||||
...options.slice(1),
|
|
||||||
];
|
|
||||||
}, [data.dsl?.globals, options]);
|
|
||||||
|
|
||||||
return nextOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBuildComponentIdOptions(nodeId?: string, parentId?: string) {
|
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
|
||||||
|
|
||||||
// Limit the nodes inside iteration to only reference peer nodes with the same parentId and other external nodes other than their parent nodes
|
|
||||||
const filterChildNodesToSameParentOrExternal = useCallback(
|
|
||||||
(node: RAGFlowNodeType) => {
|
|
||||||
// Node inside iteration
|
|
||||||
if (parentId) {
|
|
||||||
return (
|
|
||||||
(node.parentId === parentId || node.parentId === undefined) &&
|
|
||||||
node.id !== parentId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return node.parentId === undefined; // The outermost node
|
|
||||||
},
|
|
||||||
[parentId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const componentIdOptions = useMemo(() => {
|
|
||||||
return nodes
|
|
||||||
.filter(
|
|
||||||
(x) =>
|
|
||||||
x.id !== nodeId &&
|
|
||||||
!ExcludedNodes.some((y) => y === x.data.label) &&
|
|
||||||
filterChildNodesToSameParentOrExternal(x),
|
|
||||||
)
|
|
||||||
.map((x) => ({ label: x.data.name, value: x.id }));
|
|
||||||
}, [nodes, nodeId, filterChildNodesToSameParentOrExternal]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: <span>Component Output</span>,
|
|
||||||
title: 'Component Output',
|
|
||||||
options: componentIdOptions,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBuildComponentIdAndBeginOptions(
|
|
||||||
nodeId?: string,
|
|
||||||
parentId?: string,
|
|
||||||
) {
|
|
||||||
const componentIdOptions = useBuildComponentIdOptions(nodeId, parentId);
|
|
||||||
const beginOptions = useBuildBeginVariableOptions();
|
|
||||||
|
|
||||||
return [...beginOptions, ...componentIdOptions];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useGetComponentLabelByValue = (nodeId: string) => {
|
|
||||||
const options = useBuildComponentIdAndBeginOptions(nodeId);
|
|
||||||
|
|
||||||
const flattenOptions = useMemo(() => {
|
|
||||||
return options.reduce<DefaultOptionType[]>((pre, cur) => {
|
|
||||||
return [...pre, ...cur.options];
|
|
||||||
}, []);
|
|
||||||
}, [options]);
|
|
||||||
|
|
||||||
const getLabel = useCallback(
|
|
||||||
(val?: string) => {
|
|
||||||
return flattenOptions.find((x) => x.value === val)?.label;
|
|
||||||
},
|
|
||||||
[flattenOptions],
|
|
||||||
);
|
|
||||||
return getLabel;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useGetVariableLabelByValue(nodeId: string) {
|
|
||||||
const { getNode } = useGraphStore((state) => state);
|
|
||||||
const nextOptions = useBuildQueryVariableOptions(getNode(nodeId));
|
|
||||||
|
|
||||||
const flattenOptions = useMemo(() => {
|
|
||||||
return nextOptions.reduce<DefaultOptionType[]>((pre, cur) => {
|
|
||||||
return [...pre, ...cur.options];
|
|
||||||
}, []);
|
|
||||||
}, [nextOptions]);
|
|
||||||
|
|
||||||
const getLabel = useCallback(
|
|
||||||
(val?: string) => {
|
|
||||||
return flattenOptions.find((x) => x.value === val)?.label;
|
|
||||||
},
|
|
||||||
[flattenOptions],
|
|
||||||
);
|
|
||||||
return getLabel;
|
|
||||||
}
|
|
||||||
@ -21,7 +21,6 @@ export function useRunDataflow(showLogSheet: () => void) {
|
|||||||
session_id: null,
|
session_id: null,
|
||||||
files: [fileResponseData.file],
|
files: [fileResponseData.file],
|
||||||
});
|
});
|
||||||
console.log('🚀 ~ useRunDataflow ~ res:', res);
|
|
||||||
|
|
||||||
if (res && res?.response.status === 200 && res?.data?.code === 0) {
|
if (res && res?.response.status === 200 && res?.data?.code === 0) {
|
||||||
// fetch canvas
|
// fetch canvas
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import React, { useCallback, useEffect } from 'react';
|
|||||||
import { BeginId, Operator } from '../constant';
|
import { BeginId, Operator } from '../constant';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
import { useCacheChatLog } from './use-cache-chat-log';
|
import { useCacheChatLog } from './use-cache-chat-log';
|
||||||
import { useGetBeginNodeDataInputs } from './use-get-begin-query';
|
|
||||||
import { useSaveGraph } from './use-save-graph';
|
import { useSaveGraph } from './use-save-graph';
|
||||||
|
|
||||||
export const useShowFormDrawer = () => {
|
export const useShowFormDrawer = () => {
|
||||||
@ -84,20 +83,12 @@ export function useShowDrawer({
|
|||||||
} = useShowSingleDebugDrawer();
|
} = useShowSingleDebugDrawer();
|
||||||
const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } =
|
const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } =
|
||||||
useShowFormDrawer();
|
useShowFormDrawer();
|
||||||
const inputs = useGetBeginNodeDataInputs();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (drawerVisible) {
|
if (drawerVisible) {
|
||||||
showRunModal();
|
showRunModal();
|
||||||
}
|
}
|
||||||
}, [
|
}, [hideChatModal, hideRunModal, showChatModal, showRunModal, drawerVisible]);
|
||||||
hideChatModal,
|
|
||||||
hideRunModal,
|
|
||||||
showChatModal,
|
|
||||||
showRunModal,
|
|
||||||
drawerVisible,
|
|
||||||
inputs,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const hideRunOrChatDrawer = useCallback(() => {
|
const hideRunOrChatDrawer = useCallback(() => {
|
||||||
hideChatModal();
|
hideChatModal();
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import { ReactFlowProvider } from '@xyflow/react';
|
|||||||
import {
|
import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
CirclePlay,
|
CirclePlay,
|
||||||
Download,
|
|
||||||
History,
|
History,
|
||||||
LaptopMinimalCheck,
|
LaptopMinimalCheck,
|
||||||
Settings,
|
Settings,
|
||||||
@ -33,14 +32,12 @@ import DataFlowCanvas from './canvas';
|
|||||||
import { DropdownProvider } from './canvas/context';
|
import { DropdownProvider } from './canvas/context';
|
||||||
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
||||||
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||||
import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query';
|
|
||||||
import {
|
import {
|
||||||
useSaveGraph,
|
useSaveGraph,
|
||||||
useSaveGraphBeforeOpeningDebugDrawer,
|
useSaveGraphBeforeOpeningDebugDrawer,
|
||||||
useWatchAgentChange,
|
useWatchAgentChange,
|
||||||
} from './hooks/use-save-graph';
|
} from './hooks/use-save-graph';
|
||||||
import { SettingDialog } from './setting-dialog';
|
import { SettingDialog } from './setting-dialog';
|
||||||
import { UploadAgentDialog } from './upload-agent-dialog';
|
|
||||||
import { useAgentHistoryManager } from './use-agent-history-manager';
|
import { useAgentHistoryManager } from './use-agent-history-manager';
|
||||||
import { VersionDialog } from './version-dialog';
|
import { VersionDialog } from './version-dialog';
|
||||||
|
|
||||||
@ -64,24 +61,13 @@ export default function DataFlow() {
|
|||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useAgentHistoryManager();
|
useAgentHistoryManager();
|
||||||
const {
|
const { handleExportJson } = useHandleExportOrImportJsonFile();
|
||||||
handleExportJson,
|
|
||||||
handleImportJson,
|
|
||||||
fileUploadVisible,
|
|
||||||
onFileUploadOk,
|
|
||||||
hideFileUploadModal,
|
|
||||||
} = useHandleExportOrImportJsonFile();
|
|
||||||
const { saveGraph, loading } = useSaveGraph();
|
const { saveGraph, loading } = useSaveGraph();
|
||||||
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
||||||
const inputs = useGetBeginNodeDataInputs();
|
|
||||||
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
||||||
const handleRunAgent = useCallback(() => {
|
const handleRunAgent = useCallback(() => {
|
||||||
if (inputs.length > 0) {
|
handleRun();
|
||||||
showChatDrawer();
|
}, [handleRun]);
|
||||||
} else {
|
|
||||||
handleRun();
|
|
||||||
}
|
|
||||||
}, [handleRun, inputs, showChatDrawer]);
|
|
||||||
const {
|
const {
|
||||||
visible: versionDialogVisible,
|
visible: versionDialogVisible,
|
||||||
hideModal: hideVersionDialog,
|
hideModal: hideVersionDialog,
|
||||||
@ -141,11 +127,6 @@ export default function DataFlow() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<AgentDropdownMenuItem onClick={handleImportJson}>
|
|
||||||
<Download />
|
|
||||||
{t('flow.import')}
|
|
||||||
</AgentDropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<AgentDropdownMenuItem onClick={handleExportJson}>
|
<AgentDropdownMenuItem onClick={handleExportJson}>
|
||||||
<Upload />
|
<Upload />
|
||||||
{t('flow.export')}
|
{t('flow.export')}
|
||||||
@ -167,12 +148,6 @@ export default function DataFlow() {
|
|||||||
></DataFlowCanvas>
|
></DataFlowCanvas>
|
||||||
</DropdownProvider>
|
</DropdownProvider>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
{fileUploadVisible && (
|
|
||||||
<UploadAgentDialog
|
|
||||||
hideModal={hideFileUploadModal}
|
|
||||||
onOk={onFileUploadOk}
|
|
||||||
></UploadAgentDialog>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{versionDialogVisible && (
|
{versionDialogVisible && (
|
||||||
<DropdownProvider>
|
<DropdownProvider>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,36 +0,0 @@
|
|||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog';
|
|
||||||
import { LoadingButton } from '@/components/ui/loading-button';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { UploadAgentForm } from './upload-agent-form';
|
|
||||||
|
|
||||||
export function UploadAgentDialog({
|
|
||||||
hideModal,
|
|
||||||
onOk,
|
|
||||||
loading,
|
|
||||||
}: IModalProps<any>) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open onOpenChange={hideModal}>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>{t('fileManager.uploadFile')}</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<UploadAgentForm hideModal={hideModal} onOk={onOk}></UploadAgentForm>
|
|
||||||
<DialogFooter>
|
|
||||||
<LoadingButton type="submit" form={TagRenameId} loading={loading}>
|
|
||||||
{t('common.save')}
|
|
||||||
</LoadingButton>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { FileUploader } from '@/components/file-uploader';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { FileMimeType, Platform } from '@/constants/common';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
// const options = Object.values(Platform).map((x) => ({ label: x, value: x }));
|
|
||||||
|
|
||||||
export function UploadAgentForm({ hideModal, onOk }: IModalProps<any>) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const FormSchema = z.object({
|
|
||||||
platform: z
|
|
||||||
.string()
|
|
||||||
.min(1, {
|
|
||||||
message: t('common.namePlaceholder'),
|
|
||||||
})
|
|
||||||
.trim(),
|
|
||||||
fileList: z.array(z.instanceof(File)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
defaultValues: { platform: Platform.RAGFlow },
|
|
||||||
});
|
|
||||||
|
|
||||||
async function onSubmit(data: z.infer<typeof FormSchema>) {
|
|
||||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
|
||||||
const ret = await onOk?.(data);
|
|
||||||
if (ret) {
|
|
||||||
hideModal?.();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="space-y-6"
|
|
||||||
id={TagRenameId}
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="fileList"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('common.name')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<FileUploader
|
|
||||||
value={field.value}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
maxFileCount={1}
|
|
||||||
accept={{ '*.json': [FileMimeType.Json] }}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* <FormField
|
|
||||||
control={form.control}
|
|
||||||
name="platform"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('common.name')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect {...field} options={options} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/> */}
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { MessageType } from '@/constants/chat';
|
|
||||||
import { IReference } from '@/interfaces/database/chat';
|
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
|
|
||||||
export const buildAgentMessageItemReference = (
|
|
||||||
conversation: { message: IMessage[]; reference: IReference[] },
|
|
||||||
message: IMessage,
|
|
||||||
) => {
|
|
||||||
const assistantMessages = conversation.message?.filter(
|
|
||||||
(x) => x.role === MessageType.Assistant,
|
|
||||||
);
|
|
||||||
const referenceIndex = assistantMessages.findIndex(
|
|
||||||
(x) => x.id === message.id,
|
|
||||||
);
|
|
||||||
const reference = !isEmpty(message?.reference)
|
|
||||||
? message?.reference
|
|
||||||
: (conversation?.reference ?? [])[referenceIndex];
|
|
||||||
|
|
||||||
return reference ?? { doc_aggs: [], chunks: [], total: 0 };
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user