Develop Visual Editors for Backend Type Elements
After we completed Extend Your Own Element Families, although we can create instances and run them, configuration parameters need to be manually modified in files, which is not user-friendly for business developers who are not familiar with code.
This article will introduce how to develop a visual configuration editor for DingTalk robots, achieving the same graphical configuration experience as official elements in JitAi development tools.
Effect Preview
Editor Architecture
Element Level | fullName | Main Responsibilities |
---|---|---|
Editor Element | imRobots.dingTalkStreamType.Editor | type points to editors.React , provides visual configuration interface for DingTalk robot |
Target Element | imRobots.dingTalkStreamType | The target backend Type element being edited, completed in previous chapters |
Editor Directory Structure
imRobots/
└── dingTalkStreamType/ # Backend Type element (completed)
├── e.json # Type element definition
├── config.json # Configuration template
├── loader.py # Loader implementation
├── client_manager.py # Client management
├── handler.py # Message handler
└── Editor/ # Editor directory (new)
├── e.json # Editor element definition
├── index.ts # Editor entry file
├── Editor.tsx # Editor implementation file
├── Editor.style.ts # Style file
└── utils.ts # Utility functions
Operation Guide
Create Editor Directory
Create an Editor subdirectory under the dingTalkStreamType directory:
# Execute in dingTalkStreamType directory
mkdir -p Editor
Install Dependencies
Add Monaco editor dependency to the project's package.json
:
{
"dependencies": {
"@monaco-editor/react": "^4.7.0"
}
}
Implement Editor Files
- Element Definition File
- Entry File
- Editor Implementation
- Style File
- Utility Functions
Create editor element definition file Editor/e.json
:
In JitAi, editors themselves are also elements, with their own e.json
definition files.
{
"frontBundleEntry": "./index.ts",
"outputName": "index",
"tag": "editor",
"targetType": [
"imRobots.dingTalkStreamType"
],
"title": "DingTalk Robot Configuration Editor",
"type": "editors.React"
}
Editor element configuration description:
title
: Editor element display nametype
: Fixed aseditors.React
, indicating React editor elementtag
: Fixed aseditor
, indicating backend element editortargetType
: Target backend element's fullNamefrontBundleEntry
: Editor entry file
Create editor entry file Editor/index.ts
:
export { Editor } from './Editor';
Backend element editors must export a component named Editor
, which is a fixed convention for JitAi tools to recognize editors.
Create editor main file Editor/Editor.tsx
:
import type { FC } from 'react';
import { useState, useEffect, useRef } from 'react';
import { Form, Input, Select, Button, message, Space, Card } from 'antd';
import { Editor as MonacoEditor } from '@monaco-editor/react';
import { editorStyles } from './Editor.style';
import { validateConfig, formatConfigJson } from './utils';
interface DingTalkEditorProps {
elementInfo: {
fullName: string;
config: Record<string, any>;
title: string;
};
onSave: (config: Record<string, any>) => Promise<void>;
onCancel: () => void;
}
const DingTalkEditor: FC<DingTalkEditorProps> = ({ elementInfo, onSave, onCancel }) => {
const [form] = Form.useForm();
const [configJson, setConfigJson] = useState('');
const [loading, setLoading] = useState(false);
const didMountRef = useRef(false);
// Initialize form data
useEffect(() => {
if (!didMountRef.current) {
didMountRef.current = true;
const config = elementInfo.config || {};
// Set form field values
form.setFieldsValue({
agent: config.agent || '',
clientId: config.clientId || '',
clientSecret: config.clientSecret || ''
});
// Set JSON editor content
setConfigJson(formatConfigJson(config));
}
}, [elementInfo, form]);
// Handle form submission
const handleSave = async () => {
try {
setLoading(true);
// Validate form fields
const formValues = await form.validateFields();
// Validate JSON configuration
const jsonConfig = validateConfig(configJson);
// Merge form data and JSON configuration
const finalConfig = {
...jsonConfig,
...formValues
};
await onSave(finalConfig);
message.success('Configuration saved successfully');
} catch (error) {
console.error('Save failed:', error);
message.error('Save failed, please check configuration');
} finally {
setLoading(false);
}
};
// Handle JSON editor changes
const handleJsonChange = (value: string | undefined) => {
setConfigJson(value || '');
};
return (
<div style={editorStyles.container}>
<Card title="DingTalk Robot Configuration" style={editorStyles.card}>
<Form
form={form}
layout="vertical"
style={editorStyles.form}
>
<Form.Item
label="AI Agent"
name="agent"
rules={[{ required: true, message: 'Please select AI Agent' }]}
>
<Select
placeholder="Select AI Agent"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={[
{ value: 'aiagents.ragTest', label: 'RAG Test Agent' },
{ value: 'aiagents.chatBot', label: 'Chat Bot Agent' },
// Add more agent options as needed
]}
/>
</Form.Item>
<Form.Item
label="Client ID"
name="clientId"
rules={[{ required: true, message: 'Please enter Client ID' }]}
>
<Input placeholder="Enter DingTalk application Client ID" />
</Form.Item>
<Form.Item
label="Client Secret"
name="clientSecret"
rules={[{ required: true, message: 'Please enter Client Secret' }]}
>
<Input.Password placeholder="Enter DingTalk application Client Secret" />
</Form.Item>
</Form>
<div style={editorStyles.jsonSection}>
<h4>Advanced Configuration (JSON)</h4>
<MonacoEditor
height="200px"
language="json"
theme="vs-dark"
value={configJson}
onChange={handleJsonChange}
options={{
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontSize: 14,
wordWrap: 'on'
}}
/>
</div>
<div style={editorStyles.actions}>
<Space>
<Button onClick={onCancel}>
Cancel
</Button>
<Button
type="primary"
loading={loading}
onClick={handleSave}
>
Save Configuration
</Button>
</Space>
</div>
</Card>
</div>
);
};
export { DingTalkEditor as Editor };
Create style file Editor/Editor.style.ts
:
import type { CSSProperties } from 'react';
export const editorStyles: Record<string, CSSProperties> = {
container: {
padding: '20px',
height: '100%',
overflow: 'auto'
},
card: {
maxWidth: '800px',
margin: '0 auto'
},
form: {
marginBottom: '24px'
},
jsonSection: {
marginBottom: '24px'
},
actions: {
textAlign: 'right' as const,
borderTop: '1px solid #f0f0f0',
paddingTop: '16px'
}
};
Create utility functions file Editor/utils.ts
:
/**
* Validate JSON configuration
*/
export function validateConfig(configJson: string): Record<string, any> {
try {
if (!configJson.trim()) {
return {};
}
const parsed = JSON.parse(configJson);
if (typeof parsed !== 'object' || parsed === null) {
throw new Error('Configuration must be a JSON object');
}
return parsed;
} catch (error) {
throw new Error(`JSON configuration format error: ${error.message}`);
}
}
/**
* Format configuration as JSON string
*/
export function formatConfigJson(config: Record<string, any>): string {
try {
// Remove form fields, only keep advanced configuration
const { agent, clientId, clientSecret, ...advancedConfig } = config;
if (Object.keys(advancedConfig).length === 0) {
return '{\n \n}';
}
return JSON.stringify(advancedConfig, null, 2);
} catch (error) {
console.error('Format JSON failed:', error);
return '{\n \n}';
}
}
/**
* Get available AI Agent list
*/
export function getAvailableAgents(): Array<{ value: string; label: string }> {
// In actual implementation, this would fetch from API
return [
{ value: 'aiagents.ragTest', label: 'RAG Test Agent' },
{ value: 'aiagents.chatBot', label: 'Chat Bot Agent' },
{ value: 'aiagents.knowledgeBase', label: 'Knowledge Base Agent' },
];
}
Editor Working Principle
Backend Element Editor vs Frontend Component Editor
Aspect | Backend Element Editor | Frontend Component Editor |
---|---|---|
Target | Backend Type elements (like DingTalk robot) | Frontend UI components (like Counter) |
Configuration Scope | Instance-level configuration files | Component runtime parameters |
Interface | elementInfo + onSave /onCancel | CompEditorProps + onChangeCompConfig |
Save Method | Asynchronous save via onSave callback | Real-time sync via onChangeCompConfig |
Data Flow
- Configuration Reception: Editor receives current element configuration through
elementInfo
- Form Initialization: Initialize form fields and JSON editor content
- User Interaction: User modifies form fields or JSON configuration
- Validation: Validate form data and JSON format
- Save: Call
onSave
callback to persist configuration
Testing
Make Editor Take Effect
- Clear cache: Delete the
dist
directory in the application directory - Restart service: Restart the desktop client
- Trigger packaging: Access the application page, the system will automatically repackage
Verify Editor Functionality
- Open element management: Enter JitAi development tool's element management interface
- Find target element: Locate the DingTalk robot element instance
- Open editor: Click edit button, the visual editor should open
- Test configuration: Try modifying AI Agent, Client ID and other configurations
- Save verification: Save configuration and restart element, confirm configuration takes effect
Common Issue Troubleshooting
- Editor not opening: Check if
targetType
ine.json
correctly points to the target element - Configuration not saving: Confirm if
onSave
callback is called correctly and handles errors properly - JSON format error: Check if JSON editor content format is correct
Advanced Features
Dynamic Agent List
You can enhance the editor by dynamically loading available AI Agent lists:
const [agentOptions, setAgentOptions] = useState([]);
useEffect(() => {
// Load available agents
const loadAgents = async () => {
try {
const agents = await fetchAvailableAgents(); // API call
setAgentOptions(agents);
} catch (error) {
console.error('Failed to load agents:', error);
}
};
loadAgents();
}, []);
Configuration Validation
Add more comprehensive configuration validation:
export function validateDingTalkConfig(config: any): string[] {
const errors: string[] = [];
if (!config.agent) {
errors.push('AI Agent is required');
}
if (!config.clientId) {
errors.push('Client ID is required');
}
if (!config.clientSecret) {
errors.push('Client Secret is required');
}
if (config.clientId && !/^[a-zA-Z0-9]+$/.test(config.clientId)) {
errors.push('Client ID format is invalid');
}
return errors;
}
Summary
Core steps for developing visual editors for backend elements:
- Create Editor directory + configure editor element definition file
e.json
(type: "editors.React"
) - Implement Editor component: Receive
elementInfo
+ render configuration form - Correct export: Export component named
Editor
- Configuration persistence: Save configuration through
onSave
callback
Key differences from frontend component editors:
- Use
elementInfo
interface instead ofCompEditorProps
- Asynchronous save via
onSave
callback instead of real-time sync - Target backend Type elements instead of frontend UI components
- Handle instance-level configuration instead of component runtime parameters
Key points:
- Export name
Editor
cannot be changed - Must handle asynchronous save operations and error handling
- JSON editor is suitable for complex configuration scenarios
- Form validation ensures configuration correctness