mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 08:25:07 +00:00

* Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * Newflow (#89) * docs: Add doc for Xinference (#1266) Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * rename code * move code * update flow * input type selector * perf: workflow runtime * feat: node adapt newflow * feat: adapt plugin * feat: 360 connection * check workflow * perf: flow 性能 * change plugin input type (#81) * change plugin input type * plugin label mode * perf: nodecard * debug * perf: debug ui * connection ui * change workflow ui (#82) * feat: workflow debug * adapt openAPI for new workflow (#83) * adapt openAPI for new workflow * i18n * perf: plugin debug * plugin input ui * delete * perf: global variable select * fix rebase * perf: workflow performance * feat: input render type icon * input icon * adapt flow (#84) * adapt newflow * temp * temp * fix * feat: app schedule trigger * feat: app schedule trigger * perf: schedule ui * feat: ioslatevm run js code * perf: workflow varialbe table ui * feat: adapt simple mode * feat: adapt input params * output * feat: adapt tamplate * fix: ts * add if-else module (#86) * perf: worker * if else node * perf: tiktoken worker * fix: ts * perf: tiktoken * fix if-else node (#87) * fix if-else node * type * fix * perf: audio render * perf: Parallel worker * log * perf: if else node * adapt plugin * prompt * perf: reference ui * reference ui * handle ux * template ui and plugin tool * adapt v1 workflow * adapt v1 workflow completions * perf: time variables * feat: workflow keyboard shortcuts * adapt v1 workflow * update workflow example doc (#88) * fix: simple mode select tool --------- Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com> * doc * perf: extract node * extra node field * update plugin version * doc * variable * change doc & fix prompt editor (#90) * fold workflow code * value type label --------- Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
287 lines
7.9 KiB
TypeScript
287 lines
7.9 KiB
TypeScript
import type { Klass, LexicalEditor, LexicalNode } from 'lexical';
|
|
import type { EntityMatch } from '@lexical/text';
|
|
import { $createTextNode, $getRoot, $isTextNode, TextNode } from 'lexical';
|
|
import { useCallback } from 'react';
|
|
|
|
export function registerLexicalTextEntity<T extends TextNode>(
|
|
editor: LexicalEditor,
|
|
getMatch: (text: string) => null | EntityMatch,
|
|
targetNode: Klass<T>,
|
|
createNode: (textNode: TextNode) => T
|
|
): Array<() => void> {
|
|
const isTargetNode = (node: LexicalNode | null | undefined): node is T => {
|
|
return node instanceof targetNode;
|
|
};
|
|
|
|
const replaceWithSimpleText = (node: TextNode): void => {
|
|
const textNode = $createTextNode(node.getTextContent());
|
|
textNode.setFormat(node.getFormat());
|
|
node.replace(textNode);
|
|
};
|
|
|
|
const getMode = (node: TextNode): number => {
|
|
return node.getLatest().__mode;
|
|
};
|
|
|
|
const textNodeTransform = (node: TextNode) => {
|
|
if (!node.isSimpleText()) {
|
|
return;
|
|
}
|
|
|
|
const prevSibling = node.getPreviousSibling();
|
|
let text = node.getTextContent();
|
|
let currentNode = node;
|
|
let match;
|
|
|
|
if ($isTextNode(prevSibling)) {
|
|
const previousText = prevSibling.getTextContent();
|
|
const combinedText = previousText + text;
|
|
const prevMatch = getMatch(combinedText);
|
|
|
|
if (isTargetNode(prevSibling)) {
|
|
if (prevMatch === null || getMode(prevSibling) !== 0) {
|
|
replaceWithSimpleText(prevSibling);
|
|
|
|
return;
|
|
} else {
|
|
const diff = prevMatch.end - previousText.length;
|
|
|
|
if (diff > 0) {
|
|
const concatText = text.slice(0, diff);
|
|
const newTextContent = previousText + concatText;
|
|
prevSibling.select();
|
|
prevSibling.setTextContent(newTextContent);
|
|
|
|
if (diff === text.length) {
|
|
node.remove();
|
|
} else {
|
|
const remainingText = text.slice(diff);
|
|
node.setTextContent(remainingText);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
} else if (prevMatch === null || prevMatch.start < previousText.length) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
match = getMatch(text);
|
|
let nextText = match === null ? '' : text.slice(match.end);
|
|
text = nextText;
|
|
|
|
if (nextText === '') {
|
|
const nextSibling = currentNode.getNextSibling();
|
|
|
|
if ($isTextNode(nextSibling)) {
|
|
nextText = currentNode.getTextContent() + nextSibling.getTextContent();
|
|
const nextMatch = getMatch(nextText);
|
|
|
|
if (nextMatch === null) {
|
|
if (isTargetNode(nextSibling)) {
|
|
replaceWithSimpleText(nextSibling);
|
|
} else {
|
|
nextSibling.markDirty();
|
|
}
|
|
|
|
return;
|
|
} else if (nextMatch.start !== 0) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
const nextMatch = getMatch(nextText);
|
|
|
|
if (nextMatch !== null && nextMatch.start === 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (match === null) {
|
|
return;
|
|
}
|
|
|
|
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) {
|
|
continue;
|
|
}
|
|
|
|
let nodeToReplace;
|
|
|
|
if (match.start === 0) {
|
|
[nodeToReplace, currentNode] = currentNode.splitText(match.end);
|
|
} else {
|
|
[, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end);
|
|
}
|
|
|
|
const replacementNode = createNode(nodeToReplace);
|
|
replacementNode.setFormat(nodeToReplace.getFormat());
|
|
nodeToReplace.replace(replacementNode);
|
|
|
|
if (currentNode == null) {
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
const reverseNodeTransform = (node: T) => {
|
|
const text = node.getTextContent();
|
|
const match = getMatch(text);
|
|
|
|
if (match === null || match.start !== 0) {
|
|
replaceWithSimpleText(node);
|
|
|
|
return;
|
|
}
|
|
|
|
if (text.length > match.end) {
|
|
// This will split out the rest of the text as simple text
|
|
node.splitText(match.end);
|
|
|
|
return;
|
|
}
|
|
|
|
const prevSibling = node.getPreviousSibling();
|
|
|
|
if ($isTextNode(prevSibling) && prevSibling.isTextEntity()) {
|
|
replaceWithSimpleText(prevSibling);
|
|
replaceWithSimpleText(node);
|
|
}
|
|
|
|
const nextSibling = node.getNextSibling();
|
|
|
|
if ($isTextNode(nextSibling) && nextSibling.isTextEntity()) {
|
|
replaceWithSimpleText(nextSibling);
|
|
|
|
// This may have already been converted in the previous block
|
|
if (isTargetNode(node)) {
|
|
replaceWithSimpleText(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
const removePlainTextTransform = editor.registerNodeTransform(TextNode, textNodeTransform);
|
|
const removeReverseNodeTransform = editor.registerNodeTransform<T>(
|
|
targetNode,
|
|
reverseNodeTransform
|
|
);
|
|
|
|
return [removePlainTextTransform, removeReverseNodeTransform];
|
|
}
|
|
|
|
export function textToEditorState(text = '') {
|
|
const paragraph = typeof text === 'string' ? text?.split('\n') : [''];
|
|
|
|
return JSON.stringify({
|
|
root: {
|
|
children: paragraph.map((p) => {
|
|
return {
|
|
children: [
|
|
{
|
|
detail: 0,
|
|
format: 0,
|
|
mode: 'normal',
|
|
style: '',
|
|
text: p,
|
|
type: 'text',
|
|
version: 1
|
|
}
|
|
],
|
|
direction: 'ltr',
|
|
format: '',
|
|
indent: 0,
|
|
type: 'paragraph',
|
|
version: 1
|
|
};
|
|
}),
|
|
direction: 'ltr',
|
|
format: '',
|
|
indent: 0,
|
|
type: 'root',
|
|
version: 1
|
|
}
|
|
});
|
|
}
|
|
|
|
export function editorStateToText(editor: LexicalEditor) {
|
|
const editorStateTextString: string[] = [];
|
|
const paragraphs = editor.getEditorState().toJSON().root.children;
|
|
paragraphs.forEach((paragraph: any) => {
|
|
const children = paragraph.children;
|
|
const paragraphText: string[] = [];
|
|
children.forEach((child: any) => {
|
|
if (child.type === 'linebreak') {
|
|
paragraphText.push(`
|
|
`);
|
|
} else if (child.text) {
|
|
paragraphText.push(child.text);
|
|
}
|
|
});
|
|
editorStateTextString.push(paragraphText.join(''));
|
|
});
|
|
return editorStateTextString.join(`
|
|
`);
|
|
}
|
|
|
|
const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
|
|
export const getVars = (value: string) => {
|
|
if (!value) return [];
|
|
// .filter((item) => {
|
|
// return ![CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT].includes(item)
|
|
// })
|
|
const keys =
|
|
value
|
|
.match(varRegex)
|
|
?.map((item) => {
|
|
return item.replace('{{', '').replace('}}', '');
|
|
})
|
|
.filter((key) => key.length <= 10) || [];
|
|
const keyObj: Record<string, boolean> = {};
|
|
// remove duplicate keys
|
|
const res: string[] = [];
|
|
keys.forEach((key) => {
|
|
if (keyObj[key]) return;
|
|
|
|
keyObj[key] = true;
|
|
res.push(key);
|
|
});
|
|
return res;
|
|
};
|
|
|
|
export type MenuTextMatch = {
|
|
leadOffset: number;
|
|
matchingString: string;
|
|
replaceableString: string;
|
|
};
|
|
export type TriggerFn = (text: string, editor: LexicalEditor) => MenuTextMatch | null;
|
|
export const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
|
|
export function useBasicTypeaheadTriggerMatch(
|
|
trigger: string,
|
|
{ minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number }
|
|
): TriggerFn {
|
|
return useCallback(
|
|
(text: string) => {
|
|
const validChars = `[^${trigger}${PUNCTUATION}\\s]`;
|
|
const TypeaheadTriggerRegex = new RegExp(
|
|
`([^${trigger}]|^)(` + `[${trigger}]` + `((?:${validChars}){0,${maxLength}})` + ')$'
|
|
);
|
|
const match = TypeaheadTriggerRegex.exec(text);
|
|
if (match !== null) {
|
|
const maybeLeadingWhitespace = match[1];
|
|
const matchingString = match[3];
|
|
if (matchingString.length >= minLength) {
|
|
return {
|
|
leadOffset: match.index + maybeLeadingWhitespace.length,
|
|
matchingString,
|
|
replaceableString: match[2]
|
|
};
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
[maxLength, minLength, trigger]
|
|
);
|
|
}
|