mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
feat(material): prompt-editor with variables (#445)
* feat: init prompt editor * feat: simple prompt editor * feat: split prompt editor * feat: fix-layout prompt editor
This commit is contained in:
parent
800a820e10
commit
de7f2d3c07
@ -3,7 +3,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DynamicValueInput } from '@flowgram.ai/form-materials';
|
import { DynamicValueInput, PromptEditorWithVariables } from '@flowgram.ai/form-materials';
|
||||||
import { Field } from '@flowgram.ai/fixed-layout-editor';
|
import { Field } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
import { FormItem } from '../form-item';
|
import { FormItem } from '../form-item';
|
||||||
@ -13,6 +13,7 @@ import { useNodeRenderContext } from '../../hooks';
|
|||||||
|
|
||||||
export function FormInputs() {
|
export function FormInputs() {
|
||||||
const { readonly } = useNodeRenderContext();
|
const { readonly } = useNodeRenderContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field<JsonSchema> name="inputs">
|
<Field<JsonSchema> name="inputs">
|
||||||
{({ field: inputsField }) => {
|
{({ field: inputsField }) => {
|
||||||
@ -23,21 +24,39 @@ export function FormInputs() {
|
|||||||
}
|
}
|
||||||
const content = Object.keys(properties).map((key) => {
|
const content = Object.keys(properties).map((key) => {
|
||||||
const property = properties[key];
|
const property = properties[key];
|
||||||
|
|
||||||
|
const formComponent = property.extra?.formComponent;
|
||||||
|
|
||||||
|
const vertical = ['prompt-editor'].includes(formComponent || '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field key={key} name={`inputsValues.${key}`} defaultValue={property.default}>
|
<Field key={key} name={`inputsValues.${key}`} defaultValue={property.default}>
|
||||||
{({ field, fieldState }) => (
|
{({ field, fieldState }) => (
|
||||||
<FormItem
|
<FormItem
|
||||||
name={key}
|
name={key}
|
||||||
|
vertical={vertical}
|
||||||
type={property.type as string}
|
type={property.type as string}
|
||||||
required={required.includes(key)}
|
required={required.includes(key)}
|
||||||
>
|
>
|
||||||
<DynamicValueInput
|
{formComponent === 'prompt-editor' && (
|
||||||
value={field.value}
|
<PromptEditorWithVariables
|
||||||
onChange={field.onChange}
|
value={field.value}
|
||||||
readonly={readonly}
|
onChange={field.onChange}
|
||||||
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
readonly={readonly}
|
||||||
schema={property}
|
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{!formComponent && (
|
||||||
|
<DynamicValueInput
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
readonly={readonly}
|
||||||
|
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
||||||
|
constantProps={{
|
||||||
|
schema: property,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Feedback errors={fieldState?.errors} warnings={fieldState?.warnings} />
|
<Feedback errors={fieldState?.errors} warnings={fieldState?.warnings} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ interface FormItemProps {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
description?: string;
|
description?: string;
|
||||||
labelWidth?: number;
|
labelWidth?: number;
|
||||||
|
vertical?: boolean;
|
||||||
}
|
}
|
||||||
export function FormItem({
|
export function FormItem({
|
||||||
children,
|
children,
|
||||||
@ -27,6 +28,7 @@ export function FormItem({
|
|||||||
description,
|
description,
|
||||||
type,
|
type,
|
||||||
labelWidth,
|
labelWidth,
|
||||||
|
vertical,
|
||||||
}: FormItemProps): JSX.Element {
|
}: FormItemProps): JSX.Element {
|
||||||
const renderTitle = useCallback(
|
const renderTitle = useCallback(
|
||||||
(showTooltip?: boolean) => (
|
(showTooltip?: boolean) => (
|
||||||
@ -47,9 +49,13 @@ export function FormItem({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
gap: 8,
|
||||||
|
...(vertical
|
||||||
|
? { flexDirection: 'column' }
|
||||||
|
: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'You are an AI assistant.',
|
content: '# Role\nYou are an AI assistant.\n',
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@ -78,9 +78,11 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: { formComponent: 'prompt-editor' },
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: { formComponent: 'prompt-editor' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'You are an AI assistant.',
|
content: '# Role\nYou are an AI assistant.\n',
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@ -54,9 +54,11 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: { formComponent: 'prompt-editor' },
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: { formComponent: 'prompt-editor' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Field } from '@flowgram.ai/free-layout-editor';
|
import { Field } from '@flowgram.ai/free-layout-editor';
|
||||||
import { DynamicValueInput } from '@flowgram.ai/form-materials';
|
import { DynamicValueInput, PromptEditorWithVariables } from '@flowgram.ai/form-materials';
|
||||||
|
|
||||||
import { FormItem } from '../form-item';
|
import { FormItem } from '../form-item';
|
||||||
import { Feedback } from '../feedback';
|
import { Feedback } from '../feedback';
|
||||||
@ -13,6 +13,7 @@ import { useNodeRenderContext } from '../../hooks';
|
|||||||
|
|
||||||
export function FormInputs() {
|
export function FormInputs() {
|
||||||
const { readonly } = useNodeRenderContext();
|
const { readonly } = useNodeRenderContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field<JsonSchema> name="inputs">
|
<Field<JsonSchema> name="inputs">
|
||||||
{({ field: inputsField }) => {
|
{({ field: inputsField }) => {
|
||||||
@ -23,23 +24,39 @@ export function FormInputs() {
|
|||||||
}
|
}
|
||||||
const content = Object.keys(properties).map((key) => {
|
const content = Object.keys(properties).map((key) => {
|
||||||
const property = properties[key];
|
const property = properties[key];
|
||||||
|
|
||||||
|
const formComponent = property.extra?.formComponent;
|
||||||
|
|
||||||
|
const vertical = ['prompt-editor'].includes(formComponent || '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field key={key} name={`inputsValues.${key}`} defaultValue={property.default}>
|
<Field key={key} name={`inputsValues.${key}`} defaultValue={property.default}>
|
||||||
{({ field, fieldState }) => (
|
{({ field, fieldState }) => (
|
||||||
<FormItem
|
<FormItem
|
||||||
name={key}
|
name={key}
|
||||||
|
vertical={vertical}
|
||||||
type={property.type as string}
|
type={property.type as string}
|
||||||
required={required.includes(key)}
|
required={required.includes(key)}
|
||||||
>
|
>
|
||||||
<DynamicValueInput
|
{formComponent === 'prompt-editor' && (
|
||||||
value={field.value}
|
<PromptEditorWithVariables
|
||||||
onChange={field.onChange}
|
value={field.value}
|
||||||
readonly={readonly}
|
onChange={field.onChange}
|
||||||
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
readonly={readonly}
|
||||||
constantProps={{
|
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
||||||
schema: property,
|
/>
|
||||||
}}
|
)}
|
||||||
/>
|
{!formComponent && (
|
||||||
|
<DynamicValueInput
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
readonly={readonly}
|
||||||
|
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
||||||
|
constantProps={{
|
||||||
|
schema: property,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Feedback errors={fieldState?.errors} warnings={fieldState?.warnings} />
|
<Feedback errors={fieldState?.errors} warnings={fieldState?.warnings} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -181,7 +181,7 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'You are an AI assistant.',
|
content: '# Role\nYou are an AI assistant.\n',
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@ -206,9 +206,15 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -252,7 +258,7 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'You are an AI assistant.',
|
content: '# Role\nYou are an AI assistant.\n',
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@ -277,9 +283,15 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -342,7 +354,7 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'You are an AI assistant.',
|
content: '# Role\nYou are an AI assistant.\n',
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@ -367,9 +379,15 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -413,7 +431,7 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'You are an AI assistant.',
|
content: '# Role\nYou are an AI assistant.\n',
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@ -438,9 +456,15 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'You are an AI assistant.',
|
content: '# Role\nYou are an AI assistant.\n',
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@ -73,9 +73,15 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
|
|||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
extra: {
|
||||||
|
formComponent: 'prompt-editor',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
1085
common/config/rush/pnpm-lock.yaml
generated
1085
common/config/rush/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -41,7 +41,9 @@
|
|||||||
"commander": "^11.0.0",
|
"commander": "^11.0.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"inquirer": "^9.2.7",
|
"inquirer": "^9.2.7",
|
||||||
"immer": "~10.1.1"
|
"immer": "~10.1.1",
|
||||||
|
"@coze-editor/editor": "0.1.0-alpha.8d7a30",
|
||||||
|
"@codemirror/view": "~6.38.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@flowgram.ai/eslint-config": "workspace:*",
|
"@flowgram.ai/eslint-config": "workspace:*",
|
||||||
|
|||||||
@ -11,3 +11,5 @@ export * from './constant-input';
|
|||||||
export * from './dynamic-value-input';
|
export * from './dynamic-value-input';
|
||||||
export * from './condition-row';
|
export * from './condition-row';
|
||||||
export * from './batch-outputs';
|
export * from './batch-outputs';
|
||||||
|
export * from './prompt-editor';
|
||||||
|
export * from './prompt-editor-with-variables';
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "prompt-editor",
|
||||||
|
"depMaterials": [],
|
||||||
|
"depPackages": [
|
||||||
|
"@coze-editor/editor@0.1.0-alpha.8d7a30",
|
||||||
|
"@codemirror/view",
|
||||||
|
"styled-components",
|
||||||
|
"@douyinfe/semi-ui"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Popover, Tree } from '@douyinfe/semi-ui';
|
||||||
|
import {
|
||||||
|
Mention,
|
||||||
|
MentionOpenChangeEvent,
|
||||||
|
getCurrentMentionReplaceRange,
|
||||||
|
useEditor,
|
||||||
|
PositionMirror,
|
||||||
|
} from '@coze-editor/editor/react';
|
||||||
|
import { EditorAPI } from '@coze-editor/editor/preset-prompt';
|
||||||
|
|
||||||
|
import { useVariableTree } from '../../variable-selector';
|
||||||
|
|
||||||
|
function Variable() {
|
||||||
|
const [posKey, setPosKey] = useState('');
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [position, setPosition] = useState(-1);
|
||||||
|
const editor = useEditor<EditorAPI>();
|
||||||
|
|
||||||
|
function insert(variablePath: string) {
|
||||||
|
const range = getCurrentMentionReplaceRange(editor.$view.state);
|
||||||
|
|
||||||
|
if (!range) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.replaceText({
|
||||||
|
...range,
|
||||||
|
text: '{{' + variablePath + '}}',
|
||||||
|
});
|
||||||
|
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpenChange(e: MentionOpenChangeEvent) {
|
||||||
|
setPosition(e.state.selection.main.head);
|
||||||
|
setVisible(e.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [editor, visible]);
|
||||||
|
|
||||||
|
const treeData = useVariableTree({});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Mention triggerCharacters={['{', '{}']} onOpenChange={handleOpenChange} />
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
visible={visible}
|
||||||
|
trigger="custom"
|
||||||
|
position="topLeft"
|
||||||
|
rePosKey={posKey}
|
||||||
|
content={
|
||||||
|
<div style={{ width: 300 }}>
|
||||||
|
<Tree
|
||||||
|
treeData={treeData}
|
||||||
|
onSelect={(v) => {
|
||||||
|
insert(v);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{/* PositionMirror allows the Popover to appear at the specified cursor position */}
|
||||||
|
<PositionMirror
|
||||||
|
position={position}
|
||||||
|
// When Doc scroll, update position
|
||||||
|
onChange={() => setPosKey(String(Math.random()))}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Variable;
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Variable from './extensions/variable';
|
||||||
|
import { PromptEditor, PromptEditorPropsType } from '../prompt-editor';
|
||||||
|
|
||||||
|
export function PromptEditorWithVariables(props: PromptEditorPropsType) {
|
||||||
|
return (
|
||||||
|
<PromptEditor {...props}>
|
||||||
|
<Variable />
|
||||||
|
</PromptEditor>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "prompt-editor",
|
||||||
|
"depMaterials": [],
|
||||||
|
"depPackages": [
|
||||||
|
"@coze-editor/editor@0.1.0-alpha.8d7a30",
|
||||||
|
"@codemirror/view",
|
||||||
|
"styled-components"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
|
import { useInjector } from '@coze-editor/editor/react';
|
||||||
|
import { astDecorator } from '@coze-editor/editor';
|
||||||
|
import { EditorView } from '@codemirror/view';
|
||||||
|
|
||||||
|
function JinjaHighlight() {
|
||||||
|
const injector = useInjector();
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() =>
|
||||||
|
injector.inject([
|
||||||
|
astDecorator.whole.of((cursor) => {
|
||||||
|
if (cursor.name === 'JinjaStatementStart' || cursor.name === 'JinjaStatementEnd') {
|
||||||
|
return {
|
||||||
|
type: 'className',
|
||||||
|
className: 'jinja-statement-bracket',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.name === 'JinjaComment') {
|
||||||
|
return {
|
||||||
|
type: 'className',
|
||||||
|
className: 'jinja-comment',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.name === 'JinjaExpression') {
|
||||||
|
return {
|
||||||
|
type: 'className',
|
||||||
|
className: 'jinja-expression',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EditorView.theme({
|
||||||
|
'.jinja-statement-bracket': {
|
||||||
|
color: '#D1009D',
|
||||||
|
},
|
||||||
|
'.jinja-comment': {
|
||||||
|
color: '#0607094D',
|
||||||
|
},
|
||||||
|
'.jinja-expression': {
|
||||||
|
color: '#4E40E5',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
[injector]
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JinjaHighlight;
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
|
import { useInjector } from '@coze-editor/editor/react';
|
||||||
|
import { languageSupport } from '@coze-editor/editor/preset-prompt';
|
||||||
|
|
||||||
|
function LanguageSupport() {
|
||||||
|
const injector = useInjector();
|
||||||
|
|
||||||
|
useLayoutEffect(() => injector.inject([languageSupport]), [injector]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LanguageSupport;
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
|
import { useInjector } from '@coze-editor/editor/react';
|
||||||
|
import { astDecorator } from '@coze-editor/editor';
|
||||||
|
import { EditorView } from '@codemirror/view';
|
||||||
|
|
||||||
|
function MarkdownHighlight() {
|
||||||
|
const injector = useInjector();
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() =>
|
||||||
|
injector.inject([
|
||||||
|
astDecorator.whole.of((cursor) => {
|
||||||
|
// # heading
|
||||||
|
if (cursor.name.startsWith('ATXHeading')) {
|
||||||
|
return {
|
||||||
|
type: 'className',
|
||||||
|
className: 'heading',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// *italic*
|
||||||
|
if (cursor.name === 'Emphasis') {
|
||||||
|
return {
|
||||||
|
type: 'className',
|
||||||
|
className: 'emphasis',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// **bold**
|
||||||
|
if (cursor.name === 'StrongEmphasis') {
|
||||||
|
return {
|
||||||
|
type: 'className',
|
||||||
|
className: 'strong-emphasis',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// -
|
||||||
|
// 1.
|
||||||
|
// >
|
||||||
|
if (cursor.name === 'ListMark' || cursor.name === 'QuoteMark') {
|
||||||
|
return {
|
||||||
|
type: 'className',
|
||||||
|
className: 'mark',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EditorView.theme({
|
||||||
|
'.heading': {
|
||||||
|
color: '#00818C',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
'.emphasis': {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
'.strong-emphasis': {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
'.mark': {
|
||||||
|
color: '#4E40E5',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
[injector]
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MarkdownHighlight;
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Renderer, EditorProvider } from '@coze-editor/editor/react';
|
||||||
|
import preset from '@coze-editor/editor/preset-prompt';
|
||||||
|
|
||||||
|
import { PropsType } from './types';
|
||||||
|
import { UIContainer } from './styles';
|
||||||
|
import MarkdownHighlight from './extensions/markdown';
|
||||||
|
import LanguageSupport from './extensions/language-support';
|
||||||
|
import JinjaHighlight from './extensions/jinja';
|
||||||
|
|
||||||
|
export type PromptEditorPropsType = PropsType;
|
||||||
|
|
||||||
|
export function PromptEditor(props: PropsType) {
|
||||||
|
const { value, onChange, readonly, style, hasError, children } = props || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UIContainer $hasError={hasError} style={style}>
|
||||||
|
<EditorProvider>
|
||||||
|
<Renderer
|
||||||
|
plugins={preset}
|
||||||
|
defaultValue={String(value?.content)}
|
||||||
|
options={{
|
||||||
|
readOnly: readonly,
|
||||||
|
editable: !readonly,
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange({ type: 'template', content: e.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MarkdownHighlight />
|
||||||
|
<LanguageSupport />
|
||||||
|
<JinjaHighlight />
|
||||||
|
{children}
|
||||||
|
</EditorProvider>
|
||||||
|
</UIContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
export const UIContainer = styled.div<{ $hasError?: boolean }>`
|
||||||
|
background-color: var(--semi-color-fill-0);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 6px;
|
||||||
|
|
||||||
|
${({ $hasError }) =>
|
||||||
|
$hasError &&
|
||||||
|
css`
|
||||||
|
border: 1px solid var(--semi-color-danger-6);
|
||||||
|
`}
|
||||||
|
`;
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { IFlowTemplateValue } from '../../typings';
|
||||||
|
|
||||||
|
export type PropsType = React.PropsWithChildren<{
|
||||||
|
value?: IFlowTemplateValue;
|
||||||
|
onChange: (value?: IFlowTemplateValue) => void;
|
||||||
|
readonly?: boolean;
|
||||||
|
hasError?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}>;
|
||||||
@ -1,8 +1,14 @@
|
|||||||
{
|
{
|
||||||
"extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
|
"extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react"
|
"jsx": "react",
|
||||||
|
"moduleResolution": "bundler"
|
||||||
},
|
},
|
||||||
"include": ["./src", "./bin/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"./src",
|
||||||
|
"./bin/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user