mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Feat: Enables the message operator form to reference the data defined by the begin operator #3221 (#8108)
### What problem does this PR solve? Feat: Enables the message operator form to reference the data defined by the begin operator #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,12 +1,12 @@
|
|||||||
import { settledModelVariableMap } from '@/constants/knowledge';
|
import { settledModelVariableMap } from '@/constants/knowledge';
|
||||||
import { FlowFormContext } from '@/pages/agent/context';
|
import { AgentFormContext } from '@/pages/agent/context';
|
||||||
import useGraphStore from '@/pages/agent/store';
|
import useGraphStore from '@/pages/agent/store';
|
||||||
import { useCallback, useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
export function useHandleFreedomChange() {
|
export function useHandleFreedomChange() {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const node = useContext(FlowFormContext);
|
const node = useContext(AgentFormContext);
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
import useGraphStore from '../../store';
|
import useGraphStore from '../../store';
|
||||||
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
import { useTheme } from '@/components/theme-provider';
|
||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ export function ButtonEdge({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// highlight the nodes that the workflow passes through
|
// highlight the nodes that the workflow passes through
|
||||||
const { data: flowDetail } = useFetchFlow();
|
const { data: flowDetail } = useFetchAgent();
|
||||||
|
|
||||||
const graphPath = useMemo(() => {
|
const graphPath = useMemo(() => {
|
||||||
// TODO: this will be called multiple times
|
// TODO: this will be called multiple times
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import React, { MouseEventHandler, useCallback, useMemo } from 'react';
|
import React, { MouseEventHandler, useCallback, useMemo } from 'react';
|
||||||
import JsonView from 'react18-json-view';
|
import JsonView from 'react18-json-view';
|
||||||
@ -20,6 +19,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
||||||
|
|
||||||
interface IProps extends React.PropsWithChildren {
|
interface IProps extends React.PropsWithChildren {
|
||||||
@ -30,7 +30,7 @@ interface IProps extends React.PropsWithChildren {
|
|||||||
export function NextNodePopover({ children, nodeId, name }: IProps) {
|
export function NextNodePopover({ children, nodeId, name }: IProps) {
|
||||||
const { t } = useTranslate('flow');
|
const { t } = useTranslate('flow');
|
||||||
|
|
||||||
const { data } = useFetchFlow();
|
const { data } = useFetchAgent();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const component = useMemo(() => {
|
const component = useMemo(() => {
|
||||||
return get(data, ['dsl', 'components', nodeId], {});
|
return get(data, ['dsl', 'components', nodeId], {});
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { useSendNextMessage } from './hooks';
|
|||||||
import MessageInput from '@/components/message-input';
|
import MessageInput from '@/components/message-input';
|
||||||
import PdfDrawer from '@/components/pdf-drawer';
|
import PdfDrawer from '@/components/pdf-drawer';
|
||||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ const AgentChatBox = () => {
|
|||||||
useClickDrawer();
|
useClickDrawer();
|
||||||
useGetFileIcon();
|
useGetFileIcon();
|
||||||
const { data: userInfo } = useFetchUserInfo();
|
const { data: userInfo } = useFetchUserInfo();
|
||||||
const { data: canvasInfo } = useFetchFlow();
|
const { data: canvasInfo } = useFetchAgent();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
|
||||||
import {
|
import {
|
||||||
useHandleMessageInputChange,
|
useHandleMessageInputChange,
|
||||||
useSelectDerivedMessages,
|
useSelectDerivedMessages,
|
||||||
} from '@/hooks/logic-hooks';
|
} from '@/hooks/logic-hooks';
|
||||||
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import {
|
import {
|
||||||
IEventList,
|
IEventList,
|
||||||
IMessageEvent,
|
IMessageEvent,
|
||||||
@ -23,7 +23,7 @@ import { receiveMessageError } from '../utils';
|
|||||||
const antMessage = message;
|
const antMessage = message;
|
||||||
|
|
||||||
export const useSelectNextMessages = () => {
|
export const useSelectNextMessages = () => {
|
||||||
const { data: flowDetail, loading } = useFetchFlow();
|
const { data: flowDetail, loading } = useFetchAgent();
|
||||||
const reference = flowDetail.dsl.reference;
|
const reference = flowDetail.dsl.reference;
|
||||||
const {
|
const {
|
||||||
derivedMessages,
|
derivedMessages,
|
||||||
@ -69,7 +69,7 @@ export const useSendNextMessage = () => {
|
|||||||
} = useSelectNextMessages();
|
} = useSelectNextMessages();
|
||||||
const { id: agentId } = useParams();
|
const { id: agentId } = useParams();
|
||||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||||
const { refetch } = useFetchFlow();
|
const { refetch } = useFetchAgent();
|
||||||
|
|
||||||
const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
|
const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
|
||||||
api.runCanvas,
|
api.runCanvas,
|
||||||
|
|||||||
@ -696,6 +696,21 @@ export const initialAgentValues = {
|
|||||||
prompts: [],
|
prompts: [],
|
||||||
message_history_window_size: 12,
|
message_history_window_size: 12,
|
||||||
tools: [],
|
tools: [],
|
||||||
|
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 CategorizeAnchorPointPositions = [
|
export const CategorizeAnchorPointPositions = [
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
export const FlowFormContext = createContext<RAGFlowNodeType | undefined>(
|
export const AgentFormContext = createContext<RAGFlowNodeType | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { Play, X } from 'lucide-react';
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { BeginId, Operator, operatorMap } from '../constant';
|
import { BeginId, Operator, operatorMap } from '../constant';
|
||||||
import { FlowFormContext } from '../context';
|
import { AgentFormContext } from '../context';
|
||||||
import { RunTooltip } from '../flow-tooltip';
|
import { RunTooltip } from '../flow-tooltip';
|
||||||
import { useHandleNodeNameChange } from '../hooks';
|
import { useHandleNodeNameChange } from '../hooks';
|
||||||
import { useHandleFormValuesChange } from '../hooks/use-watch-form-change';
|
import { useHandleFormValuesChange } from '../hooks/use-watch-form-change';
|
||||||
@ -145,13 +145,13 @@ const FormSheet = ({
|
|||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<section className="pt-4 overflow-auto max-h-[85vh]">
|
<section className="pt-4 overflow-auto max-h-[85vh]">
|
||||||
{visible && (
|
{visible && (
|
||||||
<FlowFormContext.Provider value={node}>
|
<AgentFormContext.Provider value={node}>
|
||||||
<OperatorForm
|
<OperatorForm
|
||||||
onValuesChange={handleValuesChange}
|
onValuesChange={handleValuesChange}
|
||||||
form={form}
|
form={form}
|
||||||
node={node}
|
node={node}
|
||||||
></OperatorForm>
|
></OperatorForm>
|
||||||
</FlowFormContext.Provider>
|
</AgentFormContext.Provider>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { PromptEditor } from '@/components/prompt-editor';
|
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
import { BlockButton, Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -12,6 +11,7 @@ import { X } from 'lucide-react';
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PromptEditor } from '../components/prompt-editor';
|
||||||
|
|
||||||
export enum PromptRole {
|
export enum PromptRole {
|
||||||
User = 'user',
|
User = 'user',
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { FormContainer } from '@/components/form-container';
|
|||||||
import { LargeModelFormField } from '@/components/large-model-form-field';
|
import { LargeModelFormField } from '@/components/large-model-form-field';
|
||||||
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
||||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
||||||
import { PromptEditor } from '@/components/prompt-editor';
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -12,6 +11,7 @@ import {
|
|||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { useFetchModelId } from '@/hooks/logic-hooks';
|
import { useFetchModelId } from '@/hooks/logic-hooks';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@ -19,6 +19,8 @@ import { initialAgentValues } from '../../constant';
|
|||||||
import { useFormValues } from '../../hooks/use-form-values';
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
|
import { Output } from '../components/output';
|
||||||
|
import { PromptEditor } from '../components/prompt-editor';
|
||||||
import DynamicPrompt from './dynamic-prompt';
|
import DynamicPrompt from './dynamic-prompt';
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
@ -50,6 +52,12 @@ const AgentForm = ({ node }: INextOperatorForm) => {
|
|||||||
node,
|
node,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const outputList = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ title: 'content', type: initialAgentValues.outputs.content.type },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
defaultValues: defaultValues,
|
defaultValues: defaultValues,
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
@ -88,6 +96,7 @@ const AgentForm = ({ node }: INextOperatorForm) => {
|
|||||||
<FormContainer>
|
<FormContainer>
|
||||||
<DynamicPrompt></DynamicPrompt>
|
<DynamicPrompt></DynamicPrompt>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
|
<Output list={outputList}></Output>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { isEmpty } from 'lodash';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AgentDialogueMode } from '../../constant';
|
import { AgentDialogueMode } from '../../constant';
|
||||||
import { BeginQuery } from '../../interface';
|
import { buildBeginInputListFromObject } from './utils';
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
export function useValues(node?: RAGFlowNodeType) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -25,14 +25,7 @@ export function useValues(node?: RAGFlowNodeType) {
|
|||||||
return defaultValues;
|
return defaultValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputs = Object.entries(formData?.inputs || {}).reduce<BeginQuery[]>(
|
const inputs = buildBeginInputListFromObject(formData?.inputs);
|
||||||
(pre, [key, value]) => {
|
|
||||||
pre.push({ ...(value || {}), key });
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { ...(formData || {}), inputs };
|
return { ...(formData || {}), inputs };
|
||||||
}, [defaultValues, node?.data?.form]);
|
}, [defaultValues, node?.data?.form]);
|
||||||
|
|||||||
14
web/src/pages/agent/form/begin-form/utils.ts
Normal file
14
web/src/pages/agent/form/begin-form/utils.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
26
web/src/pages/agent/form/components/output.tsx
Normal file
26
web/src/pages/agent/form/components/output.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
type OutputType = {
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OutputProps = {
|
||||||
|
list: Array<OutputType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Output({ list }: OutputProps) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-2">
|
||||||
|
<div>Output</div>
|
||||||
|
<ul>
|
||||||
|
{list.map((x, idx) => (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
className="bg-background-highlight text-background-checked rounded-sm px-2 py-1"
|
||||||
|
>
|
||||||
|
{x.title}: {x.type}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export const ProgrammaticTag = 'programmatic';
|
||||||
76
web/src/pages/agent/form/components/prompt-editor/index.css
Normal file
76
web/src/pages/agent/form/components/prompt-editor/index.css
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
164
web/src/pages/agent/form/components/prompt-editor/index.tsx
Normal file
164
web/src/pages/agent/form/components/prompt-editor/index.tsx
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
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,
|
||||||
|
$nodesOfType,
|
||||||
|
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 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 };
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value?: string) => void;
|
||||||
|
placeholder?: ReactNode;
|
||||||
|
} & PromptContentProps;
|
||||||
|
|
||||||
|
function PromptContent({ showToolbar = 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="min-h-40 relative px-2 py-1 focus-visible:outline-none"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PromptEditor({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder,
|
||||||
|
showToolbar,
|
||||||
|
}: 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();
|
||||||
|
console.log('🚀 ~ onChange ~ allNodes:', listNodes);
|
||||||
|
|
||||||
|
const text = $getRoot().getTextContent();
|
||||||
|
|
||||||
|
onChange?.(text);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<LexicalComposer initialConfig={initialConfig}>
|
||||||
|
<RichTextPlugin
|
||||||
|
contentEditable={
|
||||||
|
<PromptContent showToolbar={showToolbar}></PromptContent>
|
||||||
|
}
|
||||||
|
placeholder={
|
||||||
|
<div
|
||||||
|
className="absolute top-10 left-2 text-text-sub-title"
|
||||||
|
data-xxx
|
||||||
|
>
|
||||||
|
{placeholder || t('common.pleaseInput')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
ErrorBoundary={LexicalErrorBoundary}
|
||||||
|
/>
|
||||||
|
<VariablePickerMenuPlugin value={value}></VariablePickerMenuPlugin>
|
||||||
|
<VariableOnChangePlugin
|
||||||
|
onChange={onValueChange}
|
||||||
|
></VariableOnChangePlugin>
|
||||||
|
</LexicalComposer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
web/src/pages/agent/form/components/prompt-editor/theme.ts
Normal file
43
web/src/pages/agent/form/components/prompt-editor/theme.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* 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',
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
import i18n from '@/locales/config';
|
||||||
|
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;
|
||||||
|
|
||||||
|
static getType(): string {
|
||||||
|
return 'variable';
|
||||||
|
}
|
||||||
|
|
||||||
|
static clone(node: VariableNode): VariableNode {
|
||||||
|
return new VariableNode(node.__value, node.__label, node.__key);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(value: string, label: string, key?: NodeKey) {
|
||||||
|
super(key);
|
||||||
|
this.__value = value;
|
||||||
|
this.__label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDOM(): HTMLElement {
|
||||||
|
const dom = document.createElement('span');
|
||||||
|
dom.className = 'mr-1';
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDOM(): false {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
decorate(): ReactNode {
|
||||||
|
let content: ReactNode = (
|
||||||
|
<span className="text-blue-600">{this.__label}</span>
|
||||||
|
);
|
||||||
|
if (this.__value.startsWith(prefix)) {
|
||||||
|
content = (
|
||||||
|
<div>
|
||||||
|
<span>{i18n.t(`flow.begin`)}</span> / {content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="bg-gray-200 dark:bg-gray-400 text-primary inline-flex items-center rounded-md px-2 py-0">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTextContent(): string {
|
||||||
|
return `{${this.__value}}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function $createVariableNode(
|
||||||
|
value: string,
|
||||||
|
label: string,
|
||||||
|
): VariableNode {
|
||||||
|
return new VariableNode(value, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function $isVariableNode(
|
||||||
|
node: LexicalNode | null | undefined,
|
||||||
|
): node is VariableNode {
|
||||||
|
return node instanceof VariableNode;
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@ -0,0 +1,283 @@
|
|||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
import * as ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import { $createVariableNode } from './variable-node';
|
||||||
|
|
||||||
|
import { AgentFormContext } from '@/pages/agent/context';
|
||||||
|
import { useBuildComponentIdSelectOptions } from '@/pages/agent/hooks/use-get-begin-query';
|
||||||
|
import { ProgrammaticTag } from './constant';
|
||||||
|
import './index.css';
|
||||||
|
class VariableInnerOption extends MenuOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
constructor(label: string, value: string) {
|
||||||
|
super(value);
|
||||||
|
this.label = label;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}) {
|
||||||
|
console.info('xxxx');
|
||||||
|
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 node = useContext(AgentFormContext);
|
||||||
|
|
||||||
|
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
|
||||||
|
minLength: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [queryString, setQueryString] = React.useState<string | null>('');
|
||||||
|
|
||||||
|
const buildGroupedOptions = useBuildComponentIdSelectOptions(
|
||||||
|
node?.id,
|
||||||
|
node?.parentId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildNextOptions = useCallback(() => {
|
||||||
|
const options = buildGroupedOptions();
|
||||||
|
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) => new VariableInnerOption(y.label, y.value)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return nextOptions;
|
||||||
|
}, [buildGroupedOptions, queryString]);
|
||||||
|
|
||||||
|
const findLabelByValue = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
const options = buildGroupedOptions();
|
||||||
|
const children = options.reduce<Array<{ label: string; value: string }>>(
|
||||||
|
(pre, cur) => {
|
||||||
|
return pre.concat(cur.options);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return children.find((x) => x.value === value)?.label;
|
||||||
|
},
|
||||||
|
[buildGroupedOptions],
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.insertNodes([
|
||||||
|
$createVariableNode(
|
||||||
|
(selectedOption as VariableInnerOption).value,
|
||||||
|
selectedOption.label as string,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
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 label = findLabelByValue(content);
|
||||||
|
if (label) {
|
||||||
|
paragraph.append($createVariableNode(content, label));
|
||||||
|
} 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);
|
||||||
|
},
|
||||||
|
[findLabelByValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
|
console.log('🚀 ~ nextOptions:', nextOptions);
|
||||||
|
return anchorElementRef.current && nextOptions.length
|
||||||
|
? ReactDOM.createPortal(
|
||||||
|
<div className="typeahead-popover w-[200px] p-2">
|
||||||
|
<ul>
|
||||||
|
{nextOptions.map((option, i: number) => (
|
||||||
|
<VariablePickerMenuItem
|
||||||
|
index={i}
|
||||||
|
key={option.key}
|
||||||
|
option={option}
|
||||||
|
selectOptionAndCleanUp={selectOptionAndCleanUp}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>,
|
||||||
|
anchorElementRef.current,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { NextLLMSelect } from '@/components/llm-select/next';
|
import { NextLLMSelect } from '@/components/llm-select/next';
|
||||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
||||||
import { PromptEditor } from '@/components/prompt-editor';
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -12,6 +11,7 @@ import {
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
|
import { PromptEditor } from '../components/prompt-editor';
|
||||||
|
|
||||||
const GenerateForm = ({ form }: INextOperatorForm) => {
|
const GenerateForm = ({ form }: INextOperatorForm) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
import { FormContainer } from '@/components/form-container';
|
||||||
import { PromptEditor } from '@/components/prompt-editor';
|
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
import { BlockButton, Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@ -15,6 +14,7 @@ import { useFieldArray, useForm } from 'react-hook-form';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
|
import { PromptEditor } from '../components/prompt-editor';
|
||||||
import { useValues } from './use-values';
|
import { useValues } from './use-values';
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
import { useWatchFormChange } from './use-watch-change';
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { PromptEditor } from '@/components/prompt-editor';
|
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IOperatorForm } from '../../interface';
|
import { IOperatorForm } from '../../interface';
|
||||||
|
import { PromptEditor } from '../components/prompt-editor';
|
||||||
|
|
||||||
const TemplateForm = ({ onValuesChange, form }: IOperatorForm) => {
|
const TemplateForm = ({ onValuesChange, form }: IOperatorForm) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
import { buildDslComponentsByGraph } from '../utils';
|
import { buildDslComponentsByGraph } from '../utils';
|
||||||
|
|
||||||
export const useBuildDslData = () => {
|
export const useBuildDslData = () => {
|
||||||
const { data } = useFetchFlow();
|
const { data } = useFetchAgent();
|
||||||
const { nodes, edges } = useGraphStore((state) => state);
|
const { nodes, edges } = useGraphStore((state) => state);
|
||||||
|
|
||||||
const buildDslData = useCallback(
|
const buildDslData = useCallback(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useToast } from '@/components/hooks/use-toast';
|
import { useToast } from '@/components/hooks/use-toast';
|
||||||
import { FileMimeType, Platform } from '@/constants/common';
|
import { FileMimeType, Platform } from '@/constants/common';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { IGraph } from '@/interfaces/database/flow';
|
import { IGraph } from '@/interfaces/database/flow';
|
||||||
import { downloadJsonFile } from '@/utils/file-util';
|
import { downloadJsonFile } from '@/utils/file-util';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
@ -19,7 +19,7 @@ export const useHandleExportOrImportJsonFile = () => {
|
|||||||
showModal: showFileUploadModal,
|
showModal: showFileUploadModal,
|
||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
const setGraphInfo = useSetGraphInfo();
|
const setGraphInfo = useSetGraphInfo();
|
||||||
const { data } = useFetchFlow();
|
const { data } = useFetchAgent();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { IGraph } from '@/interfaces/database/flow';
|
import { IGraph } from '@/interfaces/database/flow';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useSetGraphInfo } from './use-set-graph';
|
import { useSetGraphInfo } from './use-set-graph';
|
||||||
|
|
||||||
export const useFetchDataOnMount = () => {
|
export const useFetchDataOnMount = () => {
|
||||||
const { loading, data, refetch } = useFetchFlow();
|
const { loading, data, refetch } = useFetchAgent();
|
||||||
const setGraphInfo = useSetGraphInfo();
|
const setGraphInfo = useSetGraphInfo();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { DefaultOptionType } from 'antd/es/select';
|
|||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { BeginId, Operator } from '../constant';
|
import { BeginId, Operator } from '../constant';
|
||||||
|
import { buildBeginInputListFromObject } from '../form/begin-form/utils';
|
||||||
import { BeginQuery } from '../interface';
|
import { BeginQuery } from '../interface';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
|
|
||||||
@ -10,7 +11,9 @@ export const useGetBeginNodeDataQuery = () => {
|
|||||||
const getNode = useGraphStore((state) => state.getNode);
|
const getNode = useGraphStore((state) => state.getNode);
|
||||||
|
|
||||||
const getBeginNodeDataQuery = useCallback(() => {
|
const getBeginNodeDataQuery = useCallback(() => {
|
||||||
return get(getNode(BeginId), 'data.form.query', []);
|
return buildBeginInputListFromObject(
|
||||||
|
get(getNode(BeginId), 'data.form.inputs', {}),
|
||||||
|
);
|
||||||
}, [getNode]);
|
}, [getNode]);
|
||||||
|
|
||||||
return getBeginNodeDataQuery;
|
return getBeginNodeDataQuery;
|
||||||
@ -45,7 +48,6 @@ export const useBuildComponentIdSelectOptions = (
|
|||||||
) => {
|
) => {
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
const nodes = useGraphStore((state) => state.nodes);
|
||||||
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||||
const query: BeginQuery[] = getBeginNodeDataQuery();
|
|
||||||
|
|
||||||
// Limit the nodes inside iteration to only reference peer nodes with the same parentId and other external nodes other than their parent 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(
|
const filterChildNodesToSameParentOrExternal = useCallback(
|
||||||
@ -74,7 +76,9 @@ export const useBuildComponentIdSelectOptions = (
|
|||||||
.map((x) => ({ label: x.data.name, value: x.id }));
|
.map((x) => ({ label: x.data.name, value: x.id }));
|
||||||
}, [nodes, nodeId, filterChildNodesToSameParentOrExternal]);
|
}, [nodes, nodeId, filterChildNodesToSameParentOrExternal]);
|
||||||
|
|
||||||
const groupedOptions = [
|
const buildGroupedOptions = useCallback(() => {
|
||||||
|
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
label: <span>Component Output</span>,
|
label: <span>Component Output</span>,
|
||||||
title: 'Component Output',
|
title: 'Component Output',
|
||||||
@ -89,19 +93,20 @@ export const useBuildComponentIdSelectOptions = (
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
}, [componentIdOptions, getBeginNodeDataQuery]);
|
||||||
|
|
||||||
return groupedOptions;
|
return buildGroupedOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetComponentLabelByValue = (nodeId: string) => {
|
export const useGetComponentLabelByValue = (nodeId: string) => {
|
||||||
const options = useBuildComponentIdSelectOptions(nodeId);
|
const buildGroupedOptions = useBuildComponentIdSelectOptions(nodeId);
|
||||||
const flattenOptions = useMemo(
|
|
||||||
() =>
|
const flattenOptions = useMemo(() => {
|
||||||
options.reduce<DefaultOptionType[]>((pre, cur) => {
|
const options = buildGroupedOptions();
|
||||||
|
return options.reduce<DefaultOptionType[]>((pre, cur) => {
|
||||||
return [...pre, ...cur.options];
|
return [...pre, ...cur.options];
|
||||||
}, []),
|
}, []);
|
||||||
[options],
|
}, [buildGroupedOptions]);
|
||||||
);
|
|
||||||
|
|
||||||
const getLabel = useCallback(
|
const getLabel = useCallback(
|
||||||
(val?: string) => {
|
(val?: string) => {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export const useSaveGraph = () => {
|
|||||||
dsl: buildDslData(currentNodes),
|
dsl: buildDslData(currentNodes),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setAgent, id, data.title, buildDslData],
|
[setAgent, data, id, buildDslData],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { saveGraph, loading };
|
return { saveGraph, loading };
|
||||||
|
|||||||
@ -51,6 +51,7 @@ module.exports = {
|
|||||||
'background-header-bar': 'var(--background-header-bar)',
|
'background-header-bar': 'var(--background-header-bar)',
|
||||||
'background-card': 'var(--background-card)',
|
'background-card': 'var(--background-card)',
|
||||||
'background-checked': 'var(--background-checked)',
|
'background-checked': 'var(--background-checked)',
|
||||||
|
'background-highlight': 'var(--background-highlight)',
|
||||||
|
|
||||||
'input-border': 'var(--input-border)',
|
'input-border': 'var(--input-border)',
|
||||||
|
|
||||||
|
|||||||
@ -86,6 +86,7 @@
|
|||||||
--background-card: rgba(22, 22, 24, 0.05);
|
--background-card: rgba(22, 22, 24, 0.05);
|
||||||
|
|
||||||
--background-checked: rgba(76, 164, 231, 1);
|
--background-checked: rgba(76, 164, 231, 1);
|
||||||
|
--background-highlight: rgba(76, 164, 231, 0.1);
|
||||||
|
|
||||||
--input-border: rgba(22, 22, 24, 0.2);
|
--input-border: rgba(22, 22, 24, 0.2);
|
||||||
}
|
}
|
||||||
@ -195,6 +196,8 @@
|
|||||||
--background-card: rgba(255, 255, 255, 0.05);
|
--background-card: rgba(255, 255, 255, 0.05);
|
||||||
--background-checked: rgba(76, 164, 231, 1);
|
--background-checked: rgba(76, 164, 231, 1);
|
||||||
|
|
||||||
|
--background-highlight: rgba(76, 164, 231, 0.1);
|
||||||
|
|
||||||
--input-border: rgba(255, 255, 255, 0.2);
|
--input-border: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user