@flowgram.ai/form-materials 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.ts +99 -0
- package/bin/materials.ts +156 -0
- package/bin/project.ts +91 -0
- package/dist/esm/index.js +3303 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.mts +495 -0
- package/dist/index.d.ts +495 -0
- package/dist/index.js +3299 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
- package/src/components/batch-outputs/config.json +12 -0
- package/src/components/batch-outputs/index.tsx +61 -0
- package/src/components/batch-outputs/styles.tsx +19 -0
- package/src/components/batch-outputs/types.ts +22 -0
- package/src/components/batch-outputs/use-list.ts +86 -0
- package/src/components/batch-variable-selector/config.json +5 -0
- package/src/components/batch-variable-selector/index.tsx +24 -0
- package/src/components/code-editor/config.json +9 -0
- package/src/components/code-editor/index.tsx +74 -0
- package/src/components/code-editor/language-features.ts +24 -0
- package/src/components/code-editor/theme/dark.ts +119 -0
- package/src/components/code-editor/theme/index.ts +12 -0
- package/src/components/code-editor/theme/light.ts +119 -0
- package/src/components/code-editor/utils.ts +20 -0
- package/src/components/condition-row/config.json +5 -0
- package/src/components/condition-row/constants.ts +128 -0
- package/src/components/condition-row/hooks/useOp.tsx +50 -0
- package/src/components/condition-row/hooks/useRule.ts +31 -0
- package/src/components/condition-row/index.tsx +81 -0
- package/src/components/condition-row/styles.tsx +30 -0
- package/src/components/condition-row/types.ts +42 -0
- package/src/components/constant-input/config.json +6 -0
- package/src/components/constant-input/index.tsx +86 -0
- package/src/components/constant-input/types.ts +23 -0
- package/src/components/dynamic-value-input/config.json +5 -0
- package/src/components/dynamic-value-input/index.tsx +92 -0
- package/src/components/dynamic-value-input/styles.tsx +26 -0
- package/src/components/index.ts +18 -0
- package/src/components/json-editor-with-variables/config.json +13 -0
- package/src/components/json-editor-with-variables/extensions/variable-tag.tsx +173 -0
- package/src/components/json-editor-with-variables/extensions/variable-tree.tsx +83 -0
- package/src/components/json-editor-with-variables/index.tsx +19 -0
- package/src/components/json-editor-with-variables/styles.tsx +44 -0
- package/src/components/json-schema-editor/components/blur-input.tsx +27 -0
- package/src/components/json-schema-editor/config.json +13 -0
- package/src/components/json-schema-editor/default-value.tsx +135 -0
- package/src/components/json-schema-editor/hooks.tsx +166 -0
- package/src/components/json-schema-editor/index.tsx +267 -0
- package/src/components/json-schema-editor/styles.tsx +240 -0
- package/src/components/json-schema-editor/types.ts +26 -0
- package/src/components/json-schema-editor/utils.ts +29 -0
- package/src/components/prompt-editor/config.json +9 -0
- package/src/components/prompt-editor/extensions/jinja.tsx +58 -0
- package/src/components/prompt-editor/extensions/language-support.tsx +19 -0
- package/src/components/prompt-editor/extensions/markdown.tsx +75 -0
- package/src/components/prompt-editor/index.tsx +68 -0
- package/src/components/prompt-editor/styles.tsx +18 -0
- package/src/components/prompt-editor/types.tsx +18 -0
- package/src/components/prompt-editor-with-inputs/config.json +13 -0
- package/src/components/prompt-editor-with-inputs/extensions/inputs-tree.tsx +82 -0
- package/src/components/prompt-editor-with-inputs/index.tsx +22 -0
- package/src/components/prompt-editor-with-inputs/inputs-picker.tsx +100 -0
- package/src/components/prompt-editor-with-variables/config.json +13 -0
- package/src/components/prompt-editor-with-variables/extensions/variable-tag.tsx +179 -0
- package/src/components/prompt-editor-with-variables/extensions/variable-tree.tsx +83 -0
- package/src/components/prompt-editor-with-variables/index.tsx +19 -0
- package/src/components/prompt-editor-with-variables/styles.tsx +44 -0
- package/src/components/type-selector/config.json +5 -0
- package/src/components/type-selector/constants.tsx +364 -0
- package/src/components/type-selector/index.tsx +62 -0
- package/src/components/variable-selector/config.json +5 -0
- package/src/components/variable-selector/index.tsx +116 -0
- package/src/components/variable-selector/styles.tsx +59 -0
- package/src/components/variable-selector/use-variable-tree.tsx +103 -0
- package/src/effects/auto-rename-ref/config.json +5 -0
- package/src/effects/auto-rename-ref/index.ts +109 -0
- package/src/effects/index.ts +10 -0
- package/src/effects/provide-batch-input/config.json +5 -0
- package/src/effects/provide-batch-input/index.ts +43 -0
- package/src/effects/provide-batch-outputs/config.json +5 -0
- package/src/effects/provide-batch-outputs/index.ts +38 -0
- package/src/effects/provide-json-schema-outputs/config.json +8 -0
- package/src/effects/provide-json-schema-outputs/index.ts +28 -0
- package/src/effects/sync-variable-title/config.json +5 -0
- package/src/effects/sync-variable-title/index.ts +28 -0
- package/src/form-plugins/batch-outputs-plugin/config.json +7 -0
- package/src/form-plugins/batch-outputs-plugin/index.ts +104 -0
- package/src/form-plugins/index.ts +6 -0
- package/src/index.ts +10 -0
- package/src/typings/flow-value/config.json +5 -0
- package/src/typings/flow-value/index.ts +32 -0
- package/src/typings/index.ts +7 -0
- package/src/typings/json-schema/config.json +5 -0
- package/src/typings/json-schema/index.ts +36 -0
- package/src/utils/format-legacy-refs/config.json +5 -0
- package/src/utils/format-legacy-refs/index.ts +158 -0
- package/src/utils/format-legacy-refs/readme.md +38 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/json-schema/config.json +5 -0
- package/src/utils/json-schema/index.ts +180 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useLayoutEffect } from 'react';
|
|
7
|
+
|
|
8
|
+
import { useInjector } from '@coze-editor/editor/react';
|
|
9
|
+
import { astDecorator } from '@coze-editor/editor';
|
|
10
|
+
import { EditorView } from '@codemirror/view';
|
|
11
|
+
|
|
12
|
+
function MarkdownHighlight() {
|
|
13
|
+
const injector = useInjector();
|
|
14
|
+
|
|
15
|
+
useLayoutEffect(
|
|
16
|
+
() =>
|
|
17
|
+
injector.inject([
|
|
18
|
+
astDecorator.whole.of((cursor) => {
|
|
19
|
+
// # heading
|
|
20
|
+
if (cursor.name.startsWith('ATXHeading')) {
|
|
21
|
+
return {
|
|
22
|
+
type: 'className',
|
|
23
|
+
className: 'heading',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// *italic*
|
|
28
|
+
if (cursor.name === 'Emphasis') {
|
|
29
|
+
return {
|
|
30
|
+
type: 'className',
|
|
31
|
+
className: 'emphasis',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// **bold**
|
|
36
|
+
if (cursor.name === 'StrongEmphasis') {
|
|
37
|
+
return {
|
|
38
|
+
type: 'className',
|
|
39
|
+
className: 'strong-emphasis',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// -
|
|
44
|
+
// 1.
|
|
45
|
+
// >
|
|
46
|
+
if (cursor.name === 'ListMark' || cursor.name === 'QuoteMark') {
|
|
47
|
+
return {
|
|
48
|
+
type: 'className',
|
|
49
|
+
className: 'mark',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
EditorView.theme({
|
|
54
|
+
'.heading': {
|
|
55
|
+
color: '#00818C',
|
|
56
|
+
fontWeight: 'bold',
|
|
57
|
+
},
|
|
58
|
+
'.emphasis': {
|
|
59
|
+
fontStyle: 'italic',
|
|
60
|
+
},
|
|
61
|
+
'.strong-emphasis': {
|
|
62
|
+
fontWeight: 'bold',
|
|
63
|
+
},
|
|
64
|
+
'.mark': {
|
|
65
|
+
color: '#4E40E5',
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
]),
|
|
69
|
+
[injector]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default MarkdownHighlight;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
import { Renderer, EditorProvider, ActiveLinePlaceholder } from '@coze-editor/editor/react';
|
|
9
|
+
import preset, { EditorAPI } from '@coze-editor/editor/preset-prompt';
|
|
10
|
+
|
|
11
|
+
import { PropsType } from './types';
|
|
12
|
+
import { UIContainer } from './styles';
|
|
13
|
+
import MarkdownHighlight from './extensions/markdown';
|
|
14
|
+
import LanguageSupport from './extensions/language-support';
|
|
15
|
+
import JinjaHighlight from './extensions/jinja';
|
|
16
|
+
|
|
17
|
+
export type PromptEditorPropsType = PropsType;
|
|
18
|
+
|
|
19
|
+
export function PromptEditor(props: PropsType) {
|
|
20
|
+
const {
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
readonly,
|
|
24
|
+
placeholder,
|
|
25
|
+
activeLinePlaceholder,
|
|
26
|
+
style,
|
|
27
|
+
hasError,
|
|
28
|
+
children,
|
|
29
|
+
} = props || {};
|
|
30
|
+
|
|
31
|
+
const editorRef = useRef<EditorAPI | null>(null);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
// listen to value change
|
|
35
|
+
if (editorRef.current?.getValue() !== value?.content) {
|
|
36
|
+
editorRef.current?.setValue(String(value?.content || ''));
|
|
37
|
+
}
|
|
38
|
+
}, [value]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<UIContainer $hasError={hasError} style={style}>
|
|
42
|
+
<EditorProvider>
|
|
43
|
+
<Renderer
|
|
44
|
+
didMount={(editor: EditorAPI) => {
|
|
45
|
+
editorRef.current = editor;
|
|
46
|
+
}}
|
|
47
|
+
plugins={preset}
|
|
48
|
+
defaultValue={String(value?.content)}
|
|
49
|
+
options={{
|
|
50
|
+
readOnly: readonly,
|
|
51
|
+
editable: !readonly,
|
|
52
|
+
placeholder,
|
|
53
|
+
}}
|
|
54
|
+
onChange={(e) => {
|
|
55
|
+
onChange({ type: 'template', content: e.value });
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
{activeLinePlaceholder && (
|
|
59
|
+
<ActiveLinePlaceholder>{activeLinePlaceholder}</ActiveLinePlaceholder>
|
|
60
|
+
)}
|
|
61
|
+
<MarkdownHighlight />
|
|
62
|
+
<LanguageSupport />
|
|
63
|
+
<JinjaHighlight />
|
|
64
|
+
{children}
|
|
65
|
+
</EditorProvider>
|
|
66
|
+
</UIContainer>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import styled, { css } from 'styled-components';
|
|
7
|
+
|
|
8
|
+
export const UIContainer = styled.div<{ $hasError?: boolean }>`
|
|
9
|
+
background-color: var(--semi-color-fill-0);
|
|
10
|
+
padding-left: 10px;
|
|
11
|
+
padding-right: 6px;
|
|
12
|
+
|
|
13
|
+
${({ $hasError }) =>
|
|
14
|
+
$hasError &&
|
|
15
|
+
css`
|
|
16
|
+
border: 1px solid var(--semi-color-danger-6);
|
|
17
|
+
`}
|
|
18
|
+
`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { IFlowTemplateValue } from '../../typings';
|
|
9
|
+
|
|
10
|
+
export type PropsType = React.PropsWithChildren<{
|
|
11
|
+
value?: IFlowTemplateValue;
|
|
12
|
+
onChange: (value?: IFlowTemplateValue) => void;
|
|
13
|
+
readonly?: boolean;
|
|
14
|
+
hasError?: boolean;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
activeLinePlaceholder?: string;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { Popover } from '@douyinfe/semi-ui';
|
|
9
|
+
import {
|
|
10
|
+
Mention,
|
|
11
|
+
MentionOpenChangeEvent,
|
|
12
|
+
getCurrentMentionReplaceRange,
|
|
13
|
+
useEditor,
|
|
14
|
+
PositionMirror,
|
|
15
|
+
} from '@coze-editor/editor/react';
|
|
16
|
+
import { EditorAPI } from '@coze-editor/editor/preset-prompt';
|
|
17
|
+
|
|
18
|
+
import { InputsPicker } from '../inputs-picker';
|
|
19
|
+
import { IFlowValue } from '../../../typings';
|
|
20
|
+
|
|
21
|
+
export function InputsTree({ inputsValues }: { inputsValues: Record<string, IFlowValue> }) {
|
|
22
|
+
const [posKey, setPosKey] = useState('');
|
|
23
|
+
const [visible, setVisible] = useState(false);
|
|
24
|
+
const [position, setPosition] = useState(-1);
|
|
25
|
+
const editor = useEditor<EditorAPI>();
|
|
26
|
+
|
|
27
|
+
function insert(variablePath: string) {
|
|
28
|
+
const range = getCurrentMentionReplaceRange(editor.$view.state);
|
|
29
|
+
|
|
30
|
+
if (!range) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
editor.replaceText({
|
|
35
|
+
...range,
|
|
36
|
+
text: '{{' + variablePath + '}}',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
setVisible(false);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function handleOpenChange(e: MentionOpenChangeEvent) {
|
|
43
|
+
setPosition(e.state.selection.main.head);
|
|
44
|
+
setVisible(e.value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!editor) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}, [editor, visible]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
<Mention triggerCharacters={['{', '{}', '@']} onOpenChange={handleOpenChange} />
|
|
56
|
+
|
|
57
|
+
<Popover
|
|
58
|
+
visible={visible}
|
|
59
|
+
trigger="custom"
|
|
60
|
+
position="topLeft"
|
|
61
|
+
rePosKey={posKey}
|
|
62
|
+
content={
|
|
63
|
+
<div style={{ width: 300 }}>
|
|
64
|
+
<InputsPicker
|
|
65
|
+
inputsValues={inputsValues}
|
|
66
|
+
onSelect={(v) => {
|
|
67
|
+
insert(v);
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
}
|
|
72
|
+
>
|
|
73
|
+
{/* PositionMirror allows the Popover to appear at the specified cursor position */}
|
|
74
|
+
<PositionMirror
|
|
75
|
+
position={position}
|
|
76
|
+
// When Doc scroll, update position
|
|
77
|
+
onChange={() => setPosKey(String(Math.random()))}
|
|
78
|
+
/>
|
|
79
|
+
</Popover>
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { InputsTree } from './extensions/inputs-tree';
|
|
9
|
+
import { PromptEditor, PromptEditorPropsType } from '../prompt-editor';
|
|
10
|
+
import { IFlowValue } from '../../typings';
|
|
11
|
+
|
|
12
|
+
interface PropsType extends PromptEditorPropsType {
|
|
13
|
+
inputsValues: Record<string, IFlowValue>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function PromptEditorWithInputs({ inputsValues, ...restProps }: PropsType) {
|
|
17
|
+
return (
|
|
18
|
+
<PromptEditor {...restProps}>
|
|
19
|
+
<InputsTree inputsValues={inputsValues} />
|
|
20
|
+
</PromptEditor>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { last } from 'lodash';
|
|
9
|
+
import {
|
|
10
|
+
type ArrayType,
|
|
11
|
+
ASTMatch,
|
|
12
|
+
type BaseType,
|
|
13
|
+
type BaseVariableField,
|
|
14
|
+
useScopeAvailable,
|
|
15
|
+
} from '@flowgram.ai/editor';
|
|
16
|
+
import { TreeNodeData } from '@douyinfe/semi-ui/lib/es/tree';
|
|
17
|
+
import { Tree } from '@douyinfe/semi-ui';
|
|
18
|
+
|
|
19
|
+
import { IFlowValue } from '../../typings';
|
|
20
|
+
|
|
21
|
+
type VariableField = BaseVariableField<{ icon?: string | JSX.Element; title?: string }>;
|
|
22
|
+
|
|
23
|
+
export function InputsPicker({
|
|
24
|
+
inputsValues,
|
|
25
|
+
onSelect,
|
|
26
|
+
}: {
|
|
27
|
+
inputsValues: Record<string, IFlowValue>;
|
|
28
|
+
onSelect: (v: string) => void;
|
|
29
|
+
}) {
|
|
30
|
+
const available = useScopeAvailable();
|
|
31
|
+
|
|
32
|
+
const getArrayDrilldown = (type: ArrayType, depth = 1): { type: BaseType; depth: number } => {
|
|
33
|
+
if (ASTMatch.isArray(type.items)) {
|
|
34
|
+
return getArrayDrilldown(type.items, depth + 1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { type: type.items, depth: depth };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const renderVariable = (variable: VariableField, keyPath: string[]): TreeNodeData => {
|
|
41
|
+
let type = variable?.type;
|
|
42
|
+
|
|
43
|
+
let children: TreeNodeData[] | undefined;
|
|
44
|
+
|
|
45
|
+
if (ASTMatch.isObject(type)) {
|
|
46
|
+
children = (type.properties || [])
|
|
47
|
+
.map((_property) => renderVariable(_property as VariableField, [...keyPath, _property.key]))
|
|
48
|
+
.filter(Boolean) as TreeNodeData[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (ASTMatch.isArray(type)) {
|
|
52
|
+
const drilldown = getArrayDrilldown(type);
|
|
53
|
+
|
|
54
|
+
if (ASTMatch.isObject(drilldown.type)) {
|
|
55
|
+
children = (drilldown.type.properties || [])
|
|
56
|
+
.map((_property) =>
|
|
57
|
+
renderVariable(_property as VariableField, [
|
|
58
|
+
...keyPath,
|
|
59
|
+
...new Array(drilldown.depth).fill('[0]'),
|
|
60
|
+
_property.key,
|
|
61
|
+
])
|
|
62
|
+
)
|
|
63
|
+
.filter(Boolean) as TreeNodeData[];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const key = keyPath
|
|
68
|
+
.map((_key, idx) => (_key === '[0]' || idx === 0 ? _key : `.${_key}`))
|
|
69
|
+
.join('');
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
key: key,
|
|
73
|
+
label: last(keyPath),
|
|
74
|
+
value: key,
|
|
75
|
+
children,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const treeData: TreeNodeData[] = useMemo(
|
|
80
|
+
() =>
|
|
81
|
+
Object.entries(inputsValues).map(([key, value]) => {
|
|
82
|
+
if (value.type === 'ref') {
|
|
83
|
+
const variable = available.getByKeyPath(value.content || []);
|
|
84
|
+
|
|
85
|
+
if (variable) {
|
|
86
|
+
return renderVariable(variable, [key]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
key,
|
|
92
|
+
value: key,
|
|
93
|
+
label: key,
|
|
94
|
+
};
|
|
95
|
+
}),
|
|
96
|
+
[]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return <Tree treeData={treeData} onSelect={(v) => onSelect(v)} />;
|
|
100
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import ReactDOM from 'react-dom';
|
|
7
|
+
import React, { useLayoutEffect } from 'react';
|
|
8
|
+
|
|
9
|
+
import { isEqual, last } from 'lodash';
|
|
10
|
+
import {
|
|
11
|
+
BaseVariableField,
|
|
12
|
+
Disposable,
|
|
13
|
+
DisposableCollection,
|
|
14
|
+
Scope,
|
|
15
|
+
useCurrentScope,
|
|
16
|
+
} from '@flowgram.ai/editor';
|
|
17
|
+
import { Popover } from '@douyinfe/semi-ui';
|
|
18
|
+
import { IconIssueStroked } from '@douyinfe/semi-icons';
|
|
19
|
+
import { useInjector } from '@coze-editor/editor/react';
|
|
20
|
+
import {
|
|
21
|
+
Decoration,
|
|
22
|
+
DecorationSet,
|
|
23
|
+
EditorView,
|
|
24
|
+
MatchDecorator,
|
|
25
|
+
ViewPlugin,
|
|
26
|
+
WidgetType,
|
|
27
|
+
} from '@codemirror/view';
|
|
28
|
+
|
|
29
|
+
import { UIPopoverContent, UIRootTitle, UITag, UIVarName } from '../styles';
|
|
30
|
+
|
|
31
|
+
class VariableTagWidget extends WidgetType {
|
|
32
|
+
keyPath?: string[];
|
|
33
|
+
|
|
34
|
+
toDispose = new DisposableCollection();
|
|
35
|
+
|
|
36
|
+
scope: Scope;
|
|
37
|
+
|
|
38
|
+
rootDOM?: HTMLSpanElement;
|
|
39
|
+
|
|
40
|
+
constructor({ keyPath, scope }: { keyPath?: string[]; scope: Scope }) {
|
|
41
|
+
super();
|
|
42
|
+
|
|
43
|
+
this.keyPath = keyPath;
|
|
44
|
+
this.scope = scope;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
renderIcon = (icon: string | JSX.Element) => {
|
|
48
|
+
if (typeof icon === 'string') {
|
|
49
|
+
return <img style={{ marginRight: 8 }} width={12} height={12} src={icon} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return icon;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
renderReact(jsx: React.ReactElement) {
|
|
56
|
+
// NOTICE: For React 19, upgrade to 'react-dom/client'
|
|
57
|
+
ReactDOM.render(jsx, this.rootDOM!);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
renderVariable(v?: BaseVariableField) {
|
|
61
|
+
if (!v) {
|
|
62
|
+
this.renderReact(
|
|
63
|
+
<UITag prefixIcon={<IconIssueStroked />} color="amber">
|
|
64
|
+
Unknown
|
|
65
|
+
</UITag>
|
|
66
|
+
);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const rootField = last(v.parentFields);
|
|
71
|
+
|
|
72
|
+
const rootTitle = (
|
|
73
|
+
<UIRootTitle>{rootField?.meta.title ? `${rootField.meta.title} -` : ''}</UIRootTitle>
|
|
74
|
+
);
|
|
75
|
+
const rootIcon = this.renderIcon(rootField?.meta.icon);
|
|
76
|
+
|
|
77
|
+
this.renderReact(
|
|
78
|
+
<Popover
|
|
79
|
+
content={
|
|
80
|
+
<UIPopoverContent>
|
|
81
|
+
{rootIcon}
|
|
82
|
+
{rootTitle}
|
|
83
|
+
<UIVarName>{v?.keyPath.slice(1).join('.')}</UIVarName>
|
|
84
|
+
</UIPopoverContent>
|
|
85
|
+
}
|
|
86
|
+
>
|
|
87
|
+
<UITag prefixIcon={rootIcon}>
|
|
88
|
+
{rootTitle}
|
|
89
|
+
<UIVarName>{v?.key}</UIVarName>
|
|
90
|
+
</UITag>
|
|
91
|
+
</Popover>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
toDOM(view: EditorView): HTMLElement {
|
|
96
|
+
const dom = document.createElement('span');
|
|
97
|
+
|
|
98
|
+
this.rootDOM = dom;
|
|
99
|
+
|
|
100
|
+
this.toDispose.push(
|
|
101
|
+
Disposable.create(() => {
|
|
102
|
+
// NOTICE: For React 19, upgrade to 'react-dom/client'
|
|
103
|
+
ReactDOM.unmountComponentAtNode(this.rootDOM!);
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
this.toDispose.push(
|
|
108
|
+
this.scope.available.trackByKeyPath(
|
|
109
|
+
this.keyPath,
|
|
110
|
+
(v) => {
|
|
111
|
+
this.renderVariable(v);
|
|
112
|
+
},
|
|
113
|
+
{ triggerOnInit: false }
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
this.renderVariable(this.scope.available.getByKeyPath(this.keyPath));
|
|
118
|
+
|
|
119
|
+
return dom;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
eq(other: VariableTagWidget) {
|
|
123
|
+
return isEqual(this.keyPath, other.keyPath);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ignoreEvent(): boolean {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
destroy(dom: HTMLElement): void {
|
|
131
|
+
this.toDispose.dispose();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function VariableTagInject() {
|
|
136
|
+
const injector = useInjector();
|
|
137
|
+
|
|
138
|
+
const scope = useCurrentScope();
|
|
139
|
+
|
|
140
|
+
// 基于 {{var}} 的正则进行匹配,匹配后进行自定义渲染
|
|
141
|
+
useLayoutEffect(() => {
|
|
142
|
+
const atMatcher = new MatchDecorator({
|
|
143
|
+
regexp: /\{\{([^\}]+)\}\}/g,
|
|
144
|
+
decoration: (match) =>
|
|
145
|
+
Decoration.replace({
|
|
146
|
+
widget: new VariableTagWidget({
|
|
147
|
+
keyPath: match[1]?.split('.') ?? [],
|
|
148
|
+
scope,
|
|
149
|
+
}),
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return injector.inject([
|
|
154
|
+
ViewPlugin.fromClass(
|
|
155
|
+
class {
|
|
156
|
+
decorations: DecorationSet;
|
|
157
|
+
|
|
158
|
+
constructor(private view: EditorView) {
|
|
159
|
+
this.decorations = atMatcher.createDeco(view);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
update() {
|
|
163
|
+
this.decorations = atMatcher.createDeco(this.view);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
decorations: (p) => p.decorations,
|
|
168
|
+
provide(p) {
|
|
169
|
+
return EditorView.atomicRanges.of(
|
|
170
|
+
(view) => view.plugin(p)?.decorations ?? Decoration.none
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
]);
|
|
176
|
+
}, [injector]);
|
|
177
|
+
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { Popover, Tree } from '@douyinfe/semi-ui';
|
|
9
|
+
import {
|
|
10
|
+
Mention,
|
|
11
|
+
MentionOpenChangeEvent,
|
|
12
|
+
getCurrentMentionReplaceRange,
|
|
13
|
+
useEditor,
|
|
14
|
+
PositionMirror,
|
|
15
|
+
} from '@coze-editor/editor/react';
|
|
16
|
+
import { EditorAPI } from '@coze-editor/editor/preset-prompt';
|
|
17
|
+
|
|
18
|
+
import { useVariableTree } from '../../variable-selector';
|
|
19
|
+
|
|
20
|
+
export function VariableTree() {
|
|
21
|
+
const [posKey, setPosKey] = useState('');
|
|
22
|
+
const [visible, setVisible] = useState(false);
|
|
23
|
+
const [position, setPosition] = useState(-1);
|
|
24
|
+
const editor = useEditor<EditorAPI>();
|
|
25
|
+
|
|
26
|
+
function insert(variablePath: string) {
|
|
27
|
+
const range = getCurrentMentionReplaceRange(editor.$view.state);
|
|
28
|
+
|
|
29
|
+
if (!range) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
editor.replaceText({
|
|
34
|
+
...range,
|
|
35
|
+
text: '{{' + variablePath + '}}',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
setVisible(false);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleOpenChange(e: MentionOpenChangeEvent) {
|
|
42
|
+
setPosition(e.state.selection.main.head);
|
|
43
|
+
setVisible(e.value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!editor) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}, [editor, visible]);
|
|
51
|
+
|
|
52
|
+
const treeData = useVariableTree({});
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<>
|
|
56
|
+
<Mention triggerCharacters={['{', '{}', '@']} onOpenChange={handleOpenChange} />
|
|
57
|
+
|
|
58
|
+
<Popover
|
|
59
|
+
visible={visible}
|
|
60
|
+
trigger="custom"
|
|
61
|
+
position="topLeft"
|
|
62
|
+
rePosKey={posKey}
|
|
63
|
+
content={
|
|
64
|
+
<div style={{ width: 300 }}>
|
|
65
|
+
<Tree
|
|
66
|
+
treeData={treeData}
|
|
67
|
+
onSelect={(v) => {
|
|
68
|
+
insert(v);
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
}
|
|
73
|
+
>
|
|
74
|
+
{/* PositionMirror allows the Popover to appear at the specified cursor position */}
|
|
75
|
+
<PositionMirror
|
|
76
|
+
position={position}
|
|
77
|
+
// When Doc scroll, update position
|
|
78
|
+
onChange={() => setPosKey(String(Math.random()))}
|
|
79
|
+
/>
|
|
80
|
+
</Popover>
|
|
81
|
+
</>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { VariableTree } from './extensions/variable-tree';
|
|
9
|
+
import { VariableTagInject } from './extensions/variable-tag';
|
|
10
|
+
import { PromptEditor, PromptEditorPropsType } from '../prompt-editor';
|
|
11
|
+
|
|
12
|
+
export function PromptEditorWithVariables(props: PromptEditorPropsType) {
|
|
13
|
+
return (
|
|
14
|
+
<PromptEditor {...props}>
|
|
15
|
+
<VariableTree />
|
|
16
|
+
<VariableTagInject />
|
|
17
|
+
</PromptEditor>
|
|
18
|
+
);
|
|
19
|
+
}
|