mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-06 10:35:06 +08:00
Feat: Move the reasoning field to the root of the payload in the completion interface. (#12990)
### What problem does this PR solve? ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -30,6 +30,11 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { AudioButton } from '../ui/audio-button';
|
import { AudioButton } from '../ui/audio-button';
|
||||||
|
|
||||||
|
export type NextMessageInputOnPressEnterParameter = {
|
||||||
|
enableThinking: boolean;
|
||||||
|
enableInternet: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
interface NextMessageInputProps {
|
interface NextMessageInputProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
value: string;
|
value: string;
|
||||||
@ -43,10 +48,7 @@ interface NextMessageInputProps {
|
|||||||
onPressEnter({
|
onPressEnter({
|
||||||
enableThinking,
|
enableThinking,
|
||||||
enableInternet,
|
enableInternet,
|
||||||
}: {
|
}: NextMessageInputOnPressEnterParameter): void;
|
||||||
enableThinking: boolean;
|
|
||||||
enableInternet: boolean;
|
|
||||||
}): void;
|
|
||||||
onInputChange: React.ChangeEventHandler<HTMLTextAreaElement>;
|
onInputChange: React.ChangeEventHandler<HTMLTextAreaElement>;
|
||||||
createConversationBeforeUploadDocument?(message: string): Promise<any>;
|
createConversationBeforeUploadDocument?(message: string): Promise<any>;
|
||||||
stopOutputMessage?(): void;
|
stopOutputMessage?(): void;
|
||||||
@ -56,10 +58,6 @@ interface NextMessageInputProps {
|
|||||||
showInternet?: boolean;
|
showInternet?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NextMessageInputOnPressEnterParameter = Parameters<
|
|
||||||
NextMessageInputProps['onPressEnter']
|
|
||||||
>;
|
|
||||||
|
|
||||||
export function NextMessageInput({
|
export function NextMessageInput({
|
||||||
isUploading = false,
|
isUploading = false,
|
||||||
value,
|
value,
|
||||||
|
|||||||
@ -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,160 +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,
|
|
||||||
$nodesOfType,
|
|
||||||
EditorState,
|
|
||||||
Klass,
|
|
||||||
LexicalNode,
|
|
||||||
} from 'lexical';
|
|
||||||
|
|
||||||
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 { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
|
||||||
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-secondary"
|
|
||||||
data-xxx
|
|
||||||
>
|
|
||||||
{placeholder || t('common.pleaseInput')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
ErrorBoundary={LexicalErrorBoundary}
|
|
||||||
/>
|
|
||||||
<VariablePickerMenuPlugin value={value}></VariablePickerMenuPlugin>
|
|
||||||
<VariableOnChangePlugin
|
|
||||||
onChange={onValueChange}
|
|
||||||
></VariableOnChangePlugin>
|
|
||||||
</LexicalComposer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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,70 +0,0 @@
|
|||||||
import i18n from '@/locales/config';
|
|
||||||
import { BeginId } from '@/pages/agent/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;
|
|
||||||
}
|
|
||||||
@ -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,273 +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,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
} from 'react';
|
|
||||||
import * as ReactDOM from 'react-dom';
|
|
||||||
|
|
||||||
import { FlowFormContext } from '@/pages/flow/context';
|
|
||||||
import { useBuildComponentIdSelectOptions } from '@/pages/flow/hooks/use-get-begin-query';
|
|
||||||
import { $createVariableNode } from './variable-node';
|
|
||||||
|
|
||||||
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;
|
|
||||||
}) {
|
|
||||||
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(FlowFormContext);
|
|
||||||
|
|
||||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
|
|
||||||
minLength: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [queryString, setQueryString] = React.useState<string | null>('');
|
|
||||||
|
|
||||||
const options = useBuildComponentIdSelectOptions(node?.id, node?.parentId);
|
|
||||||
|
|
||||||
const filteredOptions = React.useMemo(() => {
|
|
||||||
if (!queryString) return options;
|
|
||||||
const lowerQuery = queryString.toLowerCase();
|
|
||||||
return 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);
|
|
||||||
}, [options, queryString]);
|
|
||||||
|
|
||||||
const nextOptions: VariableOption[] = filteredOptions.map(
|
|
||||||
(x) =>
|
|
||||||
new VariableOption(
|
|
||||||
x.label,
|
|
||||||
x.title,
|
|
||||||
x.options.map((y) => new VariableInnerOption(y.label, y.value)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const findLabelByValue = useCallback(
|
|
||||||
(value: string) => {
|
|
||||||
const children = options.reduce<Array<{ label: string; value: string }>>(
|
|
||||||
(pre, cur) => {
|
|
||||||
return pre.concat(cur.options);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return children.find((x) => x.value === value)?.label;
|
|
||||||
},
|
|
||||||
[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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
if ($isRangeSelection($getSelection())) {
|
|
||||||
$getRoot().selectEnd();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[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={nextOptions}
|
|
||||||
menuRenderFn={(anchorElementRef, { selectOptionAndCleanUp }) =>
|
|
||||||
anchorElementRef.current && options.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
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -100,8 +100,6 @@ export interface Message {
|
|||||||
files?: (File | UploadResponseDataType)[];
|
files?: (File | UploadResponseDataType)[];
|
||||||
chatBoxId?: string;
|
chatBoxId?: string;
|
||||||
attachment?: IAttachment;
|
attachment?: IAttachment;
|
||||||
reasoning?: boolean;
|
|
||||||
internet?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReferenceChunk {
|
export interface IReferenceChunk {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { NextMessageInputOnPressEnterParameter } from '@/components/message-input/next';
|
|
||||||
import sonnerMessage from '@/components/ui/message';
|
import sonnerMessage from '@/components/ui/message';
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import {
|
import {
|
||||||
@ -290,8 +289,6 @@ export const useSendAgentMessage = ({
|
|||||||
params.files = uploadResponseList;
|
params.files = uploadResponseList;
|
||||||
|
|
||||||
params.session_id = sessionId;
|
params.session_id = sessionId;
|
||||||
params.reasoning = message.reasoning;
|
|
||||||
params.internet = message.internet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -358,30 +355,20 @@ export const useSendAgentMessage = ({
|
|||||||
removeAllMessagesExceptFirst,
|
removeAllMessagesExceptFirst,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handlePressEnter = useCallback(
|
const handlePressEnter = useCallback(() => {
|
||||||
(
|
|
||||||
...[
|
|
||||||
{ enableThinking, enableInternet },
|
|
||||||
]: NextMessageInputOnPressEnterParameter
|
|
||||||
) => {
|
|
||||||
if (trim(value) === '') return;
|
if (trim(value) === '') return;
|
||||||
const msgBody = buildRequestBody(value);
|
const msgBody = buildRequestBody(value);
|
||||||
if (done) {
|
if (done) {
|
||||||
setValue('');
|
setValue('');
|
||||||
sendMessage({
|
sendMessage({
|
||||||
message: {
|
message: msgBody,
|
||||||
...msgBody,
|
|
||||||
reasoning: enableThinking,
|
|
||||||
internet: enableInternet,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
addNewestOneQuestion({ ...msgBody, files: fileList });
|
addNewestOneQuestion({ ...msgBody, files: fileList });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, 100);
|
}, 100);
|
||||||
},
|
}, [
|
||||||
[
|
|
||||||
value,
|
value,
|
||||||
done,
|
done,
|
||||||
addNewestOneQuestion,
|
addNewestOneQuestion,
|
||||||
@ -389,8 +376,7 @@ export const useSendAgentMessage = ({
|
|||||||
setValue,
|
setValue,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
],
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
const sendedTaskMessage = useRef<boolean>(false);
|
const sendedTaskMessage = useRef<boolean>(false);
|
||||||
|
|
||||||
|
|||||||
@ -90,11 +90,13 @@ export const useSendMessage = (controller: AbortController) => {
|
|||||||
message,
|
message,
|
||||||
currentConversationId,
|
currentConversationId,
|
||||||
messages,
|
messages,
|
||||||
|
enableInternet,
|
||||||
|
enableThinking,
|
||||||
}: {
|
}: {
|
||||||
message: IMessage;
|
message: IMessage;
|
||||||
currentConversationId?: string;
|
currentConversationId?: string;
|
||||||
messages?: IMessage[];
|
messages?: IMessage[];
|
||||||
}) => {
|
} & NextMessageInputOnPressEnterParameter) => {
|
||||||
const res = await send(
|
const res = await send(
|
||||||
{
|
{
|
||||||
conversation_id: currentConversationId ?? conversationId,
|
conversation_id: currentConversationId ?? conversationId,
|
||||||
@ -104,6 +106,8 @@ export const useSendMessage = (controller: AbortController) => {
|
|||||||
: (derivedMessages ?? [])),
|
: (derivedMessages ?? [])),
|
||||||
message,
|
message,
|
||||||
],
|
],
|
||||||
|
reasoning: enableThinking,
|
||||||
|
internet: enableInternet,
|
||||||
},
|
},
|
||||||
controller,
|
controller,
|
||||||
);
|
);
|
||||||
@ -135,11 +139,10 @@ export const useSendMessage = (controller: AbortController) => {
|
|||||||
useCreateConversationBeforeSendMessage();
|
useCreateConversationBeforeSendMessage();
|
||||||
|
|
||||||
const handlePressEnter = useCallback(
|
const handlePressEnter = useCallback(
|
||||||
async (
|
async ({
|
||||||
...[
|
enableThinking,
|
||||||
{ enableThinking, enableInternet },
|
enableInternet,
|
||||||
]: NextMessageInputOnPressEnterParameter
|
}: NextMessageInputOnPressEnterParameter) => {
|
||||||
) => {
|
|
||||||
if (trim(value) === '') return;
|
if (trim(value) === '') return;
|
||||||
|
|
||||||
const data = await createConversationBeforeSendMessage(value);
|
const data = await createConversationBeforeSendMessage(value);
|
||||||
@ -171,9 +174,9 @@ export const useSendMessage = (controller: AbortController) => {
|
|||||||
role: MessageType.User,
|
role: MessageType.User,
|
||||||
files: files,
|
files: files,
|
||||||
conversationId: targetConversationId,
|
conversationId: targetConversationId,
|
||||||
reasoning: enableThinking,
|
|
||||||
internet: enableInternet,
|
|
||||||
},
|
},
|
||||||
|
enableInternet,
|
||||||
|
enableThinking,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
clearFiles();
|
clearFiles();
|
||||||
|
|||||||
@ -138,12 +138,14 @@ export function useSendMultipleChatMessage(
|
|||||||
currentConversationId,
|
currentConversationId,
|
||||||
messages,
|
messages,
|
||||||
chatBoxId,
|
chatBoxId,
|
||||||
|
enableInternet,
|
||||||
|
enableThinking,
|
||||||
}: {
|
}: {
|
||||||
message: Message;
|
message: Message;
|
||||||
currentConversationId?: string;
|
currentConversationId?: string;
|
||||||
chatBoxId: string;
|
chatBoxId: string;
|
||||||
messages?: Message[];
|
messages?: Message[];
|
||||||
}) => {
|
} & NextMessageInputOnPressEnterParameter) => {
|
||||||
let derivedMessages: IMessage[] = [];
|
let derivedMessages: IMessage[] = [];
|
||||||
|
|
||||||
derivedMessages = messageRecord[chatBoxId];
|
derivedMessages = messageRecord[chatBoxId];
|
||||||
@ -153,6 +155,8 @@ export function useSendMultipleChatMessage(
|
|||||||
chatBoxId,
|
chatBoxId,
|
||||||
conversation_id: currentConversationId ?? conversationId,
|
conversation_id: currentConversationId ?? conversationId,
|
||||||
messages: [...(messages ?? derivedMessages ?? []), message],
|
messages: [...(messages ?? derivedMessages ?? []), message],
|
||||||
|
reasoning: enableThinking,
|
||||||
|
internet: enableInternet,
|
||||||
...getLLMConfigById(chatBoxId),
|
...getLLMConfigById(chatBoxId),
|
||||||
},
|
},
|
||||||
controller,
|
controller,
|
||||||
@ -177,11 +181,10 @@ export function useSendMultipleChatMessage(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handlePressEnter = useCallback(
|
const handlePressEnter = useCallback(
|
||||||
async (
|
async ({
|
||||||
...[
|
enableThinking,
|
||||||
{ enableThinking, enableInternet },
|
enableInternet,
|
||||||
]: NextMessageInputOnPressEnterParameter
|
}: NextMessageInputOnPressEnterParameter) => {
|
||||||
) => {
|
|
||||||
if (trim(value) === '') return;
|
if (trim(value) === '') return;
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
|
|
||||||
@ -217,12 +220,12 @@ export function useSendMultipleChatMessage(
|
|||||||
role: MessageType.User,
|
role: MessageType.User,
|
||||||
files,
|
files,
|
||||||
conversationId: targetConversationId,
|
conversationId: targetConversationId,
|
||||||
reasoning: enableThinking,
|
|
||||||
internet: enableInternet,
|
|
||||||
},
|
},
|
||||||
chatBoxId,
|
chatBoxId,
|
||||||
currentConversationId: targetConversationId,
|
currentConversationId: targetConversationId,
|
||||||
messages: currentMessages,
|
messages: currentMessages,
|
||||||
|
enableThinking,
|
||||||
|
enableInternet,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -66,14 +66,19 @@ export const useSendSharedMessage = () => {
|
|||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
|
|
||||||
const sendMessage = useCallback(
|
const sendMessage = useCallback(
|
||||||
async (message: Message, id?: string) => {
|
async (
|
||||||
|
message: Message,
|
||||||
|
id?: string,
|
||||||
|
enableThinking?: boolean,
|
||||||
|
enableInternet?: boolean,
|
||||||
|
) => {
|
||||||
const res = await send({
|
const res = await send({
|
||||||
conversation_id: id ?? conversationId,
|
conversation_id: id ?? conversationId,
|
||||||
quote: true,
|
quote: true,
|
||||||
question: message.content,
|
question: message.content,
|
||||||
session_id: get(derivedMessages, '0.session_id'),
|
session_id: get(derivedMessages, '0.session_id'),
|
||||||
reasoning: message.reasoning,
|
reasoning: enableThinking,
|
||||||
internet: message.internet,
|
internet: enableInternet,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCompletionError(res)) {
|
if (isCompletionError(res)) {
|
||||||
@ -86,14 +91,18 @@ export const useSendSharedMessage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSendMessage = useCallback(
|
const handleSendMessage = useCallback(
|
||||||
async (message: Message) => {
|
async (
|
||||||
|
message: Message,
|
||||||
|
enableThinking?: boolean,
|
||||||
|
enableInternet?: boolean,
|
||||||
|
) => {
|
||||||
if (conversationId !== '') {
|
if (conversationId !== '') {
|
||||||
sendMessage(message);
|
sendMessage(message, undefined, enableThinking, enableInternet);
|
||||||
} else {
|
} else {
|
||||||
const data = await setConversation('user id');
|
const data = await setConversation('user id');
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
const id = data.data.id;
|
const id = data.data.id;
|
||||||
sendMessage(message, id);
|
sendMessage(message, id, enableThinking, enableInternet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -120,11 +129,10 @@ export const useSendSharedMessage = () => {
|
|||||||
}, [answer, addNewestAnswer]);
|
}, [answer, addNewestAnswer]);
|
||||||
|
|
||||||
const handlePressEnter = useCallback(
|
const handlePressEnter = useCallback(
|
||||||
(
|
({
|
||||||
...[
|
enableThinking,
|
||||||
{ enableThinking, enableInternet },
|
enableInternet,
|
||||||
]: NextMessageInputOnPressEnterParameter
|
}: NextMessageInputOnPressEnterParameter) => {
|
||||||
) => {
|
|
||||||
if (trim(value) === '') return;
|
if (trim(value) === '') return;
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
if (done) {
|
if (done) {
|
||||||
@ -135,13 +143,15 @@ export const useSendSharedMessage = () => {
|
|||||||
id,
|
id,
|
||||||
role: MessageType.User,
|
role: MessageType.User,
|
||||||
});
|
});
|
||||||
handleSendMessage({
|
handleSendMessage(
|
||||||
|
{
|
||||||
content: value.trim(),
|
content: value.trim(),
|
||||||
id,
|
id,
|
||||||
role: MessageType.User,
|
role: MessageType.User,
|
||||||
reasoning: enableThinking,
|
},
|
||||||
internet: enableInternet,
|
enableThinking,
|
||||||
});
|
enableInternet,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[addNewestQuestion, done, handleSendMessage, setValue, value],
|
[addNewestQuestion, done, handleSendMessage, setValue, value],
|
||||||
|
|||||||
Reference in New Issue
Block a user